toolcraft-schema 0.0.429 → 0.0.431

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 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>` type inference
11
- - Runtime validation with `validateValue()`
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, validateValue } from "toolcraft-schema";
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 = validateValue(schema, {
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, { description?, default? })`
59
- - `S.Union([schemaA, schemaB], { description?, default? })`
60
- - `S.OneOf([schemaA, schemaB], { description?, default? })`
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` in `Static` and emit `nullable: true` in JSON Schema.
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
- - `validateValue(schema, value)` returns `{ ok: true, value }` for valid input.
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 applies defaults from schema descriptors.
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>> | ArraySchema<AnySchema> | ObjectSchema<ObjectShape> | OptionalSchema<AnySchema> | OneOfSchema<Record<string, ObjectSchema<any>>, string> | UnionSchema<readonly ObjectSchema<any>[]> | RecordSchema<AnySchema> | JsonValueSchema;
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 declare function toJsonSchema(schema: AnySchema): JsonSchema;
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
- export declare function Json(options?: Omit<JsonValueSchema, "kind">): JsonValueSchema;
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
@@ -1,6 +1,7 @@
1
+ import { withStandardSchema } from "./standard.js";
1
2
  export function Record(value) {
2
- return {
3
+ return withStandardSchema({
3
4
  kind: "record",
4
5
  value,
5
- };
6
+ });
6
7
  }
@@ -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 {};
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.429",
3
+ "version": "0.0.431",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",