effect 3.11.6 → 3.11.8

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.
Files changed (36) hide show
  1. package/dist/cjs/Arbitrary.js +46 -4
  2. package/dist/cjs/Arbitrary.js.map +1 -1
  3. package/dist/cjs/JSONSchema.js +157 -38
  4. package/dist/cjs/JSONSchema.js.map +1 -1
  5. package/dist/cjs/Schema.js +63 -51
  6. package/dist/cjs/Schema.js.map +1 -1
  7. package/dist/cjs/internal/schema/{filters.js → schemaId.js} +4 -2
  8. package/dist/cjs/internal/schema/schemaId.js.map +1 -0
  9. package/dist/cjs/internal/version.js +1 -1
  10. package/dist/dts/Arbitrary.d.ts +5 -1
  11. package/dist/dts/Arbitrary.d.ts.map +1 -1
  12. package/dist/dts/JSONSchema.d.ts +11 -2
  13. package/dist/dts/JSONSchema.d.ts.map +1 -1
  14. package/dist/dts/Schema.d.ts +16 -6
  15. package/dist/dts/Schema.d.ts.map +1 -1
  16. package/dist/dts/internal/schema/schemaId.d.ts +2 -0
  17. package/dist/dts/internal/schema/schemaId.d.ts.map +1 -0
  18. package/dist/esm/Arbitrary.js +44 -3
  19. package/dist/esm/Arbitrary.js.map +1 -1
  20. package/dist/esm/JSONSchema.js +157 -38
  21. package/dist/esm/JSONSchema.js.map +1 -1
  22. package/dist/esm/Schema.js +55 -43
  23. package/dist/esm/Schema.js.map +1 -1
  24. package/dist/esm/internal/schema/{filters.js → schemaId.js} +3 -1
  25. package/dist/esm/internal/schema/schemaId.js.map +1 -0
  26. package/dist/esm/internal/version.js +1 -1
  27. package/package.json +1 -1
  28. package/src/Arbitrary.ts +64 -13
  29. package/src/JSONSchema.ts +185 -40
  30. package/src/Schema.ts +65 -50
  31. package/src/internal/schema/{filters.ts → schemaId.ts} +5 -0
  32. package/src/internal/version.ts +1 -1
  33. package/dist/cjs/internal/schema/filters.js.map +0 -1
  34. package/dist/dts/internal/schema/filters.d.ts +0 -2
  35. package/dist/dts/internal/schema/filters.d.ts.map +0 -1
  36. package/dist/esm/internal/schema/filters.js.map +0 -1
@@ -1,4 +1,6 @@
1
1
  /** @internal */
2
+ export const DateFromSelfSchemaId = /*#__PURE__*/Symbol.for("effect/SchemaId/DateFromSelf");
3
+ /** @internal */
2
4
  export const GreaterThanSchemaId = /*#__PURE__*/Symbol.for("effect/SchemaId/GreaterThan");
3
5
  /** @internal */
4
6
  export const GreaterThanOrEqualToSchemaId = /*#__PURE__*/Symbol.for("effect/SchemaId/GreaterThanOrEqualTo");
