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.
Files changed (75) hide show
  1. package/Assessment.html +350 -0
  2. package/Assessment.md +507 -51
  3. package/CHANGELOG.md +55 -0
  4. package/CONTRIBUTING.md +129 -17
  5. package/README.md +373 -71
  6. package/SECURITY.md +60 -18
  7. package/SUPPORT.md +65 -0
  8. package/__tests__/DBC/DEFINED.test.ts +53 -0
  9. package/__tests__/DBC/Decorators.test.ts +365 -0
  10. package/__tests__/DBC/GREATER.test.ts +8 -6
  11. package/__tests__/DBC/HasAttribute.test.ts +56 -0
  12. package/__tests__/DBC/IF.test.ts +52 -0
  13. package/__tests__/DBC/JSON.Parse.test.ts +1 -1
  14. package/__tests__/DBC/OR.test.ts +1 -1
  15. package/__tests__/DBC/REGEX.test.ts +1 -1
  16. package/__tests__/DBC/TYPE.test.ts +1 -1
  17. package/__tests__/DBC/UNDEFINED.test.ts +45 -0
  18. package/__tests__/DBC/ZOD.test.ts +54 -0
  19. package/jest.config.js +21 -0
  20. package/package.json +4 -5
  21. package/src/DBC/AE.ts +10 -6
  22. package/src/DBC/COMPARISON/GREATER.ts +11 -7
  23. package/src/DBC/COMPARISON/GREATER_OR_EQUAL.ts +14 -10
  24. package/src/DBC/COMPARISON/LESS.ts +14 -10
  25. package/src/DBC/COMPARISON/LESS_OR_EQUAL.ts +14 -10
  26. package/src/DBC/COMPARISON.ts +20 -43
  27. package/src/DBC/DEFINED.ts +4 -23
  28. package/src/DBC/EQ/DIFFERENT.ts +21 -56
  29. package/src/DBC/EQ.ts +7 -26
  30. package/src/DBC/HasAttribute.ts +9 -26
  31. package/src/DBC/IF.ts +8 -27
  32. package/src/DBC/INSTANCE.ts +5 -22
  33. package/src/DBC/JSON.OP.ts +4 -34
  34. package/src/DBC/JSON.Parse.ts +5 -25
  35. package/src/DBC/OR.ts +5 -14
  36. package/src/DBC/REGEX.ts +41 -40
  37. package/src/DBC/TYPE.ts +6 -25
  38. package/src/DBC/UNDEFINED.ts +3 -22
  39. package/src/DBC/ZOD.ts +10 -27
  40. package/src/DBC.ts +223 -55
  41. package/tsconfig.json +7 -4
  42. package/tsconfig.test.json +12 -0
  43. package/.parcel-cache/bf96c58b6061a62a-BundleGraph +0 -0
  44. package/.parcel-cache/d7c812d65aeeac59-AssetGraph +0 -0
  45. package/.parcel-cache/data.mdb +0 -0
  46. package/.parcel-cache/e81759c1f106a17f-RequestGraph +0 -0
  47. package/.parcel-cache/fe0db3c4eb428be2-AssetGraph +0 -0
  48. package/.parcel-cache/lock.mdb +0 -0
  49. package/.parcel-cache/snapshot-e81759c1f106a17f.txt +0 -4609
  50. package/dist/DBC/AE.js +0 -173
  51. package/dist/DBC/COMPARISON/GREATER.js +0 -21
  52. package/dist/DBC/COMPARISON/GREATER_OR_EQUAL.js +0 -21
  53. package/dist/DBC/COMPARISON/LESS.js +0 -21
  54. package/dist/DBC/COMPARISON/LESS_OR_EQUAL.js +0 -21
  55. package/dist/DBC/COMPARISON.js +0 -99
  56. package/dist/DBC/DEFINED.js +0 -99
  57. package/dist/DBC/EQ/DIFFERENT.js +0 -21
  58. package/dist/DBC/EQ.js +0 -100
  59. package/dist/DBC/GREATER.js +0 -99
  60. package/dist/DBC/HasAttribute.js +0 -108
  61. package/dist/DBC/IF.js +0 -99
  62. package/dist/DBC/INSTANCE.js +0 -93
  63. package/dist/DBC/JSON.OP.js +0 -133
  64. package/dist/DBC/JSON.Parse.js +0 -114
  65. package/dist/DBC/OR.js +0 -113
  66. package/dist/DBC/REGEX.js +0 -110
  67. package/dist/DBC/TYPE.js +0 -87
  68. package/dist/DBC/ZOD.js +0 -114
  69. package/dist/DBC.js +0 -336
  70. package/dist/Demo.js +0 -290
  71. package/dist/Test.html +0 -18
  72. package/dist/bundle.js +0 -2064
  73. package/dist/index.html +0 -18
  74. package/jest.config.ts +0 -20
  75. 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
