xdbc 1.0.207 → 1.0.209
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/Assessment.html +350 -0
- package/Assessment.md +507 -51
- package/CHANGELOG.md +55 -0
- package/CONTRIBUTING.md +129 -17
- package/README.md +373 -71
- package/SECURITY.md +60 -18
- package/SUPPORT.md +65 -0
- package/__tests__/DBC/DEFINED.test.ts +53 -0
- package/__tests__/DBC/Decorators.test.ts +365 -0
- package/__tests__/DBC/GREATER.test.ts +8 -6
- package/__tests__/DBC/HasAttribute.test.ts +56 -0
- package/__tests__/DBC/IF.test.ts +52 -0
- package/__tests__/DBC/JSON.Parse.test.ts +1 -1
- package/__tests__/DBC/OR.test.ts +1 -1
- package/__tests__/DBC/REGEX.test.ts +1 -1
- package/__tests__/DBC/TYPE.test.ts +1 -1
- package/__tests__/DBC/UNDEFINED.test.ts +45 -0
- package/__tests__/DBC/ZOD.test.ts +54 -0
- package/jest.config.js +21 -0
- package/package.json +4 -5
- package/src/DBC/AE.ts +10 -6
- package/src/DBC/COMPARISON/GREATER.ts +11 -7
- package/src/DBC/COMPARISON/GREATER_OR_EQUAL.ts +14 -10
- package/src/DBC/COMPARISON/LESS.ts +14 -10
- package/src/DBC/COMPARISON/LESS_OR_EQUAL.ts +14 -10
- package/src/DBC/COMPARISON.ts +20 -43
- package/src/DBC/DEFINED.ts +4 -23
- package/src/DBC/EQ/DIFFERENT.ts +21 -56
- package/src/DBC/EQ.ts +7 -26
- package/src/DBC/HasAttribute.ts +9 -26
- package/src/DBC/IF.ts +8 -27
- package/src/DBC/INSTANCE.ts +5 -22
- package/src/DBC/JSON.OP.ts +4 -34
- package/src/DBC/JSON.Parse.ts +5 -25
- package/src/DBC/OR.ts +5 -14
- package/src/DBC/REGEX.ts +41 -40
- package/src/DBC/TYPE.ts +6 -25
- package/src/DBC/UNDEFINED.ts +3 -22
- package/src/DBC/ZOD.ts +10 -27
- package/src/DBC.ts +223 -55
- package/tsconfig.json +7 -4
- package/tsconfig.test.json +12 -0
- package/.parcel-cache/bf96c58b6061a62a-BundleGraph +0 -0
- package/.parcel-cache/d7c812d65aeeac59-AssetGraph +0 -0
- package/.parcel-cache/data.mdb +0 -0
- package/.parcel-cache/e81759c1f106a17f-RequestGraph +0 -0
- package/.parcel-cache/fe0db3c4eb428be2-AssetGraph +0 -0
- package/.parcel-cache/lock.mdb +0 -0
- package/.parcel-cache/snapshot-e81759c1f106a17f.txt +0 -4609
- package/dist/DBC/AE.js +0 -173
- package/dist/DBC/COMPARISON/GREATER.js +0 -21
- package/dist/DBC/COMPARISON/GREATER_OR_EQUAL.js +0 -21
- package/dist/DBC/COMPARISON/LESS.js +0 -21
- package/dist/DBC/COMPARISON/LESS_OR_EQUAL.js +0 -21
- package/dist/DBC/COMPARISON.js +0 -99
- package/dist/DBC/DEFINED.js +0 -99
- package/dist/DBC/EQ/DIFFERENT.js +0 -21
- package/dist/DBC/EQ.js +0 -100
- package/dist/DBC/GREATER.js +0 -99
- package/dist/DBC/HasAttribute.js +0 -108
- package/dist/DBC/IF.js +0 -99
- package/dist/DBC/INSTANCE.js +0 -93
- package/dist/DBC/JSON.OP.js +0 -133
- package/dist/DBC/JSON.Parse.js +0 -114
- package/dist/DBC/OR.js +0 -113
- package/dist/DBC/REGEX.js +0 -110
- package/dist/DBC/TYPE.js +0 -87
- package/dist/DBC/ZOD.js +0 -114
- package/dist/DBC.js +0 -336
- package/dist/Demo.js +0 -290
- package/dist/Test.html +0 -18
- package/dist/bundle.js +0 -2064
- package/dist/index.html +0 -18
- package/jest.config.ts +0 -20
- package/xpackage-lock.json +0 -122
package/src/DBC.ts
CHANGED
|
@@ -5,21 +5,32 @@
|
|
|
5
5
|
* Maintainer: Callari, Salvatore (XDBC@WaXCode.net) */
|
|
6
6
|
export class DBC {
|
|
7
7
|
// #region Internal caches.
|
|
8
|
+
private static readonly MAX_CACHE_SIZE = 1000;
|
|
8
9
|
private static dbcCache: Map<string, DBC> = new Map();
|
|
9
10
|
private static pathTokenCache: Map<string, string[]> = new Map();
|
|
11
|
+
/** Evicts the oldest entry if the cache exceeds the maximum size. */
|
|
12
|
+
private static evictIfNeeded<K, V>(cache: Map<K, V>): void {
|
|
13
|
+
if (cache.size > DBC.MAX_CACHE_SIZE) {
|
|
14
|
+
const oldest = cache.keys().next().value;
|
|
15
|
+
if (oldest !== undefined) cache.delete(oldest);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
10
18
|
private static getHost(): unknown {
|
|
11
19
|
return typeof window !== "undefined" ? window : globalThis;
|
|
12
20
|
}
|
|
13
|
-
private static getDBC(dbc: string | undefined): DBC {
|
|
21
|
+
private static getDBC(dbc: string | DBC | undefined): DBC {
|
|
22
|
+
if (dbc instanceof DBC) return dbc;
|
|
14
23
|
const path = dbc ?? "WaXCode.DBC";
|
|
15
24
|
if (DBC.dbcCache.has(path)) {
|
|
16
|
-
return DBC.dbcCache.get(path);
|
|
25
|
+
return DBC.dbcCache.get(path) as DBC;
|
|
17
26
|
}
|
|
18
27
|
const resolved = DBC.resolveDBCPath(DBC.getHost(), path);
|
|
19
28
|
if (resolved) {
|
|
29
|
+
DBC.evictIfNeeded(DBC.dbcCache);
|
|
20
30
|
DBC.dbcCache.set(path, resolved);
|
|
31
|
+
return resolved;
|
|
21
32
|
}
|
|
22
|
-
|
|
33
|
+
throw new Error(`[XDBC] DBC instance not found at path "${path}". Ensure a DBC instance is registered there.`);
|
|
23
34
|
}
|
|
24
35
|
// #endregion Internal caches.
|
|
25
36
|
// #region Parameter-value requests.
|
|
@@ -61,10 +72,11 @@ export class DBC {
|
|
|
61
72
|
const key = DBC.getRequestKey(target, methodName);
|
|
62
73
|
|
|
63
74
|
if (DBC.paramValueRequests.has(key)) {
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
const paramMap = DBC.paramValueRequests.get(key)!;
|
|
76
|
+
if (paramMap.has(index)) {
|
|
77
|
+
paramMap.get(index)!.push(receptor);
|
|
66
78
|
} else {
|
|
67
|
-
|
|
79
|
+
paramMap.set(index, new Array<(value: unknown) => undefined>(receptor));
|
|
68
80
|
}
|
|
69
81
|
} else {
|
|
70
82
|
DBC.paramValueRequests.set(
|
|
@@ -103,9 +115,10 @@ export class DBC {
|
|
|
103
115
|
const key = DBC.getRequestKey(actualTarget, propertyKey);
|
|
104
116
|
|
|
105
117
|
if (DBC.paramValueRequests.has(key)) {
|
|
106
|
-
|
|
118
|
+
const paramMap = DBC.paramValueRequests.get(key)!;
|
|
119
|
+
for (const index of paramMap.keys()) {
|
|
107
120
|
if (index < args.length) {
|
|
108
|
-
for (const receptor of
|
|
121
|
+
for (const receptor of paramMap.get(index)!) {
|
|
109
122
|
receptor(args[index]);
|
|
110
123
|
}
|
|
111
124
|
}
|
|
@@ -137,8 +150,10 @@ export class DBC {
|
|
|
137
150
|
path: string | undefined = undefined,
|
|
138
151
|
dbc = "WaXCode.DBC",
|
|
139
152
|
) {
|
|
153
|
+
let dbcInstance: DBC | undefined;
|
|
140
154
|
return (target: unknown, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
141
|
-
if (!DBC.getDBC(dbc)
|
|
155
|
+
if (!dbcInstance) dbcInstance = DBC.getDBC(dbc);
|
|
156
|
+
if (!dbcInstance.executionSettings.checkInvariants) {
|
|
142
157
|
return;
|
|
143
158
|
}
|
|
144
159
|
const originalSetter = descriptor.set;
|
|
@@ -148,7 +163,7 @@ export class DBC {
|
|
|
148
163
|
// #region Replace original property.
|
|
149
164
|
Object.defineProperty(target, propertyKey, {
|
|
150
165
|
get() {
|
|
151
|
-
if (!
|
|
166
|
+
if (!dbcInstance!.executionSettings.checkInvariants) {
|
|
152
167
|
return;
|
|
153
168
|
}
|
|
154
169
|
|
|
@@ -158,7 +173,7 @@ export class DBC {
|
|
|
158
173
|
const result = contract.check(realValue);
|
|
159
174
|
|
|
160
175
|
if (typeof result === "string") {
|
|
161
|
-
|
|
176
|
+
dbcInstance!.reportFieldInfringement(
|
|
162
177
|
result,
|
|
163
178
|
target as object,
|
|
164
179
|
path,
|
|
@@ -168,10 +183,10 @@ export class DBC {
|
|
|
168
183
|
}
|
|
169
184
|
}
|
|
170
185
|
// #endregion Check if all "contracts" are fulfilled.
|
|
171
|
-
return originalGetter[propertyKey];
|
|
186
|
+
return originalGetter ? (originalGetter as any)[propertyKey] : value;
|
|
172
187
|
},
|
|
173
188
|
set(newValue) {
|
|
174
|
-
if (!
|
|
189
|
+
if (!dbcInstance!.executionSettings.checkInvariants) {
|
|
175
190
|
return;
|
|
176
191
|
}
|
|
177
192
|
|
|
@@ -181,7 +196,7 @@ export class DBC {
|
|
|
181
196
|
const result = contract.check(realValue);
|
|
182
197
|
|
|
183
198
|
if (typeof result === "string") {
|
|
184
|
-
|
|
199
|
+
dbcInstance!.reportFieldInfringement(
|
|
185
200
|
result,
|
|
186
201
|
target as object,
|
|
187
202
|
path,
|
|
@@ -218,8 +233,10 @@ export class DBC {
|
|
|
218
233
|
dbc: string | undefined = undefined,
|
|
219
234
|
hint: string | undefined = undefined,
|
|
220
235
|
) {
|
|
236
|
+
let dbcInstance: DBC | undefined;
|
|
221
237
|
return (target: unknown, propertyKey: string | symbol) => {
|
|
222
|
-
if (!DBC.getDBC(dbc)
|
|
238
|
+
if (!dbcInstance) dbcInstance = DBC.getDBC(dbc);
|
|
239
|
+
if (!dbcInstance.executionSettings.checkInvariants) {
|
|
223
240
|
return;
|
|
224
241
|
}
|
|
225
242
|
// biome-ignore lint/suspicious/noExplicitAny: Necessary to intercept UNDEFINED and NULL.
|
|
@@ -227,7 +244,7 @@ export class DBC {
|
|
|
227
244
|
// #region Replace original property.
|
|
228
245
|
Object.defineProperty(target, propertyKey, {
|
|
229
246
|
set(newValue) {
|
|
230
|
-
if (!
|
|
247
|
+
if (!dbcInstance!.executionSettings.checkInvariants) {
|
|
231
248
|
return;
|
|
232
249
|
}
|
|
233
250
|
|
|
@@ -237,7 +254,7 @@ export class DBC {
|
|
|
237
254
|
const result = contract.check(realValue);
|
|
238
255
|
|
|
239
256
|
if (typeof result === "string") {
|
|
240
|
-
|
|
257
|
+
dbcInstance!.reportFieldInfringement(
|
|
241
258
|
result,
|
|
242
259
|
target as object,
|
|
243
260
|
path,
|
|
@@ -268,13 +285,15 @@ export class DBC {
|
|
|
268
285
|
* @returns The **( target : object, propertyKey : string, descriptor : PropertyDescriptor ) : PropertyDescriptor**
|
|
269
286
|
* invoked by Typescript.
|
|
270
287
|
*/
|
|
288
|
+
// biome-ignore lint/suspicious/noExplicitAny: Necessary to intercept UNDEFINED and NULL.
|
|
271
289
|
public static decPostcondition(
|
|
272
290
|
// biome-ignore lint/suspicious/noExplicitAny: Necessary to intercept UNDEFINED and NULL.
|
|
273
|
-
check: (toCheck: any, object, string) => boolean | string,
|
|
291
|
+
check: (toCheck: any, target: object, propertyKey: string) => boolean | string,
|
|
274
292
|
dbc: string | undefined = undefined,
|
|
275
293
|
path: string | undefined = undefined,
|
|
276
294
|
hint: string | undefined = undefined,
|
|
277
295
|
) {
|
|
296
|
+
let dbcInstance: DBC | undefined;
|
|
278
297
|
return (
|
|
279
298
|
target: object,
|
|
280
299
|
propertyKey: string,
|
|
@@ -283,7 +302,8 @@ export class DBC {
|
|
|
283
302
|
const originalMethod = descriptor.value;
|
|
284
303
|
// biome-ignore lint/suspicious/noExplicitAny: Necessary to intercept UNDEFINED and NULL.
|
|
285
304
|
descriptor.value = (...args: any[]) => {
|
|
286
|
-
if (!DBC.getDBC(dbc)
|
|
305
|
+
if (!dbcInstance) dbcInstance = DBC.getDBC(dbc);
|
|
306
|
+
if (!dbcInstance.executionSettings.checkPostconditions) {
|
|
287
307
|
return;
|
|
288
308
|
}
|
|
289
309
|
// biome-ignore lint/complexity/noThisInStatic: <explanation>
|
|
@@ -292,7 +312,7 @@ export class DBC {
|
|
|
292
312
|
const checkResult = check(realValue, target, propertyKey);
|
|
293
313
|
|
|
294
314
|
if (typeof checkResult === "string") {
|
|
295
|
-
|
|
315
|
+
dbcInstance.reportReturnvalueInfringement(
|
|
296
316
|
checkResult,
|
|
297
317
|
target,
|
|
298
318
|
path,
|
|
@@ -323,7 +343,8 @@ export class DBC {
|
|
|
323
343
|
*
|
|
324
344
|
* @returns The **(target: object, methodName: string | symbol, parameterIndex: number ) => void** invoked by Typescript- */
|
|
325
345
|
protected static decPrecondition(
|
|
326
|
-
|
|
346
|
+
// biome-ignore lint/suspicious/noExplicitAny: Necessary to check any parameter value
|
|
347
|
+
check: (value: unknown, target: object, methodName: string | symbol, parameterIndex: number) => boolean | string,
|
|
327
348
|
dbc: string | undefined = undefined,
|
|
328
349
|
path: string | undefined = undefined,
|
|
329
350
|
hint: string | undefined = undefined,
|
|
@@ -333,6 +354,7 @@ export class DBC {
|
|
|
333
354
|
parameterIndex: number,
|
|
334
355
|
) => void {
|
|
335
356
|
const paths = path ? path.replace(/ /g, "").split("::") : [undefined];
|
|
357
|
+
let dbcInstance: DBC | undefined;
|
|
336
358
|
return (
|
|
337
359
|
target: object,
|
|
338
360
|
methodName: string | symbol,
|
|
@@ -343,7 +365,8 @@ export class DBC {
|
|
|
343
365
|
methodName,
|
|
344
366
|
parameterIndex,
|
|
345
367
|
(value: unknown) => {
|
|
346
|
-
if (!DBC.getDBC(dbc)
|
|
368
|
+
if (!dbcInstance) dbcInstance = DBC.getDBC(dbc);
|
|
369
|
+
if (!dbcInstance.executionSettings.checkPreconditions) {
|
|
347
370
|
return;
|
|
348
371
|
}
|
|
349
372
|
|
|
@@ -352,7 +375,7 @@ export class DBC {
|
|
|
352
375
|
const result = check(realValue, target, methodName, parameterIndex);
|
|
353
376
|
|
|
354
377
|
if (typeof result === "string") {
|
|
355
|
-
|
|
378
|
+
dbcInstance.reportParameterInfringement(
|
|
356
379
|
result,
|
|
357
380
|
target,
|
|
358
381
|
singlePath,
|
|
@@ -369,6 +392,88 @@ export class DBC {
|
|
|
369
392
|
}
|
|
370
393
|
// #endregion Precondition
|
|
371
394
|
// #endregion Decorator
|
|
395
|
+
// #region Contract Factory Helpers
|
|
396
|
+
/**
|
|
397
|
+
* Creates a PRE decorator from a checkAlgorithm function and its bound arguments.
|
|
398
|
+
* Reduces boilerplate across contract classes.
|
|
399
|
+
*
|
|
400
|
+
* @param checkFn A function that takes (value, ...boundArgs) and returns true or an error string.
|
|
401
|
+
* @param boundArgs The arguments to bind to the check function after the value.
|
|
402
|
+
* @param dbc See {@link DBC.decPrecondition}.
|
|
403
|
+
* @param path See {@link DBC.decPrecondition}.
|
|
404
|
+
* @param hint See {@link DBC.decPrecondition}.
|
|
405
|
+
*/
|
|
406
|
+
public static createPRE(
|
|
407
|
+
// biome-ignore lint/suspicious/noExplicitAny: Must accept any checkAlgorithm signature
|
|
408
|
+
checkFn: (...args: any[]) => boolean | string,
|
|
409
|
+
// biome-ignore lint/suspicious/noExplicitAny: Arguments vary per contract
|
|
410
|
+
boundArgs: any[],
|
|
411
|
+
dbc?: string,
|
|
412
|
+
path?: string,
|
|
413
|
+
hint?: string,
|
|
414
|
+
) {
|
|
415
|
+
return DBC.decPrecondition(
|
|
416
|
+
(value, _target, _methodName, _parameterIndex) => {
|
|
417
|
+
return checkFn(value, ...boundArgs);
|
|
418
|
+
},
|
|
419
|
+
dbc,
|
|
420
|
+
path,
|
|
421
|
+
hint,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Creates a POST decorator from a checkAlgorithm function and its bound arguments.
|
|
426
|
+
*
|
|
427
|
+
* @param checkFn A function that takes (value, ...boundArgs) and returns true or an error string.
|
|
428
|
+
* @param boundArgs The arguments to bind to the check function after the value.
|
|
429
|
+
* @param dbc See {@link DBC.decPostcondition}.
|
|
430
|
+
* @param path See {@link DBC.decPostcondition}.
|
|
431
|
+
* @param hint See {@link DBC.decPostcondition}.
|
|
432
|
+
*/
|
|
433
|
+
public static createPOST(
|
|
434
|
+
// biome-ignore lint/suspicious/noExplicitAny: Must accept any checkAlgorithm signature
|
|
435
|
+
checkFn: (...args: any[]) => boolean | string,
|
|
436
|
+
// biome-ignore lint/suspicious/noExplicitAny: Arguments vary per contract
|
|
437
|
+
boundArgs: any[],
|
|
438
|
+
dbc?: string,
|
|
439
|
+
path?: string,
|
|
440
|
+
hint?: string,
|
|
441
|
+
) {
|
|
442
|
+
return DBC.decPostcondition(
|
|
443
|
+
(value, _target, _propertyKey) => {
|
|
444
|
+
return checkFn(value, ...boundArgs);
|
|
445
|
+
},
|
|
446
|
+
dbc,
|
|
447
|
+
path,
|
|
448
|
+
hint,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Creates an INVARIANT decorator from a contract constructor and its bound arguments.
|
|
453
|
+
*
|
|
454
|
+
* @param ContractClass A class with a constructor that produces an object with a `check` method.
|
|
455
|
+
* @param ctorArgs The arguments to pass to the contract constructor.
|
|
456
|
+
* @param dbc See {@link DBC.decInvariant}.
|
|
457
|
+
* @param path See {@link DBC.decInvariant}.
|
|
458
|
+
* @param hint See {@link DBC.decInvariant}.
|
|
459
|
+
*/
|
|
460
|
+
public static createINVARIANT(
|
|
461
|
+
// biome-ignore lint/suspicious/noExplicitAny: Must accept any contract constructor
|
|
462
|
+
ContractClass: new (...args: any[]) => { check: (toCheck: unknown | null | undefined) => boolean | string },
|
|
463
|
+
// biome-ignore lint/suspicious/noExplicitAny: Arguments vary per contract
|
|
464
|
+
ctorArgs: any[],
|
|
465
|
+
dbc?: string,
|
|
466
|
+
path?: string,
|
|
467
|
+
hint?: string,
|
|
468
|
+
) {
|
|
469
|
+
return DBC.decInvariant(
|
|
470
|
+
[new ContractClass(...ctorArgs)],
|
|
471
|
+
path,
|
|
472
|
+
dbc,
|
|
473
|
+
hint,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
// #endregion Contract Factory Helpers
|
|
372
477
|
// #region Execution Handling
|
|
373
478
|
/** Stores settings concerning the execution of checks. */
|
|
374
479
|
public executionSettings: {
|
|
@@ -402,6 +507,20 @@ export class DBC {
|
|
|
402
507
|
throwException: boolean;
|
|
403
508
|
logToConsole: boolean;
|
|
404
509
|
} = { throwException: true, logToConsole: false };
|
|
510
|
+
/** Sanitizes a value for safe inclusion in error messages. */
|
|
511
|
+
private static sanitize(value: unknown): string {
|
|
512
|
+
const str = typeof value === "string" ? value : String(value);
|
|
513
|
+
return str.replace(/[<>&"']/g, (ch) => {
|
|
514
|
+
switch (ch) {
|
|
515
|
+
case "<": return "<";
|
|
516
|
+
case ">": return ">";
|
|
517
|
+
case "&": return "&";
|
|
518
|
+
case "\"": return """;
|
|
519
|
+
case "'": return "'";
|
|
520
|
+
default: return ch;
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
405
524
|
/**
|
|
406
525
|
* Reports an infringement according to the {@link infringementSettings } also generating a proper {@link string }-wrapper
|
|
407
526
|
* for the given "message" & violator.
|
|
@@ -413,10 +532,12 @@ export class DBC {
|
|
|
413
532
|
violator: string,
|
|
414
533
|
target: object,
|
|
415
534
|
value: unknown,
|
|
416
|
-
path: string,
|
|
535
|
+
path: string | undefined,
|
|
417
536
|
hint: string | undefined = undefined,
|
|
418
537
|
): undefined {
|
|
419
|
-
const
|
|
538
|
+
const safeViolator = DBC.sanitize(violator);
|
|
539
|
+
const targetName = typeof target === "function" ? DBC.sanitize(target.name) : typeof target === "object" && target !== null && typeof target.constructor === "function" ? DBC.sanitize(target.constructor.name) : DBC.sanitize(target);
|
|
540
|
+
const finalMessage: string = `[ From "${safeViolator}" in "${targetName}"${path ? ` > "${DBC.sanitize(path)}"` : ""}: ${message} ${hint ? `✨ ${hint} ✨` : ""}]`;
|
|
420
541
|
|
|
421
542
|
if (this.infringementSettings.throwException) {
|
|
422
543
|
throw new DBC.Infringement(finalMessage);
|
|
@@ -437,7 +558,7 @@ export class DBC {
|
|
|
437
558
|
public reportParameterInfringement(
|
|
438
559
|
message: string,
|
|
439
560
|
target: object,
|
|
440
|
-
path: string,
|
|
561
|
+
path: string | undefined,
|
|
441
562
|
method: string,
|
|
442
563
|
index: number,
|
|
443
564
|
value: unknown,
|
|
@@ -466,7 +587,7 @@ export class DBC {
|
|
|
466
587
|
public reportFieldInfringement(
|
|
467
588
|
message: string,
|
|
468
589
|
target: object,
|
|
469
|
-
path: string,
|
|
590
|
+
path: string | undefined,
|
|
470
591
|
key: string,
|
|
471
592
|
value: unknown,
|
|
472
593
|
hint: string | undefined = undefined,
|
|
@@ -489,7 +610,7 @@ export class DBC {
|
|
|
489
610
|
public reportReturnvalueInfringement(
|
|
490
611
|
message: string,
|
|
491
612
|
target: object,
|
|
492
|
-
path: string,
|
|
613
|
+
path: string | undefined,
|
|
493
614
|
method: string,
|
|
494
615
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
495
616
|
value: any,
|
|
@@ -527,13 +648,15 @@ export class DBC {
|
|
|
527
648
|
*
|
|
528
649
|
* @returns The requested {@link DBC }.
|
|
529
650
|
*/
|
|
530
|
-
|
|
651
|
+
// biome-ignore lint/suspicious/noExplicitAny: Must traverse arbitrary object graphs
|
|
652
|
+
static resolveDBCPath = (obj: any, path: string): DBC =>
|
|
531
653
|
path
|
|
532
654
|
?.split(".")
|
|
533
|
-
|
|
655
|
+
// biome-ignore lint/suspicious/noExplicitAny: Must traverse arbitrary object graphs
|
|
656
|
+
.reduce((accumulator: any, current: string) => accumulator[current], obj);
|
|
534
657
|
/**
|
|
535
|
-
* Constructs this {@link DBC }
|
|
536
|
-
*
|
|
658
|
+
* Constructs this {@link DBC } without mounting it on the global namespace.
|
|
659
|
+
* Use {@link DBC.register } to make the instance available at a specific path on globalThis.
|
|
537
660
|
*
|
|
538
661
|
* @param infringementSettings See {@link DBC.infringementSettings }.
|
|
539
662
|
* @param executionSettings See {@link DBC.executionSettings }. */
|
|
@@ -553,12 +676,44 @@ export class DBC {
|
|
|
553
676
|
},
|
|
554
677
|
) {
|
|
555
678
|
this.infringementSettings = infringementSettings;
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
679
|
+
this.executionSettings = executionSettings;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Registers a {@link DBC } instance at the specified dotted path on globalThis (or window),
|
|
683
|
+
* making it available for decorator resolution via string paths.
|
|
684
|
+
*
|
|
685
|
+
* @param instance The {@link DBC } instance to register.
|
|
686
|
+
* @param path The dotted path to register at (default: `"WaXCode.DBC"`). */
|
|
687
|
+
static register(instance: DBC, path = "WaXCode.DBC"): void {
|
|
688
|
+
const segments = path.split(".");
|
|
689
|
+
// biome-ignore lint/suspicious/noExplicitAny: Must walk dynamic global namespace.
|
|
690
|
+
let obj: any = DBC.getHost();
|
|
691
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
692
|
+
if (obj[segments[i]] === undefined) obj[segments[i]] = {};
|
|
693
|
+
obj = obj[segments[i]];
|
|
694
|
+
}
|
|
695
|
+
obj[segments[segments.length - 1]] = instance;
|
|
696
|
+
DBC.dbcCache.set(path, instance);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Executes a callback with an isolated {@link DBC } instance temporarily registered at the default path.
|
|
700
|
+
* The previous instance (if any) is restored after the callback completes — even if it throws.
|
|
701
|
+
* Useful for test isolation.
|
|
702
|
+
*
|
|
703
|
+
* @param fn The callback receiving the isolated {@link DBC } instance. */
|
|
704
|
+
static isolated(fn: (dbc: DBC) => void): void {
|
|
705
|
+
const saved = DBC.dbcCache.get("WaXCode.DBC");
|
|
706
|
+
const testDbc = new DBC();
|
|
707
|
+
DBC.register(testDbc);
|
|
708
|
+
try {
|
|
709
|
+
fn(testDbc);
|
|
710
|
+
} finally {
|
|
711
|
+
if (saved) {
|
|
712
|
+
DBC.register(saved);
|
|
713
|
+
} else {
|
|
714
|
+
DBC.dbcCache.delete("WaXCode.DBC");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
562
717
|
}
|
|
563
718
|
/**
|
|
564
719
|
* Resolves the desired {@link object } out a given one **toResolveFrom** using the specified **path**.
|
|
@@ -572,40 +727,53 @@ export class DBC {
|
|
|
572
727
|
*
|
|
573
728
|
* @returns The requested {@link object }, NULL or UNDEFINED. */
|
|
574
729
|
public static resolve(toResolveFrom: unknown, path: string) {
|
|
575
|
-
if (!toResolveFrom || typeof path !== "string") {
|
|
576
|
-
|
|
577
|
-
|
|
730
|
+
if (!toResolveFrom || typeof path !== "string") { return undefined; }
|
|
731
|
+
|
|
732
|
+
// Security: block prototype pollution paths
|
|
733
|
+
const dangerousTokens = ["__proto__", "constructor", "prototype"];
|
|
734
|
+
|
|
578
735
|
const cachedParts = DBC.pathTokenCache.get(path);
|
|
579
|
-
const parts =
|
|
580
|
-
|
|
581
|
-
path.replace(/\[(['"]?)(.*?)\1\]/g, ".$2").split("."); // Handle indexers
|
|
736
|
+
const parts = cachedParts ?? path.replace(/\[(['"]?)(.*?)\1\]/g, ".$2").split(".");
|
|
737
|
+
|
|
582
738
|
if (!cachedParts) {
|
|
739
|
+
// Validate tokens before caching
|
|
740
|
+
for (const part of parts) {
|
|
741
|
+
const tokenName = part.replace(/\(.*\)$/, "");
|
|
742
|
+
if (dangerousTokens.indexOf(tokenName) >= 0) {
|
|
743
|
+
throw new Error(`[XDBC] Path "${path}" contains forbidden token "${tokenName}".`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
DBC.evictIfNeeded(DBC.pathTokenCache);
|
|
583
747
|
DBC.pathTokenCache.set(path, parts);
|
|
584
748
|
}
|
|
585
749
|
|
|
586
|
-
|
|
750
|
+
// biome-ignore lint/suspicious/noExplicitAny: Must traverse arbitrary object graphs
|
|
751
|
+
let current: any = toResolveFrom;
|
|
752
|
+
|
|
587
753
|
for (const part of parts) {
|
|
588
|
-
if (current === null || typeof current === "undefined") {
|
|
589
|
-
return undefined;
|
|
590
|
-
}
|
|
754
|
+
if (current === null || typeof current === "undefined") { return undefined; }
|
|
591
755
|
|
|
592
756
|
const methodMatch = part.match(/(\w+)\((.*)\)/);
|
|
757
|
+
|
|
593
758
|
if (methodMatch) {
|
|
594
759
|
const methodName = methodMatch[1];
|
|
595
760
|
const argsStr = methodMatch[2];
|
|
596
|
-
const args = argsStr.split(",").map((arg) => arg.trim());
|
|
761
|
+
const args = argsStr.split(",").map((arg) => arg.trim());
|
|
762
|
+
|
|
597
763
|
if (typeof current[methodName] === "function") {
|
|
598
764
|
current = current[methodName].apply(current, args);
|
|
599
|
-
} else {
|
|
600
|
-
return undefined; // Method not found or not a function
|
|
601
|
-
}
|
|
765
|
+
} else { return undefined; }
|
|
602
766
|
} else {
|
|
603
|
-
current
|
|
767
|
+
if (typeof window !== "undefined" && typeof HTMLElement !== "undefined" && current instanceof HTMLElement && part.startsWith("@")) {
|
|
768
|
+
current = current.getAttribute(part.slice(1));
|
|
769
|
+
} else if (typeof current === "object" && current !== null && part in current) { current = current[part]; }
|
|
770
|
+
else if (typeof window !== "undefined" && typeof HTMLElement !== "undefined" && current instanceof HTMLElement) { current = undefined; }
|
|
771
|
+
else { current = undefined; }
|
|
604
772
|
}
|
|
605
773
|
}
|
|
606
774
|
|
|
607
775
|
return current;
|
|
608
776
|
}
|
|
609
777
|
}
|
|
610
|
-
//
|
|
611
|
-
new DBC();
|
|
778
|
+
// Register the default instance with standard settings.
|
|
779
|
+
DBC.register(new DBC());
|
package/tsconfig.json
CHANGED
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
"experimentalDecorators": true,
|
|
5
5
|
"emitDecoratorMetadata": true,
|
|
6
6
|
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
7
8
|
"esModuleInterop": true,
|
|
8
9
|
"module": "es6",
|
|
9
|
-
"moduleResolution": "
|
|
10
|
+
"moduleResolution": "bundler",
|
|
10
11
|
"typeRoots": []
|
|
11
12
|
},
|
|
12
|
-
|
|
13
13
|
"include": [
|
|
14
14
|
"./src/**/*.ts" // or your source directories
|
|
15
15
|
],
|
|
16
|
-
"exclude": [
|
|
17
|
-
|
|
16
|
+
"exclude": [
|
|
17
|
+
"node_modules",
|
|
18
|
+
"__tests__"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
Binary file
|
|
Binary file
|
package/.parcel-cache/data.mdb
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/.parcel-cache/lock.mdb
DELETED
|
Binary file
|