@@ -38,4 +40,4 @@ export const MinItemsSchemaId = /*#__PURE__*/Symbol.for("effect/SchemaId/MinItem
38
40
  export const MaxItemsSchemaId = /*#__PURE__*/Symbol.for("effect/SchemaId/MaxItems");
39
41
  /** @internal */
40
42
  export const ItemsCountSchemaId = /*#__PURE__*/Symbol.for("effect/SchemaId/ItemsCount");
41
- //# sourceMappingURL=filters.js.map
43
+ //# sourceMappingURL=schemaId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemaId.js","names":["DateFromSelfSchemaId","Symbol","for","GreaterThanSchemaId","GreaterThanOrEqualToSchemaId","LessThanSchemaId","LessThanOrEqualToSchemaId","IntSchemaId","NonNaNSchemaId","FiniteSchemaId","JsonNumberSchemaId","BetweenSchemaId","GreaterThanBigintSchemaId","GreaterThanOrEqualToBigIntSchemaId","LessThanBigIntSchemaId","LessThanOrEqualToBigIntSchemaId","BetweenBigintSchemaId","MinLengthSchemaId","MaxLengthSchemaId","LengthSchemaId","MinItemsSchemaId","MaxItemsSchemaId","ItemsCountSchemaId"],"sources":["../../../../src/internal/schema/schemaId.ts"],"sourcesContent":[null],"mappings":"AAEA;AACA,OAAO,MAAMA,oBAAoB,gBAAgCC,MAAM,CAACC,GAAG,CACzE,8BAA8B,CACA;AAEhC;AACA,OAAO,MAAMC,mBAAmB,gBAA+BF,MAAM,CAACC,GAAG,CACvE,6BAA6B,CACA;AAE/B;AACA,OAAO,MAAME,4BAA4B,gBAAwCH,MAAM,CAACC,GAAG,CACzF,sCAAsC,CACA;AAExC;AACA,OAAO,MAAMG,gBAAgB,gBAA4BJ,MAAM,CAACC,GAAG,CACjE,0BAA0B,CACA;AAE5B;AACA,OAAO,MAAMI,yBAAyB,gBAAqCL,MAAM,CAACC,GAAG,CACnF,mCAAmC,CACA;AAErC;AACA,OAAO,MAAMK,WAAW,gBAAuBN,MAAM,CAACC,GAAG,CACvD,qBAAqB,CACA;AAEvB;AACA,OAAO,MAAMM,cAAc,gBAA0BP,MAAM,CAACC,GAAG,CAC7D,wBAAwB,CACA;AAE1B;AACA,OAAO,MAAMO,cAAc,gBAA0BR,MAAM,CAACC,GAAG,CAC7D,wBAAwB,CACA;AAE1B;AACA,OAAO,MAAMQ,kBAAkB,gBAA8BT,MAAM,CAACC,GAAG,CACrE,4BAA4B,CACA;AAE9B;AACA,OAAO,MAAMS,eAAe,gBAA2BV,MAAM,CAACC,GAAG,CAC/D,yBAAyB,CACA;AAE3B;AACA,OAAO,MAAMU,yBAAyB,gBAAqCX,MAAM,CAACC,GAAG,CACnF,mCAAmC,CACA;AAErC;AACA,OAAO,MAAMW,kCAAkC,gBAA8CZ,MAAM,CAACC,GAAG,CACrG,4CAA4C,CACA;AAE9C;AACA,OAAO,MAAMY,sBAAsB,gBAAkCb,MAAM,CAACC,GAAG,CAC7E,gCAAgC,CACA;AAElC;AACA,OAAO,MAAMa,+BAA+B,gBAA2Cd,MAAM,CAACC,GAAG,CAC/F,yCAAyC,CACA;AAE3C;AACA,OAAO,MAAMc,qBAAqB,gBAAiCf,MAAM,CAACC,GAAG,CAC3E,+BAA+B,CACA;AAEjC;AACA,OAAO,MAAMe,iBAAiB,gBAA6BhB,MAAM,CAACC,GAAG,CACnE,2BAA2B,CACA;AAE7B;AACA,OAAO,MAAMgB,iBAAiB,gBAA6BjB,MAAM,CAACC,GAAG,CACnE,2BAA2B,CACA;AAE7B;AACA,OAAO,MAAMiB,cAAc,gBAA0BlB,MAAM,CAACC,GAAG,CAC7D,wBAAwB,CACA;AAE1B;AACA,OAAO,MAAMkB,gBAAgB,gBAA4BnB,MAAM,CAACC,GAAG,CACjE,0BAA0B,CACA;AAE5B;AACA,OAAO,MAAMmB,gBAAgB,gBAA4BpB,MAAM,CAACC,GAAG,CACjE,0BAA0B,CACA;AAE5B;AACA,OAAO,MAAMoB,kBAAkB,gBAA8BrB,MAAM,CAACC,GAAG,CACrE,4BAA4B,CACA","ignoreList":[]}
@@ -1,4 +1,4 @@
1
- let moduleVersion = "3.11.6";
1
+ let moduleVersion = "3.11.8";
2
2
  export const getCurrentVersion = () => moduleVersion;
3
3
  export const setCurrentVersion = version => {
4
4
  moduleVersion = version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect",
3
- "version": "3.11.6",
3
+ "version": "3.11.8",
4
4
  "description": "The missing standard library for TypeScript, for writing production-grade software.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/Arbitrary.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  import * as Arr from "./Array.js"
6
6
  import * as FastCheck from "./FastCheck.js"
7
7
  import * as errors_ from "./internal/schema/errors.js"
8
- import * as filters_ from "./internal/schema/filters.js"
8
+ import * as schemaId_ from "./internal/schema/schemaId.js"
9
9
  import * as util_ from "./internal/schema/util.js"
10
10
  import * as Option from "./Option.js"
11
11
  import * as Predicate from "./Predicate.js"
@@ -28,7 +28,7 @@ export interface LazyArbitrary<A> {
28
28
  export interface ArbitraryGenerationContext {
29
29
  readonly maxDepth: number
30
30
  readonly depthIdentifier?: string
31
- readonly constraints?: StringConstraints | NumberConstraints | BigIntConstraints | ArrayConstraints
31
+ readonly constraints?: StringConstraints | NumberConstraints | BigIntConstraints | DateConstraints | ArrayConstraints
32
32
  }
33
33
 
34
34
  /**
@@ -110,6 +110,8 @@ class Deferred {
110
110
  }
111
111
  case "BigIntConstraints":
112
112
  return (fc) => fc.bigInt(config.constraints)
113
+ case "DateConstraints":
114
+ return (fc) => fc.date(config.constraints)
113
115
  case "ArrayConstraints":
114
116
  return goTupleType(config.ast, ctx, path, config.constraints)
115
117
  }
@@ -232,6 +234,32 @@ export const makeArrayConstraints = (options: {
232
234
  return out
233
235
  }
234
236
 
237
+ interface DateConstraints {
238
+ readonly _tag: "DateConstraints"
239
+ readonly constraints: FastCheck.DateConstraints
240
+ }
241
+
242
+ /** @internal */
243
+ export const makeDateConstraints = (options: {
244
+ readonly min?: Date | undefined
245
+ readonly max?: Date | undefined
246
+ readonly noInvalidDate?: boolean | undefined
247
+ }): DateConstraints => {
248
+ const out: Types.Mutable<DateConstraints> = {
249
+ _tag: "DateConstraints",
250
+ constraints: {
251
+ noInvalidDate: options.noInvalidDate ?? false
252
+ }
253
+ }
254
+ if (Predicate.isDate(options.min)) {
255
+ out.constraints.min = options.min
256
+ }
257
+ if (Predicate.isDate(options.max)) {
258
+ out.constraints.max = options.max
259
+ }
260
+ return out
261
+ }
262
+
235
263
  interface ArrayConfig extends ArrayConstraints {
236
264
  readonly ast: AST.TupleType
237
265
  }
@@ -246,7 +274,7 @@ const makeArrayConfig = (options: {
246
274
  }
247
275
  }
248
276
 
249
- type Config = StringConstraints | NumberConstraints | BigIntConstraints | ArrayConfig
277
+ type Config = StringConstraints | NumberConstraints | BigIntConstraints | DateConstraints | ArrayConfig
250
278
 
251
279
  const go = (
252
280
  ast: AST.AST,
@@ -268,6 +296,9 @@ const go = (
268
296
  return hook.value(ctx)
269
297
  }
270
298
  }
299
+ if (AST.isDeclaration(ast)) {
300
+ throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(path, ast))
301
+ }
271
302
  const op = toOp(ast, ctx, path)
272
303
  switch (op._tag) {
273
304
  case "Succeed":
@@ -288,8 +319,14 @@ export const toOp = (
288
319
  path: ReadonlyArray<PropertyKey>
289
320
  ): Op => {
290
321
  switch (ast._tag) {
291
- case "Declaration":
292
- throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(path, ast))
322
+ case "Declaration": {
323
+ const TypeAnnotationId: any = ast.annotations[AST.SchemaIdAnnotationId]
324
+ switch (TypeAnnotationId) {
325
+ case schemaId_.DateFromSelfSchemaId:
326
+ return new Deferred(makeDateConstraints(ast.annotations[TypeAnnotationId] as any))
327
+ }
328
+ return new Succeed(go(ast, ctx, path))
329
+ }
293
330
  case "Literal":
294
331
  return new Succeed((fc) => fc.constant(ast.literal))
295
332
  case "UniqueSymbol":
@@ -479,7 +516,7 @@ const goTupleType = (
479
516
  }
480
517
  }
481
518
 
482
- type Constraints = StringConstraints | NumberConstraints | BigIntConstraints | ArrayConstraints
519
+ type Constraints = StringConstraints | NumberConstraints | BigIntConstraints | DateConstraints | ArrayConstraints
483
520
 
484
521
  const getConstraints = (_tag: Constraints["_tag"], ast: AST.Refinement): Constraints | undefined => {
485
522
  const TypeAnnotationId: any = ast.annotations[AST.SchemaIdAnnotationId]
@@ -490,7 +527,7 @@ const getConstraints = (_tag: Constraints["_tag"], ast: AST.Refinement): Constra
490
527
  return makeStringConstraints(jsonSchema)
491
528
  case "NumberConstraints": {
492
529
  switch (TypeAnnotationId) {
493
- case filters_.NonNaNSchemaId:
530
+ case schemaId_.NonNaNSchemaId:
494
531
  return makeNumberConstraints({ noNaN: true })
495
532
  default:
496
533
  return makeNumberConstraints({
@@ -506,6 +543,8 @@ const getConstraints = (_tag: Constraints["_tag"], ast: AST.Refinement): Constra
506
543
  }
507
544
  case "BigIntConstraints":
508
545
  return makeBigIntConstraints(ast.annotations[TypeAnnotationId] as any)
546
+ case "DateConstraints":
547
+ return makeDateConstraints(ast.annotations[TypeAnnotationId] as any)
509
548
  case "ArrayConstraints":
510
549
  return makeArrayConstraints({
511
550
  minLength: jsonSchema.minItems,
@@ -514,21 +553,23 @@ const getConstraints = (_tag: Constraints["_tag"], ast: AST.Refinement): Constra
514
553
  }
515
554
  }
516
555
 
556
+ function getMax(n1: Date | undefined, n2: Date | undefined): Date | undefined
517
557
  function getMax(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined
518
558
  function getMax(n1: number | undefined, n2: number | undefined): number | undefined
519
559
  function getMax(
520
- n1: bigint | number | undefined,
521
- n2: bigint | number | undefined
522
- ): bigint | number | undefined {
560
+ n1: bigint | number | Date | undefined,
561
+ n2: bigint | number | Date | undefined
562
+ ): bigint | number | Date | undefined {
523
563
  return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n2 : n1
524
564
  }
525
565
 
566
+ function getMin(n1: Date | undefined, n2: Date | undefined): Date | undefined
526
567
  function getMin(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined
527
568
  function getMin(n1: number | undefined, n2: number | undefined): number | undefined
528
569
  function getMin(
529
- n1: bigint | number | undefined,
530
- n2: bigint | number | undefined
531
- ): bigint | number | undefined {
570
+ n1: bigint | number | Date | undefined,
571
+ n2: bigint | number | Date | undefined
572
+ ): bigint | number | Date | undefined {
532
573
  return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n1 : n2
533
574
  }
534
575
 
@@ -572,6 +613,16 @@ const merge = (c1: Config, c2: Constraints | undefined): Config => {
572
613
  }
573
614
  break
574
615
  }
616
+ case "DateConstraints": {
617
+ if (c2._tag === "DateConstraints") {
618
+ return makeDateConstraints({
619
+ min: getMax(c1.constraints.min, c2.constraints.min),
620
+ max: getMin(c1.constraints.max, c2.constraints.max),
621
+ noInvalidDate: getOr(c1.constraints.noInvalidDate, c2.constraints.noInvalidDate)
622
+ })
623
+ }
624
+ break
625
+ }
575
626
  case "ArrayConstraints": {
576
627
  if (c2._tag === "ArrayConstraints") {
577
628
  return makeArrayConfig({
package/src/JSONSchema.ts CHANGED
@@ -85,6 +85,14 @@ export interface JsonSchema7Ref extends JsonSchemaAnnotations {
85
85
  $ref: string
86
86
  }
87
87
 
88
+ /**
89
+ * @category model
90
+ * @since 3.11.7
91
+ */
92
+ export interface JsonSchema7Null extends JsonSchemaAnnotations {
93
+ type: "null"
94
+ }
95
+
88
96
  /**
89
97
  * @category model
90
98
  * @since 3.10.0
@@ -163,7 +171,8 @@ export interface JsonSchema7Array extends JsonSchemaAnnotations {
163
171
  * @since 3.10.0
164
172
  */
165
173
  export interface JsonSchema7Enum extends JsonSchemaAnnotations {
166
- enum: Array<AST.LiteralValue>
174
+ type?: "string" | "number" | "boolean"
175
+ enum: Array<string | number | boolean>
167
176
  }
168
177
 
169
178
  /**
@@ -173,6 +182,7 @@ export interface JsonSchema7Enum extends JsonSchemaAnnotations {
173
182
  export interface JsonSchema7Enums extends JsonSchemaAnnotations {
174
183
  $comment: "/schemas/enums"
175
184
  anyOf: Array<{
185
+ type: "string" | "number"
176
186
  title: string
177
187
  enum: [string | number]
178
188
  }>
@@ -211,6 +221,7 @@ export type JsonSchema7 =
211
221
  | JsonSchema7object
212
222
  | JsonSchema7empty
213
223
  | JsonSchema7Ref
224
+ | JsonSchema7Null
214
225
  | JsonSchema7String
215
226
  | JsonSchema7Number
216
227
  | JsonSchema7Integer
@@ -240,11 +251,17 @@ export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): JsonSchema7Root =
240
251
  // Special case top level `parseJson` transformations
241
252
  ? schema.ast.to
242
253
  : schema.ast
243
- const out: JsonSchema7Root = fromAST(ast, {
254
+ const jsonSchema = fromAST(ast, {
244
255
  definitions
245
256
  })
246
- out.$schema = $schema
247
- if (!Record.isEmptyRecord(definitions)) {
257
+ const out: JsonSchema7Root = {
258
+ $schema,
259
+ $defs: {},
260
+ ...jsonSchema
261
+ }
262
+ if (Record.isEmptyRecord(definitions)) {
263
+ delete out.$defs
264
+ } else {
248
265
  out.$defs = definitions
249
266
  }
250
267
  return out
@@ -405,10 +422,31 @@ const isOverrideAnnotation = (jsonSchema: JsonSchema7): boolean => {
405
422
  ("enum" in jsonSchema) || ("$ref" in jsonSchema)
406
423
  }
407
424
 
408
- // Returns true if the schema is an enum with no other properties.
409
- // This is used to merge enums together.
410
- const isEnumOnly = (schema: JsonSchema7): schema is JsonSchema7Enum =>
411
- "enum" in schema && Object.keys(schema).length === 1
425
+ // Returns true if the schema is an enum with no other properties other than the
426
+ // optional "type". This is used to merge enums together.
427
+ const isMergeableEnum = (jsonSchema: JsonSchema7): jsonSchema is JsonSchema7Enum => {
428
+ const len = Object.keys(jsonSchema).length
429
+ return "enum" in jsonSchema && (len === 1 || ("type" in jsonSchema && len === 2))
430
+ }
431
+
432
+ // Some validators do not support enums without a type keyword. This function
433
+ // adds a type keyword to the schema if it is missing and the enum values are
434
+ // homogeneous.
435
+ const addEnumType = (jsonSchema: JsonSchema7): JsonSchema7 => {
436
+ if ("enum" in jsonSchema && !("type" in jsonSchema)) {
437
+ const type: "string" | "number" | "boolean" | undefined = jsonSchema.enum.every(Predicate.isString) ?
438
+ "string" :
439
+ jsonSchema.enum.every(Predicate.isNumber) ?
440
+ "number" :
441
+ jsonSchema.enum.every(Predicate.isBoolean) ?
442
+ "boolean" :
443
+ undefined
444
+ if (type !== undefined) {
445
+ return { type, ...jsonSchema }
446
+ }
447
+ }
448
+ return jsonSchema
449
+ }
412
450
 
413
451
  const mergeRefinements = (from: any, jsonSchema: any, annotations: any): any => {
414
452
  const out: any = { ...from, ...annotations, ...jsonSchema }
@@ -438,15 +476,54 @@ const mergeRefinements = (from: any, jsonSchema: any, annotations: any): any =>
438
476
  return out
439
477
  }
440
478
 
479
+ type Options = {
480
+ readonly getRef: (id: string) => string
481
+ readonly target: Target
482
+ }
483
+
484
+ type Path = ReadonlyArray<PropertyKey>
485
+
486
+ const isContentSchemaSupported = (options: Options) => options.target !== "jsonSchema7"
487
+
488
+ const isNullTypeKeywordSupported = (options: Options) => options.target !== "openApi3.1"
489
+
490
+ // https://swagger.io/docs/specification/v3_0/data-models/data-types/#null
491
+ const isNullableKeywordSupported = (options: Options) => options.target === "openApi3.1"
492
+
493
+ const isNeverJSONSchema = (jsonSchema: JsonSchema7): jsonSchema is JsonSchema7Never =>
494
+ "$id" in jsonSchema && jsonSchema.$id === "/schemas/never"
495
+
496
+ const isAnyJSONSchema = (jsonSchema: JsonSchema7): jsonSchema is JsonSchema7Any =>
497
+ "$id" in jsonSchema && jsonSchema.$id === "/schemas/any"
498
+
499
+ const isUnknownJSONSchema = (jsonSchema: JsonSchema7): jsonSchema is JsonSchema7Unknown =>
500
+ "$id" in jsonSchema && jsonSchema.$id === "/schemas/unknown"
501
+
502
+ const isVoidJSONSchema = (jsonSchema: JsonSchema7): jsonSchema is JsonSchema7Void =>
503
+ "$id" in jsonSchema && jsonSchema.$id === "/schemas/void"
504
+
505
+ const shrink = (members: Array<JsonSchema7>): Array<JsonSchema7> => {
506
+ let i = members.findIndex(isAnyJSONSchema)
507
+ if (i !== -1) {
508
+ members = [members[i]]
509
+ }
510
+ i = members.findIndex(isUnknownJSONSchema)
511
+ if (i !== -1) {
512
+ members = [members[i]]
513
+ }
514
+ i = members.findIndex(isVoidJSONSchema)
515
+ if (i !== -1) {
516
+ members = [members[i]]
517
+ }
518
+ return members
519
+ }
520
+
441
521
  const go = (
442
522
  ast: AST.AST,
443
523
  $defs: Record<string, JsonSchema7>,
444
524
  handleIdentifier: boolean,
445
- path: ReadonlyArray<PropertyKey>,
446
- options: {
447
- readonly getRef: (id: string) => string
448
- readonly target: Target
449
- }
525
+ path: Path,
526
+ options: Options
450
527
  ): JsonSchema7 => {
451
528
  if (handleIdentifier) {
452
529
  const identifier = AST.getJSONIdentifier(ast)
@@ -487,9 +564,25 @@ const go = (
487
564
  case "Literal": {
488
565
  const literal = ast.literal
489
566
  if (literal === null) {
490
- return { enum: [null], ...getJsonSchemaAnnotations(ast) }
491
- } else if (Predicate.isString(literal) || Predicate.isNumber(literal) || Predicate.isBoolean(literal)) {
492
- return { enum: [literal], ...getJsonSchemaAnnotations(ast) }
567
+ if (isNullTypeKeywordSupported(options)) {
568
+ // https://json-schema.org/draft-07/draft-handrews-json-schema-validation-00.pdf
569
+ // Section 6.1.1
570
+ return { type: "null", ...getJsonSchemaAnnotations(ast) }
571
+ } else {
572
+ // OpenAPI 3.1 does not support the "null" type keyword
573
+ // https://swagger.io/docs/specification/v3_0/data-models/data-types/#null
574
+ return {
575
+ // @ts-expect-error
576
+ enum: [null],
577
+ ...getJsonSchemaAnnotations(ast)
578
+ }
579
+ }
580
+ } else if (Predicate.isString(literal)) {
581
+ return { type: "string", enum: [literal], ...getJsonSchemaAnnotations(ast) }
582
+ } else if (Predicate.isNumber(literal)) {
583
+ return { type: "number", enum: [literal], ...getJsonSchemaAnnotations(ast) }
584
+ } else if (Predicate.isBoolean(literal)) {
585
+ return { type: "boolean", enum: [literal], ...getJsonSchemaAnnotations(ast) }
493
586
  }
494
587
  throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
495
588
  }
@@ -636,38 +729,90 @@ const go = (
636
729
  return { ...output, ...getJsonSchemaAnnotations(ast) }
637
730
  }
638
731
  case "Union": {
639
- const anyOf: Array<JsonSchema7> = []
732
+ const members: Array<JsonSchema7> = []
640
733
  for (const type of ast.types) {
641
- const schema = go(type, $defs, true, path, options)
642
- if ("enum" in schema) {
643
- if (Object.keys(schema).length > 1) {
644
- anyOf.push(schema)
734
+ const jsonSchema = go(type, $defs, true, path, options)
735
+ if (!isNeverJSONSchema(jsonSchema)) {
736
+ const last = members[members.length - 1]
737
+ if (isMergeableEnum(jsonSchema) && last !== undefined && isMergeableEnum(last)) {
738
+ members[members.length - 1] = { enum: last.enum.concat(jsonSchema.enum) }
645
739
  } else {
646
- const last = anyOf[anyOf.length - 1]
647
- if (last !== undefined && isEnumOnly(last)) {
648
- for (const e of schema.enum) {
649
- last.enum.push(e)
650
- }
651
- } else {
652
- anyOf.push(schema)
740
+ members.push(jsonSchema)
741
+ }
742
+ }
743
+ }
744
+
745
+ const anyOf = shrink(members)
746
+
747
+ const finalize = (anyOf: Array<JsonSchema7>) => {
748
+ switch (anyOf.length) {
749
+ case 0:
750
+ return {
751
+ ...constNever,
752
+ ...getJsonSchemaAnnotations(ast)
753
+ }
754
+ case 1: {
755
+ return {
756
+ ...addEnumType(anyOf[0]),
757
+ ...getJsonSchemaAnnotations(ast)
653
758
  }
654
759
  }
655
- } else {
656
- anyOf.push(schema)
760
+ default:
761
+ return {
762
+ anyOf: anyOf.map(addEnumType),
763
+ ...getJsonSchemaAnnotations(ast)
764
+ }
657
765
  }
658
766
  }
659
- if (anyOf.length === 1 && isEnumOnly(anyOf[0])) {
660
- return { enum: anyOf[0].enum, ...getJsonSchemaAnnotations(ast) }
661
- } else {
662
- return { anyOf, ...getJsonSchemaAnnotations(ast) }
767
+
768
+ if (isNullableKeywordSupported(options)) {
769
+ let nullable = false
770
+ const nonNullables: Array<JsonSchema7> = []
771
+ for (const s of anyOf) {
772
+ if ("nullable" in s) {
773
+ nullable = true
774
+ const nn = { ...s }
775
+ delete nn.nullable
776
+ nonNullables.push(nn)
777
+ } else if (isMergeableEnum(s)) {
778
+ const nnes = s.enum.filter((e) => e !== null)
779
+ if (nnes.length < s.enum.length) {
780
+ nullable = true
781
+ if (nnes.length === 0) {
782
+ continue
783
+ }
784
+ const nn = { ...s }
785
+ nn.enum = nnes
786
+ nonNullables.push(nn)
787
+ }
788
+ } else {
789
+ nonNullables.push(s)
790
+ }
791
+ }
792
+ if (nullable) {
793
+ const out = finalize(nonNullables)
794
+ if (!isAnyJSONSchema(out) && !isUnknownJSONSchema(out)) {
795
+ // @ts-expect-error
796
+ out.nullable = nullable
797
+ }
798
+ return out
799
+ }
663
800
  }
801
+
802
+ return finalize(anyOf)
664
803
  }
665
804
  case "Enums": {
666
- return {
667
- $comment: "/schemas/enums",
668
- anyOf: ast.enums.map((e) => ({ title: e[0], enum: [e[1]] })),
669
- ...getJsonSchemaAnnotations(ast)
670
- }
805
+ const anyOf = ast.enums.map((e) => addEnumType({ title: e[0], enum: [e[1]] }))
806
+ return anyOf.length >= 1 ?
807
+ {
808
+ $comment: "/schemas/enums",
809
+ anyOf,
810
+ ...getJsonSchemaAnnotations(ast)
811
+ } :
812
+ {
813
+ ...constNever,
814
+ ...getJsonSchemaAnnotations(ast)
815
+ }
671
816
  }
672
817
  case "Refinement": {
673
818
  // The jsonSchema annotation is required only if the refinement does not have a transformation
@@ -699,7 +844,7 @@ const go = (
699
844
  "type": "string",
700
845
  "contentMediaType": "application/json"
701
846
  }
702
- if (options.target !== "jsonSchema7") {
847
+ if (isContentSchemaSupported(options)) {
703
848
  out["contentSchema"] = go(ast.to, $defs, handleIdentifier, path, options)
704
849
  }
705
850
  return out