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
initial pass at infer
Tyler
4 months ago
6e32af66
45a03e14
+633
13 changed files
expand all
collapse all
unified
split
deno.json
samples
actor-namespace.json
generator-view.json
post-view.json
primitives-array.json
primitives-blob.json
primitives-token.json
procedure-create-post.json
profile-namespace.json
profile-view-basic.json
query-search-posts.json
subscription-repos.json
src
infer.ts
+3
deno.json
···
4
4
},
5
5
"imports": {
6
6
"@std/assert": "jsr:@std/assert@1"
7
7
+
},
8
8
+
"compilerOptions": {
9
9
+
"exactOptionalPropertyTypes": true
7
10
}
8
11
}
+103
samples/actor-namespace.json
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "app.bsky.actor.defs",
4
4
+
"defs": {
5
5
+
"profileViewBasic": {
6
6
+
"type": "object",
7
7
+
"properties": {
8
8
+
"did": {
9
9
+
"type": "string",
10
10
+
"required": true,
11
11
+
"format": "did"
12
12
+
},
13
13
+
"handle": {
14
14
+
"type": "string",
15
15
+
"required": true,
16
16
+
"format": "handle"
17
17
+
},
18
18
+
"displayName": {
19
19
+
"type": "string",
20
20
+
"maxGraphemes": 64,
21
21
+
"maxLength": 640
22
22
+
},
23
23
+
"pronouns": {
24
24
+
"type": "string"
25
25
+
},
26
26
+
"avatar": {
27
27
+
"type": "string",
28
28
+
"format": "uri"
29
29
+
},
30
30
+
"associated": {
31
31
+
"type": "ref",
32
32
+
"ref": "#profileAssociated"
33
33
+
},
34
34
+
"viewer": {
35
35
+
"type": "ref",
36
36
+
"ref": "#viewerState"
37
37
+
},
38
38
+
"labels": {
39
39
+
"type": "array",
40
40
+
"items": {
41
41
+
"type": "ref",
42
42
+
"ref": "com.atproto.label.defs#label"
43
43
+
}
44
44
+
},
45
45
+
"createdAt": {
46
46
+
"type": "string",
47
47
+
"format": "datetime"
48
48
+
},
49
49
+
"verification": {
50
50
+
"type": "ref",
51
51
+
"ref": "#verificationState"
52
52
+
},
53
53
+
"status": {
54
54
+
"type": "ref",
55
55
+
"ref": "#statusView"
56
56
+
}
57
57
+
},
58
58
+
"required": [
59
59
+
"did",
60
60
+
"handle"
61
61
+
]
62
62
+
},
63
63
+
"viewerState": {
64
64
+
"type": "object",
65
65
+
"properties": {
66
66
+
"muted": {
67
67
+
"type": "boolean"
68
68
+
},
69
69
+
"mutedByList": {
70
70
+
"type": "ref",
71
71
+
"ref": "app.bsky.graph.defs#listViewBasic"
72
72
+
},
73
73
+
"blockedBy": {
74
74
+
"type": "boolean"
75
75
+
},
76
76
+
"blocking": {
77
77
+
"type": "string",
78
78
+
"format": "at-uri"
79
79
+
},
80
80
+
"blockingByList": {
81
81
+
"type": "ref",
82
82
+
"ref": "app.bsky.graph.defs#listViewBasic"
83
83
+
},
84
84
+
"following": {
85
85
+
"type": "string",
86
86
+
"format": "at-uri"
87
87
+
},
88
88
+
"followedBy": {
89
89
+
"type": "string",
90
90
+
"format": "at-uri"
91
91
+
},
92
92
+
"knownFollowers": {
93
93
+
"type": "ref",
94
94
+
"ref": "#knownFollowers"
95
95
+
},
96
96
+
"activitySubscription": {
97
97
+
"type": "ref",
98
98
+
"ref": "app.bsky.notification.defs#activitySubscription"
99
99
+
}
100
100
+
}
101
101
+
}
102
102
+
}
103
103
+
}
+83
samples/generator-view.json
···
1
1
+
{
2
2
+
"type": "object",
3
3
+
"properties": {
4
4
+
"uri": {
5
5
+
"type": "string",
6
6
+
"required": true,
7
7
+
"format": "at-uri"
8
8
+
},
9
9
+
"cid": {
10
10
+
"type": "string",
11
11
+
"required": true,
12
12
+
"format": "cid"
13
13
+
},
14
14
+
"did": {
15
15
+
"type": "string",
16
16
+
"required": true,
17
17
+
"format": "did"
18
18
+
},
19
19
+
"creator": {
20
20
+
"type": "ref",
21
21
+
"ref": "app.bsky.actor.defs#profileView",
22
22
+
"required": true
23
23
+
},
24
24
+
"displayName": {
25
25
+
"type": "string",
26
26
+
"required": true
27
27
+
},
28
28
+
"description": {
29
29
+
"type": "string",
30
30
+
"maxGraphemes": 300,
31
31
+
"maxLength": 3000
32
32
+
},
33
33
+
"descriptionFacets": {
34
34
+
"type": "array",
35
35
+
"items": {
36
36
+
"type": "ref",
37
37
+
"ref": "app.bsky.richtext.facet"
38
38
+
}
39
39
+
},
40
40
+
"avatar": {
41
41
+
"type": "string",
42
42
+
"format": "uri"
43
43
+
},
44
44
+
"likeCount": {
45
45
+
"type": "integer",
46
46
+
"minimum": 0
47
47
+
},
48
48
+
"acceptsInteractions": {
49
49
+
"type": "boolean"
50
50
+
},
51
51
+
"labels": {
52
52
+
"type": "array",
53
53
+
"items": {
54
54
+
"type": "ref",
55
55
+
"ref": "com.atproto.label.defs#label"
56
56
+
}
57
57
+
},
58
58
+
"viewer": {
59
59
+
"type": "ref",
60
60
+
"ref": "#generatorViewerState"
61
61
+
},
62
62
+
"contentMode": {
63
63
+
"type": "string",
64
64
+
"knownValues": [
65
65
+
"app.bsky.feed.defs#contentModeUnspecified",
66
66
+
"app.bsky.feed.defs#contentModeVideo"
67
67
+
]
68
68
+
},
69
69
+
"indexedAt": {
70
70
+
"type": "string",
71
71
+
"required": true,
72
72
+
"format": "datetime"
73
73
+
}
74
74
+
},
75
75
+
"required": [
76
76
+
"uri",
77
77
+
"cid",
78
78
+
"did",
79
79
+
"creator",
80
80
+
"displayName",
81
81
+
"indexedAt"
82
82
+
]
83
83
+
}
+76
samples/post-view.json
···
1
1
+
{
2
2
+
"type": "object",
3
3
+
"properties": {
4
4
+
"uri": {
5
5
+
"type": "string",
6
6
+
"required": true,
7
7
+
"format": "at-uri"
8
8
+
},
9
9
+
"cid": {
10
10
+
"type": "string",
11
11
+
"required": true,
12
12
+
"format": "cid"
13
13
+
},
14
14
+
"author": {
15
15
+
"type": "ref",
16
16
+
"ref": "app.bsky.actor.defs#profileViewBasic",
17
17
+
"required": true
18
18
+
},
19
19
+
"record": {
20
20
+
"type": "unknown",
21
21
+
"required": true
22
22
+
},
23
23
+
"embed": {
24
24
+
"type": "union",
25
25
+
"refs": [
26
26
+
"app.bsky.embed.images#view",
27
27
+
"app.bsky.embed.video#view",
28
28
+
"app.bsky.embed.external#view",
29
29
+
"app.bsky.embed.record#view",
30
30
+
"app.bsky.embed.recordWithMedia#view"
31
31
+
]
32
32
+
},
33
33
+
"bookmarkCount": {
34
34
+
"type": "integer"
35
35
+
},
36
36
+
"replyCount": {
37
37
+
"type": "integer"
38
38
+
},
39
39
+
"repostCount": {
40
40
+
"type": "integer"
41
41
+
},
42
42
+
"likeCount": {
43
43
+
"type": "integer"
44
44
+
},
45
45
+
"quoteCount": {
46
46
+
"type": "integer"
47
47
+
},
48
48
+
"indexedAt": {
49
49
+
"type": "string",
50
50
+
"required": true,
51
51
+
"format": "datetime"
52
52
+
},
53
53
+
"viewer": {
54
54
+
"type": "ref",
55
55
+
"ref": "#viewerState"
56
56
+
},
57
57
+
"labels": {
58
58
+
"type": "array",
59
59
+
"items": {
60
60
+
"type": "ref",
61
61
+
"ref": "com.atproto.label.defs#label"
62
62
+
}
63
63
+
},
64
64
+
"threadgate": {
65
65
+
"type": "ref",
66
66
+
"ref": "#threadgateView"
67
67
+
}
68
68
+
},
69
69
+
"required": [
70
70
+
"uri",
71
71
+
"cid",
72
72
+
"author",
73
73
+
"record",
74
74
+
"indexedAt"
75
75
+
]
76
76
+
}
+9
samples/primitives-array.json
···
1
1
+
{
2
2
+
"type": "array",
3
3
+
"items": {
4
4
+
"type": "string"
5
5
+
},
6
6
+
"minLength": 1,
7
7
+
"maxLength": 10,
8
8
+
"required": true
9
9
+
}
+8
samples/primitives-blob.json
···
1
1
+
{
2
2
+
"type": "blob",
3
3
+
"accept": [
4
4
+
"image/png",
5
5
+
"image/jpeg"
6
6
+
],
7
7
+
"maxSize": 5000000
8
8
+
}
+4
samples/primitives-token.json
···
1
1
+
{
2
2
+
"type": "token",
3
3
+
"description": "Request that less content like the given feed item be shown in the feed"
4
4
+
}
+61
samples/procedure-create-post.json
···
1
1
+
{
2
2
+
"type": "procedure",
3
3
+
"description": "Create a post",
4
4
+
"input": {
5
5
+
"encoding": "application/json",
6
6
+
"schema": {
7
7
+
"type": "object",
8
8
+
"properties": {
9
9
+
"repo": {
10
10
+
"type": "string",
11
11
+
"required": true
12
12
+
},
13
13
+
"collection": {
14
14
+
"type": "string",
15
15
+
"required": true
16
16
+
},
17
17
+
"record": {
18
18
+
"type": "unknown",
19
19
+
"required": true
20
20
+
},
21
21
+
"validate": {
22
22
+
"type": "boolean",
23
23
+
"default": true
24
24
+
}
25
25
+
},
26
26
+
"required": [
27
27
+
"repo",
28
28
+
"collection",
29
29
+
"record"
30
30
+
]
31
31
+
}
32
32
+
},
33
33
+
"output": {
34
34
+
"encoding": "application/json",
35
35
+
"schema": {
36
36
+
"type": "object",
37
37
+
"properties": {
38
38
+
"uri": {
39
39
+
"type": "string",
40
40
+
"required": true
41
41
+
},
42
42
+
"cid": {
43
43
+
"type": "string",
44
44
+
"required": true
45
45
+
}
46
46
+
},
47
47
+
"required": [
48
48
+
"uri",
49
49
+
"cid"
50
50
+
]
51
51
+
}
52
52
+
},
53
53
+
"errors": [
54
54
+
{
55
55
+
"name": "InvalidSwap"
56
56
+
},
57
57
+
{
58
58
+
"name": "InvalidRecord"
59
59
+
}
60
60
+
]
61
61
+
}
+25
samples/profile-namespace.json
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "app.bsky.actor.profile",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "record",
7
7
+
"key": "self",
8
8
+
"record": {
9
9
+
"type": "object",
10
10
+
"properties": {
11
11
+
"displayName": {
12
12
+
"type": "string",
13
13
+
"maxLength": 64,
14
14
+
"maxGraphemes": 64
15
15
+
},
16
16
+
"description": {
17
17
+
"type": "string",
18
18
+
"maxLength": 256,
19
19
+
"maxGraphemes": 256
20
20
+
}
21
21
+
}
22
22
+
}
23
23
+
}
24
24
+
}
25
25
+
}
+58
samples/profile-view-basic.json
···
1
1
+
{
2
2
+
"type": "object",
3
3
+
"properties": {
4
4
+
"did": {
5
5
+
"type": "string",
6
6
+
"required": true,
7
7
+
"format": "did"
8
8
+
},
9
9
+
"handle": {
10
10
+
"type": "string",
11
11
+
"required": true,
12
12
+
"format": "handle"
13
13
+
},
14
14
+
"displayName": {
15
15
+
"type": "string",
16
16
+
"maxGraphemes": 64,
17
17
+
"maxLength": 640
18
18
+
},
19
19
+
"pronouns": {
20
20
+
"type": "string"
21
21
+
},
22
22
+
"avatar": {
23
23
+
"type": "string",
24
24
+
"format": "uri"
25
25
+
},
26
26
+
"associated": {
27
27
+
"type": "ref",
28
28
+
"ref": "#profileAssociated"
29
29
+
},
30
30
+
"viewer": {
31
31
+
"type": "ref",
32
32
+
"ref": "#viewerState"
33
33
+
},
34
34
+
"labels": {
35
35
+
"type": "array",
36
36
+
"items": {
37
37
+
"type": "ref",
38
38
+
"ref": "com.atproto.label.defs#label"
39
39
+
}
40
40
+
},
41
41
+
"createdAt": {
42
42
+
"type": "string",
43
43
+
"format": "datetime"
44
44
+
},
45
45
+
"verification": {
46
46
+
"type": "ref",
47
47
+
"ref": "#verificationState"
48
48
+
},
49
49
+
"status": {
50
50
+
"type": "ref",
51
51
+
"ref": "#statusView"
52
52
+
}
53
53
+
},
54
54
+
"required": [
55
55
+
"did",
56
56
+
"handle"
57
57
+
]
58
58
+
}
+63
samples/query-search-posts.json
···
1
1
+
{
2
2
+
"type": "query",
3
3
+
"description": "Find posts matching search criteria",
4
4
+
"parameters": {
5
5
+
"type": "params",
6
6
+
"properties": {
7
7
+
"q": {
8
8
+
"type": "string",
9
9
+
"required": true
10
10
+
},
11
11
+
"sort": {
12
12
+
"type": "string",
13
13
+
"enum": [
14
14
+
"top",
15
15
+
"latest"
16
16
+
],
17
17
+
"default": "latest"
18
18
+
},
19
19
+
"limit": {
20
20
+
"type": "integer",
21
21
+
"minimum": 1,
22
22
+
"maximum": 100,
23
23
+
"default": 25
24
24
+
},
25
25
+
"cursor": {
26
26
+
"type": "string"
27
27
+
}
28
28
+
},
29
29
+
"required": [
30
30
+
"q"
31
31
+
]
32
32
+
},
33
33
+
"output": {
34
34
+
"encoding": "application/json",
35
35
+
"schema": {
36
36
+
"type": "object",
37
37
+
"properties": {
38
38
+
"cursor": {
39
39
+
"type": "string"
40
40
+
},
41
41
+
"hitsTotal": {
42
42
+
"type": "integer"
43
43
+
},
44
44
+
"posts": {
45
45
+
"type": "array",
46
46
+
"items": {
47
47
+
"type": "ref",
48
48
+
"ref": "app.bsky.feed.defs#postView"
49
49
+
},
50
50
+
"required": true
51
51
+
}
52
52
+
},
53
53
+
"required": [
54
54
+
"posts"
55
55
+
]
56
56
+
}
57
57
+
},
58
58
+
"errors": [
59
59
+
{
60
60
+
"name": "BadQueryString"
61
61
+
}
62
62
+
]
63
63
+
}
+35
samples/subscription-repos.json
···
1
1
+
{
2
2
+
"type": "subscription",
3
3
+
"description": "Repository event stream, aka Firehose endpoint",
4
4
+
"parameters": {
5
5
+
"type": "params",
6
6
+
"properties": {
7
7
+
"cursor": {
8
8
+
"type": "integer"
9
9
+
}
10
10
+
}
11
11
+
},
12
12
+
"message": {
13
13
+
"description": "Represents an update of repository state",
14
14
+
"schema": {
15
15
+
"type": "union",
16
16
+
"refs": [
17
17
+
"#commit",
18
18
+
"#identity",
19
19
+
"#account",
20
20
+
"#handle",
21
21
+
"#migrate",
22
22
+
"#tombstone",
23
23
+
"#info"
24
24
+
]
25
25
+
}
26
26
+
},
27
27
+
"errors": [
28
28
+
{
29
29
+
"name": "FutureCursor"
30
30
+
},
31
31
+
{
32
32
+
"name": "ConsumerTooSlow"
33
33
+
}
34
34
+
]
35
35
+
}
+105
src/infer.ts
···
1
1
+
type InferType<T> = T extends { type: "record" } ? InferRecord<T>
2
2
+
: T extends { type: "object" } ? InferObject<T>
3
3
+
: T extends { type: "array" } ? InferArray<T>
4
4
+
: T extends { type: "params" } ? InferParams<T>
5
5
+
: T extends { type: "union" } ? InferUnion<T>
6
6
+
: T extends { type: "token" } ? InferToken<T>
7
7
+
: T extends { type: "ref" } ? InferRef<T>
8
8
+
: T extends { type: "unknown" } ? unknown
9
9
+
: T extends { type: "null" } ? null
10
10
+
: T extends { type: "boolean" } ? boolean
11
11
+
: T extends { type: "integer" } ? number
12
12
+
: T extends { type: "string" } ? string
13
13
+
: T extends { type: "bytes" } ? Uint8Array
14
14
+
: T extends { type: "cid-link" } ? string
15
15
+
: T extends { type: "blob" } ? Blob
16
16
+
: never;
17
17
+
18
18
+
type InferToken<T> = T extends { enum: readonly (infer U)[] } ? U : string;
19
19
+
20
20
+
type InferObject<T> = T extends { properties: infer P } ?
21
21
+
& {
22
22
+
-readonly [K in keyof P as P[K] extends { type: string } ? K : never]?:
23
23
+
InferType<
24
24
+
P[K]
25
25
+
>;
26
26
+
}
27
27
+
& (T extends { required?: readonly (infer R)[] } ? {
28
28
+
-readonly [K in R extends string ? R : never]-?: InferType<
29
29
+
P[K & keyof P]
30
30
+
>;
31
31
+
}
32
32
+
: never)
33
33
+
: never;
34
34
+
35
35
+
type InferArray<T> = T extends { items: infer Items } ? InferType<Items>[]
36
36
+
: never[];
37
37
+
38
38
+
type InferUnion<T> = T extends { refs: readonly (infer R)[] }
39
39
+
? R extends string ? { $type: R; [key: string]: unknown } : never
40
40
+
: never;
41
41
+
42
42
+
type InferRef<T> = T extends { ref: infer R }
43
43
+
? R extends string ? { $type: R; [key: string]: unknown } : unknown
44
44
+
: unknown;
45
45
+
46
46
+
type InferParams<T> = T extends { properties: infer P } ? InferObject<T>
47
47
+
: never;
48
48
+
49
49
+
type InferRecord<T> = T extends { record: infer R }
50
50
+
? R extends { type: "object" } ? InferObject<R>
51
51
+
: R extends { type: "union" } ? InferUnion<R>
52
52
+
: unknown
53
53
+
: unknown;
54
54
+
55
55
+
type Prettify<T> =
56
56
+
& {
57
57
+
[K in keyof T]: T[K];
58
58
+
}
59
59
+
// deno-lint-ignore ban-types
60
60
+
& {};
61
61
+
62
62
+
export type InferDefs<T extends Record<string, unknown>> = Prettify<
63
63
+
{
64
64
+
-readonly [K in keyof T]: InferType<T[K]>;
65
65
+
}
66
66
+
>;
67
67
+
68
68
+
type InferNS<T extends { id: string; defs: Record<string, unknown> }> =
69
69
+
InferDefs<T["defs"]>;
70
70
+
71
71
+
const schema = {
72
72
+
"lexicon": 1,
73
73
+
"id": "app.bsky.actor.profile",
74
74
+
"defs": {
75
75
+
"main": {
76
76
+
"type": "record",
77
77
+
"key": "self",
78
78
+
"record": {
79
79
+
"type": "object",
80
80
+
"properties": {
81
81
+
"displayName": {
82
82
+
"type": "string",
83
83
+
"maxLength": 64,
84
84
+
"maxGraphemes": 64,
85
85
+
},
86
86
+
"description": {
87
87
+
"type": "string",
88
88
+
"maxLength": 256,
89
89
+
"maxGraphemes": 256,
90
90
+
},
91
91
+
},
92
92
+
"nullable": ["displayName"],
93
93
+
},
94
94
+
},
95
95
+
},
96
96
+
} as const;
97
97
+
98
98
+
type Schema = InferNS<typeof schema>;
99
99
+
100
100
+
const main: Schema["main"] = {
101
101
+
displayName: "blob",
102
102
+
description: "I am a blob dude",
103
103
+
};
104
104
+
105
105
+
console.log(`hi ${main.displayName}`);