+1
.nvmrc
+1
.nvmrc
···
1
+
24
+13
LICENCE
+13
LICENCE
···
1
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+
Version 2, December 2004
3
+
4
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
5
+
6
+
Everyone is permitted to copy and distribute verbatim or modified
7
+
copies of this license document, and changing it is allowed as long
8
+
as the name is changed.
9
+
10
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+
0. You just DO WHAT THE FUCK YOU WANT TO.
+6
README.md
+6
README.md
+6
lex.config.js
+6
lex.config.js
+51
lexicons/pet/mewsse/link.json
+51
lexicons/pet/mewsse/link.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "pet.mewsse.link",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Record containing a link.",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["title", "link", "createdAt"],
12
+
"properties": {
13
+
"link": {
14
+
"type": "string",
15
+
"maxLength": 3000,
16
+
"description": "The link to point to."
17
+
},
18
+
"title": {
19
+
"type": "string",
20
+
"maxLength": 3000,
21
+
"description": "Title of the given link."
22
+
},
23
+
"description": {
24
+
"type": "string",
25
+
"description": "Short description for the content of the link."
26
+
},
27
+
"tag": {
28
+
"type": "string",
29
+
"description": "A tag for classify the link."
30
+
},
31
+
"image": {
32
+
"type": "blob",
33
+
"description": "An image to illustrate the link",
34
+
"accept": ["image/*"],
35
+
"maxSize": 1000000
36
+
},
37
+
"alt": {
38
+
"type": "string",
39
+
"maxLength": 3000,
40
+
"description": "A alt text to describe the image."
41
+
},
42
+
"createdAt": {
43
+
"type": "string",
44
+
"format": "datetime",
45
+
"description": "Client-declared timestamp when this post was originally created."
46
+
}
47
+
}
48
+
}
49
+
}
50
+
}
51
+
}
+780
package-lock.json
+780
package-lock.json
···
1
+
{
2
+
"name": "mewsse-links",
3
+
"version": "0.0.1",
4
+
"lockfileVersion": 3,
5
+
"requires": true,
6
+
"packages": {
7
+
"": {
8
+
"name": "mewsse-links",
9
+
"version": "0.0.1",
10
+
"license": "WTFPL",
11
+
"dependencies": {
12
+
"@atcute/atproto": "^3.1.7",
13
+
"@atcute/car": "^3.1.2",
14
+
"@atcute/cbor": "^2.2.6",
15
+
"@atcute/client": "^4.0.4",
16
+
"@atcute/identity-resolver": "^1.1.4",
17
+
"@atcute/lex-cli": "^2.2.2",
18
+
"@atcute/lexicons": "^1.2.2",
19
+
"@skyware/jetstream": "^0.2.5",
20
+
"better-sqlite3": "^12.4.1",
21
+
"dotenv": "^17.2.3",
22
+
"kysely": "^0.28.7"
23
+
},
24
+
"devDependencies": {
25
+
"@types/better-sqlite3": "^7.6.13",
26
+
"@types/node": "^24.7.1"
27
+
}
28
+
},
29
+
"node_modules/@atcute/atproto": {
30
+
"version": "3.1.7",
31
+
"resolved": "https://registry.npmjs.org/@atcute/atproto/-/atproto-3.1.7.tgz",
32
+
"integrity": "sha512-3Ym8qaVZg2vf8qw0KO1aue39z/5oik5J+UDoSes1vr8ddw40UVLA5sV4bXSKmLnhzQHiLLgoVZXe4zaKfozPoQ==",
33
+
"license": "0BSD",
34
+
"dependencies": {
35
+
"@atcute/lexicons": "^1.2.2"
36
+
}
37
+
},
38
+
"node_modules/@atcute/bluesky": {
39
+
"version": "3.2.6",
40
+
"resolved": "https://registry.npmjs.org/@atcute/bluesky/-/bluesky-3.2.6.tgz",
41
+
"integrity": "sha512-jUSSTW5Th1vy0bWBazVHuhGQ3Xz4cX648WvLNpYDv7WPzlFzIWm6cnQCbUToQ+uK3K4WyVuuqYtZqqI0f4wWUQ==",
42
+
"license": "0BSD",
43
+
"dependencies": {
44
+
"@atcute/atproto": "^3.1.7",
45
+
"@atcute/lexicons": "^1.2.2"
46
+
}
47
+
},
48
+
"node_modules/@atcute/car": {
49
+
"version": "3.1.2",
50
+
"resolved": "https://registry.npmjs.org/@atcute/car/-/car-3.1.2.tgz",
51
+
"integrity": "sha512-OZoi1C20Nj8aDRM/A5JeeQMLsQRm6/B7PqVI7T2tyoojiBsL+Vm42QRKxtTsJg+VFaTnWhOzQbf08GZpf2YW4Q==",
52
+
"license": "0BSD",
53
+
"dependencies": {
54
+
"@atcute/cbor": "^2.2.6",
55
+
"@atcute/cid": "^2.2.4",
56
+
"@atcute/uint8array": "^1.0.5",
57
+
"@atcute/varint": "^1.0.3",
58
+
"yocto-queue": "^1.2.1"
59
+
}
60
+
},
61
+
"node_modules/@atcute/cbor": {
62
+
"version": "2.2.6",
63
+
"resolved": "https://registry.npmjs.org/@atcute/cbor/-/cbor-2.2.6.tgz",
64
+
"integrity": "sha512-pDfsn/vPTmgeXZiZdyc5vCGCPSxWlfTUIGFMCd5SroAgoLk1v9xxF7R/8+gt1lj1OKAwCwhS0doVmtLjqqzdbA==",
65
+
"license": "0BSD",
66
+
"dependencies": {
67
+
"@atcute/cid": "^2.2.4",
68
+
"@atcute/multibase": "^1.1.6",
69
+
"@atcute/uint8array": "^1.0.5"
70
+
}
71
+
},
72
+
"node_modules/@atcute/cid": {
73
+
"version": "2.2.4",
74
+
"resolved": "https://registry.npmjs.org/@atcute/cid/-/cid-2.2.4.tgz",
75
+
"integrity": "sha512-6RUMyt7rp6KOSb4TWwifOZURnFrGgKqYyjVkYjiAcscZWgJpJxwoCUCdonxCfxhQtB0yJ+WlfqNXicGB+Pe94A==",
76
+
"license": "0BSD",
77
+
"dependencies": {
78
+
"@atcute/multibase": "^1.1.6",
79
+
"@atcute/uint8array": "^1.0.5"
80
+
}
81
+
},
82
+
"node_modules/@atcute/client": {
83
+
"version": "4.0.4",
84
+
"resolved": "https://registry.npmjs.org/@atcute/client/-/client-4.0.4.tgz",
85
+
"integrity": "sha512-0vkYe6HcGAef8FS4dlGMqCCPG4I4Lve1R8Amk8UEviUVofiqlv1WGoeez9CJFL8G/7vhcgVV9rPTHLJEjZ4RdQ==",
86
+
"license": "0BSD",
87
+
"dependencies": {
88
+
"@atcute/identity": "^1.1.1",
89
+
"@atcute/lexicons": "^1.2.2"
90
+
}
91
+
},
92
+
"node_modules/@atcute/identity": {
93
+
"version": "1.1.1",
94
+
"resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.1.1.tgz",
95
+
"integrity": "sha512-zax42n693VEhnC+5tndvO2KLDTMkHOz8UExwmklvJv7R9VujfEwiSWhcv6Jgwb3ellaG8wjiQ1lMOIjLLvwh0Q==",
96
+
"license": "0BSD",
97
+
"peer": true,
98
+
"dependencies": {
99
+
"@atcute/lexicons": "^1.2.2",
100
+
"@badrap/valita": "^0.4.6"
101
+
}
102
+
},
103
+
"node_modules/@atcute/identity-resolver": {
104
+
"version": "1.1.4",
105
+
"resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-1.1.4.tgz",
106
+
"integrity": "sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA==",
107
+
"license": "0BSD",
108
+
"dependencies": {
109
+
"@atcute/lexicons": "^1.2.2",
110
+
"@atcute/util-fetch": "^1.0.3",
111
+
"@badrap/valita": "^0.4.6"
112
+
},
113
+
"peerDependencies": {
114
+
"@atcute/identity": "^1.0.0"
115
+
}
116
+
},
117
+
"node_modules/@atcute/lex-cli": {
118
+
"version": "2.2.2",
119
+
"resolved": "https://registry.npmjs.org/@atcute/lex-cli/-/lex-cli-2.2.2.tgz",
120
+
"integrity": "sha512-5hScXu4i01WNLkmMmLtQgyOBwZh9M4nijhJ9BZExA+d33/rGlJ4Us1oclw/rbEWPAjqkhA38t30KGvOfKr3chw==",
121
+
"license": "0BSD",
122
+
"dependencies": {
123
+
"@atcute/lexicon-doc": "^1.1.2",
124
+
"@badrap/valita": "^0.4.6",
125
+
"@externdefs/collider": "^0.3.0",
126
+
"picocolors": "^1.1.1",
127
+
"prettier": "^3.6.2"
128
+
},
129
+
"bin": {
130
+
"lex-cli": "cli.mjs"
131
+
}
132
+
},
133
+
"node_modules/@atcute/lexicon-doc": {
134
+
"version": "1.1.2",
135
+
"resolved": "https://registry.npmjs.org/@atcute/lexicon-doc/-/lexicon-doc-1.1.2.tgz",
136
+
"integrity": "sha512-Q3ONR2635MTVWT5Fi01FFcYTfciav0ATnX5ZBon7160hiDyk4n1a9dl8dQYgx+st2/IB0ZCNvOMHPCMZacdktg==",
137
+
"license": "0BSD",
138
+
"dependencies": {
139
+
"@badrap/valita": "^0.4.6"
140
+
}
141
+
},
142
+
"node_modules/@atcute/lexicons": {
143
+
"version": "1.2.2",
144
+
"resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-1.2.2.tgz",
145
+
"integrity": "sha512-bgEhJq5Z70/0TbK5sx+tAkrR8FsCODNiL2gUEvS5PuJfPxmFmRYNWaMGehxSPaXWpU2+Oa9ckceHiYbrItDTkA==",
146
+
"license": "0BSD",
147
+
"dependencies": {
148
+
"@standard-schema/spec": "^1.0.0",
149
+
"esm-env": "^1.2.2"
150
+
}
151
+
},
152
+
"node_modules/@atcute/multibase": {
153
+
"version": "1.1.6",
154
+
"resolved": "https://registry.npmjs.org/@atcute/multibase/-/multibase-1.1.6.tgz",
155
+
"integrity": "sha512-HBxuCgYLKPPxETV0Rot4VP9e24vKl8JdzGCZOVsDaOXJgbRZoRIF67Lp0H/OgnJeH/Xpva8Z5ReoTNJE5dn3kg==",
156
+
"license": "0BSD",
157
+
"dependencies": {
158
+
"@atcute/uint8array": "^1.0.5"
159
+
}
160
+
},
161
+
"node_modules/@atcute/uint8array": {
162
+
"version": "1.0.5",
163
+
"resolved": "https://registry.npmjs.org/@atcute/uint8array/-/uint8array-1.0.5.tgz",
164
+
"integrity": "sha512-XLWWxoR2HNl2qU+FCr0rp1APwJXci7HnzbOQLxK55OaMNBXZ19+xNC5ii4QCsThsDxa4JS/JTzuiQLziITWf2Q==",
165
+
"license": "0BSD"
166
+
},
167
+
"node_modules/@atcute/util-fetch": {
168
+
"version": "1.0.3",
169
+
"resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-1.0.3.tgz",
170
+
"integrity": "sha512-f8zzTb/xlKIwv2OQ31DhShPUNCmIIleX6p7qIXwWwEUjX6x8skUtpdISSjnImq01LXpltGV5y8yhV4/Mlb7CRQ==",
171
+
"license": "0BSD",
172
+
"dependencies": {
173
+
"@badrap/valita": "^0.4.6"
174
+
}
175
+
},
176
+
"node_modules/@atcute/varint": {
177
+
"version": "1.0.3",
178
+
"resolved": "https://registry.npmjs.org/@atcute/varint/-/varint-1.0.3.tgz",
179
+
"integrity": "sha512-fdvMPyBB+McDT+Ai5e9RwEbwYV4yjZ60S2Dn5PTjGqUyxvoCH1z42viuheDZRUDkmfQehXJTZ5az7dSozVNtog==",
180
+
"license": "0BSD"
181
+
},
182
+
"node_modules/@badrap/valita": {
183
+
"version": "0.4.6",
184
+
"resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.6.tgz",
185
+
"integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==",
186
+
"license": "MIT",
187
+
"peer": true,
188
+
"engines": {
189
+
"node": ">= 18"
190
+
}
191
+
},
192
+
"node_modules/@externdefs/collider": {
193
+
"version": "0.3.0",
194
+
"resolved": "https://registry.npmjs.org/@externdefs/collider/-/collider-0.3.0.tgz",
195
+
"integrity": "sha512-x5CpeZ4c8n+1wMFthUMWSQKqCGcQo52/Qbda5ES+JFRRg/D8Ep6/JOvUUq5HExFuv/wW+6UYG2U/mXzw0IAd8Q==",
196
+
"license": "MIT",
197
+
"peerDependencies": {
198
+
"@badrap/valita": "^0.4.4"
199
+
}
200
+
},
201
+
"node_modules/@skyware/jetstream": {
202
+
"version": "0.2.5",
203
+
"resolved": "https://registry.npmjs.org/@skyware/jetstream/-/jetstream-0.2.5.tgz",
204
+
"integrity": "sha512-fM/zs03DLwqRyzZZJFWN20e76KrdqIp97Tlm8Cek+vxn96+tu5d/fx79V6H85L0QN6HvGiX2l9A8hWFqHvYlOA==",
205
+
"license": "MPL-2.0",
206
+
"dependencies": {
207
+
"@atcute/atproto": "^3.1.0",
208
+
"@atcute/bluesky": "^3.1.4",
209
+
"@atcute/lexicons": "^1.1.0",
210
+
"partysocket": "^1.1.3",
211
+
"tiny-emitter": "^2.1.0"
212
+
}
213
+
},
214
+
"node_modules/@standard-schema/spec": {
215
+
"version": "1.0.0",
216
+
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
217
+
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
218
+
"license": "MIT"
219
+
},
220
+
"node_modules/@types/better-sqlite3": {
221
+
"version": "7.6.13",
222
+
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
223
+
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
224
+
"dev": true,
225
+
"license": "MIT",
226
+
"dependencies": {
227
+
"@types/node": "*"
228
+
}
229
+
},
230
+
"node_modules/@types/node": {
231
+
"version": "24.7.1",
232
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.1.tgz",
233
+
"integrity": "sha512-CmyhGZanP88uuC5GpWU9q+fI61j2SkhO3UGMUdfYRE6Bcy0ccyzn1Rqj9YAB/ZY4kOXmNf0ocah5GtphmLMP6Q==",
234
+
"dev": true,
235
+
"license": "MIT",
236
+
"dependencies": {
237
+
"undici-types": "~7.14.0"
238
+
}
239
+
},
240
+
"node_modules/base64-js": {
241
+
"version": "1.5.1",
242
+
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
243
+
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
244
+
"funding": [
245
+
{
246
+
"type": "github",
247
+
"url": "https://github.com/sponsors/feross"
248
+
},
249
+
{
250
+
"type": "patreon",
251
+
"url": "https://www.patreon.com/feross"
252
+
},
253
+
{
254
+
"type": "consulting",
255
+
"url": "https://feross.org/support"
256
+
}
257
+
],
258
+
"license": "MIT"
259
+
},
260
+
"node_modules/better-sqlite3": {
261
+
"version": "12.4.1",
262
+
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.4.1.tgz",
263
+
"integrity": "sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ==",
264
+
"hasInstallScript": true,
265
+
"license": "MIT",
266
+
"dependencies": {
267
+
"bindings": "^1.5.0",
268
+
"prebuild-install": "^7.1.1"
269
+
},
270
+
"engines": {
271
+
"node": "20.x || 22.x || 23.x || 24.x"
272
+
}
273
+
},
274
+
"node_modules/bindings": {
275
+
"version": "1.5.0",
276
+
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
277
+
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
278
+
"license": "MIT",
279
+
"dependencies": {
280
+
"file-uri-to-path": "1.0.0"
281
+
}
282
+
},
283
+
"node_modules/bl": {
284
+
"version": "4.1.0",
285
+
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
286
+
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
287
+
"license": "MIT",
288
+
"dependencies": {
289
+
"buffer": "^5.5.0",
290
+
"inherits": "^2.0.4",
291
+
"readable-stream": "^3.4.0"
292
+
}
293
+
},
294
+
"node_modules/buffer": {
295
+
"version": "5.7.1",
296
+
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
297
+
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
298
+
"funding": [
299
+
{
300
+
"type": "github",
301
+
"url": "https://github.com/sponsors/feross"
302
+
},
303
+
{
304
+
"type": "patreon",
305
+
"url": "https://www.patreon.com/feross"
306
+
},
307
+
{
308
+
"type": "consulting",
309
+
"url": "https://feross.org/support"
310
+
}
311
+
],
312
+
"license": "MIT",
313
+
"dependencies": {
314
+
"base64-js": "^1.3.1",
315
+
"ieee754": "^1.1.13"
316
+
}
317
+
},
318
+
"node_modules/chownr": {
319
+
"version": "1.1.4",
320
+
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
321
+
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
322
+
"license": "ISC"
323
+
},
324
+
"node_modules/decompress-response": {
325
+
"version": "6.0.0",
326
+
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
327
+
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
328
+
"license": "MIT",
329
+
"dependencies": {
330
+
"mimic-response": "^3.1.0"
331
+
},
332
+
"engines": {
333
+
"node": ">=10"
334
+
},
335
+
"funding": {
336
+
"url": "https://github.com/sponsors/sindresorhus"
337
+
}
338
+
},
339
+
"node_modules/deep-extend": {
340
+
"version": "0.6.0",
341
+
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
342
+
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
343
+
"license": "MIT",
344
+
"engines": {
345
+
"node": ">=4.0.0"
346
+
}
347
+
},
348
+
"node_modules/detect-libc": {
349
+
"version": "2.1.2",
350
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
351
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
352
+
"license": "Apache-2.0",
353
+
"engines": {
354
+
"node": ">=8"
355
+
}
356
+
},
357
+
"node_modules/dotenv": {
358
+
"version": "17.2.3",
359
+
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
360
+
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
361
+
"license": "BSD-2-Clause",
362
+
"engines": {
363
+
"node": ">=12"
364
+
},
365
+
"funding": {
366
+
"url": "https://dotenvx.com"
367
+
}
368
+
},
369
+
"node_modules/end-of-stream": {
370
+
"version": "1.4.5",
371
+
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
372
+
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
373
+
"license": "MIT",
374
+
"dependencies": {
375
+
"once": "^1.4.0"
376
+
}
377
+
},
378
+
"node_modules/esm-env": {
379
+
"version": "1.2.2",
380
+
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
381
+
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
382
+
"license": "MIT"
383
+
},
384
+
"node_modules/event-target-polyfill": {
385
+
"version": "0.0.4",
386
+
"resolved": "https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz",
387
+
"integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==",
388
+
"license": "MIT"
389
+
},
390
+
"node_modules/expand-template": {
391
+
"version": "2.0.3",
392
+
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
393
+
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
394
+
"license": "(MIT OR WTFPL)",
395
+
"engines": {
396
+
"node": ">=6"
397
+
}
398
+
},
399
+
"node_modules/file-uri-to-path": {
400
+
"version": "1.0.0",
401
+
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
402
+
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
403
+
"license": "MIT"
404
+
},
405
+
"node_modules/fs-constants": {
406
+
"version": "1.0.0",
407
+
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
408
+
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
409
+
"license": "MIT"
410
+
},
411
+
"node_modules/github-from-package": {
412
+
"version": "0.0.0",
413
+
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
414
+
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
415
+
"license": "MIT"
416
+
},
417
+
"node_modules/ieee754": {
418
+
"version": "1.2.1",
419
+
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
420
+
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
421
+
"funding": [
422
+
{
423
+
"type": "github",
424
+
"url": "https://github.com/sponsors/feross"
425
+
},
426
+
{
427
+
"type": "patreon",
428
+
"url": "https://www.patreon.com/feross"
429
+
},
430
+
{
431
+
"type": "consulting",
432
+
"url": "https://feross.org/support"
433
+
}
434
+
],
435
+
"license": "BSD-3-Clause"
436
+
},
437
+
"node_modules/inherits": {
438
+
"version": "2.0.4",
439
+
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
440
+
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
441
+
"license": "ISC"
442
+
},
443
+
"node_modules/ini": {
444
+
"version": "1.3.8",
445
+
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
446
+
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
447
+
"license": "ISC"
448
+
},
449
+
"node_modules/kysely": {
450
+
"version": "0.28.7",
451
+
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.7.tgz",
452
+
"integrity": "sha512-u/cAuTL4DRIiO2/g4vNGRgklEKNIj5Q3CG7RoUB5DV5SfEC2hMvPxKi0GWPmnzwL2ryIeud2VTcEEmqzTzEPNw==",
453
+
"license": "MIT",
454
+
"engines": {
455
+
"node": ">=20.0.0"
456
+
}
457
+
},
458
+
"node_modules/mimic-response": {
459
+
"version": "3.1.0",
460
+
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
461
+
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
462
+
"license": "MIT",
463
+
"engines": {
464
+
"node": ">=10"
465
+
},
466
+
"funding": {
467
+
"url": "https://github.com/sponsors/sindresorhus"
468
+
}
469
+
},
470
+
"node_modules/minimist": {
471
+
"version": "1.2.8",
472
+
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
473
+
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
474
+
"license": "MIT",
475
+
"funding": {
476
+
"url": "https://github.com/sponsors/ljharb"
477
+
}
478
+
},
479
+
"node_modules/mkdirp-classic": {
480
+
"version": "0.5.3",
481
+
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
482
+
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
483
+
"license": "MIT"
484
+
},
485
+
"node_modules/napi-build-utils": {
486
+
"version": "2.0.0",
487
+
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
488
+
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
489
+
"license": "MIT"
490
+
},
491
+
"node_modules/node-abi": {
492
+
"version": "3.78.0",
493
+
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz",
494
+
"integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==",
495
+
"license": "MIT",
496
+
"dependencies": {
497
+
"semver": "^7.3.5"
498
+
},
499
+
"engines": {
500
+
"node": ">=10"
501
+
}
502
+
},
503
+
"node_modules/once": {
504
+
"version": "1.4.0",
505
+
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
506
+
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
507
+
"license": "ISC",
508
+
"dependencies": {
509
+
"wrappy": "1"
510
+
}
511
+
},
512
+
"node_modules/partysocket": {
513
+
"version": "1.1.6",
514
+
"resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.1.6.tgz",
515
+
"integrity": "sha512-LkEk8N9hMDDsDT0iDK0zuwUDFVrVMUXFXCeN3850Ng8wtjPqPBeJlwdeY6ROlJSEh3tPoTTasXoSBYH76y118w==",
516
+
"license": "MIT",
517
+
"dependencies": {
518
+
"event-target-polyfill": "^0.0.4"
519
+
}
520
+
},
521
+
"node_modules/picocolors": {
522
+
"version": "1.1.1",
523
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
524
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
525
+
"license": "ISC"
526
+
},
527
+
"node_modules/prebuild-install": {
528
+
"version": "7.1.3",
529
+
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
530
+
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
531
+
"license": "MIT",
532
+
"dependencies": {
533
+
"detect-libc": "^2.0.0",
534
+
"expand-template": "^2.0.3",
535
+
"github-from-package": "0.0.0",
536
+
"minimist": "^1.2.3",
537
+
"mkdirp-classic": "^0.5.3",
538
+
"napi-build-utils": "^2.0.0",
539
+
"node-abi": "^3.3.0",
540
+
"pump": "^3.0.0",
541
+
"rc": "^1.2.7",
542
+
"simple-get": "^4.0.0",
543
+
"tar-fs": "^2.0.0",
544
+
"tunnel-agent": "^0.6.0"
545
+
},
546
+
"bin": {
547
+
"prebuild-install": "bin.js"
548
+
},
549
+
"engines": {
550
+
"node": ">=10"
551
+
}
552
+
},
553
+
"node_modules/prettier": {
554
+
"version": "3.6.2",
555
+
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
556
+
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
557
+
"license": "MIT",
558
+
"bin": {
559
+
"prettier": "bin/prettier.cjs"
560
+
},
561
+
"engines": {
562
+
"node": ">=14"
563
+
},
564
+
"funding": {
565
+
"url": "https://github.com/prettier/prettier?sponsor=1"
566
+
}
567
+
},
568
+
"node_modules/pump": {
569
+
"version": "3.0.3",
570
+
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
571
+
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
572
+
"license": "MIT",
573
+
"dependencies": {
574
+
"end-of-stream": "^1.1.0",
575
+
"once": "^1.3.1"
576
+
}
577
+
},
578
+
"node_modules/rc": {
579
+
"version": "1.2.8",
580
+
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
581
+
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
582
+
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
583
+
"dependencies": {
584
+
"deep-extend": "^0.6.0",
585
+
"ini": "~1.3.0",
586
+
"minimist": "^1.2.0",
587
+
"strip-json-comments": "~2.0.1"
588
+
},
589
+
"bin": {
590
+
"rc": "cli.js"
591
+
}
592
+
},
593
+
"node_modules/readable-stream": {
594
+
"version": "3.6.2",
595
+
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
596
+
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
597
+
"license": "MIT",
598
+
"dependencies": {
599
+
"inherits": "^2.0.3",
600
+
"string_decoder": "^1.1.1",
601
+
"util-deprecate": "^1.0.1"
602
+
},
603
+
"engines": {
604
+
"node": ">= 6"
605
+
}
606
+
},
607
+
"node_modules/safe-buffer": {
608
+
"version": "5.2.1",
609
+
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
610
+
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
611
+
"funding": [
612
+
{
613
+
"type": "github",
614
+
"url": "https://github.com/sponsors/feross"
615
+
},
616
+
{
617
+
"type": "patreon",
618
+
"url": "https://www.patreon.com/feross"
619
+
},
620
+
{
621
+
"type": "consulting",
622
+
"url": "https://feross.org/support"
623
+
}
624
+
],
625
+
"license": "MIT"
626
+
},
627
+
"node_modules/semver": {
628
+
"version": "7.7.3",
629
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
630
+
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
631
+
"license": "ISC",
632
+
"bin": {
633
+
"semver": "bin/semver.js"
634
+
},
635
+
"engines": {
636
+
"node": ">=10"
637
+
}
638
+
},
639
+
"node_modules/simple-concat": {
640
+
"version": "1.0.1",
641
+
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
642
+
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
643
+
"funding": [
644
+
{
645
+
"type": "github",
646
+
"url": "https://github.com/sponsors/feross"
647
+
},
648
+
{
649
+
"type": "patreon",
650
+
"url": "https://www.patreon.com/feross"
651
+
},
652
+
{
653
+
"type": "consulting",
654
+
"url": "https://feross.org/support"
655
+
}
656
+
],
657
+
"license": "MIT"
658
+
},
659
+
"node_modules/simple-get": {
660
+
"version": "4.0.1",
661
+
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
662
+
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
663
+
"funding": [
664
+
{
665
+
"type": "github",
666
+
"url": "https://github.com/sponsors/feross"
667
+
},
668
+
{
669
+
"type": "patreon",
670
+
"url": "https://www.patreon.com/feross"
671
+
},
672
+
{
673
+
"type": "consulting",
674
+
"url": "https://feross.org/support"
675
+
}
676
+
],
677
+
"license": "MIT",
678
+
"dependencies": {
679
+
"decompress-response": "^6.0.0",
680
+
"once": "^1.3.1",
681
+
"simple-concat": "^1.0.0"
682
+
}
683
+
},
684
+
"node_modules/string_decoder": {
685
+
"version": "1.3.0",
686
+
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
687
+
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
688
+
"license": "MIT",
689
+
"dependencies": {
690
+
"safe-buffer": "~5.2.0"
691
+
}
692
+
},
693
+
"node_modules/strip-json-comments": {
694
+
"version": "2.0.1",
695
+
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
696
+
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
697
+
"license": "MIT",
698
+
"engines": {
699
+
"node": ">=0.10.0"
700
+
}
701
+
},
702
+
"node_modules/tar-fs": {
703
+
"version": "2.1.4",
704
+
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
705
+
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
706
+
"license": "MIT",
707
+
"dependencies": {
708
+
"chownr": "^1.1.1",
709
+
"mkdirp-classic": "^0.5.2",
710
+
"pump": "^3.0.0",
711
+
"tar-stream": "^2.1.4"
712
+
}
713
+
},
714
+
"node_modules/tar-stream": {
715
+
"version": "2.2.0",
716
+
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
717
+
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
718
+
"license": "MIT",
719
+
"dependencies": {
720
+
"bl": "^4.0.3",
721
+
"end-of-stream": "^1.4.1",
722
+
"fs-constants": "^1.0.0",
723
+
"inherits": "^2.0.3",
724
+
"readable-stream": "^3.1.1"
725
+
},
726
+
"engines": {
727
+
"node": ">=6"
728
+
}
729
+
},
730
+
"node_modules/tiny-emitter": {
731
+
"version": "2.1.0",
732
+
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
733
+
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
734
+
"license": "MIT"
735
+
},
736
+
"node_modules/tunnel-agent": {
737
+
"version": "0.6.0",
738
+
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
739
+
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
740
+
"license": "Apache-2.0",
741
+
"dependencies": {
742
+
"safe-buffer": "^5.0.1"
743
+
},
744
+
"engines": {
745
+
"node": "*"
746
+
}
747
+
},
748
+
"node_modules/undici-types": {
749
+
"version": "7.14.0",
750
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
751
+
"integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
752
+
"dev": true,
753
+
"license": "MIT"
754
+
},
755
+
"node_modules/util-deprecate": {
756
+
"version": "1.0.2",
757
+
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
758
+
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
759
+
"license": "MIT"
760
+
},
761
+
"node_modules/wrappy": {
762
+
"version": "1.0.2",
763
+
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
764
+
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
765
+
"license": "ISC"
766
+
},
767
+
"node_modules/yocto-queue": {
768
+
"version": "1.2.1",
769
+
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
770
+
"integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
771
+
"license": "MIT",
772
+
"engines": {
773
+
"node": ">=12.20"
774
+
},
775
+
"funding": {
776
+
"url": "https://github.com/sponsors/sindresorhus"
777
+
}
778
+
}
779
+
}
780
+
}
+29
package.json
+29
package.json
···
1
+
{
2
+
"name": "mewsse-links",
3
+
"version": "0.0.1",
4
+
"description": "Just a cutom appview for my links",
5
+
"main": "src/index.ts",
6
+
"type": "module",
7
+
"scripts": {
8
+
"start": "node src/index.ts"
9
+
},
10
+
"author": "Mewsse",
11
+
"license": "WTFPL",
12
+
"dependencies": {
13
+
"@atcute/atproto": "^3.1.7",
14
+
"@atcute/car": "^3.1.2",
15
+
"@atcute/cbor": "^2.2.6",
16
+
"@atcute/client": "^4.0.4",
17
+
"@atcute/identity-resolver": "^1.1.4",
18
+
"@atcute/lex-cli": "^2.2.2",
19
+
"@atcute/lexicons": "^1.2.2",
20
+
"@skyware/jetstream": "^0.2.5",
21
+
"better-sqlite3": "^12.4.1",
22
+
"dotenv": "^17.2.3",
23
+
"kysely": "^0.28.7"
24
+
},
25
+
"devDependencies": {
26
+
"@types/better-sqlite3": "^7.6.13",
27
+
"@types/node": "^24.7.1"
28
+
}
29
+
}
+62
src/db.ts
+62
src/db.ts
···
1
+
import { Kysely, Migrator, SqliteDialect } from 'kysely'
2
+
import SqliteDb from 'better-sqlite3'
3
+
4
+
import type { Migration, MigrationProvider } from 'kysely'
5
+
6
+
export type DatabaseSchema = {
7
+
links: Link,
8
+
}
9
+
10
+
export type Link = {
11
+
rkey: string,
12
+
link: string,
13
+
title: string,
14
+
description: string | null,
15
+
tag: string | null,
16
+
image: string | null,
17
+
alt: string | null,
18
+
createdAt: string,
19
+
}
20
+
21
+
const migrations: Record<string, Migration> = {}
22
+
const migrationProvider: MigrationProvider = {
23
+
async getMigrations() {
24
+
return migrations
25
+
},
26
+
}
27
+
28
+
migrations['001'] = {
29
+
async up(db: Kysely<any>) {
30
+
await db.schema
31
+
.createTable('links')
32
+
.addColumn('rkey', 'varchar', (col) => col.primaryKey())
33
+
.addColumn('link', 'varchar', (col) => col.notNull())
34
+
.addColumn('title', 'varchar', (col) => col.notNull())
35
+
.addColumn('description', 'varchar')
36
+
.addColumn('tag', 'varchar')
37
+
.addColumn('image', 'varchar')
38
+
.addColumn('alt', 'varchar')
39
+
.addColumn('createdAt', 'varchar', (col) => col.notNull())
40
+
.execute()
41
+
},
42
+
43
+
async down(db: Kysely<any>) {
44
+
await db.schema.dropTable('links').execute()
45
+
},
46
+
}
47
+
48
+
export const createDb = (location: string): Database => {
49
+
return new Kysely<DatabaseSchema>({
50
+
dialect: new SqliteDialect({
51
+
database: new SqliteDb(location)
52
+
}),
53
+
})
54
+
}
55
+
56
+
export const migrateToLatest = async (db: Database) => {
57
+
const migrator = new Migrator({db, provider: migrationProvider})
58
+
const { error } = await migrator.migrateToLatest()
59
+
if(error) throw error
60
+
}
61
+
62
+
export type Database = Kysely<DatabaseSchema>
+76
src/id-resolver.ts
+76
src/id-resolver.ts
···
1
+
import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver } from '@atcute/identity-resolver'
2
+
import { isDid} from '@atcute/lexicons/syntax'
3
+
import process from 'process'
4
+
5
+
import type { DidDocument, Service } from '@atcute/identity'
6
+
import type { Did } from '@atcute/lexicons/syntax'
7
+
8
+
const docResolver = new CompositeDidDocumentResolver({
9
+
methods: {
10
+
plc: new PlcDidDocumentResolver(),
11
+
web: new WebDidDocumentResolver()
12
+
}
13
+
})
14
+
15
+
export class DIDError extends Error {
16
+
constructor(msg: string) {
17
+
super(msg)
18
+
19
+
Object.setPrototypeOf(this, DIDError.prototype)
20
+
}
21
+
}
22
+
23
+
export class ServiceError extends Error {
24
+
constructor(msg: string) {
25
+
super(msg)
26
+
27
+
Object.setPrototypeOf(this, ServiceError.prototype)
28
+
}
29
+
}
30
+
31
+
export function getUserDID() : Did<"web"> | Did<"plc"> {
32
+
const did = process.env.DID
33
+
34
+
if (!did || did == "") {
35
+
throw new DIDError("Missing DID to ingest")
36
+
}
37
+
38
+
if (!isDid(did)) {
39
+
throw new DIDError("DID is not in the correct format")
40
+
}
41
+
42
+
return did as Did<"web"> | Did<"plc">
43
+
}
44
+
45
+
export async function findUserDIDDoc() : Promise<DidDocument> {
46
+
47
+
try {
48
+
const did = getUserDID()
49
+
const doc = await docResolver.resolve(did)
50
+
return doc
51
+
} catch (err) {
52
+
throw err
53
+
}
54
+
}
55
+
56
+
export async function findUserPDS(): Promise<string> {
57
+
const didDoc = await findUserDIDDoc()
58
+
59
+
if (!didDoc.service) {
60
+
throw new ServiceError("No service found on user did doc")
61
+
}
62
+
63
+
const pds = didDoc.service.filter(service => service.id == "#atproto_pds")
64
+
65
+
if (pds.length < 1) {
66
+
throw new ServiceError(`No valid service found for ${process.env.DID}`)
67
+
}
68
+
69
+
let serviceEndpoint = pds.shift()?.serviceEndpoint
70
+
71
+
if (!serviceEndpoint || typeof serviceEndpoint != 'string' ) {
72
+
throw new ServiceError(`No valid service found for ${process.env.DID}`)
73
+
}
74
+
75
+
return serviceEndpoint
76
+
}
+69
src/index.ts
+69
src/index.ts
···
1
+
import { createDb, migrateToLatest } from './db.ts'
2
+
import { createIngester } from './ingester.ts'
3
+
import dotenv from 'dotenv'
4
+
import process from 'process'
5
+
6
+
import type { Database } from './db.ts'
7
+
import { Jetstream } from '@skyware/jetstream'
8
+
import { logger } from './lib/logger.ts'
9
+
10
+
dotenv.config()
11
+
12
+
export type Context = {
13
+
db: Database
14
+
jetstream: Jetstream
15
+
}
16
+
17
+
export class Server {
18
+
public ctx: Context
19
+
20
+
constructor(
21
+
ctx: Context
22
+
) {
23
+
this.ctx = ctx
24
+
}
25
+
26
+
static async create() {
27
+
const db = createDb(process.env.DB_PATH || ":memory")
28
+
await migrateToLatest(db)
29
+
const ingester = createIngester(db)
30
+
31
+
await ingester.backfill()
32
+
const jetstream = await ingester.jetstream()
33
+
34
+
const ctx: Context= {
35
+
db,
36
+
jetstream
37
+
}
38
+
39
+
jetstream.start()
40
+
logger.info("Starting jetstream client")
41
+
42
+
return new Server(ctx)
43
+
}
44
+
45
+
async close() {
46
+
logger.info("Stopping jetstream client")
47
+
await this.ctx.jetstream.close()
48
+
49
+
return new Promise<void>((resolve) => {
50
+
resolve()
51
+
})
52
+
}
53
+
}
54
+
55
+
56
+
const run = async () => {
57
+
const server = await Server.create()
58
+
59
+
const onClose = async () => {
60
+
setTimeout(() => process.exit(1), 10000).unref()
61
+
await server.close()
62
+
process.exit()
63
+
}
64
+
65
+
process.on('SIGINT', onClose)
66
+
process.on('SIGTERM', onClose)
67
+
}
68
+
69
+
run()
+175
src/ingester.ts
+175
src/ingester.ts
···
1
+
import type { Records as _Records } from "@atcute/lexicons/ambient"
2
+
import type { Did } from '@atcute/lexicons/syntax'
3
+
import type { Database, Link } from './db.ts'
4
+
5
+
import { Client, simpleFetchHandler } from '@atcute/client'
6
+
import { Jetstream } from '@skyware/jetstream'
7
+
import { findUserPDS, getUserDID } from './id-resolver.ts'
8
+
import { RepoReader } from '@atcute/car/v4'
9
+
import { decode } from '@atcute/cbor'
10
+
import { logger } from "./lib/logger.ts"
11
+
12
+
interface RepoParams {
13
+
did: Did<"web"> | Did<"plc">,
14
+
since?: string
15
+
}
16
+
17
+
export class IngestionError extends Error {
18
+
constructor(msg: string) {
19
+
super(msg)
20
+
21
+
Object.setPrototypeOf(this, IngestionError.prototype)
22
+
}
23
+
}
24
+
25
+
export function findImage(did: Did<"web"> | Did<"plc">, pds: string, record: any): string | null {
26
+
const imageCid = record.image ? record.image.ref.$link : null
27
+
if (!imageCid) return null
28
+
29
+
// let the user pull the blob with their browser directly fomr the pds
30
+
// decreasing space needed to run the service and prevent duplication
31
+
// if hosted at the same place as the pds (self host anyone ?)
32
+
return `${pds}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${imageCid}`
33
+
}
34
+
35
+
export function createIngester(db: Database) {
36
+
return {
37
+
async backfill() : Promise<any> {
38
+
const did = getUserDID()
39
+
const pds = await findUserPDS()
40
+
const handler = simpleFetchHandler({service: pds})
41
+
const rpc = new Client({ handler })
42
+
const now = new Date()
43
+
44
+
logger.info(`Starting backfilling`)
45
+
46
+
const params: RepoParams = {
47
+
did
48
+
}
49
+
50
+
const lastRev = await db
51
+
.selectFrom('links')
52
+
.select('rkey')
53
+
.limit(1)
54
+
.executeTakeFirst()
55
+
56
+
if (lastRev) {
57
+
params.since = lastRev.rkey
58
+
}
59
+
60
+
const {ok, data} = await rpc.get(`com.atproto.sync.getRepo`, {
61
+
params,
62
+
as: 'stream'
63
+
})
64
+
65
+
if (!ok) {
66
+
throw new IngestionError(`Error while syncing repo for ${did} on ${pds}`)
67
+
}
68
+
69
+
await using repo = RepoReader.fromStream(data)
70
+
71
+
for await (const entry of repo) {
72
+
if (entry.collection != "pet.mewsse.link") continue
73
+
const link : Link = decode(entry.bytes)
74
+
75
+
await db
76
+
.insertInto('links')
77
+
.values({
78
+
rkey: entry.rkey,
79
+
link: link.link,
80
+
title: link.title,
81
+
description: link.description,
82
+
tag: link.tag ?? null,
83
+
image: findImage(did, pds, link),
84
+
alt: link.alt ?? null,
85
+
createdAt: link.createdAt
86
+
})
87
+
.onConflict((conflict) =>
88
+
conflict.column('rkey').doUpdateSet({
89
+
link: link.link,
90
+
title: link.title,
91
+
description: link.description,
92
+
tag: link.tag ?? null,
93
+
image: findImage(did, pds, link),
94
+
alt: link.alt ?? null,
95
+
})
96
+
)
97
+
.execute()
98
+
99
+
logger.info(`Inserting record ${entry.rkey}`)
100
+
}
101
+
102
+
logger.info(`Backfilling ended`)
103
+
},
104
+
105
+
async jetstream() : Promise<Jetstream> {
106
+
const did = getUserDID()
107
+
const pds = await findUserPDS()
108
+
109
+
const jetstream = new Jetstream({
110
+
wantedCollections: ['pet.mewsse.link'],
111
+
wantedDids: [did]
112
+
})
113
+
114
+
jetstream.onCreate('pet.mewsse.link', async (event) => {
115
+
if (event.commit.record.$type != "pet.mewsse.link") return
116
+
117
+
const rev = event.commit.rev
118
+
const record = event.commit.record
119
+
120
+
121
+
await db
122
+
.insertInto('links')
123
+
.values({
124
+
rkey: rev,
125
+
link: record.link,
126
+
title: record.title,
127
+
description: record.description ?? null,
128
+
tag: record.tag ?? null,
129
+
image: findImage(did, pds, record),
130
+
alt: record.alt ?? null,
131
+
createdAt: record.createdAt
132
+
})
133
+
.execute()
134
+
135
+
logger.info(`Record ${rev} created`)
136
+
})
137
+
138
+
jetstream.onUpdate('pet.mewsse.link', async (event) => {
139
+
if (event.commit.record.$type != "pet.mewsse.link") return
140
+
141
+
const rev = event.commit.rev
142
+
const record = event.commit.record
143
+
144
+
await db
145
+
.updateTable('links')
146
+
.set({
147
+
link: record.link,
148
+
title: record.title,
149
+
description: record.description ?? null,
150
+
tag: record.tag ?? null,
151
+
image: findImage(did, pds, record),
152
+
alt: record.alt ?? null,
153
+
createdAt: record.createdAt
154
+
})
155
+
.where('rkey', '=', rev)
156
+
.executeTakeFirstOrThrow()
157
+
158
+
logger.info(`Record ${rev} updated`)
159
+
})
160
+
161
+
jetstream.onDelete('pet.mewsse.link', async (event) => {
162
+
if (event.commit.collection != "pet.mewsse.link") return
163
+
164
+
await db
165
+
.deleteFrom('links')
166
+
.where('rkey', '=', event.commit.rkey)
167
+
.executeTakeFirstOrThrow()
168
+
169
+
logger.info(`Record ${event.commit.rkey} deleted`)
170
+
})
171
+
172
+
return jetstream
173
+
}
174
+
}
175
+
}
+1
src/lexicons/index.ts
+1
src/lexicons/index.ts
···
1
+
export * as PetMewsseLink from "./types/pet/mewsse/link.js"
+65
src/lexicons/types/pet/mewsse/link.ts
+65
src/lexicons/types/pet/mewsse/link.ts
···
1
+
import type {} from "@atcute/lexicons"
2
+
import * as v from "@atcute/lexicons/validations"
3
+
import type {} from "@atcute/lexicons/ambient"
4
+
5
+
const _mainSchema = /*#__PURE__*/ v.record(
6
+
/*#__PURE__*/ v.tidString(),
7
+
/*#__PURE__*/ v.object({
8
+
$type: /*#__PURE__*/ v.literal("pet.mewsse.link"),
9
+
/**
10
+
* A alt text to describe the image.
11
+
* @maxLength 3000
12
+
*/
13
+
alt: /*#__PURE__*/ v.optional(
14
+
/*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
15
+
/*#__PURE__*/ v.stringLength(0, 3000),
16
+
]),
17
+
),
18
+
/**
19
+
* Client-declared timestamp when this post was originally created.
20
+
*/
21
+
createdAt: /*#__PURE__*/ v.datetimeString(),
22
+
/**
23
+
* Short description for the content of the link.
24
+
*/
25
+
description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()),
26
+
/**
27
+
* An image to illustrate the link
28
+
* @accept image/*
29
+
* @maxSize 1000000
30
+
*/
31
+
image: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()),
32
+
/**
33
+
* The link to point to.
34
+
* @maxLength 3000
35
+
*/
36
+
link: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
37
+
/*#__PURE__*/ v.stringLength(0, 3000),
38
+
]),
39
+
/**
40
+
* A tag for classify the link.
41
+
*/
42
+
tag: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()),
43
+
/**
44
+
* Title of the given link.
45
+
* @maxLength 3000
46
+
*/
47
+
title: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
48
+
/*#__PURE__*/ v.stringLength(0, 3000),
49
+
]),
50
+
}),
51
+
)
52
+
53
+
type main$schematype = typeof _mainSchema
54
+
55
+
export interface mainSchema extends main$schematype {}
56
+
57
+
export const mainSchema = _mainSchema as mainSchema
58
+
59
+
export interface Main extends v.InferInput<typeof mainSchema> {}
60
+
61
+
declare module "@atcute/lexicons/ambient" {
62
+
interface Records {
63
+
"pet.mewsse.link": mainSchema
64
+
}
65
+
}
+47
src/lib/logger.ts
+47
src/lib/logger.ts
···
1
+
interface logLevelgInterface {
2
+
[key:string]: number
3
+
}
4
+
5
+
6
+
export const logger = {
7
+
debug: logDebug,
8
+
info: logInfo,
9
+
warn: logWarn,
10
+
error: logError,
11
+
fatal: logFatal
12
+
}
13
+
14
+
export const logLevel:logLevelgInterface = {
15
+
DEBUG: 5,
16
+
INFO: 4,
17
+
WARN: 3,
18
+
ERROR: 2,
19
+
FATAL: 1,
20
+
} as const
21
+
22
+
export function log(level: number, type: string, message: string): void {
23
+
const envLevel = process.env.LOG_LEVEL ? logLevel[process.env.LOG_LEVEL] : logLevel.INFO
24
+
if (level > envLevel) return
25
+
26
+
console.log(`${type} ${new Date().toISOString()}: ${message}`)
27
+
}
28
+
29
+
function logDebug(message: string): void {
30
+
log(logLevel.DEBUG, "[DEBUG]", message)
31
+
}
32
+
33
+
function logInfo(message: string): void {
34
+
log(logLevel.INFO, "[INFO]", message)
35
+
}
36
+
37
+
function logWarn(message: string): void {
38
+
log(logLevel.WARN, "[WARN]", message)
39
+
}
40
+
41
+
function logError(message: string): void {
42
+
log(logLevel.ERROR, "[WARN]", message)
43
+
}
44
+
45
+
function logFatal(message: string): void {
46
+
log(logLevel.ERROR, "[WARN]", message)
47
+
}
+29
tsconfig.json
+29
tsconfig.json
···
1
+
{
2
+
"compilerOptions": {
3
+
"baseUrl": ".",
4
+
"outDir": "dist",
5
+
"importsNotUsedAsValues": "remove",
6
+
"forceConsistentCasingInFileNames": true,
7
+
"allowImportingTsExtensions": true,
8
+
"rewriteRelativeImportExtensions": true,
9
+
10
+
"lib": [
11
+
"es2024",
12
+
"ESNext.Array",
13
+
"ESNext.Collection",
14
+
"ESNext.Iterator",
15
+
"ESNext.Promise",
16
+
"DOM"
17
+
],
18
+
"types": ["node"],
19
+
"module": "nodenext",
20
+
"target": "es2024",
21
+
22
+
"strict": true,
23
+
"esModuleInterop": true,
24
+
"skipLibCheck": true,
25
+
"moduleResolution": "node16"
26
+
},
27
+
"include": ["src/**/*"],
28
+
"exclude": ["node_modules"]
29
+
}