toolcraft-schema 0.0.39 → 0.0.40
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 +13 -7
- package/dist/index.compile-check.js +7 -2
- package/dist/index.d.ts +6 -1
- package/dist/index.js +26 -19
- package/dist/json-schema-document.d.ts +14 -0
- package/dist/json-schema-document.js +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,18 +10,19 @@ Zero-dependency schema builder for typed command inputs and JSON Schema generati
|
|
|
10
10
|
- Typed schema descriptors
|
|
11
11
|
- `Static<typeof schema>` type inference
|
|
12
12
|
- JSON Schema serialization via `toJsonSchema()`
|
|
13
|
+
- JSON Schema document serialization via `toJsonSchemaDocument()`
|
|
13
14
|
|
|
14
15
|
## Usage
|
|
15
16
|
|
|
16
17
|
```ts
|
|
17
|
-
import { S, toJsonSchema } from "toolcraft-schema";
|
|
18
|
+
import { S, toJsonSchema, toJsonSchemaDocument } from "toolcraft-schema";
|
|
18
19
|
import type { Static } from "toolcraft-schema";
|
|
19
20
|
|
|
20
21
|
const schema = S.Object({
|
|
21
22
|
name: S.String({ description: "User name" }),
|
|
22
23
|
retries: S.Optional(S.Number({ default: 3 })),
|
|
23
24
|
mode: S.Enum(["fast", "safe"] as const, { default: "safe" }),
|
|
24
|
-
tags: S.Array(S.String(), { default: [] })
|
|
25
|
+
tags: S.Array(S.String(), { default: [] })
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
type Input = Static<typeof schema>;
|
|
@@ -33,17 +34,21 @@ type Input = Static<typeof schema>;
|
|
|
33
34
|
// }
|
|
34
35
|
|
|
35
36
|
const jsonSchema = toJsonSchema(schema);
|
|
37
|
+
const document = toJsonSchemaDocument(schema, {
|
|
38
|
+
id: "https://example.test/schema.json",
|
|
39
|
+
title: "Example schema"
|
|
40
|
+
});
|
|
36
41
|
```
|
|
37
42
|
|
|
38
43
|
## API
|
|
39
44
|
|
|
40
45
|
### Builders
|
|
41
46
|
|
|
42
|
-
- `S.String({ description?, default? })`
|
|
43
|
-
- `S.Number({ description?, default? })`
|
|
44
|
-
- `S.Boolean({ description?, default? })`
|
|
45
|
-
- `S.Enum(values, { description?, default? })`
|
|
46
|
-
- `S.Array(itemSchema, { description?, default? })`
|
|
47
|
+
- `S.String({ description?, default?, short?, cliAliases? })`
|
|
48
|
+
- `S.Number({ description?, default?, short?, cliAliases? })`
|
|
49
|
+
- `S.Boolean({ description?, default?, short?, cliAliases? })`
|
|
50
|
+
- `S.Enum(values, { description?, default?, short?, cliAliases? })`
|
|
51
|
+
- `S.Array(itemSchema, { description?, default?, short?, cliAliases? })`
|
|
47
52
|
- `S.Object({ [key]: schema })`
|
|
48
53
|
- `S.Optional(schema)`
|
|
49
54
|
|
|
@@ -55,6 +60,7 @@ const jsonSchema = toJsonSchema(schema);
|
|
|
55
60
|
### JSON Schema generation
|
|
56
61
|
|
|
57
62
|
- `toJsonSchema(schema)` converts any schema descriptor to standard JSON Schema.
|
|
63
|
+
- `toJsonSchemaDocument(schema, options)` wraps `toJsonSchema(schema)` in a full JSON Schema document with `$schema`, optional `$id`, `title`, and `description`.
|
|
58
64
|
- Object properties not wrapped in `S.Optional(...)` are emitted in `required`.
|
|
59
65
|
- Defaults provided to schema builders are emitted as JSON Schema `default`.
|
|
60
66
|
- Nested `S.Object(...)` schemas produce nested JSON Schema objects.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S } from "./index.js";
|
|
1
|
+
import { S, toJsonSchemaDocument } from "./index.js";
|
|
2
2
|
const ignoredStringSchema = S.String({ description: "Name", default: "guest" });
|
|
3
3
|
const ignoredNumberSchema = S.Number({ description: "Count", default: 1 });
|
|
4
4
|
const ignoredBooleanSchema = S.Boolean({ description: "Enabled", default: false });
|
|
@@ -7,6 +7,11 @@ const ignoredIntegerEnumSchema = S.Enum([1, 2], { jsonType: "integer" });
|
|
|
7
7
|
const ignoredArraySchema = S.Array(S.String(), { default: ["a"] });
|
|
8
8
|
const ignoredObjectSchema = S.Object({
|
|
9
9
|
name: S.String(),
|
|
10
|
-
retries: S.Optional(S.Number())
|
|
10
|
+
retries: S.Optional(S.Number())
|
|
11
11
|
});
|
|
12
12
|
const ignoredOptionalSchema = S.Optional(S.Boolean());
|
|
13
|
+
const ignoredJsonSchemaDocument = toJsonSchemaDocument(ignoredObjectSchema, {
|
|
14
|
+
id: "https://example.test/schema.json",
|
|
15
|
+
title: "Example schema",
|
|
16
|
+
description: "Example schema document"
|
|
17
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Record as RecordBuilder } from "./record.js";
|
|
|
4
4
|
import { Union } from "./union.js";
|
|
5
5
|
import { validate } from "./validate.js";
|
|
6
6
|
import type { JsonValue, JsonValueSchema } from "./json.js";
|
|
7
|
+
import type { JsonSchemaDocument, JsonSchemaDocumentOptions } from "./json-schema-document.js";
|
|
7
8
|
import type { OneOfSchema } from "./oneof.js";
|
|
8
9
|
import type { RecordSchema } from "./record.js";
|
|
9
10
|
import type { UnionSchema } from "./union.js";
|
|
@@ -47,6 +48,7 @@ type InferObject<TShape extends ObjectShape> = {
|
|
|
47
48
|
};
|
|
48
49
|
type SchemaOptions<TDefault> = {
|
|
49
50
|
description?: string;
|
|
51
|
+
cliAliases?: readonly string[];
|
|
50
52
|
default?: TDefault;
|
|
51
53
|
nullable?: boolean;
|
|
52
54
|
requiredScopes?: readonly SchemaScope[];
|
|
@@ -57,6 +59,7 @@ type SchemaOptions<TDefault> = {
|
|
|
57
59
|
export interface SchemaBase<TKind extends SchemaKind, TStatic> {
|
|
58
60
|
readonly kind: TKind;
|
|
59
61
|
readonly description?: string;
|
|
62
|
+
readonly cliAliases?: readonly string[];
|
|
60
63
|
readonly default?: TStatic;
|
|
61
64
|
readonly nullable?: boolean;
|
|
62
65
|
readonly requiredScopes?: readonly SchemaScope[];
|
|
@@ -140,5 +143,7 @@ export declare const S: {
|
|
|
140
143
|
readonly Json: typeof Json;
|
|
141
144
|
};
|
|
142
145
|
export declare function toJsonSchema(schema: AnySchema): JsonSchema;
|
|
146
|
+
export declare function toJsonSchemaDocument(schema: AnySchema, options?: JsonSchemaDocumentOptions): JsonSchemaDocument;
|
|
143
147
|
export { Json, OneOf, RecordBuilder as Record, Union, validate };
|
|
144
|
-
export type {
|
|
148
|
+
export type { JsonSchemaDocument, JsonSchemaDocumentOptions } from "./json-schema-document.js";
|
|
149
|
+
export type { JsonValue, JsonValueSchema, OneOfSchema, RecordSchema, UnionSchema, ValidationIssue, ValidationResult };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Json } from "./json.js";
|
|
2
|
+
import { createJsonSchemaDocument } from "./json-schema-document.js";
|
|
2
3
|
import { OneOf } from "./oneof.js";
|
|
3
4
|
import { Record as RecordBuilder } from "./record.js";
|
|
4
5
|
import { Union } from "./union.js";
|
|
@@ -117,15 +118,15 @@ function withInjectedDiscriminator(schema, discriminator, branchName) {
|
|
|
117
118
|
...(branchJsonSchema.properties ?? {}),
|
|
118
119
|
[discriminator]: {
|
|
119
120
|
type: "string",
|
|
120
|
-
enum: [branchName]
|
|
121
|
-
}
|
|
121
|
+
enum: [branchName]
|
|
122
|
+
}
|
|
122
123
|
};
|
|
123
124
|
const required = [...new Set([...(branchJsonSchema.required ?? []), discriminator])];
|
|
124
125
|
return {
|
|
125
126
|
...branchJsonSchema,
|
|
126
127
|
type: "object",
|
|
127
128
|
properties,
|
|
128
|
-
required
|
|
129
|
+
required
|
|
129
130
|
};
|
|
130
131
|
}
|
|
131
132
|
export const S = {
|
|
@@ -135,36 +136,39 @@ export const S = {
|
|
|
135
136
|
assertPattern(options.pattern);
|
|
136
137
|
return {
|
|
137
138
|
kind: "string",
|
|
138
|
-
...options
|
|
139
|
+
...options
|
|
139
140
|
};
|
|
140
141
|
},
|
|
141
142
|
Number(options = {}) {
|
|
142
143
|
assertFiniteNumber(options.minimum, "minimum");
|
|
143
144
|
assertFiniteNumber(options.maximum, "maximum");
|
|
144
145
|
assertFiniteNumber(options.default, "default");
|
|
145
|
-
if (options.jsonType === "integer" &&
|
|
146
|
+
if (options.jsonType === "integer" &&
|
|
147
|
+
options.default !== undefined &&
|
|
148
|
+
!Number.isInteger(options.default)) {
|
|
146
149
|
throw new Error("default must be an integer");
|
|
147
150
|
}
|
|
148
151
|
return {
|
|
149
152
|
kind: "number",
|
|
150
|
-
...options
|
|
153
|
+
...options
|
|
151
154
|
};
|
|
152
155
|
},
|
|
153
156
|
Boolean(options = {}) {
|
|
154
157
|
return {
|
|
155
158
|
kind: "boolean",
|
|
156
|
-
...options
|
|
159
|
+
...options
|
|
157
160
|
};
|
|
158
161
|
},
|
|
159
162
|
Enum(values, options = {}) {
|
|
160
163
|
assertValidEnumValues(values);
|
|
161
|
-
if (options.jsonType === "integer" &&
|
|
164
|
+
if (options.jsonType === "integer" &&
|
|
165
|
+
values.some((value) => typeof value !== "number" || !Number.isInteger(value))) {
|
|
162
166
|
throw new Error("Integer enum values must be integers");
|
|
163
167
|
}
|
|
164
168
|
return {
|
|
165
169
|
kind: "enum",
|
|
166
170
|
values,
|
|
167
|
-
...options
|
|
171
|
+
...options
|
|
168
172
|
};
|
|
169
173
|
},
|
|
170
174
|
Array(item, options = {}) {
|
|
@@ -173,26 +177,26 @@ export const S = {
|
|
|
173
177
|
return {
|
|
174
178
|
kind: "array",
|
|
175
179
|
item,
|
|
176
|
-
...options
|
|
180
|
+
...options
|
|
177
181
|
};
|
|
178
182
|
},
|
|
179
183
|
Object(shape, options = {}) {
|
|
180
184
|
return {
|
|
181
185
|
kind: "object",
|
|
182
186
|
shape,
|
|
183
|
-
...options
|
|
187
|
+
...options
|
|
184
188
|
};
|
|
185
189
|
},
|
|
186
190
|
Optional(inner) {
|
|
187
191
|
return {
|
|
188
192
|
kind: "optional",
|
|
189
|
-
inner
|
|
193
|
+
inner
|
|
190
194
|
};
|
|
191
195
|
},
|
|
192
196
|
OneOf,
|
|
193
197
|
Union,
|
|
194
198
|
Record: RecordBuilder,
|
|
195
|
-
Json
|
|
199
|
+
Json
|
|
196
200
|
};
|
|
197
201
|
export function toJsonSchema(schema) {
|
|
198
202
|
const unwrappedSchema = unwrapOptional(schema);
|
|
@@ -207,7 +211,7 @@ export function toJsonSchema(schema) {
|
|
|
207
211
|
const jsonSchema = {
|
|
208
212
|
enum: unwrappedSchema.nullable === true
|
|
209
213
|
? [...unwrappedSchema.values, null]
|
|
210
|
-
: [...unwrappedSchema.values]
|
|
214
|
+
: [...unwrappedSchema.values]
|
|
211
215
|
};
|
|
212
216
|
const enumType = unwrappedSchema.jsonType ?? getEnumJsonType(unwrappedSchema.values);
|
|
213
217
|
if (enumType !== undefined) {
|
|
@@ -218,7 +222,7 @@ export function toJsonSchema(schema) {
|
|
|
218
222
|
case "array":
|
|
219
223
|
return withArrayMetadata(unwrappedSchema, {
|
|
220
224
|
type: "array",
|
|
221
|
-
items: toJsonSchema(unwrappedSchema.item)
|
|
225
|
+
items: toJsonSchema(unwrappedSchema.item)
|
|
222
226
|
});
|
|
223
227
|
case "object": {
|
|
224
228
|
const properties = {};
|
|
@@ -237,24 +241,27 @@ export function toJsonSchema(schema) {
|
|
|
237
241
|
return withObjectMetadata(unwrappedSchema, {
|
|
238
242
|
type: "object",
|
|
239
243
|
properties,
|
|
240
|
-
required
|
|
244
|
+
required
|
|
241
245
|
});
|
|
242
246
|
}
|
|
243
247
|
case "oneOf":
|
|
244
248
|
return withMetadata(unwrappedSchema, {
|
|
245
|
-
oneOf: Object.entries(unwrappedSchema.branches).map(([branchName, branchSchema]) => withInjectedDiscriminator(branchSchema, unwrappedSchema.discriminator, branchName))
|
|
249
|
+
oneOf: Object.entries(unwrappedSchema.branches).map(([branchName, branchSchema]) => withInjectedDiscriminator(branchSchema, unwrappedSchema.discriminator, branchName))
|
|
246
250
|
});
|
|
247
251
|
case "union":
|
|
248
252
|
return withMetadata(unwrappedSchema, {
|
|
249
|
-
oneOf: unwrappedSchema.branches.map((branchSchema) => toJsonSchema(branchSchema))
|
|
253
|
+
oneOf: unwrappedSchema.branches.map((branchSchema) => toJsonSchema(branchSchema))
|
|
250
254
|
});
|
|
251
255
|
case "record":
|
|
252
256
|
return withMetadata(unwrappedSchema, {
|
|
253
257
|
type: "object",
|
|
254
|
-
additionalProperties: toJsonSchema(unwrappedSchema.value)
|
|
258
|
+
additionalProperties: toJsonSchema(unwrappedSchema.value)
|
|
255
259
|
});
|
|
256
260
|
case "json":
|
|
257
261
|
return withMetadata(unwrappedSchema, {});
|
|
258
262
|
}
|
|
259
263
|
}
|
|
264
|
+
export function toJsonSchemaDocument(schema, options = {}) {
|
|
265
|
+
return createJsonSchemaDocument(toJsonSchema(schema), options);
|
|
266
|
+
}
|
|
260
267
|
export { Json, OneOf, RecordBuilder as Record, Union, validate };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JsonSchema } from "./index.js";
|
|
2
|
+
export interface JsonSchemaDocumentOptions {
|
|
3
|
+
id?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
schema?: string;
|
|
7
|
+
}
|
|
8
|
+
export type JsonSchemaDocument = JsonSchema & {
|
|
9
|
+
$schema: string;
|
|
10
|
+
$id?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function createJsonSchemaDocument(jsonSchema: JsonSchema, options?: JsonSchemaDocumentOptions): JsonSchemaDocument;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function createJsonSchemaDocument(jsonSchema, options = {}) {
|
|
2
|
+
const { id, schema: schemaUri = "https://json-schema.org/draft/2020-12/schema", ...metadata } = options;
|
|
3
|
+
const document = {
|
|
4
|
+
$schema: schemaUri,
|
|
5
|
+
...jsonSchema
|
|
6
|
+
};
|
|
7
|
+
if (id !== undefined) {
|
|
8
|
+
document.$id = id;
|
|
9
|
+
}
|
|
10
|
+
if (metadata.title !== undefined) {
|
|
11
|
+
document.title = metadata.title;
|
|
12
|
+
}
|
|
13
|
+
if (metadata.description !== undefined) {
|
|
14
|
+
document.description = metadata.description;
|
|
15
|
+
}
|
|
16
|
+
return document;
|
|
17
|
+
}
|