-12
.env
-12
.env
···
1
-
# main indexers
2
-
JETSTREAM_URL="wss://jetstream.whey.party"
3
-
SPACEDUST_URL="wss://spacedust.whey.party"
4
-
5
-
# for backfill (useless if you just started the instance right now)
6
-
CONSTELLATION_URL="https://constellation.microcosm.blue"
7
-
# i dont actually know why i need this
8
-
SLINGSHOT_URL="https://slingshot.whey.party"
9
-
10
-
SERVICE_DID="did:web:local3768forumtest.whey.party"
11
-
SERVICE_ENDPOINT="https://local3768forumtest.whey.party"
12
-
SERVER_PORT="3768"
+8
.gitignore
+8
.gitignore
+35
config.jsonc.example
+35
config.jsonc.example
···
1
+
{
2
+
// Main indexers
3
+
"jetstream": "wss://jetstream1.us-east.bsky.network", // you can self host it -> https://github.com/bluesky-social/jetstream
4
+
"spacedust": "wss://spacedust.your.site", // you can self host it -> https://www.microcosm.blue
5
+
6
+
// For backfill (optional)
7
+
"constellation": "https://constellation.microcosm.blue", // (not useful on a new setup โ requires pre-existing data to backfill)
8
+
9
+
// Utility services
10
+
"slingshot": "https://slingshot.your.site", // you can self host it -> https://www.microcosm.blue
11
+
12
+
// Index Server config
13
+
"indexServer": {
14
+
"inviteOnly": true,
15
+
"port": 3767,
16
+
"did": "did:web:skyliteindexserver.your.site", // should be the same domain as the endpoint
17
+
"host": "https://skyliteindexserver.your.site"
18
+
},
19
+
20
+
// View Server config
21
+
"viewServer": {
22
+
"inviteOnly": true,
23
+
"port": 3768,
24
+
"did": "did:web:skyliteviewserver.your.site", // should be the same domain as the endpoint
25
+
"host": "https://skyliteviewserver.your.site",
26
+
27
+
// In order of which skylite index servers or bsky appviews to use first
28
+
"indexPriority": [
29
+
"user#skylite_index", // user resolved skylite index server
30
+
"did:web:backupindexserver.your.site#skylite_index", // a specific skylite index server
31
+
"user#bsky_appview", // user resolved bsky appview
32
+
"did:web:api.bsky.app#bsky_appview" // a specific bsky appview
33
+
]
34
+
}
35
+
}
+45
config.ts
+45
config.ts
···
1
+
import { parse } from "jsr:@std/jsonc";
2
+
import * as z from "npm:zod";
3
+
4
+
// configure these from the config.jsonc file (you can use config.jsonc.example as reference)
5
+
export const indexTarget = z.string().refine(
6
+
(val) => {
7
+
const parts = val.split("#");
8
+
if (parts.length !== 2) return false;
9
+
10
+
const [prefix, suffix] = parts;
11
+
const validPrefix = prefix === "user" || prefix.startsWith("did:web:");
12
+
const validSuffix = suffix === "skylite_index" || suffix === "bsky_appview";
13
+
14
+
return validPrefix && validSuffix;
15
+
},
16
+
{
17
+
message:
18
+
"Each indexPriority entry must be in the form 'user#skylite_index', 'user#bsky_appview', 'did:web:...#skylite_index', or 'did:web:...#bsky_appview'",
19
+
}
20
+
);
21
+
22
+
const ConfigSchema = z.object({
23
+
jetstream: z.string(),
24
+
spacedust: z.string(),
25
+
constellation: z.string(),
26
+
slingshot: z.string(),
27
+
indexServer: z.object({
28
+
inviteOnly: z.boolean(),
29
+
port: z.number(),
30
+
did: z.string(),
31
+
host: z.string(),
32
+
}),
33
+
viewServer: z.object({
34
+
inviteOnly: z.boolean(),
35
+
port: z.number(),
36
+
did: z.string(),
37
+
host: z.string(),
38
+
indexPriority: z.array(indexTarget),
39
+
}),
40
+
});
41
+
42
+
const raw = await Deno.readTextFile("config.jsonc");
43
+
const config = ConfigSchema.parse(parse(raw));
44
+
45
+
export { config };
+2
-1
deno.json
+2
-1
deno.json
···
1
1
{
2
2
"tasks": {
3
-
"dev": "deno run --watch -A --env-file main.ts"
3
+
"index": "deno run --watch -A --env-file --unstable-broadcast-channel main-index.ts",
4
+
"view": "deno run --watch -A --env-file --unstable-broadcast-channel main-view.ts"
4
5
},
5
6
"imports": {
6
7
"@std/assert": "jsr:@std/assert@1"
+1072
-329
deno.lock
+1072
-329
deno.lock
···
3
3
"specifiers": {
4
4
"jsr:@db/sqlite@0.11": "0.11.1",
5
5
"jsr:@denosaurs/plug@1": "1.1.0",
6
-
"jsr:@noble/secp256k1@*": "2.3.0",
7
-
"jsr:@panva/jose@*": "6.0.12",
8
6
"jsr:@std/assert@0.217": "0.217.0",
9
7
"jsr:@std/encoding@1": "1.0.10",
10
8
"jsr:@std/fmt@1": "1.0.8",
11
9
"jsr:@std/fs@1": "1.0.19",
12
10
"jsr:@std/internal@^1.0.9": "1.0.10",
11
+
"jsr:@std/jsonc@*": "1.0.2",
13
12
"jsr:@std/path@0.217": "0.217.0",
14
13
"jsr:@std/path@1": "1.1.1",
15
14
"jsr:@std/path@^1.1.1": "1.1.1",
16
-
"npm:@atproto/api@*": "0.15.12",
15
+
"npm:@atproto/api@*": "0.16.2",
17
16
"npm:@atproto/identity@*": "0.4.8",
18
-
"npm:@atproto/lexicon@*": "0.4.11",
17
+
"npm:@atproto/lexicon@*": "0.4.12",
19
18
"npm:@atproto/xrpc-server@*": "0.9.1",
20
-
"npm:@ipld/car@*": "5.4.2",
21
-
"npm:@ipld/dag-cbor@*": "9.2.4",
22
-
"npm:@types/express@4.17.15": "4.17.15",
23
-
"npm:@types/node@*": "22.15.15",
19
+
"npm:@atproto/xrpc@*": "0.7.1",
20
+
"npm:@babel/core@*": "7.28.3",
21
+
"npm:babel-plugin-react-compiler@*": "19.1.0-rc.2",
24
22
"npm:did-jwt@*": "8.0.17",
25
23
"npm:did-resolver@*": "4.1.0",
26
-
"npm:express@*": "5.1.0",
24
+
"npm:esbuild-plugin-cache@*": "0.2.10",
25
+
"npm:esbuild@0.20.2": "0.20.2",
27
26
"npm:ky@*": "1.8.1",
28
27
"npm:multiformats@*": "13.4.0",
29
28
"npm:quick-lru@*": "7.0.1",
30
-
"npm:web-did-resolver@*": "2.0.30"
29
+
"npm:web-did-resolver@*": "2.0.30",
30
+
"npm:zod@*": "4.0.17"
31
31
},
32
32
"jsr": {
33
33
"@db/sqlite@0.11.1": {
···
46
46
"jsr:@std/path@1"
47
47
]
48
48
},
49
-
"@noble/secp256k1@2.3.0": {
50
-
"integrity": "63eb4479a7c548e0ddea1cb5ea9bf7efacafe495e7f1c07bce1b4108df95967f"
51
-
},
52
-
"@panva/jose@6.0.12": {
53
-
"integrity": "b228cf79558ccc979046855bb140cd5d7bd86564dc047e563632a686b1346dcc"
54
-
},
55
49
"@std/assert@0.217.0": {
56
50
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
57
51
},
···
71
65
"@std/internal@1.0.10": {
72
66
"integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7"
73
67
},
68
+
"@std/jsonc@1.0.2": {
69
+
"integrity": "909605dae3af22bd75b1cbda8d64a32cf1fd2cf6efa3f9e224aba6d22c0f44c7"
70
+
},
74
71
"@std/path@0.217.0": {
75
72
"integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11",
76
73
"dependencies": [
···
85
82
}
86
83
},
87
84
"npm": {
88
-
"@atproto/api@0.15.12": {
89
-
"integrity": "sha512-51IHenZMA+Ekfe2OlZL/mTFqvZQU93jI4xsLvTFhGc4tSQYCHV9r/AJTANPZLFrhm9GfWZ0n90r/9IQl9eicjg==",
85
+
"@ampproject/remapping@2.3.0": {
86
+
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
87
+
"dependencies": [
88
+
"@jridgewell/gen-mapping",
89
+
"@jridgewell/trace-mapping"
90
+
]
91
+
},
92
+
"@atproto/api@0.16.2": {
93
+
"integrity": "sha512-sSTg31J8ws8DNaoiizp+/uJideRxRaJsq+Nyl8rnSxGw0w3oCvoeRU19iRWh2t0jZEmiRJAGkveGu23NKmPYEQ==",
90
94
"dependencies": [
91
95
"@atproto/common-web",
92
-
"@atproto/lexicon@0.4.11",
96
+
"@atproto/lexicon",
93
97
"@atproto/syntax",
94
-
"@atproto/xrpc@0.7.0",
98
+
"@atproto/xrpc",
95
99
"await-lock",
96
100
"multiformats@9.9.0",
97
101
"tlds",
98
-
"zod"
102
+
"zod@3.25.76"
99
103
]
100
104
},
101
105
"@atproto/common-web@0.4.2": {
···
104
108
"graphemer",
105
109
"multiformats@9.9.0",
106
110
"uint8arrays@3.0.0",
107
-
"zod"
111
+
"zod@3.25.76"
108
112
]
109
113
},
110
114
"@atproto/common@0.4.11": {
111
115
"integrity": "sha512-Knv0viYXNMfCdIE7jLUiWJKnnMfEwg+vz2epJQi8WOjqtqCFb3W/3Jn72ZiuovIfpdm13MaOiny6w2NErUQC6g==",
112
116
"dependencies": [
113
117
"@atproto/common-web",
114
-
"@ipld/dag-cbor@7.0.3",
118
+
"@ipld/dag-cbor",
115
119
"cbor-x",
116
120
"iso-datestring-validator",
117
121
"multiformats@9.9.0",
···
133
137
"@atproto/crypto"
134
138
]
135
139
},
136
-
"@atproto/lexicon@0.4.11": {
137
-
"integrity": "sha512-btefdnvNz2Ao2I+qbmj0F06HC8IlrM/IBz6qOBS50r0S6uDf5tOO+Mv2tSVdimFkdzyDdLtBI1sV36ONxz2cOw==",
138
-
"dependencies": [
139
-
"@atproto/common-web",
140
-
"@atproto/syntax",
141
-
"iso-datestring-validator",
142
-
"multiformats@9.9.0",
143
-
"zod"
144
-
]
145
-
},
146
140
"@atproto/lexicon@0.4.12": {
147
141
"integrity": "sha512-fcEvEQ1GpQYF5igZ4IZjPWEoWVpsEF22L9RexxLS3ptfySXLflEyH384e7HITzO/73McDeaJx3lqHIuqn9ulnw==",
148
142
"dependencies": [
···
150
144
"@atproto/syntax",
151
145
"iso-datestring-validator",
152
146
"multiformats@9.9.0",
153
-
"zod"
147
+
"zod@3.25.76"
154
148
]
155
149
},
156
150
"@atproto/syntax@0.4.0": {
···
161
155
"dependencies": [
162
156
"@atproto/common",
163
157
"@atproto/crypto",
164
-
"@atproto/lexicon@0.4.12",
165
-
"@atproto/xrpc@0.7.1",
158
+
"@atproto/lexicon",
159
+
"@atproto/xrpc",
166
160
"cbor-x",
167
-
"express@4.21.2",
161
+
"express",
168
162
"http-errors",
169
-
"mime-types@2.1.35",
163
+
"mime-types",
170
164
"rate-limiter-flexible",
171
165
"uint8arrays@3.0.0",
172
166
"ws",
173
-
"zod"
167
+
"zod@3.25.76"
168
+
]
169
+
},
170
+
"@atproto/xrpc@0.7.1": {
171
+
"integrity": "sha512-ANHEzlskYlMEdH18m+Itp3a8d0pEJao2qoDybDoMupTnoeNkya4VKIaOgAi6ERQnqatBBZyn9asW+7rJmSt/8g==",
172
+
"dependencies": [
173
+
"@atproto/lexicon",
174
+
"zod@3.25.76"
175
+
]
176
+
},
177
+
"@babel/code-frame@7.27.1": {
178
+
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
179
+
"dependencies": [
180
+
"@babel/helper-validator-identifier",
181
+
"js-tokens",
182
+
"picocolors"
183
+
]
184
+
},
185
+
"@babel/compat-data@7.28.0": {
186
+
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="
187
+
},
188
+
"@babel/core@7.28.3": {
189
+
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
190
+
"dependencies": [
191
+
"@ampproject/remapping",
192
+
"@babel/code-frame",
193
+
"@babel/generator",
194
+
"@babel/helper-compilation-targets",
195
+
"@babel/helper-module-transforms",
196
+
"@babel/helpers",
197
+
"@babel/parser",
198
+
"@babel/template",
199
+
"@babel/traverse",
200
+
"@babel/types",
201
+
"convert-source-map",
202
+
"debug@4.4.1",
203
+
"gensync",
204
+
"json5",
205
+
"semver"
206
+
]
207
+
},
208
+
"@babel/generator@7.28.3": {
209
+
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
210
+
"dependencies": [
211
+
"@babel/parser",
212
+
"@babel/types",
213
+
"@jridgewell/gen-mapping",
214
+
"@jridgewell/trace-mapping",
215
+
"jsesc"
216
+
]
217
+
},
218
+
"@babel/helper-compilation-targets@7.27.2": {
219
+
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
220
+
"dependencies": [
221
+
"@babel/compat-data",
222
+
"@babel/helper-validator-option",
223
+
"browserslist",
224
+
"lru-cache",
225
+
"semver"
226
+
]
227
+
},
228
+
"@babel/helper-globals@7.28.0": {
229
+
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="
230
+
},
231
+
"@babel/helper-module-imports@7.27.1": {
232
+
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
233
+
"dependencies": [
234
+
"@babel/traverse",
235
+
"@babel/types"
236
+
]
237
+
},
238
+
"@babel/helper-module-transforms@7.28.3_@babel+core@7.28.3": {
239
+
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
240
+
"dependencies": [
241
+
"@babel/core",
242
+
"@babel/helper-module-imports",
243
+
"@babel/helper-validator-identifier",
244
+
"@babel/traverse"
245
+
]
246
+
},
247
+
"@babel/helper-string-parser@7.27.1": {
248
+
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
249
+
},
250
+
"@babel/helper-validator-identifier@7.27.1": {
251
+
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
252
+
},
253
+
"@babel/helper-validator-option@7.27.1": {
254
+
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="
255
+
},
256
+
"@babel/helpers@7.28.3": {
257
+
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
258
+
"dependencies": [
259
+
"@babel/template",
260
+
"@babel/types"
261
+
]
262
+
},
263
+
"@babel/parser@7.28.3": {
264
+
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
265
+
"dependencies": [
266
+
"@babel/types"
267
+
],
268
+
"bin": true
269
+
},
270
+
"@babel/template@7.27.2": {
271
+
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
272
+
"dependencies": [
273
+
"@babel/code-frame",
274
+
"@babel/parser",
275
+
"@babel/types"
174
276
]
175
277
},
176
-
"@atproto/xrpc@0.7.0": {
177
-
"integrity": "sha512-SfhP9dGx2qclaScFDb58Jnrmim5nk4geZXCqg6sB0I/KZhZEkr9iIx1hLCp+sxkIfEsmEJjeWO4B0rjUIJW5cw==",
278
+
"@babel/traverse@7.28.3": {
279
+
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
178
280
"dependencies": [
179
-
"@atproto/lexicon@0.4.11",
180
-
"zod"
281
+
"@babel/code-frame",
282
+
"@babel/generator",
283
+
"@babel/helper-globals",
284
+
"@babel/parser",
285
+
"@babel/template",
286
+
"@babel/types",
287
+
"debug@4.4.1"
181
288
]
182
289
},
183
-
"@atproto/xrpc@0.7.1": {
184
-
"integrity": "sha512-ANHEzlskYlMEdH18m+Itp3a8d0pEJao2qoDybDoMupTnoeNkya4VKIaOgAi6ERQnqatBBZyn9asW+7rJmSt/8g==",
290
+
"@babel/types@7.28.2": {
291
+
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
185
292
"dependencies": [
186
-
"@atproto/lexicon@0.4.12",
187
-
"zod"
293
+
"@babel/helper-string-parser",
294
+
"@babel/helper-validator-identifier"
188
295
]
189
296
},
190
297
"@cbor-extract/cbor-extract-darwin-arm64@2.2.0": {
···
217
324
"os": ["win32"],
218
325
"cpu": ["x64"]
219
326
},
220
-
"@ipld/car@5.4.2": {
221
-
"integrity": "sha512-gfyrJvePyXnh2Fbj8mPg4JYvEZ3izhk8C9WgAle7xIYbrJNSXmNQ6BxAls8Gof97vvGbCROdxbTWRmHJtTCbcg==",
222
-
"dependencies": [
223
-
"@ipld/dag-cbor@9.2.4",
224
-
"cborg@4.2.11",
225
-
"multiformats@13.4.0",
226
-
"varint"
227
-
]
327
+
"@esbuild/aix-ppc64@0.20.2": {
328
+
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
329
+
"os": ["aix"],
330
+
"cpu": ["ppc64"]
331
+
},
332
+
"@esbuild/android-arm64@0.20.2": {
333
+
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
334
+
"os": ["android"],
335
+
"cpu": ["arm64"]
336
+
},
337
+
"@esbuild/android-arm@0.20.2": {
338
+
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
339
+
"os": ["android"],
340
+
"cpu": ["arm"]
341
+
},
342
+
"@esbuild/android-x64@0.20.2": {
343
+
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
344
+
"os": ["android"],
345
+
"cpu": ["x64"]
346
+
},
347
+
"@esbuild/darwin-arm64@0.20.2": {
348
+
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
349
+
"os": ["darwin"],
350
+
"cpu": ["arm64"]
351
+
},
352
+
"@esbuild/darwin-x64@0.20.2": {
353
+
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
354
+
"os": ["darwin"],
355
+
"cpu": ["x64"]
356
+
},
357
+
"@esbuild/freebsd-arm64@0.20.2": {
358
+
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
359
+
"os": ["freebsd"],
360
+
"cpu": ["arm64"]
361
+
},
362
+
"@esbuild/freebsd-x64@0.20.2": {
363
+
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
364
+
"os": ["freebsd"],
365
+
"cpu": ["x64"]
366
+
},
367
+
"@esbuild/linux-arm64@0.20.2": {
368
+
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
369
+
"os": ["linux"],
370
+
"cpu": ["arm64"]
371
+
},
372
+
"@esbuild/linux-arm@0.20.2": {
373
+
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
374
+
"os": ["linux"],
375
+
"cpu": ["arm"]
376
+
},
377
+
"@esbuild/linux-ia32@0.20.2": {
378
+
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
379
+
"os": ["linux"],
380
+
"cpu": ["ia32"]
381
+
},
382
+
"@esbuild/linux-loong64@0.20.2": {
383
+
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
384
+
"os": ["linux"],
385
+
"cpu": ["loong64"]
386
+
},
387
+
"@esbuild/linux-mips64el@0.20.2": {
388
+
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
389
+
"os": ["linux"],
390
+
"cpu": ["mips64el"]
391
+
},
392
+
"@esbuild/linux-ppc64@0.20.2": {
393
+
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
394
+
"os": ["linux"],
395
+
"cpu": ["ppc64"]
396
+
},
397
+
"@esbuild/linux-riscv64@0.20.2": {
398
+
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
399
+
"os": ["linux"],
400
+
"cpu": ["riscv64"]
401
+
},
402
+
"@esbuild/linux-s390x@0.20.2": {
403
+
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
404
+
"os": ["linux"],
405
+
"cpu": ["s390x"]
406
+
},
407
+
"@esbuild/linux-x64@0.20.2": {
408
+
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
409
+
"os": ["linux"],
410
+
"cpu": ["x64"]
411
+
},
412
+
"@esbuild/netbsd-x64@0.20.2": {
413
+
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
414
+
"os": ["netbsd"],
415
+
"cpu": ["x64"]
416
+
},
417
+
"@esbuild/openbsd-x64@0.20.2": {
418
+
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
419
+
"os": ["openbsd"],
420
+
"cpu": ["x64"]
421
+
},
422
+
"@esbuild/sunos-x64@0.20.2": {
423
+
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
424
+
"os": ["sunos"],
425
+
"cpu": ["x64"]
426
+
},
427
+
"@esbuild/win32-arm64@0.20.2": {
428
+
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
429
+
"os": ["win32"],
430
+
"cpu": ["arm64"]
431
+
},
432
+
"@esbuild/win32-ia32@0.20.2": {
433
+
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
434
+
"os": ["win32"],
435
+
"cpu": ["ia32"]
436
+
},
437
+
"@esbuild/win32-x64@0.20.2": {
438
+
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
439
+
"os": ["win32"],
440
+
"cpu": ["x64"]
228
441
},
229
442
"@ipld/dag-cbor@7.0.3": {
230
443
"integrity": "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA==",
231
444
"dependencies": [
232
-
"cborg@1.10.2",
445
+
"cborg",
233
446
"multiformats@9.9.0"
234
447
]
235
448
},
236
-
"@ipld/dag-cbor@9.2.4": {
237
-
"integrity": "sha512-GbDWYl2fdJgkYtIJN0HY9oO0o50d1nB4EQb7uYWKUd2ztxCjxiEW3PjwGG0nqUpN1G4Cug6LX8NzbA7fKT+zfA==",
449
+
"@jridgewell/gen-mapping@0.3.13": {
450
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
451
+
"dependencies": [
452
+
"@jridgewell/sourcemap-codec",
453
+
"@jridgewell/trace-mapping"
454
+
]
455
+
},
456
+
"@jridgewell/resolve-uri@3.1.2": {
457
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
458
+
},
459
+
"@jridgewell/sourcemap-codec@1.5.5": {
460
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
461
+
},
462
+
"@jridgewell/trace-mapping@0.3.30": {
463
+
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
238
464
"dependencies": [
239
-
"cborg@4.2.11",
240
-
"multiformats@13.4.0"
465
+
"@jridgewell/resolve-uri",
466
+
"@jridgewell/sourcemap-codec"
241
467
]
242
468
},
243
469
"@multiformats/base-x@4.0.1": {
···
258
484
"@scure/base@1.2.6": {
259
485
"integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="
260
486
},
261
-
"@types/body-parser@1.19.6": {
262
-
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
487
+
"@types/node-fetch@2.6.13": {
488
+
"integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
263
489
"dependencies": [
264
-
"@types/connect",
265
-
"@types/node"
266
-
]
267
-
},
268
-
"@types/connect@3.4.38": {
269
-
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
270
-
"dependencies": [
271
-
"@types/node"
272
-
]
273
-
},
274
-
"@types/express-serve-static-core@4.19.6": {
275
-
"integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
276
-
"dependencies": [
277
-
"@types/node",
278
-
"@types/qs",
279
-
"@types/range-parser",
280
-
"@types/send"
281
-
]
282
-
},
283
-
"@types/express@4.17.15": {
284
-
"integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==",
285
-
"dependencies": [
286
-
"@types/body-parser",
287
-
"@types/express-serve-static-core",
288
-
"@types/qs",
289
-
"@types/serve-static"
490
+
"@types/node@22.15.15",
491
+
"form-data"
290
492
]
291
493
},
292
-
"@types/http-errors@2.0.5": {
293
-
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="
294
-
},
295
-
"@types/mime@1.3.5": {
296
-
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
494
+
"@types/node@14.18.63": {
495
+
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="
297
496
},
298
497
"@types/node@22.15.15": {
299
498
"integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==",
···
301
500
"undici-types"
302
501
]
303
502
},
304
-
"@types/qs@6.14.0": {
305
-
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="
306
-
},
307
-
"@types/range-parser@1.2.7": {
308
-
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
309
-
},
310
-
"@types/send@0.17.5": {
311
-
"integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
312
-
"dependencies": [
313
-
"@types/mime",
314
-
"@types/node"
315
-
]
316
-
},
317
-
"@types/serve-static@1.15.8": {
318
-
"integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
319
-
"dependencies": [
320
-
"@types/http-errors",
321
-
"@types/node",
322
-
"@types/send"
323
-
]
324
-
},
325
503
"abort-controller@3.0.0": {
326
504
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
327
505
"dependencies": [
···
331
509
"accepts@1.3.8": {
332
510
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
333
511
"dependencies": [
334
-
"mime-types@2.1.35",
335
-
"negotiator@0.6.3"
336
-
]
337
-
},
338
-
"accepts@2.0.0": {
339
-
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
340
-
"dependencies": [
341
-
"mime-types@3.0.1",
342
-
"negotiator@1.0.0"
512
+
"mime-types",
513
+
"negotiator"
343
514
]
344
515
},
345
516
"array-flatten@1.1.1": {
346
517
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
347
518
},
519
+
"asynckit@0.4.0": {
520
+
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
521
+
},
348
522
"atomic-sleep@1.0.0": {
349
523
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="
350
524
},
351
525
"await-lock@2.2.2": {
352
526
"integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="
353
527
},
528
+
"babel-plugin-react-compiler@19.1.0-rc.2": {
529
+
"integrity": "sha512-kSNA//p5fMO6ypG8EkEVPIqAjwIXm5tMjfD1XRPL/sRjYSbJ6UsvORfaeolNWnZ9n310aM0xJP7peW26BuCVzA==",
530
+
"dependencies": [
531
+
"@babel/types"
532
+
]
533
+
},
354
534
"base64-js@1.5.1": {
355
535
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
356
536
},
···
363
543
"depd",
364
544
"destroy",
365
545
"http-errors",
366
-
"iconv-lite@0.4.24",
546
+
"iconv-lite",
367
547
"on-finished",
368
-
"qs@6.13.0",
369
-
"raw-body@2.5.2",
370
-
"type-is@1.6.18",
548
+
"qs",
549
+
"raw-body",
550
+
"type-is",
371
551
"unpipe"
372
552
]
373
553
},
374
-
"body-parser@2.2.0": {
375
-
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
554
+
"browserslist@4.25.3": {
555
+
"integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==",
376
556
"dependencies": [
377
-
"bytes",
378
-
"content-type",
379
-
"debug@4.4.1",
380
-
"http-errors",
381
-
"iconv-lite@0.6.3",
382
-
"on-finished",
383
-
"qs@6.14.0",
384
-
"raw-body@3.0.0",
385
-
"type-is@2.0.1"
386
-
]
557
+
"caniuse-lite",
558
+
"electron-to-chromium",
559
+
"node-releases",
560
+
"update-browserslist-db"
561
+
],
562
+
"bin": true
387
563
},
388
564
"buffer@6.0.3": {
389
565
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
···
409
585
"get-intrinsic"
410
586
]
411
587
},
588
+
"caniuse-lite@1.0.30001737": {
589
+
"integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="
590
+
},
412
591
"canonicalize@2.1.0": {
413
592
"integrity": "sha512-F705O3xrsUtgt98j7leetNhTWPe+5S72rlL5O4jA1pKqBVQ/dT1O1D6PFxmSXvc0SUOinWS57DKx0I3CHrXJHQ==",
414
593
"bin": true
···
439
618
"integrity": "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==",
440
619
"bin": true
441
620
},
442
-
"cborg@4.2.11": {
443
-
"integrity": "sha512-7gs3iaqtsD9OHowgqzc6ixQGwSBONqosVR2co0Bg0pARgrLap+LCcEIXJuuIz2jHy0WWQeDMFPEsU2r17I2XPQ==",
444
-
"bin": true
445
-
},
446
-
"content-disposition@0.5.4": {
447
-
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
621
+
"combined-stream@1.0.8": {
622
+
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
448
623
"dependencies": [
449
-
"safe-buffer"
624
+
"delayed-stream"
450
625
]
451
626
},
452
-
"content-disposition@1.0.0": {
453
-
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
627
+
"content-disposition@0.5.4": {
628
+
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
454
629
"dependencies": [
455
630
"safe-buffer"
456
631
]
···
458
633
"content-type@1.0.5": {
459
634
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
460
635
},
636
+
"convert-source-map@2.0.0": {
637
+
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
638
+
},
461
639
"cookie-signature@1.0.6": {
462
640
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
463
641
},
464
-
"cookie-signature@1.2.2": {
465
-
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="
466
-
},
467
642
"cookie@0.7.1": {
468
643
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="
469
-
},
470
-
"cookie@0.7.2": {
471
-
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
472
644
},
473
645
"cross-fetch@4.1.0": {
474
646
"integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
···
488
660
"ms@2.1.3"
489
661
]
490
662
},
663
+
"delayed-stream@1.0.0": {
664
+
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
665
+
},
666
+
"deno-cache@0.2.12": {
667
+
"integrity": "sha512-Jv8utRPQhsm+kx9ky0OdUnTWBLKGlFcBoLjQqrpuDd9zhuciCLPmklbz1YYfdaeM0dgp1nwRoqlHu5sH3vmJGQ==",
668
+
"dependencies": [
669
+
"@types/node@14.18.63",
670
+
"@types/node-fetch",
671
+
"node-fetch"
672
+
]
673
+
},
674
+
"deno-importmap@0.1.6": {
675
+
"integrity": "sha512-nZ5ZA8qW5F0Yzq1VhRp1wARpWSfD0FQvI1IUHXbE3oROO6tcYomTIWSAZGzO4LGQl1hTG6UmhPNTP3d4uMXzMg==",
676
+
"dependencies": [
677
+
"@types/node@14.18.63"
678
+
]
679
+
},
491
680
"depd@2.0.0": {
492
681
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
493
682
},
···
525
714
"ee-first@1.1.1": {
526
715
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
527
716
},
717
+
"electron-to-chromium@1.5.209": {
718
+
"integrity": "sha512-Xoz0uMrim9ZETCQt8UgM5FxQF9+imA7PBpokoGcZloA1uw2LeHzTlip5cb5KOAsXZLjh/moN2vReN3ZjJmjI9A=="
719
+
},
528
720
"encodeurl@1.0.2": {
529
721
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
530
722
},
···
543
735
"es-errors"
544
736
]
545
737
},
738
+
"es-set-tostringtag@2.1.0": {
739
+
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
740
+
"dependencies": [
741
+
"es-errors",
742
+
"get-intrinsic",
743
+
"has-tostringtag",
744
+
"hasown"
745
+
]
746
+
},
747
+
"esbuild-plugin-cache@0.2.10": {
748
+
"integrity": "sha512-e2Z8TgorvVKuj2A8/VP+sC04rt47JTpaew+9uP4CN7106W/cQxY1cqC1KWm+szwpfzJbBqXOpAqN4JPRySbW+A==",
749
+
"dependencies": [
750
+
"deno-cache",
751
+
"deno-importmap"
752
+
]
753
+
},
754
+
"esbuild@0.20.2": {
755
+
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
756
+
"optionalDependencies": [
757
+
"@esbuild/aix-ppc64",
758
+
"@esbuild/android-arm",
759
+
"@esbuild/android-arm64",
760
+
"@esbuild/android-x64",
761
+
"@esbuild/darwin-arm64",
762
+
"@esbuild/darwin-x64",
763
+
"@esbuild/freebsd-arm64",
764
+
"@esbuild/freebsd-x64",
765
+
"@esbuild/linux-arm",
766
+
"@esbuild/linux-arm64",
767
+
"@esbuild/linux-ia32",
768
+
"@esbuild/linux-loong64",
769
+
"@esbuild/linux-mips64el",
770
+
"@esbuild/linux-ppc64",
771
+
"@esbuild/linux-riscv64",
772
+
"@esbuild/linux-s390x",
773
+
"@esbuild/linux-x64",
774
+
"@esbuild/netbsd-x64",
775
+
"@esbuild/openbsd-x64",
776
+
"@esbuild/sunos-x64",
777
+
"@esbuild/win32-arm64",
778
+
"@esbuild/win32-ia32",
779
+
"@esbuild/win32-x64"
780
+
],
781
+
"scripts": true,
782
+
"bin": true
783
+
},
784
+
"escalade@3.2.0": {
785
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
786
+
},
546
787
"escape-html@1.0.3": {
547
788
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
548
789
},
···
558
799
"express@4.21.2": {
559
800
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
560
801
"dependencies": [
561
-
"accepts@1.3.8",
802
+
"accepts",
562
803
"array-flatten",
563
-
"body-parser@1.20.3",
564
-
"content-disposition@0.5.4",
804
+
"body-parser",
805
+
"content-disposition",
565
806
"content-type",
566
-
"cookie@0.7.1",
567
-
"cookie-signature@1.0.6",
807
+
"cookie",
808
+
"cookie-signature",
568
809
"debug@2.6.9",
569
810
"depd",
570
811
"encodeurl@2.0.0",
571
812
"escape-html",
572
813
"etag",
573
-
"finalhandler@1.3.1",
574
-
"fresh@0.5.2",
814
+
"finalhandler",
815
+
"fresh",
575
816
"http-errors",
576
-
"merge-descriptors@1.0.3",
817
+
"merge-descriptors",
577
818
"methods",
578
819
"on-finished",
579
820
"parseurl",
580
-
"path-to-regexp@0.1.12",
821
+
"path-to-regexp",
581
822
"proxy-addr",
582
-
"qs@6.13.0",
823
+
"qs",
583
824
"range-parser",
584
825
"safe-buffer",
585
-
"send@0.19.0",
586
-
"serve-static@1.16.2",
826
+
"send",
827
+
"serve-static",
587
828
"setprototypeof",
588
-
"statuses@2.0.1",
589
-
"type-is@1.6.18",
829
+
"statuses",
830
+
"type-is",
590
831
"utils-merge",
591
832
"vary"
592
833
]
593
834
},
594
-
"express@5.1.0": {
595
-
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
596
-
"dependencies": [
597
-
"accepts@2.0.0",
598
-
"body-parser@2.2.0",
599
-
"content-disposition@1.0.0",
600
-
"content-type",
601
-
"cookie@0.7.2",
602
-
"cookie-signature@1.2.2",
603
-
"debug@4.4.1",
604
-
"encodeurl@2.0.0",
605
-
"escape-html",
606
-
"etag",
607
-
"finalhandler@2.1.0",
608
-
"fresh@2.0.0",
609
-
"http-errors",
610
-
"merge-descriptors@2.0.0",
611
-
"mime-types@3.0.1",
612
-
"on-finished",
613
-
"once",
614
-
"parseurl",
615
-
"proxy-addr",
616
-
"qs@6.14.0",
617
-
"range-parser",
618
-
"router",
619
-
"send@1.2.0",
620
-
"serve-static@2.2.0",
621
-
"statuses@2.0.2",
622
-
"type-is@2.0.1",
623
-
"vary"
624
-
]
625
-
},
626
835
"fast-redact@3.5.0": {
627
836
"integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="
628
837
},
···
634
843
"escape-html",
635
844
"on-finished",
636
845
"parseurl",
637
-
"statuses@2.0.1",
846
+
"statuses",
638
847
"unpipe"
639
848
]
640
849
},
641
-
"finalhandler@2.1.0": {
642
-
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
850
+
"form-data@4.0.4": {
851
+
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
643
852
"dependencies": [
644
-
"debug@4.4.1",
645
-
"encodeurl@2.0.0",
646
-
"escape-html",
647
-
"on-finished",
648
-
"parseurl",
649
-
"statuses@2.0.2"
853
+
"asynckit",
854
+
"combined-stream",
855
+
"es-set-tostringtag",
856
+
"hasown",
857
+
"mime-types"
650
858
]
651
859
},
652
860
"forwarded@0.2.0": {
···
655
863
"fresh@0.5.2": {
656
864
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
657
865
},
658
-
"fresh@2.0.0": {
659
-
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="
660
-
},
661
866
"function-bind@1.1.2": {
662
867
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
868
+
},
869
+
"gensync@1.0.0-beta.2": {
870
+
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
663
871
},
664
872
"get-intrinsic@1.3.0": {
665
873
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
···
692
900
"has-symbols@1.1.0": {
693
901
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
694
902
},
903
+
"has-tostringtag@1.0.2": {
904
+
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
905
+
"dependencies": [
906
+
"has-symbols"
907
+
]
908
+
},
695
909
"hasown@2.0.2": {
696
910
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
697
911
"dependencies": [
···
704
918
"depd",
705
919
"inherits",
706
920
"setprototypeof",
707
-
"statuses@2.0.1",
921
+
"statuses",
708
922
"toidentifier"
709
923
]
710
924
},
···
714
928
"safer-buffer"
715
929
]
716
930
},
717
-
"iconv-lite@0.6.3": {
718
-
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
719
-
"dependencies": [
720
-
"safer-buffer"
721
-
]
722
-
},
723
931
"ieee754@1.2.1": {
724
932
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
725
933
},
···
729
937
"ipaddr.js@1.9.1": {
730
938
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
731
939
},
732
-
"is-promise@4.0.0": {
733
-
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
734
-
},
735
940
"iso-datestring-validator@2.2.2": {
736
941
"integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="
737
942
},
943
+
"js-tokens@4.0.0": {
944
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
945
+
},
946
+
"jsesc@3.1.0": {
947
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
948
+
"bin": true
949
+
},
950
+
"json5@2.2.3": {
951
+
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
952
+
"bin": true
953
+
},
738
954
"ky@1.8.1": {
739
955
"integrity": "sha512-7Bp3TpsE+L+TARSnnDpk3xg8Idi8RwSLdj6CMbNWoOARIrGrbuLGusV0dYwbZOm4bB3jHNxSw8Wk/ByDqJEnDw=="
740
956
},
957
+
"lru-cache@5.1.1": {
958
+
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
959
+
"dependencies": [
960
+
"yallist"
961
+
]
962
+
},
741
963
"math-intrinsics@1.1.0": {
742
964
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
743
965
},
744
966
"media-typer@0.3.0": {
745
967
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
746
-
},
747
-
"media-typer@1.1.0": {
748
-
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="
749
968
},
750
969
"merge-descriptors@1.0.3": {
751
970
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="
752
971
},
753
-
"merge-descriptors@2.0.0": {
754
-
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="
755
-
},
756
972
"methods@1.1.2": {
757
973
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
758
974
},
759
975
"mime-db@1.52.0": {
760
976
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
761
-
},
762
-
"mime-db@1.54.0": {
763
-
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="
764
977
},
765
978
"mime-types@2.1.35": {
766
979
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
767
980
"dependencies": [
768
-
"mime-db@1.52.0"
769
-
]
770
-
},
771
-
"mime-types@3.0.1": {
772
-
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
773
-
"dependencies": [
774
-
"mime-db@1.54.0"
981
+
"mime-db"
775
982
]
776
983
},
777
984
"mime@1.6.0": {
···
799
1006
},
800
1007
"negotiator@0.6.3": {
801
1008
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
802
-
},
803
-
"negotiator@1.0.0": {
804
-
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="
805
1009
},
806
1010
"node-fetch@2.7.0": {
807
1011
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
···
816
1020
],
817
1021
"bin": true
818
1022
},
1023
+
"node-releases@2.0.19": {
1024
+
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
1025
+
},
819
1026
"object-inspect@1.13.4": {
820
1027
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="
821
1028
},
···
828
1035
"ee-first"
829
1036
]
830
1037
},
831
-
"once@1.4.0": {
832
-
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
833
-
"dependencies": [
834
-
"wrappy"
835
-
]
836
-
},
837
1038
"parseurl@1.3.3": {
838
1039
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
839
1040
},
840
1041
"path-to-regexp@0.1.12": {
841
1042
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
842
1043
},
843
-
"path-to-regexp@8.2.0": {
844
-
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="
1044
+
"picocolors@1.1.1": {
1045
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
845
1046
},
846
1047
"pino-abstract-transport@1.2.0": {
847
1048
"integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==",
···
889
1090
"side-channel"
890
1091
]
891
1092
},
892
-
"qs@6.14.0": {
893
-
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
894
-
"dependencies": [
895
-
"side-channel"
896
-
]
897
-
},
898
1093
"quick-format-unescaped@4.0.4": {
899
1094
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
900
1095
},
···
912
1107
"dependencies": [
913
1108
"bytes",
914
1109
"http-errors",
915
-
"iconv-lite@0.4.24",
916
-
"unpipe"
917
-
]
918
-
},
919
-
"raw-body@3.0.0": {
920
-
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
921
-
"dependencies": [
922
-
"bytes",
923
-
"http-errors",
924
-
"iconv-lite@0.6.3",
1110
+
"iconv-lite",
925
1111
"unpipe"
926
1112
]
927
1113
},
···
938
1124
"real-require@0.2.0": {
939
1125
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="
940
1126
},
941
-
"router@2.2.0": {
942
-
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
943
-
"dependencies": [
944
-
"debug@4.4.1",
945
-
"depd",
946
-
"is-promise",
947
-
"parseurl",
948
-
"path-to-regexp@8.2.0"
949
-
]
950
-
},
951
1127
"safe-buffer@5.2.1": {
952
1128
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
953
1129
},
···
957
1133
"safer-buffer@2.1.2": {
958
1134
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
959
1135
},
1136
+
"semver@6.3.1": {
1137
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1138
+
"bin": true
1139
+
},
960
1140
"send@0.19.0": {
961
1141
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
962
1142
"dependencies": [
···
966
1146
"encodeurl@1.0.2",
967
1147
"escape-html",
968
1148
"etag",
969
-
"fresh@0.5.2",
1149
+
"fresh",
970
1150
"http-errors",
971
1151
"mime",
972
1152
"ms@2.1.3",
973
1153
"on-finished",
974
1154
"range-parser",
975
-
"statuses@2.0.1"
976
-
]
977
-
},
978
-
"send@1.2.0": {
979
-
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
980
-
"dependencies": [
981
-
"debug@4.4.1",
982
-
"encodeurl@2.0.0",
983
-
"escape-html",
984
-
"etag",
985
-
"fresh@2.0.0",
986
-
"http-errors",
987
-
"mime-types@3.0.1",
988
-
"ms@2.1.3",
989
-
"on-finished",
990
-
"range-parser",
991
-
"statuses@2.0.2"
1155
+
"statuses"
992
1156
]
993
1157
},
994
1158
"serve-static@1.16.2": {
···
997
1161
"encodeurl@2.0.0",
998
1162
"escape-html",
999
1163
"parseurl",
1000
-
"send@0.19.0"
1001
-
]
1002
-
},
1003
-
"serve-static@2.2.0": {
1004
-
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
1005
-
"dependencies": [
1006
-
"encodeurl@2.0.0",
1007
-
"escape-html",
1008
-
"parseurl",
1009
-
"send@1.2.0"
1164
+
"send"
1010
1165
]
1011
1166
},
1012
1167
"setprototypeof@1.2.0": {
···
1060
1215
"statuses@2.0.1": {
1061
1216
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
1062
1217
},
1063
-
"statuses@2.0.2": {
1064
-
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="
1065
-
},
1066
1218
"string_decoder@1.3.0": {
1067
1219
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
1068
1220
"dependencies": [
···
1088
1240
"type-is@1.6.18": {
1089
1241
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1090
1242
"dependencies": [
1091
-
"media-typer@0.3.0",
1092
-
"mime-types@2.1.35"
1093
-
]
1094
-
},
1095
-
"type-is@2.0.1": {
1096
-
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
1097
-
"dependencies": [
1098
-
"content-type",
1099
-
"media-typer@1.1.0",
1100
-
"mime-types@3.0.1"
1243
+
"media-typer",
1244
+
"mime-types"
1101
1245
]
1102
1246
},
1103
1247
"uint8arrays@3.0.0": {
···
1118
1262
"unpipe@1.0.0": {
1119
1263
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
1120
1264
},
1265
+
"update-browserslist-db@1.1.3_browserslist@4.25.3": {
1266
+
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
1267
+
"dependencies": [
1268
+
"browserslist",
1269
+
"escalade",
1270
+
"picocolors"
1271
+
],
1272
+
"bin": true
1273
+
},
1121
1274
"utils-merge@1.0.1": {
1122
1275
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
1123
-
},
1124
-
"varint@6.0.0": {
1125
-
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="
1126
1276
},
1127
1277
"vary@1.1.2": {
1128
1278
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
···
1144
1294
"webidl-conversions"
1145
1295
]
1146
1296
},
1147
-
"wrappy@1.0.2": {
1148
-
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
1149
-
},
1150
1297
"ws@8.18.3": {
1151
1298
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="
1152
1299
},
1153
-
"zod@3.25.46": {
1154
-
"integrity": "sha512-IqRxcHEIjqLd4LNS/zKffB3Jzg3NwqJxQQ0Ns7pdrvgGkwQsEBdEQcOHaBVqvvZArShRzI39+aMST3FBGmTrLQ=="
1300
+
"yallist@3.1.1": {
1301
+
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
1302
+
},
1303
+
"zod@3.25.76": {
1304
+
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
1305
+
},
1306
+
"zod@4.0.17": {
1307
+
"integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="
1155
1308
}
1156
1309
},
1310
+
"redirects": {
1311
+
"https://esm.sh/@alloc/quick-lru@^5.2.0?target=denonext": "https://esm.sh/@alloc/quick-lru@5.2.0?target=denonext",
1312
+
"https://esm.sh/@atproto/api": "https://esm.sh/@atproto/api@0.16.3",
1313
+
"https://esm.sh/@atproto/common-web@^0.4.2?target=denonext": "https://esm.sh/@atproto/common-web@0.4.2?target=denonext",
1314
+
"https://esm.sh/@atproto/lexicon@^0.4.13?target=denonext": "https://esm.sh/@atproto/lexicon@0.4.13?target=denonext",
1315
+
"https://esm.sh/@atproto/oauth-client-browser": "https://esm.sh/@atproto/oauth-client-browser@0.3.30",
1316
+
"https://esm.sh/@atproto/syntax@^0.4.0?target=denonext": "https://esm.sh/@atproto/syntax@0.4.0?target=denonext",
1317
+
"https://esm.sh/@atproto/xrpc@^0.7.2?target=denonext": "https://esm.sh/@atproto/xrpc@0.7.2?target=denonext",
1318
+
"https://esm.sh/@jridgewell/gen-mapping@^0.3.2?target=denonext": "https://esm.sh/@jridgewell/gen-mapping@0.3.13?target=denonext",
1319
+
"https://esm.sh/@jridgewell/resolve-uri@^3.1.0?target=denonext": "https://esm.sh/@jridgewell/resolve-uri@3.1.2?target=denonext",
1320
+
"https://esm.sh/@jridgewell/sourcemap-codec@^1.4.14?target=denonext": "https://esm.sh/@jridgewell/sourcemap-codec@1.5.5?target=denonext",
1321
+
"https://esm.sh/@jridgewell/sourcemap-codec@^1.5.0?target=denonext": "https://esm.sh/@jridgewell/sourcemap-codec@1.5.5?target=denonext",
1322
+
"https://esm.sh/@jridgewell/trace-mapping@^0.3.24?target=denonext": "https://esm.sh/@jridgewell/trace-mapping@0.3.30?target=denonext",
1323
+
"https://esm.sh/@nodelib/fs.stat@^2.0.2?target=denonext": "https://esm.sh/@nodelib/fs.stat@2.0.5?target=denonext",
1324
+
"https://esm.sh/@nodelib/fs.walk@^1.2.3?target=denonext": "https://esm.sh/@nodelib/fs.walk@1.2.8?target=denonext",
1325
+
"https://esm.sh/@tailwindcss/line-clamp?target=denonext": "https://esm.sh/@tailwindcss/line-clamp@0.4.4?target=denonext",
1326
+
"https://esm.sh/autoprefixer@10": "https://esm.sh/autoprefixer@10.4.21",
1327
+
"https://esm.sh/await-lock@^2.2.2?target=denonext": "https://esm.sh/await-lock@2.2.2?target=denonext",
1328
+
"https://esm.sh/braces@^3.0.3?target=denonext": "https://esm.sh/braces@3.0.3?target=denonext",
1329
+
"https://esm.sh/browserslist@^4.24.4?target=denonext": "https://esm.sh/browserslist@4.25.3?target=denonext",
1330
+
"https://esm.sh/camelcase-css@^2.0.1?target=denonext": "https://esm.sh/camelcase-css@2.0.1?target=denonext",
1331
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/background-clip-text?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/background-clip-text?target=denonext",
1332
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/background-img-opts?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/background-img-opts?target=denonext",
1333
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/border-image?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/border-image?target=denonext",
1334
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/border-radius?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/border-radius?target=denonext",
1335
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/calc?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/calc?target=denonext",
1336
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-animation?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-animation?target=denonext",
1337
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-any-link?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-any-link?target=denonext",
1338
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-appearance?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-appearance?target=denonext",
1339
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-autofill?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-autofill?target=denonext",
1340
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-backdrop-filter?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-backdrop-filter?target=denonext",
1341
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-boxdecorationbreak?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-boxdecorationbreak?target=denonext",
1342
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-boxshadow?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-boxshadow?target=denonext",
1343
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-clip-path?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-clip-path?target=denonext",
1344
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-crisp-edges?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-crisp-edges?target=denonext",
1345
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-cross-fade?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-cross-fade?target=denonext",
1346
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-deviceadaptation?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-deviceadaptation?target=denonext",
1347
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-element-function?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-element-function?target=denonext",
1348
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-featurequeries?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-featurequeries?target=denonext",
1349
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-file-selector-button?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-file-selector-button?target=denonext",
1350
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-filter-function?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-filter-function?target=denonext",
1351
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-filters?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-filters?target=denonext",
1352
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-gradients?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-gradients?target=denonext",
1353
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-grid?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-grid?target=denonext",
1354
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-hyphens?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-hyphens?target=denonext",
1355
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-image-set?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-image-set?target=denonext",
1356
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-logical-props?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-logical-props?target=denonext",
1357
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-masks?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-masks?target=denonext",
1358
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-media-resolution?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-media-resolution?target=denonext",
1359
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-overscroll-behavior?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-overscroll-behavior?target=denonext",
1360
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-placeholder-shown?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-placeholder-shown?target=denonext",
1361
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-placeholder?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-placeholder?target=denonext",
1362
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-print-color-adjust?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-print-color-adjust?target=denonext",
1363
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-read-only-write?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-read-only-write?target=denonext",
1364
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-regions?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-regions?target=denonext",
1365
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-selection?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-selection?target=denonext",
1366
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-shapes?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-shapes?target=denonext",
1367
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-snappoints?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-snappoints?target=denonext",
1368
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-sticky?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-sticky?target=denonext",
1369
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-text-align-last?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-text-align-last?target=denonext",
1370
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-text-orientation?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-text-orientation?target=denonext",
1371
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-text-spacing?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-text-spacing?target=denonext",
1372
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-transitions?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-transitions?target=denonext",
1373
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-width-stretch?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-width-stretch?target=denonext",
1374
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css-writing-mode?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-writing-mode?target=denonext",
1375
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css3-boxsizing?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css3-boxsizing?target=denonext",
1376
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css3-cursors-grab?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css3-cursors-grab?target=denonext",
1377
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css3-cursors-newer?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css3-cursors-newer?target=denonext",
1378
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/css3-tabsize?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/css3-tabsize?target=denonext",
1379
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/flexbox?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/flexbox?target=denonext",
1380
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/font-feature?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/font-feature?target=denonext",
1381
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/font-kerning?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/font-kerning?target=denonext",
1382
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/fullscreen?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/fullscreen?target=denonext",
1383
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/intrinsic-width?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/intrinsic-width?target=denonext",
1384
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/mdn-css-backdrop-pseudo-element?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-css-backdrop-pseudo-element?target=denonext",
1385
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/mdn-css-unicode-bidi-isolate-override?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-css-unicode-bidi-isolate-override?target=denonext",
1386
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/mdn-css-unicode-bidi-isolate?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-css-unicode-bidi-isolate?target=denonext",
1387
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/mdn-css-unicode-bidi-plaintext?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-css-unicode-bidi-plaintext?target=denonext",
1388
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/mdn-text-decoration-color?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-text-decoration-color?target=denonext",
1389
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/mdn-text-decoration-line?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-text-decoration-line?target=denonext",
1390
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/mdn-text-decoration-shorthand?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-text-decoration-shorthand?target=denonext",
1391
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/mdn-text-decoration-style?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-text-decoration-style?target=denonext",
1392
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/multicolumn?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/multicolumn?target=denonext",
1393
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/object-fit?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/object-fit?target=denonext",
1394
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/pointer?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/pointer?target=denonext",
1395
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/text-decoration?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/text-decoration?target=denonext",
1396
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/text-emphasis?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/text-emphasis?target=denonext",
1397
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/text-overflow?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/text-overflow?target=denonext",
1398
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/text-size-adjust?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/text-size-adjust?target=denonext",
1399
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/transforms2d?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/transforms2d?target=denonext",
1400
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/transforms3d?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/transforms3d?target=denonext",
1401
+
"https://esm.sh/caniuse-lite@^1.0.30001702/data/features/user-select-none?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/data/features/user-select-none?target=denonext",
1402
+
"https://esm.sh/caniuse-lite@^1.0.30001702/dist/unpacker/agents?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/dist/unpacker/agents?target=denonext",
1403
+
"https://esm.sh/caniuse-lite@^1.0.30001702/dist/unpacker/feature?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/dist/unpacker/feature?target=denonext",
1404
+
"https://esm.sh/caniuse-lite@^1.0.30001735/dist/unpacker/agents?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/dist/unpacker/agents?target=denonext",
1405
+
"https://esm.sh/caniuse-lite@^1.0.30001735/dist/unpacker/feature?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/dist/unpacker/feature?target=denonext",
1406
+
"https://esm.sh/caniuse-lite@^1.0.30001735/dist/unpacker/region?target=denonext": "https://esm.sh/caniuse-lite@1.0.30001737/dist/unpacker/region?target=denonext",
1407
+
"https://esm.sh/cssesc@^3.0.0?target=denonext": "https://esm.sh/cssesc@3.0.0?target=denonext",
1408
+
"https://esm.sh/didyoumean@^1.2.2?target=denonext": "https://esm.sh/didyoumean@1.2.2?target=denonext",
1409
+
"https://esm.sh/dlv@^1.1.3?target=denonext": "https://esm.sh/dlv@1.1.3?target=denonext",
1410
+
"https://esm.sh/electron-to-chromium@^1.5.204/versions?target=denonext": "https://esm.sh/electron-to-chromium@1.5.209/versions?target=denonext",
1411
+
"https://esm.sh/fast-glob@^3.3.2?target=denonext": "https://esm.sh/fast-glob@3.3.3?target=denonext",
1412
+
"https://esm.sh/fastq@^1.6.0?target=denonext": "https://esm.sh/fastq@1.19.1?target=denonext",
1413
+
"https://esm.sh/fill-range@^7.1.1?target=denonext": "https://esm.sh/fill-range@7.1.1?target=denonext",
1414
+
"https://esm.sh/fraction.js@^4.3.7?target=denonext": "https://esm.sh/fraction.js@4.3.7?target=denonext",
1415
+
"https://esm.sh/glob-parent@^5.1.2?target=denonext": "https://esm.sh/glob-parent@5.1.2?target=denonext",
1416
+
"https://esm.sh/glob-parent@^6.0.2?target=denonext": "https://esm.sh/glob-parent@6.0.2?target=denonext",
1417
+
"https://esm.sh/graphemer@^1.4.0?target=denonext": "https://esm.sh/graphemer@1.4.0?target=denonext",
1418
+
"https://esm.sh/is-extglob@^2.1.1?target=denonext": "https://esm.sh/is-extglob@2.1.1?target=denonext",
1419
+
"https://esm.sh/is-glob@^4.0.1?target=denonext": "https://esm.sh/is-glob@4.0.3?target=denonext",
1420
+
"https://esm.sh/is-glob@^4.0.3?target=denonext": "https://esm.sh/is-glob@4.0.3?target=denonext",
1421
+
"https://esm.sh/iso-datestring-validator@^2.2.2?target=denonext": "https://esm.sh/iso-datestring-validator@2.2.2?target=denonext",
1422
+
"https://esm.sh/jiti@^1.21.6/dist/babel?target=denonext": "https://esm.sh/jiti@1.21.7/dist/babel?target=denonext",
1423
+
"https://esm.sh/jiti@^1.21.6?target=denonext": "https://esm.sh/jiti@1.21.7?target=denonext",
1424
+
"https://esm.sh/jose@^5.2.0?target=denonext": "https://esm.sh/jose@5.10.0?target=denonext",
1425
+
"https://esm.sh/lines-and-columns@^1.1.6?target=denonext": "https://esm.sh/lines-and-columns@1.2.4?target=denonext",
1426
+
"https://esm.sh/lru-cache@^10.2.0?target=denonext": "https://esm.sh/lru-cache@10.4.3?target=denonext",
1427
+
"https://esm.sh/merge2@^1.3.0?target=denonext": "https://esm.sh/merge2@1.4.1?target=denonext",
1428
+
"https://esm.sh/micromatch@^4.0.8?target=denonext": "https://esm.sh/micromatch@4.0.8?target=denonext",
1429
+
"https://esm.sh/multiformats@^9.4.2/basics?target=denonext": "https://esm.sh/multiformats@9.9.0/basics?target=denonext",
1430
+
"https://esm.sh/multiformats@^9.9.0/bases/base64?target=denonext": "https://esm.sh/multiformats@9.9.0/bases/base64?target=denonext",
1431
+
"https://esm.sh/multiformats@^9.9.0/cid?target=denonext": "https://esm.sh/multiformats@9.9.0/cid?target=denonext",
1432
+
"https://esm.sh/nanoid@^3.3.11/non-secure?target=denonext": "https://esm.sh/nanoid@3.3.11/non-secure?target=denonext",
1433
+
"https://esm.sh/node-releases@^2.0.19/data/processed/envs.json?module": "https://esm.sh/node-releases@2.0.19/data/processed/envs.json?module",
1434
+
"https://esm.sh/node-releases@^2.0.19/data/release-schedule/release-schedule.json?module": "https://esm.sh/node-releases@2.0.19/data/release-schedule/release-schedule.json?module",
1435
+
"https://esm.sh/normalize-path@^3.0.0?target=denonext": "https://esm.sh/normalize-path@3.0.0?target=denonext",
1436
+
"https://esm.sh/normalize-range@^0.1.2?target=denonext": "https://esm.sh/normalize-range@0.1.2?target=denonext",
1437
+
"https://esm.sh/object-hash@^3.0.0?target=denonext": "https://esm.sh/object-hash@3.0.0?target=denonext",
1438
+
"https://esm.sh/picocolors@^1.1.1?target=denonext": "https://esm.sh/picocolors@1.1.1?target=denonext",
1439
+
"https://esm.sh/picomatch@^2.3.1/lib/utils?target=denonext": "https://esm.sh/picomatch@2.3.1/lib/utils?target=denonext",
1440
+
"https://esm.sh/picomatch@^2.3.1?target=denonext": "https://esm.sh/picomatch@2.3.1?target=denonext",
1441
+
"https://esm.sh/postcss-js@^4.0.1?target=denonext": "https://esm.sh/postcss-js@4.0.1?target=denonext",
1442
+
"https://esm.sh/postcss-nested@^6.2.0?target=denonext": "https://esm.sh/postcss-nested@6.2.0?target=denonext",
1443
+
"https://esm.sh/postcss-selector-parser@^6.1.1?target=denonext": "https://esm.sh/postcss-selector-parser@6.1.2?target=denonext",
1444
+
"https://esm.sh/postcss-selector-parser@^6.1.2/dist/util/unesc?target=denonext": "https://esm.sh/postcss-selector-parser@6.1.2/dist/util/unesc?target=denonext",
1445
+
"https://esm.sh/postcss-selector-parser@^6.1.2?target=denonext": "https://esm.sh/postcss-selector-parser@6.1.2?target=denonext",
1446
+
"https://esm.sh/postcss-value-parser@^4.2.0?target=denonext": "https://esm.sh/postcss-value-parser@4.2.0?target=denonext",
1447
+
"https://esm.sh/postcss@8": "https://esm.sh/postcss@8.5.6",
1448
+
"https://esm.sh/postcss@^8.1.0?target=denonext": "https://esm.sh/postcss@8.5.6?target=denonext",
1449
+
"https://esm.sh/postcss@^8.2.14?target=denonext": "https://esm.sh/postcss@8.5.6?target=denonext",
1450
+
"https://esm.sh/postcss@^8.4.21?target=denonext": "https://esm.sh/postcss@8.5.6?target=denonext",
1451
+
"https://esm.sh/postcss@^8.4.47?target=denonext": "https://esm.sh/postcss@8.5.6?target=denonext",
1452
+
"https://esm.sh/queue-microtask@^1.2.2?target=denonext": "https://esm.sh/queue-microtask@1.2.3?target=denonext",
1453
+
"https://esm.sh/reusify@^1.0.4?target=denonext": "https://esm.sh/reusify@1.1.0?target=denonext",
1454
+
"https://esm.sh/run-parallel@^1.1.9?target=denonext": "https://esm.sh/run-parallel@1.2.0?target=denonext",
1455
+
"https://esm.sh/scheduler@^0.26.0?target=denonext": "https://esm.sh/scheduler@0.26.0?target=denonext",
1456
+
"https://esm.sh/source-map-js@^1.2.1?target=denonext": "https://esm.sh/source-map-js@1.2.1?target=denonext",
1457
+
"https://esm.sh/sucrase@^3.35.0?target=denonext": "https://esm.sh/sucrase@3.35.0?target=denonext",
1458
+
"https://esm.sh/tailwindcss@3": "https://esm.sh/tailwindcss@3.4.17",
1459
+
"https://esm.sh/tailwindcss@^4.0.0-beta.9/plugin?target=denonext": "https://esm.sh/tailwindcss@4.1.12/plugin?target=denonext",
1460
+
"https://esm.sh/tlds@^1.234.0?target=denonext": "https://esm.sh/tlds@1.259.0?target=denonext",
1461
+
"https://esm.sh/to-regex-range@^5.0.1?target=denonext": "https://esm.sh/to-regex-range@5.0.1?target=denonext",
1462
+
"https://esm.sh/ts-interface-checker@^0.1.9?target=denonext": "https://esm.sh/ts-interface-checker@0.1.13?target=denonext",
1463
+
"https://esm.sh/util-deprecate@^1.0.2?target=denonext": "https://esm.sh/util-deprecate@1.0.2?target=denonext",
1464
+
"https://esm.sh/zod@^3.23.8?target=denonext": "https://esm.sh/zod@3.25.76?target=denonext"
1465
+
},
1157
1466
"remote": {
1158
-
"https://deno.land/x/lru@1.0.2/mod.ts": "1d44b87c4d40ff33749ae5fd85fe234344e0dace835fdfeb48413edea9461159",
1159
-
"https://esm.sh/multiformats@13.1.1/bases/base58": "3ec9726ed217d96c33924e3357102a0c4ee9130f68ea362525a1a4df0ff306a4",
1160
-
"https://esm.sh/multiformats@13.1.1/denonext/bases/base58.mjs": "d99841a98cda1b2685519b4914e62e61fd22dc2647a02cc6f70bee0658635302",
1161
-
"https://esm.sh/multiformats@13.1.1/denonext/bytes.mjs": "d8578dcc54b8e5c345f6b3b83ed1ada64f400353fc2e7d8b15179723856d7315",
1162
-
"https://esm.sh/multiformats@13.1.1/denonext/dist/src/bases/base.mjs": "a72250e285c1709319c71742654598cd0f149b03b616b268a0abd63fe396fc6c"
1467
+
"https://esm.sh/@alloc/quick-lru@5.2.0/denonext/quick-lru.mjs": "18d5ab8b0ee2b8c0ba55eab65a86f126f480d5d0ca85eeefa32f9ee2e399f110",
1468
+
"https://esm.sh/@alloc/quick-lru@5.2.0?target=denonext": "8506b81cc497d3d01a8b06176598c36333efab2f6773b5f681f4bfbeba33079d",
1469
+
"https://esm.sh/@atproto-labs/did-resolver@0.2.0/denonext/did-resolver.mjs": "a88663c45be439196b85c03cd1b6bc904b5b33c2aca0f3bab716f1a8c305d7e6",
1470
+
"https://esm.sh/@atproto-labs/fetch@0.2.3/denonext/fetch.mjs": "eaabcb75b50ef9b62d749e0e6843f11d60204526a0296ff84a99c8d8385862bc",
1471
+
"https://esm.sh/@atproto-labs/handle-resolver@0.3.0/denonext/handle-resolver.mjs": "5ce886fa149d802d864b9988d0be71d18e37e06b7aab5a941a482aaa7b2d9da6",
1472
+
"https://esm.sh/@atproto-labs/identity-resolver@0.3.0/denonext/identity-resolver.mjs": "36cf98e9478813b6e5eb6e33da8771f2dd33a2e8ba587d32803dc0b766cd5cab",
1473
+
"https://esm.sh/@atproto-labs/pipe@0.1.1/denonext/pipe.mjs": "9432ecdd24724682a612fc0f47f019366a1ebf5e3fd2ffa9ef6b5176419ed84f",
1474
+
"https://esm.sh/@atproto-labs/simple-store-memory@0.1.3/denonext/simple-store-memory.mjs": "789ef49fe6b81024631fd4340183c670f3f35b1be8ca773d5c14f7a040dd0b89",
1475
+
"https://esm.sh/@atproto-labs/simple-store@0.2.0/denonext/simple-store.mjs": "1829d20db9d7157cfd85b3968dacf60aaab1fa77742612ae0ea193a616feec21",
1476
+
"https://esm.sh/@atproto/api@0.16.3": "c50f915ec53d1d48b2fbf88c74ae6d41da255e9ac6e7449ca6c8a723a1fa419b",
1477
+
"https://esm.sh/@atproto/api@0.16.3/denonext/api.mjs": "1fd08820fbc9cc67f29c4dfacf1f5b806ac67d1ee3bbdfcbc0bafcf07fda0cf2",
1478
+
"https://esm.sh/@atproto/common-web@0.4.2/denonext/common-web.mjs": "350a01dc9ea0e21c5b85c166d269f53c3348ec3daaf94b97e035d0256366a2ef",
1479
+
"https://esm.sh/@atproto/common-web@0.4.2?target=denonext": "bb0d13657e0fc4565eaf217a456ea6a48c2ca5178ecbb6692eb0d538ef51a4cd",
1480
+
"https://esm.sh/@atproto/did@0.1.5/denonext/did.mjs": "1ae8eff7a842a509499f7efd1a1db842087499e2f03138ac8400f9558f926733",
1481
+
"https://esm.sh/@atproto/jwk-jose@0.1.10/denonext/jwk-jose.mjs": "d4e05a0828e23532f185e301ec2b70c5bc4632691992b21f7d4af43e5ffe784c",
1482
+
"https://esm.sh/@atproto/jwk-webcrypto@0.1.10/denonext/jwk-webcrypto.mjs": "ba106ae637353dcbaa7e8b7e6a0cb888e707261383a9c56eaa4a7fe4d8ad3519",
1483
+
"https://esm.sh/@atproto/jwk@0.5.0/denonext/jwk.mjs": "5791d0fc7369da7993594d23ff00df612d43b33611b293df66476e07a0033b0a",
1484
+
"https://esm.sh/@atproto/lexicon@0.4.13/denonext/lexicon.mjs": "eed494b224e5d28b2d1b1c0e88740b16eeaab1b1739c1d9b3120feb35f238de4",
1485
+
"https://esm.sh/@atproto/lexicon@0.4.13?target=denonext": "df4a5757c00257ca4289db10271075cd3e89d48c9d5664d1e40ad3db933ea3ab",
1486
+
"https://esm.sh/@atproto/oauth-client-browser@0.3.30": "d5df31ac9f9536879c8e1dbc8a5f3b9dc6635321044aaceda6f89028e416bd84",
1487
+
"https://esm.sh/@atproto/oauth-client-browser@0.3.30/denonext/oauth-client-browser.mjs": "cae96820945c788497c365ce0b99decacf17b6b0b08ab3489e71fa2f7a62fdc6",
1488
+
"https://esm.sh/@atproto/oauth-client@0.5.4/denonext/oauth-client.mjs": "75c5656274abcd6fba39aef3d6eb5b1ae53459a01665b17e9699db87278891b0",
1489
+
"https://esm.sh/@atproto/oauth-types@0.4.1/denonext/oauth-types.mjs": "aa73bced2c5dda2b693f2c7eccddf2c26ef8f263553ee3be3ce0bd387ce75de0",
1490
+
"https://esm.sh/@atproto/syntax@0.4.0/denonext/syntax.mjs": "e03d230b4bce87f75b6af7826e654db0ccc1ee1e8a0399522b708bfbb72534f9",
1491
+
"https://esm.sh/@atproto/syntax@0.4.0?target=denonext": "d64c2ed239cf18dde908f4e6e78ad2771d29f879318eec4b567e9737f0b4a0cf",
1492
+
"https://esm.sh/@atproto/xrpc@0.7.2/denonext/xrpc.mjs": "8b6a604c52f49f0e958185275f5553326d93178d468de49b0a6a8081882f8840",
1493
+
"https://esm.sh/@atproto/xrpc@0.7.2?target=denonext": "0d01d68187689e970d63c99364a99659c6846e0eedc7abb9227ba082d0546a70",
1494
+
"https://esm.sh/@jridgewell/gen-mapping@0.3.13/denonext/gen-mapping.mjs": "297f733cd1f48cde4050055e4379df23b701394106946ec8692f41fd4fbc06a3",
1495
+
"https://esm.sh/@jridgewell/gen-mapping@0.3.13?target=denonext": "d3eb8a4aee09dd989ae0d8121874e9ef41d7b1c2261a8809250654d040b54e83",
1496
+
"https://esm.sh/@jridgewell/resolve-uri@3.1.2/denonext/resolve-uri.mjs": "2da147ad4f55ddeff542640347c789ff19db8db4ead77cce27a0c171f5f5c349",
1497
+
"https://esm.sh/@jridgewell/resolve-uri@3.1.2?target=denonext": "9f734a6f626960d09b0ddb1965c08a7d5960fec667f891b4a0a55d23d0bfac51",
1498
+
"https://esm.sh/@jridgewell/sourcemap-codec@1.5.5/denonext/sourcemap-codec.mjs": "51baa3e273e77b02f11ce4ed87b71cad04b839a47ba88ab320aafd5d96780eb8",
1499
+
"https://esm.sh/@jridgewell/sourcemap-codec@1.5.5?target=denonext": "92c30f6ceeb81e53eb90d72ecbe02f22216c2c8613582768f10fca120d3812ab",
1500
+
"https://esm.sh/@jridgewell/trace-mapping@0.3.30/denonext/trace-mapping.mjs": "76ce197a54a9e8296ccefda22951f70f8c889c29bc6ba3ee49f11b6b51084478",
1501
+
"https://esm.sh/@jridgewell/trace-mapping@0.3.30?target=denonext": "b48a6080590350f580de059f118f46810488cdceb1158dc2f53513a25451cc08",
1502
+
"https://esm.sh/@nodelib/fs.scandir@2.1.5/denonext/fs.scandir.mjs": "274e376535b7b253c73f86a42b3bda10049b6fea75db4cc7b53b88db339286b9",
1503
+
"https://esm.sh/@nodelib/fs.stat@2.0.5/denonext/fs.stat.mjs": "54da00841dc5e6b5581f50e299fb9fd23864c11526145c2bd581e5b40737cecf",
1504
+
"https://esm.sh/@nodelib/fs.stat@2.0.5?target=denonext": "7703befd72f56697b978206aa0942d7f53aab31b0a55f1244b4fd794fc81499c",
1505
+
"https://esm.sh/@nodelib/fs.walk@1.2.8/denonext/fs.walk.mjs": "ce895194f61083880e5aef2390ea4edcfc12597f308be1561a1266392f0f67eb",
1506
+
"https://esm.sh/@nodelib/fs.walk@1.2.8?target=denonext": "a68ec3adf7a2812351a3ed3cb8df689acad4c7afc70fee986dcd895be3a9bbbb",
1507
+
"https://esm.sh/@tailwindcss/line-clamp@0.4.4/denonext/line-clamp.mjs": "252fa8d82ac4e569e3ee8140c9ca3e216760631da1019cac48e6750feb89d8d3",
1508
+
"https://esm.sh/@tailwindcss/line-clamp@0.4.4?target=denonext": "1fbc5f55d06b23c8fc79d58f00831983224f1c15b5249de3cd0a2d51fed22572",
1509
+
"https://esm.sh/autoprefixer@10.4.21": "2bb14916b2e4881a32ee311720679c3e57e2d04f45ac808f4f71ae4e0923801c",
1510
+
"https://esm.sh/autoprefixer@10.4.21/denonext/autoprefixer.mjs": "0d9874129a69bb35c1392f38f09e6a5a747c0eeb8c2b29e7fc8d4c1868337d92",
1511
+
"https://esm.sh/await-lock@2.2.2/denonext/await-lock.mjs": "6787201229bc4ccae3aabbcee4086ea60b803bb476b37e576ec135e954f3ae5a",
1512
+
"https://esm.sh/await-lock@2.2.2?target=denonext": "62d8dd2ac9a50e115f76d05f309f437a8dbc6e53ff508c70addf07ed40ad0077",
1513
+
"https://esm.sh/braces@3.0.3/denonext/braces.mjs": "a3909393c947301c3b6348ab2378bd4f96fe001009b9beb44dcf1030dd717f47",
1514
+
"https://esm.sh/braces@3.0.3?target=denonext": "58c0fed5cc432a2192aad7eeaf6e3988b96830a6c6ec0ad51dfc97578b2e8dee",
1515
+
"https://esm.sh/browserslist@4.25.3/denonext/browserslist.mjs": "df95a5657a9d8a83aa8e834a82e1747b3d6e8ea1ee8d41676c665e05d803c103",
1516
+
"https://esm.sh/browserslist@4.25.3?target=denonext": "31d80d40c1963ce619917d0fdb33cfad4ad4c3fb4d431522fde74a5b6dcab228",
1517
+
"https://esm.sh/camelcase-css@2.0.1/denonext/camelcase-css.mjs": "1aa67ed93327d93dae71a70444bf1408a8ab3eab8bba2a710d3643b4ed415901",
1518
+
"https://esm.sh/camelcase-css@2.0.1?target=denonext": "39dbe8afdc8dc441073d55efa7ce04adb31a07d8d31619fed5d31e299be32c93",
1519
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/background-clip-text?target=denonext": "410cd6f12e4acbbb198bcffa735247c11b114d672cd71e5c355e550394b485fc",
1520
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/background-img-opts?target=denonext": "59bc277f34121910caebe8cbebae46596ad122040cb0fc0a32d91980641cc6e5",
1521
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/border-image?target=denonext": "eaa8f44f48530b9f5b5bde4040ec6bedc60482324912efdbb9c7cbe23b4c40e6",
1522
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/border-radius?target=denonext": "2793c2fb8b38abcc35fd76f4151a8562b1ffbb360090f54d43ef0f69cabdd953",
1523
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/calc?target=denonext": "6810ac3373ad65b4f1df8d5f5df2ee51de539337676380505c69fdfba33b718a",
1524
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-animation?target=denonext": "cd08f5a8d560b438fc4742ccdb35babf59978cee4f0f4d0ea0ce35e532280b77",
1525
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-any-link?target=denonext": "5f170cdba78f28591bbc12bc2c353345fa9c9b99302767673b8ed84518a8f148",
1526
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-appearance?target=denonext": "c6fe9a1b6943c32b48604e96e3719ab7de884c02d94a5a86b299798a56c0ced3",
1527
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-autofill?target=denonext": "2d0fac9e144ea16f0cb22fc09748b4c1525475ed76df9069056e1016ca04694f",
1528
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-backdrop-filter?target=denonext": "1cbb5e4e0dc743651ad302d564dabadf9d7db009e8ecfc67459e68d6899276ef",
1529
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-boxdecorationbreak?target=denonext": "a384c16f0a989c7c11037a7beac2704b6e20880884b20e2b68ccd5caa09b1041",
1530
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-boxshadow?target=denonext": "e2d56528ee8e1fdf7b1c84f7d742411c910d8e1c5e774ca7c6f35fd4aff0185b",
1531
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-clip-path?target=denonext": "da574566b0b2e0f75e4dd20da5ace9303cc3aa783051c9a74d4990ce5783875d",
1532
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-crisp-edges?target=denonext": "d836bcdd65d84c66e14dec3713cc577416fab771869eff01d1c259d0731c60ca",
1533
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-cross-fade?target=denonext": "a1255ba2cc65f09a24d794b120e9042e53cf8182a39088f3deee1acfe4ba30e8",
1534
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-deviceadaptation?target=denonext": "5d279ede28f380339f2317871583446a89858852ff404a7c2f2681169dcdf63c",
1535
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-element-function?target=denonext": "86df7c505abc3598c283cf3bb4e1262272185d5c9c04d0bd0bc4241e744d07af",
1536
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-featurequeries?target=denonext": "cb26e40828c397b118377fe9c46d8827bb49a5c95720ed47155368f9dd9072d5",
1537
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-file-selector-button?target=denonext": "d2efe044761f04e8daa85c6931f2edb53ae8a076efee72a62bbcde98404c4546",
1538
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-filter-function?target=denonext": "00dfd6452d13e7fd78eac05c4b4460d5fca56e59a16f900e6b1aaddc13316c34",
1539
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-filters?target=denonext": "c0ed7f7a41725e851ab52e6f67c7d147566d04e2b501e3dab2c8b3d2dc8f7454",
1540
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-gradients?target=denonext": "723eac5e8252240a41abde46919d8b977516f1a1ba6acb8173a49a547d5b4c47",
1541
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-grid?target=denonext": "6dda1d6ba3b27b5d73f6b924bb8a82a34f1eb109415a20ac8f3284e75fa2c075",
1542
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-hyphens?target=denonext": "1e16430cc4bf0439bf66e6e80965ad89ac2c83a4f684b959289f1cef5929f382",
1543
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-image-set?target=denonext": "c74d4ea198e1e44dbe3a4437a58dd45ad7f9c0dbc68731903643d6aff28159eb",
1544
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-logical-props?target=denonext": "e1d3d069d199e623d5cbfd5a99b74f838fa8a29b4b9535809cc3e6061678fd71",
1545
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-masks?target=denonext": "49b3001ebf77fd07798fb3d1f8bfa397926a86c15d66ec3c745e19df652b0266",
1546
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-media-resolution?target=denonext": "44c93f9033a112f25bb41028a7aea9284c25fa0950f2b14005b5649b86acc2c2",
1547
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-overscroll-behavior?target=denonext": "fa8b69dabf3ad7cca1b765ce9b8277469d12fd92bf8dad3fe537c25ba0bc46ad",
1548
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-placeholder-shown?target=denonext": "eb885bd8860c72500e69088c19d985752d839d0bccbb21f8f5a8515afdc21e2a",
1549
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-placeholder?target=denonext": "3b845f3c43353b5bf195ec1e826bf79d299535693f0deb208693160cdf87de00",
1550
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-print-color-adjust?target=denonext": "89d6b59aef0ca1baacd771a908372ef4f3f011f9e01cd6a2c01b8ae6cb9cbc91",
1551
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-read-only-write?target=denonext": "98a11fd4a5f865982e18d76eea1c3f154bf78e3474a6c3b66e794ce59b3c6fc7",
1552
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-regions?target=denonext": "ed41dcd2492b49a91a8d3b9cdb195f56c231c0feb97470954af91c1d95f5c45f",
1553
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-selection?target=denonext": "166b5f4697cba38a485f734f88fc111158b6e8d022cc95f0da91740ef7068f4a",
1554
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-shapes?target=denonext": "62eb44b6c358eeaf93589babc2f3209f224b6749d56d8d4a87cc459ad014c085",
1555
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-snappoints?target=denonext": "8ec2ae78e2fa3635526e139a5751234da704942a912e753ba4c270aa0b3ab87d",
1556
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-sticky?target=denonext": "4b0cc7d05e9ef45fbe86a3285e915af119ecee64fa11d745ca6ae66dfa416a28",
1557
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-text-align-last?target=denonext": "11314b76a045f01304bca81b40b61d23c93ea7f157418396775a5d350d55b6bd",
1558
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-text-orientation?target=denonext": "569d5ce1d38a5003618a558a522265c2b68bc6227cd15f21fe4a2a2909c85c2d",
1559
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-text-spacing?target=denonext": "4733902f15a9f42900bc1b1fc19fb468efd7ff5afe4c6cb547779b1ddaaeb2bc",
1560
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-transitions?target=denonext": "35b76706059c4ccb1e74d7274559bb8332649a0c29cd6b198ec8174ffb5cf4f1",
1561
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-width-stretch?target=denonext": "97db70aa18a7c3500e32d8caba17de65acbc533073796bf81e032995d99fb460",
1562
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css-writing-mode?target=denonext": "d8c2c92808c011fdf3948e28938f089d0656da7aef69845745e9844f2c1f5bf5",
1563
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css3-boxsizing?target=denonext": "8b65cba0f1db351d9f412b1a48180b7ba8e3837c2268dae681b2f957d2c76c4b",
1564
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css3-cursors-grab?target=denonext": "d092f9cffa858b742b93d14366f9fa0baff600b34367098da6b6b3a76efd98f4",
1565
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css3-cursors-newer?target=denonext": "3f6f81cf79f4f977d6bebe979392c0e19b0c4e865528d80c1869230309dba559",
1566
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/css3-tabsize?target=denonext": "bf253ccfb4095831b38de00ff84e4888b33a027ff3826bfbc44738018cbb9780",
1567
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/flexbox?target=denonext": "9a6f56410f6e89c8aa34e4df56fe24ded91f4a5df9d7164abadaef021ab26642",
1568
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/font-feature?target=denonext": "4b0b57ea532fa7de37e99570197c4fb4af9dec2aed32da35e8fac42645400729",
1569
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/font-kerning?target=denonext": "e5155e132f2a99faa3c7585d36581a4bd11d64204b156be806287b4d4547b262",
1570
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/fullscreen?target=denonext": "62069c47a7f1f1eb3ce947d4af0c777859bcb9524b5393669e5fc731915e58d0",
1571
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/intrinsic-width?target=denonext": "3d081e99e46156d3630ea3ee2a027dc5fca3f256b693daf160d8348720ae70b0",
1572
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-css-backdrop-pseudo-element?target=denonext": "c36d9ae3ea67434ef7141f9e57750b9fbef89723d9fe4a4e021bebe73ee00ed6",
1573
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-css-unicode-bidi-isolate-override?target=denonext": "b5cf96212c3858b21e4345ea5164b23d54cc0b8d205b3e469ef7a5b2562f4266",
1574
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-css-unicode-bidi-isolate?target=denonext": "249db094ff9a765fe829d325dbca9efd06622515d6435f865bd2761040850b64",
1575
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-css-unicode-bidi-plaintext?target=denonext": "02a939cc49f116e74495813b3912600f7c1f541a71be82ab897c5eabe5910e7a",
1576
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-text-decoration-color?target=denonext": "364776cd45c73a6b11089ae8c8d3352fe829950c3a2dc06ed47b091b544fb146",
1577
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-text-decoration-line?target=denonext": "a639f52a7f11ae01303194ae61238c3f1ea228d6d4349846e9a76ef5dacb4405",
1578
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-text-decoration-shorthand?target=denonext": "8d27f04a38b7be26f8bdb2e2a8c4b5b9cb53adcccea404fdb538bb273ec1f3fc",
1579
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/mdn-text-decoration-style?target=denonext": "353e35877b7ec3ade3679f7101ba8b2363e6c39ea5eea8972ea1d386d6998038",
1580
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/multicolumn?target=denonext": "5c819517e4c89e4cdc626a97802996df69d2c269d01ea1ce6aca75a684448517",
1581
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/object-fit?target=denonext": "fd6fc4f7ae57de5712e5073ee6961bb57afb0c9e947b9837ea38f1842924468d",
1582
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/pointer?target=denonext": "783269befdd4f21a426a12bbb95bdc4667ecaf9e240ca8851d549e0a200494ac",
1583
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/text-decoration?target=denonext": "090bbeb9e7969784e4db951257aed5babaf0a4e98552877dbc9cf138917a2155",
1584
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/text-emphasis?target=denonext": "c21d9db705a467e4b192a86e760b2e071edababdc106db810230262a1f262522",
1585
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/text-overflow?target=denonext": "6c3c48dd88e402d658461c15cb1d7eb053d91f0866a08b416914e572f149d320",
1586
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/text-size-adjust?target=denonext": "f471ff7716b89dfda4b5296ef98dbc7fe58f3de3a6640f72b553c5f1944d6620",
1587
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/transforms2d?target=denonext": "0dcb50846de392278e49bc7f6bcd2ee37d40457ae103e2e6292726f9670cdbe9",
1588
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/transforms3d?target=denonext": "a7159901d2b73dccc4caa04bd0c0b2ab171c65cf91d7d48b3596cc727fd08316",
1589
+
"https://esm.sh/caniuse-lite@1.0.30001737/data/features/user-select-none?target=denonext": "1c5d634aed511a26198d2df4ab1a6ec00fbdde084617140d391c85cf5fea8b04",
1590
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/background-clip-text.mjs": "bdb85956ae42e455d4a62d5e6f250c7fb07ae615ec130480bb3c9d3eb5ec057e",
1591
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/background-img-opts.mjs": "86ec64e7c7278e916e4c7b646010b5648880b33bbde7656467d1b99f91f5e8a7",
1592
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/border-image.mjs": "93cac91d3b2beba05865ad31c591d1e25fb851b59a6ec357e1fa87c6314aa0b7",
1593
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/border-radius.mjs": "381bf81a142193a85d7a0165efea15ea2910ccf0a2bd4e3a8299b286bd30bb5d",
1594
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/calc.mjs": "eab741e8c7640da2c85222bf217e8d17c2f4c8669326d8c57a319ef4b20b79ac",
1595
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-animation.mjs": "607f634b5572f61d0a68604ea809cff0f2b085a860867e331d413dbb4c2745ee",
1596
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-any-link.mjs": "e25fe5d99e87b5585531cf89b8cb79ae610b54cf071f6e177d749d2ce35daf08",
1597
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-appearance.mjs": "87f1fafa407b96e8cda675edc88dd59ad43ac1564c48dd31de9b9f1751143827",
1598
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-autofill.mjs": "697f47c9f40f8a67bc1dcd5b2e97373bb5c5c74550e4d69f8b467d510403adb1",
1599
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-backdrop-filter.mjs": "e72583cd26a0e13335d2558bc5d0b9e0c83d6a71d73d5924a165585a638211c4",
1600
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-boxdecorationbreak.mjs": "a76be63656f090096b4f57e6dae05abb807e5d8eb8315e34b74480ab606eddc6",
1601
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-boxshadow.mjs": "3a9efa13ee4413ad69f18264ab56cd673ca0a5568d854061fb996652df3eeea6",
1602
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-clip-path.mjs": "05c24465e6080304e0d71f12996d004619dd098f1009aa6a6b4558defc46ca72",
1603
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-crisp-edges.mjs": "60822ba941125c8a9727a8157c2c42a2be08babd937da435921a1a7ee511667a",
1604
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-cross-fade.mjs": "d19d727301af8c921be97d963406203addba4dae4b2fef206b54fe5bb354bf37",
1605
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-deviceadaptation.mjs": "b6281816e7439f8c01be80825e7a183991ce53e50f0a1dc4ec1a938bbedf2874",
1606
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-element-function.mjs": "13d5108f90aed723cc0ae3ea8cd3f62e3884f4a28ff43b69b3e41c62e47d480b",
1607
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-featurequeries.mjs": "7352ae67e14b4a01c1add6a640789e7349419f2ef84ee010627d12c76093099b",
1608
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-file-selector-button.mjs": "91c7b2e32daa62f755276ee2cfd4b8891b8582b554f83952b654110754882f15",
1609
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-filter-function.mjs": "70759427567e55124a2ff510aacbd533959dbe4112219da4c5374e268cedb278",
1610
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-filters.mjs": "26f815df8032a8a379d2c7e606d18e3911720c6e31c4e276377f33a5ebd476d4",
1611
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-gradients.mjs": "e87494876bc0b56246c94253b544c73e9b1dc0d8a84e028b21dd629e8c9c3948",
1612
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-grid.mjs": "4fe4b29dc613965e55e5c22e31e229dfc9fbb80150d4efc1a8abc72f5f9b8e14",
1613
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-hyphens.mjs": "edcc22d5457cf0b7b503c60e55b5b0c951a4f0c523d523fea84b8bd88d15946c",
1614
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-image-set.mjs": "e7e7703827d9423f51956269a1a6166bca4ae457c07267ae442dc05fdcf61d67",
1615
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-logical-props.mjs": "ea1c94a9d807f68b0d40e037ebf30a290efcfb373aa7dbe272352a749efdf254",
1616
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-masks.mjs": "669925ddf125deee84c2d7adb1b87837fc537269cdd3ee51683d2ec4a1a52d23",
1617
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-media-resolution.mjs": "078b7975f25d500f87ef521fa7378e52ab97955bbba5918d7a04404107afeba0",
1618
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-overscroll-behavior.mjs": "dce019ccb76022b24143c73121c3851163256b61a2c68e6149751fd7652f54fb",
1619
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-placeholder-shown.mjs": "2b5179cefe731d536a0dd8c815e05d79726a7d7cc7061305e2eb50108cbd05f7",
1620
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-placeholder.mjs": "57df9a96cb719d02395d1f9935646e0218a1ceea5b903ac9c8c6ab890a56ccbb",
1621
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-print-color-adjust.mjs": "1b187f9fd1a418111ab3a0c76e86556a520f47123f5557010294d48dd409c13e",
1622
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-read-only-write.mjs": "2c1a671c7dacb1482cf9e2e0a0415338d3026bfe5e18c0058006fe98760d50d3",
1623
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-regions.mjs": "7222af95c5ccc737fa75507c9da87854d20ecb4c5077b1f629dc6fc6e47fc2a8",
1624
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-selection.mjs": "00e7b6132d470987b628b86a22f069832b31f8183f69b46d450802e47e1f74ea",
1625
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-shapes.mjs": "8e8d0689a39a85dbeabb92a90af7077bb939fd00e2817979c48bc7701f163ce0",
1626
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-snappoints.mjs": "d10e85353ec7514cf80111f85b359adcf476e3c7bb3572bf33e8829347075302",
1627
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-sticky.mjs": "6eae33fbcf2b2262f45bc5684001ed8736cf75b77e6f89870534c4deb7a61be6",
1628
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-text-align-last.mjs": "cd77db521f58d08224873c34a17f0c91009f0c6d09ab4568ff87cd7a564819eb",
1629
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-text-orientation.mjs": "ae59a4601580e55198ac6fe5fd4c947b6a9e03e37a276412a7ebe9592671c187",
1630
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-text-spacing.mjs": "6fe05cca556cc6b2f631aec00f9e55d7e69eda940cd781be84b41140f67b4ebb",
1631
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-transitions.mjs": "ca2f6ab6dac7f45a95edd0dc36d49df93b41db39afec2e476921e0649ec4daf8",
1632
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-width-stretch.mjs": "24ec65468afeb2d23746bf98d6e1057ae13712167d72c135a07333918e50fdb4",
1633
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css-writing-mode.mjs": "dc558b48d0c72a8b0af415edcb356a4b275214e91a57c2353eb4a1fa8ba116a5",
1634
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css3-boxsizing.mjs": "2d449b364db28710869692dd514313a198ffbda248508829c520f9f33c94142e",
1635
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css3-cursors-grab.mjs": "8b59711eec9bf66bf1b8fc05cf90150a0faaba7041e2a9e043ddbcc12807e7dd",
1636
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css3-cursors-newer.mjs": "d4d1aa096ee42dd314b903f4dd57105e7a3cb35a1162f7bed622d3015d2854b4",
1637
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/css3-tabsize.mjs": "5ffa1dd48a6e51d88f2c670da06097987114f79936fe2edde5994315613494d9",
1638
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/flexbox.mjs": "9534b126de76800b2525ec28866c6305213ef913ea733455769ffbbc2cec0794",
1639
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/font-feature.mjs": "7a980d615bf64ff3745fa5cf0905e294480783ce7c6c03194007a575adc58c8e",
1640
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/font-kerning.mjs": "b83954bf08d971e63fb497fa819c1adc648cdfa785837e6524697b9381ff3c1d",
1641
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/fullscreen.mjs": "5891ff5b7ec68b989b975ed6a503954dbe80948eba1a4f94bdb324fb6d100d68",
1642
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/intrinsic-width.mjs": "f20c350a8a3fe128c6199dd420567261f1b7725f034a9fd88f83c30152c53a54",
1643
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/mdn-css-backdrop-pseudo-element.mjs": "179776d08999cb1b2e8c4ae8e263b49763df8399d471f4909644a6e2c5d012b7",
1644
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/mdn-css-unicode-bidi-isolate-override.mjs": "54c2e15741d8258d040ca81fdd37bdc6f918efc32750980734885fe664a832f7",
1645
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/mdn-css-unicode-bidi-isolate.mjs": "24d550a606b8b4106781d8e563f05e2ada55ca7a1699be521a92a70c819666dd",
1646
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/mdn-css-unicode-bidi-plaintext.mjs": "42b7bc117feb8219fc7f7677bcb15c2efe49a3f6548611a8cca1b557c7ad0305",
1647
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/mdn-text-decoration-color.mjs": "fd03e9cf4d486b231246916f5085563ddc619d36bdfc2bf609a1c55571fb9c96",
1648
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/mdn-text-decoration-line.mjs": "db5a1a9338a7dbe3e88cd5abef6787a232e5434b64db37888d993ef6d55fa40e",
1649
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/mdn-text-decoration-shorthand.mjs": "9b738afd412e2c34b315ed70e59a69a7ab6ea274178720bf464876fe90fc38d9",
1650
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/mdn-text-decoration-style.mjs": "a0d504013edd6c6df38fe148fc799a957bf666b34510f24a1a610374675f7b78",
1651
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/multicolumn.mjs": "bd22ce2f445294b60b1a2b5776c7dc07d571accc521b44392dfe836adefceb87",
1652
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/object-fit.mjs": "39404d9bb268bae0c0c4b3083ae0d87e78795a02b0309ef5fe083b5fe3607e7e",
1653
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/pointer.mjs": "dfbdd4ee5120d8d674b2e23f260a1e0494cbab76c6b55745f7dff1e6580df69e",
1654
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/text-decoration.mjs": "8f6c7af83602e141080880a6654652aea09134d29db67b24f53ad599590a3f87",
1655
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/text-emphasis.mjs": "38205c00c7d5604831283bfe1b1a84561914dee827a61bd1d6f2bd8f4ad6c627",
1656
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/text-overflow.mjs": "7d011a8bc6868ecdc1fdaafa9f6f41f6e84838e406dd46bcb4a27bab7d5f2c48",
1657
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/text-size-adjust.mjs": "10c6dad907ea072ef5f0f1e33c1bde52f01671628f527cd769f499961ea6b6a2",
1658
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/transforms2d.mjs": "aa8f3bf911095e1aabd71aae3fa093e352a99e24f6f8da8821a78276d70efadb",
1659
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/transforms3d.mjs": "f54066b13aea8a87af40013481dd0967f930dae7c860168ec120b5feacab98a9",
1660
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/data/features/user-select-none.mjs": "3342ce1ea8ef2d0728d97ae94e620416ee068471998df49d9f0cb4d29323d90b",
1661
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/dist/unpacker/agents.mjs": "41dae44eebe85a886f99258ca97e9c75994f454581fdb3e35e0e2e7592fbf1cf",
1662
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/dist/unpacker/feature.mjs": "4534f80567e4d604455937c88d77df53f9b0b080d6e2c5f69bcae8e315a4a840",
1663
+
"https://esm.sh/caniuse-lite@1.0.30001737/denonext/dist/unpacker/region.mjs": "736d74f47016712cdfc6422f6285c3fb661f42358d2cd8c43d97902f65bb06d7",
1664
+
"https://esm.sh/caniuse-lite@1.0.30001737/dist/unpacker/agents?target=denonext": "36927a98266c6a725d25d812fea7c8aa1cc889b85f39a6a4323d9bb62d3e6e4f",
1665
+
"https://esm.sh/caniuse-lite@1.0.30001737/dist/unpacker/feature?target=denonext": "f258799b1015a81ad8a835cd3f9300276cad296e2380d56da2df0bffd1316c6b",
1666
+
"https://esm.sh/caniuse-lite@1.0.30001737/dist/unpacker/region?target=denonext": "e6aec30f45515f3e7b72249ba55d7344395f1f524179cb2dd1fc1914299bbf96",
1667
+
"https://esm.sh/cssesc@3.0.0/denonext/cssesc.mjs": "c548acc0c3c5ae183898994347dc3ae57b71cea73f3df1d1643c096c84e75d65",
1668
+
"https://esm.sh/cssesc@3.0.0?target=denonext": "46b22fe658d7c495f8c683cf71f88369ec073d6aef6f0f94729672791842b80c",
1669
+
"https://esm.sh/didyoumean@1.2.2/denonext/didyoumean.mjs": "eaee3cce9c279c82404e5b2e2d82a6f94c439b7bff49a2643ffed05b279e1a1c",
1670
+
"https://esm.sh/didyoumean@1.2.2?target=denonext": "9c183f2cb26fa0cbefe396d71a209ccd3fb48543050a839379a69fd403c3b9d6",
1671
+
"https://esm.sh/dlv@1.1.3/denonext/dlv.mjs": "0223283fdcab2dae38eb62b13a9a77bc9fe6026b4b62f2a8f94cf2cd35cac82c",
1672
+
"https://esm.sh/dlv@1.1.3?target=denonext": "a3e3bfa84c6b020d6b8cf93f6ac21f95ddea3c4b486e44f0af39696a6468069b",
1673
+
"https://esm.sh/electron-to-chromium@1.5.209/denonext/versions.mjs": "c20511234422f9073a83e340b2e9bec41139e3ddabb1f0beae7d78f8e50bec25",
1674
+
"https://esm.sh/electron-to-chromium@1.5.209/versions?target=denonext": "249492c791195b333ade914a005df4d04da72a6070149595c95dda573caa8487",
1675
+
"https://esm.sh/fast-glob@3.3.3/denonext/fast-glob.mjs": "65877d873498643483aa01a3d773ebf3c5283352687e3ed451fd8e3a04473eae",
1676
+
"https://esm.sh/fast-glob@3.3.3?target=denonext": "013bbfcf40dbc297f3ae57f7c85a9fa6af2029e24258e2fd69076dbf0fac7e9f",
1677
+
"https://esm.sh/fastq@1.19.1/denonext/fastq.mjs": "a637080c2d32098ce78735f27be3a80a3f33d512f4e3a38b2168e6af6b7f692b",
1678
+
"https://esm.sh/fastq@1.19.1?target=denonext": "232620945fc592798bd1f7de68116c945f458f9edc9125ed4ad6aa5d9dd61b88",
1679
+
"https://esm.sh/fill-range@7.1.1/denonext/fill-range.mjs": "a617c46f1e0a36d1aaefdcda2790bcf05c986c5ecff805ecc615faad25ff8d02",
1680
+
"https://esm.sh/fill-range@7.1.1?target=denonext": "3a1d3f856665be6a3a26a1007b55729d1eaa4142397a605b14b752458b997241",
1681
+
"https://esm.sh/fraction.js@4.3.7/denonext/fraction.mjs": "df20348b8214e3af3c8ff08526c03e86fd4ecc23bc0857706278902199d11828",
1682
+
"https://esm.sh/fraction.js@4.3.7?target=denonext": "9d79c0e257aa7b4cde79d03065818bcc604a3f27f33dfe53a19e2f94169db123",
1683
+
"https://esm.sh/glob-parent@5.1.2/denonext/glob-parent.mjs": "656e4f93468626b7b0c388908d24132e893315386ec1e83322b74cb0dcde5d1c",
1684
+
"https://esm.sh/glob-parent@5.1.2?target=denonext": "0e04ba2ebac5a052f7d5671a73c19f72407c2660bd3d5a5be577dd1ffcdfc7dc",
1685
+
"https://esm.sh/glob-parent@6.0.2/denonext/glob-parent.mjs": "e629258fece4c22cb6d3a108a97c95ea8df4d735e4c79630f2a7da870bbafa23",
1686
+
"https://esm.sh/glob-parent@6.0.2?target=denonext": "e30aa6b9dc68fbde857f7495eb6f05c7c41b5e5a7453be63a48cfd62269aacdb",
1687
+
"https://esm.sh/graphemer@1.4.0/denonext/graphemer.mjs": "5a2c9e558258abf5f67d95a6814b6a3ef0bb3ea649ed231505f42a2863d1b317",
1688
+
"https://esm.sh/graphemer@1.4.0?target=denonext": "09e56a40cab2fec5dbcce661e11517580d59b8854034d2414e4bb1576cd3254c",
1689
+
"https://esm.sh/is-extglob@2.1.1/denonext/is-extglob.mjs": "adbceb33b529cc88849ee39364a0eea16088cefb795221d3be31c7d6c2b7a47d",
1690
+
"https://esm.sh/is-extglob@2.1.1?target=denonext": "cc21d63edf9dcc600d613af9dc30eeac8c8ee324104dab9f978fa567ec8f3ae4",
1691
+
"https://esm.sh/is-glob@4.0.3/denonext/is-glob.mjs": "4555ca21a3af8a883c44c977fd1ff03be73dd7927dea188443992d01749b845d",
1692
+
"https://esm.sh/is-glob@4.0.3?target=denonext": "5fef477a765ea5f14bff33ee18156cd8398e8fba5fea6fa636ebe2280349e3ce",
1693
+
"https://esm.sh/iso-datestring-validator@2.2.2/denonext/iso-datestring-validator.mjs": "ed32c001968af2e58843b98d99f95fa09d7e5b056ae4ad2a30ce0d436daa087e",
1694
+
"https://esm.sh/iso-datestring-validator@2.2.2?target=denonext": "de296e64bf5da2fb01f4b909d22a6c1676fcc52a1c6f176aa5307df37eacb217",
1695
+
"https://esm.sh/jiti@1.21.7/denonext/dist/babel.mjs": "95d8f0b529fdeeb7b12eacfa93d6ad80c4548b2b407022e08608c6789b8e7d9f",
1696
+
"https://esm.sh/jiti@1.21.7/denonext/jiti.mjs": "75830015c5c1f473ae2014286ed7f9f66b88404641efdfef8c18731463295c7b",
1697
+
"https://esm.sh/jiti@1.21.7/dist/babel?target=denonext": "238960d30e4169f10b16b90db8b8e0e3695d3d13b589c2cbde7a70d5eb8761b2",
1698
+
"https://esm.sh/jiti@1.21.7?target=denonext": "8b16749a8a79eafecd65003c0a14082274620e9521e2a0137e5825ee353898e8",
1699
+
"https://esm.sh/jose@5.10.0/denonext/base64url.mjs": "539aace7c49b2261745da4ec543d941618d11a294c4e7799d6face203aebfc45",
1700
+
"https://esm.sh/jose@5.10.0/denonext/decode/protected_header.mjs": "73ce892cbc903722e5628006b329d836d29af384eeb99e18f4e8ad3888d5c241",
1701
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/jwt/produce.mjs": "72409fb814f902126eeca273e6c6855685ab0715f2ef95a5f61b29015862472b",
1702
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/aesgcmkw.mjs": "c0363171d95389f30ed6a8de16fdb3c8f123e1d7cb92f39011393c1038faf044",
1703
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/buffer_utils.mjs": "2b23cfebda877dbfc41c9a69438a158ffb31731d97308baa01621d73ef959f2b",
1704
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/cek.mjs": "85c4e75e70bb1da9960b6825e1bf985eb37881d7d23fa76e2bc2efb8ca071812",
1705
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/check_iv_length.mjs": "38783fdf60c458e435a42e588da9d4097fd2d096d92129673674da037d1defd8",
1706
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/check_key_type.mjs": "9d4b6990a0b8523681db4cef0bde67b957ef6f29f5708ce871225eb6f24d4e63",
1707
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/crypto_key.mjs": "59dc27357c645bbe213439acee0ef9ad481bd9ed751041ebe8801a108431aa00",
1708
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/encrypt_key_management.mjs": "35dd301ef6820956d2f5668d92786764dcbb459fae1136d5d11dae3d839a2e9d",
1709
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/epoch.mjs": "b594e6e3d563fd8b437b924b8738ce883d5d4fc1126dfd0ca230fd5c0fae02e8",
1710
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/invalid_key_input.mjs": "19a65328e952d4031c09c1315abe803ea1fdc9934f0fa4f455379bc3c072921a",
1711
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/is_disjoint.mjs": "85ebfe6f46e0feb55cd79e41626717594b2f1e997d54c9a66a255998b626bc51",
1712
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/is_jwk.mjs": "12240833a4979333cb4f8fad5f065c7cb9fff1d5e4d589a073ec99bdf2fc234d",
1713
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/is_object.mjs": "a477fb6116adc68383ea964f6e9b5542427e7638d192a23e93a683c5797ab387",
1714
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/iv.mjs": "3edfe59dfc97f12b3fff5535f95f0bf690d6dd1bd0140baf7a86d705313f09c2",
1715
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/jwt_claims_set.mjs": "67658e0bde825ea75aeecb740e34cee41b754ed0092b25f6a4e822d9d06f3d92",
1716
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/private_symbols.mjs": "5c8542caf07cd0e88f442c3cf16bf67b74108b8fa10f93f41141840fed183af9",
1717
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/secs.mjs": "aaab2389b4a39d7cdd422908aa1f60fb93b075df47acab0c4f42838ccca23c81",
1718
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/validate_algorithms.mjs": "095384ff18fb772f980bdb504474dfe87a5ec02730d5c510d59ef2d95e5efd06",
1719
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/lib/validate_crit.mjs": "176fa8b63805db36ef0eaa7490eef27c43352eec53ac2843920ab7b353282809",
1720
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/aeskw.mjs": "1ca307484491e42a7cd7be95f9a3a9a6504d433f4920f1a611f3faeb27ab9848",
1721
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/asn1.mjs": "71e4c6e67ed71454ad786e5ba25f1ed0634c527d1ef15f053116927828b9ed5b",
1722
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/base64url.mjs": "32bb04a70098ab611900258e09901161e0022265a4ff09c1f6a585f0d0dfe308",
1723
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/bogus.mjs": "26b5a2bace42941799dcb891f5676678bb2017ab64778e94923fe71a040d1719",
1724
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/check_cek_length.mjs": "b1511d0ecbc2bf90a8b50ebe9baa15eab169dfa3d3ad3a2a556474a69cb2a344",
1725
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/check_key_length.mjs": "3aec7aaa6c36c0b1c984cccfd476b310c3678dd8a5b07331e292c0a005b97798",
1726
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/decrypt.mjs": "f81f1f103e787deee890cf5d97a2c91b59a03e63efaf479c7844b0c20983a454",
1727
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/digest.mjs": "b170cf6543920f6c12d147c6a8914f8e626d686226e50af820fc0effadfa413e",
1728
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/ecdhes.mjs": "5eb732da9266ebdeb62b424dd898523ddbf4c09ca6144e53cca86793e611d65b",
1729
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/encrypt.mjs": "5a8563d9813f837850edf6e814f535e36fc6373673850ec4e75221f0de3ee90e",
1730
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/generate.mjs": "b6e73ada71e897f6f189ef2e2ffa7a4a91fbaf675d6d0aa86d6121d4c68dc07a",
1731
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/get_sign_verify_key.mjs": "8e91861e258c0fdf9988b0460a113158a01294b5541c56292314e6f345caa6a6",
1732
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/is_key_like.mjs": "43e10261870d4a65beb24cb868befc0e9aaff002b9ebf90257ffdaf073c636d7",
1733
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/jwk_to_key.mjs": "46ef0a4df4d689f2065bac957dae4e6572071797675123a08749bf83992abacc",
1734
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/normalize_key.mjs": "0e915ab84055caa0f3e031f9d5a545608e22976d22b03b35485b38611a8c282e",
1735
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/pbes2kw.mjs": "d964c662b1e8cd72e143bf13fe90eb7213ce21fa7b4aadeebba8394e0ec32db8",
1736
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/random.mjs": "1fd349b6114345bbd8d67407dcb83efca1720b9b862a4e1ca232427149a167c0",
1737
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/rsaes.mjs": "12cdf94a5b92eec19456f9df4b8ef2265cad392612345c36aa28065506cb8c51",
1738
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/subtle_dsa.mjs": "04a41e3a9ac0f77c9b34b84be050ff0a27c2d768eadb30a464d57567f857cd46",
1739
+
"https://esm.sh/jose@5.10.0/denonext/dist/browser/runtime/webcrypto.mjs": "a7ca0ccea9e87fa0698d48f5a0832cb28fea137705f4c494ff3f3c83bde57cf1",
1740
+
"https://esm.sh/jose@5.10.0/denonext/errors.mjs": "10bd291dfa016ffa8a5228e18bbc0c147b24c88725061bec323d1c1498bfde3b",
1741
+
"https://esm.sh/jose@5.10.0/denonext/jose.mjs": "745abba7b218a451fb747ab7250eb67bf62c7eaa0d2ed98fc7db4320b3ac2371",
1742
+
"https://esm.sh/jose@5.10.0/denonext/jwe/compact/decrypt.mjs": "868abeef0f4bc4bf499bbb4d64febc9121bf7f9d01132df8e19b7d7d50a0af07",
1743
+
"https://esm.sh/jose@5.10.0/denonext/jwe/compact/encrypt.mjs": "f0063936a5a8f9e4c50a978ad0bb23baf842eb20dc4e4982fa5363045685871e",
1744
+
"https://esm.sh/jose@5.10.0/denonext/jwe/flattened/decrypt.mjs": "49596d5a985ab0a850eb7924f53888253669f059f4687320790cbe04641ddea8",
1745
+
"https://esm.sh/jose@5.10.0/denonext/jwe/flattened/encrypt.mjs": "b70bb208e1cefcd08758fe39af6e9bc46dd475b3182c209c084dd6fd6575819b",
1746
+
"https://esm.sh/jose@5.10.0/denonext/jwe/general/decrypt.mjs": "16d59263b205635a9063a9c6256d92373d1e32ddf632d1ccc06a1a4c0cf769f7",
1747
+
"https://esm.sh/jose@5.10.0/denonext/jwe/general/encrypt.mjs": "6acc66f0eee89603c2bb5089af9cef5f579ab38533c3e0fe49625b479736efaa",
1748
+
"https://esm.sh/jose@5.10.0/denonext/jwk/embedded.mjs": "c13d8092a672bba2ce7fcf6d94804f89ce278842f829a041a1c8eef6019bb489",
1749
+
"https://esm.sh/jose@5.10.0/denonext/jwk/thumbprint.mjs": "428799b05c6ab4455e0d34a2974be42053f3fbcee2af5a7c829dcfc4d457aab7",
1750
+
"https://esm.sh/jose@5.10.0/denonext/jwks/local.mjs": "2f1492c3761af03bae601c48494d43b481740dcb01286461a0589f0978033696",
1751
+
"https://esm.sh/jose@5.10.0/denonext/jwks/remote.mjs": "7c352605a7b5f7048f139452253ad5705db74ed7391e36bbe6be3e947b83fd83",
1752
+
"https://esm.sh/jose@5.10.0/denonext/jws/compact/sign.mjs": "4f345eac0d98cf5074193e251879b67b9c6b5d4aea6648f0e19169776f4ee59f",
1753
+
"https://esm.sh/jose@5.10.0/denonext/jws/compact/verify.mjs": "25d3749082196faefe6ca4b656b2aba0a3c030e21a6b8b06355459dc203549e7",
1754
+
"https://esm.sh/jose@5.10.0/denonext/jws/flattened/sign.mjs": "c00c6af48d50c0d6d50490ec24666bcabb85b4f2cdef1216fd2eb7c1fff2959e",
1755
+
"https://esm.sh/jose@5.10.0/denonext/jws/flattened/verify.mjs": "d5fe93eb720b2ea659be49bbf0cadcea137de0024edd5c630ab058a69691fae7",
1756
+
"https://esm.sh/jose@5.10.0/denonext/jws/general/sign.mjs": "612872a9dda21c24756b95fdfd8555cd5e7a7541e996fdfa95d0f68a3545c4d8",
1757
+
"https://esm.sh/jose@5.10.0/denonext/jws/general/verify.mjs": "fc43cdd11c19eb9e8636b3710d4728a36f82480c19b2e75bfd9a9f4c31f7d029",
1758
+
"https://esm.sh/jose@5.10.0/denonext/jwt/decode.mjs": "40b1699324c4c97c95661ae286fff14c5b18dabe6d96343b04c31456ef73026a",
1759
+
"https://esm.sh/jose@5.10.0/denonext/jwt/decrypt.mjs": "acdf2a910bb295403edc53e7fb5f098e8d8c45c59cd079082745abd9b225f22e",
1760
+
"https://esm.sh/jose@5.10.0/denonext/jwt/encrypt.mjs": "3fe7a9dde4a875028bbd72828c86a1fcf82f168a8eeee69ce1662a6b2a4ea635",
1761
+
"https://esm.sh/jose@5.10.0/denonext/jwt/sign.mjs": "490e0d0f9f16eb648362686ec477694a37065fc5c7a4c64c5b1e6ddb2e281248",
1762
+
"https://esm.sh/jose@5.10.0/denonext/jwt/unsecured.mjs": "f20d4176b9b09f31460d141899d860b0d56d5dae4429f7625b1186ea0807489b",
1763
+
"https://esm.sh/jose@5.10.0/denonext/jwt/verify.mjs": "8baef7b4f3d23422fb51192d1df07326719db00f8fc601b35463fd8515c45a3e",
1764
+
"https://esm.sh/jose@5.10.0/denonext/key/export.mjs": "817e555aa3e0f87a9d4715affd72447f93e3bb0e4c8feafc16095f826759efcd",
1765
+
"https://esm.sh/jose@5.10.0/denonext/key/generate/keypair.mjs": "5722cb2dc742d9e1f9eec179288294a13a7c26b8b273b25a3e28830c7017b687",
1766
+
"https://esm.sh/jose@5.10.0/denonext/key/generate/secret.mjs": "74453b742576819a5f5245f7fc47617b5da766a60afd9751b5b1e14a099c9fff",
1767
+
"https://esm.sh/jose@5.10.0/denonext/key/import.mjs": "e488ba70e5eee01134d352c7ba00e3eb25093252c05a63e9d6e4185b2e6836eb",
1768
+
"https://esm.sh/jose@5.10.0?target=denonext": "43705c1f40e5191081bcac2851ad8265f03c23bf751f9b84260ac2874f8ce209",
1769
+
"https://esm.sh/lines-and-columns@1.2.4/denonext/lines-and-columns.mjs": "785cd1f5843f3645f9304248e4cf77987a71c6e2e7207d5b5200c9ea8ed67cdc",
1770
+
"https://esm.sh/lines-and-columns@1.2.4?target=denonext": "24df63eccd321681ded34cf71de26b252de505ac6fe86fff5f5fafb810f86441",
1771
+
"https://esm.sh/lru-cache@10.4.3/denonext/lru-cache.mjs": "ba5ee5ff9067a3a33de530b47c0ce7e81cdff92937e5519258ed422b7e23fbdb",
1772
+
"https://esm.sh/lru-cache@10.4.3?target=denonext": "e0a3f66d0dbb61c67fbec24fb29ba1b18e21dab43178cfba399481560e076382",
1773
+
"https://esm.sh/merge2@1.4.1/denonext/merge2.mjs": "b7e2fe0b629b75aabc963fd1e6635d6dcf59f5f2df42111df1ced9d3d064fcec",
1774
+
"https://esm.sh/merge2@1.4.1?target=denonext": "a1286ee0e809c60cde57dcd9357fe3979d10abf74324608c83eb5640088c5053",
1775
+
"https://esm.sh/micromatch@4.0.8/denonext/micromatch.mjs": "3663f155546be362a116c4a6bcececb00df29ed5870b308809ddb255ade2a78b",
1776
+
"https://esm.sh/micromatch@4.0.8?target=denonext": "5e66db24ea49ffe179d951391cb92d5173e4911a45a6793d189a83861dae0b2e",
1777
+
"https://esm.sh/multiformats@9.9.0/bases/base64?target=denonext": "9622b652809e84b8cbdc3834d15a306da9a9305552527668b13b1b16a6d686da",
1778
+
"https://esm.sh/multiformats@9.9.0/basics?target=denonext": "97fa53d99c2a3aa56367e5e2a34ab3d1b3dc496e4a4fe490460d2955de707361",
1779
+
"https://esm.sh/multiformats@9.9.0/cid?target=denonext": "be24427188844af21bc0b29187229c4c6e5a681b676db65b8c7fcf5277652b4b",
1780
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/base10.mjs": "07ac037675bfbbf0621e7f8fd3cfeb242d1ab0955d7e965f4f3a2daa1c369b85",
1781
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/base16.mjs": "7f0c9b5860c52b54170cbc8b058fe46eee1b81f52d0908055d38d2a63ec8b721",
1782
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/base2.mjs": "46527ded4d9b868600b2fd8902398a31226655790bd3c5f61ffa0bd8737b0698",
1783
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/base256emoji.mjs": "7c16b9576b295024837fe5d192f0b854951ac3a7c0be1be8a3c91d5f62505066",
1784
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/base32.mjs": "2c42d149c299e8b8934e51ccb284b01b113d3fe432177a55b7a781d10cfbe5b2",
1785
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/base36.mjs": "52dfe773e2d2650ed87c7c353e909a0d710ed83cb01eb553858eae4879a6664d",
1786
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/base58.mjs": "d45f93c89f6f8a05c7ddc132c99a2bc866d1de2e8475747b6722f6322482feab",
1787
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/base64.mjs": "1365d8ab96a8998be1663e7277eebc58d3207f785c9ab53533966626f153461b",
1788
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/base8.mjs": "9336d2259eb06c31fd0d1e7e5ae36e0826f39b2f73639849a403933348c7526c",
1789
+
"https://esm.sh/multiformats@9.9.0/denonext/bases/identity.mjs": "28acc5f7d4dfe7d5647e1c8d250540a50ba6397ff4f375bc41dc37fc5d5de510",
1790
+
"https://esm.sh/multiformats@9.9.0/denonext/basics.mjs": "edd1f5f7171a026940535586a6682c88f036b972a0158d10db2a5936239182ec",
1791
+
"https://esm.sh/multiformats@9.9.0/denonext/cid.mjs": "1945384d570468b0bb5bd0f394184835038d5778d701b50b6b7eb7273748d288",
1792
+
"https://esm.sh/multiformats@9.9.0/denonext/codecs/json.mjs": "a31ef601f2480daa1ed34393e91f937cef522517ca7d934b86939f8d761fe077",
1793
+
"https://esm.sh/multiformats@9.9.0/denonext/codecs/raw.mjs": "6d6b44e3bea526dd9930d61631709f45e9f10bf2e190dc19168e9b0297fb30dd",
1794
+
"https://esm.sh/multiformats@9.9.0/denonext/esm/src/bases/base.mjs": "f0057681c3f918b72d77de38d89d1f532ae4fc78b86f35db7f617c81e7ac504f",
1795
+
"https://esm.sh/multiformats@9.9.0/denonext/esm/src/bytes.mjs": "d2fa273fd87212f525dcd3863af8a3d2ca1e3ee42aca3eaee5ddd4ad6e43ba04",
1796
+
"https://esm.sh/multiformats@9.9.0/denonext/esm/src/varint.mjs": "5ea3af2ab0109f1e9f4f56fe35883f593f6b047899ada90095a5ed518d635899",
1797
+
"https://esm.sh/multiformats@9.9.0/denonext/hashes/digest.mjs": "fc07873514b182ae0897159ebdcad3dd3ceced04740aa76caf4b2e796ff6ca25",
1798
+
"https://esm.sh/multiformats@9.9.0/denonext/hashes/hasher.mjs": "44bfd064461927c2e8f161db65053d711e59e3df4b55b76bb3f9bf4c316492ad",
1799
+
"https://esm.sh/multiformats@9.9.0/denonext/hashes/identity.mjs": "43eaf0a3160c8344e2f70387d0a124f3f21363d82d3e246a7a7df4053c6199e4",
1800
+
"https://esm.sh/multiformats@9.9.0/denonext/hashes/sha2.mjs": "a0fd2e20d8753f6ca30db815bb7f20a57e2a23dd691384b11d07b5a52dbec74f",
1801
+
"https://esm.sh/multiformats@9.9.0/denonext/multiformats.mjs": "7449f492d80b1dcffcbbfb5599707e5f9c0e5a54694532f330b0f64defe27809",
1802
+
"https://esm.sh/nanoid@3.3.11/denonext/non-secure.mjs": "1f166118c1c4b4d4b5356ef0050fed87acf28cdb8a76813803fee72188f18b30",
1803
+
"https://esm.sh/nanoid@3.3.11/non-secure?target=denonext": "ac0c34cc5f9846db51a5d868ce6ee815f831a19b1d57a1b8bae9226fc8d68dec",
1804
+
"https://esm.sh/node-releases@2.0.19/data/processed/envs.json?module": "62fef61ca0cdd9c90f89cdbf60280aa1ebff036ddb965f4174e69a30e6efb196",
1805
+
"https://esm.sh/node-releases@2.0.19/data/release-schedule/release-schedule.json?module": "32b509aa9392ed0744a12a011b07eb5f86d1e0b361392699a20c1b70f631d6aa",
1806
+
"https://esm.sh/normalize-path@3.0.0/denonext/normalize-path.mjs": "ecabfab5ffc4bce022eed2b9c50a777cc04b44b17c477eeef8790d7602f4eb93",
1807
+
"https://esm.sh/normalize-path@3.0.0?target=denonext": "4fd9f901c0c9062a18ca83ede4b9122f9b3b67ce3839a86344bb8a4d98158359",
1808
+
"https://esm.sh/normalize-range@0.1.2/denonext/normalize-range.mjs": "33d8ef29dd3b379c687be93ed8df5b1a001eb0fa2b16e14aa50191377c5dc18f",
1809
+
"https://esm.sh/normalize-range@0.1.2?target=denonext": "10ac8bcf3a32c783d03b72181ab7f243aa17a1f7c8a7a87655e2573f2210a380",
1810
+
"https://esm.sh/object-hash@3.0.0/denonext/object-hash.mjs": "a0de679d08f71d0ef12da988f1be37af4d050e5ef55e8a35d90d7d3489b3e9da",
1811
+
"https://esm.sh/object-hash@3.0.0?target=denonext": "e218e75f3503f2bdd9f9b758658a59f8ce0a76c387ec49e979b5d2039373a97d",
1812
+
"https://esm.sh/picocolors@1.1.1/denonext/picocolors.mjs": "fa0033734392d8b8ad0c565338a77fa835e8b00e9ef244bf6f36570dd699ea3f",
1813
+
"https://esm.sh/picocolors@1.1.1?target=denonext": "97aaf037b1af74a20fe9f9f2beabba700924823101e86bdcd3ae2c9327376f74",
1814
+
"https://esm.sh/picomatch@2.3.1/denonext/lib/utils.mjs": "78f1e101706724181555315f3d8b8d0bb19c602caec154ae911e8a645d1b0b47",
1815
+
"https://esm.sh/picomatch@2.3.1/denonext/picomatch.mjs": "b238291b792bb898d06a490c316425f0921457a9ecb30b12909e00b47abd32ec",
1816
+
"https://esm.sh/picomatch@2.3.1/lib/utils?target=denonext": "07ebea80a4b1cbb4dc99b7ae7f6dd81321eb5e4ba254cc39fe57bd5a61082b7b",
1817
+
"https://esm.sh/picomatch@2.3.1?target=denonext": "e1e7cb8fa5f73ba0cefa1e19003d8adebe6c1dda6d934d656361df441ee5f4b0",
1818
+
"https://esm.sh/postcss-js@4.0.1/denonext/async.mjs": "90a7602a50926d3491ea00b07135429174b88b537929703352c8128626c3cd8d",
1819
+
"https://esm.sh/postcss-js@4.0.1/denonext/objectifier.mjs": "df146288008cae08140eede11a4152b8d3db59229a69b4d068a4254bd47850c3",
1820
+
"https://esm.sh/postcss-js@4.0.1/denonext/parser.mjs": "de35017f0f8fef175ebf8759539fb7d323df119dde251b09d6d1904035d9bab6",
1821
+
"https://esm.sh/postcss-js@4.0.1/denonext/postcss-js.mjs": "bc84d0914b55660b458011f5451d98d8c1d6c94096a866883443285482d6a5a7",
1822
+
"https://esm.sh/postcss-js@4.0.1/denonext/process-result.mjs": "96840377db6a0c0917035314f38181d3f13451100a33c71f3412d458c4a2a2b5",
1823
+
"https://esm.sh/postcss-js@4.0.1/denonext/sync.mjs": "abd39a2f4a9bc2b8c9c1cceeae397cf07f2d7fc016edc0c0edeb98b112bea04c",
1824
+
"https://esm.sh/postcss-js@4.0.1?target=denonext": "aba48f8bfa332370cb382dcb55fa57ba1a98952f3f8a4f74f4611b10058a7e3a",
1825
+
"https://esm.sh/postcss-nested@6.2.0/denonext/postcss-nested.mjs": "2c772eb1021a4642b60b6ce56739d8727c4f5f5f3f0598b2f25c3805a50c969d",
1826
+
"https://esm.sh/postcss-nested@6.2.0?target=denonext": "3f977ced1f70abf693275963f2dfef7491c1e434736b966bd194b3561f3a0ab6",
1827
+
"https://esm.sh/postcss-selector-parser@6.1.2/denonext/dist/util/unesc.mjs": "737a8526d9e2c2ace54928cbb4dc6205d850bd81550069faccf6f90dc1ce4d00",
1828
+
"https://esm.sh/postcss-selector-parser@6.1.2/denonext/postcss-selector-parser.mjs": "67baf457834e84c652cdc2d2073d072226ba2ae531996094a145ab99df3fb689",
1829
+
"https://esm.sh/postcss-selector-parser@6.1.2/dist/util/unesc?target=denonext": "c14466135461e10e035138f0e98fe4c514ed63c922e9a782d67cf6d568e3f9e6",
1830
+
"https://esm.sh/postcss-selector-parser@6.1.2?target=denonext": "2537850e0eef50737c135b11ecf6d71bcf2b19daea0a26464889e276ab46b3ec",
1831
+
"https://esm.sh/postcss-value-parser@4.2.0/denonext/postcss-value-parser.mjs": "2734c0eb4eb5e6a9905fb179ed2f329a346ac076735dbf0bc889276cbfe952bc",
1832
+
"https://esm.sh/postcss-value-parser@4.2.0?target=denonext": "f11ba11f7fe1a5768433011193e133bf80a697154aab65f582bf653305a571d1",
1833
+
"https://esm.sh/postcss@8.5.6": "d74fb26c8e5f2355773381813a2514b103cd99d9262961b6d43148f714f343da",
1834
+
"https://esm.sh/postcss@8.5.6/denonext/lib/at-rule.mjs": "4d36edc3ca42766858a10199b14ea4295b76ff96ea6b1a81aa29b7b22af7dd45",
1835
+
"https://esm.sh/postcss@8.5.6/denonext/lib/comment.mjs": "c697b26e5ba49d581727081fd3de61a65df93ae69f5ab1080df36bc523a3586f",
1836
+
"https://esm.sh/postcss@8.5.6/denonext/lib/container.mjs": "842ed0e58a3120a02b91bcc27e0bf8fa8e82281dc48f04f72e78add92fe215a6",
1837
+
"https://esm.sh/postcss@8.5.6/denonext/lib/css-syntax-error.mjs": "cde795b92e9c71048d87b903b9565e1ede241bc81e346cb0f9171c2c7825c6a4",
1838
+
"https://esm.sh/postcss@8.5.6/denonext/lib/declaration.mjs": "e2fa6617ff8a74fbcae38ab0d4f2ff5417bdb5c300d7695fcd662acdfa300cda",
1839
+
"https://esm.sh/postcss@8.5.6/denonext/lib/document.mjs": "39f9f35f25834ffd4cae54678faf5020eb68a9d990c1921cb1f4a16e8d96ec81",
1840
+
"https://esm.sh/postcss@8.5.6/denonext/lib/fromJSON.mjs": "9f2e849366a659250ed1bfa3c67faf2cf1c38b5b2677594002498a1ee0fc594e",
1841
+
"https://esm.sh/postcss@8.5.6/denonext/lib/input.mjs": "72c43722d17c332720bdcc5ca67925f08b7260643faf33a4328bf87532e9cd4d",
1842
+
"https://esm.sh/postcss@8.5.6/denonext/lib/lazy-result.mjs": "61618d7d7bc6f68ce8c7e380b15317e18036e07a29c02cdda521e632559e034a",
1843
+
"https://esm.sh/postcss@8.5.6/denonext/lib/list.mjs": "4a56db4aea2f2a981afdce9455086caea47e732c678d75b85cdb76df241314e0",
1844
+
"https://esm.sh/postcss@8.5.6/denonext/lib/map-generator.mjs": "d609c0e2114a55cc61969344edec0c8652fdabec840bdd481e133ab04f817866",
1845
+
"https://esm.sh/postcss@8.5.6/denonext/lib/no-work-result.mjs": "582acccf822251d8d664518339e62dd8fc7105bbb4488169ffe7cf5ef99fd392",
1846
+
"https://esm.sh/postcss@8.5.6/denonext/lib/node.mjs": "9cda4baedeafe756b6a10e76e906720bce6da9f9b808c74fb247d85ae6b96965",
1847
+
"https://esm.sh/postcss@8.5.6/denonext/lib/parse.mjs": "9095142e0c755897446b8fd8f9ac8633599e3ba44773498206cdcd36538bfa0a",
1848
+
"https://esm.sh/postcss@8.5.6/denonext/lib/parser.mjs": "5fa9d73cc19247b2abb636ff39973fdb4d06149f183dc4670aa8106594b3ae36",
1849
+
"https://esm.sh/postcss@8.5.6/denonext/lib/previous-map.mjs": "18e4af2bd986f9211a4961ca81e6b6d8f1ec10adbf6df20a0cbcd6eb8c696efa",
1850
+
"https://esm.sh/postcss@8.5.6/denonext/lib/processor.mjs": "bef379a8c94cdb173337b51bde0a5576db39262bf7a759c3650b4471e1fa1c96",
1851
+
"https://esm.sh/postcss@8.5.6/denonext/lib/result.mjs": "c1ba7aafbb6c66f9a213beb7fcd9bbc5c9bf8cf96c8d9d52af9ae6b6a4436b51",
1852
+
"https://esm.sh/postcss@8.5.6/denonext/lib/root.mjs": "b4fdc0d0380774f92382e32a39f2c0901e111c58d0309084e728ae085570fa72",
1853
+
"https://esm.sh/postcss@8.5.6/denonext/lib/rule.mjs": "35cb6df8a6b88a25c2cdae3c67153e1e322f0c5b2ff0af2a4b82621801818aa9",
1854
+
"https://esm.sh/postcss@8.5.6/denonext/lib/stringifier.mjs": "f7d92b74abe94e471c03e4be188b0d63f1ef59b6124be4ce1c7e6673940f8a01",
1855
+
"https://esm.sh/postcss@8.5.6/denonext/lib/stringify.mjs": "0199bda16f538996aacee2bc24d6425960706407bb966700c5e90e4ea6a18144",
1856
+
"https://esm.sh/postcss@8.5.6/denonext/lib/symbols.mjs": "62ba62162b364f32caa44d4025486db497a84f1956b40047b7cc3d0ca336103b",
1857
+
"https://esm.sh/postcss@8.5.6/denonext/lib/terminal-highlight.mjs": "261e50c0a1061a389cdd0abc7177a411f63916657a2d2493e42d2eabf8ca66e2",
1858
+
"https://esm.sh/postcss@8.5.6/denonext/lib/tokenize.mjs": "8ae691fe6c8535964103e6cbb4be154916c96a80f22746daf6c595b6186b92fa",
1859
+
"https://esm.sh/postcss@8.5.6/denonext/lib/warn-once.mjs": "29cca6093c98344a9731ad66d1935501bebfd35e11cc16c47b12ebdbb2835500",
1860
+
"https://esm.sh/postcss@8.5.6/denonext/lib/warning.mjs": "71973428e45464675ca11b47c2e463ea00f88ce53aa12335fce71f22b81b1002",
1861
+
"https://esm.sh/postcss@8.5.6/denonext/postcss.mjs": "95c0fe458dc732c2d727eb665f410662deba1982e63d7ae31eb09475e77dd0a4",
1862
+
"https://esm.sh/postcss@8.5.6?target=denonext": "d74fb26c8e5f2355773381813a2514b103cd99d9262961b6d43148f714f343da",
1863
+
"https://esm.sh/queue-microtask@1.2.3/denonext/queue-microtask.mjs": "b0e153a240d836527feea7b9ecfa99fa8e5a5ef7e48b43092b7cecd8649a8712",
1864
+
"https://esm.sh/queue-microtask@1.2.3?target=denonext": "fc6002fc339a8edbab36e4e6df1d245a0c3a5dc507edf4e5fb7e857f8b117aa2",
1865
+
"https://esm.sh/react-dom@19.1.1/client": "e610fc905fb8a0f45ef487496e6084a9c46e6a9b1b1fc9e77ae388d8a9489a9f",
1866
+
"https://esm.sh/react-dom@19.1.1/denonext/cjs/react-dom-server-legacy.browser.production.mjs": "46093e4d958c744635246158bbe9d1448c1168acc16ee248a1cb744c0194cc72",
1867
+
"https://esm.sh/react-dom@19.1.1/denonext/cjs/react-dom-server.browser.production.mjs": "1b65edfd064bcb0377f63e6d3d15176967ead3acd6b3aec869bd194e48522676",
1868
+
"https://esm.sh/react-dom@19.1.1/denonext/client.mjs": "747fb2c2a65ba2fc0e191c6be104de92f26e7b3559425fd2bebc71decf9358c9",
1869
+
"https://esm.sh/react-dom@19.1.1/denonext/react-dom.mjs": "a31239e9832b73257ff344fb3d8292982ee25cbc1da5edab9ba81648c30f4abd",
1870
+
"https://esm.sh/react-dom@19.1.1/denonext/server.mjs": "513c3f84f3402259c315ae6788c2dd9e671e8dc2adfa2f37d51165e156b4932f",
1871
+
"https://esm.sh/react-dom@19.1.1/server": "d8d537b44c19a05a7805396eac77f18c628e1019fb2a61cb1f1a1bce0eeeb939",
1872
+
"https://esm.sh/react@19.1.1": "4583aedf2a721df5ea4aa7308de82b5bcbee12e5ed7910061940f7434982847d",
1873
+
"https://esm.sh/react@19.1.1/denonext/react.mjs": "588ff2562af9e974efdb79cc945049768bf5ee0252543fd8fb6a48472f3db587",
1874
+
"https://esm.sh/reusify@1.1.0/denonext/reusify.mjs": "3b91b42988a3ec3984cef34b99cd5f67b4a008609c3d4fc5d35e8f2d1aab8cef",
1875
+
"https://esm.sh/reusify@1.1.0?target=denonext": "aea1fd894bd8993f740cf053799f9639ee649122854c1fe4a5425d5aa2dc5e96",
1876
+
"https://esm.sh/run-parallel@1.2.0/denonext/run-parallel.mjs": "77808968c9de972bb725e73559c80e64bf9524b7d2f781941115d023fd42e7b3",
1877
+
"https://esm.sh/run-parallel@1.2.0?target=denonext": "2acc47cfa930a6c5e502877b2e5ee9cd2a2ec81e4fcc579f3c0bdebe32590de6",
1878
+
"https://esm.sh/scheduler@0.26.0/denonext/scheduler.mjs": "b62267951094a7c42b7170c51e31f57198981b08ebfae60be0d076101a4c6f91",
1879
+
"https://esm.sh/scheduler@0.26.0?target=denonext": "102f3b685ea5271452bd93de89ab0d99668aecb08f7706e3b9c6629699dd92e0",
1880
+
"https://esm.sh/source-map-js@1.2.1/denonext/source-map-js.mjs": "2129223e17c258b391a47907a69c0b86d1c87cbb66eea7e9b25ff555ec0bbc7c",
1881
+
"https://esm.sh/source-map-js@1.2.1?target=denonext": "33ba5cf2ee7c1fe6b63b36c31e9075e290bc5c574055acbdc48c2f24a21d2f83",
1882
+
"https://esm.sh/sucrase@3.35.0/denonext/sucrase.mjs": "1d1c05878a5f0656226ee064c90845d01adf23b2bff84521480ffaad6da16d21",
1883
+
"https://esm.sh/sucrase@3.35.0?target=denonext": "2e4c449f97b0c91e2800bdb6a1192f46dfaba56fa048e0c50eb8d150bd513195",
1884
+
"https://esm.sh/tailwindcss@3.4.17": "0d5c6546923ca69e2d2a1cec444214f239d2df1e5f2b90708b76043809b84c12",
1885
+
"https://esm.sh/tailwindcss@3.4.17/denonext/tailwindcss.mjs": "382a6a4ed82273daead6e2eb6af426822a3fbf7303387ee49db16be7699f8b45",
1886
+
"https://esm.sh/tailwindcss@4.1.12/denonext/plugin.mjs": "349369852f7afd8ca09ef1682d94766d02e84bc45f927d0f02b374b20be70c9f",
1887
+
"https://esm.sh/tailwindcss@4.1.12/plugin?target=denonext": "05b784ebe25c01018a0eab3fe1e1dab62bdbceb114e2371e748f967552510c00",
1888
+
"https://esm.sh/tlds@1.259.0/denonext/tlds.mjs": "574135861b248c619a791bd6a1769ee6b5b45d1d7e11f8d052c8e0e43ae6e009",
1889
+
"https://esm.sh/tlds@1.259.0?target=denonext": "9a5c7ee3354f90209f3fee0255a201ec5c9f46d28dc500ac161325c945af7389",
1890
+
"https://esm.sh/to-regex-range@5.0.1/denonext/to-regex-range.mjs": "3f099b66f908b58dd6ee20afd1fed4616ea02fd0c118dc91ff1ed3e72d795bc7",
1891
+
"https://esm.sh/to-regex-range@5.0.1?target=denonext": "5d6ed8b93f9fc4009378f84ade74e2681c2264b37e79f978c557037c612086ed",
1892
+
"https://esm.sh/ts-interface-checker@0.1.13/denonext/ts-interface-checker.mjs": "482a9d9214862af127455b80dbe15559b107bb8283dc4b6ba10da16757b3dfe8",
1893
+
"https://esm.sh/ts-interface-checker@0.1.13?target=denonext": "b1c4bedafbacf4fc6ca1b02c22601fe4e3dee3d3dc14ca3e8fb6f8656a368e72",
1894
+
"https://esm.sh/uint8arrays@3.0.0/denonext/compare.mjs": "2b8ed67b92836546e504b87733eacd3f0569050ea645920d3f2503e8e335cd01",
1895
+
"https://esm.sh/uint8arrays@3.0.0/denonext/concat.mjs": "ce3cfbcdd3cc6d1d9fa75f13963c7398a377d8a1dbd6302820f2058f42545fc7",
1896
+
"https://esm.sh/uint8arrays@3.0.0/denonext/equals.mjs": "2c9a9504f97abc172b755fb5069cb9b69aac2e177ad4bc2cf9afdd6a4a322694",
1897
+
"https://esm.sh/uint8arrays@3.0.0/denonext/esm/src/util/bases.mjs": "03de06f47b410a6ed09da73d6e58696ca34ac4e51bce4a0bcfac5cf88c23b8da",
1898
+
"https://esm.sh/uint8arrays@3.0.0/denonext/from-string.mjs": "6df06b3ed43db82fa33500d4ab01a97ae4ecef234f76e06c1774b915d913bbe3",
1899
+
"https://esm.sh/uint8arrays@3.0.0/denonext/to-string.mjs": "bafce61afad1706118c9c6bd08d6d2f53dada2c0892b8fe14da8124e5951390c",
1900
+
"https://esm.sh/uint8arrays@3.0.0/denonext/uint8arrays.mjs": "2627bbf05b4b496ffe067a4f090dd91d4aa7bc8c815d36f1e543531fb0d9342f",
1901
+
"https://esm.sh/uint8arrays@3.0.0/denonext/xor.mjs": "cc93f46198dca299adc26ae5344768914923bbc88e78eefd28699d0ec28c8e71",
1902
+
"https://esm.sh/util-deprecate@1.0.2/denonext/util-deprecate.mjs": "083639894972cb68837eef26346c43bdd01357977149e0a4493f76192a4008b8",
1903
+
"https://esm.sh/util-deprecate@1.0.2?target=denonext": "859f4df8ba771a4c33143185d3db6a7edb824fab1ed4f9a4b96ac0e6bc3ef1a4",
1904
+
"https://esm.sh/zod@3.25.76/denonext/zod.mjs": "9f643d0cc560840b7fa3b6e6fc3a5a41595f12ff8863cac0ccbae3ec98989284",
1905
+
"https://esm.sh/zod@3.25.76?target=denonext": "7d2e5b7450d6f99b9b3b228958bd1291d5a1f8378f82b8cdd2ce83b2f14961c2"
1163
1906
},
1164
1907
"workspace": {
1165
1908
"dependencies": [
docs/.nojekyll
docs/.nojekyll
This is a binary file and will not be displayed.
+18
docs/README.md
+18
docs/README.md
···
1
+
# Skylite
2
+
3
+
> An attempt at splitting the (Bluesky) AppView
4
+
5
+
- Uses [Microcosm](https://microcosm.blue/)
6
+
- Portable with [SQLite](https://jsr.io/@db/sqlite) and [Deno](https://deno.land/)
7
+
- Built with [type safety](https://www.npmjs.com/package/@atproto/lex-cli)
8
+
9
+

10
+
11
+
read more about the structure of Skylite in [structure.md](structure.md)
12
+
13
+
# Skylite Dev Docs
14
+
skylite dev docs is a documentation website to host all of the notes for developing skylite
15
+
16
+
all lexicons are listed in the [Lexicons](httpindex) section
17
+
18
+
more notes in [Dev Notes](notesindex) section
+11
docs/_coverpage.md
+11
docs/_coverpage.md
···
1
+
<!--  -->
2
+
3
+
# Skylite Dev Docs <small>pre alpha</small>
4
+
5
+
> Documentation for Designing and Developing Skylite
6
+
7
+
docs are still being written!
8
+
9
+
[Git Repo](https://tangled.sh/@whey.party/skylite)
10
+
[Dev Notes](notesindex.md)
11
+
[Lexicons](httpindex.md)
+35
docs/app.bsky.actor.getProfile.md
+35
docs/app.bsky.actor.getProfile.md
···
1
+
## app.bsky.actor.getProfile
2
+
3
+
```js
4
+
{
5
+
"lexicon": 1,
6
+
"id": "app.bsky.actor.getProfile",
7
+
"defs": {
8
+
"main": {
9
+
"type": "query",
10
+
"description": "Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.",
11
+
"parameters": {
12
+
"type": "params",
13
+
"required": [
14
+
"actor"
15
+
],
16
+
"properties": {
17
+
"actor": {
18
+
"type": "string",
19
+
"format": "at-identifier",
20
+
"description": "Handle or DID of account to fetch profile of."
21
+
}
22
+
}
23
+
},
24
+
"output": {
25
+
"encoding": "application/json",
26
+
"schema": {
27
+
"type": "ref",
28
+
"ref": "app.bsky.actor.defs#profileViewDetailed"
29
+
}
30
+
}
31
+
}
32
+
}
33
+
}
34
+
```
35
+
---
+50
docs/app.bsky.actor.getProfiles.md
+50
docs/app.bsky.actor.getProfiles.md
···
1
+
2
+
## app.bsky.actor.getProfiles
3
+
4
+
```js
5
+
{
6
+
"lexicon": 1,
7
+
"id": "app.bsky.actor.getProfiles",
8
+
"defs": {
9
+
"main": {
10
+
"type": "query",
11
+
"description": "Get detailed profile views of multiple actors.",
12
+
"parameters": {
13
+
"type": "params",
14
+
"required": [
15
+
"actors"
16
+
],
17
+
"properties": {
18
+
"actors": {
19
+
"type": "array",
20
+
"items": {
21
+
"type": "string",
22
+
"format": "at-identifier"
23
+
},
24
+
"maxLength": 25
25
+
}
26
+
}
27
+
},
28
+
"output": {
29
+
"encoding": "application/json",
30
+
"schema": {
31
+
"type": "object",
32
+
"required": [
33
+
"profiles"
34
+
],
35
+
"properties": {
36
+
"profiles": {
37
+
"type": "array",
38
+
"items": {
39
+
"type": "ref",
40
+
"ref": "app.bsky.actor.defs#profileViewDetailed"
41
+
}
42
+
}
43
+
}
44
+
}
45
+
}
46
+
}
47
+
}
48
+
}
49
+
```
50
+
---
+57
docs/app.bsky.feed.getActorFeeds.md
+57
docs/app.bsky.feed.getActorFeeds.md
···
1
+
## app.bsky.feed.getActorFeeds
2
+
3
+
```js
4
+
{
5
+
"lexicon": 1,
6
+
"id": "app.bsky.feed.getActorFeeds",
7
+
"defs": {
8
+
"main": {
9
+
"type": "query",
10
+
"description": "Get a list of feeds (feed generator records) created by the actor (in the actor's repo).",
11
+
"parameters": {
12
+
"type": "params",
13
+
"required": [
14
+
"actor"
15
+
],
16
+
"properties": {
17
+
"actor": {
18
+
"type": "string",
19
+
"format": "at-identifier"
20
+
},
21
+
"limit": {
22
+
"type": "integer",
23
+
"minimum": 1,
24
+
"maximum": 100,
25
+
"default": 50
26
+
},
27
+
"cursor": {
28
+
"type": "string"
29
+
}
30
+
}
31
+
},
32
+
"output": {
33
+
"encoding": "application/json",
34
+
"schema": {
35
+
"type": "object",
36
+
"required": [
37
+
"feeds"
38
+
],
39
+
"properties": {
40
+
"cursor": {
41
+
"type": "string"
42
+
},
43
+
"feeds": {
44
+
"type": "array",
45
+
"items": {
46
+
"type": "ref",
47
+
"ref": "app.bsky.feed.defs#generatorView"
48
+
}
49
+
}
50
+
}
51
+
}
52
+
}
53
+
}
54
+
}
55
+
}
56
+
```
57
+
---
+54
docs/app.bsky.feed.getFeedGenerator.md
+54
docs/app.bsky.feed.getFeedGenerator.md
···
1
+
2
+
## app.bsky.feed.getFeedGenerator
3
+
4
+
```js
5
+
{
6
+
"lexicon": 1,
7
+
"id": "app.bsky.feed.getFeedGenerator",
8
+
"defs": {
9
+
"main": {
10
+
"type": "query",
11
+
"description": "Get information about a feed generator. Implemented by AppView.",
12
+
"parameters": {
13
+
"type": "params",
14
+
"required": [
15
+
"feed"
16
+
],
17
+
"properties": {
18
+
"feed": {
19
+
"type": "string",
20
+
"format": "at-uri",
21
+
"description": "AT-URI of the feed generator record."
22
+
}
23
+
}
24
+
},
25
+
"output": {
26
+
"encoding": "application/json",
27
+
"schema": {
28
+
"type": "object",
29
+
"required": [
30
+
"view",
31
+
"isOnline",
32
+
"isValid"
33
+
],
34
+
"properties": {
35
+
"view": {
36
+
"type": "ref",
37
+
"ref": "app.bsky.feed.defs#generatorView"
38
+
},
39
+
"isOnline": {
40
+
"type": "boolean",
41
+
"description": "Indicates whether the feed generator service has been online recently, or else seems to be inactive."
42
+
},
43
+
"isValid": {
44
+
"type": "boolean",
45
+
"description": "Indicates whether the feed generator service is compatible with the record declaration."
46
+
}
47
+
}
48
+
}
49
+
}
50
+
}
51
+
}
52
+
}
53
+
```
54
+
---
+50
docs/app.bsky.feed.getFeedGenerators.md
+50
docs/app.bsky.feed.getFeedGenerators.md
···
1
+
2
+
3
+
## app.bsky.feed.getFeedGenerators
4
+
5
+
```js
6
+
{
7
+
"lexicon": 1,
8
+
"id": "app.bsky.feed.getFeedGenerators",
9
+
"defs": {
10
+
"main": {
11
+
"type": "query",
12
+
"description": "Get information about a list of feed generators.",
13
+
"parameters": {
14
+
"type": "params",
15
+
"required": [
16
+
"feeds"
17
+
],
18
+
"properties": {
19
+
"feeds": {
20
+
"type": "array",
21
+
"items": {
22
+
"type": "string",
23
+
"format": "at-uri"
24
+
}
25
+
}
26
+
}
27
+
},
28
+
"output": {
29
+
"encoding": "application/json",
30
+
"schema": {
31
+
"type": "object",
32
+
"required": [
33
+
"feeds"
34
+
],
35
+
"properties": {
36
+
"feeds": {
37
+
"type": "array",
38
+
"items": {
39
+
"type": "ref",
40
+
"ref": "app.bsky.feed.defs#generatorView"
41
+
}
42
+
}
43
+
}
44
+
}
45
+
}
46
+
}
47
+
}
48
+
}
49
+
```
50
+
---
+52
docs/app.bsky.feed.getPosts.md
+52
docs/app.bsky.feed.getPosts.md
···
1
+
2
+
3
+
## app.bsky.feed.getPosts
4
+
5
+
```js
6
+
{
7
+
"lexicon": 1,
8
+
"id": "app.bsky.feed.getPosts",
9
+
"defs": {
10
+
"main": {
11
+
"type": "query",
12
+
"description": "Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'.",
13
+
"parameters": {
14
+
"type": "params",
15
+
"required": [
16
+
"uris"
17
+
],
18
+
"properties": {
19
+
"uris": {
20
+
"type": "array",
21
+
"description": "List of post AT-URIs to return hydrated views for.",
22
+
"items": {
23
+
"type": "string",
24
+
"format": "at-uri"
25
+
},
26
+
"maxLength": 25
27
+
}
28
+
}
29
+
},
30
+
"output": {
31
+
"encoding": "application/json",
32
+
"schema": {
33
+
"type": "object",
34
+
"required": [
35
+
"posts"
36
+
],
37
+
"properties": {
38
+
"posts": {
39
+
"type": "array",
40
+
"items": {
41
+
"type": "ref",
42
+
"ref": "app.bsky.feed.defs#postView"
43
+
}
44
+
}
45
+
}
46
+
}
47
+
}
48
+
}
49
+
}
50
+
}
51
+
```
52
+
---
docs/assets/indexapistatus.png
docs/assets/indexapistatus.png
This is a binary file and will not be displayed.
docs/assets/tldrawgraph.png
docs/assets/tldrawgraph.png
This is a binary file and will not be displayed.
docs/assets/viewapistatus.png
docs/assets/viewapistatus.png
This is a binary file and will not be displayed.
+191
docs/customdefs.md
+191
docs/customdefs.md
···
1
+
## party.whey.app.bsky.actor.defs
2
+
3
+
```js
4
+
{
5
+
"lexicon": 1,
6
+
"id": "party.whey.app.bsky.actor.defs",
7
+
"defs": {
8
+
"profileViewBasicRef": {
9
+
"type": "object",
10
+
"required": [
11
+
"did"
12
+
],
13
+
"properties": {
14
+
"did": {
15
+
"type": "string",
16
+
"format": "did"
17
+
}
18
+
}
19
+
},
20
+
"profileViewRef": {
21
+
"type": "object",
22
+
"required": [
23
+
"did"
24
+
],
25
+
"properties": {
26
+
"did": {
27
+
"type": "string",
28
+
"format": "did"
29
+
}
30
+
}
31
+
},
32
+
"profileViewDetailedRef": {
33
+
"type": "object",
34
+
"required": [
35
+
"did"
36
+
],
37
+
"properties": {
38
+
"did": {
39
+
"type": "string",
40
+
"format": "did"
41
+
}
42
+
}
43
+
}
44
+
}
45
+
}
46
+
```
47
+
---
48
+
49
+
## party.whey.app.bsky.feed.defs
50
+
51
+
```js
52
+
{
53
+
"lexicon": 1,
54
+
"id": "party.whey.app.bsky.feed.defs",
55
+
"defs": {
56
+
"postViewRef": {
57
+
"type": "object",
58
+
"description": "A pointer to a app.bsky.feed.defs#postView",
59
+
"required": [
60
+
"uri",
61
+
"cid"
62
+
],
63
+
"properties": {
64
+
"uri": {
65
+
"type": "string",
66
+
"format": "at-uri"
67
+
},
68
+
"cid": {
69
+
"type": "string",
70
+
"format": "cid"
71
+
}
72
+
}
73
+
},
74
+
"feedViewPostRef": {
75
+
"type": "object",
76
+
"required": [
77
+
"post"
78
+
],
79
+
"properties": {
80
+
"post": {
81
+
"type": "union",
82
+
"refs": [
83
+
"app.bsky.feed.defs#postView",
84
+
"#postViewRef"
85
+
]
86
+
},
87
+
"reply": {
88
+
"type": "union",
89
+
"refs": [
90
+
"app.bsky.feed.defs#replyRef",
91
+
"#replyRef"
92
+
]
93
+
},
94
+
"reason": {
95
+
"type": "union",
96
+
"refs": [
97
+
"app.bsky.feed.defs#reasonRepost",
98
+
"app.bsky.feed.defs#reasonPin"
99
+
]
100
+
},
101
+
"feedContext": {
102
+
"type": "string",
103
+
"description": "Context provided by feed generator that may be passed back alongside interactions.",
104
+
"maxLength": 2000
105
+
},
106
+
"reqId": {
107
+
"type": "string",
108
+
"description": "Unique identifier per request that may be passed back alongside interactions.",
109
+
"maxLength": 100
110
+
}
111
+
}
112
+
},
113
+
"replyRef": {
114
+
"type": "object",
115
+
"required": [
116
+
"root",
117
+
"parent"
118
+
],
119
+
"properties": {
120
+
"root": {
121
+
"type": "union",
122
+
"refs": [
123
+
"#postViewRef",
124
+
"app.bsky.feed.defs#postView",
125
+
"app.bsky.feed.defs#notFoundPost",
126
+
"app.bsky.feed.defs#blockedPost"
127
+
]
128
+
},
129
+
"parent": {
130
+
"type": "union",
131
+
"refs": [
132
+
"#postViewRef",
133
+
"app.bsky.feed.defs#postView",
134
+
"app.bsky.feed.defs#notFoundPost",
135
+
"app.bsky.feed.defs#blockedPost"
136
+
]
137
+
},
138
+
"grandparentAuthor": {
139
+
"type": "union",
140
+
"refs": [
141
+
"party.whey.app.bsky.actor.defs#profileViewBasicRef",
142
+
"app.bsky.actor.defs#profileViewBasic"
143
+
],
144
+
"description": "When parent is a reply to another post, this is the author of that post."
145
+
}
146
+
}
147
+
},
148
+
"threadViewPostRef": {
149
+
"type": "object",
150
+
"required": [
151
+
"post"
152
+
],
153
+
"properties": {
154
+
"post": {
155
+
"type": "union",
156
+
"refs": [
157
+
"#postViewRef",
158
+
"app.bsky.feed.defs#postView"
159
+
]
160
+
},
161
+
"parent": {
162
+
"type": "union",
163
+
"refs": [
164
+
"#threadViewPostRef",
165
+
"app.bsky.feed.defs#threadViewPost",
166
+
"app.bsky.feed.defs#notFoundPost",
167
+
"app.bsky.feed.defs#blockedPost"
168
+
]
169
+
},
170
+
"replies": {
171
+
"type": "array",
172
+
"items": {
173
+
"type": "union",
174
+
"refs": [
175
+
"#threadViewPostRef",
176
+
"app.bsky.feed.defs#threadViewPost",
177
+
"app.bsky.feed.defs#notFoundPost",
178
+
"app.bsky.feed.defs#blockedPost"
179
+
]
180
+
}
181
+
},
182
+
"threadContext": {
183
+
"type": "ref",
184
+
"ref": "app.bsky.feed.defs#threadContext"
185
+
}
186
+
}
187
+
}
188
+
}
189
+
}
190
+
```
191
+
---
+21
docs/httpindex.md
+21
docs/httpindex.md
···
1
+
# Lexicons
2
+
- [Why?](whylexicons.md)
3
+
- [app.bsky.actor.getProfile](app.bsky.actor.getProfile.md)
4
+
- [app.bsky.actor.getProfiles](app.bsky.actor.getProfiles.md)
5
+
- [app.bsky.feed.getActorFeeds](app.bsky.feed.getActorFeeds.md)
6
+
- [app.bsky.feed.getFeedGenerator](app.bsky.feed.getFeedGenerator.md)
7
+
- [app.bsky.feed.getFeedGenerators](app.bsky.feed.getFeedGenerators.md)
8
+
- [app.bsky.feed.getPosts](app.bsky.feed.getPosts.md)
9
+
- [custom defs](customdefs.md)
10
+
- [party.whey.app.bsky.feed.getActorLikesPartial](party.whey.app.bsky.feed.getActorLikesPartial.md)
11
+
- [party.whey.app.bsky.feed.getAuthorFeedPartial](party.whey.app.bsky.feed.getAuthorFeedPartial.md)
12
+
- [party.whey.app.bsky.feed.getLikesPartial](party.whey.app.bsky.feed.getLikesPartial.md)
13
+
- [party.whey.app.bsky.feed.getPostThreadPartial](party.whey.app.bsky.feed.getPostThreadPartial.md)
14
+
- [party.whey.app.bsky.feed.getQuotesPartial](party.whey.app.bsky.feed.getQuotesPartial.md)
15
+
- [party.whey.app.bsky.feed.getRepostedByPartial](party.whey.app.bsky.feed.getRepostedByPartial.md)
16
+
<!-- idk about these -->
17
+
- [party.whey.app.bsky.feed.getListFeedPartial](party.whey.app.bsky.feed.getListFeedPartial.md)
18
+
<!-- not implemented yet -->
19
+
- [app.bsky.graph.getLists](app.bsky.graph.getLists.md)
20
+
- [app.bsky.graph.getList](app.bsky.graph.getList.md)
21
+
- [app.bsky.graph.getActorStarterPacks](app.bsky.graph.getActorStarterPacks.md)
+48
docs/index.html
+48
docs/index.html
···
1
+
<!DOCTYPE html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8" />
5
+
<title>Document</title>
6
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
7
+
<meta name="description" content="Description" />
8
+
<meta
9
+
name="viewport"
10
+
content="width=device-width, initial-scale=1.0, minimum-scale=1.0"
11
+
/>
12
+
<link rel="stylesheet" media="(prefers-color-scheme: light)" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css">
13
+
<link rel="stylesheet" media="(prefers-color-scheme: dark)" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple-dark.css">
14
+
<style>
15
+
.cover.show{
16
+
max-height: 50dvh;
17
+
height: 50dvh;
18
+
min-height: 400px;
19
+
}
20
+
.app-nav{
21
+
position: fixed;
22
+
}
23
+
section.show+main>aside{
24
+
display: none;
25
+
}
26
+
section.show+main>.sidebar-toggle{
27
+
display: none;
28
+
}
29
+
section.show+main>.content{
30
+
margin-left: 0;
31
+
}
32
+
</style>
33
+
</head>
34
+
<body>
35
+
<div id="app"></div>
36
+
<script>
37
+
window.$docsify = {
38
+
name: "Skylite Dev Docs",
39
+
//repo: "https://tangled.sh/@whey.party/skylite",
40
+
loadSidebar: true,
41
+
loadNavbar: true,
42
+
coverpage: true,
43
+
};
44
+
</script>
45
+
<!-- Docsify v4 -->
46
+
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
47
+
</body>
48
+
</html>
+1
docs/indexing.md
+1
docs/indexing.md
···
1
+
# Indexing
+6
docs/notesindex.md
+6
docs/notesindex.md
+68
docs/party.whey.app.bsky.feed.getActorLikesPartial.md
+68
docs/party.whey.app.bsky.feed.getActorLikesPartial.md
···
1
+
## party.whey.app.bsky.feed.getActorLikesPartial
2
+
3
+
```js
4
+
{
5
+
"lexicon": 1,
6
+
"id": "party.whey.app.bsky.feed.getActorLikesPartial",
7
+
"defs": {
8
+
"main": {
9
+
"type": "query",
10
+
"description": "Get a list of posts liked by an actor. May require auth, (if so, actor must be the requesting account).",
11
+
"parameters": {
12
+
"type": "params",
13
+
"required": [
14
+
"actor"
15
+
],
16
+
"properties": {
17
+
"actor": {
18
+
"type": "string",
19
+
"format": "at-identifier"
20
+
},
21
+
"limit": {
22
+
"type": "integer",
23
+
"minimum": 1,
24
+
"maximum": 100,
25
+
"default": 50
26
+
},
27
+
"cursor": {
28
+
"type": "string"
29
+
}
30
+
}
31
+
},
32
+
"output": {
33
+
"encoding": "application/json",
34
+
"schema": {
35
+
"type": "object",
36
+
"required": [
37
+
"feed"
38
+
],
39
+
"properties": {
40
+
"cursor": {
41
+
"type": "string"
42
+
},
43
+
"feed": {
44
+
"type": "array",
45
+
"items": {
46
+
"type": "union",
47
+
"refs": [
48
+
"party.whey.app.bsky.feed.defs#feedViewPostRef",
49
+
"app.bsky.feed.defs#feedViewPost"
50
+
]
51
+
}
52
+
}
53
+
}
54
+
}
55
+
},
56
+
"errors": [
57
+
{
58
+
"name": "BlockedActor"
59
+
},
60
+
{
61
+
"name": "BlockedByActor"
62
+
}
63
+
]
64
+
}
65
+
}
66
+
}
67
+
```
68
+
---
+85
docs/party.whey.app.bsky.feed.getAuthorFeedPartial.md
+85
docs/party.whey.app.bsky.feed.getAuthorFeedPartial.md
···
1
+
2
+
## party.whey.app.bsky.feed.getAuthorFeedPartial
3
+
4
+
```js
5
+
{
6
+
"lexicon": 1,
7
+
"id": "party.whey.app.bsky.feed.getAuthorFeedPartial",
8
+
"defs": {
9
+
"main": {
10
+
"type": "query",
11
+
"description": "Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth.",
12
+
"parameters": {
13
+
"type": "params",
14
+
"required": [
15
+
"actor"
16
+
],
17
+
"properties": {
18
+
"actor": {
19
+
"type": "string",
20
+
"format": "at-identifier"
21
+
},
22
+
"limit": {
23
+
"type": "integer",
24
+
"minimum": 1,
25
+
"maximum": 100,
26
+
"default": 50
27
+
},
28
+
"cursor": {
29
+
"type": "string"
30
+
},
31
+
"filter": {
32
+
"type": "string",
33
+
"description": "Combinations of post/repost types to include in response.",
34
+
"knownValues": [
35
+
"posts_with_replies",
36
+
"posts_no_replies",
37
+
"posts_with_media",
38
+
"posts_and_author_threads",
39
+
"posts_with_video"
40
+
],
41
+
"default": "posts_with_replies"
42
+
},
43
+
"includePins": {
44
+
"type": "boolean",
45
+
"default": false
46
+
}
47
+
}
48
+
},
49
+
"output": {
50
+
"encoding": "application/json",
51
+
"schema": {
52
+
"type": "object",
53
+
"required": [
54
+
"feed"
55
+
],
56
+
"properties": {
57
+
"cursor": {
58
+
"type": "string"
59
+
},
60
+
"feed": {
61
+
"type": "array",
62
+
"items": {
63
+
"type": "union",
64
+
"refs": [
65
+
"party.whey.app.bsky.feed.defs#feedViewPostRef",
66
+
"app.bsky.feed.defs#feedViewPost"
67
+
]
68
+
}
69
+
}
70
+
}
71
+
}
72
+
},
73
+
"errors": [
74
+
{
75
+
"name": "BlockedActor"
76
+
},
77
+
{
78
+
"name": "BlockedByActor"
79
+
}
80
+
]
81
+
}
82
+
}
83
+
}
84
+
```
85
+
---
+95
docs/party.whey.app.bsky.feed.getLikesPartial.md
+95
docs/party.whey.app.bsky.feed.getLikesPartial.md
···
1
+
2
+
## party.whey.app.bsky.feed.getLikesPartial
3
+
4
+
```js
5
+
{
6
+
"lexicon": 1,
7
+
"id": "party.whey.app.bsky.feed.getLikesPartial",
8
+
"defs": {
9
+
"main": {
10
+
"type": "query",
11
+
"description": "Get like records which reference a subject (by AT-URI and CID).",
12
+
"parameters": {
13
+
"type": "params",
14
+
"required": [
15
+
"uri"
16
+
],
17
+
"properties": {
18
+
"uri": {
19
+
"type": "string",
20
+
"format": "at-uri",
21
+
"description": "AT-URI of the subject (eg, a post record)."
22
+
},
23
+
"cid": {
24
+
"type": "string",
25
+
"format": "cid",
26
+
"description": "CID of the subject record (aka, specific version of record), to filter likes."
27
+
},
28
+
"limit": {
29
+
"type": "integer",
30
+
"minimum": 1,
31
+
"maximum": 100,
32
+
"default": 50
33
+
},
34
+
"cursor": {
35
+
"type": "string"
36
+
}
37
+
}
38
+
},
39
+
"output": {
40
+
"encoding": "application/json",
41
+
"schema": {
42
+
"type": "object",
43
+
"required": [
44
+
"uri",
45
+
"likes"
46
+
],
47
+
"properties": {
48
+
"uri": {
49
+
"type": "string",
50
+
"format": "at-uri"
51
+
},
52
+
"cid": {
53
+
"type": "string",
54
+
"format": "cid"
55
+
},
56
+
"cursor": {
57
+
"type": "string"
58
+
},
59
+
"likes": {
60
+
"type": "array",
61
+
"items": {
62
+
"type": "ref",
63
+
"ref": "#like"
64
+
}
65
+
}
66
+
}
67
+
}
68
+
}
69
+
},
70
+
"like": {
71
+
"type": "object",
72
+
"required": [
73
+
"indexedAt",
74
+
"createdAt",
75
+
"actor"
76
+
],
77
+
"properties": {
78
+
"indexedAt": {
79
+
"type": "string",
80
+
"format": "datetime"
81
+
},
82
+
"createdAt": {
83
+
"type": "string",
84
+
"format": "datetime"
85
+
},
86
+
"actor": {
87
+
"type": "ref",
88
+
"ref": "party.whey.app.bsky.actor.defs#profileViewRef"
89
+
}
90
+
}
91
+
}
92
+
}
93
+
}
94
+
```
95
+
---
+65
docs/party.whey.app.bsky.feed.getListFeedPartial.md
+65
docs/party.whey.app.bsky.feed.getListFeedPartial.md
···
1
+
2
+
3
+
## party.whey.app.bsky.feed.getListFeedPartial
4
+
5
+
```js
6
+
{
7
+
"lexicon": 1,
8
+
"id": "party.whey.app.bsky.feed.getListFeedPartial",
9
+
"defs": {
10
+
"main": {
11
+
"type": "query",
12
+
"description": "Get a feed of recent posts from a list (posts and reposts from any actors on the list). Does not require auth.",
13
+
"parameters": {
14
+
"type": "params",
15
+
"required": [
16
+
"list"
17
+
],
18
+
"properties": {
19
+
"list": {
20
+
"type": "string",
21
+
"format": "at-uri",
22
+
"description": "Reference (AT-URI) to the list record."
23
+
},
24
+
"limit": {
25
+
"type": "integer",
26
+
"minimum": 1,
27
+
"maximum": 100,
28
+
"default": 50
29
+
},
30
+
"cursor": {
31
+
"type": "string"
32
+
}
33
+
}
34
+
},
35
+
"output": {
36
+
"encoding": "application/json",
37
+
"schema": {
38
+
"type": "object",
39
+
"required": [
40
+
"feed"
41
+
],
42
+
"properties": {
43
+
"cursor": {
44
+
"type": "string"
45
+
},
46
+
"feed": {
47
+
"type": "array",
48
+
"items": {
49
+
"type": "ref",
50
+
"ref": "party.whey.app.bsky.feed.defs#feedViewPostRef"
51
+
}
52
+
}
53
+
}
54
+
}
55
+
},
56
+
"errors": [
57
+
{
58
+
"name": "UnknownList"
59
+
}
60
+
]
61
+
}
62
+
}
63
+
}
64
+
```
65
+
---
+73
docs/party.whey.app.bsky.feed.getPostThreadPartial.md
+73
docs/party.whey.app.bsky.feed.getPostThreadPartial.md
···
1
+
2
+
3
+
## party.whey.app.bsky.feed.getPostThreadPartial
4
+
5
+
```js
6
+
{
7
+
"lexicon": 1,
8
+
"id": "party.whey.app.bsky.feed.getPostThreadPartial",
9
+
"defs": {
10
+
"main": {
11
+
"type": "query",
12
+
"description": "Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests.",
13
+
"parameters": {
14
+
"type": "params",
15
+
"required": [
16
+
"uri"
17
+
],
18
+
"properties": {
19
+
"uri": {
20
+
"type": "string",
21
+
"format": "at-uri",
22
+
"description": "Reference (AT-URI) to post record."
23
+
},
24
+
"depth": {
25
+
"type": "integer",
26
+
"description": "How many levels of reply depth should be included in response.",
27
+
"default": 6,
28
+
"minimum": 0,
29
+
"maximum": 1000
30
+
},
31
+
"parentHeight": {
32
+
"type": "integer",
33
+
"description": "How many levels of parent (and grandparent, etc) post to include.",
34
+
"default": 80,
35
+
"minimum": 0,
36
+
"maximum": 1000
37
+
}
38
+
}
39
+
},
40
+
"output": {
41
+
"encoding": "application/json",
42
+
"schema": {
43
+
"type": "object",
44
+
"required": [
45
+
"thread"
46
+
],
47
+
"properties": {
48
+
"thread": {
49
+
"type": "union",
50
+
"refs": [
51
+
"party.whey.app.bsky.feed.defs#threadViewPostRef",
52
+
"app.bsky.feed.defs#threadViewPost",
53
+
"app.bsky.feed.defs#notFoundPost",
54
+
"app.bsky.feed.defs#blockedPost"
55
+
]
56
+
},
57
+
"threadgate": {
58
+
"type": "ref",
59
+
"ref": "app.bsky.feed.defs#threadgateView"
60
+
}
61
+
}
62
+
}
63
+
},
64
+
"errors": [
65
+
{
66
+
"name": "NotFound"
67
+
}
68
+
]
69
+
}
70
+
}
71
+
}
72
+
```
73
+
---
+77
docs/party.whey.app.bsky.feed.getQuotesPartial.md
+77
docs/party.whey.app.bsky.feed.getQuotesPartial.md
···
1
+
2
+
3
+
## party.whey.app.bsky.feed.getQuotesPartial
4
+
5
+
```js
6
+
{
7
+
"lexicon": 1,
8
+
"id": "party.whey.app.bsky.feed.getQuotesPartial",
9
+
"defs": {
10
+
"main": {
11
+
"type": "query",
12
+
"description": "Get a list of quotes for a given post.",
13
+
"parameters": {
14
+
"type": "params",
15
+
"required": [
16
+
"uri"
17
+
],
18
+
"properties": {
19
+
"uri": {
20
+
"type": "string",
21
+
"format": "at-uri",
22
+
"description": "Reference (AT-URI) of post record"
23
+
},
24
+
"cid": {
25
+
"type": "string",
26
+
"format": "cid",
27
+
"description": "If supplied, filters to quotes of specific version (by CID) of the post record."
28
+
},
29
+
"limit": {
30
+
"type": "integer",
31
+
"minimum": 1,
32
+
"maximum": 100,
33
+
"default": 50
34
+
},
35
+
"cursor": {
36
+
"type": "string"
37
+
}
38
+
}
39
+
},
40
+
"output": {
41
+
"encoding": "application/json",
42
+
"schema": {
43
+
"type": "object",
44
+
"required": [
45
+
"uri",
46
+
"posts"
47
+
],
48
+
"properties": {
49
+
"uri": {
50
+
"type": "string",
51
+
"format": "at-uri"
52
+
},
53
+
"cid": {
54
+
"type": "string",
55
+
"format": "cid"
56
+
},
57
+
"cursor": {
58
+
"type": "string"
59
+
},
60
+
"posts": {
61
+
"type": "array",
62
+
"items": {
63
+
"type": "union",
64
+
"refs": [
65
+
"party.whey.app.bsky.feed.defs#postViewRef",
66
+
"app.bsky.feed.defs#postView"
67
+
]
68
+
}
69
+
}
70
+
}
71
+
}
72
+
}
73
+
}
74
+
}
75
+
}
76
+
```
77
+
---
+75
docs/party.whey.app.bsky.feed.getRepostedByPartial.md
+75
docs/party.whey.app.bsky.feed.getRepostedByPartial.md
···
1
+
2
+
## party.whey.app.bsky.feed.getRepostedByPartial
3
+
4
+
```js
5
+
{
6
+
"lexicon": 1,
7
+
"id": "party.whey.app.bsky.feed.getRepostedByPartial",
8
+
"defs": {
9
+
"main": {
10
+
"type": "query",
11
+
"description": "Get a list of reposts for a given post.",
12
+
"parameters": {
13
+
"type": "params",
14
+
"required": [
15
+
"uri"
16
+
],
17
+
"properties": {
18
+
"uri": {
19
+
"type": "string",
20
+
"format": "at-uri",
21
+
"description": "Reference (AT-URI) of post record"
22
+
},
23
+
"cid": {
24
+
"type": "string",
25
+
"format": "cid",
26
+
"description": "If supplied, filters to reposts of specific version (by CID) of the post record."
27
+
},
28
+
"limit": {
29
+
"type": "integer",
30
+
"minimum": 1,
31
+
"maximum": 100,
32
+
"default": 50
33
+
},
34
+
"cursor": {
35
+
"type": "string"
36
+
}
37
+
}
38
+
},
39
+
"output": {
40
+
"encoding": "application/json",
41
+
"schema": {
42
+
"type": "object",
43
+
"required": [
44
+
"uri",
45
+
"repostedBy"
46
+
],
47
+
"properties": {
48
+
"uri": {
49
+
"type": "string",
50
+
"format": "at-uri"
51
+
},
52
+
"cid": {
53
+
"type": "string",
54
+
"format": "cid"
55
+
},
56
+
"cursor": {
57
+
"type": "string"
58
+
},
59
+
"repostedBy": {
60
+
"type": "array",
61
+
"items": {
62
+
"type": "union",
63
+
"refs": [
64
+
"party.whey.app.bsky.actor.defs#profileViewRef",
65
+
"app.bsky.actor.defs#profileView"
66
+
]
67
+
}
68
+
}
69
+
}
70
+
}
71
+
}
72
+
}
73
+
}
74
+
}
75
+
```
+11
docs/structure.md
+11
docs/structure.md
···
1
+
# Structure
2
+
3
+

4
+
5
+
skylite splits the appview into two parts
6
+
7
+
- Public Access Index Server (Index)
8
+
- Contextual View Compositor Server (View)
9
+
10
+
in short, the index server hosts the hydration and skeleton indexes for users. and the view server collects data from many index servers and layers on top personal preferences and moderation data before handing it off to the application.
11
+
+6
docs/todo.md
+6
docs/todo.md
+13
docs/whylexicons.md
+13
docs/whylexicons.md
···
1
+
# why
2
+
3
+
designing lexicons are weird
4
+
5
+
these are the XRPC methods i think are required for a minimal functioning bluesky index to function and pass around data
6
+
7
+
some of the methods here reuse existing app.bsky.* XRPC methods, but some are custom under the party.whey.* namespace.
8
+
9
+
this is done because i believe that it is expensive to force every single node to return inlined / hydrated responses, when the view server that ingests and composites the final view might not even use the provided inlined / hydrated data in the first place.
10
+
11
+
and so, i created "Partial" variants of existing app.bsky.* methods, that allow for "skeleton" responses. specifically, for all methods relating to posts, they are allowed to return "PostViewRef" objects instead of the hydrated "PostView" object. PostViewRef is currently just identical to a strongRef and i am not yet certain if we should add more optional fields to it.
12
+
13
+
i do not think that the current lexicons are perfect, but they are good enough that i can continue development of other parts of the skylite system for now
+5
-3
index/jetstream.ts
+5
-3
index/jetstream.ts
···
1
-
import { handleIndex, jetstreamManager } from "../main.ts";
1
+
import { Database } from "jsr:@db/sqlite@0.11";
2
+
import { config } from "../config.ts";
2
3
import { resolveRecordFromURI } from "../utils/records.ts";
4
+
import { JetstreamManager } from "../utils/sharders.ts";
3
5
4
-
export function startJetstream() {
6
+
export function startJetstream(jetstreamManager: JetstreamManager) {
5
7
jetstreamManager.start({
6
8
// for realsies pls get from db or something instead of this shit
7
9
wantedDids: [
···
27
29
});
28
30
}
29
31
30
-
export async function handleJetstream(msg: any) {
32
+
export async function handleJetstream(msg: any, handleIndex: Function) {
31
33
console.log("Received Jetstream message: ", msg);
32
34
33
35
const op = msg.commit.operation;
+6
-2
index/onboardingBackfill.ts
+6
-2
index/onboardingBackfill.ts
···
1
-
import { systemDB, handleIndex } from "../main.ts"
1
+
import { genericIndexServer } from "../main-index.ts";
2
+
import { config } from "../config.ts"
2
3
import { FINEPDSAndHandleFromDid } from "../utils/identity.ts";
3
4
4
5
···
67
68
const doer = did;
68
69
const rev = undefined;
69
70
const aturi = uri;
71
+
const db = genericIndexServer.userManager.getDbForDid(doer);
72
+
if (!db) return;
70
73
71
-
handleIndex({
74
+
genericIndexServer.indexServerIndexer({
72
75
op,
73
76
doer,
74
77
rev,
75
78
aturi,
76
79
value,
77
80
indexsrc: "onboarding_backfill",
81
+
db: db,
78
82
})
79
83
return;
80
84
// console.log(`[BACKFILL] ${collection} -> ${uri}`);
+10
-5
index/spacedust.ts
+10
-5
index/spacedust.ts
···
1
-
import { db, handleIndex, spacedustManager } from "../main.ts";
1
+
import { Database } from "jsr:@db/sqlite@0.11";
2
+
import { config } from "../config.ts";
2
3
import { parseAtUri } from "../utils/aturi.ts";
3
4
import { resolveRecordFromURI } from "../utils/records.ts";
5
+
import { SpacedustManager } from "../utils/sharders.ts";
4
6
5
-
export function startSpacedust() {
7
+
export function startSpacedust(spacedustManager: SpacedustManager) {
6
8
spacedustManager.start({
7
9
wantedSources: [
8
10
"app.bsky.feed.like:subject.uri", // like
···
39
41
"did:plc:zzhzjga3ab5fcs2vnsv2ist3",
40
42
"did:plc:jz4ibztn56hygfld6j6zjszg",
41
43
], // this would be all users in the instance to listen for all remote mentions
44
+
instant: ["true"]
42
45
// future question for managing this specifically for like remote content tracking (followed users? items that appears in custom feeds?)
43
46
});
44
47
}
···
54
57
};
55
58
};
56
59
57
-
export async function handleSpacedust(msg: SpacedustLinkMessage) {
60
+
export async function handleSpacedust(db: Database, msg: SpacedustLinkMessage) {
58
61
if (!msg || !msg.link || !msg.link.source_record) return;
59
62
console.log("Received Spacedust message: ", msg);
60
63
···
77
80
srccol,
78
81
suburi,
79
82
subdid,
80
-
subcol
83
+
subcol,
84
+
indexedAt
81
85
) VALUES (
82
86
'${aturi}',
83
87
'${srcdid}',
···
85
89
'${msg.link.source}',
86
90
'${subject}',
87
91
'${subdid}',
88
-
'${subscol}'
92
+
'${subscol}',
93
+
'${Date.now()}'
89
94
);
90
95
`);
91
96
//if (!value) return;
+5
-1
index/types.ts
+5
-1
index/types.ts
···
1
+
import { Database } from "jsr:@db/sqlite@0.11";
2
+
1
3
export type indexHandlerContext = {
2
4
op: string;
3
5
doer: string; // the formal term for this is "repo" but whatever right
···
5
7
cid?: string;
6
8
aturi: string;
7
9
indexsrc: string;
8
-
value: Record<string, unknown>
10
+
value: Record<string, unknown>;
11
+
//userdbname: string;
12
+
db: Database;
9
13
}
+2164
-7
indexserver.ts
+2164
-7
indexserver.ts
···
1
-
export async function indexServerHandler(req: Request): Promise<Response> {
2
-
const url = new URL(req.url);
3
-
4
-
if (url.pathname === "/ping") {
5
-
return new Response("pong", { status: 200 });
1
+
import { indexHandlerContext } from "./index/types.ts";
2
+
3
+
import { assertRecord, validateRecord } from "./utils/records.ts";
4
+
import {
5
+
buildBlobUrl,
6
+
getSlingshotRecord,
7
+
resolveIdentity,
8
+
searchParamsToJson,
9
+
withCors,
10
+
} from "./utils/server.ts";
11
+
import * as IndexServerTypes from "./utils/indexservertypes.ts";
12
+
import { Database } from "jsr:@db/sqlite@0.11";
13
+
import { setupUserDb } from "./utils/dbuser.ts";
14
+
// import { systemDB } from "./env.ts";
15
+
import { JetstreamManager, SpacedustManager } from "./utils/sharders.ts";
16
+
import { handleSpacedust, SpacedustLinkMessage } from "./index/spacedust.ts";
17
+
import { handleJetstream } from "./index/jetstream.ts";
18
+
import * as ATPAPI from "npm:@atproto/api";
19
+
import { AtUri } from "npm:@atproto/api";
20
+
import * as IndexServerAPI from "./indexclient/index.ts";
21
+
import * as IndexServerUtils from "./indexclient/util.ts";
22
+
import { isPostView } from "./indexclient/types/app/bsky/feed/defs.ts";
23
+
24
+
export interface IndexServerConfig {
25
+
baseDbPath: string;
26
+
systemDbPath: string;
27
+
}
28
+
29
+
interface BaseRow {
30
+
uri: string;
31
+
did: string;
32
+
cid: string | null;
33
+
rev: string | null;
34
+
createdat: number | null;
35
+
indexedat: number;
36
+
json: string | null;
37
+
}
38
+
interface GeneratorRow extends BaseRow {
39
+
displayname: string | null;
40
+
description: string | null;
41
+
avatarcid: string | null;
42
+
}
43
+
interface LikeRow extends BaseRow {
44
+
subject: string;
45
+
}
46
+
interface RepostRow extends BaseRow {
47
+
subject: string;
48
+
}
49
+
interface BacklinkRow {
50
+
srcuri: string;
51
+
srcdid: string;
52
+
}
53
+
54
+
const FEED_LIMIT = 50;
55
+
56
+
export class IndexServer {
57
+
private config: IndexServerConfig;
58
+
public userManager: IndexServerUserManager;
59
+
public systemDB: Database;
60
+
61
+
constructor(config: IndexServerConfig) {
62
+
this.config = config;
63
+
64
+
// We will initialize the system DB and user manager here
65
+
this.systemDB = new Database(this.config.systemDbPath);
66
+
// TODO: We need to setup the system DB schema if it's new
67
+
68
+
this.userManager = new IndexServerUserManager(this); // Pass the server instance
69
+
}
70
+
71
+
public start() {
72
+
// This is where we'll kick things off, like the cold start
73
+
this.userManager.coldStart(this.systemDB);
74
+
console.log("IndexServer started.");
75
+
}
76
+
77
+
public async handleRequest(req: Request): Promise<Response> {
78
+
const url = new URL(req.url);
79
+
// We will add routing logic here later to call our handlers
80
+
if (url.pathname.startsWith("/xrpc/")) {
81
+
return this.indexServerHandler(req);
82
+
}
83
+
if (url.pathname.startsWith("/links")) {
84
+
return this.constellationAPIHandler(req);
85
+
}
86
+
return new Response("Not Found", { status: 404 });
87
+
}
88
+
89
+
public handlesDid(did: string): boolean {
90
+
return this.userManager.handlesDid(did);
91
+
}
92
+
async unspeccedGetRegisteredUsers(): Promise<{
93
+
did: string;
94
+
role: string;
95
+
registrationdate: string;
96
+
onboardingstatus: string;
97
+
pfp?: string;
98
+
displayname: string;
99
+
handle: string;
100
+
}[]|undefined> {
101
+
const stmt = this.systemDB.prepare(`
102
+
SELECT *
103
+
FROM users;
104
+
`);
105
+
const result = stmt.all() as
106
+
{
107
+
did: string;
108
+
role: string;
109
+
registrationdate: string;
110
+
onboardingstatus: string;
111
+
}[];
112
+
const hydrated = await Promise.all( result.map(async (user)=>{
113
+
const identity = await resolveIdentity(user.did);
114
+
const profile = (await getSlingshotRecord(identity.did,"app.bsky.actor.profile","self")).value as ATPAPI.AppBskyActorProfile.Record;
115
+
const avatarcid = uncid(profile.avatar?.ref);
116
+
const avatar = avatarcid
117
+
? buildBlobUrl(identity.pds, identity.did, avatarcid)
118
+
: undefined;
119
+
return {...user,handle: identity.handle,pfp: avatar, displayname:profile.displayName ?? identity.handle }
120
+
}))
121
+
//const exists = result !== undefined;
122
+
return hydrated;
123
+
}
124
+
125
+
// We will move all the global functions into this class as methods...
126
+
async indexServerHandler(req: Request): Promise<Response> {
127
+
const url = new URL(req.url);
128
+
const pathname = url.pathname;
129
+
//const bskyUrl = `https://api.bsky.app${pathname}${url.search}`;
130
+
//const hasAuth = req.headers.has("authorization");
131
+
const xrpcMethod = pathname.startsWith("/xrpc/")
132
+
? pathname.slice("/xrpc/".length)
133
+
: null;
134
+
const searchParams = searchParamsToJson(url.searchParams);
135
+
console.log(JSON.stringify(searchParams, null, 2));
136
+
const jsonUntyped = searchParams;
137
+
138
+
switch (xrpcMethod) {
139
+
case "app.bsky.actor.getProfile": {
140
+
const jsonTyped =
141
+
jsonUntyped as IndexServerTypes.AppBskyActorGetProfile.QueryParams;
142
+
143
+
const res = await this.queryProfileView(jsonTyped.actor, "Detailed");
144
+
if (!res)
145
+
return new Response(
146
+
JSON.stringify({
147
+
error: "User not found",
148
+
}),
149
+
{
150
+
status: 404,
151
+
headers: withCors({ "Content-Type": "application/json" }),
152
+
}
153
+
);
154
+
const response: IndexServerTypes.AppBskyActorGetProfile.OutputSchema =
155
+
res;
156
+
157
+
return new Response(JSON.stringify(response), {
158
+
headers: withCors({ "Content-Type": "application/json" }),
159
+
});
160
+
}
161
+
case "app.bsky.actor.getProfiles": {
162
+
const jsonTyped =
163
+
jsonUntyped as IndexServerTypes.AppBskyActorGetProfiles.QueryParams;
164
+
165
+
if (typeof jsonUntyped?.actors === "string") {
166
+
const res = await this.queryProfileView(
167
+
jsonUntyped.actors as string,
168
+
"Detailed"
169
+
);
170
+
if (!res)
171
+
return new Response(
172
+
JSON.stringify({
173
+
error: "User not found",
174
+
}),
175
+
{
176
+
status: 404,
177
+
headers: withCors({ "Content-Type": "application/json" }),
178
+
}
179
+
);
180
+
const response: IndexServerTypes.AppBskyActorGetProfiles.OutputSchema =
181
+
{
182
+
profiles: [res],
183
+
};
184
+
185
+
return new Response(JSON.stringify(response), {
186
+
headers: withCors({ "Content-Type": "application/json" }),
187
+
});
188
+
}
189
+
190
+
const res: ATPAPI.AppBskyActorDefs.ProfileViewDetailed[] =
191
+
await Promise.all(
192
+
jsonTyped.actors
193
+
.map(async (actor) => {
194
+
return await this.queryProfileView(actor, "Detailed");
195
+
})
196
+
.filter(
197
+
(
198
+
x
199
+
): x is Promise<ATPAPI.AppBskyActorDefs.ProfileViewDetailed> =>
200
+
x !== undefined
201
+
)
202
+
);
203
+
204
+
if (!res)
205
+
return new Response(
206
+
JSON.stringify({
207
+
error: "User not found",
208
+
}),
209
+
{
210
+
status: 404,
211
+
headers: withCors({ "Content-Type": "application/json" }),
212
+
}
213
+
);
214
+
215
+
const response: IndexServerTypes.AppBskyActorGetProfiles.OutputSchema =
216
+
{
217
+
profiles: res,
218
+
};
219
+
220
+
return new Response(JSON.stringify(response), {
221
+
headers: withCors({ "Content-Type": "application/json" }),
222
+
});
223
+
}
224
+
case "app.bsky.feed.getActorFeeds": {
225
+
const jsonTyped =
226
+
jsonUntyped as IndexServerTypes.AppBskyFeedGetActorFeeds.QueryParams;
227
+
228
+
const qresult = await this.queryActorFeeds(jsonTyped.actor);
229
+
230
+
const response: IndexServerTypes.AppBskyFeedGetActorFeeds.OutputSchema =
231
+
{
232
+
feeds: qresult,
233
+
};
234
+
235
+
return new Response(JSON.stringify(response), {
236
+
headers: withCors({ "Content-Type": "application/json" }),
237
+
});
238
+
}
239
+
case "app.bsky.feed.getFeedGenerator": {
240
+
const jsonTyped =
241
+
jsonUntyped as IndexServerTypes.AppBskyFeedGetFeedGenerator.QueryParams;
242
+
243
+
const qresult = await this.queryFeedGenerator(jsonTyped.feed);
244
+
if (!qresult) {
245
+
return new Response(
246
+
JSON.stringify({
247
+
error: "Feed not found",
248
+
}),
249
+
{
250
+
status: 404,
251
+
headers: withCors({ "Content-Type": "application/json" }),
252
+
}
253
+
);
254
+
}
255
+
256
+
const response: IndexServerTypes.AppBskyFeedGetFeedGenerator.OutputSchema =
257
+
{
258
+
view: qresult,
259
+
isOnline: true, // lmao
260
+
isValid: true, // lmao
261
+
};
262
+
263
+
return new Response(JSON.stringify(response), {
264
+
headers: withCors({ "Content-Type": "application/json" }),
265
+
});
266
+
}
267
+
case "app.bsky.feed.getFeedGenerators": {
268
+
const jsonTyped =
269
+
jsonUntyped as IndexServerTypes.AppBskyFeedGetFeedGenerators.QueryParams;
270
+
271
+
const qresult = await this.queryFeedGenerators(jsonTyped.feeds);
272
+
if (!qresult) {
273
+
return new Response(
274
+
JSON.stringify({
275
+
error: "Feed not found",
276
+
}),
277
+
{
278
+
status: 404,
279
+
headers: withCors({ "Content-Type": "application/json" }),
280
+
}
281
+
);
282
+
}
283
+
284
+
const response: IndexServerTypes.AppBskyFeedGetFeedGenerators.OutputSchema =
285
+
{
286
+
feeds: qresult,
287
+
};
288
+
289
+
return new Response(JSON.stringify(response), {
290
+
headers: withCors({ "Content-Type": "application/json" }),
291
+
});
292
+
}
293
+
case "app.bsky.feed.getPosts": {
294
+
const jsonTyped =
295
+
jsonUntyped as IndexServerTypes.AppBskyFeedGetPosts.QueryParams;
296
+
297
+
const posts: IndexServerTypes.AppBskyFeedGetPosts.OutputSchema["posts"] =
298
+
(
299
+
await Promise.all(
300
+
jsonTyped.uris.map((uri) => this.queryPostView(uri))
301
+
)
302
+
).filter((p): p is ATPAPI.AppBskyFeedDefs.PostView => Boolean(p));
303
+
304
+
const response: IndexServerTypes.AppBskyFeedGetPosts.OutputSchema = {
305
+
posts,
306
+
};
307
+
308
+
return new Response(JSON.stringify(response), {
309
+
headers: withCors({ "Content-Type": "application/json" }),
310
+
});
311
+
}
312
+
case "party.whey.app.bsky.feed.getActorLikesPartial": {
313
+
const jsonTyped =
314
+
jsonUntyped as IndexServerTypes.PartyWheyAppBskyFeedGetActorLikesPartial.QueryParams;
315
+
316
+
// TODO: not partial yet, currently skips refs
317
+
318
+
const qresult = await this.queryActorLikesPartial(
319
+
jsonTyped.actor,
320
+
jsonTyped.cursor
321
+
);
322
+
if (!qresult) {
323
+
return new Response(
324
+
JSON.stringify({
325
+
error: "Feed not found",
326
+
}),
327
+
{
328
+
status: 404,
329
+
headers: withCors({ "Content-Type": "application/json" }),
330
+
}
331
+
);
332
+
}
333
+
334
+
const response: IndexServerTypes.PartyWheyAppBskyFeedGetActorLikesPartial.OutputSchema =
335
+
{
336
+
feed: qresult.items as ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.FeedViewPost>[],
337
+
cursor: qresult.cursor,
338
+
};
339
+
340
+
return new Response(JSON.stringify(response), {
341
+
headers: withCors({ "Content-Type": "application/json" }),
342
+
});
343
+
}
344
+
case "party.whey.app.bsky.feed.getAuthorFeedPartial": {
345
+
const jsonTyped =
346
+
jsonUntyped as IndexServerTypes.PartyWheyAppBskyFeedGetAuthorFeedPartial.QueryParams;
347
+
348
+
// TODO: not partial yet, currently skips refs
349
+
350
+
const qresult = await this.queryAuthorFeedPartial(
351
+
jsonTyped.actor,
352
+
jsonTyped.cursor
353
+
);
354
+
if (!qresult) {
355
+
return new Response(
356
+
JSON.stringify({
357
+
error: "Feed not found",
358
+
}),
359
+
{
360
+
status: 404,
361
+
headers: withCors({ "Content-Type": "application/json" }),
362
+
}
363
+
);
364
+
}
365
+
366
+
const response: IndexServerTypes.PartyWheyAppBskyFeedGetAuthorFeedPartial.OutputSchema =
367
+
{
368
+
feed: qresult.items as ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.FeedViewPost>[],
369
+
cursor: qresult.cursor,
370
+
};
371
+
372
+
return new Response(JSON.stringify(response), {
373
+
headers: withCors({ "Content-Type": "application/json" }),
374
+
});
375
+
}
376
+
case "party.whey.app.bsky.feed.getLikesPartial": {
377
+
const jsonTyped =
378
+
jsonUntyped as IndexServerTypes.PartyWheyAppBskyFeedGetLikesPartial.QueryParams;
379
+
380
+
// TODO: not partial yet, currently skips refs
381
+
382
+
const qresult = this.queryLikes(jsonTyped.uri);
383
+
if (!qresult) {
384
+
return new Response(
385
+
JSON.stringify({
386
+
error: "Feed not found",
387
+
}),
388
+
{
389
+
status: 404,
390
+
headers: withCors({ "Content-Type": "application/json" }),
391
+
}
392
+
);
393
+
}
394
+
const response: IndexServerTypes.PartyWheyAppBskyFeedGetLikesPartial.OutputSchema =
395
+
{
396
+
// @ts-ignore whatever i dont care TODO: fix ts ignores
397
+
likes: qresult,
398
+
};
399
+
400
+
return new Response(JSON.stringify(response), {
401
+
headers: withCors({ "Content-Type": "application/json" }),
402
+
});
403
+
}
404
+
case "party.whey.app.bsky.feed.getPostThreadPartial": {
405
+
const jsonTyped =
406
+
jsonUntyped as IndexServerTypes.PartyWheyAppBskyFeedGetPostThreadPartial.QueryParams;
407
+
408
+
// TODO: not partial yet, currently skips refs
409
+
410
+
const qresult = await this.queryPostThreadPartial(jsonTyped.uri);
411
+
if (!qresult) {
412
+
return new Response(
413
+
JSON.stringify({
414
+
error: "Feed not found",
415
+
}),
416
+
{
417
+
status: 404,
418
+
headers: withCors({ "Content-Type": "application/json" }),
419
+
}
420
+
);
421
+
}
422
+
const response: IndexServerTypes.PartyWheyAppBskyFeedGetPostThreadPartial.OutputSchema =
423
+
qresult;
424
+
425
+
return new Response(JSON.stringify(response), {
426
+
headers: withCors({ "Content-Type": "application/json" }),
427
+
});
428
+
}
429
+
case "party.whey.app.bsky.feed.getQuotesPartial": {
430
+
const jsonTyped =
431
+
jsonUntyped as IndexServerTypes.PartyWheyAppBskyFeedGetQuotesPartial.QueryParams;
432
+
433
+
// TODO: not partial yet, currently skips refs
434
+
435
+
const qresult = await this.queryQuotes(jsonTyped.uri);
436
+
if (!qresult) {
437
+
return new Response(
438
+
JSON.stringify({
439
+
error: "Feed not found",
440
+
}),
441
+
{
442
+
status: 404,
443
+
headers: withCors({ "Content-Type": "application/json" }),
444
+
}
445
+
);
446
+
}
447
+
const response: IndexServerTypes.PartyWheyAppBskyFeedGetQuotesPartial.OutputSchema =
448
+
{
449
+
uri: jsonTyped.uri,
450
+
posts: qresult.map((feedviewpost) => {
451
+
return feedviewpost.post as ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView>;
452
+
}),
453
+
};
454
+
455
+
return new Response(JSON.stringify(response), {
456
+
headers: withCors({ "Content-Type": "application/json" }),
457
+
});
458
+
}
459
+
case "party.whey.app.bsky.feed.getRepostedByPartial": {
460
+
const jsonTyped =
461
+
jsonUntyped as IndexServerTypes.PartyWheyAppBskyFeedGetRepostedByPartial.QueryParams;
462
+
463
+
// TODO: not partial yet, currently skips refs
464
+
465
+
const qresult = await this.queryReposts(jsonTyped.uri);
466
+
if (!qresult) {
467
+
return new Response(
468
+
JSON.stringify({
469
+
error: "Feed not found",
470
+
}),
471
+
{
472
+
status: 404,
473
+
headers: withCors({ "Content-Type": "application/json" }),
474
+
}
475
+
);
476
+
}
477
+
const response: IndexServerTypes.PartyWheyAppBskyFeedGetRepostedByPartial.OutputSchema =
478
+
{
479
+
uri: jsonTyped.uri,
480
+
repostedBy:
481
+
qresult as ATPAPI.$Typed<ATPAPI.AppBskyActorDefs.ProfileView>[],
482
+
};
483
+
484
+
return new Response(JSON.stringify(response), {
485
+
headers: withCors({ "Content-Type": "application/json" }),
486
+
});
487
+
}
488
+
// TODO: too hard for now
489
+
// case "party.whey.app.bsky.feed.getListFeedPartial": {
490
+
// const jsonTyped =
491
+
// jsonUntyped as IndexServerTypes.PartyWheyAppBskyFeedGetListFeedPartial.QueryParams;
492
+
493
+
// const response: IndexServerTypes.PartyWheyAppBskyFeedGetListFeedPartial.OutputSchema =
494
+
// {};
495
+
496
+
// return new Response(JSON.stringify(response), {
497
+
// headers: withCors({ "Content-Type": "application/json" }),
498
+
// });
499
+
// }
500
+
/* three more coming soon
501
+
app.bsky.graph.getLists
502
+
app.bsky.graph.getList
503
+
app.bsky.graph.getActorStarterPacks
504
+
*/
505
+
default: {
506
+
return new Response(
507
+
JSON.stringify({
508
+
error: "XRPCNotSupported",
509
+
message:
510
+
"HEY hello there my name is whey dot party and you have used my custom appview that is very cool but have you considered that XRPC Not Supported",
511
+
}),
512
+
{
513
+
status: 404,
514
+
headers: withCors({ "Content-Type": "application/json" }),
515
+
}
516
+
);
517
+
}
518
+
}
519
+
520
+
// return new Response("Not Found", { status: 404 });
521
+
}
522
+
523
+
constellationAPIHandler(req: Request): Response {
524
+
const url = new URL(req.url);
525
+
const pathname = url.pathname;
526
+
const searchParams = searchParamsToJson(url.searchParams) as linksQuery;
527
+
const jsonUntyped = searchParams;
528
+
529
+
if (!jsonUntyped.target) {
530
+
return new Response(
531
+
JSON.stringify({ error: "Missing required parameter: target" }),
532
+
{
533
+
status: 400,
534
+
headers: withCors({ "Content-Type": "application/json" }),
535
+
}
536
+
);
537
+
}
538
+
539
+
const did = isDid(searchParams.target)
540
+
? searchParams.target
541
+
: new AtUri(searchParams.target).host;
542
+
const db = this.userManager.getDbForDid(did);
543
+
if (!db) {
544
+
return new Response(
545
+
JSON.stringify({
546
+
error: "User not found",
547
+
}),
548
+
{
549
+
status: 404,
550
+
headers: withCors({ "Content-Type": "application/json" }),
551
+
}
552
+
);
553
+
}
554
+
555
+
const limit = 16; //Math.min(parseInt(searchParams.limit || "50", 10), 100);
556
+
const offset = parseInt(searchParams.cursor || "0", 10);
557
+
558
+
switch (pathname) {
559
+
case "/links": {
560
+
const jsonTyped = jsonUntyped as linksQuery;
561
+
if (!jsonTyped.collection || !jsonTyped.path) {
562
+
return new Response(
563
+
JSON.stringify({
564
+
error: "Missing required parameters: collection, path",
565
+
}),
566
+
{
567
+
status: 400,
568
+
headers: withCors({ "Content-Type": "application/json" }),
569
+
}
570
+
);
571
+
}
572
+
573
+
const field = `${jsonTyped.collection}:${jsonTyped.path.replace(
574
+
/^\./,
575
+
""
576
+
)}`;
577
+
578
+
const paginatedSql = `${SQL.links} LIMIT ? OFFSET ?`;
579
+
const rows = db
580
+
.prepare(paginatedSql)
581
+
.all(jsonTyped.target, jsonTyped.collection, field, limit, offset);
582
+
583
+
const countResult = db
584
+
.prepare(SQL.count)
585
+
.get(jsonTyped.target, jsonTyped.collection, field);
586
+
const total = countResult ? Number(countResult.total) : 0;
587
+
588
+
const linking_records: linksRecord[] = rows.map((row: any) => {
589
+
const rkey = row.srcuri.split("/").pop()!;
590
+
return {
591
+
did: row.srcdid,
592
+
collection: row.srccol,
593
+
rkey,
594
+
};
595
+
});
596
+
597
+
const response: linksRecordsResponse = {
598
+
total: total.toString(),
599
+
linking_records,
600
+
};
601
+
602
+
const nextCursor = offset + linking_records.length;
603
+
if (nextCursor < total) {
604
+
response.cursor = nextCursor.toString();
605
+
}
606
+
607
+
return new Response(JSON.stringify(response), {
608
+
headers: withCors({ "Content-Type": "application/json" }),
609
+
});
610
+
}
611
+
case "/links/distinct-dids": {
612
+
const jsonTyped = jsonUntyped as linksQuery;
613
+
if (!jsonTyped.collection || !jsonTyped.path) {
614
+
return new Response(
615
+
JSON.stringify({
616
+
error: "Missing required parameters: collection, path",
617
+
}),
618
+
{
619
+
status: 400,
620
+
headers: withCors({ "Content-Type": "application/json" }),
621
+
}
622
+
);
623
+
}
624
+
625
+
const field = `${jsonTyped.collection}:${jsonTyped.path.replace(
626
+
/^\./,
627
+
""
628
+
)}`;
629
+
630
+
const paginatedSql = `${SQL.distinctDids} LIMIT ? OFFSET ?`;
631
+
const rows = db
632
+
.prepare(paginatedSql)
633
+
.all(jsonTyped.target, jsonTyped.collection, field, limit, offset);
634
+
635
+
const countResult = db
636
+
.prepare(SQL.countDistinctDids)
637
+
.get(jsonTyped.target, jsonTyped.collection, field);
638
+
const total = countResult ? Number(countResult.total) : 0;
639
+
640
+
const linking_dids: string[] = rows.map((row: any) => row.srcdid);
641
+
642
+
const response: linksDidsResponse = {
643
+
total: total.toString(),
644
+
linking_dids,
645
+
};
646
+
647
+
const nextCursor = offset + linking_dids.length;
648
+
if (nextCursor < total) {
649
+
response.cursor = nextCursor.toString();
650
+
}
651
+
652
+
return new Response(JSON.stringify(response), {
653
+
headers: withCors({ "Content-Type": "application/json" }),
654
+
});
655
+
}
656
+
case "/links/count": {
657
+
const jsonTyped = jsonUntyped as linksQuery;
658
+
if (!jsonTyped.collection || !jsonTyped.path) {
659
+
return new Response(
660
+
JSON.stringify({
661
+
error: "Missing required parameters: collection, path",
662
+
}),
663
+
{
664
+
status: 400,
665
+
headers: withCors({ "Content-Type": "application/json" }),
666
+
}
667
+
);
668
+
}
669
+
670
+
const field = `${jsonTyped.collection}:${jsonTyped.path.replace(
671
+
/^\./,
672
+
""
673
+
)}`;
674
+
675
+
const result = db
676
+
.prepare(SQL.count)
677
+
.get(jsonTyped.target, jsonTyped.collection, field);
678
+
679
+
const response: linksCountResponse = {
680
+
total: result && result.total ? result.total.toString() : "0",
681
+
};
682
+
683
+
return new Response(JSON.stringify(response), {
684
+
headers: withCors({ "Content-Type": "application/json" }),
685
+
});
686
+
}
687
+
case "/links/count/distinct-dids": {
688
+
const jsonTyped = jsonUntyped as linksQuery;
689
+
if (!jsonTyped.collection || !jsonTyped.path) {
690
+
return new Response(
691
+
JSON.stringify({
692
+
error: "Missing required parameters: collection, path",
693
+
}),
694
+
{
695
+
status: 400,
696
+
headers: withCors({ "Content-Type": "application/json" }),
697
+
}
698
+
);
699
+
}
700
+
701
+
const field = `${jsonTyped.collection}:${jsonTyped.path.replace(
702
+
/^\./,
703
+
""
704
+
)}`;
705
+
706
+
const result = db
707
+
.prepare(SQL.countDistinctDids)
708
+
.get(jsonTyped.target, jsonTyped.collection, field);
709
+
710
+
const response: linksCountResponse = {
711
+
total: result && result.total ? result.total.toString() : "0",
712
+
};
713
+
714
+
return new Response(JSON.stringify(response), {
715
+
headers: withCors({ "Content-Type": "application/json" }),
716
+
});
717
+
}
718
+
case "/links/all": {
719
+
const jsonTyped = jsonUntyped as linksAllQuery;
720
+
721
+
const rows = db.prepare(SQL.all).all(jsonTyped.target) as any[];
722
+
723
+
const links: linksAllResponse["links"] = {};
724
+
725
+
for (const row of rows) {
726
+
if (!links[row.suburi]) {
727
+
links[row.suburi] = {};
728
+
}
729
+
links[row.suburi][row.srccol] = {
730
+
records: row.records,
731
+
distinct_dids: row.distinct_dids,
732
+
};
733
+
}
734
+
735
+
const response: linksAllResponse = {
736
+
links,
737
+
};
738
+
739
+
return new Response(JSON.stringify(response), {
740
+
headers: withCors({ "Content-Type": "application/json" }),
741
+
});
742
+
}
743
+
default: {
744
+
return new Response(
745
+
JSON.stringify({
746
+
error: "NotSupported",
747
+
message:
748
+
"The requested endpoint is not supported by this Constellation implementation.",
749
+
}),
750
+
{
751
+
status: 404,
752
+
headers: withCors({ "Content-Type": "application/json" }),
753
+
}
754
+
);
755
+
}
756
+
}
757
+
}
758
+
759
+
indexServerIndexer(ctx: indexHandlerContext) {
760
+
const record = assertRecord(ctx.value);
761
+
//const record = validateRecord(ctx.value);
762
+
const db = this.userManager.getDbForDid(ctx.doer);
763
+
if (!db) return;
764
+
console.log("indexering");
765
+
switch (record?.$type) {
766
+
case "app.bsky.feed.like": {
767
+
return;
768
+
}
769
+
case "app.bsky.actor.profile": {
770
+
console.log("bsky profuile");
771
+
772
+
try {
773
+
const stmt = db.prepare(`
774
+
INSERT OR IGNORE INTO app_bsky_actor_profile (
775
+
uri, did, cid, rev, createdat, indexedat, json,
776
+
displayname,
777
+
description,
778
+
avatarcid,
779
+
avatarmime,
780
+
bannercid,
781
+
bannermime
782
+
) VALUES (?, ?, ?, ?, ?, ?, ?,
783
+
?, ?, ?,
784
+
?, ?, ?)
785
+
`);
786
+
console.log({
787
+
uri: ctx.aturi,
788
+
did: ctx.doer,
789
+
cid: ctx.cid,
790
+
rev: ctx.rev,
791
+
createdat: record.createdAt,
792
+
indexedat: Date.now(),
793
+
json: JSON.stringify(record),
794
+
displayname: record.displayName,
795
+
description: record.description,
796
+
avatarcid: uncid(record.avatar?.ref),
797
+
avatarmime: record.avatar?.mimeType,
798
+
bannercid: uncid(record.banner?.ref),
799
+
bannermime: record.banner?.mimeType,
800
+
});
801
+
stmt.run(
802
+
ctx.aturi ?? null,
803
+
ctx.doer ?? null,
804
+
ctx.cid ?? null,
805
+
ctx.rev ?? null,
806
+
record.createdAt ?? null,
807
+
Date.now(),
808
+
JSON.stringify(record),
809
+
810
+
record.displayName ?? null,
811
+
record.description ?? null,
812
+
uncid(record.avatar?.ref) ?? null,
813
+
record.avatar?.mimeType ?? null,
814
+
uncid(record.banner?.ref) ?? null,
815
+
record.banner?.mimeType ?? null
816
+
// TODO please add pinned posts
817
+
);
818
+
} catch (err) {
819
+
console.error("stmt.run failed:", err);
820
+
}
821
+
return;
822
+
}
823
+
case "app.bsky.feed.post": {
824
+
console.log("bsky post");
825
+
const stmt = db.prepare(`
826
+
INSERT OR IGNORE INTO app_bsky_feed_post (
827
+
uri, did, cid, rev, createdat, indexedat, json,
828
+
text, replyroot, replyparent, quote,
829
+
imagecount, image1cid, image1mime, image1aspect,
830
+
image2cid, image2mime, image2aspect,
831
+
image3cid, image3mime, image3aspect,
832
+
image4cid, image4mime, image4aspect,
833
+
videocount, videocid, videomime, videoaspect
834
+
) VALUES (?, ?, ?, ?, ?, ?, ?,
835
+
?, ?, ?, ?,
836
+
?, ?, ?, ?,
837
+
?, ?, ?,
838
+
?, ?, ?,
839
+
?, ?, ?,
840
+
?, ?, ?, ?)
841
+
`);
842
+
843
+
const embed = record.embed;
844
+
845
+
const images = extractImages(embed);
846
+
const video = extractVideo(embed);
847
+
const quoteUri = extractQuoteUri(embed);
848
+
try {
849
+
stmt.run(
850
+
ctx.aturi ?? null,
851
+
ctx.doer ?? null,
852
+
ctx.cid ?? null,
853
+
ctx.rev ?? null,
854
+
record.createdAt,
855
+
Date.now(),
856
+
JSON.stringify(record),
857
+
858
+
record.text ?? null,
859
+
record.reply?.root?.uri ?? null,
860
+
record.reply?.parent?.uri ?? null,
861
+
862
+
quoteUri,
863
+
864
+
images.length,
865
+
uncid(images[0]?.image?.ref) ?? null,
866
+
images[0]?.image?.mimeType ?? null,
867
+
images[0]?.aspectRatio &&
868
+
images[0].aspectRatio.width &&
869
+
images[0].aspectRatio.height
870
+
? `${images[0].aspectRatio.width}:${images[0].aspectRatio.height}`
871
+
: null,
872
+
873
+
uncid(images[1]?.image?.ref) ?? null,
874
+
images[1]?.image?.mimeType ?? null,
875
+
images[1]?.aspectRatio &&
876
+
images[1].aspectRatio.width &&
877
+
images[1].aspectRatio.height
878
+
? `${images[1].aspectRatio.width}:${images[1].aspectRatio.height}`
879
+
: null,
880
+
881
+
uncid(images[2]?.image?.ref) ?? null,
882
+
images[2]?.image?.mimeType ?? null,
883
+
images[2]?.aspectRatio &&
884
+
images[2].aspectRatio.width &&
885
+
images[2].aspectRatio.height
886
+
? `${images[2].aspectRatio.width}:${images[2].aspectRatio.height}`
887
+
: null,
888
+
889
+
uncid(images[3]?.image?.ref) ?? null,
890
+
images[3]?.image?.mimeType ?? null,
891
+
images[3]?.aspectRatio &&
892
+
images[3].aspectRatio.width &&
893
+
images[3].aspectRatio.height
894
+
? `${images[3].aspectRatio.width}:${images[3].aspectRatio.height}`
895
+
: null,
896
+
897
+
uncid(video?.video) ? 1 : 0,
898
+
uncid(video?.video) ?? null,
899
+
uncid(video?.video) ? "video/mp4" : null,
900
+
video?.aspectRatio
901
+
? `${video.aspectRatio.width}:${video.aspectRatio.height}`
902
+
: null
903
+
);
904
+
} catch (err) {
905
+
console.error("stmt.run failed:", err);
906
+
}
907
+
return;
908
+
}
909
+
default: {
910
+
// what the hell
911
+
return;
912
+
}
913
+
}
914
+
}
915
+
916
+
// user data
917
+
async queryProfileView(
918
+
did: string,
919
+
type: ""
920
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileView | undefined>;
921
+
async queryProfileView(
922
+
did: string,
923
+
type: "Basic"
924
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileViewBasic | undefined>;
925
+
async queryProfileView(
926
+
did: string,
927
+
type: "Detailed"
928
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileViewDetailed | undefined>;
929
+
async queryProfileView(
930
+
did: string,
931
+
type: "" | "Basic" | "Detailed"
932
+
): Promise<
933
+
| ATPAPI.AppBskyActorDefs.ProfileView
934
+
| ATPAPI.AppBskyActorDefs.ProfileViewBasic
935
+
| ATPAPI.AppBskyActorDefs.ProfileViewDetailed
936
+
| undefined
937
+
> {
938
+
if (!this.isRegisteredIndexUser(did)) return;
939
+
const db = this.userManager.getDbForDid(did);
940
+
if (!db) return;
941
+
942
+
const stmt = db.prepare(`
943
+
SELECT *
944
+
FROM app_bsky_actor_profile
945
+
WHERE did = ?
946
+
LIMIT 1;
947
+
`);
948
+
949
+
const row = stmt.get(did) as ProfileRow;
950
+
951
+
const identity = await resolveIdentity(did);
952
+
const avatar = row.avatarcid ? buildBlobUrl(
953
+
identity.pds,
954
+
identity.did,
955
+
row.avatarcid
956
+
) : undefined
957
+
const banner = row.bannercid ? buildBlobUrl(
958
+
identity.pds,
959
+
identity.did,
960
+
row.bannercid
961
+
) : undefined
962
+
// simulate different types returned
963
+
switch (type) {
964
+
case "": {
965
+
const result: ATPAPI.AppBskyActorDefs.ProfileView = {
966
+
$type: "app.bsky.actor.defs#profileView",
967
+
did: did,
968
+
handle: identity.handle, // TODO: Resolve user identity here for the handle
969
+
displayName: row.displayname ?? undefined,
970
+
description: row.description ?? undefined,
971
+
avatar: avatar, // create profile URL from resolved identity
972
+
//associated?: ProfileAssociated,
973
+
indexedAt: row.createdat
974
+
? new Date(row.createdat).toISOString()
975
+
: undefined,
976
+
createdAt: row.createdat
977
+
? new Date(row.createdat).toISOString()
978
+
: undefined,
979
+
//viewer?: ViewerState,
980
+
//labels?: ComAtprotoLabelDefs.Label[],
981
+
//verification?: VerificationState,
982
+
//status?: StatusView,
983
+
};
984
+
return result;
985
+
}
986
+
case "Basic": {
987
+
const result: ATPAPI.AppBskyActorDefs.ProfileViewBasic = {
988
+
$type: "app.bsky.actor.defs#profileViewBasic",
989
+
did: did,
990
+
handle: identity.handle, // TODO: Resolve user identity here for the handle
991
+
displayName: row.displayname ?? undefined,
992
+
avatar: avatar, // create profile URL from resolved identity
993
+
//associated?: ProfileAssociated,
994
+
createdAt: row.createdat
995
+
? new Date(row.createdat).toISOString()
996
+
: undefined,
997
+
//viewer?: ViewerState,
998
+
//labels?: ComAtprotoLabelDefs.Label[],
999
+
//verification?: VerificationState,
1000
+
//status?: StatusView,
1001
+
};
1002
+
return result;
1003
+
}
1004
+
case "Detailed": {
1005
+
// Query for follower count from the backlink_skeleton table
1006
+
const followersStmt = db.prepare(`
1007
+
SELECT COUNT(*) as count
1008
+
FROM backlink_skeleton
1009
+
WHERE subdid = ? AND srccol = 'app.bsky.graph.follow'
1010
+
`);
1011
+
const followersResult = followersStmt.get(did) as { count: number };
1012
+
const followersCount = followersResult?.count ?? 0;
1013
+
1014
+
// Query for following count from the app_bsky_graph_follow table
1015
+
const followingStmt = db.prepare(`
1016
+
SELECT COUNT(*) as count
1017
+
FROM app_bsky_graph_follow
1018
+
WHERE did = ?
1019
+
`);
1020
+
const followingResult = followingStmt.get(did) as { count: number };
1021
+
const followsCount = followingResult?.count ?? 0;
1022
+
1023
+
// Query for post count from the app_bsky_feed_post table
1024
+
const postsStmt = db.prepare(`
1025
+
SELECT COUNT(*) as count
1026
+
FROM app_bsky_feed_post
1027
+
WHERE did = ?
1028
+
`);
1029
+
const postsResult = postsStmt.get(did) as { count: number };
1030
+
const postsCount = postsResult?.count ?? 0;
1031
+
1032
+
const result: ATPAPI.AppBskyActorDefs.ProfileViewDetailed = {
1033
+
$type: "app.bsky.actor.defs#profileViewDetailed",
1034
+
did: did,
1035
+
handle: identity.handle, // TODO: Resolve user identity here for the handle
1036
+
displayName: row.displayname ?? undefined,
1037
+
description: row.description ?? undefined,
1038
+
avatar: avatar, // TODO: create profile URL from resolved identity
1039
+
banner: banner, // same here
1040
+
followersCount: followersCount,
1041
+
followsCount: followsCount,
1042
+
postsCount: postsCount,
1043
+
//associated?: ProfileAssociated,
1044
+
//joinedViaStarterPack?: // AppBskyGraphDefs.StarterPackViewBasic;
1045
+
indexedAt: row.createdat
1046
+
? new Date(row.createdat).toISOString()
1047
+
: undefined,
1048
+
createdAt: row.createdat
1049
+
? new Date(row.createdat).toISOString()
1050
+
: undefined,
1051
+
//viewer?: ViewerState,
1052
+
//labels?: ComAtprotoLabelDefs.Label[],
1053
+
pinnedPost: undefined, //row.; // TODO: i forgot to put pinnedp posts in db schema oops
1054
+
//verification?: VerificationState,
1055
+
//status?: StatusView,
1056
+
};
1057
+
return result;
1058
+
}
1059
+
default:
1060
+
throw new Error("Invalid type");
1061
+
}
1062
+
}
1063
+
1064
+
// post hydration
1065
+
async queryPostView(
1066
+
uri: string
1067
+
): Promise<ATPAPI.AppBskyFeedDefs.PostView | undefined> {
1068
+
const URI = new AtUri(uri);
1069
+
const did = URI.host;
1070
+
if (!this.isRegisteredIndexUser(did)) return;
1071
+
const db = this.userManager.getDbForDid(did);
1072
+
if (!db) return;
1073
+
1074
+
const stmt = db.prepare(`
1075
+
SELECT *
1076
+
FROM app_bsky_feed_post
1077
+
WHERE uri = ?
1078
+
LIMIT 1;
1079
+
`);
1080
+
1081
+
const row = stmt.get(uri) as PostRow;
1082
+
const profileView = await this.queryProfileView(did, "Basic");
1083
+
if (!row || !row.cid || !profileView || !row.json) return;
1084
+
const value = JSON.parse(row.json) as ATPAPI.AppBskyFeedPost.Record;
1085
+
1086
+
const post: ATPAPI.AppBskyFeedDefs.PostView = {
1087
+
uri: row.uri,
1088
+
cid: row.cid,
1089
+
author: profileView,
1090
+
record: value,
1091
+
indexedAt: new Date(row.indexedat).toISOString(),
1092
+
embed: value.embed,
1093
+
};
1094
+
1095
+
return post;
1096
+
}
1097
+
1098
+
constructPostViewRef(
1099
+
uri: string
1100
+
): IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef {
1101
+
const post: IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef = {
1102
+
uri: uri,
1103
+
cid: "cid.invalid", // oh shit we dont know the cid TODO: major design flaw
1104
+
};
1105
+
1106
+
return post;
1107
+
}
1108
+
1109
+
async queryFeedViewPost(
1110
+
uri: string
1111
+
): Promise<ATPAPI.AppBskyFeedDefs.FeedViewPost | undefined> {
1112
+
const post = await this.queryPostView(uri);
1113
+
if (!post) return;
1114
+
1115
+
const feedviewpost: ATPAPI.AppBskyFeedDefs.FeedViewPost = {
1116
+
$type: "app.bsky.feed.defs#feedViewPost",
1117
+
post: post,
1118
+
//reply: ReplyRef,
1119
+
//reason: ,
1120
+
};
1121
+
1122
+
return feedviewpost;
1123
+
}
1124
+
1125
+
constructFeedViewPostRef(
1126
+
uri: string
1127
+
): IndexServerAPI.PartyWheyAppBskyFeedDefs.FeedViewPostRef {
1128
+
const post = this.constructPostViewRef(uri);
1129
+
1130
+
const feedviewpostref: IndexServerAPI.PartyWheyAppBskyFeedDefs.FeedViewPostRef =
1131
+
{
1132
+
$type: "party.whey.app.bsky.feed.defs#feedViewPostRef",
1133
+
post: post as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef>,
1134
+
};
1135
+
1136
+
return feedviewpostref;
1137
+
}
1138
+
1139
+
// user feedgens
1140
+
1141
+
async queryActorFeeds(
1142
+
did: string
1143
+
): Promise<ATPAPI.AppBskyFeedDefs.GeneratorView[]> {
1144
+
if (!this.isRegisteredIndexUser(did)) return [];
1145
+
const db = this.userManager.getDbForDid(did);
1146
+
if (!db) return [];
1147
+
1148
+
const stmt = db.prepare(`
1149
+
SELECT uri, cid, did, json, indexedat
1150
+
FROM app_bsky_feed_generator
1151
+
WHERE did = ?
1152
+
ORDER BY createdat DESC;
1153
+
`);
1154
+
1155
+
const rows = stmt.all(did) as unknown as GeneratorRow[];
1156
+
const creatorView = await this.queryProfileView(did, "Basic");
1157
+
if (!creatorView) return [];
1158
+
1159
+
return rows
1160
+
.map((row) => {
1161
+
try {
1162
+
if (!row.json) return;
1163
+
const record = JSON.parse(
1164
+
row.json
1165
+
) as ATPAPI.AppBskyFeedGenerator.Record;
1166
+
return {
1167
+
$type: "app.bsky.feed.defs#generatorView",
1168
+
uri: row.uri,
1169
+
cid: row.cid,
1170
+
did: row.did,
1171
+
creator: creatorView,
1172
+
displayName: record.displayName,
1173
+
description: record.description,
1174
+
descriptionFacets: record.descriptionFacets,
1175
+
avatar: record.avatar,
1176
+
likeCount: 0, // TODO: this should be easy
1177
+
indexedAt: new Date(row.indexedat).toISOString(),
1178
+
} as ATPAPI.AppBskyFeedDefs.GeneratorView;
1179
+
} catch {
1180
+
return undefined;
1181
+
}
1182
+
})
1183
+
.filter((v): v is ATPAPI.AppBskyFeedDefs.GeneratorView => !!v);
1184
+
}
1185
+
1186
+
async queryFeedGenerator(
1187
+
uri: string
1188
+
): Promise<ATPAPI.AppBskyFeedDefs.GeneratorView | undefined> {
1189
+
const gens = await this.queryFeedGenerators([uri]); // gens: GeneratorView[]
1190
+
return gens[0];
1191
+
}
1192
+
1193
+
async queryFeedGenerators(
1194
+
uris: string[]
1195
+
): Promise<ATPAPI.AppBskyFeedDefs.GeneratorView[]> {
1196
+
const generators: ATPAPI.AppBskyFeedDefs.GeneratorView[] = [];
1197
+
const urisByDid = new Map<string, string[]>();
1198
+
1199
+
for (const uri of uris) {
1200
+
try {
1201
+
const { host: did } = new AtUri(uri);
1202
+
if (!urisByDid.has(did)) {
1203
+
urisByDid.set(did, []);
1204
+
}
1205
+
urisByDid.get(did)!.push(uri);
1206
+
} catch {}
1207
+
}
1208
+
1209
+
for (const [did, didUris] of urisByDid.entries()) {
1210
+
if (!this.isRegisteredIndexUser(did)) continue;
1211
+
const db = this.userManager.getDbForDid(did);
1212
+
if (!db) continue;
1213
+
1214
+
const placeholders = didUris.map(() => "?").join(",");
1215
+
const stmt = db.prepare(`
1216
+
SELECT uri, cid, did, json, indexedat
1217
+
FROM app_bsky_feed_generator
1218
+
WHERE uri IN (${placeholders});
1219
+
`);
1220
+
1221
+
const rows = stmt.all(...didUris) as unknown as GeneratorRow[];
1222
+
if (rows.length === 0) continue;
1223
+
1224
+
const creatorView = await this.queryProfileView(did, "");
1225
+
if (!creatorView) continue;
1226
+
1227
+
for (const row of rows) {
1228
+
try {
1229
+
if (!row.json || !row.cid) continue;
1230
+
const record = JSON.parse(
1231
+
row.json
1232
+
) as ATPAPI.AppBskyFeedGenerator.Record;
1233
+
generators.push({
1234
+
$type: "app.bsky.feed.defs#generatorView",
1235
+
uri: row.uri,
1236
+
cid: row.cid,
1237
+
did: row.did,
1238
+
creator: creatorView,
1239
+
displayName: record.displayName,
1240
+
description: record.description,
1241
+
descriptionFacets: record.descriptionFacets,
1242
+
avatar: record.avatar as string | undefined,
1243
+
likeCount: 0,
1244
+
indexedAt: new Date(row.indexedat).toISOString(),
1245
+
});
1246
+
} catch {}
1247
+
}
1248
+
}
1249
+
return generators;
1250
+
}
1251
+
1252
+
// user feeds
1253
+
1254
+
async queryAuthorFeedPartial(
1255
+
did: string,
1256
+
cursor?: string
1257
+
): Promise<
1258
+
| {
1259
+
items: (
1260
+
| ATPAPI.AppBskyFeedDefs.FeedViewPost
1261
+
| IndexServerAPI.PartyWheyAppBskyFeedDefs.FeedViewPostRef
1262
+
)[];
1263
+
cursor: string | undefined;
1264
+
}
1265
+
| undefined
1266
+
> {
1267
+
if (!this.isRegisteredIndexUser(did)) return;
1268
+
const db = this.userManager.getDbForDid(did);
1269
+
if (!db) return;
1270
+
1271
+
const subquery = `
1272
+
SELECT uri, cid, indexedat, 'post' as type, null as subject
1273
+
FROM app_bsky_feed_post
1274
+
WHERE did = ?
1275
+
UNION ALL
1276
+
SELECT uri, cid, indexedat, 'repost' as type, subject
1277
+
FROM app_bsky_feed_repost
1278
+
WHERE did = ?
1279
+
`;
1280
+
1281
+
let query = `SELECT * FROM (${subquery}) as feed_items`;
1282
+
const params: (string | number)[] = [did, did];
1283
+
1284
+
if (cursor) {
1285
+
const [indexedat, cid] = cursor.split("::");
1286
+
query += ` WHERE (indexedat < ? OR (indexedat = ? AND cid < ?))`;
1287
+
params.push(parseInt(indexedat, 10), parseInt(indexedat, 10), cid);
1288
+
}
1289
+
1290
+
query += ` ORDER BY indexedat DESC, cid DESC LIMIT ${FEED_LIMIT}`;
1291
+
1292
+
const stmt = db.prepare(query);
1293
+
const rows = stmt.all(...params) as {
1294
+
uri: string;
1295
+
indexedat: number;
1296
+
cid: string;
1297
+
type: "post" | "repost";
1298
+
subject: string | null;
1299
+
}[];
1300
+
1301
+
const authorProfile = await this.queryProfileView(did, "Basic");
1302
+
1303
+
const items = await Promise.all(
1304
+
rows
1305
+
.map((row) => {
1306
+
if (row.type === "repost" && row.subject) {
1307
+
const subjectDid = new AtUri(row.subject).host;
1308
+
1309
+
const originalPost = this.handlesDid(subjectDid)
1310
+
? this.queryFeedViewPost(row.subject)
1311
+
: this.constructFeedViewPostRef(row.subject);
1312
+
1313
+
if (!originalPost || !authorProfile) return null;
1314
+
1315
+
return {
1316
+
post: originalPost,
1317
+
reason: {
1318
+
$type: "app.bsky.feed.defs#reasonRepost",
1319
+
by: authorProfile,
1320
+
indexedAt: new Date(row.indexedat).toISOString(),
1321
+
},
1322
+
};
1323
+
} else {
1324
+
return this.queryFeedViewPost(row.uri);
1325
+
}
1326
+
})
1327
+
.filter((p): p is Promise<ATPAPI.AppBskyFeedDefs.FeedViewPost> => !!p)
1328
+
);
1329
+
1330
+
const lastItem = rows[rows.length - 1];
1331
+
const nextCursor = lastItem
1332
+
? `${lastItem.indexedat}::${lastItem.cid}`
1333
+
: undefined;
1334
+
1335
+
return { items, cursor: nextCursor };
1336
+
}
1337
+
1338
+
queryListFeed(
1339
+
uri: string,
1340
+
cursor?: string
1341
+
):
1342
+
| {
1343
+
items: ATPAPI.AppBskyFeedDefs.FeedViewPost[];
1344
+
cursor: string | undefined;
1345
+
}
1346
+
| undefined {
1347
+
return { items: [], cursor: undefined };
1348
+
}
1349
+
1350
+
async queryActorLikesPartial(
1351
+
did: string,
1352
+
cursor?: string
1353
+
): Promise<
1354
+
| {
1355
+
items: (
1356
+
| ATPAPI.AppBskyFeedDefs.FeedViewPost
1357
+
| IndexServerAPI.PartyWheyAppBskyFeedDefs.FeedViewPostRef
1358
+
)[];
1359
+
cursor: string | undefined;
1360
+
}
1361
+
| undefined
1362
+
> {
1363
+
// early return only if the actor did is not registered
1364
+
if (!this.isRegisteredIndexUser(did)) return;
1365
+
const db = this.userManager.getDbForDid(did);
1366
+
if (!db) return;
1367
+
1368
+
let query = `
1369
+
SELECT subject, indexedat, cid
1370
+
FROM app_bsky_feed_like
1371
+
WHERE did = ?
1372
+
`;
1373
+
const params: (string | number)[] = [did];
1374
+
1375
+
if (cursor) {
1376
+
const [indexedat, cid] = cursor.split("::");
1377
+
query += ` AND (indexedat < ? OR (indexedat = ? AND cid < ?))`;
1378
+
params.push(parseInt(indexedat, 10), parseInt(indexedat, 10), cid);
1379
+
}
1380
+
1381
+
query += ` ORDER BY indexedat DESC, cid DESC LIMIT ${FEED_LIMIT}`;
1382
+
1383
+
const stmt = db.prepare(query);
1384
+
const rows = stmt.all(...params) as {
1385
+
subject: string;
1386
+
indexedat: number;
1387
+
cid: string;
1388
+
}[];
1389
+
1390
+
const items = await Promise.all(
1391
+
rows
1392
+
.map(async (row) => {
1393
+
const subjectDid = new AtUri(row.subject).host;
1394
+
1395
+
if (this.handlesDid(subjectDid)) {
1396
+
return await this.queryFeedViewPost(row.subject);
1397
+
} else {
1398
+
return this.constructFeedViewPostRef(row.subject);
1399
+
}
1400
+
})
1401
+
.filter(
1402
+
(
1403
+
p
1404
+
): p is Promise<
1405
+
| ATPAPI.AppBskyFeedDefs.FeedViewPost
1406
+
| IndexServerAPI.PartyWheyAppBskyFeedDefs.FeedViewPostRef
1407
+
> => !!p
1408
+
)
1409
+
);
1410
+
1411
+
const lastItem = rows[rows.length - 1];
1412
+
const nextCursor = lastItem
1413
+
? `${lastItem.indexedat}::${lastItem.cid}`
1414
+
: undefined;
1415
+
1416
+
return { items, cursor: nextCursor };
1417
+
}
1418
+
1419
+
// post metadata
1420
+
1421
+
async queryLikes(
1422
+
uri: string
1423
+
): Promise<ATPAPI.AppBskyFeedGetLikes.Like[] | undefined> {
1424
+
const postUri = new AtUri(uri);
1425
+
const postAuthorDid = postUri.hostname;
1426
+
if (!this.isRegisteredIndexUser(postAuthorDid)) return;
1427
+
const db = this.userManager.getDbForDid(postAuthorDid);
1428
+
if (!db) return;
1429
+
1430
+
const stmt = db.prepare(`
1431
+
SELECT b.srcdid, b.srcuri
1432
+
FROM backlink_skeleton AS b
1433
+
WHERE b.suburi = ? AND b.srccol = 'app_bsky_feed_like'
1434
+
ORDER BY b.id DESC;
1435
+
`);
1436
+
1437
+
const rows = stmt.all(uri) as unknown as BacklinkRow[];
1438
+
1439
+
return await Promise.all(
1440
+
rows
1441
+
.map(async (row) => {
1442
+
const actor = await this.queryProfileView(row.srcdid, "");
1443
+
if (!actor) return;
1444
+
1445
+
return {
1446
+
// TODO write indexedAt for spacedust indexes
1447
+
createdAt: new Date(Date.now()).toISOString(),
1448
+
indexedAt: new Date(Date.now()).toISOString(),
1449
+
actor: actor,
1450
+
};
1451
+
})
1452
+
.filter(
1453
+
(like): like is Promise<ATPAPI.AppBskyFeedGetLikes.Like> => !!like
1454
+
)
1455
+
);
1456
+
}
1457
+
1458
+
async queryReposts(
1459
+
uri: string
1460
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileView[]> {
1461
+
const postUri = new AtUri(uri);
1462
+
const postAuthorDid = postUri.hostname;
1463
+
if (!this.isRegisteredIndexUser(postAuthorDid)) return [];
1464
+
const db = this.userManager.getDbForDid(postAuthorDid);
1465
+
if (!db) return [];
1466
+
1467
+
const stmt = db.prepare(`
1468
+
SELECT srcdid
1469
+
FROM backlink_skeleton
1470
+
WHERE suburi = ? AND srccol = 'app_bsky_feed_repost'
1471
+
ORDER BY id DESC;
1472
+
`);
1473
+
1474
+
const rows = stmt.all(uri) as { srcdid: string }[];
1475
+
1476
+
return await Promise.all(
1477
+
rows
1478
+
.map(async (row) => await this.queryProfileView(row.srcdid, ""))
1479
+
.filter((p): p is Promise<ATPAPI.AppBskyActorDefs.ProfileView> => !!p)
1480
+
);
1481
+
}
1482
+
1483
+
async queryQuotes(
1484
+
uri: string
1485
+
): Promise<ATPAPI.AppBskyFeedDefs.FeedViewPost[]> {
1486
+
const postUri = new AtUri(uri);
1487
+
const postAuthorDid = postUri.hostname;
1488
+
if (!this.isRegisteredIndexUser(postAuthorDid)) return [];
1489
+
const db = this.userManager.getDbForDid(postAuthorDid);
1490
+
if (!db) return [];
1491
+
1492
+
const stmt = db.prepare(`
1493
+
SELECT srcuri
1494
+
FROM backlink_skeleton
1495
+
WHERE suburi = ? AND srccol = 'app_bsky_feed_post' AND srcfield = 'quote'
1496
+
ORDER BY id DESC;
1497
+
`);
1498
+
1499
+
const rows = stmt.all(uri) as { srcuri: string }[];
1500
+
1501
+
return await Promise.all(
1502
+
rows
1503
+
.map(async (row) => await this.queryFeedViewPost(row.srcuri))
1504
+
.filter((p): p is Promise<ATPAPI.AppBskyFeedDefs.FeedViewPost> => !!p)
1505
+
);
1506
+
}
1507
+
async _getPostViewUnion(
1508
+
uri: string
1509
+
): Promise<
1510
+
| ATPAPI.AppBskyFeedDefs.PostView
1511
+
| IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef
1512
+
| undefined
1513
+
> {
1514
+
try {
1515
+
const postDid = new AtUri(uri).hostname;
1516
+
if (this.handlesDid(postDid)) {
1517
+
return await this.queryPostView(uri);
1518
+
} else {
1519
+
return this.constructPostViewRef(uri);
1520
+
}
1521
+
} catch (_e) {
1522
+
return undefined;
1523
+
}
1524
+
}
1525
+
async queryPostThreadPartial(
1526
+
uri: string
1527
+
): Promise<
1528
+
| IndexServerTypes.PartyWheyAppBskyFeedGetPostThreadPartial.OutputSchema
1529
+
| undefined
1530
+
> {
1531
+
const post = await this._getPostViewUnion(uri);
1532
+
1533
+
if (!post) {
1534
+
return {
1535
+
thread: {
1536
+
$type: "app.bsky.feed.defs#notFoundPost",
1537
+
uri: uri,
1538
+
notFound: true,
1539
+
} as ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.NotFoundPost>,
1540
+
};
1541
+
}
1542
+
1543
+
const thread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef = {
1544
+
$type: "party.whey.app.bsky.feed.defs#threadViewPostRef",
1545
+
post: post as
1546
+
| ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView>
1547
+
| IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef>,
1548
+
replies: [],
1549
+
};
1550
+
1551
+
let current = thread;
1552
+
// we can only climb the parent tree if we have the full post record.
1553
+
// which is not implemented yet (sad i know)
1554
+
if (
1555
+
isPostView(current.post) &&
1556
+
isFeedPostRecord(current.post.record) &&
1557
+
current.post.record?.reply?.parent?.uri
1558
+
) {
1559
+
let parentUri: string | undefined = current.post.record.reply.parent.uri;
1560
+
1561
+
// keep climbing as long as we find a valid parent post.
1562
+
while (parentUri) {
1563
+
const parentPost = await this._getPostViewUnion(parentUri);
1564
+
if (!parentPost) break; // stop if a parent in the chain is not found.
1565
+
1566
+
const parentThread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef =
1567
+
{
1568
+
$type: "party.whey.app.bsky.feed.defs#threadViewPostRef",
1569
+
post: parentPost as ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView>,
1570
+
replies: [
1571
+
current as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>,
1572
+
],
1573
+
};
1574
+
current.parent =
1575
+
parentThread as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>;
1576
+
current = parentThread;
1577
+
1578
+
// check if the new current post has a parent to continue the loop
1579
+
parentUri =
1580
+
isPostView(current.post) && isFeedPostRecord(current.post.record)
1581
+
? current.post.record?.reply?.parent?.uri
1582
+
: undefined;
1583
+
}
1584
+
}
1585
+
1586
+
const seenUris = new Set<string>();
1587
+
const fetchReplies = async (
1588
+
parentThread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef
1589
+
) => {
1590
+
if (!parentThread.post || !("uri" in parentThread.post)) {
1591
+
return;
1592
+
}
1593
+
if (seenUris.has(parentThread.post.uri)) return;
1594
+
seenUris.add(parentThread.post.uri);
1595
+
1596
+
const parentUri = new AtUri(parentThread.post.uri);
1597
+
const parentAuthorDid = parentUri.hostname;
1598
+
1599
+
// replies can only be discovered for local posts where we have the backlink data
1600
+
if (!this.handlesDid(parentAuthorDid)) return;
1601
+
1602
+
const db = this.userManager.getDbForDid(parentAuthorDid);
1603
+
if (!db) return;
1604
+
1605
+
const stmt = db.prepare(`
1606
+
SELECT srcuri
1607
+
FROM backlink_skeleton
1608
+
WHERE suburi = ? AND srccol = 'app_bsky_feed_post' AND srcfield = 'replyparent'
1609
+
`);
1610
+
const replyRows = stmt.all(parentThread.post.uri) as { srcuri: string }[];
1611
+
1612
+
const replies = await Promise.all(
1613
+
replyRows
1614
+
.map(async (row) => await this._getPostViewUnion(row.srcuri))
1615
+
.filter(
1616
+
(
1617
+
p
1618
+
): p is Promise<
1619
+
| ATPAPI.AppBskyFeedDefs.PostView
1620
+
| IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef
1621
+
> => !!p
1622
+
)
1623
+
);
1624
+
1625
+
for (const replyPost of replies) {
1626
+
const replyThread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef =
1627
+
{
1628
+
$type: "party.whey.app.bsky.feed.defs#threadViewPostRef",
1629
+
post: replyPost as
1630
+
| ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView>
1631
+
| IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef>,
1632
+
parent:
1633
+
parentThread as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>,
1634
+
replies: [],
1635
+
};
1636
+
parentThread.replies?.push(
1637
+
replyThread as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>
1638
+
);
1639
+
fetchReplies(replyThread); // recurse
1640
+
}
1641
+
};
1642
+
1643
+
fetchReplies(thread);
1644
+
1645
+
const returned =
1646
+
current as unknown as IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef;
1647
+
1648
+
return {
1649
+
thread:
1650
+
returned as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>,
1651
+
};
1652
+
}
1653
+
1654
+
/**
1655
+
* please do not use this, use openDbForDid() instead
1656
+
* @param did
1657
+
* @returns
1658
+
*/
1659
+
internalCreateDbForDid(did: string): Database {
1660
+
const path = `${this.config.baseDbPath}/${did}.sqlite`;
1661
+
const db = new Database(path);
1662
+
setupUserDb(db);
1663
+
//await db.exec(/* CREATE IF NOT EXISTS statements */);
1664
+
return db;
1665
+
}
1666
+
/**
1667
+
* @deprecated use handlesDid() instead
1668
+
* @param did
1669
+
* @returns
1670
+
*/
1671
+
isRegisteredIndexUser(did: string): boolean {
1672
+
const stmt = this.systemDB.prepare(`
1673
+
SELECT 1
1674
+
FROM users
1675
+
WHERE did = ?
1676
+
AND onboardingstatus != 'onboarding-backfill'
1677
+
LIMIT 1;
1678
+
`);
1679
+
const result = stmt.value<[number]>(did);
1680
+
const exists = result !== undefined;
1681
+
return exists;
1682
+
}
1683
+
}
1684
+
1685
+
export class IndexServerUserManager {
1686
+
public indexServer: IndexServer;
1687
+
1688
+
constructor(indexServer: IndexServer) {
1689
+
this.indexServer = indexServer;
1690
+
}
1691
+
1692
+
public users = new Map<string, UserIndexServer>();
1693
+
public handlesDid(did: string): boolean {
1694
+
return this.users.has(did);
1695
+
}
1696
+
1697
+
/*async*/ addUser(did: string) {
1698
+
if (this.users.has(did)) return;
1699
+
const instance = new UserIndexServer(this, did);
1700
+
//await instance.initialize();
1701
+
this.users.set(did, instance);
1702
+
}
1703
+
1704
+
// async handleRequest({
1705
+
// did,
1706
+
// route,
1707
+
// req,
1708
+
// }: {
1709
+
// did: string;
1710
+
// route: string;
1711
+
// req: Request;
1712
+
// }) {
1713
+
// if (!this.users.has(did)) await this.addUser(did);
1714
+
// const user = this.users.get(did)!;
1715
+
// return await user.handleHttpRequest(route, req);
1716
+
// }
1717
+
1718
+
removeUser(did: string) {
1719
+
const instance = this.users.get(did);
1720
+
if (!instance) return;
1721
+
/*await*/ instance.shutdown();
1722
+
this.users.delete(did);
1723
+
}
1724
+
1725
+
getDbForDid(did: string): Database | null {
1726
+
if (!this.users.has(did)) {
1727
+
return null;
1728
+
}
1729
+
return this.users.get(did)?.db ?? null;
1730
+
}
1731
+
1732
+
coldStart(db: Database) {
1733
+
const rows = db.prepare("SELECT did FROM users").all();
1734
+
for (const row of rows) {
1735
+
this.addUser(row.did);
1736
+
}
1737
+
}
1738
+
}
1739
+
1740
+
class UserIndexServer {
1741
+
public indexServerUserManager: IndexServerUserManager;
1742
+
did: string;
1743
+
db: Database; // | undefined;
1744
+
jetstream: JetstreamManager; // | undefined;
1745
+
spacedust: SpacedustManager; // | undefined;
1746
+
1747
+
constructor(indexServerUserManager: IndexServerUserManager, did: string) {
1748
+
this.did = did;
1749
+
this.indexServerUserManager = indexServerUserManager;
1750
+
this.db = this.indexServerUserManager.indexServer.internalCreateDbForDid(
1751
+
this.did
1752
+
);
1753
+
// should probably put the params of exactly what were listening to here
1754
+
this.jetstream = new JetstreamManager((msg) => {
1755
+
console.log("Received Jetstream message: ", msg);
1756
+
1757
+
const op = msg.commit.operation;
1758
+
const doer = msg.did;
1759
+
const rev = msg.commit.rev;
1760
+
const aturi = `${msg.did}/${msg.commit.collection}/${msg.commit.rkey}`;
1761
+
const value = msg.commit.record;
1762
+
1763
+
if (!doer || !value) return;
1764
+
this.indexServerUserManager.indexServer.indexServerIndexer({
1765
+
op,
1766
+
doer,
1767
+
cid: msg.commit.cid,
1768
+
rev,
1769
+
aturi,
1770
+
value,
1771
+
indexsrc: `jetstream-${op}`,
1772
+
db: this.db,
1773
+
});
1774
+
});
1775
+
this.jetstream.start({
1776
+
// for realsies pls get from db or something instead of this shit
1777
+
wantedDids: [
1778
+
this.did,
1779
+
// "did:plc:mn45tewwnse5btfftvd3powc",
1780
+
// "did:plc:yy6kbriyxtimkjqonqatv2rb",
1781
+
// "did:plc:zzhzjga3ab5fcs2vnsv2ist3",
1782
+
// "did:plc:jz4ibztn56hygfld6j6zjszg",
1783
+
],
1784
+
wantedCollections: [
1785
+
"app.bsky.actor.profile",
1786
+
"app.bsky.feed.generator",
1787
+
"app.bsky.feed.like",
1788
+
"app.bsky.feed.post",
1789
+
"app.bsky.feed.repost",
1790
+
"app.bsky.feed.threadgate",
1791
+
"app.bsky.graph.block",
1792
+
"app.bsky.graph.follow",
1793
+
"app.bsky.graph.list",
1794
+
"app.bsky.graph.listblock",
1795
+
"app.bsky.graph.listitem",
1796
+
"app.bsky.notification.declaration",
1797
+
],
1798
+
});
1799
+
//await connectToJetstream(this.did, this.db);
1800
+
this.spacedust = new SpacedustManager((msg: SpacedustLinkMessage) => {
1801
+
console.log("Received Spacedust message: ", msg);
1802
+
const operation = msg.link.operation;
1803
+
1804
+
const sourceURI = new AtUri(msg.link.source_record);
1805
+
const srcUri = msg.link.source_record;
1806
+
const srcDid = sourceURI.host;
1807
+
const srcField = msg.link.source;
1808
+
const srcCol = sourceURI.collection;
1809
+
const subjectURI = new AtUri(msg.link.subject);
1810
+
const subUri = msg.link.subject;
1811
+
const subDid = subjectURI.host;
1812
+
const subCol = subjectURI.collection;
1813
+
1814
+
if (operation === "delete") {
1815
+
this.db.run(
1816
+
`DELETE FROM backlink_skeleton
1817
+
WHERE srcuri = ? AND srcfield = ? AND suburi = ?`,
1818
+
[srcUri, srcField, subUri]
1819
+
);
1820
+
} else if (operation === "create") {
1821
+
this.db.run(
1822
+
`INSERT OR REPLACE INTO backlink_skeleton (
1823
+
srcuri,
1824
+
srcdid,
1825
+
srcfield,
1826
+
srccol,
1827
+
suburi,
1828
+
subdid,
1829
+
subcol,
1830
+
indexedAt
1831
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
1832
+
[
1833
+
srcUri, // full AT URI of the source record
1834
+
srcDid, // did: of the source
1835
+
srcField, // e.g., "reply.parent.uri" or "facets.features.did"
1836
+
srcCol, // e.g., "app.bsky.feed.post"
1837
+
subUri, // full AT URI of the subject (linked record)
1838
+
subDid, // did: of the subject
1839
+
subCol, // subject collection (can be inferred or passed)
1840
+
Date.now()
1841
+
]
1842
+
);
1843
+
}
1844
+
});
1845
+
this.spacedust.start({
1846
+
wantedSources: [
1847
+
"app.bsky.feed.like:subject.uri", // like
1848
+
"app.bsky.feed.like:via.uri", // liked repost
1849
+
"app.bsky.feed.repost:subject.uri", // repost
1850
+
"app.bsky.feed.repost:via.uri", // reposted repost
1851
+
"app.bsky.feed.post:reply.root.uri", // thread OP
1852
+
"app.bsky.feed.post:reply.parent.uri", // direct parent
1853
+
"app.bsky.feed.post:embed.media.record.record.uri", // quote with media
1854
+
"app.bsky.feed.post:embed.record.uri", // quote without media
1855
+
"app.bsky.feed.threadgate:post", // threadgate subject
1856
+
"app.bsky.feed.threadgate:hiddenReplies", // threadgate items (array)
1857
+
"app.bsky.feed.post:facets.features.did", // facet item (array): mention
1858
+
"app.bsky.graph.block:subject", // blocks
1859
+
"app.bsky.graph.follow:subject", // follow
1860
+
"app.bsky.graph.listblock:subject", // list item (blocks)
1861
+
"app.bsky.graph.listblock:list", // blocklist mention (might not exist)
1862
+
"app.bsky.graph.listitem:subject", // list item (blocks)
1863
+
"app.bsky.graph.listitem:list", // list mention
1864
+
],
1865
+
// should be getting from DB but whatever right
1866
+
wantedSubjects: [
1867
+
// as noted i dont need to write down each post, just the user to listen to !
1868
+
// hell yeah
1869
+
// "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvybv7b6ic2h",
1870
+
// "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvybws4avc2h",
1871
+
// "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvvkcxcscs2h",
1872
+
// "at://did:plc:yy6kbriyxtimkjqonqatv2rb/app.bsky.feed.post/3l63ogxocq42f",
1873
+
// "at://did:plc:yy6kbriyxtimkjqonqatv2rb/app.bsky.feed.post/3lw3wamvflu23",
1874
+
],
1875
+
wantedSubjectDids: [
1876
+
this.did,
1877
+
//"did:plc:mn45tewwnse5btfftvd3powc",
1878
+
//"did:plc:yy6kbriyxtimkjqonqatv2rb",
1879
+
//"did:plc:zzhzjga3ab5fcs2vnsv2ist3",
1880
+
//"did:plc:jz4ibztn56hygfld6j6zjszg",
1881
+
],
1882
+
instant: ["true"]
1883
+
});
1884
+
//await connectToConstellation(this.did, this.db);
6
1885
}
7
1886
8
-
return new Response("Not Found", { status: 404 });
9
-
}
1887
+
// initialize() {
1888
+
1889
+
// }
1890
+
1891
+
// async handleHttpRequest(route: string, req: Request): Promise<Response> {
1892
+
// if (route === "posts") {
1893
+
// const posts = await this.queryPosts();
1894
+
// return new Response(JSON.stringify(posts), {
1895
+
// headers: { "content-type": "application/json" },
1896
+
// });
1897
+
// }
1898
+
1899
+
// return new Response("Unknown route", { status: 404 });
1900
+
// }
1901
+
1902
+
// private async queryPosts() {
1903
+
// return this.db.run(
1904
+
// "SELECT * FROM posts ORDER BY created_at DESC LIMIT 100"
1905
+
// );
1906
+
// }
1907
+
1908
+
shutdown() {
1909
+
this.jetstream.stop();
1910
+
this.spacedust.stop();
1911
+
this.db.close?.();
1912
+
}
1913
+
}
1914
+
1915
+
// /**
1916
+
// * please do not use this, use openDbForDid() instead
1917
+
// * @param did
1918
+
// * @returns
1919
+
// */
1920
+
// function internalCreateDbForDid(did: string): Database {
1921
+
// const path = `./dbs/${did}.sqlite`;
1922
+
// const db = new Database(path);
1923
+
// setupUserDb(db);
1924
+
// //await db.exec(/* CREATE IF NOT EXISTS statements */);
1925
+
// return db;
1926
+
// }
1927
+
1928
+
// function getDbForDid(did: string): Database | undefined {
1929
+
// const db = indexerUserManager.getDbForDid(did);
1930
+
// if (!db) return;
1931
+
// return db;
1932
+
// }
1933
+
1934
+
// async function connectToJetstream(did: string, db: Database): Promise<WebSocket> {
1935
+
// const url = `${jetstreamurl}/xrpc/com.atproto.sync.subscribeRepos?did=${did}`;
1936
+
// const ws = new WebSocket(url);
1937
+
// ws.onmessage = (msg) => {
1938
+
// //handleJetstreamMessage(evt.data, db)
1939
+
1940
+
// const op = msg.commit.operation;
1941
+
// const doer = msg.did;
1942
+
// const rev = msg.commit.rev;
1943
+
// const aturi = `${msg.did}/${msg.commit.collection}/${msg.commit.rkey}`;
1944
+
// const value = msg.commit.record;
1945
+
1946
+
// if (!doer || !value) return;
1947
+
// indexServerIndexer({
1948
+
// op,
1949
+
// doer,
1950
+
// rev,
1951
+
// aturi,
1952
+
// value,
1953
+
// indexsrc: "onboarding_backfill",
1954
+
// userdbname: did,
1955
+
// })
1956
+
// };
1957
+
1958
+
// return ws;
1959
+
// }
1960
+
1961
+
// async function connectToConstellation(did: string, db: D1Database): Promise<WebSocket> {
1962
+
// const url = `wss://bsky.social/xrpc/com.atproto.sync.subscribeLabels?did=${did}`;
1963
+
// const ws = new WebSocket(url);
1964
+
// ws.onmessage = (evt) => handleConstellationMessage(evt.data, db);
1965
+
// return ws;
1966
+
// }
1967
+
1968
+
type PostRow = {
1969
+
uri: string;
1970
+
did: string;
1971
+
cid: string | null;
1972
+
rev: string | null;
1973
+
createdat: number | null;
1974
+
indexedat: number;
1975
+
json: string | null;
1976
+
1977
+
text: string | null;
1978
+
replyroot: string | null;
1979
+
replyparent: string | null;
1980
+
quote: string | null;
1981
+
1982
+
imagecount: number | null;
1983
+
image1cid: string | null;
1984
+
image1mime: string | null;
1985
+
image1aspect: string | null;
1986
+
image2cid: string | null;
1987
+
image2mime: string | null;
1988
+
image2aspect: string | null;
1989
+
image3cid: string | null;
1990
+
image3mime: string | null;
1991
+
image3aspect: string | null;
1992
+
image4cid: string | null;
1993
+
image4mime: string | null;
1994
+
image4aspect: string | null;
1995
+
1996
+
videocount: number | null;
1997
+
videocid: string | null;
1998
+
videomime: string | null;
1999
+
videoaspect: string | null;
2000
+
};
2001
+
2002
+
type ProfileRow = {
2003
+
uri: string;
2004
+
cid: string | null;
2005
+
rev: string | null;
2006
+
createdat: number | null;
2007
+
indexedat: number;
2008
+
json: string | null;
2009
+
displayname: string | null;
2010
+
description: string | null;
2011
+
avatarcid: string | null;
2012
+
avatarmime: string | null;
2013
+
bannercid: string | null;
2014
+
bannermime: string | null;
2015
+
};
2016
+
2017
+
type linksQuery = {
2018
+
target: string;
2019
+
collection: string;
2020
+
path: string;
2021
+
cursor?: string;
2022
+
};
2023
+
type linksRecord = {
2024
+
did: string;
2025
+
collection: string;
2026
+
rkey: string;
2027
+
};
2028
+
type linksRecordsResponse = {
2029
+
total: string;
2030
+
linking_records: linksRecord[];
2031
+
cursor?: string;
2032
+
};
2033
+
type linksDidsResponse = {
2034
+
total: string;
2035
+
linking_dids: string[];
2036
+
cursor?: string;
2037
+
};
2038
+
type linksCountResponse = {
2039
+
total: string;
2040
+
};
2041
+
type linksAllResponse = {
2042
+
links: Record<
2043
+
string,
2044
+
Record<
2045
+
string,
2046
+
{
2047
+
records: number;
2048
+
distinct_dids: number;
2049
+
}
2050
+
>
2051
+
>;
2052
+
};
2053
+
2054
+
type linksAllQuery = {
2055
+
target: string;
2056
+
};
2057
+
2058
+
const SQL = {
2059
+
links: `
2060
+
SELECT srcuri, srcdid, srccol
2061
+
FROM backlink_skeleton
2062
+
WHERE suburi = ? AND srccol = ? AND srcfield = ?
2063
+
`,
2064
+
distinctDids: `
2065
+
SELECT DISTINCT srcdid
2066
+
FROM backlink_skeleton
2067
+
WHERE suburi = ? AND srccol = ? AND srcfield = ?
2068
+
`,
2069
+
count: `
2070
+
SELECT COUNT(*) as total
2071
+
FROM backlink_skeleton
2072
+
WHERE suburi = ? AND srccol = ? AND srcfield = ?
2073
+
`,
2074
+
countDistinctDids: `
2075
+
SELECT COUNT(DISTINCT srcdid) as total
2076
+
FROM backlink_skeleton
2077
+
WHERE suburi = ? AND srccol = ? AND srcfield = ?
2078
+
`,
2079
+
all: `
2080
+
SELECT suburi, srccol, COUNT(*) as records, COUNT(DISTINCT srcdid) as distinct_dids
2081
+
FROM backlink_skeleton
2082
+
WHERE suburi = ?
2083
+
GROUP BY suburi, srccol
2084
+
`,
2085
+
};
2086
+
2087
+
export function isDid(str: string): boolean {
2088
+
return typeof str === "string" && str.startsWith("did:");
2089
+
}
2090
+
2091
+
function isFeedPostRecord(
2092
+
post: unknown
2093
+
): post is ATPAPI.AppBskyFeedPost.Record {
2094
+
return (
2095
+
typeof post === "object" &&
2096
+
post !== null &&
2097
+
"$type" in post &&
2098
+
(post as any).$type === "app.bsky.feed.post"
2099
+
);
2100
+
}
2101
+
2102
+
function isImageEmbed(embed: unknown): embed is ATPAPI.AppBskyEmbedImages.Main {
2103
+
return (
2104
+
typeof embed === "object" &&
2105
+
embed !== null &&
2106
+
"$type" in embed &&
2107
+
(embed as any).$type === "app.bsky.embed.images"
2108
+
);
2109
+
}
2110
+
2111
+
function isVideoEmbed(embed: unknown): embed is ATPAPI.AppBskyEmbedVideo.Main {
2112
+
return (
2113
+
typeof embed === "object" &&
2114
+
embed !== null &&
2115
+
"$type" in embed &&
2116
+
(embed as any).$type === "app.bsky.embed.video"
2117
+
);
2118
+
}
2119
+
2120
+
function isRecordEmbed(
2121
+
embed: unknown
2122
+
): embed is ATPAPI.AppBskyEmbedRecord.Main {
2123
+
return (
2124
+
typeof embed === "object" &&
2125
+
embed !== null &&
2126
+
"$type" in embed &&
2127
+
(embed as any).$type === "app.bsky.embed.record"
2128
+
);
2129
+
}
2130
+
2131
+
function isRecordWithMediaEmbed(
2132
+
embed: unknown
2133
+
): embed is ATPAPI.AppBskyEmbedRecordWithMedia.Main {
2134
+
return (
2135
+
typeof embed === "object" &&
2136
+
embed !== null &&
2137
+
"$type" in embed &&
2138
+
(embed as any).$type === "app.bsky.embed.recordWithMedia"
2139
+
);
2140
+
}
2141
+
2142
+
export function uncid(anything: any): string | null {
2143
+
return (
2144
+
((anything as Record<string, unknown>)?.["$link"] as string | null) || null
2145
+
);
2146
+
}
2147
+
2148
+
function extractImages(embed: unknown) {
2149
+
if (isImageEmbed(embed)) return embed.images;
2150
+
if (isRecordWithMediaEmbed(embed) && isImageEmbed(embed.media))
2151
+
return embed.media.images;
2152
+
return [];
2153
+
}
2154
+
2155
+
function extractVideo(embed: unknown) {
2156
+
if (isVideoEmbed(embed)) return embed;
2157
+
if (isRecordWithMediaEmbed(embed) && isVideoEmbed(embed.media))
2158
+
return embed.media;
2159
+
return null;
2160
+
}
2161
+
2162
+
function extractQuoteUri(embed: unknown): string | null {
2163
+
if (isRecordEmbed(embed)) return embed.record.uri;
2164
+
if (isRecordWithMediaEmbed(embed)) return embed.record.record.uri;
2165
+
return null;
2166
+
}
+255
main-index.ts
+255
main-index.ts
···
1
+
import { setupAuth, getAuthenticatedDid, authVerifier } from "./utils/auth.ts";
2
+
import { setupSystemDb } from "./utils/dbsystem.ts";
3
+
import { didDocument } from "./utils/diddoc.ts";
4
+
import { cachedFetch, searchParamsToJson, withCors } from "./utils/server.ts";
5
+
import { IndexServer, IndexServerConfig } from "./indexserver.ts";
6
+
import { extractDid } from "./utils/identity.ts";
7
+
import { config } from "./config.ts";
8
+
import { compile, devWatch } from "./shared-landing/build.ts";
9
+
// ------------------------------------------
10
+
// AppView Setup
11
+
// ------------------------------------------
12
+
13
+
const indexServerConfig: IndexServerConfig = {
14
+
baseDbPath: "./dbs/index/registered-users", // The directory for user databases
15
+
systemDbPath: "./dbs/index/registered-users/system.db", // The path for the main system database
16
+
};
17
+
export const genericIndexServer = new IndexServer(indexServerConfig);
18
+
setupSystemDb(genericIndexServer.systemDB);
19
+
20
+
let { js, html, css } = await compile({
21
+
target: "index",
22
+
initialData: {
23
+
config: config.indexServer,
24
+
users: (await genericIndexServer.unspeccedGetRegisteredUsers()) ?? [],
25
+
},
26
+
});
27
+
28
+
// add me lol
29
+
genericIndexServer.systemDB.exec(`
30
+
INSERT OR IGNORE INTO users (did, role, registrationdate, onboardingstatus)
31
+
VALUES (
32
+
'did:plc:mn45tewwnse5btfftvd3powc',
33
+
'admin',
34
+
datetime('now'),
35
+
'ready'
36
+
);
37
+
38
+
INSERT OR IGNORE INTO users (did, role, registrationdate, onboardingstatus)
39
+
VALUES (
40
+
'did:web:did12.whey.party',
41
+
'admin',
42
+
datetime('now'),
43
+
'ready'
44
+
);
45
+
`);
46
+
47
+
genericIndexServer.start();
48
+
49
+
// ------------------------------------------
50
+
// XRPC Method Implementations
51
+
// ------------------------------------------
52
+
53
+
// const indexServerRoutes = new Set([
54
+
// "/xrpc/app.bsky.actor.getProfile",
55
+
// "/xrpc/app.bsky.actor.getProfiles",
56
+
// "/xrpc/app.bsky.feed.getActorFeeds",
57
+
// "/xrpc/app.bsky.feed.getFeedGenerator",
58
+
// "/xrpc/app.bsky.feed.getFeedGenerators",
59
+
// "/xrpc/app.bsky.feed.getPosts",
60
+
// "/xrpc/party.whey.app.bsky.feed.getActorLikesPartial",
61
+
// "/xrpc/party.whey.app.bsky.feed.getAuthorFeedPartial",
62
+
// "/xrpc/party.whey.app.bsky.feed.getLikesPartial",
63
+
// "/xrpc/party.whey.app.bsky.feed.getPostThreadPartial",
64
+
// "/xrpc/party.whey.app.bsky.feed.getQuotesPartial",
65
+
// "/xrpc/party.whey.app.bsky.feed.getRepostedByPartial",
66
+
// // more federated endpoints, not planned yet, lexicons will come later
67
+
// /*
68
+
// app.bsky.graph.getLists // doesnt need to because theres no items[], and its self ProfileViewBasic
69
+
// app.bsky.graph.getList // needs to be Partial-ed (items[] union with ProfileViewRef)
70
+
// app.bsky.graph.getActorStarterPacks // maybe doesnt need to be Partial-ed because its self ProfileViewBasic
71
+
72
+
// app.bsky.feed.getListFeed // uhh actually already exists its getListFeedPartial
73
+
// */
74
+
// "/xrpc/party.whey.app.bsky.feed.getListFeedPartial",
75
+
// ]);
76
+
const placeholderselfcheckstatus = {
77
+
"#skylite_index:/xrpc/app.bsky.actor.getProfile": "green",
78
+
"#skylite_index:/xrpc/app.bsky.actor.getProfiles": "green",
79
+
"#skylite_index:/xrpc/app.bsky.feed.getActorFeeds": "green",
80
+
"#skylite_index:/xrpc/app.bsky.feed.getFeedGenerator": "green",
81
+
"#skylite_index:/xrpc/app.bsky.feed.getFeedGenerators": "green",
82
+
"#skylite_index:/xrpc/app.bsky.feed.getPosts": "green",
83
+
"#skylite_index:/xrpc/app.bsky.graph.getLists": "black",
84
+
"#skylite_index:/xrpc/app.bsky.graph.getList": "black",
85
+
"#skylite_index:/xrpc/app.bsky.graph.getActorStarterPacks": "black",
86
+
"#skylite_index:/xrpc/party.whey.app.bsky.feed.getActorLikesPartial": "green",
87
+
"#skylite_index:/xrpc/party.whey.app.bsky.feed.getAuthorFeedPartial": "green",
88
+
"#skylite_index:/xrpc/party.whey.app.bsky.feed.getLikesPartial": "orange",
89
+
"#skylite_index:/xrpc/party.whey.app.bsky.feed.getPostThreadPartial": "green",
90
+
"#skylite_index:/xrpc/party.whey.app.bsky.feed.getQuotesPartial": "orange",
91
+
"#skylite_index:/xrpc/party.whey.app.bsky.feed.getRepostedByPartial":
92
+
"orange",
93
+
"#skylite_index:/xrpc/party.whey.app.bsky.feed.getListFeedPartial": "black",
94
+
95
+
"constellation:/links": "green",
96
+
"constellation:/links/distinct-dids": "green",
97
+
"constellation:/links/count": "green",
98
+
"constellation:/links/count/distinct-dids": "green",
99
+
"constellation:/links/all": "green",
100
+
};
101
+
102
+
//console.log("ready to serve");
103
+
Deno.serve(
104
+
{ port: config.indexServer.port },
105
+
async (req: Request): Promise<Response> => {
106
+
const url = new URL(req.url);
107
+
const pathname = url.pathname;
108
+
const searchParams = searchParamsToJson(url.searchParams);
109
+
110
+
const publicdir = "/public";
111
+
if (pathname.startsWith(publicdir)) {
112
+
const filepath = decodeURIComponent(pathname.slice(publicdir.length));
113
+
try {
114
+
const file = await Deno.open("./public" + filepath, { read: true });
115
+
return new Response(file.readable);
116
+
} catch {
117
+
return new Response("404 Not Found", { status: 404 });
118
+
}
119
+
}
120
+
121
+
const todopleasespecthis = "/_unspecced";
122
+
if (pathname.startsWith(todopleasespecthis)) {
123
+
const unspeccedroute = decodeURIComponent(
124
+
pathname.slice(todopleasespecthis.length)
125
+
);
126
+
if (unspeccedroute === "/config") {
127
+
const safeconfig = {
128
+
inviteOnly: config.indexServer.inviteOnly,
129
+
//port: number,
130
+
did: config.indexServer.did,
131
+
host: config.indexServer.host,
132
+
};
133
+
return new Response(JSON.stringify(safeconfig), {
134
+
headers: withCors({
135
+
"content-type": "application/json; charset=utf-8",
136
+
}),
137
+
});
138
+
}
139
+
if (unspeccedroute === "/users") {
140
+
const res = await genericIndexServer.unspeccedGetRegisteredUsers();
141
+
return new Response(JSON.stringify(res), {
142
+
headers: withCors({
143
+
"content-type": "application/json; charset=utf-8",
144
+
}),
145
+
});
146
+
}
147
+
if (unspeccedroute === "/apitest") {
148
+
return new Response(JSON.stringify(placeholderselfcheckstatus), {
149
+
headers: withCors({
150
+
"content-type": "application/json; charset=utf-8",
151
+
}),
152
+
});
153
+
}
154
+
}
155
+
156
+
if (html && js) {
157
+
if (pathname === "/" || pathname === "") {
158
+
return new Response(html, {
159
+
headers: withCors({ "content-type": "text/html; charset=utf-8" }),
160
+
});
161
+
}
162
+
if (pathname === "/landing-index.js") {
163
+
return new Response(js, {
164
+
headers: withCors({
165
+
"content-type": "application/javascript; charset=utf-8",
166
+
}),
167
+
});
168
+
}
169
+
} else {
170
+
if (pathname === "/" || pathname === "") {
171
+
return new Response(`server is compiling your webpage. loading...`, {
172
+
headers: withCors({ "content-type": "text/html; charset=utf-8" }),
173
+
});
174
+
}
175
+
}
176
+
if (pathname === "/app.css") {
177
+
return new Response(css, {
178
+
headers: withCors({
179
+
"content-type": "text/css; charset=utf-8",
180
+
}),
181
+
});
182
+
}
183
+
184
+
if (pathname === "/.well-known/did.json") {
185
+
return new Response(
186
+
JSON.stringify(
187
+
didDocument(
188
+
"index",
189
+
config.indexServer.did,
190
+
config.indexServer.host,
191
+
"whatever"
192
+
)
193
+
),
194
+
{
195
+
headers: withCors({ "Content-Type": "application/json" }),
196
+
}
197
+
);
198
+
}
199
+
if (pathname === "/health") {
200
+
return new Response("OK", {
201
+
status: 200,
202
+
headers: withCors({
203
+
"Content-Type": "text/plain",
204
+
}),
205
+
});
206
+
}
207
+
if (req.method === "OPTIONS") {
208
+
return new Response(null, {
209
+
status: 204,
210
+
headers: {
211
+
"Access-Control-Allow-Origin": "*",
212
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
213
+
"Access-Control-Allow-Headers": "*",
214
+
},
215
+
});
216
+
}
217
+
console.log(`request for "${pathname}"`);
218
+
const constellation = pathname.startsWith("/links");
219
+
220
+
if (constellation) {
221
+
const target = searchParams?.target as string;
222
+
const safeDid = extractDid(target);
223
+
const targetserver = genericIndexServer.handlesDid(safeDid);
224
+
if (targetserver) {
225
+
return genericIndexServer.constellationAPIHandler(req);
226
+
} else {
227
+
return new Response(
228
+
JSON.stringify({
229
+
error: "User not found",
230
+
}),
231
+
{
232
+
status: 404,
233
+
headers: withCors({ "Content-Type": "application/json" }),
234
+
}
235
+
);
236
+
}
237
+
} else {
238
+
// indexServerRoutes.has(pathname)
239
+
return await genericIndexServer.indexServerHandler(req);
240
+
}
241
+
}
242
+
);
243
+
244
+
devWatch({
245
+
target: "index",
246
+
initialData: {
247
+
config: config.indexServer,
248
+
users: (await genericIndexServer.unspeccedGetRegisteredUsers()) ?? [],
249
+
},
250
+
onBuild: ({ js: newjs, html: newhtml, css: newcss }) => {
251
+
js = newjs;
252
+
html = newhtml;
253
+
css = newcss;
254
+
},
255
+
});
+251
main-view.ts
+251
main-view.ts
···
1
+
import { setupAuth, getAuthenticatedDid, authVerifier } from "./utils/auth.ts";
2
+
import { setupSystemDb } from "./utils/dbsystem.ts";
3
+
import { didDocument } from "./utils/diddoc.ts";
4
+
import { cachedFetch, searchParamsToJson, withCors } from "./utils/server.ts";
5
+
import { ViewServer, ViewServerConfig } from "./viewserver.ts";
6
+
import { extractDid } from "./utils/identity.ts";
7
+
import { config } from "./config.ts";
8
+
import { compile, devWatch } from "./shared-landing/build.ts";
9
+
10
+
// ------------------------------------------
11
+
// AppView Setup
12
+
// ------------------------------------------
13
+
14
+
setupAuth({
15
+
serviceDid: config.viewServer.did,
16
+
//keyCacheSize: 500,
17
+
//keyCacheTTL: 10 * 60 * 1000,
18
+
});
19
+
20
+
const viewServerConfig: ViewServerConfig = {
21
+
baseDbPath: "./dbs/view/registered-users", // The directory for user databases
22
+
systemDbPath: "./dbs/view/registered-users/system.db", // The path for the main system database
23
+
};
24
+
export const genericViewServer = new ViewServer(viewServerConfig);
25
+
setupSystemDb(genericViewServer.systemDB);
26
+
let { js, html, css } = await compile({
27
+
target: "view",
28
+
initialData: {
29
+
config: config.viewServer,
30
+
users: (await genericViewServer.unspeccedGetRegisteredUsers()) ?? [],
31
+
},
32
+
});
33
+
34
+
// add me lol
35
+
genericViewServer.systemDB.exec(`
36
+
INSERT OR IGNORE INTO users (did, role, registrationdate, onboardingstatus)
37
+
VALUES (
38
+
'did:plc:mn45tewwnse5btfftvd3powc',
39
+
'admin',
40
+
datetime('now'),
41
+
'ready'
42
+
);
43
+
44
+
INSERT OR IGNORE INTO users (did, role, registrationdate, onboardingstatus)
45
+
VALUES (
46
+
'did:web:did12.whey.party',
47
+
'admin',
48
+
datetime('now'),
49
+
'ready'
50
+
);
51
+
`);
52
+
53
+
genericViewServer.start();
54
+
55
+
// ------------------------------------------
56
+
// XRPC Method Implementations
57
+
// ------------------------------------------
58
+
59
+
const placeholderselfcheckstatus = {
60
+
"#bsky_appview:/xrpc/app.bsky.actor.getPreferences": "black",
61
+
"#bsky_appview:/xrpc/app.bsky.actor.getProfile": "green",
62
+
"#bsky_appview:/xrpc/app.bsky.actor.getProfiles": "green",
63
+
"#bsky_appview:/xrpc/app.bsky.actor.getSuggestions": "black",
64
+
"#bsky_appview:/xrpc/app.bsky.actor.putPreferences": "black",
65
+
"#bsky_appview:/xrpc/app.bsky.actor.searchActorsTypeahead": "black",
66
+
"#bsky_appview:/xrpc/app.bsky.actor.searchActors": "black",
67
+
"#bsky_appview:/xrpc/app.bsky.feed.describeFeedGenerator": "black",
68
+
"#bsky_appview:/xrpc/app.bsky.feed.getActorFeeds": "black",
69
+
"#bsky_appview:/xrpc/app.bsky.feed.getActorLikes": "black",
70
+
"#bsky_appview:/xrpc/app.bsky.feed.getAuthorFeed": "red",
71
+
"#bsky_appview:/xrpc/app.bsky.feed.getFeedGenerator": "black",
72
+
"#bsky_appview:/xrpc/app.bsky.feed.getFeedGenerators": "green", // arguably
73
+
"#bsky_appview:/xrpc/app.bsky.feed.getFeedSkeleton": "black",
74
+
"#bsky_appview:/xrpc/app.bsky.feed.getFeed": "red",
75
+
"#bsky_appview:/xrpc/app.bsky.feed.getLikes": "black",
76
+
"#bsky_appview:/xrpc/app.bsky.feed.getListFeed": "black",
77
+
"#bsky_appview:/xrpc/app.bsky.feed.getPostThread": "red",
78
+
"#bsky_appview:/xrpc/app.bsky.unspecced.getPostThreadV2": "red",
79
+
"#bsky_appview:/xrpc/app.bsky.feed.getPosts": "green",
80
+
"#bsky_appview:/xrpc/app.bsky.feed.getQuotes": "black",
81
+
"#bsky_appview:/xrpc/app.bsky.feed.getRepostedBy": "black",
82
+
"#bsky_appview:/xrpc/app.bsky.feed.getSuggestedFeeds": "black",
83
+
"#bsky_appview:/xrpc/app.bsky.feed.getTimeline": "black",
84
+
"#bsky_appview:/xrpc/app.bsky.feed.searchPosts": "black",
85
+
"#bsky_appview:/xrpc/app.bsky.feed.sendInteractions": "black",
86
+
"#bsky_appview:/xrpc/app.bsky.graph.getActorStarterPacks": "black",
87
+
"#bsky_appview:/xrpc/app.bsky.graph.getBlocks": "black",
88
+
"#bsky_appview:/xrpc/app.bsky.graph.getFollowers": "black",
89
+
"#bsky_appview:/xrpc/app.bsky.graph.getFollows": "black",
90
+
"#bsky_appview:/xrpc/app.bsky.graph.getKnownFollowers": "black",
91
+
"#bsky_appview:/xrpc/app.bsky.graph.getListBlocks": "black",
92
+
"#bsky_appview:/xrpc/app.bsky.graph.getListMutes": "black",
93
+
"#bsky_appview:/xrpc/app.bsky.graph.getList": "black",
94
+
"#bsky_appview:/xrpc/app.bsky.graph.getLists": "black",
95
+
"#bsky_appview:/xrpc/app.bsky.graph.getMutes": "black",
96
+
"#bsky_appview:/xrpc/app.bsky.graph.getRelationships": "black",
97
+
"#bsky_appview:/xrpc/app.bsky.graph.getStarterPack": "black",
98
+
"#bsky_appview:/xrpc/app.bsky.graph.getStarterPacks": "black",
99
+
"#bsky_appview:/xrpc/app.bsky.graph.getSuggestedFollowsByActor": "black",
100
+
"#bsky_appview:/xrpc/app.bsky.graph.muteActorList": "black",
101
+
"#bsky_appview:/xrpc/app.bsky.graph.muteActor": "black",
102
+
"#bsky_appview:/xrpc/app.bsky.graph.muteThread": "black",
103
+
"#bsky_appview:/xrpc/app.bsky.graph.searchStarterPacks": "black",
104
+
"#bsky_appview:/xrpc/app.bsky.graph.unmuteActorList": "black",
105
+
"#bsky_appview:/xrpc/app.bsky.graph.unmuteActor": "black",
106
+
"#bsky_appview:/xrpc/app.bsky.graph.unmuteThread": "black",
107
+
"#bsky_appview:/xrpc/app.bsky.labeler.getServices": "black",
108
+
"#bsky_appview:/xrpc/app.bsky.notification.getUnreadCount": "black",
109
+
"#bsky_appview:/xrpc/app.bsky.notification.listNotifications": "green",
110
+
"#bsky_appview:/xrpc/app.bsky.notification.putPreferences": "black",
111
+
"#bsky_appview:/xrpc/app.bsky.notification.registerPush": "black",
112
+
"#bsky_appview:/xrpc/app.bsky.notification.updateSeen": "black",
113
+
"#bsky_appview:/xrpc/app.bsky.video.getJobStatus": "black",
114
+
"#bsky_appview:/xrpc/app.bsky.video.getUploadLimits": "black",
115
+
"#bsky_appview:/xrpc/app.bsky.video.uploadVideo": "black",
116
+
"#bsky_appview:/xrpc/app.bsky.unspecced.getTrendingTopics": "red",
117
+
"#bsky_appview:/xrpc/app.bsky.unspecced.getConfig": "red",
118
+
};
119
+
120
+
Deno.serve(
121
+
{ port: config.viewServer.port },
122
+
async (req: Request): Promise<Response> => {
123
+
const url = new URL(req.url);
124
+
const pathname = url.pathname;
125
+
const searchParams = searchParamsToJson(url.searchParams);
126
+
127
+
const publicdir = "/public";
128
+
if (pathname.startsWith(publicdir)) {
129
+
const filepath = decodeURIComponent(pathname.slice(publicdir.length));
130
+
try {
131
+
const file = await Deno.open("." + filepath, { read: true });
132
+
return new Response(file.readable);
133
+
} catch {
134
+
return new Response("404 Not Found", { status: 404 });
135
+
}
136
+
}
137
+
138
+
const todopleasespecthis = "/_unspecced";
139
+
if (pathname.startsWith(todopleasespecthis)) {
140
+
const unspeccedroute = decodeURIComponent(
141
+
pathname.slice(todopleasespecthis.length)
142
+
);
143
+
if (unspeccedroute === "/config") {
144
+
const safeconfig = {
145
+
inviteOnly: config.viewServer.inviteOnly,
146
+
//port: number,
147
+
did: config.viewServer.did,
148
+
host: config.viewServer.host,
149
+
indexPriority: config.viewServer.indexPriority,
150
+
};
151
+
return new Response(JSON.stringify(safeconfig), {
152
+
headers: withCors({
153
+
"content-type": "application/json; charset=utf-8",
154
+
}),
155
+
});
156
+
}
157
+
if (unspeccedroute === "/users") {
158
+
const res = await genericViewServer.unspeccedGetRegisteredUsers();
159
+
return new Response(JSON.stringify(res), {
160
+
headers: withCors({
161
+
"content-type": "application/json; charset=utf-8",
162
+
}),
163
+
});
164
+
}
165
+
if (unspeccedroute === "/apitest") {
166
+
return new Response(JSON.stringify(placeholderselfcheckstatus), {
167
+
headers: withCors({
168
+
"content-type": "application/json; charset=utf-8",
169
+
}),
170
+
});
171
+
}
172
+
}
173
+
174
+
if (html && js) {
175
+
if (pathname === "/" || pathname === "") {
176
+
return new Response(html, {
177
+
headers: withCors({ "content-type": "text/html; charset=utf-8" }),
178
+
});
179
+
}
180
+
if (pathname === "/landing-view.js") {
181
+
return new Response(js, {
182
+
headers: withCors({
183
+
"content-type": "application/javascript; charset=utf-8",
184
+
}),
185
+
});
186
+
}
187
+
} else {
188
+
if (pathname === "/" || pathname === "") {
189
+
return new Response(`server is compiling your webpage. loading...`, {
190
+
headers: withCors({ "content-type": "text/html; charset=utf-8" }),
191
+
});
192
+
}
193
+
}
194
+
if (pathname === "/app.css") {
195
+
return new Response(css, {
196
+
headers: withCors({
197
+
"content-type": "text/css; charset=utf-8",
198
+
}),
199
+
});
200
+
}
201
+
202
+
if (pathname === "/.well-known/did.json") {
203
+
return new Response(
204
+
JSON.stringify(
205
+
didDocument(
206
+
"view",
207
+
config.viewServer.did,
208
+
config.viewServer.host,
209
+
"whatever"
210
+
)
211
+
),
212
+
{
213
+
headers: withCors({ "Content-Type": "application/json" }),
214
+
}
215
+
);
216
+
}
217
+
if (pathname === "/health") {
218
+
return new Response("OK", {
219
+
status: 200,
220
+
headers: withCors({
221
+
"Content-Type": "text/plain",
222
+
}),
223
+
});
224
+
}
225
+
if (req.method === "OPTIONS") {
226
+
return new Response(null, {
227
+
status: 204,
228
+
headers: {
229
+
"Access-Control-Allow-Origin": "*",
230
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
231
+
"Access-Control-Allow-Headers": "*",
232
+
},
233
+
});
234
+
}
235
+
console.log(`request for "${pathname}"`);
236
+
return await genericViewServer.viewServerHandler(req);
237
+
}
238
+
);
239
+
240
+
devWatch({
241
+
target: "view",
242
+
initialData: {
243
+
config: config.viewServer,
244
+
users: await genericViewServer.unspeccedGetRegisteredUsers() ?? [],
245
+
},
246
+
onBuild: ({ js: newjs, html: newhtml, css: newcss }) => {
247
+
js = newjs;
248
+
html = newhtml;
249
+
css = newcss;
250
+
},
251
+
});
-145
main.ts
-145
main.ts
···
1
-
import { setupAuth, getAuthenticatedDid, authVerifier } from "./utils/auth.ts";
2
-
import { JetstreamManager, SpacedustManager } from "./utils/sharders.ts";
3
-
import { resolveRecordFromURI, validateRecord } from "./utils/records.ts";
4
-
import { setupSystemDb } from "./utils/dbsystem.ts";
5
-
import { setupUserDb } from "./utils/dbuser.ts";
6
-
import { handleSpacedust, startSpacedust } from "./index/spacedust.ts";
7
-
import { handleJetstream, startJetstream } from "./index/jetstream.ts";
8
-
import { Database } from "jsr:@db/sqlite@0.11";
9
-
//import express from "npm:express";
10
-
//import { createServer } from "./xrpc/index.ts";
11
-
import { indexHandlerContext } from "./index/types.ts";
12
-
import * as IndexServerTypes from "./utils/indexservertypes.ts";
13
-
import * as ViewServerTypes from "./utils/viewservertypes.ts";
14
-
import * as ATPAPI from "npm:@atproto/api";
15
-
import { didDocument } from "./utils/diddoc.ts";
16
-
import { cachedFetch, searchParamsToJson } from "./utils/server.ts";
17
-
import { indexServerHandler } from "./indexserver.ts";
18
-
import { viewServerHandler } from "./viewserver.ts";
19
-
20
-
export const slingshoturl = Deno.env.get("SLINGSHOT_URL");
21
-
export const constellationurl = Deno.env.get("CONSTELLATION_URL");
22
-
export const spacedusturl = Deno.env.get("SPACEDUST_URL");
23
-
24
-
// ------------------------------------------
25
-
// AppView Setup
26
-
// ------------------------------------------
27
-
28
-
export const systemDB = new Database("system.db");
29
-
setupSystemDb(systemDB);
30
-
31
-
export const spacedustManager = new SpacedustManager((msg) =>
32
-
handleSpacedust(msg)
33
-
);
34
-
export const jetstreamManager = new JetstreamManager((msg) =>
35
-
handleJetstream(msg)
36
-
);
37
-
startSpacedust();
38
-
startJetstream();
39
-
40
-
setupAuth({
41
-
// local3768forumtest is just my tunnel from my dev env to the outside web that im reusing from forumtest
42
-
serviceDid: `${Deno.env.get("SERVICE_DID")}`,
43
-
//keyCacheSize: 500,
44
-
//keyCacheTTL: 10 * 60 * 1000,
45
-
});
46
-
47
-
// ------------------------------------------
48
-
// XRPC Method Implementations
49
-
// ------------------------------------------
50
-
51
-
const indexServerRoutes = new Set([
52
-
"/xrpc/app.bsky.actor.getProfile",
53
-
"/xrpc/app.bsky.actor.getProfiles",
54
-
"/xrpc/app.bsky.feed.getActorFeeds",
55
-
"/xrpc/app.bsky.feed.getFeedGenerator",
56
-
"/xrpc/app.bsky.feed.getFeedGenerators",
57
-
"/xrpc/app.bsky.feed.getPosts",
58
-
"/xrpc/party.whey.app.bsky.feed.getActorLikesPartial",
59
-
"/xrpc/party.whey.app.bsky.feed.getAuthorFeedPartial",
60
-
"/xrpc/party.whey.app.bsky.feed.getLikesPartial",
61
-
"/xrpc/party.whey.app.bsky.feed.getPostThreadPartial",
62
-
"/xrpc/party.whey.app.bsky.feed.getQuotesPartial",
63
-
"/xrpc/party.whey.app.bsky.feed.getRepostedByPartial",
64
-
]);
65
-
66
-
Deno.serve(
67
-
{ port: Number(`${Deno.env.get("SERVER_PORT")}`) },
68
-
async (req: Request): Promise<Response> => {
69
-
const url = new URL(req.url);
70
-
const pathname = url.pathname;
71
-
// const searchParams = searchParamsToJson(url.searchParams);
72
-
// let reqBody: undefined | string;
73
-
// let jsonbody: undefined | Record<string, unknown>;
74
-
// try {
75
-
// const clone = req.clone();
76
-
// jsonbody = await clone.json();
77
-
// } catch (e) {
78
-
// console.warn("Request body is not valid JSON:", e);
79
-
// }
80
-
if (pathname === "/.well-known/did.json") {
81
-
return new Response(JSON.stringify(didDocument), {
82
-
headers: withCors({ "Content-Type": "application/json" }),
83
-
});
84
-
}
85
-
if (pathname === "/health") {
86
-
return new Response("OK", {
87
-
status: 200,
88
-
headers: withCors({
89
-
"Content-Type": "text/plain",
90
-
}),
91
-
});
92
-
}
93
-
if (req.method === "OPTIONS") {
94
-
return new Response(null, {
95
-
status: 204,
96
-
headers: {
97
-
"Access-Control-Allow-Origin": "*",
98
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
99
-
"Access-Control-Allow-Headers": "*",
100
-
},
101
-
});
102
-
}
103
-
// const bskyUrl = `https://api.bsky.app${pathname}${url.search}`;
104
-
// const hasAuth = req.headers.has("authorization");
105
-
// const xrpcMethod = pathname.startsWith("/xrpc/")
106
-
// ? pathname.slice("/xrpc/".length)
107
-
// : null;
108
-
109
-
if (indexServerRoutes.has(pathname)) {
110
-
return await indexServerHandler(req);
111
-
} else {
112
-
return await viewServerHandler(req);
113
-
}
114
-
}
115
-
);
116
-
117
-
export function withCors(headers: HeadersInit = {}) {
118
-
return {
119
-
"Access-Control-Allow-Origin": "*",
120
-
...headers,
121
-
};
122
-
}
123
-
const corsfree = {
124
-
"Access-Control-Allow-Origin": "*",
125
-
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
126
-
"Access-Control-Allow-Headers": "Content-Type, Authorization",
127
-
};
128
-
const json = "application/json";
129
-
130
-
// ------------------------------------------
131
-
// Indexer
132
-
// ------------------------------------------
133
-
134
-
export function handleIndex(ctx: indexHandlerContext) {
135
-
const record = validateRecord(ctx.value);
136
-
switch (record?.$type) {
137
-
case "app.bsky.feed.like": {
138
-
return;
139
-
}
140
-
default: {
141
-
// what the hell
142
-
return;
143
-
}
144
-
}
145
-
}
+22
public/client-metadata.json
+22
public/client-metadata.json
···
1
+
{
2
+
"client_id": "https://local3768forumtest.whey.party/client-metadata.json",
3
+
"client_name": "ForumTest",
4
+
"client_uri": "https://local3768forumtest.whey.party",
5
+
"logo_uri": "https://local3768forumtest.whey.party/logo192.png",
6
+
"tos_uri": "https://local3768forumtest.whey.party/terms-of-service",
7
+
"policy_uri": "https://local3768forumtest.whey.party/privacy-policy",
8
+
"redirect_uris": [
9
+
"https://local3768forumtest.whey.party/callback"
10
+
],
11
+
"scope": "atproto transition:generic",
12
+
"grant_types": [
13
+
"authorization_code",
14
+
"refresh_token"
15
+
],
16
+
"response_types": [
17
+
"code"
18
+
],
19
+
"token_endpoint_auth_method": "none",
20
+
"application_type": "web",
21
+
"dpop_bound_access_tokens": true
22
+
}
public/index.ico
public/index.ico
This is a binary file and will not be displayed.
public/view.ico
public/view.ico
This is a binary file and will not be displayed.
+50
-35
readme.md
+50
-35
readme.md
···
1
-
# (wip)(not done)(pre alpha) skylite (temporary name probably)(test thing)
2
-
an attempt to make a lightweight, easily self-hostable, scoped appview (kinda like fedi instances, so that means users need to register to an instance to have the content they should see be indexed) using:
3
-
- live sync systems:
4
-
- [jetstream](https://github.com/bluesky-social/jetstream)
5
-
- [spacedust](https://spacedust.microcosm.blue/)
6
-
- backfill:
7
-
- [listRecords](https://docs.bsky.app/docs/api/com-atproto-repo-list-records)
8
-
- [constellation](https://constellation.microcosm.blue/)
9
-
- the server stuff:
10
-
- [sqlite](https://jsr.io/@db/sqlite)
11
-
- the usage of [typescript](https://www.npmjs.com/package/@atproto/lex-cli)
12
-
- [deno](https://deno.com/)
1
+
# skylite (pre alpha)
2
+
an attempt to make a lightweight, easily self-hostable, scoped Bluesky appview
3
+
4
+
(as of 28 aug 2025)
5
+
currently the state of the project is:
6
+

7
+

8
+
9
+
this project uses:
10
+
- live sync systems: [jetstream](https://github.com/bluesky-social/jetstream) and [spacedust](https://spacedust.microcosm.blue/)
11
+
- backfill: [listRecords](https://docs.bsky.app/docs/api/com-atproto-repo-list-records) and [constellation](https://constellation.microcosm.blue/)
12
+
- the backend server stuff: [sqlite](https://jsr.io/@db/sqlite) db, typescript with [codegen](https://www.npmjs.com/package/@atproto/lex-cli), and [deno](https://deno.com/)
13
+
- frontend: still deno and esbuild and tailwind and react and jsx and typescript (was fun getting these to run on deno)
14
+
15
+
## Running
16
+
this project is pre-alpha and not intended for general use yet. you are welcome to experiment if you dont mind errors or breaking changes.
17
+
18
+
the project is split into two, the "Index Server" and the "View Server".
19
+
despite both living in this repo, they run different http servers with different configs
20
+
21
+
example configuration is in the `config.jsonc.example` file
22
+
23
+
### Index Server
24
+
start it by running
25
+
```sh
26
+
deno task index
27
+
```
28
+
it should just work actually
29
+
30
+
there is no way to register users to be indexed by the server yet (either Index nor View servers) so you can just manually add your account to the `system.db` file for now
13
31
14
-
this uses XRPC server tooling and codegen so the repo is kinda large despite not being functional at all
32
+
### View Server
33
+
start it by running
34
+
```sh
35
+
deno task view
36
+
```
37
+
expose your localhost to the web using a tunnel or something and use that url as the custom appview url
15
38
16
-
currently the state of the project is:
17
-
- db:
18
-
- is not ready but ive started working on it
19
-
- db schema is kinda there but still iffy on the insertion (kysely with better-sqlite3 or just use raw sql queries with deno jsr:@db/sqlite ?) (or maybe should it not use sqlite at all?)
20
-
- indexing:
21
-
- the meta framework of the parsing incoming data is kinda done (good enough for MVP but i would like more indexing provenance)
22
-
- but the actual logic for index handling to the db is not done
23
-
- registration:
24
-
- user registration is not there yet, the wrapper around listRecords exists though (for onboarding/backfill)
25
-
- XRPC Server:
26
-
- setup is done, just need to actually implement all 80+ routes
27
-
- got auth working recently but havent hooked up the validator to the xrpc method/route handler so it can access who requested it
39
+
this should work on any bluesky client that supports changing the appview URL (im using an unreleased custom fork for development) as the view server implements the `#bsky_appview` routes for compatibility with existing clients
28
40
41
+
ive got a custom `social-app` fork here [https://github.com/rimar1337/social-app/tree/publicappview-colorable](https://github.com/rimar1337/social-app/tree/publicappview-colorable)
29
42
30
-
there is still a lot of design work i havent done regarding stuff like:
31
-
- moderation
32
-
- indexing provenance
33
-
- registration/unregistration APIs
34
-
- cross instance backfills/viewing
35
-
- also im pretty sure im not listening to all of the app.bsky.* records that i should be listening to but i havent re checked it even though i already imported the entire lexicon directory from the bsky atproto git repo but i just havent checked yet
36
-
- and more
43
+
the view server has extra configurations that you need to understand.
44
+
the view server hydrates content by calling other servers (either an `#skylite_index` or `#bsky_appview`) and so you need to write the order of which servers are prioritized first for resolving the hydration endpoints
45
+
```js
46
+
// In order of which skylite index servers or bsky appviews to use first
47
+
"indexPriority": [
48
+
"user#skylite_index", // user resolved skylite index server
49
+
"did:web:backupindexserver.your.site#skylite_index", // a specific skylite index server
50
+
"user#bsky_appview", // user resolved bsky appview
51
+
"did:web:api.bsky.app#bsky_appview" // a specific bsky appview
52
+
]
53
+
```
37
54
38
-
still very early
39
-
this does not run on any bsky clients yet
40
-
practically none of the api routes have been implemented yet
55
+
id say this project is like uhh ~20% done so not a lot of things you can do with this right now
+11
tailwind.config.ts
+11
tailwind.config.ts
···
1
+
/** @type {import('npm:tailwindcss').Config} */
2
+
export default {
3
+
content: ["./**/*.{tsx,html}"], // make sure all your HTML/JSX paths are here
4
+
theme: {
5
+
extend: {},
6
+
},
7
+
plugins: [
8
+
// add Tailwind UI plugins if you want, like forms/typography
9
+
// require("@tailwindcss/forms"), etc.
10
+
],
11
+
};
+2
-2
utils/auth.borrowed.ts
+2
-2
utils/auth.borrowed.ts
···
22
22
return { DidPlcResolver: resolve }
23
23
}
24
24
const myResolver = getResolver()
25
-
const web = getWebResolver()
25
+
const webResolver = getWebResolver()
26
26
const resolver: ResolverRegistry = {
27
27
'plc': myResolver.DidPlcResolver as unknown as DIDResolver,
28
-
'web': web as unknown as DIDResolver,
28
+
...webResolver
29
29
}
30
30
export const resolverInstance = new Resolver(resolver)
31
31
export type Service = {
+5
-1
utils/auth.ts
+5
-1
utils/auth.ts
···
61
61
return null;
62
62
}
63
63
}
64
-
64
+
/**
65
+
* @deprecated dont use this use getAuthenticatedDid() instead
66
+
* @param param0
67
+
* @returns
68
+
*/
65
69
export const authVerifier: MethodAuthVerifier<AuthResult> = async ({ req }) => {
66
70
//console.log("help us all fuck you",req)
67
71
console.log("you are doing well")
-16
utils/dbsystem.ts
-16
utils/dbsystem.ts
···
33
33
handle TEXT
34
34
);
35
35
${createIndexINE} idx_did_handle ON did(handle);
36
-
37
-
-- A global index for relationships between any two pieces of content
38
-
${createTableINE} backlink_skeleton (
39
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
40
-
srcuri TEXT,
41
-
srcdid TEXT,
42
-
srcfield TEXT,
43
-
srccol TEXT,
44
-
suburi TEXT,
45
-
subdid TEXT,
46
-
subcol TEXT
47
-
);
48
-
${createIndexINE} idx_backlink_subdid_mod ON backlink_skeleton(subdid, srcdid);
49
-
${createIndexINE} idx_backlink_suburi_mod ON backlink_skeleton(suburi, srcdid);
50
-
${createIndexINE} idx_backlink_subdid_filter_mod ON backlink_skeleton(subdid, srccol, srcdid);
51
-
${createIndexINE} idx_backlink_suburi_filter_mod ON backlink_skeleton(suburi, srccol, srcdid);
52
36
`);
53
37
}
+19
-1
utils/dbuser.ts
+19
-1
utils/dbuser.ts
···
32
32
avatarcid TEXT,
33
33
avatarmime TEXT,
34
34
bannercid TEXT,
35
-
bannermime TEXT
35
+
bannermime TEXT,
36
+
pinned TEXT
36
37
);
37
38
${createIndexINE} idx_actor_profile_did ON app_bsky_actor_profile(did);
38
39
···
131
132
-- User's notification settings declaration
132
133
${createTableINE} app_bsky_notification_declaration ( ${baseColumns}, allowSubscriptions TEXT );
133
134
${createIndexINE} idx_notification_declaration_author ON app_bsky_notification_declaration(did);
135
+
136
+
-- A global index for relationships between any two pieces of content
137
+
${createTableINE} backlink_skeleton (
138
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
139
+
srcuri TEXT,
140
+
srcdid TEXT,
141
+
srcfield TEXT,
142
+
srccol TEXT,
143
+
suburi TEXT,
144
+
subdid TEXT,
145
+
subcol TEXT,
146
+
indexedAt INTEGER NOT NULL
147
+
);
148
+
${createIndexINE} idx_backlink_subdid_mod ON backlink_skeleton(subdid, srcdid);
149
+
${createIndexINE} idx_backlink_suburi_mod ON backlink_skeleton(suburi, srcdid);
150
+
${createIndexINE} idx_backlink_subdid_filter_mod ON backlink_skeleton(subdid, srccol, srcdid);
151
+
${createIndexINE} idx_backlink_suburi_filter_mod ON backlink_skeleton(suburi, srccol, srcdid);
134
152
`);
135
153
}
+37
-22
utils/diddoc.ts
+37
-22
utils/diddoc.ts
···
1
-
export const didDocument = {
2
-
"@context": [
3
-
"https://www.w3.org/ns/did/v1",
4
-
"https://w3id.org/security/multikey/v1",
5
-
],
6
-
id: `${Deno.env.get("SERVICE_DID")}`,
7
-
verificationMethod: [
8
-
{
9
-
id: `${Deno.env.get("SERVICE_DID")}#atproto`,
10
-
type: "Multikey",
11
-
controller: `${Deno.env.get("SERVICE_DID")}`,
12
-
publicKeyMultibase: "bullshit",
1
+
// type "both" should not be used
2
+
export function didDocument(
3
+
type: "view" | "index" | "both",
4
+
did: string,
5
+
endpoint: string,
6
+
publicKeyMultibase: string,
7
+
) {
8
+
const services = [
9
+
(type === "view" || type === "both") && {
10
+
id: "#bsky_appview",
11
+
type: "BskyAppView",
12
+
serviceEndpoint: endpoint,
13
13
},
14
-
],
15
-
service: [
16
-
{
14
+
(type === "view" || type === "both") && {
17
15
id: "#bsky_notif",
18
16
type: "BskyNotificationService",
19
-
serviceEndpoint: `${Deno.env.get("SERVICE_ENDPOINT")}`,
17
+
serviceEndpoint: endpoint,
20
18
},
21
-
{
22
-
id: "#bsky_appview",
23
-
type: "BskyAppView",
24
-
serviceEndpoint: `${Deno.env.get("SERVICE_ENDPOINT")}`,
19
+
(type === "index" || type === "both") && {
20
+
id: "#skylite_index",
21
+
type: "SkyliteIndexServer",
22
+
serviceEndpoint: endpoint,
25
23
},
26
-
],
27
-
};
24
+
].filter(Boolean);
25
+
26
+
return {
27
+
"@context": [
28
+
"https://www.w3.org/ns/did/v1",
29
+
"https://w3id.org/security/multikey/v1",
30
+
],
31
+
id: did,
32
+
verificationMethod: [
33
+
{
34
+
id: `${did}#atproto`,
35
+
type: "Multikey",
36
+
controller: did,
37
+
publicKeyMultibase: publicKeyMultibase,
38
+
},
39
+
],
40
+
service: services,
41
+
};
42
+
}
+16
-1
utils/identity.ts
+16
-1
utils/identity.ts
···
1
1
2
2
import { DidResolver, HandleResolver } from "npm:@atproto/identity";
3
-
import { systemDB } from "../main.ts";
3
+
import { Database } from "jsr:@db/sqlite@0.11";
4
+
import { AtUri } from "npm:@atproto/api";
5
+
const systemDB = new Database("./system.db") // TODO: temporary shim. should seperate this to its own central system db instead of the now instantiated system dbs
4
6
type DidMethod = "web" | "plc";
5
7
type DidDoc = {
6
8
"@context"?: unknown;
···
223
225
} catch (err) {
224
226
console.error(`Failed to extract/store PDS and handle for '${did}':`, err);
225
227
return null;
228
+
}
229
+
}
230
+
231
+
export function extractDid(input: string): string {
232
+
if (input.startsWith('did:')) {
233
+
return input
234
+
}
235
+
236
+
try {
237
+
const uri = new AtUri(input)
238
+
return uri.host
239
+
} catch (e) {
240
+
throw new Error(`Invalid input: expected a DID or a valid AT URI, got "${input}"`)
226
241
}
227
242
}
+12
-1
utils/records.ts
+12
-1
utils/records.ts
···
75
75
if (result.success) return result.value;
76
76
return undefined;
77
77
}
78
-
78
+
export function assertRecord<T extends KnownRecordType>(
79
+
record: unknown
80
+
): RecordTypeMap[T] | undefined {
81
+
if (typeof record !== 'object' || record === null) {
82
+
return undefined;
83
+
}
84
+
const type = (record as { $type?: string })?.$type;
85
+
if (typeof type !== 'string' || !(type in recordValidators)) {
86
+
return undefined;
87
+
}
88
+
return record as RecordTypeMap[T];
89
+
}
79
90
export async function resolveRecordFromURI({
80
91
did,
81
92
uri,
+14
-4
utils/server.ts
+14
-4
utils/server.ts
···
1
1
import ky from "npm:ky";
2
2
import QuickLRU from "npm:quick-lru";
3
3
import { createHash } from "node:crypto";
4
-
import { slingshoturl, constellationurl } from "../main.ts";
4
+
import { config } from "../config.ts";
5
5
import * as ATPAPI from "npm:@atproto/api";
6
6
7
7
const cache = new QuickLRU({ maxSize: 10000 });
···
57
57
export async function resolveIdentity(
58
58
actor: string
59
59
): Promise<SlingshotMiniDoc> {
60
-
const url = `${slingshoturl}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${actor}`;
60
+
const url = `${config.slingshot}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${actor}`;
61
61
return (await cachedFetch(url)) as SlingshotMiniDoc;
62
62
}
63
63
export async function getRecord({
···
155
155
collection: string,
156
156
rkey: string
157
157
): Promise<GetRecord> {
158
-
const url = `${slingshoturl}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=${collection}&rkey=${rkey}`;
158
+
const identity = await resolveIdentity(did);
159
+
//const url = `${config.slingshot}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=${collection}&rkey=${rkey}`;
160
+
const url = `${identity.pds}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=${collection}&rkey=${rkey}`;
159
161
const result = (await cachedFetch(url)) as GetRecord;
160
162
return result as GetRecord;
161
163
}
···
169
171
collection: string;
170
172
path: string;
171
173
}): Promise<number> {
172
-
const url = `${constellationurl}/links/count/distinct-dids?target=${did}&collection=${collection}&path=${path}`;
174
+
const url = `${config.constellation}/links/count/distinct-dids?target=${did}&collection=${collection}&path=${path}`;
173
175
const result = (await cachedFetch(url)) as ConstellationDistinctDids;
174
176
return result.total;
175
177
}
178
+
179
+
180
+
export function withCors(headers: HeadersInit = {}) {
181
+
return {
182
+
"Access-Control-Allow-Origin": "*",
183
+
...headers,
184
+
};
185
+
}
+5
-2
utils/sharders.ts
+5
-2
utils/sharders.ts
···
1
+
import { config } from "../config.ts";
2
+
1
3
function getShardId(params: URLSearchParams): string {
2
4
params.sort();
3
5
return params.toString();
···
240
242
wantedSubjects: string[];
241
243
wantedSubjectDids: string[];
242
244
wantedSources: string[];
245
+
instant: string[];
243
246
}
244
247
245
248
export class JetstreamManager extends ShardedConnectionManager<JetstreamParams> {
246
249
constructor(onMessage: (msg: any) => void) {
247
250
super(
248
-
`${Deno.env.get("JETSTREAM_URL")}/subscribe`,
251
+
`${config.jetstream}/subscribe`,
249
252
{ wantedDids: 10000, wantedCollections: 100 },
250
253
onMessage
251
254
);
···
255
258
export class SpacedustManager extends ShardedConnectionManager<SpacedustParams> {
256
259
constructor(onMessage: (msg: any) => void) {
257
260
super(
258
-
`${Deno.env.get("SPACEDUST_URL")}/subscribe`,
261
+
`${config.spacedust}/subscribe`,
259
262
{ wantedSubjects: 100, wantedSubjectDids: 100, wantedSources: 100 },
260
263
onMessage
261
264
);
+1206
-419
viewserver.ts
+1206
-419
viewserver.ts
···
1
1
import ky from "npm:ky";
2
2
import { isGeneratorView } from "./indexserver/types/app/bsky/feed/defs.ts";
3
-
import { withCors } from "./main.ts";
4
3
import * as ViewServerTypes from "./utils/viewservertypes.ts";
5
4
import * as ATPAPI from "npm:@atproto/api";
6
5
import {
···
10
9
cachedFetch,
11
10
didWebToHttps,
12
11
getSlingshotRecord,
12
+
withCors,
13
13
} from "./utils/server.ts";
14
+
import QuickLRU from "npm:quick-lru";
15
+
import { validateRecord } from "./utils/records.ts";
16
+
import { indexHandlerContext } from "./index/types.ts";
17
+
import { Database } from "jsr:@db/sqlite@0.11";
18
+
import { JetstreamManager, SpacedustManager } from "./utils/sharders.ts";
19
+
import { SpacedustLinkMessage } from "./index/spacedust.ts";
20
+
import { setupUserDb } from "./utils/dbuser.ts";
21
+
import { config } from "./config.ts";
22
+
import { AtUri } from "npm:@atproto/api";
23
+
import { CID } from "../../Library/Caches/deno/npm/registry.npmjs.org/multiformats/9.9.0/cjs/src/cid.js";
24
+
import { uncid } from "./indexserver.ts";
25
+
import { getAuthenticatedDid } from "./utils/auth.ts";
14
26
15
-
export async function viewServerHandler(req: Request): Promise<Response> {
16
-
const url = new URL(req.url);
17
-
const pathname = url.pathname;
18
-
const bskyUrl = `https://api.bsky.app${pathname}${url.search}`;
19
-
const hasAuth = req.headers.has("authorization");
20
-
const xrpcMethod = pathname.startsWith("/xrpc/")
21
-
? pathname.slice("/xrpc/".length)
22
-
: null;
23
-
const searchParams = searchParamsToJson(url.searchParams);
24
-
const jsonUntyped = searchParams;
27
+
const temporarydevelopmentblockednotiftypes: ATPAPI.AppBskyNotificationListNotifications.Notification["reason"][] = [
28
+
//'like',
29
+
//'repost',
30
+
//'follow',
31
+
//'mention',
32
+
//'reply',
33
+
//'quote',
34
+
//'starterpack-joined',
35
+
//'liked-via-repost',
36
+
//'repost-via-repost',
37
+
];
25
38
26
-
if (xrpcMethod === "app.bsky.unspecced.getTrendingTopics") {
27
-
// const jsonTyped =
28
-
// jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.QueryParams;
39
+
export interface ViewServerConfig {
40
+
baseDbPath: string;
41
+
systemDbPath: string;
42
+
}
29
43
30
-
const faketopics: ATPAPI.AppBskyUnspeccedDefs.TrendingTopic[] = [
31
-
{
32
-
$type: "app.bsky.unspecced.defs#trendingTopic",
33
-
topic: "Git Repo",
34
-
displayName: "Git Repo",
35
-
description: "Git Repo",
36
-
link: "https://tangled.sh/@whey.party/skylite",
37
-
},
38
-
{
39
-
$type: "app.bsky.unspecced.defs#trendingTopic",
40
-
topic: "Red Dwarf Lite",
41
-
displayName: "Red Dwarf Lite",
42
-
description: "Red Dwarf Lite",
43
-
link: "https://reddwarflite.whey.party/",
44
-
},
45
-
{
46
-
$type: "app.bsky.unspecced.defs#trendingTopic",
47
-
topic: "whey dot party",
48
-
displayName: "whey dot party",
49
-
description: "whey dot party",
50
-
link: "https://whey.party/",
51
-
},
52
-
];
44
+
interface BaseRow {
45
+
uri: string;
46
+
did: string;
47
+
cid: string | null;
48
+
rev: string | null;
49
+
createdat: number | null;
50
+
indexedAt: number;
51
+
json: string | null;
52
+
}
53
+
interface GeneratorRow extends BaseRow {
54
+
displayname: string | null;
55
+
description: string | null;
56
+
avatarcid: string | null;
57
+
}
58
+
interface LikeRow extends BaseRow {
59
+
subject: string;
60
+
}
61
+
interface RepostRow extends BaseRow {
62
+
subject: string;
63
+
}
64
+
interface BacklinkRow {
65
+
srcuri: string;
66
+
srcdid: string;
67
+
}
53
68
54
-
const response: ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.OutputSchema =
55
-
{
56
-
topics: faketopics,
57
-
suggested: faketopics,
58
-
};
69
+
const FEED_LIMIT = 50;
59
70
60
-
return new Response(JSON.stringify(response), {
61
-
headers: withCors({ "Content-Type": "application/json" }),
62
-
});
71
+
export class ViewServer {
72
+
private config: ViewServerConfig;
73
+
public userManager: ViewServerUserManager;
74
+
public systemDB: Database;
75
+
76
+
constructor(config: ViewServerConfig) {
77
+
this.config = config;
78
+
79
+
// We will initialize the system DB and user manager here
80
+
this.systemDB = new Database(this.config.systemDbPath);
81
+
// TODO: We need to setup the system DB schema if it's new
82
+
83
+
this.userManager = new ViewServerUserManager(this); // Pass the server instance
63
84
}
64
85
65
-
//if (xrpcMethod !== 'app.bsky.actor.getPreferences' && xrpcMethod !== 'app.bsky.notification.listNotifications') {
66
-
if (
67
-
!hasAuth
68
-
// (!hasAuth ||
69
-
// xrpcMethod === "app.bsky.labeler.getServices" ||
70
-
// xrpcMethod === "app.bsky.unspecced.getConfig") &&
71
-
// xrpcMethod !== "app.bsky.notification.putPreferences"
72
-
) {
73
-
return new Response(
74
-
JSON.stringify({
75
-
error: "XRPCNotSupported",
76
-
message:
77
-
"HEY hello there my name is whey dot party and you have used my custom appview that is very cool but have you considered that XRPC Not Supported",
78
-
}),
79
-
{
80
-
status: 404,
81
-
headers: withCors({ "Content-Type": "application/json" }),
82
-
}
83
-
);
84
-
//return await sendItToApiBskyApp(req);
86
+
public start() {
87
+
// This is where we'll kick things off, like the cold start
88
+
this.userManager.coldStart(this.systemDB);
89
+
console.log("viewServer started.");
85
90
}
86
-
if (
87
-
// !hasAuth ||
88
-
xrpcMethod === "app.bsky.labeler.getServices" ||
89
-
xrpcMethod === "app.bsky.unspecced.getConfig" //&&
90
-
//xrpcMethod !== "app.bsky.notification.putPreferences"
91
-
) {
92
-
return new Response(
93
-
JSON.stringify({
94
-
error: "XRPCNotSupported",
95
-
message:
96
-
"HEY hello there my name is whey dot party and you have used my custom appview that is very cool but have you considered that XRPC Not Supported",
97
-
}),
98
-
{
99
-
status: 404,
91
+
92
+
async unspeccedGetRegisteredUsers(): Promise<{
93
+
did: string;
94
+
role: string;
95
+
registrationdate: string;
96
+
onboardingstatus: string;
97
+
pfp?: string;
98
+
displayname: string;
99
+
handle: string;
100
+
}[]|undefined> {
101
+
const stmt = this.systemDB.prepare(`
102
+
SELECT *
103
+
FROM users;
104
+
`);
105
+
const result = stmt.all() as
106
+
{
107
+
did: string;
108
+
role: string;
109
+
registrationdate: string;
110
+
onboardingstatus: string;
111
+
}[];
112
+
const hydrated = await Promise.all( result.map(async (user)=>{
113
+
const identity = await resolveIdentity(user.did);
114
+
const profile = (await getSlingshotRecord(identity.did,"app.bsky.actor.profile","self")).value as ATPAPI.AppBskyActorProfile.Record;
115
+
const avatarcid = uncid(profile.avatar?.ref);
116
+
const avatar = avatarcid
117
+
? buildBlobUrl(identity.pds, identity.did, avatarcid)
118
+
: undefined;
119
+
return {...user,handle: identity.handle,pfp: avatar, displayname:profile.displayName ?? identity.handle }
120
+
}))
121
+
//const exists = result !== undefined;
122
+
return hydrated;
123
+
}
124
+
125
+
async viewServerHandler(req: Request): Promise<Response> {
126
+
const url = new URL(req.url);
127
+
const pathname = url.pathname;
128
+
const bskyUrl = `https://api.bsky.app${pathname}${url.search}`;
129
+
const hasAuth = req.headers.has("authorization");
130
+
const xrpcMethod = pathname.startsWith("/xrpc/")
131
+
? pathname.slice("/xrpc/".length)
132
+
: null;
133
+
const searchParams = searchParamsToJson(url.searchParams);
134
+
const jsonUntyped = searchParams;
135
+
136
+
let tempauthdid: string | undefined = undefined;
137
+
try {
138
+
tempauthdid = (await getAuthenticatedDid(req)) ?? undefined;
139
+
} catch (_e) {
140
+
// nothing lol
141
+
}
142
+
const authdid = tempauthdid
143
+
? this.handlesDid(tempauthdid)
144
+
? tempauthdid
145
+
: undefined
146
+
: undefined;
147
+
console.log("authed:", authdid);
148
+
149
+
if (xrpcMethod === "app.bsky.unspecced.getTrendingTopics") {
150
+
// const jsonTyped =
151
+
// jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.QueryParams;
152
+
153
+
const faketopics: ATPAPI.AppBskyUnspeccedDefs.TrendingTopic[] = [
154
+
{
155
+
$type: "app.bsky.unspecced.defs#trendingTopic",
156
+
topic: "Git Repo",
157
+
displayName: "Git Repo",
158
+
description: "Git Repo",
159
+
link: "https://tangled.sh/@whey.party/skylite",
160
+
},
161
+
{
162
+
$type: "app.bsky.unspecced.defs#trendingTopic",
163
+
topic: "this View Server url",
164
+
displayName: "this View Server url",
165
+
description: "this View Server url",
166
+
link: config.viewServer.host,
167
+
},
168
+
{
169
+
$type: "app.bsky.unspecced.defs#trendingTopic",
170
+
topic: "this social-app fork url",
171
+
displayName: "this social-app fork url",
172
+
description: "this social-app fork url",
173
+
link: "https://github.com/rimar1337/social-app/tree/publicappview-colorable",
174
+
},
175
+
{
176
+
$type: "app.bsky.unspecced.defs#trendingTopic",
177
+
topic: "whey dot party",
178
+
displayName: "whey dot party",
179
+
description: "whey dot party",
180
+
link: "https://whey.party/",
181
+
},
182
+
];
183
+
184
+
const response: ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.OutputSchema =
185
+
{
186
+
topics: faketopics,
187
+
suggested: faketopics,
188
+
};
189
+
190
+
return new Response(JSON.stringify(response), {
100
191
headers: withCors({ "Content-Type": "application/json" }),
101
-
}
102
-
);
103
-
//return await sendItToApiBskyApp(req);
104
-
}
192
+
});
193
+
}
105
194
106
-
const authDID = "did:plc:mn45tewwnse5btfftvd3powc"; //getAuthenticatedDid(req);
195
+
//if (xrpcMethod !== 'app.bsky.actor.getPreferences' && xrpcMethod !== 'app.bsky.notification.listNotifications') {
196
+
if (
197
+
!hasAuth
198
+
// (!hasAuth ||
199
+
// xrpcMethod === "app.bsky.labeler.getServices" ||
200
+
// xrpcMethod === "app.bsky.unspecced.getConfig") &&
201
+
// xrpcMethod !== "app.bsky.notification.putPreferences"
202
+
) {
203
+
return new Response(
204
+
JSON.stringify({
205
+
error: "XRPCNotSupported",
206
+
message:
207
+
"(no auth) HEY hello there my name is whey dot party and you have used my custom appview that is very cool but have you considered that XRPC Not Supported",
208
+
}),
209
+
{
210
+
status: 404,
211
+
headers: withCors({ "Content-Type": "application/json" }),
212
+
}
213
+
);
214
+
//return await sendItToApiBskyApp(req);
215
+
}
216
+
if (
217
+
// !hasAuth ||
218
+
xrpcMethod === "app.bsky.labeler.getServices" ||
219
+
xrpcMethod === "app.bsky.unspecced.getConfig" //&&
220
+
//xrpcMethod !== "app.bsky.notification.putPreferences"
221
+
) {
222
+
return new Response(
223
+
JSON.stringify({
224
+
error: "XRPCNotSupported",
225
+
message:
226
+
"(getservices / getconfig) HEY hello there my name is whey dot party and you have used my custom appview that is very cool but have you considered that XRPC Not Supported",
227
+
}),
228
+
{
229
+
status: 404,
230
+
headers: withCors({ "Content-Type": "application/json" }),
231
+
}
232
+
);
233
+
//return await sendItToApiBskyApp(req);
234
+
}
107
235
108
-
switch (xrpcMethod) {
109
-
case "app.bsky.feed.getFeedGenerators": {
110
-
const jsonTyped =
111
-
jsonUntyped as ViewServerTypes.AppBskyFeedGetFeedGenerators.QueryParams;
236
+
const authDID = "did:plc:mn45tewwnse5btfftvd3powc"; //getAuthenticatedDid(req);
237
+
238
+
switch (xrpcMethod) {
239
+
case "app.bsky.feed.getFeedGenerators": {
240
+
const jsonTyped =
241
+
jsonUntyped as ViewServerTypes.AppBskyFeedGetFeedGenerators.QueryParams;
112
242
113
-
const feeds: ATPAPI.AppBskyFeedDefs.GeneratorView[] = (
114
-
await Promise.all(
115
-
jsonTyped.feeds.map(async (feed) => {
116
-
try {
117
-
const did = new ATPAPI.AtUri(feed).hostname;
118
-
const rkey = new ATPAPI.AtUri(feed).rkey;
119
-
const identity = await resolveIdentity(did);
120
-
const feedgetRecord = await getSlingshotRecord(
121
-
identity.did,
122
-
"app.bsky.feed.generator",
123
-
rkey
124
-
);
125
-
const profile = (
126
-
await getSlingshotRecord(
243
+
const feeds: ATPAPI.AppBskyFeedDefs.GeneratorView[] = (
244
+
await Promise.all(
245
+
jsonTyped.feeds.map(async (feed) => {
246
+
try {
247
+
const did = new ATPAPI.AtUri(feed).hostname;
248
+
const rkey = new ATPAPI.AtUri(feed).rkey;
249
+
const identity = await resolveIdentity(did);
250
+
const feedgetRecord = await getSlingshotRecord(
127
251
identity.did,
128
-
"app.bsky.actor.profile",
129
-
"self"
130
-
)
131
-
).value as ATPAPI.AppBskyActorProfile.Record;
132
-
const anyprofile = profile as any;
133
-
const value =
134
-
feedgetRecord.value as ATPAPI.AppBskyFeedGenerator.Record;
252
+
"app.bsky.feed.generator",
253
+
rkey
254
+
);
255
+
const profile = (
256
+
await getSlingshotRecord(
257
+
identity.did,
258
+
"app.bsky.actor.profile",
259
+
"self"
260
+
)
261
+
).value as ATPAPI.AppBskyActorProfile.Record;
262
+
const anyprofile = profile as any;
263
+
const value =
264
+
feedgetRecord.value as ATPAPI.AppBskyFeedGenerator.Record;
135
265
136
-
return {
137
-
$type: "app.bsky.feed.defs#generatorView",
138
-
uri: feed,
139
-
cid: feedgetRecord.cid,
140
-
did: identity.did,
141
-
creator: /*AppBskyActorDefs.ProfileView*/ {
142
-
$type: "app.bsky.actor.defs#profileView",
266
+
return {
267
+
$type: "app.bsky.feed.defs#generatorView",
268
+
uri: feed,
269
+
cid: feedgetRecord.cid,
143
270
did: identity.did,
144
-
handle: identity.handle,
145
-
displayName: profile.displayName,
146
-
description: profile.description,
271
+
creator: /*AppBskyActorDefs.ProfileView*/ {
272
+
$type: "app.bsky.actor.defs#profileView",
273
+
did: identity.did,
274
+
handle: identity.handle,
275
+
displayName: profile.displayName,
276
+
description: profile.description,
277
+
avatar: buildBlobUrl(
278
+
identity.pds,
279
+
identity.did,
280
+
anyprofile.avatar.ref["$link"]
281
+
),
282
+
//associated?: ProfileAssociated
283
+
//indexedAt?: string
284
+
//createdAt?: string
285
+
//viewer?: ViewerState
286
+
//labels?: ComAtprotoLabelDefs.Label[]
287
+
//verification?: VerificationState
288
+
//status?: StatusView
289
+
},
290
+
displayName: value.displayName,
291
+
description: value.description,
292
+
//descriptionFacets?: AppBskyRichtextFacet.Main[]
147
293
avatar: buildBlobUrl(
148
294
identity.pds,
149
295
identity.did,
150
-
anyprofile.avatar.ref["$link"]
296
+
(value as any).avatar.ref["$link"]
151
297
),
152
-
//associated?: ProfileAssociated
153
-
//indexedAt?: string
154
-
//createdAt?: string
155
-
//viewer?: ViewerState
298
+
//likeCount?: number
299
+
//acceptsInteractions?: boolean
156
300
//labels?: ComAtprotoLabelDefs.Label[]
157
-
//verification?: VerificationState
158
-
//status?: StatusView
159
-
},
160
-
displayName: value.displayName,
161
-
description: value.description,
162
-
//descriptionFacets?: AppBskyRichtextFacet.Main[]
163
-
avatar: buildBlobUrl(
164
-
identity.pds,
165
-
identity.did,
166
-
(value as any).avatar.ref["$link"]
167
-
),
168
-
//likeCount?: number
169
-
//acceptsInteractions?: boolean
170
-
//labels?: ComAtprotoLabelDefs.Label[]
171
-
//viewer?: GeneratorViewerState
172
-
contentMode: value.contentMode,
173
-
indexedAt: new Date().toISOString(),
174
-
};
175
-
} catch (err) {
176
-
return undefined;
301
+
//viewer?: GeneratorViewerState
302
+
contentMode: value.contentMode,
303
+
indexedAt: new Date().toISOString(),
304
+
};
305
+
} catch (err) {
306
+
return undefined;
307
+
}
308
+
})
309
+
)
310
+
).filter(isGeneratorView);
311
+
312
+
const response: ViewServerTypes.AppBskyFeedGetFeedGenerators.OutputSchema =
313
+
{
314
+
feeds: feeds ? feeds : [],
315
+
};
316
+
317
+
return new Response(JSON.stringify(response), {
318
+
headers: withCors({ "Content-Type": "application/json" }),
319
+
});
320
+
}
321
+
case "app.bsky.feed.getFeed": {
322
+
const jsonTyped =
323
+
jsonUntyped as ViewServerTypes.AppBskyFeedGetFeed.QueryParams;
324
+
const cursor = jsonTyped.cursor;
325
+
const feed = jsonTyped.feed;
326
+
const limit = jsonTyped.limit;
327
+
const proxyauth = req.headers.get("authorization") || "";
328
+
329
+
const did = new ATPAPI.AtUri(feed).hostname;
330
+
const rkey = new ATPAPI.AtUri(feed).rkey;
331
+
const identity = await resolveIdentity(did);
332
+
const feedgetRecord = (
333
+
await getSlingshotRecord(
334
+
identity.did,
335
+
"app.bsky.feed.generator",
336
+
rkey
337
+
)
338
+
).value as ATPAPI.AppBskyFeedGenerator.Record;
339
+
340
+
const skeleton = (await cachedFetch(
341
+
`${didWebToHttps(
342
+
feedgetRecord.did
343
+
)}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${jsonTyped.feed}${
344
+
cursor ? `&cursor=${cursor}` : ""
345
+
}${limit ? `&limit=${limit}` : ""}`,
346
+
proxyauth
347
+
)) as ATPAPI.AppBskyFeedGetFeedSkeleton.OutputSchema;
348
+
349
+
const nextcursor = skeleton.cursor;
350
+
const dbgrqstid = skeleton.reqId;
351
+
const uriarray = skeleton.feed;
352
+
353
+
// Step 1: Chunk into 25 max
354
+
const chunks = [];
355
+
for (let i = 0; i < uriarray.length; i += 25) {
356
+
chunks.push(uriarray.slice(i, i + 25));
357
+
}
358
+
359
+
// Step 2: Hydrate via getPosts
360
+
const hydratedPosts: ATPAPI.AppBskyFeedDefs.FeedViewPost[] = [];
361
+
362
+
for (const chunk of chunks) {
363
+
const searchParams = new URLSearchParams();
364
+
for (const uri of chunk.map((item) => item.post)) {
365
+
searchParams.append("uris", uri);
366
+
}
367
+
368
+
const postResp = await ky
369
+
// TODO aaaaaa dont do this please use the new getServiceEndpointFromIdentity()
370
+
.get(`https://api.bsky.app/xrpc/app.bsky.feed.getPosts`, {
371
+
// headers: {
372
+
// Authorization: proxyauth,
373
+
// },
374
+
searchParams,
375
+
})
376
+
.json<ATPAPI.AppBskyFeedGetPosts.OutputSchema>();
377
+
378
+
for (const post of postResp.posts) {
379
+
const matchingSkeleton = uriarray.find(
380
+
(item) => item.post === post.uri
381
+
);
382
+
if (matchingSkeleton) {
383
+
//post.author.handle = post.author.handle + ".percent40.api.bsky.app"; // or any logic to modify it
384
+
hydratedPosts.push({
385
+
post,
386
+
reason: matchingSkeleton.reason,
387
+
//reply: matchingSkeleton,
388
+
});
177
389
}
178
-
})
179
-
)
180
-
).filter(isGeneratorView);
390
+
}
391
+
}
181
392
182
-
const response: ViewServerTypes.AppBskyFeedGetFeedGenerators.OutputSchema =
183
-
{
184
-
feeds: feeds ? feeds : [],
393
+
// Step 3: Compose final response
394
+
const response: ViewServerTypes.AppBskyFeedGetFeed.OutputSchema = {
395
+
feed: hydratedPosts,
396
+
cursor: nextcursor,
185
397
};
186
398
187
-
return new Response(JSON.stringify(response), {
188
-
headers: withCors({ "Content-Type": "application/json" }),
189
-
});
190
-
}
191
-
case "app.bsky.feed.getFeed": {
192
-
const jsonTyped =
193
-
jsonUntyped as ViewServerTypes.AppBskyFeedGetFeed.QueryParams;
194
-
const cursor = jsonTyped.cursor;
195
-
const feed = jsonTyped.feed;
196
-
const limit = jsonTyped.limit;
197
-
const proxyauth = req.headers.get("authorization") || "";
399
+
return new Response(JSON.stringify(response), {
400
+
headers: withCors({ "Content-Type": "application/json" }),
401
+
});
402
+
}
403
+
case "app.bsky.actor.getProfile": {
404
+
const jsonTyped =
405
+
jsonUntyped as ViewServerTypes.AppBskyActorGetProfile.QueryParams;
198
406
199
-
const did = new ATPAPI.AtUri(feed).hostname;
200
-
const rkey = new ATPAPI.AtUri(feed).rkey;
201
-
const identity = await resolveIdentity(did);
202
-
const feedgetRecord = (
203
-
await getSlingshotRecord(identity.did, "app.bsky.feed.generator", rkey)
204
-
).value as ATPAPI.AppBskyFeedGenerator.Record;
407
+
const response: ViewServerTypes.AppBskyActorGetProfile.OutputSchema =
408
+
((await this.resolveGetProfiles([jsonTyped.actor])) ?? [])[0];
205
409
206
-
const skeleton = (await cachedFetch(
207
-
`${didWebToHttps(
208
-
feedgetRecord.did
209
-
)}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${jsonTyped.feed}${
210
-
cursor ? `&cursor=${cursor}` : ""
211
-
}${limit ? `&limit=${limit}` : ""}`,
212
-
proxyauth
213
-
)) as ATPAPI.AppBskyFeedGetFeedSkeleton.OutputSchema;
410
+
return new Response(JSON.stringify(response), {
411
+
headers: withCors({ "Content-Type": "application/json" }),
412
+
});
413
+
}
214
414
215
-
const nextcursor = skeleton.cursor;
216
-
const dbgrqstid = skeleton.reqId;
217
-
const uriarray = skeleton.feed;
415
+
case "app.bsky.actor.getProfiles": {
416
+
const jsonhalfTyped =
417
+
jsonUntyped as ViewServerTypes.AppBskyActorGetProfiles.QueryParams;
418
+
const actors = jsonhalfTyped.actors as string[] | string
419
+
const queryactors = Array.isArray(actors)
420
+
? actors
421
+
: [actors];
422
+
//console.log("queryactors:",jsonTyped.actors)
423
+
const response: ViewServerTypes.AppBskyActorGetProfiles.OutputSchema = {
424
+
profiles: (await this.resolveGetProfiles(queryactors)) ?? [],
425
+
};
218
426
219
-
// Step 1: Chunk into 25 max
220
-
const chunks = [];
221
-
for (let i = 0; i < uriarray.length; i += 25) {
222
-
chunks.push(uriarray.slice(i, i + 25));
427
+
return new Response(JSON.stringify(response), {
428
+
headers: withCors({ "Content-Type": "application/json" }),
429
+
});
223
430
}
431
+
case "app.bsky.feed.getAuthorFeed": {
432
+
const jsonTyped =
433
+
jsonUntyped as ViewServerTypes.AppBskyFeedGetAuthorFeed.QueryParams;
224
434
225
-
// Step 2: Hydrate via getPosts
226
-
const hydratedPosts: ATPAPI.AppBskyFeedDefs.FeedViewPost[] = [];
435
+
const userindexservice = "";
436
+
const isbskyfallback = true;
437
+
if (isbskyfallback) {
438
+
return this.sendItToApiBskyApp(req);
439
+
}
227
440
228
-
for (const chunk of chunks) {
229
-
const searchParams = new URLSearchParams();
230
-
for (const uri of chunk.map((item) => item.post)) {
231
-
searchParams.append("uris", uri);
441
+
const response: ViewServerTypes.AppBskyFeedGetAuthorFeed.OutputSchema =
442
+
{};
443
+
444
+
return new Response(JSON.stringify(response), {
445
+
headers: withCors({ "Content-Type": "application/json" }),
446
+
});
447
+
}
448
+
case "app.bsky.feed.getPostThread": {
449
+
const jsonTyped =
450
+
jsonUntyped as ViewServerTypes.AppBskyFeedGetPostThread.QueryParams;
451
+
452
+
const userindexservice = "";
453
+
const isbskyfallback = true;
454
+
if (isbskyfallback) {
455
+
return this.sendItToApiBskyApp(req);
232
456
}
233
457
234
-
const postResp = await ky
235
-
.get(`https://api.bsky.app/xrpc/app.bsky.feed.getPosts`, {
236
-
// headers: {
237
-
// Authorization: proxyauth,
238
-
// },
239
-
searchParams,
240
-
})
241
-
.json<ATPAPI.AppBskyFeedGetPosts.OutputSchema>();
458
+
const response: ViewServerTypes.AppBskyFeedGetPostThread.OutputSchema =
459
+
{};
242
460
243
-
for (const post of postResp.posts) {
244
-
const matchingSkeleton = uriarray.find(
245
-
(item) => item.post === post.uri
246
-
);
247
-
if (matchingSkeleton) {
248
-
//post.author.handle = post.author.handle + ".percent40.api.bsky.app"; // or any logic to modify it
249
-
hydratedPosts.push({
250
-
post,
251
-
reason: matchingSkeleton.reason,
252
-
//reply: matchingSkeleton,
253
-
});
254
-
}
461
+
return new Response(JSON.stringify(response), {
462
+
headers: withCors({ "Content-Type": "application/json" }),
463
+
});
464
+
}
465
+
case "app.bsky.unspecced.getPostThreadV2": {
466
+
const jsonTyped =
467
+
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetPostThreadV2.QueryParams;
468
+
469
+
const userindexservice = "";
470
+
const isbskyfallback = true;
471
+
if (isbskyfallback) {
472
+
return this.sendItToApiBskyApp(req);
255
473
}
474
+
475
+
const response: ViewServerTypes.AppBskyUnspeccedGetPostThreadV2.OutputSchema =
476
+
{};
477
+
478
+
return new Response(JSON.stringify(response), {
479
+
headers: withCors({ "Content-Type": "application/json" }),
480
+
});
256
481
}
257
482
258
-
// Step 3: Compose final response
259
-
const response: ViewServerTypes.AppBskyFeedGetFeed.OutputSchema = {
260
-
feed: hydratedPosts,
261
-
cursor: nextcursor,
262
-
};
483
+
// case "app.bsky.actor.getProfile": {
484
+
// const jsonTyped =
485
+
// jsonUntyped as ViewServerTypes.AppBskyActorGetProfile.QueryParams;
263
486
264
-
return new Response(JSON.stringify(response), {
265
-
headers: withCors({ "Content-Type": "application/json" }),
266
-
});
267
-
}
268
-
case "app.bsky.actor.getProfile": {
269
-
const jsonTyped =
270
-
jsonUntyped as ViewServerTypes.AppBskyActorGetProfile.QueryParams;
487
+
// const response: ViewServerTypes.AppBskyActorGetProfile.OutputSchema= {};
271
488
272
-
const userindexservice = "";
273
-
const isbskyfallback = true;
274
-
if (isbskyfallback) {
275
-
return sendItToApiBskyApp(req);
489
+
// return new Response(JSON.stringify(response), {
490
+
// headers: withCors({ "Content-Type": "application/json" }),
491
+
// });
492
+
// }
493
+
// case "app.bsky.actor.getProfiles": {
494
+
// const jsonTyped = jsonUntyped as ViewServerTypes.AppBskyActorGetProfiles.QueryParams;
495
+
496
+
// const response: ViewServerTypes.AppBskyActorGetProfiles.OutputSchema = {};
497
+
498
+
// return new Response(JSON.stringify(response), {
499
+
// headers: withCors({ "Content-Type": "application/json" }),
500
+
// });
501
+
// }
502
+
// case "whatever": {
503
+
// const jsonTyped = jsonUntyped as ViewServerTypes.AppBskyFeedGetAuthorFeed.QueryParams;
504
+
505
+
// const response: ViewServerTypes.AppBskyFeedGetAuthorFeed.OutputSchema = {}
506
+
507
+
// return new Response(JSON.stringify(response), {
508
+
// headers: withCors({ "Content-Type": "application/json" }),
509
+
// });
510
+
// }
511
+
case "app.bsky.notification.listNotifications": {
512
+
if (!authdid) return new Response("Not Found", { status: 404 });
513
+
const jsonTyped =
514
+
jsonUntyped as ViewServerTypes.AppBskyNotificationListNotifications.QueryParams;
515
+
516
+
const response: ViewServerTypes.AppBskyNotificationListNotifications.OutputSchema =
517
+
await this.queryNotificationsList(authdid, jsonTyped.cursor, jsonTyped.limit);
518
+
519
+
return new Response(JSON.stringify(response), {
520
+
headers: withCors({ "Content-Type": "application/json" }),
521
+
});
276
522
}
523
+
case "app.bsky.feed.getPosts": {
524
+
const jsonTyped =
525
+
jsonUntyped as ViewServerTypes.AppBskyFeedGetPosts.QueryParams;
526
+
const inputUris = Array.isArray(jsonTyped.uris)
527
+
? jsonTyped.uris
528
+
: [jsonTyped.uris];
529
+
const response: ViewServerTypes.AppBskyFeedGetPosts.OutputSchema = {
530
+
posts: await this.resolveGetPosts(inputUris),
531
+
};
532
+
533
+
return new Response(JSON.stringify(response), {
534
+
headers: withCors({ "Content-Type": "application/json" }),
535
+
});
536
+
}
537
+
538
+
case "app.bsky.unspecced.getConfig": {
539
+
const jsonTyped =
540
+
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetConfig.QueryParams;
277
541
278
-
const response: ViewServerTypes.AppBskyActorGetProfile.OutputSchema = {};
542
+
const response: ViewServerTypes.AppBskyUnspeccedGetConfig.OutputSchema =
543
+
{
544
+
checkEmailConfirmed: true,
545
+
liveNow: [
546
+
{
547
+
$type: "app.bsky.unspecced.getConfig#liveNowConfig",
548
+
did: "did:plc:mn45tewwnse5btfftvd3powc",
549
+
domains: ["local3768forumtest.whey.party"],
550
+
},
551
+
],
552
+
};
279
553
280
-
return new Response(JSON.stringify(response), {
281
-
headers: withCors({ "Content-Type": "application/json" }),
282
-
});
283
-
}
554
+
return new Response(JSON.stringify(response), {
555
+
headers: withCors({ "Content-Type": "application/json" }),
556
+
});
557
+
}
558
+
case "app.bsky.graph.getLists": {
559
+
const jsonTyped =
560
+
jsonUntyped as ViewServerTypes.AppBskyGraphGetLists.QueryParams;
284
561
285
-
case "app.bsky.actor.getProfiles": {
286
-
const jsonTyped =
287
-
jsonUntyped as ViewServerTypes.AppBskyActorGetProfiles.QueryParams;
562
+
const response: ViewServerTypes.AppBskyGraphGetLists.OutputSchema = {
563
+
lists: [],
564
+
};
288
565
289
-
const userindexservice = "";
290
-
const isbskyfallback = true;
291
-
if (isbskyfallback) {
292
-
return sendItToApiBskyApp(req);
566
+
return new Response(JSON.stringify(response), {
567
+
headers: withCors({ "Content-Type": "application/json" }),
568
+
});
293
569
}
570
+
//https://shimeji.us-east.host.bsky.network/xrpc/app.bsky.unspecced.getTrendingTopics?limit=14
571
+
case "app.bsky.unspecced.getTrendingTopics": {
572
+
const jsonTyped =
573
+
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.QueryParams;
294
574
295
-
const response: ViewServerTypes.AppBskyActorGetProfiles.OutputSchema = {};
575
+
const faketopics: ATPAPI.AppBskyUnspeccedDefs.TrendingTopic[] = [
576
+
{
577
+
$type: "app.bsky.unspecced.defs#trendingTopic",
578
+
topic: "Git Repo",
579
+
displayName: "Git Repo",
580
+
description: "Git Repo",
581
+
link: "https://tangled.sh/@whey.party/skylite",
582
+
},
583
+
{
584
+
$type: "app.bsky.unspecced.defs#trendingTopic",
585
+
topic: "Red Dwarf Lite",
586
+
displayName: "Red Dwarf Lite",
587
+
description: "Red Dwarf Lite",
588
+
link: "https://reddwarf.whey.party/",
589
+
},
590
+
{
591
+
$type: "app.bsky.unspecced.defs#trendingTopic",
592
+
topic: "whey dot party",
593
+
displayName: "whey dot party",
594
+
description: "whey dot party",
595
+
link: "https://whey.party/",
596
+
},
597
+
];
296
598
297
-
return new Response(JSON.stringify(response), {
298
-
headers: withCors({ "Content-Type": "application/json" }),
299
-
});
300
-
}
301
-
case "app.bsky.feed.getAuthorFeed": {
302
-
const jsonTyped =
303
-
jsonUntyped as ViewServerTypes.AppBskyFeedGetAuthorFeed.QueryParams;
599
+
const response: ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.OutputSchema =
600
+
{
601
+
topics: faketopics,
602
+
suggested: faketopics,
603
+
};
304
604
305
-
const userindexservice = "";
306
-
const isbskyfallback = true;
307
-
if (isbskyfallback) {
308
-
return sendItToApiBskyApp(req);
605
+
return new Response(JSON.stringify(response), {
606
+
headers: withCors({ "Content-Type": "application/json" }),
607
+
});
608
+
}
609
+
default: {
610
+
return new Response(
611
+
JSON.stringify({
612
+
error: "XRPCNotSupported",
613
+
message:
614
+
"(default) HEY hello there my name is whey dot party and you have used my custom appview that is very cool but have you considered that XRPC Not Supported",
615
+
}),
616
+
{
617
+
status: 404,
618
+
headers: withCors({ "Content-Type": "application/json" }),
619
+
}
620
+
);
309
621
}
622
+
}
310
623
311
-
const response: ViewServerTypes.AppBskyFeedGetAuthorFeed.OutputSchema =
312
-
{};
624
+
// return new Response("Not Found", { status: 404 });
625
+
}
313
626
314
-
return new Response(JSON.stringify(response), {
315
-
headers: withCors({ "Content-Type": "application/json" }),
316
-
});
627
+
async sendItToApiBskyApp(req: Request): Promise<Response> {
628
+
const url = new URL(req.url);
629
+
const pathname = url.pathname;
630
+
const searchParams = searchParamsToJson(url.searchParams);
631
+
let reqBody: undefined | string;
632
+
let jsonbody: undefined | Record<string, unknown>;
633
+
if (req.body) {
634
+
const body = await req.json();
635
+
jsonbody = body;
636
+
// console.log(
637
+
// `called at euh reqreqreqreq: ${pathname}\n\n${JSON.stringify(body)}`
638
+
// );
639
+
reqBody = JSON.stringify(body, null, 2);
317
640
}
318
-
case "app.bsky.feed.getPostThread": {
319
-
const jsonTyped =
320
-
jsonUntyped as ViewServerTypes.AppBskyFeedGetPostThread.QueryParams;
641
+
const bskyUrl = `https://public.api.bsky.app${pathname}${url.search}`;
642
+
console.log("request", searchParams);
643
+
const proxyHeaders = new Headers(req.headers);
644
+
645
+
// Remove Authorization and set browser-like User-Agent
646
+
proxyHeaders.delete("authorization");
647
+
proxyHeaders.delete("Access-Control-Allow-Origin"),
648
+
proxyHeaders.set(
649
+
"user-agent",
650
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
651
+
);
652
+
proxyHeaders.set("Access-Control-Allow-Origin", "*");
653
+
654
+
const proxyRes = await fetch(bskyUrl, {
655
+
method: req.method,
656
+
headers: proxyHeaders,
657
+
body: ["GET", "HEAD"].includes(req.method.toUpperCase())
658
+
? undefined
659
+
: reqBody,
660
+
});
661
+
662
+
const resBody = await proxyRes.text();
663
+
664
+
// console.log(
665
+
// "โ Response:",
666
+
// JSON.stringify(await JSON.parse(resBody), null, 2)
667
+
// );
321
668
322
-
const userindexservice = "";
323
-
const isbskyfallback = true;
324
-
if (isbskyfallback) {
325
-
return sendItToApiBskyApp(req);
669
+
return new Response(resBody, {
670
+
status: proxyRes.status,
671
+
headers: proxyRes.headers,
672
+
});
673
+
}
674
+
675
+
viewServerIndexer(ctx: indexHandlerContext) {
676
+
const record = validateRecord(ctx.value);
677
+
switch (record?.$type) {
678
+
case "app.bsky.feed.like": {
679
+
return;
326
680
}
681
+
default: {
682
+
// what the hell
683
+
return;
684
+
}
685
+
}
686
+
}
327
687
328
-
const response: ViewServerTypes.AppBskyFeedGetPostThread.OutputSchema =
329
-
{};
688
+
/**
689
+
* please do not use this, use openDbForDid() instead
690
+
* @param did
691
+
* @returns
692
+
*/
693
+
internalCreateDbForDid(did: string): Database {
694
+
const path = `${this.config.baseDbPath}/${did}.sqlite`;
695
+
const db = new Database(path);
696
+
// TODO maybe split the user db schema between view server and index server
697
+
setupUserDb(db);
698
+
//await db.exec(/* CREATE IF NOT EXISTS statements */);
699
+
return db;
700
+
}
701
+
public handlesDid(did: string): boolean {
702
+
return this.userManager.handlesDid(did);
703
+
}
704
+
705
+
async resolveGetPosts(
706
+
uris: string[]
707
+
): Promise<ATPAPI.AppBskyFeedDefs.PostView[]> {
708
+
const grouped: Record<string, string[]> = {};
709
+
710
+
// Group URIs by resolved endpoint
711
+
for (const uri of uris) {
712
+
const did = new AtUri(uri).host;
713
+
const endpoint = await getSkyliteEndpoint(did);
714
+
if (!endpoint) continue;
330
715
331
-
return new Response(JSON.stringify(response), {
332
-
headers: withCors({ "Content-Type": "application/json" }),
333
-
});
716
+
if (!grouped[endpoint]) {
717
+
grouped[endpoint] = [];
718
+
}
719
+
grouped[endpoint].push(uri);
334
720
}
335
-
case "app.bsky.unspecced.getPostThreadV2": {
336
-
const jsonTyped =
337
-
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetPostThreadV2.QueryParams;
721
+
722
+
const postviews: ATPAPI.AppBskyFeedDefs.PostView[] = [];
338
723
339
-
const userindexservice = "";
340
-
const isbskyfallback = true;
341
-
if (isbskyfallback) {
342
-
return sendItToApiBskyApp(req);
724
+
// Fetch posts per endpoint
725
+
for (const [endpoint, urisForEndpoint] of Object.entries(grouped)) {
726
+
const query = urisForEndpoint
727
+
.map((u) => `uris=${encodeURIComponent(u)}`)
728
+
.join("&");
729
+
730
+
const url = `${endpoint}/xrpc/app.bsky.feed.getPosts?${query}`;
731
+
const resp = await fetch(url);
732
+
if (!resp.ok) {
733
+
throw new Error(
734
+
`Failed to fetch posts from ${endpoint} for uris=${urisForEndpoint.join(
735
+
","
736
+
)}`
737
+
);
343
738
}
344
739
345
-
const response: ViewServerTypes.AppBskyUnspeccedGetPostThreadV2.OutputSchema =
346
-
{};
740
+
const raw =
741
+
(await resp.json()) as ATPAPI.AppBskyFeedGetPosts.OutputSchema;
742
+
postviews.push(...raw.posts);
743
+
}
744
+
745
+
return postviews;
746
+
}
747
+
748
+
async resolveGetProfiles(
749
+
dids: string[]
750
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileViewDetailed[] | undefined> {
751
+
const profiles: ATPAPI.AppBskyActorDefs.ProfileViewDetailed[] = [];
752
+
753
+
for (const did of dids) {
754
+
const endpoint = await getSkyliteEndpoint(did);
755
+
const url = `${endpoint}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(
756
+
did
757
+
)}`;
758
+
const resp = await fetch(url);
759
+
if (!resp.ok)
760
+
throw new Error(`Failed to fetch profile for ${did} via ${url}`);
347
761
348
-
return new Response(JSON.stringify(response), {
349
-
headers: withCors({ "Content-Type": "application/json" }),
350
-
});
762
+
const raw =
763
+
(await resp.json()) as ATPAPI.AppBskyActorGetProfile.OutputSchema;
764
+
profiles.push(raw);
351
765
}
352
766
353
-
// case "app.bsky.actor.getProfile": {
354
-
// const jsonTyped =
355
-
// jsonUntyped as ViewServerTypes.AppBskyActorGetProfile.QueryParams;
767
+
return profiles;
768
+
}
769
+
async queryNotificationsList(
770
+
did: string,
771
+
cursor?: string,
772
+
limit?: number
773
+
): Promise<ATPAPI.AppBskyNotificationListNotifications.OutputSchema> {
774
+
if (!this.handlesDid(did)) {
775
+
return { notifications: [] };
776
+
}
777
+
const db = this.userManager.getDbForDid(did);
778
+
if (!db) {
779
+
return { notifications: [] };
780
+
}
356
781
357
-
// const response: ViewServerTypes.AppBskyActorGetProfile.OutputSchema= {};
782
+
const NOTIFS_LIMIT = limit ?? 30;
783
+
const offset = cursor ? parseInt(cursor, 10) : 0;
358
784
359
-
// return new Response(JSON.stringify(response), {
360
-
// headers: withCors({ "Content-Type": "application/json" }),
361
-
// });
362
-
// }
363
-
// case "app.bsky.actor.getProfiles": {
364
-
// const jsonTyped = jsonUntyped as ViewServerTypes.AppBskyActorGetProfiles.QueryParams;
785
+
const mapReason = (
786
+
field: string
787
+
):
788
+
| ATPAPI.AppBskyNotificationListNotifications.Notification["reason"]
789
+
| undefined => {
790
+
switch (field) {
791
+
//'like' | 'repost' | 'follow' | 'mention' | 'reply' | 'quote' | 'starterpack-joined' | 'verified' | 'unverified' | 'like-via-repost' | 'repost-via-repost' |
792
+
case "app.bsky.feed.like:subject.uri":
793
+
return "like";
794
+
case "app.bsky.feed.like:via.uri":
795
+
return "liked-via-repost";
796
+
case "app.bsky.feed.repost:subject.uri":
797
+
return "repost";
798
+
case "app.bsky.feed.repost:via.uri":
799
+
return "repost-via-repost";
800
+
case "app.bsky.feed.post:reply.root.uri":
801
+
return "reply";
802
+
case "app.bsky.feed.post:reply.parent.uri":
803
+
return "reply";
804
+
case "app.bsky.feed.post:embed.media.record.record.uri":
805
+
return "quote";
806
+
case "app.bsky.feed.post:embed.record.uri":
807
+
return "quote";
808
+
//case"app.bsky.feed.threadgate:post": return "threadgate subject
809
+
//case"app.bsky.feed.threadgate:hiddenReplies": return "threadgate items (array)
810
+
case "app.bsky.feed.post:facets.features.did":
811
+
return "mention";
812
+
//case"app.bsky.graph.block:subject": return "blocks
813
+
case "app.bsky.graph.follow:subject":
814
+
return "follow";
815
+
//case"app.bsky.graph.listblock:subject": return "list item (blocks)
816
+
//case"app.bsky.graph.listblock:list": return "blocklist mention (might not exist)
817
+
//case"app.bsky.graph.listitem:subject": return "list item (blocks)
818
+
//"app.bsky.graph.listitem:list": return "list mention
819
+
// case "like": return "like";
820
+
// case "repost": return "repost";
821
+
// case "follow": return "follow";
822
+
// case "replyparent": return "reply";
823
+
// case "replyroot": return "reply";
824
+
// case "mention": return "mention";
825
+
default:
826
+
return undefined;
827
+
}
828
+
};
365
829
366
-
// const response: ViewServerTypes.AppBskyActorGetProfiles.OutputSchema = {};
830
+
// --- Build Query ---
831
+
let query = `
832
+
SELECT srcuri, suburi, srcfield, indexedAt
833
+
FROM backlink_skeleton
834
+
WHERE
835
+
-- Find actions targeting the user's content or profile
836
+
(suburi LIKE ? OR suburi = ?)
837
+
-- Exclude notifications from the user themselves
838
+
AND srcuri NOT LIKE ?
839
+
ORDER BY indexedAt DESC, srcuri DESC
840
+
LIMIT ? OFFSET ?
841
+
`;
842
+
const params: (string | number)[] = [
843
+
`at://${did}/%`,
844
+
did,
845
+
`at://${did}/%`,
846
+
NOTIFS_LIMIT,
847
+
offset
848
+
];
367
849
368
-
// return new Response(JSON.stringify(response), {
369
-
// headers: withCors({ "Content-Type": "application/json" }),
370
-
// });
850
+
// if (cursor) {
851
+
// const [indexedAt, srcuri] = cursor.split("::");
852
+
// if (indexedAt && srcuri && !Number.isNaN(+indexedAt)) {
853
+
// query += ` AND (indexedAt < ? OR (indexedAt = ? AND srcuri < ?))`;
854
+
// params.push(+indexedAt, +indexedAt, srcuri);
855
+
// }
371
856
// }
372
-
// case "whatever": {
373
-
// const jsonTyped = jsonUntyped as ViewServerTypes.AppBskyFeedGetAuthorFeed.QueryParams;
374
857
375
-
// const response: ViewServerTypes.AppBskyFeedGetAuthorFeed.OutputSchema = {}
858
+
// query += ` ORDER BY indexedAt DESC, srcuri DESC LIMIT ${NOTIFS_LIMIT}`;
376
859
377
-
// return new Response(JSON.stringify(response), {
378
-
// headers: withCors({ "Content-Type": "application/json" }),
379
-
// });
380
-
// }
381
-
// case "app.bsky.notification.listNotifications": {
382
-
// const jsonTyped =
383
-
// jsonUntyped as ViewServerTypes.AppBskyNotificationListNotifications.QueryParams;
860
+
// --- Fetch and Process ---
861
+
const stmt = db.prepare(query);
862
+
const rows = stmt.all(...params) as {
863
+
srcuri: string;
864
+
suburi: string; // might be uri, might be just a did
865
+
srcfield: string;
866
+
indexedAt: number;
867
+
}[];
384
868
385
-
// const response: ViewServerTypes.AppBskyNotificationListNotifications.OutputSchema = {};
869
+
const notificationPromises = rows.map(async (row) => {
870
+
try {
871
+
const reason = mapReason(row.srcfield);
872
+
// i have a hunch that follow notifs are crashing the client
873
+
if (!reason || temporarydevelopmentblockednotiftypes.includes(reason)) {
874
+
return null;
875
+
}
876
+
// Skip if it's a backlink type we don't have a notification for
877
+
if (!reason) return null;
386
878
387
-
// return new Response(JSON.stringify(response), {
388
-
// headers: withCors({ "Content-Type": "application/json" }),
389
-
// });
390
-
// }
879
+
const srcURI = new AtUri(row.srcuri);
880
+
const [authorRes, recordRes] = await Promise.allSettled([
881
+
this.resolveProfileView(srcURI.host, ""),
882
+
getSlingshotRecord(srcURI.host, srcURI.collection, srcURI.rkey),
883
+
]);
391
884
392
-
case "app.bsky.unspecced.getConfig": {
393
-
const jsonTyped =
394
-
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetConfig.QueryParams;
885
+
const author = authorRes.status === "fulfilled" ? authorRes.value : null;
886
+
const getrecord = recordRes.status === "fulfilled" ? recordRes.value : null;
395
887
396
-
const response: ViewServerTypes.AppBskyUnspeccedGetConfig.OutputSchema = {
397
-
checkEmailConfirmed: true,
398
-
liveNow: [
399
-
{
400
-
$type: "app.bsky.unspecced.getConfig#liveNowConfig",
401
-
did: "did:plc:mn45tewwnse5btfftvd3powc",
402
-
domains: ["local3768forumtest.whey.party"],
403
-
},
404
-
],
405
-
};
888
+
const reasonsubject =
889
+
row.suburi.startsWith("at://") ? row.suburi : `at://${row.suburi}`;
890
+
// If we can't resolve the author or the record, we can't form a valid notification
891
+
if (!author || !getrecord || !reason || !reasonsubject) return null;
406
892
407
-
return new Response(JSON.stringify(response), {
408
-
headers: withCors({ "Content-Type": "application/json" }),
409
-
});
410
-
}
411
-
case "app.bsky.graph.getLists": {
412
-
const jsonTyped =
413
-
jsonUntyped as ViewServerTypes.AppBskyGraphGetLists.QueryParams;
893
+
author.viewer = {
894
+
"muted": false,
895
+
"blockedBy": false,
896
+
//"following":
897
+
} // TODO: proper mutes and blocks here
414
898
415
-
const response: ViewServerTypes.AppBskyGraphGetLists.OutputSchema = {
416
-
lists: [],
417
-
};
899
+
if (!getrecord?.value?.$type) getrecord.value.$type = srcURI.collection
900
+
return {
901
+
uri: row.srcuri,
902
+
cid: getrecord.cid,
903
+
author: author,
904
+
reason: reason,
905
+
// The reasonSubject is the URI of the post that was liked, reposted, or replied to
906
+
reasonSubject: reasonsubject,
907
+
record: getrecord.value,
908
+
isRead: false, // Placeholder for read-state logic
909
+
indexedAt: new Date(row.indexedAt).toISOString(),
910
+
labels: [], // Placeholder for label logic
911
+
} as ATPAPI.AppBskyNotificationListNotifications.Notification;
912
+
} catch (e) {console.log("error:",e)}
913
+
});
418
914
419
-
return new Response(JSON.stringify(response), {
420
-
headers: withCors({ "Content-Type": "application/json" }),
915
+
const seen = new Set<string>();
916
+
const notifications = (await Promise.all(notificationPromises))
917
+
.filter((n): n is ATPAPI.AppBskyNotificationListNotifications.Notification => {
918
+
if (!n) return false;
919
+
const key = `${n.uri}:${n.reason}:${n.reasonSubject}`;
920
+
if (seen.has(key)) return false;
921
+
seen.add(key);
922
+
return true;
421
923
});
422
-
}
423
-
//https://shimeji.us-east.host.bsky.network/xrpc/app.bsky.unspecced.getTrendingTopics?limit=14
424
-
case "app.bsky.unspecced.getTrendingTopics": {
425
-
const jsonTyped =
426
-
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.QueryParams;
427
924
428
-
const faketopics: ATPAPI.AppBskyUnspeccedDefs.TrendingTopic[] = [
429
-
{
430
-
$type: "app.bsky.unspecced.defs#trendingTopic",
431
-
topic: "Git Repo",
432
-
displayName: "Git Repo",
433
-
description: "Git Repo",
434
-
link: "https://tangled.sh/@whey.party/skylite",
435
-
},
436
-
{
437
-
$type: "app.bsky.unspecced.defs#trendingTopic",
438
-
topic: "Red Dwarf Lite",
439
-
displayName: "Red Dwarf Lite",
440
-
description: "Red Dwarf Lite",
441
-
link: "https://reddwarf.whey.party/",
442
-
},
443
-
{
444
-
$type: "app.bsky.unspecced.defs#trendingTopic",
445
-
topic: "whey dot party",
446
-
displayName: "whey dot party",
447
-
description: "whey dot party",
448
-
link: "https://whey.party/",
449
-
},
450
-
];
925
+
// --- Create next cursor ---
926
+
const nextCursor:number = Number(offset) + Number(limit ?? 0)
927
+
// const lastItem = rows[rows.length - 1];
928
+
// const nextCursor = lastItem
929
+
// ? `${lastItem.indexedAt}::${lastItem.srcuri}`
930
+
// : undefined;
451
931
452
-
const response: ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.OutputSchema =
453
-
{
454
-
topics: faketopics,
455
-
suggested: faketopics,
456
-
};
932
+
return {
933
+
cursor: `${nextCursor}`,//nextCursor,
934
+
notifications: notifications,
935
+
priority:false,
936
+
seenAt: new Date().toISOString()
937
+
};
938
+
}
939
+
async resolveProfileView(
940
+
did: string,
941
+
type: ""
942
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileView | undefined>;
943
+
async resolveProfileView(
944
+
did: string,
945
+
type: "Basic"
946
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileViewBasic | undefined>;
947
+
async resolveProfileView(
948
+
did: string,
949
+
type: "Detailed"
950
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileViewDetailed | undefined>;
951
+
async resolveProfileView(
952
+
did: string,
953
+
type: "" | "Basic" | "Detailed"
954
+
): Promise<
955
+
| ATPAPI.AppBskyActorDefs.ProfileView
956
+
| ATPAPI.AppBskyActorDefs.ProfileViewBasic
957
+
| ATPAPI.AppBskyActorDefs.ProfileViewDetailed
958
+
| undefined
959
+
> {
960
+
const record = (
961
+
await getSlingshotRecord(did, "app.bsky.actor.profile", "self")
962
+
).value as ATPAPI.AppBskyActorProfile.Record;
457
963
458
-
return new Response(JSON.stringify(response), {
459
-
headers: withCors({ "Content-Type": "application/json" }),
460
-
});
964
+
const identity = await resolveIdentity(did);
965
+
const avatarcid = uncid(record.avatar?.ref);
966
+
const avatar = avatarcid
967
+
? buildBlobUrl(identity.pds, identity.did, avatarcid)
968
+
: undefined;
969
+
const bannercid = uncid(record.banner?.ref);
970
+
const banner = bannercid
971
+
? buildBlobUrl(identity.pds, identity.did, bannercid)
972
+
: undefined;
973
+
// simulate different types returned
974
+
switch (type) {
975
+
case "": {
976
+
const result: ATPAPI.AppBskyActorDefs.ProfileView = {
977
+
$type: "app.bsky.actor.defs#profileView",
978
+
did: did,
979
+
handle: identity.handle,
980
+
displayName: record.displayName ?? identity.handle,
981
+
description: record.description ?? undefined,
982
+
avatar: avatar, // create profile URL from resolved identity
983
+
//associated?: ProfileAssociated,
984
+
indexedAt: record.createdAt
985
+
? new Date(record.createdAt).toISOString()
986
+
: undefined,
987
+
createdAt: record.createdAt
988
+
? new Date(record.createdAt).toISOString()
989
+
: undefined,
990
+
//viewer?: ViewerState,
991
+
//labels?: ComAtprotoLabelDefs.Label[],
992
+
//verification?: VerificationState,
993
+
//status?: StatusView,
994
+
};
995
+
return result;
996
+
}
997
+
case "Basic": {
998
+
const result: ATPAPI.AppBskyActorDefs.ProfileViewBasic = {
999
+
$type: "app.bsky.actor.defs#profileViewBasic",
1000
+
did: did,
1001
+
handle: identity.handle,
1002
+
displayName: record.displayName ?? identity.handle,
1003
+
avatar: avatar, // create profile URL from resolved identity
1004
+
//associated?: ProfileAssociated,
1005
+
createdAt: record.createdAt
1006
+
? new Date(record.createdAt).toISOString()
1007
+
: undefined,
1008
+
//viewer?: ViewerState,
1009
+
//labels?: ComAtprotoLabelDefs.Label[],
1010
+
//verification?: VerificationState,
1011
+
//status?: StatusView,
1012
+
};
1013
+
return result;
1014
+
}
1015
+
case "Detailed": {
1016
+
const response: ViewServerTypes.AppBskyActorGetProfile.OutputSchema =
1017
+
((await this.resolveGetProfiles([did])) ?? [])[0];
1018
+
return response;
1019
+
}
1020
+
default:
1021
+
throw new Error("Invalid type");
461
1022
}
462
-
default: {
463
-
return new Response(
464
-
JSON.stringify({
465
-
error: "XRPCNotSupported",
466
-
message:
467
-
"HEY hello there my name is whey dot party and you have used my custom appview that is very cool but have you considered that XRPC Not Supported",
468
-
}),
469
-
{
470
-
status: 404,
471
-
headers: withCors({ "Content-Type": "application/json" }),
472
-
}
473
-
);
1023
+
}
1024
+
}
1025
+
1026
+
export class ViewServerUserManager {
1027
+
public viewServer: ViewServer;
1028
+
1029
+
constructor(viewServer: ViewServer) {
1030
+
this.viewServer = viewServer;
1031
+
}
1032
+
1033
+
public users = new Map<string, UserViewServer>();
1034
+
public handlesDid(did: string): boolean {
1035
+
return this.users.has(did);
1036
+
}
1037
+
1038
+
/*async*/ addUser(did: string) {
1039
+
if (this.users.has(did)) return;
1040
+
const instance = new UserViewServer(this, did);
1041
+
//await instance.initialize();
1042
+
this.users.set(did, instance);
1043
+
}
1044
+
1045
+
// async handleRequest({
1046
+
// did,
1047
+
// route,
1048
+
// req,
1049
+
// }: {
1050
+
// did: string;
1051
+
// route: string;
1052
+
// req: Request;
1053
+
// }) {
1054
+
// if (!this.users.has(did)) await this.addUser(did);
1055
+
// const user = this.users.get(did)!;
1056
+
// return await user.handleHttpRequest(route, req);
1057
+
// }
1058
+
1059
+
removeUser(did: string) {
1060
+
const instance = this.users.get(did);
1061
+
if (!instance) return;
1062
+
/*await*/ instance.shutdown();
1063
+
this.users.delete(did);
1064
+
}
1065
+
1066
+
getDbForDid(did: string): Database | null {
1067
+
if (!this.users.has(did)) {
1068
+
return null;
474
1069
}
1070
+
return this.users.get(did)?.db ?? null;
475
1071
}
476
1072
477
-
return new Response("Not Found", { status: 404 });
1073
+
coldStart(db: Database) {
1074
+
const rows = db.prepare("SELECT did FROM users").all();
1075
+
for (const row of rows) {
1076
+
this.addUser(row.did);
1077
+
}
1078
+
}
478
1079
}
479
1080
480
-
async function sendItToApiBskyApp(req: Request): Promise<Response> {
481
-
const url = new URL(req.url);
482
-
const pathname = url.pathname;
483
-
const searchParams = searchParamsToJson(url.searchParams);
484
-
let reqBody: undefined | string;
485
-
let jsonbody: undefined | Record<string, unknown>;
486
-
if (req.body) {
487
-
const body = await req.json();
488
-
jsonbody = body;
489
-
// console.log(
490
-
// `called at euh reqreqreqreq: ${pathname}\n\n${JSON.stringify(body)}`
491
-
// );
492
-
reqBody = JSON.stringify(body, null, 2);
493
-
}
494
-
const bskyUrl = `https://api.bsky.app${pathname}${url.search}`;
495
-
const proxyHeaders = new Headers(req.headers);
1081
+
class UserViewServer {
1082
+
public viewServerUserManager: ViewServerUserManager;
1083
+
did: string;
1084
+
db: Database; // | undefined;
1085
+
jetstream: JetstreamManager; // | undefined;
1086
+
spacedust: SpacedustManager; // | undefined;
496
1087
497
-
// Remove Authorization and set browser-like User-Agent
498
-
proxyHeaders.delete("authorization");
499
-
proxyHeaders.delete("Access-Control-Allow-Origin"),
500
-
proxyHeaders.set(
501
-
"user-agent",
502
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
1088
+
constructor(viewServerUserManager: ViewServerUserManager, did: string) {
1089
+
this.did = did;
1090
+
this.viewServerUserManager = viewServerUserManager;
1091
+
this.db = this.viewServerUserManager.viewServer.internalCreateDbForDid(
1092
+
this.did
503
1093
);
504
-
proxyHeaders.set("Access-Control-Allow-Origin", "*");
1094
+
// should probably put the params of exactly what were listening to here
1095
+
this.jetstream = new JetstreamManager((msg) => {
1096
+
console.log("Received Jetstream message: ", msg);
505
1097
506
-
const proxyRes = await fetch(bskyUrl, {
507
-
method: req.method,
508
-
headers: proxyHeaders,
509
-
body: ["GET", "HEAD"].includes(req.method.toUpperCase())
510
-
? undefined
511
-
: reqBody,
512
-
});
1098
+
const op = msg.commit.operation;
1099
+
const doer = msg.did;
1100
+
const rev = msg.commit.rev;
1101
+
const aturi = `${msg.did}/${msg.commit.collection}/${msg.commit.rkey}`;
1102
+
const value = msg.commit.record;
513
1103
514
-
const resBody = await proxyRes.text();
1104
+
if (!doer || !value) return;
1105
+
this.viewServerUserManager.viewServer.viewServerIndexer({
1106
+
op,
1107
+
doer,
1108
+
cid: msg.commit.cid,
1109
+
rev,
1110
+
aturi,
1111
+
value,
1112
+
indexsrc: `jetstream-${op}`,
1113
+
db: this.db,
1114
+
});
1115
+
});
1116
+
this.jetstream.start({
1117
+
// for realsies pls get from db or something instead of this shit
1118
+
wantedDids: [
1119
+
this.did,
1120
+
// "did:plc:mn45tewwnse5btfftvd3powc",
1121
+
// "did:plc:yy6kbriyxtimkjqonqatv2rb",
1122
+
// "did:plc:zzhzjga3ab5fcs2vnsv2ist3",
1123
+
// "did:plc:jz4ibztn56hygfld6j6zjszg",
1124
+
],
1125
+
wantedCollections: [
1126
+
// View server only needs some of the things related to user views mutes, not all of them
1127
+
//"app.bsky.actor.profile",
1128
+
//"app.bsky.feed.generator",
1129
+
//"app.bsky.feed.like",
1130
+
//"app.bsky.feed.post",
1131
+
//"app.bsky.feed.repost",
1132
+
"app.bsky.feed.threadgate", // mod
1133
+
"app.bsky.graph.block", // mod
1134
+
"app.bsky.graph.follow", // graphing
1135
+
//"app.bsky.graph.list",
1136
+
"app.bsky.graph.listblock", // mod
1137
+
//"app.bsky.graph.listitem",
1138
+
"app.bsky.notification.declaration", // mod
1139
+
],
1140
+
});
1141
+
//await connectToJetstream(this.did, this.db);
1142
+
this.spacedust = new SpacedustManager((msg: SpacedustLinkMessage) => {
1143
+
console.log("Received Spacedust message: ", msg);
1144
+
const operation = msg.link.operation;
515
1145
516
-
// console.log(
517
-
// "โ Response:",
518
-
// JSON.stringify(await JSON.parse(resBody), null, 2)
519
-
// );
1146
+
const sourceURI = new ATPAPI.AtUri(msg.link.source_record);
1147
+
const srcUri = msg.link.source_record;
1148
+
const srcDid = sourceURI.host;
1149
+
const srcField = msg.link.source;
1150
+
const srcCol = sourceURI.collection;
1151
+
const subjectURI = new ATPAPI.AtUri(msg.link.subject);
1152
+
const subUri = msg.link.subject;
1153
+
const subDid = subjectURI.host;
1154
+
const subCol = subjectURI.collection;
1155
+
1156
+
if (operation === "delete") {
1157
+
this.db.run(
1158
+
`DELETE FROM backlink_skeleton
1159
+
WHERE srcuri = ? AND srcfield = ? AND suburi = ?`,
1160
+
[srcUri, srcField, subUri]
1161
+
);
1162
+
} else if (operation === "create") {
1163
+
this.db.run(
1164
+
`INSERT OR REPLACE INTO backlink_skeleton (
1165
+
srcuri,
1166
+
srcdid,
1167
+
srcfield,
1168
+
srccol,
1169
+
suburi,
1170
+
subdid,
1171
+
subcol,
1172
+
indexedAt
1173
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
1174
+
[
1175
+
srcUri, // full AT URI of the source record
1176
+
srcDid, // did: of the source
1177
+
srcField, // e.g., "reply.parent.uri" or "facets.features.did"
1178
+
srcCol, // e.g., "app.bsky.feed.post"
1179
+
subUri, // full AT URI of the subject (linked record)
1180
+
subDid, // did: of the subject
1181
+
subCol, // subject collection (can be inferred or passed)
1182
+
Date.now(),
1183
+
]
1184
+
);
1185
+
}
1186
+
});
1187
+
this.spacedust.start({
1188
+
wantedSources: [
1189
+
// view server keeps all of this because notifications are a thing
1190
+
"app.bsky.feed.like:subject.uri", // like
1191
+
"app.bsky.feed.like:via.uri", // liked repost
1192
+
"app.bsky.feed.repost:subject.uri", // repost
1193
+
"app.bsky.feed.repost:via.uri", // reposted repost
1194
+
"app.bsky.feed.post:reply.root.uri", // thread OP
1195
+
"app.bsky.feed.post:reply.parent.uri", // direct parent
1196
+
"app.bsky.feed.post:embed.media.record.record.uri", // quote with media
1197
+
"app.bsky.feed.post:embed.record.uri", // quote without media
1198
+
"app.bsky.feed.threadgate:post", // threadgate subject
1199
+
"app.bsky.feed.threadgate:hiddenReplies", // threadgate items (array)
1200
+
"app.bsky.feed.post:facets.features.did", // facet item (array): mention
1201
+
"app.bsky.graph.block:subject", // blocks
1202
+
"app.bsky.graph.follow:subject", // follow
1203
+
"app.bsky.graph.listblock:subject", // list item (blocks)
1204
+
"app.bsky.graph.listblock:list", // blocklist mention (might not exist)
1205
+
"app.bsky.graph.listitem:subject", // list item (blocks)
1206
+
"app.bsky.graph.listitem:list", // list mention
1207
+
],
1208
+
// should be getting from DB but whatever right
1209
+
wantedSubjects: [
1210
+
// as noted i dont need to write down each post, just the user to listen to !
1211
+
// hell yeah
1212
+
// "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvybv7b6ic2h",
1213
+
// "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvybws4avc2h",
1214
+
// "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvvkcxcscs2h",
1215
+
// "at://did:plc:yy6kbriyxtimkjqonqatv2rb/app.bsky.feed.post/3l63ogxocq42f",
1216
+
// "at://did:plc:yy6kbriyxtimkjqonqatv2rb/app.bsky.feed.post/3lw3wamvflu23",
1217
+
],
1218
+
wantedSubjectDids: [
1219
+
this.did,
1220
+
//"did:plc:mn45tewwnse5btfftvd3powc",
1221
+
//"did:plc:yy6kbriyxtimkjqonqatv2rb",
1222
+
//"did:plc:zzhzjga3ab5fcs2vnsv2ist3",
1223
+
//"did:plc:jz4ibztn56hygfld6j6zjszg",
1224
+
],
1225
+
instant: ["true"]
1226
+
});
1227
+
//await connectToConstellation(this.did, this.db);
1228
+
}
1229
+
1230
+
// initialize() {
1231
+
1232
+
// }
1233
+
1234
+
// async handleHttpRequest(route: string, req: Request): Promise<Response> {
1235
+
// if (route === "posts") {
1236
+
// const posts = await this.queryPosts();
1237
+
// return new Response(JSON.stringify(posts), {
1238
+
// headers: { "content-type": "application/json" },
1239
+
// });
1240
+
// }
1241
+
1242
+
// return new Response("Unknown route", { status: 404 });
1243
+
// }
1244
+
1245
+
// private async queryPosts() {
1246
+
// return this.db.run(
1247
+
// "SELECT * FROM posts ORDER BY created_at DESC LIMIT 100"
1248
+
// );
1249
+
// }
1250
+
1251
+
shutdown() {
1252
+
this.jetstream.stop();
1253
+
this.spacedust.stop();
1254
+
this.db.close?.();
1255
+
}
1256
+
}
1257
+
1258
+
async function getServiceEndpointFromIdentity(
1259
+
did: string,
1260
+
kind: "skylite_index" | "bsky_appview"
1261
+
): Promise<string | null> {
1262
+
//const identity = await resolveIdentity(did);
1263
+
//const declUrl = `${identity.pds}/xrpc/com.atproto.repo.getRecord?repo=${identity.did}&collection=party.whey.skylite.declaration&rkey=self`;
1264
+
1265
+
//const data = (await cachedFetch(declUrl)) as any;
1266
+
const data = await getSlingshotRecord(did,"party.whey.skylite.declaration","self") as any;
1267
+
//if (!resp.ok) throw new Error(`Failed to fetch declaration for ${did}`);
1268
+
//const data = await resp.json();
520
1269
521
-
return new Response(resBody, {
522
-
status: proxyRes.status,
523
-
headers: proxyRes.headers,
524
-
});
1270
+
const svc = data?.value?.service?.find((s: any) => s.id === `#${kind}`);
1271
+
return svc?.serviceEndpoint ?? null;
525
1272
}
1273
+
1274
+
const cache = new QuickLRU({ maxSize: 10000 });
1275
+
1276
+
async function getSkyliteEndpoint(did: string): Promise<string | null> {
1277
+
if (cache.has(did)) return cache.get(did) as string;
1278
+
for (const resolver of config.viewServer.indexPriority) {
1279
+
try {
1280
+
const [prefix, suffix] = resolver.split("#") as [
1281
+
"user" | `did:web:${string}`,
1282
+
"skylite_index" | "bsky_appview"
1283
+
];
1284
+
if (prefix === "user") {
1285
+
return await getServiceEndpointFromIdentity(did, suffix);
1286
+
} else if (prefix.startsWith("did:web:")) {
1287
+
// map did:web:foo.com -> https://foo.com
1288
+
return prefix.replace("did:web:", "https://");
1289
+
}
1290
+
} catch (err) {
1291
+
// continue to next resolver
1292
+
continue;
1293
+
}
1294
+
}
1295
+
return null; //throw new Error(`No endpoint found for ${resolver}`);
1296
+
}
1297
+
1298
+
// export interface Notification {
1299
+
// $type?: 'app.bsky.notification.listNotifications#notification';
1300
+
// uri: string;
1301
+
// cid: string;
1302
+
// author: AppBskyActorDefs.ProfileView;
1303
+
// /** The reason why this notification was delivered - e.g. your post was liked, or you received a new follower. */
1304
+
// reason: 'like' | 'repost' | 'follow' | 'mention' | 'reply' | 'quote' | 'starterpack-joined' | 'verified' | 'unverified' | 'like-via-repost' | 'repost-via-repost' | (string & {});
1305
+
// reasonSubject?: string;
1306
+
// record: {
1307
+
// [_ in string]: unknown;
1308
+
// };
1309
+
// isRead: boolean;
1310
+
// indexedAt: string;
1311
+
// labels?: ComAtprotoLabelDefs.Label[];
1312
+
// }