prototypey.org - atproto lexicon typescript toolkit - mirror https://github.com/tylersayshi/prototypey

update docs and add bsky profile test for fromJSON

Tyler d5d3143d df46810b

+89 -116
+5
.changeset/cold-bugs-turn.md
···
··· 1 + --- 2 + "prototypey": patch 3 + --- 4 + 5 + update docs - we're featured!
+1 -1
packages/prototypey/README.md
··· 1 # prototypey 2 3 - A (soon-to-be) fully-featured sdk for developing lexicons with typescript. 4 5 ## Installation 6
··· 1 # prototypey 2 3 + A fully-featured sdk for developing lexicons with typescript. 4 5 ## Installation 6
+83 -115
packages/prototypey/core/tests/from-json-infer.test.ts
··· 1 import { test } from "vitest"; 2 import { attest } from "@ark/attest"; 3 import { fromJSON } from "../lib.ts"; 4 5 test("fromJSON InferNS produces expected type shape", () => { 6 const exampleLexicon = fromJSON({ ··· 503 // REF TYPE TESTS 504 // ============================================================================ 505 506 - test("fromJSON InferRef handles basic reference", () => { 507 const lexicon = fromJSON({ 508 id: "test.ref", 509 defs: { ··· 524 }`); 525 }); 526 527 - test("fromJSON InferRef handles required reference", () => { 528 - const lexicon = fromJSON({ 529 - id: "test.refRequired", 530 - defs: { 531 - main: { 532 - type: "object", 533 - properties: { 534 - author: { type: "ref", ref: "com.example.user", required: true }, 535 - }, 536 - required: ["author"], 537 - }, 538 - }, 539 - }); 540 - 541 - attest(lexicon["~infer"]).type.toString.snap(`{ 542 - $type: "test.refRequired" 543 - author: { 544 - [x: string]: unknown 545 - $type: "com.example.user" 546 - } 547 - }`); 548 - }); 549 - 550 - test("fromJSON InferRef handles nullable reference", () => { 551 - const lexicon = fromJSON({ 552 - id: "test.refNullable", 553 - defs: { 554 - main: { 555 - type: "object", 556 - properties: { 557 - parent: { type: "ref", ref: "com.example.node", nullable: true }, 558 - }, 559 - nullable: ["parent"], 560 - }, 561 - }, 562 - }); 563 - 564 - attest(lexicon["~infer"]).type.toString.snap(`{ 565 - $type: "test.refNullable" 566 - parent?: 567 - | { [x: string]: unknown; $type: "com.example.node" } 568 - | null 569 - | undefined 570 - }`); 571 - }); 572 - 573 // ============================================================================ 574 // UNION TYPE TESTS 575 // ============================================================================ 576 577 - test("fromJSON InferUnion handles basic union", () => { 578 const lexicon = fromJSON({ 579 id: "test.union", 580 defs: { ··· 599 }`); 600 }); 601 602 - test("fromJSON InferUnion handles required union", () => { 603 - const lexicon = fromJSON({ 604 - id: "test.unionRequired", 605 - defs: { 606 - main: { 607 - type: "object", 608 - properties: { 609 - media: { 610 - type: "union", 611 - refs: ["com.example.video", "com.example.audio"], 612 - required: true, 613 - }, 614 - }, 615 - required: ["media"], 616 - }, 617 - }, 618 - }); 619 - 620 - attest(lexicon["~infer"]).type.toString.snap(`{ 621 - $type: "test.unionRequired" 622 - media: 623 - | { [x: string]: unknown; $type: "com.example.video" } 624 - | { [x: string]: unknown; $type: "com.example.audio" } 625 - }`); 626 - }); 627 - 628 - test("fromJSON InferUnion handles union with many types", () => { 629 - const lexicon = fromJSON({ 630 - id: "test.unionMultiple", 631 - defs: { 632 - main: { 633 - type: "object", 634 - properties: { 635 - attachment: { 636 - type: "union", 637 - refs: [ 638 - "com.example.image", 639 - "com.example.video", 640 - "com.example.audio", 641 - "com.example.document", 642 - ], 643 - }, 644 - }, 645 - }, 646 - }, 647 - }); 648 - 649 - attest(lexicon["~infer"]).type.toString.snap(`{ 650 - $type: "test.unionMultiple" 651 - attachment?: 652 - | { [x: string]: unknown; $type: "com.example.image" } 653 - | { [x: string]: unknown; $type: "com.example.video" } 654 - | { [x: string]: unknown; $type: "com.example.audio" } 655 - | { 656 - [x: string]: unknown 657 - $type: "com.example.document" 658 - } 659 - | undefined 660 - }`); 661 - }); 662 - 663 // ============================================================================ 664 // PARAMS TYPE TESTS 665 // ============================================================================ ··· 869 const lexicon = fromJSON({ 870 id: "test.arrayOfRefs", 871 defs: { 872 main: { 873 type: "object", 874 properties: { 875 followers: { 876 type: "array", 877 - items: { type: "ref", ref: "com.example.user" }, 878 }, 879 }, 880 }, ··· 884 attest(lexicon["~infer"]).type.toString.snap(`{ 885 $type: "test.arrayOfRefs" 886 followers?: 887 - | { [x: string]: unknown; $type: "com.example.user" }[] 888 | undefined 889 }`); 890 }); ··· 897 const lexicon = fromJSON({ 898 id: "test.complex", 899 defs: { 900 main: { 901 type: "object", 902 properties: { ··· 912 }, 913 content: { 914 type: "union", 915 - refs: ["com.example.text", "com.example.image"], 916 }, 917 tags: { type: "array", items: { type: "string" }, maxLength: 10 }, 918 metadata: { ··· 933 $type: "test.complex" 934 tags?: string[] | undefined 935 content?: 936 - | { [x: string]: unknown; $type: "com.example.text" } 937 - | { [x: string]: unknown; $type: "com.example.image" } 938 | undefined 939 author?: 940 | { ··· 982 type: "object", 983 properties: { 984 text: { type: "string", required: true }, 985 - author: { type: "ref", ref: "com.example.user" }, 986 }, 987 required: ["text"], 988 }, ··· 1256 author: "[Reference not found: #user]" 1257 }`); 1258 });
··· 1 import { test } from "vitest"; 2 import { attest } from "@ark/attest"; 3 import { fromJSON } from "../lib.ts"; 4 + import { Infer } from "../infer.ts"; 5 6 test("fromJSON InferNS produces expected type shape", () => { 7 const exampleLexicon = fromJSON({ ··· 504 // REF TYPE TESTS 505 // ============================================================================ 506 507 + test("fromJSON InferRef handles external reference (unknown)", () => { 508 const lexicon = fromJSON({ 509 id: "test.ref", 510 defs: { ··· 525 }`); 526 }); 527 528 // ============================================================================ 529 // UNION TYPE TESTS 530 // ============================================================================ 531 532 + test("fromJSON InferUnion handles external union (unknown)", () => { 533 const lexicon = fromJSON({ 534 id: "test.union", 535 defs: { ··· 554 }`); 555 }); 556 557 // ============================================================================ 558 // PARAMS TYPE TESTS 559 // ============================================================================ ··· 763 const lexicon = fromJSON({ 764 id: "test.arrayOfRefs", 765 defs: { 766 + user: { 767 + type: "object", 768 + properties: { 769 + name: { type: "string", required: true }, 770 + handle: { type: "string", required: true }, 771 + }, 772 + required: ["name", "handle"], 773 + }, 774 main: { 775 type: "object", 776 properties: { 777 followers: { 778 type: "array", 779 + items: { type: "ref", ref: "#user" }, 780 }, 781 }, 782 }, ··· 786 attest(lexicon["~infer"]).type.toString.snap(`{ 787 $type: "test.arrayOfRefs" 788 followers?: 789 + | { handle: string; name: string; $type: "#user" }[] 790 | undefined 791 }`); 792 }); ··· 799 const lexicon = fromJSON({ 800 id: "test.complex", 801 defs: { 802 + text: { 803 + type: "object", 804 + properties: { 805 + content: { type: "string", required: true }, 806 + }, 807 + required: ["content"], 808 + }, 809 + image: { 810 + type: "object", 811 + properties: { 812 + url: { type: "string", required: true }, 813 + alt: { type: "string" }, 814 + }, 815 + required: ["url"], 816 + }, 817 main: { 818 type: "object", 819 properties: { ··· 829 }, 830 content: { 831 type: "union", 832 + refs: ["#text", "#image"], 833 }, 834 tags: { type: "array", items: { type: "string" }, maxLength: 10 }, 835 metadata: { ··· 850 $type: "test.complex" 851 tags?: string[] | undefined 852 content?: 853 + | { content: string; $type: "#text" } 854 + | { 855 + alt?: string | undefined 856 + url: string 857 + $type: "#image" 858 + } 859 | undefined 860 author?: 861 | { ··· 903 type: "object", 904 properties: { 905 text: { type: "string", required: true }, 906 + author: { type: "ref", ref: "#user" }, 907 }, 908 required: ["text"], 909 }, ··· 1177 author: "[Reference not found: #user]" 1178 }`); 1179 }); 1180 + 1181 + // ============================================================================ 1182 + // REAL-WORLD EXAMPLE: BLUESKY PROFILE 1183 + // ============================================================================ 1184 + 1185 + test("fromJSON Real-world example: app.bsky.actor.profile", () => { 1186 + const lexicon = fromJSON({ 1187 + id: "app.bsky.actor.profile", 1188 + defs: { 1189 + main: { 1190 + type: "record", 1191 + key: "self", 1192 + record: { 1193 + type: "object", 1194 + properties: { 1195 + displayName: { 1196 + type: "string", 1197 + maxLength: 64, 1198 + maxGraphemes: 64, 1199 + }, 1200 + description: { 1201 + type: "string", 1202 + maxLength: 256, 1203 + maxGraphemes: 256, 1204 + }, 1205 + }, 1206 + }, 1207 + }, 1208 + }, 1209 + }); 1210 + 1211 + type Profile = Infer<typeof lexicon>; 1212 + 1213 + const george: Profile = { 1214 + $type: "app.bsky.actor.profile", 1215 + description: "George", 1216 + }; 1217 + 1218 + lexicon.validate({ foo: "bar" }); // will fail 1219 + lexicon.validate(george); // will pass 🎉 1220 + 1221 + attest(lexicon["~infer"]).type.toString.snap(`{ 1222 + $type: "app.bsky.actor.profile" 1223 + displayName?: string | undefined 1224 + description?: string | undefined 1225 + }`); 1226 + });