Scratch space for learning atproto app development

setup basic html views

Changed files
+276 -20
src
firehose
pages
routes
+5 -9
package.json
···
"kysely": "^0.27.4",
"multiformats": "^9.9.0",
"pino": "^9.3.2",
-
"pino-http": "^10.0.0"
},
"devDependencies": {
"@atproto/lex-cli": "^0.4.1",
···
"pino-pretty": "^11.0.0",
"rimraf": "^5.0.0",
"supertest": "^7.0.0",
"tsup": "^8.0.2",
"tsx": "^4.7.2",
"typescript": "^5.4.4",
···
"vitest": "^2.0.0"
},
"lint-staged": {
-
"*.{js,ts,cjs,mjs,d.cts,d.mts,json,jsonc}": [
-
"biome check --apply --no-errors-on-unmatched"
-
]
},
"tsup": {
-
"entry": [
-
"src",
-
"!src/**/__tests__/**",
-
"!src/**/*.test.*"
-
],
"splitting": false,
"sourcemap": true,
"clean": true
···
"kysely": "^0.27.4",
"multiformats": "^9.9.0",
"pino": "^9.3.2",
+
"pino-http": "^10.0.0",
+
"uhtml": "^4.5.9"
},
"devDependencies": {
"@atproto/lex-cli": "^0.4.1",
···
"pino-pretty": "^11.0.0",
"rimraf": "^5.0.0",
"supertest": "^7.0.0",
+
"ts-node": "^10.9.2",
"tsup": "^8.0.2",
"tsx": "^4.7.2",
"typescript": "^5.4.4",
···
"vitest": "^2.0.0"
},
"lint-staged": {
+
"*.{js,ts,cjs,mjs,d.cts,d.mts,json,jsonc}": ["biome check --apply --no-errors-on-unmatched"]
},
"tsup": {
+
"entry": ["src", "!src/**/__tests__/**", "!src/**/*.test.*"],
"splitting": false,
"sourcemap": true,
"clean": true
+209 -7
pnpm-lock.yaml
···
pino-http:
specifier: ^10.0.0
version: 10.2.0
devDependencies:
'@atproto/lex-cli':
···
supertest:
specifier: ^7.0.0
version: 7.0.0
tsup:
specifier: ^8.0.2
version: 8.2.4(tsx@4.16.5)(typescript@5.5.4)
···
version: 4.3.2(typescript@5.5.4)
vitest:
specifier: ^2.0.0
-
version: 2.0.5
packages:
···
requiresBuild: true
dev: false
optional: true
/@esbuild/aix-ppc64@0.21.5:
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
···
'@jridgewell/sourcemap-codec': 1.5.0
dev: true
/@noble/curves@1.4.2:
resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==}
dependencies:
···
dev: true
optional: true
/@rollup/rollup-android-arm-eabi@4.20.0:
resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==}
cpu: [arm]
···
path-browserify: 1.0.1
dev: true
/@types/better-sqlite3@7.6.11:
resolution: {integrity: sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==}
dependencies:
···
tinyrainbow: 1.2.0
dev: true
/abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
···
mime-types: 2.1.35
negotiator: 0.6.3
dev: false
/ansi-escapes@7.0.0:
resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
···
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: true
/array-flatten@1.1.1:
···
vary: 1.1.2
dev: false
/cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
···
shebang-command: 2.0.0
which: 2.0.2
dev: true
/dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
···
wrappy: 1.0.2
dev: true
/dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
···
path-type: 4.0.0
dev: true
/dotenv@16.4.5:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
···
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
dependencies:
once: 1.4.0
/envalid@8.0.0:
resolution: {integrity: sha512-PGeYJnJB5naN0ME6SH8nFcDj9HVbLpYIfg1p5lAyM9T4cH2lwtu2fLbozC/bq+HUUOIFxhX/LP0/GmlqPHT4tQ==}
···
/function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
···
engines: {node: '>=8'}
dev: true
/http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
···
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
dev: true
/media-typer@0.3.0:
···
code-block-writer: 11.0.3
dev: true
/tsconfck@3.1.1(typescript@5.5.4):
resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==}
engines: {node: ^18 || >=20}
···
hasBin: true
dev: true
/uint8arrays@3.0.0:
resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==}
dependencies:
···
engines: {node: '>= 0.4.0'}
dev: false
/varint@6.0.0:
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
dev: false
···
engines: {node: '>= 0.8'}
dev: false
-
/vite-node@2.0.5:
resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
···
debug: 4.3.6
pathe: 1.1.2
tinyrainbow: 1.2.0
-
vite: 5.3.5
transitivePeerDependencies:
- '@types/node'
- less
···
- typescript
dev: true
-
/vite@5.3.5:
resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
···
terser:
optional: true
dependencies:
esbuild: 0.21.5
postcss: 8.4.41
rollup: 4.20.0
···
fsevents: 2.3.3
dev: true
-
/vitest@2.0.5:
resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
···
optional: true
dependencies:
'@ampproject/remapping': 2.3.0
'@vitest/expect': 2.0.5
'@vitest/pretty-format': 2.0.5
'@vitest/runner': 2.0.5
···
tinybench: 2.9.0
tinypool: 1.0.0
tinyrainbow: 1.2.0
-
vite: 5.3.5
-
vite-node: 2.0.5
why-is-node-running: 2.3.0
transitivePeerDependencies:
- less
···
/yesno@0.4.0:
resolution: {integrity: sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==}
dev: true
/zod@3.23.8:
···
pino-http:
specifier: ^10.0.0
version: 10.2.0
+
uhtml:
+
specifier: ^4.5.9
+
version: 4.5.9
devDependencies:
'@atproto/lex-cli':
···
supertest:
specifier: ^7.0.0
version: 7.0.0
+
ts-node:
+
specifier: ^10.9.2
+
version: 10.9.2(@types/node@22.1.0)(typescript@5.5.4)
tsup:
specifier: ^8.0.2
version: 8.2.4(tsx@4.16.5)(typescript@5.5.4)
···
version: 4.3.2(typescript@5.5.4)
vitest:
specifier: ^2.0.0
+
version: 2.0.5(@types/node@22.1.0)
packages:
···
requiresBuild: true
dev: false
optional: true
+
+
/@cspotcode/source-map-support@0.8.1:
+
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+
engines: {node: '>=12'}
+
dependencies:
+
'@jridgewell/trace-mapping': 0.3.9
+
dev: true
/@esbuild/aix-ppc64@0.21.5:
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
···
'@jridgewell/sourcemap-codec': 1.5.0
dev: true
+
/@jridgewell/trace-mapping@0.3.9:
+
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
dependencies:
+
'@jridgewell/resolve-uri': 3.1.2
+
'@jridgewell/sourcemap-codec': 1.5.0
+
dev: true
+
/@noble/curves@1.4.2:
resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==}
dependencies:
···
dev: true
optional: true
+
/@preact/signals-core@1.8.0:
+
resolution: {integrity: sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==}
+
requiresBuild: true
+
dev: false
+
optional: true
+
/@rollup/rollup-android-arm-eabi@4.20.0:
resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==}
cpu: [arm]
···
path-browserify: 1.0.1
dev: true
+
/@tsconfig/node10@1.0.11:
+
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
+
dev: true
+
+
/@tsconfig/node12@1.0.11:
+
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
dev: true
+
+
/@tsconfig/node14@1.0.3:
+
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
dev: true
+
+
/@tsconfig/node16@1.0.4:
+
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
dev: true
+
/@types/better-sqlite3@7.6.11:
resolution: {integrity: sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==}
dependencies:
···
tinyrainbow: 1.2.0
dev: true
+
/@webreflection/signal@2.1.2:
+
resolution: {integrity: sha512-0dW0fstQQkIt588JwhDiPS4xgeeQcQnBHn6MVInrBzmFlnLtzoSJL9G7JqdAlZVVi19tfb8R1QisZIT31cgiug==}
+
requiresBuild: true
+
dev: false
+
optional: true
+
+
/@webreflection/uparser@0.3.3:
+
resolution: {integrity: sha512-XxGfo8jr2eVuvP5lrmwjgMAM7QjtZ0ngFD+dd9Fd3GStcEb4QhLlTiqZYF5O3l5k4sU/V6ZiPrVCzCWXWFEmCw==}
+
dependencies:
+
domconstants: 1.1.6
+
dev: false
+
/abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
···
mime-types: 2.1.35
negotiator: 0.6.3
dev: false
+
+
/acorn-walk@8.3.3:
+
resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==}
+
engines: {node: '>=0.4.0'}
+
dependencies:
+
acorn: 8.12.1
+
dev: true
+
+
/acorn@8.12.1:
+
resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
+
engines: {node: '>=0.4.0'}
+
hasBin: true
+
dev: true
/ansi-escapes@7.0.0:
resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
···
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
+
dev: true
+
+
/arg@4.1.3:
+
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/array-flatten@1.1.1:
···
vary: 1.1.2
dev: false
+
/create-require@1.1.1:
+
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
dev: true
+
/cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
···
shebang-command: 2.0.0
which: 2.0.2
dev: true
+
+
/custom-function@1.0.6:
+
resolution: {integrity: sha512-styyvwOki/EYr+VBe7/m9xAjq6uKx87SpDKIpFRdTQnofBDSZpBEFc9qJLmaJihjjTeEpAIJ+nz+9fUXj+BPNQ==}
+
dev: false
/dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
···
wrappy: 1.0.2
dev: true
+
/diff@4.0.2:
+
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
+
engines: {node: '>=0.3.1'}
+
dev: true
+
/dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
···
path-type: 4.0.0
dev: true
+
/dom-serializer@2.0.0:
+
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
dependencies:
+
domelementtype: 2.3.0
+
domhandler: 5.0.3
+
entities: 4.5.0
+
dev: false
+
+
/domconstants@1.1.6:
+
resolution: {integrity: sha512-CuaDrThJ4VM+LyZ4ax8n52k0KbLJZtffyGkuj1WhpTRRcSfcy/9DfOBa68jenhX96oNUTunblSJEUNC4baFdmQ==}
+
dev: false
+
+
/domelementtype@2.3.0:
+
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
dev: false
+
+
/domhandler@5.0.3:
+
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+
engines: {node: '>= 4'}
+
dependencies:
+
domelementtype: 2.3.0
+
dev: false
+
+
/domutils@3.1.0:
+
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
+
dependencies:
+
dom-serializer: 2.0.0
+
domelementtype: 2.3.0
+
domhandler: 5.0.3
+
dev: false
+
/dotenv@16.4.5:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
···
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
dependencies:
once: 1.4.0
+
+
/entities@4.5.0:
+
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+
engines: {node: '>=0.12'}
+
dev: false
/envalid@8.0.0:
resolution: {integrity: sha512-PGeYJnJB5naN0ME6SH8nFcDj9HVbLpYIfg1p5lAyM9T4cH2lwtu2fLbozC/bq+HUUOIFxhX/LP0/GmlqPHT4tQ==}
···
/function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+
/gc-hook@0.3.1:
+
resolution: {integrity: sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A==}
+
dev: false
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
···
engines: {node: '>=8'}
dev: true
+
/html-escaper@3.0.3:
+
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
+
dev: false
+
+
/htmlparser2@9.1.0:
+
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
+
dependencies:
+
domelementtype: 2.3.0
+
domhandler: 5.0.3
+
domutils: 3.1.0
+
entities: 4.5.0
+
dev: false
+
/http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
···
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
+
dev: true
+
+
/make-error@1.3.6:
+
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/media-typer@0.3.0:
···
code-block-writer: 11.0.3
dev: true
+
/ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4):
+
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+
hasBin: true
+
peerDependencies:
+
'@swc/core': '>=1.2.50'
+
'@swc/wasm': '>=1.2.50'
+
'@types/node': '*'
+
typescript: '>=2.7'
+
peerDependenciesMeta:
+
'@swc/core':
+
optional: true
+
'@swc/wasm':
+
optional: true
+
dependencies:
+
'@cspotcode/source-map-support': 0.8.1
+
'@tsconfig/node10': 1.0.11
+
'@tsconfig/node12': 1.0.11
+
'@tsconfig/node14': 1.0.3
+
'@tsconfig/node16': 1.0.4
+
'@types/node': 22.1.0
+
acorn: 8.12.1
+
acorn-walk: 8.3.3
+
arg: 4.1.3
+
create-require: 1.1.1
+
diff: 4.0.2
+
make-error: 1.3.6
+
typescript: 5.5.4
+
v8-compile-cache-lib: 3.0.1
+
yn: 3.1.1
+
dev: true
+
/tsconfck@3.1.1(typescript@5.5.4):
resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==}
engines: {node: ^18 || >=20}
···
hasBin: true
dev: true
+
/udomdiff@1.1.0:
+
resolution: {integrity: sha512-aqjTs5x/wsShZBkVagdafJkP8S3UMGhkHKszsu1cszjjZ7iOp86+Qb3QOFYh01oWjPMy5ZTuxD6hw5uTKxd+VA==}
+
dev: false
+
+
/uhtml@4.5.9:
+
resolution: {integrity: sha512-WAfIK/E3ZJpaFl0MSzGSB54r7I8Vc8ZyUlOsN8GnLnEaxuioOUyKAS6q/N/xQ5GD9vFFBnx6q+3N3Eq9KNCvTQ==}
+
dependencies:
+
'@webreflection/uparser': 0.3.3
+
custom-function: 1.0.6
+
domconstants: 1.1.6
+
gc-hook: 0.3.1
+
html-escaper: 3.0.3
+
htmlparser2: 9.1.0
+
udomdiff: 1.1.0
+
optionalDependencies:
+
'@preact/signals-core': 1.8.0
+
'@webreflection/signal': 2.1.2
+
dev: false
+
/uint8arrays@3.0.0:
resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==}
dependencies:
···
engines: {node: '>= 0.4.0'}
dev: false
+
/v8-compile-cache-lib@3.0.1:
+
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+
dev: true
+
/varint@6.0.0:
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
dev: false
···
engines: {node: '>= 0.8'}
dev: false
+
/vite-node@2.0.5(@types/node@22.1.0):
resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
···
debug: 4.3.6
pathe: 1.1.2
tinyrainbow: 1.2.0
+
vite: 5.3.5(@types/node@22.1.0)
transitivePeerDependencies:
- '@types/node'
- less
···
- typescript
dev: true
+
/vite@5.3.5(@types/node@22.1.0):
resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
···
terser:
optional: true
dependencies:
+
'@types/node': 22.1.0
esbuild: 0.21.5
postcss: 8.4.41
rollup: 4.20.0
···
fsevents: 2.3.3
dev: true
+
/vitest@2.0.5(@types/node@22.1.0):
resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
···
optional: true
dependencies:
'@ampproject/remapping': 2.3.0
+
'@types/node': 22.1.0
'@vitest/expect': 2.0.5
'@vitest/pretty-format': 2.0.5
'@vitest/runner': 2.0.5
···
tinybench: 2.9.0
tinypool: 1.0.0
tinyrainbow: 1.2.0
+
vite: 5.3.5(@types/node@22.1.0)
+
vite-node: 2.0.5(@types/node@22.1.0)
why-is-node-running: 2.3.0
transitivePeerDependencies:
- less
···
/yesno@0.4.0:
resolution: {integrity: sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==}
+
dev: true
+
+
/yn@3.1.1:
+
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+
engines: {node: '>=6'}
dev: true
/zod@3.23.8:
+2 -1
src/firehose/ingester.ts
···
-
import type { Database } from '#/db'
import { Firehose } from '#/firehose/firehose'
export class Ingester {
···
text: post.text as string,
indexedAt: new Date().toISOString(),
})
.execute()
}
}
···
+
import type { Database } from '#/db/index'
import { Firehose } from '#/firehose/firehose'
export class Ingester {
···
text: post.text as string,
indexedAt: new Date().toISOString(),
})
+
.onConflict((oc) => oc.doNothing())
.execute()
}
}
+32
src/pages/home.ts
···
···
+
import { AtUri } from '@atproto/syntax'
+
import type { Post } from '#/db/schema'
+
import { html } from '../view'
+
import { shell } from './shell'
+
+
export function home(posts: Post[]) {
+
return shell({
+
title: 'Home',
+
content: content(posts),
+
})
+
}
+
+
function content(posts: Post[]) {
+
return html`<div>
+
<h1>Welcome to My Page</h1>
+
<p>It's pretty special here.</p>
+
<ul>
+
${posts.map((post) => {
+
return html`<li>
+
<a href="${toBskyLink(post.uri)}" target="_blank">🔗</a>
+
${post.text}
+
</li>`
+
})}
+
</ul>
+
<a href="/">Give me more</a>
+
</div>`
+
}
+
+
function toBskyLink(uriStr: string) {
+
const uri = new AtUri(uriStr)
+
return `https://bsky.app/profile/${uri.host}/post/${uri.rkey}`
+
}
+12
src/pages/shell.ts
···
···
+
import { type Hole, html } from '../view'
+
+
export function shell({ title, content }: { title: string; content: Hole }) {
+
return html`<html>
+
<head>
+
<title>${title}</title>
+
</head>
+
<body>
+
${content}
+
</body>
+
</html>`
+
}
+3 -2
src/routes/index.ts
···
import express from 'express'
import type { AppContext } from '#/config'
import { handler } from './util'
export const createRouter = (ctx: AppContext) => {
···
'/',
handler(async (req, res) => {
const posts = await ctx.db.selectFrom('post').selectAll().orderBy('indexedAt', 'desc').limit(10).execute()
-
const postTexts = posts.map((row) => row.text)
-
res.json(postTexts)
}),
)
···
import express from 'express'
import type { AppContext } from '#/config'
+
import { home } from '#/pages/home'
+
import { page } from '#/view'
import { handler } from './util'
export const createRouter = (ctx: AppContext) => {
···
'/',
handler(async (req, res) => {
const posts = await ctx.db.selectFrom('post').selectAll().orderBy('indexedAt', 'desc').limit(10).execute()
+
return res.type('html').send(page(home(posts)))
}),
)
+12
src/view.ts
···
···
+
// @ts-ignore
+
import ssr from 'uhtml/ssr'
+
import type initSSR from 'uhtml/types/init-ssr'
+
import type { Hole } from 'uhtml/types/keyed'
+
+
export type { Hole }
+
+
export const { html }: ReturnType<typeof initSSR> = ssr()
+
+
export function page(hole: Hole) {
+
return `<!DOCTYPE html>\n${hole.toDOM().toString()}`
+
}
+1 -1
tsconfig.json
···
"paths": {
"#/*": ["src/*"]
},
-
"moduleResolution": "Node",
"outDir": "dist",
"importsNotUsedAsValues": "remove",
"strict": true,
···
"paths": {
"#/*": ["src/*"]
},
+
"moduleResolution": "Node10",
"outDir": "dist",
"importsNotUsedAsValues": "remove",
"strict": true,