tangled
alpha
login
or
join now
tylur.dev
/
prototypey
prototypey.org - atproto lexicon typescript toolkit - mirror https://github.com/tylersayshi/prototypey
1
fork
atom
overview
issues
pulls
pipelines
update docs and add bsky profile test for fromJSON
Tyler
3 months ago
d5d3143d
df46810b
+89
-116
3 changed files
expand all
collapse all
unified
split
.changeset
cold-bugs-turn.md
packages
prototypey
README.md
core
tests
from-json-infer.test.ts
+5
.changeset/cold-bugs-turn.md
···
0
0
0
0
0
···
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";
0
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: {
0
0
0
0
0
0
0
0
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: {
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
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" }
0
0
0
0
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
});
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
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
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
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
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
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
+
});