toolcraft-schema 0.0.23 → 0.0.25
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/dist/index.js +44 -1
- package/dist/oneof.js +7 -2
- package/dist/union.js +13 -6
- package/dist/validate.js +33 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -80,6 +80,30 @@ function assertValidEnumValues(values) {
|
|
|
80
80
|
if (uniqueValues.size !== values.length) {
|
|
81
81
|
throw new Error("Enum schema values must be unique");
|
|
82
82
|
}
|
|
83
|
+
if (values.some((value) => typeof value === "number" && !Number.isFinite(value))) {
|
|
84
|
+
throw new Error("Enum schema numeric values must be finite");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function assertNonNegativeInteger(value, name) {
|
|
88
|
+
if (value !== undefined && (!Number.isInteger(value) || value < 0)) {
|
|
89
|
+
throw new Error(`${name} must be a non-negative integer`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function assertFiniteNumber(value, name) {
|
|
93
|
+
if (value !== undefined && !Number.isFinite(value)) {
|
|
94
|
+
throw new Error(`${name} must be finite`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function assertPattern(pattern) {
|
|
98
|
+
if (pattern === undefined) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
new RegExp(pattern);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new Error("pattern must be a valid regular expression");
|
|
106
|
+
}
|
|
83
107
|
}
|
|
84
108
|
function unwrapOptional(schema) {
|
|
85
109
|
if (isOptionalSchema(schema)) {
|
|
@@ -106,12 +130,21 @@ function withInjectedDiscriminator(schema, discriminator, branchName) {
|
|
|
106
130
|
}
|
|
107
131
|
export const S = {
|
|
108
132
|
String(options = {}) {
|
|
133
|
+
assertNonNegativeInteger(options.minLength, "minLength");
|
|
134
|
+
assertNonNegativeInteger(options.maxLength, "maxLength");
|
|
135
|
+
assertPattern(options.pattern);
|
|
109
136
|
return {
|
|
110
137
|
kind: "string",
|
|
111
138
|
...options,
|
|
112
139
|
};
|
|
113
140
|
},
|
|
114
141
|
Number(options = {}) {
|
|
142
|
+
assertFiniteNumber(options.minimum, "minimum");
|
|
143
|
+
assertFiniteNumber(options.maximum, "maximum");
|
|
144
|
+
assertFiniteNumber(options.default, "default");
|
|
145
|
+
if (options.jsonType === "integer" && options.default !== undefined && !Number.isInteger(options.default)) {
|
|
146
|
+
throw new Error("default must be an integer");
|
|
147
|
+
}
|
|
115
148
|
return {
|
|
116
149
|
kind: "number",
|
|
117
150
|
...options,
|
|
@@ -125,6 +158,9 @@ export const S = {
|
|
|
125
158
|
},
|
|
126
159
|
Enum(values, options = {}) {
|
|
127
160
|
assertValidEnumValues(values);
|
|
161
|
+
if (options.jsonType === "integer" && values.some((value) => typeof value !== "number" || !Number.isInteger(value))) {
|
|
162
|
+
throw new Error("Integer enum values must be integers");
|
|
163
|
+
}
|
|
128
164
|
return {
|
|
129
165
|
kind: "enum",
|
|
130
166
|
values,
|
|
@@ -132,6 +168,8 @@ export const S = {
|
|
|
132
168
|
};
|
|
133
169
|
},
|
|
134
170
|
Array(item, options = {}) {
|
|
171
|
+
assertNonNegativeInteger(options.minItems, "minItems");
|
|
172
|
+
assertNonNegativeInteger(options.maxItems, "maxItems");
|
|
135
173
|
return {
|
|
136
174
|
kind: "array",
|
|
137
175
|
item,
|
|
@@ -186,7 +224,12 @@ export function toJsonSchema(schema) {
|
|
|
186
224
|
const properties = {};
|
|
187
225
|
const required = [];
|
|
188
226
|
for (const [key, propertySchema] of Object.entries(unwrappedSchema.shape)) {
|
|
189
|
-
properties
|
|
227
|
+
Object.defineProperty(properties, key, {
|
|
228
|
+
enumerable: true,
|
|
229
|
+
configurable: true,
|
|
230
|
+
writable: true,
|
|
231
|
+
value: toJsonSchema(propertySchema)
|
|
232
|
+
});
|
|
190
233
|
if (!isOptionalSchema(propertySchema)) {
|
|
191
234
|
required.push(key);
|
|
192
235
|
}
|
package/dist/oneof.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
function assertValidBranches(branches) {
|
|
1
|
+
function assertValidBranches(branches, discriminator) {
|
|
2
2
|
if (Object.keys(branches).length === 0) {
|
|
3
3
|
throw new Error("OneOf schema requires at least one branch");
|
|
4
4
|
}
|
|
5
|
+
for (const [branchName, branch] of Object.entries(branches)) {
|
|
6
|
+
if (Object.prototype.hasOwnProperty.call(branch.shape, discriminator)) {
|
|
7
|
+
throw new Error(`OneOf branch "${branchName}" must not declare discriminator field "${discriminator}".`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
5
10
|
}
|
|
6
11
|
export function OneOf(config) {
|
|
7
|
-
assertValidBranches(config.branches);
|
|
12
|
+
assertValidBranches(config.branches, config.discriminator);
|
|
8
13
|
return {
|
|
9
14
|
kind: "oneOf",
|
|
10
15
|
discriminator: config.discriminator,
|
package/dist/union.js
CHANGED
|
@@ -12,14 +12,21 @@ export function getRequiredKeyFingerprint(schema) {
|
|
|
12
12
|
function assertUniqueRequiredKeyFingerprints(branches) {
|
|
13
13
|
const fingerprints = new Map();
|
|
14
14
|
branches.forEach((branch, index) => {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const requiredKeys = getRequiredKeys(branch);
|
|
16
|
+
const fingerprint = JSON.stringify(requiredKeys);
|
|
17
|
+
const existing = fingerprints.get(fingerprint);
|
|
18
|
+
if (existing === undefined) {
|
|
19
|
+
fingerprints.set(fingerprint, {
|
|
20
|
+
display: requiredKeys.join("+"),
|
|
21
|
+
indices: [index],
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
existing.indices.push(index);
|
|
19
26
|
});
|
|
20
|
-
for (const
|
|
27
|
+
for (const { display, indices } of fingerprints.values()) {
|
|
21
28
|
if (indices.length > 1) {
|
|
22
|
-
throw new Error(`Union branches [${indices.join(", ")}] share required-key fingerprint "${
|
|
29
|
+
throw new Error(`Union branches [${indices.join(", ")}] share required-key fingerprint "${display}". Each branch must require a distinct set of keys.`);
|
|
23
30
|
}
|
|
24
31
|
}
|
|
25
32
|
}
|
package/dist/validate.js
CHANGED
|
@@ -46,7 +46,7 @@ function walkOptional(schema, value, path, state) {
|
|
|
46
46
|
if (value === missingValue || value === undefined) {
|
|
47
47
|
const defaultValue = getDefault(schema.inner);
|
|
48
48
|
if (defaultValue.present) {
|
|
49
|
-
return
|
|
49
|
+
return walkSchema(schema.inner, cloneDefault(defaultValue.value), path, state);
|
|
50
50
|
}
|
|
51
51
|
return { present: false };
|
|
52
52
|
}
|
|
@@ -75,7 +75,7 @@ function walkString(schema, value, path, state) {
|
|
|
75
75
|
return { present: true, value };
|
|
76
76
|
}
|
|
77
77
|
function walkNumber(schema, value, path, state) {
|
|
78
|
-
if (typeof value !== "number" || Number.
|
|
78
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
79
79
|
addExpectedIssue(state, path, schema.jsonType === "integer" ? "integer" : "number", value);
|
|
80
80
|
return { present: true, value };
|
|
81
81
|
}
|
|
@@ -135,15 +135,15 @@ function walkObject(schema, value, path, state, injectedProperties = {}) {
|
|
|
135
135
|
const propertyValue = Object.hasOwn(value, key) ? value[key] : missingValue;
|
|
136
136
|
const result = walkSchema(propertySchema, propertyValue, [...path, key], state);
|
|
137
137
|
if (result.present) {
|
|
138
|
-
nextValue
|
|
138
|
+
setOwnValue(nextValue, key, result.value);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
for (const [key, injectedValue] of Object.entries(injectedProperties)) {
|
|
142
142
|
if (Object.hasOwn(value, key)) {
|
|
143
|
-
nextValue
|
|
143
|
+
setOwnValue(nextValue, key, value[key]);
|
|
144
144
|
}
|
|
145
145
|
else {
|
|
146
|
-
nextValue
|
|
146
|
+
setOwnValue(nextValue, key, injectedValue);
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
for (const [key, propertyValue] of Object.entries(value)) {
|
|
@@ -151,7 +151,7 @@ function walkObject(schema, value, path, state, injectedProperties = {}) {
|
|
|
151
151
|
continue;
|
|
152
152
|
}
|
|
153
153
|
if (schema.additionalProperties === true) {
|
|
154
|
-
nextValue
|
|
154
|
+
setOwnValue(nextValue, key, propertyValue);
|
|
155
155
|
}
|
|
156
156
|
else {
|
|
157
157
|
addUnexpectedPropertyIssue(state, [...path, key]);
|
|
@@ -224,7 +224,7 @@ function walkRecord(schema, value, path, state) {
|
|
|
224
224
|
for (const [key, propertyValue] of Object.entries(value)) {
|
|
225
225
|
const result = walkSchema(schema.value, propertyValue, [...path, key], state);
|
|
226
226
|
if (result.present) {
|
|
227
|
-
nextValue
|
|
227
|
+
setOwnValue(nextValue, key, result.value);
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
return { present: true, value: nextValue };
|
|
@@ -252,7 +252,7 @@ function isPlainRecord(value) {
|
|
|
252
252
|
const prototype = Object.getPrototypeOf(value);
|
|
253
253
|
return prototype === Object.prototype || prototype === null;
|
|
254
254
|
}
|
|
255
|
-
function isJsonValue(value) {
|
|
255
|
+
function isJsonValue(value, ancestors = new Set()) {
|
|
256
256
|
if (value === null ||
|
|
257
257
|
typeof value === "string" ||
|
|
258
258
|
typeof value === "number" ||
|
|
@@ -260,13 +260,36 @@ function isJsonValue(value) {
|
|
|
260
260
|
return typeof value !== "number" || Number.isFinite(value);
|
|
261
261
|
}
|
|
262
262
|
if (Array.isArray(value)) {
|
|
263
|
-
|
|
263
|
+
if (ancestors.has(value)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
ancestors.add(value);
|
|
267
|
+
const result = value.every((item) => isJsonValue(item, ancestors));
|
|
268
|
+
ancestors.delete(value);
|
|
269
|
+
return result;
|
|
264
270
|
}
|
|
265
271
|
if (isPlainRecord(value)) {
|
|
266
|
-
|
|
272
|
+
if (ancestors.has(value)) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
ancestors.add(value);
|
|
276
|
+
const result = Object.values(value).every((item) => isJsonValue(item, ancestors));
|
|
277
|
+
ancestors.delete(value);
|
|
278
|
+
return result;
|
|
267
279
|
}
|
|
268
280
|
return false;
|
|
269
281
|
}
|
|
282
|
+
function cloneDefault(value) {
|
|
283
|
+
return structuredClone(value);
|
|
284
|
+
}
|
|
285
|
+
function setOwnValue(target, key, value) {
|
|
286
|
+
Object.defineProperty(target, key, {
|
|
287
|
+
configurable: true,
|
|
288
|
+
enumerable: true,
|
|
289
|
+
writable: true,
|
|
290
|
+
value
|
|
291
|
+
});
|
|
292
|
+
}
|
|
270
293
|
function expectedFor(schema) {
|
|
271
294
|
switch (schema.kind) {
|
|
272
295
|
case "string":
|