Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

gzip and base64 files to get around pds mimetype sniffing as well as serve files as gzip's for faster serving

Changed files
+1148 -168
hosting-service
lexicons
src
lexicon
types
place
wisp
lib
routes
+1 -1
.gitignore
··· 1 # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 - 3 # dependencies 4 /node_modules 5 /.pnp
··· 1 # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 + .env 3 # dependencies 4 /node_modules 5 /.pnp
+6
bun.lock
··· 28 "tailwind-merge": "^3.3.1", 29 "tailwindcss": "4", 30 "tw-animate-css": "^1.4.0", 31 }, 32 "devDependencies": { 33 "@types/react": "^19.2.2", ··· 500 501 "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], 502 503 "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], 504 505 "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], ··· 523 "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], 524 525 "yesno": ["yesno@0.4.0", "", {}, "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA=="], 526 527 "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 528
··· 28 "tailwind-merge": "^3.3.1", 29 "tailwindcss": "4", 30 "tw-animate-css": "^1.4.0", 31 + "typescript": "^5.9.3", 32 + "zlib": "^1.0.5", 33 }, 34 "devDependencies": { 35 "@types/react": "^19.2.2", ··· 502 503 "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], 504 505 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 506 + 507 "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], 508 509 "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], ··· 527 "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], 528 529 "yesno": ["yesno@0.4.0", "", {}, "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA=="], 530 + 531 + "zlib": ["zlib@1.0.5", "", {}, "sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w=="], 532 533 "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 534
+553
hosting-service/bun.lock
···
··· 1 + { 2 + "lockfileVersion": 1, 3 + "workspaces": { 4 + "": { 5 + "name": "wisp-hosting-service", 6 + "dependencies": { 7 + "@atproto/api": "^0.17.4", 8 + "@atproto/identity": "^0.4.9", 9 + "@atproto/lexicon": "^0.5.1", 10 + "@atproto/sync": "^0.1.35", 11 + "@atproto/xrpc": "^0.7.5", 12 + "@elysiajs/node": "^1.4.1", 13 + "@elysiajs/opentelemetry": "latest", 14 + "elysia": "latest", 15 + "mime-types": "^2.1.35", 16 + "multiformats": "^13.4.1", 17 + "postgres": "^3.4.5", 18 + }, 19 + "devDependencies": { 20 + "@types/mime-types": "^2.1.4", 21 + "@types/node": "^22.10.5", 22 + "tsx": "^4.19.2", 23 + }, 24 + }, 25 + }, 26 + "packages": { 27 + "@atproto/api": ["@atproto/api@0.17.4", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "@atproto/xrpc": "^0.7.5", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, ""], 28 + 29 + "@atproto/common": ["@atproto/common@0.4.12", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@ipld/dag-cbor": "^7.0.3", "cbor-x": "^1.5.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, ""], 30 + 31 + "@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""], 32 + 33 + "@atproto/crypto": ["@atproto/crypto@0.4.4", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, ""], 34 + 35 + "@atproto/identity": ["@atproto/identity@0.4.9", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/crypto": "^0.4.4" } }, ""], 36 + 37 + "@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""], 38 + 39 + "@atproto/repo": ["@atproto/repo@0.8.10", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/common-web": "^0.4.3", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.1", "@ipld/dag-cbor": "^7.0.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "varint": "^6.0.0", "zod": "^3.23.8" } }, ""], 40 + 41 + "@atproto/sync": ["@atproto/sync@0.1.35", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/identity": "^0.4.9", "@atproto/lexicon": "^0.5.1", "@atproto/repo": "^0.8.10", "@atproto/syntax": "^0.4.1", "@atproto/xrpc-server": "^0.9.5", "multiformats": "^9.9.0", "p-queue": "^6.6.2", "ws": "^8.12.0" } }, ""], 42 + 43 + "@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""], 44 + 45 + "@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""], 46 + 47 + "@atproto/xrpc-server": ["@atproto/xrpc-server@0.9.5", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.1", "@atproto/xrpc": "^0.7.5", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.23.8" } }, ""], 48 + 49 + "@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="], 50 + 51 + "@cbor-extract/cbor-extract-darwin-arm64": ["@cbor-extract/cbor-extract-darwin-arm64@2.2.0", "", { "os": "darwin", "cpu": "arm64" }, ""], 52 + 53 + "@elysiajs/node": ["@elysiajs/node@1.4.1", "", { "dependencies": { "crossws": "^0.4.1", "srvx": "^0.8.9" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-2wAALwHK3IYi1XJPnxfp1xJsvps5FqqcQqe+QXjYlGQvsmSG+vI5wNDIuvIlB+6p9NE/laLbqV0aFromf3X7yg=="], 54 + 55 + "@elysiajs/opentelemetry": ["@elysiajs/opentelemetry@1.4.6", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/sdk-node": "^0.200.0" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-jR7t4M6ZvMnBqzzHsNTL6y3sNq9jbGi2vKxbkizi/OO5tlvlKl/rnBGyFjZUjQ1Hte7rCz+2kfmgOQMhkjk+Og=="], 56 + 57 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="], 58 + 59 + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="], 60 + 61 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="], 62 + 63 + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="], 64 + 65 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="], 66 + 67 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="], 68 + 69 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="], 70 + 71 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="], 72 + 73 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="], 74 + 75 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="], 76 + 77 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="], 78 + 79 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="], 80 + 81 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="], 82 + 83 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="], 84 + 85 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="], 86 + 87 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="], 88 + 89 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="], 90 + 91 + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="], 92 + 93 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="], 94 + 95 + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="], 96 + 97 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="], 98 + 99 + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="], 100 + 101 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="], 102 + 103 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="], 104 + 105 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="], 106 + 107 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="], 108 + 109 + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.0", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg=="], 110 + 111 + "@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], 112 + 113 + "@ipld/dag-cbor": ["@ipld/dag-cbor@7.0.3", "", { "dependencies": { "cborg": "^1.6.0", "multiformats": "^9.5.4" } }, ""], 114 + 115 + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], 116 + 117 + "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, ""], 118 + 119 + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, ""], 120 + 121 + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], 122 + 123 + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q=="], 124 + 125 + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.0.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-IEkJGzK1A9v3/EHjXh3s2IiFc6L4jfK+lNgKVgUjeUJQRRhnVFMIO3TAvKwonm9O1HebCuoOt98v8bZW7oVQHA=="], 126 + 127 + "@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="], 128 + 129 + "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.200.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-grpc-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/sdk-logs": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+3MDfa5YQPGM3WXxW9kqGD85Q7s9wlEMVNhXXG7tYFLnIeaseUt9YtCeFhEDFzfEktacdFpOtXmJuNW8cHbU5A=="], 130 + 131 + "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/sdk-logs": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-KfWw49htbGGp9s8N4KI8EQ9XuqKJ0VG+yVYVYFiCYSjEV32qpQ5qZ9UZBzOZ6xRb+E16SXOSCT3RkqBVSABZ+g=="], 132 + 133 + "@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-trace-base": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-GmahpUU/55hxfH4TP77ChOfftADsCq/nuri73I/AVLe2s4NIglvTsaACkFVZAVmnXXyPS00Fk3x27WS3yO07zA=="], 134 + 135 + "@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.200.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.0", "@opentelemetry/exporter-metrics-otlp-http": "0.200.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-grpc-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-uHawPRvKIrhqH09GloTuYeq2BjyieYHIpiklOvxm9zhrCL2eRsnI/6g9v2BZTVtGp8tEgIa7rCQ6Ltxw6NBgew=="], 136 + 137 + "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5BiR6i8yHc9+qW7F6LqkuUnIzVNA7lt0qRxIKcKT+gq3eGUPHZ3DY29sfxI3tkvnwMgtnHDMNze5DdxW39HsAw=="], 138 + 139 + "@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/exporter-metrics-otlp-http": "0.200.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-E+uPj0yyvz81U9pvLZp3oHtFrEzNSqKGVkIViTQY1rH3TOobeJPSpLnTVXACnCwkPR5XeTvPnK3pZ2Kni8AFMg=="], 140 + 141 + "@opentelemetry/exporter-prometheus": ["@opentelemetry/exporter-prometheus@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ZYdlU9r0USuuYppiDyU2VFRA0kFl855ylnb3N/2aOlXrbA4PMCznen7gmPbetGQu7pz8Jbaf4fwvrDnVdQQXSw=="], 142 + 143 + "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.200.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-grpc-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-hmeZrUkFl1YMsgukSuHCFPYeF9df0hHoKeHUthRKFCxiURs+GwF1VuabuHmBMZnjTbsuvNjOB+JSs37Csem/5Q=="], 144 + 145 + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Goi//m/7ZHeUedxTGVmEzH19NgqJY+Bzr6zXo1Rni1+hwqaksEyJ44gdlEMREu6dzX1DlAaH/qSykSVzdrdafA=="], 146 + 147 + "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-V9TDSD3PjK1OREw2iT9TUTzNYEVWJk4Nhodzhp9eiz4onDMYmPy3LaGbPv81yIR6dUb/hNp/SIhpiCHwFUq2Vg=="], 148 + 149 + "@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-icxaKZ+jZL/NHXX8Aru4HGsrdhK0MLcuRXkX5G5IRmCgoRLw+Br6I/nMVozX2xjGGwV7hw2g+4Slj8K7s4HbVg=="], 150 + 151 + "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="], 152 + 153 + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="], 154 + 155 + "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.200.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CK2S+bFgOZ66Bsu5hlDeOX6cvW5FVtVjFFbWuaJP0ELxJKBB6HlbLZQ2phqz/uLj1cWap5xJr/PsR3iGoB7Vqw=="], 156 + 157 + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="], 158 + 159 + "@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-blx9S2EI49Ycuw6VZq+bkpaIoiJFhsDuvFGhBIoH3vJ5oYjJ2U0s3fAM5jYft99xVIAv6HqoPtlP9gpVA2IZtA=="], 160 + 161 + "@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Mbm/LSFyAtQKP0AQah4AfGgsD+vsZcyreZoQ5okFBk33hU7AquU4TltgyL9dvaO8/Zkoud8/0gEvwfOZ5d7EPA=="], 162 + 163 + "@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="], 164 + 165 + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-VZG870063NLfObmQQNtCVcdXXLzI3vOjjrRENmU37HYiPFa0ZXpXVDsTD02Nh3AT3xYJzQaWKl2X2lQ2l7TWJA=="], 166 + 167 + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="], 168 + 169 + "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/exporter-logs-otlp-grpc": "0.200.0", "@opentelemetry/exporter-logs-otlp-http": "0.200.0", "@opentelemetry/exporter-logs-otlp-proto": "0.200.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.200.0", "@opentelemetry/exporter-metrics-otlp-http": "0.200.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.200.0", "@opentelemetry/exporter-prometheus": "0.200.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.200.0", "@opentelemetry/exporter-trace-otlp-http": "0.200.0", "@opentelemetry/exporter-trace-otlp-proto": "0.200.0", "@opentelemetry/exporter-zipkin": "2.0.0", "@opentelemetry/instrumentation": "0.200.0", "@opentelemetry/propagator-b3": "2.0.0", "@opentelemetry/propagator-jaeger": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "@opentelemetry/sdk-trace-node": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-S/YSy9GIswnhYoDor1RusNkmRughipvTCOQrlF1dzI70yQaf68qgf5WMnzUxdlCl3/et/pvaO75xfPfuEmCK5A=="], 170 + 171 + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-qQnYdX+ZCkonM7tA5iU4fSRsVxbFGml8jbxOgipRGMFHKaXKHQ30js03rTobYjKjIfnOsZSbHKWF0/0v0OQGfw=="], 172 + 173 + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.0.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.0.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-omdilCZozUjQwY3uZRBwbaRMJ3p09l4t187Lsdf0dGMye9WKD4NGcpgZRvqhI1dwcH6og+YXQEtoO9Wx3ykilg=="], 174 + 175 + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.37.0", "", {}, "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA=="], 176 + 177 + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], 178 + 179 + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], 180 + 181 + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], 182 + 183 + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], 184 + 185 + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], 186 + 187 + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], 188 + 189 + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], 190 + 191 + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], 192 + 193 + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], 194 + 195 + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], 196 + 197 + "@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="], 198 + 199 + "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], 200 + 201 + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], 202 + 203 + "@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="], 204 + 205 + "@types/node": ["@types/node@22.18.12", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog=="], 206 + 207 + "@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="], 208 + 209 + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, ""], 210 + 211 + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, ""], 212 + 213 + "acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 214 + 215 + "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], 216 + 217 + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 218 + 219 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 220 + 221 + "array-flatten": ["array-flatten@1.1.1", "", {}, ""], 222 + 223 + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, ""], 224 + 225 + "await-lock": ["await-lock@2.2.2", "", {}, ""], 226 + 227 + "base64-js": ["base64-js@1.5.1", "", {}, ""], 228 + 229 + "body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, ""], 230 + 231 + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, ""], 232 + 233 + "bytes": ["bytes@3.1.2", "", {}, ""], 234 + 235 + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, ""], 236 + 237 + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, ""], 238 + 239 + "cbor-extract": ["cbor-extract@2.2.0", "", { "dependencies": { "node-gyp-build-optional-packages": "5.1.1" }, "optionalDependencies": { "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0" }, "bin": { "download-cbor-prebuilds": "bin/download-prebuilds.js" } }, ""], 240 + 241 + "cbor-x": ["cbor-x@1.6.0", "", { "optionalDependencies": { "cbor-extract": "^2.2.0" } }, ""], 242 + 243 + "cborg": ["cborg@1.10.2", "", { "bin": "cli.js" }, ""], 244 + 245 + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], 246 + 247 + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], 248 + 249 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 250 + 251 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 252 + 253 + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, ""], 254 + 255 + "content-type": ["content-type@1.0.5", "", {}, ""], 256 + 257 + "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], 258 + 259 + "cookie-signature": ["cookie-signature@1.0.6", "", {}, ""], 260 + 261 + "crossws": ["crossws@0.4.1", "", { "peerDependencies": { "srvx": ">=0.7.1" }, "optionalPeers": ["srvx"] }, "sha512-E7WKBcHVhAVrY6JYD5kteNqVq1GSZxqGrdSiwXR9at+XHi43HJoCQKXcCczR5LBnBquFZPsB3o7HklulKoBU5w=="], 262 + 263 + "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, ""], 264 + 265 + "depd": ["depd@2.0.0", "", {}, ""], 266 + 267 + "destroy": ["destroy@1.2.0", "", {}, ""], 268 + 269 + "detect-libc": ["detect-libc@2.1.2", "", {}, ""], 270 + 271 + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, ""], 272 + 273 + "ee-first": ["ee-first@1.1.1", "", {}, ""], 274 + 275 + "elysia": ["elysia@1.4.13", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "exact-mirror": ">= 0.0.9", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-6QaWQEm7QN1UCo1TPpEjaRJPHUmnM7R29y6LY224frDGk5PrpAnWmdHkoZxkcv+JRWp1j2ROr2IHbxHbG/jRjw=="], 276 + 277 + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 278 + 279 + "encodeurl": ["encodeurl@2.0.0", "", {}, ""], 280 + 281 + "es-define-property": ["es-define-property@1.0.1", "", {}, ""], 282 + 283 + "es-errors": ["es-errors@1.3.0", "", {}, ""], 284 + 285 + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, ""], 286 + 287 + "esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": "bin/esbuild" }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="], 288 + 289 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 290 + 291 + "escape-html": ["escape-html@1.0.3", "", {}, ""], 292 + 293 + "etag": ["etag@1.8.1", "", {}, ""], 294 + 295 + "event-target-shim": ["event-target-shim@5.0.1", "", {}, ""], 296 + 297 + "eventemitter3": ["eventemitter3@4.0.7", "", {}, ""], 298 + 299 + "events": ["events@3.3.0", "", {}, ""], 300 + 301 + "exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" } }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="], 302 + 303 + "express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, ""], 304 + 305 + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], 306 + 307 + "fast-redact": ["fast-redact@3.5.0", "", {}, ""], 308 + 309 + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], 310 + 311 + "file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="], 312 + 313 + "finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, ""], 314 + 315 + "forwarded": ["forwarded@0.2.0", "", {}, ""], 316 + 317 + "fresh": ["fresh@0.5.2", "", {}, ""], 318 + 319 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 320 + 321 + "function-bind": ["function-bind@1.1.2", "", {}, ""], 322 + 323 + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], 324 + 325 + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, ""], 326 + 327 + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, ""], 328 + 329 + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], 330 + 331 + "gopd": ["gopd@1.2.0", "", {}, ""], 332 + 333 + "graphemer": ["graphemer@1.4.0", "", {}, ""], 334 + 335 + "has-symbols": ["has-symbols@1.1.0", "", {}, ""], 336 + 337 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, ""], 338 + 339 + "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, ""], 340 + 341 + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, ""], 342 + 343 + "ieee754": ["ieee754@1.2.1", "", {}, ""], 344 + 345 + "import-in-the-middle": ["import-in-the-middle@1.15.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA=="], 346 + 347 + "inherits": ["inherits@2.0.4", "", {}, ""], 348 + 349 + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, ""], 350 + 351 + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], 352 + 353 + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], 354 + 355 + "iso-datestring-validator": ["iso-datestring-validator@2.2.2", "", {}, ""], 356 + 357 + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], 358 + 359 + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], 360 + 361 + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, ""], 362 + 363 + "media-typer": ["media-typer@0.3.0", "", {}, ""], 364 + 365 + "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], 366 + 367 + "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, ""], 368 + 369 + "methods": ["methods@1.1.2", "", {}, ""], 370 + 371 + "mime": ["mime@1.6.0", "", { "bin": "cli.js" }, ""], 372 + 373 + "mime-db": ["mime-db@1.52.0", "", {}, ""], 374 + 375 + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, ""], 376 + 377 + "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], 378 + 379 + "ms": ["ms@2.0.0", "", {}, ""], 380 + 381 + "multiformats": ["multiformats@13.4.1", "", {}, ""], 382 + 383 + "negotiator": ["negotiator@0.6.3", "", {}, ""], 384 + 385 + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.1.1", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, ""], 386 + 387 + "object-inspect": ["object-inspect@1.13.4", "", {}, ""], 388 + 389 + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, ""], 390 + 391 + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, ""], 392 + 393 + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], 394 + 395 + "p-finally": ["p-finally@1.0.0", "", {}, ""], 396 + 397 + "p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, ""], 398 + 399 + "p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, ""], 400 + 401 + "parseurl": ["parseurl@1.3.3", "", {}, ""], 402 + 403 + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], 404 + 405 + "path-to-regexp": ["path-to-regexp@0.1.12", "", {}, ""], 406 + 407 + "pino": ["pino@8.21.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^1.2.0", "pino-std-serializers": "^6.0.0", "process-warning": "^3.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^3.7.0", "thread-stream": "^2.6.0" }, "bin": "bin.js" }, ""], 408 + 409 + "pino-abstract-transport": ["pino-abstract-transport@1.2.0", "", { "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" } }, ""], 410 + 411 + "pino-std-serializers": ["pino-std-serializers@6.2.2", "", {}, ""], 412 + 413 + "postgres": ["postgres@3.4.7", "", {}, ""], 414 + 415 + "process": ["process@0.11.10", "", {}, ""], 416 + 417 + "process-warning": ["process-warning@3.0.0", "", {}, ""], 418 + 419 + "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], 420 + 421 + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, ""], 422 + 423 + "qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, ""], 424 + 425 + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, ""], 426 + 427 + "range-parser": ["range-parser@1.2.1", "", {}, ""], 428 + 429 + "rate-limiter-flexible": ["rate-limiter-flexible@2.4.2", "", {}, ""], 430 + 431 + "raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, ""], 432 + 433 + "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, ""], 434 + 435 + "real-require": ["real-require@0.2.0", "", {}, ""], 436 + 437 + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], 438 + 439 + "require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="], 440 + 441 + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], 442 + 443 + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 444 + 445 + "safe-buffer": ["safe-buffer@5.2.1", "", {}, ""], 446 + 447 + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, ""], 448 + 449 + "safer-buffer": ["safer-buffer@2.1.2", "", {}, ""], 450 + 451 + "send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, ""], 452 + 453 + "serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, ""], 454 + 455 + "setprototypeof": ["setprototypeof@1.2.0", "", {}, ""], 456 + 457 + "shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="], 458 + 459 + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, ""], 460 + 461 + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, ""], 462 + 463 + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, ""], 464 + 465 + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, ""], 466 + 467 + "sonic-boom": ["sonic-boom@3.8.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, ""], 468 + 469 + "split2": ["split2@4.2.0", "", {}, ""], 470 + 471 + "srvx": ["srvx@0.8.16", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-hmcGW4CgroeSmzgF1Ihwgl+Ths0JqAJ7HwjP2X7e3JzY7u4IydLMcdnlqGQiQGUswz+PO9oh/KtCpOISIvs9QQ=="], 472 + 473 + "statuses": ["statuses@2.0.1", "", {}, ""], 474 + 475 + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 476 + 477 + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, ""], 478 + 479 + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 480 + 481 + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], 482 + 483 + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], 484 + 485 + "thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, ""], 486 + 487 + "tlds": ["tlds@1.261.0", "", { "bin": "bin.js" }, ""], 488 + 489 + "toidentifier": ["toidentifier@1.0.1", "", {}, ""], 490 + 491 + "token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="], 492 + 493 + "tsx": ["tsx@4.20.6", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": "dist/cli.mjs" }, "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg=="], 494 + 495 + "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, ""], 496 + 497 + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], 498 + 499 + "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, ""], 500 + 501 + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 502 + 503 + "unpipe": ["unpipe@1.0.0", "", {}, ""], 504 + 505 + "utils-merge": ["utils-merge@1.0.1", "", {}, ""], 506 + 507 + "varint": ["varint@6.0.0", "", {}, ""], 508 + 509 + "vary": ["vary@1.1.2", "", {}, ""], 510 + 511 + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], 512 + 513 + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, ""], 514 + 515 + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], 516 + 517 + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], 518 + 519 + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], 520 + 521 + "zod": ["zod@3.25.76", "", {}, ""], 522 + 523 + "@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, ""], 524 + 525 + "@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, ""], 526 + 527 + "@atproto/common-web/multiformats": ["multiformats@9.9.0", "", {}, ""], 528 + 529 + "@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""], 530 + 531 + "@atproto/repo/multiformats": ["multiformats@9.9.0", "", {}, ""], 532 + 533 + "@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, ""], 534 + 535 + "@ipld/dag-cbor/multiformats": ["multiformats@9.9.0", "", {}, ""], 536 + 537 + "@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 538 + 539 + "express/cookie": ["cookie@0.7.1", "", {}, ""], 540 + 541 + "require-in-the-middle/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 542 + 543 + "send/encodeurl": ["encodeurl@1.0.2", "", {}, ""], 544 + 545 + "send/ms": ["ms@2.1.3", "", {}, ""], 546 + 547 + "uint8arrays/multiformats": ["multiformats@9.9.0", "", {}, ""], 548 + 549 + "@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 550 + 551 + "require-in-the-middle/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 552 + } 553 + }
+2 -2
hosting-service/package.json
··· 12 "@atproto/lexicon": "^0.5.1", 13 "@atproto/sync": "^0.1.35", 14 "@atproto/xrpc": "^0.7.5", 15 - "@hono/node-server": "^1.13.7", 16 - "hono": "^4.6.14", 17 "mime-types": "^2.1.35", 18 "multiformats": "^13.4.1", 19 "postgres": "^3.4.5"
··· 12 "@atproto/lexicon": "^0.5.1", 13 "@atproto/sync": "^0.1.35", 14 "@atproto/xrpc": "^0.7.5", 15 + "@elysiajs/opentelemetry": "latest", 16 + "elysia": "latest", 17 "mime-types": "^2.1.35", 18 "multiformats": "^13.4.1", 19 "postgres": "^3.4.5"
+10 -14
hosting-service/src/index.ts
··· 1 - import { serve } from '@hono/node-server'; 2 - import app from './server.js'; 3 - import { FirehoseWorker } from './lib/firehose.js'; 4 import { mkdirSync, existsSync } from 'fs'; 5 6 const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001; ··· 20 firehose.start(); 21 22 // Add health check endpoint 23 - app.get('/health', (c) => { 24 const firehoseHealth = firehose.getHealth(); 25 - return c.json({ 26 status: 'ok', 27 firehose: firehoseHealth, 28 - }); 29 }); 30 31 // Start HTTP server 32 - const server = serve({ 33 - port: PORT, 34 - fetch: app.fetch, 35 - }); 36 - 37 - console.log(` 38 Wisp Hosting Service 39 40 Server: http://localhost:${PORT} ··· 42 Cache: ${CACHE_DIR} 43 Firehose: Connected to Firehose 44 `); 45 46 // Graceful shutdown 47 process.on('SIGINT', async () => { 48 console.log('\n🛑 Shutting down...'); 49 firehose.stop(); 50 - server.close(); 51 process.exit(0); 52 }); 53 54 process.on('SIGTERM', async () => { 55 console.log('\n🛑 Shutting down...'); 56 firehose.stop(); 57 - server.close(); 58 process.exit(0); 59 });
··· 1 + import app from './server'; 2 + import { FirehoseWorker } from './lib/firehose'; 3 import { mkdirSync, existsSync } from 'fs'; 4 5 const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001; ··· 19 firehose.start(); 20 21 // Add health check endpoint 22 + app.get('/health', () => { 23 const firehoseHealth = firehose.getHealth(); 24 + return { 25 status: 'ok', 26 firehose: firehoseHealth, 27 + }; 28 }); 29 30 // Start HTTP server 31 + app.listen(PORT, () => { 32 + console.log(` 33 Wisp Hosting Service 34 35 Server: http://localhost:${PORT} ··· 37 Cache: ${CACHE_DIR} 38 Firehose: Connected to Firehose 39 `); 40 + }); 41 42 // Graceful shutdown 43 process.on('SIGINT', async () => { 44 console.log('\n🛑 Shutting down...'); 45 firehose.stop(); 46 + app.stop(); 47 process.exit(0); 48 }); 49 50 process.on('SIGTERM', async () => { 51 console.log('\n🛑 Shutting down...'); 52 firehose.stop(); 53 + app.stop(); 54 process.exit(0); 55 });
+44
hosting-service/src/lexicon/index.ts
···
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + type Auth, 6 + type Options as XrpcOptions, 7 + Server as XrpcServer, 8 + type StreamConfigOrHandler, 9 + type MethodConfigOrHandler, 10 + createServer as createXrpcServer, 11 + } from '@atproto/xrpc-server' 12 + import { schemas } from './lexicons.js' 13 + 14 + export function createServer(options?: XrpcOptions): Server { 15 + return new Server(options) 16 + } 17 + 18 + export class Server { 19 + xrpc: XrpcServer 20 + place: PlaceNS 21 + 22 + constructor(options?: XrpcOptions) { 23 + this.xrpc = createXrpcServer(schemas, options) 24 + this.place = new PlaceNS(this) 25 + } 26 + } 27 + 28 + export class PlaceNS { 29 + _server: Server 30 + wisp: PlaceWispNS 31 + 32 + constructor(server: Server) { 33 + this._server = server 34 + this.wisp = new PlaceWispNS(server) 35 + } 36 + } 37 + 38 + export class PlaceWispNS { 39 + _server: Server 40 + 41 + constructor(server: Server) { 42 + this._server = server 43 + } 44 + }
+14
hosting-service/src/lexicon/lexicons.ts
··· 54 maxSize: 1000000, 55 description: 'Content blob ref', 56 }, 57 }, 58 }, 59 directory: {
··· 54 maxSize: 1000000, 55 description: 'Content blob ref', 56 }, 57 + encoding: { 58 + type: 'string', 59 + enum: ['gzip'], 60 + description: 'Content encoding (e.g., gzip for compressed files)', 61 + }, 62 + mimeType: { 63 + type: 'string', 64 + description: 'Original MIME type before compression', 65 + }, 66 + base64: { 67 + type: 'boolean', 68 + description: 69 + 'True if blob content is base64-encoded (used to bypass PDS content sniffing)', 70 + }, 71 }, 72 }, 73 directory: {
+6
hosting-service/src/lexicon/types/place/wisp/fs.ts
··· 34 type: 'file' 35 /** Content blob ref */ 36 blob: BlobRef 37 } 38 39 const hashFile = 'file'
··· 34 type: 'file' 35 /** Content blob ref */ 36 blob: BlobRef 37 + /** Content encoding (e.g., gzip for compressed files) */ 38 + encoding?: 'gzip' 39 + /** Original MIME type before compression */ 40 + mimeType?: string 41 + /** True if blob content is base64-encoded (used to bypass PDS content sniffing) */ 42 + base64?: boolean 43 } 44 45 const hashFile = 'file'
+98 -5
hosting-service/src/lib/db.ts
··· 21 verified: boolean; 22 } 23 24 export async function getWispDomain(domain: string): Promise<DomainLookup | null> { 25 const result = await sql<DomainLookup[]>` 26 - SELECT did, rkey FROM domains WHERE domain = ${domain.toLowerCase()} LIMIT 1 27 `; 28 - return result[0] || null; 29 } 30 31 export async function getCustomDomain(domain: string): Promise<CustomDomainLookup | null> { 32 const result = await sql<CustomDomainLookup[]>` 33 SELECT id, domain, did, rkey, verified FROM custom_domains 34 - WHERE domain = ${domain.toLowerCase()} AND verified = true LIMIT 1 35 `; 36 - return result[0] || null; 37 } 38 39 export async function getCustomDomainByHash(hash: string): Promise<CustomDomainLookup | null> { 40 const result = await sql<CustomDomainLookup[]>` 41 SELECT id, domain, did, rkey, verified FROM custom_domains 42 WHERE id = ${hash} AND verified = true LIMIT 1 43 `; 44 - return result[0] || null; 45 } 46 47 export async function upsertSite(did: string, rkey: string, displayName?: string) {
··· 21 verified: boolean; 22 } 23 24 + // In-memory cache with TTL 25 + interface CacheEntry<T> { 26 + data: T; 27 + expiry: number; 28 + } 29 + 30 + const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes 31 + 32 + class SimpleCache<T> { 33 + private cache = new Map<string, CacheEntry<T>>(); 34 + 35 + get(key: string): T | null { 36 + const entry = this.cache.get(key); 37 + if (!entry) return null; 38 + 39 + if (Date.now() > entry.expiry) { 40 + this.cache.delete(key); 41 + return null; 42 + } 43 + 44 + return entry.data; 45 + } 46 + 47 + set(key: string, data: T): void { 48 + this.cache.set(key, { 49 + data, 50 + expiry: Date.now() + CACHE_TTL_MS, 51 + }); 52 + } 53 + 54 + // Periodic cleanup to prevent memory leaks 55 + cleanup(): void { 56 + const now = Date.now(); 57 + for (const [key, entry] of this.cache.entries()) { 58 + if (now > entry.expiry) { 59 + this.cache.delete(key); 60 + } 61 + } 62 + } 63 + } 64 + 65 + // Create cache instances 66 + const wispDomainCache = new SimpleCache<DomainLookup | null>(); 67 + const customDomainCache = new SimpleCache<CustomDomainLookup | null>(); 68 + const customDomainHashCache = new SimpleCache<CustomDomainLookup | null>(); 69 + 70 + // Run cleanup every 5 minutes 71 + setInterval(() => { 72 + wispDomainCache.cleanup(); 73 + customDomainCache.cleanup(); 74 + customDomainHashCache.cleanup(); 75 + }, 5 * 60 * 1000); 76 + 77 export async function getWispDomain(domain: string): Promise<DomainLookup | null> { 78 + const key = domain.toLowerCase(); 79 + 80 + // Check cache first 81 + const cached = wispDomainCache.get(key); 82 + if (cached !== null) { 83 + return cached; 84 + } 85 + 86 + // Query database 87 const result = await sql<DomainLookup[]>` 88 + SELECT did, rkey FROM domains WHERE domain = ${key} LIMIT 1 89 `; 90 + const data = result[0] || null; 91 + 92 + // Store in cache 93 + wispDomainCache.set(key, data); 94 + 95 + return data; 96 } 97 98 export async function getCustomDomain(domain: string): Promise<CustomDomainLookup | null> { 99 + const key = domain.toLowerCase(); 100 + 101 + // Check cache first 102 + const cached = customDomainCache.get(key); 103 + if (cached !== null) { 104 + return cached; 105 + } 106 + 107 + // Query database 108 const result = await sql<CustomDomainLookup[]>` 109 SELECT id, domain, did, rkey, verified FROM custom_domains 110 + WHERE domain = ${key} AND verified = true LIMIT 1 111 `; 112 + const data = result[0] || null; 113 + 114 + // Store in cache 115 + customDomainCache.set(key, data); 116 + 117 + return data; 118 } 119 120 export async function getCustomDomainByHash(hash: string): Promise<CustomDomainLookup | null> { 121 + // Check cache first 122 + const cached = customDomainHashCache.get(hash); 123 + if (cached !== null) { 124 + return cached; 125 + } 126 + 127 + // Query database 128 const result = await sql<CustomDomainLookup[]>` 129 SELECT id, domain, did, rkey, verified FROM custom_domains 130 WHERE id = ${hash} AND verified = true LIMIT 1 131 `; 132 + const data = result[0] || null; 133 + 134 + // Store in cache 135 + customDomainHashCache.set(hash, data); 136 + 137 + return data; 138 } 139 140 export async function upsertSite(did: string, rkey: string, displayName?: string) {
+1 -1
hosting-service/src/lib/firehose.ts
··· 157 return; 158 } 159 160 - // Cache the record with verified CID 161 await downloadAndCacheSite(did, site, fsRecord, pdsEndpoint, verifiedCid); 162 163 // Upsert site to database
··· 157 return; 158 } 159 160 + // Cache the record with verified CID (uses atomic swap internally) 161 await downloadAndCacheSite(did, site, fsRecord, pdsEndpoint, verifiedCid); 162 163 // Upsert site to database
+29 -10
hosting-service/src/lib/html-rewriter.ts
··· 16 * Check if a path should be rewritten 17 */ 18 function shouldRewritePath(path: string): boolean { 19 - // Must start with / 20 - if (!path.startsWith('/')) return false; 21 22 - // Don't rewrite protocol-relative URLs 23 - if (path.startsWith('//')) return false; 24 25 - // Don't rewrite anchors 26 - if (path.startsWith('/#')) return false; 27 28 - // Don't rewrite data URIs or other schemes 29 - if (path.includes(':')) return false; 30 31 return true; 32 } 33 ··· 39 return path; 40 } 41 42 - // Remove leading slash and prepend base path 43 - return basePath + path.slice(1); 44 } 45 46 /**
··· 16 * Check if a path should be rewritten 17 */ 18 function shouldRewritePath(path: string): boolean { 19 + // Don't rewrite empty paths 20 + if (!path) return false; 21 22 + // Don't rewrite external URLs (http://, https://, //) 23 + if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('//')) { 24 + return false; 25 + } 26 27 + // Don't rewrite data URIs or other schemes (except file paths) 28 + if (path.includes(':') && !path.startsWith('./') && !path.startsWith('../')) { 29 + return false; 30 + } 31 32 + // Don't rewrite pure anchors 33 + if (path.startsWith('#')) return false; 34 35 + // Rewrite absolute paths (/) and relative paths (./ or ../ or plain filenames) 36 return true; 37 } 38 ··· 44 return path; 45 } 46 47 + // Handle absolute paths: /file.js -> /base/file.js 48 + if (path.startsWith('/')) { 49 + return basePath + path.slice(1); 50 + } 51 + 52 + // Handle relative paths: ./file.js or ../file.js or file.js -> /base/file.js 53 + // Strip leading ./ or ../ and just use the base path 54 + let cleanPath = path; 55 + if (cleanPath.startsWith('./')) { 56 + cleanPath = cleanPath.slice(2); 57 + } else if (cleanPath.startsWith('../')) { 58 + // For sites.wisp.place, we can't go up from the site root, so just use base path 59 + cleanPath = cleanPath.replace(/^(\.\.\/)+/, ''); 60 + } 61 + 62 + return basePath + cleanPath; 63 } 64 65 /**
+75 -13
hosting-service/src/lib/utils.ts
··· 1 import { AtpAgent } from '@atproto/api'; 2 import type { WispFsRecord, Directory, Entry, File } from './types'; 3 - import { existsSync, mkdirSync, readFileSync } from 'fs'; 4 - import { writeFile, readFile } from 'fs/promises'; 5 import { safeFetchJson, safeFetchBlob } from './safe-fetch'; 6 import { CID } from 'multiformats/cid'; 7 ··· 153 throw new Error('Invalid record structure: root missing entries array'); 154 } 155 156 - await cacheFiles(did, rkey, record.root.entries, pdsEndpoint, ''); 157 158 - await saveCacheMetadata(did, rkey, recordCid); 159 } 160 161 async function cacheFiles( ··· 163 site: string, 164 entries: Entry[], 165 pdsEndpoint: string, 166 - pathPrefix: string 167 ): Promise<void> { 168 for (const entry of entries) { 169 const currentPath = pathPrefix ? `${pathPrefix}/${entry.name}` : entry.name; 170 const node = entry.node; 171 172 if ('type' in node && node.type === 'directory' && 'entries' in node) { 173 - await cacheFiles(did, site, node.entries, pdsEndpoint, currentPath); 174 } else if ('type' in node && node.type === 'file' && 'blob' in node) { 175 - await cacheFileBlob(did, site, currentPath, node.blob, pdsEndpoint); 176 } 177 } 178 } ··· 182 site: string, 183 filePath: string, 184 blobRef: any, 185 - pdsEndpoint: string 186 ): Promise<void> { 187 const cid = extractBlobCid(blobRef); 188 if (!cid) { ··· 193 const blobUrl = `${pdsEndpoint}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`; 194 195 // Allow up to 100MB per file blob 196 - const content = await safeFetchBlob(blobUrl, { maxSize: 100 * 1024 * 1024 }); 197 198 - const cacheFile = `${CACHE_DIR}/${did}/${site}/${filePath}`; 199 const fileDir = cacheFile.substring(0, cacheFile.lastIndexOf('/')); 200 201 if (fileDir && !existsSync(fileDir)) { ··· 203 } 204 205 await writeFile(cacheFile, content); 206 - console.log('Cached file', filePath, content.length, 'bytes'); 207 } 208 209 /** ··· 238 return existsSync(`${CACHE_DIR}/${did}/${site}`); 239 } 240 241 - async function saveCacheMetadata(did: string, rkey: string, recordCid: string): Promise<void> { 242 const metadata: CacheMetadata = { 243 recordCid, 244 cachedAt: Date.now(), ··· 246 rkey 247 }; 248 249 - const metadataPath = `${CACHE_DIR}/${did}/${rkey}/.metadata.json`; 250 const metadataDir = metadataPath.substring(0, metadataPath.lastIndexOf('/')); 251 252 if (!existsSync(metadataDir)) {
··· 1 import { AtpAgent } from '@atproto/api'; 2 import type { WispFsRecord, Directory, Entry, File } from './types'; 3 + import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs'; 4 + import { writeFile, readFile, rename } from 'fs/promises'; 5 import { safeFetchJson, safeFetchBlob } from './safe-fetch'; 6 import { CID } from 'multiformats/cid'; 7 ··· 153 throw new Error('Invalid record structure: root missing entries array'); 154 } 155 156 + // Use a temporary directory with timestamp to avoid collisions 157 + const tempSuffix = `.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; 158 + const tempDir = `${CACHE_DIR}/${did}/${rkey}${tempSuffix}`; 159 + const finalDir = `${CACHE_DIR}/${did}/${rkey}`; 160 161 + try { 162 + // Download to temporary directory 163 + await cacheFiles(did, rkey, record.root.entries, pdsEndpoint, '', tempSuffix); 164 + await saveCacheMetadata(did, rkey, recordCid, tempSuffix); 165 + 166 + // Atomically replace old cache with new cache 167 + // On POSIX systems (Linux/macOS), rename is atomic 168 + if (existsSync(finalDir)) { 169 + // Rename old directory to backup 170 + const backupDir = `${finalDir}.old-${Date.now()}`; 171 + await rename(finalDir, backupDir); 172 + 173 + try { 174 + // Rename new directory to final location 175 + await rename(tempDir, finalDir); 176 + 177 + // Clean up old backup 178 + rmSync(backupDir, { recursive: true, force: true }); 179 + } catch (err) { 180 + // If rename failed, restore backup 181 + if (existsSync(backupDir) && !existsSync(finalDir)) { 182 + await rename(backupDir, finalDir); 183 + } 184 + throw err; 185 + } 186 + } else { 187 + // No existing cache, just rename temp to final 188 + await rename(tempDir, finalDir); 189 + } 190 + 191 + console.log('Successfully cached site atomically', did, rkey); 192 + } catch (err) { 193 + // Clean up temp directory on failure 194 + if (existsSync(tempDir)) { 195 + rmSync(tempDir, { recursive: true, force: true }); 196 + } 197 + throw err; 198 + } 199 } 200 201 async function cacheFiles( ··· 203 site: string, 204 entries: Entry[], 205 pdsEndpoint: string, 206 + pathPrefix: string, 207 + dirSuffix: string = '' 208 ): Promise<void> { 209 for (const entry of entries) { 210 const currentPath = pathPrefix ? `${pathPrefix}/${entry.name}` : entry.name; 211 const node = entry.node; 212 213 if ('type' in node && node.type === 'directory' && 'entries' in node) { 214 + await cacheFiles(did, site, node.entries, pdsEndpoint, currentPath, dirSuffix); 215 } else if ('type' in node && node.type === 'file' && 'blob' in node) { 216 + const fileNode = node as File; 217 + await cacheFileBlob(did, site, currentPath, fileNode.blob, pdsEndpoint, fileNode.encoding, fileNode.mimeType, fileNode.base64, dirSuffix); 218 } 219 } 220 } ··· 224 site: string, 225 filePath: string, 226 blobRef: any, 227 + pdsEndpoint: string, 228 + encoding?: 'gzip', 229 + mimeType?: string, 230 + base64?: boolean, 231 + dirSuffix: string = '' 232 ): Promise<void> { 233 const cid = extractBlobCid(blobRef); 234 if (!cid) { ··· 239 const blobUrl = `${pdsEndpoint}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`; 240 241 // Allow up to 100MB per file blob 242 + let content = await safeFetchBlob(blobUrl, { maxSize: 100 * 1024 * 1024 }); 243 244 + // If content is base64-encoded, decode it back to gzipped binary 245 + if (base64 && encoding === 'gzip') { 246 + // Convert Uint8Array to Buffer for proper string conversion 247 + const buffer = Buffer.from(content); 248 + const base64String = buffer.toString('utf-8'); 249 + content = Buffer.from(base64String, 'base64'); 250 + } 251 + 252 + const cacheFile = `${CACHE_DIR}/${did}/${site}${dirSuffix}/${filePath}`; 253 const fileDir = cacheFile.substring(0, cacheFile.lastIndexOf('/')); 254 255 if (fileDir && !existsSync(fileDir)) { ··· 257 } 258 259 await writeFile(cacheFile, content); 260 + 261 + // Store metadata if file is compressed 262 + if (encoding === 'gzip' && mimeType) { 263 + const metaFile = `${cacheFile}.meta`; 264 + await writeFile(metaFile, JSON.stringify({ encoding, mimeType })); 265 + console.log('Cached file', filePath, content.length, 'bytes (gzipped,', mimeType + ')'); 266 + } else { 267 + console.log('Cached file', filePath, content.length, 'bytes'); 268 + } 269 } 270 271 /** ··· 300 return existsSync(`${CACHE_DIR}/${did}/${site}`); 301 } 302 303 + async function saveCacheMetadata(did: string, rkey: string, recordCid: string, dirSuffix: string = ''): Promise<void> { 304 const metadata: CacheMetadata = { 305 recordCid, 306 cachedAt: Date.now(), ··· 308 rkey 309 }; 310 311 + const metadataPath = `${CACHE_DIR}/${did}/${rkey}${dirSuffix}/.metadata.json`; 312 const metadataDir = metadataPath.substring(0, metadataPath.lastIndexOf('/')); 313 314 if (!existsSync(metadataDir)) {
+188 -106
hosting-service/src/server.ts
··· 1 - import { Hono } from 'hono'; 2 import { getWispDomain, getCustomDomain, getCustomDomainByHash } from './lib/db'; 3 import { resolveDid, getPdsForDid, fetchSiteRecord, downloadAndCacheSite, getCachedFilePath, isCached, sanitizePath } from './lib/utils'; 4 import { rewriteHtmlPaths, isHtmlContent } from './lib/html-rewriter'; 5 import { existsSync, readFileSync } from 'fs'; 6 import { lookup } from 'mime-types'; 7 - 8 - const app = new Hono(); 9 10 const BASE_HOST = process.env.BASE_HOST || 'wisp.place'; 11 ··· 34 35 if (existsSync(cachedFile)) { 36 const content = readFileSync(cachedFile); 37 const mimeType = lookup(cachedFile) || 'application/octet-stream'; 38 return new Response(content, { 39 headers: { ··· 47 const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`); 48 if (existsSync(indexFile)) { 49 const content = readFileSync(indexFile); 50 return new Response(content, { 51 headers: { 52 'Content-Type': 'text/html; charset=utf-8', ··· 74 const cachedFile = getCachedFilePath(did, rkey, requestPath); 75 76 if (existsSync(cachedFile)) { 77 - const mimeType = lookup(cachedFile) || 'application/octet-stream'; 78 79 // Check if this is HTML content that needs rewriting 80 if (isHtmlContent(requestPath, mimeType)) { 81 - const content = readFileSync(cachedFile, 'utf-8'); 82 const rewritten = rewriteHtmlPaths(content, basePath); 83 return new Response(rewritten, { 84 headers: { ··· 87 }); 88 } 89 90 - // Non-HTML files served with proper MIME type 91 const content = readFileSync(cachedFile); 92 return new Response(content, { 93 headers: { 94 'Content-Type': mimeType, ··· 100 if (!requestPath.includes('.')) { 101 const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`); 102 if (existsSync(indexFile)) { 103 - const content = readFileSync(indexFile, 'utf-8'); 104 const rewritten = rewriteHtmlPaths(content, basePath); 105 return new Response(rewritten, { 106 headers: { ··· 141 } 142 } 143 144 - // Route 4: Direct file serving (no DB) - sites.wisp.place/:identifier/:site/* 145 - // This route is now handled in the catch-all route below 146 147 - // Route 3: DNS routing for custom domains - /hash.dns.wisp.place/* 148 - app.get('/*', async (c) => { 149 - const hostname = c.req.header('host') || ''; 150 - const rawPath = c.req.path.replace(/^\//, ''); 151 - const path = sanitizePath(rawPath); 152 153 - console.log('[Request]', { hostname, path }); 154 - 155 - // Check if this is sites.wisp.place subdomain 156 - if (hostname === `sites.${BASE_HOST}` || hostname === `sites.${BASE_HOST}:${process.env.PORT || 3000}`) { 157 - // Sanitize the path FIRST to prevent path traversal 158 - const sanitizedFullPath = sanitizePath(rawPath); 159 160 - // Extract identifier and site from sanitized path: did:plc:123abc/sitename/file.html 161 - const pathParts = sanitizedFullPath.split('/'); 162 - if (pathParts.length < 2) { 163 - return c.text('Invalid path format. Expected: /identifier/sitename/path', 400); 164 - } 165 166 - const identifier = pathParts[0]; 167 - const site = pathParts[1]; 168 - const filePath = pathParts.slice(2).join('/'); 169 170 - console.log('[Sites] Serving', { identifier, site, filePath }); 171 172 - // Additional validation: identifier must be a valid DID or handle format 173 - if (!identifier || identifier.length < 3 || identifier.includes('..') || identifier.includes('\0')) { 174 - return c.text('Invalid identifier', 400); 175 - } 176 177 - // Validate site name (rkey) 178 - if (!isValidRkey(site)) { 179 - return c.text('Invalid site name', 400); 180 - } 181 182 - // Resolve identifier to DID 183 - const did = await resolveDid(identifier); 184 - if (!did) { 185 - return c.text('Invalid identifier', 400); 186 } 187 188 - // Ensure site is cached 189 - const cached = await ensureSiteCached(did, site); 190 - if (!cached) { 191 - return c.text('Site not found', 404); 192 - } 193 194 - // Serve with HTML path rewriting to handle absolute paths 195 - const basePath = `/${identifier}/${site}/`; 196 - return serveFromCacheWithRewrite(did, site, filePath, basePath); 197 - } 198 199 - // Check if this is a DNS hash subdomain 200 - const dnsMatch = hostname.match(/^([a-f0-9]{16})\.dns\.(.+)$/); 201 - if (dnsMatch) { 202 - const hash = dnsMatch[1]; 203 - const baseDomain = dnsMatch[2]; 204 205 - console.log('[DNS Hash] Looking up', { hash, baseDomain }); 206 207 - if (baseDomain !== BASE_HOST) { 208 - return c.text('Invalid base domain', 400); 209 - } 210 211 - const customDomain = await getCustomDomainByHash(hash); 212 - if (!customDomain) { 213 - return c.text('Custom domain not found or not verified', 404); 214 } 215 216 - const rkey = customDomain.rkey || 'self'; 217 - if (!isValidRkey(rkey)) { 218 - return c.text('Invalid site configuration', 500); 219 - } 220 221 - const cached = await ensureSiteCached(customDomain.did, rkey); 222 - if (!cached) { 223 - return c.text('Site not found', 404); 224 - } 225 226 - return serveFromCache(customDomain.did, rkey, path); 227 - } 228 229 - // Route 2: Registered subdomains - /*.wisp.place/* 230 - if (hostname.endsWith(`.${BASE_HOST}`)) { 231 - const subdomain = hostname.replace(`.${BASE_HOST}`, ''); 232 233 - console.log('[Subdomain] Looking up', { subdomain, fullDomain: hostname }); 234 235 - const domainInfo = await getWispDomain(hostname); 236 - if (!domainInfo) { 237 - return c.text('Subdomain not registered', 404); 238 } 239 240 - const rkey = domainInfo.rkey || 'self'; 241 if (!isValidRkey(rkey)) { 242 - return c.text('Invalid site configuration', 500); 243 } 244 245 - const cached = await ensureSiteCached(domainInfo.did, rkey); 246 if (!cached) { 247 - return c.text('Site not found', 404); 248 } 249 250 - return serveFromCache(domainInfo.did, rkey, path); 251 - } 252 - 253 - // Route 1: Custom domains - /* 254 - console.log('[Custom Domain] Looking up', { hostname }); 255 - 256 - const customDomain = await getCustomDomain(hostname); 257 - if (!customDomain) { 258 - return c.text('Custom domain not found or not verified', 404); 259 - } 260 - 261 - const rkey = customDomain.rkey || 'self'; 262 - if (!isValidRkey(rkey)) { 263 - return c.text('Invalid site configuration', 500); 264 - } 265 - 266 - const cached = await ensureSiteCached(customDomain.did, rkey); 267 - if (!cached) { 268 - return c.text('Site not found', 404); 269 - } 270 - 271 - return serveFromCache(customDomain.did, rkey, path); 272 - }); 273 274 export default app;
··· 1 + import { Elysia } from 'elysia'; 2 + import { node } from '@elysiajs/node' 3 + import { opentelemetry } from '@elysiajs/opentelemetry'; 4 import { getWispDomain, getCustomDomain, getCustomDomainByHash } from './lib/db'; 5 import { resolveDid, getPdsForDid, fetchSiteRecord, downloadAndCacheSite, getCachedFilePath, isCached, sanitizePath } from './lib/utils'; 6 import { rewriteHtmlPaths, isHtmlContent } from './lib/html-rewriter'; 7 import { existsSync, readFileSync } from 'fs'; 8 import { lookup } from 'mime-types'; 9 10 const BASE_HOST = process.env.BASE_HOST || 'wisp.place'; 11 ··· 34 35 if (existsSync(cachedFile)) { 36 const content = readFileSync(cachedFile); 37 + const metaFile = `${cachedFile}.meta`; 38 + 39 + // Check if file has compression metadata 40 + if (existsSync(metaFile)) { 41 + const meta = JSON.parse(readFileSync(metaFile, 'utf-8')); 42 + if (meta.encoding === 'gzip' && meta.mimeType) { 43 + // Serve gzipped content with proper headers 44 + return new Response(content, { 45 + headers: { 46 + 'Content-Type': meta.mimeType, 47 + 'Content-Encoding': 'gzip', 48 + }, 49 + }); 50 + } 51 + } 52 + 53 + // Serve non-compressed files normally 54 const mimeType = lookup(cachedFile) || 'application/octet-stream'; 55 return new Response(content, { 56 headers: { ··· 64 const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`); 65 if (existsSync(indexFile)) { 66 const content = readFileSync(indexFile); 67 + const metaFile = `${indexFile}.meta`; 68 + 69 + // Check if file has compression metadata 70 + if (existsSync(metaFile)) { 71 + const meta = JSON.parse(readFileSync(metaFile, 'utf-8')); 72 + if (meta.encoding === 'gzip' && meta.mimeType) { 73 + return new Response(content, { 74 + headers: { 75 + 'Content-Type': meta.mimeType, 76 + 'Content-Encoding': 'gzip', 77 + }, 78 + }); 79 + } 80 + } 81 + 82 return new Response(content, { 83 headers: { 84 'Content-Type': 'text/html; charset=utf-8', ··· 106 const cachedFile = getCachedFilePath(did, rkey, requestPath); 107 108 if (existsSync(cachedFile)) { 109 + const metaFile = `${cachedFile}.meta`; 110 + let mimeType = lookup(cachedFile) || 'application/octet-stream'; 111 + let isGzipped = false; 112 + 113 + // Check if file has compression metadata 114 + if (existsSync(metaFile)) { 115 + const meta = JSON.parse(readFileSync(metaFile, 'utf-8')); 116 + if (meta.encoding === 'gzip' && meta.mimeType) { 117 + mimeType = meta.mimeType; 118 + isGzipped = true; 119 + } 120 + } 121 122 // Check if this is HTML content that needs rewriting 123 + // Note: For gzipped HTML with path rewriting, we need to decompress, rewrite, and serve uncompressed 124 + // This is a trade-off for the sites.wisp.place domain which needs path rewriting 125 if (isHtmlContent(requestPath, mimeType)) { 126 + let content: string; 127 + if (isGzipped) { 128 + const { gunzipSync } = await import('zlib'); 129 + const compressed = readFileSync(cachedFile); 130 + content = gunzipSync(compressed).toString('utf-8'); 131 + } else { 132 + content = readFileSync(cachedFile, 'utf-8'); 133 + } 134 const rewritten = rewriteHtmlPaths(content, basePath); 135 return new Response(rewritten, { 136 headers: { ··· 139 }); 140 } 141 142 + // Non-HTML files: serve gzipped content as-is with proper headers 143 const content = readFileSync(cachedFile); 144 + if (isGzipped) { 145 + return new Response(content, { 146 + headers: { 147 + 'Content-Type': mimeType, 148 + 'Content-Encoding': 'gzip', 149 + }, 150 + }); 151 + } 152 return new Response(content, { 153 headers: { 154 'Content-Type': mimeType, ··· 160 if (!requestPath.includes('.')) { 161 const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`); 162 if (existsSync(indexFile)) { 163 + const metaFile = `${indexFile}.meta`; 164 + let isGzipped = false; 165 + 166 + if (existsSync(metaFile)) { 167 + const meta = JSON.parse(readFileSync(metaFile, 'utf-8')); 168 + if (meta.encoding === 'gzip') { 169 + isGzipped = true; 170 + } 171 + } 172 + 173 + // HTML needs path rewriting, so decompress if needed 174 + let content: string; 175 + if (isGzipped) { 176 + const { gunzipSync } = await import('zlib'); 177 + const compressed = readFileSync(indexFile); 178 + content = gunzipSync(compressed).toString('utf-8'); 179 + } else { 180 + content = readFileSync(indexFile, 'utf-8'); 181 + } 182 const rewritten = rewriteHtmlPaths(content, basePath); 183 return new Response(rewritten, { 184 headers: { ··· 219 } 220 } 221 222 + const app = new Elysia({ adapter: node() }) 223 + .use(opentelemetry()) 224 + .get('/*', async ({ request, set }) => { 225 + const url = new URL(request.url); 226 + const hostname = request.headers.get('host') || ''; 227 + const rawPath = url.pathname.replace(/^\//, ''); 228 + const path = sanitizePath(rawPath); 229 230 + // Check if this is sites.wisp.place subdomain 231 + if (hostname === `sites.${BASE_HOST}` || hostname === `sites.${BASE_HOST}:${process.env.PORT || 3000}`) { 232 + // Sanitize the path FIRST to prevent path traversal 233 + const sanitizedFullPath = sanitizePath(rawPath); 234 235 + // Extract identifier and site from sanitized path: did:plc:123abc/sitename/file.html 236 + const pathParts = sanitizedFullPath.split('/'); 237 + if (pathParts.length < 2) { 238 + set.status = 400; 239 + return 'Invalid path format. Expected: /identifier/sitename/path'; 240 + } 241 242 + const identifier = pathParts[0]; 243 + const site = pathParts[1]; 244 + const filePath = pathParts.slice(2).join('/'); 245 246 + // Additional validation: identifier must be a valid DID or handle format 247 + if (!identifier || identifier.length < 3 || identifier.includes('..') || identifier.includes('\0')) { 248 + set.status = 400; 249 + return 'Invalid identifier'; 250 + } 251 252 + // Validate site name (rkey) 253 + if (!isValidRkey(site)) { 254 + set.status = 400; 255 + return 'Invalid site name'; 256 + } 257 258 + // Resolve identifier to DID 259 + const did = await resolveDid(identifier); 260 + if (!did) { 261 + set.status = 400; 262 + return 'Invalid identifier'; 263 + } 264 265 + // Ensure site is cached 266 + const cached = await ensureSiteCached(did, site); 267 + if (!cached) { 268 + set.status = 404; 269 + return 'Site not found'; 270 + } 271 272 + // Serve with HTML path rewriting to handle absolute paths 273 + const basePath = `/${identifier}/${site}/`; 274 + return serveFromCacheWithRewrite(did, site, filePath, basePath); 275 } 276 277 + // Check if this is a DNS hash subdomain 278 + const dnsMatch = hostname.match(/^([a-f0-9]{16})\.dns\.(.+)$/); 279 + if (dnsMatch) { 280 + const hash = dnsMatch[1]; 281 + const baseDomain = dnsMatch[2]; 282 283 + if (baseDomain !== BASE_HOST) { 284 + set.status = 400; 285 + return 'Invalid base domain'; 286 + } 287 288 + const customDomain = await getCustomDomainByHash(hash); 289 + if (!customDomain) { 290 + set.status = 404; 291 + return 'Custom domain not found or not verified'; 292 + } 293 294 + const rkey = customDomain.rkey || 'self'; 295 + if (!isValidRkey(rkey)) { 296 + set.status = 500; 297 + return 'Invalid site configuration'; 298 + } 299 300 + const cached = await ensureSiteCached(customDomain.did, rkey); 301 + if (!cached) { 302 + set.status = 404; 303 + return 'Site not found'; 304 + } 305 306 + return serveFromCache(customDomain.did, rkey, path); 307 } 308 309 + // Route 2: Registered subdomains - /*.wisp.place/* 310 + if (hostname.endsWith(`.${BASE_HOST}`)) { 311 + const subdomain = hostname.replace(`.${BASE_HOST}`, ''); 312 313 + const domainInfo = await getWispDomain(hostname); 314 + if (!domainInfo) { 315 + set.status = 404; 316 + return 'Subdomain not registered'; 317 + } 318 319 + const rkey = domainInfo.rkey || 'self'; 320 + if (!isValidRkey(rkey)) { 321 + set.status = 500; 322 + return 'Invalid site configuration'; 323 + } 324 325 + const cached = await ensureSiteCached(domainInfo.did, rkey); 326 + if (!cached) { 327 + set.status = 404; 328 + return 'Site not found'; 329 + } 330 331 + return serveFromCache(domainInfo.did, rkey, path); 332 + } 333 334 + // Route 1: Custom domains - /* 335 + const customDomain = await getCustomDomain(hostname); 336 + if (!customDomain) { 337 + set.status = 404; 338 + return 'Custom domain not found or not verified'; 339 } 340 341 + const rkey = customDomain.rkey || 'self'; 342 if (!isValidRkey(rkey)) { 343 + set.status = 500; 344 + return 'Invalid site configuration'; 345 } 346 347 + const cached = await ensureSiteCached(customDomain.did, rkey); 348 if (!cached) { 349 + set.status = 404; 350 + return 'Site not found'; 351 } 352 353 + return serveFromCache(customDomain.did, rkey, path); 354 + }); 355 356 export default app;
+4 -1
lexicons/fs.json
··· 21 "required": ["type", "blob"], 22 "properties": { 23 "type": { "type": "string", "const": "file" }, 24 - "blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000, "description": "Content blob ref" } 25 } 26 }, 27 "directory": {
··· 21 "required": ["type", "blob"], 22 "properties": { 23 "type": { "type": "string", "const": "file" }, 24 + "blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000, "description": "Content blob ref" }, 25 + "encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" }, 26 + "mimeType": { "type": "string", "description": "Original MIME type before compression" }, 27 + "base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" } 28 } 29 }, 30 "directory": {
+3 -1
package.json
··· 4 "scripts": { 5 "test": "echo \"Error: no test specified\" && exit 1", 6 "dev": "bun run --watch src/index.ts", 7 "build": "bun build --compile --target bun --outfile server src/index.ts" 8 }, 9 "dependencies": { ··· 31 "tailwind-merge": "^3.3.1", 32 "tailwindcss": "4", 33 "tw-animate-css": "^1.4.0", 34 - "typescript": "^5.9.3" 35 }, 36 "devDependencies": { 37 "@types/react": "^19.2.2",
··· 4 "scripts": { 5 "test": "echo \"Error: no test specified\" && exit 1", 6 "dev": "bun run --watch src/index.ts", 7 + "start": "bun run src/index.ts", 8 "build": "bun build --compile --target bun --outfile server src/index.ts" 9 }, 10 "dependencies": { ··· 32 "tailwind-merge": "^3.3.1", 33 "tailwindcss": "4", 34 "tw-animate-css": "^1.4.0", 35 + "typescript": "^5.9.3", 36 + "zlib": "^1.0.5" 37 }, 38 "devDependencies": { 39 "@types/react": "^19.2.2",
+14
src/lexicon/lexicons.ts
··· 54 maxSize: 1000000, 55 description: 'Content blob ref', 56 }, 57 }, 58 }, 59 directory: {
··· 54 maxSize: 1000000, 55 description: 'Content blob ref', 56 }, 57 + encoding: { 58 + type: 'string', 59 + enum: ['gzip'], 60 + description: 'Content encoding (e.g., gzip for compressed files)', 61 + }, 62 + mimeType: { 63 + type: 'string', 64 + description: 'Original MIME type before compression', 65 + }, 66 + base64: { 67 + type: 'boolean', 68 + description: 69 + 'True if blob content is base64-encoded (used to bypass PDS content sniffing)', 70 + }, 71 }, 72 }, 73 directory: {
+6
src/lexicon/types/place/wisp/fs.ts
··· 34 type: 'file' 35 /** Content blob ref */ 36 blob: BlobRef 37 } 38 39 const hashFile = 'file'
··· 34 type: 'file' 35 /** Content blob ref */ 36 blob: BlobRef 37 + /** Content encoding (e.g., gzip for compressed files) */ 38 + encoding?: 'gzip' 39 + /** Original MIME type before compression */ 40 + mimeType?: string 41 + /** True if blob content is base64-encoded (used to bypass PDS content sniffing) */ 42 + base64?: boolean 43 } 44 45 const hashFile = 'file'
+41 -2
src/lib/wisp-utils.ts
··· 1 import type { BlobRef } from "@atproto/api"; 2 import type { Record, Directory, File, Entry } from "../lexicon/types/place/wisp/fs"; 3 import { validateRecord } from "../lexicon/types/place/wisp/fs"; 4 5 export interface UploadedFile { 6 name: string; 7 content: Buffer; 8 mimeType: string; 9 size: number; 10 } 11 12 export interface FileUploadResult { 13 hash: string; 14 blobRef: BlobRef; 15 } 16 17 export interface ProcessedDirectory { 18 directory: Directory; 19 fileCount: number; 20 } 21 22 /** ··· 168 }); 169 170 if (fileIndex !== -1 && uploadResults[fileIndex]) { 171 - const blobRef = uploadResults[fileIndex].blobRef; 172 173 return { 174 ...entry, 175 node: { 176 $type: 'place.wisp.fs#file' as const, 177 type: 'file' as const, 178 - blob: blobRef 179 } 180 }; 181 } else {
··· 1 import type { BlobRef } from "@atproto/api"; 2 import type { Record, Directory, File, Entry } from "../lexicon/types/place/wisp/fs"; 3 import { validateRecord } from "../lexicon/types/place/wisp/fs"; 4 + import { gzipSync } from 'zlib'; 5 6 export interface UploadedFile { 7 name: string; 8 content: Buffer; 9 mimeType: string; 10 size: number; 11 + compressed?: boolean; 12 + originalMimeType?: string; 13 } 14 15 export interface FileUploadResult { 16 hash: string; 17 blobRef: BlobRef; 18 + encoding?: 'gzip'; 19 + mimeType?: string; 20 + base64?: boolean; 21 } 22 23 export interface ProcessedDirectory { 24 directory: Directory; 25 fileCount: number; 26 + } 27 + 28 + /** 29 + * Determine if a file should be gzip compressed based on its MIME type 30 + */ 31 + export function shouldCompressFile(mimeType: string): boolean { 32 + // Compress text-based files 33 + const compressibleTypes = [ 34 + 'text/html', 35 + 'text/css', 36 + 'text/javascript', 37 + 'application/javascript', 38 + 'application/json', 39 + 'image/svg+xml', 40 + 'text/xml', 41 + 'application/xml', 42 + 'text/plain', 43 + 'application/x-javascript' 44 + ]; 45 + 46 + // Check if mime type starts with any compressible type 47 + return compressibleTypes.some(type => mimeType.startsWith(type)); 48 + } 49 + 50 + /** 51 + * Compress a file using gzip 52 + */ 53 + export function compressFile(content: Buffer): Buffer { 54 + return gzipSync(content, { level: 9 }); 55 } 56 57 /** ··· 203 }); 204 205 if (fileIndex !== -1 && uploadResults[fileIndex]) { 206 + const result = uploadResults[fileIndex]; 207 + const blobRef = result.blobRef; 208 209 return { 210 ...entry, 211 node: { 212 $type: 'place.wisp.fs#file' as const, 213 type: 'file' as const, 214 + blob: blobRef, 215 + ...(result.encoding && { encoding: result.encoding }), 216 + ...(result.mimeType && { mimeType: result.mimeType }), 217 + ...(result.base64 && { base64: result.base64 }) 218 } 219 }; 220 } else {
+53 -12
src/routes/wisp.ts
··· 7 type FileUploadResult, 8 processUploadedFiles, 9 createManifest, 10 - updateFileBlobs 11 } from '../lib/wisp-utils' 12 import { upsertSite } from '../lib/db' 13 import { logger } from '../lib/logger' ··· 164 } 165 166 const arrayBuffer = await file.arrayBuffer(); 167 - uploadedFiles.push({ 168 - name: file.name, 169 - content: Buffer.from(arrayBuffer), 170 - mimeType: 'application/octet-stream', 171 - size: file.size 172 - }); 173 } 174 175 // Check total size limit (300MB) ··· 226 // Process files into directory structure 227 const { directory, fileCount } = processUploadedFiles(uploadedFiles); 228 229 - // Upload files as blobs in parallel (always as octet-stream) 230 const uploadPromises = uploadedFiles.map(async (file, i) => { 231 try { 232 const uploadResult = await agent.com.atproto.repo.uploadBlob( 233 file.content, 234 { 235 - encoding: 'application/octet-stream' 236 } 237 ); 238 239 - const sentMimeType = file.mimeType; 240 const returnedBlobRef = uploadResult.data.blob; 241 242 // Use the blob ref exactly as returned from PDS 243 return { 244 result: { 245 hash: returnedBlobRef.ref.toString(), 246 - blobRef: returnedBlobRef 247 }, 248 filePath: file.name, 249 - sentMimeType, 250 returnedMimeType: returnedBlobRef.mimeType 251 }; 252 } catch (uploadError) {
··· 7 type FileUploadResult, 8 processUploadedFiles, 9 createManifest, 10 + updateFileBlobs, 11 + shouldCompressFile, 12 + compressFile 13 } from '../lib/wisp-utils' 14 import { upsertSite } from '../lib/db' 15 import { logger } from '../lib/logger' ··· 166 } 167 168 const arrayBuffer = await file.arrayBuffer(); 169 + const originalContent = Buffer.from(arrayBuffer); 170 + const originalMimeType = file.type || 'application/octet-stream'; 171 + 172 + // Determine if we should compress this file 173 + const shouldCompress = shouldCompressFile(originalMimeType); 174 + 175 + if (shouldCompress) { 176 + const compressedContent = compressFile(originalContent); 177 + // Base64 encode the gzipped content to prevent PDS content sniffing 178 + const base64Content = Buffer.from(compressedContent.toString('base64'), 'utf-8'); 179 + const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1); 180 + logger.info(`[Wisp] Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${base64Content.length} bytes`); 181 + 182 + uploadedFiles.push({ 183 + name: file.name, 184 + content: base64Content, 185 + mimeType: originalMimeType, 186 + size: base64Content.length, 187 + compressed: true, 188 + originalMimeType 189 + }); 190 + } else { 191 + uploadedFiles.push({ 192 + name: file.name, 193 + content: originalContent, 194 + mimeType: originalMimeType, 195 + size: file.size, 196 + compressed: false 197 + }); 198 + } 199 } 200 201 // Check total size limit (300MB) ··· 252 // Process files into directory structure 253 const { directory, fileCount } = processUploadedFiles(uploadedFiles); 254 255 + // Upload files as blobs in parallel 256 + // For compressed files, we upload as octet-stream and store the original MIME type in metadata 257 + // For text/html files, we also use octet-stream as a workaround for PDS image pipeline issues 258 const uploadPromises = uploadedFiles.map(async (file, i) => { 259 try { 260 + // If compressed, always upload as octet-stream 261 + // Otherwise, workaround: PDS incorrectly processes text/html through image pipeline 262 + const uploadMimeType = file.compressed || file.mimeType.startsWith('text/html') 263 + ? 'application/octet-stream' 264 + : file.mimeType; 265 + 266 + const compressionInfo = file.compressed ? ' (gzipped)' : ''; 267 + logger.info(`[Wisp] Uploading file: ${file.name} (original: ${file.mimeType}, sending as: ${uploadMimeType}, ${file.size} bytes${compressionInfo})`); 268 + 269 const uploadResult = await agent.com.atproto.repo.uploadBlob( 270 file.content, 271 { 272 + encoding: uploadMimeType 273 } 274 ); 275 276 const returnedBlobRef = uploadResult.data.blob; 277 278 // Use the blob ref exactly as returned from PDS 279 return { 280 result: { 281 hash: returnedBlobRef.ref.toString(), 282 + blobRef: returnedBlobRef, 283 + ...(file.compressed && { 284 + encoding: 'gzip' as const, 285 + mimeType: file.originalMimeType || file.mimeType, 286 + base64: true 287 + }) 288 }, 289 filePath: file.name, 290 + sentMimeType: file.mimeType, 291 returnedMimeType: returnedBlobRef.mimeType 292 }; 293 } catch (uploadError) {