diff --git a/.gitignore b/.gitignore index 0cbc178a9..e70c2b63f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ dist.frozen __target__ *.swp .agent -lib/.claude \ No newline at end of file +lib/.claude +.wrangler diff --git a/package.json b/package.json index f269c3093..02e19ae21 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,8 @@ "esbuild", "protobufjs", "sharp", - "unrs-resolver", - "vue-demi" + "vue-demi", + "workerd" ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 21bf2f36f..7299d4727 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -224,6 +224,18 @@ importers: specifier: ^3.5.31 version: 3.5.31(typescript@6.0.2) + wasmegg/_cloud-worker: + devDependencies: + '@cloudflare/workers-types': + specifier: ^4 + version: 4.20260401.1 + typescript: + specifier: ^6.0.2 + version: 6.0.2 + wrangler: + specifier: ^4 + version: 4.79.0(@cloudflare/workers-types@4.20260401.1) + wasmegg/_home: dependencies: lib: @@ -1678,6 +1690,52 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@cloudflare/kv-asset-handler@0.4.2': + resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} + engines: {node: '>=18.0.0'} + + '@cloudflare/unenv-preset@2.16.0': + resolution: {integrity: sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0 + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/workerd-darwin-64@1.20260329.1': + resolution: {integrity: sha512-oyDXYlPBuGXKkZ85+M3jFz0/qYmvA4AEURN8USIGPDCR5q+HFSRwywSd9neTx3Wi7jhey2wuYaEpD3fEFWyWUA==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260329.1': + resolution: {integrity: sha512-++ZxVa3ovzYeDLEG6zMqql9gzZAG8vak6ZSBQgprGKZp7akr+GKTpw9f3RrMP552NSi3gTisroLobrrkPBtYLQ==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260329.1': + resolution: {integrity: sha512-kkeywAgIHwbqHkVILqbj/YkfbrA6ARbmutjiYzZA2MwMSfNXlw6/kedAKOY8YwcymZIgepx3YTIPnBP50pOotw==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260329.1': + resolution: {integrity: sha512-eYBN20+B7XOUSWEe0mlqkMUbfLoIKjKZnpqQiSxnLbL72JKY0D/KlfN/b7RVGLpewB7i8rTrwTNr0szCKnZzSQ==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260329.1': + resolution: {integrity: sha512-5R+/oxrDhS9nL3oA3ZWtD6ndMOqm7RfKknDNxLcmYW5DkUu7UH3J/s1t/Dz66iFePzr5BJmE7/8gbmve6TjtZQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workers-types@4.20260401.1': + resolution: {integrity: sha512-tKBeV/ySfJjbO0qMKkFrstHDdWzZHAcW4vCpO5QaqjB/667y9lhZt9gZyTKeJ0gluIBwpeQ/efBjqRLqpkgw9g==} + '@codemirror/autocomplete@6.20.1': resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==} @@ -1715,156 +1773,312 @@ packages: '@emnapi/wasi-threads@1.2.0': resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.27.4': resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.27.4': resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.27.4': resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.27.4': resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.27.4': resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.27.4': resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.27.4': resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.4': resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.27.4': resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.27.4': resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.27.4': resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.27.4': resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.27.4': resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.27.4': resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.27.4': resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.27.4': resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.27.4': resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.4': resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.4': resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.4': resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.4': resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.4': resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.27.4': resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.27.4': resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.27.4': resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.27.4': resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} engines: {node: '>=18'} @@ -2226,9 +2440,6 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -2292,6 +2503,15 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + '@profoundlogic/hogan@3.0.4': resolution: {integrity: sha512-pmNVGuooS30Mm7YbZd5T7E5zYVO6D5Ct91sn4T39mUvMUc3sCGridcnhAufL1/Bz2QzAtzEn0agNrdk3+5yWzw==} hasBin: true @@ -2534,12 +2754,19 @@ packages: '@sinclair/typebox@0.34.48': resolution: {integrity: sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==} + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} '@sinonjs/fake-timers@15.1.1': resolution: {integrity: sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==} + '@speed-highlight/core@1.2.15': + resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==} + '@tailwindcss/forms@0.5.10': resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==} peerDependencies: @@ -3103,6 +3330,9 @@ packages: birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} @@ -3271,6 +3501,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + copy-anything@4.0.5: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} @@ -3463,6 +3697,14 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.27.4: resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} engines: {node: '>=18'} @@ -4103,6 +4345,10 @@ packages: klaw@3.0.0: resolution: {integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -4234,6 +4480,9 @@ packages: lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + loglevel-colored-level-prefix@1.0.0: resolution: {integrity: sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==} @@ -4317,8 +4566,13 @@ packages: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true - minimatch@10.2.4: - resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + miniflare@4.20260329.0: + resolution: {integrity: sha512-+G+1YFVeuEpw/gZZmUHQR7IfzJV+DDGvnSl0yXzhgvHh8Nbr8Go5uiWIwl17EyZ1Uors3FKUMDUyU6+ejeKZOw==} + engines: {node: '>=18.0.0'} + hasBin: true + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} minimatch@3.1.2: @@ -4929,6 +5183,10 @@ packages: resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} engines: {node: '>=16'} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -5164,6 +5422,13 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici@7.24.4: + resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -5397,6 +5662,21 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + workerd@1.20260329.1: + resolution: {integrity: sha512-+ifMv3uBuD33ee7pan5n8+sgVxm2u5HnbgfXzHKwMNTKw86znqBJSnJoBqtP88+2T5U2Lu11xXUt+khPYioXwQ==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.79.0: + resolution: {integrity: sha512-NMinIdB1pXIqdk+NLw4+RjzB7K5z4+lWMxhTxFTfZomwJu3Pm6N+kZ+a66D3nI7w0oCjsdv/umrZVmSHCBp2cg==} + engines: {node: '>=20.3.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20260329.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -5412,6 +5692,18 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -5452,6 +5744,12 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -5711,6 +6009,31 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@cloudflare/kv-asset-handler@0.4.2': {} + + '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260329.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260329.1 + + '@cloudflare/workerd-darwin-64@1.20260329.1': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260329.1': + optional: true + + '@cloudflare/workerd-linux-64@1.20260329.1': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260329.1': + optional: true + + '@cloudflare/workerd-windows-64@1.20260329.1': + optional: true + + '@cloudflare/workers-types@4.20260401.1': {} + '@codemirror/autocomplete@6.20.1': dependencies: '@codemirror/language': 6.12.3 @@ -5765,7 +6088,6 @@ snapshots: '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - optional: true '@emnapi/core@1.9.1': dependencies: @@ -5783,81 +6105,159 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.27.3': + optional: true + '@esbuild/aix-ppc64@0.27.4': optional: true + '@esbuild/android-arm64@0.27.3': + optional: true + '@esbuild/android-arm64@0.27.4': optional: true + '@esbuild/android-arm@0.27.3': + optional: true + '@esbuild/android-arm@0.27.4': optional: true + '@esbuild/android-x64@0.27.3': + optional: true + '@esbuild/android-x64@0.27.4': optional: true + '@esbuild/darwin-arm64@0.27.3': + optional: true + '@esbuild/darwin-arm64@0.27.4': optional: true + '@esbuild/darwin-x64@0.27.3': + optional: true + '@esbuild/darwin-x64@0.27.4': optional: true + '@esbuild/freebsd-arm64@0.27.3': + optional: true + '@esbuild/freebsd-arm64@0.27.4': optional: true + '@esbuild/freebsd-x64@0.27.3': + optional: true + '@esbuild/freebsd-x64@0.27.4': optional: true + '@esbuild/linux-arm64@0.27.3': + optional: true + '@esbuild/linux-arm64@0.27.4': optional: true + '@esbuild/linux-arm@0.27.3': + optional: true + '@esbuild/linux-arm@0.27.4': optional: true + '@esbuild/linux-ia32@0.27.3': + optional: true + '@esbuild/linux-ia32@0.27.4': optional: true + '@esbuild/linux-loong64@0.27.3': + optional: true + '@esbuild/linux-loong64@0.27.4': optional: true + '@esbuild/linux-mips64el@0.27.3': + optional: true + '@esbuild/linux-mips64el@0.27.4': optional: true + '@esbuild/linux-ppc64@0.27.3': + optional: true + '@esbuild/linux-ppc64@0.27.4': optional: true + '@esbuild/linux-riscv64@0.27.3': + optional: true + '@esbuild/linux-riscv64@0.27.4': optional: true + '@esbuild/linux-s390x@0.27.3': + optional: true + '@esbuild/linux-s390x@0.27.4': optional: true + '@esbuild/linux-x64@0.27.3': + optional: true + '@esbuild/linux-x64@0.27.4': optional: true + '@esbuild/netbsd-arm64@0.27.3': + optional: true + '@esbuild/netbsd-arm64@0.27.4': optional: true + '@esbuild/netbsd-x64@0.27.3': + optional: true + '@esbuild/netbsd-x64@0.27.4': optional: true + '@esbuild/openbsd-arm64@0.27.3': + optional: true + '@esbuild/openbsd-arm64@0.27.4': optional: true + '@esbuild/openbsd-x64@0.27.3': + optional: true + '@esbuild/openbsd-x64@0.27.4': optional: true + '@esbuild/openharmony-arm64@0.27.3': + optional: true + '@esbuild/openharmony-arm64@0.27.4': optional: true + '@esbuild/sunos-x64@0.27.3': + optional: true + '@esbuild/sunos-x64@0.27.4': optional: true + '@esbuild/win32-arm64@0.27.3': + optional: true + '@esbuild/win32-arm64@0.27.4': optional: true + '@esbuild/win32-ia32@0.27.3': + optional: true + '@esbuild/win32-ia32@0.27.4': optional: true + '@esbuild/win32-x64@0.27.3': + optional: true + '@esbuild/win32-x64@0.27.4': optional: true @@ -5877,7 +6277,7 @@ snapshots: dependencies: '@eslint/object-schema': 3.0.3 debug: 4.4.3 - minimatch: 10.2.4 + minimatch: 10.2.5 transitivePeerDependencies: - supports-color @@ -6251,7 +6651,7 @@ snapshots: dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': dependencies: @@ -6264,11 +6664,6 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -6278,7 +6673,6 @@ snapshots: dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - optional: true '@jsdoc/salty@0.2.7': dependencies: @@ -6344,6 +6738,18 @@ snapshots: '@popperjs/core@2.11.8': {} + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + '@profoundlogic/hogan@3.0.4': dependencies: nopt: 1.0.10 @@ -6480,6 +6886,8 @@ snapshots: '@sinclair/typebox@0.34.48': {} + '@sindresorhus/is@7.2.0': {} + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 @@ -6488,6 +6896,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@speed-highlight/core@1.2.15': {} + '@tailwindcss/forms@0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@25.5.0)(typescript@6.0.2)))': dependencies: mini-svg-data-uri: 1.4.4 @@ -6715,7 +7125,7 @@ snapshots: '@typescript-eslint/types': 8.58.0 '@typescript-eslint/visitor-keys': 8.58.0 debug: 4.4.3 - minimatch: 10.2.4 + minimatch: 10.2.5 semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.5.0(typescript@6.0.2) @@ -7132,6 +7542,8 @@ snapshots: birpc@2.9.0: {} + blake3-wasm@2.1.5: {} + bluebird@3.7.2: {} boolbase@1.0.0: {} @@ -7304,6 +7716,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie@1.1.1: {} + copy-anything@4.0.5: dependencies: is-what: 5.5.0 @@ -7463,6 +7877,37 @@ snapshots: dependencies: is-arrayish: 0.2.1 + error-stack-parser-es@1.0.5: {} + + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + esbuild@0.27.4: optionalDependencies: '@esbuild/aix-ppc64': 0.27.4 @@ -7582,7 +8027,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.2.4 + minimatch: 10.2.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -8374,6 +8819,8 @@ snapshots: dependencies: graceful-fs: 4.2.11 + kleur@4.1.5: {} + leven@3.1.0: {} levn@0.3.0: @@ -8475,6 +8922,8 @@ snapshots: lodash@4.17.23: {} + lodash@4.18.1: {} + loglevel-colored-level-prefix@1.0.0: dependencies: chalk: 1.1.3 @@ -8549,7 +8998,19 @@ snapshots: mini-svg-data-uri@1.4.4: {} - minimatch@10.2.4: + miniflare@4.20260329.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.24.4 + workerd: 1.20260329.1 + ws: 8.18.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + minimatch@10.2.5: dependencies: brace-expansion: 5.0.5 @@ -9170,6 +9631,8 @@ snapshots: dependencies: copy-anything: 4.0.5 + supports-color@10.2.2: {} + supports-color@2.0.0: {} supports-color@7.2.0: @@ -9442,6 +9905,12 @@ snapshots: undici-types@7.18.2: {} + undici@7.24.4: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + unpipe@1.0.0: {} unplugin-utils@0.3.1: @@ -9591,7 +10060,7 @@ snapshots: eslint-visitor-keys: 3.4.3 espree: 9.6.1 esquery: 1.7.0 - lodash: 4.17.23 + lodash: 4.18.1 semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -9662,6 +10131,31 @@ snapshots: wordwrap@1.0.0: {} + workerd@1.20260329.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260329.1 + '@cloudflare/workerd-darwin-arm64': 1.20260329.1 + '@cloudflare/workerd-linux-64': 1.20260329.1 + '@cloudflare/workerd-linux-arm64': 1.20260329.1 + '@cloudflare/workerd-windows-64': 1.20260329.1 + + wrangler@4.79.0(@cloudflare/workers-types@4.20260401.1): + dependencies: + '@cloudflare/kv-asset-handler': 0.4.2 + '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260329.1) + blake3-wasm: 2.1.5 + esbuild: 0.27.3 + miniflare: 4.20260329.0 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260329.1 + optionalDependencies: + '@cloudflare/workers-types': 4.20260401.1 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -9681,6 +10175,8 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 + ws@8.18.0: {} + xml-name-validator@4.0.0: {} xmlcreate@2.0.4: {} @@ -9710,4 +10206,17 @@ snapshots: yocto-queue@0.1.0: {} + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.15 + cookie: 1.1.1 + youch-core: 0.3.3 + zod@4.3.6: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6d107746d..96a045322 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,6 +9,7 @@ packages: - wasmegg/enlightenment - wasmegg/events - wasmegg/_home + - wasmegg/_cloud-worker - wasmegg/inventory-visualizer - wasmegg/legendary-study - wasmegg/loot-simulator diff --git a/wasmegg/_cloud-worker/README.md b/wasmegg/_cloud-worker/README.md new file mode 100644 index 000000000..069d769b1 --- /dev/null +++ b/wasmegg/_cloud-worker/README.md @@ -0,0 +1,58 @@ +# egg-cloud-storage + +Cloudflare Worker providing cloud backup for wasmegg tools, starting with +ascension planner plans. + +## KV key scheme + +``` +plans:{eid_hash_16}:index → JSON: PlanIndexEntry[] +plans:{eid_hash_16}:{plan_id} → gzip-compressed plan JSON (binary) +settings:{eid_hash_16}:{tool_name} → JSON (future: per-tool user settings) +``` + +`eid_hash_16` = first 16 hex characters of SHA-256(lowercased EID). + +## Setup + +```bash +npm install + +# Create a KV namespace +npx wrangler kv:namespace create "WASMEGG_DATA" +npx wrangler kv:namespace create "WASMEGG_DATA" --preview + +# Paste the returned IDs into wrangler.toml (replace the placeholders) + +# Local dev +npm run dev + +# Deploy +npm run deploy +``` + +## Routes + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/v1/plans/:eid_hash` | — | List plan index | +| GET | `/v1/plans/:eid_hash/:plan_id` | — | Download gzipped plan | +| PUT | `/v1/plans/:eid_hash/:plan_id` | X-EID header | Upload / overwrite plan | +| DELETE | `/v1/plans/:eid_hash/:plan_id` | X-EID header | Delete plan | +| GET | `/v1/settings/:eid_hash/:tool` | — | Get tool settings (future) | +| PUT | `/v1/settings/:eid_hash/:tool` | X-EID header | Save tool settings (future) | + +### PUT /v1/plans headers + +| Header | Required | Description | +|--------|----------|-------------| +| `X-EID` | Yes | Raw player EID — hashed and checked against `:eid_hash` param | +| `X-Plan-Name` | Yes | Plan display name | +| `X-Plan-Timestamp` | Yes | Unix millisecond timestamp as decimal string | + +Request body: raw gzip-compressed plan JSON (binary). + +## Client integration + +Set `VITE_CLOUD_WORKER_URL` in the ascension-planner `.env.local` to the deployed +worker URL. Cloud sync controls appear in the Plan Library panel automatically. diff --git a/wasmegg/_cloud-worker/package.json b/wasmegg/_cloud-worker/package.json new file mode 100644 index 000000000..2e6bac47e --- /dev/null +++ b/wasmegg/_cloud-worker/package.json @@ -0,0 +1,15 @@ +{ + "name": "egg-cloud-worker", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "type-check": "tsc --noEmit" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4", + "typescript": "^6.0.2", + "wrangler": "^4" + } +} diff --git a/wasmegg/_cloud-worker/src/auth.ts b/wasmegg/_cloud-worker/src/auth.ts new file mode 100644 index 000000000..87beb6a1a --- /dev/null +++ b/wasmegg/_cloud-worker/src/auth.ts @@ -0,0 +1,39 @@ +/** + * EID hashing and request validation. + * + * Privacy is not a primary concern for this service — the EID hash is used + * only to partition storage so each player's plans are grouped together. + * The raw EID is never stored; only the 16-char hex prefix of its SHA-256 is + * used as a key prefix. + */ + +/** + * Compute the 16-char hex prefix of the SHA-256 of a lowercased EID. + * Matches the client-side cloudEidHash() in src/lib/storage/db.ts. + */ +export async function hashEid(eid: string): Promise { + const encoded = new TextEncoder().encode(eid.toLowerCase().trim()); + const hashBuffer = await crypto.subtle.digest('SHA-256', encoded); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray + .map(b => b.toString(16).padStart(2, '0')) + .join('') + .substring(0, 16); +} + +/** + * Validate that the X-EID header's hash matches the eidHash URL parameter. + * Returns the validated eidHash string, or null if validation fails. + * + * This is a consistency check, not a security gate. It prevents accidentally + * writing to the wrong partition if a client sends mismatched values. + */ +export async function validateEidHash( + request: Request, + expectedHash: string +): Promise { + const eid = request.headers.get('X-EID'); + if (!eid) return false; + const computed = await hashEid(eid); + return computed === expectedHash; +} diff --git a/wasmegg/_cloud-worker/src/index.ts b/wasmegg/_cloud-worker/src/index.ts new file mode 100644 index 000000000..56db46475 --- /dev/null +++ b/wasmegg/_cloud-worker/src/index.ts @@ -0,0 +1,140 @@ +/** + * egg-cloud-storage Cloudflare Worker + * + * Routes: + * GET /v1/plans/:eid_hash → list plan index for a user + * GET /v1/plans/:eid_hash/:plan_id → download a single plan (gzipped binary) + * PUT /v1/plans/:eid_hash/:plan_id → upload / overwrite a plan + * DELETE /v1/plans/:eid_hash/:plan_id → delete a plan + * + * GET /v1/settings/:eid_hash/:tool → get settings for a tool + * PUT /v1/settings/:eid_hash/:tool → save settings for a tool + */ + +import type { Env } from './types'; +import { + handleListPlans, + handleGetPlan, + handleUploadPlan, + handleDeletePlan, + handleTouchPlan, +} from './plans'; +import { handleGetSettings, handlePutSettings } from './settings'; + +// ── CORS ──────────────────────────────────────────────────────────────────── + +const ALLOWED_ORIGINS = new Set([ + 'https://wasmegg-carpet.netlify.app', + 'https://wasmegg.netlify.app', +]); + +function isAllowedOrigin(origin: string | null): boolean { + if (!origin) return false; + if (ALLOWED_ORIGINS.has(origin)) return true; + // Allow any localhost port for development + if (/^https?:\/\/localhost(:\d+)?$/.test(origin)) return true; + if (/^https?:\/\/127\.0\.0\.1(:\d+)?$/.test(origin)) return true; + return false; +} + +function corsHeaders(origin: string | null): Record { + const allowed = isAllowedOrigin(origin) ? origin! : ''; + return { + 'Access-Control-Allow-Origin': allowed, + 'Access-Control-Allow-Methods': 'GET, PUT, PATCH, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': + 'Content-Type, X-EID, X-Plan-Name, X-Plan-Timestamp', + 'Access-Control-Max-Age': '86400', + Vary: 'Origin', + }; +} + +function addCors(response: Response, origin: string | null): Response { + const headers = new Headers(response.headers); + for (const [k, v] of Object.entries(corsHeaders(origin))) { + headers.set(k, v); + } + return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); +} + +// ── Router ────────────────────────────────────────────────────────────────── + +const PLAN_LIST_RE = /^\/v1\/plans\/([a-f0-9]{16})$/; +const PLAN_ITEM_RE = /^\/v1\/plans\/([a-f0-9]{16})\/([a-zA-Z0-9_-]+)$/; +const SETTINGS_RE = /^\/v1\/settings\/([a-f0-9]{16})\/([a-z0-9_-]+)$/; + +export default { + async fetch(request: Request, env: Env): Promise { + const origin = request.headers.get('Origin'); + + // Handle CORS preflight + if (request.method === 'OPTIONS') { + return new Response(null, { + status: 204, + headers: corsHeaders(origin), + }); + } + + const url = new URL(request.url); + const path = url.pathname; + const method = request.method; + + let response: Response; + + // ── /v1/plans/:eid_hash ────────────────────────────────────────────── + const planListMatch = path.match(PLAN_LIST_RE); + if (planListMatch) { + const [, eidHash] = planListMatch; + if (method === 'GET') { + response = await handleListPlans(eidHash, env); + } else { + response = new Response('Method Not Allowed', { status: 405 }); + } + return addCors(response, origin); + } + + // ── /v1/plans/:eid_hash/:plan_id ───────────────────────────────────── + const planItemMatch = path.match(PLAN_ITEM_RE); + if (planItemMatch) { + const [, eidHash, planId] = planItemMatch; + if (method === 'GET') { + response = await handleGetPlan(eidHash, planId, env); + } else if (method === 'PUT') { + response = await handleUploadPlan(eidHash, planId, request, env); + } else if (method === 'PATCH') { + response = await handleTouchPlan(eidHash, planId, env); + } else if (method === 'DELETE') { + response = await handleDeletePlan(eidHash, planId, request, env); + } else { + response = new Response('Method Not Allowed', { status: 405 }); + } + return addCors(response, origin); + } + + // ── /v1/settings/:eid_hash/:tool ───────────────────────────────────── + const settingsMatch = path.match(SETTINGS_RE); + if (settingsMatch) { + const [, eidHash, tool] = settingsMatch; + if (method === 'GET') { + response = await handleGetSettings(eidHash, tool, env); + } else if (method === 'PUT') { + response = await handlePutSettings(eidHash, tool, request, env); + } else { + response = new Response('Method Not Allowed', { status: 405 }); + } + return addCors(response, origin); + } + + // ── Root / info ─────────────────────────────────────────────────────── + if (path === '/' || path === '') { + response = new Response( + JSON.stringify({ service: 'egg-cloud-storage', version: 1 }), + { headers: { 'Content-Type': 'application/json' } } + ); + return addCors(response, origin); + } + + response = new Response('Not Found', { status: 404 }); + return addCors(response, origin); + }, +}; diff --git a/wasmegg/_cloud-worker/src/plans.ts b/wasmegg/_cloud-worker/src/plans.ts new file mode 100644 index 000000000..de98c3873 --- /dev/null +++ b/wasmegg/_cloud-worker/src/plans.ts @@ -0,0 +1,162 @@ +/** + * Plan CRUD handlers for the egg cloud storage worker. + * + * KV key scheme: + * plans:{eid_hash}:index → JSON: PlanIndexEntry[] + * plans:{eid_hash}:{plan_id} → gzipped plan binary (ArrayBuffer) + */ + +import type { Env, PlanIndexEntry } from './types'; +import { validateEidHash } from './auth'; + +const INDEX_KEY_TTL = undefined; // No expiry — plans live forever + +function planDataKey(eidHash: string, planId: string): string { + return `plans:${eidHash}:${planId}`; +} + +function planIndexKey(eidHash: string): string { + return `plans:${eidHash}:index`; +} + +async function readIndex(env: Env, eidHash: string): Promise { + const raw = await env.WASMEGG_DATA.get(planIndexKey(eidHash)); + if (!raw) return []; + try { + return JSON.parse(raw) as PlanIndexEntry[]; + } catch { + return []; + } +} + +async function writeIndex(env: Env, eidHash: string, index: PlanIndexEntry[]): Promise { + await env.WASMEGG_DATA.put(planIndexKey(eidHash), JSON.stringify(index)); +} + +/** + * GET /v1/plans/:eid_hash + * Returns the plan index (lightweight manifest) — no auth required. + */ +export async function handleListPlans(eidHash: string, env: Env): Promise { + const index = await readIndex(env, eidHash); + return Response.json(index); +} + +/** + * GET /v1/plans/:eid_hash/:plan_id + * Returns the raw gzipped plan binary — no auth required. + * Client decompresses with DecompressionStream. + */ +export async function handleGetPlan( + eidHash: string, + planId: string, + env: Env +): Promise { + const data = await env.WASMEGG_DATA.get(planDataKey(eidHash, planId), 'arrayBuffer'); + if (!data) { + return new Response('Plan not found', { status: 404 }); + } + return new Response(data, { + headers: { + 'Content-Type': 'application/octet-stream', + 'Content-Encoding': 'gzip', + }, + }); +} + +/** + * PUT /v1/plans/:eid_hash/:plan_id + * Uploads a gzip-compressed plan. Requires X-EID header for consistency validation. + * + * Request headers: + * X-EID: + * X-Plan-Name: + * X-Plan-Timestamp: + * Request body: gzipped JSON binary + */ +export async function handleUploadPlan( + eidHash: string, + planId: string, + request: Request, + env: Env +): Promise { + if (!(await validateEidHash(request, eidHash))) { + return new Response('EID hash mismatch', { status: 403 }); + } + + const name = request.headers.get('X-Plan-Name'); + const timestampStr = request.headers.get('X-Plan-Timestamp'); + if (!name || !timestampStr) { + return new Response('Missing X-Plan-Name or X-Plan-Timestamp headers', { status: 400 }); + } + const timestamp = parseInt(timestampStr, 10); + if (isNaN(timestamp)) { + return new Response('Invalid X-Plan-Timestamp', { status: 400 }); + } + + const body = await request.arrayBuffer(); + if (body.byteLength === 0) { + return new Response('Empty body', { status: 400 }); + } + + // Store the compressed plan data + await env.WASMEGG_DATA.put(planDataKey(eidHash, planId), body, { expirationTtl: INDEX_KEY_TTL }); + + // Upsert the index entry + const index = await readIndex(env, eidHash); + const existingIdx = index.findIndex(e => e.id === planId); + const entry: PlanIndexEntry = { id: planId, name, timestamp, size: body.byteLength }; + if (existingIdx >= 0) { + index[existingIdx] = entry; + } else { + index.push(entry); + } + // Keep index sorted by timestamp descending (newest first) + index.sort((a, b) => b.timestamp - a.timestamp); + await writeIndex(env, eidHash, index); + + return new Response(null, { status: 204 }); +} + +/** + * PATCH /v1/plans/:eid_hash/:plan_id + * Touch a plan — updates lastAccessed in the index without modifying plan data. + * No auth required (it's only a read-activity signal). + */ +export async function handleTouchPlan( + eidHash: string, + planId: string, + env: Env +): Promise { + const index = await readIndex(env, eidHash); + const entry = index.find(e => e.id === planId); + if (!entry) { + return new Response('Plan not found', { status: 404 }); + } + entry.lastAccessed = Date.now(); + await writeIndex(env, eidHash, index); + return new Response(null, { status: 204 }); +} + +/** + * DELETE /v1/plans/:eid_hash/:plan_id + * Deletes a plan and removes it from the index. Requires X-EID header. + */ +export async function handleDeletePlan( + eidHash: string, + planId: string, + request: Request, + env: Env +): Promise { + if (!(await validateEidHash(request, eidHash))) { + return new Response('EID hash mismatch', { status: 403 }); + } + + await env.WASMEGG_DATA.delete(planDataKey(eidHash, planId)); + + const index = await readIndex(env, eidHash); + const filtered = index.filter(e => e.id !== planId); + await writeIndex(env, eidHash, filtered); + + return new Response(null, { status: 204 }); +} diff --git a/wasmegg/_cloud-worker/src/settings.ts b/wasmegg/_cloud-worker/src/settings.ts new file mode 100644 index 000000000..81d54091e --- /dev/null +++ b/wasmegg/_cloud-worker/src/settings.ts @@ -0,0 +1,60 @@ +/** + * User settings handlers — stub for future use. + * + * KV key scheme: + * settings:{eid_hash}:{tool_name} → JSON blob + * + * Intended to store per-tool user preferences for wasmegg tools other than + * the ascension planner (e.g., rockets-tracker layout, smart-assistant config). + */ + +import type { Env } from './types'; +import { validateEidHash } from './auth'; + +function settingsKey(eidHash: string, tool: string): string { + return `settings:${eidHash}:${tool}`; +} + +/** + * GET /v1/settings/:eid_hash/:tool + * Returns the stored settings JSON for a tool, or 404 if not set. + */ +export async function handleGetSettings( + eidHash: string, + tool: string, + env: Env +): Promise { + const raw = await env.WASMEGG_DATA.get(settingsKey(eidHash, tool)); + if (!raw) return new Response('Not found', { status: 404 }); + return new Response(raw, { headers: { 'Content-Type': 'application/json' } }); +} + +/** + * PUT /v1/settings/:eid_hash/:tool + * Stores settings JSON for a tool. Requires X-EID header. + */ +export async function handlePutSettings( + eidHash: string, + tool: string, + request: Request, + env: Env +): Promise { + if (!(await validateEidHash(request, eidHash))) { + return new Response('EID hash mismatch', { status: 403 }); + } + + const contentType = request.headers.get('Content-Type') ?? ''; + if (!contentType.includes('application/json')) { + return new Response('Content-Type must be application/json', { status: 415 }); + } + + const body = await request.text(); + try { + JSON.parse(body); // Validate it's actually JSON + } catch { + return new Response('Invalid JSON body', { status: 400 }); + } + + await env.WASMEGG_DATA.put(settingsKey(eidHash, tool), body); + return new Response(null, { status: 204 }); +} diff --git a/wasmegg/_cloud-worker/src/types.ts b/wasmegg/_cloud-worker/src/types.ts new file mode 100644 index 000000000..f0bcc9b24 --- /dev/null +++ b/wasmegg/_cloud-worker/src/types.ts @@ -0,0 +1,24 @@ +/** + * Shared types for the egg cloud storage worker. + */ + +export interface Env { + WASMEGG_DATA: KVNamespace; +} + +/** Entry in the per-user plan index stored at plans:{eid_hash}:index */ +export interface PlanIndexEntry { + id: string; + name: string; + timestamp: number; + /** Compressed byte size of the plan value in KV */ + size: number; + /** Unix ms timestamp of the last time the plan was opened/accessed. */ + lastAccessed?: number; +} + +/** JSON body expected when uploading a plan */ +export interface UploadPlanMeta { + name: string; + timestamp: number; +} diff --git a/wasmegg/_cloud-worker/tsconfig.json b/wasmegg/_cloud-worker/tsconfig.json new file mode 100644 index 000000000..ef932b123 --- /dev/null +++ b/wasmegg/_cloud-worker/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "types": ["@cloudflare/workers-types"], + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/wasmegg/_cloud-worker/wrangler.toml b/wasmegg/_cloud-worker/wrangler.toml new file mode 100644 index 000000000..6aa2493e5 --- /dev/null +++ b/wasmegg/_cloud-worker/wrangler.toml @@ -0,0 +1,9 @@ +name = "egg-cloud-storage" +main = "src/index.ts" +compatibility_date = "2024-09-23" +compatibility_flags = ["nodejs_compat"] + +[[kv_namespaces]] +binding = "WASMEGG_DATA" +id = "ea5acf7700b64496a7e860b400079f80" +preview_id = "a6ea789838aa4108a79d8090d85b65a5" \ No newline at end of file diff --git a/wasmegg/ascension-planner/src/App.vue b/wasmegg/ascension-planner/src/App.vue index 29813d8f4..499f8d642 100644 --- a/wasmegg/ascension-planner/src/App.vue +++ b/wasmegg/ascension-planner/src/App.vue @@ -425,6 +425,7 @@ import WarningDialog from '@/components/WarningDialog.vue'; import RecalculationOverlay from '@/components/RecalculationOverlay.vue'; import PlanLibrary from '@/components/PlanLibrary.vue'; import PlanSelectionDialog from '@/components/PlanSelectionDialog.vue'; +import ChevronIcon from '@/components/ChevronIcon.vue'; import { useSalesStore } from '@/stores/sales'; import { hashID, saveMetadata, loadMetadata } from '@/lib/storage/db'; import { useActionExecutor } from '@/composables/useActionExecutor'; @@ -1100,21 +1101,7 @@ async function savePlanAs() { } } -// Chevron icon component -const ChevronIcon = { - props: { expanded: Boolean }, - template: ` - - - - `, -}; +