1import { assert, describe, it } from "@effect/vitest"
2import { Schema, SchemaAST, SchemaRepresentation } from "effect"
3
4describe("SchemaRepresentation.toRepresentation", () => {
5 describe("node conversion", () => {
6 const keywords = [
7 ["Null", Schema.Null],
8 ["Undefined", Schema.Undefined],
9 ["Void", Schema.Void],
10 ["Never", Schema.Never],
11 ["Unknown", Schema.Unknown],
12 ["Any", Schema.Any],
13 ["String", Schema.String],
14 ["Number", Schema.Number],
15 ["Boolean", Schema.Boolean],
16 ["BigInt", Schema.BigInt],
17 ["Symbol", Schema.Symbol],
18 ["ObjectKeyword", Schema.ObjectKeyword]
19 ] as const
20
21 for (const [tag, schema] of keywords) {
22 it(tag, () => {
23 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
24 representation: { _tag: tag, checks: [] },
25 references: {}
26 })
27 })
28 }
29
30 it("literal", () => {
31 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.Literal("value").ast), {
32 representation: { _tag: "Literal", literal: "value", checks: [] },
33 references: {}
34 })
35 })
36
37 it("global unique symbol", () => {
38 const symbol = Symbol.for("value")
39 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.UniqueSymbol(symbol).ast), {
40 representation: { _tag: "UniqueSymbol", symbol, checks: [] },
41 references: {}
42 })
43 })
44
45 it("enum", () => {
46 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.Enum({ A: "a", One: 1 }).ast), {
47 representation: {
48 _tag: "Enum",
49 enums: [
50 ["A", "a"],
51 ["One", 1]
52 ],
53 checks: []
54 },
55 references: {}
56 })
57 })
58
59 it("template literal", () => {
60 assert.deepStrictEqual(
61 SchemaRepresentation.toRepresentation(Schema.TemplateLiteral(["prefix-", Schema.String, Schema.Number]).ast),
62 {
63 representation: {
64 _tag: "TemplateLiteral",
65 parts: [
66 { _tag: "Literal", literal: "prefix-", checks: [] },
67 { _tag: "String", checks: [] },
68 { _tag: "Number", checks: [] }
69 ],
70 checks: []
71 },
72 references: {}
73 }
74 )
75 })
76
77 it("tuple elements and rest", () => {
78 const schema = Schema.TupleWithRest(
79 Schema.Tuple([Schema.String, Schema.optionalKey(Schema.Number)]),
80 [Schema.Boolean]
81 )
82
83 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
84 representation: {
85 _tag: "Arrays",
86 elements: [
87 { type: { _tag: "String", checks: [] }, isOptional: false },
88 { type: { _tag: "Number", checks: [] }, isOptional: true }
89 ],
90 rest: [{ _tag: "Boolean", checks: [] }],
91 checks: []
92 },
93 references: {}
94 })
95 })
96
97 it("object properties and index signatures", () => {
98 const schema = Schema.StructWithRest(
99 Schema.Struct({
100 required: Schema.String,
101 optional: Schema.optionalKey(Schema.Number),
102 mutable: Schema.mutableKey(Schema.Boolean)
103 }),
104 [Schema.Record(Schema.Symbol, Schema.BigInt)]
105 )
106
107 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
108 representation: {
109 _tag: "Objects",
110 propertySignatures: [
111 {
112 name: "required",
113 type: { _tag: "String", checks: [] },
114 isOptional: false,
115 isMutable: false
116 },
117 {
118 name: "optional",
119 type: { _tag: "Number", checks: [] },
120 isOptional: true,
121 isMutable: false
122 },
123 {
124 name: "mutable",
125 type: { _tag: "Boolean", checks: [] },
126 isOptional: false,
127 isMutable: true
128 }
129 ],
130 indexSignatures: [{
131 parameter: { _tag: "Symbol", checks: [] },
132 type: { _tag: "BigInt", checks: [] }
133 }],
134 checks: []
135 },
136 references: {}
137 })
138 })
139
140 it("union member order", () => {
141 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.Union([Schema.String, Schema.BigInt]).ast), {
142 representation: {
143 _tag: "Union",
144 types: [
145 { _tag: "String", checks: [] },
146 { _tag: "BigInt", checks: [] }
147 ],
148 checks: []
149 },
150 references: {}
151 })
152 })
153
154 it("preserves supplied Union options and omits absent options", () => {
155 const unionOptions: SchemaAST.UnionOptions = { mode: "oneOf" }
156 const union = SchemaRepresentation.toRepresentation(
157 new SchemaAST.Union([Schema.String.ast, Schema.Number.ast], unionOptions)
158 ).representation
159
160 assert.strictEqual(union._tag, "Union")
161 if (union._tag === "Union") assert.strictEqual(union.options, unionOptions)
162
163 const withoutOptions = SchemaRepresentation.toRepresentation(
164 Schema.Union([Schema.String, Schema.Number]).ast
165 ).representation
166 assert.strictEqual(withoutOptions._tag, "Union")
167 assert.isFalse(Object.hasOwn(withoutOptions, "options"))
168 })
169 })
170
171 describe("encoded and type projections", () => {
172 it("uses the encoded side of a transformation", () => {
173 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.NumberFromString.ast), {
174 representation: {
175 _tag: "String",
176 annotations: { expected: "a string that will be decoded as a number" },
177 checks: []
178 },
179 references: {}
180 })
181 })
182
183 it("uses a type-side identifier as a fallback for the encoded representation", () => {
184 const schema = Schema.NumberFromString.annotate({ identifier: "Finite" })
185
186 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
187 representation: { _tag: "Reference", $ref: "FiniteEncoded" },
188 references: {
189 FiniteEncoded: {
190 _tag: "String",
191 annotations: {
192 expected: "a string that will be decoded as a number",
193 "~identifier": "Finite"
194 },
195 checks: []
196 }
197 }
198 })
199 })
200
201 it("prefers an explicit encoded-side identifier over a type-side identifier", () => {
202 const schema = Schema.NumberFromString.pipe(
203 Schema.annotateEncoded({ identifier: "EncodedFinite" }),
204 Schema.annotate({ identifier: "Finite" })
205 )
206
207 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
208 representation: { _tag: "Reference", $ref: "EncodedFinite" },
209 references: {
210 EncodedFinite: {
211 _tag: "String",
212 annotations: {
213 expected: "a string that will be decoded as a number",
214 identifier: "EncodedFinite"
215 },
216 checks: []
217 }
218 }
219 })
220 })
221
222 it("overrides an encoded-side fallback with the type-side identifier", () => {
223 const schema = Schema.NumberFromString.pipe(
224 Schema.annotateEncoded({ "~identifier": "Previous" }),
225 Schema.annotate({ identifier: "Finite" })
226 )
227
228 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
229 representation: { _tag: "Reference", $ref: "FiniteEncoded" },
230 references: {
231 FiniteEncoded: {
232 _tag: "String",
233 annotations: {
234 expected: "a string that will be decoded as a number",
235 "~identifier": "Finite"
236 },
237 checks: []
238 }
239 }
240 })
241 })
242
243 it("uses the type side when the caller projects it", () => {
244 assert.deepStrictEqual(
245 SchemaRepresentation.toRepresentation(SchemaAST.toType(Schema.NumberFromString.ast)),
246 {
247 representation: { _tag: "Number", checks: [] },
248 references: {}
249 }
250 )
251 })
252 })
253
254 describe("schema annotations and declarations", () => {
255 it("omits brands", () => {
256 assert.deepStrictEqual(
257 SchemaRepresentation.toRepresentation(Schema.String.pipe(Schema.brand("A"), Schema.brand("B")).ast),
258 {
259 representation: { _tag: "String", checks: [] },
260 references: {}
261 }
262 )
263 })
264
265 it("preserves declaration code callbacks", () => {
266 const toCode: SchemaRepresentation.Generation.Declaration = () => ({ runtime: "Custom", Type: "string" })
267 const schema = Schema.declare<string>((input): input is string => typeof input === "string", {
268 representation: {
269 id: "acme/schema/Custom",
270 payload: null
271 },
272 toCode
273 })
274
275 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
276 representation: {
277 _tag: "Declaration",
278 typeParameters: [],
279 checks: [],
280 representation: {
281 id: "acme/schema/Custom",
282 payload: null
283 },
284 annotations: {
285 toCode
286 }
287 },
288 references: {}
289 })
290 })
291
292 it("converts declaration type parameters", () => {
293 const representation = SchemaRepresentation.toRepresentation(Schema.Option(Schema.Number).ast).representation
294
295 assert.strictEqual(representation._tag, "Declaration")
296 if (representation._tag !== "Declaration") return
297 assert.deepStrictEqual(representation.typeParameters, [{ _tag: "Number", checks: [] }])
298 })
299 })
300
301 describe("checks", () => {
302 it("preserves custom filter callbacks, dependencies and aborted state", () => {
303 const toCode: SchemaRepresentation.Generation.Check = () => ({ runtime: "Custom" })
304 const toJsonSchema: SchemaRepresentation.ToJsonSchema.Check = () => ({ minLength: 1 })
305 const marker = () => "marker"
306 const filter = Schema.makeFilter<string>(() => true, {
307 representation: {
308 id: "acme/schema/Custom",
309 payload: { minimum: 1 },
310 schemas: [Schema.Number.ast]
311 },
312 toCode,
313 toJsonSchema,
314 marker
315 }).abort()
316
317 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.String.check(filter).ast), {
318 representation: {
319 _tag: "String",
320 checks: [{
321 _tag: "Filter",
322 representation: {
323 id: "acme/schema/Custom",
324 payload: { minimum: 1 },
325 schemas: [{ _tag: "Number", checks: [] }]
326 },
327 annotations: {
328 toCode,
329 toJsonSchema,
330 marker
331 },
332 aborted: true
333 }]
334 },
335 references: {}
336 })
337 })
338
339 it("preserves filters without persistence metadata", () => {
340 const filter = Schema.makeFilter<string>(() => true, { expected: "custom" })
341
342 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.String.check(filter).ast), {
343 representation: {
344 _tag: "String",
345 checks: [{
346 _tag: "Filter",
347 annotations: { expected: "custom" },
348 aborted: false
349 }]
350 },
351 references: {}
352 })
353 })
354
355 it("preserves a filter without annotations", () => {
356 const filter = Schema.makeFilter<string>(() => true)
357
358 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.String.check(filter).ast), {
359 representation: {
360 _tag: "String",
361 checks: [{ _tag: "Filter", aborted: false }]
362 },
363 references: {}
364 })
365 })
366
367 it("preserves filter groups", () => {
368 const first = Schema.makeFilter<string>(() => true, { expected: "first" })
369 const second = Schema.makeFilter<string>(() => true, { expected: "second" })
370 const group = Schema.makeFilterGroup([first, second], { description: "group" })
371
372 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.String.check(group).ast), {
373 representation: {
374 _tag: "String",
375 checks: [{
376 _tag: "FilterGroup",
377 annotations: { description: "group" },
378 checks: [
379 { _tag: "Filter", annotations: { expected: "first" }, aborted: false },
380 { _tag: "Filter", annotations: { expected: "second" }, aborted: false }
381 ]
382 }]
383 },
384 references: {}
385 })
386 })
387
388 it("converts representation dependencies of built-in filters", () => {
389 const schema = Schema.Record(Schema.String, Schema.Number).check(
390 Schema.isPropertyNames(Schema.String.check(Schema.isPattern(/^[A-Z]/)))
391 )
392 const representation = SchemaRepresentation.toRepresentation(schema.ast).representation
393
394 assert.strictEqual(representation._tag, "Objects")
395 if (representation._tag !== "Objects") return
396 const check = representation.checks[0]
397 assert.strictEqual(check._tag, "Filter")
398 if (check._tag !== "Filter") return
399 const dependency = check.representation?.schemas?.[0]
400 assert.isDefined(dependency)
401 assert.deepStrictEqual(
402 SchemaRepresentation.toJson({ representation: dependency, references: {} }),
403 {
404 representation: {
405 _tag: "String",
406 checks: [{
407 _tag: "Filter",
408 representation: {
409 id: "effect/schema/isPattern",
410 payload: { source: "^[A-Z]", flags: "" }
411 },
412 annotations: {
413 expected: "a string matching the RegExp ^[A-Z]",
414 arbitraryConstraint: { patterns: [{ source: "^[A-Z]", flags: "" }] }
415 },
416 aborted: false
417 }]
418 },
419 references: {}
420 }
421 )
422 })
423
424 it("extracts shared representation dependencies of filters", () => {
425 const shared = Schema.Struct({ value: Schema.String })
426 const filter = Schema.makeFilter<string>(() => true, {
427 representation: {
428 id: "acme/schema/Custom",
429 payload: null,
430 schemas: [shared.ast, shared.ast]
431 }
432 })
433 const document = SchemaRepresentation.toRepresentation(Schema.String.check(filter).ast, {
434 referencePolicy: ({ ast, occurrences }) => occurrences > 1 ? `${ast._tag}_` : undefined
435 })
436 const representation = document.representation
437
438 assert.strictEqual(representation._tag, "String")
439 if (representation._tag !== "String") return
440 assert.deepStrictEqual(representation.checks[0].representation?.schemas, [
441 { _tag: "Reference", $ref: "Objects_" },
442 { _tag: "Reference", $ref: "Objects_" }
443 ])
444 assert.deepStrictEqual(Object.keys(document.references), ["Objects_"])
445 })
446
447 it("converts checks on arrays", () => {
448 const representation = SchemaRepresentation.toRepresentation(
449 Schema.Array(Schema.String).check(Schema.isMinLength(1)).ast
450 ).representation
451
452 assert.strictEqual(representation._tag, "Arrays")
453 if (representation._tag !== "Arrays") return
454 assert.strictEqual(representation.checks.length, 1)
455 assert.deepStrictEqual(representation.checks[0].representation, {
456 id: "effect/schema/isMinLength",
457 payload: { minLength: 1 }
458 })
459 })
460
461 it("converts checks on objects", () => {
462 const representation = SchemaRepresentation.toRepresentation(
463 Schema.Record(Schema.String, Schema.Number).check(Schema.isMinProperties(1)).ast
464 ).representation
465
466 assert.strictEqual(representation._tag, "Objects")
467 if (representation._tag !== "Objects") return
468 assert.strictEqual(representation.checks.length, 1)
469 assert.deepStrictEqual(representation.checks[0].representation, {
470 id: "effect/schema/isMinProperties",
471 payload: { minProperties: 1 }
472 })
473 })
474 })
475
476 describe("contextual annotations", () => {
477 it("preserves fromJsonString annotations", () => {
478 const schema = SchemaAST.toEncoded(Schema.fromJsonString(Schema.Struct({ value: Schema.Number })).ast)
479 const document = SchemaRepresentation.toRepresentation(schema)
480
481 assert.deepStrictEqual(document, {
482 representation: {
483 _tag: "String",
484 annotations: {
485 contentMediaType: "application/json",
486 expected: "a string that will be decoded as JSON"
487 },
488 checks: []
489 },
490 references: {}
491 })
492 })
493
494 it("preserves tuple element annotations", () => {
495 const marker = () => "element"
496 const schema = Schema.Tuple([Schema.String.annotateKey({ description: "element", marker })])
497
498 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
499 representation: {
500 _tag: "Arrays",
501 elements: [{
502 type: { _tag: "String", checks: [] },
503 isOptional: false,
504 annotations: { description: "element", marker }
505 }],
506 rest: [],
507 checks: []
508 },
509 references: {}
510 })
511 })
512
513 it("preserves property annotations", () => {
514 const marker = () => "property"
515 const schema = Schema.Struct({
516 value: Schema.String.annotateKey({ description: "property", marker })
517 })
518
519 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
520 representation: {
521 _tag: "Objects",
522 propertySignatures: [{
523 name: "value",
524 type: { _tag: "String", checks: [] },
525 isOptional: false,
526 isMutable: false,
527 annotations: { description: "property", marker }
528 }],
529 indexSignatures: [],
530 checks: []
531 },
532 references: {}
533 })
534 })
535 })
536
537 describe("reference policies", () => {
538 it("extracts only candidates with identifiers by default", () => {
539 const anonymous = Schema.Struct({ value: Schema.String })
540 const identified = Schema.Struct({ value: Schema.String }).annotate({ identifier: "Identified" })
541 const document = SchemaRepresentation.toRepresentation(
542 Schema.Tuple([anonymous, identified]).ast
543 )
544
545 assert.deepStrictEqual(Object.keys(document.references), ["Identified"])
546 assert.strictEqual(document.representation._tag, "Arrays")
547 if (document.representation._tag === "Arrays") {
548 assert.deepStrictEqual(document.representation.elements.map((element) => element.type._tag), [
549 "Objects",
550 "Reference"
551 ])
552 }
553 })
554
555 it("allows a policy to inline an explicitly identified schema", () => {
556 const schema = Schema.String.annotate({ identifier: "Value" })
557
558 assert.deepStrictEqual(
559 SchemaRepresentation.toRepresentation(schema.ast, { referencePolicy: () => undefined }),
560 {
561 representation: {
562 _tag: "String",
563 annotations: { identifier: "Value" },
564 checks: []
565 },
566 references: {}
567 }
568 )
569 })
570
571 it("forces a synthetic reference when a policy inlines a recursive schema", () => {
572 interface Node {
573 readonly next?: Node
574 }
575 const Node = Schema.Struct({
576 next: Schema.optionalKey(Schema.suspend((): Schema.Codec<Node> => Node))
577 }).annotate({ identifier: "Node" })
578 const document = SchemaRepresentation.toRepresentation(Node.ast, {
579 referencePolicy: () => undefined
580 })
581
582 assert.deepStrictEqual(document.representation, { _tag: "Reference", $ref: "Objects_" })
583 assert.deepStrictEqual(Object.keys(document.references), ["Objects_"])
584 })
585 })
586
587 describe("reference allocation and recursion", () => {
588 it("extracts a named schema into references", () => {
589 const schema = Schema.String.annotate({ identifier: "Value", description: "value" })
590
591 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
592 representation: { _tag: "Reference", $ref: "Value" },
593 references: {
594 Value: {
595 _tag: "String",
596 annotations: { identifier: "Value", description: "value" },
597 checks: []
598 }
599 }
600 })
601 })
602
603 it("supports __proto__ as an identifier", () => {
604 const document = SchemaRepresentation.toRepresentation(
605 Schema.String.annotate({ identifier: "__proto__" }).ast
606 )
607
608 assert.deepStrictEqual(document.representation, { _tag: "Reference", $ref: "__proto__" })
609 assert.deepStrictEqual(Object.keys(document.references), ["__proto__"])
610 assert.strictEqual(Object.getPrototypeOf(document.references), Object.prototype)
611 assert.isTrue(Object.hasOwn(document.references, "__proto__"))
612 assert.strictEqual(document.references["__proto__"]._tag, "String")
613 })
614
615 it("converts a non-recursive suspend", () => {
616 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Schema.suspend(() => Schema.String).ast), {
617 representation: {
618 _tag: "Suspend",
619 thunk: { _tag: "String", checks: [] },
620 checks: []
621 },
622 references: {}
623 })
624 })
625
626 it("uses an outer identifier for a recursive schema", () => {
627 interface Node {
628 readonly next?: Node
629 }
630 const Node = Schema.Struct({
631 next: Schema.optionalKey(Schema.suspend((): Schema.Codec<Node> => Node))
632 }).annotate({ identifier: "Node" })
633
634 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(Node.ast), {
635 representation: { _tag: "Reference", $ref: "Node" },
636 references: {
637 Node: {
638 _tag: "Objects",
639 annotations: { identifier: "Node" },
640 propertySignatures: [{
641 name: "next",
642 type: {
643 _tag: "Suspend",
644 thunk: { _tag: "Reference", $ref: "Node" },
645 checks: []
646 },
647 isOptional: true,
648 isMutable: false
649 }],
650 indexSignatures: [],
651 checks: []
652 }
653 }
654 })
655 })
656
657 it("suffixes duplicate identifiers on recursive schemas", () => {
658 interface First {
659 readonly next?: First
660 }
661 const First = Schema.Struct({
662 next: Schema.optionalKey(Schema.suspend((): Schema.Codec<First> => First))
663 }).annotate({ identifier: "Node" })
664 interface Second {
665 readonly next?: Second
666 }
667 const Second = Schema.Struct({
668 next: Schema.optionalKey(Schema.suspend((): Schema.Codec<Second> => Second))
669 }).annotate({ identifier: "Node" })
670
671 assert.deepStrictEqual(
672 SchemaRepresentation.toRepresentation(Schema.Tuple([First, Second]).ast),
673 {
674 representation: {
675 _tag: "Arrays",
676 elements: [
677 { isOptional: false, type: { _tag: "Reference", $ref: "Node" } },
678 { isOptional: false, type: { _tag: "Reference", $ref: "Node_1" } }
679 ],
680 rest: [],
681 checks: []
682 },
683 references: {
684 Node: {
685 _tag: "Objects",
686 propertySignatures: [{
687 name: "next",
688 type: {
689 _tag: "Suspend",
690 checks: [],
691 thunk: { _tag: "Reference", $ref: "Node" }
692 },
693 isOptional: true,
694 isMutable: false
695 }],
696 indexSignatures: [],
697 checks: [],
698 annotations: { identifier: "Node" }
699 },
700 Node_1: {
701 _tag: "Objects",
702 propertySignatures: [{
703 name: "next",
704 type: {
705 _tag: "Suspend",
706 checks: [],
707 thunk: { _tag: "Reference", $ref: "Node_1" }
708 },
709 isOptional: true,
710 isMutable: false
711 }],
712 indexSignatures: [],
713 checks: [],
714 annotations: { identifier: "Node_1" }
715 }
716 }
717 }
718 )
719 })
720
721 it("does not resolve an identifier below a check", () => {
722 const schema = Schema.String
723 .annotate({ identifier: "Text" })
724 .pipe(Schema.check(Schema.isMinLength(1)))
725 const document = SchemaRepresentation.toRepresentation(schema.ast)
726
727 assert.strictEqual(document.representation._tag, "String")
728 assert.deepStrictEqual(document.references, {})
729 if (document.representation._tag === "String") {
730 assert.deepStrictEqual(document.representation.annotations, { identifier: "Text" })
731 assert.strictEqual(document.representation.checks.length, 1)
732 assert.strictEqual(document.representation.checks[0].representation?.id, "effect/schema/isMinLength")
733 }
734 })
735
736 it("uses a fallback identifier for encoded representations", () => {
737 const schema = Schema.String.annotate({ "~identifier": "Person" })
738
739 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
740 representation: { _tag: "Reference", $ref: "PersonEncoded" },
741 references: {
742 PersonEncoded: {
743 _tag: "String",
744 checks: [],
745 annotations: { "~identifier": "Person" }
746 }
747 }
748 })
749 })
750
751 it("prefers an identifier over a fallback identifier", () => {
752 const schema = Schema.String.annotate({ identifier: "EncodedPerson", "~identifier": "Person" })
753 const document = SchemaRepresentation.toRepresentation(schema.ast)
754
755 assert.deepStrictEqual(document.representation, { _tag: "Reference", $ref: "EncodedPerson" })
756 assert.deepStrictEqual(Object.keys(document.references), ["EncodedPerson"])
757 })
758
759 it("reuses a Class identifier across repeated type-side occurrences", () => {
760 class User extends Schema.Class<User>("User")({ name: Schema.String }) {}
761
762 const document = SchemaRepresentation.toRepresentation(SchemaAST.toType(Schema.Tuple([User, User]).ast))
763 assert.deepStrictEqual(document.representation, {
764 _tag: "Arrays",
765 elements: [
766 { type: { _tag: "Reference", $ref: "User" }, isOptional: false },
767 { type: { _tag: "Reference", $ref: "User" }, isOptional: false }
768 ],
769 rest: [],
770 checks: []
771 })
772 assert.deepStrictEqual(Object.keys(document.references), ["User"])
773 })
774
775 it("extracts anonymous recursion into references", () => {
776 let schema: Schema.Codec<unknown>
777 schema = Schema.suspend((): Schema.Codec<unknown> => schema)
778
779 assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schema.ast), {
780 representation: { _tag: "Reference", $ref: "Suspend_" },
781 references: {
782 Suspend_: {
783 _tag: "Suspend",
784 thunk: { _tag: "Reference", $ref: "Suspend_" },
785 checks: []
786 }
787 }
788 })
789 })
790 })
791})