+1
-1
.gitignore
+1
-1
.gitignore
+6
bun.lock
+6
bun.lock
···
28
"tailwind-merge": "^3.3.1",
29
"tailwindcss": "4",
30
"tw-animate-css": "^1.4.0",
31
},
32
"devDependencies": {
33
"@types/react": "^19.2.2",
···
500
501
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
502
503
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
504
505
"uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="],
···
523
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
524
525
"yesno": ["yesno@0.4.0", "", {}, "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA=="],
526
527
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
528
···
28
"tailwind-merge": "^3.3.1",
29
"tailwindcss": "4",
30
"tw-animate-css": "^1.4.0",
31
+
"typescript": "^5.9.3",
32
+
"zlib": "^1.0.5",
33
},
34
"devDependencies": {
35
"@types/react": "^19.2.2",
···
502
503
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
504
505
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
506
+
507
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
508
509
"uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="],
···
527
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
528
529
"yesno": ["yesno@0.4.0", "", {}, "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA=="],
530
+
531
+
"zlib": ["zlib@1.0.5", "", {}, "sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w=="],
532
533
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
534
+553
hosting-service/bun.lock
+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
+2
-2
hosting-service/package.json
+10
-14
hosting-service/src/index.ts
+10
-14
hosting-service/src/index.ts
···
1
-
import { serve } from '@hono/node-server';
2
-
import app from './server.js';
3
-
import { FirehoseWorker } from './lib/firehose.js';
4
import { mkdirSync, existsSync } from 'fs';
5
6
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;
···
20
firehose.start();
21
22
// Add health check endpoint
23
-
app.get('/health', (c) => {
24
const firehoseHealth = firehose.getHealth();
25
-
return c.json({
26
status: 'ok',
27
firehose: firehoseHealth,
28
-
});
29
});
30
31
// Start HTTP server
32
-
const server = serve({
33
-
port: PORT,
34
-
fetch: app.fetch,
35
-
});
36
-
37
-
console.log(`
38
Wisp Hosting Service
39
40
Server: http://localhost:${PORT}
···
42
Cache: ${CACHE_DIR}
43
Firehose: Connected to Firehose
44
`);
45
46
// Graceful shutdown
47
process.on('SIGINT', async () => {
48
console.log('\n🛑 Shutting down...');
49
firehose.stop();
50
-
server.close();
51
process.exit(0);
52
});
53
54
process.on('SIGTERM', async () => {
55
console.log('\n🛑 Shutting down...');
56
firehose.stop();
57
-
server.close();
58
process.exit(0);
59
});
···
1
+
import app from './server';
2
+
import { FirehoseWorker } from './lib/firehose';
3
import { mkdirSync, existsSync } from 'fs';
4
5
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;
···
19
firehose.start();
20
21
// Add health check endpoint
22
+
app.get('/health', () => {
23
const firehoseHealth = firehose.getHealth();
24
+
return {
25
status: 'ok',
26
firehose: firehoseHealth,
27
+
};
28
});
29
30
// Start HTTP server
31
+
app.listen(PORT, () => {
32
+
console.log(`
33
Wisp Hosting Service
34
35
Server: http://localhost:${PORT}
···
37
Cache: ${CACHE_DIR}
38
Firehose: Connected to Firehose
39
`);
40
+
});
41
42
// Graceful shutdown
43
process.on('SIGINT', async () => {
44
console.log('\n🛑 Shutting down...');
45
firehose.stop();
46
+
app.stop();
47
process.exit(0);
48
});
49
50
process.on('SIGTERM', async () => {
51
console.log('\n🛑 Shutting down...');
52
firehose.stop();
53
+
app.stop();
54
process.exit(0);
55
});
+44
hosting-service/src/lexicon/index.ts
+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
+14
hosting-service/src/lexicon/lexicons.ts
···
54
maxSize: 1000000,
55
description: 'Content blob ref',
56
},
57
+
encoding: {
58
+
type: 'string',
59
+
enum: ['gzip'],
60
+
description: 'Content encoding (e.g., gzip for compressed files)',
61
+
},
62
+
mimeType: {
63
+
type: 'string',
64
+
description: 'Original MIME type before compression',
65
+
},
66
+
base64: {
67
+
type: 'boolean',
68
+
description:
69
+
'True if blob content is base64-encoded (used to bypass PDS content sniffing)',
70
+
},
71
},
72
},
73
directory: {
+6
hosting-service/src/lexicon/types/place/wisp/fs.ts
+6
hosting-service/src/lexicon/types/place/wisp/fs.ts
···
34
type: 'file'
35
/** Content blob ref */
36
blob: BlobRef
37
+
/** Content encoding (e.g., gzip for compressed files) */
38
+
encoding?: 'gzip'
39
+
/** Original MIME type before compression */
40
+
mimeType?: string
41
+
/** True if blob content is base64-encoded (used to bypass PDS content sniffing) */
42
+
base64?: boolean
43
}
44
45
const hashFile = 'file'
+98
-5
hosting-service/src/lib/db.ts
+98
-5
hosting-service/src/lib/db.ts
···
21
verified: boolean;
22
}
23
24
export async function getWispDomain(domain: string): Promise<DomainLookup | null> {
25
const result = await sql<DomainLookup[]>`
26
-
SELECT did, rkey FROM domains WHERE domain = ${domain.toLowerCase()} LIMIT 1
27
`;
28
-
return result[0] || null;
29
}
30
31
export async function getCustomDomain(domain: string): Promise<CustomDomainLookup | null> {
32
const result = await sql<CustomDomainLookup[]>`
33
SELECT id, domain, did, rkey, verified FROM custom_domains
34
-
WHERE domain = ${domain.toLowerCase()} AND verified = true LIMIT 1
35
`;
36
-
return result[0] || null;
37
}
38
39
export async function getCustomDomainByHash(hash: string): Promise<CustomDomainLookup | null> {
40
const result = await sql<CustomDomainLookup[]>`
41
SELECT id, domain, did, rkey, verified FROM custom_domains
42
WHERE id = ${hash} AND verified = true LIMIT 1
43
`;
44
-
return result[0] || null;
45
}
46
47
export async function upsertSite(did: string, rkey: string, displayName?: string) {
···
21
verified: boolean;
22
}
23
24
+
// In-memory cache with TTL
25
+
interface CacheEntry<T> {
26
+
data: T;
27
+
expiry: number;
28
+
}
29
+
30
+
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
31
+
32
+
class SimpleCache<T> {
33
+
private cache = new Map<string, CacheEntry<T>>();
34
+
35
+
get(key: string): T | null {
36
+
const entry = this.cache.get(key);
37
+
if (!entry) return null;
38
+
39
+
if (Date.now() > entry.expiry) {
40
+
this.cache.delete(key);
41
+
return null;
42
+
}
43
+
44
+
return entry.data;
45
+
}
46
+
47
+
set(key: string, data: T): void {
48
+
this.cache.set(key, {
49
+
data,
50
+
expiry: Date.now() + CACHE_TTL_MS,
51
+
});
52
+
}
53
+
54
+
// Periodic cleanup to prevent memory leaks
55
+
cleanup(): void {
56
+
const now = Date.now();
57
+
for (const [key, entry] of this.cache.entries()) {
58
+
if (now > entry.expiry) {
59
+
this.cache.delete(key);
60
+
}
61
+
}
62
+
}
63
+
}
64
+
65
+
// Create cache instances
66
+
const wispDomainCache = new SimpleCache<DomainLookup | null>();
67
+
const customDomainCache = new SimpleCache<CustomDomainLookup | null>();
68
+
const customDomainHashCache = new SimpleCache<CustomDomainLookup | null>();
69
+
70
+
// Run cleanup every 5 minutes
71
+
setInterval(() => {
72
+
wispDomainCache.cleanup();
73
+
customDomainCache.cleanup();
74
+
customDomainHashCache.cleanup();
75
+
}, 5 * 60 * 1000);
76
+
77
export async function getWispDomain(domain: string): Promise<DomainLookup | null> {
78
+
const key = domain.toLowerCase();
79
+
80
+
// Check cache first
81
+
const cached = wispDomainCache.get(key);
82
+
if (cached !== null) {
83
+
return cached;
84
+
}
85
+
86
+
// Query database
87
const result = await sql<DomainLookup[]>`
88
+
SELECT did, rkey FROM domains WHERE domain = ${key} LIMIT 1
89
`;
90
+
const data = result[0] || null;
91
+
92
+
// Store in cache
93
+
wispDomainCache.set(key, data);
94
+
95
+
return data;
96
}
97
98
export async function getCustomDomain(domain: string): Promise<CustomDomainLookup | null> {
99
+
const key = domain.toLowerCase();
100
+
101
+
// Check cache first
102
+
const cached = customDomainCache.get(key);
103
+
if (cached !== null) {
104
+
return cached;
105
+
}
106
+
107
+
// Query database
108
const result = await sql<CustomDomainLookup[]>`
109
SELECT id, domain, did, rkey, verified FROM custom_domains
110
+
WHERE domain = ${key} AND verified = true LIMIT 1
111
`;
112
+
const data = result[0] || null;
113
+
114
+
// Store in cache
115
+
customDomainCache.set(key, data);
116
+
117
+
return data;
118
}
119
120
export async function getCustomDomainByHash(hash: string): Promise<CustomDomainLookup | null> {
121
+
// Check cache first
122
+
const cached = customDomainHashCache.get(hash);
123
+
if (cached !== null) {
124
+
return cached;
125
+
}
126
+
127
+
// Query database
128
const result = await sql<CustomDomainLookup[]>`
129
SELECT id, domain, did, rkey, verified FROM custom_domains
130
WHERE id = ${hash} AND verified = true LIMIT 1
131
`;
132
+
const data = result[0] || null;
133
+
134
+
// Store in cache
135
+
customDomainHashCache.set(hash, data);
136
+
137
+
return data;
138
}
139
140
export async function upsertSite(did: string, rkey: string, displayName?: string) {
+1
-1
hosting-service/src/lib/firehose.ts
+1
-1
hosting-service/src/lib/firehose.ts
+29
-10
hosting-service/src/lib/html-rewriter.ts
+29
-10
hosting-service/src/lib/html-rewriter.ts
···
16
* Check if a path should be rewritten
17
*/
18
function shouldRewritePath(path: string): boolean {
19
-
// Must start with /
20
-
if (!path.startsWith('/')) return false;
21
22
-
// Don't rewrite protocol-relative URLs
23
-
if (path.startsWith('//')) return false;
24
25
-
// Don't rewrite anchors
26
-
if (path.startsWith('/#')) return false;
27
28
-
// Don't rewrite data URIs or other schemes
29
-
if (path.includes(':')) return false;
30
31
return true;
32
}
33
···
39
return path;
40
}
41
42
-
// Remove leading slash and prepend base path
43
-
return basePath + path.slice(1);
44
}
45
46
/**
···
16
* Check if a path should be rewritten
17
*/
18
function shouldRewritePath(path: string): boolean {
19
+
// Don't rewrite empty paths
20
+
if (!path) return false;
21
22
+
// Don't rewrite external URLs (http://, https://, //)
23
+
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('//')) {
24
+
return false;
25
+
}
26
27
+
// Don't rewrite data URIs or other schemes (except file paths)
28
+
if (path.includes(':') && !path.startsWith('./') && !path.startsWith('../')) {
29
+
return false;
30
+
}
31
32
+
// Don't rewrite pure anchors
33
+
if (path.startsWith('#')) return false;
34
35
+
// Rewrite absolute paths (/) and relative paths (./ or ../ or plain filenames)
36
return true;
37
}
38
···
44
return path;
45
}
46
47
+
// Handle absolute paths: /file.js -> /base/file.js
48
+
if (path.startsWith('/')) {
49
+
return basePath + path.slice(1);
50
+
}
51
+
52
+
// Handle relative paths: ./file.js or ../file.js or file.js -> /base/file.js
53
+
// Strip leading ./ or ../ and just use the base path
54
+
let cleanPath = path;
55
+
if (cleanPath.startsWith('./')) {
56
+
cleanPath = cleanPath.slice(2);
57
+
} else if (cleanPath.startsWith('../')) {
58
+
// For sites.wisp.place, we can't go up from the site root, so just use base path
59
+
cleanPath = cleanPath.replace(/^(\.\.\/)+/, '');
60
+
}
61
+
62
+
return basePath + cleanPath;
63
}
64
65
/**
+75
-13
hosting-service/src/lib/utils.ts
+75
-13
hosting-service/src/lib/utils.ts
···
1
import { AtpAgent } from '@atproto/api';
2
import type { WispFsRecord, Directory, Entry, File } from './types';
3
-
import { existsSync, mkdirSync, readFileSync } from 'fs';
4
-
import { writeFile, readFile } from 'fs/promises';
5
import { safeFetchJson, safeFetchBlob } from './safe-fetch';
6
import { CID } from 'multiformats/cid';
7
···
153
throw new Error('Invalid record structure: root missing entries array');
154
}
155
156
-
await cacheFiles(did, rkey, record.root.entries, pdsEndpoint, '');
157
158
-
await saveCacheMetadata(did, rkey, recordCid);
159
}
160
161
async function cacheFiles(
···
163
site: string,
164
entries: Entry[],
165
pdsEndpoint: string,
166
-
pathPrefix: string
167
): Promise<void> {
168
for (const entry of entries) {
169
const currentPath = pathPrefix ? `${pathPrefix}/${entry.name}` : entry.name;
170
const node = entry.node;
171
172
if ('type' in node && node.type === 'directory' && 'entries' in node) {
173
-
await cacheFiles(did, site, node.entries, pdsEndpoint, currentPath);
174
} else if ('type' in node && node.type === 'file' && 'blob' in node) {
175
-
await cacheFileBlob(did, site, currentPath, node.blob, pdsEndpoint);
176
}
177
}
178
}
···
182
site: string,
183
filePath: string,
184
blobRef: any,
185
-
pdsEndpoint: string
186
): Promise<void> {
187
const cid = extractBlobCid(blobRef);
188
if (!cid) {
···
193
const blobUrl = `${pdsEndpoint}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`;
194
195
// Allow up to 100MB per file blob
196
-
const content = await safeFetchBlob(blobUrl, { maxSize: 100 * 1024 * 1024 });
197
198
-
const cacheFile = `${CACHE_DIR}/${did}/${site}/${filePath}`;
199
const fileDir = cacheFile.substring(0, cacheFile.lastIndexOf('/'));
200
201
if (fileDir && !existsSync(fileDir)) {
···
203
}
204
205
await writeFile(cacheFile, content);
206
-
console.log('Cached file', filePath, content.length, 'bytes');
207
}
208
209
/**
···
238
return existsSync(`${CACHE_DIR}/${did}/${site}`);
239
}
240
241
-
async function saveCacheMetadata(did: string, rkey: string, recordCid: string): Promise<void> {
242
const metadata: CacheMetadata = {
243
recordCid,
244
cachedAt: Date.now(),
···
246
rkey
247
};
248
249
-
const metadataPath = `${CACHE_DIR}/${did}/${rkey}/.metadata.json`;
250
const metadataDir = metadataPath.substring(0, metadataPath.lastIndexOf('/'));
251
252
if (!existsSync(metadataDir)) {
···
1
import { AtpAgent } from '@atproto/api';
2
import type { WispFsRecord, Directory, Entry, File } from './types';
3
+
import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs';
4
+
import { writeFile, readFile, rename } from 'fs/promises';
5
import { safeFetchJson, safeFetchBlob } from './safe-fetch';
6
import { CID } from 'multiformats/cid';
7
···
153
throw new Error('Invalid record structure: root missing entries array');
154
}
155
156
+
// Use a temporary directory with timestamp to avoid collisions
157
+
const tempSuffix = `.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
158
+
const tempDir = `${CACHE_DIR}/${did}/${rkey}${tempSuffix}`;
159
+
const finalDir = `${CACHE_DIR}/${did}/${rkey}`;
160
161
+
try {
162
+
// Download to temporary directory
163
+
await cacheFiles(did, rkey, record.root.entries, pdsEndpoint, '', tempSuffix);
164
+
await saveCacheMetadata(did, rkey, recordCid, tempSuffix);
165
+
166
+
// Atomically replace old cache with new cache
167
+
// On POSIX systems (Linux/macOS), rename is atomic
168
+
if (existsSync(finalDir)) {
169
+
// Rename old directory to backup
170
+
const backupDir = `${finalDir}.old-${Date.now()}`;
171
+
await rename(finalDir, backupDir);
172
+
173
+
try {
174
+
// Rename new directory to final location
175
+
await rename(tempDir, finalDir);
176
+
177
+
// Clean up old backup
178
+
rmSync(backupDir, { recursive: true, force: true });
179
+
} catch (err) {
180
+
// If rename failed, restore backup
181
+
if (existsSync(backupDir) && !existsSync(finalDir)) {
182
+
await rename(backupDir, finalDir);
183
+
}
184
+
throw err;
185
+
}
186
+
} else {
187
+
// No existing cache, just rename temp to final
188
+
await rename(tempDir, finalDir);
189
+
}
190
+
191
+
console.log('Successfully cached site atomically', did, rkey);
192
+
} catch (err) {
193
+
// Clean up temp directory on failure
194
+
if (existsSync(tempDir)) {
195
+
rmSync(tempDir, { recursive: true, force: true });
196
+
}
197
+
throw err;
198
+
}
199
}
200
201
async function cacheFiles(
···
203
site: string,
204
entries: Entry[],
205
pdsEndpoint: string,
206
+
pathPrefix: string,
207
+
dirSuffix: string = ''
208
): Promise<void> {
209
for (const entry of entries) {
210
const currentPath = pathPrefix ? `${pathPrefix}/${entry.name}` : entry.name;
211
const node = entry.node;
212
213
if ('type' in node && node.type === 'directory' && 'entries' in node) {
214
+
await cacheFiles(did, site, node.entries, pdsEndpoint, currentPath, dirSuffix);
215
} else if ('type' in node && node.type === 'file' && 'blob' in node) {
216
+
const fileNode = node as File;
217
+
await cacheFileBlob(did, site, currentPath, fileNode.blob, pdsEndpoint, fileNode.encoding, fileNode.mimeType, fileNode.base64, dirSuffix);
218
}
219
}
220
}
···
224
site: string,
225
filePath: string,
226
blobRef: any,
227
+
pdsEndpoint: string,
228
+
encoding?: 'gzip',
229
+
mimeType?: string,
230
+
base64?: boolean,
231
+
dirSuffix: string = ''
232
): Promise<void> {
233
const cid = extractBlobCid(blobRef);
234
if (!cid) {
···
239
const blobUrl = `${pdsEndpoint}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`;
240
241
// Allow up to 100MB per file blob
242
+
let content = await safeFetchBlob(blobUrl, { maxSize: 100 * 1024 * 1024 });
243
244
+
// If content is base64-encoded, decode it back to gzipped binary
245
+
if (base64 && encoding === 'gzip') {
246
+
// Convert Uint8Array to Buffer for proper string conversion
247
+
const buffer = Buffer.from(content);
248
+
const base64String = buffer.toString('utf-8');
249
+
content = Buffer.from(base64String, 'base64');
250
+
}
251
+
252
+
const cacheFile = `${CACHE_DIR}/${did}/${site}${dirSuffix}/${filePath}`;
253
const fileDir = cacheFile.substring(0, cacheFile.lastIndexOf('/'));
254
255
if (fileDir && !existsSync(fileDir)) {
···
257
}
258
259
await writeFile(cacheFile, content);
260
+
261
+
// Store metadata if file is compressed
262
+
if (encoding === 'gzip' && mimeType) {
263
+
const metaFile = `${cacheFile}.meta`;
264
+
await writeFile(metaFile, JSON.stringify({ encoding, mimeType }));
265
+
console.log('Cached file', filePath, content.length, 'bytes (gzipped,', mimeType + ')');
266
+
} else {
267
+
console.log('Cached file', filePath, content.length, 'bytes');
268
+
}
269
}
270
271
/**
···
300
return existsSync(`${CACHE_DIR}/${did}/${site}`);
301
}
302
303
+
async function saveCacheMetadata(did: string, rkey: string, recordCid: string, dirSuffix: string = ''): Promise<void> {
304
const metadata: CacheMetadata = {
305
recordCid,
306
cachedAt: Date.now(),
···
308
rkey
309
};
310
311
+
const metadataPath = `${CACHE_DIR}/${did}/${rkey}${dirSuffix}/.metadata.json`;
312
const metadataDir = metadataPath.substring(0, metadataPath.lastIndexOf('/'));
313
314
if (!existsSync(metadataDir)) {
+188
-106
hosting-service/src/server.ts
+188
-106
hosting-service/src/server.ts
···
1
-
import { Hono } from 'hono';
2
import { getWispDomain, getCustomDomain, getCustomDomainByHash } from './lib/db';
3
import { resolveDid, getPdsForDid, fetchSiteRecord, downloadAndCacheSite, getCachedFilePath, isCached, sanitizePath } from './lib/utils';
4
import { rewriteHtmlPaths, isHtmlContent } from './lib/html-rewriter';
5
import { existsSync, readFileSync } from 'fs';
6
import { lookup } from 'mime-types';
7
-
8
-
const app = new Hono();
9
10
const BASE_HOST = process.env.BASE_HOST || 'wisp.place';
11
···
34
35
if (existsSync(cachedFile)) {
36
const content = readFileSync(cachedFile);
37
const mimeType = lookup(cachedFile) || 'application/octet-stream';
38
return new Response(content, {
39
headers: {
···
47
const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`);
48
if (existsSync(indexFile)) {
49
const content = readFileSync(indexFile);
50
return new Response(content, {
51
headers: {
52
'Content-Type': 'text/html; charset=utf-8',
···
74
const cachedFile = getCachedFilePath(did, rkey, requestPath);
75
76
if (existsSync(cachedFile)) {
77
-
const mimeType = lookup(cachedFile) || 'application/octet-stream';
78
79
// Check if this is HTML content that needs rewriting
80
if (isHtmlContent(requestPath, mimeType)) {
81
-
const content = readFileSync(cachedFile, 'utf-8');
82
const rewritten = rewriteHtmlPaths(content, basePath);
83
return new Response(rewritten, {
84
headers: {
···
87
});
88
}
89
90
-
// Non-HTML files served with proper MIME type
91
const content = readFileSync(cachedFile);
92
return new Response(content, {
93
headers: {
94
'Content-Type': mimeType,
···
100
if (!requestPath.includes('.')) {
101
const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`);
102
if (existsSync(indexFile)) {
103
-
const content = readFileSync(indexFile, 'utf-8');
104
const rewritten = rewriteHtmlPaths(content, basePath);
105
return new Response(rewritten, {
106
headers: {
···
141
}
142
}
143
144
-
// Route 4: Direct file serving (no DB) - sites.wisp.place/:identifier/:site/*
145
-
// This route is now handled in the catch-all route below
146
147
-
// Route 3: DNS routing for custom domains - /hash.dns.wisp.place/*
148
-
app.get('/*', async (c) => {
149
-
const hostname = c.req.header('host') || '';
150
-
const rawPath = c.req.path.replace(/^\//, '');
151
-
const path = sanitizePath(rawPath);
152
153
-
console.log('[Request]', { hostname, path });
154
-
155
-
// Check if this is sites.wisp.place subdomain
156
-
if (hostname === `sites.${BASE_HOST}` || hostname === `sites.${BASE_HOST}:${process.env.PORT || 3000}`) {
157
-
// Sanitize the path FIRST to prevent path traversal
158
-
const sanitizedFullPath = sanitizePath(rawPath);
159
160
-
// Extract identifier and site from sanitized path: did:plc:123abc/sitename/file.html
161
-
const pathParts = sanitizedFullPath.split('/');
162
-
if (pathParts.length < 2) {
163
-
return c.text('Invalid path format. Expected: /identifier/sitename/path', 400);
164
-
}
165
166
-
const identifier = pathParts[0];
167
-
const site = pathParts[1];
168
-
const filePath = pathParts.slice(2).join('/');
169
170
-
console.log('[Sites] Serving', { identifier, site, filePath });
171
172
-
// Additional validation: identifier must be a valid DID or handle format
173
-
if (!identifier || identifier.length < 3 || identifier.includes('..') || identifier.includes('\0')) {
174
-
return c.text('Invalid identifier', 400);
175
-
}
176
177
-
// Validate site name (rkey)
178
-
if (!isValidRkey(site)) {
179
-
return c.text('Invalid site name', 400);
180
-
}
181
182
-
// Resolve identifier to DID
183
-
const did = await resolveDid(identifier);
184
-
if (!did) {
185
-
return c.text('Invalid identifier', 400);
186
}
187
188
-
// Ensure site is cached
189
-
const cached = await ensureSiteCached(did, site);
190
-
if (!cached) {
191
-
return c.text('Site not found', 404);
192
-
}
193
194
-
// Serve with HTML path rewriting to handle absolute paths
195
-
const basePath = `/${identifier}/${site}/`;
196
-
return serveFromCacheWithRewrite(did, site, filePath, basePath);
197
-
}
198
199
-
// Check if this is a DNS hash subdomain
200
-
const dnsMatch = hostname.match(/^([a-f0-9]{16})\.dns\.(.+)$/);
201
-
if (dnsMatch) {
202
-
const hash = dnsMatch[1];
203
-
const baseDomain = dnsMatch[2];
204
205
-
console.log('[DNS Hash] Looking up', { hash, baseDomain });
206
207
-
if (baseDomain !== BASE_HOST) {
208
-
return c.text('Invalid base domain', 400);
209
-
}
210
211
-
const customDomain = await getCustomDomainByHash(hash);
212
-
if (!customDomain) {
213
-
return c.text('Custom domain not found or not verified', 404);
214
}
215
216
-
const rkey = customDomain.rkey || 'self';
217
-
if (!isValidRkey(rkey)) {
218
-
return c.text('Invalid site configuration', 500);
219
-
}
220
221
-
const cached = await ensureSiteCached(customDomain.did, rkey);
222
-
if (!cached) {
223
-
return c.text('Site not found', 404);
224
-
}
225
226
-
return serveFromCache(customDomain.did, rkey, path);
227
-
}
228
229
-
// Route 2: Registered subdomains - /*.wisp.place/*
230
-
if (hostname.endsWith(`.${BASE_HOST}`)) {
231
-
const subdomain = hostname.replace(`.${BASE_HOST}`, '');
232
233
-
console.log('[Subdomain] Looking up', { subdomain, fullDomain: hostname });
234
235
-
const domainInfo = await getWispDomain(hostname);
236
-
if (!domainInfo) {
237
-
return c.text('Subdomain not registered', 404);
238
}
239
240
-
const rkey = domainInfo.rkey || 'self';
241
if (!isValidRkey(rkey)) {
242
-
return c.text('Invalid site configuration', 500);
243
}
244
245
-
const cached = await ensureSiteCached(domainInfo.did, rkey);
246
if (!cached) {
247
-
return c.text('Site not found', 404);
248
}
249
250
-
return serveFromCache(domainInfo.did, rkey, path);
251
-
}
252
-
253
-
// Route 1: Custom domains - /*
254
-
console.log('[Custom Domain] Looking up', { hostname });
255
-
256
-
const customDomain = await getCustomDomain(hostname);
257
-
if (!customDomain) {
258
-
return c.text('Custom domain not found or not verified', 404);
259
-
}
260
-
261
-
const rkey = customDomain.rkey || 'self';
262
-
if (!isValidRkey(rkey)) {
263
-
return c.text('Invalid site configuration', 500);
264
-
}
265
-
266
-
const cached = await ensureSiteCached(customDomain.did, rkey);
267
-
if (!cached) {
268
-
return c.text('Site not found', 404);
269
-
}
270
-
271
-
return serveFromCache(customDomain.did, rkey, path);
272
-
});
273
274
export default app;
···
1
+
import { Elysia } from 'elysia';
2
+
import { node } from '@elysiajs/node'
3
+
import { opentelemetry } from '@elysiajs/opentelemetry';
4
import { getWispDomain, getCustomDomain, getCustomDomainByHash } from './lib/db';
5
import { resolveDid, getPdsForDid, fetchSiteRecord, downloadAndCacheSite, getCachedFilePath, isCached, sanitizePath } from './lib/utils';
6
import { rewriteHtmlPaths, isHtmlContent } from './lib/html-rewriter';
7
import { existsSync, readFileSync } from 'fs';
8
import { lookup } from 'mime-types';
9
10
const BASE_HOST = process.env.BASE_HOST || 'wisp.place';
11
···
34
35
if (existsSync(cachedFile)) {
36
const content = readFileSync(cachedFile);
37
+
const metaFile = `${cachedFile}.meta`;
38
+
39
+
// Check if file has compression metadata
40
+
if (existsSync(metaFile)) {
41
+
const meta = JSON.parse(readFileSync(metaFile, 'utf-8'));
42
+
if (meta.encoding === 'gzip' && meta.mimeType) {
43
+
// Serve gzipped content with proper headers
44
+
return new Response(content, {
45
+
headers: {
46
+
'Content-Type': meta.mimeType,
47
+
'Content-Encoding': 'gzip',
48
+
},
49
+
});
50
+
}
51
+
}
52
+
53
+
// Serve non-compressed files normally
54
const mimeType = lookup(cachedFile) || 'application/octet-stream';
55
return new Response(content, {
56
headers: {
···
64
const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`);
65
if (existsSync(indexFile)) {
66
const content = readFileSync(indexFile);
67
+
const metaFile = `${indexFile}.meta`;
68
+
69
+
// Check if file has compression metadata
70
+
if (existsSync(metaFile)) {
71
+
const meta = JSON.parse(readFileSync(metaFile, 'utf-8'));
72
+
if (meta.encoding === 'gzip' && meta.mimeType) {
73
+
return new Response(content, {
74
+
headers: {
75
+
'Content-Type': meta.mimeType,
76
+
'Content-Encoding': 'gzip',
77
+
},
78
+
});
79
+
}
80
+
}
81
+
82
return new Response(content, {
83
headers: {
84
'Content-Type': 'text/html; charset=utf-8',
···
106
const cachedFile = getCachedFilePath(did, rkey, requestPath);
107
108
if (existsSync(cachedFile)) {
109
+
const metaFile = `${cachedFile}.meta`;
110
+
let mimeType = lookup(cachedFile) || 'application/octet-stream';
111
+
let isGzipped = false;
112
+
113
+
// Check if file has compression metadata
114
+
if (existsSync(metaFile)) {
115
+
const meta = JSON.parse(readFileSync(metaFile, 'utf-8'));
116
+
if (meta.encoding === 'gzip' && meta.mimeType) {
117
+
mimeType = meta.mimeType;
118
+
isGzipped = true;
119
+
}
120
+
}
121
122
// Check if this is HTML content that needs rewriting
123
+
// Note: For gzipped HTML with path rewriting, we need to decompress, rewrite, and serve uncompressed
124
+
// This is a trade-off for the sites.wisp.place domain which needs path rewriting
125
if (isHtmlContent(requestPath, mimeType)) {
126
+
let content: string;
127
+
if (isGzipped) {
128
+
const { gunzipSync } = await import('zlib');
129
+
const compressed = readFileSync(cachedFile);
130
+
content = gunzipSync(compressed).toString('utf-8');
131
+
} else {
132
+
content = readFileSync(cachedFile, 'utf-8');
133
+
}
134
const rewritten = rewriteHtmlPaths(content, basePath);
135
return new Response(rewritten, {
136
headers: {
···
139
});
140
}
141
142
+
// Non-HTML files: serve gzipped content as-is with proper headers
143
const content = readFileSync(cachedFile);
144
+
if (isGzipped) {
145
+
return new Response(content, {
146
+
headers: {
147
+
'Content-Type': mimeType,
148
+
'Content-Encoding': 'gzip',
149
+
},
150
+
});
151
+
}
152
return new Response(content, {
153
headers: {
154
'Content-Type': mimeType,
···
160
if (!requestPath.includes('.')) {
161
const indexFile = getCachedFilePath(did, rkey, `${requestPath}/index.html`);
162
if (existsSync(indexFile)) {
163
+
const metaFile = `${indexFile}.meta`;
164
+
let isGzipped = false;
165
+
166
+
if (existsSync(metaFile)) {
167
+
const meta = JSON.parse(readFileSync(metaFile, 'utf-8'));
168
+
if (meta.encoding === 'gzip') {
169
+
isGzipped = true;
170
+
}
171
+
}
172
+
173
+
// HTML needs path rewriting, so decompress if needed
174
+
let content: string;
175
+
if (isGzipped) {
176
+
const { gunzipSync } = await import('zlib');
177
+
const compressed = readFileSync(indexFile);
178
+
content = gunzipSync(compressed).toString('utf-8');
179
+
} else {
180
+
content = readFileSync(indexFile, 'utf-8');
181
+
}
182
const rewritten = rewriteHtmlPaths(content, basePath);
183
return new Response(rewritten, {
184
headers: {
···
219
}
220
}
221
222
+
const app = new Elysia({ adapter: node() })
223
+
.use(opentelemetry())
224
+
.get('/*', async ({ request, set }) => {
225
+
const url = new URL(request.url);
226
+
const hostname = request.headers.get('host') || '';
227
+
const rawPath = url.pathname.replace(/^\//, '');
228
+
const path = sanitizePath(rawPath);
229
230
+
// Check if this is sites.wisp.place subdomain
231
+
if (hostname === `sites.${BASE_HOST}` || hostname === `sites.${BASE_HOST}:${process.env.PORT || 3000}`) {
232
+
// Sanitize the path FIRST to prevent path traversal
233
+
const sanitizedFullPath = sanitizePath(rawPath);
234
235
+
// Extract identifier and site from sanitized path: did:plc:123abc/sitename/file.html
236
+
const pathParts = sanitizedFullPath.split('/');
237
+
if (pathParts.length < 2) {
238
+
set.status = 400;
239
+
return 'Invalid path format. Expected: /identifier/sitename/path';
240
+
}
241
242
+
const identifier = pathParts[0];
243
+
const site = pathParts[1];
244
+
const filePath = pathParts.slice(2).join('/');
245
246
+
// Additional validation: identifier must be a valid DID or handle format
247
+
if (!identifier || identifier.length < 3 || identifier.includes('..') || identifier.includes('\0')) {
248
+
set.status = 400;
249
+
return 'Invalid identifier';
250
+
}
251
252
+
// Validate site name (rkey)
253
+
if (!isValidRkey(site)) {
254
+
set.status = 400;
255
+
return 'Invalid site name';
256
+
}
257
258
+
// Resolve identifier to DID
259
+
const did = await resolveDid(identifier);
260
+
if (!did) {
261
+
set.status = 400;
262
+
return 'Invalid identifier';
263
+
}
264
265
+
// Ensure site is cached
266
+
const cached = await ensureSiteCached(did, site);
267
+
if (!cached) {
268
+
set.status = 404;
269
+
return 'Site not found';
270
+
}
271
272
+
// Serve with HTML path rewriting to handle absolute paths
273
+
const basePath = `/${identifier}/${site}/`;
274
+
return serveFromCacheWithRewrite(did, site, filePath, basePath);
275
}
276
277
+
// Check if this is a DNS hash subdomain
278
+
const dnsMatch = hostname.match(/^([a-f0-9]{16})\.dns\.(.+)$/);
279
+
if (dnsMatch) {
280
+
const hash = dnsMatch[1];
281
+
const baseDomain = dnsMatch[2];
282
283
+
if (baseDomain !== BASE_HOST) {
284
+
set.status = 400;
285
+
return 'Invalid base domain';
286
+
}
287
288
+
const customDomain = await getCustomDomainByHash(hash);
289
+
if (!customDomain) {
290
+
set.status = 404;
291
+
return 'Custom domain not found or not verified';
292
+
}
293
294
+
const rkey = customDomain.rkey || 'self';
295
+
if (!isValidRkey(rkey)) {
296
+
set.status = 500;
297
+
return 'Invalid site configuration';
298
+
}
299
300
+
const cached = await ensureSiteCached(customDomain.did, rkey);
301
+
if (!cached) {
302
+
set.status = 404;
303
+
return 'Site not found';
304
+
}
305
306
+
return serveFromCache(customDomain.did, rkey, path);
307
}
308
309
+
// Route 2: Registered subdomains - /*.wisp.place/*
310
+
if (hostname.endsWith(`.${BASE_HOST}`)) {
311
+
const subdomain = hostname.replace(`.${BASE_HOST}`, '');
312
313
+
const domainInfo = await getWispDomain(hostname);
314
+
if (!domainInfo) {
315
+
set.status = 404;
316
+
return 'Subdomain not registered';
317
+
}
318
319
+
const rkey = domainInfo.rkey || 'self';
320
+
if (!isValidRkey(rkey)) {
321
+
set.status = 500;
322
+
return 'Invalid site configuration';
323
+
}
324
325
+
const cached = await ensureSiteCached(domainInfo.did, rkey);
326
+
if (!cached) {
327
+
set.status = 404;
328
+
return 'Site not found';
329
+
}
330
331
+
return serveFromCache(domainInfo.did, rkey, path);
332
+
}
333
334
+
// Route 1: Custom domains - /*
335
+
const customDomain = await getCustomDomain(hostname);
336
+
if (!customDomain) {
337
+
set.status = 404;
338
+
return 'Custom domain not found or not verified';
339
}
340
341
+
const rkey = customDomain.rkey || 'self';
342
if (!isValidRkey(rkey)) {
343
+
set.status = 500;
344
+
return 'Invalid site configuration';
345
}
346
347
+
const cached = await ensureSiteCached(customDomain.did, rkey);
348
if (!cached) {
349
+
set.status = 404;
350
+
return 'Site not found';
351
}
352
353
+
return serveFromCache(customDomain.did, rkey, path);
354
+
});
355
356
export default app;
+4
-1
lexicons/fs.json
+4
-1
lexicons/fs.json
···
21
"required": ["type", "blob"],
22
"properties": {
23
"type": { "type": "string", "const": "file" },
24
+
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000, "description": "Content blob ref" },
25
+
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
26
+
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
27
+
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" }
28
}
29
},
30
"directory": {
+3
-1
package.json
+3
-1
package.json
···
4
"scripts": {
5
"test": "echo \"Error: no test specified\" && exit 1",
6
"dev": "bun run --watch src/index.ts",
7
"build": "bun build --compile --target bun --outfile server src/index.ts"
8
},
9
"dependencies": {
···
31
"tailwind-merge": "^3.3.1",
32
"tailwindcss": "4",
33
"tw-animate-css": "^1.4.0",
34
-
"typescript": "^5.9.3"
35
},
36
"devDependencies": {
37
"@types/react": "^19.2.2",
···
4
"scripts": {
5
"test": "echo \"Error: no test specified\" && exit 1",
6
"dev": "bun run --watch src/index.ts",
7
+
"start": "bun run src/index.ts",
8
"build": "bun build --compile --target bun --outfile server src/index.ts"
9
},
10
"dependencies": {
···
32
"tailwind-merge": "^3.3.1",
33
"tailwindcss": "4",
34
"tw-animate-css": "^1.4.0",
35
+
"typescript": "^5.9.3",
36
+
"zlib": "^1.0.5"
37
},
38
"devDependencies": {
39
"@types/react": "^19.2.2",
+14
src/lexicon/lexicons.ts
+14
src/lexicon/lexicons.ts
···
54
maxSize: 1000000,
55
description: 'Content blob ref',
56
},
57
+
encoding: {
58
+
type: 'string',
59
+
enum: ['gzip'],
60
+
description: 'Content encoding (e.g., gzip for compressed files)',
61
+
},
62
+
mimeType: {
63
+
type: 'string',
64
+
description: 'Original MIME type before compression',
65
+
},
66
+
base64: {
67
+
type: 'boolean',
68
+
description:
69
+
'True if blob content is base64-encoded (used to bypass PDS content sniffing)',
70
+
},
71
},
72
},
73
directory: {
+6
src/lexicon/types/place/wisp/fs.ts
+6
src/lexicon/types/place/wisp/fs.ts
···
34
type: 'file'
35
/** Content blob ref */
36
blob: BlobRef
37
+
/** Content encoding (e.g., gzip for compressed files) */
38
+
encoding?: 'gzip'
39
+
/** Original MIME type before compression */
40
+
mimeType?: string
41
+
/** True if blob content is base64-encoded (used to bypass PDS content sniffing) */
42
+
base64?: boolean
43
}
44
45
const hashFile = 'file'
+41
-2
src/lib/wisp-utils.ts
+41
-2
src/lib/wisp-utils.ts
···
1
import type { BlobRef } from "@atproto/api";
2
import type { Record, Directory, File, Entry } from "../lexicon/types/place/wisp/fs";
3
import { validateRecord } from "../lexicon/types/place/wisp/fs";
4
5
export interface UploadedFile {
6
name: string;
7
content: Buffer;
8
mimeType: string;
9
size: number;
10
}
11
12
export interface FileUploadResult {
13
hash: string;
14
blobRef: BlobRef;
15
}
16
17
export interface ProcessedDirectory {
18
directory: Directory;
19
fileCount: number;
20
}
21
22
/**
···
168
});
169
170
if (fileIndex !== -1 && uploadResults[fileIndex]) {
171
-
const blobRef = uploadResults[fileIndex].blobRef;
172
173
return {
174
...entry,
175
node: {
176
$type: 'place.wisp.fs#file' as const,
177
type: 'file' as const,
178
-
blob: blobRef
179
}
180
};
181
} else {
···
1
import type { BlobRef } from "@atproto/api";
2
import type { Record, Directory, File, Entry } from "../lexicon/types/place/wisp/fs";
3
import { validateRecord } from "../lexicon/types/place/wisp/fs";
4
+
import { gzipSync } from 'zlib';
5
6
export interface UploadedFile {
7
name: string;
8
content: Buffer;
9
mimeType: string;
10
size: number;
11
+
compressed?: boolean;
12
+
originalMimeType?: string;
13
}
14
15
export interface FileUploadResult {
16
hash: string;
17
blobRef: BlobRef;
18
+
encoding?: 'gzip';
19
+
mimeType?: string;
20
+
base64?: boolean;
21
}
22
23
export interface ProcessedDirectory {
24
directory: Directory;
25
fileCount: number;
26
+
}
27
+
28
+
/**
29
+
* Determine if a file should be gzip compressed based on its MIME type
30
+
*/
31
+
export function shouldCompressFile(mimeType: string): boolean {
32
+
// Compress text-based files
33
+
const compressibleTypes = [
34
+
'text/html',
35
+
'text/css',
36
+
'text/javascript',
37
+
'application/javascript',
38
+
'application/json',
39
+
'image/svg+xml',
40
+
'text/xml',
41
+
'application/xml',
42
+
'text/plain',
43
+
'application/x-javascript'
44
+
];
45
+
46
+
// Check if mime type starts with any compressible type
47
+
return compressibleTypes.some(type => mimeType.startsWith(type));
48
+
}
49
+
50
+
/**
51
+
* Compress a file using gzip
52
+
*/
53
+
export function compressFile(content: Buffer): Buffer {
54
+
return gzipSync(content, { level: 9 });
55
}
56
57
/**
···
203
});
204
205
if (fileIndex !== -1 && uploadResults[fileIndex]) {
206
+
const result = uploadResults[fileIndex];
207
+
const blobRef = result.blobRef;
208
209
return {
210
...entry,
211
node: {
212
$type: 'place.wisp.fs#file' as const,
213
type: 'file' as const,
214
+
blob: blobRef,
215
+
...(result.encoding && { encoding: result.encoding }),
216
+
...(result.mimeType && { mimeType: result.mimeType }),
217
+
...(result.base64 && { base64: result.base64 })
218
}
219
};
220
} else {
+53
-12
src/routes/wisp.ts
+53
-12
src/routes/wisp.ts
···
7
type FileUploadResult,
8
processUploadedFiles,
9
createManifest,
10
-
updateFileBlobs
11
} from '../lib/wisp-utils'
12
import { upsertSite } from '../lib/db'
13
import { logger } from '../lib/logger'
···
164
}
165
166
const arrayBuffer = await file.arrayBuffer();
167
-
uploadedFiles.push({
168
-
name: file.name,
169
-
content: Buffer.from(arrayBuffer),
170
-
mimeType: 'application/octet-stream',
171
-
size: file.size
172
-
});
173
}
174
175
// Check total size limit (300MB)
···
226
// Process files into directory structure
227
const { directory, fileCount } = processUploadedFiles(uploadedFiles);
228
229
-
// Upload files as blobs in parallel (always as octet-stream)
230
const uploadPromises = uploadedFiles.map(async (file, i) => {
231
try {
232
const uploadResult = await agent.com.atproto.repo.uploadBlob(
233
file.content,
234
{
235
-
encoding: 'application/octet-stream'
236
}
237
);
238
239
-
const sentMimeType = file.mimeType;
240
const returnedBlobRef = uploadResult.data.blob;
241
242
// Use the blob ref exactly as returned from PDS
243
return {
244
result: {
245
hash: returnedBlobRef.ref.toString(),
246
-
blobRef: returnedBlobRef
247
},
248
filePath: file.name,
249
-
sentMimeType,
250
returnedMimeType: returnedBlobRef.mimeType
251
};
252
} catch (uploadError) {
···
7
type FileUploadResult,
8
processUploadedFiles,
9
createManifest,
10
+
updateFileBlobs,
11
+
shouldCompressFile,
12
+
compressFile
13
} from '../lib/wisp-utils'
14
import { upsertSite } from '../lib/db'
15
import { logger } from '../lib/logger'
···
166
}
167
168
const arrayBuffer = await file.arrayBuffer();
169
+
const originalContent = Buffer.from(arrayBuffer);
170
+
const originalMimeType = file.type || 'application/octet-stream';
171
+
172
+
// Determine if we should compress this file
173
+
const shouldCompress = shouldCompressFile(originalMimeType);
174
+
175
+
if (shouldCompress) {
176
+
const compressedContent = compressFile(originalContent);
177
+
// Base64 encode the gzipped content to prevent PDS content sniffing
178
+
const base64Content = Buffer.from(compressedContent.toString('base64'), 'utf-8');
179
+
const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1);
180
+
logger.info(`[Wisp] Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${base64Content.length} bytes`);
181
+
182
+
uploadedFiles.push({
183
+
name: file.name,
184
+
content: base64Content,
185
+
mimeType: originalMimeType,
186
+
size: base64Content.length,
187
+
compressed: true,
188
+
originalMimeType
189
+
});
190
+
} else {
191
+
uploadedFiles.push({
192
+
name: file.name,
193
+
content: originalContent,
194
+
mimeType: originalMimeType,
195
+
size: file.size,
196
+
compressed: false
197
+
});
198
+
}
199
}
200
201
// Check total size limit (300MB)
···
252
// Process files into directory structure
253
const { directory, fileCount } = processUploadedFiles(uploadedFiles);
254
255
+
// Upload files as blobs in parallel
256
+
// For compressed files, we upload as octet-stream and store the original MIME type in metadata
257
+
// For text/html files, we also use octet-stream as a workaround for PDS image pipeline issues
258
const uploadPromises = uploadedFiles.map(async (file, i) => {
259
try {
260
+
// If compressed, always upload as octet-stream
261
+
// Otherwise, workaround: PDS incorrectly processes text/html through image pipeline
262
+
const uploadMimeType = file.compressed || file.mimeType.startsWith('text/html')
263
+
? 'application/octet-stream'
264
+
: file.mimeType;
265
+
266
+
const compressionInfo = file.compressed ? ' (gzipped)' : '';
267
+
logger.info(`[Wisp] Uploading file: ${file.name} (original: ${file.mimeType}, sending as: ${uploadMimeType}, ${file.size} bytes${compressionInfo})`);
268
+
269
const uploadResult = await agent.com.atproto.repo.uploadBlob(
270
file.content,
271
{
272
+
encoding: uploadMimeType
273
}
274
);
275
276
const returnedBlobRef = uploadResult.data.blob;
277
278
// Use the blob ref exactly as returned from PDS
279
return {
280
result: {
281
hash: returnedBlobRef.ref.toString(),
282
+
blobRef: returnedBlobRef,
283
+
...(file.compressed && {
284
+
encoding: 'gzip' as const,
285
+
mimeType: file.originalMimeType || file.mimeType,
286
+
base64: true
287
+
})
288
},
289
filePath: file.name,
290
+
sentMimeType: file.mimeType,
291
returnedMimeType: returnedBlobRef.mimeType
292
};
293
} catch (uploadError) {