@rainbow-o23/n3 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.babelrc +11 -0
  2. package/.eslintrc +23 -0
  3. package/README.md +584 -0
  4. package/index.cjs +2283 -0
  5. package/index.d.ts +5 -0
  6. package/index.js +2229 -0
  7. package/lib/error-codes.d.ts +17 -0
  8. package/lib/http/fetch-step.d.ts +53 -0
  9. package/lib/http/index.d.ts +2 -0
  10. package/lib/http/types.d.ts +21 -0
  11. package/lib/step/abstract-fragmentary-pipeline-step.d.ts +44 -0
  12. package/lib/step/async-step-sets.d.ts +5 -0
  13. package/lib/step/conditional-step-sets.d.ts +23 -0
  14. package/lib/step/delete-property-step.d.ts +11 -0
  15. package/lib/step/each-step-sets.d.ts +14 -0
  16. package/lib/step/get-property-step.d.ts +11 -0
  17. package/lib/step/index.d.ts +14 -0
  18. package/lib/step/ref-pipeline-step.d.ts +15 -0
  19. package/lib/step/ref-step-step.d.ts +14 -0
  20. package/lib/step/routes-step-sets.d.ts +30 -0
  21. package/lib/step/snippet-step.d.ts +19 -0
  22. package/lib/step/snowflake-step.d.ts +6 -0
  23. package/lib/step/step-sets.d.ts +19 -0
  24. package/lib/step/types.d.ts +13 -0
  25. package/lib/step/utils.d.ts +23 -0
  26. package/lib/typeorm/abstract-datasource.d.ts +22 -0
  27. package/lib/typeorm/better-sqlite3-datasource.d.ts +7 -0
  28. package/lib/typeorm/datasource-manager.d.ts +25 -0
  29. package/lib/typeorm/index.d.ts +7 -0
  30. package/lib/typeorm/mssql-datasource.d.ts +6 -0
  31. package/lib/typeorm/mysql-datasource.d.ts +6 -0
  32. package/lib/typeorm/oracle-datasource.d.ts +6 -0
  33. package/lib/typeorm/pgsql-datasource.d.ts +6 -0
  34. package/lib/typeorm-step/abstract-typeorm-by-sql-step.d.ts +62 -0
  35. package/lib/typeorm-step/abstract-typeorm-load-by-sql-step.d.ts +10 -0
  36. package/lib/typeorm-step/abstract-typeorm-step.d.ts +30 -0
  37. package/lib/typeorm-step/index.d.ts +12 -0
  38. package/lib/typeorm-step/type-orm-transactional-step-sets.d.ts +22 -0
  39. package/lib/typeorm-step/typeorm-bulk-save-by-sql-step.d.ts +9 -0
  40. package/lib/typeorm-step/typeorm-by-snippet-step.d.ts +21 -0
  41. package/lib/typeorm-step/typeorm-load-entity-by-id-step.d.ts +12 -0
  42. package/lib/typeorm-step/typeorm-load-many-by-sql-step.d.ts +6 -0
  43. package/lib/typeorm-step/typeorm-load-one-by-sql-step.d.ts +6 -0
  44. package/lib/typeorm-step/typeorm-save-by-sql-step.d.ts +9 -0
  45. package/lib/typeorm-step/typeorm-save-entity-step.d.ts +32 -0
  46. package/lib/typeorm-step/types.d.ts +22 -0
  47. package/package.json +74 -0
  48. package/rollup.config.base.js +33 -0
  49. package/rollup.config.ci.js +3 -0
  50. package/rollup.config.js +3 -0
  51. package/src/index.ts +7 -0
  52. package/src/lib/error-codes.ts +18 -0
  53. package/src/lib/http/fetch-step.ts +290 -0
  54. package/src/lib/http/index.ts +3 -0
  55. package/src/lib/http/types.ts +33 -0
  56. package/src/lib/step/abstract-fragmentary-pipeline-step.ts +367 -0
  57. package/src/lib/step/async-step-sets.ts +14 -0
  58. package/src/lib/step/conditional-step-sets.ts +115 -0
  59. package/src/lib/step/delete-property-step.ts +33 -0
  60. package/src/lib/step/each-step-sets.ts +80 -0
  61. package/src/lib/step/get-property-step.ts +34 -0
  62. package/src/lib/step/index.ts +18 -0
  63. package/src/lib/step/ref-pipeline-step.ts +65 -0
  64. package/src/lib/step/ref-step-step.ts +59 -0
  65. package/src/lib/step/routes-step-sets.ts +147 -0
  66. package/src/lib/step/snippet-step.ts +80 -0
  67. package/src/lib/step/snowflake-step.ts +16 -0
  68. package/src/lib/step/step-sets.ts +99 -0
  69. package/src/lib/step/types.ts +23 -0
  70. package/src/lib/step/utils.ts +109 -0
  71. package/src/lib/typeorm/abstract-datasource.ts +58 -0
  72. package/src/lib/typeorm/better-sqlite3-datasource.ts +29 -0
  73. package/src/lib/typeorm/datasource-manager.ts +104 -0
  74. package/src/lib/typeorm/index.ts +9 -0
  75. package/src/lib/typeorm/mssql-datasource.ts +131 -0
  76. package/src/lib/typeorm/mysql-datasource.ts +35 -0
  77. package/src/lib/typeorm/oracle-datasource.ts +27 -0
  78. package/src/lib/typeorm/pgsql-datasource.ts +25 -0
  79. package/src/lib/typeorm-step/abstract-typeorm-by-sql-step.ts +556 -0
  80. package/src/lib/typeorm-step/abstract-typeorm-load-by-sql-step.ts +31 -0
  81. package/src/lib/typeorm-step/abstract-typeorm-step.ts +241 -0
  82. package/src/lib/typeorm-step/index.ts +17 -0
  83. package/src/lib/typeorm-step/type-orm-transactional-step-sets.ts +129 -0
  84. package/src/lib/typeorm-step/typeorm-bulk-save-by-sql-step.ts +29 -0
  85. package/src/lib/typeorm-step/typeorm-by-snippet-step.ts +83 -0
  86. package/src/lib/typeorm-step/typeorm-load-entity-by-id-step.ts +35 -0
  87. package/src/lib/typeorm-step/typeorm-load-many-by-sql-step.ts +13 -0
  88. package/src/lib/typeorm-step/typeorm-load-one-by-sql-step.ts +13 -0
  89. package/src/lib/typeorm-step/typeorm-save-by-sql-step.ts +24 -0
  90. package/src/lib/typeorm-step/typeorm-save-entity-step.ts +109 -0
  91. package/src/lib/typeorm-step/types.ts +25 -0
  92. package/test/step/snippet-step.test.ts +21 -0
  93. package/test/step/snowflake-step.test.ts +22 -0
  94. package/test/step/typeorm-by-sql-autonomous.test.ts +117 -0
  95. package/test/step/typeorm-by-sql-transactional.test.ts +215 -0
  96. package/test/step/typeorm-entity.test.ts +50 -0
  97. package/tsconfig.json +36 -0
