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.
- package/.gitattributes +8 -0
- package/.vscode/settings.json +3 -3
- package/.vscode/tasks.json +23 -23
- package/ASSESSMENT.md +249 -0
- package/README.md +131 -1
- package/__tests__/DBC/AE.test.ts +62 -62
- package/__tests__/DBC/ARRAY.test.ts +91 -91
- package/__tests__/DBC/DEFINED.test.ts +53 -53
- package/__tests__/DBC/DOM.test.ts +481 -0
- package/__tests__/DBC/Decorators.test.ts +367 -367
- package/__tests__/DBC/EQ.test.ts +13 -13
- package/__tests__/DBC/GREATER.test.ts +31 -31
- package/__tests__/DBC/HasAttribute.test.ts +60 -60
- package/__tests__/DBC/IF.test.ts +62 -62
- package/__tests__/DBC/INSTANCE.test.ts +13 -13
- package/__tests__/DBC/JSON.OP.test.ts +47 -47
- package/__tests__/DBC/JSON.Parse.test.ts +17 -17
- package/__tests__/DBC/OR.test.ts +14 -14
- package/__tests__/DBC/PLAIN_OBJECT.test.ts +109 -109
- package/__tests__/DBC/REGEX.test.ts +17 -17
- package/__tests__/DBC/TYPE.test.ts +13 -13
- package/__tests__/DBC/UNDEFINED.test.ts +45 -45
- package/__tests__/DBC/ZOD.test.ts +54 -54
- package/__tests__/DBC/onInfringement.test.ts +262 -0
- package/biome.json +40 -40
- package/dist/DBC/AE.js +172 -0
- package/dist/DBC/ARR/PLAIN_OBJECT.d.ts +0 -3
- package/dist/DBC/ARR/PLAIN_OBJECT.js +95 -0
- package/dist/DBC/ARRAY.d.ts +0 -3
- package/dist/DBC/ARRAY.js +90 -0
- package/dist/DBC/COMPARISON/GREATER.js +21 -0
- package/dist/DBC/COMPARISON/GREATER_OR_EQUAL.js +21 -0
- package/dist/DBC/COMPARISON/LESS.js +21 -0
- package/dist/DBC/COMPARISON/LESS_OR_EQUAL.js +21 -0
- package/dist/DBC/COMPARISON.js +98 -0
- package/dist/DBC/DEFINED.js +87 -0
- package/dist/DBC/DOM.d.ts +87 -0
- package/dist/DBC/DOM.js +223 -0
- package/dist/DBC/EQ/DIFFERENT.js +34 -0
- package/dist/DBC/EQ.js +101 -0
- package/dist/DBC/HasAttribute.js +101 -0
- package/dist/DBC/IF.js +96 -0
- package/dist/DBC/INSTANCE.js +122 -0
- package/dist/DBC/JSON.OP.js +120 -0
- package/dist/DBC/JSON.Parse.js +104 -0
- package/dist/DBC/OR.js +125 -0
- package/dist/DBC/REGEX.js +136 -0
- package/dist/DBC/TYPE.js +112 -0
- package/dist/DBC/UNDEFINED.js +87 -0
- package/dist/DBC/ZOD.js +99 -0
- package/dist/DBC.d.ts +18 -4
- package/dist/DBC.js +645 -0
- package/dist/Demo.d.ts +10 -0
- package/dist/Demo.js +713 -0
- package/dist/bundle.js +6140 -405
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/jest.config.js +32 -32
- package/package.json +71 -55
- package/src/DBC/AE.ts +269 -288
- package/src/DBC/ARR/PLAIN_OBJECT.ts +122 -133
- package/src/DBC/ARRAY.ts +117 -127
- package/src/DBC/COMPARISON/GREATER.ts +41 -46
- package/src/DBC/COMPARISON/GREATER_OR_EQUAL.ts +41 -45
- package/src/DBC/COMPARISON/LESS.ts +41 -45
- package/src/DBC/COMPARISON/LESS_OR_EQUAL.ts +41 -45
- package/src/DBC/COMPARISON.ts +149 -159
- package/src/DBC/DEFINED.ts +117 -122
- package/src/DBC/DOM.ts +291 -0
- package/src/DBC/EQ/DIFFERENT.ts +51 -57
- package/src/DBC/EQ.ts +154 -163
- package/src/DBC/HasAttribute.ts +149 -154
- package/src/DBC/IF.ts +173 -179
- package/src/DBC/INSTANCE.ts +168 -171
- package/src/DBC/JSON.OP.ts +178 -186
- package/src/DBC/JSON.Parse.ts +150 -157
- package/src/DBC/OR.ts +183 -187
- package/src/DBC/REGEX.ts +195 -196
- package/src/DBC/TYPE.ts +142 -149
- package/src/DBC/UNDEFINED.ts +115 -117
- package/src/DBC/ZOD.ts +130 -135
- package/src/DBC.ts +902 -904
- package/src/Demo.ts +537 -404
- package/src/index.ts +22 -0
- package/tsconfig.json +18 -18
- package/tsconfig.test.json +7 -7
- package/typedoc.json +16 -16
- package/webpack.config.js +27 -27
- package/Assessment.md +0 -507
package/src/DBC/DEFINED.ts
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* @
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* @
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* @
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
}
|
package/src/DBC/EQ/DIFFERENT.ts
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
}
|