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 1 # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 - 2 + .env 3 3 # dependencies 4 4 /node_modules 5 5 /.pnp
+6
bun.lock
··· 28 28 "tailwind-merge": "^3.3.1", 29 29 "tailwindcss": "4", 30 30 "tw-animate-css": "^1.4.0", 31 + "typescript": "^5.9.3", 32 + "zlib": "^1.0.5", 31 33 }, 32 34 "devDependencies": { 33 35 "@types/react": "^19.2.2", ··· 500 502 501 503 "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], 502 504 505 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 506 + 503 507 "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], 504 508 505 509 "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], ··· 523 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=="], 524 528 525 529 "yesno": ["yesno@0.4.0", "", {}, "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA=="], 530 + 531 + "zlib": ["zlib@1.0.5", "", {}, "sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w=="], 526 532 527 533 "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 528 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 12 "@atproto/lexicon": "^0.5.1", 13 13 "@atproto/sync": "^0.1.35", 14 14 "@atproto/xrpc": "^0.7.5", 15 - "@hono/node-server": "^1.13.7", 16 - "hono": "^4.6.14", 15 + "@elysiajs/opentelemetry": "latest", 16 + "elysia": "latest", 17 17 "mime-types": "^2.1.35", 18 18 "multiformats": "^13.4.1", 19 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'; 1 + import app from './server'; 2 + import { FirehoseWorker } from './lib/firehose'; 4 3 import { mkdirSync, existsSync } from 'fs'; 5 4 6 5 const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001; ··· 20 19 firehose.start(); 21 20 22 21 // Add health check endpoint 23 - app.get('/health', (c) => { 22 + app.get('/health', () => { 24 23 const firehoseHealth = firehose.getHealth(); 25 - return c.json({ 24 + return { 26 25 status: 'ok', 27 26 firehose: firehoseHealth, 28 - }); 27 + }; 29 28 }); 30 29 31 30 // Start HTTP server 32 - const server = serve({ 33 - port: PORT, 34 - fetch: app.fetch, 35 - }); 36 - 37 - console.log(` 31 + app.listen(PORT, () => { 32 + console.log(` 38 33 Wisp Hosting Service 39 34 40 35 Server: http://localhost:${PORT} ··· 42 37 Cache: ${CACHE_DIR} 43 38 Firehose: Connected to Firehose 44 39 `); 40 + }); 45 41 46 42 // Graceful shutdown 47 43 process.on('SIGINT', async () => { 48 44 console.log('\n🛑 Shutting down...'); 49 45 firehose.stop(); 50 - server.close(); 46 + app.stop(); 51 47 process.exit(0); 52 48 }); 53 49 54 50 process.on('SIGTERM', async () => { 55 51 console.log('\n🛑 Shutting down...'); 56 52 firehose.stop(); 57 - server.close(); 53 + app.stop(); 58 54 process.exit(0); 59 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 54 maxSize: 1000000, 55 55 description: 'Content blob ref', 56 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 + }, 57 71 }, 58 72 }, 59 73 directory: {
+6
hosting-service/src/lexicon/types/place/wisp/fs.ts
··· 34 34 type: 'file' 35 35 /** Content blob ref */ 36 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 37 43 } 38 44 39 45 const hashFile = 'file'
+98 -5
hosting-service/src/lib/db.ts
··· 21 21 verified: boolean; 22 22 } 23 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 + 24 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 25 87 const result = await sql<DomainLookup[]>` 26 - SELECT did, rkey FROM domains WHERE domain = ${domain.toLowerCase()} LIMIT 1 88 + SELECT did, rkey FROM domains WHERE domain = ${key} LIMIT 1 27 89 `; 28 - return result[0] || null; 90 + const data = result[0] || null; 91 + 92 + // Store in cache 93 + wispDomainCache.set(key, data); 94 + 95 + return data; 29 96 } 30 97 31 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 32 108 const result = await sql<CustomDomainLookup[]>` 33 109 SELECT id, domain, did, rkey, verified FROM custom_domains 34 - WHERE domain = ${domain.toLowerCase()} AND verified = true LIMIT 1 110 + WHERE domain = ${key} AND verified = true LIMIT 1 35 111 `; 36 - return result[0] || null; 112 + const data = result[0] || null; 113 + 114 + // Store in cache 115 + customDomainCache.set(key, data); 116 + 117 + return data; 37 118 } 38 119 39 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 40 128 const result = await sql<CustomDomainLookup[]>` 41 129 SELECT id, domain, did, rkey, verified FROM custom_domains 42 130 WHERE id = ${hash} AND verified = true LIMIT 1 43 131 `; 44 - return result[0] || null; 132 + const data = result[0] || null; 133 + 134 + // Store in cache 135 + customDomainHashCache.set(hash, data); 136 + 137 + return data; 45 138 } 46 139 47 140 export async function upsertSite(did: string, rkey: string, displayName?: string) {
+1 -1
hosting-service/src/lib/firehose.ts
··· 157 157 return; 158 158 } 159 159 160 - // Cache the record with verified CID 160 + // Cache the record with verified CID (uses atomic swap internally) 161 161 await downloadAndCacheSite(did, site, fsRecord, pdsEndpoint, verifiedCid); 162 162 163 163 // Upsert site to database
+29 -10
hosting-service/src/lib/html-rewriter.ts
··· 16 16 * Check if a path should be rewritten 17 17 */ 18 18 function shouldRewritePath(path: string): boolean { 19 - // Must start with / 20 - if (!path.startsWith('/')) return false; 19 + // Don't rewrite empty paths 20 + if (!path) return false; 21 21 22 - // Don't rewrite protocol-relative URLs 23 - if (path.startsWith('//')) return false; 22 + // Don't rewrite external URLs (http://, https://, //) 23 + if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('//')) { 24 + return false; 25 + } 24 26 25 - // Don't rewrite anchors 26 - if (path.startsWith('/#')) return false; 27 + // Don't rewrite data URIs or other schemes (except file paths) 28 + if (path.includes(':') && !path.startsWith('./') && !path.startsWith('../')) { 29 + return false; 30 + } 27 31 28 - // Don't rewrite data URIs or other schemes 29 - if (path.includes(':')) return false; 32 + // Don't rewrite pure anchors 33 + if (path.startsWith('#')) return false; 30 34 35 + // Rewrite absolute paths (/) and relative paths (./ or ../ or plain filenames) 31 36 return true; 32 37 } 33 38 ··· 39 44 return path; 40 45 } 41 46 42 - // Remove leading slash and prepend base path 43 - return basePath + path.slice(1); 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; 44 63 } 45 64 46 65 /**
+75 -13
hosting-service/src/lib/utils.ts
··· 1 1 import { AtpAgent } from '@atproto/api'; 2 2 import type { WispFsRecord, Directory, Entry, File } from './types'; 3 - import { existsSync, mkdirSync, readFileSync } from 'fs'; 4 - import { writeFile, readFile } from 'fs/promises'; 3 + import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs'; 4 + import { writeFile, readFile, rename } from 'fs/promises'; 5 5 import { safeFetchJson, safeFetchBlob } from './safe-fetch'; 6 6 import { CID } from 'multiformats/cid'; 7 7 ··· 153 153 throw new Error('Invalid record structure: root missing entries array'); 154 154 } 155 155 156 - await cacheFiles(did, rkey, record.root.entries, pdsEndpoint, ''); 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}`; 157 160 158 - await saveCacheMetadata(did, rkey, recordCid); 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 + } 159 199 } 160 200 161 201 async function cacheFiles( ··· 163 203 site: string, 164 204 entries: Entry[], 165 205 pdsEndpoint: string, 166 - pathPrefix: string 206 + pathPrefix: string, 207 + dirSuffix: string = '' 167 208 ): Promise<void> { 168 209 for (const entry of entries) { 169 210 const currentPath = pathPrefix ? `${pathPrefix}/${entry.name}` : entry.name; 170 211 const node = entry.node; 171 212 172 213 if ('type' in node && node.type === 'directory' && 'entries' in node) { 173 - await cacheFiles(did, site, node.entries, pdsEndpoint, currentPath); 214 + await cacheFiles(did, site, node.entries, pdsEndpoint, currentPath, dirSuffix); 174 215 } else if ('type' in node && node.type === 'file' && 'blob' in node) { 175 - await cacheFileBlob(did, site, currentPath, node.blob, pdsEndpoint); 216 + const fileNode = node as File; 217 + await cacheFileBlob(did, site, currentPath, fileNode.blob, pdsEndpoint, fileNode.encoding, fileNode.mimeType, fileNode.base64, dirSuffix); 176 218 } 177 219 } 178 220 } ··· 182 224 site: string, 183 225 filePath: string, 184 226 blobRef: any, 185 - pdsEndpoint: string 227 + pdsEndpoint: string, 228 + encoding?: 'gzip', 229 + mimeType?: string, 230 + base64?: boolean, 231 + dirSuffix: string = '' 186 232 ): Promise<void> { 187 233 const cid = extractBlobCid(blobRef); 188 234 if (!cid) { ··· 193 239 const blobUrl = `${pdsEndpoint}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`; 194 240 195 241 // Allow up to 100MB per file blob 196 - const content = await safeFetchBlob(blobUrl, { maxSize: 100 * 1024 * 1024 }); 242 + let content = await safeFetchBlob(blobUrl, { maxSize: 100 * 1024 * 1024 }); 197 243 198 - const cacheFile = `${CACHE_DIR}/${did}/${site}/${filePath}`; 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}`; 199 253 const fileDir = cacheFile.substring(0, cacheFile.lastIndexOf('/')); 200 254 201 255 if (fileDir && !existsSync(fileDir)) { ··· 203 257 } 204 258 205 259 await writeFile(cacheFile, content); 206 - console.log('Cached file', filePath, content.length, 'bytes'); 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 + } 207 269 } 208 270 209 271 /** ··· 238 300 return existsSync(`${CACHE_DIR}/${did}/${site}`); 239 301 } 240 302 241 - async function saveCacheMetadata(did: string, rkey: string, recordCid: string): Promise<void> { 303 + async function saveCacheMetadata(did: string, rkey: string, recordCid: string, dirSuffix: string = ''): Promise<void> { 242 304 const metadata: CacheMetadata = { 243 305 recordCid, 244 306 cachedAt: Date.now(), ··· 246 308 rkey 247 309 }; 248 310 249 - const metadataPath = `${CACHE_DIR}/${did}/${rkey}/.metadata.json`; 311 + const metadataPath = `${CACHE_DIR}/${did}/${rkey}${dirSuffix}/.metadata.json`; 250 312 const metadataDir = metadataPath.substring(0, metadataPath.lastIndexOf('/')); 251 313 252 314 if (!existsSync(metadataDir)) {
+188 -106
hosting-service/src/server.ts
··· 1 - import { Hono } from 'hono'; 1 + import { Elysia } from 'elysia'; 2 + import { node } from '@elysiajs/node' 3 + import { opentelemetry } from '@elysiajs/opentelemetry'; 2 4 import { getWispDomain, getCustomDomain, getCustomDomainByHash } from './lib/db'; 3 5 import { resolveDid, getPdsForDid, fetchSiteRecord, downloadAndCacheSite, getCachedFilePath, isCached, sanitizePath } from './lib/utils'; 4 6 import { rewriteHtmlPaths, isHtmlContent } from './lib/html-rewriter'; 5 7 import { existsSync, readFileSync } from 'fs'; 6 8 import { lookup } from 'mime-types'; 7 - 8 - const app = new Hono(); 9 9 10 10 const BASE_HOST = process.env.BASE_HOST || 'wisp.place'; 11 11 ··· 34 34 35 35 if (existsSync(cachedFile)) { 36 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 37 54 const mimeType = lookup(cachedFile) || 'application/octet-stream'; 38 55 return new Response(content, { 39 56 headers: { ··· 47 64 const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`); 48 65 if (existsSync(indexFile)) { 49 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 + 50 82 return new Response(content, { 51 83 headers: { 52 84 'Content-Type': 'text/html; charset=utf-8', ··· 74 106 const cachedFile = getCachedFilePath(did, rkey, requestPath); 75 107 76 108 if (existsSync(cachedFile)) { 77 - const mimeType = lookup(cachedFile) || 'application/octet-stream'; 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 + } 78 121 79 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 80 125 if (isHtmlContent(requestPath, mimeType)) { 81 - const content = readFileSync(cachedFile, 'utf-8'); 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 + } 82 134 const rewritten = rewriteHtmlPaths(content, basePath); 83 135 return new Response(rewritten, { 84 136 headers: { ··· 87 139 }); 88 140 } 89 141 90 - // Non-HTML files served with proper MIME type 142 + // Non-HTML files: serve gzipped content as-is with proper headers 91 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 + } 92 152 return new Response(content, { 93 153 headers: { 94 154 'Content-Type': mimeType, ··· 100 160 if (!requestPath.includes('.')) { 101 161 const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`); 102 162 if (existsSync(indexFile)) { 103 - const content = readFileSync(indexFile, 'utf-8'); 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 + } 104 182 const rewritten = rewriteHtmlPaths(content, basePath); 105 183 return new Response(rewritten, { 106 184 headers: { ··· 141 219 } 142 220 } 143 221 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 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); 146 229 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); 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); 152 234 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); 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 + } 159 241 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 - } 242 + const identifier = pathParts[0]; 243 + const site = pathParts[1]; 244 + const filePath = pathParts.slice(2).join('/'); 165 245 166 - const identifier = pathParts[0]; 167 - const site = pathParts[1]; 168 - const filePath = pathParts.slice(2).join('/'); 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 + } 169 251 170 - console.log('[Sites] Serving', { identifier, site, filePath }); 252 + // Validate site name (rkey) 253 + if (!isValidRkey(site)) { 254 + set.status = 400; 255 + return 'Invalid site name'; 256 + } 171 257 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 - } 258 + // Resolve identifier to DID 259 + const did = await resolveDid(identifier); 260 + if (!did) { 261 + set.status = 400; 262 + return 'Invalid identifier'; 263 + } 176 264 177 - // Validate site name (rkey) 178 - if (!isValidRkey(site)) { 179 - return c.text('Invalid site name', 400); 180 - } 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 + } 181 271 182 - // Resolve identifier to DID 183 - const did = await resolveDid(identifier); 184 - if (!did) { 185 - return c.text('Invalid identifier', 400); 272 + // Serve with HTML path rewriting to handle absolute paths 273 + const basePath = `/${identifier}/${site}/`; 274 + return serveFromCacheWithRewrite(did, site, filePath, basePath); 186 275 } 187 276 188 - // Ensure site is cached 189 - const cached = await ensureSiteCached(did, site); 190 - if (!cached) { 191 - return c.text('Site not found', 404); 192 - } 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]; 193 282 194 - // Serve with HTML path rewriting to handle absolute paths 195 - const basePath = `/${identifier}/${site}/`; 196 - return serveFromCacheWithRewrite(did, site, filePath, basePath); 197 - } 283 + if (baseDomain !== BASE_HOST) { 284 + set.status = 400; 285 + return 'Invalid base domain'; 286 + } 198 287 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]; 288 + const customDomain = await getCustomDomainByHash(hash); 289 + if (!customDomain) { 290 + set.status = 404; 291 + return 'Custom domain not found or not verified'; 292 + } 204 293 205 - console.log('[DNS Hash] Looking up', { hash, baseDomain }); 294 + const rkey = customDomain.rkey || 'self'; 295 + if (!isValidRkey(rkey)) { 296 + set.status = 500; 297 + return 'Invalid site configuration'; 298 + } 206 299 207 - if (baseDomain !== BASE_HOST) { 208 - return c.text('Invalid base domain', 400); 209 - } 300 + const cached = await ensureSiteCached(customDomain.did, rkey); 301 + if (!cached) { 302 + set.status = 404; 303 + return 'Site not found'; 304 + } 210 305 211 - const customDomain = await getCustomDomainByHash(hash); 212 - if (!customDomain) { 213 - return c.text('Custom domain not found or not verified', 404); 306 + return serveFromCache(customDomain.did, rkey, path); 214 307 } 215 308 216 - const rkey = customDomain.rkey || 'self'; 217 - if (!isValidRkey(rkey)) { 218 - return c.text('Invalid site configuration', 500); 219 - } 309 + // Route 2: Registered subdomains - /*.wisp.place/* 310 + if (hostname.endsWith(`.${BASE_HOST}`)) { 311 + const subdomain = hostname.replace(`.${BASE_HOST}`, ''); 220 312 221 - const cached = await ensureSiteCached(customDomain.did, rkey); 222 - if (!cached) { 223 - return c.text('Site not found', 404); 224 - } 313 + const domainInfo = await getWispDomain(hostname); 314 + if (!domainInfo) { 315 + set.status = 404; 316 + return 'Subdomain not registered'; 317 + } 225 318 226 - return serveFromCache(customDomain.did, rkey, path); 227 - } 319 + const rkey = domainInfo.rkey || 'self'; 320 + if (!isValidRkey(rkey)) { 321 + set.status = 500; 322 + return 'Invalid site configuration'; 323 + } 228 324 229 - // Route 2: Registered subdomains - /*.wisp.place/* 230 - if (hostname.endsWith(`.${BASE_HOST}`)) { 231 - const subdomain = hostname.replace(`.${BASE_HOST}`, ''); 325 + const cached = await ensureSiteCached(domainInfo.did, rkey); 326 + if (!cached) { 327 + set.status = 404; 328 + return 'Site not found'; 329 + } 232 330 233 - console.log('[Subdomain] Looking up', { subdomain, fullDomain: hostname }); 331 + return serveFromCache(domainInfo.did, rkey, path); 332 + } 234 333 235 - const domainInfo = await getWispDomain(hostname); 236 - if (!domainInfo) { 237 - return c.text('Subdomain not registered', 404); 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'; 238 339 } 239 340 240 - const rkey = domainInfo.rkey || 'self'; 341 + const rkey = customDomain.rkey || 'self'; 241 342 if (!isValidRkey(rkey)) { 242 - return c.text('Invalid site configuration', 500); 343 + set.status = 500; 344 + return 'Invalid site configuration'; 243 345 } 244 346 245 - const cached = await ensureSiteCached(domainInfo.did, rkey); 347 + const cached = await ensureSiteCached(customDomain.did, rkey); 246 348 if (!cached) { 247 - return c.text('Site not found', 404); 349 + set.status = 404; 350 + return 'Site not found'; 248 351 } 249 352 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 - }); 353 + return serveFromCache(customDomain.did, rkey, path); 354 + }); 273 355 274 356 export default app;
+4 -1
lexicons/fs.json
··· 21 21 "required": ["type", "blob"], 22 22 "properties": { 23 23 "type": { "type": "string", "const": "file" }, 24 - "blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000, "description": "Content blob ref" } 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)" } 25 28 } 26 29 }, 27 30 "directory": {
+3 -1
package.json
··· 4 4 "scripts": { 5 5 "test": "echo \"Error: no test specified\" && exit 1", 6 6 "dev": "bun run --watch src/index.ts", 7 + "start": "bun run src/index.ts", 7 8 "build": "bun build --compile --target bun --outfile server src/index.ts" 8 9 }, 9 10 "dependencies": { ··· 31 32 "tailwind-merge": "^3.3.1", 32 33 "tailwindcss": "4", 33 34 "tw-animate-css": "^1.4.0", 34 - "typescript": "^5.9.3" 35 + "typescript": "^5.9.3", 36 + "zlib": "^1.0.5" 35 37 }, 36 38 "devDependencies": { 37 39 "@types/react": "^19.2.2",
+14
src/lexicon/lexicons.ts
··· 54 54 maxSize: 1000000, 55 55 description: 'Content blob ref', 56 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 + }, 57 71 }, 58 72 }, 59 73 directory: {
+6
src/lexicon/types/place/wisp/fs.ts
··· 34 34 type: 'file' 35 35 /** Content blob ref */ 36 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 37 43 } 38 44 39 45 const hashFile = 'file'
+41 -2
src/lib/wisp-utils.ts
··· 1 1 import type { BlobRef } from "@atproto/api"; 2 2 import type { Record, Directory, File, Entry } from "../lexicon/types/place/wisp/fs"; 3 3 import { validateRecord } from "../lexicon/types/place/wisp/fs"; 4 + import { gzipSync } from 'zlib'; 4 5 5 6 export interface UploadedFile { 6 7 name: string; 7 8 content: Buffer; 8 9 mimeType: string; 9 10 size: number; 11 + compressed?: boolean; 12 + originalMimeType?: string; 10 13 } 11 14 12 15 export interface FileUploadResult { 13 16 hash: string; 14 17 blobRef: BlobRef; 18 + encoding?: 'gzip'; 19 + mimeType?: string; 20 + base64?: boolean; 15 21 } 16 22 17 23 export interface ProcessedDirectory { 18 24 directory: Directory; 19 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 }); 20 55 } 21 56 22 57 /** ··· 168 203 }); 169 204 170 205 if (fileIndex !== -1 && uploadResults[fileIndex]) { 171 - const blobRef = uploadResults[fileIndex].blobRef; 206 + const result = uploadResults[fileIndex]; 207 + const blobRef = result.blobRef; 172 208 173 209 return { 174 210 ...entry, 175 211 node: { 176 212 $type: 'place.wisp.fs#file' as const, 177 213 type: 'file' as const, 178 - blob: blobRef 214 + blob: blobRef, 215 + ...(result.encoding && { encoding: result.encoding }), 216 + ...(result.mimeType && { mimeType: result.mimeType }), 217 + ...(result.base64 && { base64: result.base64 }) 179 218 } 180 219 }; 181 220 } else {
+53 -12
src/routes/wisp.ts
··· 7 7 type FileUploadResult, 8 8 processUploadedFiles, 9 9 createManifest, 10 - updateFileBlobs 10 + updateFileBlobs, 11 + shouldCompressFile, 12 + compressFile 11 13 } from '../lib/wisp-utils' 12 14 import { upsertSite } from '../lib/db' 13 15 import { logger } from '../lib/logger' ··· 164 166 } 165 167 166 168 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 - }); 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 + } 173 199 } 174 200 175 201 // Check total size limit (300MB) ··· 226 252 // Process files into directory structure 227 253 const { directory, fileCount } = processUploadedFiles(uploadedFiles); 228 254 229 - // Upload files as blobs in parallel (always as octet-stream) 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 230 258 const uploadPromises = uploadedFiles.map(async (file, i) => { 231 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 + 232 269 const uploadResult = await agent.com.atproto.repo.uploadBlob( 233 270 file.content, 234 271 { 235 - encoding: 'application/octet-stream' 272 + encoding: uploadMimeType 236 273 } 237 274 ); 238 275 239 - const sentMimeType = file.mimeType; 240 276 const returnedBlobRef = uploadResult.data.blob; 241 277 242 278 // Use the blob ref exactly as returned from PDS 243 279 return { 244 280 result: { 245 281 hash: returnedBlobRef.ref.toString(), 246 - blobRef: returnedBlobRef 282 + blobRef: returnedBlobRef, 283 + ...(file.compressed && { 284 + encoding: 'gzip' as const, 285 + mimeType: file.originalMimeType || file.mimeType, 286 + base64: true 287 + }) 247 288 }, 248 289 filePath: file.name, 249 - sentMimeType, 290 + sentMimeType: file.mimeType, 250 291 returnedMimeType: returnedBlobRef.mimeType 251 292 }; 252 293 } catch (uploadError) {