xdbc 1.0.217 → 1.0.218

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 (89) hide show
  1. package/.gitattributes +8 -0
  2. package/.vscode/settings.json +3 -3
  3. package/.vscode/tasks.json +23 -23
  4. package/ASSESSMENT.md +249 -0
  5. package/README.md +131 -1
  6. package/__tests__/DBC/AE.test.ts +62 -62
  7. package/__tests__/DBC/ARRAY.test.ts +91 -91
  8. package/__tests__/DBC/DEFINED.test.ts +53 -53
  9. package/__tests__/DBC/DOM.test.ts +481 -0
  10. package/__tests__/DBC/Decorators.test.ts +367 -367
  11. package/__tests__/DBC/EQ.test.ts +13 -13
  12. package/__tests__/DBC/GREATER.test.ts +31 -31
  13. package/__tests__/DBC/HasAttribute.test.ts +60 -60
  14. package/__tests__/DBC/IF.test.ts +62 -62
  15. package/__tests__/DBC/INSTANCE.test.ts +13 -13
  16. package/__tests__/DBC/JSON.OP.test.ts +47 -47
  17. package/__tests__/DBC/JSON.Parse.test.ts +17 -17
  18. package/__tests__/DBC/OR.test.ts +14 -14
  19. package/__tests__/DBC/PLAIN_OBJECT.test.ts +109 -109
  20. package/__tests__/DBC/REGEX.test.ts +17 -17
  21. package/__tests__/DBC/TYPE.test.ts +13 -13
  22. package/__tests__/DBC/UNDEFINED.test.ts +45 -45
  23. package/__tests__/DBC/ZOD.test.ts +54 -54
  24. package/__tests__/DBC/onInfringement.test.ts +262 -0
  25. package/biome.json +40 -40
  26. package/dist/DBC/AE.js +172 -0
  27. package/dist/DBC/ARR/PLAIN_OBJECT.d.ts +0 -3
  28. package/dist/DBC/ARR/PLAIN_OBJECT.js +95 -0
  29. package/dist/DBC/ARRAY.d.ts +0 -3
  30. package/dist/DBC/ARRAY.js +90 -0
  31. package/dist/DBC/COMPARISON/GREATER.js +21 -0
  32. package/dist/DBC/COMPARISON/GREATER_OR_EQUAL.js +21 -0
  33. package/dist/DBC/COMPARISON/LESS.js +21 -0
  34. package/dist/DBC/COMPARISON/LESS_OR_EQUAL.js +21 -0
  35. package/dist/DBC/COMPARISON.js +98 -0
  36. package/dist/DBC/DEFINED.js +87 -0
  37. package/dist/DBC/DOM.d.ts +87 -0
  38. package/dist/DBC/DOM.js +223 -0
  39. package/dist/DBC/EQ/DIFFERENT.js +34 -0
  40. package/dist/DBC/EQ.js +101 -0
  41. package/dist/DBC/HasAttribute.js +101 -0
  42. package/dist/DBC/IF.js +96 -0
  43. package/dist/DBC/INSTANCE.js +122 -0
  44. package/dist/DBC/JSON.OP.js +120 -0
  45. package/dist/DBC/JSON.Parse.js +104 -0
  46. package/dist/DBC/OR.js +125 -0
  47. package/dist/DBC/REGEX.js +136 -0
  48. package/dist/DBC/TYPE.js +112 -0
  49. package/dist/DBC/UNDEFINED.js +87 -0
  50. package/dist/DBC/ZOD.js +99 -0
  51. package/dist/DBC.d.ts +18 -4
  52. package/dist/DBC.js +645 -0
  53. package/dist/Demo.d.ts +10 -0
  54. package/dist/Demo.js +713 -0
  55. package/dist/bundle.js +6140 -405
  56. package/dist/index.d.ts +22 -0
  57. package/dist/index.js +22 -0
  58. package/jest.config.js +32 -32
  59. package/package.json +71 -55
  60. package/src/DBC/AE.ts +269 -288
  61. package/src/DBC/ARR/PLAIN_OBJECT.ts +122 -133
  62. package/src/DBC/ARRAY.ts +117 -127
  63. package/src/DBC/COMPARISON/GREATER.ts +41 -46
  64. package/src/DBC/COMPARISON/GREATER_OR_EQUAL.ts +41 -45
  65. package/src/DBC/COMPARISON/LESS.ts +41 -45
  66. package/src/DBC/COMPARISON/LESS_OR_EQUAL.ts +41 -45
  67. package/src/DBC/COMPARISON.ts +149 -159
  68. package/src/DBC/DEFINED.ts +117 -122
  69. package/src/DBC/DOM.ts +291 -0
  70. package/src/DBC/EQ/DIFFERENT.ts +51 -57
  71. package/src/DBC/EQ.ts +154 -163
  72. package/src/DBC/HasAttribute.ts +149 -154
  73. package/src/DBC/IF.ts +173 -179
  74. package/src/DBC/INSTANCE.ts +168 -171
  75. package/src/DBC/JSON.OP.ts +178 -186
  76. package/src/DBC/JSON.Parse.ts +150 -157
  77. package/src/DBC/OR.ts +183 -187
  78. package/src/DBC/REGEX.ts +195 -196
  79. package/src/DBC/TYPE.ts +142 -149
  80. package/src/DBC/UNDEFINED.ts +115 -117
  81. package/src/DBC/ZOD.ts +130 -135
  82. package/src/DBC.ts +902 -904
  83. package/src/Demo.ts +537 -404
  84. package/src/index.ts +22 -0
  85. package/tsconfig.json +18 -18
  86. package/tsconfig.test.json +7 -7
  87. package/typedoc.json +16 -16
  88. package/webpack.config.js +27 -27
  89. package/Assessment.md +0 -507
