@dynz/to-json-schema 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -114,6 +114,12 @@ function applyRule(jsonSchema, rule, schemaType, context) {
114
114
  else reportIssue(context, `"equals" rule with a non-static value cannot be converted to JSON Schema.`);
115
115
  break;
116
116
  }
117
+ case "not_equals": {
118
+ const notEquals = resolveStatic(rule.notEquals);
119
+ if (notEquals.ok) setKeyword(jsonSchema, "not", { const: notEquals.value });
120
+ else reportIssue(context, `"not_equals" rule with a non-static value cannot be converted to JSON Schema.`);
121
+ break;
122
+ }
117
123
  case "includes": {
118
124
  const includes = resolveStatic(rule.includes);
119
125
  if (!includes.ok) {
@@ -174,6 +180,8 @@ function applyRule(jsonSchema, rule, schemaType, context) {
174
180
  case "max_date":
175
181
  case "before":
176
182
  case "after":
183
+ case "same_calendar":
184
+ case "boundary_day":
177
185
  reportIssue(context, `"${rule.type}" rule has no JSON Schema equivalent (date comparisons aren't representable).`);
178
186
  break;
179
187
  case "min_size":
@@ -186,6 +194,9 @@ function applyRule(jsonSchema, rule, schemaType, context) {
186
194
  case "conditional":
187
195
  reportIssue(context, `"conditional" rule has no JSON Schema equivalent.`);
188
196
  break;
197
+ case "satisfies":
198
+ reportIssue(context, `"satisfies" rule has no JSON Schema equivalent.`);
199
+ break;
189
200
  default: reportIssue(context, `Unknown rule cannot be converted to JSON Schema.`);
190
201
  }
191
202
  }
@@ -198,7 +209,8 @@ function applyRules(jsonSchema, rules, schemaType, context) {
198
209
  function isMandatory(schema) {
199
210
  const isIncluded = schema.included === void 0 || schema.included === true;
200
211
  const isRequired = schema.required === void 0 || schema.required === true;
201
- return isIncluded && isRequired;
212
+ const hasDefault = "default" in schema && schema.default !== void 0;
213
+ return isIncluded && isRequired && !hasDefault;
202
214
  }
203
215
  function jsonTypeOf(value) {
204
216
  return typeof value;
@@ -211,47 +223,101 @@ function applyEnumValues(jsonSchema, values) {
211
223
  }
212
224
  function collectOptionValues(options, context) {
213
225
  const values = [];
214
- const visit = (option) => {
226
+ for (const option of options) {
215
227
  if (typeof option !== "object") {
216
228
  values.push(option);
217
- return;
229
+ continue;
218
230
  }
219
- if (option.enabled === false) return;
231
+ if (option.enabled === false) continue;
220
232
  if (typeof option.enabled === "object") reportIssue(context, `An "options" entry has a predicate-based "enabled" flag; whether it's enabled can't be determined statically, so it was included in the JSON Schema "enum" anyway.`);
221
- visit(option.value);
222
- };
223
- for (const option of options) visit(option);
233
+ values.push(option.value);
234
+ }
224
235
  return values;
225
236
  }
226
237
  function isSchema(value) {
227
238
  return typeof value === "object" && value !== null && "type" in value;
228
239
  }
240
+ /**
241
+ * Wraps a property's schema so it also accepts `null` — strict mode's only
242
+ * way to express "may be absent", since every property must be listed in
243
+ * `required`.
244
+ */
245
+ function withNullable(jsonSchema) {
246
+ return { anyOf: [jsonSchema, { type: "null" }] };
247
+ }
248
+ /**
249
+ * A `const` node, paired with its inferred `type` under `strict` (strict
250
+ * mode requires `type` everywhere; a bare `const` has none otherwise).
251
+ */
252
+ function constNode(value, strict) {
253
+ return strict ? {
254
+ const: value,
255
+ type: value === null ? "null" : typeof value
256
+ } : { const: value };
257
+ }
258
+ /**
259
+ * Converts a set of object fields into `properties`/`required`, shared by
260
+ * the OBJECT case and each `discriminatedUnion()` member. Under `strict`,
261
+ * every non-omitted field is listed in `required` — one that isn't
262
+ * otherwise mandatory has its schema widened to also accept `null`.
263
+ */
264
+ function convertObjectFields(fields, context) {
265
+ const properties = {};
266
+ const required = [];
267
+ for (const [key, fieldSchema] of Object.entries(fields)) {
268
+ if (shouldOmitField(fieldSchema, context)) continue;
269
+ const propertySchema = convertSchema(fieldSchema, context);
270
+ const mandatory = isMandatory(fieldSchema);
271
+ properties[key] = context.strict && !mandatory ? withNullable(propertySchema) : propertySchema;
272
+ if (mandatory || context.strict) required.push(key);
273
+ }
274
+ return {
275
+ properties,
276
+ required
277
+ };
278
+ }
229
279
  function shouldOmitField(schema, context) {
230
280
  if (schema.included === false) return true;
231
281
  return context.mode === "input" && schema.type === dynz.SchemaType.EXPRESSION;
232
282
  }
233
- function applyPrivacyWrapper(schema, innerSchema) {
283
+ function applyPrivacyWrapper(schema, innerSchema, context) {
234
284
  if (schema.private !== true) return innerSchema;
235
- return { oneOf: [{
285
+ const plain = {
236
286
  type: "object",
237
287
  properties: {
238
- state: { const: "plain" },
288
+ state: constNode("plain", context.strict),
239
289
  value: innerSchema
240
290
  },
241
- required: ["state"]
242
- }, {
291
+ required: context.strict ? ["state", "value"] : ["state"]
292
+ };
293
+ const masked = {
243
294
  type: "object",
244
295
  properties: {
245
- state: { const: "masked" },
296
+ state: constNode("masked", context.strict),
246
297
  value: { type: "string" }
247
298
  },
248
299
  required: ["state", "value"]
249
- }] };
300
+ };
301
+ if (context.strict) {
302
+ plain.additionalProperties = false;
303
+ masked.additionalProperties = false;
304
+ }
305
+ return { [context.unionKeyword]: [plain, masked] };
250
306
  }
251
307
  function applyDefault(schema, jsonSchema) {
252
308
  if (schema.default === void 0) return;
253
309
  jsonSchema.default = schema.default instanceof Date ? schema.default.toISOString() : schema.default;
254
310
  }
311
+ function applyMeta(schema, jsonSchema) {
312
+ const meta = schema.meta;
313
+ if (!meta) return;
314
+ const { id, title, description, deprecated, ...rest } = meta;
315
+ if (id !== void 0) jsonSchema.$id = id;
316
+ if (title !== void 0) jsonSchema.title = title;
317
+ if (description !== void 0) jsonSchema.description = description;
318
+ if (deprecated !== void 0) jsonSchema.deprecated = deprecated;
319
+ Object.assign(jsonSchema, rest);
320
+ }
255
321
  /**
256
322
  * Converts a dynz schema to a JSON Schema (2020-12) document. Rule values
257
323
  * and schema kinds without a JSON Schema equivalent are handled per
@@ -260,7 +326,8 @@ function applyDefault(schema, jsonSchema) {
260
326
  function convertSchema(schema, context) {
261
327
  const jsonSchema = convertSchemaKind(schema, context);
262
328
  applyDefault(schema, jsonSchema);
263
- return applyPrivacyWrapper(schema, jsonSchema);
329
+ applyMeta(schema, jsonSchema);
330
+ return applyPrivacyWrapper(schema, jsonSchema, context);
264
331
  }
265
332
  function convertSchemaKind(schema, context) {
266
333
  switch (schema.type) {
@@ -287,7 +354,7 @@ function convertSchemaKind(schema, context) {
287
354
  applyRules(jsonSchema, schema.rules, schema.type, context);
288
355
  return jsonSchema;
289
356
  }
290
- case dynz.SchemaType.LITERAL: return { const: schema.value };
357
+ case dynz.SchemaType.LITERAL: return constNode(schema.value, context.strict);
291
358
  case dynz.SchemaType.ENUM: {
292
359
  const jsonSchema = {};
293
360
  applyEnumValues(jsonSchema, Object.values(schema.enum));
@@ -315,35 +382,33 @@ function convertSchemaKind(schema, context) {
315
382
  return jsonSchema;
316
383
  }
317
384
  case dynz.SchemaType.OBJECT: {
318
- const properties = {};
319
- const required = [];
320
- for (const [key, fieldSchema] of Object.entries(schema.fields)) {
321
- if (shouldOmitField(fieldSchema, context)) continue;
322
- properties[key] = convertSchema(fieldSchema, context);
323
- if (isMandatory(fieldSchema)) required.push(key);
324
- }
385
+ const { properties, required } = convertObjectFields(schema.fields, context);
325
386
  const jsonSchema = {
326
387
  type: "object",
327
388
  properties
328
389
  };
329
390
  if (required.length > 0) jsonSchema.required = required;
391
+ if (context.strict) jsonSchema.additionalProperties = false;
330
392
  applyRules(jsonSchema, schema.rules, schema.type, context);
331
393
  return jsonSchema;
332
394
  }
333
- case dynz.SchemaType.DISCRIMINATED_UNION: return { oneOf: schema.schemas.map((member) => {
334
- const properties = { [schema.key]: { const: member[schema.key] } };
335
- const required = [schema.key];
336
- for (const [key, value] of Object.entries(member)) {
337
- if (key === schema.key || !isSchema(value) || shouldOmitField(value, context)) continue;
338
- properties[key] = convertSchema(value, context);
339
- if (isMandatory(value)) required.push(key);
340
- }
341
- return {
342
- type: "object",
343
- properties,
344
- required
345
- };
346
- }) };
395
+ case dynz.SchemaType.DISCRIMINATED_UNION: {
396
+ const members = schema.schemas.map((member) => {
397
+ const memberFields = {};
398
+ for (const [key, value] of Object.entries(member)) if (key !== schema.key && isSchema(value)) memberFields[key] = value;
399
+ const { properties, required } = convertObjectFields(memberFields, context);
400
+ properties[schema.key] = constNode(member[schema.key], context.strict);
401
+ required.unshift(schema.key);
402
+ const jsonSchema = {
403
+ type: "object",
404
+ properties,
405
+ required
406
+ };
407
+ if (context.strict) jsonSchema.additionalProperties = false;
408
+ return jsonSchema;
409
+ });
410
+ return { [context.unionKeyword]: members };
411
+ }
347
412
  default:
348
413
  reportIssue(context, `Unknown schema type cannot be converted to JSON Schema.`);
349
414
  return {};
@@ -359,14 +424,19 @@ function convertSchemaKind(schema, context) {
359
424
  * transformers) and schema kinds without a JSON Schema equivalent are
360
425
  * handled according to `config.errorMode` (default `"warn"`). Whether
361
426
  * `expression` fields are included is controlled by `config.mode`
362
- * (default `"input"`, which omits them).
427
+ * (default `"input"`, which omits them). `config.strict` (default `true`)
428
+ * produces output compatible with LLM structured-output "strict" modes,
429
+ * and `config.unionKeyword` (default `"oneOf"`) controls whether schema-
430
+ * selection unions use `oneOf` or `anyOf`.
363
431
  */
364
432
  function toStandardJsonSchema(schema, config = {}) {
365
433
  return {
366
434
  $schema: "https://json-schema.org/draft/2020-12/schema",
367
435
  ...convertSchema(schema, {
368
436
  errorMode: config.errorMode ?? "warn",
369
- mode: config.mode ?? "input"
437
+ mode: config.mode ?? "input",
438
+ strict: config.strict ?? true,
439
+ unionKeyword: config.unionKeyword ?? "oneOf"
370
440
  })
371
441
  };
372
442
  }
package/dist/index.d.cts CHANGED
@@ -8,6 +8,10 @@ import { Schema } from "dynz";
8
8
  */
9
9
  interface JsonSchema {
10
10
  $schema?: string;
11
+ $id?: string;
12
+ title?: string;
13
+ description?: string;
14
+ deprecated?: boolean;
11
15
  type?: string | string[];
12
16
  properties?: Record<string, JsonSchema>;
13
17
  required?: string[];
@@ -46,6 +50,11 @@ type ErrorMode = "throw" | "warn" | "ignore";
46
50
  * `"output"` mode they're included (using their best-effort mapping).
47
51
  */
48
52
  type ConversionMode = "input" | "output";
53
+ /**
54
+ * The keyword used for a schema-selection union — `discriminatedUnion()`
55
+ * members, and the plain/masked wrapper for `.setPrivate(true)` fields.
56
+ */
57
+ type UnionKeyword = "oneOf" | "anyOf";
49
58
  interface ConversionConfig {
50
59
  /**
51
60
  * Policy for handling unsupported/unresolvable rule values and schema
@@ -56,6 +65,26 @@ interface ConversionConfig {
56
65
  * Whether to convert for input or output data. Defaults to `"input"`.
57
66
  */
58
67
  mode?: ConversionMode;
68
+ /**
69
+ * Produces strict-mode-compatible output for LLM structured outputs
70
+ * (OpenAI `strict: true`, Anthropic's native structured output): every
71
+ * object node gets `additionalProperties: false` and a complete
72
+ * `required` list — a property that isn't otherwise mandatory has its
73
+ * schema widened to also accept `null` (via `anyOf`), since strict mode
74
+ * has no other way to express "may be absent". `literal()` fields (and
75
+ * the discriminator key of a `discriminatedUnion()`) also get an
76
+ * inferred `type`, which strict mode requires everywhere. Defaults to
77
+ * `true`.
78
+ */
79
+ strict?: boolean;
80
+ /**
81
+ * The keyword used for every schema-selection union this package emits
82
+ * — `discriminatedUnion()` members, and the plain/masked wrapper for
83
+ * `.setPrivate(true)` fields. Some strict-mode consumers (e.g.
84
+ * Anthropic's structured output) reject `oneOf` outright and require
85
+ * `anyOf`. Defaults to `"oneOf"`.
86
+ */
87
+ unionKeyword?: UnionKeyword;
59
88
  }
60
89
  //#endregion
61
90
  //#region src/to-standard-json-schema.d.ts
@@ -66,8 +95,11 @@ interface ConversionConfig {
66
95
  * transformers) and schema kinds without a JSON Schema equivalent are
67
96
  * handled according to `config.errorMode` (default `"warn"`). Whether
68
97
  * `expression` fields are included is controlled by `config.mode`
69
- * (default `"input"`, which omits them).
98
+ * (default `"input"`, which omits them). `config.strict` (default `true`)
99
+ * produces output compatible with LLM structured-output "strict" modes,
100
+ * and `config.unionKeyword` (default `"oneOf"`) controls whether schema-
101
+ * selection unions use `oneOf` or `anyOf`.
70
102
  */
71
103
  declare function toStandardJsonSchema<T extends Schema>(schema: T, config?: ConversionConfig): JsonSchema;
72
104
  //#endregion
73
- export { type ConversionConfig, type ErrorMode, type JsonSchema, toStandardJsonSchema };
105
+ export { type ConversionConfig, type ErrorMode, type JsonSchema, type UnionKeyword, toStandardJsonSchema };
package/dist/index.d.mts CHANGED
@@ -8,6 +8,10 @@ import { Schema } from "dynz";
8
8
  */
9
9
  interface JsonSchema {
10
10
  $schema?: string;
11
+ $id?: string;
12
+ title?: string;
13
+ description?: string;
14
+ deprecated?: boolean;
11
15
  type?: string | string[];
12
16
  properties?: Record<string, JsonSchema>;
13
17
  required?: string[];
@@ -46,6 +50,11 @@ type ErrorMode = "throw" | "warn" | "ignore";
46
50
  * `"output"` mode they're included (using their best-effort mapping).
47
51
  */
48
52
  type ConversionMode = "input" | "output";
53
+ /**
54
+ * The keyword used for a schema-selection union — `discriminatedUnion()`
55
+ * members, and the plain/masked wrapper for `.setPrivate(true)` fields.
56
+ */
57
+ type UnionKeyword = "oneOf" | "anyOf";
49
58
  interface ConversionConfig {
50
59
  /**
51
60
  * Policy for handling unsupported/unresolvable rule values and schema
@@ -56,6 +65,26 @@ interface ConversionConfig {
56
65
  * Whether to convert for input or output data. Defaults to `"input"`.
57
66
  */
58
67
  mode?: ConversionMode;
68
+ /**
69
+ * Produces strict-mode-compatible output for LLM structured outputs
70
+ * (OpenAI `strict: true`, Anthropic's native structured output): every
71
+ * object node gets `additionalProperties: false` and a complete
72
+ * `required` list — a property that isn't otherwise mandatory has its
73
+ * schema widened to also accept `null` (via `anyOf`), since strict mode
74
+ * has no other way to express "may be absent". `literal()` fields (and
75
+ * the discriminator key of a `discriminatedUnion()`) also get an
76
+ * inferred `type`, which strict mode requires everywhere. Defaults to
77
+ * `true`.
78
+ */
79
+ strict?: boolean;
80
+ /**
81
+ * The keyword used for every schema-selection union this package emits
82
+ * — `discriminatedUnion()` members, and the plain/masked wrapper for
83
+ * `.setPrivate(true)` fields. Some strict-mode consumers (e.g.
84
+ * Anthropic's structured output) reject `oneOf` outright and require
85
+ * `anyOf`. Defaults to `"oneOf"`.
86
+ */
87
+ unionKeyword?: UnionKeyword;
59
88
  }
60
89
  //#endregion
61
90
  //#region src/to-standard-json-schema.d.ts
@@ -66,8 +95,11 @@ interface ConversionConfig {
66
95
  * transformers) and schema kinds without a JSON Schema equivalent are
67
96
  * handled according to `config.errorMode` (default `"warn"`). Whether
68
97
  * `expression` fields are included is controlled by `config.mode`
69
- * (default `"input"`, which omits them).
98
+ * (default `"input"`, which omits them). `config.strict` (default `true`)
99
+ * produces output compatible with LLM structured-output "strict" modes,
100
+ * and `config.unionKeyword` (default `"oneOf"`) controls whether schema-
101
+ * selection unions use `oneOf` or `anyOf`.
70
102
  */
71
103
  declare function toStandardJsonSchema<T extends Schema>(schema: T, config?: ConversionConfig): JsonSchema;
72
104
  //#endregion
73
- export { type ConversionConfig, type ErrorMode, type JsonSchema, toStandardJsonSchema };
105
+ export { type ConversionConfig, type ErrorMode, type JsonSchema, type UnionKeyword, toStandardJsonSchema };
@@ -1 +1 @@
1
- let e=require(`dynz`);function t(e,t){if(e.errorMode===`throw`)throw Error(t);e.errorMode===`warn`&&console.warn(`[@dynz/to-json-schema] ${t}`)}function n(e){return e!==void 0&&typeof e==`object`&&`type`in e&&e.type===`st`?{ok:!0,value:e.value}:{ok:!1}}function r(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function i(e,t,n){if(e[t]===void 0){e[t]=n;return}e.allOf=[...e.allOf??[],{[t]:n}]}function a(e,a,o,s){switch(a.type){case`min`:{let r=n(a.min);r.ok?i(e,`minimum`,r.value):t(s,`"min" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max`:{let r=n(a.max);r.ok?i(e,`maximum`,r.value):t(s,`"max" rule with a non-static value cannot be converted to JSON Schema.`);break}case`min_length`:{let r=n(a.min);r.ok?o===`array`?i(e,`minItems`,r.value):i(e,`minLength`,r.value):t(s,`"min_length" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_length`:{let r=n(a.max);r.ok?o===`array`?i(e,`maxItems`,r.value):i(e,`maxLength`,r.value):t(s,`"max_length" rule with a non-static value cannot be converted to JSON Schema.`);break}case`min_entries`:{let r=n(a.min);r.ok?i(e,`minProperties`,r.value):t(s,`"min_entries" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_entries`:{let r=n(a.max);r.ok?i(e,`maxProperties`,r.value):t(s,`"max_entries" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_precision`:{let r=n(a.maxPrecision);r.ok?i(e,`multipleOf`,1/10**r.value):t(s,`"max_precision" rule with a non-static value cannot be converted to JSON Schema.`);break}case`regex`:i(e,`pattern`,a.regex),a.flags&&t(s,`"regex" rule flags ("${a.flags}") cannot be represented in JSON Schema's "pattern" keyword and were ignored.`);break;case`email`:i(e,`format`,`email`);break;case`is_numeric`:i(e,`pattern`,`^[+-]?\\d+(\\.\\d+)?$`);break;case`equals`:{let r=n(a.equals);r.ok?i(e,`const`,r.value):t(s,`"equals" rule with a non-static value cannot be converted to JSON Schema.`);break}case`includes`:{let c=n(a.includes);if(!c.ok){t(s,`"includes" rule with a non-static value cannot be converted to JSON Schema.`);break}o===`array`?i(e,`contains`,{const:c.value}):i(e,`pattern`,r(String(c.value)));break}case`not_includes`:{let c=n(a.notIncludes);if(!c.ok){t(s,`"not_includes" rule with a non-static value cannot be converted to JSON Schema.`);break}i(e,`not`,o===`array`?{contains:{const:c.value}}:{pattern:r(String(c.value))});break}case`one_of`:{let r=[];for(let e of a.values){let i=n(e);if(!i.ok){t(s,`"one_of" rule with a non-static value cannot be converted to JSON Schema.`);return}r.push(i.value)}i(e,`enum`,r);break}case`not_one_of`:{let r=[];for(let e of a.values){let i=n(e);if(!i.ok){t(s,`"not_one_of" rule with a non-static value cannot be converted to JSON Schema.`);return}r.push(i.value)}i(e,`not`,{enum:r});break}case`mime_type`:{let r=n(a.mimeType);if(!r.ok){t(s,`"mime_type" rule with a non-static value cannot be converted to JSON Schema.`);break}typeof r.value==`string`?i(e,`contentMediaType`,r.value):Array.isArray(r.value)&&r.value.length===1?i(e,`contentMediaType`,r.value[0]):t(s,`"mime_type" rule with multiple mime types has no JSON Schema equivalent.`);break}case`min_date`:case`max_date`:case`before`:case`after`:t(s,`"${a.type}" rule has no JSON Schema equivalent (date comparisons aren't representable).`);break;case`min_size`:case`max_size`:t(s,`"${a.type}" rule has no JSON Schema equivalent (byte-size constraints aren't representable).`);break;case`custom`:t(s,`"custom" rule "${a.name}" has no JSON Schema equivalent.`);break;case`conditional`:t(s,`"conditional" rule has no JSON Schema equivalent.`);break;default:t(s,`Unknown rule cannot be converted to JSON Schema.`)}}function o(e,t,n,r){for(let i of t??[])a(e,i,n,r)}function s(e){let t=e.included===void 0||e.included===!0,n=e.required===void 0||e.required===!0;return t&&n}function c(e){return typeof e}function l(e,t){e.enum=t;let n=Array.from(new Set(t.map(c)));n.length===1?e.type=n[0]:n.length>1&&(e.type=n)}function u(e,n){let r=[],i=e=>{if(typeof e!=`object`){r.push(e);return}e.enabled!==!1&&(typeof e.enabled==`object`&&t(n,`An "options" entry has a predicate-based "enabled" flag; whether it's enabled can't be determined statically, so it was included in the JSON Schema "enum" anyway.`),i(e.value))};for(let t of e)i(t);return r}function d(e){return typeof e==`object`&&!!e&&`type`in e}function f(t,n){return t.included===!1?!0:n.mode===`input`&&t.type===e.SchemaType.EXPRESSION}function p(e,t){return e.private===!0?{oneOf:[{type:`object`,properties:{state:{const:`plain`},value:t},required:[`state`]},{type:`object`,properties:{state:{const:`masked`},value:{type:`string`}},required:[`state`,`value`]}]}:t}function m(e,t){e.default!==void 0&&(t.default=e.default instanceof Date?e.default.toISOString():e.default)}function h(e,t){let n=g(e,t);return m(e,n),p(e,n)}function g(n,r){switch(n.type){case e.SchemaType.STRING:{let e={type:`string`};return o(e,n.rules,n.type,r),e}case e.SchemaType.NUMBER:{let e={type:`number`};return o(e,n.rules,n.type,r),e}case e.SchemaType.BOOLEAN:{let e={type:`boolean`};return o(e,n.rules,n.type,r),e}case e.SchemaType.DATE:{let e={type:`string`,format:`date-time`};return o(e,n.rules,n.type,r),e}case e.SchemaType.LITERAL:return{const:n.value};case e.SchemaType.ENUM:{let e={};return l(e,Object.values(n.enum)),o(e,n.rules,n.type,r),e}case e.SchemaType.OPTIONS:{let e={};return l(e,u(n.options,r)),o(e,n.rules,n.type,r),e}case e.SchemaType.FILE:{let e={type:`string`};return o(e,n.rules,n.type,r),e}case e.SchemaType.EXPRESSION:return{};case e.SchemaType.ARRAY:{let e={type:`array`,items:h(n.schema,r)};return o(e,n.rules,n.type,r),e}case e.SchemaType.OBJECT:{let e={},t=[];for(let[i,a]of Object.entries(n.fields))f(a,r)||(e[i]=h(a,r),s(a)&&t.push(i));let i={type:`object`,properties:e};return t.length>0&&(i.required=t),o(i,n.rules,n.type,r),i}case e.SchemaType.DISCRIMINATED_UNION:return{oneOf:n.schemas.map(e=>{let t={[n.key]:{const:e[n.key]}},i=[n.key];for(let[a,o]of Object.entries(e))a===n.key||!d(o)||f(o,r)||(t[a]=h(o,r),s(o)&&i.push(a));return{type:`object`,properties:t,required:i}})};default:return t(r,`Unknown schema type cannot be converted to JSON Schema.`),{}}}function _(e,t={}){return{$schema:`https://json-schema.org/draft/2020-12/schema`,...h(e,{errorMode:t.errorMode??`warn`,mode:t.mode??`input`})}}exports.toStandardJsonSchema=_;
1
+ let e=require(`dynz`);function t(e,t){if(e.errorMode===`throw`)throw Error(t);e.errorMode===`warn`&&console.warn(`[@dynz/to-json-schema] ${t}`)}function n(e){return e!==void 0&&typeof e==`object`&&`type`in e&&e.type===`st`?{ok:!0,value:e.value}:{ok:!1}}function r(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function i(e,t,n){if(e[t]===void 0){e[t]=n;return}e.allOf=[...e.allOf??[],{[t]:n}]}function a(e,a,o,s){switch(a.type){case`min`:{let r=n(a.min);r.ok?i(e,`minimum`,r.value):t(s,`"min" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max`:{let r=n(a.max);r.ok?i(e,`maximum`,r.value):t(s,`"max" rule with a non-static value cannot be converted to JSON Schema.`);break}case`min_length`:{let r=n(a.min);r.ok?o===`array`?i(e,`minItems`,r.value):i(e,`minLength`,r.value):t(s,`"min_length" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_length`:{let r=n(a.max);r.ok?o===`array`?i(e,`maxItems`,r.value):i(e,`maxLength`,r.value):t(s,`"max_length" rule with a non-static value cannot be converted to JSON Schema.`);break}case`min_entries`:{let r=n(a.min);r.ok?i(e,`minProperties`,r.value):t(s,`"min_entries" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_entries`:{let r=n(a.max);r.ok?i(e,`maxProperties`,r.value):t(s,`"max_entries" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_precision`:{let r=n(a.maxPrecision);r.ok?i(e,`multipleOf`,1/10**r.value):t(s,`"max_precision" rule with a non-static value cannot be converted to JSON Schema.`);break}case`regex`:i(e,`pattern`,a.regex),a.flags&&t(s,`"regex" rule flags ("${a.flags}") cannot be represented in JSON Schema's "pattern" keyword and were ignored.`);break;case`email`:i(e,`format`,`email`);break;case`is_numeric`:i(e,`pattern`,`^[+-]?\\d+(\\.\\d+)?$`);break;case`equals`:{let r=n(a.equals);r.ok?i(e,`const`,r.value):t(s,`"equals" rule with a non-static value cannot be converted to JSON Schema.`);break}case`not_equals`:{let r=n(a.notEquals);r.ok?i(e,`not`,{const:r.value}):t(s,`"not_equals" rule with a non-static value cannot be converted to JSON Schema.`);break}case`includes`:{let c=n(a.includes);if(!c.ok){t(s,`"includes" rule with a non-static value cannot be converted to JSON Schema.`);break}o===`array`?i(e,`contains`,{const:c.value}):i(e,`pattern`,r(String(c.value)));break}case`not_includes`:{let c=n(a.notIncludes);if(!c.ok){t(s,`"not_includes" rule with a non-static value cannot be converted to JSON Schema.`);break}i(e,`not`,o===`array`?{contains:{const:c.value}}:{pattern:r(String(c.value))});break}case`one_of`:{let r=[];for(let e of a.values){let i=n(e);if(!i.ok){t(s,`"one_of" rule with a non-static value cannot be converted to JSON Schema.`);return}r.push(i.value)}i(e,`enum`,r);break}case`not_one_of`:{let r=[];for(let e of a.values){let i=n(e);if(!i.ok){t(s,`"not_one_of" rule with a non-static value cannot be converted to JSON Schema.`);return}r.push(i.value)}i(e,`not`,{enum:r});break}case`mime_type`:{let r=n(a.mimeType);if(!r.ok){t(s,`"mime_type" rule with a non-static value cannot be converted to JSON Schema.`);break}typeof r.value==`string`?i(e,`contentMediaType`,r.value):Array.isArray(r.value)&&r.value.length===1?i(e,`contentMediaType`,r.value[0]):t(s,`"mime_type" rule with multiple mime types has no JSON Schema equivalent.`);break}case`min_date`:case`max_date`:case`before`:case`after`:case`same_calendar`:case`boundary_day`:t(s,`"${a.type}" rule has no JSON Schema equivalent (date comparisons aren't representable).`);break;case`min_size`:case`max_size`:t(s,`"${a.type}" rule has no JSON Schema equivalent (byte-size constraints aren't representable).`);break;case`custom`:t(s,`"custom" rule "${a.name}" has no JSON Schema equivalent.`);break;case`conditional`:t(s,`"conditional" rule has no JSON Schema equivalent.`);break;case`satisfies`:t(s,`"satisfies" rule has no JSON Schema equivalent.`);break;default:t(s,`Unknown rule cannot be converted to JSON Schema.`)}}function o(e,t,n,r){for(let i of t??[])a(e,i,n,r)}function s(e){let t=e.included===void 0||e.included===!0,n=e.required===void 0||e.required===!0,r=`default`in e&&e.default!==void 0;return t&&n&&!r}function c(e){return typeof e}function l(e,t){e.enum=t;let n=Array.from(new Set(t.map(c)));n.length===1?e.type=n[0]:n.length>1&&(e.type=n)}function u(e,n){let r=[];for(let i of e){if(typeof i!=`object`){r.push(i);continue}i.enabled!==!1&&(typeof i.enabled==`object`&&t(n,`An "options" entry has a predicate-based "enabled" flag; whether it's enabled can't be determined statically, so it was included in the JSON Schema "enum" anyway.`),r.push(i.value))}return r}function d(e){return typeof e==`object`&&!!e&&`type`in e}function f(e){return{anyOf:[e,{type:`null`}]}}function p(e,t){return t?{const:e,type:e===null?`null`:typeof e}:{const:e}}function m(e,t){let n={},r=[];for(let[i,a]of Object.entries(e)){if(h(a,t))continue;let e=y(a,t),o=s(a);n[i]=t.strict&&!o?f(e):e,(o||t.strict)&&r.push(i)}return{properties:n,required:r}}function h(t,n){return t.included===!1?!0:n.mode===`input`&&t.type===e.SchemaType.EXPRESSION}function g(e,t,n){if(e.private!==!0)return t;let r={type:`object`,properties:{state:p(`plain`,n.strict),value:t},required:n.strict?[`state`,`value`]:[`state`]},i={type:`object`,properties:{state:p(`masked`,n.strict),value:{type:`string`}},required:[`state`,`value`]};return n.strict&&(r.additionalProperties=!1,i.additionalProperties=!1),{[n.unionKeyword]:[r,i]}}function _(e,t){e.default!==void 0&&(t.default=e.default instanceof Date?e.default.toISOString():e.default)}function v(e,t){let n=e.meta;if(!n)return;let{id:r,title:i,description:a,deprecated:o,...s}=n;r!==void 0&&(t.$id=r),i!==void 0&&(t.title=i),a!==void 0&&(t.description=a),o!==void 0&&(t.deprecated=o),Object.assign(t,s)}function y(e,t){let n=b(e,t);return _(e,n),v(e,n),g(e,n,t)}function b(n,r){switch(n.type){case e.SchemaType.STRING:{let e={type:`string`};return o(e,n.rules,n.type,r),e}case e.SchemaType.NUMBER:{let e={type:`number`};return o(e,n.rules,n.type,r),e}case e.SchemaType.BOOLEAN:{let e={type:`boolean`};return o(e,n.rules,n.type,r),e}case e.SchemaType.DATE:{let e={type:`string`,format:`date-time`};return o(e,n.rules,n.type,r),e}case e.SchemaType.LITERAL:return p(n.value,r.strict);case e.SchemaType.ENUM:{let e={};return l(e,Object.values(n.enum)),o(e,n.rules,n.type,r),e}case e.SchemaType.OPTIONS:{let e={};return l(e,u(n.options,r)),o(e,n.rules,n.type,r),e}case e.SchemaType.FILE:{let e={type:`string`};return o(e,n.rules,n.type,r),e}case e.SchemaType.EXPRESSION:return{};case e.SchemaType.ARRAY:{let e={type:`array`,items:y(n.schema,r)};return o(e,n.rules,n.type,r),e}case e.SchemaType.OBJECT:{let{properties:e,required:t}=m(n.fields,r),i={type:`object`,properties:e};return t.length>0&&(i.required=t),r.strict&&(i.additionalProperties=!1),o(i,n.rules,n.type,r),i}case e.SchemaType.DISCRIMINATED_UNION:{let e=n.schemas.map(e=>{let t={};for(let[r,i]of Object.entries(e))r!==n.key&&d(i)&&(t[r]=i);let{properties:i,required:a}=m(t,r);i[n.key]=p(e[n.key],r.strict),a.unshift(n.key);let o={type:`object`,properties:i,required:a};return r.strict&&(o.additionalProperties=!1),o});return{[r.unionKeyword]:e}}default:return t(r,`Unknown schema type cannot be converted to JSON Schema.`),{}}}function x(e,t={}){return{$schema:`https://json-schema.org/draft/2020-12/schema`,...y(e,{errorMode:t.errorMode??`warn`,mode:t.mode??`input`,strict:t.strict??!0,unionKeyword:t.unionKeyword??`oneOf`})}}exports.toStandardJsonSchema=x;
package/dist/index.min.js CHANGED
@@ -1 +1 @@
1
- import{SchemaType as e}from"dynz";function t(e,t){if(e.errorMode===`throw`)throw Error(t);e.errorMode===`warn`&&console.warn(`[@dynz/to-json-schema] ${t}`)}function n(e){return e!==void 0&&typeof e==`object`&&`type`in e&&e.type===`st`?{ok:!0,value:e.value}:{ok:!1}}function r(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function i(e,t,n){if(e[t]===void 0){e[t]=n;return}e.allOf=[...e.allOf??[],{[t]:n}]}function a(e,a,o,s){switch(a.type){case`min`:{let r=n(a.min);r.ok?i(e,`minimum`,r.value):t(s,`"min" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max`:{let r=n(a.max);r.ok?i(e,`maximum`,r.value):t(s,`"max" rule with a non-static value cannot be converted to JSON Schema.`);break}case`min_length`:{let r=n(a.min);r.ok?o===`array`?i(e,`minItems`,r.value):i(e,`minLength`,r.value):t(s,`"min_length" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_length`:{let r=n(a.max);r.ok?o===`array`?i(e,`maxItems`,r.value):i(e,`maxLength`,r.value):t(s,`"max_length" rule with a non-static value cannot be converted to JSON Schema.`);break}case`min_entries`:{let r=n(a.min);r.ok?i(e,`minProperties`,r.value):t(s,`"min_entries" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_entries`:{let r=n(a.max);r.ok?i(e,`maxProperties`,r.value):t(s,`"max_entries" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_precision`:{let r=n(a.maxPrecision);r.ok?i(e,`multipleOf`,1/10**r.value):t(s,`"max_precision" rule with a non-static value cannot be converted to JSON Schema.`);break}case`regex`:i(e,`pattern`,a.regex),a.flags&&t(s,`"regex" rule flags ("${a.flags}") cannot be represented in JSON Schema's "pattern" keyword and were ignored.`);break;case`email`:i(e,`format`,`email`);break;case`is_numeric`:i(e,`pattern`,`^[+-]?\\d+(\\.\\d+)?$`);break;case`equals`:{let r=n(a.equals);r.ok?i(e,`const`,r.value):t(s,`"equals" rule with a non-static value cannot be converted to JSON Schema.`);break}case`includes`:{let c=n(a.includes);if(!c.ok){t(s,`"includes" rule with a non-static value cannot be converted to JSON Schema.`);break}o===`array`?i(e,`contains`,{const:c.value}):i(e,`pattern`,r(String(c.value)));break}case`not_includes`:{let c=n(a.notIncludes);if(!c.ok){t(s,`"not_includes" rule with a non-static value cannot be converted to JSON Schema.`);break}i(e,`not`,o===`array`?{contains:{const:c.value}}:{pattern:r(String(c.value))});break}case`one_of`:{let r=[];for(let e of a.values){let i=n(e);if(!i.ok){t(s,`"one_of" rule with a non-static value cannot be converted to JSON Schema.`);return}r.push(i.value)}i(e,`enum`,r);break}case`not_one_of`:{let r=[];for(let e of a.values){let i=n(e);if(!i.ok){t(s,`"not_one_of" rule with a non-static value cannot be converted to JSON Schema.`);return}r.push(i.value)}i(e,`not`,{enum:r});break}case`mime_type`:{let r=n(a.mimeType);if(!r.ok){t(s,`"mime_type" rule with a non-static value cannot be converted to JSON Schema.`);break}typeof r.value==`string`?i(e,`contentMediaType`,r.value):Array.isArray(r.value)&&r.value.length===1?i(e,`contentMediaType`,r.value[0]):t(s,`"mime_type" rule with multiple mime types has no JSON Schema equivalent.`);break}case`min_date`:case`max_date`:case`before`:case`after`:t(s,`"${a.type}" rule has no JSON Schema equivalent (date comparisons aren't representable).`);break;case`min_size`:case`max_size`:t(s,`"${a.type}" rule has no JSON Schema equivalent (byte-size constraints aren't representable).`);break;case`custom`:t(s,`"custom" rule "${a.name}" has no JSON Schema equivalent.`);break;case`conditional`:t(s,`"conditional" rule has no JSON Schema equivalent.`);break;default:t(s,`Unknown rule cannot be converted to JSON Schema.`)}}function o(e,t,n,r){for(let i of t??[])a(e,i,n,r)}function s(e){let t=e.included===void 0||e.included===!0,n=e.required===void 0||e.required===!0;return t&&n}function c(e){return typeof e}function l(e,t){e.enum=t;let n=Array.from(new Set(t.map(c)));n.length===1?e.type=n[0]:n.length>1&&(e.type=n)}function u(e,n){let r=[],i=e=>{if(typeof e!=`object`){r.push(e);return}e.enabled!==!1&&(typeof e.enabled==`object`&&t(n,`An "options" entry has a predicate-based "enabled" flag; whether it's enabled can't be determined statically, so it was included in the JSON Schema "enum" anyway.`),i(e.value))};for(let t of e)i(t);return r}function d(e){return typeof e==`object`&&!!e&&`type`in e}function f(t,n){return t.included===!1?!0:n.mode===`input`&&t.type===e.EXPRESSION}function p(e,t){return e.private===!0?{oneOf:[{type:`object`,properties:{state:{const:`plain`},value:t},required:[`state`]},{type:`object`,properties:{state:{const:`masked`},value:{type:`string`}},required:[`state`,`value`]}]}:t}function m(e,t){e.default!==void 0&&(t.default=e.default instanceof Date?e.default.toISOString():e.default)}function h(e,t){let n=g(e,t);return m(e,n),p(e,n)}function g(n,r){switch(n.type){case e.STRING:{let e={type:`string`};return o(e,n.rules,n.type,r),e}case e.NUMBER:{let e={type:`number`};return o(e,n.rules,n.type,r),e}case e.BOOLEAN:{let e={type:`boolean`};return o(e,n.rules,n.type,r),e}case e.DATE:{let e={type:`string`,format:`date-time`};return o(e,n.rules,n.type,r),e}case e.LITERAL:return{const:n.value};case e.ENUM:{let e={};return l(e,Object.values(n.enum)),o(e,n.rules,n.type,r),e}case e.OPTIONS:{let e={};return l(e,u(n.options,r)),o(e,n.rules,n.type,r),e}case e.FILE:{let e={type:`string`};return o(e,n.rules,n.type,r),e}case e.EXPRESSION:return{};case e.ARRAY:{let e={type:`array`,items:h(n.schema,r)};return o(e,n.rules,n.type,r),e}case e.OBJECT:{let e={},t=[];for(let[i,a]of Object.entries(n.fields))f(a,r)||(e[i]=h(a,r),s(a)&&t.push(i));let i={type:`object`,properties:e};return t.length>0&&(i.required=t),o(i,n.rules,n.type,r),i}case e.DISCRIMINATED_UNION:return{oneOf:n.schemas.map(e=>{let t={[n.key]:{const:e[n.key]}},i=[n.key];for(let[a,o]of Object.entries(e))a===n.key||!d(o)||f(o,r)||(t[a]=h(o,r),s(o)&&i.push(a));return{type:`object`,properties:t,required:i}})};default:return t(r,`Unknown schema type cannot be converted to JSON Schema.`),{}}}function _(e,t={}){return{$schema:`https://json-schema.org/draft/2020-12/schema`,...h(e,{errorMode:t.errorMode??`warn`,mode:t.mode??`input`})}}export{_ as toStandardJsonSchema};
1
+ import{SchemaType as e}from"dynz";function t(e,t){if(e.errorMode===`throw`)throw Error(t);e.errorMode===`warn`&&console.warn(`[@dynz/to-json-schema] ${t}`)}function n(e){return e!==void 0&&typeof e==`object`&&`type`in e&&e.type===`st`?{ok:!0,value:e.value}:{ok:!1}}function r(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function i(e,t,n){if(e[t]===void 0){e[t]=n;return}e.allOf=[...e.allOf??[],{[t]:n}]}function a(e,a,o,s){switch(a.type){case`min`:{let r=n(a.min);r.ok?i(e,`minimum`,r.value):t(s,`"min" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max`:{let r=n(a.max);r.ok?i(e,`maximum`,r.value):t(s,`"max" rule with a non-static value cannot be converted to JSON Schema.`);break}case`min_length`:{let r=n(a.min);r.ok?o===`array`?i(e,`minItems`,r.value):i(e,`minLength`,r.value):t(s,`"min_length" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_length`:{let r=n(a.max);r.ok?o===`array`?i(e,`maxItems`,r.value):i(e,`maxLength`,r.value):t(s,`"max_length" rule with a non-static value cannot be converted to JSON Schema.`);break}case`min_entries`:{let r=n(a.min);r.ok?i(e,`minProperties`,r.value):t(s,`"min_entries" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_entries`:{let r=n(a.max);r.ok?i(e,`maxProperties`,r.value):t(s,`"max_entries" rule with a non-static value cannot be converted to JSON Schema.`);break}case`max_precision`:{let r=n(a.maxPrecision);r.ok?i(e,`multipleOf`,1/10**r.value):t(s,`"max_precision" rule with a non-static value cannot be converted to JSON Schema.`);break}case`regex`:i(e,`pattern`,a.regex),a.flags&&t(s,`"regex" rule flags ("${a.flags}") cannot be represented in JSON Schema's "pattern" keyword and were ignored.`);break;case`email`:i(e,`format`,`email`);break;case`is_numeric`:i(e,`pattern`,`^[+-]?\\d+(\\.\\d+)?$`);break;case`equals`:{let r=n(a.equals);r.ok?i(e,`const`,r.value):t(s,`"equals" rule with a non-static value cannot be converted to JSON Schema.`);break}case`not_equals`:{let r=n(a.notEquals);r.ok?i(e,`not`,{const:r.value}):t(s,`"not_equals" rule with a non-static value cannot be converted to JSON Schema.`);break}case`includes`:{let c=n(a.includes);if(!c.ok){t(s,`"includes" rule with a non-static value cannot be converted to JSON Schema.`);break}o===`array`?i(e,`contains`,{const:c.value}):i(e,`pattern`,r(String(c.value)));break}case`not_includes`:{let c=n(a.notIncludes);if(!c.ok){t(s,`"not_includes" rule with a non-static value cannot be converted to JSON Schema.`);break}i(e,`not`,o===`array`?{contains:{const:c.value}}:{pattern:r(String(c.value))});break}case`one_of`:{let r=[];for(let e of a.values){let i=n(e);if(!i.ok){t(s,`"one_of" rule with a non-static value cannot be converted to JSON Schema.`);return}r.push(i.value)}i(e,`enum`,r);break}case`not_one_of`:{let r=[];for(let e of a.values){let i=n(e);if(!i.ok){t(s,`"not_one_of" rule with a non-static value cannot be converted to JSON Schema.`);return}r.push(i.value)}i(e,`not`,{enum:r});break}case`mime_type`:{let r=n(a.mimeType);if(!r.ok){t(s,`"mime_type" rule with a non-static value cannot be converted to JSON Schema.`);break}typeof r.value==`string`?i(e,`contentMediaType`,r.value):Array.isArray(r.value)&&r.value.length===1?i(e,`contentMediaType`,r.value[0]):t(s,`"mime_type" rule with multiple mime types has no JSON Schema equivalent.`);break}case`min_date`:case`max_date`:case`before`:case`after`:case`same_calendar`:case`boundary_day`:t(s,`"${a.type}" rule has no JSON Schema equivalent (date comparisons aren't representable).`);break;case`min_size`:case`max_size`:t(s,`"${a.type}" rule has no JSON Schema equivalent (byte-size constraints aren't representable).`);break;case`custom`:t(s,`"custom" rule "${a.name}" has no JSON Schema equivalent.`);break;case`conditional`:t(s,`"conditional" rule has no JSON Schema equivalent.`);break;case`satisfies`:t(s,`"satisfies" rule has no JSON Schema equivalent.`);break;default:t(s,`Unknown rule cannot be converted to JSON Schema.`)}}function o(e,t,n,r){for(let i of t??[])a(e,i,n,r)}function s(e){let t=e.included===void 0||e.included===!0,n=e.required===void 0||e.required===!0,r=`default`in e&&e.default!==void 0;return t&&n&&!r}function c(e){return typeof e}function l(e,t){e.enum=t;let n=Array.from(new Set(t.map(c)));n.length===1?e.type=n[0]:n.length>1&&(e.type=n)}function u(e,n){let r=[];for(let i of e){if(typeof i!=`object`){r.push(i);continue}i.enabled!==!1&&(typeof i.enabled==`object`&&t(n,`An "options" entry has a predicate-based "enabled" flag; whether it's enabled can't be determined statically, so it was included in the JSON Schema "enum" anyway.`),r.push(i.value))}return r}function d(e){return typeof e==`object`&&!!e&&`type`in e}function f(e){return{anyOf:[e,{type:`null`}]}}function p(e,t){return t?{const:e,type:e===null?`null`:typeof e}:{const:e}}function m(e,t){let n={},r=[];for(let[i,a]of Object.entries(e)){if(h(a,t))continue;let e=y(a,t),o=s(a);n[i]=t.strict&&!o?f(e):e,(o||t.strict)&&r.push(i)}return{properties:n,required:r}}function h(t,n){return t.included===!1?!0:n.mode===`input`&&t.type===e.EXPRESSION}function g(e,t,n){if(e.private!==!0)return t;let r={type:`object`,properties:{state:p(`plain`,n.strict),value:t},required:n.strict?[`state`,`value`]:[`state`]},i={type:`object`,properties:{state:p(`masked`,n.strict),value:{type:`string`}},required:[`state`,`value`]};return n.strict&&(r.additionalProperties=!1,i.additionalProperties=!1),{[n.unionKeyword]:[r,i]}}function _(e,t){e.default!==void 0&&(t.default=e.default instanceof Date?e.default.toISOString():e.default)}function v(e,t){let n=e.meta;if(!n)return;let{id:r,title:i,description:a,deprecated:o,...s}=n;r!==void 0&&(t.$id=r),i!==void 0&&(t.title=i),a!==void 0&&(t.description=a),o!==void 0&&(t.deprecated=o),Object.assign(t,s)}function y(e,t){let n=b(e,t);return _(e,n),v(e,n),g(e,n,t)}function b(n,r){switch(n.type){case e.STRING:{let e={type:`string`};return o(e,n.rules,n.type,r),e}case e.NUMBER:{let e={type:`number`};return o(e,n.rules,n.type,r),e}case e.BOOLEAN:{let e={type:`boolean`};return o(e,n.rules,n.type,r),e}case e.DATE:{let e={type:`string`,format:`date-time`};return o(e,n.rules,n.type,r),e}case e.LITERAL:return p(n.value,r.strict);case e.ENUM:{let e={};return l(e,Object.values(n.enum)),o(e,n.rules,n.type,r),e}case e.OPTIONS:{let e={};return l(e,u(n.options,r)),o(e,n.rules,n.type,r),e}case e.FILE:{let e={type:`string`};return o(e,n.rules,n.type,r),e}case e.EXPRESSION:return{};case e.ARRAY:{let e={type:`array`,items:y(n.schema,r)};return o(e,n.rules,n.type,r),e}case e.OBJECT:{let{properties:e,required:t}=m(n.fields,r),i={type:`object`,properties:e};return t.length>0&&(i.required=t),r.strict&&(i.additionalProperties=!1),o(i,n.rules,n.type,r),i}case e.DISCRIMINATED_UNION:{let e=n.schemas.map(e=>{let t={};for(let[r,i]of Object.entries(e))r!==n.key&&d(i)&&(t[r]=i);let{properties:i,required:a}=m(t,r);i[n.key]=p(e[n.key],r.strict),a.unshift(n.key);let o={type:`object`,properties:i,required:a};return r.strict&&(o.additionalProperties=!1),o});return{[r.unionKeyword]:e}}default:return t(r,`Unknown schema type cannot be converted to JSON Schema.`),{}}}function x(e,t={}){return{$schema:`https://json-schema.org/draft/2020-12/schema`,...y(e,{errorMode:t.errorMode??`warn`,mode:t.mode??`input`,strict:t.strict??!0,unionKeyword:t.unionKeyword??`oneOf`})}}export{x as toStandardJsonSchema};
package/dist/index.mjs CHANGED
@@ -114,6 +114,12 @@ function applyRule(jsonSchema, rule, schemaType, context) {
114
114
  else reportIssue(context, `"equals" rule with a non-static value cannot be converted to JSON Schema.`);
115
115
  break;
116
116
  }
117
+ case "not_equals": {
118
+ const notEquals = resolveStatic(rule.notEquals);
119
+ if (notEquals.ok) setKeyword(jsonSchema, "not", { const: notEquals.value });
120
+ else reportIssue(context, `"not_equals" rule with a non-static value cannot be converted to JSON Schema.`);
121
+ break;
122
+ }
117
123
  case "includes": {
118
124
  const includes = resolveStatic(rule.includes);
119
125
  if (!includes.ok) {
@@ -174,6 +180,8 @@ function applyRule(jsonSchema, rule, schemaType, context) {
174
180
  case "max_date":
175
181
  case "before":
176
182
  case "after":
183
+ case "same_calendar":
184
+ case "boundary_day":
177
185
  reportIssue(context, `"${rule.type}" rule has no JSON Schema equivalent (date comparisons aren't representable).`);
178
186
  break;
179
187
  case "min_size":
@@ -186,6 +194,9 @@ function applyRule(jsonSchema, rule, schemaType, context) {
186
194
  case "conditional":
187
195
  reportIssue(context, `"conditional" rule has no JSON Schema equivalent.`);
188
196
  break;
197
+ case "satisfies":
198
+ reportIssue(context, `"satisfies" rule has no JSON Schema equivalent.`);
199
+ break;
189
200
  default: reportIssue(context, `Unknown rule cannot be converted to JSON Schema.`);
190
201
  }
191
202
  }
@@ -198,7 +209,8 @@ function applyRules(jsonSchema, rules, schemaType, context) {
198
209
  function isMandatory(schema) {
199
210
  const isIncluded = schema.included === void 0 || schema.included === true;
200
211
  const isRequired = schema.required === void 0 || schema.required === true;
201
- return isIncluded && isRequired;
212
+ const hasDefault = "default" in schema && schema.default !== void 0;
213
+ return isIncluded && isRequired && !hasDefault;
202
214
  }
203
215
  function jsonTypeOf(value) {
204
216
  return typeof value;
@@ -211,47 +223,101 @@ function applyEnumValues(jsonSchema, values) {
211
223
  }
212
224
  function collectOptionValues(options, context) {
213
225
  const values = [];
214
- const visit = (option) => {
226
+ for (const option of options) {
215
227
  if (typeof option !== "object") {
216
228
  values.push(option);
217
- return;
229
+ continue;
218
230
  }
219
- if (option.enabled === false) return;
231
+ if (option.enabled === false) continue;
220
232
  if (typeof option.enabled === "object") reportIssue(context, `An "options" entry has a predicate-based "enabled" flag; whether it's enabled can't be determined statically, so it was included in the JSON Schema "enum" anyway.`);
221
- visit(option.value);
222
- };
223
- for (const option of options) visit(option);
233
+ values.push(option.value);
234
+ }
224
235
  return values;
225
236
  }
226
237
  function isSchema(value) {
227
238
  return typeof value === "object" && value !== null && "type" in value;
228
239
  }
240
+ /**
241
+ * Wraps a property's schema so it also accepts `null` — strict mode's only
242
+ * way to express "may be absent", since every property must be listed in
243
+ * `required`.
244
+ */
245
+ function withNullable(jsonSchema) {
246
+ return { anyOf: [jsonSchema, { type: "null" }] };
247
+ }
248
+ /**
249
+ * A `const` node, paired with its inferred `type` under `strict` (strict
250
+ * mode requires `type` everywhere; a bare `const` has none otherwise).
251
+ */
252
+ function constNode(value, strict) {
253
+ return strict ? {
254
+ const: value,
255
+ type: value === null ? "null" : typeof value
256
+ } : { const: value };
257
+ }
258
+ /**
259
+ * Converts a set of object fields into `properties`/`required`, shared by
260
+ * the OBJECT case and each `discriminatedUnion()` member. Under `strict`,
261
+ * every non-omitted field is listed in `required` — one that isn't
262
+ * otherwise mandatory has its schema widened to also accept `null`.
263
+ */
264
+ function convertObjectFields(fields, context) {
265
+ const properties = {};
266
+ const required = [];
267
+ for (const [key, fieldSchema] of Object.entries(fields)) {
268
+ if (shouldOmitField(fieldSchema, context)) continue;
269
+ const propertySchema = convertSchema(fieldSchema, context);
270
+ const mandatory = isMandatory(fieldSchema);
271
+ properties[key] = context.strict && !mandatory ? withNullable(propertySchema) : propertySchema;
272
+ if (mandatory || context.strict) required.push(key);
273
+ }
274
+ return {
275
+ properties,
276
+ required
277
+ };
278
+ }
229
279
  function shouldOmitField(schema, context) {
230
280
  if (schema.included === false) return true;
231
281
  return context.mode === "input" && schema.type === SchemaType.EXPRESSION;
232
282
  }
233
- function applyPrivacyWrapper(schema, innerSchema) {
283
+ function applyPrivacyWrapper(schema, innerSchema, context) {
234
284
  if (schema.private !== true) return innerSchema;
235
- return { oneOf: [{
285
+ const plain = {
236
286
  type: "object",
237
287
  properties: {
238
- state: { const: "plain" },
288
+ state: constNode("plain", context.strict),
239
289
  value: innerSchema
240
290
  },
241
- required: ["state"]
242
- }, {
291
+ required: context.strict ? ["state", "value"] : ["state"]
292
+ };
293
+ const masked = {
243
294
  type: "object",
244
295
  properties: {
245
- state: { const: "masked" },
296
+ state: constNode("masked", context.strict),
246
297
  value: { type: "string" }
247
298
  },
248
299
  required: ["state", "value"]
249
- }] };
300
+ };
301
+ if (context.strict) {
302
+ plain.additionalProperties = false;
303
+ masked.additionalProperties = false;
304
+ }
305
+ return { [context.unionKeyword]: [plain, masked] };
250
306
  }
251
307
  function applyDefault(schema, jsonSchema) {
252
308
  if (schema.default === void 0) return;
253
309
  jsonSchema.default = schema.default instanceof Date ? schema.default.toISOString() : schema.default;
254
310
  }
311
+ function applyMeta(schema, jsonSchema) {
312
+ const meta = schema.meta;
313
+ if (!meta) return;
314
+ const { id, title, description, deprecated, ...rest } = meta;
315
+ if (id !== void 0) jsonSchema.$id = id;
316
+ if (title !== void 0) jsonSchema.title = title;
317
+ if (description !== void 0) jsonSchema.description = description;
318
+ if (deprecated !== void 0) jsonSchema.deprecated = deprecated;
319
+ Object.assign(jsonSchema, rest);
320
+ }
255
321
  /**
256
322
  * Converts a dynz schema to a JSON Schema (2020-12) document. Rule values
257
323
  * and schema kinds without a JSON Schema equivalent are handled per
@@ -260,7 +326,8 @@ function applyDefault(schema, jsonSchema) {
260
326
  function convertSchema(schema, context) {
261
327
  const jsonSchema = convertSchemaKind(schema, context);
262
328
  applyDefault(schema, jsonSchema);
263
- return applyPrivacyWrapper(schema, jsonSchema);
329
+ applyMeta(schema, jsonSchema);
330
+ return applyPrivacyWrapper(schema, jsonSchema, context);
264
331
  }
265
332
  function convertSchemaKind(schema, context) {
266
333
  switch (schema.type) {
@@ -287,7 +354,7 @@ function convertSchemaKind(schema, context) {
287
354
  applyRules(jsonSchema, schema.rules, schema.type, context);
288
355
  return jsonSchema;
289
356
  }
290
- case SchemaType.LITERAL: return { const: schema.value };
357
+ case SchemaType.LITERAL: return constNode(schema.value, context.strict);
291
358
  case SchemaType.ENUM: {
292
359
  const jsonSchema = {};
293
360
  applyEnumValues(jsonSchema, Object.values(schema.enum));
@@ -315,35 +382,33 @@ function convertSchemaKind(schema, context) {
315
382
  return jsonSchema;
316
383
  }
317
384
  case SchemaType.OBJECT: {
318
- const properties = {};
319
- const required = [];
320
- for (const [key, fieldSchema] of Object.entries(schema.fields)) {
321
- if (shouldOmitField(fieldSchema, context)) continue;
322
- properties[key] = convertSchema(fieldSchema, context);
323
- if (isMandatory(fieldSchema)) required.push(key);
324
- }
385
+ const { properties, required } = convertObjectFields(schema.fields, context);
325
386
  const jsonSchema = {
326
387
  type: "object",
327
388
  properties
328
389
  };
329
390
  if (required.length > 0) jsonSchema.required = required;
391
+ if (context.strict) jsonSchema.additionalProperties = false;
330
392
  applyRules(jsonSchema, schema.rules, schema.type, context);
331
393
  return jsonSchema;
332
394
  }
333
- case SchemaType.DISCRIMINATED_UNION: return { oneOf: schema.schemas.map((member) => {
334
- const properties = { [schema.key]: { const: member[schema.key] } };
335
- const required = [schema.key];
336
- for (const [key, value] of Object.entries(member)) {
337
- if (key === schema.key || !isSchema(value) || shouldOmitField(value, context)) continue;
338
- properties[key] = convertSchema(value, context);
339
- if (isMandatory(value)) required.push(key);
340
- }
341
- return {
342
- type: "object",
343
- properties,
344
- required
345
- };
346
- }) };
395
+ case SchemaType.DISCRIMINATED_UNION: {
396
+ const members = schema.schemas.map((member) => {
397
+ const memberFields = {};
398
+ for (const [key, value] of Object.entries(member)) if (key !== schema.key && isSchema(value)) memberFields[key] = value;
399
+ const { properties, required } = convertObjectFields(memberFields, context);
400
+ properties[schema.key] = constNode(member[schema.key], context.strict);
401
+ required.unshift(schema.key);
402
+ const jsonSchema = {
403
+ type: "object",
404
+ properties,
405
+ required
406
+ };
407
+ if (context.strict) jsonSchema.additionalProperties = false;
408
+ return jsonSchema;
409
+ });
410
+ return { [context.unionKeyword]: members };
411
+ }
347
412
  default:
348
413
  reportIssue(context, `Unknown schema type cannot be converted to JSON Schema.`);
349
414
  return {};
@@ -359,14 +424,19 @@ function convertSchemaKind(schema, context) {
359
424
  * transformers) and schema kinds without a JSON Schema equivalent are
360
425
  * handled according to `config.errorMode` (default `"warn"`). Whether
361
426
  * `expression` fields are included is controlled by `config.mode`
362
- * (default `"input"`, which omits them).
427
+ * (default `"input"`, which omits them). `config.strict` (default `true`)
428
+ * produces output compatible with LLM structured-output "strict" modes,
429
+ * and `config.unionKeyword` (default `"oneOf"`) controls whether schema-
430
+ * selection unions use `oneOf` or `anyOf`.
363
431
  */
364
432
  function toStandardJsonSchema(schema, config = {}) {
365
433
  return {
366
434
  $schema: "https://json-schema.org/draft/2020-12/schema",
367
435
  ...convertSchema(schema, {
368
436
  errorMode: config.errorMode ?? "warn",
369
- mode: config.mode ?? "input"
437
+ mode: config.mode ?? "input",
438
+ strict: config.strict ?? true,
439
+ unionKeyword: config.unionKeyword ?? "oneOf"
370
440
  })
371
441
  };
372
442
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynz/to-json-schema",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "private": false,
5
5
  "license": "BUSL-1.1",
6
6
  "type": "module",
@@ -39,8 +39,8 @@
39
39
  }
40
40
  }
41
41
  },
42
- "peerDependencies": {
43
- "dynz": "^1.0.0"
42
+ "dependencies": {
43
+ "dynz": "1.2.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "tsdown": "^0.16.5"