@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,367 @@
1
+ import {
2
+ AbstractPipelineStep,
3
+ CatchableError,
4
+ ERR_UNKNOWN,
5
+ ExposedUncatchableError,
6
+ PipelineStepBuilder,
7
+ PipelineStepData,
8
+ PipelineStepHelpers,
9
+ PipelineStepOptions,
10
+ PipelineStepPayload,
11
+ UncatchableError
12
+ } from '@rainbow-o23/n1';
13
+ import {PipelineStepSetsContext} from './step-sets';
14
+ import {
15
+ HandleAnyError,
16
+ HandleCatchableError,
17
+ HandleExposedUncatchableError,
18
+ HandleUncatchableError,
19
+ ScriptFuncOrBody
20
+ } from './types';
21
+ import {Utils} from './utils';
22
+
23
+ export interface FragmentaryPipelineStepOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
24
+ extends PipelineStepOptions {
25
+ fromRequest?: ScriptFuncOrBody<GetInFragmentFromRequestFunc<In, InFragment>>;
26
+ toResponse?: ScriptFuncOrBody<SetOutFragmentToResponseFunc<In, Out, OutFragment>>;
27
+ /**
28
+ * false: return OutFragment as Out directly,
29
+ * true: merge In and OutFragment as an object, make sure the In and OutFragment can be unboxed.
30
+ * and if there is duplicated keys, value from OutFragment has high priority, value with same key of In will be replaced.
31
+ * string: merge OutFragment into In, use given string as property name.
32
+ */
33
+ mergeRequest?: boolean | string;
34
+ errorHandles?: {
35
+ catchable?: ScriptFuncOrBody<HandleCatchableError<In, InFragment, OutFragment>> | Array<PipelineStepBuilder>;
36
+ uncatchable?: ScriptFuncOrBody<HandleUncatchableError<In, InFragment, OutFragment>> | Array<PipelineStepBuilder>;
37
+ exposed?: ScriptFuncOrBody<HandleExposedUncatchableError<In, InFragment, OutFragment>> | Array<PipelineStepBuilder>;
38
+ any?: ScriptFuncOrBody<HandleAnyError<In, InFragment, OutFragment>> | Array<PipelineStepBuilder>;
39
+ };
40
+ }
41
+
42
+ /**
43
+ * parameter names could be change {@link AbstractFragmentaryPipelineStep#generateFromRequestVariableNames}
44
+ */
45
+ export type GetInFragmentFromRequestFunc<In, InFragment> = ($factor: In, $request: PipelineStepData<In>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers) => InFragment;
46
+ /**
47
+ * parameter names could be change {@link AbstractFragmentaryPipelineStep#generateToResponseVariableNames}
48
+ */
49
+ export type SetOutFragmentToResponseFunc<In, Out, OutFragment> = ($result: OutFragment, $request: PipelineStepData<In>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers) => Out;
50
+
51
+ /**
52
+ * deal with fragment data from request, and put result into response.
53
+ * default get content from request, and put result as response content.
54
+ */
55
+ export abstract class AbstractFragmentaryPipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
56
+ extends AbstractPipelineStep<In, Out> {
57
+ private readonly _fromRequestSnippet: ScriptFuncOrBody<GetInFragmentFromRequestFunc<In, InFragment>>;
58
+ private readonly _toResponseSnippet: ScriptFuncOrBody<SetOutFragmentToResponseFunc<In, Out, OutFragment>>;
59
+ private readonly _mergeRequest: boolean | string;
60
+ private readonly _fromRequestFunc: GetInFragmentFromRequestFunc<In, InFragment>;
61
+ private readonly _toResponseFunc: SetOutFragmentToResponseFunc<In, Out, OutFragment>;
62
+ private readonly _errorsSnippets?: {
63
+ catchable?: ScriptFuncOrBody<HandleCatchableError<In, InFragment, OutFragment>> | Array<PipelineStepBuilder>;
64
+ uncatchable?: ScriptFuncOrBody<HandleUncatchableError<In, InFragment, OutFragment>> | Array<PipelineStepBuilder>;
65
+ exposed?: ScriptFuncOrBody<HandleExposedUncatchableError<In, InFragment, OutFragment>> | Array<PipelineStepBuilder>;
66
+ any?: ScriptFuncOrBody<HandleAnyError<In, InFragment, OutFragment>> | Array<PipelineStepBuilder>;
67
+ };
68
+ private readonly _errorsFuncs: {
69
+ catchable?: HandleCatchableError<In, InFragment, OutFragment> | Array<PipelineStepBuilder>;
70
+ uncatchable?: HandleUncatchableError<In, InFragment, OutFragment> | Array<PipelineStepBuilder>;
71
+ exposed?: HandleExposedUncatchableError<In, InFragment, OutFragment> | Array<PipelineStepBuilder>;
72
+ any?: HandleAnyError<In, InFragment, OutFragment> | Array<PipelineStepBuilder>;
73
+ };
74
+
75
+ // noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected
76
+ public constructor(options: FragmentaryPipelineStepOptions<In, Out, InFragment, OutFragment>) {
77
+ super(options);
78
+ this._fromRequestSnippet = options.fromRequest;
79
+ this._toResponseSnippet = options.toResponse;
80
+ if (typeof options.mergeRequest === 'string') {
81
+ this._mergeRequest = options.mergeRequest.trim();
82
+ if (this._mergeRequest === '') {
83
+ this._mergeRequest = false;
84
+ }
85
+ } else {
86
+ this._mergeRequest = options.mergeRequest ?? false;
87
+ }
88
+ this._fromRequestFunc = Utils.createSyncFunction(this.getFromRequestSnippet(), {
89
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
90
+ createDefault: () => ($factor: In, _$request: PipelineStepData<In>, _$helpers: PipelineStepHelpers, _$: PipelineStepHelpers): InFragment => $factor as unknown as InFragment,
91
+ getVariableNames: () => this.generateFromRequestVariableNames(),
92
+ error: (e: Error) => {
93
+ this.getLogger().error(`Failed on create function for from request transformer, snippet is [${this.getFromRequestSnippet()}].`);
94
+ throw e;
95
+ }
96
+ });
97
+ this._toResponseFunc = this.createToResponseFunc();
98
+ this._errorsSnippets = options.errorHandles;
99
+ this._errorsFuncs = {
100
+ catchable: (this._errorsSnippets?.catchable != null && Array.isArray(this._errorsSnippets?.catchable))
101
+ ? this._errorsSnippets?.catchable
102
+ : Utils.createAsyncFunction(this._errorsSnippets?.catchable, {
103
+ createDefault: () => (void 0),
104
+ getVariableNames: () => ['$options', ...this.getHelpersVariableNames()],
105
+ error: (e: Error) => {
106
+ this.getLogger().error(`Failed on create function for catchable error handler, snippet is [${this._errorsSnippets?.catchable}].`);
107
+ throw e;
108
+ }
109
+ }),
110
+ uncatchable: (this._errorsSnippets?.uncatchable != null && Array.isArray(this._errorsSnippets?.uncatchable))
111
+ ? this._errorsSnippets?.uncatchable
112
+ : Utils.createAsyncFunction(this._errorsSnippets?.uncatchable, {
113
+ createDefault: () => (void 0),
114
+ getVariableNames: () => ['$options', ...this.getHelpersVariableNames()],
115
+ error: (e: Error) => {
116
+ this.getLogger().error(`Failed on create function for uncatchable error handler, snippet is [${this._errorsSnippets?.uncatchable}].`);
117
+ throw e;
118
+ }
119
+ }),
120
+ exposed: (this._errorsSnippets?.exposed != null && Array.isArray(this._errorsSnippets?.exposed))
121
+ ? this._errorsSnippets?.exposed
122
+ : Utils.createAsyncFunction(this._errorsSnippets?.exposed, {
123
+ createDefault: () => (void 0),
124
+ getVariableNames: () => ['$options', ...this.getHelpersVariableNames()],
125
+ error: (e: Error) => {
126
+ this.getLogger().error(`Failed on create function for exposed uncatchable error handler, snippet is [${this._errorsSnippets?.exposed}].`);
127
+ throw e;
128
+ }
129
+ }),
130
+ any: (this._errorsSnippets?.any != null && Array.isArray(this._errorsSnippets?.any))
131
+ ? this._errorsSnippets?.any
132
+ : Utils.createAsyncFunction(this._errorsSnippets?.any, {
133
+ createDefault: () => (void 0),
134
+ getVariableNames: () => ['$options', ...this.getHelpersVariableNames()],
135
+ error: (e: Error) => {
136
+ this.getLogger().error(`Failed on create function for any error handler, snippet is [${this._errorsSnippets?.any}].`);
137
+ throw e;
138
+ }
139
+ })
140
+ };
141
+ }
142
+
143
+ public getFromRequestSnippet(): ScriptFuncOrBody<GetInFragmentFromRequestFunc<In, InFragment>> {
144
+ return this._fromRequestSnippet;
145
+ }
146
+
147
+ public getToResponseSnippet(): ScriptFuncOrBody<SetOutFragmentToResponseFunc<In, Out, OutFragment>> {
148
+ return this._toResponseSnippet;
149
+ }
150
+
151
+ /**
152
+ * request should be merged into response automatically or not.
153
+ * be noted that when enable this, the request content and result both need to be an object,
154
+ * they will be unboxed and merged into a new object, and putting as response content
155
+ */
156
+ public isMergeRequest(): boolean {
157
+ return this.useUnboxMerging() || this.hasMergeKey();
158
+ }
159
+
160
+ public useUnboxMerging() {
161
+ return this._mergeRequest === true;
162
+ }
163
+
164
+ public hasMergeKey(): boolean {
165
+ return typeof this._mergeRequest === 'string' && this._mergeRequest.length !== 0;
166
+ }
167
+
168
+ public getMergeKey(): string {
169
+ return this._mergeRequest as string;
170
+ }
171
+
172
+ /**
173
+ * default is $factor and $request
174
+ */
175
+ protected generateFromRequestVariableNames(): Array<string> {
176
+ return ['$factor', '$request', ...this.getHelpersVariableNames()];
177
+ }
178
+
179
+ /**
180
+ * default is $result and $request
181
+ */
182
+ protected generateToResponseVariableNames(): Array<string> {
183
+ return ['$result', '$request', ...this.getHelpersVariableNames()];
184
+ }
185
+
186
+ /**
187
+ * default behavior depends on merge request flag or not
188
+ * 1. when need to merge with request, unbox request content and result, merge them to a new object
189
+ * 2. when no need to merge with request, return result directly.
190
+ * when use given snippet to build response content, still follows the same rule,
191
+ * the only difference is result is returned by given snippet.
192
+ */
193
+ protected createToResponseFunc(): SetOutFragmentToResponseFunc<In, Out, OutFragment> {
194
+ const funcOrSnippet = this.getToResponseSnippet();
195
+ if (funcOrSnippet == null || (typeof funcOrSnippet === 'string' && funcOrSnippet.trim().length === 0)) {
196
+ if (this.useUnboxMerging()) {
197
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
198
+ return ($result: OutFragment, $request: PipelineStepData<In>, _$helpers: PipelineStepHelpers, _$: PipelineStepHelpers): Out => {
199
+ return {...$request.content, ...$result} as Out;
200
+ };
201
+ } else if (this.hasMergeKey()) {
202
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
203
+ return ($result: OutFragment, $request: PipelineStepData<In>, _$helpers: PipelineStepHelpers, _$: PipelineStepHelpers): Out => {
204
+ return {...$request.content, [this.getMergeKey()]: $result} as Out;
205
+ };
206
+ } else {
207
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
208
+ return ($result: OutFragment, _$request: PipelineStepData<In>, _$helpers: PipelineStepHelpers, _$: PipelineStepHelpers): Out => {
209
+ return $result as unknown as Out;
210
+ };
211
+ }
212
+ } else if (typeof funcOrSnippet === 'string') {
213
+ const func = new Function(...this.generateToResponseVariableNames(), funcOrSnippet);
214
+ if (this.useUnboxMerging()) {
215
+ return ($result: OutFragment, $request: PipelineStepData<In>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers): Out => {
216
+ const r = func($result, $request, $helpers, $);
217
+ return {...$request.content, ...r};
218
+ };
219
+ } else if (this.hasMergeKey()) {
220
+ return ($result: OutFragment, $request: PipelineStepData<In>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers): Out => {
221
+ const r = func($result, $request, $helpers, $);
222
+ return {...$request.content, [this.getMergeKey()]: r} as Out;
223
+ };
224
+ } else {
225
+ return func as SetOutFragmentToResponseFunc<In, Out, OutFragment>;
226
+ }
227
+ } else if (this.useUnboxMerging()) {
228
+ return ($result: OutFragment, $request: PipelineStepData<In>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers): Out => {
229
+ const r = funcOrSnippet($result, $request, $helpers, $);
230
+ return {...$request.content, ...r};
231
+ };
232
+ } else if (this.hasMergeKey()) {
233
+ return ($result: OutFragment, $request: PipelineStepData<In>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers): Out => {
234
+ const r = funcOrSnippet($result, $request, $helpers, $);
235
+ return {...$request.content, [this.getMergeKey()]: r} as Out;
236
+ };
237
+ } else {
238
+ return funcOrSnippet;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * default get request content
244
+ */
245
+ protected getFromInput($factor: In, $request: PipelineStepData<In>): InFragment {
246
+ const $helpers = this.getHelpers();
247
+ return this._fromRequestFunc($factor, $request, $helpers, $helpers);
248
+ }
249
+
250
+ /**
251
+ * default set to response content
252
+ */
253
+ protected setToOutput($result: OutFragment, $request: PipelineStepData<In>): PipelineStepData<Out> {
254
+ const $helpers = this.getHelpers();
255
+ return {content: this._toResponseFunc($result, $request, $helpers, $helpers)};
256
+ }
257
+
258
+ /**
259
+ * @param data
260
+ * @param request might be used
261
+ * @protected
262
+ */
263
+ protected abstract doPerform(data: InFragment, request: PipelineStepData<In>): Promise<OutFragment>;
264
+
265
+ protected buildStepOptions(): Pick<PipelineStepOptions, 'config' | 'logger'> {
266
+ return {config: this.getConfig(), logger: this.getLogger()};
267
+ }
268
+
269
+ protected createErrorHandleContext(request: PipelineStepData<In>): PipelineStepSetsContext {
270
+ const context = request.$context;
271
+ if (context == null) {
272
+ return {};
273
+ } else {
274
+ return Utils.clone(context);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * same logic as step-sets, but cannot depend on step-sets, since circular dependency leading error
280
+ */
281
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
282
+ protected async handleErrorSteps(fragment: InFragment, errorCode: string, error: any, request: PipelineStepData<In>, builders: Array<PipelineStepBuilder>): Promise<OutFragment> {
283
+ const {$context: {traceId} = {}} = request;
284
+ const errorContext = this.createErrorHandleContext(request);
285
+ const options = this.buildStepOptions();
286
+ const steps = await Promise.all(builders.map(async builder => await builder.create(options)));
287
+ const response = await steps.reduce(async (promise, step) => {
288
+ const request = await promise;
289
+ return await this.measurePerformance(traceId, 'STEP')
290
+ .execute(async () => {
291
+ this.traceStepIn(traceId, step, request);
292
+ const response = await step.perform({...request, $context: {...errorContext, traceId}});
293
+ this.traceStepOut(traceId, step, response);
294
+ // if no response returned, keep using request for next
295
+ return this.returnOrContinueOrClear(request, response);
296
+ });
297
+ // build request for first step
298
+ }, Promise.resolve({
299
+ content: {$code: errorCode, $error: error, $factor: fragment, $request: request},
300
+ $context: request.$context
301
+ }));
302
+ return response.content;
303
+ }
304
+
305
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
306
+ protected async handleError(fragment: InFragment, request: PipelineStepData<In>, error: any): Promise<OutFragment> {
307
+ if (this._errorsFuncs.catchable != null && error instanceof CatchableError) {
308
+ if (Array.isArray(this._errorsFuncs.catchable)) {
309
+ return await this.handleErrorSteps(fragment, error.getCode(), error, request, this._errorsFuncs.catchable);
310
+ } else {
311
+ const $helpers = this.getHelpers();
312
+ return await this._errorsFuncs.catchable({
313
+ $code: error.getCode(), $error: error, $factor: fragment, $request: request
314
+ }, $helpers, $helpers);
315
+ }
316
+ } else if (this._errorsFuncs.exposed != null && error instanceof ExposedUncatchableError) {
317
+ if (Array.isArray(this._errorsFuncs.exposed)) {
318
+ return await this.handleErrorSteps(fragment, error.getCode(), error, request, this._errorsFuncs.exposed);
319
+ } else {
320
+ const $helpers = this.getHelpers();
321
+ return await this._errorsFuncs.exposed({
322
+ $code: error.getCode(), $error: error, $factor: fragment, $request: request
323
+ }, $helpers, $helpers);
324
+ }
325
+ } else if (this._errorsFuncs.uncatchable != null && error instanceof UncatchableError) {
326
+ if (Array.isArray(this._errorsFuncs.uncatchable)) {
327
+ return await this.handleErrorSteps(fragment, error.getCode(), error, request, this._errorsFuncs.uncatchable);
328
+ } else {
329
+ const $helpers = this.getHelpers();
330
+ return await this._errorsFuncs.uncatchable({
331
+ $code: error.getCode(), $error: error, $factor: fragment, $request: request
332
+ }, $helpers, $helpers);
333
+ }
334
+ } else if (this._errorsFuncs.any != null) {
335
+ if (Array.isArray(this._errorsFuncs.any)) {
336
+ return await this.handleErrorSteps(fragment, ERR_UNKNOWN, error, request, this._errorsFuncs.any);
337
+ } else {
338
+ const $helpers = this.getHelpers();
339
+ return await this._errorsFuncs.any({
340
+ $code: ERR_UNKNOWN, $error: error, $factor: fragment, $request: request
341
+ }, $helpers, $helpers);
342
+ }
343
+ } else {
344
+ // rethrow error
345
+ throw error;
346
+ }
347
+ }
348
+
349
+ public async performAndCatch(request: PipelineStepData<In>, perform: (data: InFragment) => Promise<PipelineStepData<Out>>): Promise<PipelineStepData<Out>> {
350
+ let fragment = null;
351
+ try {
352
+ fragment = this.getFromInput(request.content, request);
353
+ return await perform(fragment);
354
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
355
+ } catch (e: any) {
356
+ const result = await this.handleError(fragment, request, e);
357
+ return this.setToOutput(result, request);
358
+ }
359
+ }
360
+
361
+ public async perform(request: PipelineStepData<In>): Promise<PipelineStepData<Out>> {
362
+ return await this.performAndCatch(request, async (fragment) => {
363
+ const result = await this.doPerform(fragment, request);
364
+ return this.setToOutput(result, request);
365
+ });
366
+ }
367
+ }
@@ -0,0 +1,14 @@
1
+ import {PipelineStepData, PipelineStepPayload} from '@rainbow-o23/n1';
2
+ import {PipelineStepSets} from './step-sets';
3
+
4
+ /**
5
+ * pipeline steps to execute sets of steps asynchronous.
6
+ */
7
+ export class AsyncPipelineStepSets<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In>
8
+ extends PipelineStepSets<In, Out, InFragment, void> {
9
+ protected async doPerform(data: InFragment, request: PipelineStepData<In>): Promise<void> {
10
+ // noinspection ES6MissingAwait
11
+ super.doPerform(data, request);
12
+ return;
13
+ }
14
+ }
@@ -0,0 +1,115 @@
1
+ import {
2
+ PipelineStepBuilder,
3
+ PipelineStepData,
4
+ PipelineStepHelpers,
5
+ PipelineStepPayload,
6
+ UncatchableError,
7
+ Undefinable
8
+ } from '@rainbow-o23/n1';
9
+ import {ERR_PIPELINE_STEP_CONDITIONAL_SNIPPET_NOT_EMPTY} from '../error-codes';
10
+ import {PipelineStepSets, PipelineStepSetsOptions} from './step-sets';
11
+ import {ScriptFuncOrBody} from './types';
12
+ import {Utils} from './utils';
13
+
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ export type ConditionCheckWithInFragment<InFragment> = ($factor: InFragment, $helpers: PipelineStepHelpers, $: PipelineStepHelpers) => Promise<boolean>;
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ export type ConditionCheckWithoutInFragment = ($helpers: PipelineStepHelpers, $: PipelineStepHelpers) => Promise<boolean>;
18
+ export type ConditionCheckFunc<InFragment> =
19
+ ConditionCheckWithInFragment<InFragment> | ConditionCheckWithoutInFragment;
20
+
21
+ export interface ConditionalPipelineStepSetsOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
22
+ extends PipelineStepSetsOptions<In, Out, InFragment, OutFragment> {
23
+ check: ScriptFuncOrBody<ConditionCheckFunc<InFragment>>;
24
+ otherwiseSteps: Array<PipelineStepBuilder>;
25
+ }
26
+
27
+ /**ß
28
+ * pipeline steps to execute internal step conditional
29
+ */
30
+ export class ConditionalPipelineStepSets<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
31
+ extends PipelineStepSets<In, Out, InFragment, OutFragment> {
32
+ private readonly _checkSnippet: ScriptFuncOrBody<ConditionCheckFunc<InFragment>>;
33
+ private readonly _func: ConditionCheckFunc<InFragment>;
34
+ private readonly _otherwiseStepBuilders: Undefinable<Array<PipelineStepBuilder>>;
35
+
36
+ // noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected
37
+ public constructor(options: ConditionalPipelineStepSetsOptions<In, Out, InFragment, OutFragment>) {
38
+ super(options);
39
+ this._checkSnippet = options.check;
40
+ this._func = Utils.createAsyncFunction(this.getCheckSnippet(), {
41
+ createDefault: (): never => {
42
+ throw new UncatchableError(ERR_PIPELINE_STEP_CONDITIONAL_SNIPPET_NOT_EMPTY, 'Cannot create perform func on empty conditional snippet.');
43
+ },
44
+ getVariableNames: () => this.generateVariableNames(),
45
+ error: (e: Error) => {
46
+ this.getLogger().error(`Failed on create function for conditional check, snippet is [${this.getCheckSnippet()}].`);
47
+ throw e;
48
+ }
49
+ });
50
+ this._otherwiseStepBuilders = options.otherwiseSteps;
51
+ }
52
+
53
+ public getCheckSnippet(): ScriptFuncOrBody<ConditionCheckFunc<InFragment>> {
54
+ return this._checkSnippet;
55
+ }
56
+
57
+ protected getOtherwiseStepBuilders(): Undefinable<Array<PipelineStepBuilder>> {
58
+ return this._otherwiseStepBuilders;
59
+ }
60
+
61
+ protected generateVariableNames(): Array<string> {
62
+ return [
63
+ this.isInFragmentIgnored() ? null : this.getInFragmentVariableName(),
64
+ ...this.getHelpersVariableNames()
65
+ ].filter(x => x != null);
66
+ }
67
+
68
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
69
+ protected async check(data: InFragment, _request: PipelineStepData<In>): Promise<boolean> {
70
+ const $helpers = this.getHelpers();
71
+ if (this.isInFragmentIgnored()) {
72
+ return await (this._func as ConditionCheckWithoutInFragment)($helpers, $helpers);
73
+ } else {
74
+ return await (this._func as ConditionCheckWithInFragment<InFragment>)(data, $helpers, $helpers);
75
+ }
76
+ }
77
+
78
+ public async perform(request: PipelineStepData<In>): Promise<PipelineStepData<Out>> {
79
+ return await this.performAndCatch(request, async (fragment) => {
80
+ const checked = await this.check(fragment, request);
81
+ if (checked) {
82
+ const result = await this.doPerform(request.content as unknown as InFragment, request);
83
+ return this.setToOutput(result, request);
84
+ }
85
+ // noinspection DuplicatedCode
86
+ const otherwiseStepBuilders = this.getOtherwiseStepBuilders();
87
+ if (otherwiseStepBuilders != null) {
88
+ // no conditional step performed
89
+ const sets = new PipelineStepSets({
90
+ ...this.buildStepOptions(), name: this.getName(), steps: otherwiseStepBuilders
91
+ });
92
+ const result = await sets.perform(request);
93
+ return this.setToOutput(result.content, request);
94
+ } else {
95
+ // otherwise route not declared
96
+ return request as unknown as PipelineStepData<Out>;
97
+ }
98
+ });
99
+ }
100
+
101
+ /**
102
+ * is request step data ignored to snippet function.
103
+ * default returns false
104
+ */
105
+ protected isInFragmentIgnored(): boolean {
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * override this method when want to use another variable name rather than "$factor"
111
+ */
112
+ protected getInFragmentVariableName(): string {
113
+ return '$factor';
114
+ }
115
+ }
@@ -0,0 +1,33 @@
1
+ import {PipelineStepData, PipelineStepOptions, PipelineStepPayload} from '@rainbow-o23/n1';
2
+ import {AbstractFragmentaryPipelineStep} from './abstract-fragmentary-pipeline-step';
3
+
4
+ export interface DeletePropertyPipelineStepOptions extends PipelineStepOptions {
5
+ propertyNames: string | Array<string>;
6
+ }
7
+
8
+ /**
9
+ * delete given property names from in fragment, and return request content directly
10
+ */
11
+ export class DeletePropertyPipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
12
+ extends AbstractFragmentaryPipelineStep<In, Out, InFragment, OutFragment> {
13
+ private readonly _propertyNames: Array<string>;
14
+
15
+ public constructor(options: DeletePropertyPipelineStepOptions) {
16
+ super(options);
17
+ this._propertyNames = (Array.isArray(options.propertyNames) ? options.propertyNames : [options.propertyNames])
18
+ .filter(name => name != null && name.trim().length !== 0);
19
+ }
20
+
21
+ public getPropertyNames(): Array<string> {
22
+ return this._propertyNames;
23
+ }
24
+
25
+ protected async doPerform(data: InFragment, request: PipelineStepData<In>): Promise<OutFragment> {
26
+ if (data != null) {
27
+ this.getPropertyNames().forEach(name => {
28
+ delete data[name];
29
+ });
30
+ }
31
+ return request.content as unknown as OutFragment;
32
+ }
33
+ }
@@ -0,0 +1,80 @@
1
+ import {PipelineStepData, PipelineStepPayload, UncatchableError} from '@rainbow-o23/n1';
2
+ import {ERR_EACH_FRAGMENT_NOT_ANY_ARRAY} from '../error-codes';
3
+ import {PipelineStepSets, PipelineStepSetsOptions} from './step-sets';
4
+
5
+ export interface EachPipelineStepSetsOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
6
+ extends PipelineStepSetsOptions<In, Out, InFragment, OutFragment> {
7
+ originalContentName?: string;
8
+ itemName?: string;
9
+ }
10
+
11
+ /**ß
12
+ * pipeline steps to execute internal step conditional
13
+ */
14
+ export class EachPipelineStepSets<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
15
+ extends PipelineStepSets<In, Out, InFragment, OutFragment> {
16
+ private readonly _originalContentPropertyName: string;
17
+ private readonly _itemPropertyName: string;
18
+
19
+ public constructor(options: EachPipelineStepSetsOptions<In, Out, InFragment, OutFragment>) {
20
+ super(options);
21
+ this._originalContentPropertyName = options.originalContentName || '$content';
22
+ if (this._originalContentPropertyName.trim().length === 0) {
23
+ this._originalContentPropertyName = '$content';
24
+ }
25
+ this._itemPropertyName = options.itemName || '$item';
26
+ if (this._itemPropertyName.trim().length === 0) {
27
+ this._itemPropertyName = '$item';
28
+ }
29
+ }
30
+
31
+ protected getOriginalContentPropertyName(): string {
32
+ return this._originalContentPropertyName;
33
+ }
34
+
35
+ /**
36
+ * override this method when want to use another variable name rather than "$item"
37
+ */
38
+ protected getItemPropertyName(): string {
39
+ return this._itemPropertyName;
40
+ }
41
+
42
+ public async perform(request: PipelineStepData<In>): Promise<PipelineStepData<Out>> {
43
+ return await this.performAndCatch(request, async (fragment) => {
44
+ if (fragment == null) {
45
+ // simply pass request as response
46
+ return request as unknown as PipelineStepData<Out>;
47
+ }
48
+ if (!Array.isArray(fragment)) {
49
+ // noinspection ExceptionCaughtLocallyJS
50
+ throw new UncatchableError(ERR_EACH_FRAGMENT_NOT_ANY_ARRAY, `Given in fragment[${JSON.stringify(fragment)}] is not an array, cannot be performed in for each step.`);
51
+ }
52
+ if (fragment.length === 0) {
53
+ // simply pass request as response
54
+ return request as unknown as PipelineStepData<Out>;
55
+ }
56
+ // should create request for each item
57
+ const $context = request.$context;
58
+ const content = request.content;
59
+ const semaphore = Symbol();
60
+ // sequential
61
+ const results = [];
62
+ for (const item of fragment) {
63
+ // build item content
64
+ const itemContent = {
65
+ [this.getOriginalContentPropertyName()]: content,
66
+ [this.getItemPropertyName()]: item,
67
+ $semaphore: semaphore
68
+ };
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ const result = await this.doPerform(itemContent as any, {content: itemContent as any, $context});
71
+ if (result === semaphore) {
72
+ break;
73
+ }
74
+ results.push(result);
75
+ }
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ return this.setToOutput(results as any, request);
78
+ });
79
+ }
80
+ }
@@ -0,0 +1,34 @@
1
+ import {PipelineStepData, PipelineStepPayload} from '@rainbow-o23/n1';
2
+ import {AbstractFragmentaryPipelineStep, FragmentaryPipelineStepOptions} from './abstract-fragmentary-pipeline-step';
3
+ import {Utils} from './utils';
4
+
5
+ export interface GetPropertyPipelineStepOptions extends FragmentaryPipelineStepOptions {
6
+ /** When obtaining hierarchical tree nodes, concatenate names by ".". */
7
+ propertyName: string;
8
+ }
9
+
10
+ /**
11
+ * get value from in fragment by given property name, recursive property names is supported.
12
+ * array index in property name is not supported.
13
+ */
14
+ export class GetPropertyPipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
15
+ extends AbstractFragmentaryPipelineStep<In, Out, InFragment, OutFragment> {
16
+ private readonly _propertyName: string;
17
+
18
+ public constructor(options: GetPropertyPipelineStepOptions) {
19
+ super(options);
20
+ this._propertyName = options.propertyName;
21
+ }
22
+
23
+ public getPropertyName(): string {
24
+ return this._propertyName;
25
+ }
26
+
27
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
+ protected async doPerform(data: InFragment, _request: PipelineStepData<In>): Promise<OutFragment> {
29
+ if (data != null) {
30
+ return Utils.getValue(data, this.getPropertyName());
31
+ }
32
+ return null;
33
+ }
34
+ }
@@ -0,0 +1,18 @@
1
+ export * from './types';
2
+ export * from './utils';
3
+
4
+ export * from './abstract-fragmentary-pipeline-step';
5
+
6
+ export * from './step-sets';
7
+ export * from './async-step-sets';
8
+ export * from './each-step-sets';
9
+ export * from './conditional-step-sets';
10
+ export * from './routes-step-sets';
11
+
12
+ export * from './snippet-step';
13
+ export * from './snowflake-step';
14
+ export * from './get-property-step';
15
+ export * from './delete-property-step';
16
+
17
+ export * from './ref-pipeline-step';
18
+ export * from './ref-step-step';