effect 3.11.4 → 3.11.5

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/src/JSONSchema.ts CHANGED
@@ -20,6 +20,15 @@ export interface JsonSchemaAnnotations {
20
20
  examples?: Array<unknown>
21
21
  }
22
22
 
23
+ /**
24
+ * @category model
25
+ * @since 3.11.5
26
+ */
27
+ export interface JsonSchema7Never extends JsonSchemaAnnotations {
28
+ $id: "/schemas/never"
29
+ not: {}
30
+ }
31
+
23
32
  /**
24
33
  * @category model
25
34
  * @since 3.10.0
@@ -86,6 +95,12 @@ export interface JsonSchema7String extends JsonSchemaAnnotations {
86
95
  maxLength?: number
87
96
  pattern?: string
88
97
  format?: string
98
+ contentMediaType?: string
99
+ allOf?: Array<{
100
+ minLength?: number
101
+ maxLength?: number
102
+ pattern?: string
103
+ }>
89
104
  }
90
105
 
91
106
  /**
@@ -97,6 +112,14 @@ export interface JsonSchema7Numeric extends JsonSchemaAnnotations {
97
112
  exclusiveMinimum?: number
98
113
  maximum?: number
99
114
  exclusiveMaximum?: number
115
+ multipleOf?: number
116
+ allOf?: Array<{
117
+ minimum?: number
118
+ exclusiveMinimum?: number
119
+ maximum?: number
120
+ exclusiveMaximum?: number
121
+ multipleOf?: number
122
+ }>
100
123
  }
101
124
 
102
125
  /**
@@ -181,6 +204,7 @@ export interface JsonSchema7Object extends JsonSchemaAnnotations {
181
204
  * @since 3.10.0
182
205
  */
183
206
  export type JsonSchema7 =
207
+ | JsonSchema7Never
184
208
  | JsonSchema7Any
185
209
  | JsonSchema7Unknown
186
210
  | JsonSchema7Void
