From 960f9ff7828d970b1ec4598f6bc7ea7d1ffcba6f Mon Sep 17 00:00:00 2001 From: "nekomimi.pet" Date: Wed, 29 Oct 2025 13:42:03 -0400 Subject: [PATCH] gzip and base64 files to get around pds mimetype sniffing as well as serve files as gzip's for faster serving --- .gitignore | 2 +- bun.lock | 6 + hosting-service/bun.lock | 553 ++++++++++++++++++ hosting-service/package.json | 4 +- hosting-service/src/index.ts | 24 +- hosting-service/src/lexicon/index.ts | 44 ++ hosting-service/src/lexicon/lexicons.ts | 14 + .../src/lexicon/types/place/wisp/fs.ts | 6 + hosting-service/src/lib/db.ts | 103 +++- hosting-service/src/lib/firehose.ts | 2 +- hosting-service/src/lib/html-rewriter.ts | 39 +- hosting-service/src/lib/utils.ts | 88 ++- hosting-service/src/server.ts | 318 ++++++---- lexicons/fs.json | 5 +- package.json | 4 +- src/lexicon/lexicons.ts | 14 + src/lexicon/types/place/wisp/fs.ts | 6 + src/lib/wisp-utils.ts | 43 +- src/routes/wisp.ts | 65 +- 19 files changed, 1160 insertions(+), 180 deletions(-) create mode 100644 hosting-service/bun.lock create mode 100644 hosting-service/src/lexicon/index.ts diff --git a/.gitignore b/.gitignore index 0749512..2f3a6d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - +.env # dependencies /node_modules /.pnp diff --git a/bun.lock b/bun.lock index f31d707..4319e81 100644 --- a/bun.lock +++ b/bun.lock @@ -28,6 +28,8 @@ "tailwind-merge": "^3.3.1", "tailwindcss": "4", "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "zlib": "^1.0.5", }, "devDependencies": { "@types/react": "^19.2.2", @@ -500,6 +502,8 @@ "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], @@ -524,6 +528,8 @@ "yesno": ["yesno@0.4.0", "", {}, "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA=="], + "zlib": ["zlib@1.0.5", "", {}, "sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w=="], + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], diff --git a/hosting-service/bun.lock b/hosting-service/bun.lock new file mode 100644 index 0000000..fa8bae8 --- /dev/null +++ b/hosting-service/bun.lock @@ -0,0 +1,553 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "wisp-hosting-service", + "dependencies": { + "@atproto/api": "^0.17.4", + "@atproto/identity": "^0.4.9", + "@atproto/lexicon": "^0.5.1", + "@atproto/sync": "^0.1.35", + "@atproto/xrpc": "^0.7.5", + "@elysiajs/node": "^1.4.1", + "@elysiajs/opentelemetry": "latest", + "elysia": "latest", + "mime-types": "^2.1.35", + "multiformats": "^13.4.1", + "postgres": "^3.4.5", + }, + "devDependencies": { + "@types/mime-types": "^2.1.4", + "@types/node": "^22.10.5", + "tsx": "^4.19.2", + }, + }, + }, + "packages": { + "@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" } }, ""], + + "@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" } }, ""], + + "@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" } }, ""], + + "@atproto/crypto": ["@atproto/crypto@0.4.4", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, ""], + + "@atproto/identity": ["@atproto/identity@0.4.9", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/crypto": "^0.4.4" } }, ""], + + "@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" } }, ""], + + "@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" } }, ""], + + "@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" } }, ""], + + "@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""], + + "@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""], + + "@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" } }, ""], + + "@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="], + + "@cbor-extract/cbor-extract-darwin-arm64": ["@cbor-extract/cbor-extract-darwin-arm64@2.2.0", "", { "os": "darwin", "cpu": "arm64" }, ""], + + "@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=="], + + "@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=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="], + + "@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=="], + + "@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=="], + + "@ipld/dag-cbor": ["@ipld/dag-cbor@7.0.3", "", { "dependencies": { "cborg": "^1.6.0", "multiformats": "^9.5.4" } }, ""], + + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + + "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, ""], + + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, ""], + + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q=="], + + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.0.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-IEkJGzK1A9v3/EHjXh3s2IiFc6L4jfK+lNgKVgUjeUJQRRhnVFMIO3TAvKwonm9O1HebCuoOt98v8bZW7oVQHA=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@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=="], + + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.37.0", "", {}, "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="], + + "@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=="], + + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + + "@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="], + + "@types/node": ["@types/node@22.18.12", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog=="], + + "@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="], + + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, ""], + + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, ""], + + "acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "array-flatten": ["array-flatten@1.1.1", "", {}, ""], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, ""], + + "await-lock": ["await-lock@2.2.2", "", {}, ""], + + "base64-js": ["base64-js@1.5.1", "", {}, ""], + + "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" } }, ""], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, ""], + + "bytes": ["bytes@3.1.2", "", {}, ""], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, ""], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, ""], + + "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" } }, ""], + + "cbor-x": ["cbor-x@1.6.0", "", { "optionalDependencies": { "cbor-extract": "^2.2.0" } }, ""], + + "cborg": ["cborg@1.10.2", "", { "bin": "cli.js" }, ""], + + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], + + "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=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, ""], + + "content-type": ["content-type@1.0.5", "", {}, ""], + + "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], + + "cookie-signature": ["cookie-signature@1.0.6", "", {}, ""], + + "crossws": ["crossws@0.4.1", "", { "peerDependencies": { "srvx": ">=0.7.1" }, "optionalPeers": ["srvx"] }, "sha512-E7WKBcHVhAVrY6JYD5kteNqVq1GSZxqGrdSiwXR9at+XHi43HJoCQKXcCczR5LBnBquFZPsB3o7HklulKoBU5w=="], + + "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, ""], + + "depd": ["depd@2.0.0", "", {}, ""], + + "destroy": ["destroy@1.2.0", "", {}, ""], + + "detect-libc": ["detect-libc@2.1.2", "", {}, ""], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, ""], + + "ee-first": ["ee-first@1.1.1", "", {}, ""], + + "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=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encodeurl": ["encodeurl@2.0.0", "", {}, ""], + + "es-define-property": ["es-define-property@1.0.1", "", {}, ""], + + "es-errors": ["es-errors@1.3.0", "", {}, ""], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, ""], + + "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=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-html": ["escape-html@1.0.3", "", {}, ""], + + "etag": ["etag@1.8.1", "", {}, ""], + + "event-target-shim": ["event-target-shim@5.0.1", "", {}, ""], + + "eventemitter3": ["eventemitter3@4.0.7", "", {}, ""], + + "events": ["events@3.3.0", "", {}, ""], + + "exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" } }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="], + + "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" } }, ""], + + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + + "fast-redact": ["fast-redact@3.5.0", "", {}, ""], + + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + + "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=="], + + "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" } }, ""], + + "forwarded": ["forwarded@0.2.0", "", {}, ""], + + "fresh": ["fresh@0.5.2", "", {}, ""], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, ""], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "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" } }, ""], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, ""], + + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + + "gopd": ["gopd@1.2.0", "", {}, ""], + + "graphemer": ["graphemer@1.4.0", "", {}, ""], + + "has-symbols": ["has-symbols@1.1.0", "", {}, ""], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, ""], + + "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" } }, ""], + + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, ""], + + "ieee754": ["ieee754@1.2.1", "", {}, ""], + + "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=="], + + "inherits": ["inherits@2.0.4", "", {}, ""], + + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, ""], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "iso-datestring-validator": ["iso-datestring-validator@2.2.2", "", {}, ""], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, ""], + + "media-typer": ["media-typer@0.3.0", "", {}, ""], + + "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], + + "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, ""], + + "methods": ["methods@1.1.2", "", {}, ""], + + "mime": ["mime@1.6.0", "", { "bin": "cli.js" }, ""], + + "mime-db": ["mime-db@1.52.0", "", {}, ""], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, ""], + + "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], + + "ms": ["ms@2.0.0", "", {}, ""], + + "multiformats": ["multiformats@13.4.1", "", {}, ""], + + "negotiator": ["negotiator@0.6.3", "", {}, ""], + + "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" } }, ""], + + "object-inspect": ["object-inspect@1.13.4", "", {}, ""], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, ""], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, ""], + + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + + "p-finally": ["p-finally@1.0.0", "", {}, ""], + + "p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, ""], + + "p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, ""], + + "parseurl": ["parseurl@1.3.3", "", {}, ""], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-to-regexp": ["path-to-regexp@0.1.12", "", {}, ""], + + "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" }, ""], + + "pino-abstract-transport": ["pino-abstract-transport@1.2.0", "", { "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" } }, ""], + + "pino-std-serializers": ["pino-std-serializers@6.2.2", "", {}, ""], + + "postgres": ["postgres@3.4.7", "", {}, ""], + + "process": ["process@0.11.10", "", {}, ""], + + "process-warning": ["process-warning@3.0.0", "", {}, ""], + + "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=="], + + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, ""], + + "qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, ""], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, ""], + + "range-parser": ["range-parser@1.2.1", "", {}, ""], + + "rate-limiter-flexible": ["rate-limiter-flexible@2.4.2", "", {}, ""], + + "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" } }, ""], + + "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" } }, ""], + + "real-require": ["real-require@0.2.0", "", {}, ""], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "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=="], + + "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=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, ""], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, ""], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, ""], + + "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" } }, ""], + + "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" } }, ""], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, ""], + + "shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="], + + "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" } }, ""], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, ""], + + "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" } }, ""], + + "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" } }, ""], + + "sonic-boom": ["sonic-boom@3.8.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, ""], + + "split2": ["split2@4.2.0", "", {}, ""], + + "srvx": ["srvx@0.8.16", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-hmcGW4CgroeSmzgF1Ihwgl+Ths0JqAJ7HwjP2X7e3JzY7u4IydLMcdnlqGQiQGUswz+PO9oh/KtCpOISIvs9QQ=="], + + "statuses": ["statuses@2.0.1", "", {}, ""], + + "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=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, ""], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, ""], + + "tlds": ["tlds@1.261.0", "", { "bin": "bin.js" }, ""], + + "toidentifier": ["toidentifier@1.0.1", "", {}, ""], + + "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=="], + + "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=="], + + "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, ""], + + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + + "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, ""], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unpipe": ["unpipe@1.0.0", "", {}, ""], + + "utils-merge": ["utils-merge@1.0.1", "", {}, ""], + + "varint": ["varint@6.0.0", "", {}, ""], + + "vary": ["vary@1.1.2", "", {}, ""], + + "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=="], + + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, ""], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "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=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "zod": ["zod@3.25.76", "", {}, ""], + + "@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, ""], + + "@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, ""], + + "@atproto/common-web/multiformats": ["multiformats@9.9.0", "", {}, ""], + + "@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""], + + "@atproto/repo/multiformats": ["multiformats@9.9.0", "", {}, ""], + + "@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, ""], + + "@ipld/dag-cbor/multiformats": ["multiformats@9.9.0", "", {}, ""], + + "@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "express/cookie": ["cookie@0.7.1", "", {}, ""], + + "require-in-the-middle/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "send/encodeurl": ["encodeurl@1.0.2", "", {}, ""], + + "send/ms": ["ms@2.1.3", "", {}, ""], + + "uint8arrays/multiformats": ["multiformats@9.9.0", "", {}, ""], + + "@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "require-in-the-middle/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + } +} diff --git a/hosting-service/package.json b/hosting-service/package.json index e0c1537..dac3eec 100644 --- a/hosting-service/package.json +++ b/hosting-service/package.json @@ -12,8 +12,8 @@ "@atproto/lexicon": "^0.5.1", "@atproto/sync": "^0.1.35", "@atproto/xrpc": "^0.7.5", - "@hono/node-server": "^1.13.7", - "hono": "^4.6.14", + "@elysiajs/opentelemetry": "latest", + "elysia": "latest", "mime-types": "^2.1.35", "multiformats": "^13.4.1", "postgres": "^3.4.5" diff --git a/hosting-service/src/index.ts b/hosting-service/src/index.ts index 629648f..e1e31e3 100644 --- a/hosting-service/src/index.ts +++ b/hosting-service/src/index.ts @@ -1,6 +1,5 @@ -import { serve } from '@hono/node-server'; -import app from './server.js'; -import { FirehoseWorker } from './lib/firehose.js'; +import app from './server'; +import { FirehoseWorker } from './lib/firehose'; import { mkdirSync, existsSync } from 'fs'; const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001; @@ -20,21 +19,17 @@ const firehose = new FirehoseWorker((msg, data) => { firehose.start(); // Add health check endpoint -app.get('/health', (c) => { +app.get('/health', () => { const firehoseHealth = firehose.getHealth(); - return c.json({ + return { status: 'ok', firehose: firehoseHealth, - }); + }; }); // Start HTTP server -const server = serve({ - port: PORT, - fetch: app.fetch, -}); - -console.log(` +app.listen(PORT, () => { + console.log(` Wisp Hosting Service Server: http://localhost:${PORT} @@ -42,18 +37,19 @@ Health: http://localhost:${PORT}/health Cache: ${CACHE_DIR} Firehose: Connected to Firehose `); +}); // Graceful shutdown process.on('SIGINT', async () => { console.log('\nšŸ›‘ Shutting down...'); firehose.stop(); - server.close(); + app.stop(); process.exit(0); }); process.on('SIGTERM', async () => { console.log('\nšŸ›‘ Shutting down...'); firehose.stop(); - server.close(); + app.stop(); process.exit(0); }); diff --git a/hosting-service/src/lexicon/index.ts b/hosting-service/src/lexicon/index.ts new file mode 100644 index 0000000..19fe80a --- /dev/null +++ b/hosting-service/src/lexicon/index.ts @@ -0,0 +1,44 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { + type Auth, + type Options as XrpcOptions, + Server as XrpcServer, + type StreamConfigOrHandler, + type MethodConfigOrHandler, + createServer as createXrpcServer, +} from '@atproto/xrpc-server' +import { schemas } from './lexicons.js' + +export function createServer(options?: XrpcOptions): Server { + return new Server(options) +} + +export class Server { + xrpc: XrpcServer + place: PlaceNS + + constructor(options?: XrpcOptions) { + this.xrpc = createXrpcServer(schemas, options) + this.place = new PlaceNS(this) + } +} + +export class PlaceNS { + _server: Server + wisp: PlaceWispNS + + constructor(server: Server) { + this._server = server + this.wisp = new PlaceWispNS(server) + } +} + +export class PlaceWispNS { + _server: Server + + constructor(server: Server) { + this._server = server + } +} diff --git a/hosting-service/src/lexicon/lexicons.ts b/hosting-service/src/lexicon/lexicons.ts index 686102f..4d14960 100644 --- a/hosting-service/src/lexicon/lexicons.ts +++ b/hosting-service/src/lexicon/lexicons.ts @@ -54,6 +54,20 @@ export const schemaDict = { maxSize: 1000000, description: 'Content blob ref', }, + encoding: { + type: 'string', + enum: ['gzip'], + description: 'Content encoding (e.g., gzip for compressed files)', + }, + mimeType: { + type: 'string', + description: 'Original MIME type before compression', + }, + base64: { + type: 'boolean', + description: + 'True if blob content is base64-encoded (used to bypass PDS content sniffing)', + }, }, }, directory: { diff --git a/hosting-service/src/lexicon/types/place/wisp/fs.ts b/hosting-service/src/lexicon/types/place/wisp/fs.ts index ad8104f..715f7be 100644 --- a/hosting-service/src/lexicon/types/place/wisp/fs.ts +++ b/hosting-service/src/lexicon/types/place/wisp/fs.ts @@ -34,6 +34,12 @@ export interface File { type: 'file' /** Content blob ref */ blob: BlobRef + /** Content encoding (e.g., gzip for compressed files) */ + encoding?: 'gzip' + /** Original MIME type before compression */ + mimeType?: string + /** True if blob content is base64-encoded (used to bypass PDS content sniffing) */ + base64?: boolean } const hashFile = 'file' diff --git a/hosting-service/src/lib/db.ts b/hosting-service/src/lib/db.ts index 7af424b..e5ff648 100644 --- a/hosting-service/src/lib/db.ts +++ b/hosting-service/src/lib/db.ts @@ -21,27 +21,120 @@ export interface CustomDomainLookup { verified: boolean; } +// In-memory cache with TTL +interface CacheEntry { + data: T; + expiry: number; +} + +const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes + +class SimpleCache { + private cache = new Map>(); + + get(key: string): T | null { + const entry = this.cache.get(key); + if (!entry) return null; + + if (Date.now() > entry.expiry) { + this.cache.delete(key); + return null; + } + + return entry.data; + } + + set(key: string, data: T): void { + this.cache.set(key, { + data, + expiry: Date.now() + CACHE_TTL_MS, + }); + } + + // Periodic cleanup to prevent memory leaks + cleanup(): void { + const now = Date.now(); + for (const [key, entry] of this.cache.entries()) { + if (now > entry.expiry) { + this.cache.delete(key); + } + } + } +} + +// Create cache instances +const wispDomainCache = new SimpleCache(); +const customDomainCache = new SimpleCache(); +const customDomainHashCache = new SimpleCache(); + +// Run cleanup every 5 minutes +setInterval(() => { + wispDomainCache.cleanup(); + customDomainCache.cleanup(); + customDomainHashCache.cleanup(); +}, 5 * 60 * 1000); + export async function getWispDomain(domain: string): Promise { + const key = domain.toLowerCase(); + + // Check cache first + const cached = wispDomainCache.get(key); + if (cached !== null) { + return cached; + } + + // Query database const result = await sql` - SELECT did, rkey FROM domains WHERE domain = ${domain.toLowerCase()} LIMIT 1 + SELECT did, rkey FROM domains WHERE domain = ${key} LIMIT 1 `; - return result[0] || null; + const data = result[0] || null; + + // Store in cache + wispDomainCache.set(key, data); + + return data; } export async function getCustomDomain(domain: string): Promise { + const key = domain.toLowerCase(); + + // Check cache first + const cached = customDomainCache.get(key); + if (cached !== null) { + return cached; + } + + // Query database const result = await sql` SELECT id, domain, did, rkey, verified FROM custom_domains - WHERE domain = ${domain.toLowerCase()} AND verified = true LIMIT 1 + WHERE domain = ${key} AND verified = true LIMIT 1 `; - return result[0] || null; + const data = result[0] || null; + + // Store in cache + customDomainCache.set(key, data); + + return data; } export async function getCustomDomainByHash(hash: string): Promise { + // Check cache first + const cached = customDomainHashCache.get(hash); + if (cached !== null) { + return cached; + } + + // Query database const result = await sql` SELECT id, domain, did, rkey, verified FROM custom_domains WHERE id = ${hash} AND verified = true LIMIT 1 `; - return result[0] || null; + const data = result[0] || null; + + // Store in cache + customDomainHashCache.set(hash, data); + + return data; } export async function upsertSite(did: string, rkey: string, displayName?: string) { diff --git a/hosting-service/src/lib/firehose.ts b/hosting-service/src/lib/firehose.ts index 1337745..7e57049 100644 --- a/hosting-service/src/lib/firehose.ts +++ b/hosting-service/src/lib/firehose.ts @@ -157,7 +157,7 @@ export class FirehoseWorker { return; } - // Cache the record with verified CID + // Cache the record with verified CID (uses atomic swap internally) await downloadAndCacheSite(did, site, fsRecord, pdsEndpoint, verifiedCid); // Upsert site to database diff --git a/hosting-service/src/lib/html-rewriter.ts b/hosting-service/src/lib/html-rewriter.ts index 41769f6..274b35b 100644 --- a/hosting-service/src/lib/html-rewriter.ts +++ b/hosting-service/src/lib/html-rewriter.ts @@ -16,18 +16,23 @@ const REWRITABLE_ATTRIBUTES = [ * Check if a path should be rewritten */ function shouldRewritePath(path: string): boolean { - // Must start with / - if (!path.startsWith('/')) return false; + // Don't rewrite empty paths + if (!path) return false; - // Don't rewrite protocol-relative URLs - if (path.startsWith('//')) return false; + // Don't rewrite external URLs (http://, https://, //) + if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('//')) { + return false; + } - // Don't rewrite anchors - if (path.startsWith('/#')) return false; + // Don't rewrite data URIs or other schemes (except file paths) + if (path.includes(':') && !path.startsWith('./') && !path.startsWith('../')) { + return false; + } - // Don't rewrite data URIs or other schemes - if (path.includes(':')) return false; + // Don't rewrite pure anchors + if (path.startsWith('#')) return false; + // Rewrite absolute paths (/) and relative paths (./ or ../ or plain filenames) return true; } @@ -39,8 +44,22 @@ function rewritePath(path: string, basePath: string): string { return path; } - // Remove leading slash and prepend base path - return basePath + path.slice(1); + // Handle absolute paths: /file.js -> /base/file.js + if (path.startsWith('/')) { + return basePath + path.slice(1); + } + + // Handle relative paths: ./file.js or ../file.js or file.js -> /base/file.js + // Strip leading ./ or ../ and just use the base path + let cleanPath = path; + if (cleanPath.startsWith('./')) { + cleanPath = cleanPath.slice(2); + } else if (cleanPath.startsWith('../')) { + // For sites.wisp.place, we can't go up from the site root, so just use base path + cleanPath = cleanPath.replace(/^(\.\.\/)+/, ''); + } + + return basePath + cleanPath; } /** diff --git a/hosting-service/src/lib/utils.ts b/hosting-service/src/lib/utils.ts index b07184e..c03ec5b 100644 --- a/hosting-service/src/lib/utils.ts +++ b/hosting-service/src/lib/utils.ts @@ -1,7 +1,7 @@ import { AtpAgent } from '@atproto/api'; import type { WispFsRecord, Directory, Entry, File } from './types'; -import { existsSync, mkdirSync, readFileSync } from 'fs'; -import { writeFile, readFile } from 'fs/promises'; +import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs'; +import { writeFile, readFile, rename } from 'fs/promises'; import { safeFetchJson, safeFetchBlob } from './safe-fetch'; import { CID } from 'multiformats/cid'; @@ -153,9 +153,49 @@ export async function downloadAndCacheSite(did: string, rkey: string, record: Wi throw new Error('Invalid record structure: root missing entries array'); } - await cacheFiles(did, rkey, record.root.entries, pdsEndpoint, ''); + // Use a temporary directory with timestamp to avoid collisions + const tempSuffix = `.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; + const tempDir = `${CACHE_DIR}/${did}/${rkey}${tempSuffix}`; + const finalDir = `${CACHE_DIR}/${did}/${rkey}`; - await saveCacheMetadata(did, rkey, recordCid); + try { + // Download to temporary directory + await cacheFiles(did, rkey, record.root.entries, pdsEndpoint, '', tempSuffix); + await saveCacheMetadata(did, rkey, recordCid, tempSuffix); + + // Atomically replace old cache with new cache + // On POSIX systems (Linux/macOS), rename is atomic + if (existsSync(finalDir)) { + // Rename old directory to backup + const backupDir = `${finalDir}.old-${Date.now()}`; + await rename(finalDir, backupDir); + + try { + // Rename new directory to final location + await rename(tempDir, finalDir); + + // Clean up old backup + rmSync(backupDir, { recursive: true, force: true }); + } catch (err) { + // If rename failed, restore backup + if (existsSync(backupDir) && !existsSync(finalDir)) { + await rename(backupDir, finalDir); + } + throw err; + } + } else { + // No existing cache, just rename temp to final + await rename(tempDir, finalDir); + } + + console.log('Successfully cached site atomically', did, rkey); + } catch (err) { + // Clean up temp directory on failure + if (existsSync(tempDir)) { + rmSync(tempDir, { recursive: true, force: true }); + } + throw err; + } } async function cacheFiles( @@ -163,16 +203,18 @@ async function cacheFiles( site: string, entries: Entry[], pdsEndpoint: string, - pathPrefix: string + pathPrefix: string, + dirSuffix: string = '' ): Promise { for (const entry of entries) { const currentPath = pathPrefix ? `${pathPrefix}/${entry.name}` : entry.name; const node = entry.node; if ('type' in node && node.type === 'directory' && 'entries' in node) { - await cacheFiles(did, site, node.entries, pdsEndpoint, currentPath); + await cacheFiles(did, site, node.entries, pdsEndpoint, currentPath, dirSuffix); } else if ('type' in node && node.type === 'file' && 'blob' in node) { - await cacheFileBlob(did, site, currentPath, node.blob, pdsEndpoint); + const fileNode = node as File; + await cacheFileBlob(did, site, currentPath, fileNode.blob, pdsEndpoint, fileNode.encoding, fileNode.mimeType, fileNode.base64, dirSuffix); } } } @@ -182,7 +224,11 @@ async function cacheFileBlob( site: string, filePath: string, blobRef: any, - pdsEndpoint: string + pdsEndpoint: string, + encoding?: 'gzip', + mimeType?: string, + base64?: boolean, + dirSuffix: string = '' ): Promise { const cid = extractBlobCid(blobRef); if (!cid) { @@ -193,9 +239,17 @@ async function cacheFileBlob( const blobUrl = `${pdsEndpoint}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`; // Allow up to 100MB per file blob - const content = await safeFetchBlob(blobUrl, { maxSize: 100 * 1024 * 1024 }); + let content = await safeFetchBlob(blobUrl, { maxSize: 100 * 1024 * 1024 }); + + // If content is base64-encoded, decode it back to gzipped binary + if (base64 && encoding === 'gzip') { + // Convert Uint8Array to Buffer for proper string conversion + const buffer = Buffer.from(content); + const base64String = buffer.toString('utf-8'); + content = Buffer.from(base64String, 'base64'); + } - const cacheFile = `${CACHE_DIR}/${did}/${site}/${filePath}`; + const cacheFile = `${CACHE_DIR}/${did}/${site}${dirSuffix}/${filePath}`; const fileDir = cacheFile.substring(0, cacheFile.lastIndexOf('/')); if (fileDir && !existsSync(fileDir)) { @@ -203,7 +257,15 @@ async function cacheFileBlob( } await writeFile(cacheFile, content); - console.log('Cached file', filePath, content.length, 'bytes'); + + // Store metadata if file is compressed + if (encoding === 'gzip' && mimeType) { + const metaFile = `${cacheFile}.meta`; + await writeFile(metaFile, JSON.stringify({ encoding, mimeType })); + console.log('Cached file', filePath, content.length, 'bytes (gzipped,', mimeType + ')'); + } else { + console.log('Cached file', filePath, content.length, 'bytes'); + } } /** @@ -238,7 +300,7 @@ export function isCached(did: string, site: string): boolean { return existsSync(`${CACHE_DIR}/${did}/${site}`); } -async function saveCacheMetadata(did: string, rkey: string, recordCid: string): Promise { +async function saveCacheMetadata(did: string, rkey: string, recordCid: string, dirSuffix: string = ''): Promise { const metadata: CacheMetadata = { recordCid, cachedAt: Date.now(), @@ -246,7 +308,7 @@ async function saveCacheMetadata(did: string, rkey: string, recordCid: string): rkey }; - const metadataPath = `${CACHE_DIR}/${did}/${rkey}/.metadata.json`; + const metadataPath = `${CACHE_DIR}/${did}/${rkey}${dirSuffix}/.metadata.json`; const metadataDir = metadataPath.substring(0, metadataPath.lastIndexOf('/')); if (!existsSync(metadataDir)) { diff --git a/hosting-service/src/server.ts b/hosting-service/src/server.ts index 874539e..05fd0cd 100644 --- a/hosting-service/src/server.ts +++ b/hosting-service/src/server.ts @@ -1,12 +1,12 @@ -import { Hono } from 'hono'; +import { Elysia } from 'elysia'; +import { node } from '@elysiajs/node' +import { opentelemetry } from '@elysiajs/opentelemetry'; import { getWispDomain, getCustomDomain, getCustomDomainByHash } from './lib/db'; import { resolveDid, getPdsForDid, fetchSiteRecord, downloadAndCacheSite, getCachedFilePath, isCached, sanitizePath } from './lib/utils'; import { rewriteHtmlPaths, isHtmlContent } from './lib/html-rewriter'; import { existsSync, readFileSync } from 'fs'; import { lookup } from 'mime-types'; -const app = new Hono(); - const BASE_HOST = process.env.BASE_HOST || 'wisp.place'; /** @@ -34,6 +34,23 @@ async function serveFromCache(did: string, rkey: string, filePath: string) { if (existsSync(cachedFile)) { const content = readFileSync(cachedFile); + const metaFile = `${cachedFile}.meta`; + + // Check if file has compression metadata + if (existsSync(metaFile)) { + const meta = JSON.parse(readFileSync(metaFile, 'utf-8')); + if (meta.encoding === 'gzip' && meta.mimeType) { + // Serve gzipped content with proper headers + return new Response(content, { + headers: { + 'Content-Type': meta.mimeType, + 'Content-Encoding': 'gzip', + }, + }); + } + } + + // Serve non-compressed files normally const mimeType = lookup(cachedFile) || 'application/octet-stream'; return new Response(content, { headers: { @@ -47,6 +64,21 @@ async function serveFromCache(did: string, rkey: string, filePath: string) { const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`); if (existsSync(indexFile)) { const content = readFileSync(indexFile); + const metaFile = `${indexFile}.meta`; + + // Check if file has compression metadata + if (existsSync(metaFile)) { + const meta = JSON.parse(readFileSync(metaFile, 'utf-8')); + if (meta.encoding === 'gzip' && meta.mimeType) { + return new Response(content, { + headers: { + 'Content-Type': meta.mimeType, + 'Content-Encoding': 'gzip', + }, + }); + } + } + return new Response(content, { headers: { 'Content-Type': 'text/html; charset=utf-8', @@ -74,11 +106,31 @@ async function serveFromCacheWithRewrite( const cachedFile = getCachedFilePath(did, rkey, requestPath); if (existsSync(cachedFile)) { - const mimeType = lookup(cachedFile) || 'application/octet-stream'; + const metaFile = `${cachedFile}.meta`; + let mimeType = lookup(cachedFile) || 'application/octet-stream'; + let isGzipped = false; + + // Check if file has compression metadata + if (existsSync(metaFile)) { + const meta = JSON.parse(readFileSync(metaFile, 'utf-8')); + if (meta.encoding === 'gzip' && meta.mimeType) { + mimeType = meta.mimeType; + isGzipped = true; + } + } // Check if this is HTML content that needs rewriting + // Note: For gzipped HTML with path rewriting, we need to decompress, rewrite, and serve uncompressed + // This is a trade-off for the sites.wisp.place domain which needs path rewriting if (isHtmlContent(requestPath, mimeType)) { - const content = readFileSync(cachedFile, 'utf-8'); + let content: string; + if (isGzipped) { + const { gunzipSync } = await import('zlib'); + const compressed = readFileSync(cachedFile); + content = gunzipSync(compressed).toString('utf-8'); + } else { + content = readFileSync(cachedFile, 'utf-8'); + } const rewritten = rewriteHtmlPaths(content, basePath); return new Response(rewritten, { headers: { @@ -87,8 +139,16 @@ async function serveFromCacheWithRewrite( }); } - // Non-HTML files served with proper MIME type + // Non-HTML files: serve gzipped content as-is with proper headers const content = readFileSync(cachedFile); + if (isGzipped) { + return new Response(content, { + headers: { + 'Content-Type': mimeType, + 'Content-Encoding': 'gzip', + }, + }); + } return new Response(content, { headers: { 'Content-Type': mimeType, @@ -100,7 +160,25 @@ async function serveFromCacheWithRewrite( if (!requestPath.includes('.')) { const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`); if (existsSync(indexFile)) { - const content = readFileSync(indexFile, 'utf-8'); + const metaFile = `${indexFile}.meta`; + let isGzipped = false; + + if (existsSync(metaFile)) { + const meta = JSON.parse(readFileSync(metaFile, 'utf-8')); + if (meta.encoding === 'gzip') { + isGzipped = true; + } + } + + // HTML needs path rewriting, so decompress if needed + let content: string; + if (isGzipped) { + const { gunzipSync } = await import('zlib'); + const compressed = readFileSync(indexFile); + content = gunzipSync(compressed).toString('utf-8'); + } else { + content = readFileSync(indexFile, 'utf-8'); + } const rewritten = rewriteHtmlPaths(content, basePath); return new Response(rewritten, { headers: { @@ -141,134 +219,138 @@ async function ensureSiteCached(did: string, rkey: string): Promise { } } -// Route 4: Direct file serving (no DB) - sites.wisp.place/:identifier/:site/* -// This route is now handled in the catch-all route below - -// Route 3: DNS routing for custom domains - /hash.dns.wisp.place/* -app.get('/*', async (c) => { - const hostname = c.req.header('host') || ''; - const rawPath = c.req.path.replace(/^\//, ''); - const path = sanitizePath(rawPath); - - console.log('[Request]', { hostname, path }); - - // Check if this is sites.wisp.place subdomain - if (hostname === `sites.${BASE_HOST}` || hostname === `sites.${BASE_HOST}:${process.env.PORT || 3000}`) { - // Sanitize the path FIRST to prevent path traversal - const sanitizedFullPath = sanitizePath(rawPath); - - // Extract identifier and site from sanitized path: did:plc:123abc/sitename/file.html - const pathParts = sanitizedFullPath.split('/'); - if (pathParts.length < 2) { - return c.text('Invalid path format. Expected: /identifier/sitename/path', 400); - } - - const identifier = pathParts[0]; - const site = pathParts[1]; - const filePath = pathParts.slice(2).join('/'); - - console.log('[Sites] Serving', { identifier, site, filePath }); - - // Additional validation: identifier must be a valid DID or handle format - if (!identifier || identifier.length < 3 || identifier.includes('..') || identifier.includes('\0')) { - return c.text('Invalid identifier', 400); +const app = new Elysia({ adapter: node() }) + .use(opentelemetry()) + .get('/*', async ({ request, set }) => { + const url = new URL(request.url); + const hostname = request.headers.get('host') || ''; + const rawPath = url.pathname.replace(/^\//, ''); + const path = sanitizePath(rawPath); + + // Check if this is sites.wisp.place subdomain + if (hostname === `sites.${BASE_HOST}` || hostname === `sites.${BASE_HOST}:${process.env.PORT || 3000}`) { + // Sanitize the path FIRST to prevent path traversal + const sanitizedFullPath = sanitizePath(rawPath); + + // Extract identifier and site from sanitized path: did:plc:123abc/sitename/file.html + const pathParts = sanitizedFullPath.split('/'); + if (pathParts.length < 2) { + set.status = 400; + return 'Invalid path format. Expected: /identifier/sitename/path'; + } + + const identifier = pathParts[0]; + const site = pathParts[1]; + const filePath = pathParts.slice(2).join('/'); + + // Additional validation: identifier must be a valid DID or handle format + if (!identifier || identifier.length < 3 || identifier.includes('..') || identifier.includes('\0')) { + set.status = 400; + return 'Invalid identifier'; + } + + // Validate site name (rkey) + if (!isValidRkey(site)) { + set.status = 400; + return 'Invalid site name'; + } + + // Resolve identifier to DID + const did = await resolveDid(identifier); + if (!did) { + set.status = 400; + return 'Invalid identifier'; + } + + // Ensure site is cached + const cached = await ensureSiteCached(did, site); + if (!cached) { + set.status = 404; + return 'Site not found'; + } + + // Serve with HTML path rewriting to handle absolute paths + const basePath = `/${identifier}/${site}/`; + return serveFromCacheWithRewrite(did, site, filePath, basePath); } - // Validate site name (rkey) - if (!isValidRkey(site)) { - return c.text('Invalid site name', 400); + // Check if this is a DNS hash subdomain + const dnsMatch = hostname.match(/^([a-f0-9]{16})\.dns\.(.+)$/); + if (dnsMatch) { + const hash = dnsMatch[1]; + const baseDomain = dnsMatch[2]; + + if (baseDomain !== BASE_HOST) { + set.status = 400; + return 'Invalid base domain'; + } + + const customDomain = await getCustomDomainByHash(hash); + if (!customDomain) { + set.status = 404; + return 'Custom domain not found or not verified'; + } + + const rkey = customDomain.rkey || 'self'; + if (!isValidRkey(rkey)) { + set.status = 500; + return 'Invalid site configuration'; + } + + const cached = await ensureSiteCached(customDomain.did, rkey); + if (!cached) { + set.status = 404; + return 'Site not found'; + } + + return serveFromCache(customDomain.did, rkey, path); } - // Resolve identifier to DID - const did = await resolveDid(identifier); - if (!did) { - return c.text('Invalid identifier', 400); + // Route 2: Registered subdomains - /*.wisp.place/* + if (hostname.endsWith(`.${BASE_HOST}`)) { + const subdomain = hostname.replace(`.${BASE_HOST}`, ''); + + const domainInfo = await getWispDomain(hostname); + if (!domainInfo) { + set.status = 404; + return 'Subdomain not registered'; + } + + const rkey = domainInfo.rkey || 'self'; + if (!isValidRkey(rkey)) { + set.status = 500; + return 'Invalid site configuration'; + } + + const cached = await ensureSiteCached(domainInfo.did, rkey); + if (!cached) { + set.status = 404; + return 'Site not found'; + } + + return serveFromCache(domainInfo.did, rkey, path); } - // Ensure site is cached - const cached = await ensureSiteCached(did, site); - if (!cached) { - return c.text('Site not found', 404); - } - - // Serve with HTML path rewriting to handle absolute paths - const basePath = `/${identifier}/${site}/`; - return serveFromCacheWithRewrite(did, site, filePath, basePath); - } - - // Check if this is a DNS hash subdomain - const dnsMatch = hostname.match(/^([a-f0-9]{16})\.dns\.(.+)$/); - if (dnsMatch) { - const hash = dnsMatch[1]; - const baseDomain = dnsMatch[2]; - - console.log('[DNS Hash] Looking up', { hash, baseDomain }); - - if (baseDomain !== BASE_HOST) { - return c.text('Invalid base domain', 400); - } - - const customDomain = await getCustomDomainByHash(hash); + // Route 1: Custom domains - /* + const customDomain = await getCustomDomain(hostname); if (!customDomain) { - return c.text('Custom domain not found or not verified', 404); + set.status = 404; + return 'Custom domain not found or not verified'; } const rkey = customDomain.rkey || 'self'; if (!isValidRkey(rkey)) { - return c.text('Invalid site configuration', 500); + set.status = 500; + return 'Invalid site configuration'; } const cached = await ensureSiteCached(customDomain.did, rkey); if (!cached) { - return c.text('Site not found', 404); + set.status = 404; + return 'Site not found'; } return serveFromCache(customDomain.did, rkey, path); - } - - // Route 2: Registered subdomains - /*.wisp.place/* - if (hostname.endsWith(`.${BASE_HOST}`)) { - const subdomain = hostname.replace(`.${BASE_HOST}`, ''); - - console.log('[Subdomain] Looking up', { subdomain, fullDomain: hostname }); - - const domainInfo = await getWispDomain(hostname); - if (!domainInfo) { - return c.text('Subdomain not registered', 404); - } - - const rkey = domainInfo.rkey || 'self'; - if (!isValidRkey(rkey)) { - return c.text('Invalid site configuration', 500); - } - - const cached = await ensureSiteCached(domainInfo.did, rkey); - if (!cached) { - return c.text('Site not found', 404); - } - - return serveFromCache(domainInfo.did, rkey, path); - } - - // Route 1: Custom domains - /* - console.log('[Custom Domain] Looking up', { hostname }); - - const customDomain = await getCustomDomain(hostname); - if (!customDomain) { - return c.text('Custom domain not found or not verified', 404); - } - - const rkey = customDomain.rkey || 'self'; - if (!isValidRkey(rkey)) { - return c.text('Invalid site configuration', 500); - } - - const cached = await ensureSiteCached(customDomain.did, rkey); - if (!cached) { - return c.text('Site not found', 404); - } - - return serveFromCache(customDomain.did, rkey, path); -}); + }); export default app; diff --git a/lexicons/fs.json b/lexicons/fs.json index 0d56812..e87efde 100644 --- a/lexicons/fs.json +++ b/lexicons/fs.json @@ -21,7 +21,10 @@ "required": ["type", "blob"], "properties": { "type": { "type": "string", "const": "file" }, - "blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000, "description": "Content blob ref" } + "blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000, "description": "Content blob ref" }, + "encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" }, + "mimeType": { "type": "string", "description": "Original MIME type before compression" }, + "base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" } } }, "directory": { diff --git a/package.json b/package.json index e879243..1f8e7bc 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "bun run --watch src/index.ts", + "start": "bun run src/index.ts", "build": "bun build --compile --target bun --outfile server src/index.ts" }, "dependencies": { @@ -31,7 +32,8 @@ "tailwind-merge": "^3.3.1", "tailwindcss": "4", "tw-animate-css": "^1.4.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "zlib": "^1.0.5" }, "devDependencies": { "@types/react": "^19.2.2", diff --git a/src/lexicon/lexicons.ts b/src/lexicon/lexicons.ts index 686102f..4d14960 100644 --- a/src/lexicon/lexicons.ts +++ b/src/lexicon/lexicons.ts @@ -54,6 +54,20 @@ export const schemaDict = { maxSize: 1000000, description: 'Content blob ref', }, + encoding: { + type: 'string', + enum: ['gzip'], + description: 'Content encoding (e.g., gzip for compressed files)', + }, + mimeType: { + type: 'string', + description: 'Original MIME type before compression', + }, + base64: { + type: 'boolean', + description: + 'True if blob content is base64-encoded (used to bypass PDS content sniffing)', + }, }, }, directory: { diff --git a/src/lexicon/types/place/wisp/fs.ts b/src/lexicon/types/place/wisp/fs.ts index ad8104f..715f7be 100644 --- a/src/lexicon/types/place/wisp/fs.ts +++ b/src/lexicon/types/place/wisp/fs.ts @@ -34,6 +34,12 @@ export interface File { type: 'file' /** Content blob ref */ blob: BlobRef + /** Content encoding (e.g., gzip for compressed files) */ + encoding?: 'gzip' + /** Original MIME type before compression */ + mimeType?: string + /** True if blob content is base64-encoded (used to bypass PDS content sniffing) */ + base64?: boolean } const hashFile = 'file' diff --git a/src/lib/wisp-utils.ts b/src/lib/wisp-utils.ts index 457f3c0..56b81d7 100644 --- a/src/lib/wisp-utils.ts +++ b/src/lib/wisp-utils.ts @@ -1,17 +1,23 @@ import type { BlobRef } from "@atproto/api"; import type { Record, Directory, File, Entry } from "../lexicon/types/place/wisp/fs"; import { validateRecord } from "../lexicon/types/place/wisp/fs"; +import { gzipSync } from 'zlib'; export interface UploadedFile { name: string; content: Buffer; mimeType: string; size: number; + compressed?: boolean; + originalMimeType?: string; } export interface FileUploadResult { hash: string; blobRef: BlobRef; + encoding?: 'gzip'; + mimeType?: string; + base64?: boolean; } export interface ProcessedDirectory { @@ -19,6 +25,35 @@ export interface ProcessedDirectory { fileCount: number; } +/** + * Determine if a file should be gzip compressed based on its MIME type + */ +export function shouldCompressFile(mimeType: string): boolean { + // Compress text-based files + const compressibleTypes = [ + 'text/html', + 'text/css', + 'text/javascript', + 'application/javascript', + 'application/json', + 'image/svg+xml', + 'text/xml', + 'application/xml', + 'text/plain', + 'application/x-javascript' + ]; + + // Check if mime type starts with any compressible type + return compressibleTypes.some(type => mimeType.startsWith(type)); +} + +/** + * Compress a file using gzip + */ +export function compressFile(content: Buffer): Buffer { + return gzipSync(content, { level: 9 }); +} + /** * Process uploaded files into a directory structure */ @@ -168,14 +203,18 @@ export function updateFileBlobs( }); if (fileIndex !== -1 && uploadResults[fileIndex]) { - const blobRef = uploadResults[fileIndex].blobRef; + const result = uploadResults[fileIndex]; + const blobRef = result.blobRef; return { ...entry, node: { $type: 'place.wisp.fs#file' as const, type: 'file' as const, - blob: blobRef + blob: blobRef, + ...(result.encoding && { encoding: result.encoding }), + ...(result.mimeType && { mimeType: result.mimeType }), + ...(result.base64 && { base64: result.base64 }) } }; } else { diff --git a/src/routes/wisp.ts b/src/routes/wisp.ts index 561514c..e75d41e 100644 --- a/src/routes/wisp.ts +++ b/src/routes/wisp.ts @@ -7,7 +7,9 @@ import { type FileUploadResult, processUploadedFiles, createManifest, - updateFileBlobs + updateFileBlobs, + shouldCompressFile, + compressFile } from '../lib/wisp-utils' import { upsertSite } from '../lib/db' import { logger } from '../lib/logger' @@ -164,12 +166,36 @@ export const wispRoutes = (client: NodeOAuthClient) => } const arrayBuffer = await file.arrayBuffer(); - uploadedFiles.push({ - name: file.name, - content: Buffer.from(arrayBuffer), - mimeType: 'application/octet-stream', - size: file.size - }); + const originalContent = Buffer.from(arrayBuffer); + const originalMimeType = file.type || 'application/octet-stream'; + + // Determine if we should compress this file + const shouldCompress = shouldCompressFile(originalMimeType); + + if (shouldCompress) { + const compressedContent = compressFile(originalContent); + // Base64 encode the gzipped content to prevent PDS content sniffing + const base64Content = Buffer.from(compressedContent.toString('base64'), 'utf-8'); + const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1); + logger.info(`[Wisp] Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${base64Content.length} bytes`); + + uploadedFiles.push({ + name: file.name, + content: base64Content, + mimeType: originalMimeType, + size: base64Content.length, + compressed: true, + originalMimeType + }); + } else { + uploadedFiles.push({ + name: file.name, + content: originalContent, + mimeType: originalMimeType, + size: file.size, + compressed: false + }); + } } // Check total size limit (300MB) @@ -226,27 +252,42 @@ export const wispRoutes = (client: NodeOAuthClient) => // Process files into directory structure const { directory, fileCount } = processUploadedFiles(uploadedFiles); - // Upload files as blobs in parallel (always as octet-stream) + // Upload files as blobs in parallel + // For compressed files, we upload as octet-stream and store the original MIME type in metadata + // For text/html files, we also use octet-stream as a workaround for PDS image pipeline issues const uploadPromises = uploadedFiles.map(async (file, i) => { try { + // If compressed, always upload as octet-stream + // Otherwise, workaround: PDS incorrectly processes text/html through image pipeline + const uploadMimeType = file.compressed || file.mimeType.startsWith('text/html') + ? 'application/octet-stream' + : file.mimeType; + + const compressionInfo = file.compressed ? ' (gzipped)' : ''; + logger.info(`[Wisp] Uploading file: ${file.name} (original: ${file.mimeType}, sending as: ${uploadMimeType}, ${file.size} bytes${compressionInfo})`); + const uploadResult = await agent.com.atproto.repo.uploadBlob( file.content, { - encoding: 'application/octet-stream' + encoding: uploadMimeType } ); - const sentMimeType = file.mimeType; const returnedBlobRef = uploadResult.data.blob; // Use the blob ref exactly as returned from PDS return { result: { hash: returnedBlobRef.ref.toString(), - blobRef: returnedBlobRef + blobRef: returnedBlobRef, + ...(file.compressed && { + encoding: 'gzip' as const, + mimeType: file.originalMimeType || file.mimeType, + base64: true + }) }, filePath: file.name, - sentMimeType, + sentMimeType: file.mimeType, returnedMimeType: returnedBlobRef.mimeType }; } catch (uploadError) { -- 2.54.0