@veryfront/ext-schema-zod 0.1.1186 → 0.1.1189
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 +24 -0
- package/esm/adapter.d.ts +6 -4
- package/esm/adapter.d.ts.map +1 -1
- package/esm/adapter.js +541 -7
- package/esm/json-schema.d.ts +5 -3
- package/esm/json-schema.d.ts.map +1 -1
- package/esm/json-schema.js +398 -20
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -78,6 +78,30 @@ register("SchemaValidator", createZodAdapter());
|
|
|
78
78
|
|
|
79
79
|
`toJsonSchema(schema)` converts any `Schema<T>` to a JSON Schema object for OpenAPI or tool-call definitions.
|
|
80
80
|
|
|
81
|
+
`compileJsonSchema(schema)` compiles raw JSON Schema with strict validation,
|
|
82
|
+
standard formats, and no coercion, default insertion, or removal of additional
|
|
83
|
+
properties.
|
|
84
|
+
|
|
85
|
+
| Boundary | Limit |
|
|
86
|
+
| --------------------------------- | --------------------------------------------- |
|
|
87
|
+
| Schema depth | 64 levels |
|
|
88
|
+
| Schema nodes | 8,192 |
|
|
89
|
+
| Serialized schema | 512 KiB |
|
|
90
|
+
| Schema string | 256 KiB |
|
|
91
|
+
| Schema property name | 4 KiB UTF-8 |
|
|
92
|
+
| Code-generating collection fanout | 256 entries |
|
|
93
|
+
| Nested compilation work | 24,000 units |
|
|
94
|
+
| Validation input | 128 levels, 100,000 nodes, 4 MiB serialized |
|
|
95
|
+
| Compiled-validator cache | 128 entries and 2 MiB aggregate source weight |
|
|
96
|
+
|
|
97
|
+
Schemas and inputs must be data-only JSON. Accessors, symbol or hidden keys,
|
|
98
|
+
custom object prototypes, cycles, sparse arrays, and revoked proxies are
|
|
99
|
+
rejected without invoking property getters, setters, iterators, or `toJSON`.
|
|
100
|
+
Inspecting a Proxy necessarily invokes its reflection traps; a trap may run or
|
|
101
|
+
throw before the value is rejected. Successful validation returns the
|
|
102
|
+
adapter-owned input snapshot. Cache eviction is least-recently used when either
|
|
103
|
+
cache limit is exceeded.
|
|
104
|
+
|
|
81
105
|
## Running Tests
|
|
82
106
|
|
|
83
107
|
```sh
|
package/esm/adapter.d.ts
CHANGED
|
@@ -13,10 +13,12 @@ import type { SchemaValidator } from "veryfront/extensions/schema";
|
|
|
13
13
|
/**
|
|
14
14
|
* Build a zod-backed `SchemaValidator` instance.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Adapter instances snapshot schemas into plain JSON and retain validators in
|
|
17
|
+
* an entry- and weight-bounded LRU cache. Each unique validator owns an isolated
|
|
18
|
+
* Ajv compiler, so unrelated `$id` values cannot collide or accumulate in a
|
|
19
|
+
* process-wide registry. It is therefore safe to call this once at extension setup and pass the returned value to
|
|
20
|
+
* `ctx.provide("SchemaValidator", …)`. Tests that need to register the
|
|
21
|
+
* validator without full extension bootstrap can call this factory directly.
|
|
20
22
|
*/
|
|
21
23
|
export declare function createZodAdapter(): SchemaValidator;
|
|
22
24
|
//# sourceMappingURL=adapter.d.ts.map
|
package/esm/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EASV,eAAe,EAIhB,MAAM,6BAA6B,CAAC;AA4vBrC;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,eAAe,CAiGlD"}
|
package/esm/adapter.js
CHANGED
|
@@ -9,8 +9,15 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module extensions/ext-schema-zod/adapter
|
|
11
11
|
*/
|
|
12
|
+
import { Ajv as AjvDraft7 } from "ajv";
|
|
13
|
+
import { Ajv2019 } from "ajv/dist/2019.js";
|
|
14
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
15
|
+
import addFormatsModule from "ajv-formats";
|
|
12
16
|
import { z } from "zod";
|
|
13
|
-
import { isOptionalSchema, zodToJsonSchema } from "./json-schema.js";
|
|
17
|
+
import { isOptionalSchema, recordStaticJsonSchemaDefault, zodToJsonSchema } from "./json-schema.js";
|
|
18
|
+
// Deno exposes the CommonJS default as a callable value at runtime while the
|
|
19
|
+
// package declaration describes the imported value as a module namespace.
|
|
20
|
+
const addFormats = addFormatsModule;
|
|
14
21
|
/** Unwrap our opaque Schema<T> back to the underlying zod schema. */
|
|
15
22
|
function toZod(schema) {
|
|
16
23
|
return schema.__zod;
|
|
@@ -27,7 +34,13 @@ function wrap(zs) {
|
|
|
27
34
|
optional: () => wrap(zs.optional()),
|
|
28
35
|
nullable: () => wrap(zs.nullable()),
|
|
29
36
|
nullish: () => wrap(zs.nullish()),
|
|
30
|
-
default: (value) =>
|
|
37
|
+
default: (value) => {
|
|
38
|
+
const defaulted = anyZs.default(value);
|
|
39
|
+
if (typeof value !== "function") {
|
|
40
|
+
recordStaticJsonSchemaDefault(defaulted, value);
|
|
41
|
+
}
|
|
42
|
+
return wrap(defaulted);
|
|
43
|
+
},
|
|
31
44
|
describe: (description) => wrap(zs.describe(description)),
|
|
32
45
|
refine: (check, message) => wrap(zs.refine(check, message)),
|
|
33
46
|
superRefine: (check) => wrap(
|
|
@@ -94,25 +107,545 @@ function wrap(zs) {
|
|
|
94
107
|
function toZodShape(shape) {
|
|
95
108
|
const out = {};
|
|
96
109
|
for (const [key, value] of Object.entries(shape)) {
|
|
97
|
-
out
|
|
110
|
+
defineOwnDataProperty(out, key, toZod(value));
|
|
98
111
|
}
|
|
99
112
|
return out;
|
|
100
113
|
}
|
|
114
|
+
function defineOwnDataProperty(target, key, value) {
|
|
115
|
+
// A data descriptor preserves keys such as `__proto__` as ordinary data
|
|
116
|
+
// without invoking inherited setters in runtimes that expose them.
|
|
117
|
+
Object.defineProperty(target, key, {
|
|
118
|
+
value,
|
|
119
|
+
enumerable: true,
|
|
120
|
+
configurable: true,
|
|
121
|
+
writable: true,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
101
124
|
const coerce = {
|
|
102
125
|
string: () => wrap(z.coerce.string()),
|
|
103
126
|
number: () => wrap(z.coerce.number()),
|
|
104
127
|
boolean: () => wrap(z.coerce.boolean()),
|
|
105
128
|
date: () => wrap(z.coerce.date()),
|
|
106
129
|
};
|
|
130
|
+
const JSON_SCHEMA_VALIDATOR_CACHE_SIZE = 128;
|
|
131
|
+
const JSON_SCHEMA_VALIDATOR_CACHE_MAX_WEIGHT = 2 * 1024 * 1024;
|
|
132
|
+
// Raw schemas can originate in extensions and tool metadata, so snapshot them
|
|
133
|
+
// through a deliberately bounded JSON boundary before handing them to Ajv.
|
|
134
|
+
// These ceilings are comfortably above practical tool schemas while keeping a
|
|
135
|
+
// single compilation from consuming unbounded stack, CPU, or memory.
|
|
136
|
+
const JSON_SCHEMA_MAX_DEPTH = 64;
|
|
137
|
+
const JSON_SCHEMA_MAX_NODES = 8_192;
|
|
138
|
+
const JSON_SCHEMA_MAX_SERIALIZED_BYTES = 512 * 1024;
|
|
139
|
+
const JSON_SCHEMA_MAX_STRING_BYTES = 256 * 1024;
|
|
140
|
+
const JSON_SCHEMA_MAX_KEY_BYTES = 4 * 1024;
|
|
141
|
+
const JSON_SCHEMA_MAX_COMBINATOR_FANOUT = 256;
|
|
142
|
+
const JSON_SCHEMA_MAX_COMPILATION_WORK = 24_000;
|
|
143
|
+
const JSON_INSTANCE_MAX_DEPTH = 128;
|
|
144
|
+
const JSON_INSTANCE_MAX_NODES = 100_000;
|
|
145
|
+
const JSON_INSTANCE_MAX_SERIALIZED_BYTES = 4 * 1024 * 1024;
|
|
146
|
+
const JSON_INSTANCE_MAX_STRING_BYTES = 1024 * 1024;
|
|
147
|
+
const JSON_INSTANCE_MAX_KEY_BYTES = 16 * 1024;
|
|
148
|
+
const JSON_UTF8_ENCODER = new TextEncoder();
|
|
149
|
+
const JSON_SCHEMA_LIMITS = {
|
|
150
|
+
maxDepth: JSON_SCHEMA_MAX_DEPTH,
|
|
151
|
+
maxNodes: JSON_SCHEMA_MAX_NODES,
|
|
152
|
+
maxSerializedBytes: JSON_SCHEMA_MAX_SERIALIZED_BYTES,
|
|
153
|
+
maxStringBytes: JSON_SCHEMA_MAX_STRING_BYTES,
|
|
154
|
+
maxKeyBytes: JSON_SCHEMA_MAX_KEY_BYTES,
|
|
155
|
+
};
|
|
156
|
+
const JSON_INSTANCE_LIMITS = {
|
|
157
|
+
maxDepth: JSON_INSTANCE_MAX_DEPTH,
|
|
158
|
+
maxNodes: JSON_INSTANCE_MAX_NODES,
|
|
159
|
+
maxSerializedBytes: JSON_INSTANCE_MAX_SERIALIZED_BYTES,
|
|
160
|
+
maxStringBytes: JSON_INSTANCE_MAX_STRING_BYTES,
|
|
161
|
+
maxKeyBytes: JSON_INSTANCE_MAX_KEY_BYTES,
|
|
162
|
+
};
|
|
163
|
+
const JSON_SCHEMA_COMPILER_OPTIONS = {
|
|
164
|
+
strict: true,
|
|
165
|
+
allErrors: true,
|
|
166
|
+
addUsedSchema: false,
|
|
167
|
+
allowUnionTypes: true,
|
|
168
|
+
coerceTypes: false,
|
|
169
|
+
ownProperties: true,
|
|
170
|
+
useDefaults: false,
|
|
171
|
+
removeAdditional: false,
|
|
172
|
+
};
|
|
173
|
+
const DRAFT_7_META_SCHEMA_URI = "https://json-schema.org/draft-07/schema";
|
|
174
|
+
const AJV_DRAFT_7_META_SCHEMA_URI = "http://json-schema.org/draft-07/schema#";
|
|
175
|
+
function normalizeMetaSchemaUri(uri) {
|
|
176
|
+
return uri.trim().replace(/#$/, "").replace(/^http:/, "https:");
|
|
177
|
+
}
|
|
178
|
+
function createJsonSchemaCompiler(schema) {
|
|
179
|
+
const declaredDraft = typeof schema.$schema === "string"
|
|
180
|
+
? normalizeMetaSchemaUri(schema.$schema)
|
|
181
|
+
: "https://json-schema.org/draft/2020-12/schema";
|
|
182
|
+
const compiler = declaredDraft === DRAFT_7_META_SCHEMA_URI
|
|
183
|
+
? new AjvDraft7(JSON_SCHEMA_COMPILER_OPTIONS)
|
|
184
|
+
: declaredDraft === "https://json-schema.org/draft/2019-09/schema"
|
|
185
|
+
? new Ajv2019(JSON_SCHEMA_COMPILER_OPTIONS)
|
|
186
|
+
: declaredDraft === "https://json-schema.org/draft/2020-12/schema"
|
|
187
|
+
? new Ajv2020(JSON_SCHEMA_COMPILER_OPTIONS)
|
|
188
|
+
: undefined;
|
|
189
|
+
if (!compiler) {
|
|
190
|
+
throw new Error(`Unsupported JSON Schema draft: ${schema.$schema}`);
|
|
191
|
+
}
|
|
192
|
+
addFormats(compiler);
|
|
193
|
+
return compiler;
|
|
194
|
+
}
|
|
195
|
+
function schemaForCompilation(schema) {
|
|
196
|
+
if (typeof schema.$schema !== "string" ||
|
|
197
|
+
normalizeMetaSchemaUri(schema.$schema) !== DRAFT_7_META_SCHEMA_URI) {
|
|
198
|
+
return schema;
|
|
199
|
+
}
|
|
200
|
+
// Ajv's Draft 7 compiler registers the canonical HTTP identifier. Compile
|
|
201
|
+
// an isolated root snapshot using that identifier while leaving the
|
|
202
|
+
// caller-owned and cache-key snapshots unchanged.
|
|
203
|
+
return {
|
|
204
|
+
...schema,
|
|
205
|
+
$schema: AJV_DRAFT_7_META_SCHEMA_URI,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function boundedUtf8Length(value, limit, label) {
|
|
209
|
+
// Every UTF-16 code unit contributes at least one UTF-8 byte. This cheap
|
|
210
|
+
// guard avoids allocating an encoded copy once the value is already known
|
|
211
|
+
// to exceed the byte ceiling.
|
|
212
|
+
if (value.length > limit) {
|
|
213
|
+
throw new TypeError(`JSON Schema ${label} exceeds the ${limit}-byte limit`);
|
|
214
|
+
}
|
|
215
|
+
const byteLength = JSON_UTF8_ENCODER.encode(value).byteLength;
|
|
216
|
+
if (byteLength > limit) {
|
|
217
|
+
throw new TypeError(`JSON Schema ${label} exceeds the ${limit}-byte limit`);
|
|
218
|
+
}
|
|
219
|
+
return byteLength;
|
|
220
|
+
}
|
|
221
|
+
function serializedJsonTokenByteLength(value) {
|
|
222
|
+
const serialized = JSON.stringify(value);
|
|
223
|
+
if (serialized === undefined) {
|
|
224
|
+
throw new TypeError("JSON Schema must contain only JSON values");
|
|
225
|
+
}
|
|
226
|
+
return JSON_UTF8_ENCODER.encode(serialized).byteLength;
|
|
227
|
+
}
|
|
228
|
+
class JsonSchemaCanonicalizer {
|
|
229
|
+
limits;
|
|
230
|
+
activeAncestors = new Set();
|
|
231
|
+
stack = [];
|
|
232
|
+
canonicalRoot;
|
|
233
|
+
rootAssigned = false;
|
|
234
|
+
nodeCount = 0;
|
|
235
|
+
serializedBytes = 0;
|
|
236
|
+
constructor(limits) {
|
|
237
|
+
this.limits = limits;
|
|
238
|
+
}
|
|
239
|
+
canonicalize(value) {
|
|
240
|
+
this.stack.push({ kind: "visit", value, depth: 0 });
|
|
241
|
+
while (this.stack.length > 0) {
|
|
242
|
+
const frame = this.stack.pop();
|
|
243
|
+
if (!frame)
|
|
244
|
+
break;
|
|
245
|
+
if (frame.kind === "exit") {
|
|
246
|
+
this.activeAncestors.delete(frame.value);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
this.visit(frame);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (!this.rootAssigned) {
|
|
253
|
+
throw new TypeError("JSON Schema must contain a JSON value");
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
value: this.canonicalRoot,
|
|
257
|
+
nodeCount: this.nodeCount,
|
|
258
|
+
serializedBytes: this.serializedBytes,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
visit(frame) {
|
|
262
|
+
this.consumeNode(frame.depth);
|
|
263
|
+
if (this.visitScalar(frame))
|
|
264
|
+
return;
|
|
265
|
+
const current = frame.value;
|
|
266
|
+
if (typeof current !== "object" || current === null) {
|
|
267
|
+
throw new TypeError("JSON Schema must contain only JSON values");
|
|
268
|
+
}
|
|
269
|
+
if (this.activeAncestors.has(current)) {
|
|
270
|
+
throw new TypeError("JSON Schema must not contain cycles");
|
|
271
|
+
}
|
|
272
|
+
if (Array.isArray(current)) {
|
|
273
|
+
this.visitArray(frame, current);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
this.visitObject(frame, current);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
visitScalar(frame) {
|
|
280
|
+
const current = frame.value;
|
|
281
|
+
if (current === null || typeof current === "boolean") {
|
|
282
|
+
this.assign(frame, current);
|
|
283
|
+
this.addSerializedBytes(serializedJsonTokenByteLength(current));
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
if (typeof current === "string") {
|
|
287
|
+
boundedUtf8Length(current, this.limits.maxStringBytes, "string");
|
|
288
|
+
this.assign(frame, current);
|
|
289
|
+
this.addSerializedBytes(serializedJsonTokenByteLength(current));
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
if (typeof current === "number") {
|
|
293
|
+
if (!Number.isFinite(current)) {
|
|
294
|
+
throw new TypeError("JSON Schema numbers must be finite");
|
|
295
|
+
}
|
|
296
|
+
this.assign(frame, current);
|
|
297
|
+
this.addSerializedBytes(serializedJsonTokenByteLength(current));
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
if (typeof current !== "object") {
|
|
301
|
+
throw new TypeError("JSON Schema must contain only JSON values");
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
visitArray(frame, current) {
|
|
306
|
+
const lengthDescriptor = Reflect.getOwnPropertyDescriptor(current, "length");
|
|
307
|
+
const length = lengthDescriptor && "value" in lengthDescriptor
|
|
308
|
+
? lengthDescriptor.value
|
|
309
|
+
: undefined;
|
|
310
|
+
if (typeof length !== "number" ||
|
|
311
|
+
!Number.isSafeInteger(length) ||
|
|
312
|
+
length < 0) {
|
|
313
|
+
throw new TypeError("JSON Schema arrays must have a non-negative integer data length");
|
|
314
|
+
}
|
|
315
|
+
if (length > this.limits.maxNodes) {
|
|
316
|
+
throw new TypeError(`JSON Schema exceeds the maximum node count of ${this.limits.maxNodes}`);
|
|
317
|
+
}
|
|
318
|
+
const ownKeys = Reflect.ownKeys(current);
|
|
319
|
+
if (ownKeys.some((key) => typeof key === "symbol") ||
|
|
320
|
+
ownKeys.length !== length + 1 ||
|
|
321
|
+
!ownKeys.includes("length")) {
|
|
322
|
+
throw new TypeError("JSON Schema arrays must be dense JSON arrays without extra properties");
|
|
323
|
+
}
|
|
324
|
+
this.addSerializedBytes(2 + Math.max(0, length - 1));
|
|
325
|
+
const descriptors = [];
|
|
326
|
+
for (let index = 0; index < length; index++) {
|
|
327
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(current, String(index));
|
|
328
|
+
if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) {
|
|
329
|
+
throw new TypeError("JSON Schema arrays must be dense data-only JSON arrays without accessors");
|
|
330
|
+
}
|
|
331
|
+
descriptors.push(descriptor);
|
|
332
|
+
}
|
|
333
|
+
const canonical = new Array(length);
|
|
334
|
+
this.assign(frame, canonical);
|
|
335
|
+
this.activeAncestors.add(current);
|
|
336
|
+
this.stack.push({ kind: "exit", value: current });
|
|
337
|
+
for (let index = descriptors.length - 1; index >= 0; index--) {
|
|
338
|
+
const descriptor = descriptors[index];
|
|
339
|
+
if (!descriptor) {
|
|
340
|
+
throw new TypeError("JSON Schema array snapshot is internally inconsistent");
|
|
341
|
+
}
|
|
342
|
+
this.stack.push({
|
|
343
|
+
kind: "visit",
|
|
344
|
+
value: descriptor.value,
|
|
345
|
+
depth: frame.depth + 1,
|
|
346
|
+
parent: canonical,
|
|
347
|
+
key: index,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
visitObject(frame, current) {
|
|
352
|
+
const prototype = Object.getPrototypeOf(current);
|
|
353
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
354
|
+
throw new TypeError("JSON Schema objects must be plain JSON objects");
|
|
355
|
+
}
|
|
356
|
+
const ownKeys = Reflect.ownKeys(current);
|
|
357
|
+
if (ownKeys.length > this.limits.maxNodes) {
|
|
358
|
+
throw new TypeError(`JSON Schema exceeds the maximum node count of ${this.limits.maxNodes}`);
|
|
359
|
+
}
|
|
360
|
+
if (ownKeys.some((key) => typeof key === "symbol")) {
|
|
361
|
+
throw new TypeError("JSON Schema objects must not contain symbol keys");
|
|
362
|
+
}
|
|
363
|
+
this.addSerializedBytes(2 + Math.max(0, ownKeys.length - 1));
|
|
364
|
+
const entries = ownKeys.map((key) => {
|
|
365
|
+
boundedUtf8Length(key, this.limits.maxKeyBytes, "key");
|
|
366
|
+
this.addSerializedBytes(serializedJsonTokenByteLength(key) + 1);
|
|
367
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
368
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
369
|
+
throw new TypeError("JSON Schema objects must be data-only and must not contain accessors");
|
|
370
|
+
}
|
|
371
|
+
if (descriptor.enumerable !== true) {
|
|
372
|
+
throw new TypeError("JSON Schema objects must not contain non-enumerable properties");
|
|
373
|
+
}
|
|
374
|
+
return { key, value: descriptor.value };
|
|
375
|
+
}).sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
|
|
376
|
+
const canonical = {};
|
|
377
|
+
this.assign(frame, canonical);
|
|
378
|
+
this.activeAncestors.add(current);
|
|
379
|
+
this.stack.push({ kind: "exit", value: current });
|
|
380
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
381
|
+
const entry = entries[index];
|
|
382
|
+
if (!entry) {
|
|
383
|
+
throw new TypeError("JSON Schema object snapshot is internally inconsistent");
|
|
384
|
+
}
|
|
385
|
+
this.stack.push({
|
|
386
|
+
kind: "visit",
|
|
387
|
+
value: entry.value,
|
|
388
|
+
depth: frame.depth + 1,
|
|
389
|
+
parent: canonical,
|
|
390
|
+
key: entry.key,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
consumeNode(depth) {
|
|
395
|
+
if (depth > this.limits.maxDepth) {
|
|
396
|
+
throw new TypeError(`JSON Schema exceeds the maximum depth of ${this.limits.maxDepth}`);
|
|
397
|
+
}
|
|
398
|
+
this.nodeCount++;
|
|
399
|
+
if (this.nodeCount > this.limits.maxNodes) {
|
|
400
|
+
throw new TypeError(`JSON Schema exceeds the maximum node count of ${this.limits.maxNodes}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
addSerializedBytes(amount) {
|
|
404
|
+
this.serializedBytes += amount;
|
|
405
|
+
if (this.serializedBytes > this.limits.maxSerializedBytes) {
|
|
406
|
+
throw new TypeError(`JSON Schema exceeds the ${this.limits.maxSerializedBytes}-byte serialized limit`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
assign(frame, canonical) {
|
|
410
|
+
if (frame.parent === undefined) {
|
|
411
|
+
this.canonicalRoot = canonical;
|
|
412
|
+
this.rootAssigned = true;
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (Array.isArray(frame.parent)) {
|
|
416
|
+
frame.parent[frame.key] = canonical;
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
defineOwnDataProperty(frame.parent, frame.key, canonical);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function canonicalizeJsonValue(value, limits) {
|
|
423
|
+
return new JsonSchemaCanonicalizer(limits).canonicalize(value);
|
|
424
|
+
}
|
|
425
|
+
const COMBINATOR_KEYWORDS = new Set(["allOf", "anyOf", "oneOf"]);
|
|
426
|
+
const CODE_GENERATING_MAP_KEYWORDS = new Map([
|
|
427
|
+
["properties", 4],
|
|
428
|
+
["patternProperties", 8],
|
|
429
|
+
["dependencies", 4],
|
|
430
|
+
["dependentRequired", 4],
|
|
431
|
+
["dependentSchemas", 4],
|
|
432
|
+
]);
|
|
433
|
+
const RETAINED_SCHEMA_MAP_KEYWORDS = new Set(["$defs", "definitions"]);
|
|
434
|
+
function isJsonObject(value) {
|
|
435
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Estimate generated-validator work before Ajv sees a schema. Node and byte
|
|
439
|
+
* limits alone do not capture wide combinators, large enums, or property maps,
|
|
440
|
+
* all of which expand generated code and retained compiler state.
|
|
441
|
+
*/
|
|
442
|
+
function estimateCompilationWork(schema) {
|
|
443
|
+
const stack = [schema];
|
|
444
|
+
let work = 0;
|
|
445
|
+
const addWork = (amount) => {
|
|
446
|
+
work += amount;
|
|
447
|
+
if (work > JSON_SCHEMA_MAX_COMPILATION_WORK) {
|
|
448
|
+
throw new TypeError(`JSON Schema exceeds the compilation work limit of ${JSON_SCHEMA_MAX_COMPILATION_WORK}`);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
const addBoundedCollectionWork = (keyword, length, multiplier) => {
|
|
452
|
+
if (length > JSON_SCHEMA_MAX_COMBINATOR_FANOUT) {
|
|
453
|
+
throw new TypeError(`JSON Schema ${keyword} exceeds the code-generation collection limit of ${JSON_SCHEMA_MAX_COMBINATOR_FANOUT}`);
|
|
454
|
+
}
|
|
455
|
+
addWork(length * multiplier);
|
|
456
|
+
};
|
|
457
|
+
while (stack.length > 0) {
|
|
458
|
+
const value = stack.pop();
|
|
459
|
+
if (!value || typeof value !== "object")
|
|
460
|
+
continue;
|
|
461
|
+
if (Array.isArray(value)) {
|
|
462
|
+
for (let index = value.length - 1; index >= 0; index--)
|
|
463
|
+
stack.push(value[index]);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
const schemaObject = value;
|
|
467
|
+
const propertyCount = isJsonObject(schemaObject.properties)
|
|
468
|
+
? Object.keys(schemaObject.properties).length
|
|
469
|
+
: 0;
|
|
470
|
+
const patternPropertyCount = isJsonObject(schemaObject.patternProperties)
|
|
471
|
+
? Object.keys(schemaObject.patternProperties).length
|
|
472
|
+
: 0;
|
|
473
|
+
if (propertyCount > 0 && patternPropertyCount > 0) {
|
|
474
|
+
// In Ajv strict mode every pattern is checked against every named
|
|
475
|
+
// property during compilation, so account for the cross-product.
|
|
476
|
+
addWork(propertyCount * patternPropertyCount);
|
|
477
|
+
}
|
|
478
|
+
for (const [keyword, keywordValue] of Object.entries(schemaObject)) {
|
|
479
|
+
addWork(1);
|
|
480
|
+
if (COMBINATOR_KEYWORDS.has(keyword) && Array.isArray(keywordValue)) {
|
|
481
|
+
addBoundedCollectionWork(keyword, keywordValue.length, 8);
|
|
482
|
+
}
|
|
483
|
+
else if ((keyword === "prefixItems" || keyword === "items") && Array.isArray(keywordValue)) {
|
|
484
|
+
addBoundedCollectionWork(keyword, keywordValue.length, 2);
|
|
485
|
+
}
|
|
486
|
+
else if ((keyword === "enum" || keyword === "required" || keyword === "type") &&
|
|
487
|
+
Array.isArray(keywordValue)) {
|
|
488
|
+
addBoundedCollectionWork(keyword, keywordValue.length, keyword === "required" ? 4 : 2);
|
|
489
|
+
}
|
|
490
|
+
else if (isJsonObject(keywordValue) && CODE_GENERATING_MAP_KEYWORDS.has(keyword)) {
|
|
491
|
+
const entryCount = Object.keys(keywordValue).length;
|
|
492
|
+
addBoundedCollectionWork(keyword, entryCount, CODE_GENERATING_MAP_KEYWORDS.get(keyword));
|
|
493
|
+
if (keyword === "dependentRequired" || keyword === "dependencies") {
|
|
494
|
+
for (const dependencyValue of Object.values(keywordValue)) {
|
|
495
|
+
if (Array.isArray(dependencyValue)) {
|
|
496
|
+
addBoundedCollectionWork(`${keyword} entry`, dependencyValue.length, 4);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
else if (isJsonObject(keywordValue) && RETAINED_SCHEMA_MAP_KEYWORDS.has(keyword)) {
|
|
502
|
+
addWork(Object.keys(keywordValue).length * 2);
|
|
503
|
+
}
|
|
504
|
+
else if (keyword === "pattern" && typeof keywordValue === "string") {
|
|
505
|
+
addWork(Math.ceil(keywordValue.length / 8));
|
|
506
|
+
}
|
|
507
|
+
stack.push(keywordValue);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return work;
|
|
511
|
+
}
|
|
512
|
+
function snapshotJsonSchema(schema) {
|
|
513
|
+
const snapshot = canonicalizeJsonValue(schema, JSON_SCHEMA_LIMITS);
|
|
514
|
+
const canonical = snapshot.value;
|
|
515
|
+
if (canonical === null || typeof canonical !== "object" || Array.isArray(canonical)) {
|
|
516
|
+
throw new TypeError("JSON Schema root must be a plain object");
|
|
517
|
+
}
|
|
518
|
+
const key = JSON.stringify(canonical);
|
|
519
|
+
if (key === undefined)
|
|
520
|
+
throw new TypeError("JSON Schema must be JSON serializable");
|
|
521
|
+
const compilationWork = estimateCompilationWork(canonical);
|
|
522
|
+
const cacheWeight = snapshot.serializedBytes + snapshot.nodeCount * 64 + compilationWork * 32;
|
|
523
|
+
return {
|
|
524
|
+
key,
|
|
525
|
+
schema: canonical,
|
|
526
|
+
nodeCount: snapshot.nodeCount,
|
|
527
|
+
serializedBytes: snapshot.serializedBytes,
|
|
528
|
+
compilationWork,
|
|
529
|
+
cacheWeight,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function copyValidationIssue(error) {
|
|
533
|
+
let params;
|
|
534
|
+
try {
|
|
535
|
+
params = structuredClone(error.params);
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
params = { ...error.params };
|
|
539
|
+
}
|
|
540
|
+
return {
|
|
541
|
+
instancePath: error.instancePath,
|
|
542
|
+
schemaPath: error.schemaPath,
|
|
543
|
+
keyword: error.keyword,
|
|
544
|
+
params,
|
|
545
|
+
...(error.message === undefined ? {} : { message: error.message }),
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function validationFailure(errors) {
|
|
549
|
+
return {
|
|
550
|
+
success: false,
|
|
551
|
+
errors: (errors ?? []).map(copyValidationIssue),
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function validationSuccess(input) {
|
|
555
|
+
return { success: true, value: input };
|
|
556
|
+
}
|
|
557
|
+
const DATA_ONLY_INPUT_FAILURE = Object.freeze({
|
|
558
|
+
success: false,
|
|
559
|
+
errors: Object.freeze([
|
|
560
|
+
Object.freeze({
|
|
561
|
+
instancePath: "",
|
|
562
|
+
schemaPath: "",
|
|
563
|
+
keyword: "veryfrontDataOnly",
|
|
564
|
+
params: Object.freeze({}),
|
|
565
|
+
message: "Input must be a bounded, data-only JSON value",
|
|
566
|
+
}),
|
|
567
|
+
]),
|
|
568
|
+
});
|
|
569
|
+
function snapshotJsonInstance(input) {
|
|
570
|
+
try {
|
|
571
|
+
return canonicalizeJsonValue(input, JSON_INSTANCE_LIMITS);
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
// Accessors, throwing/revoked Proxies, cycles, custom prototypes, and
|
|
575
|
+
// oversized inputs are ordinary validation failures at this boundary.
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function errorObjectsFromUnknown(error) {
|
|
580
|
+
if (!error || typeof error !== "object" || !("errors" in error)) {
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
return Array.isArray(error.errors) ? error.errors : undefined;
|
|
584
|
+
}
|
|
585
|
+
function isPromiseLike(value) {
|
|
586
|
+
return !!value &&
|
|
587
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
588
|
+
"then" in value &&
|
|
589
|
+
typeof value.then === "function";
|
|
590
|
+
}
|
|
591
|
+
function compileJsonSchemaValidator(schema) {
|
|
592
|
+
const validate = createJsonSchemaCompiler(schema).compile(schemaForCompilation(schema));
|
|
593
|
+
return (input) => {
|
|
594
|
+
const inputSnapshot = snapshotJsonInstance(input);
|
|
595
|
+
if (!inputSnapshot)
|
|
596
|
+
return DATA_ONLY_INPUT_FAILURE;
|
|
597
|
+
const acceptedInput = inputSnapshot.value;
|
|
598
|
+
const outcome = validate(acceptedInput);
|
|
599
|
+
if (isPromiseLike(outcome)) {
|
|
600
|
+
return Promise.resolve(outcome).then(() => validationSuccess(acceptedInput), (error) => {
|
|
601
|
+
const errors = errorObjectsFromUnknown(error);
|
|
602
|
+
if (errors)
|
|
603
|
+
return validationFailure(errors);
|
|
604
|
+
throw error;
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
return outcome ? validationSuccess(acceptedInput) : validationFailure(validate.errors);
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
function createJsonSchemaCompilationCache() {
|
|
611
|
+
const cache = new Map();
|
|
612
|
+
let cacheWeight = 0;
|
|
613
|
+
return (schema) => {
|
|
614
|
+
const snapshot = snapshotJsonSchema(schema);
|
|
615
|
+
const cached = cache.get(snapshot.key);
|
|
616
|
+
if (cached) {
|
|
617
|
+
cache.delete(snapshot.key);
|
|
618
|
+
cache.set(snapshot.key, cached);
|
|
619
|
+
return cached.validator;
|
|
620
|
+
}
|
|
621
|
+
const compiled = compileJsonSchemaValidator(snapshot.schema);
|
|
622
|
+
while (cache.size >= JSON_SCHEMA_VALIDATOR_CACHE_SIZE ||
|
|
623
|
+
cacheWeight + snapshot.cacheWeight > JSON_SCHEMA_VALIDATOR_CACHE_MAX_WEIGHT) {
|
|
624
|
+
const oldestKey = cache.keys().next().value;
|
|
625
|
+
if (oldestKey === undefined)
|
|
626
|
+
break;
|
|
627
|
+
const oldest = cache.get(oldestKey);
|
|
628
|
+
cache.delete(oldestKey);
|
|
629
|
+
if (oldest)
|
|
630
|
+
cacheWeight -= oldest.weight;
|
|
631
|
+
}
|
|
632
|
+
cache.set(snapshot.key, { validator: compiled, weight: snapshot.cacheWeight });
|
|
633
|
+
cacheWeight += snapshot.cacheWeight;
|
|
634
|
+
return compiled;
|
|
635
|
+
};
|
|
636
|
+
}
|
|
107
637
|
/**
|
|
108
638
|
* Build a zod-backed `SchemaValidator` instance.
|
|
109
639
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
640
|
+
* Adapter instances snapshot schemas into plain JSON and retain validators in
|
|
641
|
+
* an entry- and weight-bounded LRU cache. Each unique validator owns an isolated
|
|
642
|
+
* Ajv compiler, so unrelated `$id` values cannot collide or accumulate in a
|
|
643
|
+
* process-wide registry. It is therefore safe to call this once at extension setup and pass the returned value to
|
|
644
|
+
* `ctx.provide("SchemaValidator", …)`. Tests that need to register the
|
|
645
|
+
* validator without full extension bootstrap can call this factory directly.
|
|
114
646
|
*/
|
|
115
647
|
export function createZodAdapter() {
|
|
648
|
+
const compileJsonSchema = createJsonSchemaCompilationCache();
|
|
116
649
|
return {
|
|
117
650
|
string: () => wrap(z.string()),
|
|
118
651
|
number: () => wrap(z.number()),
|
|
@@ -153,6 +686,7 @@ export function createZodAdapter() {
|
|
|
153
686
|
custom: (check, message) => wrap(z.custom(check, message)),
|
|
154
687
|
coerce,
|
|
155
688
|
validate: (schema, data) => schema.safeParse(data),
|
|
689
|
+
compileJsonSchema,
|
|
156
690
|
toJsonSchema: (schema) => zodToJsonSchema(toZod(schema)),
|
|
157
691
|
isOptional: (schema) => isOptionalSchema(toZod(schema)),
|
|
158
692
|
};
|
package/esm/json-schema.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Zod-to-JSON-Schema converter used by the `SchemaValidator` adapter.
|
|
3
3
|
*
|
|
4
|
-
* Operates directly on zod's internal `_def` shape
|
|
5
|
-
*
|
|
6
|
-
* `extensions/ext-schema-zod/` so the rest of the codebase never
|
|
4
|
+
* Operates directly on zod's internal `_def` shape to preserve the adapter's
|
|
5
|
+
* stable v3 and v4 compatibility behavior. It is confined to
|
|
6
|
+
* `extensions/ext-schema-zod/` so the rest of the codebase never imports zod
|
|
7
7
|
* to learn what a tool's input schema looks like.
|
|
8
8
|
*
|
|
9
9
|
* @module extensions/ext-schema-zod/json-schema
|
|
10
10
|
*/
|
|
11
11
|
import type { z } from "zod";
|
|
12
12
|
import type { JsonSchema } from "veryfront/extensions/schema";
|
|
13
|
+
/** Record a literal default without evaluating Zod's possibly dynamic getter. */
|
|
14
|
+
export declare function recordStaticJsonSchemaDefault(schema: z.ZodTypeAny, value: unknown): void;
|
|
13
15
|
export declare function zodToJsonSchema(schema: z.ZodTypeAny): JsonSchema;
|
|
14
16
|
export declare function isOptionalSchema(schema: z.ZodTypeAny): boolean;
|
|
15
17
|
//# sourceMappingURL=json-schema.d.ts.map
|
package/esm/json-schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-schema.d.ts","sourceRoot":"","sources":["../src/json-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"json-schema.d.ts","sourceRoot":"","sources":["../src/json-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAqO9D,iFAAiF;AACjF,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAOxF;AAmLD,wBAAgB,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,UAAU,CAMhE;AAgBD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,OAAO,CAE9D"}
|
package/esm/json-schema.js
CHANGED
|
@@ -1,21 +1,122 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Zod-to-JSON-Schema converter used by the `SchemaValidator` adapter.
|
|
3
3
|
*
|
|
4
|
-
* Operates directly on zod's internal `_def` shape
|
|
5
|
-
*
|
|
6
|
-
* `extensions/ext-schema-zod/` so the rest of the codebase never
|
|
4
|
+
* Operates directly on zod's internal `_def` shape to preserve the adapter's
|
|
5
|
+
* stable v3 and v4 compatibility behavior. It is confined to
|
|
6
|
+
* `extensions/ext-schema-zod/` so the rest of the codebase never imports zod
|
|
7
7
|
* to learn what a tool's input schema looks like.
|
|
8
8
|
*
|
|
9
9
|
* @module extensions/ext-schema-zod/json-schema
|
|
10
10
|
*/
|
|
11
|
+
const MAX_CONVERSION_DEPTH = 128;
|
|
12
|
+
const MAX_CONVERSION_NODES = 100_000;
|
|
13
|
+
function assertConversionDepth(depth) {
|
|
14
|
+
if (depth > MAX_CONVERSION_DEPTH) {
|
|
15
|
+
throw new RangeError(`Zod schema exceeds the maximum conversion depth of ${MAX_CONVERSION_DEPTH}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const STATIC_DEFAULT_VALUE = Symbol("veryfront.staticJsonSchemaDefault");
|
|
11
19
|
const LITERAL_TYPE_MAP = {
|
|
12
20
|
string: "string",
|
|
13
21
|
number: "number",
|
|
14
22
|
boolean: "boolean",
|
|
15
23
|
};
|
|
16
24
|
function getLiteralType(value) {
|
|
25
|
+
if (value === null)
|
|
26
|
+
return "null";
|
|
17
27
|
return LITERAL_TYPE_MAP[typeof value];
|
|
18
28
|
}
|
|
29
|
+
function finiteStringRecordKeys(schema, context) {
|
|
30
|
+
const activeSchemas = new WeakSet();
|
|
31
|
+
const keys = [];
|
|
32
|
+
const stack = [{
|
|
33
|
+
kind: "visit",
|
|
34
|
+
schema,
|
|
35
|
+
depth: context.depth,
|
|
36
|
+
}];
|
|
37
|
+
let pendingVisitCount = 1;
|
|
38
|
+
let visitedNodes = 0;
|
|
39
|
+
const finish = (result) => {
|
|
40
|
+
context.nodeCount += visitedNodes;
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
const assertCapacity = (additionalNodes) => {
|
|
44
|
+
if (context.nodeCount +
|
|
45
|
+
visitedNodes +
|
|
46
|
+
pendingVisitCount +
|
|
47
|
+
additionalNodes >
|
|
48
|
+
MAX_CONVERSION_NODES) {
|
|
49
|
+
throw new RangeError(`Zod schema exceeds the maximum conversion node count of ${MAX_CONVERSION_NODES}`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
while (stack.length > 0) {
|
|
53
|
+
const frame = stack.pop();
|
|
54
|
+
if (!frame)
|
|
55
|
+
break;
|
|
56
|
+
if (frame.kind === "exit") {
|
|
57
|
+
activeSchemas.delete(frame.schema);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
pendingVisitCount--;
|
|
61
|
+
assertConversionDepth(frame.depth);
|
|
62
|
+
visitedNodes++;
|
|
63
|
+
if (context.nodeCount + visitedNodes > MAX_CONVERSION_NODES) {
|
|
64
|
+
throw new RangeError(`Zod schema exceeds the maximum conversion node count of ${MAX_CONVERSION_NODES}`);
|
|
65
|
+
}
|
|
66
|
+
if (activeSchemas.has(frame.schema))
|
|
67
|
+
return finish(undefined);
|
|
68
|
+
const tag = getTypeTag(frame.schema);
|
|
69
|
+
const def = getDef(frame.schema);
|
|
70
|
+
if (tag === "ZodLiteral" || tag === "literal") {
|
|
71
|
+
const literal = def.value ?? (Array.isArray(def.values) ? def.values[0] : def.values);
|
|
72
|
+
if (typeof literal !== "string")
|
|
73
|
+
return finish(undefined);
|
|
74
|
+
keys.push(literal);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (tag === "ZodEnum" || tag === "enum") {
|
|
78
|
+
const rawValues = Array.isArray(def.values)
|
|
79
|
+
? def.values
|
|
80
|
+
: def.entries
|
|
81
|
+
? Object.values(def.entries)
|
|
82
|
+
: [];
|
|
83
|
+
assertCapacity(rawValues.length);
|
|
84
|
+
for (const value of rawValues) {
|
|
85
|
+
visitedNodes++;
|
|
86
|
+
if (typeof value !== "string")
|
|
87
|
+
return finish(undefined);
|
|
88
|
+
keys.push(value);
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (tag !== "ZodUnion" &&
|
|
93
|
+
tag !== "ZodDiscriminatedUnion" &&
|
|
94
|
+
tag !== "union") {
|
|
95
|
+
return finish(undefined);
|
|
96
|
+
}
|
|
97
|
+
const options = def.options ?? [];
|
|
98
|
+
const optionCount = options instanceof Map ? options.size : options.length;
|
|
99
|
+
assertCapacity(optionCount);
|
|
100
|
+
if (optionCount > 0 && frame.depth + 1 > MAX_CONVERSION_DEPTH) {
|
|
101
|
+
throw new RangeError(`Zod schema exceeds the maximum conversion depth of ${MAX_CONVERSION_DEPTH}`);
|
|
102
|
+
}
|
|
103
|
+
const optionArray = options instanceof Map ? Array.from(options.values()) : options;
|
|
104
|
+
activeSchemas.add(frame.schema);
|
|
105
|
+
stack.push({ kind: "exit", schema: frame.schema });
|
|
106
|
+
pendingVisitCount += optionCount;
|
|
107
|
+
for (let index = optionArray.length - 1; index >= 0; index--) {
|
|
108
|
+
const option = optionArray[index];
|
|
109
|
+
if (!option)
|
|
110
|
+
return finish(undefined);
|
|
111
|
+
stack.push({
|
|
112
|
+
kind: "visit",
|
|
113
|
+
schema: option,
|
|
114
|
+
depth: frame.depth + 1,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return finish(Array.from(new Set(keys)));
|
|
119
|
+
}
|
|
19
120
|
function getDef(schema) {
|
|
20
121
|
return schema._def;
|
|
21
122
|
}
|
|
@@ -24,8 +125,217 @@ function getTypeTag(schema) {
|
|
|
24
125
|
const def = getDef(schema);
|
|
25
126
|
return def.typeName ?? def.type;
|
|
26
127
|
}
|
|
128
|
+
function originatesFromCustomSchema(schema) {
|
|
129
|
+
const seen = new WeakSet();
|
|
130
|
+
let current = schema;
|
|
131
|
+
let depth = 0;
|
|
132
|
+
while (!seen.has(current)) {
|
|
133
|
+
assertConversionDepth(depth);
|
|
134
|
+
seen.add(current);
|
|
135
|
+
const tag = getTypeTag(current);
|
|
136
|
+
if (tag === "custom" || tag === "ZodCustom")
|
|
137
|
+
return true;
|
|
138
|
+
if (tag !== "pipe" && tag !== "ZodEffects")
|
|
139
|
+
return false;
|
|
140
|
+
const def = getDef(current);
|
|
141
|
+
const input = def.schema ?? def.in;
|
|
142
|
+
if (!input || input === current)
|
|
143
|
+
return false;
|
|
144
|
+
current = input;
|
|
145
|
+
depth++;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
function representedPipeSchema(def) {
|
|
150
|
+
const input = def.schema ?? def.in;
|
|
151
|
+
return input && originatesFromCustomSchema(input) && def.out ? def.out : input;
|
|
152
|
+
}
|
|
153
|
+
/** Record a literal default without evaluating Zod's possibly dynamic getter. */
|
|
154
|
+
export function recordStaticJsonSchemaDefault(schema, value) {
|
|
155
|
+
Object.defineProperty(getDef(schema), STATIC_DEFAULT_VALUE, {
|
|
156
|
+
configurable: true,
|
|
157
|
+
enumerable: true,
|
|
158
|
+
value: { value },
|
|
159
|
+
writable: false,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function getStaticJsonSchemaDefault(def) {
|
|
163
|
+
const marked = def[STATIC_DEFAULT_VALUE];
|
|
164
|
+
if (!marked || typeof marked !== "object" || !("value" in marked))
|
|
165
|
+
return undefined;
|
|
166
|
+
return marked;
|
|
167
|
+
}
|
|
168
|
+
function getCheckDefinitions(def) {
|
|
169
|
+
return (def.checks ?? []).flatMap((check) => {
|
|
170
|
+
if (!check || typeof check !== "object")
|
|
171
|
+
return [];
|
|
172
|
+
const internal = check._zod?.def;
|
|
173
|
+
const definition = internal ?? check;
|
|
174
|
+
return definition && typeof definition === "object" ? [definition] : [];
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function finiteNumber(value) {
|
|
178
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
179
|
+
}
|
|
180
|
+
function addConjunctiveConstraints(json, keyword, values) {
|
|
181
|
+
const uniqueValues = Array.from(new Set(values));
|
|
182
|
+
if (uniqueValues.length === 0)
|
|
183
|
+
return;
|
|
184
|
+
if (uniqueValues.length === 1) {
|
|
185
|
+
json[keyword] = uniqueValues[0];
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
json.allOf = [
|
|
189
|
+
...(json.allOf ?? []),
|
|
190
|
+
...uniqueValues.map((value) => ({ [keyword]: value })),
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
const STRING_FORMAT_MAP = {
|
|
194
|
+
email: "email",
|
|
195
|
+
url: "uri",
|
|
196
|
+
uuid: "uuid",
|
|
197
|
+
datetime: "date-time",
|
|
198
|
+
};
|
|
199
|
+
function convertString(def) {
|
|
200
|
+
const json = { type: "string" };
|
|
201
|
+
const patterns = [];
|
|
202
|
+
const formats = [];
|
|
203
|
+
for (const check of getCheckDefinitions(def)) {
|
|
204
|
+
if (check.check === "min_length" && finiteNumber(check.minimum)) {
|
|
205
|
+
json.minLength = Math.max(json.minLength ?? 0, check.minimum);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (check.check === "max_length" && finiteNumber(check.maximum)) {
|
|
209
|
+
json.maxLength = Math.min(json.maxLength ?? Number.POSITIVE_INFINITY, check.maximum);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const format = check.check === "string_format" ? check.format : check.kind;
|
|
213
|
+
if (format === "min" && finiteNumber(check.value)) {
|
|
214
|
+
json.minLength = Math.max(json.minLength ?? 0, check.value);
|
|
215
|
+
}
|
|
216
|
+
else if (format === "max" && finiteNumber(check.value)) {
|
|
217
|
+
json.maxLength = Math.min(json.maxLength ?? Number.POSITIVE_INFINITY, check.value);
|
|
218
|
+
}
|
|
219
|
+
else if (format === "regex") {
|
|
220
|
+
const regex = check.pattern ?? check.regex;
|
|
221
|
+
if (regex && regex.flags.length === 0)
|
|
222
|
+
patterns.push(regex.source);
|
|
223
|
+
}
|
|
224
|
+
else if (format && STRING_FORMAT_MAP[format]) {
|
|
225
|
+
formats.push(STRING_FORMAT_MAP[format]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
addConjunctiveConstraints(json, "pattern", patterns);
|
|
229
|
+
addConjunctiveConstraints(json, "format", formats);
|
|
230
|
+
return json;
|
|
231
|
+
}
|
|
232
|
+
function tighterLowerBoundary(current, candidate) {
|
|
233
|
+
if (!current || candidate.value > current.value)
|
|
234
|
+
return candidate;
|
|
235
|
+
if (candidate.value < current.value)
|
|
236
|
+
return current;
|
|
237
|
+
return { value: current.value, exclusive: current.exclusive || candidate.exclusive };
|
|
238
|
+
}
|
|
239
|
+
function tighterUpperBoundary(current, candidate) {
|
|
240
|
+
if (!current || candidate.value < current.value)
|
|
241
|
+
return candidate;
|
|
242
|
+
if (candidate.value > current.value)
|
|
243
|
+
return current;
|
|
244
|
+
return { value: current.value, exclusive: current.exclusive || candidate.exclusive };
|
|
245
|
+
}
|
|
246
|
+
function convertNumber(def) {
|
|
247
|
+
let integer = false;
|
|
248
|
+
let lower;
|
|
249
|
+
let upper;
|
|
250
|
+
for (const check of getCheckDefinitions(def)) {
|
|
251
|
+
if ((check.check === "number_format" && check.format === "safeint") ||
|
|
252
|
+
check.kind === "int") {
|
|
253
|
+
integer = true;
|
|
254
|
+
lower = tighterLowerBoundary(lower, {
|
|
255
|
+
value: -Number.MAX_SAFE_INTEGER,
|
|
256
|
+
exclusive: false,
|
|
257
|
+
});
|
|
258
|
+
upper = tighterUpperBoundary(upper, {
|
|
259
|
+
value: Number.MAX_SAFE_INTEGER,
|
|
260
|
+
exclusive: false,
|
|
261
|
+
});
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (check.check === "greater_than" && finiteNumber(check.value)) {
|
|
265
|
+
lower = tighterLowerBoundary(lower, {
|
|
266
|
+
value: check.value,
|
|
267
|
+
exclusive: check.inclusive !== true,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else if (check.check === "less_than" && finiteNumber(check.value)) {
|
|
271
|
+
upper = tighterUpperBoundary(upper, {
|
|
272
|
+
value: check.value,
|
|
273
|
+
exclusive: check.inclusive !== true,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else if (check.kind === "min" && finiteNumber(check.value)) {
|
|
277
|
+
lower = tighterLowerBoundary(lower, {
|
|
278
|
+
value: check.value,
|
|
279
|
+
exclusive: check.inclusive === false,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
else if (check.kind === "max" && finiteNumber(check.value)) {
|
|
283
|
+
upper = tighterUpperBoundary(upper, {
|
|
284
|
+
value: check.value,
|
|
285
|
+
exclusive: check.inclusive === false,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const json = { type: integer ? "integer" : "number" };
|
|
290
|
+
if (lower) {
|
|
291
|
+
if (lower.exclusive)
|
|
292
|
+
json.exclusiveMinimum = lower.value;
|
|
293
|
+
else
|
|
294
|
+
json.minimum = lower.value;
|
|
295
|
+
}
|
|
296
|
+
if (upper) {
|
|
297
|
+
if (upper.exclusive)
|
|
298
|
+
json.exclusiveMaximum = upper.value;
|
|
299
|
+
else
|
|
300
|
+
json.maximum = upper.value;
|
|
301
|
+
}
|
|
302
|
+
return json;
|
|
303
|
+
}
|
|
304
|
+
function arrayLimit(value) {
|
|
305
|
+
if (finiteNumber(value))
|
|
306
|
+
return value;
|
|
307
|
+
if (value && typeof value === "object" && finiteNumber(value.value))
|
|
308
|
+
return value.value;
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
function applyArrayLimits(json, def) {
|
|
312
|
+
const minimums = [];
|
|
313
|
+
const maximums = [];
|
|
314
|
+
const legacyMinimum = arrayLimit(def.minLength);
|
|
315
|
+
const legacyMaximum = arrayLimit(def.maxLength);
|
|
316
|
+
if (legacyMinimum !== undefined)
|
|
317
|
+
minimums.push(legacyMinimum);
|
|
318
|
+
if (legacyMaximum !== undefined)
|
|
319
|
+
maximums.push(legacyMaximum);
|
|
320
|
+
for (const check of getCheckDefinitions(def)) {
|
|
321
|
+
if (check.check === "min_length" && finiteNumber(check.minimum)) {
|
|
322
|
+
minimums.push(check.minimum);
|
|
323
|
+
}
|
|
324
|
+
else if (check.check === "max_length" && finiteNumber(check.maximum)) {
|
|
325
|
+
maximums.push(check.maximum);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (minimums.length > 0)
|
|
329
|
+
json.minItems = Math.max(...minimums);
|
|
330
|
+
if (maximums.length > 0)
|
|
331
|
+
json.maxItems = Math.min(...maximums);
|
|
332
|
+
}
|
|
27
333
|
export function zodToJsonSchema(schema) {
|
|
28
|
-
return convertSchema(schema, {
|
|
334
|
+
return convertSchema(schema, {
|
|
335
|
+
seen: new WeakSet(),
|
|
336
|
+
depth: 0,
|
|
337
|
+
nodeCount: 0,
|
|
338
|
+
});
|
|
29
339
|
}
|
|
30
340
|
function convertSchema(schema, context) {
|
|
31
341
|
// Guard against invalid schemas (can happen with different zod instances in npm bundle)
|
|
@@ -45,11 +355,18 @@ export function isOptionalSchema(schema) {
|
|
|
45
355
|
function convert(schema, context) {
|
|
46
356
|
if (context.seen.has(schema))
|
|
47
357
|
return {};
|
|
358
|
+
assertConversionDepth(context.depth);
|
|
359
|
+
context.nodeCount += 1;
|
|
360
|
+
if (context.nodeCount > MAX_CONVERSION_NODES) {
|
|
361
|
+
throw new RangeError(`Zod schema exceeds the maximum conversion node count of ${MAX_CONVERSION_NODES}`);
|
|
362
|
+
}
|
|
48
363
|
context.seen.add(schema);
|
|
364
|
+
context.depth += 1;
|
|
49
365
|
try {
|
|
50
366
|
return convertInner(schema, context);
|
|
51
367
|
}
|
|
52
368
|
finally {
|
|
369
|
+
context.depth -= 1;
|
|
53
370
|
context.seen.delete(schema);
|
|
54
371
|
}
|
|
55
372
|
}
|
|
@@ -59,13 +376,23 @@ function convertInner(schema, context) {
|
|
|
59
376
|
switch (tag) {
|
|
60
377
|
case "ZodString":
|
|
61
378
|
case "string":
|
|
62
|
-
return
|
|
379
|
+
return convertString(def);
|
|
63
380
|
case "ZodNumber":
|
|
64
381
|
case "number":
|
|
65
|
-
return
|
|
382
|
+
return convertNumber(def);
|
|
66
383
|
case "ZodBoolean":
|
|
67
384
|
case "boolean":
|
|
68
385
|
return { type: "boolean" };
|
|
386
|
+
case "ZodNull":
|
|
387
|
+
case "null":
|
|
388
|
+
return { type: "null" };
|
|
389
|
+
case "ZodUnknown":
|
|
390
|
+
case "unknown":
|
|
391
|
+
case "ZodAny":
|
|
392
|
+
case "any":
|
|
393
|
+
case "ZodCustom":
|
|
394
|
+
case "custom":
|
|
395
|
+
return {};
|
|
69
396
|
case "ZodBigInt":
|
|
70
397
|
case "bigint":
|
|
71
398
|
return { type: "integer" };
|
|
@@ -95,7 +422,12 @@ function convertInner(schema, context) {
|
|
|
95
422
|
const required = [];
|
|
96
423
|
for (const [key, value] of Object.entries(shape ?? {})) {
|
|
97
424
|
const zodSchema = value;
|
|
98
|
-
properties
|
|
425
|
+
Object.defineProperty(properties, key, {
|
|
426
|
+
configurable: true,
|
|
427
|
+
enumerable: true,
|
|
428
|
+
value: convertSchema(zodSchema, context),
|
|
429
|
+
writable: true,
|
|
430
|
+
});
|
|
99
431
|
if (!isOptionalSchema(zodSchema))
|
|
100
432
|
required.push(key);
|
|
101
433
|
}
|
|
@@ -111,9 +443,12 @@ function convertInner(schema, context) {
|
|
|
111
443
|
case "array": {
|
|
112
444
|
// v3: _def.type (item schema), v4: _def.element (item schema)
|
|
113
445
|
const itemType = def.element ?? def.type;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
446
|
+
const json = { type: "array" };
|
|
447
|
+
if (itemType && typeof itemType !== "string") {
|
|
448
|
+
json.items = convertSchema(itemType, context);
|
|
449
|
+
}
|
|
450
|
+
applyArrayLimits(json, def);
|
|
451
|
+
return json;
|
|
117
452
|
}
|
|
118
453
|
case "ZodTuple":
|
|
119
454
|
case "tuple": {
|
|
@@ -137,7 +472,37 @@ function convertInner(schema, context) {
|
|
|
137
472
|
const valueSchema = def.valueType ?? def.element;
|
|
138
473
|
if (!valueSchema)
|
|
139
474
|
return { type: "object" };
|
|
140
|
-
|
|
475
|
+
const valueJsonSchema = convertSchema(valueSchema, context);
|
|
476
|
+
const finiteKeys = def.keyType && finiteStringRecordKeys(def.keyType, context);
|
|
477
|
+
if (finiteKeys) {
|
|
478
|
+
const properties = {};
|
|
479
|
+
for (const key of finiteKeys) {
|
|
480
|
+
Object.defineProperty(properties, key, {
|
|
481
|
+
configurable: true,
|
|
482
|
+
enumerable: true,
|
|
483
|
+
value: valueJsonSchema,
|
|
484
|
+
writable: true,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
type: "object",
|
|
489
|
+
properties,
|
|
490
|
+
required: finiteKeys,
|
|
491
|
+
additionalProperties: false,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
const json = {
|
|
495
|
+
type: "object",
|
|
496
|
+
additionalProperties: valueJsonSchema,
|
|
497
|
+
};
|
|
498
|
+
if (def.keyType) {
|
|
499
|
+
const propertyNames = convertSchema(def.keyType, context);
|
|
500
|
+
if (Object.keys(propertyNames).length !== 1 ||
|
|
501
|
+
propertyNames.type !== "string") {
|
|
502
|
+
json.propertyNames = propertyNames;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return json;
|
|
141
506
|
}
|
|
142
507
|
case "ZodDefault":
|
|
143
508
|
case "default": {
|
|
@@ -145,22 +510,19 @@ function convertInner(schema, context) {
|
|
|
145
510
|
if (!innerType)
|
|
146
511
|
return { type: "object" };
|
|
147
512
|
const inner = convertSchema(innerType, context);
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (typeof inner === "object" && !("anyOf" in inner) && defaultValue !== undefined) {
|
|
152
|
-
inner.default = defaultValue;
|
|
153
|
-
}
|
|
513
|
+
const staticDefault = getStaticJsonSchemaDefault(def);
|
|
514
|
+
if (staticDefault)
|
|
515
|
+
inner.default = staticDefault.value;
|
|
154
516
|
return inner;
|
|
155
517
|
}
|
|
156
518
|
case "ZodLazy":
|
|
157
519
|
case "lazy":
|
|
158
|
-
return def.getter ?
|
|
520
|
+
return def.getter ? convertSchema(def.getter(), context) : { type: "object" };
|
|
159
521
|
case "ZodEffects":
|
|
160
522
|
case "pipe": {
|
|
161
523
|
// v3: ZodEffects wraps schema in _def.schema
|
|
162
524
|
// v4: pipe wraps in _def.in (input schema)
|
|
163
|
-
const innerSchema = def
|
|
525
|
+
const innerSchema = representedPipeSchema(def);
|
|
164
526
|
return innerSchema ? convert(innerSchema, context) : { type: "object" };
|
|
165
527
|
}
|
|
166
528
|
default:
|
|
@@ -184,10 +546,16 @@ function getObjectAdditionalProperties(def, context) {
|
|
|
184
546
|
return convertSchema(def.catchall, context);
|
|
185
547
|
}
|
|
186
548
|
function unwrapSchema(schema) {
|
|
549
|
+
const seen = new WeakSet();
|
|
187
550
|
let current = schema;
|
|
188
551
|
let nullable = false;
|
|
189
552
|
let optional = false;
|
|
553
|
+
let depth = 0;
|
|
190
554
|
while (true) {
|
|
555
|
+
assertConversionDepth(depth);
|
|
556
|
+
if (seen.has(current))
|
|
557
|
+
return { schema: current, nullable, optional };
|
|
558
|
+
seen.add(current);
|
|
191
559
|
const tag = getTypeTag(current);
|
|
192
560
|
const def = getDef(current);
|
|
193
561
|
switch (tag) {
|
|
@@ -195,17 +563,20 @@ function unwrapSchema(schema) {
|
|
|
195
563
|
case "nullable":
|
|
196
564
|
nullable = true;
|
|
197
565
|
current = (def.innerType ?? def.schema);
|
|
566
|
+
depth++;
|
|
198
567
|
break;
|
|
199
568
|
case "ZodOptional":
|
|
200
569
|
case "optional":
|
|
201
570
|
optional = true;
|
|
202
571
|
current = (def.innerType ?? def.schema);
|
|
572
|
+
depth++;
|
|
203
573
|
break;
|
|
204
574
|
case "ZodEffects":
|
|
205
575
|
case "pipe":
|
|
206
|
-
current = def
|
|
576
|
+
current = representedPipeSchema(def) ?? current;
|
|
207
577
|
if (current === schema)
|
|
208
578
|
return { schema: current, nullable, optional };
|
|
579
|
+
depth++;
|
|
209
580
|
break;
|
|
210
581
|
default:
|
|
211
582
|
return { schema: current, nullable, optional };
|
|
@@ -213,8 +584,14 @@ function unwrapSchema(schema) {
|
|
|
213
584
|
}
|
|
214
585
|
}
|
|
215
586
|
function hasDefaultSchema(schema) {
|
|
587
|
+
const seen = new WeakSet();
|
|
216
588
|
let current = schema;
|
|
589
|
+
let depth = 0;
|
|
217
590
|
while (true) {
|
|
591
|
+
assertConversionDepth(depth);
|
|
592
|
+
if (seen.has(current))
|
|
593
|
+
return false;
|
|
594
|
+
seen.add(current);
|
|
218
595
|
const tag = getTypeTag(current);
|
|
219
596
|
const def = getDef(current);
|
|
220
597
|
switch (tag) {
|
|
@@ -231,6 +608,7 @@ function hasDefaultSchema(schema) {
|
|
|
231
608
|
if (!inner || inner === current)
|
|
232
609
|
return false;
|
|
233
610
|
current = inner;
|
|
611
|
+
depth++;
|
|
234
612
|
break;
|
|
235
613
|
}
|
|
236
614
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veryfront/ext-schema-zod",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1189",
|
|
4
4
|
"description": "Veryfront first-party extension package for ext-schema-zod",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"veryfront",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"scripts": {},
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=
|
|
30
|
+
"node": ">=22.3.0"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
@@ -42,10 +42,12 @@
|
|
|
42
42
|
"capabilities": []
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
+
"ajv": "8.18.0",
|
|
46
|
+
"ajv-formats": "3.0.1",
|
|
45
47
|
"zod": "4.3.6"
|
|
46
48
|
},
|
|
47
49
|
"peerDependencies": {
|
|
48
|
-
"veryfront": "^0.1.
|
|
50
|
+
"veryfront": "^0.1.1189"
|
|
49
51
|
},
|
|
50
52
|
"type": "module",
|
|
51
53
|
"types": "./esm/index.d.ts",
|