tracks lexicons and how many times they appeared on the jetstream

refactor: prepare for rust server

ptr.pet fb7bb04c 859d9807

verified
+2 -2
.gitignore
··· 5 5 .vercel 6 6 .netlify 7 7 .wrangler 8 - /.svelte-kit 9 - /build 8 + client/.svelte-kit 9 + client/build 10 10 11 11 # OS 12 12 .DS_Store
.npmrc client/.npmrc
+4 -31
bun.lock client/bun.lock
··· 3 3 "workspaces": { 4 4 "": { 5 5 "name": "nsid-tracker", 6 - "dependencies": { 7 - "@atcute/jetstream": "^1.0.2", 8 - }, 9 6 "devDependencies": { 10 7 "@eslint/compat": "^1.2.5", 11 8 "@eslint/js": "^9.18.0", 9 + "@sveltejs/adapter-static": "^3.0.8", 12 10 "@sveltejs/kit": "^2.22.0", 13 11 "@sveltejs/vite-plugin-svelte": "^6.0.0", 14 12 "@tailwindcss/vite": "^4.0.0", ··· 17 15 "eslint-plugin-svelte": "^3.0.0", 18 16 "globals": "^16.0.0", 19 17 "svelte": "^5.0.0", 20 - "svelte-adapter-bun": "^0.5.2", 21 18 "svelte-check": "^4.0.0", 22 19 "tailwindcss": "^4.0.0", 23 20 "typescript": "^5.0.0", ··· 29 26 "packages": { 30 27 "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], 31 28 32 - "@atcute/jetstream": ["@atcute/jetstream@1.0.2", "", { "dependencies": { "@atcute/lexicons": "^1.0.2", "@badrap/valita": "^0.4.2", "@mary-ext/event-iterator": "^1.0.0", "@mary-ext/simple-event-emitter": "^1.0.0", "partysocket": "^1.1.4", "type-fest": "^4.41.0", "yocto-queue": "^1.2.1" } }, "sha512-ZtdNNxl4zq9cgUpXSL9F+AsXUZt0Zuyj0V7974D7LxdMxfTItPnMZ9dRG8GoFkkGz3+pszdsG888Ix8C0F2+mA=="], 33 - 34 - "@atcute/lexicons": ["@atcute/lexicons@1.1.0", "", { "dependencies": { "esm-env": "^1.2.2" } }, "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q=="], 35 - 36 - "@badrap/valita": ["@badrap/valita@0.4.5", "", {}, "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ=="], 37 - 38 29 "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.6", "", { "os": "aix", "cpu": "ppc64" }, "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw=="], 39 30 40 31 "@esbuild/android-arm": ["@esbuild/android-arm@0.25.6", "", { "os": "android", "cpu": "arm" }, "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg=="], ··· 125 116 126 117 "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], 127 118 128 - "@mary-ext/event-iterator": ["@mary-ext/event-iterator@1.0.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-l6gCPsWJ8aRCe/s7/oCmero70kDHgIK5m4uJvYgwEYTqVxoBOIXbKr5tnkLqUHEg6mNduB4IWvms3h70Hp9ADQ=="], 129 - 130 - "@mary-ext/simple-event-emitter": ["@mary-ext/simple-event-emitter@1.0.0", "", {}, "sha512-meA/zJZKIN1RVBNEYIbjufkUrW7/tRjHH60FjolpG1ixJKo76TB208qefQLNdOVDA7uIG0CGEDuhmMirtHKLAg=="], 131 - 132 119 "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 133 120 134 121 "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], ··· 178 165 "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.1", "", { "os": "win32", "cpu": "x64" }, "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA=="], 179 166 180 167 "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="], 168 + 169 + "@sveltejs/adapter-static": ["@sveltejs/adapter-static@3.0.8", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg=="], 181 170 182 171 "@sveltejs/kit": ["@sveltejs/kit@2.25.1", "", { "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-8H+fxDEp7Xq6tLFdrGdS5fLu6ONDQQ9DgyjboXpChubuFdfH9QoFX09ypssBpyNkJNZFt9eW3yLmXIc9CesPCA=="], 183 172 ··· 331 320 332 321 "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 333 322 334 - "event-target-polyfill": ["event-target-polyfill@0.0.4", "", {}, "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ=="], 335 - 336 323 "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 337 324 338 325 "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], ··· 360 347 "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 361 348 362 349 "globals": ["globals@16.3.0", "", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="], 363 - 364 - "globalyzer": ["globalyzer@0.1.0", "", {}, "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="], 365 - 366 - "globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="], 367 350 368 351 "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 369 352 ··· 467 450 468 451 "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 469 452 470 - "partysocket": ["partysocket@1.1.4", "", { "dependencies": { "event-target-polyfill": "^0.0.4" } }, "sha512-jXP7PFj2h5/v4UjDS8P7MZy6NJUQ7sspiFyxL4uc/+oKOL+KdtXzHnTV8INPGxBrLTXgalyG3kd12Qm7WrYc3A=="], 471 - 472 453 "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 473 454 474 455 "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], ··· 522 503 "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 523 504 524 505 "svelte": ["svelte@5.36.8", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-8JbZWQu96hMjH/oYQPxXW6taeC6Awl6muGHeZzJTxQx7NGRQ/J9wN1hkzRKLOlSDlbS2igiFg7p5xyTp5uXG3A=="], 525 - 526 - "svelte-adapter-bun": ["svelte-adapter-bun@0.5.2", "", { "dependencies": { "tiny-glob": "^0.2.9" } }, "sha512-xEtFgaal6UgrCwwkSIcapO9kopoFNUYCYqyKCikdqxX9bz2TDYnrWQZ7qBnkunMxi1HOIERUCvTcebYGiarZLA=="], 527 506 528 507 "svelte-check": ["svelte-check@4.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-Iz8dFXzBNAM7XlEIsUjUGQhbEE+Pvv9odb9+0+ITTgFWZBGeJRRYqHUUglwe2EkLD5LIsQaAc4IUJyvtKuOO5w=="], 529 508 ··· 535 514 536 515 "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], 537 516 538 - "tiny-glob": ["tiny-glob@0.2.9", "", { "dependencies": { "globalyzer": "0.1.0", "globrex": "^0.1.2" } }, "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg=="], 539 - 540 517 "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], 541 518 542 519 "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], ··· 546 523 "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], 547 524 548 525 "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 549 - 550 - "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], 551 526 552 527 "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 553 528 ··· 571 546 572 547 "yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], 573 548 574 - "yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="], 549 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 575 550 576 551 "zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], 577 552 ··· 600 575 "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 601 576 602 577 "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 603 - 604 - "p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 605 578 606 579 "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], 607 580
+6
client/src/lib/types.ts
··· 1 + export type EventRecord = { 2 + nsid: string; 3 + timestamp: number; 4 + count: number; 5 + deleted_count: number; 6 + };
+2
client/src/routes/+layout.ts
··· 1 + export const prerender = true; 2 + export const ssr = false;
eslint.config.js client/eslint.config.js
+12 -15
package.json client/package.json
··· 1 1 { 2 2 "name": "nsid-tracker", 3 - "private": true, 4 3 "version": "0.0.1", 5 - "type": "module", 6 - "scripts": { 7 - "dev": "vite dev", 8 - "build": "vite build", 9 - "preview": "vite preview", 10 - "prepare": "svelte-kit sync || echo ''", 11 - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 13 - "lint": "eslint ." 14 - }, 15 4 "devDependencies": { 16 5 "@eslint/compat": "^1.2.5", 17 6 "@eslint/js": "^9.18.0", 7 + "@sveltejs/adapter-static": "^3.0.8", 18 8 "@sveltejs/kit": "^2.22.0", 19 9 "@sveltejs/vite-plugin-svelte": "^6.0.0", 20 10 "@tailwindcss/vite": "^4.0.0", ··· 23 13 "eslint-plugin-svelte": "^3.0.0", 24 14 "globals": "^16.0.0", 25 15 "svelte": "^5.0.0", 26 - "svelte-adapter-bun": "^0.5.2", 27 16 "svelte-check": "^4.0.0", 28 17 "tailwindcss": "^4.0.0", 29 18 "typescript": "^5.0.0", 30 19 "typescript-eslint": "^8.20.0", 31 20 "vite": "^7.0.4" 32 21 }, 33 - "dependencies": { 34 - "@atcute/jetstream": "^1.0.2" 35 - } 22 + "private": true, 23 + "scripts": { 24 + "dev": "vite dev", 25 + "build": "vite build", 26 + "preview": "vite preview", 27 + "prepare": "svelte-kit sync || echo ''", 28 + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 29 + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 30 + "lint": "eslint ." 31 + }, 32 + "type": "module" 36 33 }
src/app.css client/src/app.css
src/app.d.ts client/src/app.d.ts
src/app.html client/src/app.html
-12
src/hooks.server.ts
··· 1 - import { eventTracker } from "$lib/db"; 2 - import { exit as workerExit, start } from "$lib/worker_manager.js"; 3 - 4 - const exit = () => { 5 - workerExit(); 6 - eventTracker.exit(); 7 - setTimeout(process.exit, 2000); 8 - }; 9 - start(); 10 - 11 - process.on("SIGINT", exit); 12 - process.on("SIGTERM", exit);
-137
src/lib/db.ts
··· 1 - import { Database } from "bun:sqlite"; 2 - import { env } from "process"; 3 - import type { WorkerEventData } from "./types"; 4 - 5 - export interface EventRecord { 6 - nsid: string; 7 - timestamp: number; 8 - count: number; 9 - deleted_count: number; 10 - } 11 - 12 - const ALL_NSID = "*"; 13 - 14 - class EventTracker { 15 - private db: Database; 16 - private insertNsidQuery; 17 - private insertEventQuery; 18 - private updateCountQuery; 19 - private getNsidCountQuery; 20 - 21 - // private bufferedRecords: WorkerEventData[]; 22 - 23 - constructor() { 24 - // this.bufferedRecords = []; 25 - this.db = new Database(env.DB_PATH ?? "events.sqlite"); 26 - // init db 27 - this.db.run("PRAGMA journal_mode = WAL;"); 28 - // events 29 - this.db.run(` 30 - CREATE TABLE IF NOT EXISTS events ( 31 - nsid_idx INTEGER NOT NULL, 32 - timestamp INTEGER NOT NULL, 33 - deleted INTEGER NOT NULL, 34 - PRIMARY KEY (nsid_idx, timestamp) 35 - ) 36 - `); 37 - // aggregated counts 38 - this.db.run(` 39 - CREATE TABLE IF NOT EXISTS nsid_counts ( 40 - nsid_idx INTEGER PRIMARY KEY, 41 - count INTEGER NOT NULL, 42 - deleted_count INTEGER NOT NULL, 43 - last_updated INTEGER NOT NULL 44 - ) 45 - `); 46 - this.db.run(` 47 - CREATE TABLE IF NOT EXISTS nsid_types ( 48 - id INTEGER PRIMARY KEY AUTOINCREMENT, 49 - nsid TEXT UNIQUE NOT NULL 50 - ); 51 - `); 52 - // compile queries 53 - this.insertNsidQuery = this.db.query( 54 - "INSERT OR IGNORE INTO nsid_types (nsid) VALUES (?)", 55 - ); 56 - this.insertEventQuery = this.db.query(` 57 - INSERT OR IGNORE INTO events (nsid_idx, timestamp, deleted) 58 - VALUES ( 59 - (SELECT id FROM nsid_types WHERE nsid = ?), 60 - ?, 61 - ? 62 - ) 63 - `); 64 - this.updateCountQuery = this.db.query(` 65 - INSERT INTO nsid_counts (nsid_idx, count, deleted_count, last_updated) 66 - VALUES ( 67 - (SELECT id FROM nsid_types WHERE nsid = $nsid), 68 - 1 - $deleted, 69 - $deleted, 70 - $timestamp 71 - ) 72 - ON CONFLICT(nsid_idx) DO UPDATE SET 73 - count = count + (1 - $deleted), 74 - deleted_count = deleted_count + $deleted, 75 - last_updated = $timestamp 76 - `); 77 - this.getNsidCountQuery = this.db.query(` 78 - SELECT 79 - (SELECT nsid FROM nsid_types WHERE id = nsid_idx) as nsid, 80 - count, 81 - deleted_count, 82 - last_updated as timestamp 83 - FROM nsid_counts 84 - ORDER BY count DESC 85 - `); 86 - this.insertNsidQuery.run(ALL_NSID); 87 - } 88 - 89 - // private recordBufferedEvents = () => { 90 - // try { 91 - // this.db.transaction(() => { 92 - // for (const event of this.bufferedRecords) { 93 - // this.insertEventQuery.run(event.nsid, event.timestamp, event.deleted); 94 - // } 95 - // })(); 96 - // this.bufferedRecords = []; 97 - // } catch (e) { 98 - // console.error(`can't commit to db: ${e}`); 99 - // } 100 - // }; 101 - 102 - recordEventHit = (data: WorkerEventData) => { 103 - try { 104 - this.db.transaction(() => { 105 - const { nsid, timestamp, deleted } = data; 106 - this.insertNsidQuery.run(nsid); 107 - this.insertEventQuery.run(nsid, timestamp, deleted); 108 - this.updateCountQuery.run({ 109 - $nsid: nsid, 110 - $deleted: deleted, 111 - $timestamp: timestamp, 112 - }); 113 - this.updateCountQuery.run({ 114 - $nsid: ALL_NSID, 115 - $deleted: deleted, 116 - $timestamp: timestamp, 117 - }); 118 - })(); 119 - // this.bufferedRecords.push(data); 120 - } catch (e) { 121 - console.error(`can't commit to db: ${e}`); 122 - } 123 - // commit buffered if at 10k 124 - // if (this.bufferedRecords.length > 9999) this.recordBufferedEvents(); 125 - }; 126 - 127 - getNsidCounts = (): EventRecord[] => { 128 - return this.getNsidCountQuery.all() as EventRecord[]; 129 - }; 130 - 131 - exit = () => { 132 - // this.recordBufferedEvents(); 133 - this.db.close(); 134 - }; 135 - } 136 - 137 - export const eventTracker = new EventTracker();
-6
src/lib/types.ts
··· 1 - export type WorkerEventData = { 2 - nsid: string; 3 - timestamp: number; 4 - deleted: boolean; 5 - }; 6 - export type WorkerCommand = "exit";
-61
src/lib/worker.ts
··· 1 - import { JetstreamSubscription } from "@atcute/jetstream"; 2 - import type { WorkerEventData, WorkerCommand } from "./types.js"; 3 - import * as wt from "node:worker_threads"; 4 - 5 - const port = wt.parentPort!; 6 - let subscription: JetstreamSubscription | null = null; 7 - 8 - const track = async () => { 9 - subscription = new JetstreamSubscription({ 10 - url: "wss://jetstream2.us-east.bsky.network", 11 - validateEvents: false, // trust the jetstream :3 12 - }); 13 - 14 - for await (const event of subscription) { 15 - if (event.kind !== "commit") { 16 - continue; 17 - } 18 - 19 - const { operation, collection } = event.commit; 20 - 21 - const data: WorkerEventData = { 22 - nsid: collection, 23 - timestamp: event.time_us, 24 - deleted: operation === "delete", 25 - }; 26 - 27 - port.postMessage(data); 28 - } 29 - }; 30 - 31 - const trackLoop = async () => { 32 - if (subscription !== null) { 33 - return; 34 - } 35 - 36 - let retryCount = 0; 37 - const baseDelay = 1000; // 1 second 38 - const maxDelay = 60000; // 60 seconds 39 - 40 - // if the above fails we fall into here 41 - while (true) { 42 - try { 43 - await track(); 44 - retryCount = 0; // Reset on success 45 - } catch (e) { 46 - console.log(`tracking failed: ${e}`); 47 - 48 - const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay); 49 - console.log(`retrying in ${delay}ms (attempt ${retryCount + 1})`); 50 - 51 - await new Promise((resolve) => setTimeout(resolve, delay)); 52 - retryCount++; 53 - } 54 - } 55 - }; 56 - 57 - port.on("message", (command: WorkerCommand) => { 58 - if (command === "exit") process.exit(); 59 - }); 60 - 61 - trackLoop().catch(console.error);
-21
src/lib/worker_manager.ts
··· 1 - import { eventTracker } from "./db.js"; 2 - import type { WorkerCommand } from "./types.js"; 3 - import * as wt from "node:worker_threads"; 4 - import workerSrc from "./worker.js?raw"; 5 - 6 - let worker: wt.Worker | null = null; 7 - 8 - const sendCommand = (command: WorkerCommand) => { 9 - worker?.postMessage(command); 10 - }; 11 - 12 - export const start = () => { 13 - worker = new wt.Worker(workerSrc, { eval: true }); 14 - worker.on("message", eventTracker.recordEventHit); 15 - }; 16 - 17 - export const exit = () => { 18 - if (worker === null) return; 19 - sendCommand("exit"); 20 - worker = null; 21 - };
src/routes/+layout.svelte client/src/routes/+layout.svelte
-9
src/routes/+page.server.ts
··· 1 - import { eventTracker } from "$lib/db"; 2 - 3 - export const load = async () => { 4 - const events = eventTracker.getNsidCounts(); 5 - 6 - return { 7 - events, 8 - }; 9 - };
+1 -1
src/routes/+page.svelte client/src/routes/+page.svelte
··· 1 1 <script lang="ts"> 2 - import type { EventRecord } from "$lib/db.js"; 2 + import type { EventRecord } from "$lib/types"; 3 3 4 4 interface Props { 5 5 // eslint-disable-next-line @typescript-eslint/no-explicit-any
-13
src/routes/api/events/+server.ts
··· 1 - import { json } from "@sveltejs/kit"; 2 - import { eventTracker } from "$lib/db.js"; 3 - 4 - export const GET = async () => { 5 - try { 6 - const events = eventTracker.getNsidCounts(); 7 - 8 - return json({ events }); 9 - } catch (error) { 10 - console.error("error fetching events:", error); 11 - return json({ error: "failed to fetch events" }, { status: 500 }); 12 - } 13 - };
static/favicon.svg client/static/favicon.svg
+1 -1
svelte.config.js client/svelte.config.js
··· 1 - import adapter from "svelte-adapter-bun"; 1 + import adapter from "@sveltejs/adapter-static"; 2 2 import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 3 3 4 4 /** @type {import('@sveltejs/kit').Config} */
tsconfig.json client/tsconfig.json
vite.config.ts client/vite.config.ts