@rainbow-o23/n1 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 +233 -0
- package/index.cjs +760 -0
- package/index.d.ts +3 -0
- package/index.js +732 -0
- package/lib/pipeline/envs.d.ts +1 -0
- package/lib/pipeline/index.d.ts +6 -0
- package/lib/pipeline/pipeline-execution.d.ts +38 -0
- package/lib/pipeline/pipeline-step.d.ts +41 -0
- package/lib/pipeline/pipeline.d.ts +47 -0
- package/lib/pipeline/step-helpers-utils.d.ts +41 -0
- package/lib/pipeline/step-helpers.d.ts +31 -0
- package/lib/repo/index.d.ts +1 -0
- package/lib/repo/pipeline-repository.d.ts +11 -0
- package/lib/utils/config.d.ts +16 -0
- package/lib/utils/error.d.ts +21 -0
- package/lib/utils/index.d.ts +4 -0
- package/lib/utils/logger.d.ts +50 -0
- package/lib/utils/types.d.ts +7 -0
- package/package.json +41 -0
- package/rollup.config.base.js +30 -0
- package/rollup.config.ci.js +3 -0
- package/rollup.config.js +3 -0
- package/src/index.ts +4 -0
- package/src/lib/pipeline/envs.ts +20 -0
- package/src/lib/pipeline/index.ts +7 -0
- package/src/lib/pipeline/pipeline-execution.ts +137 -0
- package/src/lib/pipeline/pipeline-step.ts +79 -0
- package/src/lib/pipeline/pipeline.ts +148 -0
- package/src/lib/pipeline/step-helpers-utils.ts +143 -0
- package/src/lib/pipeline/step-helpers.ts +77 -0
- package/src/lib/repo/index.ts +1 -0
- package/src/lib/repo/pipeline-repository.ts +53 -0
- package/src/lib/utils/config.ts +110 -0
- package/src/lib/utils/error.ts +41 -0
- package/src/lib/utils/index.ts +6 -0
- package/src/lib/utils/logger.ts +292 -0
- package/src/lib/utils/types.ts +11 -0
- package/tsconfig.json +36 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {nanoid} from 'nanoid';
|
|
2
|
+
import {Config, Logger} from '../utils';
|
|
3
|
+
import {AbstractPipelineExecution, PipelineExecutionOptions} from './pipeline-execution';
|
|
4
|
+
import {
|
|
5
|
+
DefaultPipelineStepBuilder,
|
|
6
|
+
PipelineStep,
|
|
7
|
+
PipelineStepBuilder,
|
|
8
|
+
PipelineStepData,
|
|
9
|
+
PipelineStepOptions,
|
|
10
|
+
PipelineStepType
|
|
11
|
+
} from './pipeline-step';
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
export type PipelineRequestPayload = any;
|
|
15
|
+
|
|
16
|
+
export interface PipelineRequest<C = PipelineRequestPayload> {
|
|
17
|
+
payload: C;
|
|
18
|
+
traceId?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
export type PipelineResponsePayload = any;
|
|
23
|
+
|
|
24
|
+
export interface PipelineResponse<C = PipelineResponsePayload> {
|
|
25
|
+
payload: C;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type PipelineCode = string;
|
|
29
|
+
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
export interface Pipeline<In = any, Out = any> {
|
|
32
|
+
/**
|
|
33
|
+
* code should be unique globally
|
|
34
|
+
*/
|
|
35
|
+
getCode(): PipelineCode;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* perform pipeline
|
|
39
|
+
*/
|
|
40
|
+
perform(request: PipelineRequest<In>): Promise<PipelineResponse<Out>>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface PipelineOptions extends PipelineExecutionOptions {
|
|
44
|
+
config?: Config;
|
|
45
|
+
logger?: Logger;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
49
|
+
export interface PipelineType<P = Pipeline> extends Function {
|
|
50
|
+
new(options?: PipelineOptions): P;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
+
export abstract class AbstractPipeline<In = any, Out = any> extends AbstractPipelineExecution implements Pipeline<In, Out> {
|
|
55
|
+
// noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected
|
|
56
|
+
public constructor(options?: PipelineOptions) {
|
|
57
|
+
super(options);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public abstract getCode(): PipelineCode;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* get type of steps
|
|
64
|
+
*/
|
|
65
|
+
protected abstract getStepBuilders(): Array<PipelineStepBuilder>;
|
|
66
|
+
|
|
67
|
+
protected buildStepOptions(): Pick<PipelineStepOptions, 'config' | 'logger'> {
|
|
68
|
+
return {config: this._config, logger: this._logger};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* create all steps
|
|
73
|
+
*/
|
|
74
|
+
public async createSteps(): Promise<Array<PipelineStep>> {
|
|
75
|
+
const options = this.buildStepOptions();
|
|
76
|
+
return await Promise.all(this.getStepBuilders().map(async builder => await builder.create(options)));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public convertRequestToPipelineData<I, FirstStepIn>(request: PipelineRequest<I>): PipelineStepData<FirstStepIn> {
|
|
80
|
+
return {content: request.payload as unknown as FirstStepIn};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public convertPipelineDataToResponse<LastStepOut, O>(result: PipelineStepData<LastStepOut>): PipelineResponse<O> {
|
|
84
|
+
return {payload: result.content as unknown as O};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected createTraceId(request: PipelineRequest<In>): string {
|
|
88
|
+
const {traceId} = request;
|
|
89
|
+
if (traceId == null || traceId.trim().length === 0) {
|
|
90
|
+
return nanoid(16);
|
|
91
|
+
} else {
|
|
92
|
+
return traceId;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* perform pipeline.
|
|
98
|
+
* - first step use request as input,
|
|
99
|
+
* - other steps use the result of previous step,
|
|
100
|
+
* - use last step's result as response.
|
|
101
|
+
*/
|
|
102
|
+
public async perform(request: PipelineRequest<In>): Promise<PipelineResponse<Out>> {
|
|
103
|
+
const traceId = this.createTraceId(request);
|
|
104
|
+
const response = await this.measurePerformance(traceId, 'PIPELINE')
|
|
105
|
+
.execute(async () => {
|
|
106
|
+
this.traceRequest(traceId, request);
|
|
107
|
+
const steps = await this.createSteps();
|
|
108
|
+
const data = await steps.reduce(async (promise, step) => {
|
|
109
|
+
const request = await promise;
|
|
110
|
+
return await this.measurePerformance(traceId, 'STEP')
|
|
111
|
+
.execute(async () => {
|
|
112
|
+
this.traceStepIn(traceId, step, request);
|
|
113
|
+
const response = await step.perform({
|
|
114
|
+
...request, $context: {...request.$context, traceId}
|
|
115
|
+
});
|
|
116
|
+
this.traceStepOut(traceId, step, response);
|
|
117
|
+
// if no response returned, keep using request for next
|
|
118
|
+
return this.returnOrContinueOrClear(request, response);
|
|
119
|
+
});
|
|
120
|
+
}, Promise.resolve(this.convertRequestToPipelineData(request)));
|
|
121
|
+
return this.traceResponse(traceId, this.convertPipelineDataToResponse(data));
|
|
122
|
+
});
|
|
123
|
+
return response as PipelineResponse<Out>;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
128
|
+
export abstract class AbstractStaticPipeline<In = any, Out = any> extends AbstractPipeline<In, Out> {
|
|
129
|
+
protected abstract getStepTypes(): Array<PipelineStepType>;
|
|
130
|
+
|
|
131
|
+
protected getStepBuilders(): Array<PipelineStepBuilder> {
|
|
132
|
+
return this.getStepTypes().map(type => new DefaultPipelineStepBuilder(type));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface PipelineBuilder {
|
|
137
|
+
create(options?: PipelineOptions): Promise<Pipeline>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// noinspection JSUnusedGlobalSymbols
|
|
141
|
+
export class DefaultPipelineBuilder implements PipelineBuilder {
|
|
142
|
+
public constructor(protected readonly pipeline: PipelineType) {
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public async create(options?: PipelineOptions): Promise<Pipeline> {
|
|
146
|
+
return new this.pipeline(options);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {customAlphabet, nanoid} from 'nanoid';
|
|
2
|
+
import {CatchableError, ERR_TRIM_NON_STRING, ExposedUncatchableError, UncatchableError} from '../utils';
|
|
3
|
+
import {PipelineStepHelpers} from './index';
|
|
4
|
+
|
|
5
|
+
export interface PipelineStepErrorOptions {
|
|
6
|
+
// exactly same as http status
|
|
7
|
+
status: number;
|
|
8
|
+
code: string;
|
|
9
|
+
reason: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PipelineStepFileOptions {
|
|
13
|
+
name?: string;
|
|
14
|
+
type?: string;
|
|
15
|
+
content: string | Buffer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const PIPELINE_STEP_FILE_SYMBOL = Symbol();
|
|
19
|
+
export const PIPELINE_STEP_RETURN_NULL = Symbol();
|
|
20
|
+
|
|
21
|
+
export interface PipelineStepFile {
|
|
22
|
+
$file: typeof PIPELINE_STEP_FILE_SYMBOL;
|
|
23
|
+
name?: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
content: Buffer;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class StepHelpersUtils {
|
|
29
|
+
private static asciiNanoId = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_', 32);
|
|
30
|
+
private static OBJECT_PROTOTYPE = Object.prototype;
|
|
31
|
+
|
|
32
|
+
private constructor() {
|
|
33
|
+
// avoid extend
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// nano id
|
|
37
|
+
public static $nano(size?: number): string {
|
|
38
|
+
return nanoid(size);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public static $ascii(size?: number): string {
|
|
42
|
+
return StepHelpersUtils.asciiNanoId(size);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// error
|
|
46
|
+
public static createCatchableError = (options: Omit<PipelineStepErrorOptions, 'status'>): never => {
|
|
47
|
+
throw new CatchableError(options.code, options.reason);
|
|
48
|
+
};
|
|
49
|
+
public static createExposedUncatchableError = (options: PipelineStepErrorOptions): never => {
|
|
50
|
+
throw new ExposedUncatchableError(options.status, options.code, options.reason);
|
|
51
|
+
};
|
|
52
|
+
public static createUncatchableError = (options: Omit<PipelineStepErrorOptions, 'status'>): never => {
|
|
53
|
+
throw new UncatchableError(options.code, options.reason);
|
|
54
|
+
};
|
|
55
|
+
public static readonly $errors: PipelineStepHelpers['$errors'] = {
|
|
56
|
+
catchable: StepHelpersUtils.createCatchableError,
|
|
57
|
+
exposed: StepHelpersUtils.createExposedUncatchableError,
|
|
58
|
+
uncatchable: StepHelpersUtils.createUncatchableError
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// file
|
|
62
|
+
public static createFile(options: PipelineStepFileOptions): PipelineStepFile {
|
|
63
|
+
return {
|
|
64
|
+
$file: PIPELINE_STEP_FILE_SYMBOL,
|
|
65
|
+
name: options.name, type: options.type,
|
|
66
|
+
content: typeof options.content === 'string' ? Buffer.from(options.content) : options.content
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// semaphore
|
|
71
|
+
public static $clearContextData(): typeof PIPELINE_STEP_RETURN_NULL {
|
|
72
|
+
return PIPELINE_STEP_RETURN_NULL;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// utils
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
public static isPrototype(value: any): boolean {
|
|
78
|
+
const Ctor = value && value.constructor;
|
|
79
|
+
const proto = (typeof Ctor === 'function' && Ctor.prototype) || StepHelpersUtils.OBJECT_PROTOTYPE;
|
|
80
|
+
|
|
81
|
+
return value === proto;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
public static isLength(value: any): boolean {
|
|
86
|
+
return typeof value === 'number' && value > -1 && value % 1 === 0 && value <= Number.MAX_SAFE_INTEGER;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
public static isArrayLike(value: any): boolean {
|
|
91
|
+
return value != null && typeof value !== 'function' && StepHelpersUtils.isLength(value.length);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
+
public static isEmpty(value: any): boolean {
|
|
96
|
+
if (value == null) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (StepHelpersUtils.isArrayLike(value) && (Array.isArray(value) || typeof value === 'string')) {
|
|
100
|
+
return value.length === 0;
|
|
101
|
+
} else if (value instanceof Map) {
|
|
102
|
+
return value.size === 0;
|
|
103
|
+
} else if (value instanceof Set) {
|
|
104
|
+
return value.size === 0;
|
|
105
|
+
} else if (StepHelpersUtils.isPrototype(value)) {
|
|
106
|
+
return Object.keys(value).length === 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
113
|
+
public static isNotEmpty(value: any): boolean {
|
|
114
|
+
return !StepHelpersUtils.isEmpty(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
+
public static isBlank(value: any): boolean {
|
|
119
|
+
if (value == null) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (typeof value !== 'string') {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return value.trim().length === 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
|
+
public static isNotBlank(value: any): boolean {
|
|
130
|
+
return !StepHelpersUtils.isBlank(value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
134
|
+
public static trim(value: any): string {
|
|
135
|
+
if (value == null) {
|
|
136
|
+
return '';
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === 'string') {
|
|
139
|
+
return value.trim();
|
|
140
|
+
}
|
|
141
|
+
throw new UncatchableError(ERR_TRIM_NON_STRING, `Cannot apply trim to non-string object[type=${typeof value}, value=${value}].`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import {Config, DateTime, Logger} from '../utils';
|
|
3
|
+
import {
|
|
4
|
+
PIPELINE_STEP_RETURN_NULL,
|
|
5
|
+
PipelineStepErrorOptions,
|
|
6
|
+
PipelineStepFile,
|
|
7
|
+
PipelineStepFileOptions,
|
|
8
|
+
StepHelpersUtils
|
|
9
|
+
} from './step-helpers-utils';
|
|
10
|
+
|
|
11
|
+
export class PipelineStepDateHelper {
|
|
12
|
+
private readonly _dateTimeFormat: string;
|
|
13
|
+
|
|
14
|
+
public constructor(config: Config) {
|
|
15
|
+
this._dateTimeFormat = config.getString('format.datetime', 'YYYY-MM-DD HH:mm:ss');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public getDateTimeFormat(): string {
|
|
19
|
+
return this._dateTimeFormat;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public now(): DateTime {
|
|
23
|
+
return dayjs().format(this.getDateTimeFormat());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public get dayjs(): typeof dayjs {
|
|
27
|
+
return dayjs;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PipelineStepHelpers {
|
|
32
|
+
$config?: Config;
|
|
33
|
+
$logger?: Logger;
|
|
34
|
+
$date: PipelineStepDateHelper;
|
|
35
|
+
$nano: (size?: number) => string;
|
|
36
|
+
$ascii: (size?: number) => string;
|
|
37
|
+
/** create an exposed uncatchable error*/
|
|
38
|
+
$error: (options: PipelineStepErrorOptions) => never;
|
|
39
|
+
$errors: {
|
|
40
|
+
catchable: (options: Omit<PipelineStepErrorOptions, 'status'>) => never;
|
|
41
|
+
exposed: (options: PipelineStepErrorOptions) => never;
|
|
42
|
+
uncatchable: (options: Omit<PipelineStepErrorOptions, 'status'>) => never;
|
|
43
|
+
};
|
|
44
|
+
/** create a file */
|
|
45
|
+
$file: (options: PipelineStepFileOptions) => PipelineStepFile;
|
|
46
|
+
$clearContextData: () => typeof PIPELINE_STEP_RETURN_NULL;
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
isEmpty: (value: any) => boolean;
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
isNotEmpty: (value: any) => boolean;
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
isBlank: (value: any) => boolean;
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
+
isNotBlank: (value: any) => boolean;
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
+
trim: (value: any) => string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const createStepHelpers = (config: Config, logger: Logger): PipelineStepHelpers => {
|
|
60
|
+
return {
|
|
61
|
+
$config: config, $logger: logger,
|
|
62
|
+
// date
|
|
63
|
+
$date: new PipelineStepDateHelper(config),
|
|
64
|
+
// nano
|
|
65
|
+
$nano: StepHelpersUtils.$nano, $ascii: StepHelpersUtils.$ascii,
|
|
66
|
+
// errors
|
|
67
|
+
$error: StepHelpersUtils.createExposedUncatchableError, $errors: StepHelpersUtils.$errors,
|
|
68
|
+
// file
|
|
69
|
+
$file: StepHelpersUtils.createFile,
|
|
70
|
+
// semaphore
|
|
71
|
+
$clearContextData: StepHelpersUtils.$clearContextData,
|
|
72
|
+
// utilities
|
|
73
|
+
isEmpty: StepHelpersUtils.isEmpty, isNotEmpty: StepHelpersUtils.isNotEmpty,
|
|
74
|
+
isBlank: StepHelpersUtils.isBlank, isNotBlank: StepHelpersUtils.isNotBlank,
|
|
75
|
+
trim: StepHelpersUtils.trim
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './pipeline-repository';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Pipeline,
|
|
3
|
+
PipelineBuilder,
|
|
4
|
+
PipelineCode,
|
|
5
|
+
PipelineOptions,
|
|
6
|
+
PipelineStepBuilder,
|
|
7
|
+
PipelineStepCode
|
|
8
|
+
} from '../pipeline';
|
|
9
|
+
import {Undefinable} from '../utils';
|
|
10
|
+
|
|
11
|
+
export class PipelineRepository {
|
|
12
|
+
private static readonly PIPELINE_BUILDERS: Record<PipelineCode, PipelineBuilder> = {};
|
|
13
|
+
private static readonly STEP_BUILDERS: Record<PipelineStepCode, PipelineStepBuilder> = {};
|
|
14
|
+
|
|
15
|
+
private constructor() {
|
|
16
|
+
// avoid extend
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public static async findPipeline(code: PipelineCode, options: PipelineOptions): Promise<Undefinable<Pipeline>> {
|
|
20
|
+
const builder = this.PIPELINE_BUILDERS[code];
|
|
21
|
+
if (builder == null) {
|
|
22
|
+
return (void 0);
|
|
23
|
+
} else {
|
|
24
|
+
return await builder.create(options);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static putPipeline(builders: Record<PipelineCode, PipelineBuilder>): Record<PipelineCode, PipelineBuilder> {
|
|
29
|
+
return Object.keys(builders).reduce((duplicated, code) => {
|
|
30
|
+
const existing = PipelineRepository.PIPELINE_BUILDERS[code];
|
|
31
|
+
PipelineRepository.PIPELINE_BUILDERS[code] = builders[code];
|
|
32
|
+
if (existing != null) {
|
|
33
|
+
duplicated[code] = existing;
|
|
34
|
+
}
|
|
35
|
+
return duplicated;
|
|
36
|
+
}, {} as Record<PipelineCode, PipelineBuilder>);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public static async findStep(code: PipelineStepCode): Promise<Undefinable<PipelineStepBuilder>> {
|
|
40
|
+
return PipelineRepository.STEP_BUILDERS[code];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static putStep(builders: Record<PipelineStepCode, PipelineStepBuilder>): Record<PipelineStepCode, PipelineStepBuilder> {
|
|
44
|
+
return Object.keys(builders).reduce((duplicated, code) => {
|
|
45
|
+
const existing = PipelineRepository.STEP_BUILDERS[code];
|
|
46
|
+
PipelineRepository.STEP_BUILDERS[code] = builders[code];
|
|
47
|
+
if (existing != null) {
|
|
48
|
+
duplicated[code] = existing;
|
|
49
|
+
}
|
|
50
|
+
return duplicated;
|
|
51
|
+
}, {} as Record<PipelineStepCode, PipelineStepBuilder>);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {createLogger, Logger} from './logger';
|
|
2
|
+
import {Undefinable} from './types';
|
|
3
|
+
|
|
4
|
+
const MISSED = Symbol();
|
|
5
|
+
|
|
6
|
+
export class Config {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
private valueCache: Record<string, any> = {};
|
|
9
|
+
|
|
10
|
+
public constructor(private _logger: Logger) {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public getLogger(): Logger {
|
|
14
|
+
return this._logger;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public setLogger(value: Logger) {
|
|
18
|
+
this._logger = value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected generateKey(name: string) {
|
|
22
|
+
const key = name
|
|
23
|
+
.split('.')
|
|
24
|
+
.filter(s => s.trim().length !== 0)
|
|
25
|
+
.map(s => s.toUpperCase())
|
|
26
|
+
.join('_');
|
|
27
|
+
return `CFG_${key}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected getFromCache<T>(name: string, read: () => Undefinable<T>, defaultValue?: T): Undefinable<T> {
|
|
31
|
+
const cached = this.valueCache[name];
|
|
32
|
+
if (cached === MISSED) {
|
|
33
|
+
return defaultValue ?? (void 0);
|
|
34
|
+
} else if (cached != null) {
|
|
35
|
+
return cached;
|
|
36
|
+
} else {
|
|
37
|
+
const value = read();
|
|
38
|
+
this.valueCache[name] = value ?? MISSED;
|
|
39
|
+
return value ?? defaultValue ?? (void 0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public getString(name: string, defaultValue?: string): Undefinable<string> {
|
|
44
|
+
return this.getFromCache(name, () => {
|
|
45
|
+
// noinspection JSUnresolvedReference
|
|
46
|
+
const value = process.env[this.generateKey(name)];
|
|
47
|
+
if (value == null || value.trim().length === 0) {
|
|
48
|
+
return (void 0);
|
|
49
|
+
} else {
|
|
50
|
+
return value.trim();
|
|
51
|
+
}
|
|
52
|
+
}, defaultValue);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public getBoolean(name: string, defaultValue?: boolean): Undefinable<boolean> {
|
|
56
|
+
return this.getFromCache(name, () => {
|
|
57
|
+
// noinspection JSUnresolvedReference
|
|
58
|
+
const value = process.env[this.generateKey(name)];
|
|
59
|
+
if (value == null || value.trim().length === 0) {
|
|
60
|
+
return (void 0);
|
|
61
|
+
} else if (['TRUE', 'YES', 'ON', '1'].includes(value.trim().toUpperCase())) {
|
|
62
|
+
return true;
|
|
63
|
+
} else if (['FALSE', 'NO', 'OFF', '0'].includes(value.trim().toUpperCase())) {
|
|
64
|
+
return false;
|
|
65
|
+
} else {
|
|
66
|
+
this._logger.warn(`Cannot parse given configuration item value[${value}] to boolean, ignored.`, Config.name);
|
|
67
|
+
return (void 0);
|
|
68
|
+
}
|
|
69
|
+
}, defaultValue);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public getNumber(name: string, defaultValue?: number): Undefinable<number> {
|
|
73
|
+
return this.getFromCache(name, () => {
|
|
74
|
+
// noinspection JSUnresolvedReference
|
|
75
|
+
const value = process.env[this.generateKey(name)];
|
|
76
|
+
if (value == null || value.trim().length === 0) {
|
|
77
|
+
return (void 0);
|
|
78
|
+
}
|
|
79
|
+
const v = Number(value);
|
|
80
|
+
if (isNaN(v)) {
|
|
81
|
+
this._logger.error(`Cannot parse given configuration item value[${value}] to number, ignored.`, Config.name);
|
|
82
|
+
return (void 0);
|
|
83
|
+
} else {
|
|
84
|
+
return v;
|
|
85
|
+
}
|
|
86
|
+
}, defaultValue);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
public getJson(name: string, defaultValue?: any): Undefinable<any> {
|
|
91
|
+
return this.getFromCache(name, () => {
|
|
92
|
+
// noinspection JSUnresolvedReference
|
|
93
|
+
const value = process.env[this.generateKey(name)];
|
|
94
|
+
if (value == null || value.trim().length === 0) {
|
|
95
|
+
return (void 0);
|
|
96
|
+
} else {
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(value);
|
|
99
|
+
} catch {
|
|
100
|
+
this._logger.warn(`Cannot parse given configuration item value[${value}] to boolean, ignored.`, Config.name);
|
|
101
|
+
return (void 0);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, defaultValue);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const createConfig = (logger?: Logger) => {
|
|
109
|
+
return new Config(logger ?? createLogger());
|
|
110
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** should be O00-00000 - O99-99999 */
|
|
2
|
+
export type O23ReservedErrorCode = `O${number}-${number}`;
|
|
3
|
+
/** prefix should be any uppercase but O00 - O99, number should be 00000 - 99999 */
|
|
4
|
+
export type O23ExternalErrorCode = `O${Uppercase<string>}-${number}`;
|
|
5
|
+
export type O23ErrorCode = O23ReservedErrorCode | O23ExternalErrorCode;
|
|
6
|
+
export const ERR_PIPELINE_NOT_FOUND: O23ReservedErrorCode = 'O01-00001';
|
|
7
|
+
export const ERR_TRIM_NON_STRING: O23ReservedErrorCode = 'O01-00002';
|
|
8
|
+
export const ERR_UNKNOWN: O23ReservedErrorCode = 'O01-99999';
|
|
9
|
+
|
|
10
|
+
export class CatchableError extends Error {
|
|
11
|
+
public constructor(private readonly _code: string, message: string) {
|
|
12
|
+
super(message);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public getCode(): string {
|
|
16
|
+
return this._code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class UncatchableError extends Error {
|
|
21
|
+
public constructor(private readonly _code: string, message: string) {
|
|
22
|
+
super(message);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public getCode(): string {
|
|
26
|
+
return this._code;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ExposedUncatchableError extends UncatchableError {
|
|
31
|
+
private readonly _status: number;
|
|
32
|
+
|
|
33
|
+
public constructor(status: number, code: string, message: string) {
|
|
34
|
+
super(code, message);
|
|
35
|
+
this._status = status;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public getStatus(): number {
|
|
39
|
+
return this._status;
|
|
40
|
+
}
|
|
41
|
+
}
|