@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.
- package/.babelrc +11 -0
- package/.eslintrc +23 -0
- package/README.md +584 -0
- package/index.cjs +2283 -0
- package/index.d.ts +5 -0
- package/index.js +2229 -0
- package/lib/error-codes.d.ts +17 -0
- package/lib/http/fetch-step.d.ts +53 -0
- package/lib/http/index.d.ts +2 -0
- package/lib/http/types.d.ts +21 -0
- package/lib/step/abstract-fragmentary-pipeline-step.d.ts +44 -0
- package/lib/step/async-step-sets.d.ts +5 -0
- package/lib/step/conditional-step-sets.d.ts +23 -0
- package/lib/step/delete-property-step.d.ts +11 -0
- package/lib/step/each-step-sets.d.ts +14 -0
- package/lib/step/get-property-step.d.ts +11 -0
- package/lib/step/index.d.ts +14 -0
- package/lib/step/ref-pipeline-step.d.ts +15 -0
- package/lib/step/ref-step-step.d.ts +14 -0
- package/lib/step/routes-step-sets.d.ts +30 -0
- package/lib/step/snippet-step.d.ts +19 -0
- package/lib/step/snowflake-step.d.ts +6 -0
- package/lib/step/step-sets.d.ts +19 -0
- package/lib/step/types.d.ts +13 -0
- package/lib/step/utils.d.ts +23 -0
- package/lib/typeorm/abstract-datasource.d.ts +22 -0
- package/lib/typeorm/better-sqlite3-datasource.d.ts +7 -0
- package/lib/typeorm/datasource-manager.d.ts +25 -0
- package/lib/typeorm/index.d.ts +7 -0
- package/lib/typeorm/mssql-datasource.d.ts +6 -0
- package/lib/typeorm/mysql-datasource.d.ts +6 -0
- package/lib/typeorm/oracle-datasource.d.ts +6 -0
- package/lib/typeorm/pgsql-datasource.d.ts +6 -0
- package/lib/typeorm-step/abstract-typeorm-by-sql-step.d.ts +62 -0
- package/lib/typeorm-step/abstract-typeorm-load-by-sql-step.d.ts +10 -0
- package/lib/typeorm-step/abstract-typeorm-step.d.ts +30 -0
- package/lib/typeorm-step/index.d.ts +12 -0
- package/lib/typeorm-step/type-orm-transactional-step-sets.d.ts +22 -0
- package/lib/typeorm-step/typeorm-bulk-save-by-sql-step.d.ts +9 -0
- package/lib/typeorm-step/typeorm-by-snippet-step.d.ts +21 -0
- package/lib/typeorm-step/typeorm-load-entity-by-id-step.d.ts +12 -0
- package/lib/typeorm-step/typeorm-load-many-by-sql-step.d.ts +6 -0
- package/lib/typeorm-step/typeorm-load-one-by-sql-step.d.ts +6 -0
- package/lib/typeorm-step/typeorm-save-by-sql-step.d.ts +9 -0
- package/lib/typeorm-step/typeorm-save-entity-step.d.ts +32 -0
- package/lib/typeorm-step/types.d.ts +22 -0
- package/package.json +74 -0
- package/rollup.config.base.js +33 -0
- package/rollup.config.ci.js +3 -0
- package/rollup.config.js +3 -0
- package/src/index.ts +7 -0
- package/src/lib/error-codes.ts +18 -0
- package/src/lib/http/fetch-step.ts +290 -0
- package/src/lib/http/index.ts +3 -0
- package/src/lib/http/types.ts +33 -0
- package/src/lib/step/abstract-fragmentary-pipeline-step.ts +367 -0
- package/src/lib/step/async-step-sets.ts +14 -0
- package/src/lib/step/conditional-step-sets.ts +115 -0
- package/src/lib/step/delete-property-step.ts +33 -0
- package/src/lib/step/each-step-sets.ts +80 -0
- package/src/lib/step/get-property-step.ts +34 -0
- package/src/lib/step/index.ts +18 -0
- package/src/lib/step/ref-pipeline-step.ts +65 -0
- package/src/lib/step/ref-step-step.ts +59 -0
- package/src/lib/step/routes-step-sets.ts +147 -0
- package/src/lib/step/snippet-step.ts +80 -0
- package/src/lib/step/snowflake-step.ts +16 -0
- package/src/lib/step/step-sets.ts +99 -0
- package/src/lib/step/types.ts +23 -0
- package/src/lib/step/utils.ts +109 -0
- package/src/lib/typeorm/abstract-datasource.ts +58 -0
- package/src/lib/typeorm/better-sqlite3-datasource.ts +29 -0
- package/src/lib/typeorm/datasource-manager.ts +104 -0
- package/src/lib/typeorm/index.ts +9 -0
- package/src/lib/typeorm/mssql-datasource.ts +131 -0
- package/src/lib/typeorm/mysql-datasource.ts +35 -0
- package/src/lib/typeorm/oracle-datasource.ts +27 -0
- package/src/lib/typeorm/pgsql-datasource.ts +25 -0
- package/src/lib/typeorm-step/abstract-typeorm-by-sql-step.ts +556 -0
- package/src/lib/typeorm-step/abstract-typeorm-load-by-sql-step.ts +31 -0
- package/src/lib/typeorm-step/abstract-typeorm-step.ts +241 -0
- package/src/lib/typeorm-step/index.ts +17 -0
- package/src/lib/typeorm-step/type-orm-transactional-step-sets.ts +129 -0
- package/src/lib/typeorm-step/typeorm-bulk-save-by-sql-step.ts +29 -0
- package/src/lib/typeorm-step/typeorm-by-snippet-step.ts +83 -0
- package/src/lib/typeorm-step/typeorm-load-entity-by-id-step.ts +35 -0
- package/src/lib/typeorm-step/typeorm-load-many-by-sql-step.ts +13 -0
- package/src/lib/typeorm-step/typeorm-load-one-by-sql-step.ts +13 -0
- package/src/lib/typeorm-step/typeorm-save-by-sql-step.ts +24 -0
- package/src/lib/typeorm-step/typeorm-save-entity-step.ts +109 -0
- package/src/lib/typeorm-step/types.ts +25 -0
- package/test/step/snippet-step.test.ts +21 -0
- package/test/step/snowflake-step.test.ts +22 -0
- package/test/step/typeorm-by-sql-autonomous.test.ts +117 -0
- package/test/step/typeorm-by-sql-transactional.test.ts +215 -0
- package/test/step/typeorm-entity.test.ts +50 -0
- package/tsconfig.json +36 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Pipeline,
|
|
3
|
+
PipelineBuilder,
|
|
4
|
+
PipelineCode,
|
|
5
|
+
PipelineOptions,
|
|
6
|
+
PipelineRepository,
|
|
7
|
+
PipelineStepData,
|
|
8
|
+
PipelineStepOptions,
|
|
9
|
+
PipelineStepPayload,
|
|
10
|
+
UncatchableError
|
|
11
|
+
} from '@rainbow-o23/n1';
|
|
12
|
+
import {ERR_PIPELINE_REF_NOT_EMPTY, ERR_PIPELINE_REF_NOT_FOUND} from '../error-codes';
|
|
13
|
+
import {AbstractFragmentaryPipelineStep, FragmentaryPipelineStepOptions} from './abstract-fragmentary-pipeline-step';
|
|
14
|
+
|
|
15
|
+
export interface RefPipelinePipelineStepOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
16
|
+
extends FragmentaryPipelineStepOptions<In, Out, InFragment, OutFragment> {
|
|
17
|
+
code: PipelineCode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class RefPipelinePipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
21
|
+
extends AbstractFragmentaryPipelineStep<In, Out, InFragment, OutFragment> {
|
|
22
|
+
private readonly _code: PipelineCode;
|
|
23
|
+
private readonly _pipelineBuilder: PipelineBuilder;
|
|
24
|
+
|
|
25
|
+
public constructor(options: RefPipelinePipelineStepOptions<In, Out, InFragment, OutFragment>) {
|
|
26
|
+
super(options);
|
|
27
|
+
if (options.code == null || options.code.trim().length === 0) {
|
|
28
|
+
throw new UncatchableError(ERR_PIPELINE_REF_NOT_EMPTY, `Reference pipeline code cannot be empty.`);
|
|
29
|
+
}
|
|
30
|
+
this._code = options.code;
|
|
31
|
+
this._pipelineBuilder = {
|
|
32
|
+
create: async (options?: PipelineOptions): Promise<Pipeline> => {
|
|
33
|
+
const pipeline = await PipelineRepository.findPipeline(this.getCode(), options ?? this.buildPipelineOptions());
|
|
34
|
+
if (pipeline == null) {
|
|
35
|
+
throw new UncatchableError(ERR_PIPELINE_REF_NOT_FOUND, `Reference pipeline builder[${this.getCode()}] not found.`);
|
|
36
|
+
}
|
|
37
|
+
return pipeline;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public getCode(): PipelineCode {
|
|
43
|
+
return this._code;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected getPipelineBuilder(): PipelineBuilder {
|
|
47
|
+
return this._pipelineBuilder;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected buildPipelineOptions(): PipelineOptions {
|
|
51
|
+
return {config: this.getConfig(), logger: this.getLogger()};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected buildStepOptions(): Pick<PipelineStepOptions, 'config' | 'logger'> {
|
|
55
|
+
return {config: this.getConfig(), logger: this.getLogger()};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected async doPerform(data: InFragment, request: PipelineStepData<In>): Promise<OutFragment> {
|
|
59
|
+
const {$context: {traceId} = {}} = request;
|
|
60
|
+
const pipeline = await this.getPipelineBuilder().create(this.buildPipelineOptions());
|
|
61
|
+
const result = await pipeline.perform({payload: data, traceId});
|
|
62
|
+
// const result = await step.perform({content: data, $context: request.$context});
|
|
63
|
+
return result.payload;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PipelineRepository,
|
|
3
|
+
PipelineStep,
|
|
4
|
+
PipelineStepBuilder,
|
|
5
|
+
PipelineStepCode,
|
|
6
|
+
PipelineStepData,
|
|
7
|
+
PipelineStepOptions,
|
|
8
|
+
PipelineStepPayload,
|
|
9
|
+
UncatchableError
|
|
10
|
+
} from '@rainbow-o23/n1';
|
|
11
|
+
import {ERR_PIPELINE_STEP_REF_NOT_EMPTY, ERR_PIPELINE_STEP_REF_NOT_FOUND} from '../error-codes';
|
|
12
|
+
import {AbstractFragmentaryPipelineStep, FragmentaryPipelineStepOptions} from './abstract-fragmentary-pipeline-step';
|
|
13
|
+
|
|
14
|
+
export interface RefStepPipelineStepOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
15
|
+
extends FragmentaryPipelineStepOptions<In, Out, InFragment, OutFragment> {
|
|
16
|
+
code: PipelineStepCode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class RefStepPipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
20
|
+
extends AbstractFragmentaryPipelineStep<In, Out, InFragment, OutFragment> {
|
|
21
|
+
private readonly _code: PipelineStepCode;
|
|
22
|
+
private readonly _stepBuilder: PipelineStepBuilder;
|
|
23
|
+
|
|
24
|
+
public constructor(options: RefStepPipelineStepOptions<In, Out, InFragment, OutFragment>) {
|
|
25
|
+
super(options);
|
|
26
|
+
if (options.code == null || options.code.trim().length === 0) {
|
|
27
|
+
throw new UncatchableError(ERR_PIPELINE_STEP_REF_NOT_EMPTY, `Reference step code cannot be empty.`);
|
|
28
|
+
}
|
|
29
|
+
this._code = options.code;
|
|
30
|
+
this._stepBuilder = {
|
|
31
|
+
create: async (options?: PipelineStepOptions): Promise<PipelineStep> => {
|
|
32
|
+
const builder = await PipelineRepository.findStep(this.getCode());
|
|
33
|
+
if (builder == null) {
|
|
34
|
+
throw new UncatchableError(ERR_PIPELINE_STEP_REF_NOT_FOUND, `Reference step builder[${this.getCode()}] not found.`);
|
|
35
|
+
}
|
|
36
|
+
return builder.create(options);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public getCode(): PipelineStepCode {
|
|
42
|
+
return this._code;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected getStepBuilder(): PipelineStepBuilder {
|
|
46
|
+
return this._stepBuilder;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected buildStepOptions(): Pick<PipelineStepOptions, 'config' | 'logger'> {
|
|
50
|
+
return {config: this.getConfig(), logger: this.getLogger()};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected async doPerform(data: InFragment, request: PipelineStepData<In>): Promise<OutFragment> {
|
|
54
|
+
const builder = this.getStepBuilder();
|
|
55
|
+
const step = await builder.create(this.buildStepOptions());
|
|
56
|
+
const result = await step.perform({content: data, $context: request.$context});
|
|
57
|
+
return result.content;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PipelineStepBuilder,
|
|
3
|
+
PipelineStepData,
|
|
4
|
+
PipelineStepOptions,
|
|
5
|
+
PipelineStepPayload,
|
|
6
|
+
UncatchableError,
|
|
7
|
+
Undefinable
|
|
8
|
+
} from '@rainbow-o23/n1';
|
|
9
|
+
import {ERR_PIPELINE_STEP_CONDITIONAL_SNIPPET_NOT_EMPTY, ERR_PIPELINE_STEP_METHOD_NOT_SUPPORTED} from '../error-codes';
|
|
10
|
+
import {AbstractFragmentaryPipelineStep, FragmentaryPipelineStepOptions} from './abstract-fragmentary-pipeline-step';
|
|
11
|
+
import {
|
|
12
|
+
ConditionCheckFunc,
|
|
13
|
+
ConditionCheckWithInFragment,
|
|
14
|
+
ConditionCheckWithoutInFragment
|
|
15
|
+
} from './conditional-step-sets';
|
|
16
|
+
import {PipelineStepSets} from './step-sets';
|
|
17
|
+
import {ScriptFuncOrBody} from './types';
|
|
18
|
+
import {Utils} from './utils';
|
|
19
|
+
|
|
20
|
+
export interface RoutesConditionalStepOptions<InFragment> {
|
|
21
|
+
check: ScriptFuncOrBody<ConditionCheckFunc<InFragment>>,
|
|
22
|
+
steps?: Array<PipelineStepBuilder>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RoutesPipelineStepSetsOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
26
|
+
extends FragmentaryPipelineStepOptions<In, Out, InFragment, OutFragment> {
|
|
27
|
+
conditionalSteps: Array<RoutesConditionalStepOptions<InFragment>>;
|
|
28
|
+
otherwiseSteps?: Array<PipelineStepBuilder>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RoutesConditionalStep<InFragment> {
|
|
32
|
+
check: ConditionCheckFunc<InFragment>;
|
|
33
|
+
steps: Array<PipelineStepBuilder>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* pipeline steps to execute routes of steps, only one route will be executed
|
|
38
|
+
*/
|
|
39
|
+
export class RoutesPipelineStepSets<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
40
|
+
extends AbstractFragmentaryPipelineStep<In, Out, InFragment, OutFragment> {
|
|
41
|
+
private readonly _conditionalStepBuilders: Array<RoutesConditionalStep<InFragment>>;
|
|
42
|
+
private readonly _otherwiseStepBuilders: Undefinable<Array<PipelineStepBuilder>>;
|
|
43
|
+
|
|
44
|
+
constructor(options: RoutesPipelineStepSetsOptions<In, Out, InFragment, OutFragment>) {
|
|
45
|
+
super(options);
|
|
46
|
+
this._conditionalStepBuilders = (options.conditionalSteps || []).map(({check, steps}, stepIndex) => {
|
|
47
|
+
return {
|
|
48
|
+
check: Utils.createAsyncFunction(check, {
|
|
49
|
+
createDefault: (): never => {
|
|
50
|
+
throw new UncatchableError(ERR_PIPELINE_STEP_CONDITIONAL_SNIPPET_NOT_EMPTY, 'Cannot create perform func on empty conditional snippet.');
|
|
51
|
+
},
|
|
52
|
+
getVariableNames: () => this.generateVariableNames(),
|
|
53
|
+
error: (e: Error) => {
|
|
54
|
+
this.getLogger().error(`Failed on create function for route check[${stepIndex + 1}], snippet is [${check}].`);
|
|
55
|
+
throw e;
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
58
|
+
steps
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
this._otherwiseStepBuilders = options.otherwiseSteps;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected getConditionalStepBuilders(): Array<RoutesConditionalStep<InFragment>> {
|
|
65
|
+
return this._conditionalStepBuilders;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected getOtherwiseStepBuilders(): Undefinable<Array<PipelineStepBuilder>> {
|
|
69
|
+
return this._otherwiseStepBuilders;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected buildStepOptions(): Pick<PipelineStepOptions, 'config' | 'logger'> {
|
|
73
|
+
return {config: this.getConfig(), logger: this.getLogger()};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
protected generateVariableNames(): Array<string> {
|
|
77
|
+
return [
|
|
78
|
+
this.isInFragmentIgnored() ? null : this.getInFragmentVariableName(),
|
|
79
|
+
...this.getHelpersVariableNames()
|
|
80
|
+
].filter(x => x != null);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
84
|
+
protected async check(func: ConditionCheckFunc<InFragment>, data: InFragment, _request: PipelineStepData<In>): Promise<boolean> {
|
|
85
|
+
const $helpers = this.getHelpers();
|
|
86
|
+
if (this.isInFragmentIgnored()) {
|
|
87
|
+
return await (func as ConditionCheckWithoutInFragment)($helpers, $helpers);
|
|
88
|
+
} else {
|
|
89
|
+
return await (func as ConditionCheckWithInFragment<InFragment>)(data, $helpers, $helpers);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* not used, throw error
|
|
95
|
+
*/
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
97
|
+
protected async doPerform(data: InFragment, request: PipelineStepData<In>): Promise<OutFragment> {
|
|
98
|
+
throw new UncatchableError(ERR_PIPELINE_STEP_METHOD_NOT_SUPPORTED, `Method[${RoutesPipelineStepSets.name}.doPerform] not supported.`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public async perform(request: PipelineStepData<In>): Promise<PipelineStepData<Out>> {
|
|
102
|
+
return await this.performAndCatch(request, async (fragment) => {
|
|
103
|
+
const conditionalStepBuilders = this.getConditionalStepBuilders();
|
|
104
|
+
if (conditionalStepBuilders != null && conditionalStepBuilders.length !== 0) {
|
|
105
|
+
for (const conditionalStepBuilder of conditionalStepBuilders) {
|
|
106
|
+
const {check, steps} = conditionalStepBuilder;
|
|
107
|
+
const checked = await this.check(check, fragment, request);
|
|
108
|
+
if (checked) {
|
|
109
|
+
const sets = new PipelineStepSets({
|
|
110
|
+
...this.buildStepOptions(), name: this.getName(), steps
|
|
111
|
+
});
|
|
112
|
+
const result = await sets.perform(request);
|
|
113
|
+
return this.setToOutput(result.content, request);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// noinspection DuplicatedCode
|
|
118
|
+
const otherwiseStepBuilders = this.getOtherwiseStepBuilders();
|
|
119
|
+
if (otherwiseStepBuilders != null) {
|
|
120
|
+
// no conditional step performed
|
|
121
|
+
const sets = new PipelineStepSets({
|
|
122
|
+
...this.buildStepOptions(), name: this.getName(), steps: otherwiseStepBuilders
|
|
123
|
+
});
|
|
124
|
+
const result = await sets.perform(request);
|
|
125
|
+
return this.setToOutput(result.content, request);
|
|
126
|
+
} else {
|
|
127
|
+
// otherwise route not declared
|
|
128
|
+
return request as unknown as PipelineStepData<Out>;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* is request step data ignored to snippet function.
|
|
135
|
+
* default returns false
|
|
136
|
+
*/
|
|
137
|
+
protected isInFragmentIgnored(): boolean {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* override this method when want to use another variable name rather than "$factor"
|
|
143
|
+
*/
|
|
144
|
+
protected getInFragmentVariableName(): string {
|
|
145
|
+
return '$factor';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {PipelineStepData, PipelineStepHelpers, PipelineStepPayload, UncatchableError} from '@rainbow-o23/n1';
|
|
2
|
+
import {ERR_PIPELINE_STEP_SNIPPET_NOT_EMPTY} from '../error-codes';
|
|
3
|
+
import {AbstractFragmentaryPipelineStep, FragmentaryPipelineStepOptions} from './abstract-fragmentary-pipeline-step';
|
|
4
|
+
import {ScriptFuncOrBody} from './types';
|
|
5
|
+
import {Utils} from './utils';
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
export type PerformWithInFragment<InFragment, OutFragment> = ($factor: InFragment, $helpers: PipelineStepHelpers, $: PipelineStepHelpers) => Promise<OutFragment>;
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
export type PerformWithoutInFragment<OutFragment> = ($helpers: PipelineStepHelpers, $: PipelineStepHelpers) => Promise<OutFragment>;
|
|
11
|
+
export type PerformFunc<InFragment, OutFragment> =
|
|
12
|
+
PerformWithInFragment<InFragment, OutFragment> | PerformWithoutInFragment<OutFragment>;
|
|
13
|
+
|
|
14
|
+
export interface SnippetPipelineStepOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
15
|
+
extends FragmentaryPipelineStepOptions<In, Out, InFragment, OutFragment> {
|
|
16
|
+
snippet: ScriptFuncOrBody<PerformFunc<InFragment, OutFragment>>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Please note this step use given snippet to create dynamic function,
|
|
21
|
+
* which means THERE MIGHT BE SECURITY CONCERN.
|
|
22
|
+
*
|
|
23
|
+
* await is supported in snippet.
|
|
24
|
+
*/
|
|
25
|
+
export class SnippetPipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
26
|
+
extends AbstractFragmentaryPipelineStep<In, Out, InFragment, OutFragment> {
|
|
27
|
+
private readonly _snippet: ScriptFuncOrBody<PerformFunc<InFragment, OutFragment>>;
|
|
28
|
+
private readonly _func: PerformFunc<InFragment, OutFragment>;
|
|
29
|
+
|
|
30
|
+
public constructor(options: SnippetPipelineStepOptions<In, Out, InFragment, OutFragment>) {
|
|
31
|
+
super(options);
|
|
32
|
+
this._snippet = options.snippet;
|
|
33
|
+
this._func = Utils.createAsyncFunction(this.getSnippet(), {
|
|
34
|
+
createDefault: (): never => {
|
|
35
|
+
throw new UncatchableError(ERR_PIPELINE_STEP_SNIPPET_NOT_EMPTY, 'Cannot create perform func on empty snippet.');
|
|
36
|
+
},
|
|
37
|
+
getVariableNames: () => this.generateVariableNames(),
|
|
38
|
+
error: (e: Error) => {
|
|
39
|
+
this.getLogger().error(`Failed on create function for snippet[${this.getSnippet()}].`);
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public getSnippet(): ScriptFuncOrBody<PerformFunc<InFragment, OutFragment>> {
|
|
46
|
+
return this._snippet;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected generateVariableNames(): Array<string> {
|
|
50
|
+
return [
|
|
51
|
+
this.isInFragmentIgnored() ? null : this.getInFragmentVariableName(),
|
|
52
|
+
...this.getHelpersVariableNames()
|
|
53
|
+
].filter(x => x != null);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
57
|
+
protected async doPerform(data: InFragment, _request: PipelineStepData<In>): Promise<OutFragment> {
|
|
58
|
+
const $helpers = this.getHelpers();
|
|
59
|
+
if (this.isInFragmentIgnored()) {
|
|
60
|
+
return await (this._func as PerformWithoutInFragment<OutFragment>)($helpers, $helpers);
|
|
61
|
+
} else {
|
|
62
|
+
return await (this._func as PerformWithInFragment<InFragment, OutFragment>)(data, $helpers, $helpers);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* is request step data ignored to snippet function.
|
|
68
|
+
* default returns false
|
|
69
|
+
*/
|
|
70
|
+
protected isInFragmentIgnored(): boolean {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* override this method when want to use another variable name rather than "$factor"
|
|
76
|
+
*/
|
|
77
|
+
protected getInFragmentVariableName(): string {
|
|
78
|
+
return '$factor';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {PipelineStepData, PipelineStepPayload} from '@rainbow-o23/n1';
|
|
2
|
+
import {Snowflake} from '@theinternetfolks/snowflake';
|
|
3
|
+
import {AbstractFragmentaryPipelineStep} from './abstract-fragmentary-pipeline-step';
|
|
4
|
+
|
|
5
|
+
export type SnowflakeId = string;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* create a snowflake id as out fragment
|
|
9
|
+
*/
|
|
10
|
+
export class SnowflakePipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In>
|
|
11
|
+
extends AbstractFragmentaryPipelineStep<In, Out, InFragment, SnowflakeId> {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
13
|
+
protected async doPerform(_data: InFragment, _request: PipelineStepData<In>): Promise<SnowflakeId> {
|
|
14
|
+
return Snowflake.generate();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PipelineStep,
|
|
3
|
+
PipelineStepBuilder,
|
|
4
|
+
PipelineStepData,
|
|
5
|
+
PipelineStepOptions,
|
|
6
|
+
PipelineStepPayload
|
|
7
|
+
} from '@rainbow-o23/n1';
|
|
8
|
+
import {AbstractFragmentaryPipelineStep, FragmentaryPipelineStepOptions} from './abstract-fragmentary-pipeline-step';
|
|
9
|
+
import {Utils} from './utils';
|
|
10
|
+
|
|
11
|
+
export interface PipelineStepSetsContext {
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PipelineStepSetsOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
15
|
+
extends FragmentaryPipelineStepOptions<In, Out, InFragment, OutFragment> {
|
|
16
|
+
steps: Array<PipelineStepBuilder>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* pipeline steps to execute sets of steps. it will build a context for sub steps.
|
|
21
|
+
*/
|
|
22
|
+
export class PipelineStepSets<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
|
|
23
|
+
extends AbstractFragmentaryPipelineStep<In, Out, InFragment, OutFragment> {
|
|
24
|
+
private readonly _stepBuilders: Array<PipelineStepBuilder>;
|
|
25
|
+
|
|
26
|
+
// noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected
|
|
27
|
+
public constructor(options: PipelineStepSetsOptions<In, Out, InFragment, OutFragment>) {
|
|
28
|
+
super(options);
|
|
29
|
+
this._stepBuilders = options.steps;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected getStepBuilders(): Array<PipelineStepBuilder> {
|
|
33
|
+
return this._stepBuilders ?? [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected buildStepOptions(): Pick<PipelineStepOptions, 'config' | 'logger'> {
|
|
37
|
+
return {config: this.getConfig(), logger: this.getLogger()};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* create all steps
|
|
42
|
+
*/
|
|
43
|
+
public async createSteps(): Promise<Array<PipelineStep>> {
|
|
44
|
+
const options = this.buildStepOptions();
|
|
45
|
+
return await Promise.all(this.getStepBuilders().map(async builder => await builder.create(options)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected inheritContext(request: PipelineStepData<In>): PipelineStepSetsContext {
|
|
49
|
+
const context = request.$context;
|
|
50
|
+
if (context == null) {
|
|
51
|
+
return {};
|
|
52
|
+
} else {
|
|
53
|
+
return Utils.clone(context);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* default do nothing, return given inherited context directly
|
|
59
|
+
*/
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
61
|
+
protected async attachMineToInternalContext(inheritedContext: PipelineStepSetsContext, _request: PipelineStepData<In>): Promise<PipelineStepSetsContext> {
|
|
62
|
+
return inheritedContext;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected async createInternalContext<Ctx extends PipelineStepSetsContext>(request: PipelineStepData<In>): Promise<Ctx> {
|
|
66
|
+
return await this.attachMineToInternalContext(this.inheritContext(request), request) as Ctx;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* to create an internal context which should be mounted on the step data
|
|
71
|
+
*/
|
|
72
|
+
protected async performWithContext(
|
|
73
|
+
request: PipelineStepData<In>,
|
|
74
|
+
run: (request: PipelineStepData<In>, context: PipelineStepSetsContext) => Promise<OutFragment>): Promise<OutFragment> {
|
|
75
|
+
const context = await this.createInternalContext(request);
|
|
76
|
+
return await run(request, context);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected async doPerform(data: InFragment, request: PipelineStepData<In>): Promise<OutFragment> {
|
|
80
|
+
return await this.performWithContext(
|
|
81
|
+
request, async (request: PipelineStepData<In>, context: PipelineStepSetsContext): Promise<OutFragment> => {
|
|
82
|
+
const {$context: {traceId} = {}} = request;
|
|
83
|
+
const steps = await this.createSteps();
|
|
84
|
+
const response = await steps.reduce(async (promise, step) => {
|
|
85
|
+
const request = await promise;
|
|
86
|
+
return await this.measurePerformance(traceId, 'STEP')
|
|
87
|
+
.execute(async () => {
|
|
88
|
+
this.traceStepOut(traceId, step, request);
|
|
89
|
+
const response = await step.perform({...request, $context: {...context, traceId}});
|
|
90
|
+
this.traceStepOut(traceId, step, response);
|
|
91
|
+
// if no response returned, keep using request for next
|
|
92
|
+
return this.returnOrContinueOrClear(request, response);
|
|
93
|
+
});
|
|
94
|
+
// build request for first step
|
|
95
|
+
}, Promise.resolve({content: data, $context: request.$context}));
|
|
96
|
+
return response.content;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CatchableError,
|
|
3
|
+
ExposedUncatchableError,
|
|
4
|
+
PipelineStepData,
|
|
5
|
+
PipelineStepHelpers,
|
|
6
|
+
UncatchableError
|
|
7
|
+
} from '@rainbow-o23/n1';
|
|
8
|
+
|
|
9
|
+
export type ScriptFunctionBody = string;
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
11
|
+
export type ScriptFuncOrBody<F = Function> = F | ScriptFunctionBody;
|
|
12
|
+
|
|
13
|
+
export interface ErrorHandleOptions<In, InFragment, E extends Error = Error> {
|
|
14
|
+
$code: string;
|
|
15
|
+
$error: E;
|
|
16
|
+
$factor: InFragment;
|
|
17
|
+
$request: PipelineStepData<In>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type HandleCatchableError<In, InFragment, OutFragment> = ($options: ErrorHandleOptions<In, InFragment, CatchableError>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers) => Promise<OutFragment> | never;
|
|
21
|
+
export type HandleUncatchableError<In, InFragment, OutFragment> = ($options: ErrorHandleOptions<In, InFragment, UncatchableError>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers) => Promise<OutFragment> | never;
|
|
22
|
+
export type HandleExposedUncatchableError<In, InFragment, OutFragment> = ($options: ErrorHandleOptions<In, InFragment, ExposedUncatchableError>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers) => Promise<OutFragment> | never;
|
|
23
|
+
export type HandleAnyError<In, InFragment, OutFragment> = ($options: ErrorHandleOptions<In, InFragment>, $helpers: PipelineStepHelpers, $: PipelineStepHelpers) => Promise<OutFragment> | never;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {Nullable, Undefinable} from '@rainbow-o23/n1';
|
|
2
|
+
import {ScriptFuncOrBody} from './types';
|
|
3
|
+
|
|
4
|
+
// get async function constructor, to create the dynamic function
|
|
5
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {
|
|
6
|
+
// nothing, since this purpose is get the constructor, body is not concerned
|
|
7
|
+
}).constructor;
|
|
8
|
+
|
|
9
|
+
export class Utils {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
11
|
+
public static createFunction<F = Function>(snippet: ScriptFuncOrBody<F>, creators: {
|
|
12
|
+
createDefault: () => Undefinable<F> | never;
|
|
13
|
+
getVariableNames: () => Array<string>;
|
|
14
|
+
async?: true;
|
|
15
|
+
error: (e: Error) => never;
|
|
16
|
+
}): F {
|
|
17
|
+
try {
|
|
18
|
+
if (snippet == null || (typeof snippet === 'string' && snippet.trim().length === 0)) {
|
|
19
|
+
return creators.createDefault();
|
|
20
|
+
} else if (typeof snippet === 'string') {
|
|
21
|
+
const F = creators.async ? AsyncFunction : Function;
|
|
22
|
+
return new F(...creators.getVariableNames(), snippet) as F;
|
|
23
|
+
} else {
|
|
24
|
+
return snippet;
|
|
25
|
+
}
|
|
26
|
+
} catch (e) {
|
|
27
|
+
creators.error(e);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
32
|
+
public static createSyncFunction<F = Function>(snippet: ScriptFuncOrBody<F>, creators: {
|
|
33
|
+
createDefault: () => Undefinable<F> | never;
|
|
34
|
+
getVariableNames: () => Array<string>;
|
|
35
|
+
error: (e: Error) => never;
|
|
36
|
+
}): F {
|
|
37
|
+
return Utils.createFunction(snippet, {...creators});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
41
|
+
public static createAsyncFunction<F = Function>(snippet: ScriptFuncOrBody<F>, creators: {
|
|
42
|
+
createDefault: () => Undefinable<F> | never;
|
|
43
|
+
getVariableNames: () => Array<string>;
|
|
44
|
+
error: (e: Error) => never;
|
|
45
|
+
}): F {
|
|
46
|
+
return Utils.createFunction(snippet, {...creators, async: true});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
public static isPrimitive(value: any): value is string | number | boolean | symbol | bigint {
|
|
51
|
+
return value != null && ['string', 'number', 'boolean', 'symbol', 'bigint'].includes(typeof value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
public static getValue<V>(model: any, propertyPath: string): Nullable<V> {
|
|
56
|
+
if (model == null) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (propertyPath == null || propertyPath.trim() === '.' || propertyPath.trim() === '.') {
|
|
60
|
+
// returns parent itself if property points me
|
|
61
|
+
return model;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const segments = propertyPath.split('.');
|
|
65
|
+
|
|
66
|
+
return segments.reduce((fromModel, segment) => {
|
|
67
|
+
if (fromModel == null) {
|
|
68
|
+
// cannot know what the accurate type of parent, return null anyway.
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (Utils.isPrimitive(fromModel)) {
|
|
72
|
+
// cannot get property from primitive value, raise exception here
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (Array.isArray(fromModel)) {
|
|
77
|
+
// get values from every item and merge into one array
|
|
78
|
+
return fromModel.map(item => {
|
|
79
|
+
if (item == null) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (Utils.isPrimitive(item)) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return item[segment];
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
// get value from model directly
|
|
89
|
+
return fromModel[segment];
|
|
90
|
+
}
|
|
91
|
+
}, model);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
+
public static clone(obj: any): any {
|
|
96
|
+
if (obj == null) {
|
|
97
|
+
return obj;
|
|
98
|
+
} else if (Array.isArray(obj)) {
|
|
99
|
+
return obj.map(item => Utils.clone(item));
|
|
100
|
+
} else if (typeof obj === 'object') {
|
|
101
|
+
return Object.keys(obj).reduce((cloned: object, key: string) => {
|
|
102
|
+
cloned[key] = Utils.clone(obj[key]);
|
|
103
|
+
return cloned;
|
|
104
|
+
}, {});
|
|
105
|
+
} else {
|
|
106
|
+
return obj;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {Config} from '@rainbow-o23/n1';
|
|
2
|
+
import {DataSource, DataSourceOptions} from 'typeorm';
|
|
3
|
+
|
|
4
|
+
export interface TypeOrmDataSource {
|
|
5
|
+
getName(): string;
|
|
6
|
+
|
|
7
|
+
shouldKeptOnGlobal(): boolean;
|
|
8
|
+
|
|
9
|
+
getDataSource(): DataSource;
|
|
10
|
+
|
|
11
|
+
initialize(config: Config, entities: DataSourceOptions['entities']): Promise<DataSource>;
|
|
12
|
+
|
|
13
|
+
destroy(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export abstract class AbstractTypeOrmDataSource<O extends DataSourceOptions> implements TypeOrmDataSource {
|
|
17
|
+
private _initialized = false;
|
|
18
|
+
private _dataSource: DataSource;
|
|
19
|
+
|
|
20
|
+
// noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected
|
|
21
|
+
public constructor(private readonly _name: string) {
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public getName(): string {
|
|
25
|
+
return this._name;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public isInitialized(): boolean {
|
|
29
|
+
return this._initialized;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public getDataSource(): DataSource {
|
|
33
|
+
return this._dataSource;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* default true
|
|
38
|
+
*/
|
|
39
|
+
public shouldKeptOnGlobal(): boolean {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected abstract createOptions(config: Config): O;
|
|
44
|
+
|
|
45
|
+
public async initialize(config: Config, entities: DataSourceOptions['entities']): Promise<DataSource> {
|
|
46
|
+
if (this.isInitialized()) {
|
|
47
|
+
return this._dataSource;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this._dataSource = new DataSource({...this.createOptions(config), entities});
|
|
51
|
+
this._dataSource = await this._dataSource.initialize();
|
|
52
|
+
return this._dataSource;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public async destroy(): Promise<void> {
|
|
56
|
+
return await this._dataSource.destroy();
|
|
57
|
+
}
|
|
58
|
+
}
|