💉
aquinas

Advanced Usage

advanced usage of aquinas

Derived References

You can create derived references using the derivedReference function and Reference type.

This is useful when you want to create a reference that is based on another reference.

interface Filter<E extends ApplicationError> {
    catch(error: E): void;
}

export function FilterReference<E extends ApplicationError>(ref: Reference<E>) {
    return derivedReference<Filter<E>>("Filter", ref);
}

Overriding Injectables

Suppose you build a bunch of repositories in your application, each one being an injectable.

It may happen that you want to run these repositories in a transaction context, but that's impossible to do if your repositories use the default database connection.

The normal (but not ideal) solution to this problem would be to instantiate repositories upon use, and pass the database connection as a parameter.

But since we are dealing with IoC, we can create a DatabaseConnection which all repositories depend on, and then overwrite this connection in a transaction context.

All repositories will then use the transaction database connection, without having to change their code.

The code below illustrates this approach.

export const TransactionService = injectable(TransactionServiceRef)
	.deps({ databaseConnection: DatabaseConnectionReference })
	.implements(({ ctx, databaseConnection }) => {
		let refs = {};

		return {
			deps(newRefs) {
				refs = { ...refs, ...newRefs };

				return this;
			},

			async run(fn) {
				return await databaseConnection.transaction(async (tx) => {
					const transactionDock = cloneDock(ctx);

					transactionDock.override({
						reference: DatabaseConnectionReference,
						implementation: () => tx,
					});

					const deps = resolveReferences(refs, transactionDock);

					await fn({
						undo: async () => {
							await tx.query(sql.void`ROLLBACK`);
						},
						...deps,
					});
				});
			},
		};
	});

This allows you to run any repository in a transaction, without having to change the repository itself.

export const OnboardUsecase = injectable(OnboardUsecaseReference)
  .deps({
    transactionService: TransactionServiceReference,
  })
  .implements(({ transactionService }) => ({
    async execute() {
	  const user = new User();

	  const subscription = new Subscription();

      await transactionService
        .deps({
          userRepository: UserRepositoryReference,
          subscriptionRepository: SubscriptionRepositoryReference,
        })
        .run(async ({ undo }) => {
            await userRepository.create(user);

            await subscriptionRepository.create(subscription);
        });
    },
  }));