toolcraft-schema 0.0.430 → 0.0.432
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 +51 -10
- package/dist/index.d.ts +35 -11
- package/dist/index.js +40 -20
- package/dist/json.d.ts +7 -1
- package/dist/json.js +3 -2
- package/dist/native-json-schema.d.ts +4 -1
- package/dist/native-json-schema.js +2 -1
- package/dist/oneof.d.ts +2 -1
- package/dist/oneof.js +3 -2
- package/dist/record.d.ts +2 -1
- package/dist/record.js +3 -2
- package/dist/standard.compile-check.d.ts +1 -0
- package/dist/standard.compile-check.js +20 -0
- package/dist/standard.d.ts +92 -0
- package/dist/standard.js +38 -0
- package/dist/union.d.ts +2 -1
- package/dist/union.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,8 +7,9 @@ and JSON Schema generation.
|
|
|
7
7
|
|
|
8
8
|
- Zero runtime dependencies
|
|
9
9
|
- Typed schema descriptors
|
|
10
|
-
- `Static<typeof schema>`
|
|
11
|
-
-
|
|
10
|
+
- `Static<typeof schema>` for legacy inference; `Input` and `Output` for parsed defaults
|
|
11
|
+
- Standard Schema v1 validation and Standard JSON Schema v1 conversion
|
|
12
|
+
- Runtime validation with `validate()`
|
|
12
13
|
- JSON Schema serialization via `toJsonSchema()`
|
|
13
14
|
- JSON Schema document serialization via `toJsonSchemaDocument()`
|
|
14
15
|
- Native JSON Schema compilation and property projection with reference support
|
|
@@ -16,7 +17,7 @@ and JSON Schema generation.
|
|
|
16
17
|
## Usage
|
|
17
18
|
|
|
18
19
|
```ts
|
|
19
|
-
import { S, toJsonSchema, toJsonSchemaDocument,
|
|
20
|
+
import { S, toJsonSchema, toJsonSchemaDocument, validate } from "toolcraft-schema";
|
|
20
21
|
import type { Static } from "toolcraft-schema";
|
|
21
22
|
|
|
22
23
|
const schema = S.Object({
|
|
@@ -39,7 +40,7 @@ const document = toJsonSchemaDocument(schema, {
|
|
|
39
40
|
id: "https://example.test/schema.json",
|
|
40
41
|
title: "Example schema"
|
|
41
42
|
});
|
|
42
|
-
const validation =
|
|
43
|
+
const validation = validate(schema, {
|
|
43
44
|
name: "Ada",
|
|
44
45
|
mode: "safe",
|
|
45
46
|
tags: []
|
|
@@ -55,9 +56,9 @@ const validation = validateValue(schema, {
|
|
|
55
56
|
- `S.Boolean({ description?, default?, short?, cliAliases? })`
|
|
56
57
|
- `S.Enum(values, { description?, default?, short?, cliAliases? })`
|
|
57
58
|
- `S.Array(itemSchema, { description?, default?, short?, cliAliases? })`
|
|
58
|
-
- `S.Record(valueSchema
|
|
59
|
-
- `S.Union([
|
|
60
|
-
- `S.OneOf(
|
|
59
|
+
- `S.Record(valueSchema)`
|
|
60
|
+
- `S.Union([objectSchemaA, objectSchemaB])`
|
|
61
|
+
- `S.OneOf({ discriminator: "kind", branches: { text: objectSchemaA, count: objectSchemaB } })`
|
|
61
62
|
- `S.Object({ [key]: schema })`
|
|
62
63
|
- `S.Optional(schema)`
|
|
63
64
|
|
|
@@ -65,7 +66,8 @@ const validation = validateValue(schema, {
|
|
|
65
66
|
|
|
66
67
|
- `Static<typeof schema>` infers the runtime TypeScript shape for a schema descriptor.
|
|
67
68
|
- Object properties wrapped in `S.Optional(...)` become optional properties in `Static`.
|
|
68
|
-
- Schemas declared with `nullable: true` infer `null`
|
|
69
|
+
- Schemas declared with `nullable: true` infer `null` and emit standard JSON Schema null unions.
|
|
70
|
+
- `Input<typeof schema>` describes accepted inputs. `Output<typeof schema>` makes optional properties with defaults required after parsing, including nested objects and arrays. `Static` keeps its existing optional-property behavior.
|
|
69
71
|
|
|
70
72
|
### JSON Schema generation
|
|
71
73
|
|
|
@@ -80,9 +82,9 @@ const validation = validateValue(schema, {
|
|
|
80
82
|
|
|
81
83
|
### Runtime validation
|
|
82
84
|
|
|
83
|
-
- `
|
|
85
|
+
- `validate(schema, value)` returns `{ ok: true, value }` for valid input.
|
|
84
86
|
- Invalid input returns `{ ok: false, issues }` with path-aware diagnostics.
|
|
85
|
-
- Validation
|
|
87
|
+
- Validation fills missing optional properties with their inner schema defaults. Required properties remain required. Use `{ defaults: "none" }` to disable defaults, or `{ defaults: "all" }` to also fill required properties. Each parsed default is an independent copy.
|
|
86
88
|
|
|
87
89
|
`compileJsonSchema(document, options)` validates complete native JSON Schema
|
|
88
90
|
documents. `projectJsonSchemaProperties(document, options)` supplies stable
|
|
@@ -101,6 +103,45 @@ Use `maxNodes` for larger bounded documents, or `maxDepth` (0–256) to choose a
|
|
|
101
103
|
depth budget. Shared objects count once for each occurrence in the JSON tree;
|
|
102
104
|
options never change other calls' budgets.
|
|
103
105
|
|
|
106
|
+
## Use with Standard Schema consumers
|
|
107
|
+
|
|
108
|
+
Pass a builder result directly to libraries that accept Standard Schema and
|
|
109
|
+
Standard JSON Schema, including tiny MCP servers. No adapter is required.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { S } from "toolcraft-schema";
|
|
113
|
+
import { createServer } from "tiny-stdio-mcp-server";
|
|
114
|
+
|
|
115
|
+
const input = S.Object({
|
|
116
|
+
query: S.String(),
|
|
117
|
+
limit: S.Optional(S.Number({ default: 10 }))
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
createServer({ name: "search", version: "1" })
|
|
121
|
+
.tool("search", "Search", input, ({ query, limit }) => {
|
|
122
|
+
// query: string; limit: number (the default has been applied)
|
|
123
|
+
return `${query}: ${limit}`;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const parsed = await input["~standard"].validate({ query: "hello" });
|
|
127
|
+
// { value: { query: "hello", limit: 10 } } or { issues: [...] }
|
|
128
|
+
const document = input["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
129
|
+
const outputDocument = input["~standard"].jsonSchema.output({ target: "draft-2020-12" });
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Standard validation uses the default optional-property behavior of `validate`.
|
|
133
|
+
Input conversion keeps defaulted optional properties optional; output conversion
|
|
134
|
+
marks them required. Direct `toJsonSchema(schema, { io: "output" })` also exposes
|
|
135
|
+
this output shape. Converters support `draft-07` and `draft-2020-12`, and reject
|
|
136
|
+
unsupported targets. Native schemas attached with `withJsonSchema` remain the
|
|
137
|
+
authoritative contract; projection defaults do not alter their values.
|
|
138
|
+
|
|
139
|
+
The `~standard` methods are non-enumerable. JSON serialization and structured
|
|
140
|
+
cloning keep the existing data descriptors. A spread or structured clone does
|
|
141
|
+
not retain methods: call `withStandardSchema(copiedDescriptor)` to attach them
|
|
142
|
+
when passing the copy to a standard consumer. Existing `validate` and
|
|
143
|
+
`toJsonSchema` functions continue to accept these plain descriptors.
|
|
144
|
+
|
|
104
145
|
## Environment Variables
|
|
105
146
|
|
|
106
147
|
This package exposes no environment variables.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Standardized } from "./standard.js";
|
|
1
2
|
import { Json } from "./json.js";
|
|
2
3
|
import { OneOf } from "./oneof.js";
|
|
3
4
|
import { Record as RecordBuilder } from "./record.js";
|
|
@@ -79,11 +80,15 @@ type SchemaOptions<TDefault> = {
|
|
|
79
80
|
};
|
|
80
81
|
type WithNullable<TSchema extends AnySchema, TOptions extends {
|
|
81
82
|
nullable?: boolean;
|
|
82
|
-
}> = TOptions extends {
|
|
83
|
+
}> = (TOptions extends {
|
|
83
84
|
readonly nullable: true;
|
|
84
85
|
} ? TSchema & {
|
|
85
86
|
readonly nullable: true;
|
|
86
|
-
} : TSchema
|
|
87
|
+
} : TSchema) & (TOptions extends {
|
|
88
|
+
readonly default: infer Value;
|
|
89
|
+
} ? undefined extends Value ? unknown : {
|
|
90
|
+
readonly default: Exclude<TSchema["default"], undefined>;
|
|
91
|
+
} : unknown);
|
|
87
92
|
export interface SchemaBase<TKind extends SchemaKind, TStatic> {
|
|
88
93
|
readonly kind: TKind;
|
|
89
94
|
readonly cli?: CliSchemaOptions<TStatic>;
|
|
@@ -149,16 +154,29 @@ export interface ObjectSchema<TShape extends ObjectShape> extends SchemaBase<"ob
|
|
|
149
154
|
export interface OptionalSchema<TInner extends AnySchema> extends SchemaBase<"optional", Static<TInner> | undefined> {
|
|
150
155
|
readonly inner: TInner;
|
|
151
156
|
}
|
|
152
|
-
export type AnySchema = StringSchema | NumberSchema | BooleanSchema | EnumSchema<NonEmptyReadonlyArray<EnumValue>> |
|
|
157
|
+
export type AnySchema = StringSchema | NumberSchema | BooleanSchema | EnumSchema<NonEmptyReadonlyArray<EnumValue>> | (SchemaBase<"array", any> & ArrayMetadata & {
|
|
158
|
+
readonly item: AnySchema;
|
|
159
|
+
}) | (SchemaBase<"object", any> & ObjectMetadata & {
|
|
160
|
+
readonly shape: ObjectShape;
|
|
161
|
+
}) | (SchemaBase<"optional", any> & {
|
|
162
|
+
readonly inner: AnySchema;
|
|
163
|
+
}) | (SchemaBase<"oneOf", any> & {
|
|
164
|
+
readonly discriminator: string;
|
|
165
|
+
readonly branches: Record<string, ObjectSchema<any>>;
|
|
166
|
+
}) | (SchemaBase<"union", any> & {
|
|
167
|
+
readonly branches: readonly ObjectSchema<any>[];
|
|
168
|
+
}) | (SchemaBase<"record", any> & {
|
|
169
|
+
readonly value: AnySchema;
|
|
170
|
+
}) | JsonValueSchema;
|
|
153
171
|
export type Static<TSchema extends AnySchema> = TSchema extends {
|
|
154
172
|
readonly nullable: true;
|
|
155
173
|
} ? TSchema extends SchemaBase<any, infer TStatic> ? TStatic | null : never : TSchema extends SchemaBase<any, infer TStatic> ? TStatic : never;
|
|
156
174
|
export declare const S: {
|
|
157
|
-
readonly String: <const TOptions extends SchemaOptions<string> & StringMetadata = EmptyOptions>(options?: TOptions) => WithNullable<StringSchema, TOptions
|
|
175
|
+
readonly String: <const TOptions extends SchemaOptions<string> & StringMetadata = EmptyOptions>(options?: TOptions) => Standardized<WithNullable<StringSchema, TOptions>>;
|
|
158
176
|
readonly Number: <const TOptions extends SchemaOptions<number> & NumberMetadata & {
|
|
159
177
|
jsonType?: NumberJsonType;
|
|
160
|
-
} = EmptyOptions>(options?: TOptions) => WithNullable<NumberSchema, TOptions
|
|
161
|
-
readonly Boolean: <const TOptions extends SchemaOptions<boolean> = EmptyOptions>(options?: TOptions) => WithNullable<BooleanSchema, TOptions
|
|
178
|
+
} = EmptyOptions>(options?: TOptions) => Standardized<WithNullable<NumberSchema, TOptions>>;
|
|
179
|
+
readonly Boolean: <const TOptions extends SchemaOptions<boolean> = EmptyOptions>(options?: TOptions) => Standardized<WithNullable<BooleanSchema, TOptions>>;
|
|
162
180
|
readonly Enum: <const TValues extends NonEmptyReadonlyArray<EnumValue>, const TOptions extends SchemaOptions<TValues[number]> & {
|
|
163
181
|
jsonType?: "integer";
|
|
164
182
|
labels?: Partial<Record<string, string>>;
|
|
@@ -169,16 +187,20 @@ export declare const S: {
|
|
|
169
187
|
label: string;
|
|
170
188
|
value: string;
|
|
171
189
|
}>>);
|
|
172
|
-
} = EmptyOptions>(values: TValues, options?: TOptions) => WithNullable<EnumSchema<TValues>, TOptions
|
|
173
|
-
readonly Array: <TItem extends AnySchema, const TOptions extends SchemaOptions<Array<Static<TItem>>> & ArrayMetadata = EmptyOptions>(item: TItem, options?: TOptions) => WithNullable<ArraySchema<TItem>, TOptions
|
|
174
|
-
readonly Object: <const TShape extends ObjectShape, const TOptions extends SchemaOptions<InferObject<TShape>> & ObjectMetadata = EmptyOptions>(shape: TShape, options?: TOptions) => WithNullable<ObjectSchema<TShape>, TOptions
|
|
175
|
-
readonly Optional: <TInner extends AnySchema>(inner: TInner) => OptionalSchema<TInner
|
|
190
|
+
} = EmptyOptions>(values: TValues, options?: TOptions) => Standardized<WithNullable<EnumSchema<TValues>, TOptions>>;
|
|
191
|
+
readonly Array: <TItem extends AnySchema, const TOptions extends SchemaOptions<Array<Static<TItem>>> & ArrayMetadata = EmptyOptions>(item: TItem, options?: TOptions) => Standardized<WithNullable<ArraySchema<TItem>, TOptions>>;
|
|
192
|
+
readonly Object: <const TShape extends ObjectShape, const TOptions extends SchemaOptions<InferObject<TShape>> & ObjectMetadata = EmptyOptions>(shape: TShape, options?: TOptions) => Standardized<WithNullable<ObjectSchema<TShape>, TOptions>>;
|
|
193
|
+
readonly Optional: <TInner extends AnySchema>(inner: TInner) => Standardized<OptionalSchema<TInner>>;
|
|
176
194
|
readonly OneOf: typeof OneOf;
|
|
177
195
|
readonly Union: typeof Union;
|
|
178
196
|
readonly Record: typeof RecordBuilder;
|
|
179
197
|
readonly Json: typeof Json;
|
|
180
198
|
};
|
|
181
|
-
export
|
|
199
|
+
export interface JsonSchemaOptions {
|
|
200
|
+
io?: "input" | "output";
|
|
201
|
+
target?: "draft-07" | "draft-2020-12";
|
|
202
|
+
}
|
|
203
|
+
export declare function toJsonSchema(schema: AnySchema, options?: JsonSchemaOptions): JsonSchema;
|
|
182
204
|
export declare function toJsonSchemaDocument(schema: AnySchema, options?: JsonSchemaDocumentOptions): JsonSchemaDocument;
|
|
183
205
|
export { Json, OneOf, RecordBuilder as Record, Union, isPlainRecord, validate };
|
|
184
206
|
export { isJsonValue } from "./json.js";
|
|
@@ -194,3 +216,5 @@ export type { NativeSchema } from "./native-json-schema.js";
|
|
|
194
216
|
export type { CompileJsonSchemaOptions, CompiledJsonSchema } from "./json-schema/index.js";
|
|
195
217
|
export type { JsonSchemaDocument, JsonSchemaDocumentOptions } from "./json-schema-document.js";
|
|
196
218
|
export type { JsonValue, JsonValueSchema, OneOfSchema, RecordSchema, UnionSchema, ValidationIssue, ValidationOptions, ValidationResult };
|
|
219
|
+
export { withStandardSchema } from "./standard.js";
|
|
220
|
+
export type { Input, Output, StandardSchema, Standardized, StandardIssue, StandardResult, StandardJsonSchemaOptions } from "./standard.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withStandardSchema } from "./standard.js";
|
|
1
2
|
import { Json } from "./json.js";
|
|
2
3
|
import { nativeJsonSchema } from "./native-json-schema.js";
|
|
3
4
|
import { cloneDefaultValue } from "./clone-default.js";
|
|
@@ -133,16 +134,16 @@ function unwrapOptional(schema) {
|
|
|
133
134
|
}
|
|
134
135
|
return schema;
|
|
135
136
|
}
|
|
136
|
-
function toObjectBranchJsonSchema(schema) {
|
|
137
|
+
function toObjectBranchJsonSchema(schema, options) {
|
|
137
138
|
const objectSchema = { ...schema, nullable: false };
|
|
138
|
-
const branchJsonSchema = toJsonSchema(objectSchema);
|
|
139
|
+
const branchJsonSchema = toJsonSchema(objectSchema, options);
|
|
139
140
|
if (branchJsonSchema.default === null) {
|
|
140
141
|
delete branchJsonSchema.default;
|
|
141
142
|
}
|
|
142
143
|
return branchJsonSchema;
|
|
143
144
|
}
|
|
144
|
-
function withInjectedDiscriminator(schema, discriminator, branchName) {
|
|
145
|
-
const branchJsonSchema = toObjectBranchJsonSchema(schema);
|
|
145
|
+
function withInjectedDiscriminator(schema, discriminator, branchName, options) {
|
|
146
|
+
const branchJsonSchema = toObjectBranchJsonSchema(schema, options);
|
|
146
147
|
if (branchJsonSchema.default !== undefined) {
|
|
147
148
|
branchJsonSchema.default = {
|
|
148
149
|
...branchJsonSchema.default,
|
|
@@ -175,7 +176,7 @@ export const S = {
|
|
|
175
176
|
...options
|
|
176
177
|
};
|
|
177
178
|
assertValidDefault(schema);
|
|
178
|
-
return schema;
|
|
179
|
+
return withStandardSchema(schema);
|
|
179
180
|
},
|
|
180
181
|
Number(options = {}) {
|
|
181
182
|
assertFiniteNumber(options.minimum, "minimum");
|
|
@@ -192,7 +193,7 @@ export const S = {
|
|
|
192
193
|
...options
|
|
193
194
|
};
|
|
194
195
|
assertValidDefault(schema);
|
|
195
|
-
return schema;
|
|
196
|
+
return withStandardSchema(schema);
|
|
196
197
|
},
|
|
197
198
|
Boolean(options = {}) {
|
|
198
199
|
const schema = {
|
|
@@ -200,7 +201,7 @@ export const S = {
|
|
|
200
201
|
...options
|
|
201
202
|
};
|
|
202
203
|
assertValidDefault(schema);
|
|
203
|
-
return schema;
|
|
204
|
+
return withStandardSchema(schema);
|
|
204
205
|
},
|
|
205
206
|
Enum(values, options = {}) {
|
|
206
207
|
assertValidEnumValues(values);
|
|
@@ -214,7 +215,7 @@ export const S = {
|
|
|
214
215
|
...options
|
|
215
216
|
};
|
|
216
217
|
assertValidDefault(schema);
|
|
217
|
-
return schema;
|
|
218
|
+
return withStandardSchema(schema);
|
|
218
219
|
},
|
|
219
220
|
Array(item, options = {}) {
|
|
220
221
|
assertNonNegativeInteger(options.minItems, "minItems");
|
|
@@ -226,7 +227,7 @@ export const S = {
|
|
|
226
227
|
...options
|
|
227
228
|
};
|
|
228
229
|
assertValidDefault(schema);
|
|
229
|
-
return schema;
|
|
230
|
+
return withStandardSchema(schema);
|
|
230
231
|
},
|
|
231
232
|
Object(shape, options = {}) {
|
|
232
233
|
const schema = {
|
|
@@ -235,24 +236,32 @@ export const S = {
|
|
|
235
236
|
...options
|
|
236
237
|
};
|
|
237
238
|
assertValidDefault(schema);
|
|
238
|
-
return schema;
|
|
239
|
+
return withStandardSchema(schema);
|
|
239
240
|
},
|
|
240
241
|
Optional(inner) {
|
|
241
|
-
return {
|
|
242
|
+
return withStandardSchema({
|
|
242
243
|
kind: "optional",
|
|
243
244
|
inner
|
|
244
|
-
};
|
|
245
|
+
});
|
|
245
246
|
},
|
|
246
247
|
OneOf,
|
|
247
248
|
Union,
|
|
248
249
|
Record: RecordBuilder,
|
|
249
250
|
Json
|
|
250
251
|
};
|
|
251
|
-
export function toJsonSchema(schema) {
|
|
252
|
+
export function toJsonSchema(schema, options = {}) {
|
|
252
253
|
const unwrappedSchema = unwrapOptional(schema);
|
|
253
254
|
const native = unwrappedSchema[nativeJsonSchema];
|
|
254
|
-
if (native !== undefined)
|
|
255
|
+
if (native !== undefined) {
|
|
256
|
+
if (options.target !== undefined) {
|
|
257
|
+
const declared = native.document.$schema;
|
|
258
|
+
const dialect = declared === "http://json-schema.org/draft-07/schema#" || declared === "http://json-schema.org/draft-07/schema"
|
|
259
|
+
? "draft-07" : "draft-2020-12";
|
|
260
|
+
if (options.target !== dialect)
|
|
261
|
+
throw new Error(`Native JSON Schema dialect does not match target: ${options.target}`);
|
|
262
|
+
}
|
|
255
263
|
return structuredClone(native.document);
|
|
264
|
+
}
|
|
256
265
|
switch (unwrappedSchema.kind) {
|
|
257
266
|
case "string":
|
|
258
267
|
return withStringMetadata(unwrappedSchema, { type: "string" });
|
|
@@ -275,7 +284,7 @@ export function toJsonSchema(schema) {
|
|
|
275
284
|
case "array":
|
|
276
285
|
return withArrayMetadata(unwrappedSchema, {
|
|
277
286
|
type: "array",
|
|
278
|
-
items: toJsonSchema(unwrappedSchema.item)
|
|
287
|
+
items: toJsonSchema(unwrappedSchema.item, options)
|
|
279
288
|
});
|
|
280
289
|
case "object": {
|
|
281
290
|
const properties = {};
|
|
@@ -285,9 +294,10 @@ export function toJsonSchema(schema) {
|
|
|
285
294
|
enumerable: true,
|
|
286
295
|
configurable: true,
|
|
287
296
|
writable: true,
|
|
288
|
-
value: toJsonSchema(propertySchema)
|
|
297
|
+
value: toJsonSchema(propertySchema, options)
|
|
289
298
|
});
|
|
290
|
-
if (!isOptionalSchema(propertySchema)
|
|
299
|
+
if (!isOptionalSchema(propertySchema) ||
|
|
300
|
+
(options.io === "output" && unwrapOptional(propertySchema).default !== undefined)) {
|
|
291
301
|
required.push(key);
|
|
292
302
|
}
|
|
293
303
|
}
|
|
@@ -299,16 +309,25 @@ export function toJsonSchema(schema) {
|
|
|
299
309
|
}
|
|
300
310
|
case "oneOf":
|
|
301
311
|
return withMetadata(unwrappedSchema, {
|
|
302
|
-
oneOf: Object.entries(unwrappedSchema.branches).map(([branchName, branchSchema]) => withInjectedDiscriminator(branchSchema, unwrappedSchema.discriminator, branchName))
|
|
312
|
+
oneOf: Object.entries(unwrappedSchema.branches).map(([branchName, branchSchema]) => withInjectedDiscriminator(branchSchema, unwrappedSchema.discriminator, branchName, options))
|
|
303
313
|
});
|
|
304
314
|
case "union":
|
|
315
|
+
// Parsing defaults can make previously distinct branches produce the same
|
|
316
|
+
// object. Outputs need only satisfy one branch, not exactly one branch.
|
|
317
|
+
if (options.io === "output")
|
|
318
|
+
return withMetadata(unwrappedSchema, {
|
|
319
|
+
anyOf: [
|
|
320
|
+
...unwrappedSchema.branches.map((branchSchema) => toObjectBranchJsonSchema(branchSchema, options)),
|
|
321
|
+
...(unwrappedSchema.nullable === true ? [{ type: "null" }] : [])
|
|
322
|
+
]
|
|
323
|
+
});
|
|
305
324
|
return withMetadata(unwrappedSchema, {
|
|
306
|
-
oneOf: unwrappedSchema.branches.map((branchSchema) => toObjectBranchJsonSchema(branchSchema))
|
|
325
|
+
oneOf: unwrappedSchema.branches.map((branchSchema) => toObjectBranchJsonSchema(branchSchema, options))
|
|
307
326
|
});
|
|
308
327
|
case "record":
|
|
309
328
|
return withMetadata(unwrappedSchema, {
|
|
310
329
|
type: "object",
|
|
311
|
-
additionalProperties: toJsonSchema(unwrappedSchema.value)
|
|
330
|
+
additionalProperties: toJsonSchema(unwrappedSchema.value, options)
|
|
312
331
|
});
|
|
313
332
|
case "json": {
|
|
314
333
|
const constraints = {
|
|
@@ -332,3 +351,4 @@ export { compileJsonSchema, formatIssues } from "./json-schema/index.js";
|
|
|
332
351
|
export { projectJsonSchemaProperties } from "./json-schema/properties.js";
|
|
333
352
|
export { normalizeLegacyNullability } from "./json-schema/normalize-nullability.js";
|
|
334
353
|
export { withJsonSchema, nativeJsonSchema } from "./native-json-schema.js";
|
|
354
|
+
export { withStandardSchema } from "./standard.js";
|
package/dist/json.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Standardized } from "./standard.js";
|
|
1
2
|
import type { SchemaBase } from "./index.js";
|
|
2
3
|
type JsonPrimitive = string | number | boolean | null;
|
|
3
4
|
export type JsonValue = JsonPrimitive | {
|
|
@@ -8,7 +9,12 @@ export interface JsonValueSchema extends SchemaBase<"json", JsonValue> {
|
|
|
8
9
|
readonly const?: JsonValue;
|
|
9
10
|
readonly enum?: readonly JsonValue[];
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
+
type ConfiguredJson<TOptions> = JsonValueSchema & (TOptions extends {
|
|
13
|
+
readonly default: infer Value;
|
|
14
|
+
} ? undefined extends Value ? unknown : {
|
|
15
|
+
readonly default: JsonValue;
|
|
16
|
+
} : unknown);
|
|
17
|
+
export declare function Json<const TOptions extends Omit<JsonValueSchema, "kind"> = Record<never, never>>(options?: TOptions): Standardized<ConfiguredJson<TOptions>>;
|
|
12
18
|
export interface JsonValueValidationOptions {
|
|
13
19
|
readonly maxNodes?: number;
|
|
14
20
|
readonly maxDepth?: number;
|
package/dist/json.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { withStandardSchema } from "./standard.js";
|
|
1
2
|
export function Json(options = {}) {
|
|
2
|
-
return {
|
|
3
|
+
return withStandardSchema({
|
|
3
4
|
kind: "json",
|
|
4
5
|
...options,
|
|
5
|
-
};
|
|
6
|
+
});
|
|
6
7
|
}
|
|
7
8
|
export function isJsonValue(value, options = {}) {
|
|
8
9
|
const maxNodes = options.maxNodes ?? 10_000;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Standardized } from "./standard.js";
|
|
1
2
|
import type { AnySchema, JsonSchema } from "./index.js";
|
|
2
3
|
import type { CompiledJsonSchema } from "./json-schema/types.js";
|
|
3
4
|
export declare const nativeJsonSchema: unique symbol;
|
|
@@ -7,4 +8,6 @@ export type NativeSchema = AnySchema & {
|
|
|
7
8
|
validator: CompiledJsonSchema;
|
|
8
9
|
};
|
|
9
10
|
};
|
|
10
|
-
export declare function withJsonSchema<T extends AnySchema>(projection: T, document: object): T
|
|
11
|
+
export declare function withJsonSchema<T extends AnySchema>(projection: T, document: object): Standardized<T & {
|
|
12
|
+
[nativeJsonSchema]: NonNullable<NativeSchema[typeof nativeJsonSchema]>;
|
|
13
|
+
}>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withStandardSchema } from "./standard.js";
|
|
1
2
|
import { isJsonValue } from "./json.js";
|
|
2
3
|
import { compileJsonSchema } from "./json-schema/index.js";
|
|
3
4
|
export const nativeJsonSchema = Symbol("toolcraft.nativeJsonSchema");
|
|
@@ -7,5 +8,5 @@ export function withJsonSchema(projection, document) {
|
|
|
7
8
|
}
|
|
8
9
|
const snapshot = structuredClone(document);
|
|
9
10
|
const validator = compileJsonSchema(snapshot);
|
|
10
|
-
return { ...projection, [nativeJsonSchema]: { document: snapshot, validator } };
|
|
11
|
+
return withStandardSchema({ ...projection, [nativeJsonSchema]: { document: snapshot, validator } });
|
|
11
12
|
}
|
package/dist/oneof.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Standardized } from "./standard.js";
|
|
1
2
|
import type { ObjectSchema, SchemaBase, Static } from "./index.js";
|
|
2
3
|
type OneOfStatic<TBranches extends Record<string, ObjectSchema<any>>, TDiscriminator extends string> = {
|
|
3
4
|
[TBranchName in keyof TBranches & string]: Omit<Static<TBranches[TBranchName]>, TDiscriminator> & {
|
|
@@ -11,5 +12,5 @@ export interface OneOfSchema<TBranches extends Record<string, ObjectSchema<any>>
|
|
|
11
12
|
export declare function OneOf<TDiscriminator extends string, TBranches extends Record<string, ObjectSchema<any>>>(config: {
|
|
12
13
|
discriminator: TDiscriminator;
|
|
13
14
|
branches: TBranches;
|
|
14
|
-
}): OneOfSchema<TBranches, TDiscriminator
|
|
15
|
+
}): Standardized<OneOfSchema<TBranches, TDiscriminator>>;
|
|
15
16
|
export {};
|
package/dist/oneof.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withStandardSchema } from "./standard.js";
|
|
1
2
|
function assertValidBranches(branches, discriminator) {
|
|
2
3
|
if (Object.keys(branches).length === 0) {
|
|
3
4
|
throw new Error("OneOf schema requires at least one branch");
|
|
@@ -10,9 +11,9 @@ function assertValidBranches(branches, discriminator) {
|
|
|
10
11
|
}
|
|
11
12
|
export function OneOf(config) {
|
|
12
13
|
assertValidBranches(config.branches, config.discriminator);
|
|
13
|
-
return {
|
|
14
|
+
return withStandardSchema({
|
|
14
15
|
kind: "oneOf",
|
|
15
16
|
discriminator: config.discriminator,
|
|
16
17
|
branches: config.branches,
|
|
17
|
-
};
|
|
18
|
+
});
|
|
18
19
|
}
|
package/dist/record.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type Standardized } from "./standard.js";
|
|
1
2
|
import type { AnySchema, SchemaBase, Static } from "./index.js";
|
|
2
3
|
export interface RecordSchema<TValue extends AnySchema> extends SchemaBase<"record", Record<string, Static<TValue>>> {
|
|
3
4
|
readonly value: TValue;
|
|
4
5
|
}
|
|
5
|
-
export declare function Record<TValue extends AnySchema>(value: TValue): RecordSchema<TValue
|
|
6
|
+
export declare function Record<TValue extends AnySchema>(value: TValue): Standardized<RecordSchema<TValue>>;
|
package/dist/record.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { S, withJsonSchema, withStandardSchema, toJsonSchema } from "./index.js";
|
|
2
|
+
const ignoredSchema = S.Object({
|
|
3
|
+
query: S.String(),
|
|
4
|
+
limit: S.Optional(S.Number({ default: 10 })),
|
|
5
|
+
note: S.Optional(S.String()),
|
|
6
|
+
nested: S.Array(S.Object({ enabled: S.Optional(S.Boolean({ default: true })) })),
|
|
7
|
+
nullable: S.String({ nullable: true })
|
|
8
|
+
});
|
|
9
|
+
// @ts-expect-error A parsed default is always present.
|
|
10
|
+
const ignoredMissingDefault = { query: "x", nested: [], nullable: null };
|
|
11
|
+
const ignoredNullableDefault = S.Optional(S.String({ nullable: true, default: "x" }));
|
|
12
|
+
const ignoredUnionSchema = S.Union([S.Object({ a: S.Optional(S.Number({ default: 1 })) }), S.Object({ b: S.String() })]);
|
|
13
|
+
const ignoredTaggedSchema = S.OneOf({ discriminator: "kind", branches: { text: S.Object({ text: S.Optional(S.String({ default: "x" })) }) } });
|
|
14
|
+
const ignoredNative = withJsonSchema(S.Object({ count: S.Optional(S.Number({ default: 1 })) }), { type: "object" });
|
|
15
|
+
toJsonSchema(withStandardSchema(descriptor));
|
|
16
|
+
const ignoredJsonDefault = S.Object({ data: S.Optional(S.Json({ default: null })) });
|
|
17
|
+
const ignoredUndefinedDefaults = S.Object({
|
|
18
|
+
count: S.Optional(S.Number({ default: undefined })),
|
|
19
|
+
data: S.Optional(S.Json({ default: undefined }))
|
|
20
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type AnySchema, type Static } from "./index.js";
|
|
2
|
+
import { nativeJsonSchema } from "./native-json-schema.js";
|
|
3
|
+
/** Structural Standard Schema / Standard JSON Schema v1 contracts. No runtime dependency. */
|
|
4
|
+
export interface StandardIssue {
|
|
5
|
+
readonly message: string;
|
|
6
|
+
readonly path?: ReadonlyArray<PropertyKey | {
|
|
7
|
+
readonly key: PropertyKey;
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
10
|
+
export type StandardResult<T> = {
|
|
11
|
+
readonly value: T;
|
|
12
|
+
readonly issues?: undefined;
|
|
13
|
+
} | {
|
|
14
|
+
readonly issues: readonly StandardIssue[];
|
|
15
|
+
readonly value?: undefined;
|
|
16
|
+
};
|
|
17
|
+
export interface StandardSchema<Input = unknown, Output = Input> {
|
|
18
|
+
readonly "~standard": {
|
|
19
|
+
readonly version: 1;
|
|
20
|
+
readonly vendor: string;
|
|
21
|
+
readonly types?: {
|
|
22
|
+
readonly input: Input;
|
|
23
|
+
readonly output: Output;
|
|
24
|
+
};
|
|
25
|
+
readonly validate: (value: unknown, options?: {
|
|
26
|
+
readonly libraryOptions?: Record<string, unknown>;
|
|
27
|
+
}) => StandardResult<Output> | Promise<StandardResult<Output>>;
|
|
28
|
+
readonly jsonSchema: {
|
|
29
|
+
readonly input: (options: StandardJsonSchemaOptions) => Record<string, unknown>;
|
|
30
|
+
readonly output: (options: StandardJsonSchemaOptions) => Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface StandardJsonSchemaOptions {
|
|
35
|
+
readonly target: string;
|
|
36
|
+
readonly libraryOptions?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
export type Input<T extends AnySchema> = Static<T>;
|
|
39
|
+
type HasDefault<T> = AnySchema extends T ? false : T extends {
|
|
40
|
+
readonly inner: infer Inner;
|
|
41
|
+
} ? HasDefault<Inner> : T extends {
|
|
42
|
+
readonly default: infer Value;
|
|
43
|
+
} ? undefined extends Value ? false : true : false;
|
|
44
|
+
type OptionalOutputKeys<Shape> = {
|
|
45
|
+
[K in keyof Shape]: Shape[K] extends {
|
|
46
|
+
readonly kind: "optional";
|
|
47
|
+
} ? HasDefault<Shape[K]> extends true ? never : K : never;
|
|
48
|
+
}[keyof Shape];
|
|
49
|
+
type ObjectOutput<Shape extends Record<string, AnySchema>> = {
|
|
50
|
+
[K in Exclude<keyof Shape, OptionalOutputKeys<Shape>>]: Output<Shape[K]>;
|
|
51
|
+
} & {
|
|
52
|
+
[K in OptionalOutputKeys<Shape>]?: Output<Shape[K]>;
|
|
53
|
+
};
|
|
54
|
+
type OutputValue<T extends AnySchema> = T extends {
|
|
55
|
+
readonly [nativeJsonSchema]: unknown;
|
|
56
|
+
} ? Static<T> : T extends {
|
|
57
|
+
readonly kind: "object";
|
|
58
|
+
readonly shape: infer Shape extends Record<string, AnySchema>;
|
|
59
|
+
} ? ObjectOutput<Shape> : T extends {
|
|
60
|
+
readonly kind: "array";
|
|
61
|
+
readonly item: infer Item extends AnySchema;
|
|
62
|
+
} ? Output<Item>[] : T extends {
|
|
63
|
+
readonly kind: "record";
|
|
64
|
+
readonly value: infer Value extends AnySchema;
|
|
65
|
+
} ? Record<string, Output<Value>> : T extends {
|
|
66
|
+
readonly kind: "optional";
|
|
67
|
+
readonly inner: infer Inner extends AnySchema;
|
|
68
|
+
} ? Output<Inner> | (HasDefault<Inner> extends true ? never : undefined) : T extends {
|
|
69
|
+
readonly kind: "union";
|
|
70
|
+
readonly branches: infer Branches extends readonly AnySchema[];
|
|
71
|
+
} ? Output<Branches[number]> : T extends {
|
|
72
|
+
readonly kind: "oneOf";
|
|
73
|
+
readonly branches: infer Branches extends Record<string, AnySchema>;
|
|
74
|
+
readonly discriminator: infer Key extends string;
|
|
75
|
+
} ? {
|
|
76
|
+
[Name in keyof Branches & string]: Output<Branches[Name]> & Record<Key, Name>;
|
|
77
|
+
}[keyof Branches & string] : Static<T>;
|
|
78
|
+
export type Output<T extends AnySchema> = 0 extends (1 & T) ? any : AnySchema extends T ? Static<T> : T extends {
|
|
79
|
+
readonly "~standard": {
|
|
80
|
+
readonly types?: {
|
|
81
|
+
readonly output: infer Value;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
} ? Value : OutputValue<T> | (T extends {
|
|
85
|
+
readonly nullable: true;
|
|
86
|
+
} ? null : never);
|
|
87
|
+
export type Standardized<T extends AnySchema> = T extends unknown ? (T extends StandardSchema ? Omit<T, "~standard"> : T) & StandardSchema<Input<T>, OutputValue<T> | (T extends {
|
|
88
|
+
readonly nullable: true;
|
|
89
|
+
} ? null : never)> : never;
|
|
90
|
+
/** Attach interoperable methods without changing the enumerable descriptor or its JSON form. */
|
|
91
|
+
export declare function withStandardSchema<T extends AnySchema>(schema: T): Standardized<T>;
|
|
92
|
+
export {};
|
package/dist/standard.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { toJsonSchema } from "./index.js";
|
|
2
|
+
import { validate } from "./validate.js";
|
|
3
|
+
import { nativeJsonSchema } from "./native-json-schema.js";
|
|
4
|
+
function standardDocument(schema, io, options) {
|
|
5
|
+
const uri = options.target === "draft-2020-12" ? "https://json-schema.org/draft/2020-12/schema"
|
|
6
|
+
: options.target === "draft-07" ? "http://json-schema.org/draft-07/schema#" : undefined;
|
|
7
|
+
if (uri === undefined)
|
|
8
|
+
throw new Error(`Unsupported JSON Schema target: ${options.target}`);
|
|
9
|
+
const document = toJsonSchema(schema, { io, target: options.target });
|
|
10
|
+
if (document.$schema !== undefined && document.$schema !== uri && document.$schema !== (uri.endsWith("#") ? uri.slice(0, -1) : uri)) {
|
|
11
|
+
throw new Error(`Native JSON Schema dialect does not match target: ${options.target}`);
|
|
12
|
+
}
|
|
13
|
+
if ((schema.kind === "oneOf" || schema.kind === "union") && schema.nullable !== true)
|
|
14
|
+
document.type = "object";
|
|
15
|
+
return { $schema: uri, ...document };
|
|
16
|
+
}
|
|
17
|
+
/** Attach interoperable methods without changing the enumerable descriptor or its JSON form. */
|
|
18
|
+
export function withStandardSchema(schema) {
|
|
19
|
+
Object.defineProperty(schema, "~standard", {
|
|
20
|
+
configurable: true,
|
|
21
|
+
get() {
|
|
22
|
+
const descriptor = this;
|
|
23
|
+
return {
|
|
24
|
+
version: 1,
|
|
25
|
+
vendor: "toolcraft-schema",
|
|
26
|
+
validate(value) {
|
|
27
|
+
const result = validate(descriptor, value);
|
|
28
|
+
return result.ok ? { value: result.value } : { issues: result.issues };
|
|
29
|
+
},
|
|
30
|
+
jsonSchema: {
|
|
31
|
+
input: (options) => standardDocument(descriptor, "input", options),
|
|
32
|
+
output: (options) => standardDocument(descriptor, "output", options)
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return schema;
|
|
38
|
+
}
|
package/dist/union.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { type Standardized } from "./standard.js";
|
|
1
2
|
import type { ObjectSchema, SchemaBase, Static } from "./index.js";
|
|
2
3
|
type UnionStatic<TBranches extends readonly ObjectSchema<any>[]> = Static<TBranches[number]>;
|
|
3
4
|
export interface UnionSchema<TBranches extends readonly ObjectSchema<any>[]> extends SchemaBase<"union", UnionStatic<TBranches>> {
|
|
4
5
|
readonly branches: TBranches;
|
|
5
6
|
}
|
|
6
7
|
export declare function getRequiredKeyFingerprint(schema: ObjectSchema<any>): string;
|
|
7
|
-
export declare function Union<const TBranches extends readonly ObjectSchema<any>[]>(branches: TBranches): UnionSchema<TBranches
|
|
8
|
+
export declare function Union<const TBranches extends readonly ObjectSchema<any>[]>(branches: TBranches): Standardized<UnionSchema<TBranches>>;
|
|
8
9
|
export {};
|
package/dist/union.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withStandardSchema } from "./standard.js";
|
|
1
2
|
function isOptionalSchema(schema) {
|
|
2
3
|
return schema.kind === "optional";
|
|
3
4
|
}
|
|
@@ -38,8 +39,8 @@ function assertValidBranches(branches) {
|
|
|
38
39
|
}
|
|
39
40
|
export function Union(branches) {
|
|
40
41
|
assertValidBranches(branches);
|
|
41
|
-
return {
|
|
42
|
+
return withStandardSchema({
|
|
42
43
|
kind: "union",
|
|
43
44
|
branches,
|
|
44
|
-
};
|
|
45
|
+
});
|
|
45
46
|
}
|