@@ -0,0 +1,109 @@
1
+ import {
2
+ CatchableError,
3
+ O23ExternalErrorCode,
4
+ PipelineStepData,
5
+ PipelineStepPayload,
6
+ Undefinable
7
+ } from '@rainbow-o23/n1';
8
+ import {Snowflake} from '@theinternetfolks/snowflake';
9
+ import {DeepPartial, ObjectLiteral} from 'typeorm';
10
+ import {ScriptFuncOrBody, Utils} from '../step';
11
+ import {AbstractTypeOrmPipelineStep, TypeOrmPipelineStepOptions} from './abstract-typeorm-step';
12
+ import {TypeOrmEntityName} from './types';
13
+
14
+ export type EntityToSave = DeepPartial<ObjectLiteral>;
15
+
16
+ export type UniquenessCheckResult = { pass: true } | { pass: false, code: O23ExternalErrorCode, message: string };
17
+ /**
18
+ * parameter names could be change {@link TypeOrmSaveEntityPipelineStep#generateUniquenessCheckVariableNames}
19
+ */
20
+ export type UniquenessCheckFunc<EntityToSave> = (given: EntityToSave, existing: EntityToSave) => UniquenessCheckResult;
21
+
22
+ export interface TypeOrmSaveEntityPipelineStepOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = EntityToSave, OutFragment = EntityToSave>
23
+ extends TypeOrmPipelineStepOptions<In, Out, InFragment, OutFragment> {
24
+ entityName: TypeOrmEntityName;
25
+ fillIdBySnowflake?: boolean;
26
+ uniquenessCheckSnippet?: ScriptFuncOrBody<UniquenessCheckFunc<InFragment>>;
27
+ }
28
+
29
+ /**
30
+ * no transaction here
31
+ */
32
+ export class TypeOrmSaveEntityPipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = EntityToSave, OutFragment = EntityToSave>
33
+ extends AbstractTypeOrmPipelineStep<In, Out, InFragment, OutFragment> {
34
+ private readonly _entityName: TypeOrmEntityName;
35
+ private readonly _fillIdBySnowflake: boolean;
36
+ private readonly _uniquenessCheckSnippet?: ScriptFuncOrBody<UniquenessCheckFunc<InFragment>>;
37
+ private readonly _uniquenessCheckFunc?: UniquenessCheckFunc<InFragment>;
38
+
39
+ public constructor(options: TypeOrmSaveEntityPipelineStepOptions<In, Out, InFragment, OutFragment>) {
40
+ super(options);
41
+ this._entityName = options.entityName;
42
+ this._fillIdBySnowflake = options.fillIdBySnowflake ?? false;
43
+ this._uniquenessCheckSnippet = options.uniquenessCheckSnippet;
44
+ this._uniquenessCheckFunc = Utils.createSyncFunction(this.getUniquenessCheckSnippet(), {
45
+ createDefault: () => (void 0),
46
+ getVariableNames: () => this.generateUniquenessCheckVariableNames(),
47
+ error: (e: Error) => {
48
+ this.getLogger().error(`Failed on create function for uniqueness check, snippet is [${this.getUniquenessCheckSnippet()}].`);
49
+ throw e;
50
+ }
51
+ });
52
+ }
53
+
54
+ public getEntityName(): TypeOrmEntityName {
55
+ return this._entityName;
56
+ }
57
+
58
+ public isFillIdBySnowflake(): boolean {
59
+ return this._fillIdBySnowflake;
60
+ }
61
+
62
+ public getUniquenessCheckSnippet(): Undefinable<ScriptFuncOrBody<UniquenessCheckFunc<InFragment>>> {
63
+ return this._uniquenessCheckSnippet;
64
+ }
65
+
66
+ public needUniquenessCheck(): boolean {
67
+ return this._uniquenessCheckFunc != null;
68
+ }
69
+
70
+ protected generateUniquenessCheckVariableNames(): Array<string> {
71
+ return ['given', 'existing'];
72
+ }
73
+
74
+ /**
75
+ * 1. check uniqueness of given entity,
76
+ * - only when need it, see {@link needUniquenessCheck}
77
+ * - load entity by id, which get from given entity,
78
+ * - compare given and existing by uniqueness check snippet (see {@link _uniquenessCheckFunc}), only when existing loaded from database,
79
+ * - throw catchable error if failed on uniqueness check,
80
+ * 2. generate snowflake id and set into given entity,
81
+ * - only when need it, see {@link isFillIdBySnowflake}
82
+ * 3. save it.
83
+ */
84
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
85
+ protected async doPerform(entity: InFragment, request: PipelineStepData<In>): Promise<OutFragment> {
86
+ const [, column, repository] = await this.findMetadata(this.getEntityName(), request);
87
+ if (this.needUniquenessCheck()) {
88
+ const id = entity[column.propertyName];
89
+ if (`${id ?? ''}`.trim().length !== 0) {
90
+ const existing = await repository.findOneBy({[column.propertyName]: id}) as InFragment;
91
+ if (existing != null) {
92
+ const checked = this._uniquenessCheckFunc(entity, existing);
93
+ if (checked.pass !== true) {
94
+ throw new CatchableError(checked.code, checked.message);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ if (this.isFillIdBySnowflake()) {
100
+ // fill id if not exists
101
+ const id = entity[column.propertyName];
102
+ if (id == null || `${id ?? ''}`.trim().length === 0) {
103
+ entity[column.propertyName] = Snowflake.generate() as unknown as number;
104
+ }
105
+ }
106
+
107
+ return await repository.save(entity) as OutFragment;
108
+ }
109
+ }
@@ -0,0 +1,25 @@
1
+ import {DeepPartial, ObjectLiteral, QueryRunner} from 'typeorm';
2
+ import {PipelineStepSetsContext} from '../step';
3
+ import {TypeOrmDataSource} from '../typeorm';
4
+
5
+ export type TypeOrmIdType = string | number | bigint;
6
+ export type TypeOrmDataSourceName = string;
7
+ export type TypeOrmTransactionName = string;
8
+ export type TypeOrmTransactionKey = `${TypeOrmDataSourceName}.${TypeOrmTransactionName}`;
9
+ export type TypeOrmEntityName = string;
10
+ export type TypeOrmEntityValue = string | number | bigint | boolean | Date | null | undefined;
11
+ export type TypeOrmEntityToLoad = DeepPartial<ObjectLiteral>;
12
+ export type TypeOrmEntityToSave = DeepPartial<ObjectLiteral>;
13
+ export type TypeOrmSql = string;
14
+ export type TypeOrmIdOfInserted = TypeOrmIdType;
15
+ export type TypeOrmIdsOfInserted = Array<TypeOrmIdOfInserted>;
16
+ export type TypeOrmCountOfAffected = number;
17
+ export type TypeOrmCountsOfAffected = Array<TypeOrmCountOfAffected>;
18
+ export type TypeOrmWrittenResult = TypeOrmIdOfInserted | TypeOrmCountOfAffected;
19
+ export type TypeOrmBulkWrittenResult = TypeOrmIdsOfInserted | TypeOrmCountsOfAffected;
20
+
21
+ export const DEFAULT_TRANSACTION_NAME: TypeOrmTransactionName = '$default-transaction';
22
+
23
+ export interface TypeOrmTransactionalContext extends PipelineStepSetsContext {
24
+ $trans: Record<TypeOrmTransactionKey, [TypeOrmDataSource, QueryRunner]>;
25
+ }
@@ -0,0 +1,21 @@
1
+ import {createConfig, createLogger} from '@rainbow-o23/n1';
2
+ import {SnippetPipelineStep} from '../../src';
3
+
4
+ const logger = createLogger();
5
+ const config = createConfig(logger);
6
+
7
+ test('Snippet Pipeline Step Test #1, + 100', async () => {
8
+ const snippet = 'return $factor.base + 100;';
9
+ const step = new SnippetPipelineStep({config, logger, snippet});
10
+ const request = {content: {base: 1}};
11
+ const response = await step.perform(request);
12
+ expect(response.content).toEqual(101);
13
+ });
14
+
15
+ test('Snippet Pipeline Step Test #2, async + 100', async () => {
16
+ const snippet = 'return await new Promise(resolve => resolve($factor.base + 100));';
17
+ const step = new SnippetPipelineStep({config, logger, snippet});
18
+ const request = {content: {base: 1}};
19
+ const response = await step.perform(request);
20
+ expect(response.content).toEqual(101);
21
+ });
@@ -0,0 +1,22 @@
1
+ import {createConfig, createLogger} from '@rainbow-o23/n1';
2
+ import {SnowflakePipelineStep} from '../../src';
3
+
4
+ const logger = createLogger();
5
+ const config = createConfig(logger);
6
+
7
+ test('Snowflake Pipeline Step Test #1, replace content', async () => {
8
+ const step = new SnowflakePipelineStep({config, logger, mergeRequest: '$id'});
9
+ const request = {content: (void 0)};
10
+ const response = await step.perform(request);
11
+ expect(response.content).not.toBeNull();
12
+ expect(response.content.$id).not.toBeNull();
13
+ });
14
+
15
+ test('Snowflake Pipeline Step Test #2, merge content', async () => {
16
+ const step = new SnowflakePipelineStep({config, logger, mergeRequest: '$id'});
17
+ const request = {content: {base: 1}};
18
+ const response = await step.perform(request);
19
+ expect(response.content).not.toBeNull();
20
+ expect(response.content.base).toEqual(1);
21
+ expect(response.content.$id).not.toBeNull();
22
+ });
@@ -0,0 +1,117 @@
1
+ import {createConfig, createLogger} from '@rainbow-o23/n1';
2
+ import {Column, Entity, PrimaryColumn} from 'typeorm';
3
+ import {
4
+ TypeOrmBulkSaveBySQLPipelineStep,
5
+ TypeOrmDataSourceHelper,
6
+ TypeOrmDataSourceManager,
7
+ TypeOrmLoadManyBySQLPipelineStep,
8
+ TypeOrmLoadOneBySQLPipelineStep,
9
+ TypeOrmSaveBySQLPipelineStep
10
+ } from '../../src';
11
+
12
+ const logger = createLogger();
13
+ const config = createConfig(logger);
14
+
15
+ // decorators are leading syntax errors in ide, since the test folder is not included in tsconfig.json
16
+ // but the tricky thing is, once test folder included into tsconfig.json, d.ts files will be created in src folder
17
+ // which cause currently use ts-ignore to avoid this syntax errors
18
+ // @ts-ignore
19
+ @Entity({name: 'T_TEST_TABLE'})
20
+ export class TestTable {
21
+ // @ts-ignore
22
+ @PrimaryColumn('bigint', {name: 'ID'})
23
+ id: number;
24
+ // @ts-ignore
25
+ @Column('varchar', {name: 'CONTENT'})
26
+ content: string;
27
+ }
28
+
29
+ describe('TypeORM SQL Autonomous Suite', () => {
30
+ beforeAll(async () => {
31
+ process.env.CFG_TYPEORM_TEST_TYPE = 'better-sqlite3';
32
+ process.env.CFG_TYPEORM_TEST_SYNCHRONIZE = 'true';
33
+ // in memory, kept in global cache, to make sure always use same one.
34
+ process.env.CFG_TYPEORM_TEST_KEPT_ON_GLOBAL = 'true';
35
+ // process.env.CFG_TYPEORM_TEST_LOGGING = 'true';
36
+ await new TypeOrmDataSourceHelper(config).create({
37
+ 'TEST': [TestTable]
38
+ });
39
+ const repo = (await TypeOrmDataSourceManager.findDataSource('TEST')).getDataSource().getRepository(TestTable);
40
+ await repo.insert({id: 1, content: 'hello world!'});
41
+ await repo.insert({id: 2, content: 'good-bye world!'});
42
+ });
43
+
44
+ test('TypeORM load one by sql Pipeline Step Test #1', async () => {
45
+ // noinspection SqlResolve
46
+ const step = new TypeOrmLoadOneBySQLPipelineStep({
47
+ config, logger, dataSourceName: 'TEST', sql: 'SELECT ID id, CONTENT content FROM T_TEST_TABLE WHERE ID = ?',
48
+ autonomous: true
49
+ });
50
+ const request = {content: {params: [1]}};
51
+ const response = await step.perform(request);
52
+ expect(response.content).not.toBeNull();
53
+ expect(response.content.id).toBe(1);
54
+ expect(response.content.content).toBe('hello world!');
55
+ });
56
+
57
+ test('TypeORM insert one by sql Pipeline Step Test #1', async () => {
58
+ // noinspection SqlResolve
59
+ const step = new TypeOrmSaveBySQLPipelineStep({
60
+ config, logger, dataSourceName: 'TEST', sql: 'INSERT INTO T_TEST_TABLE(ID, CONTENT) VALUES (?, ?)',
61
+ autonomous: true, mergeRequest: 'id'
62
+ });
63
+ const request = {content: {values: [3, 'another world!']}};
64
+ const response = await step.perform(request);
65
+ expect(response.content).not.toBeNull();
66
+ expect(response.content.id).toBe(3);
67
+ });
68
+
69
+ test('TypeORM update one by sql Pipeline Step Test #1', async () => {
70
+ // noinspection SqlResolve
71
+ const step = new TypeOrmSaveBySQLPipelineStep({
72
+ config, logger, dataSourceName: 'TEST', sql: 'UPDATE T_TEST_TABLE SET CONTENT = ? WHERE ID = ?',
73
+ autonomous: true, mergeRequest: 'count'
74
+ });
75
+ const request = {content: {values: ['world #3!', 3]}};
76
+ const response = await step.perform(request);
77
+ expect(response.content).not.toBeNull();
78
+ // DON'T KNOW WHY THIS IS 3, SEEMS SHOULD BE 1 ACCORDING TO BETTER-SQLITE3 DOCUMENT
79
+ // BUT CURRENTLY IT RETURNS COUNT OF THIS TABLE, NOT IMPACTED ROW COUNT
80
+ expect(response.content.count).toBe(3);
81
+ });
82
+
83
+ test('TypeORM insert many by sql Pipeline Step Test #1', async () => {
84
+ // noinspection SqlResolve
85
+ const step = new TypeOrmBulkSaveBySQLPipelineStep({
86
+ config, logger, dataSourceName: 'TEST', sql: 'INSERT INTO T_TEST_TABLE(ID, CONTENT) VALUES (?, ?)',
87
+ autonomous: true, mergeRequest: 'ids'
88
+ });
89
+ const request = {
90
+ content: {
91
+ items: [[4, 'world #4!'], [5, 'world #5!']]
92
+ }
93
+ };
94
+ const response = await step.perform(request);
95
+ expect(response.content).not.toBeNull();
96
+ expect(response.content.ids.length).toBe(2);
97
+ expect(response.content.ids[0]).toBe(4);
98
+ expect(response.content.ids[1]).toBe(5);
99
+ });
100
+
101
+ test('TypeORM load many by sql Pipeline Step Test #1', async () => {
102
+ // noinspection SqlResolve
103
+ const step = new TypeOrmLoadManyBySQLPipelineStep<any, Array<TestTable>>({
104
+ config, logger, dataSourceName: 'TEST', sql: 'SELECT ID id, CONTENT content FROM T_TEST_TABLE',
105
+ autonomous: true
106
+ });
107
+ const request = {content: (void 0)};
108
+ const response = await step.perform(request);
109
+ expect(response.content).not.toBeNull();
110
+ expect(Array.isArray(response.content)).toBeTruthy();
111
+ expect(response.content[2].content).toBe('world #3!');
112
+ });
113
+
114
+ afterAll((done) => {
115
+ done();
116
+ });
117
+ });
@@ -0,0 +1,215 @@
1
+ import {
2
+ AbstractStaticPipeline,
3
+ createConfig,
4
+ createLogger,
5
+ PipelineCode,
6
+ PipelineStepData,
7
+ PipelineStepType,
8
+ Undefinable
9
+ } from '@rainbow-o23/n1';
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import {Column, Entity, PrimaryColumn} from 'typeorm';
13
+ import {
14
+ TypeOrmBasis,
15
+ TypeOrmBulkSaveBySQLPipelineStep,
16
+ TypeOrmCountOfAffected,
17
+ TypeOrmDataSourceHelper,
18
+ TypeOrmDataSourceManager,
19
+ TypeOrmIdOfInserted,
20
+ TypeOrmIdsOfInserted,
21
+ TypeOrmLoadManyBySQLPipelineStep,
22
+ TypeOrmLoadOneBySQLPipelineStep,
23
+ TypeOrmSaveBySQLPipelineStep,
24
+ TypeOrmTransactionalPipelineStepSets,
25
+ TypeOrmTransactionalPipelineStepSetsOptions
26
+ } from '../../src';
27
+
28
+ const logger = createLogger();
29
+ const config = createConfig(logger);
30
+
31
+ // decorators are leading syntax errors in ide, since the test folder is not included in tsconfig.json
32
+ // but the tricky thing is, once test folder included into tsconfig.json, d.ts files will be created in src folder
33
+ // which cause currently use ts-ignore to avoid this syntax errors
34
+ // @ts-ignore
35
+ @Entity({name: 'T_TEST_TABLE'})
36
+ export class TestTable {
37
+ // @ts-ignore
38
+ @PrimaryColumn('bigint', {name: 'ID'})
39
+ id: number;
40
+ // @ts-ignore
41
+ @Column('varchar', {name: 'CONTENT'})
42
+ content: string;
43
+ }
44
+
45
+ interface TransactionalRequest {
46
+ idToLoad: number,
47
+ item3: TestTable;
48
+ item3ChangeTo: TestTable;
49
+ item4: TestTable;
50
+ item5: TestTable;
51
+ }
52
+
53
+ interface TransactionalResponse extends TransactionalRequest {
54
+ loadedById: TestTable;
55
+ insertedId: TypeOrmIdOfInserted;
56
+ updatedCount: TypeOrmCountOfAffected;
57
+ insertedIds: TypeOrmIdsOfInserted;
58
+ all: Array<TestTable>;
59
+ }
60
+
61
+ class TransactionalTestStepSets extends TypeOrmTransactionalPipelineStepSets {
62
+ constructor(options: Omit<TypeOrmTransactionalPipelineStepSetsOptions, 'dataSourceName'>) {
63
+ // noinspection SqlResolve
64
+ super({
65
+ ...options, dataSourceName: 'TEST',
66
+ steps: [
67
+ {
68
+ create: async ({config, logger}) => new TypeOrmLoadOneBySQLPipelineStep({
69
+ config,
70
+ logger,
71
+ dataSourceName: 'TEST',
72
+ sql: 'SELECT ID id, CONTENT content FROM T_TEST_TABLE WHERE ID = ?',
73
+ fromRequest: ($factor: TransactionalRequest, _$request: PipelineStepData<TransactionalRequest>) => {
74
+ return {params: [$factor.idToLoad]} as TypeOrmBasis;
75
+ },
76
+ toResponse: ($result: TestTable, _$request: PipelineStepData) => {
77
+ expect($result.id).toBe(1);
78
+ expect($result.content).toBe('hello world!');
79
+ return {loadedById: $result};
80
+ },
81
+ mergeRequest: true
82
+ })
83
+ },
84
+ {
85
+ create: async ({config, logger}) => new TypeOrmSaveBySQLPipelineStep({
86
+ config,
87
+ logger,
88
+ dataSourceName: 'TEST',
89
+ sql: 'INSERT INTO T_TEST_TABLE(ID, CONTENT) VALUES (?, ?)',
90
+ fromRequest: ($factor: TransactionalRequest, _$request: PipelineStepData<TransactionalRequest>) => {
91
+ return {values: [$factor.item3.id, $factor.item3.content]};
92
+ },
93
+ toResponse: ($result: TypeOrmIdOfInserted, _$request: PipelineStepData) => {
94
+ expect($result).toBe(3);
95
+ return {insertedId: $result};
96
+ },
97
+ mergeRequest: true
98
+ })
99
+ },
100
+ {
101
+ create: async ({config, logger}) => new TypeOrmLoadOneBySQLPipelineStep({
102
+ config,
103
+ logger,
104
+ dataSourceName: 'TEST',
105
+ autonomous: true,
106
+ sql: 'SELECT ID id, CONTENT content FROM T_TEST_TABLE WHERE ID = ?',
107
+ fromRequest: ($factor: TransactionalRequest, _$request: PipelineStepData<TransactionalRequest>) => {
108
+ return {params: [$factor.item3.id]};
109
+ },
110
+ toResponse: ($result: Undefinable<TestTable>, $request: PipelineStepData) => {
111
+ expect($result).toBeUndefined();
112
+ return $request.content;
113
+ }
114
+ })
115
+ },
116
+ {
117
+ create: async ({config, logger}) => new TypeOrmSaveBySQLPipelineStep({
118
+ config, logger, dataSourceName: 'TEST', sql: 'UPDATE T_TEST_TABLE SET CONTENT = ? WHERE ID = ?',
119
+ fromRequest: ($factor: TransactionalRequest, _$request: PipelineStepData<TransactionalRequest>) => {
120
+ return {values: [$factor.item3ChangeTo.content, $factor.item3ChangeTo.id]};
121
+ },
122
+ toResponse: ($result: TypeOrmCountOfAffected, _$request: PipelineStepData) => {
123
+ // DON'T KNOW WHY THIS IS 3, SEEMS SHOULD BE 1 ACCORDING TO BETTER-SQLITE3 DOCUMENT
124
+ // BUT CURRENTLY IT RETURNS COUNT OF THIS TABLE, NOT IMPACTED ROW COUNT
125
+ expect($result).toBe(3);
126
+ return {updatedCount: $result};
127
+ },
128
+ mergeRequest: true
129
+ })
130
+ },
131
+ {
132
+ create: async ({config, logger}) => new TypeOrmBulkSaveBySQLPipelineStep({
133
+ config,
134
+ logger,
135
+ dataSourceName: 'TEST',
136
+ sql: 'INSERT INTO T_TEST_TABLE(ID, CONTENT) VALUES (?, ?)',
137
+ fromRequest: ($factor: TransactionalRequest, _$request: PipelineStepData<TransactionalRequest>) => {
138
+ return {
139
+ items: [
140
+ [$factor.item4.id, $factor.item4.content], [$factor.item5.id, $factor.item5.content]
141
+ ]
142
+ };
143
+ },
144
+ toResponse: ($result: TypeOrmIdsOfInserted, _$request: PipelineStepData) => {
145
+ return {insertedIds: $result};
146
+ },
147
+ mergeRequest: true
148
+ })
149
+ },
150
+ {
151
+ create: async ({config, logger}) => new TypeOrmLoadManyBySQLPipelineStep({
152
+ config, logger, dataSourceName: 'TEST', sql: 'SELECT ID id, CONTENT content FROM T_TEST_TABLE',
153
+ toResponse: ($result: Array<TestTable>, _$request: PipelineStepData) => {
154
+ return {all: $result};
155
+ },
156
+ mergeRequest: true
157
+ })
158
+ }
159
+ ]
160
+ });
161
+ }
162
+ }
163
+
164
+ class TransactionalPipeline extends AbstractStaticPipeline<TransactionalRequest, TransactionalResponse> {
165
+ public getCode(): PipelineCode {
166
+ return 'TransactionalPipeline';
167
+ }
168
+
169
+ protected getStepTypes(): Array<PipelineStepType> {
170
+ return [TransactionalTestStepSets];
171
+ }
172
+ }
173
+
174
+ const filename = 'TypeORM SQL Transactional.db';
175
+ describe('TypeORM SQL Transactional Suite', () => {
176
+ beforeAll(async () => {
177
+ if (fs.existsSync(path.resolve(filename))) {
178
+ fs.rmSync(path.resolve(filename));
179
+ }
180
+ process.env.CFG_TYPEORM_TEST_TYPE = 'better-sqlite3';
181
+ process.env.CFG_TYPEORM_TEST_SYNCHRONIZE = 'true';
182
+ // process.env.CFG_TYPEORM_TEST_LOGGING = 'true';
183
+ // cannot use in memory, since when cache it, only one connection exists,
184
+ // when don't cache it, doesn't share the same memory.
185
+ // therefore use file
186
+ process.env.CFG_TYPEORM_TEST_DATABASE = filename;
187
+ await new TypeOrmDataSourceHelper(config).create({
188
+ 'TEST': [TestTable]
189
+ });
190
+ const repo = (await TypeOrmDataSourceManager.findDataSource('TEST')).getDataSource().getRepository(TestTable);
191
+ await repo.insert({id: 1, content: 'hello world!'});
192
+ await repo.insert({id: 2, content: 'good-bye world!'});
193
+ });
194
+
195
+ test('TypeORM transactional by sql Pipeline Step Test #1', async () => {
196
+ const pipeline = new TransactionalPipeline({config, logger});
197
+ const response = await pipeline.perform({
198
+ payload: {
199
+ idToLoad: 1,
200
+ item3: {id: 3, content: 'another world!'},
201
+ item3ChangeTo: {id: 3, content: 'world #3!'},
202
+ item4: {id: 4, content: 'world #4'},
203
+ item5: {id: 5, content: 'world #5'}
204
+ }
205
+ });
206
+ expect(response).not.toBeNull();
207
+ });
208
+
209
+ afterAll((done) => {
210
+ if (fs.existsSync(path.resolve(filename))) {
211
+ fs.rmSync(path.resolve(filename));
212
+ }
213
+ done();
214
+ });
215
+ });
@@ -0,0 +1,50 @@
1
+ import {createConfig, createLogger} from '@rainbow-o23/n1';
2
+ import {Column, Entity, PrimaryColumn} from 'typeorm';
3
+ import {TypeOrmDataSourceHelper, TypeOrmDataSourceManager, TypeOrmLoadEntityByIdPipelineStep} from '../../src';
4
+
5
+ const logger = createLogger();
6
+ const config = createConfig(logger);
7
+
8
+ // decorators are leading syntax errors in ide, since the test folder is not included in tsconfig.json
9
+ // but the tricky thing is, once test folder included into tsconfig.json, d.ts files will be created in src folder
10
+ // which cause currently use ts-ignore to avoid this syntax errors
11
+ // @ts-ignore
12
+ @Entity({name: 'T_TEST_TABLE'})
13
+ export class TestTable {
14
+ // @ts-ignore
15
+ @PrimaryColumn('bigint', {name: 'ID'})
16
+ id: number;
17
+ // @ts-ignore
18
+ @Column('varchar', {name: 'CONTENT'})
19
+ content: string;
20
+ }
21
+
22
+ describe('TypeORM Entity Suite', () => {
23
+ beforeAll(async () => {
24
+ process.env.CFG_TYPEORM_TEST_TYPE = 'better-sqlite3';
25
+ process.env.CFG_TYPEORM_TEST_SYNCHRONIZE = 'true';
26
+ // in memory, kept in global cache, to make sure always use same one.
27
+ process.env.CFG_TYPEORM_TEST_KEPT_ON_GLOBAL = 'true';
28
+ await new TypeOrmDataSourceHelper(config).create({
29
+ 'TEST': [TestTable]
30
+ });
31
+ const repo = (await TypeOrmDataSourceManager.findDataSource('TEST')).getDataSource().getRepository(TestTable);
32
+ await repo.insert({id: 1, content: 'hello world!'});
33
+ });
34
+
35
+ test('TypeORM load entity by id Pipeline Step Test #1', async () => {
36
+ const step = new TypeOrmLoadEntityByIdPipelineStep({
37
+ config, logger, dataSourceName: 'TEST', entityName: 'TestTable',
38
+ autonomous: true
39
+ });
40
+ const request = {content: 1};
41
+ const response = await step.perform(request);
42
+ expect(response.content).not.toBeNull();
43
+ expect(response.content.id).toBe(1);
44
+ expect(response.content.content).toBe('hello world!');
45
+ });
46
+
47
+ afterAll((done) => {
48
+ done();
49
+ });
50
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "outDir": "./dist",
5
+ "declarationDir": "./lib",
6
+ "module": "ES2020",
7
+ "target": "ESNext",
8
+ "moduleResolution": "node",
9
+ "sourceMap": true,
10
+ // create d.ts file
11
+ "declaration": true,
12
+ "removeComments": true,
13
+ "noImplicitAny": false,
14
+ "allowSyntheticDefaultImports": true,
15
+ "allowUnreachableCode": true,
16
+ "allowUnusedLabels": false,
17
+ "alwaysStrict": true,
18
+ "allowJs": true,
19
+ "experimentalDecorators": true,
20
+ "lib": [
21
+ "es5",
22
+ "es2015",
23
+ "es2016",
24
+ "es2017",
25
+ "es2018",
26
+ "dom"
27
+ ]
28
+ },
29
+ "include": [
30
+ "src/**/*"
31
+ ],
32
+ "exclude": [
33
+ "node_modules",
34
+ "dist"
35
+ ]
36
+ }