- return resolved;
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
- if (DBC.paramValueRequests.get(key).has(index)) {
65
- DBC.paramValueRequests.get(key).get(index).push(receptor);
75
+ const paramMap = DBC.paramValueRequests.get(key)!;
76
+ if (paramMap.has(index)) {
77
+ paramMap.get(index)!.push(receptor);
66
78
  } else {
67
- DBC.paramValueRequests.get(key).set(index, new Array<(value: unknown) => undefined>(receptor));
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
- for (const index of DBC.paramValueRequests.get(key).keys()) {
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 DBC.paramValueRequests.get(key).get(index)) {
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).executionSettings.checkInvariants) {
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 (!DBC.getDBC(dbc).executionSettings.checkInvariants) {
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
- DBC.getDBC(dbc).reportFieldInfringement(
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 (!DBC.getDBC(dbc).executionSettings.checkInvariants) {
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
- DBC.getDBC(dbc).reportFieldInfringement(
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).executionSettings.checkInvariants) {
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 (!DBC.getDBC(dbc).executionSettings.checkInvariants) {
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
- DBC.getDBC(dbc).reportFieldInfringement(
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).executionSettings.checkPostconditions) {
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
- DBC.getDBC(dbc).reportReturnvalueInfringement(
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
- check: (unknown, object, string, number) => boolean | string,
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).executionSettings.checkPreconditions) {
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
- DBC.getDBC(dbc).reportParameterInfringement(
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 "&lt;";
516
+ case ">": return "&gt;";
517
+ case "&": return "&amp;";
518
+ case "\"": return "&quot;";
519
+ case "'": return "&#39;";
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 finalMessage: string = `[ From "${violator}"${typeof target === "function" ? ` in "${target.name}"` : typeof target === "object" && target !== null && typeof target.constructor === "function" ? ` in "${target.constructor.name}"` : `in "${target}"`}${path ? ` > "${path}"` : ""}: ${message} ${hint ? `✨ ${hint} ✨` : ""}]`;
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
- static resolveDBCPath = (obj, path): DBC =>
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
- .reduce((accumulator, current) => accumulator[current], obj);
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 } by setting the {@link DBC.infringementSettings }, define the **WaXCode** namespace in
536
- * **window** if not yet available and setting the property **DBC** in there to the instance of this {@link DBC }.
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
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
558
- if ((DBC.getHost() as any).WaXCode === undefined) (DBC.getHost() as any).WaXCode = {};
559
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
560
- (DBC.getHost() as any).WaXCode.DBC = this;
561
- DBC.dbcCache.set("WaXCode.DBC", this);
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
- return undefined;
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
- cachedParts ??
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
- let current = toResolveFrom;
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()); // Simple argument parsing
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 = current[part];
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
- // Set the main instance with standard **DBC.infringementSettings**.
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": "node",
10
+ "moduleResolution": "bundler",
10
11
  "typeRoots": []
11
12
  },
12
-
13
13
  "include": [
14
14
  "./src/**/*.ts" // or your source directories
15
15
  ],
16
- "exclude": ["node_modules", "__tests__"]
17
- }
16
+ "exclude": [
17
+ "node_modules",
18
+ "__tests__"
19
+ ]
20
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "typeRoots": [
5
+ "./node_modules/@types"
6
+ ]
7
+ },
8
+ "include": [
9
+ "./src/**/*.ts",
10
+ "./__tests__/**/*.ts"
11
+ ]
12
+ }
Binary file
Binary file