this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial commit.

alice 07e12d2b

+2215
+2
.gitignore
··· 1 + data/posts.db 2 + node_modules
data/.gitkeep

This is a binary file and will not be displayed.

+31
package.json
··· 1 + { 2 + "name": "bluesky-firehose-consumer", 3 + "version": "1.0.0", 4 + "description": "Consumes Bluesky firehose, processes posts, and exposes metrics for Prometheus.", 5 + "main": "dist/index.js", 6 + "scripts": { 7 + "start": "npx tsx src/index.ts", 8 + "dev": "npx tsx --watch src/index.ts" 9 + }, 10 + "dependencies": { 11 + "better-sqlite3": "^11.3.0", 12 + "dotenv": "^16.4.5", 13 + "express": "^4.21.0", 14 + "pino": "^9.4.0", 15 + "pino-pretty": "^11.2.2", 16 + "prom-client": "^15.1.3", 17 + "rate-limiter-flexible": "^5.0.3", 18 + "ws": "^8.18.0" 19 + }, 20 + "devDependencies": { 21 + "@types/better-sqlite3": "^7.6.11", 22 + "@types/express": "^4.17.21", 23 + "@types/node": "^20.16.5", 24 + "@types/ws": "^8.5.12", 25 + "tsx": "^4.19.1", 26 + "typescript": "^5.6.2" 27 + }, 28 + "author": "Your Name", 29 + "license": "MIT", 30 + "packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c" 31 + }
+1482
pnpm-lock.yaml
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + importers: 8 + 9 + .: 10 + dependencies: 11 + better-sqlite3: 12 + specifier: ^11.3.0 13 + version: 11.3.0 14 + dotenv: 15 + specifier: ^16.4.5 16 + version: 16.4.5 17 + express: 18 + specifier: ^4.21.0 19 + version: 4.21.0 20 + pino: 21 + specifier: ^9.4.0 22 + version: 9.4.0 23 + pino-pretty: 24 + specifier: ^11.2.2 25 + version: 11.2.2 26 + prom-client: 27 + specifier: ^15.1.3 28 + version: 15.1.3 29 + rate-limiter-flexible: 30 + specifier: ^5.0.3 31 + version: 5.0.3 32 + ws: 33 + specifier: ^8.18.0 34 + version: 8.18.0 35 + devDependencies: 36 + '@types/better-sqlite3': 37 + specifier: ^7.6.11 38 + version: 7.6.11 39 + '@types/express': 40 + specifier: ^4.17.21 41 + version: 4.17.21 42 + '@types/node': 43 + specifier: ^20.16.5 44 + version: 20.16.5 45 + '@types/ws': 46 + specifier: ^8.5.12 47 + version: 8.5.12 48 + tsx: 49 + specifier: ^4.19.1 50 + version: 4.19.1 51 + typescript: 52 + specifier: ^5.6.2 53 + version: 5.6.2 54 + 55 + packages: 56 + 57 + '@esbuild/aix-ppc64@0.23.1': 58 + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} 59 + engines: {node: '>=18'} 60 + cpu: [ppc64] 61 + os: [aix] 62 + 63 + '@esbuild/android-arm64@0.23.1': 64 + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} 65 + engines: {node: '>=18'} 66 + cpu: [arm64] 67 + os: [android] 68 + 69 + '@esbuild/android-arm@0.23.1': 70 + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} 71 + engines: {node: '>=18'} 72 + cpu: [arm] 73 + os: [android] 74 + 75 + '@esbuild/android-x64@0.23.1': 76 + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} 77 + engines: {node: '>=18'} 78 + cpu: [x64] 79 + os: [android] 80 + 81 + '@esbuild/darwin-arm64@0.23.1': 82 + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} 83 + engines: {node: '>=18'} 84 + cpu: [arm64] 85 + os: [darwin] 86 + 87 + '@esbuild/darwin-x64@0.23.1': 88 + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} 89 + engines: {node: '>=18'} 90 + cpu: [x64] 91 + os: [darwin] 92 + 93 + '@esbuild/freebsd-arm64@0.23.1': 94 + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} 95 + engines: {node: '>=18'} 96 + cpu: [arm64] 97 + os: [freebsd] 98 + 99 + '@esbuild/freebsd-x64@0.23.1': 100 + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} 101 + engines: {node: '>=18'} 102 + cpu: [x64] 103 + os: [freebsd] 104 + 105 + '@esbuild/linux-arm64@0.23.1': 106 + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} 107 + engines: {node: '>=18'} 108 + cpu: [arm64] 109 + os: [linux] 110 + 111 + '@esbuild/linux-arm@0.23.1': 112 + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} 113 + engines: {node: '>=18'} 114 + cpu: [arm] 115 + os: [linux] 116 + 117 + '@esbuild/linux-ia32@0.23.1': 118 + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} 119 + engines: {node: '>=18'} 120 + cpu: [ia32] 121 + os: [linux] 122 + 123 + '@esbuild/linux-loong64@0.23.1': 124 + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} 125 + engines: {node: '>=18'} 126 + cpu: [loong64] 127 + os: [linux] 128 + 129 + '@esbuild/linux-mips64el@0.23.1': 130 + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} 131 + engines: {node: '>=18'} 132 + cpu: [mips64el] 133 + os: [linux] 134 + 135 + '@esbuild/linux-ppc64@0.23.1': 136 + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} 137 + engines: {node: '>=18'} 138 + cpu: [ppc64] 139 + os: [linux] 140 + 141 + '@esbuild/linux-riscv64@0.23.1': 142 + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} 143 + engines: {node: '>=18'} 144 + cpu: [riscv64] 145 + os: [linux] 146 + 147 + '@esbuild/linux-s390x@0.23.1': 148 + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} 149 + engines: {node: '>=18'} 150 + cpu: [s390x] 151 + os: [linux] 152 + 153 + '@esbuild/linux-x64@0.23.1': 154 + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} 155 + engines: {node: '>=18'} 156 + cpu: [x64] 157 + os: [linux] 158 + 159 + '@esbuild/netbsd-x64@0.23.1': 160 + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} 161 + engines: {node: '>=18'} 162 + cpu: [x64] 163 + os: [netbsd] 164 + 165 + '@esbuild/openbsd-arm64@0.23.1': 166 + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} 167 + engines: {node: '>=18'} 168 + cpu: [arm64] 169 + os: [openbsd] 170 + 171 + '@esbuild/openbsd-x64@0.23.1': 172 + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} 173 + engines: {node: '>=18'} 174 + cpu: [x64] 175 + os: [openbsd] 176 + 177 + '@esbuild/sunos-x64@0.23.1': 178 + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} 179 + engines: {node: '>=18'} 180 + cpu: [x64] 181 + os: [sunos] 182 + 183 + '@esbuild/win32-arm64@0.23.1': 184 + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} 185 + engines: {node: '>=18'} 186 + cpu: [arm64] 187 + os: [win32] 188 + 189 + '@esbuild/win32-ia32@0.23.1': 190 + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} 191 + engines: {node: '>=18'} 192 + cpu: [ia32] 193 + os: [win32] 194 + 195 + '@esbuild/win32-x64@0.23.1': 196 + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} 197 + engines: {node: '>=18'} 198 + cpu: [x64] 199 + os: [win32] 200 + 201 + '@opentelemetry/api@1.9.0': 202 + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} 203 + engines: {node: '>=8.0.0'} 204 + 205 + '@types/better-sqlite3@7.6.11': 206 + resolution: {integrity: sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==} 207 + 208 + '@types/body-parser@1.19.5': 209 + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} 210 + 211 + '@types/connect@3.4.38': 212 + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} 213 + 214 + '@types/express-serve-static-core@4.19.5': 215 + resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==} 216 + 217 + '@types/express@4.17.21': 218 + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} 219 + 220 + '@types/http-errors@2.0.4': 221 + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} 222 + 223 + '@types/mime@1.3.5': 224 + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} 225 + 226 + '@types/node@20.16.5': 227 + resolution: {integrity: sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==} 228 + 229 + '@types/qs@6.9.15': 230 + resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} 231 + 232 + '@types/range-parser@1.2.7': 233 + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} 234 + 235 + '@types/send@0.17.4': 236 + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} 237 + 238 + '@types/serve-static@1.15.7': 239 + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} 240 + 241 + '@types/ws@8.5.12': 242 + resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} 243 + 244 + abort-controller@3.0.0: 245 + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} 246 + engines: {node: '>=6.5'} 247 + 248 + accepts@1.3.8: 249 + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} 250 + engines: {node: '>= 0.6'} 251 + 252 + array-flatten@1.1.1: 253 + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} 254 + 255 + atomic-sleep@1.0.0: 256 + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} 257 + engines: {node: '>=8.0.0'} 258 + 259 + base64-js@1.5.1: 260 + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 261 + 262 + better-sqlite3@11.3.0: 263 + resolution: {integrity: sha512-iHt9j8NPYF3oKCNOO5ZI4JwThjt3Z6J6XrcwG85VNMVzv1ByqrHWv5VILEbCMFWDsoHhXvQ7oC8vgRXFAKgl9w==} 264 + 265 + bindings@1.5.0: 266 + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} 267 + 268 + bintrees@1.0.2: 269 + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} 270 + 271 + bl@4.1.0: 272 + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} 273 + 274 + body-parser@1.20.3: 275 + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} 276 + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 277 + 278 + buffer@5.7.1: 279 + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} 280 + 281 + buffer@6.0.3: 282 + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 283 + 284 + bytes@3.1.2: 285 + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} 286 + engines: {node: '>= 0.8'} 287 + 288 + call-bind@1.0.7: 289 + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} 290 + engines: {node: '>= 0.4'} 291 + 292 + chownr@1.1.4: 293 + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} 294 + 295 + colorette@2.0.20: 296 + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} 297 + 298 + content-disposition@0.5.4: 299 + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} 300 + engines: {node: '>= 0.6'} 301 + 302 + content-type@1.0.5: 303 + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} 304 + engines: {node: '>= 0.6'} 305 + 306 + cookie-signature@1.0.6: 307 + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} 308 + 309 + cookie@0.6.0: 310 + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} 311 + engines: {node: '>= 0.6'} 312 + 313 + dateformat@4.6.3: 314 + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} 315 + 316 + debug@2.6.9: 317 + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} 318 + peerDependencies: 319 + supports-color: '*' 320 + peerDependenciesMeta: 321 + supports-color: 322 + optional: true 323 + 324 + decompress-response@6.0.0: 325 + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} 326 + engines: {node: '>=10'} 327 + 328 + deep-extend@0.6.0: 329 + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} 330 + engines: {node: '>=4.0.0'} 331 + 332 + define-data-property@1.1.4: 333 + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 334 + engines: {node: '>= 0.4'} 335 + 336 + depd@2.0.0: 337 + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} 338 + engines: {node: '>= 0.8'} 339 + 340 + destroy@1.2.0: 341 + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} 342 + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 343 + 344 + detect-libc@2.0.3: 345 + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} 346 + engines: {node: '>=8'} 347 + 348 + dotenv@16.4.5: 349 + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} 350 + engines: {node: '>=12'} 351 + 352 + ee-first@1.1.1: 353 + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} 354 + 355 + encodeurl@1.0.2: 356 + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} 357 + engines: {node: '>= 0.8'} 358 + 359 + encodeurl@2.0.0: 360 + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} 361 + engines: {node: '>= 0.8'} 362 + 363 + end-of-stream@1.4.4: 364 + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} 365 + 366 + es-define-property@1.0.0: 367 + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} 368 + engines: {node: '>= 0.4'} 369 + 370 + es-errors@1.3.0: 371 + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 372 + engines: {node: '>= 0.4'} 373 + 374 + esbuild@0.23.1: 375 + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} 376 + engines: {node: '>=18'} 377 + hasBin: true 378 + 379 + escape-html@1.0.3: 380 + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} 381 + 382 + etag@1.8.1: 383 + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} 384 + engines: {node: '>= 0.6'} 385 + 386 + event-target-shim@5.0.1: 387 + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} 388 + engines: {node: '>=6'} 389 + 390 + events@3.3.0: 391 + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 392 + engines: {node: '>=0.8.x'} 393 + 394 + expand-template@2.0.3: 395 + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} 396 + engines: {node: '>=6'} 397 + 398 + express@4.21.0: 399 + resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==} 400 + engines: {node: '>= 0.10.0'} 401 + 402 + fast-copy@3.0.2: 403 + resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} 404 + 405 + fast-redact@3.5.0: 406 + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} 407 + engines: {node: '>=6'} 408 + 409 + fast-safe-stringify@2.1.1: 410 + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} 411 + 412 + file-uri-to-path@1.0.0: 413 + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} 414 + 415 + finalhandler@1.3.1: 416 + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} 417 + engines: {node: '>= 0.8'} 418 + 419 + forwarded@0.2.0: 420 + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} 421 + engines: {node: '>= 0.6'} 422 + 423 + fresh@0.5.2: 424 + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} 425 + engines: {node: '>= 0.6'} 426 + 427 + fs-constants@1.0.0: 428 + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} 429 + 430 + fsevents@2.3.3: 431 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 432 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 433 + os: [darwin] 434 + 435 + function-bind@1.1.2: 436 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 437 + 438 + get-intrinsic@1.2.4: 439 + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} 440 + engines: {node: '>= 0.4'} 441 + 442 + get-tsconfig@4.8.1: 443 + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} 444 + 445 + github-from-package@0.0.0: 446 + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} 447 + 448 + gopd@1.0.1: 449 + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} 450 + 451 + has-property-descriptors@1.0.2: 452 + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} 453 + 454 + has-proto@1.0.3: 455 + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} 456 + engines: {node: '>= 0.4'} 457 + 458 + has-symbols@1.0.3: 459 + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 460 + engines: {node: '>= 0.4'} 461 + 462 + hasown@2.0.2: 463 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 464 + engines: {node: '>= 0.4'} 465 + 466 + help-me@5.0.0: 467 + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} 468 + 469 + http-errors@2.0.0: 470 + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} 471 + engines: {node: '>= 0.8'} 472 + 473 + iconv-lite@0.4.24: 474 + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} 475 + engines: {node: '>=0.10.0'} 476 + 477 + ieee754@1.2.1: 478 + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 479 + 480 + inherits@2.0.4: 481 + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 482 + 483 + ini@1.3.8: 484 + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} 485 + 486 + ipaddr.js@1.9.1: 487 + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} 488 + engines: {node: '>= 0.10'} 489 + 490 + joycon@3.1.1: 491 + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} 492 + engines: {node: '>=10'} 493 + 494 + media-typer@0.3.0: 495 + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} 496 + engines: {node: '>= 0.6'} 497 + 498 + merge-descriptors@1.0.3: 499 + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} 500 + 501 + methods@1.1.2: 502 + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} 503 + engines: {node: '>= 0.6'} 504 + 505 + mime-db@1.52.0: 506 + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 507 + engines: {node: '>= 0.6'} 508 + 509 + mime-types@2.1.35: 510 + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 511 + engines: {node: '>= 0.6'} 512 + 513 + mime@1.6.0: 514 + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} 515 + engines: {node: '>=4'} 516 + hasBin: true 517 + 518 + mimic-response@3.1.0: 519 + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} 520 + engines: {node: '>=10'} 521 + 522 + minimist@1.2.8: 523 + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 524 + 525 + mkdirp-classic@0.5.3: 526 + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} 527 + 528 + ms@2.0.0: 529 + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} 530 + 531 + ms@2.1.3: 532 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 533 + 534 + napi-build-utils@1.0.2: 535 + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} 536 + 537 + negotiator@0.6.3: 538 + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} 539 + engines: {node: '>= 0.6'} 540 + 541 + node-abi@3.67.0: 542 + resolution: {integrity: sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==} 543 + engines: {node: '>=10'} 544 + 545 + object-inspect@1.13.2: 546 + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} 547 + engines: {node: '>= 0.4'} 548 + 549 + on-exit-leak-free@2.1.2: 550 + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} 551 + engines: {node: '>=14.0.0'} 552 + 553 + on-finished@2.4.1: 554 + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} 555 + engines: {node: '>= 0.8'} 556 + 557 + once@1.4.0: 558 + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 559 + 560 + parseurl@1.3.3: 561 + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} 562 + engines: {node: '>= 0.8'} 563 + 564 + path-to-regexp@0.1.10: 565 + resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} 566 + 567 + pino-abstract-transport@1.2.0: 568 + resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} 569 + 570 + pino-pretty@11.2.2: 571 + resolution: {integrity: sha512-2FnyGir8nAJAqD3srROdrF1J5BIcMT4nwj7hHSc60El6Uxlym00UbCCd8pYIterstVBFlMyF1yFV8XdGIPbj4A==} 572 + hasBin: true 573 + 574 + pino-std-serializers@7.0.0: 575 + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} 576 + 577 + pino@9.4.0: 578 + resolution: {integrity: sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w==} 579 + hasBin: true 580 + 581 + prebuild-install@7.1.2: 582 + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} 583 + engines: {node: '>=10'} 584 + hasBin: true 585 + 586 + process-warning@4.0.0: 587 + resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==} 588 + 589 + process@0.11.10: 590 + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} 591 + engines: {node: '>= 0.6.0'} 592 + 593 + prom-client@15.1.3: 594 + resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} 595 + engines: {node: ^16 || ^18 || >=20} 596 + 597 + proxy-addr@2.0.7: 598 + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} 599 + engines: {node: '>= 0.10'} 600 + 601 + pump@3.0.2: 602 + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} 603 + 604 + qs@6.13.0: 605 + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} 606 + engines: {node: '>=0.6'} 607 + 608 + quick-format-unescaped@4.0.4: 609 + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} 610 + 611 + range-parser@1.2.1: 612 + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} 613 + engines: {node: '>= 0.6'} 614 + 615 + rate-limiter-flexible@5.0.3: 616 + resolution: {integrity: sha512-lWx2y8NBVlTOLPyqs+6y7dxfEpT6YFqKy3MzWbCy95sTTOhOuxufP2QvRyOHpfXpB9OUJPbVLybw3z3AVAS5fA==} 617 + 618 + raw-body@2.5.2: 619 + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} 620 + engines: {node: '>= 0.8'} 621 + 622 + rc@1.2.8: 623 + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} 624 + hasBin: true 625 + 626 + readable-stream@3.6.2: 627 + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 628 + engines: {node: '>= 6'} 629 + 630 + readable-stream@4.5.2: 631 + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} 632 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 633 + 634 + real-require@0.2.0: 635 + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} 636 + engines: {node: '>= 12.13.0'} 637 + 638 + resolve-pkg-maps@1.0.0: 639 + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 640 + 641 + safe-buffer@5.2.1: 642 + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 643 + 644 + safe-stable-stringify@2.5.0: 645 + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} 646 + engines: {node: '>=10'} 647 + 648 + safer-buffer@2.1.2: 649 + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 650 + 651 + secure-json-parse@2.7.0: 652 + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} 653 + 654 + semver@7.6.3: 655 + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} 656 + engines: {node: '>=10'} 657 + hasBin: true 658 + 659 + send@0.19.0: 660 + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} 661 + engines: {node: '>= 0.8.0'} 662 + 663 + serve-static@1.16.2: 664 + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} 665 + engines: {node: '>= 0.8.0'} 666 + 667 + set-function-length@1.2.2: 668 + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} 669 + engines: {node: '>= 0.4'} 670 + 671 + setprototypeof@1.2.0: 672 + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 673 + 674 + side-channel@1.0.6: 675 + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} 676 + engines: {node: '>= 0.4'} 677 + 678 + simple-concat@1.0.1: 679 + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} 680 + 681 + simple-get@4.0.1: 682 + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} 683 + 684 + sonic-boom@4.1.0: 685 + resolution: {integrity: sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw==} 686 + 687 + split2@4.2.0: 688 + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} 689 + engines: {node: '>= 10.x'} 690 + 691 + statuses@2.0.1: 692 + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} 693 + engines: {node: '>= 0.8'} 694 + 695 + string_decoder@1.3.0: 696 + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 697 + 698 + strip-json-comments@2.0.1: 699 + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} 700 + engines: {node: '>=0.10.0'} 701 + 702 + strip-json-comments@3.1.1: 703 + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 704 + engines: {node: '>=8'} 705 + 706 + tar-fs@2.1.1: 707 + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} 708 + 709 + tar-stream@2.2.0: 710 + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} 711 + engines: {node: '>=6'} 712 + 713 + tdigest@0.1.2: 714 + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} 715 + 716 + thread-stream@3.1.0: 717 + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} 718 + 719 + toidentifier@1.0.1: 720 + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} 721 + engines: {node: '>=0.6'} 722 + 723 + tsx@4.19.1: 724 + resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} 725 + engines: {node: '>=18.0.0'} 726 + hasBin: true 727 + 728 + tunnel-agent@0.6.0: 729 + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} 730 + 731 + type-is@1.6.18: 732 + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} 733 + engines: {node: '>= 0.6'} 734 + 735 + typescript@5.6.2: 736 + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} 737 + engines: {node: '>=14.17'} 738 + hasBin: true 739 + 740 + undici-types@6.19.8: 741 + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} 742 + 743 + unpipe@1.0.0: 744 + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} 745 + engines: {node: '>= 0.8'} 746 + 747 + util-deprecate@1.0.2: 748 + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 749 + 750 + utils-merge@1.0.1: 751 + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} 752 + engines: {node: '>= 0.4.0'} 753 + 754 + vary@1.1.2: 755 + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} 756 + engines: {node: '>= 0.8'} 757 + 758 + wrappy@1.0.2: 759 + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 760 + 761 + ws@8.18.0: 762 + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} 763 + engines: {node: '>=10.0.0'} 764 + peerDependencies: 765 + bufferutil: ^4.0.1 766 + utf-8-validate: '>=5.0.2' 767 + peerDependenciesMeta: 768 + bufferutil: 769 + optional: true 770 + utf-8-validate: 771 + optional: true 772 + 773 + snapshots: 774 + 775 + '@esbuild/aix-ppc64@0.23.1': 776 + optional: true 777 + 778 + '@esbuild/android-arm64@0.23.1': 779 + optional: true 780 + 781 + '@esbuild/android-arm@0.23.1': 782 + optional: true 783 + 784 + '@esbuild/android-x64@0.23.1': 785 + optional: true 786 + 787 + '@esbuild/darwin-arm64@0.23.1': 788 + optional: true 789 + 790 + '@esbuild/darwin-x64@0.23.1': 791 + optional: true 792 + 793 + '@esbuild/freebsd-arm64@0.23.1': 794 + optional: true 795 + 796 + '@esbuild/freebsd-x64@0.23.1': 797 + optional: true 798 + 799 + '@esbuild/linux-arm64@0.23.1': 800 + optional: true 801 + 802 + '@esbuild/linux-arm@0.23.1': 803 + optional: true 804 + 805 + '@esbuild/linux-ia32@0.23.1': 806 + optional: true 807 + 808 + '@esbuild/linux-loong64@0.23.1': 809 + optional: true 810 + 811 + '@esbuild/linux-mips64el@0.23.1': 812 + optional: true 813 + 814 + '@esbuild/linux-ppc64@0.23.1': 815 + optional: true 816 + 817 + '@esbuild/linux-riscv64@0.23.1': 818 + optional: true 819 + 820 + '@esbuild/linux-s390x@0.23.1': 821 + optional: true 822 + 823 + '@esbuild/linux-x64@0.23.1': 824 + optional: true 825 + 826 + '@esbuild/netbsd-x64@0.23.1': 827 + optional: true 828 + 829 + '@esbuild/openbsd-arm64@0.23.1': 830 + optional: true 831 + 832 + '@esbuild/openbsd-x64@0.23.1': 833 + optional: true 834 + 835 + '@esbuild/sunos-x64@0.23.1': 836 + optional: true 837 + 838 + '@esbuild/win32-arm64@0.23.1': 839 + optional: true 840 + 841 + '@esbuild/win32-ia32@0.23.1': 842 + optional: true 843 + 844 + '@esbuild/win32-x64@0.23.1': 845 + optional: true 846 + 847 + '@opentelemetry/api@1.9.0': {} 848 + 849 + '@types/better-sqlite3@7.6.11': 850 + dependencies: 851 + '@types/node': 20.16.5 852 + 853 + '@types/body-parser@1.19.5': 854 + dependencies: 855 + '@types/connect': 3.4.38 856 + '@types/node': 20.16.5 857 + 858 + '@types/connect@3.4.38': 859 + dependencies: 860 + '@types/node': 20.16.5 861 + 862 + '@types/express-serve-static-core@4.19.5': 863 + dependencies: 864 + '@types/node': 20.16.5 865 + '@types/qs': 6.9.15 866 + '@types/range-parser': 1.2.7 867 + '@types/send': 0.17.4 868 + 869 + '@types/express@4.17.21': 870 + dependencies: 871 + '@types/body-parser': 1.19.5 872 + '@types/express-serve-static-core': 4.19.5 873 + '@types/qs': 6.9.15 874 + '@types/serve-static': 1.15.7 875 + 876 + '@types/http-errors@2.0.4': {} 877 + 878 + '@types/mime@1.3.5': {} 879 + 880 + '@types/node@20.16.5': 881 + dependencies: 882 + undici-types: 6.19.8 883 + 884 + '@types/qs@6.9.15': {} 885 + 886 + '@types/range-parser@1.2.7': {} 887 + 888 + '@types/send@0.17.4': 889 + dependencies: 890 + '@types/mime': 1.3.5 891 + '@types/node': 20.16.5 892 + 893 + '@types/serve-static@1.15.7': 894 + dependencies: 895 + '@types/http-errors': 2.0.4 896 + '@types/node': 20.16.5 897 + '@types/send': 0.17.4 898 + 899 + '@types/ws@8.5.12': 900 + dependencies: 901 + '@types/node': 20.16.5 902 + 903 + abort-controller@3.0.0: 904 + dependencies: 905 + event-target-shim: 5.0.1 906 + 907 + accepts@1.3.8: 908 + dependencies: 909 + mime-types: 2.1.35 910 + negotiator: 0.6.3 911 + 912 + array-flatten@1.1.1: {} 913 + 914 + atomic-sleep@1.0.0: {} 915 + 916 + base64-js@1.5.1: {} 917 + 918 + better-sqlite3@11.3.0: 919 + dependencies: 920 + bindings: 1.5.0 921 + prebuild-install: 7.1.2 922 + 923 + bindings@1.5.0: 924 + dependencies: 925 + file-uri-to-path: 1.0.0 926 + 927 + bintrees@1.0.2: {} 928 + 929 + bl@4.1.0: 930 + dependencies: 931 + buffer: 5.7.1 932 + inherits: 2.0.4 933 + readable-stream: 3.6.2 934 + 935 + body-parser@1.20.3: 936 + dependencies: 937 + bytes: 3.1.2 938 + content-type: 1.0.5 939 + debug: 2.6.9 940 + depd: 2.0.0 941 + destroy: 1.2.0 942 + http-errors: 2.0.0 943 + iconv-lite: 0.4.24 944 + on-finished: 2.4.1 945 + qs: 6.13.0 946 + raw-body: 2.5.2 947 + type-is: 1.6.18 948 + unpipe: 1.0.0 949 + transitivePeerDependencies: 950 + - supports-color 951 + 952 + buffer@5.7.1: 953 + dependencies: 954 + base64-js: 1.5.1 955 + ieee754: 1.2.1 956 + 957 + buffer@6.0.3: 958 + dependencies: 959 + base64-js: 1.5.1 960 + ieee754: 1.2.1 961 + 962 + bytes@3.1.2: {} 963 + 964 + call-bind@1.0.7: 965 + dependencies: 966 + es-define-property: 1.0.0 967 + es-errors: 1.3.0 968 + function-bind: 1.1.2 969 + get-intrinsic: 1.2.4 970 + set-function-length: 1.2.2 971 + 972 + chownr@1.1.4: {} 973 + 974 + colorette@2.0.20: {} 975 + 976 + content-disposition@0.5.4: 977 + dependencies: 978 + safe-buffer: 5.2.1 979 + 980 + content-type@1.0.5: {} 981 + 982 + cookie-signature@1.0.6: {} 983 + 984 + cookie@0.6.0: {} 985 + 986 + dateformat@4.6.3: {} 987 + 988 + debug@2.6.9: 989 + dependencies: 990 + ms: 2.0.0 991 + 992 + decompress-response@6.0.0: 993 + dependencies: 994 + mimic-response: 3.1.0 995 + 996 + deep-extend@0.6.0: {} 997 + 998 + define-data-property@1.1.4: 999 + dependencies: 1000 + es-define-property: 1.0.0 1001 + es-errors: 1.3.0 1002 + gopd: 1.0.1 1003 + 1004 + depd@2.0.0: {} 1005 + 1006 + destroy@1.2.0: {} 1007 + 1008 + detect-libc@2.0.3: {} 1009 + 1010 + dotenv@16.4.5: {} 1011 + 1012 + ee-first@1.1.1: {} 1013 + 1014 + encodeurl@1.0.2: {} 1015 + 1016 + encodeurl@2.0.0: {} 1017 + 1018 + end-of-stream@1.4.4: 1019 + dependencies: 1020 + once: 1.4.0 1021 + 1022 + es-define-property@1.0.0: 1023 + dependencies: 1024 + get-intrinsic: 1.2.4 1025 + 1026 + es-errors@1.3.0: {} 1027 + 1028 + esbuild@0.23.1: 1029 + optionalDependencies: 1030 + '@esbuild/aix-ppc64': 0.23.1 1031 + '@esbuild/android-arm': 0.23.1 1032 + '@esbuild/android-arm64': 0.23.1 1033 + '@esbuild/android-x64': 0.23.1 1034 + '@esbuild/darwin-arm64': 0.23.1 1035 + '@esbuild/darwin-x64': 0.23.1 1036 + '@esbuild/freebsd-arm64': 0.23.1 1037 + '@esbuild/freebsd-x64': 0.23.1 1038 + '@esbuild/linux-arm': 0.23.1 1039 + '@esbuild/linux-arm64': 0.23.1 1040 + '@esbuild/linux-ia32': 0.23.1 1041 + '@esbuild/linux-loong64': 0.23.1 1042 + '@esbuild/linux-mips64el': 0.23.1 1043 + '@esbuild/linux-ppc64': 0.23.1 1044 + '@esbuild/linux-riscv64': 0.23.1 1045 + '@esbuild/linux-s390x': 0.23.1 1046 + '@esbuild/linux-x64': 0.23.1 1047 + '@esbuild/netbsd-x64': 0.23.1 1048 + '@esbuild/openbsd-arm64': 0.23.1 1049 + '@esbuild/openbsd-x64': 0.23.1 1050 + '@esbuild/sunos-x64': 0.23.1 1051 + '@esbuild/win32-arm64': 0.23.1 1052 + '@esbuild/win32-ia32': 0.23.1 1053 + '@esbuild/win32-x64': 0.23.1 1054 + 1055 + escape-html@1.0.3: {} 1056 + 1057 + etag@1.8.1: {} 1058 + 1059 + event-target-shim@5.0.1: {} 1060 + 1061 + events@3.3.0: {} 1062 + 1063 + expand-template@2.0.3: {} 1064 + 1065 + express@4.21.0: 1066 + dependencies: 1067 + accepts: 1.3.8 1068 + array-flatten: 1.1.1 1069 + body-parser: 1.20.3 1070 + content-disposition: 0.5.4 1071 + content-type: 1.0.5 1072 + cookie: 0.6.0 1073 + cookie-signature: 1.0.6 1074 + debug: 2.6.9 1075 + depd: 2.0.0 1076 + encodeurl: 2.0.0 1077 + escape-html: 1.0.3 1078 + etag: 1.8.1 1079 + finalhandler: 1.3.1 1080 + fresh: 0.5.2 1081 + http-errors: 2.0.0 1082 + merge-descriptors: 1.0.3 1083 + methods: 1.1.2 1084 + on-finished: 2.4.1 1085 + parseurl: 1.3.3 1086 + path-to-regexp: 0.1.10 1087 + proxy-addr: 2.0.7 1088 + qs: 6.13.0 1089 + range-parser: 1.2.1 1090 + safe-buffer: 5.2.1 1091 + send: 0.19.0 1092 + serve-static: 1.16.2 1093 + setprototypeof: 1.2.0 1094 + statuses: 2.0.1 1095 + type-is: 1.6.18 1096 + utils-merge: 1.0.1 1097 + vary: 1.1.2 1098 + transitivePeerDependencies: 1099 + - supports-color 1100 + 1101 + fast-copy@3.0.2: {} 1102 + 1103 + fast-redact@3.5.0: {} 1104 + 1105 + fast-safe-stringify@2.1.1: {} 1106 + 1107 + file-uri-to-path@1.0.0: {} 1108 + 1109 + finalhandler@1.3.1: 1110 + dependencies: 1111 + debug: 2.6.9 1112 + encodeurl: 2.0.0 1113 + escape-html: 1.0.3 1114 + on-finished: 2.4.1 1115 + parseurl: 1.3.3 1116 + statuses: 2.0.1 1117 + unpipe: 1.0.0 1118 + transitivePeerDependencies: 1119 + - supports-color 1120 + 1121 + forwarded@0.2.0: {} 1122 + 1123 + fresh@0.5.2: {} 1124 + 1125 + fs-constants@1.0.0: {} 1126 + 1127 + fsevents@2.3.3: 1128 + optional: true 1129 + 1130 + function-bind@1.1.2: {} 1131 + 1132 + get-intrinsic@1.2.4: 1133 + dependencies: 1134 + es-errors: 1.3.0 1135 + function-bind: 1.1.2 1136 + has-proto: 1.0.3 1137 + has-symbols: 1.0.3 1138 + hasown: 2.0.2 1139 + 1140 + get-tsconfig@4.8.1: 1141 + dependencies: 1142 + resolve-pkg-maps: 1.0.0 1143 + 1144 + github-from-package@0.0.0: {} 1145 + 1146 + gopd@1.0.1: 1147 + dependencies: 1148 + get-intrinsic: 1.2.4 1149 + 1150 + has-property-descriptors@1.0.2: 1151 + dependencies: 1152 + es-define-property: 1.0.0 1153 + 1154 + has-proto@1.0.3: {} 1155 + 1156 + has-symbols@1.0.3: {} 1157 + 1158 + hasown@2.0.2: 1159 + dependencies: 1160 + function-bind: 1.1.2 1161 + 1162 + help-me@5.0.0: {} 1163 + 1164 + http-errors@2.0.0: 1165 + dependencies: 1166 + depd: 2.0.0 1167 + inherits: 2.0.4 1168 + setprototypeof: 1.2.0 1169 + statuses: 2.0.1 1170 + toidentifier: 1.0.1 1171 + 1172 + iconv-lite@0.4.24: 1173 + dependencies: 1174 + safer-buffer: 2.1.2 1175 + 1176 + ieee754@1.2.1: {} 1177 + 1178 + inherits@2.0.4: {} 1179 + 1180 + ini@1.3.8: {} 1181 + 1182 + ipaddr.js@1.9.1: {} 1183 + 1184 + joycon@3.1.1: {} 1185 + 1186 + media-typer@0.3.0: {} 1187 + 1188 + merge-descriptors@1.0.3: {} 1189 + 1190 + methods@1.1.2: {} 1191 + 1192 + mime-db@1.52.0: {} 1193 + 1194 + mime-types@2.1.35: 1195 + dependencies: 1196 + mime-db: 1.52.0 1197 + 1198 + mime@1.6.0: {} 1199 + 1200 + mimic-response@3.1.0: {} 1201 + 1202 + minimist@1.2.8: {} 1203 + 1204 + mkdirp-classic@0.5.3: {} 1205 + 1206 + ms@2.0.0: {} 1207 + 1208 + ms@2.1.3: {} 1209 + 1210 + napi-build-utils@1.0.2: {} 1211 + 1212 + negotiator@0.6.3: {} 1213 + 1214 + node-abi@3.67.0: 1215 + dependencies: 1216 + semver: 7.6.3 1217 + 1218 + object-inspect@1.13.2: {} 1219 + 1220 + on-exit-leak-free@2.1.2: {} 1221 + 1222 + on-finished@2.4.1: 1223 + dependencies: 1224 + ee-first: 1.1.1 1225 + 1226 + once@1.4.0: 1227 + dependencies: 1228 + wrappy: 1.0.2 1229 + 1230 + parseurl@1.3.3: {} 1231 + 1232 + path-to-regexp@0.1.10: {} 1233 + 1234 + pino-abstract-transport@1.2.0: 1235 + dependencies: 1236 + readable-stream: 4.5.2 1237 + split2: 4.2.0 1238 + 1239 + pino-pretty@11.2.2: 1240 + dependencies: 1241 + colorette: 2.0.20 1242 + dateformat: 4.6.3 1243 + fast-copy: 3.0.2 1244 + fast-safe-stringify: 2.1.1 1245 + help-me: 5.0.0 1246 + joycon: 3.1.1 1247 + minimist: 1.2.8 1248 + on-exit-leak-free: 2.1.2 1249 + pino-abstract-transport: 1.2.0 1250 + pump: 3.0.2 1251 + readable-stream: 4.5.2 1252 + secure-json-parse: 2.7.0 1253 + sonic-boom: 4.1.0 1254 + strip-json-comments: 3.1.1 1255 + 1256 + pino-std-serializers@7.0.0: {} 1257 + 1258 + pino@9.4.0: 1259 + dependencies: 1260 + atomic-sleep: 1.0.0 1261 + fast-redact: 3.5.0 1262 + on-exit-leak-free: 2.1.2 1263 + pino-abstract-transport: 1.2.0 1264 + pino-std-serializers: 7.0.0 1265 + process-warning: 4.0.0 1266 + quick-format-unescaped: 4.0.4 1267 + real-require: 0.2.0 1268 + safe-stable-stringify: 2.5.0 1269 + sonic-boom: 4.1.0 1270 + thread-stream: 3.1.0 1271 + 1272 + prebuild-install@7.1.2: 1273 + dependencies: 1274 + detect-libc: 2.0.3 1275 + expand-template: 2.0.3 1276 + github-from-package: 0.0.0 1277 + minimist: 1.2.8 1278 + mkdirp-classic: 0.5.3 1279 + napi-build-utils: 1.0.2 1280 + node-abi: 3.67.0 1281 + pump: 3.0.2 1282 + rc: 1.2.8 1283 + simple-get: 4.0.1 1284 + tar-fs: 2.1.1 1285 + tunnel-agent: 0.6.0 1286 + 1287 + process-warning@4.0.0: {} 1288 + 1289 + process@0.11.10: {} 1290 + 1291 + prom-client@15.1.3: 1292 + dependencies: 1293 + '@opentelemetry/api': 1.9.0 1294 + tdigest: 0.1.2 1295 + 1296 + proxy-addr@2.0.7: 1297 + dependencies: 1298 + forwarded: 0.2.0 1299 + ipaddr.js: 1.9.1 1300 + 1301 + pump@3.0.2: 1302 + dependencies: 1303 + end-of-stream: 1.4.4 1304 + once: 1.4.0 1305 + 1306 + qs@6.13.0: 1307 + dependencies: 1308 + side-channel: 1.0.6 1309 + 1310 + quick-format-unescaped@4.0.4: {} 1311 + 1312 + range-parser@1.2.1: {} 1313 + 1314 + rate-limiter-flexible@5.0.3: {} 1315 + 1316 + raw-body@2.5.2: 1317 + dependencies: 1318 + bytes: 3.1.2 1319 + http-errors: 2.0.0 1320 + iconv-lite: 0.4.24 1321 + unpipe: 1.0.0 1322 + 1323 + rc@1.2.8: 1324 + dependencies: 1325 + deep-extend: 0.6.0 1326 + ini: 1.3.8 1327 + minimist: 1.2.8 1328 + strip-json-comments: 2.0.1 1329 + 1330 + readable-stream@3.6.2: 1331 + dependencies: 1332 + inherits: 2.0.4 1333 + string_decoder: 1.3.0 1334 + util-deprecate: 1.0.2 1335 + 1336 + readable-stream@4.5.2: 1337 + dependencies: 1338 + abort-controller: 3.0.0 1339 + buffer: 6.0.3 1340 + events: 3.3.0 1341 + process: 0.11.10 1342 + string_decoder: 1.3.0 1343 + 1344 + real-require@0.2.0: {} 1345 + 1346 + resolve-pkg-maps@1.0.0: {} 1347 + 1348 + safe-buffer@5.2.1: {} 1349 + 1350 + safe-stable-stringify@2.5.0: {} 1351 + 1352 + safer-buffer@2.1.2: {} 1353 + 1354 + secure-json-parse@2.7.0: {} 1355 + 1356 + semver@7.6.3: {} 1357 + 1358 + send@0.19.0: 1359 + dependencies: 1360 + debug: 2.6.9 1361 + depd: 2.0.0 1362 + destroy: 1.2.0 1363 + encodeurl: 1.0.2 1364 + escape-html: 1.0.3 1365 + etag: 1.8.1 1366 + fresh: 0.5.2 1367 + http-errors: 2.0.0 1368 + mime: 1.6.0 1369 + ms: 2.1.3 1370 + on-finished: 2.4.1 1371 + range-parser: 1.2.1 1372 + statuses: 2.0.1 1373 + transitivePeerDependencies: 1374 + - supports-color 1375 + 1376 + serve-static@1.16.2: 1377 + dependencies: 1378 + encodeurl: 2.0.0 1379 + escape-html: 1.0.3 1380 + parseurl: 1.3.3 1381 + send: 0.19.0 1382 + transitivePeerDependencies: 1383 + - supports-color 1384 + 1385 + set-function-length@1.2.2: 1386 + dependencies: 1387 + define-data-property: 1.1.4 1388 + es-errors: 1.3.0 1389 + function-bind: 1.1.2 1390 + get-intrinsic: 1.2.4 1391 + gopd: 1.0.1 1392 + has-property-descriptors: 1.0.2 1393 + 1394 + setprototypeof@1.2.0: {} 1395 + 1396 + side-channel@1.0.6: 1397 + dependencies: 1398 + call-bind: 1.0.7 1399 + es-errors: 1.3.0 1400 + get-intrinsic: 1.2.4 1401 + object-inspect: 1.13.2 1402 + 1403 + simple-concat@1.0.1: {} 1404 + 1405 + simple-get@4.0.1: 1406 + dependencies: 1407 + decompress-response: 6.0.0 1408 + once: 1.4.0 1409 + simple-concat: 1.0.1 1410 + 1411 + sonic-boom@4.1.0: 1412 + dependencies: 1413 + atomic-sleep: 1.0.0 1414 + 1415 + split2@4.2.0: {} 1416 + 1417 + statuses@2.0.1: {} 1418 + 1419 + string_decoder@1.3.0: 1420 + dependencies: 1421 + safe-buffer: 5.2.1 1422 + 1423 + strip-json-comments@2.0.1: {} 1424 + 1425 + strip-json-comments@3.1.1: {} 1426 + 1427 + tar-fs@2.1.1: 1428 + dependencies: 1429 + chownr: 1.1.4 1430 + mkdirp-classic: 0.5.3 1431 + pump: 3.0.2 1432 + tar-stream: 2.2.0 1433 + 1434 + tar-stream@2.2.0: 1435 + dependencies: 1436 + bl: 4.1.0 1437 + end-of-stream: 1.4.4 1438 + fs-constants: 1.0.0 1439 + inherits: 2.0.4 1440 + readable-stream: 3.6.2 1441 + 1442 + tdigest@0.1.2: 1443 + dependencies: 1444 + bintrees: 1.0.2 1445 + 1446 + thread-stream@3.1.0: 1447 + dependencies: 1448 + real-require: 0.2.0 1449 + 1450 + toidentifier@1.0.1: {} 1451 + 1452 + tsx@4.19.1: 1453 + dependencies: 1454 + esbuild: 0.23.1 1455 + get-tsconfig: 4.8.1 1456 + optionalDependencies: 1457 + fsevents: 2.3.3 1458 + 1459 + tunnel-agent@0.6.0: 1460 + dependencies: 1461 + safe-buffer: 5.2.1 1462 + 1463 + type-is@1.6.18: 1464 + dependencies: 1465 + media-typer: 0.3.0 1466 + mime-types: 2.1.35 1467 + 1468 + typescript@5.6.2: {} 1469 + 1470 + undici-types@6.19.8: {} 1471 + 1472 + unpipe@1.0.0: {} 1473 + 1474 + util-deprecate@1.0.2: {} 1475 + 1476 + utils-merge@1.0.1: {} 1477 + 1478 + vary@1.1.2: {} 1479 + 1480 + wrappy@1.0.2: {} 1481 + 1482 + ws@8.18.0: {}
+209
src/db.ts
··· 1 + // src/db.ts 2 + 3 + import Database from 'better-sqlite3'; 4 + import path from 'path'; 5 + import logger from './logger'; 6 + 7 + // Define the path for the SQLite database 8 + const dbPath = path.resolve(__dirname, '../data/posts.db'); 9 + const db = new Database(dbPath); 10 + 11 + // Initialize the database schema with normalized languages and cursor management 12 + db.exec(` 13 + PRAGMA foreign_keys = ON; 14 + 15 + CREATE TABLE IF NOT EXISTS posts ( 16 + id TEXT PRIMARY KEY, 17 + created_at TEXT, 18 + did TEXT, 19 + time_us INTEGER, 20 + type TEXT, 21 + collection TEXT, 22 + rkey TEXT, 23 + cursor INTEGER, 24 + is_deleted BOOLEAN DEFAULT FALSE, 25 + embed TEXT, -- Serialized embed data 26 + reply TEXT -- Serialized reply data 27 + ); 28 + 29 + CREATE TABLE IF NOT EXISTS languages ( 30 + post_id TEXT, 31 + language TEXT, 32 + FOREIGN KEY(post_id) REFERENCES posts(id) ON DELETE CASCADE 33 + ); 34 + 35 + CREATE INDEX IF NOT EXISTS idx_languages_language ON languages(language); 36 + 37 + CREATE TABLE IF NOT EXISTS cursor ( 38 + id INTEGER PRIMARY KEY CHECK (id = 1), 39 + last_cursor INTEGER 40 + ); 41 + 42 + INSERT OR IGNORE INTO cursor (id, last_cursor) VALUES (1, 0); 43 + `); 44 + 45 + // Prepared statements for performance 46 + const insertPost = db.prepare(` 47 + INSERT OR IGNORE INTO posts (id, created_at, did, time_us, type, collection, rkey, cursor, embed, reply) 48 + VALUES (@id, @created_at, @did, @time_us, @type, @collection, @rkey, @cursor, @embed, @reply) 49 + `); 50 + 51 + const updatePost = db.prepare(` 52 + UPDATE posts 53 + SET created_at = @created_at, 54 + did = @did, 55 + time_us = @time_us, 56 + type = @type, 57 + collection = @collection, 58 + rkey = @rkey, 59 + cursor = @cursor, 60 + is_deleted = @is_deleted, 61 + embed = @embed, 62 + reply = @reply 63 + WHERE id = @id 64 + `); 65 + 66 + const softDeletePostStmt = db.prepare(` 67 + UPDATE posts 68 + SET is_deleted = TRUE, cursor = @cursor 69 + WHERE id = @id 70 + `); 71 + 72 + const insertLanguage = db.prepare(` 73 + INSERT INTO languages (post_id, language) 74 + VALUES (@post_id, @language) 75 + `); 76 + 77 + const getLastCursorStmt = db.prepare(`SELECT last_cursor FROM cursor WHERE id = 1`); 78 + const updateCursorStmt = db.prepare(`UPDATE cursor SET last_cursor = @last_cursor WHERE id = 1`); 79 + 80 + // Function to get the last cursor 81 + export function getLastCursor(): number { 82 + const row = getLastCursorStmt.get(); 83 + return row ? row.last_cursor : 0; 84 + } 85 + 86 + // Function to update the last cursor 87 + export function updateLastCursor(newCursor: number): void { 88 + updateCursorStmt.run({ last_cursor: newCursor }); 89 + logger.debug(`Updated last cursor to ${newCursor}`); 90 + } 91 + 92 + // Function to insert or update a post and its languages 93 + export function savePost(post: { 94 + id: string; 95 + created_at: string; 96 + langs: string[]; 97 + did: string; 98 + time_us: number; 99 + type: string; 100 + collection: string; 101 + rkey: string; 102 + cursor: number; 103 + embed?: any; // Optional field 104 + reply?: any; // Optional field 105 + }) { 106 + const insertOrUpdate = db.transaction((postData: typeof post) => { 107 + insertPost.run({ 108 + id: postData.id, 109 + created_at: postData.created_at, 110 + did: postData.did, 111 + time_us: postData.time_us, 112 + type: postData.type, 113 + collection: postData.collection, 114 + rkey: postData.rkey, 115 + cursor: postData.cursor, 116 + embed: postData.embed ? JSON.stringify(postData.embed) : null, 117 + reply: postData.reply ? JSON.stringify(postData.reply) : null, 118 + }); 119 + 120 + postData.langs.forEach((lang) => { 121 + if (typeof lang === 'string') { 122 + insertLanguage.run({ 123 + post_id: postData.id, 124 + language: lang, 125 + }); 126 + } else { 127 + logger.warn(`Invalid language type: ${typeof lang} for lang: ${lang}`, { lang }); 128 + } 129 + }); 130 + 131 + logger.debug(`Saved/Updated post ${postData.id}`); 132 + }); 133 + 134 + try { 135 + // Validate data types before running the transaction 136 + if (typeof post.id !== 'string' || post.id.trim() === '') { 137 + throw new Error('Invalid or missing "id"'); 138 + } 139 + if (typeof post.created_at !== 'string') { 140 + throw new Error('Invalid "created_at"'); 141 + } 142 + if (!Array.isArray(post.langs)) { 143 + throw new Error('Invalid "langs"'); 144 + } 145 + if (typeof post.did !== 'string') { 146 + throw new Error('Invalid "did"'); 147 + } 148 + if (typeof post.time_us !== 'number') { 149 + throw new Error('Invalid "time_us"'); 150 + } 151 + if (typeof post.type !== 'string') { 152 + throw new Error('Invalid "type"'); 153 + } 154 + if (typeof post.collection !== 'string') { 155 + throw new Error('Invalid "collection"'); 156 + } 157 + if (typeof post.rkey !== 'string') { 158 + throw new Error('Invalid "rkey"'); 159 + } 160 + if (typeof post.cursor !== 'number') { 161 + throw new Error('Invalid "cursor"'); 162 + } 163 + 164 + insertOrUpdate(post); 165 + } catch (error) { 166 + logger.error(`Database insertion/update error: ${(error as Error).message}`, { post }); 167 + // Optionally, handle specific error types or implement retries 168 + } 169 + } 170 + 171 + // Function to perform soft delete 172 + export function softDeletePost(postId: string, cursor: number) { 173 + try { 174 + const info = softDeletePostStmt.run({ 175 + id: postId, 176 + cursor: cursor, 177 + }); 178 + if (info.changes > 0) { 179 + logger.debug(`Soft deleted post ${postId}`); 180 + } else { 181 + logger.warn(`Attempted to soft delete non-existent post ${postId}`); 182 + } 183 + } catch (error) { 184 + logger.error(`Error soft deleting post: ${(error as Error).message}`, { postId }); 185 + } 186 + } 187 + 188 + // Function to purge old posts (older than X days) 189 + export function purgeOldPosts(days: number) { 190 + const cutoffDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); 191 + const stmt = db.prepare(`DELETE FROM posts WHERE created_at < ? AND is_deleted = TRUE`); 192 + const info = stmt.run(cutoffDate); 193 + logger.info(`Purged ${info.changes} soft-deleted posts older than ${days} days.`); 194 + } 195 + 196 + // Schedule periodic purging (e.g., every hour) 197 + const PURGE_INTERVAL_MS = 60 * 60 * 1000; // 1 hour 198 + const PURGE_DAYS_ENV = parseInt(process.env.PURGE_DAYS || '7', 10); 199 + 200 + const purgeInterval = setInterval(() => { 201 + purgeOldPosts(PURGE_DAYS_ENV); 202 + }, PURGE_INTERVAL_MS); 203 + 204 + // Function to gracefully close the database 205 + export function closeDatabase() { 206 + clearInterval(purgeInterval); 207 + db.close(); 208 + logger.info('Database connection closed.'); 209 + }
+375
src/index.ts
··· 1 + // src/index.ts 2 + 3 + import WebSocket from 'ws'; 4 + import { savePost, softDeletePost, closeDatabase, getLastCursor, updateLastCursor } from './db'; 5 + import { updateMetrics, incrementPosts, incrementErrors, incrementUnexpectedEvent, register } from './metrics'; 6 + import express from 'express'; 7 + import dotenv from 'dotenv'; 8 + import process from 'process'; 9 + import logger from './logger'; 10 + import { RateLimiterMemory } from 'rate-limiter-flexible'; 11 + 12 + dotenv.config(); 13 + 14 + // Configuration 15 + const FIREHOSE_URL = process.env.FIREHOSE_URL || 'ws://localhost:8080'; // Pointing to mock server for testing 16 + const PORT = parseInt(process.env.PORT || '3000', 10); 17 + const WANTED_COLLECTIONS = process.env.WANTED_COLLECTIONS 18 + ? process.env.WANTED_COLLECTIONS.split(',') 19 + : ['app.bsky.feed.post']; 20 + const PURGE_DAYS = parseInt(process.env.PURGE_DAYS || '7', 10); 21 + const RECONNECT_DELAY_MS = 1000; // Initial reconnect delay in ms 22 + const CURSOR_UPDATE_INTERVAL_MS = 10 * 1000; // 10 seconds 23 + 24 + // Validate Environment Variables 25 + if (!FIREHOSE_URL) { 26 + logger.error('FIREHOSE_URL is not defined in the environment variables.'); 27 + process.exit(1); 28 + } 29 + 30 + // Initialize Rate Limiter for Unexpected Events 31 + const unexpectedEventRateLimiter = new RateLimiterMemory({ 32 + points: 100, // 100 points 33 + duration: 60, // Per 60 seconds 34 + }); 35 + 36 + // Function to log unexpected events with rate limiting 37 + function logUnexpectedEvent(eventType: string, collection: string, eventId: string, did: string, rawEvent?: any) { 38 + unexpectedEventRateLimiter.consume('unexpectedEvent') 39 + .then(() => { 40 + logger.warn({ 41 + message: 'Received unexpected event structure', 42 + eventType, 43 + collection, 44 + eventId, 45 + did, 46 + rawEvent, 47 + }, 'Received unexpected event structure'); 48 + }) 49 + .catch(() => { 50 + // Rate limit exceeded: do not log 51 + }); 52 + } 53 + 54 + // Create WebSocket connection 55 + function constructFirehoseURL(cursor: number = 0): string { 56 + const url = new URL(FIREHOSE_URL); 57 + WANTED_COLLECTIONS.forEach((collection) => url.searchParams.append('wantedCollections', collection)); 58 + if (cursor > 0) { 59 + url.searchParams.append('cursor', cursor.toString()); 60 + } 61 + return url.toString(); 62 + } 63 + 64 + let ws: WebSocket | null = null; 65 + let reconnectAttempts = 0; 66 + let latestCursor = getLastCursor(); 67 + let cursorUpdateInterval: NodeJS.Timeout | null = null; 68 + 69 + // Function to initialize cursor update interval 70 + function initializeCursorUpdate() { 71 + cursorUpdateInterval = setInterval(() => { 72 + if (latestCursor > 0) { 73 + updateLastCursor(latestCursor); 74 + logger.debug(`Cursor updated to ${latestCursor} at ${new Date().toISOString()}`); 75 + } 76 + }, CURSOR_UPDATE_INTERVAL_MS); 77 + } 78 + 79 + // Function to handle "com" events 80 + function handleComEvent(event: any) { 81 + const commit = event.commit; 82 + if (!commit) { 83 + logger.warn('Commit field is missing in "com" event', { event }); 84 + incrementUnexpectedEvent('com', 'unknown'); 85 + return; 86 + } 87 + 88 + const { type: opType, collection, rkey, record } = commit; 89 + 90 + if (collection !== 'app.bsky.feed.post') { 91 + // Handle other collections if needed 92 + logger.warn(`Unhandled collection: ${collection}`, { collection }); 93 + incrementUnexpectedEvent('com', collection); 94 + return; 95 + } 96 + 97 + if (!rkey) { 98 + logger.warn('RKey is missing in "com" event commit', { commit }); 99 + incrementUnexpectedEvent('com', collection); 100 + return; 101 + } 102 + 103 + switch (opType) { 104 + case 'c': // Create 105 + if (!record) { 106 + logger.warn('Record is missing in "create" commit', { commit }); 107 + incrementUnexpectedEvent('com', collection); 108 + return; 109 + } 110 + try { 111 + // Parse the record 112 + let postRecord; 113 + if (typeof record === 'string') { 114 + postRecord = JSON.parse(record); 115 + } else if (typeof record === 'object') { 116 + postRecord = record; 117 + } else { 118 + throw new Error('Record is neither a string nor an object'); 119 + } 120 + 121 + // Validate postRecord fields 122 + const postType = postRecord['$type'] || postRecord.type; 123 + if (typeof postType !== 'string') { 124 + throw new Error('Invalid or missing "$type" in record'); 125 + } 126 + if (typeof postRecord.createdAt !== 'string') { 127 + throw new Error('Invalid or missing "createdAt" in record'); 128 + } 129 + if (!Array.isArray(postRecord.langs)) { 130 + throw new Error('"langs" must be an array in record'); 131 + } 132 + 133 + const post = { 134 + id: `${event.did}:${rkey}`, 135 + created_at: postRecord.createdAt || new Date().toISOString(), 136 + langs: postRecord.langs.filter((lang: any) => typeof lang === 'string'), 137 + did: event.did || 'unknown', 138 + time_us: typeof event.time_us === 'number' ? event.time_us : Date.now() * 1000, 139 + type: postType, // Assign the correct type 140 + collection: collection || 'unknown', 141 + rkey: rkey, 142 + cursor: event.time_us, 143 + embed: postRecord.embed || null, // Handle optional embed 144 + reply: postRecord.reply || null, // Handle optional reply 145 + }; 146 + savePost(post); 147 + updateMetrics(post.langs); 148 + incrementPosts(); 149 + if (event.time_us > latestCursor) { 150 + latestCursor = event.time_us; 151 + } 152 + } catch (error) { 153 + logger.error(`Error parsing record in "create" commit: ${(error as Error).message}`, { commit, record }); 154 + // Log the entire record for debugging 155 + logger.error(`Malformed record data: ${JSON.stringify(record)}`); 156 + incrementErrors(); 157 + } 158 + break; 159 + 160 + case 'u': // Update 161 + if (!record) { 162 + logger.warn('Record is missing in "update" commit', { commit }); 163 + incrementUnexpectedEvent('com', collection); 164 + return; 165 + } 166 + try { 167 + // Parse the record 168 + let postRecord; 169 + if (typeof record === 'string') { 170 + postRecord = JSON.parse(record); 171 + } else if (typeof record === 'object') { 172 + postRecord = record; 173 + } else { 174 + throw new Error('Record is neither a string nor an object'); 175 + } 176 + 177 + // Validate postRecord fields 178 + const postType = postRecord['$type'] || postRecord.type; 179 + if (typeof postType !== 'string') { 180 + throw new Error('Invalid or missing "$type" in record'); 181 + } 182 + if (typeof postRecord.createdAt !== 'string') { 183 + throw new Error('Invalid or missing "createdAt" in record'); 184 + } 185 + if (!Array.isArray(postRecord.langs)) { 186 + throw new Error('"langs" must be an array in record'); 187 + } 188 + 189 + const post = { 190 + id: `${event.did}:${rkey}`, 191 + created_at: postRecord.createdAt || new Date().toISOString(), 192 + langs: postRecord.langs.filter((lang: any) => typeof lang === 'string'), 193 + did: event.did || 'unknown', 194 + time_us: typeof event.time_us === 'number' ? event.time_us : Date.now() * 1000, 195 + type: postType, 196 + collection: collection || 'unknown', 197 + rkey: rkey, 198 + cursor: event.time_us, 199 + embed: postRecord.embed || null, 200 + reply: postRecord.reply || null, 201 + }; 202 + savePost(post); // Assuming savePost can handle both insert and update 203 + updateMetrics(post.langs); 204 + incrementPosts(); 205 + if (event.time_us > latestCursor) { 206 + latestCursor = event.time_us; 207 + } 208 + } catch (error) { 209 + logger.error(`Error parsing record in "update" commit: ${(error as Error).message}`, { commit, record }); 210 + // Log the entire record for debugging 211 + logger.error(`Malformed record data: ${JSON.stringify(record)}`); 212 + incrementErrors(); 213 + } 214 + break; 215 + 216 + case 'd': // Delete 217 + try { 218 + const postId = `${event.did}:${rkey}`; 219 + softDeletePost(postId, event.time_us); 220 + // Optionally, update metrics or handle language counts 221 + if (event.time_us > latestCursor) { 222 + latestCursor = event.time_us; 223 + } 224 + } catch (error) { 225 + logger.error(`Error deleting post: ${(error as Error).message}`, { rkey }); 226 + incrementErrors(); 227 + } 228 + break; 229 + 230 + default: 231 + logger.warn(`Unhandled commit type: ${opType}`, { opType }); 232 + incrementUnexpectedEvent('com', collection); 233 + break; 234 + } 235 + } 236 + 237 + // Function to handle `"acc"` events (optional) 238 + function handleAccEvent(event: any) { 239 + const account = event.account; 240 + if (!account) { 241 + logger.warn('Account field is missing in "acc" event', { event }); 242 + incrementUnexpectedEvent('acc', 'unknown'); 243 + return; 244 + } 245 + 246 + // Implement account-related logic if needed 247 + // For example, update account status in the database 248 + logger.info('Received "acc" event', { account }); 249 + } 250 + 251 + // Function to handle `"id"` events (optional) 252 + function handleIdEvent(event: any) { 253 + const identity = event.identity; 254 + if (!identity) { 255 + logger.warn('Identity field is missing in "id" event', { event }); 256 + incrementUnexpectedEvent('id', 'unknown'); 257 + return; 258 + } 259 + 260 + // Implement identity-related logic if needed 261 + // For example, update user identity information in the database 262 + logger.info('Received "id" event', { identity }); 263 + } 264 + 265 + // Function to process incoming events 266 + function processEvent(event: any) { 267 + const eventType = event.type || 'unknown'; 268 + switch (eventType) { 269 + case 'com': 270 + handleComEvent(event); 271 + break; 272 + case 'acc': 273 + handleAccEvent(event); 274 + break; 275 + case 'id': 276 + handleIdEvent(event); 277 + break; 278 + default: 279 + logger.warn(`Unhandled event type: ${eventType}`, { eventType }); 280 + incrementUnexpectedEvent(eventType, 'unknown'); 281 + break; 282 + } 283 + } 284 + 285 + // Function to connect to Jetstream Firehose 286 + function connect() { 287 + const url = constructFirehoseURL(latestCursor); 288 + logger.info(`Connecting to Jetstream at ${url}...`); 289 + ws = new WebSocket(url); 290 + 291 + ws.on('open', () => { 292 + logger.info('Connected to Jetstream firehose.'); 293 + reconnectAttempts = 0; 294 + // Initialize cursor update interval upon successful connection 295 + if (!cursorUpdateInterval) { 296 + initializeCursorUpdate(); 297 + } 298 + }); 299 + 300 + ws.on('message', (data: WebSocket.Data) => { 301 + try { 302 + const event = JSON.parse(data.toString()); 303 + processEvent(event); 304 + } catch (error) { 305 + logger.error(`Error processing message: ${(error as Error).message}`); 306 + incrementErrors(); 307 + } 308 + }); 309 + 310 + ws.on('close', (code, reason) => { 311 + logger.warn(`WebSocket closed: Code=${code}, Reason=${reason.toString()}`); 312 + attemptReconnect(); 313 + }); 314 + 315 + ws.on('error', (error) => { 316 + logger.error(`WebSocket error: ${(error as Error).message}`); 317 + incrementErrors(); 318 + ws?.close(); 319 + }); 320 + } 321 + 322 + // Reconnection logic with exponential backoff 323 + async function attemptReconnect() { 324 + reconnectAttempts += 1; 325 + const delay = Math.min(RECONNECT_DELAY_MS * 2 ** reconnectAttempts, 30000); // Up to 30 seconds 326 + logger.info(`Reconnecting in ${delay / 1000} seconds...`); 327 + await new Promise((resolve) => setTimeout(resolve, delay)); 328 + connect(); 329 + } 330 + 331 + // Start initial connection 332 + connect(); 333 + 334 + // Set up Express server for Prometheus metrics 335 + const app = express(); 336 + 337 + app.get('/metrics', async (req, res) => { 338 + try { 339 + res.set('Content-Type', register.contentType); 340 + const metrics = await register.metrics(); 341 + res.send(metrics); 342 + } catch (ex) { 343 + logger.error(`Error serving metrics: ${(ex as Error).message}`); 344 + res.status(500).end(ex.toString()); 345 + } 346 + }); 347 + 348 + const server = app.listen(PORT, () => { 349 + logger.info(`Metrics server listening on port ${PORT}`); 350 + }); 351 + 352 + // Graceful Shutdown 353 + function shutdown() { 354 + logger.info('Shutting down gracefully...'); 355 + if (ws) { 356 + ws.close(); 357 + } 358 + if (cursorUpdateInterval) { 359 + clearInterval(cursorUpdateInterval); 360 + } 361 + server.close(() => { 362 + logger.info('HTTP server closed.'); 363 + closeDatabase(); 364 + process.exit(0); 365 + }); 366 + 367 + // Force shutdown after 10 seconds 368 + setTimeout(() => { 369 + logger.error('Forcing shutdown.'); 370 + process.exit(1); 371 + }, 10000); 372 + } 373 + 374 + process.on('SIGINT', shutdown); 375 + process.on('SIGTERM', shutdown);
+19
src/logger.ts
··· 1 + // src/logger.ts 2 + 3 + import pino from 'pino'; 4 + 5 + // Configure Pino logger 6 + const logger = pino({ 7 + level: process.env.LOG_LEVEL || 'info', 8 + transport: process.env.NODE_ENV !== 'production' ? { 9 + target: 'pino-pretty', 10 + options: { 11 + colorize: true, 12 + translateTime: 'SYS:standard', 13 + ignore: 'pid,hostname', 14 + }, 15 + } : undefined, 16 + timestamp: pino.stdTimeFunctions.isoTime, 17 + }); 18 + 19 + export default logger;
+86
src/metrics.ts
··· 1 + // src/metrics.ts 2 + 3 + import { Registry, Gauge, Counter } from 'prom-client'; 4 + import logger from './logger'; 5 + 6 + const register = new Registry(); 7 + 8 + // Define Metrics 9 + export const languageGauge = new Gauge({ 10 + name: 'bluesky_post_languages', 11 + help: 'Number of posts per language', 12 + labelNames: ['language'], 13 + registers: [register], 14 + }); 15 + 16 + export const totalPosts = new Counter({ 17 + name: 'bluesky_total_posts', 18 + help: 'Total number of posts processed', 19 + registers: [register], 20 + }); 21 + 22 + export const postsPerSecond = new Gauge({ 23 + name: 'bluesky_posts_per_second', 24 + help: 'Number of posts processed per second', 25 + registers: [register], 26 + }); 27 + 28 + export const errorCounter = new Counter({ 29 + name: 'bluesky_error_count', 30 + help: 'Total number of errors encountered', 31 + registers: [register], 32 + }); 33 + 34 + // New Metric for Unexpected Event Types 35 + export const unexpectedEventCounter = new Counter({ 36 + name: 'bluesky_unexpected_event_count', 37 + help: 'Total number of unexpected events received', 38 + labelNames: ['event_type', 'collection'], 39 + registers: [register], 40 + }); 41 + 42 + // In-memory storage for language counts 43 + const languageCounts: Record<string, number> = {}; 44 + 45 + // Function to update metrics 46 + export function updateMetrics(langs: string[]) { 47 + langs.forEach((lang) => { 48 + if (typeof lang !== 'string') { 49 + logger.warn(`Invalid language type encountered: ${typeof lang}`, { lang }); 50 + return; 51 + } 52 + if (languageCounts[lang]) { 53 + languageCounts[lang] += 1; 54 + } else { 55 + languageCounts[lang] = 1; 56 + } 57 + languageGauge.set({ language: lang }, languageCounts[lang]); 58 + }); 59 + 60 + // Increment totalPosts by 1 per post 61 + totalPosts.inc(1); 62 + } 63 + 64 + // Calculate Posts Per Second 65 + let postsLastInterval = 0; 66 + setInterval(() => { 67 + postsPerSecond.set(postsLastInterval); 68 + postsLastInterval = 0; 69 + }, 1000); 70 + 71 + // Exported function to increment posts per second 72 + export function incrementPosts(count: number = 1) { 73 + postsLastInterval += count; 74 + } 75 + 76 + // Function to increment error count 77 + export function incrementErrors() { 78 + errorCounter.inc(); 79 + } 80 + 81 + // Function to increment unexpected event count 82 + export function incrementUnexpectedEvent(eventType: string, collection: string) { 83 + unexpectedEventCounter.inc({ event_type: eventType, collection }); 84 + } 85 + 86 + export { register };
+11
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "module": "commonjs", 5 + "strict": true, 6 + "esModuleInterop": true, 7 + "outDir": "./dist", 8 + "skipLibCheck": true 9 + }, 10 + "include": ["src/**/*"] 11 + }