@ti-engine/web-framework 1.18.1 → 1.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.env +4 -4
  2. package/CHANGELOG.md +362 -341
  3. package/README.md +73 -40
  4. package/bin/build/post-install.js +18 -18
  5. package/bin/localization/web-server-labels.json +27 -27
  6. package/bin/static/.well-known/appspecific/com.chrome.devtools.json +5 -5
  7. package/bin/static/fragments/components/component-notification-bar.html +21 -21
  8. package/bin/static/fragments/components/component-sidebar-flyout.html +36 -36
  9. package/bin/static/fragments/components/component-sidebar.html +33 -33
  10. package/bin/static/fragments/components/component-tooltip.html +10 -10
  11. package/bin/static/fragments/components/component-topbar.html +5 -5
  12. package/bin/static/fragments/frame-administration.html +2 -2
  13. package/bin/static/fragments/frame-application.html +18 -18
  14. package/bin/static/fragments/frame-dashboard.html +2 -2
  15. package/bin/static/fragments/frame-login.html +119 -119
  16. package/bin/static/fragments/frame-not-found.html +2 -2
  17. package/bin/static/fragments/frame-profile.html +2 -2
  18. package/bin/static/index.html +22 -22
  19. package/bin/static/scripts/ti-charts.js +1591 -1591
  20. package/bin/static/scripts/ti-framework.css +3194 -3194
  21. package/bin/static/scripts/ti-framework.js +1427 -1427
  22. package/bin/static/scripts/ti-theme-black-glass.css +216 -216
  23. package/bin/static/scripts/ti-theme-daylight.css +87 -87
  24. package/bin/web-app-manager.js +663 -663
  25. package/bin/web-server.js +936 -744
  26. package/bin/web-server.json +48 -48
  27. package/components/admin-config-handlers.js +92 -92
  28. package/components/auth-manager.js +441 -441
  29. package/components/authorization.js +135 -135
  30. package/components/config-change-notifier.js +98 -98
  31. package/components/config-registry.js +260 -260
  32. package/components/config-service.js +360 -360
  33. package/components/config-store.js +246 -246
  34. package/components/definitions.types.js +26 -26
  35. package/components/session-store.js +110 -110
  36. package/components/user.js +132 -132
  37. package/components/web-config-env.js +19 -2
  38. package/components/web-handlers.js +800 -800
  39. package/package.json +76 -67
