@syncbridge/common 0.4.0
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/LICENSE +15 -0
- package/README.md +1 -0
- package/classes/base-element.d.ts +46 -0
- package/classes/base-element.js +80 -0
- package/classes/component-base.d.ts +26 -0
- package/classes/component-base.js +12 -0
- package/classes/frame-stream.d.ts +34 -0
- package/classes/frame-stream.js +140 -0
- package/classes/processor-base.d.ts +15 -0
- package/classes/processor-base.js +8 -0
- package/classes/runnable.d.ts +86 -0
- package/classes/runnable.js +228 -0
- package/classes/sb-error.d.ts +11 -0
- package/classes/sb-error.js +54 -0
- package/classes/stack-executor.d.ts +10 -0
- package/classes/stack-executor.js +37 -0
- package/constants.d.ts +6 -0
- package/constants.js +17 -0
- package/decorators/component.decorator.d.ts +15 -0
- package/decorators/component.decorator.js +28 -0
- package/decorators/decorator-helpers.d.ts +2 -0
- package/decorators/decorator-helpers.js +13 -0
- package/decorators/define-variable.decorator.d.ts +14 -0
- package/decorators/define-variable.decorator.js +57 -0
- package/decorators/finalize-element-metadata.d.ts +4 -0
- package/decorators/finalize-element-metadata.js +30 -0
- package/decorators/processor-events.decorator.d.ts +5 -0
- package/decorators/processor-events.decorator.js +21 -0
- package/decorators/processor.decorator.d.ts +31 -0
- package/decorators/processor.decorator.js +42 -0
- package/decorators/use-component.decorator.d.ts +9 -0
- package/decorators/use-component.decorator.js +24 -0
- package/decorators/use-variables.decorator.d.ts +5 -0
- package/decorators/use-variables.decorator.js +20 -0
- package/index.d.ts +20 -0
- package/index.js +20 -0
- package/interfaces/extension-package.interface.d.ts +6 -0
- package/interfaces/extension-package.interface.js +1 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/index.js +2 -0
- package/interfaces/logger.interface.d.ts +17 -0
- package/interfaces/logger.interface.js +1 -0
- package/models/enums/log-level.d.ts +9 -0
- package/models/enums/log-level.js +15 -0
- package/models/enums/service-status.d.ts +8 -0
- package/models/enums/service-status.js +14 -0
- package/models/enums/variable-format.enum.d.ts +9 -0
- package/models/enums/variable-format.enum.js +15 -0
- package/models/enums/variable-type.enum.d.ts +8 -0
- package/models/enums/variable-type.enum.js +14 -0
- package/models/index.d.ts +13 -0
- package/models/index.js +13 -0
- package/models/metadata/author-metadata.d.ts +8 -0
- package/models/metadata/author-metadata.js +48 -0
- package/models/metadata/component-metadata.d.ts +8 -0
- package/models/metadata/component-metadata.js +25 -0
- package/models/metadata/element-component-metadata.d.ts +19 -0
- package/models/metadata/element-component-metadata.js +70 -0
- package/models/metadata/element-metadata.d.ts +18 -0
- package/models/metadata/element-metadata.js +81 -0
- package/models/metadata/package-metadata.d.ts +7 -0
- package/models/metadata/package-metadata.js +27 -0
- package/models/metadata/processor-metadata.d.ts +5 -0
- package/models/metadata/processor-metadata.js +14 -0
- package/models/metadata/variable-metadata.d.ts +31 -0
- package/models/metadata/variable-metadata.js +138 -0
- package/models/profile/log-options.d.ts +16 -0
- package/models/profile/log-options.js +56 -0
- package/models/profile/profile-component.d.ts +23 -0
- package/models/profile/profile-component.js +77 -0
- package/models/profile/profile.d.ts +25 -0
- package/models/profile/profile.js +115 -0
- package/models-document.d.ts +3 -0
- package/models-document.js +15 -0
- package/package.json +33 -0
- package/processor-factory.d.ts +13 -0
- package/processor-factory.js +106 -0
- package/registry.d.ts +32 -0
- package/registry.js +205 -0
- package/utils/encrypt-helpers.d.ts +2 -0
- package/utils/encrypt-helpers.js +43 -0
- package/utils/make-extension-package.d.ts +3 -0
- package/utils/make-extension-package.js +13 -0
- package/utils/metadata-utils.d.ts +7 -0
- package/utils/metadata-utils.js +99 -0
- package/utils/profile-utils.d.ts +21 -0
- package/utils/profile-utils.js +222 -0
package/registry.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import { deepClone, isPlainObject } from '@jsopen/objects';
|
|
6
|
+
import { COMPONENT_OPTIONS, PROCESSOR_OPTIONS } from './constants.js';
|
|
7
|
+
import { isExtensionPackage } from './utils/make-extension-package.js';
|
|
8
|
+
export var ExtensionRegistry;
|
|
9
|
+
(function (ExtensionRegistry) {
|
|
10
|
+
let _resolver = import.meta.resolve;
|
|
11
|
+
ExtensionRegistry.packages = new Map();
|
|
12
|
+
ExtensionRegistry.components = new Map();
|
|
13
|
+
ExtensionRegistry.processors = new Map();
|
|
14
|
+
function setResolver(resolver) {
|
|
15
|
+
_resolver = resolver;
|
|
16
|
+
}
|
|
17
|
+
ExtensionRegistry.setResolver = setResolver;
|
|
18
|
+
function getProcessor(className) {
|
|
19
|
+
const rec = ExtensionRegistry.processors.get(className);
|
|
20
|
+
if (!rec) {
|
|
21
|
+
throw new Error(`Processor "${className}" not found in registry`);
|
|
22
|
+
}
|
|
23
|
+
return rec;
|
|
24
|
+
}
|
|
25
|
+
ExtensionRegistry.getProcessor = getProcessor;
|
|
26
|
+
function getComponent(className) {
|
|
27
|
+
const rec = ExtensionRegistry.components.get(className);
|
|
28
|
+
if (!rec) {
|
|
29
|
+
throw new Error(`Component "${className}" not found in registry`);
|
|
30
|
+
}
|
|
31
|
+
return rec;
|
|
32
|
+
}
|
|
33
|
+
ExtensionRegistry.getComponent = getComponent;
|
|
34
|
+
async function registerPackage(...specifiers) {
|
|
35
|
+
const errors = [];
|
|
36
|
+
await Promise.all(specifiers.map(specifier => _registerPackage(specifier).catch(err => {
|
|
37
|
+
errors.push(`\n ${specifier}: ${err.message}`);
|
|
38
|
+
})));
|
|
39
|
+
if (errors.length) {
|
|
40
|
+
console.error(`Failed to register extensions:${errors}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
ExtensionRegistry.registerPackage = registerPackage;
|
|
44
|
+
async function _registerPackage(specifier, options) {
|
|
45
|
+
let packagePath = fileURLToPath(_resolver(specifier));
|
|
46
|
+
const json = locatePkgJson(path.dirname(packagePath));
|
|
47
|
+
if (!json) {
|
|
48
|
+
if (options?.ignoreInvalid)
|
|
49
|
+
return;
|
|
50
|
+
throw new TypeError(`Can't locate package.json file for "${specifier}"`);
|
|
51
|
+
}
|
|
52
|
+
if (path.isAbsolute(packagePath) && process.platform === 'win32') {
|
|
53
|
+
packagePath = pathToFileURL(packagePath).href;
|
|
54
|
+
}
|
|
55
|
+
const pkg = (await import(packagePath)).default;
|
|
56
|
+
if (!isExtensionPackage(pkg)) {
|
|
57
|
+
if (options?.ignoreInvalid)
|
|
58
|
+
return;
|
|
59
|
+
throw new TypeError(`"${specifier}" do not export ExtensionPackage interface`);
|
|
60
|
+
}
|
|
61
|
+
// ignore if already registered
|
|
62
|
+
if (ExtensionRegistry.packages.get(json.name))
|
|
63
|
+
return;
|
|
64
|
+
// Register dependencies
|
|
65
|
+
if (pkg.dependencies) {
|
|
66
|
+
for (const dep of pkg.dependencies)
|
|
67
|
+
await _registerPackage(dep, { ignoreInvalid: true });
|
|
68
|
+
}
|
|
69
|
+
const packageRec = Object.freeze({
|
|
70
|
+
name: json.name,
|
|
71
|
+
version: json.version,
|
|
72
|
+
description: json.description,
|
|
73
|
+
});
|
|
74
|
+
ExtensionRegistry.packages.set(packageRec.name, packageRec);
|
|
75
|
+
if (Array.isArray(pkg.components)) {
|
|
76
|
+
for (const ctor of pkg.components) {
|
|
77
|
+
await registerComponent(ctor, packageRec.name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(pkg.processors)) {
|
|
81
|
+
for (const ctor of pkg.processors) {
|
|
82
|
+
await registerProcessor(ctor, packageRec.name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function registerComponent(ctor, packageName) {
|
|
87
|
+
const _metadata = Reflect.getMetadata(COMPONENT_OPTIONS, ctor);
|
|
88
|
+
if (!_metadata)
|
|
89
|
+
throw new TypeError(`Class "${ctor.name}" has no component metadata.`);
|
|
90
|
+
if (_metadata.abstract)
|
|
91
|
+
return;
|
|
92
|
+
let componentRec = ExtensionRegistry.components.get(_metadata.className);
|
|
93
|
+
if (componentRec)
|
|
94
|
+
throw new TypeError(`Component "${_metadata.className}" already registered by "${componentRec.package}" package`);
|
|
95
|
+
let metadata = await resolvePromisesDeep(_metadata);
|
|
96
|
+
metadata = deepClone({
|
|
97
|
+
className: metadata.className,
|
|
98
|
+
displayName: metadata.displayName,
|
|
99
|
+
description: metadata.description,
|
|
100
|
+
iconUrl: metadata.iconUrl,
|
|
101
|
+
author: metadata.author,
|
|
102
|
+
interfaces: metadata.interfaces,
|
|
103
|
+
tags: metadata.tags,
|
|
104
|
+
abstract: metadata.abstract,
|
|
105
|
+
variables: metadata.variables,
|
|
106
|
+
components: metadata.components,
|
|
107
|
+
...metadata,
|
|
108
|
+
});
|
|
109
|
+
if (metadata.variables) {
|
|
110
|
+
/** Convert object enumValues to arrays */
|
|
111
|
+
for (const v of Object.values(metadata.variables)) {
|
|
112
|
+
if (v.enumValues &&
|
|
113
|
+
typeof v.enumValues === 'object' &&
|
|
114
|
+
!Array.isArray(v.enumValues)) {
|
|
115
|
+
let values = v.enumValues;
|
|
116
|
+
const keys = Object.keys(values).filter(k => !/^\d+$/.test(k));
|
|
117
|
+
values = keys.reduce((a, k) => {
|
|
118
|
+
if (values[k] != null)
|
|
119
|
+
a.push(values[k]);
|
|
120
|
+
return a;
|
|
121
|
+
// v => !(typeof v === 'number' && !keys.includes(String(v))),
|
|
122
|
+
}, []);
|
|
123
|
+
v.enumValues = values;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
componentRec = {
|
|
128
|
+
package: packageName,
|
|
129
|
+
ctor,
|
|
130
|
+
metadata,
|
|
131
|
+
};
|
|
132
|
+
ExtensionRegistry.components.set(metadata.className, componentRec);
|
|
133
|
+
}
|
|
134
|
+
ExtensionRegistry.registerComponent = registerComponent;
|
|
135
|
+
async function registerProcessor(ctor, packageName) {
|
|
136
|
+
const _metadata = Reflect.getMetadata(PROCESSOR_OPTIONS, ctor);
|
|
137
|
+
if (!_metadata)
|
|
138
|
+
throw new TypeError(`Class "${ctor.name}" has no processor metadata.`);
|
|
139
|
+
if (_metadata.abstract)
|
|
140
|
+
return;
|
|
141
|
+
let processorRec = ExtensionRegistry.processors.get(_metadata.className);
|
|
142
|
+
if (processorRec)
|
|
143
|
+
throw new TypeError(`Processor "${_metadata.className}" already registered by "${processorRec.package}" package`);
|
|
144
|
+
let metadata = await resolvePromisesDeep(_metadata);
|
|
145
|
+
metadata = deepClone({
|
|
146
|
+
className: metadata.className,
|
|
147
|
+
displayName: metadata.displayName,
|
|
148
|
+
description: metadata.description,
|
|
149
|
+
iconUrl: metadata.iconUrl,
|
|
150
|
+
author: metadata.author,
|
|
151
|
+
tags: metadata.tags,
|
|
152
|
+
abstract: metadata.abstract,
|
|
153
|
+
variables: metadata.variables,
|
|
154
|
+
components: metadata.components,
|
|
155
|
+
...metadata,
|
|
156
|
+
});
|
|
157
|
+
if (metadata.variables) {
|
|
158
|
+
/** Convert object enumValues to arrays */
|
|
159
|
+
for (const v of Object.values(metadata.variables)) {
|
|
160
|
+
if (v.enumValues &&
|
|
161
|
+
typeof v.enumValues === 'object' &&
|
|
162
|
+
!Array.isArray(v.enumValues)) {
|
|
163
|
+
let values = v.enumValues;
|
|
164
|
+
const keys = Object.keys(values).filter(k => !/^\d+$/.test(k));
|
|
165
|
+
values = keys.reduce((a, k) => {
|
|
166
|
+
if (values[k] != null)
|
|
167
|
+
a.push(values[k]);
|
|
168
|
+
return a;
|
|
169
|
+
// v => !(typeof v === 'number' && !keys.includes(String(v))),
|
|
170
|
+
}, []);
|
|
171
|
+
v.enumValues = values;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
processorRec = {
|
|
176
|
+
package: packageName,
|
|
177
|
+
ctor,
|
|
178
|
+
metadata,
|
|
179
|
+
};
|
|
180
|
+
ExtensionRegistry.processors.set(metadata.className, processorRec);
|
|
181
|
+
}
|
|
182
|
+
ExtensionRegistry.registerProcessor = registerProcessor;
|
|
183
|
+
})(ExtensionRegistry || (ExtensionRegistry = {}));
|
|
184
|
+
function locatePkgJson(directory) {
|
|
185
|
+
if (directory.startsWith('file:'))
|
|
186
|
+
directory = fileURLToPath(directory);
|
|
187
|
+
for (let i = 0; i < 3; i++) {
|
|
188
|
+
const f = path.resolve(directory, 'package.json');
|
|
189
|
+
if (fs.existsSync(f)) {
|
|
190
|
+
const json = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
191
|
+
if (json.name && json.version)
|
|
192
|
+
return json;
|
|
193
|
+
}
|
|
194
|
+
directory = path.dirname(directory);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function resolvePromisesDeep(obj) {
|
|
198
|
+
obj = await obj;
|
|
199
|
+
if (obj && isPlainObject(obj)) {
|
|
200
|
+
for (const k of Object.keys(obj)) {
|
|
201
|
+
obj[k] = await resolvePromisesDeep(obj[k]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return obj;
|
|
205
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const secretEncryptor: (secretKey: string) => import("valgen").Validator<string, string, import("valgen").ExecutionOptions>;
|
|
2
|
+
export declare const secretDecryptor: (secretKey: string) => import("valgen").Validator<string, string, import("valgen").ExecutionOptions>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { validator } from 'valgen';
|
|
3
|
+
export const secretEncryptor = (secretKey) => {
|
|
4
|
+
return validator((input) => {
|
|
5
|
+
if (input.startsWith('sbscrt::')) {
|
|
6
|
+
input = decryptText(input, secretKey);
|
|
7
|
+
}
|
|
8
|
+
return 'sbscrt::' + encryptText(input, secretKey);
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
export const secretDecryptor = (secretKey) => {
|
|
12
|
+
return validator((input) => {
|
|
13
|
+
if (input.startsWith('sbscrt::')) {
|
|
14
|
+
return decryptText(input.substring(6), secretKey);
|
|
15
|
+
}
|
|
16
|
+
return input;
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
function encryptText(input, secretKey) {
|
|
20
|
+
const salt = crypto.randomBytes(16);
|
|
21
|
+
const key = crypto.scryptSync(secretKey, salt, 32);
|
|
22
|
+
const iv = crypto.randomBytes(16);
|
|
23
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
|
24
|
+
const encrypted = Buffer.concat([
|
|
25
|
+
cipher.update(input, 'utf8'),
|
|
26
|
+
cipher.final(),
|
|
27
|
+
]);
|
|
28
|
+
// Store salt + IV + EncryptedData
|
|
29
|
+
return Buffer.concat([salt, iv, encrypted]).toString('base64');
|
|
30
|
+
}
|
|
31
|
+
function decryptText(input, secretKey) {
|
|
32
|
+
const data = Buffer.from(input, 'base64');
|
|
33
|
+
const salt = data.subarray(0, 16);
|
|
34
|
+
const iv = data.subarray(16, 32);
|
|
35
|
+
const encrypted = data.subarray(32);
|
|
36
|
+
const key = crypto.scryptSync(secretKey, salt, 32);
|
|
37
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
38
|
+
const decrypted = Buffer.concat([
|
|
39
|
+
decipher.update(encrypted),
|
|
40
|
+
decipher.final(),
|
|
41
|
+
]);
|
|
42
|
+
return decrypted.toString('utf8');
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const marker = Symbol('marker');
|
|
2
|
+
export function makeExtensionPackage(pkg) {
|
|
3
|
+
Object.defineProperty(pkg, marker, {
|
|
4
|
+
configurable: false,
|
|
5
|
+
writable: false,
|
|
6
|
+
enumerable: false,
|
|
7
|
+
value: 1,
|
|
8
|
+
});
|
|
9
|
+
return pkg;
|
|
10
|
+
}
|
|
11
|
+
export function isExtensionPackage(pkg) {
|
|
12
|
+
return pkg && typeof pkg === 'object' && pkg[marker] === 1;
|
|
13
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { StackExecutor } from '../classes/stack-executor.js';
|
|
2
|
+
import { ProcessorMetadata, Profile } from '../models/index.js';
|
|
3
|
+
export declare function materializeMetadata(profile: Profile): ProcessorMetadata;
|
|
4
|
+
export declare function materializeMetadataSilent(profile: Profile): {
|
|
5
|
+
metadata: ProcessorMetadata;
|
|
6
|
+
issues?: StackExecutor.Issue[];
|
|
7
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { clone, merge, omitUndefined } from '@jsopen/objects';
|
|
2
|
+
import { SbError } from '../classes/sb-error.js';
|
|
3
|
+
import { StackExecutor } from '../classes/stack-executor.js';
|
|
4
|
+
import { ExtensionRegistry } from '../registry.js';
|
|
5
|
+
export function materializeMetadata(profile) {
|
|
6
|
+
const { metadata, issues } = materializeMetadataSilent(profile);
|
|
7
|
+
if (issues?.length) {
|
|
8
|
+
const msg = 'Worker profile validation error.\n ' +
|
|
9
|
+
issues
|
|
10
|
+
.map(issue => `- ${issue.message}. Location: /${issue.location}`)
|
|
11
|
+
.join('\n ');
|
|
12
|
+
throw new SbError(msg, {
|
|
13
|
+
workerId: profile.id,
|
|
14
|
+
issues,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return metadata;
|
|
18
|
+
}
|
|
19
|
+
export function materializeMetadataSilent(profile) {
|
|
20
|
+
const stackExecutor = new StackExecutor();
|
|
21
|
+
const processorRec = ExtensionRegistry.getProcessor(profile.processorClass);
|
|
22
|
+
const metadata = omitUndefined(clone({
|
|
23
|
+
className: processorRec.metadata.className,
|
|
24
|
+
displayName: processorRec.metadata.displayName,
|
|
25
|
+
description: processorRec.metadata.description,
|
|
26
|
+
iconUrl: processorRec.metadata.iconUrl,
|
|
27
|
+
author: processorRec.metadata.author,
|
|
28
|
+
tags: processorRec.metadata.tags,
|
|
29
|
+
variables: processorRec.metadata.variables,
|
|
30
|
+
components: processorRec.metadata.components,
|
|
31
|
+
...processorRec.metadata,
|
|
32
|
+
}, { deep: 'full' }));
|
|
33
|
+
delete metadata.components;
|
|
34
|
+
if (processorRec.metadata.components) {
|
|
35
|
+
stackExecutor.execute('components', () => {
|
|
36
|
+
metadata.components = _materializeComponents(stackExecutor, processorRec.metadata, profile);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (stackExecutor.issues?.length)
|
|
40
|
+
return { metadata, issues: stackExecutor.issues };
|
|
41
|
+
return { metadata };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
function _materializeComponents(stackExecutor, metadata, profile) {
|
|
48
|
+
const out = {};
|
|
49
|
+
for (const [componentName, metadataComponent,] of Object.entries(metadata.components)) {
|
|
50
|
+
stackExecutor.execute(componentName, () => {
|
|
51
|
+
const materializedComponent = {
|
|
52
|
+
interfaces: metadataComponent.interfaces
|
|
53
|
+
? [...metadataComponent.interfaces]
|
|
54
|
+
: undefined,
|
|
55
|
+
required: metadataComponent.required,
|
|
56
|
+
...metadataComponent,
|
|
57
|
+
};
|
|
58
|
+
out[componentName] = materializedComponent;
|
|
59
|
+
/** Check if component defined in profile */
|
|
60
|
+
const profileComponent = profile?.components?.[componentName];
|
|
61
|
+
if (!profileComponent) {
|
|
62
|
+
if (metadataComponent.required)
|
|
63
|
+
throw new Error(`Component "${componentName}" should be configured`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
/** istanbul ignore next */
|
|
67
|
+
if (!profileComponent.className)
|
|
68
|
+
throw new Error(`"className" property required`);
|
|
69
|
+
const reg = ExtensionRegistry.getComponent(profileComponent.className);
|
|
70
|
+
materializedComponent.className = profileComponent.className;
|
|
71
|
+
materializedComponent.variables = merge({}, [reg.metadata.variables || {}, metadataComponent.variables || {}], { deep: 'full' });
|
|
72
|
+
if (metadataComponent.className) {
|
|
73
|
+
if (materializedComponent.className &&
|
|
74
|
+
materializedComponent.className !== metadataComponent.className)
|
|
75
|
+
throw new Error(`Selected component class "${profileComponent.className}" do not match with definition component class (${metadataComponent.className}).`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const requiredInterfaces = metadataComponent.interfaces || [];
|
|
79
|
+
const implementations = reg.metadata.interfaces || [];
|
|
80
|
+
const filtered = requiredInterfaces.filter(x => !implementations.includes(x));
|
|
81
|
+
if (filtered.length) {
|
|
82
|
+
throw new Error(`Selected component "${profileComponent.className}" do not implements some of required interfaces (${filtered.join(',')}).`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (materializedComponent.variables) {
|
|
86
|
+
for (const [k, variable] of Object.entries(materializedComponent.variables)) {
|
|
87
|
+
if (variable.ignored)
|
|
88
|
+
delete materializedComponent.variables[k];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (reg.metadata.components) {
|
|
92
|
+
stackExecutor.execute('components', () => {
|
|
93
|
+
materializedComponent.components = _materializeComponents(stackExecutor, reg.metadata, profileComponent);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { StackExecutor } from '../classes/stack-executor.js';
|
|
2
|
+
import { ProcessorMetadata, Profile } from '../models/index.js';
|
|
3
|
+
export interface CodecOptions {
|
|
4
|
+
setDefaultValues?: boolean;
|
|
5
|
+
cryptoSecretKey?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Decodes profile
|
|
9
|
+
*/
|
|
10
|
+
export declare function decodeProfile(materializedMetadata: ProcessorMetadata, rawProfile: Profile, options?: CodecOptions): Profile;
|
|
11
|
+
export declare function decodeProfileSilent(materializedMetadata: ProcessorMetadata, rawProfile: Profile, options?: CodecOptions): {
|
|
12
|
+
profile: Profile;
|
|
13
|
+
issues?: StackExecutor.Issue[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Encodes profile
|
|
17
|
+
*/
|
|
18
|
+
export declare function encodeProfileSilent(metadata: ProcessorMetadata, rawProfile: Profile, options?: CodecOptions): {
|
|
19
|
+
profile: Profile;
|
|
20
|
+
issues?: StackExecutor.Issue[];
|
|
21
|
+
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { isAny, isBase64, isBoolean, isDateString, isEmail, isHex, isNumber, isString, isTime, isURL, validator, vg, } from 'valgen';
|
|
2
|
+
import { SbError } from '../classes/sb-error.js';
|
|
3
|
+
import { StackExecutor } from '../classes/stack-executor.js';
|
|
4
|
+
import { Profile, VariableType, } from '../models/index.js';
|
|
5
|
+
import { getModelsDocument } from '../models-document.js';
|
|
6
|
+
import { secretDecryptor, secretEncryptor } from './encrypt-helpers.js';
|
|
7
|
+
/**
|
|
8
|
+
* Decodes profile
|
|
9
|
+
*/
|
|
10
|
+
export function decodeProfile(materializedMetadata, rawProfile, options) {
|
|
11
|
+
const { profile: out, issues } = decodeProfileSilent(materializedMetadata, rawProfile, options);
|
|
12
|
+
if (issues?.length) {
|
|
13
|
+
const msg = 'Worker profile validation error.\n ' +
|
|
14
|
+
issues
|
|
15
|
+
.map(issue => `- ${issue.message}. Location: /${issue.location}`)
|
|
16
|
+
.join('\n ');
|
|
17
|
+
throw new SbError(msg, {
|
|
18
|
+
workerId: rawProfile.id,
|
|
19
|
+
issues,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
export function decodeProfileSilent(materializedMetadata, rawProfile, options) {
|
|
25
|
+
const modelsDocument = getModelsDocument();
|
|
26
|
+
const t = modelsDocument.node.getComplexType(Profile);
|
|
27
|
+
const fn = t.generateCodec('decode');
|
|
28
|
+
const out = fn(rawProfile);
|
|
29
|
+
const stackExecutor = new StackExecutor();
|
|
30
|
+
_codecProcessElement(stackExecutor, 'decode', materializedMetadata, out, options);
|
|
31
|
+
return { profile: out, issues: stackExecutor.issues };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Encodes profile
|
|
35
|
+
*/
|
|
36
|
+
export function encodeProfileSilent(metadata, rawProfile, options) {
|
|
37
|
+
const modelsDocument = getModelsDocument();
|
|
38
|
+
const t = modelsDocument.node.getComplexType(Profile);
|
|
39
|
+
const fn = t.generateCodec('encode', { coerce: true });
|
|
40
|
+
const out = fn({
|
|
41
|
+
id: rawProfile.id,
|
|
42
|
+
processorClass: rawProfile.processorClass,
|
|
43
|
+
displayName: rawProfile.displayName,
|
|
44
|
+
group: rawProfile.group,
|
|
45
|
+
description: rawProfile.description,
|
|
46
|
+
iconUrl: rawProfile.iconUrl,
|
|
47
|
+
settings: {
|
|
48
|
+
autostart: rawProfile.settings?.autostart ?? false,
|
|
49
|
+
logs: rawProfile.settings?.logs ?? {},
|
|
50
|
+
...rawProfile.settings,
|
|
51
|
+
},
|
|
52
|
+
values: rawProfile.values,
|
|
53
|
+
...rawProfile,
|
|
54
|
+
});
|
|
55
|
+
const stackExecutor = new StackExecutor();
|
|
56
|
+
_codecProcessElement(stackExecutor, 'encode', metadata, out, options);
|
|
57
|
+
return { profile: out, issues: stackExecutor.issues };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
function _codecProcessElement(stackExecutor, codec, metadata, profile, options) {
|
|
64
|
+
profile.values = profile.values || {};
|
|
65
|
+
if (metadata.variables) {
|
|
66
|
+
stackExecutor.execute('values', () => {
|
|
67
|
+
_decodeVariables(stackExecutor, codec, metadata, profile, options);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else
|
|
71
|
+
delete profile.values;
|
|
72
|
+
if (metadata.components) {
|
|
73
|
+
stackExecutor.execute('components', () => {
|
|
74
|
+
profile.components = profile.components || {};
|
|
75
|
+
for (const [k, defComponent] of Object.entries(metadata.components)) {
|
|
76
|
+
if (!profile.components[k]) {
|
|
77
|
+
if (defComponent.required) {
|
|
78
|
+
stackExecutor.issues.push({
|
|
79
|
+
message: `Component "${k}" is required`,
|
|
80
|
+
severity: 'error',
|
|
81
|
+
location: `/components/${k}`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
stackExecutor.execute(k, () => {
|
|
87
|
+
_codecProcessElement(stackExecutor, codec, defComponent, profile.components[k], options);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
delete profile?.components;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
*
|
|
97
|
+
* @protected
|
|
98
|
+
*/
|
|
99
|
+
function _decodeVariables(stackExecutor, codec, metadata, profile, options) {
|
|
100
|
+
if (!metadata.variables) {
|
|
101
|
+
delete profile.values;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const oldValues = profile.values;
|
|
105
|
+
profile.values = {};
|
|
106
|
+
const process = (variables, target, oldVals) => {
|
|
107
|
+
for (const [k, defVariable] of Object.entries(variables)) {
|
|
108
|
+
stackExecutor.execute(k, () => {
|
|
109
|
+
if (defVariable == null)
|
|
110
|
+
return;
|
|
111
|
+
const fns = [];
|
|
112
|
+
switch (defVariable.type) {
|
|
113
|
+
case VariableType.String: {
|
|
114
|
+
switch (defVariable.format) {
|
|
115
|
+
case 'hex':
|
|
116
|
+
fns.push(isHex);
|
|
117
|
+
break;
|
|
118
|
+
case 'base64':
|
|
119
|
+
fns.push(isBase64);
|
|
120
|
+
break;
|
|
121
|
+
case 'url':
|
|
122
|
+
fns.push(isURL);
|
|
123
|
+
break;
|
|
124
|
+
case 'email':
|
|
125
|
+
fns.push(isEmail);
|
|
126
|
+
break;
|
|
127
|
+
case 'date':
|
|
128
|
+
fns.push(vg.isDateString({
|
|
129
|
+
precisionMin: 'day',
|
|
130
|
+
precisionMax: 'day',
|
|
131
|
+
trim: true,
|
|
132
|
+
}));
|
|
133
|
+
break;
|
|
134
|
+
case 'date-time':
|
|
135
|
+
fns.push(isDateString);
|
|
136
|
+
break;
|
|
137
|
+
case 'time':
|
|
138
|
+
fns.push(isTime);
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
fns.push(isString);
|
|
142
|
+
}
|
|
143
|
+
if (defVariable.pattern)
|
|
144
|
+
fns.push(vg.matches(defVariable.pattern));
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case VariableType.Secret: {
|
|
148
|
+
fns.push(isString);
|
|
149
|
+
if (options?.cryptoSecretKey) {
|
|
150
|
+
if (codec === 'decode')
|
|
151
|
+
fns.push(secretDecryptor(options.cryptoSecretKey));
|
|
152
|
+
else
|
|
153
|
+
fns.push(secretEncryptor(options.cryptoSecretKey));
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case VariableType.Number: {
|
|
158
|
+
fns.push(isNumber);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case VariableType.Boolean: {
|
|
162
|
+
fns.push(isBoolean);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case VariableType.Enum: {
|
|
166
|
+
fns.push(vg.isEnum(defVariable.enumValues));
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case VariableType.Nested: {
|
|
170
|
+
if (!defVariable.variables) {
|
|
171
|
+
delete target[k];
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
fns.push(validator(old => {
|
|
175
|
+
if (!old)
|
|
176
|
+
return;
|
|
177
|
+
const v = {};
|
|
178
|
+
process(defVariable.variables, v, old);
|
|
179
|
+
return v;
|
|
180
|
+
}));
|
|
181
|
+
// fns.push(isAny);
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
default:
|
|
186
|
+
fns.push(isAny);
|
|
187
|
+
}
|
|
188
|
+
if (codec === 'decode') {
|
|
189
|
+
if (defVariable.minValue)
|
|
190
|
+
fns.push(vg.isGte(defVariable.minValue));
|
|
191
|
+
if (defVariable.maxValue)
|
|
192
|
+
fns.push(vg.isLt(defVariable.maxValue));
|
|
193
|
+
}
|
|
194
|
+
let fn = fns.length > 1 ? vg.pipe(fns) : fns[0];
|
|
195
|
+
if (defVariable.isArray) {
|
|
196
|
+
fn = vg.isArray(fn);
|
|
197
|
+
if (defVariable.minOccurs !== null || defVariable.maxOccurs) {
|
|
198
|
+
const p = [fn];
|
|
199
|
+
if (defVariable.minOccurs != null)
|
|
200
|
+
p.push(vg.lengthMin(1));
|
|
201
|
+
if (defVariable.maxOccurs != null)
|
|
202
|
+
p.push(vg.lengthMax(defVariable.maxOccurs));
|
|
203
|
+
fn = vg.pipe(p, { returnIndex: 0 });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (options?.setDefaultValues ?? true) {
|
|
207
|
+
fn =
|
|
208
|
+
defVariable.required || defVariable.default != null
|
|
209
|
+
? vg.required(fn, { default: defVariable.default })
|
|
210
|
+
: vg.optional(fn);
|
|
211
|
+
}
|
|
212
|
+
else
|
|
213
|
+
fn =
|
|
214
|
+
!defVariable.required || defVariable.default != null
|
|
215
|
+
? vg.optional(fn)
|
|
216
|
+
: vg.required(fn);
|
|
217
|
+
target[k] = fn(oldVals?.[k], { coerce: true });
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
process(metadata.variables, profile.values, oldValues);
|
|
222
|
+
}
|