@@ -211,31 +235,84 @@ export type JsonSchema7Root = JsonSchema7 & {
211
235
  * @since 3.10.0
212
236
  */
213
237
  export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): JsonSchema7Root => {
214
- const $defs: Record<string, any> = {}
215
- const jsonSchema = go(schema.ast, $defs, true, [])
216
- const out: JsonSchema7Root = {
217
- $schema,
218
- ...jsonSchema
219
- }
220
- // clean up self-referencing entries
221
- for (const id in $defs) {
222
- if ($defs[id]["$ref"] === get$ref(id)) {
223
- delete $defs[id]
224
- }
225
- }
226
- if (!Record.isEmptyRecord($defs)) {
227
- out.$defs = $defs
238
+ const definitions: Record<string, any> = {}
239
+ const ast = AST.isTransformation(schema.ast) && isParseJsonTransformation(schema.ast.from)
240
+ // Special case top level `parseJson` transformations
241
+ ? schema.ast.to
242
+ : schema.ast
243
+ const out: JsonSchema7Root = fromAST(ast, {
244
+ definitions
245
+ })
246
+ out.$schema = $schema
247
+ if (!Record.isEmptyRecord(definitions)) {
248
+ out.$defs = definitions
228
249
  }
229
250
  return out
230
251
  }
231
252
 
232
- const anyJsonSchema: JsonSchema7 = { $id: "/schemas/any" }
253
+ type Target = "jsonSchema7" | "jsonSchema2019-09" | "openApi3.1"
254
+
255
+ type TopLevelReferenceStrategy = "skip" | "keep"
256
+
257
+ /**
258
+ * Returns a JSON Schema with additional options and definitions.
259
+ *
260
+ * **Warning**
261
+ *
262
+ * This function is experimental and subject to change.
263
+ *
264
+ * **Details**
265
+ *
266
+ * - `definitions`: A record of definitions that are included in the schema.
267
+ * - `definitionPath`: The path to the definitions within the schema (defaults
268
+ * to "#/$defs/").
269
+ * - `target`: Which spec to target. Possible values are:
270
+ * - `'jsonSchema7'`: JSON Schema draft-07 (default behavior).
271
+ * - `'jsonSchema2019-09'`: JSON Schema draft-2019-09.
272
+ * - `'openApi3.1'`: OpenAPI 3.1.
273
+ * - `topLevelReferenceStrategy`: Controls the handling of the top-level
274
+ * reference. Possible values are:
275
+ * - `"keep"`: Keep the top-level reference (default behavior).
276
+ * - `"skip"`: Skip the top-level reference.
277
+ *
278
+ * @category encoding
279
+ * @since 3.11.5
280
+ * @experimental
281
+ */
282
+ export const fromAST = (ast: AST.AST, options: {
283
+ readonly definitions: Record<string, JsonSchema7>
284
+ readonly definitionPath?: string
285
+ readonly target?: Target
286
+ readonly topLevelReferenceStrategy?: TopLevelReferenceStrategy
287
+ }): JsonSchema7 => {
288
+ const definitionPath = options.definitionPath ?? "#/$defs/"
289
+ const getRef = (id: string) => definitionPath + id
290
+ const target: Target = options.target ?? "jsonSchema7"
291
+ const handleIdentifier = options.topLevelReferenceStrategy !== "skip"
292
+ return go(ast, options.definitions, handleIdentifier, [], {
293
+ getRef,
294
+ target
295
+ })
296
+ }
297
+
298
+ const constNever: JsonSchema7 = {
299
+ "$id": "/schemas/never",
300
+ "not": {}
301
+ }
302
+
303
+ const constAny: JsonSchema7 = {
304
+ "$id": "/schemas/any"
305
+ }
233
306
 
234
- const unknownJsonSchema: JsonSchema7 = { $id: "/schemas/unknown" }
307
+ const constUnknown: JsonSchema7 = {
308
+ "$id": "/schemas/unknown"
309
+ }
235
310
 
236
- const voidJsonSchema: JsonSchema7 = { $id: "/schemas/void" }
311
+ const constVoid: JsonSchema7 = {
312
+ "$id": "/schemas/void"
313
+ }
237
314
 
238
- const objectJsonSchema: JsonSchema7 = {
315
+ const constAnyObject: JsonSchema7 = {
239
316
  "$id": "/schemas/object",
240
317
  "anyOf": [
241
318
  { "type": "object" },
@@ -243,13 +320,13 @@ const objectJsonSchema: JsonSchema7 = {
243
320
  ]
244
321
  }
245
322
 
246
- const empty = (): JsonSchema7 => ({
323
+ const constEmpty: JsonSchema7 = {
247
324
  "$id": "/schemas/{}",
248
325
  "anyOf": [
249
326
  { "type": "object" },
250
327
  { "type": "array" }
251
328
  ]
252
- })
329
+ }
253
330
 
254
331
  const $schema = "http://json-schema.org/draft-07/schema#"
255
332
 
@@ -291,35 +368,31 @@ const getASTJsonSchemaAnnotations = (ast: AST.AST): JsonSchemaAnnotations => {
291
368
  const pruneUndefinedFromPropertySignature = (ast: AST.AST): AST.AST | undefined => {
292
369
  if (Option.isNone(AST.getJSONSchemaAnnotation(ast))) {
293
370
  switch (ast._tag) {
371
+ case "UndefinedKeyword":
372
+ return AST.neverKeyword
294
373
  case "Union": {
295
- const types = ast.types.filter((type) => !AST.isUndefinedKeyword(type))
296
- if (types.length < ast.types.length) {
297
- return AST.Union.make(types, ast.annotations)
374
+ const types: Array<AST.AST> = []
375
+ let hasUndefined = false
376
+ for (const type of ast.types) {
377
+ const pruned = pruneUndefinedFromPropertySignature(type)
378
+ if (pruned) {
379
+ hasUndefined = true
380
+ if (!AST.isNeverKeyword(pruned)) {
381
+ types.push(pruned)
382
+ }
383
+ } else {
384
+ types.push(type)
385
+ }
386
+ }
387
+ if (hasUndefined) {
388
+ return AST.Union.make(types)
298
389
  }
299
390
  break
300
391
  }
392
+ case "Suspend":
393
+ return pruneUndefinedFromPropertySignature(ast.f())
301
394
  case "Transformation":
302
- return pruneUndefinedFromPropertySignature(isParseJsonTransformation(ast.from) ? ast.to : ast.from)
303
- }
304
- }
305
- }
306
-
307
- /** @internal */
308
- export const DEFINITION_PREFIX = "#/$defs/"
309
-
310
- const get$ref = (id: string): string => `${DEFINITION_PREFIX}${id}`
311
-
312
- const getRefinementInnerTransformation = (ast: AST.Refinement): AST.AST | undefined => {
313
- switch (ast.from._tag) {
314
- case "Transformation":
315
- return ast.from
316
- case "Refinement":
317
- return getRefinementInnerTransformation(ast.from)
318
- case "Suspend": {
319
- const from = ast.from.f()
320
- if (AST.isRefinement(from)) {
321
- return getRefinementInnerTransformation(from)
322
- }
395
+ return pruneUndefinedFromPropertySignature(ast.from)
323
396
  }
324
397
  }
325
398
  }
@@ -327,63 +400,86 @@ const getRefinementInnerTransformation = (ast: AST.Refinement): AST.AST | undefi
327
400
  const isParseJsonTransformation = (ast: AST.AST): boolean =>
328
401
  ast.annotations[AST.SchemaIdAnnotationId] === AST.ParseJsonSchemaId
329
402
 
330
- function merge(a: JsonSchemaAnnotations, b: JsonSchema7): JsonSchema7
331
- function merge(a: JsonSchema7, b: JsonSchemaAnnotations): JsonSchema7
332
- function merge(a: JsonSchema7, b: JsonSchema7): JsonSchema7
333
- function merge(a: object, b: object): object {
334
- return { ...a, ...b }
335
- }
336
-
337
403
  const isOverrideAnnotation = (jsonSchema: JsonSchema7): boolean => {
338
404
  return ("type" in jsonSchema) || ("oneOf" in jsonSchema) || ("anyOf" in jsonSchema) || ("const" in jsonSchema) ||
339
405
  ("enum" in jsonSchema) || ("$ref" in jsonSchema)
340
406
  }
341
407
 
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
412
+
413
+ const mergeRefinements = (from: any, jsonSchema: any, annotations: any): any => {
414
+ const out: any = { ...from, ...annotations, ...jsonSchema }
415
+ out.allOf ??= []
416
+
417
+ const handle = (name: string, filter: (i: any) => boolean) => {
418
+ if (name in jsonSchema && name in from) {
419
+ out.allOf.unshift({ [name]: from[name] })
420
+ out.allOf = out.allOf.filter(filter)
421
+ }
422
+ }
423
+
424
+ handle("minLength", (i) => i.minLength > jsonSchema.minLength)
425
+ handle("maxLength", (i) => i.maxLength < jsonSchema.maxLength)
426
+ handle("pattern", (i) => i.pattern !== jsonSchema.pattern)
427
+ handle("minItems", (i) => i.minItems > jsonSchema.minItems)
428
+ handle("maxItems", (i) => i.maxItems < jsonSchema.maxItems)
429
+ handle("minimum", (i) => i.minimum > jsonSchema.minimum)
430
+ handle("maximum", (i) => i.maximum < jsonSchema.maximum)
431
+ handle("exclusiveMinimum", (i) => i.exclusiveMinimum > jsonSchema.exclusiveMinimum)
432
+ handle("exclusiveMaximum", (i) => i.exclusiveMaximum < jsonSchema.exclusiveMaximum)
433
+ handle("multipleOf", (i) => i.multipleOf !== jsonSchema.multipleOf)
434
+
435
+ if (out.allOf.length === 0) {
436
+ delete out.allOf
437
+ }
438
+ return out
439
+ }
440
+
342
441
  const go = (
343
442
  ast: AST.AST,
344
443
  $defs: Record<string, JsonSchema7>,
345
444
  handleIdentifier: boolean,
346
- path: ReadonlyArray<PropertyKey>
445
+ path: ReadonlyArray<PropertyKey>,
446
+ options: {
447
+ readonly getRef: (id: string) => string
448
+ readonly target: Target
449
+ }
347
450
  ): JsonSchema7 => {
451
+ if (handleIdentifier) {
452
+ const identifier = AST.getJSONIdentifier(ast)
453
+ if (Option.isSome(identifier)) {
454
+ const id = identifier.value
455
+ const out = { $ref: options.getRef(id) }
456
+ if (!Record.has($defs, id)) {
457
+ $defs[id] = out
458
+ $defs[id] = go(ast, $defs, false, path, options)
459
+ }
460
+ return out
461
+ }
462
+ }
348
463
  const hook = AST.getJSONSchemaAnnotation(ast)
349
464
  if (Option.isSome(hook)) {
350
465
  const handler = hook.value as JsonSchema7
351
466
  if (AST.isRefinement(ast)) {
352
- const t = getRefinementInnerTransformation(ast)
467
+ const t = AST.getTransformationFrom(ast)
353
468
  if (t === undefined) {
354
- try {
355
- return {
356
- ...go(ast.from, $defs, true, path),
357
- ...getJsonSchemaAnnotations(ast),
358
- ...handler
359
- }
360
- } catch (e) {
361
- return {
362
- ...getJsonSchemaAnnotations(ast),
363
- ...handler
364
- }
365
- }
469
+ return mergeRefinements(
470
+ go(ast.from, $defs, handleIdentifier, path, options),
471
+ handler,
472
+ getJsonSchemaAnnotations(ast)
473
+ )
366
474
  } else if (!isOverrideAnnotation(handler)) {
367
- return go(t, $defs, true, path)
475
+ return go(t, $defs, handleIdentifier, path, options)
368
476
  }
369
477
  }
370
478
  return handler
371
479
  }
372
480
  const surrogate = AST.getSurrogateAnnotation(ast)
373
481
  if (Option.isSome(surrogate)) {
374
- return go(surrogate.value, $defs, handleIdentifier, path)
375
- }
376
- if (handleIdentifier && !AST.isTransformation(ast) && !AST.isRefinement(ast)) {
377
- const identifier = AST.getJSONIdentifier(ast)
378
- if (Option.isSome(identifier)) {
379
- const id = identifier.value
380
- const out = { $ref: get$ref(id) }
381
- if (!Record.has($defs, id)) {
382
- $defs[id] = out
383
- $defs[id] = go(ast, $defs, false, path)
384
- }
385
- return out
386
- }
482
+ return go(surrogate.value, $defs, handleIdentifier, path, options)
387
483
  }
388
484
  switch (ast._tag) {
389
485
  case "Declaration":
@@ -391,9 +487,9 @@ const go = (
391
487
  case "Literal": {
392
488
  const literal = ast.literal
393
489
  if (literal === null) {
394
- return merge({ enum: [null] }, getJsonSchemaAnnotations(ast))
490
+ return { enum: [null], ...getJsonSchemaAnnotations(ast) }
395
491
  } else if (Predicate.isString(literal) || Predicate.isNumber(literal) || Predicate.isBoolean(literal)) {
396
- return merge({ enum: [literal] }, getJsonSchemaAnnotations(ast))
492
+ return { enum: [literal], ...getJsonSchemaAnnotations(ast) }
397
493
  }
398
494
  throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
399
495
  }
@@ -402,15 +498,15 @@ const go = (
402
498
  case "UndefinedKeyword":
403
499
  throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
404
500
  case "VoidKeyword":
405
- return merge(voidJsonSchema, getJsonSchemaAnnotations(ast))
501
+ return { ...constVoid, ...getJsonSchemaAnnotations(ast) }
406
502
  case "NeverKeyword":
407
- throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
503
+ return { ...constNever, ...getJsonSchemaAnnotations(ast) }
408
504
  case "UnknownKeyword":
409
- return merge(unknownJsonSchema, getJsonSchemaAnnotations(ast))
505
+ return { ...constUnknown, ...getJsonSchemaAnnotations(ast) }
410
506
  case "AnyKeyword":
411
- return merge(anyJsonSchema, getJsonSchemaAnnotations(ast))
507
+ return { ...constAny, ...getJsonSchemaAnnotations(ast) }
412
508
  case "ObjectKeyword":
413
- return merge(objectJsonSchema, getJsonSchemaAnnotations(ast))
509
+ return { ...constAnyObject, ...getJsonSchemaAnnotations(ast) }
414
510
  case "StringKeyword":
415
511
  return { type: "string", ...getASTJsonSchemaAnnotations(ast) }
416
512
  case "NumberKeyword":
@@ -422,18 +518,14 @@ const go = (
422
518
  case "SymbolKeyword":
423
519
  throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
424
520
  case "TupleType": {
425
- const elements = ast.elements.map((e, i) =>
426
- merge(
427
- go(e.type, $defs, true, path.concat(i)),
428
- getJsonSchemaAnnotations(e)
429
- )
430
- )
431
- const rest = ast.rest.map((annotatedAST) =>
432
- merge(
433
- go(annotatedAST.type, $defs, true, path),
434
- getJsonSchemaAnnotations(annotatedAST)
435
- )
436
- )
521
+ const elements = ast.elements.map((e, i) => ({
522
+ ...go(e.type, $defs, true, path.concat(i), options),
523
+ ...getJsonSchemaAnnotations(e)
524
+ }))
525
+ const rest = ast.rest.map((annotatedAST) => ({
526
+ ...go(annotatedAST.type, $defs, true, path, options),
527
+ ...getJsonSchemaAnnotations(annotatedAST)
528
+ }))
437
529
  const output: JsonSchema7Array = { type: "array" }
438
530
  // ---------------------------------------------
439
531
  // handle elements
@@ -470,11 +562,11 @@ const go = (
470
562
  }
471
563
  }
472
564
 
473
- return merge(output, getJsonSchemaAnnotations(ast))
565
+ return { ...output, ...getJsonSchemaAnnotations(ast) }
474
566
  }
475
567
  case "TypeLiteral": {
476
568
  if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
477
- return merge(empty(), getJsonSchemaAnnotations(ast))
569
+ return { ...constEmpty, ...getJsonSchemaAnnotations(ast) }
478
570
  }
479
571
  let patternProperties: JsonSchema7 | undefined = undefined
480
572
  let propertyNames: JsonSchema7 | undefined = undefined
@@ -482,11 +574,11 @@ const go = (
482
574
  const parameter = is.parameter
483
575
  switch (parameter._tag) {
484
576
  case "StringKeyword": {
485
- patternProperties = go(is.type, $defs, true, path)
577
+ patternProperties = go(is.type, $defs, true, path, options)
486
578
  break
487
579
  }
488
580
  case "TemplateLiteral": {
489
- patternProperties = go(is.type, $defs, true, path)
581
+ patternProperties = go(is.type, $defs, true, path, options)
490
582
  propertyNames = {
491
583
  type: "string",
492
584
  pattern: AST.getTemplateLiteralRegExp(parameter).source
@@ -494,8 +586,8 @@ const go = (
494
586
  break
495
587
  }
496
588
  case "Refinement": {
497
- patternProperties = go(is.type, $defs, true, path)
498
- propertyNames = go(parameter, $defs, true, path)
589
+ patternProperties = go(is.type, $defs, true, path, options)
590
+ propertyNames = go(parameter, $defs, true, path, options)
499
591
  break
500
592
  }
501
593
  case "SymbolKeyword":
@@ -516,10 +608,10 @@ const go = (
516
608
  const name = ps.name
517
609
  if (Predicate.isString(name)) {
518
610
  const pruned = pruneUndefinedFromPropertySignature(ps.type)
519
- output.properties[name] = merge(
520
- go(pruned ? pruned : ps.type, $defs, true, path.concat(ps.name)),
521
- getJsonSchemaAnnotations(ps)
522
- )
611
+ output.properties[name] = {
612
+ ...go(pruned ?? ps.type, $defs, true, path.concat(ps.name), options),
613
+ ...getJsonSchemaAnnotations(ps)
614
+ }
523
615
  // ---------------------------------------------
524
616
  // handle optional property signatures
525
617
  // ---------------------------------------------
@@ -541,69 +633,76 @@ const go = (
541
633
  output.propertyNames = propertyNames
542
634
  }
543
635
 
544
- return merge(output, getJsonSchemaAnnotations(ast))
636
+ return { ...output, ...getJsonSchemaAnnotations(ast) }
545
637
  }
546
638
  case "Union": {
547
- const enums: Array<AST.LiteralValue> = []
548
639
  const anyOf: Array<JsonSchema7> = []
549
640
  for (const type of ast.types) {
550
- const schema = go(type, $defs, true, path)
641
+ const schema = go(type, $defs, true, path, options)
551
642
  if ("enum" in schema) {
552
643
  if (Object.keys(schema).length > 1) {
553
644
  anyOf.push(schema)
554
645
  } else {
555
- for (const e of schema.enum) {
556
- enums.push(e)
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)
557
653
  }
558
654
  }
559
655
  } else {
560
656
  anyOf.push(schema)
561
657
  }
562
658
  }
563
- if (anyOf.length === 0) {
564
- return merge({ enum: enums }, getJsonSchemaAnnotations(ast))
659
+ if (anyOf.length === 1 && isEnumOnly(anyOf[0])) {
660
+ return { enum: anyOf[0].enum, ...getJsonSchemaAnnotations(ast) }
565
661
  } else {
566
- if (enums.length >= 1) {
567
- anyOf.push({ enum: enums })
568
- }
569
- return merge({ anyOf }, getJsonSchemaAnnotations(ast))
662
+ return { anyOf, ...getJsonSchemaAnnotations(ast) }
570
663
  }
571
664
  }
572
665
  case "Enums": {
573
- return merge({
666
+ return {
574
667
  $comment: "/schemas/enums",
575
- anyOf: ast.enums.map((e) => ({ title: e[0], enum: [e[1]] }))
576
- }, getJsonSchemaAnnotations(ast))
668
+ anyOf: ast.enums.map((e) => ({ title: e[0], enum: [e[1]] })),
669
+ ...getJsonSchemaAnnotations(ast)
670
+ }
577
671
  }
578
672
  case "Refinement": {
579
- if (AST.encodedBoundAST(ast) === ast) {
673
+ // The jsonSchema annotation is required only if the refinement does not have a transformation
674
+ if (AST.getTransformationFrom(ast) === undefined) {
580
675
  throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
581
676
  }
582
- return go(ast.from, $defs, true, path)
677
+ return go(ast.from, $defs, handleIdentifier, path, options)
583
678
  }
584
679
  case "TemplateLiteral": {
585
680
  const regex = AST.getTemplateLiteralRegExp(ast)
586
- return merge({
681
+ return {
587
682
  type: "string",
588
683
  title: String(ast),
589
684
  description: "a template literal",
590
- pattern: regex.source
591
- }, getJsonSchemaAnnotations(ast))
685
+ pattern: regex.source,
686
+ ...getJsonSchemaAnnotations(ast)
687
+ }
592
688
  }
593
689
  case "Suspend": {
594
690
  const identifier = Option.orElse(AST.getJSONIdentifier(ast), () => AST.getJSONIdentifier(ast.f()))
595
691
  if (Option.isNone(identifier)) {
596
692
  throw new Error(errors_.getJSONSchemaMissingIdentifierAnnotationErrorMessage(path, ast))
597
693
  }
598
- return go(ast.f(), $defs, true, path)
694
+ return go(ast.f(), $defs, handleIdentifier, path, options)
599
695
  }
600
696
  case "Transformation": {
601
- // Properly handle S.parseJson transformations by focusing on
602
- // the 'to' side of the AST. This approach prevents the generation of useless schemas
603
- // derived from the 'from' side (type: string), ensuring the output matches the intended
604
- // complex schema type.
605
697
  if (isParseJsonTransformation(ast.from)) {
606
- return go(ast.to, $defs, true, path)
698
+ const out: JsonSchema7String & { contentSchema?: JsonSchema7 } = {
699
+ "type": "string",
700
+ "contentMediaType": "application/json"
701
+ }
702
+ if (options.target !== "jsonSchema7") {
703
+ out["contentSchema"] = go(ast.to, $defs, handleIdentifier, path, options)
704
+ }
705
+ return out
607
706
  }
608
707
  let next = ast.from
609
708
  if (AST.isTypeLiteralTransformation(ast.transformation)) {
@@ -622,7 +721,7 @@ const go = (
622
721
  next = AST.annotations(next, { [AST.DescriptionAnnotationId]: description.value })
623
722
  }
624
723
  }
625
- return go(next, $defs, true, path)
724
+ return go(next, $defs, handleIdentifier, path, options)
626
725
  }
627
726
  }
628
727
  }
package/src/Schema.ts CHANGED
@@ -2632,7 +2632,9 @@ export interface TypeLiteral<
2632
2632
  annotations: Annotations.Schema<Simplify<TypeLiteral.Type<Fields, Records>>>
2633
2633
  ): TypeLiteral<Fields, Records>
2634
2634
  make(
2635
- props: Simplify<TypeLiteral.Constructor<Fields, Records>>,
2635
+ props: RequiredKeys<TypeLiteral.Constructor<Fields, Records>> extends never
2636
+ ? void | Simplify<TypeLiteral.Constructor<Fields, Records>>
2637
+ : Simplify<TypeLiteral.Constructor<Fields, Records>>,
2636
2638
  options?: MakeOptions
2637
2639
  ): Simplify<TypeLiteral.Type<Fields, Records>>
2638
2640
  }
@@ -4669,6 +4671,7 @@ export const lowercased =
4669
4671
  filter((a) => a === a.toLowerCase(), {
4670
4672
  schemaId: LowercasedSchemaId,
4671
4673
  description: "a lowercase string",
4674
+ jsonSchema: { pattern: "^[^A-Z]*$" },
4672
4675
  ...annotations
4673
4676
  })
4674
4677
  )
@@ -4699,6 +4702,7 @@ export const capitalized =
4699
4702
  filter((a) => a[0]?.toUpperCase() === a[0], {
4700
4703
  schemaId: CapitalizedSchemaId,
4701
4704
  description: "a capitalized string",
4705
+ jsonSchema: { pattern: "^[^a-z]?.*$" },
4702
4706
  ...annotations
4703
4707
  })
4704
4708
  )
@@ -4729,6 +4733,7 @@ export const uncapitalized =
4729
4733
  filter((a) => a[0]?.toLowerCase() === a[0], {
4730
4734
  schemaId: UncapitalizedSchemaId,
4731
4735
  description: "a uncapitalized string",
4736
+ jsonSchema: { pattern: "^[^A-Z]?.*$" },
4732
4737
  ...annotations
4733
4738
  })
4734
4739
  )
@@ -4759,6 +4764,7 @@ export const uppercased =
4759
4764
  filter((a) => a === a.toUpperCase(), {
4760
4765
  schemaId: UppercasedSchemaId,
4761
4766
  description: "an uppercase string",
4767
+ jsonSchema: { pattern: "^[^a-z]*$" },
4762
4768
  ...annotations
4763
4769
  })
4764
4770
  )
@@ -4947,7 +4953,7 @@ export type ParseJsonOptions = {
4947
4953
  const JsonString = String$.annotations({
4948
4954
  [AST.IdentifierAnnotationId]: "JsonString",
4949
4955
  [AST.TitleAnnotationId]: "JsonString",
4950
- [AST.DescriptionAnnotationId]: "a JSON string"
4956
+ [AST.DescriptionAnnotationId]: "a string that will be parsed as JSON"
4951
4957
  })
4952
4958
 
4953
4959
  const getParseJsonTransformation = (options?: ParseJsonOptions) =>
@@ -6401,7 +6407,7 @@ export const minItems = <A>(
6401
6407
  (a) => a.length >= minItems,
6402
6408
  {
6403
6409
  schemaId: MinItemsSchemaId,
6404
- description: `an array of at least ${minItems} items`,
6410
+ description: `an array of at least ${minItems} item(s)`,
6405
6411
  jsonSchema: { minItems },
6406
6412
  [AST.StableFilterAnnotationId]: true,
6407
6413
  ...annotations
@@ -6434,7 +6440,7 @@ export const maxItems = <A>(
6434
6440
  self.pipe(
6435
6441
  filter((a) => a.length <= n, {
6436
6442
  schemaId: MaxItemsSchemaId,
6437
- description: `an array of at most ${n} items`,
6443
+ description: `an array of at most ${n} item(s)`,
6438
6444
  jsonSchema: { maxItems: n },
6439
6445
  [AST.StableFilterAnnotationId]: true,
6440
6446
  ...annotations
@@ -8738,10 +8744,16 @@ const makeClass = ({ Base, annotations, disableToString, fields, identifier, kin
8738
8744
  disableToString?: boolean | undefined
8739
8745
  }): any => {
8740
8746
  const classSymbol = Symbol.for(`effect/Schema/${kind}/${identifier}`)
8747
+
8748
+ const ts = typeSchema(schema)
8749
+ const declarationSurrogate = ts.annotations({ identifier, ...annotations })
8750
+ const typeSide = ts.annotations({ [AST.AutoTitleAnnotationId]: `${identifier} (Type side)` })
8751
+ const transformationSurrogate = schema.annotations({ ...annotations })
8741
8752
  const validateSchema = schema.annotations({ [AST.AutoTitleAnnotationId]: `${identifier} (Constructor)` })
8742
- const encodedSide: Schema.Any = schema.annotations({ [AST.AutoTitleAnnotationId]: `${identifier} (Encoded side)` })
8743
- const typeSide = typeSchema(schema).annotations({ [AST.AutoTitleAnnotationId]: `${identifier} (Type side)` })
8753
+ const encodedSide = schema.annotations({ [AST.AutoTitleAnnotationId]: `${identifier} (Encoded side)` })
8754
+
8744
8755
  const fallbackInstanceOf = (u: unknown) => Predicate.hasProperty(u, classSymbol) && ParseResult.is(typeSide)(u)
8756
+
8745
8757
  const klass = class extends Base {
8746
8758
  constructor(
8747
8759
  props: { [x: string | symbol]: unknown } = {},
@@ -8768,6 +8780,7 @@ const makeClass = ({ Base, annotations, disableToString, fields, identifier, kin
8768
8780
  if (astCache.has(this)) {
8769
8781
  return astCache.get(this)!
8770
8782
  }
8783
+
8771
8784
  const declaration: Schema.Any = declare(
8772
8785
  [typeSide],
8773
8786
  {
@@ -8785,21 +8798,23 @@ const makeClass = ({ Base, annotations, disableToString, fields, identifier, kin
8785
8798
  },
8786
8799
  {
8787
8800
  identifier,
8788
- title: identifier,
8789
- description: `an instance of ${identifier}`,
8790
8801
  pretty: (pretty) => (self: any) => `${identifier}(${pretty(self)})`,
8791
8802
  // @ts-expect-error
8792
8803
  arbitrary: (arb) => (fc) => arb(fc).map((props) => new this(props)),
8793
8804
  equivalence: identity,
8794
- [AST.SurrogateAnnotationId]: typeSide.ast,
8805
+ [AST.SurrogateAnnotationId]: declarationSurrogate.ast,
8795
8806
  ...annotations
8796
8807
  }
8797
8808
  )
8809
+
8798
8810
  const transformation = transform(
8799
8811
  encodedSide,
8800
8812
  declaration,
8801
8813
  { strict: true, decode: (input) => new this(input, true), encode: identity }
8802
- ).annotations({ [AST.SurrogateAnnotationId]: schema.ast })
8814
+ ).annotations({
8815
+ [AST.JSONIdentifierAnnotationId]: identifier,
8816
+ [AST.SurrogateAnnotationId]: transformationSurrogate.ast
8817
+ })
8803
8818
  astCache.set(this, transformation.ast)
8804
8819
  return transformation.ast
8805
8820
  }