@oh-my-pi/omptype 17.2.6 → 17.2.7
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/CHANGELOG.md +17 -2
- package/README.md +83 -14
- package/dist/js/ark.js +16 -0
- package/dist/js/compile.js +747 -0
- package/dist/js/errors.js +199 -0
- package/dist/js/index.js +12 -0
- package/dist/js/infer.js +2 -0
- package/dist/js/interp.js +476 -0
- package/dist/js/ir.js +930 -0
- package/dist/js/json-schema.js +273 -0
- package/dist/js/keywords.js +233 -0
- package/dist/js/type.js +1078 -0
- package/dist/js/typebox.js +346 -0
- package/dist/js/zod.js +269 -0
- package/dist/types/ark.d.ts +3 -11
- package/dist/types/compile.d.ts +2 -0
- package/dist/types/errors.d.ts +51 -15
- package/dist/types/index.d.ts +3 -4
- package/dist/types/infer.d.ts +107 -30
- package/dist/types/interp.d.ts +5 -3
- package/dist/types/ir.d.ts +76 -7
- package/dist/types/json-schema.d.ts +6 -1
- package/dist/types/keywords.d.ts +7 -0
- package/dist/types/type.d.ts +238 -49
- package/dist/types/zod.d.ts +1 -1
- package/package.json +20 -8
- package/src/ark.ts +4 -14
- package/src/compile.ts +432 -54
- package/src/errors.ts +146 -34
- package/src/index.ts +3 -4
- package/src/infer.ts +284 -77
- package/src/interp.ts +153 -9
- package/src/ir.ts +636 -89
- package/src/json-schema.ts +109 -14
- package/src/keywords.ts +270 -0
- package/src/type.ts +1302 -147
- package/src/typebox.ts +1 -1
- package/src/zod.ts +40 -27
package/src/errors.ts
CHANGED
|
@@ -8,33 +8,86 @@
|
|
|
8
8
|
* All human-readable strings are built lazily on property access.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
/** Context supplied to configurable error formatters. */
|
|
12
|
+
export interface ErrorContext {
|
|
13
|
+
readonly code: string;
|
|
14
|
+
readonly path: readonly PropertyKey[];
|
|
15
|
+
readonly data: unknown;
|
|
16
|
+
readonly expected: string;
|
|
17
|
+
readonly actual: string;
|
|
18
|
+
readonly problem: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Per-schema overrides for validation error text. */
|
|
22
|
+
export interface ErrorConfig {
|
|
23
|
+
readonly expected?: string | ((context: ErrorContext) => string);
|
|
24
|
+
readonly actual?: string | ((context: ErrorContext) => string);
|
|
25
|
+
readonly problem?: string | ((context: ErrorContext) => string);
|
|
26
|
+
readonly message?: string | ((context: ErrorContext) => string);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function format(
|
|
30
|
+
override: string | ((context: ErrorContext) => string) | undefined,
|
|
31
|
+
context: ErrorContext,
|
|
32
|
+
fallback: string,
|
|
33
|
+
): string {
|
|
34
|
+
return typeof override === "function" ? override(context) : (override ?? fallback);
|
|
35
|
+
}
|
|
36
|
+
|
|
11
37
|
/** A single validation failure at one path. */
|
|
12
38
|
export class OmpError {
|
|
39
|
+
#rawExpected: string;
|
|
40
|
+
#config: ErrorConfig | undefined;
|
|
41
|
+
|
|
13
42
|
constructor(
|
|
14
43
|
/** Property path from the root to the failing value (empty at root). */
|
|
15
44
|
readonly path: PropertyKey[],
|
|
16
|
-
|
|
17
|
-
readonly expected: string,
|
|
45
|
+
expected: string,
|
|
18
46
|
/** The value that failed validation. */
|
|
19
47
|
readonly data: unknown,
|
|
20
|
-
|
|
48
|
+
config?: ErrorConfig,
|
|
49
|
+
) {
|
|
50
|
+
this.#rawExpected = expected;
|
|
51
|
+
this.#config = config;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Stable category for programmatic error handling. */
|
|
55
|
+
get code(): string {
|
|
56
|
+
return errorCode(this.#rawExpected, this.data);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#context(expected: string, actual: string, problem = ""): ErrorContext {
|
|
60
|
+
return { code: this.code, path: this.path, data: this.data, expected, actual, problem };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Human-readable expectation, including a configured override. */
|
|
64
|
+
get expected(): string {
|
|
65
|
+
const actual = describeValue(this.data);
|
|
66
|
+
return format(this.#config?.expected, this.#context(this.#rawExpected, actual), this.#rawExpected);
|
|
67
|
+
}
|
|
21
68
|
|
|
22
69
|
/** Short description of the received value, e.g. `"a number"` or `"missing"`. */
|
|
23
70
|
get actual(): string {
|
|
24
|
-
|
|
71
|
+
const actual = describeValue(this.data);
|
|
72
|
+
return format(this.#config?.actual, this.#context(this.expected, actual), actual);
|
|
25
73
|
}
|
|
26
74
|
|
|
27
75
|
/** Path-less problem statement: `must be <expected> (was <actual>)`. */
|
|
28
76
|
get problem(): string {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
77
|
+
const expected = this.expected;
|
|
78
|
+
const actual = this.actual;
|
|
79
|
+
const fallback =
|
|
80
|
+
this.data === MISSING ? `must be ${expected} (was missing)` : `must be ${expected} (was ${actual})`;
|
|
81
|
+
return format(this.#config?.problem, this.#context(expected, actual, fallback), fallback);
|
|
32
82
|
}
|
|
33
83
|
|
|
34
84
|
/** Full message including the path prefix. */
|
|
35
85
|
get message(): string {
|
|
86
|
+
const expected = this.expected;
|
|
87
|
+
const actual = this.actual;
|
|
88
|
+
const problem = this.problem;
|
|
36
89
|
const at = this.path.length === 0 ? "" : `${this.path.map(String).join(".")} `;
|
|
37
|
-
return `${at}${
|
|
90
|
+
return format(this.#config?.message, this.#context(expected, actual, problem), `${at}${problem}`);
|
|
38
91
|
}
|
|
39
92
|
|
|
40
93
|
toString(): string {
|
|
@@ -68,48 +121,107 @@ function describeValue(data: unknown): string {
|
|
|
68
121
|
}
|
|
69
122
|
}
|
|
70
123
|
|
|
124
|
+
function errorCode(expected: string, data: unknown): string {
|
|
125
|
+
if (data === MISSING) return "required";
|
|
126
|
+
if (expected.includes("divisible by")) return "divisor";
|
|
127
|
+
if (expected.includes("at least") || expected.includes("more than")) return "min";
|
|
128
|
+
if (expected.includes("at most") || expected.includes("less than")) return "max";
|
|
129
|
+
if (expected.includes("matching") || expected.includes("format") || expected.includes("email")) return "pattern";
|
|
130
|
+
if (expected.includes("predicate") || expected.includes("satisfying")) return "predicate";
|
|
131
|
+
if (expected.startsWith('"') || expected.startsWith("the date ")) return "unit";
|
|
132
|
+
return "domain";
|
|
133
|
+
}
|
|
134
|
+
|
|
71
135
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
136
|
+
* Single-failure validation result with a lazy array-like entry.
|
|
137
|
+
*
|
|
138
|
+
* Validators fast-fail, so allocating an `Array` subclass and a separate entry
|
|
139
|
+
* on every rejection only penalizes callers that inspect errors by identity.
|
|
140
|
+
* Indexing, iteration, and `map` materialize the entry on demand.
|
|
74
141
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
142
|
+
type StoredPath = PropertyKey[] | PropertyKey | undefined;
|
|
143
|
+
|
|
144
|
+
export class OmpErrors implements Iterable<OmpError> {
|
|
145
|
+
#path: StoredPath;
|
|
146
|
+
#expected: string;
|
|
147
|
+
#data: unknown;
|
|
148
|
+
#entry: OmpError | undefined;
|
|
149
|
+
#config: ErrorConfig | undefined;
|
|
150
|
+
|
|
151
|
+
/** Number of failures; omptype validators fast-fail on the first error. */
|
|
152
|
+
readonly length = 1;
|
|
153
|
+
|
|
154
|
+
constructor(path: StoredPath, expected: string, data: unknown, config?: ErrorConfig) {
|
|
155
|
+
this.#path = path;
|
|
156
|
+
this.#expected = expected;
|
|
157
|
+
this.#data = data;
|
|
158
|
+
this.#config = config;
|
|
80
159
|
}
|
|
81
160
|
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
out.push(new OmpError(path, expected, data));
|
|
86
|
-
return out;
|
|
161
|
+
/** First and only validation failure, materialized on demand. */
|
|
162
|
+
get 0(): OmpError {
|
|
163
|
+
return this.#getEntry();
|
|
87
164
|
}
|
|
88
165
|
|
|
89
|
-
|
|
166
|
+
static single(path: PropertyKey[], expected: string, data: unknown, config?: ErrorConfig): OmpErrors {
|
|
167
|
+
return new OmpErrors(path, expected, data, config);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#getEntry(): OmpError {
|
|
171
|
+
if (this.#entry) return this.#entry;
|
|
172
|
+
const path = this.#path === undefined ? [] : Array.isArray(this.#path) ? [...this.#path] : [this.#path];
|
|
173
|
+
const entry = new OmpError(path, this.#expected, this.#data, this.#config);
|
|
174
|
+
this.#entry = entry;
|
|
175
|
+
return entry;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Prefix the failure path with `key` when nesting sub-schemas. */
|
|
90
179
|
prefix(key: PropertyKey): this {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
180
|
+
const path = this.#path;
|
|
181
|
+
this.#path = path === undefined ? [key] : Array.isArray(path) ? [key, ...path] : [key, path];
|
|
182
|
+
this.#entry = undefined;
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Apply schema-local message formatting without rebuilding the failure. */
|
|
187
|
+
configure(config: ErrorConfig): this {
|
|
188
|
+
this.#config = { ...this.#config, ...config };
|
|
189
|
+
this.#entry = undefined;
|
|
95
190
|
return this;
|
|
96
191
|
}
|
|
97
192
|
|
|
98
|
-
/**
|
|
193
|
+
/** Index the failure by its dotted property path (`""` for the root). */
|
|
194
|
+
get byPath(): Readonly<Record<string, OmpError>> {
|
|
195
|
+
const entry = this.#getEntry();
|
|
196
|
+
return { [entry.path.map(String).join(".")]: entry };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Transform the failure entry into a plain array. */
|
|
200
|
+
map<result>(fn: (error: OmpError, index: number, errors: OmpErrors) => result): result[] {
|
|
201
|
+
return [fn(this.#getEntry(), 0, this)];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Select the failure entry into a plain array. */
|
|
205
|
+
filter(fn: (error: OmpError, index: number, errors: OmpErrors) => unknown): OmpError[] {
|
|
206
|
+
const entry = this.#getEntry();
|
|
207
|
+
return fn(entry, 0, this) ? [entry] : [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Iterate over the single failure entry. */
|
|
211
|
+
*[Symbol.iterator](): IterableIterator<OmpError> {
|
|
212
|
+
yield this.#getEntry();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Human-readable failure text, materialized only when requested. */
|
|
99
216
|
get summary(): string {
|
|
100
|
-
|
|
101
|
-
for (let i = 0; i < this.length; i++) {
|
|
102
|
-
if (i > 0) out += "\n";
|
|
103
|
-
out += this[i].message;
|
|
104
|
-
}
|
|
105
|
-
return out;
|
|
217
|
+
return this.#getEntry().message;
|
|
106
218
|
}
|
|
107
219
|
|
|
108
|
-
|
|
220
|
+
toString(): string {
|
|
109
221
|
return this.summary;
|
|
110
222
|
}
|
|
111
223
|
|
|
112
|
-
/** Throw a `TraversalError` carrying
|
|
224
|
+
/** Throw a `TraversalError` carrying this result. */
|
|
113
225
|
throw(): never {
|
|
114
226
|
throw new TraversalError(this);
|
|
115
227
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* omptype — ArkType-compatible schema validation with a lazy JIT runtime.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* plus `typeof schema.infer` static inference.
|
|
4
|
+
* ArkType-compatible `type()`/`Type`, keyword modules, recursive scopes,
|
|
5
|
+
* composition and morph APIs, structured errors, input/output inference, and
|
|
6
|
+
* JSON Schema emission.
|
|
8
7
|
*/
|
|
9
8
|
export * from "./errors";
|
|
10
9
|
export * from "./infer";
|