From 0bd768202a76960a05814e082d2e3b3858b498dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0vanda?= <46406259+Papooch@users.noreply.github.com> Date: Fri, 19 Jan 2024 15:00:08 +0100 Subject: [PATCH] feat: transactional plugin (#102) --- .github/workflows/publish-to-npm.workflow.yml | 3 +- .github/workflows/run-tests.workflow.yml | 3 +- package.json | 6 +- packages/core/src/index.ts | 1 + .../lib/cls-initializers/use-cls.decorator.ts | 20 +- .../core/src/utils/copy-method-metadata.ts | 16 ++ packages/transactional/jest.config.js | 17 ++ packages/transactional/package.json | 70 ++++++ .../transactional/src/index.ts | 0 packages/transactional/src/lib/interfaces.ts | 46 ++++ .../src/lib/plugin-transactional.ts | 27 ++ packages/transactional/src/lib/symbols.ts | 3 + .../transactional/src/lib/transaction-host.ts | 59 +++++ .../src/lib/transactional.decorator.ts | 40 +++ packages/transactional/test/mockDbAdapter.ts | 75 ++++++ .../transactional/test/transactional.spec.ts | 234 ++++++++++++++++++ packages/transactional/tsconfig.json | 8 + tsconfig.json | 57 +++-- yarn.lock | 34 +++ 19 files changed, 669 insertions(+), 50 deletions(-) create mode 100644 packages/core/src/utils/copy-method-metadata.ts create mode 100644 packages/transactional/jest.config.js create mode 100644 packages/transactional/package.json rename dependency-graph.html => packages/transactional/src/index.ts (100%) create mode 100644 packages/transactional/src/lib/interfaces.ts create mode 100644 packages/transactional/src/lib/plugin-transactional.ts create mode 100644 packages/transactional/src/lib/symbols.ts create mode 100644 packages/transactional/src/lib/transaction-host.ts create mode 100644 packages/transactional/src/lib/transactional.decorator.ts create mode 100644 packages/transactional/test/mockDbAdapter.ts create mode 100644 packages/transactional/test/transactional.spec.ts create mode 100644 packages/transactional/tsconfig.json diff --git a/.github/workflows/publish-to-npm.workflow.yml b/.github/workflows/publish-to-npm.workflow.yml index e63ae18d..d7676314 100644 --- a/.github/workflows/publish-to-npm.workflow.yml +++ b/.github/workflows/publish-to-npm.workflow.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16.x - registry-url: "https://registry.npmjs.org" + registry-url: 'https://registry.npmjs.org' cache: npm - name: Configure git @@ -29,6 +29,7 @@ jobs: git config --local user.email 'github-action[bot]@github.com' - run: yarn + - run: yarn workspace nestjs-cls build - run: yarn test - run: yarn monodeploy env: diff --git a/.github/workflows/run-tests.workflow.yml b/.github/workflows/run-tests.workflow.yml index b7e6b3ab..2df87f64 100644 --- a/.github/workflows/run-tests.workflow.yml +++ b/.github/workflows/run-tests.workflow.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [16, 18] + node: [16, 18, 20] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 @@ -24,5 +24,6 @@ jobs: node-version: ${{ matrix.node }}.x cache: npm - run: yarn + - run: yarn workspace nestjs-cls build - run: yarn lint - run: yarn test diff --git a/package.json b/package.json index d31c3861..1c803378 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ ], "packageManager": "yarn@3.6.1", "scripts": { - "test": "yarn workspaces foreach run test", - "build": "yarn workspaces foreach run build", - "format": "prettier --write \"packages/**/*.ts\" \"test/**/*.ts\"", + "test": "yarn workspaces foreach --topological-dev run test", + "build": "yarn workspaces foreach --topological-dev run build", + "format": "prettier --write \"packages/**/*.ts\"", "lint": "eslint \"packages/**/*.ts\"", "lint:fix": "eslint \"packages/**/*.ts\" --fix", "depcruise": "yarn depcruise packages --include-only \"^packages/.*/src\" --exclude \"\\.spec\\.ts\" --config --output-type dot | dot -T svg | yarn depcruise-wrap-stream-in-html > dependency-graph.html" diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2865dff8..16e60b15 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -9,4 +9,5 @@ export * from './lib/cls.service'; export * from './lib/cls.decorators'; export * from './lib/cls.options'; export * from './lib/plugin/cls-plugin.interface'; +export * from './utils/copy-method-metadata'; export { Terminal } from './types/terminal.type'; diff --git a/packages/core/src/lib/cls-initializers/use-cls.decorator.ts b/packages/core/src/lib/cls-initializers/use-cls.decorator.ts index 1026e113..524c9d14 100644 --- a/packages/core/src/lib/cls-initializers/use-cls.decorator.ts +++ b/packages/core/src/lib/cls-initializers/use-cls.decorator.ts @@ -1,4 +1,5 @@ import 'reflect-metadata'; +import { copyMethodMetadata } from '../../utils/copy-method-metadata'; import { ClsServiceManager } from '../cls-service-manager'; import { CLS_ID } from '../cls.constants'; import { ClsDecoratorOptions } from '../cls.options'; @@ -60,23 +61,6 @@ export function UseCls( return original.apply(this, args); }); }; - copyMetadata(original, descriptor.value); + copyMethodMetadata(original, descriptor.value); }; } - -/** - * Copies all metadata from one object to another. - * Useful for overwriting function definition in - * decorators while keeping all previously - * attached metadata - * - * @param from object to copy metadata from - * @param to object to copy metadata to - */ -function copyMetadata(from: any, to: any) { - const metadataKeys = Reflect.getMetadataKeys(from); - metadataKeys.map((key) => { - const value = Reflect.getMetadata(key, from); - Reflect.defineMetadata(key, value, to); - }); -} diff --git a/packages/core/src/utils/copy-method-metadata.ts b/packages/core/src/utils/copy-method-metadata.ts new file mode 100644 index 00000000..8ffeb702 --- /dev/null +++ b/packages/core/src/utils/copy-method-metadata.ts @@ -0,0 +1,16 @@ +/** + * Copies all metadata from one object to another. + * Useful for overwriting function definition in + * decorators while keeping all previously + * attached metadata + * + * @param from object to copy metadata from + * @param to object to copy metadata to + */ +export function copyMethodMetadata(from: any, to: any) { + const metadataKeys = Reflect.getMetadataKeys(from); + metadataKeys.map((key) => { + const value = Reflect.getMetadata(key, from); + Reflect.defineMetadata(key, value, to); + }); +} diff --git a/packages/transactional/jest.config.js b/packages/transactional/jest.config.js new file mode 100644 index 00000000..1a5556d1 --- /dev/null +++ b/packages/transactional/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + moduleFileExtensions: ['js', 'json', 'ts'], + rootDir: '.', + testRegex: '.*\\.spec\\.ts$', + transform: { + '^.+\\.(t|j)s$': 'ts-jest', + }, + collectCoverageFrom: ['src/**/*.(t|j)s'], + coverageDirectory: '../coverage', + testEnvironment: 'node', + globals: { + 'ts-jest': { + isolatedModules: true, + maxWorkers: 1, + }, + }, +}; diff --git a/packages/transactional/package.json b/packages/transactional/package.json new file mode 100644 index 00000000..9e54abc6 --- /dev/null +++ b/packages/transactional/package.json @@ -0,0 +1,70 @@ +{ + "name": "@nestjs-cls/transactional", + "version": "0.0.1", + "description": "A nestjs-cls plugin for transactional decorators", + "author": "papooch", + "license": "MIT", + "engines": { + "node": ">=12.17.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Papooch/nestjs-cls.git" + }, + "homepage": "https://papooch.github.io/nestjs-cls/", + "keywords": [ + "nest", + "nestjs", + "cls", + "continuation-local-storage", + "als", + "AsyncLocalStorage", + "async_hooks", + "request context", + "async context" + ], + "main": "dist/src/index.js", + "types": "dist/src/index.d.ts", + "files": [ + "dist/src/**/!(*.spec).d.ts", + "dist/src/**/!(*.spec).js" + ], + "scripts": { + "prepack": "cp ../../LICENSE ./LICENSE && cp ../../README.md ./README.md && yarn build", + "prebuild": "rimraf dist", + "build": "tsc", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage" + }, + "peerDependencies": { + "@nestjs/common": "> 7.0.0 < 11", + "@nestjs/core": "> 7.0.0 < 11", + "nestjs-cls": "workspace:*", + "reflect-metadata": "*", + "rxjs": ">= 7" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.2", + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/graphql": "^12.0.1", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/schematics": "^10.0.1", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.13", + "@types/jest": "^28.1.2", + "@types/node": "^18.0.0", + "@types/supertest": "^2.0.12", + "jest": "^28.1.1", + "reflect-metadata": "^0.1.13", + "rimraf": "^3.0.2", + "rxjs": "^7.5.5", + "supertest": "^6.2.3", + "ts-jest": "^28.0.5", + "ts-loader": "^9.3.0", + "ts-node": "^10.8.1", + "tsconfig-paths": "^4.0.0", + "typescript": "~4.8.0" + } +} diff --git a/dependency-graph.html b/packages/transactional/src/index.ts similarity index 100% rename from dependency-graph.html rename to packages/transactional/src/index.ts diff --git a/packages/transactional/src/lib/interfaces.ts b/packages/transactional/src/lib/interfaces.ts new file mode 100644 index 00000000..c3f83477 --- /dev/null +++ b/packages/transactional/src/lib/interfaces.ts @@ -0,0 +1,46 @@ +export interface TransactionalAdapterOptions { + startTransaction: ( + options: TOptions, + fn: (...args: any[]) => Promise, + setClient: (client?: TClient) => void, + ) => Promise; + getClient: () => TClient; +} + +export type TransactionalOptionsAdapterFactory = + (connection: TConnection) => TransactionalAdapterOptions; + +export interface TransactionalAdapter { + /** + * Token used to inject the `connection` into the adapter. + * It is later used to create transactions. + */ + connectionToken: any; + + /** + * Function that accepts the `connection` based on the `connectionToken` + * + * Returns an object implementing the `TransactionalAdapterOptions` interface + * with the `startTransaction` and `getClient` methods. + */ + optionsFactory: TransactionalOptionsAdapterFactory< + TConnection, + TClient, + TOptions + >; +} + +export interface TransactionalPluginOptions { + adapter: TransactionalAdapter; + imports?: any[]; +} + +export type TClientFromAdapter = + TAdapter extends TransactionalAdapter + ? TClient + : never; + +export type TOptionsFromAdapter = + TAdapter extends TransactionalAdapter + ? TOptions + : never; diff --git a/packages/transactional/src/lib/plugin-transactional.ts b/packages/transactional/src/lib/plugin-transactional.ts new file mode 100644 index 00000000..31a64ded --- /dev/null +++ b/packages/transactional/src/lib/plugin-transactional.ts @@ -0,0 +1,27 @@ +import { Provider } from '@nestjs/common'; +import { ClsPlugin } from 'nestjs-cls'; +import { TransactionalPluginOptions } from './interfaces'; +import { TRANSACTIONAL_OPTIONS, TRANSACTION_CONNECTION } from './symbols'; +import { TransactionHost } from './transaction-host'; + +export class ClsPluginTransactional implements ClsPlugin { + name: 'cls-plugin-transactional'; + providers: Provider[]; + imports?: any[]; + + constructor(options: TransactionalPluginOptions) { + this.imports = options.imports; + this.providers = [ + TransactionHost, + { + provide: TRANSACTION_CONNECTION, + useExisting: options.adapter.connectionToken, + }, + { + provide: TRANSACTIONAL_OPTIONS, + inject: [TRANSACTION_CONNECTION], + useFactory: options.adapter.optionsFactory, + }, + ]; + } +} diff --git a/packages/transactional/src/lib/symbols.ts b/packages/transactional/src/lib/symbols.ts new file mode 100644 index 00000000..39ea55f6 --- /dev/null +++ b/packages/transactional/src/lib/symbols.ts @@ -0,0 +1,3 @@ +export const TRANSACTION_CONNECTION = Symbol('TRANSACTION_CONNECTION'); +export const TRANSACTIONAL_CLIENT = Symbol('TRANSACTIONAL_CLIENT'); +export const TRANSACTIONAL_OPTIONS = Symbol('TRANSACTIONAL_OPTIONS'); diff --git a/packages/transactional/src/lib/transaction-host.ts b/packages/transactional/src/lib/transaction-host.ts new file mode 100644 index 00000000..114125f2 --- /dev/null +++ b/packages/transactional/src/lib/transaction-host.ts @@ -0,0 +1,59 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ClsServiceManager } from 'nestjs-cls'; +import { + TClientFromAdapter, + TOptionsFromAdapter, + TransactionalAdapterOptions, +} from './interfaces'; +import { TRANSACTIONAL_OPTIONS, TRANSACTIONAL_CLIENT } from './symbols'; + +@Injectable() +export class TransactionHost { + private cls = ClsServiceManager.getClsService(); + + constructor( + @Inject(TRANSACTIONAL_OPTIONS) + private _options: TransactionalAdapterOptions< + TClientFromAdapter, + TOptionsFromAdapter + >, + ) {} + + get client(): TClientFromAdapter { + if (!this.cls.isActive()) { + return this._options.getClient(); + } + return this.cls.get(TRANSACTIONAL_CLIENT) ?? this._options.getClient(); + } + + withTransaction(fn: (...args: any[]) => Promise): Promise; + withTransaction( + options: TOptionsFromAdapter, + fn: (...args: any[]) => Promise, + ): Promise; + withTransaction( + optionsOrFn: any, + maybeFn?: (...args: any[]) => Promise, + ) { + let options: any; + let fn: (...args: any[]) => Promise; + if (maybeFn) { + options = optionsOrFn; + fn = maybeFn; + } else { + options = {}; + fn = optionsOrFn; + } + return this.cls.run({ ifNested: 'inherit' }, () => + this._options.startTransaction( + options, + fn, + this.setClient.bind(this), + ), + ); + } + + private setClient(client?: TClientFromAdapter) { + this.cls.set(TRANSACTIONAL_CLIENT, client); + } +} diff --git a/packages/transactional/src/lib/transactional.decorator.ts b/packages/transactional/src/lib/transactional.decorator.ts new file mode 100644 index 00000000..5eeb3b55 --- /dev/null +++ b/packages/transactional/src/lib/transactional.decorator.ts @@ -0,0 +1,40 @@ +import { Inject } from '@nestjs/common'; +import { copyMethodMetadata } from 'nestjs-cls'; +import { TOptionsFromAdapter } from './interfaces'; +import { TransactionHost } from './transaction-host'; + +export function Transactional( + options?: TOptionsFromAdapter, +) { + const injectTransactionHost = Inject(TransactionHost); + return ( + target: any, + propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor<(...args: any) => Promise>, + ) => { + if (!target.__transactionHost) { + injectTransactionHost(target, '__transactionHost'); + } + const original = descriptor.value; + if (typeof original !== 'function') { + throw new Error( + `The @Transactional decorator can be only used on functions, but ${propertyKey.toString()} is not a function.`, + ); + } + descriptor.value = function ( + this: { __transactionHost: TransactionHost }, + ...args: any[] + ) { + if (!this.__transactionHost) { + throw new Error( + `Failed to inject transaction host into ${target.constructor.name}`, + ); + } + return this.__transactionHost.withTransaction( + options as never, + original.bind(this, ...args), + ); + }; + copyMethodMetadata(original, descriptor.value); + }; +} diff --git a/packages/transactional/test/mockDbAdapter.ts b/packages/transactional/test/mockDbAdapter.ts new file mode 100644 index 00000000..4389ed23 --- /dev/null +++ b/packages/transactional/test/mockDbAdapter.ts @@ -0,0 +1,75 @@ +import { TransactionalAdapter } from '../src/lib/interfaces'; + +export class MockDbClient { + private _operations: string[] = []; + get operations() { + return this._operations; + } + + async query(query: string) { + this._operations.push(query); + return { query }; + } +} + +export class MockDbConnection { + clients: MockDbClient[] = []; + + getClient() { + const client = new MockDbClient(); + this.clients.push(client); + return client; + } + + getClientsQueries() { + return this.clients.map((c) => c.operations); + } +} + +export interface MockTransactionOptions { + serializable?: boolean; +} + +export class MockTransactionAdapter + implements + TransactionalAdapter< + MockDbConnection, + MockDbClient, + MockTransactionOptions + > +{ + connectionToken: any; + constructor(options: { connectionToken: any }) { + this.connectionToken = options.connectionToken; + } + optionsFactory = (connection: MockDbConnection) => ({ + startTransaction: async ( + options: MockTransactionOptions | undefined, + fn: (...args: any[]) => Promise, + setClient: (client?: MockDbClient) => void, + ) => { + const client = connection.getClient(); + setClient(client); + let beginQuery = 'BEGIN TRANSACTION;'; + if (options?.serializable) { + beginQuery = + 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ' + + beginQuery; + } + await client.query(beginQuery); + try { + const result = await fn(); + await client.query('COMMIT TRANSACTION;'); + return result; + } catch (e) { + await client.query('ROLLBACK TRANSACTION;'); + throw e; + } finally { + setClient(undefined); + } + }, + getClient: () => { + return connection.getClient(); + }, + }); +} diff --git a/packages/transactional/test/transactional.spec.ts b/packages/transactional/test/transactional.spec.ts new file mode 100644 index 00000000..12492b50 --- /dev/null +++ b/packages/transactional/test/transactional.spec.ts @@ -0,0 +1,234 @@ +import { Injectable, Module } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ClsModule } from 'nestjs-cls'; +import { MockDbConnection, MockTransactionAdapter } from './mockDbAdapter'; + +import { ClsPluginTransactional } from '../src/lib/plugin-transactional'; +import { TransactionHost } from '../src/lib/transaction-host'; +import { Transactional } from '../src/lib/transactional.decorator'; + +@Injectable() +class CalledService { + constructor( + private readonly txHost: TransactionHost, + ) {} + + async doWork(num: number) { + return this.txHost.client.query(`SELECT ${num}`); + } + + async doOtherWork(num: number) { + return this.txHost.client.query(`SELECT ${num}`); + } + + getPerformedOperations() { + return this.txHost.client.operations; + } +} + +@Injectable() +class CallingService { + constructor( + private readonly calledService: CalledService, + private readonly txHost: TransactionHost, + ) {} + + @Transactional() + async transactionWithDecorator() { + const q1 = await this.calledService.doWork(1); + const q2 = await this.calledService.doOtherWork(2); + return { q1, q2 }; + } + + @Transactional({ serializable: true }) + async transactionWithDecoratorWithOptions() { + await this.calledService.doWork(1); + await this.calledService.doOtherWork(2); + } + + async parallelTransactions() { + return Promise.all([ + this.nestedStartTransaction(7), + this.calledService.doWork(9), + this.nestedDecorator(8), + ]); + } + + @Transactional() + private async nestedDecorator(num: number) { + return this.calledService.doWork(num); + } + + private async nestedStartTransaction(num: number) { + return this.txHost.withTransaction(async () => { + return this.calledService.doWork(num); + }); + } + + async startTransaction() { + return this.txHost.withTransaction(async () => { + const q1 = await this.calledService.doWork(3); + const q2 = await this.calledService.doOtherWork(4); + return { q1, q2 }; + }); + } + + async startTransactionWithOptions() { + await this.txHost.withTransaction({ serializable: true }, async () => { + await this.calledService.doWork(3); + await this.calledService.doOtherWork(4); + }); + } + + async withoutTransaction() { + await this.calledService.doWork(5); + await this.calledService.doOtherWork(6); + } + + @Transactional() + async multipleNestedTransactions() { + await this.calledService.doWork(10); + await this.txHost.withTransaction(async () => { + await this.calledService.doWork(11); + await this.nestedDecorator(12); + await this.calledService.doWork(13); + }); + } +} + +@Module({ + providers: [MockDbConnection], + exports: [MockDbConnection], +}) +class DbConnectionModule {} + +@Module({ + imports: [ + ClsModule.forRoot({ + plugins: [ + new ClsPluginTransactional({ + imports: [DbConnectionModule], + adapter: new MockTransactionAdapter({ + connectionToken: MockDbConnection, + }), + }), + ], + }), + ], + providers: [CallingService, CalledService], +}) +class AppModule {} + +describe('Transactional', () => { + let module: TestingModule; + let callingService: CallingService; + let mockDbConnection: MockDbConnection; + beforeEach(async () => { + module = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + await module.init(); + callingService = module.get(CallingService); + mockDbConnection = module.get(MockDbConnection); + }); + + describe('when using the @Transactional decorator', () => { + it('should start a transaction', async () => { + const result = await callingService.transactionWithDecorator(); + expect(result).toEqual({ + q1: { query: 'SELECT 1' }, + q2: { query: 'SELECT 2' }, + }); + const queries = mockDbConnection.getClientsQueries(); + expect(queries).toEqual([ + [ + 'BEGIN TRANSACTION;', + 'SELECT 1', + 'SELECT 2', + 'COMMIT TRANSACTION;', + ], + ]); + }); + it('should start a transaction with options', async () => { + await callingService.transactionWithDecoratorWithOptions(); + const queries = mockDbConnection.getClientsQueries(); + expect(queries).toEqual([ + [ + 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; BEGIN TRANSACTION;', + 'SELECT 1', + 'SELECT 2', + 'COMMIT TRANSACTION;', + ], + ]); + }); + it('should start two transaction in parallel', async () => { + const results = await callingService.parallelTransactions(); + expect(results).toEqual([ + { query: 'SELECT 7' }, + { query: 'SELECT 9' }, + { query: 'SELECT 8' }, + ]); + const queries = mockDbConnection.getClientsQueries(); + expect(queries).toEqual([ + ['BEGIN TRANSACTION;', 'SELECT 7', 'COMMIT TRANSACTION;'], + ['SELECT 9'], + ['BEGIN TRANSACTION;', 'SELECT 8', 'COMMIT TRANSACTION;'], + ]); + }); + }); + describe('when using the startTransaction method on TransactionHost', () => { + it('should start a transaction', async () => { + const result = await callingService.startTransaction(); + expect(result).toEqual({ + q1: { query: 'SELECT 3' }, + q2: { query: 'SELECT 4' }, + }); + const queries = mockDbConnection.getClientsQueries(); + expect(queries).toEqual([ + [ + 'BEGIN TRANSACTION;', + 'SELECT 3', + 'SELECT 4', + 'COMMIT TRANSACTION;', + ], + ]); + }); + it('should start a transaction with options', async () => { + await callingService.startTransactionWithOptions(); + const queries = mockDbConnection.getClientsQueries(); + expect(queries).toEqual([ + [ + 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; BEGIN TRANSACTION;', + 'SELECT 3', + 'SELECT 4', + 'COMMIT TRANSACTION;', + ], + ]); + }); + }); + + describe('when not using a transaction', () => { + it('should not start a transaction', async () => { + await callingService.withoutTransaction(); + const queries = mockDbConnection.getClientsQueries(); + expect(queries).toEqual([['SELECT 5'], ['SELECT 6']]); + }); + }); + + describe('when using nested transactions', () => { + it('should start a transaction', async () => { + await callingService.multipleNestedTransactions(); + const queries = mockDbConnection.getClientsQueries(); + expect(queries).toEqual([ + ['BEGIN TRANSACTION;', 'SELECT 10', 'COMMIT TRANSACTION;'], + [ + 'BEGIN TRANSACTION;', + 'SELECT 11', + 'SELECT 13', + 'COMMIT TRANSACTION;', + ], + ['BEGIN TRANSACTION;', 'SELECT 12', 'COMMIT TRANSACTION;'], + ]); + }); + }); +}); diff --git a/packages/transactional/tsconfig.json b/packages/transactional/tsconfig.json new file mode 100644 index 00000000..74cdb992 --- /dev/null +++ b/packages/transactional/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "." + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index 32025e2d..c4376a8d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,29 +1,32 @@ { - "compilerOptions": { - "module": "commonjs", - "esModuleInterop": true, - "declaration": true, - "removeComments": false, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "target": "es2017", - "sourceMap": true, - "incremental": true, - "skipLibCheck": true, - "strictNullChecks": true, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "compilerOptions": { + "module": "commonjs", + "esModuleInterop": true, + "declaration": true, + "removeComments": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "es2017", + "sourceMap": true, + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + }, + "include": [], + "references": [ + { + "path": "docs" }, - "include": [], - "references": [ - { - "path": "docs" - }, - { - "path": "packages/core" - } - ] -} + { + "path": "packages/core" + }, + { + "path": "packages/transactional" + } + ] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c8f8519c..0818b977 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3543,6 +3543,40 @@ __metadata: languageName: node linkType: hard +"@nestjs-cls/transactional@workspace:packages/transactional": + version: 0.0.0-use.local + resolution: "@nestjs-cls/transactional@workspace:packages/transactional" + dependencies: + "@nestjs/cli": ^10.0.2 + "@nestjs/common": ^10.0.0 + "@nestjs/core": ^10.0.0 + "@nestjs/graphql": ^12.0.1 + "@nestjs/platform-express": ^10.0.0 + "@nestjs/schematics": ^10.0.1 + "@nestjs/testing": ^10.0.0 + "@types/express": ^4.17.13 + "@types/jest": ^28.1.2 + "@types/node": ^18.0.0 + "@types/supertest": ^2.0.12 + jest: ^28.1.1 + reflect-metadata: ^0.1.13 + rimraf: ^3.0.2 + rxjs: ^7.5.5 + supertest: ^6.2.3 + ts-jest: ^28.0.5 + ts-loader: ^9.3.0 + ts-node: ^10.8.1 + tsconfig-paths: ^4.0.0 + typescript: ~4.8.0 + peerDependencies: + "@nestjs/common": "> 7.0.0 < 11" + "@nestjs/core": "> 7.0.0 < 11" + nestjs-cls: "workspace:*" + reflect-metadata: "*" + rxjs: ">= 7" + languageName: unknown + linkType: soft + "@nestjs/apollo@npm:^12.0.1": version: 12.0.7 resolution: "@nestjs/apollo@npm:12.0.7"