@@ -1,122 +1,117 @@
1
- import { DBC } from "../DBC";
2
- /**
3
- * A {@link DBC } defining that an {@link object }s must be defined thus it's value may not be **null** or **undefined**.
4
- *
5
- * @remarks
6
- * Maintainer: Salvatore Callari (XDBC@WaXCode.net) */
7
- export class DEFINED extends DBC {
8
- /**
9
- * Checks if the value **toCheck** is null or undefined.
10
- *
11
- * @param toCheck The {@link Object } to check.
12
- *
13
- * @returns TRUE if the value **toCheck** is of the specified **type**, otherwise FALSE. */
14
- // biome-ignore lint/suspicious/noExplicitAny: Necessary for dynamic type checking of also UNDEFINED.
15
- public static checkAlgorithm(toCheck: any): boolean | string {
16
- // biome-ignore lint/suspicious/useValidTypeof: Necessary
17
- if (toCheck === undefined || toCheck === null) {
18
- return `Value may not be UNDEFINED or NULL but it is ${toCheck === undefined ? "UNDEFINED" : "NULL"}`;
19
- }
20
-
21
- return true;
22
- }
23
- /**
24
- * A parameter-decorator factory using the {@link DEFINED.checkAlgorithm } to determine whether this {@link DBC } is fulfilled
25
- * by the tagged parameter.
26
- *
27
- * @param type See {@link DEFINED.checkAlgorithm }.
28
- * @param path See {@link DBC.decPrecondition }.
29
- * @param dbc See {@link DBC.decPrecondition }.
30
- *
31
- * @returns See {@link DBC.decPrecondition }. */
32
- public static PRE(
33
- path: string | undefined = undefined,
34
- hint: string | undefined = undefined,
35
- dbc: string | undefined = undefined,
36
- ): (
37
- target: object,
38
- methodName: string | symbol | undefined,
39
- parameterIndex: number,
40
- ) => void {
41
- return DBC.createPRE(DEFINED.checkAlgorithm, [], dbc, path, hint);
42
- }
43
- /**
44
- * A method-decorator factory using the {@link DEFINED.checkAlgorithm } to determine whether this {@link DBC } is fulfilled
45
- * by the tagged method's returnvalue.
46
- *
47
- * @param type See {@link DEFINED.checkAlgorithm }.
48
- * @param path See {@link DBC.Postcondition }.
49
- * @param dbc See {@link DBC.decPostcondition }.
50
- *
51
- * @returns See {@link DBC.decPostcondition }. */
52
- public static POST(
53
- type: string,
54
- path: string | undefined = undefined,
55
- hint: string | undefined = undefined,
56
- dbc: string | undefined = undefined,
57
- ): (
58
- target: object,
59
- propertyKey: string,
60
- descriptor: PropertyDescriptor,
61
- ) => PropertyDescriptor {
62
- return DBC.createPOST(DEFINED.checkAlgorithm, [], dbc, path, hint);
63
- }
64
- /**
65
- * A field-decorator factory using the {@link DEFINED.checkAlgorithm } to determine whether this {@link DBC } is fulfilled
66
- * by the tagged field.
67
- *
68
- * @param type See {@link DEFINED.checkAlgorithm }.
69
- * @param path See {@link DBC.decInvariant }.
70
- * @param dbc See {@link DBC.decInvariant }.
71
- *
72
- * @returns See {@link DBC.decInvariant }. */
73
- public static INVARIANT(
74
- type: string,
75
- path: string | undefined = undefined,
76
- hint: string | undefined = undefined,
77
- dbc: string | undefined = undefined,
78
- ) {
79
- return DBC.createINVARIANT(DEFINED, [], dbc, path, hint);
80
- }
81
- // #endregion Condition checking.
82
- // #region Referenced Condition checking.
83
- //
84
- // For usage in dynamic scenarios (like with AE-DBC).
85
- //
86
- /**
87
- * Invokes the {@link DEFINED.checkAlgorithm } passing the value **toCheck** and the {@link DEFINED.type } .
88
- *
89
- * @param toCheck See {@link DEFINED.checkAlgorithm }.
90
- *
91
- * @returns See {@link DEFINED.checkAlgorithm}. */
92
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
93
- public check(toCheck: any) {
94
- return DEFINED.checkAlgorithm(toCheck);
95
- }
96
- /**
97
- * Invokes the {@link DEFINED.checkAlgorithm } passing the value **toCheck** and the {@link DEFINED.type } .
98
- *
99
- * @param toCheck See {@link DEFINED.checkAlgorithm }.
100
- * @param id A {@link string } identifying this {@link INSTANCE } via the {@link DBC.Infringement }-Message.
101
- *
102
- * @returns The **CANDIDATE** **toCheck** doesn't fulfill this {@link DEFINED }.
103
- *
104
- * @throws A {@link DBC.Infringement } if the **CANDIDATE** **toCheck** does not fulfill this {@link DEFINED }.*/
105
- public static tsCheck<CANDIDATE = unknown>(
106
- toCheck: CANDIDATE | undefined | null,
107
- hint: string | undefined = undefined,
108
- id: string | undefined = undefined,
109
- dbc: string | undefined = undefined,
110
- ): CANDIDATE {
111
- const result = DEFINED.checkAlgorithm(toCheck);
112
-
113
- if (result === true) {
114
- return toCheck as CANDIDATE;
115
- }
116
- DBC.reportTsCheckInfringement(
117
- `${id ? `(${id}) ` : ""}${result as string}${hint ? ` ✨ ${hint} ✨` : ""}`,
118
- dbc,
119
- );
120
- return toCheck as CANDIDATE;
121
- }
122
- }
1
+ import { DBC } from "../DBC";
2
+ /**
3
+ * A {@link DBC } defining that an {@link object }s must be defined thus it's value may not be **null** or **undefined**.
4
+ *
5
+ * @remarks
6
+ * Maintainer: Salvatore Callari (XDBC@WaXCode.net) */
7
+ export class DEFINED extends DBC {
8
+ /**
9
+ * Checks if the value **toCheck** is null or undefined.
10
+ *
11
+ * @param toCheck The {@link Object } to check.
12
+ *
13
+ * @returns TRUE if the value **toCheck** is of the specified **type**, otherwise FALSE. */
14
+ public static checkAlgorithm(toCheck: any): boolean | string {
15
+ if (toCheck === undefined || toCheck === null) {
16
+ return `Value may not be UNDEFINED or NULL but it is ${toCheck === undefined ? "UNDEFINED" : "NULL"}`;
17
+ }
18
+ return true;
19
+ }
20
+ /**
21
+ * A parameter-decorator factory using the {@link DEFINED.checkAlgorithm } to determine whether this {@link DBC } is fulfilled
22
+ * by the tagged parameter.
23
+ *
24
+ * @param type See {@link DEFINED.checkAlgorithm }.
25
+ * @param path See {@link DBC.decPrecondition }.
26
+ * @param dbc See {@link DBC.decPrecondition }.
27
+ *
28
+ * @returns See {@link DBC.decPrecondition }. */
29
+ public static PRE(
30
+ path: string | undefined = undefined,
31
+ hint: string | undefined = undefined,
32
+ dbc: string | undefined = undefined,
33
+ ): (
34
+ target: object,
35
+ methodName: string | symbol | undefined,
36
+ parameterIndex: number,
37
+ ) => void {
38
+ return DBC.createPRE(DEFINED.checkAlgorithm, [], dbc, path, hint);
39
+ }
40
+ /**
41
+ * A method-decorator factory using the {@link DEFINED.checkAlgorithm } to determine whether this {@link DBC } is fulfilled
42
+ * by the tagged method's returnvalue.
43
+ *
44
+ * @param type See {@link DEFINED.checkAlgorithm }.
45
+ * @param path See {@link DBC.Postcondition }.
46
+ * @param dbc See {@link DBC.decPostcondition }.
47
+ *
48
+ * @returns See {@link DBC.decPostcondition }. */
49
+ public static POST(
50
+ type: string,
51
+ path: string | undefined = undefined,
52
+ hint: string | undefined = undefined,
53
+ dbc: string | undefined = undefined,
54
+ ): (
55
+ target: object,
56
+ propertyKey: string,
57
+ descriptor: PropertyDescriptor,
58
+ ) => PropertyDescriptor {
59
+ return DBC.createPOST(DEFINED.checkAlgorithm, [], dbc, path, hint);
60
+ }
61
+ /**
62
+ * A field-decorator factory using the {@link DEFINED.checkAlgorithm } to determine whether this {@link DBC } is fulfilled
63
+ * by the tagged field.
64
+ *
65
+ * @param type See {@link DEFINED.checkAlgorithm }.
66
+ * @param path See {@link DBC.decInvariant }.
67
+ * @param dbc See {@link DBC.decInvariant }.
68
+ *
69
+ * @returns See {@link DBC.decInvariant }. */
70
+ public static INVARIANT(
71
+ type: string,
72
+ path: string | undefined = undefined,
73
+ hint: string | undefined = undefined,
74
+ dbc: string | undefined = undefined,
75
+ ) {
76
+ return DBC.createINVARIANT(DEFINED, [], dbc, path, hint);
77
+ }
78
+ // #endregion Condition checking.
79
+ // #region Referenced Condition checking.
80
+ //
81
+ // For usage in dynamic scenarios (like with AE-DBC).
82
+ //
83
+ /**
84
+ * Invokes the {@link DEFINED.checkAlgorithm } passing the value **toCheck** and the {@link DEFINED.type } .
85
+ *
86
+ * @param toCheck See {@link DEFINED.checkAlgorithm }.
87
+ *
88
+ * @returns See {@link DEFINED.checkAlgorithm}. */
89
+ public check(toCheck: any) {
90
+ return DEFINED.checkAlgorithm(toCheck);
91
+ }
92
+ /**
93
+ * Invokes the {@link DEFINED.checkAlgorithm } passing the value **toCheck** and the {@link DEFINED.type } .
94
+ *
95
+ * @param toCheck See {@link DEFINED.checkAlgorithm }.
96
+ * @param id A {@link string } identifying this {@link INSTANCE } via the {@link DBC.Infringement }-Message.
97
+ *
98
+ * @returns The **CANDIDATE** **toCheck** doesn't fulfill this {@link DEFINED }.
99
+ *
100
+ * @throws A {@link DBC.Infringement } if the **CANDIDATE** **toCheck** does not fulfill this {@link DEFINED }.*/
101
+ public static tsCheck<CANDIDATE = unknown>(
102
+ toCheck: CANDIDATE | undefined | null,
103
+ hint: string | undefined = undefined,
104
+ id: string | undefined = undefined,
105
+ dbc: string | undefined = undefined,
106
+ ): CANDIDATE {
107
+ const result = DEFINED.checkAlgorithm(toCheck);
108
+ if (result === true) {
109
+ return toCheck as CANDIDATE;
110
+ }
111
+ DBC.reportTsCheckInfringement(
112
+ `${id ? `(${id}) ` : ""}${result as string}${hint ? ` ✨ ${hint} ✨` : ""}`,
113
+ dbc,
114
+ );
115
+ return toCheck as CANDIDATE;
116
+ }
117
+ }
package/src/DBC/DOM.ts ADDED
@@ -0,0 +1,291 @@
1
+ import { DBC } from "../DBC";
2
+ import { COMPARISON } from "./COMPARISON";
3
+ import { DEFINED } from "./DEFINED";
4
+ import { EQ } from "./EQ";
5
+ import { REGEX } from "./REGEX";
6
+ import { TYPE } from "./TYPE";
7
+ import { UNDEFINED } from "./UNDEFINED";
8
+
9
+ // ─── Types ────────────────────────────────────────────────────────────────────
10
+
11
+ /** A check function that receives the current field value and the raw attribute string. */
12
+ export type DOMContractCheck = (
13
+ value: string,
14
+ attrValue: string,
15
+ ) => boolean | string;
16
+
17
+ type BoundEntry = {
18
+ element: HTMLInputElement | HTMLTextAreaElement;
19
+ inputListener: () => void;
20
+ compositionStartListener: () => void;
21
+ compositionEndListener: () => void;
22
+ };
23
+
24
+ // ─── Contract registry ────────────────────────────────────────────────────────
25
+
26
+ /**
27
+ * Maps a `data-xdbc-<key>` attribute suffix to a check function.
28
+ * Populated by {@link registerDOMContract } and the built-in defaults below.
29
+ */
30
+ const registry = new Map<string, DOMContractCheck>();
31
+
32
+ /**
33
+ * Registers a check function for a `data-xdbc-<key>` attribute.
34
+ * Call this before {@link scanDOM } to make a contract available declaratively.
35
+ *
36
+ * @param key The attribute suffix (e.g. `"type"` → `data-xdbc-type`).
37
+ * @param checkFn Receives the live field value and the raw attribute string.
38
+ * Return `true` when the value is valid, or a `string` message when it is not.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { registerDOMContract } from "xdbc/DBC/DOM";
43
+ * import { MY_CONTRACT } from "./MY_CONTRACT";
44
+ *
45
+ * registerDOMContract("my-contract", (value, attr) =>
46
+ * MY_CONTRACT.checkAlgorithm(value, attr),
47
+ * );
48
+ * ```
49
+ */
50
+ export function registerDOMContract(
51
+ key: string,
52
+ checkFn: DOMContractCheck,
53
+ ): void {
54
+ registry.set(key, checkFn);
55
+ }
56
+
57
+ // ─── Built-in registrations ───────────────────────────────────────────────────
58
+
59
+ // data-xdbc-regex="^\d*$"
60
+ registerDOMContract("regex", (value, attr) => {
61
+ let rx: RegExp;
62
+ try {
63
+ rx = new RegExp(attr);
64
+ } catch {
65
+ return `[XDBC] Invalid RegExp pattern: "${attr}"`;
66
+ }
67
+ return REGEX.checkAlgorithm(value, rx);
68
+ });
69
+
70
+ // data-xdbc-type="string|number"
71
+ registerDOMContract("type", (value, attr) => TYPE.checkAlgorithm(value, attr));
72
+
73
+ // data-xdbc-eq="hello"
74
+ registerDOMContract("eq", (value, attr) =>
75
+ EQ.checkAlgorithm(value, attr as unknown as object, false),
76
+ );
77
+
78
+ // data-xdbc-different="forbidden"
79
+ registerDOMContract("different", (value, attr) =>
80
+ EQ.checkAlgorithm(value, attr as unknown as object, true),
81
+ );
82
+
83
+ // data-xdbc-defined (attribute presence is enough; value ignored)
84
+ registerDOMContract("defined", (value) => DEFINED.checkAlgorithm(value));
85
+
86
+ // data-xdbc-undefined
87
+ registerDOMContract("undefined", (value) => UNDEFINED.checkAlgorithm(value));
88
+
89
+ // data-xdbc-greater="5"
90
+ registerDOMContract("greater", (value, attr) =>
91
+ COMPARISON.checkAlgorithm(Number(value), Number(attr), false, false),
92
+ );
93
+
94
+ // data-xdbc-greater-or-equal="5"
95
+ registerDOMContract("greater-or-equal", (value, attr) =>
96
+ COMPARISON.checkAlgorithm(Number(value), Number(attr), true, false),
97
+ );
98
+
99
+ // data-xdbc-less="100"
100
+ registerDOMContract("less", (value, attr) =>
101
+ COMPARISON.checkAlgorithm(Number(value), Number(attr), false, true),
102
+ );
103
+
104
+ // data-xdbc-less-or-equal="100"
105
+ registerDOMContract("less-or-equal", (value, attr) =>
106
+ COMPARISON.checkAlgorithm(Number(value), Number(attr), true, true),
107
+ );
108
+
109
+ // data-xdbc-or="regex:^\d+$;;type:string;;eq:42"
110
+ // Fragments are separated by ";;". Each fragment is "<contract-key>:<attr-value>",
111
+ // where the split is on the FIRST ":" only, so colons in the value (e.g. regex) are safe.
112
+ // The OR passes as long as at least one fragment passes.
113
+ registerDOMContract("or", (value, attr) => {
114
+ const fragments = attr.split(";;");
115
+ const messages: string[] = [];
116
+ for (const fragment of fragments) {
117
+ const colonIdx = fragment.indexOf(":");
118
+ const key =
119
+ colonIdx === -1 ? fragment.trim() : fragment.slice(0, colonIdx).trim();
120
+ const fragAttr = colonIdx === -1 ? "" : fragment.slice(colonIdx + 1);
121
+ const checkFn = registry.get(key);
122
+ if (!checkFn) {
123
+ console.warn(`[XDBC] data-xdbc-or: unknown contract key "${key}"`);
124
+ continue;
125
+ }
126
+ const result = checkFn(value, fragAttr);
127
+ if (result === true) return true; // short-circuit on first pass
128
+ if (typeof result === "string") messages.push(result);
129
+ }
130
+ return messages.length > 0
131
+ ? `Value did not satisfy any of: ${messages.join(" | ")}`
132
+ : true;
133
+ });
134
+
135
+ // ─── scanDOM ──────────────────────────────────────────────────────────────────
136
+
137
+ /**
138
+ * Scans the given **root** for `<input>` and `<textarea>` elements marked with the
139
+ * `data-xdbc` attribute, and binds XDBC contracts to their `input` events (covering
140
+ * keyboard, paste, IME / mobile on-screen keyboard, voice input, and autofill).
141
+ *
142
+ * ### Supported attributes
143
+ *
144
+ * | Attribute | Example value | Contract |
145
+ * |----------------------------|----------------------------|-----------------|
146
+ * | `data-xdbc` | *(path or empty)* | marker / DBC path |
147
+ * | `data-xdbc-regex` | `^\d*$` | {@link REGEX} |
148
+ * | `data-xdbc-type` | `string\|number` | {@link TYPE} |
149
+ * | `data-xdbc-eq` | `hello` | {@link EQ} |
150
+ * | `data-xdbc-different` | `forbidden` | {@link EQ} (inverted) |
151
+ * | `data-xdbc-defined` | *(no value needed)* | {@link DEFINED} |
152
+ * | `data-xdbc-undefined` | *(no value needed)* | {@link UNDEFINED} |
153
+ * | `data-xdbc-greater` | `5` | {@link COMPARISON} |
154
+ * | `data-xdbc-greater-or-equal` | `5` | {@link COMPARISON} |
155
+ * | `data-xdbc-less` | `100` | {@link COMPARISON} |
156
+ * | `data-xdbc-less-or-equal` | `100` | {@link COMPARISON} |
157
+ * | `data-xdbc-or` | `regex:^\d+$;;type:string` | {@link OR} (fragment syntax) |
158
+ *
159
+ * Use {@link registerDOMContract } to add further contracts at any time.
160
+ *
161
+ * ### OR fragment syntax
162
+ *
163
+ * Fragments are separated by `;;`. Each fragment is `<contract-key>:<value>`, where the
164
+ * split is on the **first** `:` only, so colons inside values (e.g. regex) are safe.
165
+ * The OR passes as long as **at least one** fragment passes.
166
+ *
167
+ * ```html
168
+ * <!-- digits OR exactly "N/A" -->
169
+ * <input data-xdbc data-xdbc-or="regex:^\d+$;;eq:N/A" />
170
+ * ```
171
+ *
172
+ * ### Behaviour on infringement
173
+ *
174
+ * 1. The element's value is **reverted** to the last accepted state (blocking the invalid input).
175
+ * 2. The configured DBC instance's infringement settings are honoured — `logToConsole`,
176
+ * `onInfringement`, and `throwException` all fire in the usual order. Any throw is caught
177
+ * internally so it cannot propagate out of the DOM event handler.
178
+ *
179
+ * ### IME / composition awareness
180
+ *
181
+ * Validation is suspended during IME composition (e.g. CJK input) and runs once after
182
+ * `compositionend`, so partially composed characters are never incorrectly rejected.
183
+ *
184
+ * @param root Element or Document to scan (default: `document`).
185
+ * @returns A cleanup function that removes all bound event listeners.
186
+ *
187
+ * @example
188
+ * ```html
189
+ * <input type="text" data-xdbc data-xdbc-regex="^\d*$" />
190
+ * <input type="text" data-xdbc="MyApp.DBC" data-xdbc-type="string" data-xdbc-greater="0" />
191
+ * <input type="text" data-xdbc data-xdbc-or="regex:^\d+$;;eq:N/A" />
192
+ * <textarea data-xdbc data-xdbc-regex="^[\w\s]*$"></textarea>
193
+ * ```
194
+ * ```ts
195
+ * import { scanDOM, registerDOMContract } from "xdbc/DBC/DOM";
196
+ *
197
+ * const cleanup = scanDOM();
198
+ * // later:
199
+ * cleanup();
200
+ * ```
201
+ */
202
+ export function scanDOM(root: Element | Document = document): () => void {
203
+ const bound: BoundEntry[] = [];
204
+
205
+ const elements = Array.from(
206
+ root.querySelectorAll<HTMLInputElement | HTMLTextAreaElement>(
207
+ "[data-xdbc]",
208
+ ),
209
+ );
210
+
211
+ for (const el of elements) {
212
+ const dbcPath = el.dataset.xdbc || "WaXCode.DBC";
213
+
214
+ // Collect every registered contract that has a corresponding attribute on this element.
215
+ const checks: Array<{ checkFn: DOMContractCheck; attrValue: string }> = [];
216
+ for (const [key, checkFn] of registry) {
217
+ // dataset converts "xdbc-greater-or-equal" → "xdbcGreaterOrEqual" via camelCase.
218
+ // Build the camelCase key from the registry key.
219
+ const datasetKey = `xdbc${key
220
+ .split("-")
221
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
222
+ .join("")}`;
223
+ if (datasetKey in el.dataset) {
224
+ checks.push({ checkFn, attrValue: el.dataset[datasetKey] ?? "" });
225
+ }
226
+ }
227
+
228
+ if (checks.length === 0) continue;
229
+
230
+ let lastValid = el.value;
231
+ let composing = false;
232
+
233
+ const inputListener = () => {
234
+ if (composing) return;
235
+ const value = el.value;
236
+ for (const { checkFn, attrValue } of checks) {
237
+ const result = checkFn(value, attrValue);
238
+ if (typeof result === "string") {
239
+ el.value = lastValid;
240
+ try {
241
+ DBC.getRegistered(dbcPath).reportParameterInfringement(
242
+ result,
243
+ el,
244
+ undefined,
245
+ el.name || el.id || "input",
246
+ 0,
247
+ value,
248
+ );
249
+ } catch {
250
+ // swallowed — throwException must not propagate out of a DOM event handler
251
+ }
252
+ return; // stop checking further contracts once one fails
253
+ }
254
+ }
255
+ lastValid = value;
256
+ };
257
+
258
+ const compositionStartListener = () => {
259
+ composing = true;
260
+ };
261
+
262
+ const compositionEndListener = () => {
263
+ composing = false;
264
+ inputListener();
265
+ };
266
+
267
+ el.addEventListener("input", inputListener);
268
+ el.addEventListener("compositionstart", compositionStartListener);
269
+ el.addEventListener("compositionend", compositionEndListener);
270
+
271
+ bound.push({
272
+ element: el,
273
+ inputListener,
274
+ compositionStartListener,
275
+ compositionEndListener,
276
+ });
277
+ }
278
+
279
+ return () => {
280
+ for (const {
281
+ element,
282
+ inputListener,
283
+ compositionStartListener,
284
+ compositionEndListener,
285
+ } of bound) {
286
+ element.removeEventListener("input", inputListener);
287
+ element.removeEventListener("compositionstart", compositionStartListener);
288
+ element.removeEventListener("compositionend", compositionEndListener);
289
+ }
290
+ };
291
+ }
@@ -1,57 +1,51 @@
1
- import { EQ } from "../EQ";
2
- /**
3
- * DIFFERENT class for inequality comparisons.
4
- *
5
- * This class extends EQ and provides methods to check if a value is different (not equal)
6
- * from a specified equivalent value. It inverts the equality check by always passing
7
- * `true` for the invert parameter to the parent EQ class methods.
8
- *
9
- * @remarks
10
- * The class provides precondition (PRE), postcondition (POST), and invariant (INVARIANT)
11
- * checks for Design by Contract programming patterns.
12
- *
13
- * @see {@link COMPARISON}
14
- * @see {@link EQ}
15
- */
16
- export class DIFFERENT extends EQ {
17
- /** See {@link EQ.PRE }. Always inverts equality check. */
18
- // biome-ignore lint/suspicious/noExplicitAny: Must match parent signature
19
- public static override PRE(
20
- equivalent: any,
21
- _invert = false,
22
- path: string | undefined = undefined,
23
- hint: string | undefined = undefined,
24
- dbc: string | undefined = undefined,
25
- ) {
26
- return EQ.PRE(equivalent, true, path, hint, dbc);
27
- }
28
-
29
- /** See {@link EQ.POST }. Always inverts equality check. */
30
- // biome-ignore lint/suspicious/noExplicitAny: Must match parent signature
31
- public static override POST(
32
- equivalent: any,
33
- _invert = false,
34
- path: string | undefined = undefined,
35
- hint: string | undefined = undefined,
36
- dbc: string | undefined = undefined,
37
- ) {
38
- return EQ.POST(equivalent, true, path, hint, dbc);
39
- }
40
-
41
- /** See {@link EQ.INVARIANT }. Always inverts equality check. */
42
- // biome-ignore lint/suspicious/noExplicitAny: Must match parent signature
43
- public static INVARIANT(
44
- equivalent: any,
45
- _invert = false,
46
- path: string | undefined = undefined,
47
- hint: string | undefined = undefined,
48
- dbc: string | undefined = undefined,
49
- ) {
50
- return EQ.INVARIANT(equivalent, true, path, hint, dbc);
51
- }
52
- /** See {@link EQ.constructor }. */
53
- // biome-ignore lint/suspicious/noExplicitAny: Must match parent signature
54
- constructor(public equivalent: any) {
55
- super(equivalent, true);
56
- }
57
- }
1
+ import { EQ } from "../EQ";
2
+ /**
3
+ * DIFFERENT class for inequality comparisons.
4
+ *
5
+ * This class extends EQ and provides methods to check if a value is different (not equal)
6
+ * from a specified equivalent value. It inverts the equality check by always passing
7
+ * `true` for the invert parameter to the parent EQ class methods.
8
+ *
9
+ * @remarks
10
+ * The class provides precondition (PRE), postcondition (POST), and invariant (INVARIANT)
11
+ * checks for Design by Contract programming patterns.
12
+ *
13
+ * @see {@link COMPARISON}
14
+ * @see {@link EQ}
15
+ */
16
+ export class DIFFERENT extends EQ {
17
+ /** See {@link EQ.PRE }. Always inverts equality check. */
18
+ public static override PRE(
19
+ equivalent: any,
20
+ _invert = false,
21
+ path: string | undefined = undefined,
22
+ hint: string | undefined = undefined,
23
+ dbc: string | undefined = undefined,
24
+ ) {
25
+ return EQ.PRE(equivalent, true, path, hint, dbc);
26
+ }
27
+ /** See {@link EQ.POST }. Always inverts equality check. */
28
+ public static override POST(
29
+ equivalent: any,
30
+ _invert = false,
31
+ path: string | undefined = undefined,
32
+ hint: string | undefined = undefined,
33
+ dbc: string | undefined = undefined,
34
+ ) {
35
+ return EQ.POST(equivalent, true, path, hint, dbc);
36
+ }
37
+ /** See {@link EQ.INVARIANT }. Always inverts equality check. */
38
+ public static INVARIANT(
39
+ equivalent: any,
40
+ _invert = false,
41
+ path: string | undefined = undefined,
42
+ hint: string | undefined = undefined,
43
+ dbc: string | undefined = undefined,
44
+ ) {
45
+ return EQ.INVARIANT(equivalent, true, path, hint, dbc);
46
+ }
47
+ /** See {@link EQ.constructor }. */
48
+ constructor(public equivalent: any) {
49
+ super(equivalent, true);
50
+ }
51
+ }