@@ -1,260 +1,260 @@
1
- /*
2
- * The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
3
- * Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
4
- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6
- * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
7
- */
8
-
9
- const Ajv = require( "ajv" );
10
- const exceptions = require( "@ti-engine/core/exceptions" );
11
-
12
- /**
13
- * Converts an ajv `instancePath` (a JSON Pointer, e.g. `/competencies/E1-1/name`) into the dot/bracket data path this
14
- * registry has always exposed on schema issues (e.g. `.competencies.E1-1.name`, array indices rendered as `[0]`).
15
- * ajv 8 renamed `dataPath` (dot style) to `instancePath` (JSON Pointer); normalizing here keeps the public
16
- * {@link ConfigValidationIssue} `path` contract stable across the ajv 6 → 8 upgrade.
17
- *
18
- * @param {string} [instancePath] The ajv instance path (JSON Pointer), or empty for the document root.
19
- * @returns {string} The dot/bracket data path, or "" for the document root.
20
- */
21
- const instancePathToDataPath = ( instancePath ) => {
22
- if ( !instancePath ) {
23
- return "";
24
- }
25
- return instancePath
26
- .split( "/" )
27
- .slice( 1 )
28
- .map( ( segment ) => {
29
- const key = segment.replace( /~1/g, "/" ).replace( /~0/g, "~" );
30
- return ( /^\d+$/.test( key ) ) ? `[${ key }]` : `.${ key }`;
31
- } )
32
- .join( "" );
33
- };
34
-
35
- /**
36
- * @typedef {Object} ConfigValidationIssue
37
- * @property {string} path A JSON pointer / data path to the offending value (e.g. ".competencies.E1-1.name"), or "".
38
- * @property {string} message Human-readable problem description.
39
- * @property {string} code "schema" for JSON-Schema failures, or a validator-supplied code (default "semantic").
40
- * @property {Object} [params] Optional structured details (e.g. ajv params).
41
- */
42
-
43
- /**
44
- * The cross-document read context passed to every {@link SemanticValidator}, built fresh for each
45
- * {@link ConfigService#applyEdits} call.
46
- *
47
- * @typedef {Object} ValidatorContext
48
- * @property {function(string): Promise<*>} getConfig Resolves the *pending* value of `key` when it is part of the
49
- * current edit batch, otherwise its current committed value. Lets a validator check a sibling document's
50
- * post-edit state — but calling this for the document being validated itself just returns the same incoming
51
- * value already passed as the validator's first argument, not its prior state.
52
- * @property {function(string): Promise<*>} getStoredConfig Always resolves the current committed value of `key`,
53
- * even when `key` is the document currently under validation. Use this to compare a document against its own
54
- * previous state (e.g. detecting an edit that should have bumped a version marker).
55
- */
56
-
57
- /**
58
- * @typedef {function(Object, ValidatorContext): (ConfigValidationIssue[]|Promise<ConfigValidationIssue[]>)} SemanticValidator
59
- * A semantic validator receives the candidate value and a {@link ValidatorContext} and returns the issues it found
60
- * (empty array = OK). May be async.
61
- */
62
-
63
- /**
64
- * Registry of editable configuration *documents* and the gate that validates a candidate value against a
65
- * document's JSON Schema (ajv) plus its semantic validators. The framework stays domain-agnostic: an application
66
- * registers its config documents (schemas, validators, defaults, editor metadata) at startup; this component knows
67
- * only "validated, versioned JSON documents". Validation must pass *before* {@link ConfigStore#saveChangeSet}.
68
- *
69
- * @class ConfigRegistry
70
- * @public
71
- */
72
- class ConfigRegistry {
73
-
74
- #ajv;
75
- #registrations = new Map();
76
-
77
- constructor() {
78
- // ajv 8 speaks Draft-07 by default; our schema files annotate Draft 2020-12 only for editor support, so we
79
- // strip that annotation (see #stripUnsupportedMeta) and skip meta-validation.
80
- this.#ajv = new Ajv( { allErrors: true, schemaId: "$id", meta: true, validateSchema: false } );
81
- }
82
-
83
- /* Public interface */
84
-
85
- /**
86
- * Registers an editable configuration document.
87
- *
88
- * @method
89
- * @param {string} configKey Stable identifier for the document (also the ConfigStore key).
90
- * @param {Object} definition
91
- * @param {Object} definition.schema A JSON Schema for the document.
92
- * @param {SemanticValidator[]} [definition.validators] Cross-cutting/semantic checks beyond the schema.
93
- * @param {Object} [definition.defaultValue] The bootstrap default (seeds an empty store).
94
- * @param {Object} [definition.metadata] Editor metadata (label, editor type, group, risk class, …).
95
- * @returns {ConfigRegistry} this (chainable)
96
- * @throws {TiException.E_WEB_INVALID_REQUEST_PARAMETERS} On missing key/schema.
97
- * @public
98
- */
99
- register( configKey, definition ) {
100
- const { schema, validators = [], defaultValue, metadata = {} } = definition || {};
101
- if ( !configKey || !schema || typeof schema !== "object" ) {
102
- throw exceptions.raise( exceptions.exceptionCode.E_WEB_INVALID_REQUEST_PARAMETERS, { reason: "invalid-config-registration", configKey: configKey } );
103
- }
104
- const prepared = this.#stripUnsupportedMeta( schema );
105
- this.addSchema( prepared );
106
- this.#registrations.set( configKey, {
107
- schema: prepared,
108
- validators: Array.isArray( validators ) ? validators : [],
109
- defaultValue: defaultValue,
110
- metadata: metadata || {},
111
- compiled: null
112
- } );
113
- return this;
114
- }
115
-
116
- /**
117
- * Adds a schema that is referenced (via `$ref`/`$id`) by document schemas but is not itself a document.
118
- *
119
- * @method
120
- * @param {Object} schema
121
- * @returns {ConfigRegistry} this (chainable)
122
- * @public
123
- */
124
- addSchema( schema ) {
125
- const prepared = this.#stripUnsupportedMeta( schema );
126
- if ( prepared && prepared.$id && !this.#ajv.getSchema( prepared.$id ) ) {
127
- this.#ajv.addSchema( prepared );
128
- }
129
- return this;
130
- }
131
-
132
- /**
133
- * @method
134
- * @param {string} configKey
135
- * @returns {boolean}
136
- * @public
137
- */
138
- has( configKey ) {
139
- return this.#registrations.has( configKey );
140
- }
141
-
142
- /**
143
- * @method
144
- * @returns {string[]} All registered configuration keys.
145
- * @public
146
- */
147
- list() {
148
- return Array.from( this.#registrations.keys() );
149
- }
150
-
151
- /**
152
- * @method
153
- * @param {string} configKey
154
- * @returns {Object|undefined} The editor metadata registered for the document.
155
- * @public
156
- */
157
- metadataFor( configKey ) {
158
- const registration = this.#registrations.get( configKey );
159
- return registration ? registration.metadata : undefined;
160
- }
161
-
162
- /**
163
- * @method
164
- * @param {string} configKey
165
- * @returns {Object|undefined} The bootstrap default value registered for the document.
166
- * @public
167
- */
168
- getDefault( configKey ) {
169
- const registration = this.#registrations.get( configKey );
170
- return registration ? registration.defaultValue : undefined;
171
- }
172
-
173
- /**
174
- * Validates a candidate value for a registered document: JSON Schema first, then the semantic validators.
175
- * Resolves with `{ valid, errors }` (errors is an array of {@link ConfigValidationIssue}).
176
- *
177
- * @method
178
- * @param {string} configKey
179
- * @param {Object} value
180
- * @param {ValidatorContext} [context] Passed to each semantic validator.
181
- * @returns {Promise<{valid: boolean, errors: ConfigValidationIssue[]}>}
182
- * @throws {TiException.E_WEB_INVALID_REQUEST_PARAMETERS} If the document is not registered.
183
- * @public
184
- */
185
- validate( configKey, value, context = {} ) {
186
- const registration = this.#registrations.get( configKey );
187
- if ( !registration ) {
188
- return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_WEB_INVALID_REQUEST_PARAMETERS, { reason: "unknown-config", configKey: configKey } ) );
189
- }
190
-
191
- const errors = [];
192
- const validateSchema = this.#schemaValidator( registration );
193
- if ( !validateSchema( value ) ) {
194
- for ( const error of ( validateSchema.errors || [] ) ) {
195
- errors.push( { path: error.dataPath || instancePathToDataPath( error.instancePath ), message: error.message || "schema violation", code: "schema", params: error.params } );
196
- }
197
- }
198
-
199
- return Promise.all( registration.validators.map( ( validator ) => {
200
- return Promise.resolve( validator( value, context ) ).then( ( issues ) => ( Array.isArray( issues ) ? issues : [] ) );
201
- } ) ).then( ( results ) => {
202
- for ( const issues of results ) {
203
- for ( const issue of issues ) {
204
- errors.push( this.#normalizeIssue( issue ) );
205
- }
206
- }
207
- return { valid: errors.length === 0, errors: errors };
208
- } );
209
- }
210
-
211
- /* Private interface */
212
-
213
- /**
214
- * @method
215
- * @param {Object} registration
216
- * @returns {Function} The compiled (and cached) ajv validate function for the document's schema.
217
- * @private
218
- */
219
- #schemaValidator( registration ) {
220
- if ( !registration.compiled ) {
221
- registration.compiled = registration.schema.$id
222
- ? this.#ajv.getSchema( registration.schema.$id )
223
- : this.#ajv.compile( registration.schema );
224
- }
225
- return registration.compiled;
226
- }
227
-
228
- /**
229
- * @method
230
- * @param {Object} schema
231
- * @returns {Object} A shallow copy without the ajv-6-incompatible Draft-2020-12 `$schema` annotation.
232
- * @private
233
- */
234
- #stripUnsupportedMeta( schema ) {
235
- if ( schema && schema.$schema && String( schema.$schema ).includes( "draft/2020-12" ) ) {
236
- const clone = { ...schema };
237
- delete clone.$schema;
238
- return clone;
239
- }
240
- return schema;
241
- }
242
-
243
- /**
244
- * @method
245
- * @param {ConfigValidationIssue|string} issue
246
- * @returns {ConfigValidationIssue}
247
- * @private
248
- */
249
- #normalizeIssue( issue ) {
250
- if ( typeof issue === "string" ) {
251
- return { path: "", message: issue, code: "semantic" };
252
- }
253
- return { path: issue.path || "", message: issue.message || "validation failed", code: issue.code || "semantic", params: issue.params };
254
- }
255
-
256
- }
257
-
258
- const instance = new ConfigRegistry();
259
- module.exports = ConfigRegistry;
260
- module.exports.instance = instance;
1
+ /*
2
+ * The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
3
+ * Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
4
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6
+ * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
7
+ */
8
+
9
+ const Ajv = require( "ajv" );
10
+ const exceptions = require( "@ti-engine/core/exceptions" );
11
+
12
+ /**
13
+ * Converts an ajv `instancePath` (a JSON Pointer, e.g. `/competencies/E1-1/name`) into the dot/bracket data path this
14
+ * registry has always exposed on schema issues (e.g. `.competencies.E1-1.name`, array indices rendered as `[0]`).
15
+ * ajv 8 renamed `dataPath` (dot style) to `instancePath` (JSON Pointer); normalizing here keeps the public
16
+ * {@link ConfigValidationIssue} `path` contract stable across the ajv 6 → 8 upgrade.
17
+ *
18
+ * @param {string} [instancePath] The ajv instance path (JSON Pointer), or empty for the document root.
19
+ * @returns {string} The dot/bracket data path, or "" for the document root.
20
+ */
21
+ const instancePathToDataPath = ( instancePath ) => {
22
+ if ( !instancePath ) {
23
+ return "";
24
+ }
25
+ return instancePath
26
+ .split( "/" )
27
+ .slice( 1 )
28
+ .map( ( segment ) => {
29
+ const key = segment.replace( /~1/g, "/" ).replace( /~0/g, "~" );
30
+ return ( /^\d+$/.test( key ) ) ? `[${ key }]` : `.${ key }`;
31
+ } )
32
+ .join( "" );
33
+ };
34
+
35
+ /**
36
+ * @typedef {Object} ConfigValidationIssue
37
+ * @property {string} path A JSON pointer / data path to the offending value (e.g. ".competencies.E1-1.name"), or "".
38
+ * @property {string} message Human-readable problem description.
39
+ * @property {string} code "schema" for JSON-Schema failures, or a validator-supplied code (default "semantic").
40
+ * @property {Object} [params] Optional structured details (e.g. ajv params).
41
+ */
42
+
43
+ /**
44
+ * The cross-document read context passed to every {@link SemanticValidator}, built fresh for each
45
+ * {@link ConfigService#applyEdits} call.
46
+ *
47
+ * @typedef {Object} ValidatorContext
48
+ * @property {function(string): Promise<*>} getConfig Resolves the *pending* value of `key` when it is part of the
49
+ * current edit batch, otherwise its current committed value. Lets a validator check a sibling document's
50
+ * post-edit state — but calling this for the document being validated itself just returns the same incoming
51
+ * value already passed as the validator's first argument, not its prior state.
52
+ * @property {function(string): Promise<*>} getStoredConfig Always resolves the current committed value of `key`,
53
+ * even when `key` is the document currently under validation. Use this to compare a document against its own
54
+ * previous state (e.g. detecting an edit that should have bumped a version marker).
55
+ */
56
+
57
+ /**
58
+ * @typedef {(value: Object, context: ValidatorContext) => ConfigValidationIssue[]|Promise<ConfigValidationIssue[]>} SemanticValidator
59
+ * A semantic validator receives the candidate value and a {@link ValidatorContext} and returns the issues it found
60
+ * (empty array = OK). May be async.
61
+ */
62
+
63
+ /**
64
+ * Registry of editable configuration *documents* and the gate that validates a candidate value against a
65
+ * document's JSON Schema (ajv) plus its semantic validators. The framework stays domain-agnostic: an application
66
+ * registers its config documents (schemas, validators, defaults, editor metadata) at startup; this component knows
67
+ * only "validated, versioned JSON documents". Validation must pass *before* {@link ConfigStore#saveChangeSet}.
68
+ *
69
+ * @class ConfigRegistry
70
+ * @public
71
+ */
72
+ class ConfigRegistry {
73
+
74
+ #ajv;
75
+ #registrations = new Map();
76
+
77
+ constructor() {
78
+ // ajv 8 speaks Draft-07 by default; our schema files annotate Draft 2020-12 only for editor support, so we
79
+ // strip that annotation (see #stripUnsupportedMeta) and skip meta-validation.
80
+ this.#ajv = new Ajv( { allErrors: true, schemaId: "$id", meta: true, validateSchema: false } );
81
+ }
82
+
83
+ /* Public interface */
84
+
85
+ /**
86
+ * Registers an editable configuration document.
87
+ *
88
+ * @method
89
+ * @param {string} configKey Stable identifier for the document (also the ConfigStore key).
90
+ * @param {Object} definition
91
+ * @param {Object} definition.schema A JSON Schema for the document.
92
+ * @param {SemanticValidator[]} [definition.validators] Cross-cutting/semantic checks beyond the schema.
93
+ * @param {Object} [definition.defaultValue] The bootstrap default (seeds an empty store).
94
+ * @param {Object} [definition.metadata] Editor metadata (label, editor type, group, risk class, …).
95
+ * @returns {ConfigRegistry} this (chainable)
96
+ * @throws {TiException.E_WEB_INVALID_REQUEST_PARAMETERS} On missing key/schema.
97
+ * @public
98
+ */
99
+ register( configKey, definition ) {
100
+ const { schema, validators = [], defaultValue, metadata = {} } = definition || {};
101
+ if ( !configKey || !schema || typeof schema !== "object" ) {
102
+ throw exceptions.raise( exceptions.exceptionCode.E_WEB_INVALID_REQUEST_PARAMETERS, { reason: "invalid-config-registration", configKey: configKey } );
103
+ }
104
+ const prepared = this.#stripUnsupportedMeta( schema );
105
+ this.addSchema( prepared );
106
+ this.#registrations.set( configKey, {
107
+ schema: prepared,
108
+ validators: Array.isArray( validators ) ? validators : [],
109
+ defaultValue: defaultValue,
110
+ metadata: metadata || {},
111
+ compiled: null
112
+ } );
113
+ return this;
114
+ }
115
+
116
+ /**
117
+ * Adds a schema that is referenced (via `$ref`/`$id`) by document schemas but is not itself a document.
118
+ *
119
+ * @method
120
+ * @param {Object} schema
121
+ * @returns {ConfigRegistry} this (chainable)
122
+ * @public
123
+ */
124
+ addSchema( schema ) {
125
+ const prepared = this.#stripUnsupportedMeta( schema );
126
+ if ( prepared && prepared.$id && !this.#ajv.getSchema( prepared.$id ) ) {
127
+ this.#ajv.addSchema( prepared );
128
+ }
129
+ return this;
130
+ }
131
+
132
+ /**
133
+ * @method
134
+ * @param {string} configKey
135
+ * @returns {boolean}
136
+ * @public
137
+ */
138
+ has( configKey ) {
139
+ return this.#registrations.has( configKey );
140
+ }
141
+
142
+ /**
143
+ * @method
144
+ * @returns {string[]} All registered configuration keys.
145
+ * @public
146
+ */
147
+ list() {
148
+ return Array.from( this.#registrations.keys() );
149
+ }
150
+
151
+ /**
152
+ * @method
153
+ * @param {string} configKey
154
+ * @returns {Object|undefined} The editor metadata registered for the document.
155
+ * @public
156
+ */
157
+ metadataFor( configKey ) {
158
+ const registration = this.#registrations.get( configKey );
159
+ return registration ? registration.metadata : undefined;
160
+ }
161
+
162
+ /**
163
+ * @method
164
+ * @param {string} configKey
165
+ * @returns {Object|undefined} The bootstrap default value registered for the document.
166
+ * @public
167
+ */
168
+ getDefault( configKey ) {
169
+ const registration = this.#registrations.get( configKey );
170
+ return registration ? registration.defaultValue : undefined;
171
+ }
172
+
173
+ /**
174
+ * Validates a candidate value for a registered document: JSON Schema first, then the semantic validators.
175
+ * Resolves with `{ valid, errors }` (errors is an array of {@link ConfigValidationIssue}).
176
+ *
177
+ * @method
178
+ * @param {string} configKey
179
+ * @param {Object} value
180
+ * @param {ValidatorContext} [context] Passed to each semantic validator.
181
+ * @returns {Promise<{valid: boolean, errors: ConfigValidationIssue[]}>}
182
+ * @throws {TiException.E_WEB_INVALID_REQUEST_PARAMETERS} If the document is not registered.
183
+ * @public
184
+ */
185
+ validate( configKey, value, context = {} ) {
186
+ const registration = this.#registrations.get( configKey );
187
+ if ( !registration ) {
188
+ return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_WEB_INVALID_REQUEST_PARAMETERS, { reason: "unknown-config", configKey: configKey } ) );
189
+ }
190
+
191
+ const errors = [];
192
+ const validateSchema = this.#schemaValidator( registration );
193
+ if ( !validateSchema( value ) ) {
194
+ for ( const error of ( validateSchema.errors || [] ) ) {
195
+ errors.push( { path: error.dataPath || instancePathToDataPath( error.instancePath ), message: error.message || "schema violation", code: "schema", params: error.params } );
196
+ }
197
+ }
198
+
199
+ return Promise.all( registration.validators.map( ( validator ) => {
200
+ return Promise.resolve( validator( value, context ) ).then( ( issues ) => ( Array.isArray( issues ) ? issues : [] ) );
201
+ } ) ).then( ( results ) => {
202
+ for ( const issues of results ) {
203
+ for ( const issue of issues ) {
204
+ errors.push( this.#normalizeIssue( issue ) );
205
+ }
206
+ }
207
+ return { valid: errors.length === 0, errors: errors };
208
+ } );
209
+ }
210
+
211
+ /* Private interface */
212
+
213
+ /**
214
+ * @method
215
+ * @param {Object} registration
216
+ * @returns {Function} The compiled (and cached) ajv validate function for the document's schema.
217
+ * @private
218
+ */
219
+ #schemaValidator( registration ) {
220
+ if ( !registration.compiled ) {
221
+ registration.compiled = registration.schema.$id
222
+ ? this.#ajv.getSchema( registration.schema.$id )
223
+ : this.#ajv.compile( registration.schema );
224
+ }
225
+ return registration.compiled;
226
+ }
227
+
228
+ /**
229
+ * @method
230
+ * @param {Object} schema
231
+ * @returns {Object} A shallow copy without the ajv-6-incompatible Draft-2020-12 `$schema` annotation.
232
+ * @private
233
+ */
234
+ #stripUnsupportedMeta( schema ) {
235
+ if ( schema && schema.$schema && String( schema.$schema ).includes( "draft/2020-12" ) ) {
236
+ const clone = { ...schema };
237
+ delete clone.$schema;
238
+ return clone;
239
+ }
240
+ return schema;
241
+ }
242
+
243
+ /**
244
+ * @method
245
+ * @param {ConfigValidationIssue|string} issue
246
+ * @returns {ConfigValidationIssue}
247
+ * @private
248
+ */
249
+ #normalizeIssue( issue ) {
250
+ if ( typeof issue === "string" ) {
251
+ return { path: "", message: issue, code: "semantic" };
252
+ }
253
+ return { path: issue.path || "", message: issue.message || "validation failed", code: issue.code || "semantic", params: issue.params };
254
+ }
255
+
256
+ }
257
+
258
+ const instance = new ConfigRegistry();
259
+ module.exports = ConfigRegistry;
260
+ module.exports.instance = instance;