@bonsae/nrg 0.1.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/README.md +130 -0
- package/build/server/index.cjs +910 -0
- package/build/server/resources/nrg-client.js +6530 -0
- package/build/server/resources/vue.esm-browser.prod.js +13 -0
- package/build/vite/index.js +1893 -0
- package/build/vite/utils.js +60 -0
- package/package.json +110 -0
- package/src/core/client/api/index.ts +17 -0
- package/src/core/client/app.vue +201 -0
- package/src/core/client/components/node-red-config-input.vue +57 -0
- package/src/core/client/components/node-red-editor-input.vue +283 -0
- package/src/core/client/components/node-red-input.vue +71 -0
- package/src/core/client/components/node-red-json-schema-form.vue +369 -0
- package/src/core/client/components/node-red-select-input.vue +86 -0
- package/src/core/client/components/node-red-typed-input.vue +130 -0
- package/src/core/client/components.d.ts +18 -0
- package/src/core/client/globals.d.ts +17 -0
- package/src/core/client/index.ts +504 -0
- package/src/core/client/shims-vue.d.ts +5 -0
- package/src/core/client/tsconfig.json +18 -0
- package/src/core/client/virtual.d.ts +5 -0
- package/src/core/constants.ts +18 -0
- package/src/core/server/index.ts +209 -0
- package/src/core/server/nodes/config-node.ts +67 -0
- package/src/core/server/nodes/index.ts +4 -0
- package/src/core/server/nodes/io-node.ts +178 -0
- package/src/core/server/nodes/node.ts +255 -0
- package/src/core/server/nodes/types/config-node.ts +28 -0
- package/src/core/server/nodes/types/index.ts +3 -0
- package/src/core/server/nodes/types/io-node.ts +37 -0
- package/src/core/server/nodes/types/node.ts +41 -0
- package/src/core/server/nodes/utils.ts +83 -0
- package/src/core/server/schemas/base.ts +66 -0
- package/src/core/server/schemas/index.ts +3 -0
- package/src/core/server/schemas/type.ts +95 -0
- package/src/core/server/schemas/types/index.ts +73 -0
- package/src/core/server/tsconfig.json +17 -0
- package/src/core/server/types/index.ts +73 -0
- package/src/core/server/utils.ts +56 -0
- package/src/core/server/validator.ts +32 -0
- package/src/core/validator.ts +222 -0
- package/src/tsconfig/base.json +23 -0
- package/src/tsconfig/client.json +11 -0
- package/src/tsconfig/server.json +6 -0
- package/src/vite/async-utils.ts +61 -0
- package/src/vite/client/build.ts +223 -0
- package/src/vite/client/index.ts +1 -0
- package/src/vite/client/plugins/html-generator.ts +75 -0
- package/src/vite/client/plugins/index.ts +5 -0
- package/src/vite/client/plugins/locales-generator.ts +126 -0
- package/src/vite/client/plugins/minifier.ts +22 -0
- package/src/vite/client/plugins/node-definitions-inliner.ts +224 -0
- package/src/vite/client/plugins/static-copy.ts +43 -0
- package/src/vite/defaults.ts +77 -0
- package/src/vite/errors.ts +37 -0
- package/src/vite/index.ts +3 -0
- package/src/vite/logger.ts +94 -0
- package/src/vite/node-red-launcher.ts +344 -0
- package/src/vite/plugin.ts +61 -0
- package/src/vite/plugins/build.ts +73 -0
- package/src/vite/plugins/index.ts +2 -0
- package/src/vite/plugins/server.ts +267 -0
- package/src/vite/server/build.ts +124 -0
- package/src/vite/server/index.ts +1 -0
- package/src/vite/server/plugins/index.ts +3 -0
- package/src/vite/server/plugins/output-wrapper.ts +109 -0
- package/src/vite/server/plugins/package-json-generator.ts +203 -0
- package/src/vite/server/plugins/type-generator.ts +285 -0
- package/src/vite/types.ts +369 -0
- package/src/vite/utils.ts +103 -0
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/core/server/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ConfigNode: () => ConfigNode,
|
|
34
|
+
IONode: () => IONode,
|
|
35
|
+
Node: () => Node,
|
|
36
|
+
SchemaType: () => SchemaType,
|
|
37
|
+
defineSchema: () => defineSchema,
|
|
38
|
+
registerType: () => registerType,
|
|
39
|
+
registerTypes: () => registerTypes
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
var import_path = __toESM(require("path"), 1);
|
|
43
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
44
|
+
|
|
45
|
+
// src/core/server/utils.ts
|
|
46
|
+
function getCredentialsFromSchema(schema) {
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
49
|
+
const property = value;
|
|
50
|
+
result[key] = {
|
|
51
|
+
// NOTE: required is always false because it is controlled by the JSON Schema and AJV validation instead of using node-red client core
|
|
52
|
+
required: false,
|
|
53
|
+
type: property.format === "password" ? "password" : "text",
|
|
54
|
+
value: property.default ?? void 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/core/validator.ts
|
|
61
|
+
var import_ajv = __toESM(require("ajv"), 1);
|
|
62
|
+
var import_ajv_formats = __toESM(require("ajv-formats"), 1);
|
|
63
|
+
var import_ajv_errors = __toESM(require("ajv-errors"), 1);
|
|
64
|
+
var Validator = class {
|
|
65
|
+
ajv;
|
|
66
|
+
constructor(options) {
|
|
67
|
+
const { customKeywords, customFormats, ...ajvOptions } = options || {};
|
|
68
|
+
this.ajv = new import_ajv.default({
|
|
69
|
+
allErrors: true,
|
|
70
|
+
code: {
|
|
71
|
+
source: false
|
|
72
|
+
},
|
|
73
|
+
coerceTypes: true,
|
|
74
|
+
removeAdditional: false,
|
|
75
|
+
strict: false,
|
|
76
|
+
strictSchema: false,
|
|
77
|
+
useDefaults: true,
|
|
78
|
+
validateFormats: true,
|
|
79
|
+
// NOTE: typebox handles validation via typescript
|
|
80
|
+
// NOTE: if true, types that are not serializable JSON, like Function, would not work
|
|
81
|
+
validateSchema: false,
|
|
82
|
+
verbose: true,
|
|
83
|
+
...ajvOptions
|
|
84
|
+
});
|
|
85
|
+
(0, import_ajv_formats.default)(this.ajv);
|
|
86
|
+
(0, import_ajv_errors.default)(this.ajv);
|
|
87
|
+
this.addCustomKeywords(customKeywords || []);
|
|
88
|
+
this.addCustomFormats(customFormats || {});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Add custom keywords to the validator
|
|
92
|
+
*/
|
|
93
|
+
addCustomKeywords(keywords) {
|
|
94
|
+
if (!keywords) return;
|
|
95
|
+
keywords.forEach((keyword) => {
|
|
96
|
+
this.ajv.addKeyword(keyword);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Add custom formats to the validator
|
|
101
|
+
*/
|
|
102
|
+
addCustomFormats(formats) {
|
|
103
|
+
if (!formats) return;
|
|
104
|
+
Object.entries(formats).forEach(([name, validator3]) => {
|
|
105
|
+
if (validator3 instanceof RegExp) {
|
|
106
|
+
this.ajv.addFormat(name, validator3);
|
|
107
|
+
} else {
|
|
108
|
+
this.ajv.addFormat(name, { validate: validator3 });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Create a validator function with caching
|
|
114
|
+
* @param schema - JSON Schema to validate against
|
|
115
|
+
* @param cacheKey - Optional cache key for reusing validators
|
|
116
|
+
*/
|
|
117
|
+
createValidator(schema, cacheKey) {
|
|
118
|
+
if (cacheKey && !schema.$id) {
|
|
119
|
+
schema.$id = cacheKey;
|
|
120
|
+
}
|
|
121
|
+
if (schema.$id) {
|
|
122
|
+
const cached = this.ajv.getSchema(schema.$id);
|
|
123
|
+
if (cached) return cached;
|
|
124
|
+
}
|
|
125
|
+
const validator3 = this.ajv.compile(schema);
|
|
126
|
+
return validator3;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Validate data against a schema and return a structured result
|
|
130
|
+
*/
|
|
131
|
+
validate(data, schema, options) {
|
|
132
|
+
const validator3 = this.createValidator(schema, options?.cacheKey);
|
|
133
|
+
const valid = validator3(data);
|
|
134
|
+
if (!valid) {
|
|
135
|
+
const errorMessage = this.formatErrors(validator3.errors);
|
|
136
|
+
if (options?.throwOnError) {
|
|
137
|
+
throw new ValidationError(errorMessage, validator3.errors || []);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
valid: false,
|
|
141
|
+
errors: validator3.errors || void 0,
|
|
142
|
+
errorMessage
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
valid: true,
|
|
147
|
+
data
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Format errors into a human-readable string
|
|
152
|
+
*/
|
|
153
|
+
formatErrors(errors, options) {
|
|
154
|
+
if (!errors || errors.length === 0) {
|
|
155
|
+
return "No errors";
|
|
156
|
+
}
|
|
157
|
+
return this.ajv.errorsText(errors, {
|
|
158
|
+
separator: "; ",
|
|
159
|
+
dataVar: "data",
|
|
160
|
+
...options
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get detailed error information
|
|
165
|
+
*/
|
|
166
|
+
getDetailedErrors(errors) {
|
|
167
|
+
if (!errors || errors.length === 0) return [];
|
|
168
|
+
return errors.map((error) => ({
|
|
169
|
+
field: error.instancePath || "/",
|
|
170
|
+
message: error.message || "Validation failed",
|
|
171
|
+
keyword: error.keyword,
|
|
172
|
+
params: error.params,
|
|
173
|
+
schemaPath: error.schemaPath
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Add a schema to the validator for reference
|
|
178
|
+
*/
|
|
179
|
+
addSchema(schema, key) {
|
|
180
|
+
this.ajv.addSchema(schema, key);
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Remove a schema from the validator
|
|
185
|
+
*/
|
|
186
|
+
removeSchema(key) {
|
|
187
|
+
this.ajv.removeSchema(key);
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
var ValidationError = class _ValidationError extends Error {
|
|
192
|
+
constructor(message, errors) {
|
|
193
|
+
super(message);
|
|
194
|
+
this.errors = errors;
|
|
195
|
+
this.name = "ValidationError";
|
|
196
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
var validator = new Validator();
|
|
200
|
+
|
|
201
|
+
// src/core/server/validator.ts
|
|
202
|
+
var NodeRedValidator = class extends Validator {
|
|
203
|
+
constructor(RED) {
|
|
204
|
+
super({
|
|
205
|
+
customKeywords: [
|
|
206
|
+
{ keyword: "skip-validation", schemaType: "boolean", valid: true },
|
|
207
|
+
{
|
|
208
|
+
keyword: "node-type",
|
|
209
|
+
type: "string",
|
|
210
|
+
validate: (schemaValue, dataValue) => {
|
|
211
|
+
if (!dataValue) return true;
|
|
212
|
+
const node = RED.nodes.getNode(dataValue);
|
|
213
|
+
return node?.type === schemaValue;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
customFormats: {
|
|
218
|
+
"node-id": /^[a-zA-Z0-9-_]+$/,
|
|
219
|
+
"flow-id": /^[a-f0-9]{16}$/,
|
|
220
|
+
"topic-path": (data) => /^[a-zA-Z0-9/_-]+$/.test(data)
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
var validator2 = void 0;
|
|
226
|
+
function initValidator(RED) {
|
|
227
|
+
validator2 = new NodeRedValidator(RED);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/core/server/nodes/utils.ts
|
|
231
|
+
function setupContext(context, store) {
|
|
232
|
+
return {
|
|
233
|
+
get: (key) => new Promise(
|
|
234
|
+
(resolve, reject) => context.get(
|
|
235
|
+
key,
|
|
236
|
+
store,
|
|
237
|
+
(error, value) => error ? reject(error) : resolve(value)
|
|
238
|
+
)
|
|
239
|
+
),
|
|
240
|
+
set: (key, value) => new Promise(
|
|
241
|
+
(resolve, reject) => context.set(
|
|
242
|
+
key,
|
|
243
|
+
value,
|
|
244
|
+
store,
|
|
245
|
+
(error) => error ? reject(error) : resolve()
|
|
246
|
+
)
|
|
247
|
+
),
|
|
248
|
+
keys: () => new Promise(
|
|
249
|
+
(resolve, reject) => context.keys(store, (error, k) => error ? reject(error) : resolve(k))
|
|
250
|
+
)
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function setupConfigProxy(RED, config) {
|
|
254
|
+
const SKIP_PROPS = /* @__PURE__ */ new Set(["id", "_id", "_users"]);
|
|
255
|
+
const createProxy = (obj) => {
|
|
256
|
+
return new Proxy(obj, {
|
|
257
|
+
get(target, prop) {
|
|
258
|
+
if (typeof prop === "symbol") {
|
|
259
|
+
return target[prop];
|
|
260
|
+
}
|
|
261
|
+
if (SKIP_PROPS.has(prop)) {
|
|
262
|
+
return target[prop];
|
|
263
|
+
}
|
|
264
|
+
const value = target[prop];
|
|
265
|
+
if (typeof value === "string" && value.length > 0) {
|
|
266
|
+
const node = RED.nodes.getNode(value)?._node;
|
|
267
|
+
return node || value;
|
|
268
|
+
}
|
|
269
|
+
if (Array.isArray(value)) {
|
|
270
|
+
return value.map((item) => {
|
|
271
|
+
if (typeof item === "string") {
|
|
272
|
+
const node = RED.nodes.getNode(item)?._node;
|
|
273
|
+
return node || item;
|
|
274
|
+
}
|
|
275
|
+
if (item && typeof item === "object") {
|
|
276
|
+
return createProxy(item);
|
|
277
|
+
}
|
|
278
|
+
return item;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (value && typeof value === "object") {
|
|
282
|
+
return createProxy(value);
|
|
283
|
+
}
|
|
284
|
+
return value;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
return createProxy(config);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/core/server/nodes/node.ts
|
|
292
|
+
var Node = class {
|
|
293
|
+
static type;
|
|
294
|
+
static category;
|
|
295
|
+
static configSchema;
|
|
296
|
+
static credentialsSchema;
|
|
297
|
+
static settingsSchema;
|
|
298
|
+
static _cachedSettings = null;
|
|
299
|
+
/** @internal */
|
|
300
|
+
static _settings() {
|
|
301
|
+
if (!this.settingsSchema) return;
|
|
302
|
+
const settings = {};
|
|
303
|
+
const prefix = this.type.replace(/-./g, (x) => x[1].toUpperCase());
|
|
304
|
+
for (const [key, prop] of Object.entries(this.settingsSchema.properties)) {
|
|
305
|
+
const settingKey = prefix + key.charAt(0).toUpperCase() + key.slice(1);
|
|
306
|
+
settings[settingKey] = {
|
|
307
|
+
value: prop.default,
|
|
308
|
+
exportable: prop.exportable ?? false
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return settings;
|
|
312
|
+
}
|
|
313
|
+
// NOTE:
|
|
314
|
+
static validateSettings(RED) {
|
|
315
|
+
if (!this.settingsSchema) return;
|
|
316
|
+
RED.log.info("Validating settings");
|
|
317
|
+
const prefix = this.type.replace(/-./g, (x) => x[1].toUpperCase());
|
|
318
|
+
const properties = this.settingsSchema.properties;
|
|
319
|
+
const settings = {};
|
|
320
|
+
for (const key of Object.keys(properties)) {
|
|
321
|
+
const settingKey = prefix + key.charAt(0).toUpperCase() + key.slice(1);
|
|
322
|
+
const value = RED.settings[settingKey];
|
|
323
|
+
if (value !== void 0) {
|
|
324
|
+
settings[key] = value;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
328
|
+
if (settings[key] === void 0) {
|
|
329
|
+
const defaultValue = prop.default ?? prop._default;
|
|
330
|
+
if (defaultValue !== void 0) {
|
|
331
|
+
settings[key] = defaultValue;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
validator2.validate(settings, this.settingsSchema, {
|
|
336
|
+
cacheKey: this.settingsSchema.$id || `${this.type}:settings`,
|
|
337
|
+
throwOnError: true
|
|
338
|
+
});
|
|
339
|
+
this._cachedSettings = settings;
|
|
340
|
+
RED.log.info("Settings are valid");
|
|
341
|
+
}
|
|
342
|
+
RED;
|
|
343
|
+
node;
|
|
344
|
+
context;
|
|
345
|
+
config;
|
|
346
|
+
timers = /* @__PURE__ */ new Set();
|
|
347
|
+
intervals = /* @__PURE__ */ new Set();
|
|
348
|
+
constructor(RED, node, config, credentials) {
|
|
349
|
+
this.RED = RED;
|
|
350
|
+
this.node = node;
|
|
351
|
+
const constructor = this.constructor;
|
|
352
|
+
if (constructor.configSchema) {
|
|
353
|
+
this.log("Validating configs");
|
|
354
|
+
const configResult = validator2.validate(
|
|
355
|
+
config,
|
|
356
|
+
constructor.configSchema,
|
|
357
|
+
{
|
|
358
|
+
cacheKey: constructor.configSchema.$id || `${constructor.type}:configs-schema`,
|
|
359
|
+
throwOnError: false
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
if (!configResult.valid && configResult.errors?.length) {
|
|
363
|
+
this.warn(
|
|
364
|
+
`Config validation errors: ${configResult.errors.map((e) => `${e.instancePath} ${e.message}`).join("; ")}`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
this.config = setupConfigProxy(RED, config);
|
|
369
|
+
if (constructor.credentialsSchema && credentials) {
|
|
370
|
+
this.log("Validating credentials");
|
|
371
|
+
const credResult = validator2.validate(
|
|
372
|
+
credentials,
|
|
373
|
+
constructor.credentialsSchema,
|
|
374
|
+
{
|
|
375
|
+
cacheKey: constructor.credentialsSchema.$id || `${constructor.type}:credentials-schema`,
|
|
376
|
+
throwOnError: false
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
if (!credResult.valid && credResult.errors?.length) {
|
|
380
|
+
this.warn(
|
|
381
|
+
`Credentials validation errors: ${credResult.errors.map((e) => `${e.instancePath} ${e.message}`).join("; ")}`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
i18n(key, substitutions) {
|
|
387
|
+
const nodeType = this.constructor.type;
|
|
388
|
+
return this.RED._(`${nodeType}.${key}`, substitutions);
|
|
389
|
+
}
|
|
390
|
+
setTimeout(fn, ms) {
|
|
391
|
+
const timer = setTimeout(() => {
|
|
392
|
+
this.timers.delete(timer);
|
|
393
|
+
fn();
|
|
394
|
+
}, ms);
|
|
395
|
+
this.timers.add(timer);
|
|
396
|
+
return timer;
|
|
397
|
+
}
|
|
398
|
+
setInterval(fn, ms) {
|
|
399
|
+
const interval = setInterval(fn, ms);
|
|
400
|
+
this.intervals.add(interval);
|
|
401
|
+
return interval;
|
|
402
|
+
}
|
|
403
|
+
clearTimeout(timer) {
|
|
404
|
+
clearTimeout(timer);
|
|
405
|
+
this.timers.delete(timer);
|
|
406
|
+
}
|
|
407
|
+
clearInterval(interval) {
|
|
408
|
+
clearInterval(interval);
|
|
409
|
+
this.intervals.delete(interval);
|
|
410
|
+
}
|
|
411
|
+
// NOTE: typing msg isnt necessary in this case
|
|
412
|
+
resolveTypedInput(typedInput, msg) {
|
|
413
|
+
return new Promise((resolve, reject) => {
|
|
414
|
+
this.RED.util.evaluateNodeProperty(
|
|
415
|
+
typedInput.value,
|
|
416
|
+
typedInput.type,
|
|
417
|
+
this.node,
|
|
418
|
+
msg,
|
|
419
|
+
(error, result) => {
|
|
420
|
+
if (error) {
|
|
421
|
+
reject(error);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (typedInput.type === "node" && result) {
|
|
425
|
+
resolve(result._node ?? result);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
resolve(result);
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's closed again
|
|
434
|
+
/** @internal */
|
|
435
|
+
async _closed(removed) {
|
|
436
|
+
try {
|
|
437
|
+
await Promise.resolve(this.closed?.(removed));
|
|
438
|
+
} finally {
|
|
439
|
+
this.log("clearing timers and intervals");
|
|
440
|
+
this.timers.forEach((t) => clearTimeout(t));
|
|
441
|
+
this.intervals.forEach((i) => clearInterval(i));
|
|
442
|
+
this.timers.clear();
|
|
443
|
+
this.intervals.clear();
|
|
444
|
+
this.log("timers and intervals cleared");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
on(event, callback) {
|
|
448
|
+
this.node.on(event, callback);
|
|
449
|
+
}
|
|
450
|
+
log(msg) {
|
|
451
|
+
this.node.log(msg);
|
|
452
|
+
}
|
|
453
|
+
warn(message) {
|
|
454
|
+
this.node.warn(message);
|
|
455
|
+
}
|
|
456
|
+
error(message, msg) {
|
|
457
|
+
this.node.error(message, msg);
|
|
458
|
+
}
|
|
459
|
+
get id() {
|
|
460
|
+
return this.node.id;
|
|
461
|
+
}
|
|
462
|
+
get name() {
|
|
463
|
+
return this.node.name;
|
|
464
|
+
}
|
|
465
|
+
get z() {
|
|
466
|
+
return this.node.z;
|
|
467
|
+
}
|
|
468
|
+
get credentials() {
|
|
469
|
+
return this.node.credentials;
|
|
470
|
+
}
|
|
471
|
+
get settings() {
|
|
472
|
+
const constructor = this.constructor;
|
|
473
|
+
return constructor._cachedSettings ?? {};
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// src/core/server/nodes/io-node.ts
|
|
478
|
+
var IONode = class extends Node {
|
|
479
|
+
static align;
|
|
480
|
+
static color;
|
|
481
|
+
static labelStyle;
|
|
482
|
+
static paletteLabel;
|
|
483
|
+
static inputs = 0;
|
|
484
|
+
static outputs = 0;
|
|
485
|
+
static inputLabels;
|
|
486
|
+
static outputLabels;
|
|
487
|
+
static inputSchema;
|
|
488
|
+
static outputsSchema;
|
|
489
|
+
static validateInput = false;
|
|
490
|
+
static validateOutput = false;
|
|
491
|
+
_send;
|
|
492
|
+
context;
|
|
493
|
+
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
|
|
494
|
+
/** @internal */
|
|
495
|
+
static _registered(RED) {
|
|
496
|
+
this.validateSettings(RED);
|
|
497
|
+
return this.registered?.(RED);
|
|
498
|
+
}
|
|
499
|
+
constructor(RED, node, config, credentials) {
|
|
500
|
+
super(RED, node, config, credentials);
|
|
501
|
+
const context = node.context();
|
|
502
|
+
const fn = (scope, store) => {
|
|
503
|
+
const target = scope === "global" ? context.global : scope === "flow" ? context.flow : context;
|
|
504
|
+
return setupContext(target, store);
|
|
505
|
+
};
|
|
506
|
+
fn.node = setupContext(context);
|
|
507
|
+
fn.flow = setupContext(context.flow);
|
|
508
|
+
fn.global = setupContext(context.global);
|
|
509
|
+
this.context = fn;
|
|
510
|
+
}
|
|
511
|
+
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
|
|
512
|
+
/** @internal */
|
|
513
|
+
async _input(msg, send) {
|
|
514
|
+
const NodeClass = this.constructor;
|
|
515
|
+
const shouldValidateInput = this.config.validateInput ?? NodeClass.validateInput;
|
|
516
|
+
if (shouldValidateInput && NodeClass.inputSchema) {
|
|
517
|
+
this.log("Validating input");
|
|
518
|
+
validator2.validate(msg, NodeClass.inputSchema, {
|
|
519
|
+
cacheKey: NodeClass.inputSchema.$id || `${NodeClass.type}:input-schema`,
|
|
520
|
+
throwOnError: true
|
|
521
|
+
});
|
|
522
|
+
this.log("Input is valid");
|
|
523
|
+
}
|
|
524
|
+
this._send = send;
|
|
525
|
+
try {
|
|
526
|
+
await Promise.resolve(this.input(msg));
|
|
527
|
+
} finally {
|
|
528
|
+
this._send = void 0;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
send(msg) {
|
|
532
|
+
const NodeClass = this.constructor;
|
|
533
|
+
const shouldValidateOutput = this.config.validateOutput ?? NodeClass.validateOutput;
|
|
534
|
+
if (shouldValidateOutput && NodeClass.outputsSchema) {
|
|
535
|
+
this.log("Validating output");
|
|
536
|
+
const schemas = NodeClass.outputsSchema;
|
|
537
|
+
if (Array.isArray(schemas)) {
|
|
538
|
+
const msgs = msg;
|
|
539
|
+
for (let i = 0; i < schemas.length; i++) {
|
|
540
|
+
if (msgs[i] == null) continue;
|
|
541
|
+
validator2.validate(msgs[i], schemas[i], {
|
|
542
|
+
cacheKey: schemas[i].$id || `${NodeClass.type}:output-schema:${i}`,
|
|
543
|
+
throwOnError: true
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
} else if (Array.isArray(msg)) {
|
|
547
|
+
for (let i = 0; i < msg.length; i++) {
|
|
548
|
+
if (msg[i] == null) continue;
|
|
549
|
+
validator2.validate(msg[i], schemas, {
|
|
550
|
+
cacheKey: schemas.$id || `${NodeClass.type}:output-schema`,
|
|
551
|
+
throwOnError: true
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
validator2.validate(msg, schemas, {
|
|
556
|
+
cacheKey: schemas.$id || `${NodeClass.type}:output-schema`,
|
|
557
|
+
throwOnError: true
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
this.log("Output is valid");
|
|
561
|
+
}
|
|
562
|
+
if (this._send) {
|
|
563
|
+
this._send(msg);
|
|
564
|
+
} else {
|
|
565
|
+
this.node.send(msg);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
status(status) {
|
|
569
|
+
this.node.status(status);
|
|
570
|
+
}
|
|
571
|
+
updateWires(wires) {
|
|
572
|
+
this.node.updateWires(wires);
|
|
573
|
+
}
|
|
574
|
+
receive(msg) {
|
|
575
|
+
this.node.receive(msg);
|
|
576
|
+
}
|
|
577
|
+
get x() {
|
|
578
|
+
return this.node.x;
|
|
579
|
+
}
|
|
580
|
+
get y() {
|
|
581
|
+
return this.node.y;
|
|
582
|
+
}
|
|
583
|
+
get g() {
|
|
584
|
+
return this.node.g;
|
|
585
|
+
}
|
|
586
|
+
get wires() {
|
|
587
|
+
return this.node.wires;
|
|
588
|
+
}
|
|
589
|
+
get credentials() {
|
|
590
|
+
return this.node.credentials;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// src/core/server/nodes/config-node.ts
|
|
595
|
+
var ConfigNode = class extends Node {
|
|
596
|
+
static category = "config";
|
|
597
|
+
context;
|
|
598
|
+
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
|
|
599
|
+
/** @internal */
|
|
600
|
+
static _registered(RED) {
|
|
601
|
+
this.validateSettings(RED);
|
|
602
|
+
return this.registered?.(RED);
|
|
603
|
+
}
|
|
604
|
+
constructor(RED, node, config, credentials) {
|
|
605
|
+
super(RED, node, config, credentials);
|
|
606
|
+
const context = node.context();
|
|
607
|
+
const fn = (scope, store) => {
|
|
608
|
+
const target = scope === "global" ? context.global : context;
|
|
609
|
+
return setupContext(target, store);
|
|
610
|
+
};
|
|
611
|
+
fn.node = setupContext(context);
|
|
612
|
+
fn.global = setupContext(context.global);
|
|
613
|
+
this.context = fn;
|
|
614
|
+
}
|
|
615
|
+
get userIds() {
|
|
616
|
+
return this.config._users;
|
|
617
|
+
}
|
|
618
|
+
get users() {
|
|
619
|
+
return this.userIds.map((id) => this.RED.nodes.getNode(id)?._node).filter((node) => node != null);
|
|
620
|
+
}
|
|
621
|
+
getUser(index) {
|
|
622
|
+
const id = this.userIds[index];
|
|
623
|
+
if (!id) return void 0;
|
|
624
|
+
return this.RED.nodes.getNode(id)?._node;
|
|
625
|
+
}
|
|
626
|
+
get credentials() {
|
|
627
|
+
return this.node.credentials;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// src/core/constants.ts
|
|
632
|
+
var TYPED_INPUT_TYPES = [
|
|
633
|
+
"msg",
|
|
634
|
+
"flow",
|
|
635
|
+
"global",
|
|
636
|
+
"str",
|
|
637
|
+
"num",
|
|
638
|
+
"bool",
|
|
639
|
+
"json",
|
|
640
|
+
"bin",
|
|
641
|
+
"re",
|
|
642
|
+
"jsonata",
|
|
643
|
+
"date",
|
|
644
|
+
"env",
|
|
645
|
+
"node",
|
|
646
|
+
"cred"
|
|
647
|
+
];
|
|
648
|
+
|
|
649
|
+
// src/core/server/schemas/type.ts
|
|
650
|
+
var import_typebox = require("@sinclair/typebox");
|
|
651
|
+
var import_rules = require("ajv/dist/compile/rules");
|
|
652
|
+
function NodeRef(nodeClass, options) {
|
|
653
|
+
return {
|
|
654
|
+
...SchemaType.String({
|
|
655
|
+
description: options?.description || `Reference to ${nodeClass.type}`,
|
|
656
|
+
format: "node-id"
|
|
657
|
+
}),
|
|
658
|
+
"node-type": nodeClass.type,
|
|
659
|
+
...options,
|
|
660
|
+
[import_typebox.Kind]: "NodeRef"
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
function TypedInput(options) {
|
|
664
|
+
return {
|
|
665
|
+
...TypedInputSchema,
|
|
666
|
+
...options,
|
|
667
|
+
[import_typebox.Kind]: "TypedInput"
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
var SchemaType = Object.assign({}, import_typebox.Type, {
|
|
671
|
+
NodeRef,
|
|
672
|
+
TypedInput
|
|
673
|
+
});
|
|
674
|
+
function markNonValidatable(schema) {
|
|
675
|
+
const type = schema.type;
|
|
676
|
+
const hasInvalidType = type !== void 0 && (Array.isArray(type) ? !type.every(import_rules.isJSONType) : !(0, import_rules.isJSONType)(type));
|
|
677
|
+
if (hasInvalidType) {
|
|
678
|
+
schema["skip-validation"] = true;
|
|
679
|
+
if (schema.default !== void 0) {
|
|
680
|
+
schema._default = schema.default;
|
|
681
|
+
delete schema.default;
|
|
682
|
+
}
|
|
683
|
+
delete schema.type;
|
|
684
|
+
}
|
|
685
|
+
if (schema.properties) {
|
|
686
|
+
for (const prop of Object.values(schema.properties)) {
|
|
687
|
+
markNonValidatable(prop);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (schema.items) {
|
|
691
|
+
markNonValidatable(schema.items);
|
|
692
|
+
}
|
|
693
|
+
if (schema.anyOf) {
|
|
694
|
+
schema.anyOf.forEach((s) => markNonValidatable(s));
|
|
695
|
+
}
|
|
696
|
+
if (schema.allOf) {
|
|
697
|
+
schema.allOf.forEach((s) => markNonValidatable(s));
|
|
698
|
+
}
|
|
699
|
+
if (schema.oneOf) {
|
|
700
|
+
schema.oneOf.forEach((s) => markNonValidatable(s));
|
|
701
|
+
}
|
|
702
|
+
return schema;
|
|
703
|
+
}
|
|
704
|
+
function defineSchema(properties, options) {
|
|
705
|
+
const schema = SchemaType.Object(properties, options);
|
|
706
|
+
return markNonValidatable(schema);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/core/server/schemas/base.ts
|
|
710
|
+
var NodeConfigSchema = SchemaType.Object({
|
|
711
|
+
id: SchemaType.String(),
|
|
712
|
+
type: SchemaType.String(),
|
|
713
|
+
name: SchemaType.String(),
|
|
714
|
+
z: SchemaType.Optional(SchemaType.String())
|
|
715
|
+
});
|
|
716
|
+
var ConfigNodeConfigSchema = SchemaType.Object({
|
|
717
|
+
...NodeConfigSchema.properties,
|
|
718
|
+
_users: SchemaType.Array(SchemaType.String())
|
|
719
|
+
});
|
|
720
|
+
var IONodeConfigSchema = SchemaType.Object({
|
|
721
|
+
...NodeConfigSchema.properties,
|
|
722
|
+
wires: SchemaType.Array(
|
|
723
|
+
SchemaType.Array(SchemaType.String(), { default: [] }),
|
|
724
|
+
{
|
|
725
|
+
default: [[]]
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
x: SchemaType.Number(),
|
|
729
|
+
y: SchemaType.Number(),
|
|
730
|
+
g: SchemaType.Optional(SchemaType.String())
|
|
731
|
+
});
|
|
732
|
+
var TypedInputSchema = SchemaType.Object(
|
|
733
|
+
{
|
|
734
|
+
value: SchemaType.Union(
|
|
735
|
+
[
|
|
736
|
+
SchemaType.String(),
|
|
737
|
+
SchemaType.Number(),
|
|
738
|
+
SchemaType.Boolean(),
|
|
739
|
+
SchemaType.Null()
|
|
740
|
+
],
|
|
741
|
+
{
|
|
742
|
+
description: "The actual value entered or selected.",
|
|
743
|
+
default: ""
|
|
744
|
+
}
|
|
745
|
+
),
|
|
746
|
+
type: SchemaType.Union(
|
|
747
|
+
TYPED_INPUT_TYPES.map((type) => SchemaType.Literal(type)),
|
|
748
|
+
{
|
|
749
|
+
description: "The type of the value (string, number, message property, etc.)",
|
|
750
|
+
default: "str"
|
|
751
|
+
}
|
|
752
|
+
)
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
description: "Represents a Node-RED TypedInput value and its type.",
|
|
756
|
+
default: {
|
|
757
|
+
type: "str",
|
|
758
|
+
value: ""
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
// src/core/server/index.ts
|
|
764
|
+
var MIME = {
|
|
765
|
+
".js": "application/javascript",
|
|
766
|
+
".mjs": "application/javascript",
|
|
767
|
+
".css": "text/css",
|
|
768
|
+
".json": "application/json",
|
|
769
|
+
".map": "application/json",
|
|
770
|
+
".png": "image/png",
|
|
771
|
+
".svg": "image/svg+xml"
|
|
772
|
+
};
|
|
773
|
+
var _nrgResourcesRegistered = false;
|
|
774
|
+
function serveNrgResources(RED) {
|
|
775
|
+
if (_nrgResourcesRegistered) return;
|
|
776
|
+
_nrgResourcesRegistered = true;
|
|
777
|
+
const clientDir = import_path.default.resolve(__dirname, "./resources");
|
|
778
|
+
if (!import_fs.default.existsSync(clientDir)) return;
|
|
779
|
+
const httpAdmin = RED.httpAdmin;
|
|
780
|
+
if (!httpAdmin) return;
|
|
781
|
+
httpAdmin.use(function(req, res, next) {
|
|
782
|
+
const prefix = "/nrg/assets/";
|
|
783
|
+
if (!req.path.startsWith(prefix)) return next();
|
|
784
|
+
const reqPath = req.path.slice(prefix.length).replace(/\.\./g, "");
|
|
785
|
+
const filePath = import_path.default.resolve(clientDir, reqPath);
|
|
786
|
+
if (!filePath.startsWith(clientDir)) return next();
|
|
787
|
+
if (!import_fs.default.existsSync(filePath) || !import_fs.default.statSync(filePath).isFile())
|
|
788
|
+
return next();
|
|
789
|
+
const ext = import_path.default.extname(filePath);
|
|
790
|
+
res.setHeader("Content-Type", MIME[ext] ?? "application/octet-stream");
|
|
791
|
+
import_fs.default.createReadStream(filePath).pipe(res);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
async function registerType(RED, NodeClass) {
|
|
795
|
+
const NC = NodeClass;
|
|
796
|
+
RED.log.debug(`Registering Type: ${NC.type}`);
|
|
797
|
+
if (!(NC.prototype instanceof Node)) {
|
|
798
|
+
throw new Error(`${NC.name} must extend IONode or ConfigNode classes`);
|
|
799
|
+
}
|
|
800
|
+
if (!NC.type) {
|
|
801
|
+
throw new Error("type must be provided when registering the node");
|
|
802
|
+
}
|
|
803
|
+
if (NC.color && !/^#[0-9A-Fa-f]{6}$/.test(NC.color)) {
|
|
804
|
+
throw new Error(
|
|
805
|
+
`Invalid color for ${NodeClass.type}: ${NC.color} color must be in hex format`
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
if (NC.inputs !== void 0 && (!Number.isInteger(NC.inputs) || NC.inputs != 0 && NC.inputs != 1)) {
|
|
809
|
+
throw new Error(
|
|
810
|
+
`Invalid number of inputs for ${NodeClass.type}: inputs must be 0 or 1`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
if (NC.outputs !== void 0 && (!Number.isInteger(NC.outputs) || NC.outputs < 0)) {
|
|
814
|
+
throw new Error(
|
|
815
|
+
`Invalid number of outputs for ${NodeClass.type}: outputs must be a positive integer`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
RED.nodes.registerType(
|
|
819
|
+
NC.type,
|
|
820
|
+
function(config) {
|
|
821
|
+
RED.nodes.createNode(this, config);
|
|
822
|
+
const node = new NC(RED, this, config, this.credentials);
|
|
823
|
+
this._node = node;
|
|
824
|
+
const createdPromise = Promise.resolve(node.created?.()).catch(
|
|
825
|
+
(error) => {
|
|
826
|
+
this.error("Error during created hook: " + error.message);
|
|
827
|
+
throw error;
|
|
828
|
+
}
|
|
829
|
+
);
|
|
830
|
+
this.on(
|
|
831
|
+
"input",
|
|
832
|
+
async (msg, send, done) => {
|
|
833
|
+
try {
|
|
834
|
+
await createdPromise;
|
|
835
|
+
} catch {
|
|
836
|
+
done(new Error("Node failed to initialize"));
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
this.log("Calling input");
|
|
841
|
+
await Promise.resolve(node._input(msg, send));
|
|
842
|
+
done();
|
|
843
|
+
this.log("Input processed");
|
|
844
|
+
} catch (error) {
|
|
845
|
+
if (error instanceof Error) {
|
|
846
|
+
this.error("Error while processing input: " + error.message, msg);
|
|
847
|
+
done(error);
|
|
848
|
+
} else {
|
|
849
|
+
this.error("Unknown error occurred during input handling", msg);
|
|
850
|
+
done(new Error("Unknown error during input handling"));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
);
|
|
855
|
+
this.on(
|
|
856
|
+
"close",
|
|
857
|
+
async (removed, done) => {
|
|
858
|
+
try {
|
|
859
|
+
this.log("Calling closed");
|
|
860
|
+
await Promise.resolve(node._closed(removed));
|
|
861
|
+
this.log("Node was closed");
|
|
862
|
+
done();
|
|
863
|
+
} catch (error) {
|
|
864
|
+
if (error instanceof Error) {
|
|
865
|
+
this.error("Error while closing node: " + error.message);
|
|
866
|
+
done(error);
|
|
867
|
+
} else {
|
|
868
|
+
this.error("Unknown error occurred while closing node");
|
|
869
|
+
done(new Error("Unknown error occurred while closing node"));
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
);
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
credentials: NC.credentialsSchema ? getCredentialsFromSchema(NC.credentialsSchema) : {},
|
|
877
|
+
settings: NC._settings?.()
|
|
878
|
+
}
|
|
879
|
+
);
|
|
880
|
+
await Promise.resolve(NC._registered?.(RED));
|
|
881
|
+
RED.log.debug(`Type registered: ${NC.type}`);
|
|
882
|
+
}
|
|
883
|
+
function registerTypes(nodes) {
|
|
884
|
+
const fn = async function(RED) {
|
|
885
|
+
initValidator(RED);
|
|
886
|
+
serveNrgResources(RED);
|
|
887
|
+
try {
|
|
888
|
+
RED.log.info("Registering node types in series");
|
|
889
|
+
for (const NodeClass of nodes) {
|
|
890
|
+
await registerType(RED, NodeClass);
|
|
891
|
+
}
|
|
892
|
+
RED.log.info("All node types registered in series");
|
|
893
|
+
} catch (error) {
|
|
894
|
+
RED.log.error("Error registering node types:", error);
|
|
895
|
+
throw error;
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
fn.nodes = nodes;
|
|
899
|
+
return fn;
|
|
900
|
+
}
|
|
901
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
902
|
+
0 && (module.exports = {
|
|
903
|
+
ConfigNode,
|
|
904
|
+
IONode,
|
|
905
|
+
Node,
|
|
906
|
+
SchemaType,
|
|
907
|
+
defineSchema,
|
|
908
|
+
registerType,
|
|
909
|
+
registerTypes
|
|
910
|
+
});
|