@zmdb/validator 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @zmdb/validator
2
+
3
+ `@zmdb/validator` is zmdb's compiler-free validation and serialization runtime. Generated application code calls this package's errors and helpers; it does not load TypeScript, filesystem tooling,
4
+ build plugins, Metro, or lint hosts.
5
+
6
+ Part of **[zmdb](https://github.com/ambasta/zmdb)**. The TypeScript front end, reflection, emitters, project compilation, and host adapters live in `@zmdb/compiler`.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ yarn add @zmdb/validator@1.0.0-beta.1
12
+ ```
13
+
14
+ > **Prerelease** (`1.0.0-beta.1`). Requires **Node.js 26+** and is **ESM-only**.
15
+
16
+ ## Entry points
17
+
18
+ - `@zmdb/validator`
19
+ - `@zmdb/validator/advanced`
20
+ - `@zmdb/validator/errors`
21
+ - `@zmdb/validator/serialization`
22
+
23
+ For build-time reflection and AOT compilation:
24
+
25
+ ```bash
26
+ yarn add --dev @zmdb/compiler@1.0.0-beta.1 typescript@^7
27
+ ```
28
+
29
+ ## Documentation
30
+
31
+ Full docs: **https://ambasta.github.io/zmdb/**
32
+
33
+ ## License
34
+
35
+ GNU General Public License v3.0 or later (GPL-3.0-or-later) — see [LICENSE](./LICENSE).
@@ -0,0 +1,53 @@
1
+ import { type Rule, type ValidationIssue } from '../index.js';
2
+ export type { ValidationIssue };
3
+ declare const __brand: unique symbol;
4
+ export type Brand<Base, Tag extends string> = Base & {
5
+ readonly [__brand]: Tag;
6
+ };
7
+ export type RefinePredicate = (v: unknown) => boolean;
8
+ export type TransformFn = (v: unknown) => unknown;
9
+ /**
10
+ * A refinement rule: a real predicate plus its intrinsic source text for inspection.
11
+ *
12
+ * The predicate is passed in as a *function value*, not a source string that we
13
+ * compile or interpret at import time. A string form creates a second executable
14
+ * language whose property and call reachability must be secured; a function is
15
+ * instead typechecked at the call site. `source` records the function's intrinsic
16
+ * text for inspection; the current emitter does not consume advanced-rule source
17
+ * (PRD §9.5).
18
+ */
19
+ export interface RefineRule extends Rule {
20
+ readonly kind: 'refine';
21
+ readonly source: string;
22
+ readonly message: string;
23
+ readonly predicate: RefinePredicate;
24
+ }
25
+ /** A post-validation conversion. Same function-not-source rule as {@link RefineRule}. */
26
+ export interface TransformRule extends Rule {
27
+ readonly kind: 'transform';
28
+ readonly source: string;
29
+ readonly apply: TransformFn;
30
+ }
31
+ export interface UnionRule extends Rule {
32
+ readonly kind: 'union';
33
+ readonly branches: readonly Rule[];
34
+ }
35
+ export interface DiscriminatedRule extends Rule {
36
+ readonly kind: 'discriminated';
37
+ readonly key: string;
38
+ readonly map: Record<string, Rule>;
39
+ }
40
+ export declare function refine(predicate: RefinePredicate, message: string): RefineRule;
41
+ export declare function transform(apply: TransformFn): TransformRule;
42
+ export declare function union(...rules: readonly Rule[]): UnionRule;
43
+ export declare function discriminated(key: string, map: Record<string, Rule>): DiscriminatedRule;
44
+ export declare function evalRule(rule: Rule, value: unknown): boolean;
45
+ export declare const coerce: {
46
+ readonly number: (expr: unknown) => number;
47
+ };
48
+ export type ObjectMode = 'strict' | 'strip' | 'passthrough';
49
+ export declare function validateObject(value: unknown, rules: Record<string, Rule>, mode: ObjectMode): {
50
+ success: boolean;
51
+ issues: readonly ValidationIssue[];
52
+ };
53
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/advanced/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9D,YAAY,EAAE,eAAe,EAAE,CAAC;AAKhC,OAAO,CAAC,MAAM,OAAO,EAAE,OAAO,MAAM,CAAC;AACrC,MAAM,MAAM,KAAK,CAAC,IAAI,EAAE,GAAG,SAAS,MAAM,IAAI,IAAI,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC;AAOjF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;AACtD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;AAElD;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAW,SAAQ,IAAI;IACtC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;CACrC;AACD,yFAAyF;AACzF,MAAM,WAAW,aAAc,SAAQ,IAAI;IACzC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;CAC7B;AACD,MAAM,WAAW,SAAU,SAAQ,IAAI;IACrC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,IAAI,EAAE,CAAC;CACpC;AACD,MAAM,WAAW,iBAAkB,SAAQ,IAAI;IAC7C,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CACpC;AAED,wBAAgB,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAY9E;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAW3D;AAED,wBAAgB,KAAK,CAAC,GAAG,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,SAAS,CAE1D;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,iBAAiB,CAOvF;AAgBD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAW5D;AAED,eAAO,MAAM,MAAM;aACjB,MAAM,SAAO,OAAO,KAAG,MAAM;CAKrB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,aAAa,CAAC;AA2B5D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAC3B,IAAI,EAAE,UAAU,GACf;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;CAAE,CA0B1D"}
@@ -0,0 +1,132 @@
1
+ // Advanced validation — implementation.
2
+ // #46 refinement compilation (refine + refinement-aware validateObject with
3
+ // exact error paths). Coercion + object strictness (#49) are co-implemented
4
+ // because the same validateObject drives them. #47 transform remains a rule
5
+ // constructor only; no validator path applies it.
6
+ import {} from '../index.js';
7
+ /** True for a non-null, non-array object — proves a keyed read is safe. */
8
+ function isRecord(value) {
9
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
10
+ }
11
+ export function refine(predicate, message) {
12
+ if (typeof predicate !== 'function') {
13
+ throw new TypeError('refine() requires a function value; source strings are not supported');
14
+ }
15
+ const source = Function.prototype.toString.call(predicate);
16
+ return Object.freeze({
17
+ kind: 'refine',
18
+ args: Object.freeze([source, message]),
19
+ source,
20
+ message,
21
+ predicate,
22
+ });
23
+ }
24
+ export function transform(apply) {
25
+ if (typeof apply !== 'function') {
26
+ throw new TypeError('transform() requires a function value; source strings are not supported');
27
+ }
28
+ const source = Function.prototype.toString.call(apply);
29
+ return Object.freeze({
30
+ kind: 'transform',
31
+ args: Object.freeze([source]),
32
+ source,
33
+ apply,
34
+ });
35
+ }
36
+ export function union(...rules) {
37
+ return Object.freeze({ kind: 'union', args: Object.freeze(rules), branches: rules });
38
+ }
39
+ export function discriminated(key, map) {
40
+ return Object.freeze({
41
+ kind: 'discriminated',
42
+ args: Object.freeze([key]),
43
+ key,
44
+ map,
45
+ });
46
+ }
47
+ // `Rule` is an open interface (any package may add a kind), so `rule.kind === x`
48
+ // cannot narrow it the way a closed union would. These guards check the tag *and*
49
+ // the payload it implies, which is what makes the narrowing sound without a cast.
50
+ function isRefine(rule) {
51
+ return rule.kind === 'refine' && 'predicate' in rule && typeof rule.predicate === 'function';
52
+ }
53
+ function isUnion(rule) {
54
+ return rule.kind === 'union' && 'branches' in rule && Array.isArray(rule.branches);
55
+ }
56
+ function isDiscriminated(rule) {
57
+ return rule.kind === 'discriminated' && 'key' in rule && typeof rule.key === 'string' && 'map' in rule;
58
+ }
59
+ // Runtime evaluator for the rule-value API. The type-first emitter is a separate path.
60
+ export function evalRule(rule, value) {
61
+ if (isUnion(rule))
62
+ return rule.branches.some(b => evalRule(b, value));
63
+ if (isDiscriminated(rule)) {
64
+ if (!isRecord(value))
65
+ return false;
66
+ const disc = value[rule.key];
67
+ if (typeof disc !== 'string')
68
+ return false;
69
+ const branch = rule.map[disc];
70
+ if (!branch)
71
+ return false;
72
+ return evalRule(branch, value.value);
73
+ }
74
+ return checkRule(rule, value).ok;
75
+ }
76
+ export const coerce = {
77
+ number(expr) {
78
+ const n = Number(expr);
79
+ if (Number.isNaN(n))
80
+ throw new TypeError(`cannot coerce to number: ${String(expr)}`);
81
+ return n;
82
+ },
83
+ };
84
+ function checkRule(rule, value) {
85
+ // Refinement rule.
86
+ if (isRefine(rule)) {
87
+ return { ok: rule.predicate(value), expected: rule.source, message: rule.message };
88
+ }
89
+ // Primitive tag rules (mirror the aot-validator runtime fallback).
90
+ const [arg] = rule.args;
91
+ switch (rule.kind) {
92
+ case 'Min':
93
+ return {
94
+ ok: typeof value === 'number' && typeof arg === 'number' && value >= arg,
95
+ expected: `number >= ${String(arg)}`,
96
+ message: `must be >= ${String(arg)}`,
97
+ };
98
+ case 'MaxLength':
99
+ return {
100
+ ok: typeof value === 'string' && typeof arg === 'number' && value.length <= arg,
101
+ expected: `string length <= ${String(arg)}`,
102
+ message: `length must be <= ${String(arg)}`,
103
+ };
104
+ default:
105
+ return { ok: true, expected: rule.kind, message: '' };
106
+ }
107
+ }
108
+ export function validateObject(value, rules, mode) {
109
+ const issues = [];
110
+ const obj = isRecord(value) ? value : {};
111
+ // Excess-key handling for strict mode.
112
+ if (mode === 'strict') {
113
+ for (const key of Object.keys(obj)) {
114
+ if (!(key in rules)) {
115
+ issues.push({
116
+ path: `input.${key}`,
117
+ expected: 'no excess property',
118
+ value: obj[key],
119
+ message: `unexpected property "${key}"`,
120
+ });
121
+ }
122
+ }
123
+ }
124
+ for (const [key, rule] of Object.entries(rules)) {
125
+ const res = checkRule(rule, obj[key]);
126
+ if (!res.ok) {
127
+ issues.push({ path: `input.${key}`, expected: res.expected, value: obj[key], message: res.message });
128
+ }
129
+ }
130
+ return { success: issues.length === 0, issues };
131
+ }
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/advanced/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type ValidationIssue } from './index.js';
2
+ export declare class AssertError extends Error {
3
+ readonly issues: readonly ValidationIssue[];
4
+ constructor(message: string, issues?: readonly ValidationIssue[]);
5
+ }
6
+ /** Throw an `AssertError` carrying `issues`; the first issue supplies the message. */
7
+ export declare function failWith(issues: readonly ValidationIssue[]): never;
8
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IAE5C,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,SAAS,eAAe,EAAO,EAInE;CACF;AAED,sFAAsF;AACtF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,KAAK,CAGlE"}
package/dist/errors.js ADDED
@@ -0,0 +1,24 @@
1
+ // The one thing emitted code imports at runtime.
2
+ //
3
+ // `assert<T>(x)` has to throw something, and the interesting question is *what*. A
4
+ // hoisted class in the emitted prelude would make `err instanceof AssertError` false in
5
+ // a built bundle and true in dev — the exact class of bug the AOT path exists to avoid,
6
+ // since the two paths are supposed to be indistinguishable (REQ-AV-4).
7
+ //
8
+ // So the emitted prelude imports this module, and it is kept in its own file precisely
9
+ // so that import costs a few lines rather than the whole runtime walker.
10
+ import {} from './index.js';
11
+ export class AssertError extends Error {
12
+ issues;
13
+ constructor(message, issues = []) {
14
+ super(message);
15
+ this.name = 'AssertError';
16
+ this.issues = issues;
17
+ }
18
+ }
19
+ /** Throw an `AssertError` carrying `issues`; the first issue supplies the message. */
20
+ export function failWith(issues) {
21
+ const first = issues[0];
22
+ throw new AssertError(first ? first.message : 'validation failed', issues);
23
+ }
24
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,EAAE;AACF,mFAAmF;AACnF,wFAAwF;AACxF,wFAAwF;AACxF,uEAAuE;AACvE,EAAE;AACF,uFAAuF;AACvF,yEAAyE;AAEzE,OAAO,EAAwB,MAAM,YAAY,CAAC;AAElD,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,MAAM,CAA6B;IAE5C,YAAY,OAAe,EAAE,MAAM,GAA+B,EAAE;QAClE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,sFAAsF;AACtF,MAAM,UAAU,QAAQ,CAAC,MAAkC;IACzD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;AAC7E,CAAC"}
@@ -0,0 +1,21 @@
1
+ export { AssertError, failWith } from './errors.js';
2
+ export { getCachedRegExp, MAX_REGEX_CACHE_SIZE, validatePatternComplexity } from './regex-complexity.js';
3
+ export interface Rule {
4
+ readonly kind: string;
5
+ readonly args: readonly unknown[];
6
+ }
7
+ export declare const tags: {
8
+ readonly Min: (n: number) => Rule;
9
+ readonly Max: (n: number) => Rule;
10
+ readonly MinLength: (n: number) => Rule;
11
+ readonly MaxLength: (n: number) => Rule;
12
+ readonly Pattern: (re: string) => Rule;
13
+ readonly Enum: (...values: readonly string[]) => Rule;
14
+ };
15
+ export declare function getRegExp(pattern: string): RegExp;
16
+ export declare function getEnumSet(values: readonly unknown[]): Set<unknown>;
17
+ export declare function validateRule(r: Rule, expr: unknown): boolean;
18
+ export { ValidationError, claimsValidationIssues, validationIssuesOf } from './validation-error.js';
19
+ export type { ValidationIssue } from './validation-error.js';
20
+ export * from './utilities/index.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAEzG,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;CACnC;AAYD,eAAO,MAAM,IAAI;aACf,GAAG,MAAI,MAAM,KAAG,IAAI;aAGpB,GAAG,MAAI,MAAM,KAAG,IAAI;aAGpB,SAAS,MAAI,MAAM,KAAG,IAAI;aAG1B,SAAS,MAAI,MAAM,KAAG,IAAI;aAG1B,OAAO,OAAK,MAAM,KAAG,IAAI;aAIzB,IAAI,cAAY,SAAS,MAAM,EAAE,KAAG,IAAI;CAGhC,CAAC;AAIX,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBjD;AAGD,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,CAOnE;AAID,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CA6B5D;AAED,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACpG,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,cAAc,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,102 @@
1
+ // @zmdb/validator — the runtime half.
2
+ //
3
+ // Nothing reachable from this entry point may import `typescript`. The compiler is a
4
+ // build-time dependency, and an application that imports `tags` to declare a
5
+ // constraint must not pull it into a runtime bundle. Compiler ownership lives in
6
+ // `@zmdb/compiler`; `.github/scripts/verify-exports.mjs` enforces the split.
7
+ import { getCachedRegExp, MAX_REGEX_CACHE_SIZE, validatePatternComplexity } from './regex-complexity.js';
8
+ export { AssertError, failWith } from './errors.js';
9
+ export { getCachedRegExp, MAX_REGEX_CACHE_SIZE, validatePatternComplexity } from './regex-complexity.js';
10
+ function rule(kind, ...args) {
11
+ return Object.freeze({ kind, args: Object.freeze(args) });
12
+ }
13
+ // One spelling per constraint, and it is the tag's. `Min`/`Max` used to be `Minimum`/
14
+ // `Maximum` here while the type-level tag has always been `Min<N>`/`Max<N>`, which left
15
+ // `ir/index.ts` folding case *and* mapping two names onto one kind. Now that a constraint
16
+ // is declared as `number & Min<18>`, the `tags.Min(18)` call has no declaration role
17
+ // left — the `Rule` object is the AOT's pre-transform fallback representation — so the
18
+ // name that stays is the one you write in a type.
19
+ export const tags = {
20
+ Min(n) {
21
+ return rule('Min', n);
22
+ },
23
+ Max(n) {
24
+ return rule('Max', n);
25
+ },
26
+ MinLength(n) {
27
+ return rule('MinLength', n);
28
+ },
29
+ MaxLength(n) {
30
+ return rule('MaxLength', n);
31
+ },
32
+ Pattern(re) {
33
+ validatePatternComplexity(re);
34
+ return rule('Pattern', re);
35
+ },
36
+ Enum(...values) {
37
+ return rule('Enum', ...values);
38
+ },
39
+ };
40
+ // Caches for zero-allocation fallback validation.
41
+ const regexCache = new Map();
42
+ export function getRegExp(pattern) {
43
+ let re = regexCache.get(pattern);
44
+ if (re) {
45
+ regexCache.delete(pattern);
46
+ regexCache.set(pattern, re);
47
+ return re;
48
+ }
49
+ if (regexCache.size >= MAX_REGEX_CACHE_SIZE) {
50
+ const oldestKey = regexCache.keys().next().value;
51
+ if (oldestKey !== undefined) {
52
+ regexCache.delete(oldestKey);
53
+ }
54
+ }
55
+ re = new RegExp(pattern);
56
+ regexCache.set(pattern, re);
57
+ return re;
58
+ }
59
+ const enumSetCache = new WeakMap();
60
+ export function getEnumSet(values) {
61
+ let set = enumSetCache.get(values);
62
+ if (!set) {
63
+ set = new Set(values);
64
+ enumSetCache.set(values, set);
65
+ }
66
+ return set;
67
+ }
68
+ // Runtime-safety fallback: identical boolean semantics to the inlined form.
69
+ // This is what executes pre-transform (dev / ts-node).
70
+ export function validateRule(r, expr) {
71
+ // `args` is `readonly unknown[]` (any package may define a kind), so the bound
72
+ // is re-checked rather than asserted: `typeof arg === 'number'` costs nothing a
73
+ // JIT can't fold, and this is the fallback path — the AOT emission is what the
74
+ // benchmarks measure.
75
+ const [arg] = r.args;
76
+ switch (r.kind) {
77
+ case 'Min':
78
+ return typeof expr === 'number' && typeof arg === 'number' && expr >= arg;
79
+ case 'Max':
80
+ return typeof expr === 'number' && typeof arg === 'number' && expr <= arg;
81
+ case 'MinLength':
82
+ return typeof expr === 'string' && typeof arg === 'number' && expr.length >= arg;
83
+ case 'MaxLength':
84
+ return typeof expr === 'string' && typeof arg === 'number' && expr.length <= arg;
85
+ case 'Pattern':
86
+ // No input-length cap. There used to be one — 10 000 characters, and a throw past it
87
+ // — but the inlined form is `/pat/.test(x)` with no such limit, so the same call
88
+ // answered `false` in a build and threw in dev. REQ-AV-4 does not allow that, and of
89
+ // the two behaviours the cap is the one with no counterpart to move it to.
90
+ // Re-checked, not asserted, for the reason above: an unchecked `args[0]` would have
91
+ // reached `new RegExp` and been coerced, so `Pattern` with a number in it compiled a
92
+ // pattern rather than answering `false` like every other rule with a bad argument.
93
+ return typeof expr === 'string' && typeof arg === 'string' && getCachedRegExp(arg).test(expr);
94
+ case 'Enum':
95
+ return getEnumSet(r.args).has(expr);
96
+ default:
97
+ throw new Error(`unknown rule kind: ${r.kind}`);
98
+ }
99
+ }
100
+ export { ValidationError, claimsValidationIssues, validationIssuesOf } from './validation-error.js';
101
+ export * from './utilities/index.js';
102
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { ValidationError } from './index.js';
2
+ export { ValidationError };
3
+ export declare function validatePatternComplexity(pattern: string): void;
4
+ export declare const MAX_REGEX_CACHE_SIZE = 1000;
5
+ export declare function getCachedRegExp(pattern: string): RegExp;
6
+ //# sourceMappingURL=regex-complexity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex-complexity.d.ts","sourceRoot":"","sources":["../src/regex-complexity.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAO/D;AAED,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAGzC,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiBvD"}
@@ -0,0 +1,42 @@
1
+ // Regular expression syntax validation and a bounded compiled-RegExp cache.
2
+ //
3
+ // This validates syntax only; there is no static ReDoS analysis here, and the name is a
4
+ // leftover from when there was meant to be.
5
+ //
6
+ // There used to also be a `safeTestPattern` that refused inputs over 10 000 characters. It
7
+ // is gone, because the emitted form of the same check is `/pat/.test(x)` and has no such
8
+ // limit: keeping it meant one call site answering `false` after a build and throwing before
9
+ // one, which is precisely the divergence REQ-AV-4 exists to rule out. A cap that only one
10
+ // of the two paths can honour is not a safety feature.
11
+ import { ValidationError } from './index.js';
12
+ export { ValidationError };
13
+ export function validatePatternComplexity(pattern) {
14
+ try {
15
+ RegExp(pattern);
16
+ }
17
+ catch (err) {
18
+ const msg = err instanceof Error ? err.message : String(err);
19
+ throw new ValidationError(`Invalid regular expression pattern: ${msg}`);
20
+ }
21
+ }
22
+ export const MAX_REGEX_CACHE_SIZE = 1000;
23
+ const patternCache = new Map();
24
+ export function getCachedRegExp(pattern) {
25
+ let re = patternCache.get(pattern);
26
+ if (re) {
27
+ patternCache.delete(pattern);
28
+ patternCache.set(pattern, re);
29
+ return re;
30
+ }
31
+ validatePatternComplexity(pattern);
32
+ if (patternCache.size >= MAX_REGEX_CACHE_SIZE) {
33
+ const oldestKey = patternCache.keys().next().value;
34
+ if (oldestKey !== undefined) {
35
+ patternCache.delete(oldestKey);
36
+ }
37
+ }
38
+ re = new RegExp(pattern);
39
+ patternCache.set(pattern, re);
40
+ return re;
41
+ }
42
+ //# sourceMappingURL=regex-complexity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex-complexity.js","sourceRoot":"","sources":["../src/regex-complexity.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,wFAAwF;AACxF,4CAA4C;AAC5C,EAAE;AACF,2FAA2F;AAC3F,yFAAyF;AACzF,4FAA4F;AAC5F,0FAA0F;AAC1F,uDAAuD;AAEvD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,MAAM,UAAU,yBAAyB,CAAC,OAAe;IACvD,IAAI,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,eAAe,CAAC,uCAAuC,GAAG,EAAE,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AACzC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE/C,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,IAAI,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,EAAE,EAAE,CAAC;QACP,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7B,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC9B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,yBAAyB,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,YAAY,CAAC,IAAI,IAAI,oBAAoB,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QACnD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IACD,EAAE,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC9B,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type ValidationIssue } from '../advanced/index.js';
2
+ import { type TypeIR } from '../index.js';
3
+ export declare function stringify(value: unknown): string;
4
+ export declare function assertStringify(value: unknown, schema?: TypeIR): string;
5
+ export interface ParseResult<T> {
6
+ readonly success: boolean;
7
+ readonly data?: T;
8
+ readonly issues?: readonly ValidationIssue[];
9
+ }
10
+ /**
11
+ * Parse JSON without rebuilding the object graph. Reports malformed input as
12
+ * structured issues instead of throwing.
13
+ *
14
+ * `T` is an *unvalidated* claim about the payload — exactly as much as
15
+ * `JSON.parse` gives you, and no more. Use {@link decode} when you need the
16
+ * claim checked against a schema.
17
+ */
18
+ export declare function parse<T = unknown>(text: string): ParseResult<T>;
19
+ export declare function decode<T = unknown>(text: string, schema?: TypeIR): ParseResult<T>;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/serialization/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAI/D,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKhD;AAID,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAIvE;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;CAC9C;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAmB/D;AAKD,wBAAgB,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAUjF"}
@@ -0,0 +1,64 @@
1
+ // JSON serialization and validated parsing.
2
+ import {} from '../advanced/index.js';
3
+ import { assert, AssertError } from '../index.js';
4
+ // Native JSON owns property traversal and toJSON calls. Primitive bigint is
5
+ // rejected before a root toJSON hook can run; native JSON rejects nested bigint.
6
+ export function stringify(value) {
7
+ if (typeof value === 'bigint') {
8
+ throw new TypeError('Do not know how to serialize a BigInt');
9
+ }
10
+ return JSON.stringify(value);
11
+ }
12
+ // `TypeIR`: the witness a user has is the generated one, and there is no longer a
13
+ // hand-written form of it to accept (REQ-TF-9).
14
+ export function assertStringify(value, schema) {
15
+ // Validate first (throws AssertError on failure), then serialize.
16
+ assert(value, schema);
17
+ return stringify(value);
18
+ }
19
+ /**
20
+ * Parse JSON without rebuilding the object graph. Reports malformed input as
21
+ * structured issues instead of throwing.
22
+ *
23
+ * `T` is an *unvalidated* claim about the payload — exactly as much as
24
+ * `JSON.parse` gives you, and no more. Use {@link decode} when you need the
25
+ * claim checked against a schema.
26
+ */
27
+ export function parse(text) {
28
+ try {
29
+ // boundary: JSON.parse is `any` by definition; the caller's `T` is asserted,
30
+ // not proven. `decode` is the proving variant.
31
+ const data = JSON.parse(text);
32
+ return { success: true, data };
33
+ }
34
+ catch (err) {
35
+ return {
36
+ success: false,
37
+ issues: [
38
+ {
39
+ path: 'input',
40
+ expected: 'valid JSON',
41
+ value: text,
42
+ message: err instanceof Error ? err.message : 'invalid JSON',
43
+ },
44
+ ],
45
+ };
46
+ }
47
+ }
48
+ // #54 — typed parse/decode: parse JSON then validate into T against a schema.
49
+ // Malformed JSON or a validation failure yields success:false with structured
50
+ // issues (exact paths).
51
+ export function decode(text, schema) {
52
+ const parsed = parse(text);
53
+ if (!parsed.success)
54
+ return parsed;
55
+ try {
56
+ const data = assert(parsed.data, schema);
57
+ return { success: true, data };
58
+ }
59
+ catch (err) {
60
+ const issues = err instanceof AssertError ? err.issues : [];
61
+ return { success: false, issues };
62
+ }
63
+ }
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/serialization/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,OAAO,EAAwB,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,WAAW,EAAe,MAAM,aAAa,CAAC;AAE/D,4EAA4E;AAC5E,iFAAiF;AACjF,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,kFAAkF;AAClF,gDAAgD;AAChD,MAAM,UAAU,eAAe,CAAC,KAAc,EAAE,MAAe;IAC7D,kEAAkE;IAClE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACtB,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC;AAQD;;;;;;;GAOG;AACH,MAAM,UAAU,KAAK,CAAc,IAAY;IAC7C,IAAI,CAAC;QACH,6EAA6E;QAC7E,+CAA+C;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;QACnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE;gBACN;oBACE,IAAI,EAAE,OAAO;oBACb,QAAQ,EAAE,YAAY;oBACtB,KAAK,EAAE,IAAI;oBACX,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc;iBAC7D;aACF;SACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,8EAA8E;AAC9E,wBAAwB;AACxB,MAAM,UAAU,MAAM,CAAc,IAAY,EAAE,MAAe;IAC/D,MAAM,MAAM,GAAG,KAAK,CAAI,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAI,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,GAAG,YAAY,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACpC,CAAC;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { type TypeIR } from '@zmdb/schema/ir';
2
+ import { type ValidationIssue } from '../index.js';
3
+ export { AssertError, failWith } from '../errors.js';
4
+ export interface ValidateResult<T> {
5
+ readonly success: boolean;
6
+ readonly data?: T;
7
+ readonly errors?: readonly ValidationIssue[];
8
+ }
9
+ export declare function is<T = unknown>(input: unknown, schema?: TypeIR): input is T;
10
+ export declare function assert<T = unknown>(input: unknown, schema?: TypeIR): T;
11
+ export declare function validate<T = unknown>(input: unknown, schema?: TypeIR): ValidateResult<T>;
12
+ /**
13
+ * Validate only through depth `D`; an ordinary call is replaced at build time.
14
+ *
15
+ * The optional witness and runtime depth exist for tests and generated fallback
16
+ * modules. A real untransformed call has neither and throws `MISSING`, exactly as
17
+ * the full-depth utility family does.
18
+ */
19
+ export declare function isShallow<T = unknown, D extends number = 1>(input: unknown, schema?: TypeIR, depth?: D): input is T;
20
+ export declare function assertShallow<T = unknown, D extends number = 1>(input: unknown, schema?: TypeIR, depth?: D): T;
21
+ export declare function validateShallow<T = unknown, D extends number = 1>(input: unknown, schema?: TypeIR, depth?: D): ValidateResult<T>;
22
+ export declare function equals<T = unknown>(input: unknown, schema?: TypeIR): input is T;
23
+ export declare function assertEquals<T = unknown>(input: unknown, schema?: TypeIR): T;
24
+ export declare function random<T = unknown>(schema?: TypeIR, rng?: () => number): T;
25
+ /** Every issue, for a caller that wants them without a `ValidateResult` wrapper. */
26
+ export declare function issuesFor(input: unknown, schema: TypeIR, path?: string): readonly ValidationIssue[];
27
+ export type { TypeIR };
28
+ export type { ValidationIssue };
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utilities/index.ts"],"names":[],"mappings":"AAyBA,OAAO,EAWL,KAAK,MAAM,EAEZ,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAOrD,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;CAC9C;AAqkBD,wBAAgB,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,IAAI,CAAC,CAG3E;AAED,wBAAgB,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,CAWtE;AAED,wBAAgB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAOxF;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,CAGnH;AAED,wBAAgB,aAAa,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAQ9G;AAED,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,MAAM,GAAG,CAAC,EAC/D,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,CAAC,GACR,cAAc,CAAC,CAAC,CAAC,CAQnB;AAED,wBAAgB,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,IAAI,CAAC,CAI/E;AAED,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,CAe5E;AAED,wBAAgB,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,MAAM,GAAG,CAAC,CAW1E;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,SAAU,GAAG,SAAS,eAAe,EAAE,CAKpG;AAKD,YAAY,EAAE,MAAM,EAAE,CAAC;AACvB,YAAY,EAAE,eAAe,EAAE,CAAC"}