@es-joy/jsoe 0.26.1 → 0.28.0
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/.gitignore +10 -0
- package/.npmignore +9 -0
- package/CHANGES.md +76 -0
- package/README.md +6 -4
- package/badges/coverage-badge.svg +1 -1
- package/badges/tests-badge.svg +1 -0
- package/dist/formats/schema.d.ts +11 -0
- package/dist/formats/schema.d.ts.map +1 -1
- package/dist/formats/structuredCloning.d.ts.map +1 -1
- package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
- package/dist/fundamentalTypes/stringType.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/typeChoices.d.ts +32 -2
- package/dist/typeChoices.d.ts.map +1 -1
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/rawTypesonEditor.d.ts +110 -0
- package/dist/utils/rawTypesonEditor.d.ts.map +1 -0
- package/dist/vendor-imports.d.ts +4 -0
- package/docs/proposals/raw-typeson-edit-view.md +575 -0
- package/eslint.config.js +36 -27
- package/mmr.json +6 -0
- package/package.json +22 -12
- package/pnpm-workspace.yaml +4 -2
- package/src/formats/schema.js +38 -7
- package/src/formats/structuredCloning.js +24 -17
- package/src/fundamentalTypes/arrayType.js +135 -7
- package/src/fundamentalTypes/stringType.js +5 -0
- package/src/jsoe.css +12 -0
- package/src/typeChoices.js +39 -8
- package/src/types.js +20 -2
- package/src/utils/rawTypesonEditor.js +669 -0
- package/src/vendor-imports.js +12 -0
- package/tsconfig.json +1 -1
- package/typings/json-6.d.ts +12 -0
- package/vendor/@codemirror/autocomplete/dist/index.js +2125 -0
- package/vendor/@codemirror/commands/dist/index.js +1826 -0
- package/vendor/@codemirror/lang-javascript/dist/index.js +513 -0
- package/vendor/@codemirror/language/dist/index.js +2693 -0
- package/vendor/@codemirror/lint/dist/index.js +956 -0
- package/vendor/@codemirror/search/dist/index.js +1238 -0
- package/vendor/@codemirror/state/dist/index.js +3947 -0
- package/vendor/@codemirror/view/dist/index.js +11867 -0
- package/vendor/@lezer/common/dist/index.js +2202 -0
- package/vendor/@lezer/highlight/dist/index.js +927 -0
- package/vendor/@lezer/javascript/dist/index.js +192 -0
- package/vendor/@lezer/lr/dist/index.js +1889 -0
- package/vendor/@marijn/find-cluster-break/src/index.js +87 -0
- package/vendor/codemirror/dist/index.js +96 -0
- package/vendor/crelt/index.js +28 -0
- package/vendor/json-6/dist/index.mjs +1783 -0
- package/vendor/style-mod/src/style-mod.js +172 -0
- package/vendor/typeson-registry/dist/index.js +83 -10
- package/vendor/w3c-keyname/index.js +119 -0
- package/vendor/zod/classic/checks.d.ts +1 -1
- package/vendor/zod/classic/checks.js +1 -1
- package/vendor/zod/classic/external.d.ts +1 -1
- package/vendor/zod/classic/external.js +1 -1
- package/vendor/zod/classic/from-json-schema.js +245 -24
- package/vendor/zod/classic/schemas.d.ts +17 -1
- package/vendor/zod/classic/schemas.js +77 -29
- package/vendor/zod/core/api.d.ts +6 -4
- package/vendor/zod/core/api.js +17 -2
- package/vendor/zod/core/checks.d.ts +1 -1
- package/vendor/zod/core/checks.js +0 -96
- package/vendor/zod/core/compile.d.ts +18 -4
- package/vendor/zod/core/compile.js +146 -67
- package/vendor/zod/core/doc.js +7 -2
- package/vendor/zod/core/errors.js +2 -5
- package/vendor/zod/core/json-schema-generator.js +2 -2
- package/vendor/zod/core/json-schema-processors.d.ts +17 -0
- package/vendor/zod/core/json-schema-processors.js +195 -64
- package/vendor/zod/core/memoizer.js +72 -23
- package/vendor/zod/core/parse.js +37 -16
- package/vendor/zod/core/regexes.d.ts +2 -0
- package/vendor/zod/core/regexes.js +8 -4
- package/vendor/zod/core/schemas.d.ts +42 -7
- package/vendor/zod/core/schemas.js +301 -94
- package/vendor/zod/core/to-json-schema.d.ts +7 -3
- package/vendor/zod/core/to-json-schema.js +8 -7
- package/vendor/zod/core/util.d.ts +25 -4
- package/vendor/zod/core/util.js +160 -143
- package/vendor/zod/core/versions.d.ts +1 -1
- package/vendor/zod/core/versions.js +2 -2
- package/vendor/zod/core/visit.js +12 -0
- package/vendor/zod/locales/ar.js +1 -0
- package/vendor/zod/locales/az.js +1 -0
- package/vendor/zod/locales/be.js +1 -0
- package/vendor/zod/locales/bg.js +1 -0
- package/vendor/zod/locales/bn.js +1 -0
- package/vendor/zod/locales/ca.js +1 -0
- package/vendor/zod/locales/ckb.js +1 -0
- package/vendor/zod/locales/cs.js +1 -0
- package/vendor/zod/locales/da.js +1 -0
- package/vendor/zod/locales/de.js +1 -0
- package/vendor/zod/locales/el.js +1 -0
- package/vendor/zod/locales/en.js +1 -0
- package/vendor/zod/locales/eo.js +1 -0
- package/vendor/zod/locales/es.js +1 -0
- package/vendor/zod/locales/fa.js +1 -0
- package/vendor/zod/locales/fi.js +1 -0
- package/vendor/zod/locales/fr-CA.js +1 -0
- package/vendor/zod/locales/fr.js +1 -0
- package/vendor/zod/locales/gu.js +1 -0
- package/vendor/zod/locales/he.js +1 -0
- package/vendor/zod/locales/hi.js +1 -0
- package/vendor/zod/locales/hr.js +1 -0
- package/vendor/zod/locales/hu.js +1 -0
- package/vendor/zod/locales/hy.js +1 -0
- package/vendor/zod/locales/id.js +1 -0
- package/vendor/zod/locales/index.d.ts +1 -0
- package/vendor/zod/locales/index.js +1 -0
- package/vendor/zod/locales/is.js +1 -0
- package/vendor/zod/locales/it.js +1 -0
- package/vendor/zod/locales/ja.js +1 -0
- package/vendor/zod/locales/ka.js +1 -0
- package/vendor/zod/locales/km.js +1 -0
- package/vendor/zod/locales/kn.js +1 -0
- package/vendor/zod/locales/ko.js +1 -0
- package/vendor/zod/locales/lt.js +1 -0
- package/vendor/zod/locales/mk.js +1 -0
- package/vendor/zod/locales/ms.js +1 -0
- package/vendor/zod/locales/ne.js +1 -0
- package/vendor/zod/locales/nl.js +1 -0
- package/vendor/zod/locales/nn.js +1 -0
- package/vendor/zod/locales/no.js +1 -0
- package/vendor/zod/locales/ota.js +1 -0
- package/vendor/zod/locales/pl.js +1 -0
- package/vendor/zod/locales/ps.js +1 -0
- package/vendor/zod/locales/pt-BR.js +1 -0
- package/vendor/zod/locales/pt.js +1 -0
- package/vendor/zod/locales/ro.js +1 -0
- package/vendor/zod/locales/ru.js +1 -0
- package/vendor/zod/locales/sk.js +1 -0
- package/vendor/zod/locales/sl.js +1 -0
- package/vendor/zod/locales/sv.js +1 -0
- package/vendor/zod/locales/ta.js +1 -0
- package/vendor/zod/locales/tg.d.ts +4 -0
- package/vendor/zod/locales/tg.js +116 -0
- package/vendor/zod/locales/th.js +1 -0
- package/vendor/zod/locales/tk.js +1 -0
- package/vendor/zod/locales/tr.js +1 -0
- package/vendor/zod/locales/uk.js +1 -0
- package/vendor/zod/locales/ur.js +1 -0
- package/vendor/zod/locales/uz.js +1 -0
- package/vendor/zod/locales/vi.js +1 -0
- package/vendor/zod/locales/yo.js +1 -0
- package/vendor/zod/locales/zh-CN.js +1 -0
- package/vendor/zod/locales/zh-TW.js +1 -0
- package/vendor/zod/mini/checks.d.ts +1 -1
- package/vendor/zod/mini/checks.js +1 -1
- package/vendor/zod/mini/external.d.ts +1 -1
- package/vendor/zod/mini/external.js +1 -1
- package/vendor/zod/mini/schemas.d.ts +8 -0
- package/vendor/zod/mini/schemas.js +19 -2
- package/vendor/zodexy/dist/esm/index.js +34 -1
- package/vendor/zodexy/dist/schema.zodexy.json +18 -0
|
@@ -20,7 +20,6 @@ const _messageDesc = {
|
|
|
20
20
|
enumerable: true,
|
|
21
21
|
configurable: true,
|
|
22
22
|
};
|
|
23
|
-
const _zodDesc = { value: undefined, enumerable: false };
|
|
24
23
|
const _issuesDesc = { value: undefined, enumerable: false };
|
|
25
24
|
/* Prototypes that already carry the lazy `toString`. Seeded with the
|
|
26
25
|
* intrinsics so that `init` on a foreign object — it accepts any object —
|
|
@@ -28,12 +27,10 @@ const _issuesDesc = { value: undefined, enumerable: false };
|
|
|
28
27
|
const _installedToString = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
|
|
29
28
|
const initializer = (inst, def) => {
|
|
30
29
|
inst.name = "$ZodError";
|
|
31
|
-
|
|
32
|
-
Object.defineProperty(inst, "_zod", _zodDesc);
|
|
30
|
+
// `_zod` is already non-enumerable: $constructor's init defined it with this same descriptor
|
|
33
31
|
_issuesDesc.value = def;
|
|
34
32
|
Object.defineProperty(inst, "issues", _issuesDesc);
|
|
35
|
-
// Clear the shared
|
|
36
|
-
_zodDesc.value = undefined;
|
|
33
|
+
// Clear the shared slot; a retained `value` pins the last error's issues.
|
|
37
34
|
_issuesDesc.value = undefined;
|
|
38
35
|
Object.defineProperty(inst, "message", _messageDesc);
|
|
39
36
|
/* `toString` lives as a non-enumerable lazy getter on the shared
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { allProcessors } from "./json-schema-processors.js";
|
|
2
|
-
import { extractDefs, finalize, initializeContext,
|
|
2
|
+
import { extractDefs, finalize, initializeContext, processSchema, } from "./to-json-schema.js";
|
|
3
3
|
/**
|
|
4
4
|
* Legacy class-based interface for JSON Schema generation.
|
|
5
5
|
* This class wraps the new functional implementation to provide backward compatibility.
|
|
@@ -71,7 +71,7 @@ export class JSONSchemaGenerator {
|
|
|
71
71
|
* This must be called before emit().
|
|
72
72
|
*/
|
|
73
73
|
process(schema, _params = { path: [], schemaPath: [] }) {
|
|
74
|
-
return
|
|
74
|
+
return processSchema(schema, this.ctx, _params);
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Emit the final JSON Schema after processing.
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import type { $ZodRegistry } from "./registries.js";
|
|
2
2
|
import type * as schemas from "./schemas.js";
|
|
3
3
|
import { type Processor, type RegistryToJSONSchemaParams, type ToJSONSchemaParams, type ZodStandardJSONSchemaPayload } from "./to-json-schema.js";
|
|
4
|
+
type Bound = number | bigint | Date;
|
|
5
|
+
interface CheckAggregate<N extends Bound = Bound> {
|
|
6
|
+
minimum?: N;
|
|
7
|
+
maximum?: N;
|
|
8
|
+
exclusiveMinimum?: N;
|
|
9
|
+
exclusiveMaximum?: N;
|
|
10
|
+
multipleOf?: number[];
|
|
11
|
+
format?: string;
|
|
12
|
+
isInt?: boolean;
|
|
13
|
+
patterns?: Set<RegExp>;
|
|
14
|
+
mime?: string[];
|
|
15
|
+
contentEncoding?: string;
|
|
16
|
+
laxFormat?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function aggregateChecks<N extends Bound = Bound>(schema: schemas.$ZodType): CheckAggregate<N>;
|
|
4
19
|
export declare const stringProcessor: Processor<schemas.$ZodString>;
|
|
5
20
|
export declare const numberProcessor: Processor<schemas.$ZodNumber>;
|
|
6
21
|
export declare const booleanProcessor: Processor<schemas.$ZodBoolean>;
|
|
@@ -26,6 +41,7 @@ export declare const mapProcessor: Processor<schemas.$ZodMap>;
|
|
|
26
41
|
export declare const setProcessor: Processor<schemas.$ZodSet>;
|
|
27
42
|
export declare const arrayProcessor: Processor<schemas.$ZodArray>;
|
|
28
43
|
export declare const objectProcessor: Processor<schemas.$ZodObject>;
|
|
44
|
+
export declare const propertiesProcessor: Processor<schemas.$ZodProperties>;
|
|
29
45
|
export declare const unionProcessor: Processor<schemas.$ZodUnion>;
|
|
30
46
|
export declare const intersectionProcessor: Processor<schemas.$ZodIntersection>;
|
|
31
47
|
export declare const tupleProcessor: Processor<schemas.$ZodTuple>;
|
|
@@ -47,3 +63,4 @@ export declare function toJSONSchema(registry: $ZodRegistry<{
|
|
|
47
63
|
}>, params?: RegistryToJSONSchemaParams): {
|
|
48
64
|
schemas: Record<string, ZodStandardJSONSchemaPayload<schemas.$ZodType>>;
|
|
49
65
|
};
|
|
66
|
+
export {};
|
|
@@ -1,6 +1,100 @@
|
|
|
1
1
|
import * as regexes from "./regexes.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { base64Charset, base64urlCharset } from "./schemas.js";
|
|
3
|
+
import { extractDefs, finalize, handleUnrepresentable, initializeContext, isTransforming, processSchema, } from "./to-json-schema.js";
|
|
4
|
+
import { BIGINT_FORMAT_RANGES, NUMBER_FORMAT_RANGES, assignProp, getEnumValues } from "./util.js";
|
|
5
|
+
const narrowMin = (agg, key, value) => {
|
|
6
|
+
if (agg[key] === undefined || value > agg[key])
|
|
7
|
+
agg[key] = value;
|
|
8
|
+
};
|
|
9
|
+
const narrowMax = (agg, key, value) => {
|
|
10
|
+
if (agg[key] === undefined || value < agg[key])
|
|
11
|
+
agg[key] = value;
|
|
12
|
+
};
|
|
13
|
+
const narrowBoth = (agg, value) => {
|
|
14
|
+
narrowMin(agg, "minimum", value);
|
|
15
|
+
narrowMax(agg, "maximum", value);
|
|
16
|
+
};
|
|
17
|
+
const addDivisor = (agg, value) => {
|
|
18
|
+
agg.multipleOf ?? (agg.multipleOf = []);
|
|
19
|
+
if (!agg.multipleOf.includes(value))
|
|
20
|
+
agg.multipleOf.push(value);
|
|
21
|
+
};
|
|
22
|
+
const addPattern = (agg, pattern) => {
|
|
23
|
+
agg.patterns ?? (agg.patterns = new Set());
|
|
24
|
+
agg.patterns.add(pattern);
|
|
25
|
+
};
|
|
26
|
+
const intersectMime = (agg, mime) => {
|
|
27
|
+
agg.mime = agg.mime ? agg.mime.filter((m) => mime.includes(m)) : [...mime];
|
|
28
|
+
};
|
|
29
|
+
// last-wins, matching the bag's historical write order; the flag keeps an integer format from being lost to a later float one
|
|
30
|
+
const setFormat = (agg, format) => {
|
|
31
|
+
agg.format = format;
|
|
32
|
+
if (format.includes("int"))
|
|
33
|
+
agg.isInt = true;
|
|
34
|
+
};
|
|
35
|
+
const minContributor = (agg, def) => narrowMin(agg, "minimum", def.minimum);
|
|
36
|
+
const maxContributor = (agg, def) => narrowMax(agg, "maximum", def.maximum);
|
|
37
|
+
const formatContributor = (ranges) => (agg, def) => {
|
|
38
|
+
setFormat(agg, def.format);
|
|
39
|
+
const [minimum, maximum] = ranges[def.format];
|
|
40
|
+
narrowMin(agg, "minimum", minimum);
|
|
41
|
+
narrowMax(agg, "maximum", maximum);
|
|
42
|
+
};
|
|
43
|
+
const contributors = {
|
|
44
|
+
greater_than: (agg, def) => narrowMin(agg, def.inclusive ? "minimum" : "exclusiveMinimum", def.value),
|
|
45
|
+
less_than: (agg, def) => narrowMax(agg, def.inclusive ? "maximum" : "exclusiveMaximum", def.value),
|
|
46
|
+
multiple_of: (agg, def) => addDivisor(agg, def.value),
|
|
47
|
+
number_format: formatContributor(NUMBER_FORMAT_RANGES),
|
|
48
|
+
bigint_format: formatContributor(BIGINT_FORMAT_RANGES),
|
|
49
|
+
min_length: minContributor,
|
|
50
|
+
max_length: maxContributor,
|
|
51
|
+
length_equals: (agg, def) => narrowBoth(agg, def.length),
|
|
52
|
+
min_size: minContributor,
|
|
53
|
+
max_size: maxContributor,
|
|
54
|
+
size_equals: (agg, def) => narrowBoth(agg, def.size),
|
|
55
|
+
string_format: (agg, def) => {
|
|
56
|
+
setFormat(agg, def.format);
|
|
57
|
+
if (def.pattern)
|
|
58
|
+
addPattern(agg, def.pattern);
|
|
59
|
+
if (def.format === "base64" || def.format === "base64url")
|
|
60
|
+
agg.contentEncoding = def.format;
|
|
61
|
+
if (def.local || def.precision === -1)
|
|
62
|
+
agg.laxFormat = true;
|
|
63
|
+
},
|
|
64
|
+
mime_type: (agg, def) => intersectMime(agg, def.mime),
|
|
65
|
+
};
|
|
66
|
+
export function aggregateChecks(schema) {
|
|
67
|
+
const agg = {};
|
|
68
|
+
const def = schema._zod.def;
|
|
69
|
+
// a format schema is its own first check, same rule as $ZodType init
|
|
70
|
+
const list = schema._zod.traits.has("$ZodCheck")
|
|
71
|
+
? [schema, ...(def.checks ?? [])]
|
|
72
|
+
: (def.checks ?? []);
|
|
73
|
+
for (const ch of list)
|
|
74
|
+
contributors[ch._zod.def.check]?.(agg, ch._zod.def);
|
|
75
|
+
// reconcile with the bag so third-party onattach contributions still land; first-party residue is never tighter than the fold, so merging it back is idempotent for one and additive for the other
|
|
76
|
+
const bag = schema._zod.bag;
|
|
77
|
+
if (bag.minimum !== undefined)
|
|
78
|
+
narrowMin(agg, "minimum", bag.minimum);
|
|
79
|
+
if (bag.exclusiveMinimum !== undefined)
|
|
80
|
+
narrowMin(agg, "exclusiveMinimum", bag.exclusiveMinimum);
|
|
81
|
+
if (bag.maximum !== undefined)
|
|
82
|
+
narrowMax(agg, "maximum", bag.maximum);
|
|
83
|
+
if (bag.exclusiveMaximum !== undefined)
|
|
84
|
+
narrowMax(agg, "exclusiveMaximum", bag.exclusiveMaximum);
|
|
85
|
+
if (bag.multipleOf !== undefined)
|
|
86
|
+
addDivisor(agg, bag.multipleOf);
|
|
87
|
+
if (bag.format !== undefined) {
|
|
88
|
+
agg.format ?? (agg.format = bag.format);
|
|
89
|
+
if (bag.format.includes("int"))
|
|
90
|
+
agg.isInt = true;
|
|
91
|
+
}
|
|
92
|
+
if (bag.mime)
|
|
93
|
+
intersectMime(agg, bag.mime);
|
|
94
|
+
for (const pattern of bag.patterns ?? [])
|
|
95
|
+
addPattern(agg, pattern);
|
|
96
|
+
return agg;
|
|
97
|
+
}
|
|
4
98
|
const formatMap = {
|
|
5
99
|
guid: "uuid",
|
|
6
100
|
url: "uri",
|
|
@@ -9,11 +103,16 @@ const formatMap = {
|
|
|
9
103
|
regex: "", // do not set
|
|
10
104
|
};
|
|
11
105
|
// ==================== SIMPLE TYPE PROCESSORS ====================
|
|
106
|
+
// the runtime patterns are lax so parse paths never overflow the regex stack; the emitted schema swaps in the exact block forms, which zod itself never executes
|
|
107
|
+
const exactPatterns = new Map([
|
|
108
|
+
[base64Charset, regexes.base64],
|
|
109
|
+
[base64urlCharset, regexes.base64url],
|
|
110
|
+
]);
|
|
111
|
+
const exactPattern = (p) => exactPatterns.get(p) ?? p;
|
|
12
112
|
export const stringProcessor = (schema, ctx, _json, _params) => {
|
|
13
113
|
const json = _json;
|
|
14
114
|
json.type = "string";
|
|
15
|
-
const { minimum, maximum, format, patterns, contentEncoding, laxFormat } = schema
|
|
16
|
-
.bag;
|
|
115
|
+
const { minimum, maximum, format, patterns, contentEncoding, laxFormat } = aggregateChecks(schema);
|
|
17
116
|
if (typeof minimum === "number")
|
|
18
117
|
json.minLength = minimum;
|
|
19
118
|
if (typeof maximum === "number")
|
|
@@ -31,7 +130,7 @@ export const stringProcessor = (schema, ctx, _json, _params) => {
|
|
|
31
130
|
if (contentEncoding)
|
|
32
131
|
json.contentEncoding = contentEncoding;
|
|
33
132
|
if (patterns && patterns.size > 0) {
|
|
34
|
-
const patternList = [...patterns];
|
|
133
|
+
const patternList = [...patterns].map(exactPattern);
|
|
35
134
|
if (patternList.length === 1)
|
|
36
135
|
json.pattern = patternList[0].source;
|
|
37
136
|
else if (patternList.length > 1) {
|
|
@@ -48,11 +147,8 @@ export const stringProcessor = (schema, ctx, _json, _params) => {
|
|
|
48
147
|
};
|
|
49
148
|
export const numberProcessor = (schema, ctx, _json, params) => {
|
|
50
149
|
const json = _json;
|
|
51
|
-
const { minimum, maximum,
|
|
52
|
-
|
|
53
|
-
json.type = "integer";
|
|
54
|
-
else
|
|
55
|
-
json.type = "number";
|
|
150
|
+
const { minimum, maximum, multipleOf, exclusiveMaximum, exclusiveMinimum, isInt } = aggregateChecks(schema);
|
|
151
|
+
json.type = isInt ? "integer" : "number";
|
|
56
152
|
// when both minimum and exclusiveMinimum exist, pick the more restrictive one
|
|
57
153
|
const exMin = typeof exclusiveMinimum === "number" && exclusiveMinimum >= (minimum ?? Number.NEGATIVE_INFINITY);
|
|
58
154
|
const exMax = typeof exclusiveMaximum === "number" && exclusiveMaximum <= (maximum ?? Number.POSITIVE_INFINITY);
|
|
@@ -81,12 +177,21 @@ export const numberProcessor = (schema, ctx, _json, params) => {
|
|
|
81
177
|
else if (typeof maximum === "number") {
|
|
82
178
|
json.maximum = maximum;
|
|
83
179
|
}
|
|
84
|
-
if (
|
|
180
|
+
if (multipleOf) {
|
|
85
181
|
// JSON Schema requires a divisor strictly greater than zero, and a non-finite one does not survive JSON at all. A negative divisor accepts exactly what its absolute value accepts, so it still maps; zero, NaN and Infinity have no keyword form.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
182
|
+
const divisors = new Set();
|
|
183
|
+
for (const divisor of multipleOf) {
|
|
184
|
+
if (Number.isFinite(divisor) && divisor !== 0)
|
|
185
|
+
divisors.add(Math.abs(divisor));
|
|
186
|
+
else
|
|
187
|
+
handleUnrepresentable(schema, ctx, json, params, `A multipleOf divisor of ${divisor} cannot be represented in JSON Schema`);
|
|
188
|
+
}
|
|
189
|
+
// chained divisors are a conjunction the keyword cannot carry alone, so extras ride an allOf, same as stacked patterns
|
|
190
|
+
const [first, ...rest] = divisors;
|
|
191
|
+
if (first !== undefined)
|
|
192
|
+
json.multipleOf = first;
|
|
193
|
+
if (rest.length)
|
|
194
|
+
json.allOf = [...(json.allOf ?? []), ...rest.map((m) => ({ multipleOf: m }))];
|
|
90
195
|
}
|
|
91
196
|
};
|
|
92
197
|
export const booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
@@ -203,29 +308,24 @@ export const templateLiteralProcessor = (schema, _ctx, json, _params) => {
|
|
|
203
308
|
};
|
|
204
309
|
export const fileProcessor = (schema, _ctx, json, _params) => {
|
|
205
310
|
const _json = json;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
};
|
|
211
|
-
const { minimum, maximum, mime } = schema._zod.bag;
|
|
311
|
+
_json.type = "string";
|
|
312
|
+
_json.format = "binary";
|
|
313
|
+
_json.contentEncoding = "binary";
|
|
314
|
+
const { minimum, maximum, mime } = aggregateChecks(schema);
|
|
212
315
|
if (minimum !== undefined)
|
|
213
|
-
|
|
316
|
+
_json.minLength = minimum;
|
|
214
317
|
if (maximum !== undefined)
|
|
215
|
-
|
|
216
|
-
if (mime)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
else {
|
|
227
|
-
Object.assign(_json, file);
|
|
228
|
-
}
|
|
318
|
+
_json.maxLength = maximum;
|
|
319
|
+
if (!mime)
|
|
320
|
+
return;
|
|
321
|
+
// an empty intersection means the mime checks share no value, so nothing passes at runtime; `anyOf` must be non-empty, so the false schema is `not: {}`
|
|
322
|
+
if (mime.length === 0)
|
|
323
|
+
_json.not = {};
|
|
324
|
+
else if (mime.length === 1)
|
|
325
|
+
_json.contentMediaType = mime[0];
|
|
326
|
+
// only contentMediaType differs, so the shared props stay at the root
|
|
327
|
+
else
|
|
328
|
+
_json.anyOf = mime.map((m) => ({ contentMediaType: m }));
|
|
229
329
|
};
|
|
230
330
|
export const successProcessor = (_schema, _ctx, json, _params) => {
|
|
231
331
|
json.type = "boolean";
|
|
@@ -249,13 +349,13 @@ export const setProcessor = (schema, ctx, json, params) => {
|
|
|
249
349
|
export const arrayProcessor = (schema, ctx, _json, params) => {
|
|
250
350
|
const json = _json;
|
|
251
351
|
const def = schema._zod.def;
|
|
252
|
-
const { minimum, maximum } = schema
|
|
352
|
+
const { minimum, maximum } = aggregateChecks(schema);
|
|
253
353
|
if (typeof minimum === "number")
|
|
254
354
|
json.minItems = minimum;
|
|
255
355
|
if (typeof maximum === "number")
|
|
256
356
|
json.maxItems = maximum;
|
|
257
357
|
json.type = "array";
|
|
258
|
-
json.items =
|
|
358
|
+
json.items = processSchema(def.element, ctx, {
|
|
259
359
|
...params,
|
|
260
360
|
path: [...params.path, "items"],
|
|
261
361
|
});
|
|
@@ -289,7 +389,7 @@ export const objectProcessor = (schema, ctx, _json, params) => {
|
|
|
289
389
|
json.properties = {};
|
|
290
390
|
for (const key in shape) {
|
|
291
391
|
// assignProp so a __proto__ key becomes an own property instead of hitting the inherited setter on the plain {} we build into
|
|
292
|
-
assignProp(json.properties, key,
|
|
392
|
+
assignProp(json.properties, key, processSchema(shape[key], ctx, {
|
|
293
393
|
...params,
|
|
294
394
|
path: [...params.path, "properties", key],
|
|
295
395
|
}));
|
|
@@ -319,17 +419,48 @@ export const objectProcessor = (schema, ctx, _json, params) => {
|
|
|
319
419
|
json.additionalProperties = false;
|
|
320
420
|
}
|
|
321
421
|
else if (def.catchall) {
|
|
322
|
-
json.additionalProperties =
|
|
422
|
+
json.additionalProperties = processSchema(def.catchall, ctx, {
|
|
323
423
|
...params,
|
|
324
424
|
path: [...params.path, "additionalProperties"],
|
|
325
425
|
});
|
|
326
426
|
}
|
|
327
427
|
};
|
|
428
|
+
// asserts named properties in place and passes everything else through, so no additionalProperties constraint is emitted
|
|
429
|
+
export const propertiesProcessor = (schema, ctx, _json, params) => {
|
|
430
|
+
const json = _json;
|
|
431
|
+
const def = schema._zod.def;
|
|
432
|
+
// dropping a symbol key silently would emit a schema that asserts less than this one does
|
|
433
|
+
if (Object.getOwnPropertySymbols(def.shape).length &&
|
|
434
|
+
handleUnrepresentable(schema, ctx, json, params, "Symbol keys cannot be represented in JSON Schema")) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
// the parsed value is the input, so an output-mode emission would describe a transformed value this schema never returns
|
|
438
|
+
if (ctx.io === "output") {
|
|
439
|
+
for (const key in def.shape) {
|
|
440
|
+
if (isTransforming(def.shape[key]) &&
|
|
441
|
+
handleUnrepresentable(schema, ctx, json, params, `z.properties() returns its input, so the output of a transforming schema at key "${key}" cannot be represented in JSON Schema`)) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
json.type = "object";
|
|
447
|
+
json.properties = {};
|
|
448
|
+
for (const key in def.shape) {
|
|
449
|
+
assignProp(json.properties, key, processSchema(def.shape[key], ctx, {
|
|
450
|
+
...params,
|
|
451
|
+
path: [...params.path, "properties", key],
|
|
452
|
+
}));
|
|
453
|
+
}
|
|
454
|
+
// input-side optionality in both modes: the parsed value is the input, so a defaulted key that stays absent must not be required of the output either
|
|
455
|
+
const required = Object.keys(def.shape).filter((key) => inputOptin(def.shape[key]) === undefined);
|
|
456
|
+
if (required.length > 0)
|
|
457
|
+
json.required = required;
|
|
458
|
+
};
|
|
328
459
|
export const unionProcessor = (schema, ctx, json, params) => {
|
|
329
460
|
const def = schema._zod.def;
|
|
330
461
|
// Exclusive unions (inclusive === false) use oneOf (exactly one match) instead of anyOf (one or more matches). This includes both z.xor() and discriminated unions
|
|
331
462
|
const isExclusive = def.inclusive === false;
|
|
332
|
-
const options = def.options.map((x, i) =>
|
|
463
|
+
const options = def.options.map((x, i) => processSchema(x, ctx, {
|
|
333
464
|
...params,
|
|
334
465
|
path: [...params.path, isExclusive ? "oneOf" : "anyOf", i],
|
|
335
466
|
}));
|
|
@@ -342,11 +473,11 @@ export const unionProcessor = (schema, ctx, json, params) => {
|
|
|
342
473
|
};
|
|
343
474
|
export const intersectionProcessor = (schema, ctx, json, params) => {
|
|
344
475
|
const def = schema._zod.def;
|
|
345
|
-
const a =
|
|
476
|
+
const a = processSchema(def.left, ctx, {
|
|
346
477
|
...params,
|
|
347
478
|
path: [...params.path, "allOf", 0],
|
|
348
479
|
});
|
|
349
|
-
const b =
|
|
480
|
+
const b = processSchema(def.right, ctx, {
|
|
350
481
|
...params,
|
|
351
482
|
path: [...params.path, "allOf", 1],
|
|
352
483
|
});
|
|
@@ -365,12 +496,12 @@ export const tupleProcessor = (schema, ctx, _json, params) => {
|
|
|
365
496
|
json.type = "array";
|
|
366
497
|
const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
367
498
|
const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
368
|
-
const prefixItems = def.items.map((x, i) =>
|
|
499
|
+
const prefixItems = def.items.map((x, i) => processSchema(x, ctx, {
|
|
369
500
|
...params,
|
|
370
501
|
path: [...params.path, prefixPath, i],
|
|
371
502
|
}));
|
|
372
503
|
const rest = def.rest
|
|
373
|
-
?
|
|
504
|
+
? processSchema(def.rest, ctx, {
|
|
374
505
|
...params,
|
|
375
506
|
path: [...params.path, restPath, ...(ctx.target === "openapi-3.0" ? [def.items.length] : [])],
|
|
376
507
|
})
|
|
@@ -424,7 +555,7 @@ export const tupleProcessor = (schema, ctx, _json, params) => {
|
|
|
424
555
|
json.maxItems = maxItems;
|
|
425
556
|
}
|
|
426
557
|
// explicit user-defined length checks take precedence
|
|
427
|
-
const { minimum, maximum } = schema
|
|
558
|
+
const { minimum, maximum } = aggregateChecks(schema);
|
|
428
559
|
if (typeof minimum === "number")
|
|
429
560
|
json.minItems = minimum;
|
|
430
561
|
if (typeof maximum === "number")
|
|
@@ -517,23 +648,22 @@ export const recordProcessor = (schema, ctx, _json, params) => {
|
|
|
517
648
|
json.type = "object";
|
|
518
649
|
// For looseRecord with regex patterns, use patternProperties. This correctly represents "only validate keys matching the pattern" semantics and composes well with allOf (intersections)
|
|
519
650
|
const keyType = def.keyType;
|
|
520
|
-
const
|
|
521
|
-
const patterns = keyBag?.patterns;
|
|
651
|
+
const patterns = aggregateChecks(keyType).patterns;
|
|
522
652
|
if (def.mode === "loose" && patterns && patterns.size > 0) {
|
|
523
653
|
// Use patternProperties for looseRecord with regex patterns
|
|
524
|
-
const valueSchema =
|
|
654
|
+
const valueSchema = processSchema(def.valueType, ctx, {
|
|
525
655
|
...params,
|
|
526
656
|
path: [...params.path, "patternProperties", "*"],
|
|
527
657
|
});
|
|
528
658
|
json.patternProperties = {};
|
|
529
659
|
for (const pattern of patterns) {
|
|
530
|
-
assignProp(json.patternProperties, pattern.source, valueSchema);
|
|
660
|
+
assignProp(json.patternProperties, exactPattern(pattern).source, valueSchema);
|
|
531
661
|
}
|
|
532
662
|
}
|
|
533
663
|
else {
|
|
534
664
|
// Default behavior: use propertyNames + additionalProperties
|
|
535
665
|
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
|
|
536
|
-
json.propertyNames =
|
|
666
|
+
json.propertyNames = processSchema(def.keyType, ctx, {
|
|
537
667
|
...params,
|
|
538
668
|
path: [...params.path, "propertyNames"],
|
|
539
669
|
});
|
|
@@ -545,7 +675,7 @@ export const recordProcessor = (schema, ctx, _json, params) => {
|
|
|
545
675
|
}
|
|
546
676
|
pending.push(schema);
|
|
547
677
|
}
|
|
548
|
-
json.additionalProperties =
|
|
678
|
+
json.additionalProperties = processSchema(def.valueType, ctx, {
|
|
549
679
|
...params,
|
|
550
680
|
path: [...params.path, "additionalProperties"],
|
|
551
681
|
});
|
|
@@ -563,7 +693,7 @@ export const recordProcessor = (schema, ctx, _json, params) => {
|
|
|
563
693
|
};
|
|
564
694
|
export const nullableProcessor = (schema, ctx, json, params) => {
|
|
565
695
|
const def = schema._zod.def;
|
|
566
|
-
const inner =
|
|
696
|
+
const inner = processSchema(def.innerType, ctx, params);
|
|
567
697
|
const seen = ctx.seen.get(schema);
|
|
568
698
|
if (ctx.target === "openapi-3.0") {
|
|
569
699
|
seen.ref = def.innerType;
|
|
@@ -575,7 +705,7 @@ export const nullableProcessor = (schema, ctx, json, params) => {
|
|
|
575
705
|
};
|
|
576
706
|
export const nonoptionalProcessor = (schema, ctx, _json, params) => {
|
|
577
707
|
const def = schema._zod.def;
|
|
578
|
-
|
|
708
|
+
processSchema(def.innerType, ctx, params);
|
|
579
709
|
const seen = ctx.seen.get(schema);
|
|
580
710
|
seen.ref = def.innerType;
|
|
581
711
|
};
|
|
@@ -598,7 +728,7 @@ function serializeDefaultValue(value, schema, ctx, json, params) {
|
|
|
598
728
|
}
|
|
599
729
|
export const defaultProcessor = (schema, ctx, json, params) => {
|
|
600
730
|
const def = schema._zod.def;
|
|
601
|
-
|
|
731
|
+
processSchema(def.innerType, ctx, params);
|
|
602
732
|
const seen = ctx.seen.get(schema);
|
|
603
733
|
seen.ref = def.innerType;
|
|
604
734
|
const value = serializeDefaultValue(def.defaultValue, schema, ctx, json, params);
|
|
@@ -607,7 +737,7 @@ export const defaultProcessor = (schema, ctx, json, params) => {
|
|
|
607
737
|
};
|
|
608
738
|
export const prefaultProcessor = (schema, ctx, json, params) => {
|
|
609
739
|
const def = schema._zod.def;
|
|
610
|
-
|
|
740
|
+
processSchema(def.innerType, ctx, params);
|
|
611
741
|
const seen = ctx.seen.get(schema);
|
|
612
742
|
seen.ref = def.innerType;
|
|
613
743
|
if (ctx.io !== "input")
|
|
@@ -618,7 +748,7 @@ export const prefaultProcessor = (schema, ctx, json, params) => {
|
|
|
618
748
|
};
|
|
619
749
|
export const catchProcessor = (schema, ctx, json, params) => {
|
|
620
750
|
const def = schema._zod.def;
|
|
621
|
-
|
|
751
|
+
processSchema(def.innerType, ctx, params);
|
|
622
752
|
const seen = ctx.seen.get(schema);
|
|
623
753
|
seen.ref = def.innerType;
|
|
624
754
|
let catchValue;
|
|
@@ -635,32 +765,32 @@ export const pipeProcessor = (schema, ctx, _json, params) => {
|
|
|
635
765
|
const def = schema._zod.def;
|
|
636
766
|
const inIsTransform = def.in._zod.traits.has("$ZodTransform");
|
|
637
767
|
const innerType = ctx.io === "input" ? (inIsTransform ? def.out : def.in) : def.out;
|
|
638
|
-
|
|
768
|
+
processSchema(innerType, ctx, params);
|
|
639
769
|
const seen = ctx.seen.get(schema);
|
|
640
770
|
seen.ref = innerType;
|
|
641
771
|
};
|
|
642
772
|
export const readonlyProcessor = (schema, ctx, json, params) => {
|
|
643
773
|
const def = schema._zod.def;
|
|
644
|
-
|
|
774
|
+
processSchema(def.innerType, ctx, params);
|
|
645
775
|
const seen = ctx.seen.get(schema);
|
|
646
776
|
seen.ref = def.innerType;
|
|
647
777
|
json.readOnly = true;
|
|
648
778
|
};
|
|
649
779
|
export const promiseProcessor = (schema, ctx, _json, params) => {
|
|
650
780
|
const def = schema._zod.def;
|
|
651
|
-
|
|
781
|
+
processSchema(def.innerType, ctx, params);
|
|
652
782
|
const seen = ctx.seen.get(schema);
|
|
653
783
|
seen.ref = def.innerType;
|
|
654
784
|
};
|
|
655
785
|
export const optionalProcessor = (schema, ctx, _json, params) => {
|
|
656
786
|
const def = schema._zod.def;
|
|
657
|
-
|
|
787
|
+
processSchema(def.innerType, ctx, params);
|
|
658
788
|
const seen = ctx.seen.get(schema);
|
|
659
789
|
seen.ref = def.innerType;
|
|
660
790
|
};
|
|
661
791
|
export const lazyProcessor = (schema, ctx, _json, params) => {
|
|
662
792
|
const innerType = schema._zod.innerType;
|
|
663
|
-
|
|
793
|
+
processSchema(innerType, ctx, params);
|
|
664
794
|
const seen = ctx.seen.get(schema);
|
|
665
795
|
seen.ref = innerType;
|
|
666
796
|
};
|
|
@@ -685,6 +815,7 @@ export const allProcessors = {
|
|
|
685
815
|
file: fileProcessor,
|
|
686
816
|
success: successProcessor,
|
|
687
817
|
custom: customProcessor,
|
|
818
|
+
properties: propertiesProcessor,
|
|
688
819
|
function: functionProcessor,
|
|
689
820
|
transform: transformProcessor,
|
|
690
821
|
map: mapProcessor,
|
|
@@ -715,7 +846,7 @@ export function toJSONSchema(input, params) {
|
|
|
715
846
|
// First pass: process all schemas to build the seen map
|
|
716
847
|
for (const entry of registry._idmap.entries()) {
|
|
717
848
|
const [_, schema] = entry;
|
|
718
|
-
|
|
849
|
+
processSchema(schema, ctx);
|
|
719
850
|
}
|
|
720
851
|
const schemas = {};
|
|
721
852
|
const external = {
|
|
@@ -741,7 +872,7 @@ export function toJSONSchema(input, params) {
|
|
|
741
872
|
}
|
|
742
873
|
// Single schema case
|
|
743
874
|
const ctx = initializeContext({ ...params, processors: allProcessors });
|
|
744
|
-
|
|
875
|
+
processSchema(input, ctx);
|
|
745
876
|
extractDefs(ctx, input);
|
|
746
877
|
return finalize(ctx, input);
|
|
747
878
|
}
|