A simple AtProto app to read pet.mewsse.link records on my PDS.

Initial commit

Mewsse 9f78dde7

+3
.env.example
··· 1 + DB_PATH=":memory:" 2 + DID="did:plc:you_did_here" 3 + LOG_LEVEL="INFO"
+3
.gitignore
··· 1 + node_modules 2 + .env 3 + *.db
+1
.nvmrc
··· 1 + 24
+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
··· 1 + # pet.mewsse.link 2 + 3 + A simple AtProto app to read `pet.mewsse.link` records on my PDS. 4 + AppView will come later. 5 + 6 + Built with [atcute](https://codeberg.org/mary-ext/atcute) and [skyware](https://skyware.js.org)
+6
lex.config.js
··· 1 + import { defineLexiconConfig } from '@atcute/lex-cli'; 2 + 3 + export default defineLexiconConfig({ 4 + files: ['lexicons/**/*.json'], 5 + outdir: 'src/lexicons/', 6 + });
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 + export * as PetMewsseLink from "./types/pet/mewsse/link.js"
+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
··· 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
··· 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 + }