From 33a1ccbe6cd1dec279c126bc26f62d5e208e0d57 Mon Sep 17 00:00:00 2001 From: emilkrebs Date: Sat, 10 Jan 2026 01:38:55 +0100 Subject: [PATCH 1/5] improved database initialization --- components/CopyButton.tsx | 58 +++ components/CopyField.tsx | 55 +-- deno.json | 48 +-- deno.lock | 630 +++++++++++++++---------------- islands/ViewNote.tsx | 9 +- lib/rate-limiting/src/hashing.ts | 11 +- lib/services/database-service.ts | 14 - main.ts | 7 + routes/[id].tsx | 8 +- routes/api/notes.ts | 32 +- routes/api/notes/[id].ts | 11 +- tests/arc-rate-limiter_test.ts | 1 + tests/main_test.ts | 2 + 13 files changed, 424 insertions(+), 462 deletions(-) create mode 100644 components/CopyButton.tsx delete mode 100644 lib/services/database-service.ts diff --git a/components/CopyButton.tsx b/components/CopyButton.tsx new file mode 100644 index 0000000..54fe4b9 --- /dev/null +++ b/components/CopyButton.tsx @@ -0,0 +1,58 @@ +import { useState } from 'preact/hooks'; + +export default function CopyButton({ value }: { value: string }) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(value); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } catch (e) { + console.error('Failed to copy:', e); + } + }; + + return ( + + ); +} diff --git a/components/CopyField.tsx b/components/CopyField.tsx index b895c1c..72cd2f3 100644 --- a/components/CopyField.tsx +++ b/components/CopyField.tsx @@ -1,4 +1,4 @@ -import { useState } from 'preact/hooks'; +import CopyButton from './CopyButton.tsx'; type CopyFieldProps = { label: string; @@ -7,18 +7,6 @@ type CopyFieldProps = { }; export default function CopyField({ label, value, title }: CopyFieldProps) { - const [copied, setCopied] = useState(false); - - const handleCopy = async () => { - try { - await navigator.clipboard.writeText(value); - setCopied(true); - setTimeout(() => setCopied(false), 1500); - } catch (e) { - console.error('Failed to copy:', e); - } - }; - return (
@@ -31,46 +19,7 @@ export default function CopyField({ label, value, title }: CopyFieldProps) { > {value} - +
diff --git a/deno.json b/deno.json index 4c9c633..13bd56f 100644 --- a/deno.json +++ b/deno.json @@ -9,39 +9,24 @@ "preview": "deno task build && deno task start", "clean": "rm -rf ./dist && rm -rf ./_fresh && rm -rf ./node_modules && rm -f ./deno.lock && rm -f ./package-lock.json" }, - "lint": { - "rules": { - "tags": [ - "fresh", - "recommended" - ] - } - }, - "exclude": [ - "**/_fresh/*" - ], + "lint": { "rules": { "tags": ["fresh", "recommended"] } }, + "exclude": ["**/_fresh/*"], "imports": { "@/": "./", "@deno/gfm": "jsr:@deno/gfm@^0.11.0", - "fresh": "jsr:@fresh/core@^2.1.4", - "preact": "npm:preact@^10.27.2", - "@preact/signals": "npm:@preact/signals@^2.3.2", - "@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.0.7", - "vite": "npm:vite@^7.1.12", - "tailwindcss": "npm:tailwindcss@^4.1.16", - "@tailwindcss/vite": "npm:@tailwindcss/vite@^4.1.16", - "@valibot/valibot": "jsr:@valibot/valibot@^1.1.0", + "fresh": "jsr:@fresh/core@^2.2.0", + "preact": "npm:preact@^10.28.2", + "@preact/signals": "npm:@preact/signals@^2.5.1", + "@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.0.8", + "vite": "npm:vite@^7.3.1", + "tailwindcss": "npm:tailwindcss@^4.1.18", + "@tailwindcss/vite": "npm:@tailwindcss/vite@^4.1.18", + "@valibot/valibot": "jsr:@valibot/valibot@^1.2.0", "$std/": "https://deno.land/std@0.216.0/", "bcrypt": "https://deno.land/x/bcrypt/mod.ts" }, "compilerOptions": { - "lib": [ - "dom", - "dom.asynciterable", - "dom.iterable", - "deno.ns", - "deno.unstable" - ], + "lib": ["dom", "dom.asynciterable", "dom.iterable", "deno.ns", "deno.unstable"], "jsx": "precompile", "jsxImportSource": "preact", "jsxPrecompileSkipElements": [ @@ -60,9 +45,7 @@ "noscript", "template" ], - "types": [ - "vite/client" - ] + "types": ["vite/client"] }, "fmt": { "useTabs": false, @@ -70,12 +53,7 @@ "indentWidth": 4, "singleQuote": true, "proseWrap": "always", - "exclude": [ - "**/node_modules/**", - "**/.git/**", - "**/.vscode/**", - "**/.github/**" - ] + "exclude": ["**/node_modules/**", "**/.git/**", "**/.vscode/**", "**/.github/**"] }, "nodeModulesDir": "auto" } diff --git a/deno.lock b/deno.lock index 344f979..a6d5de2 100644 --- a/deno.lock +++ b/deno.lock @@ -1,41 +1,42 @@ { "version": "5", "specifiers": { - "jsr:@deno/esbuild-plugin@^1.2.0": "1.2.0", + "jsr:@deno/esbuild-plugin@^1.2.0": "1.2.1", "jsr:@deno/gfm@0.11": "0.11.0", - "jsr:@deno/loader@~0.3.2": "0.3.9", - "jsr:@deno/loader@~0.3.3": "0.3.9", + "jsr:@deno/loader@~0.3.10": "0.3.11", + "jsr:@deno/loader@~0.3.2": "0.3.11", "jsr:@denosaurs/emoji@~0.3.1": "0.3.1", "jsr:@fresh/build-id@1": "1.0.1", - "jsr:@fresh/core@2": "2.1.4", - "jsr:@fresh/core@^2.1.4": "2.1.4", - "jsr:@fresh/plugin-vite@^1.0.7": "1.0.7", + "jsr:@fresh/core@2": "2.2.0", + "jsr:@fresh/core@^2.2.0": "2.2.0", + "jsr:@fresh/plugin-vite@^1.0.8": "1.0.8", "jsr:@std/bytes@^1.0.6": "1.0.6", - "jsr:@std/dotenv@~0.225.5": "0.225.5", + "jsr:@std/dotenv@~0.225.5": "0.225.6", "jsr:@std/encoding@^1.0.10": "1.0.10", "jsr:@std/fmt@^1.0.7": "1.0.8", "jsr:@std/fmt@^1.0.8": "1.0.8", - "jsr:@std/fs@^1.0.19": "1.0.19", + "jsr:@std/fs@^1.0.19": "1.0.21", "jsr:@std/html@^1.0.5": "1.0.5", - "jsr:@std/http@^1.0.21": "1.0.21", - "jsr:@std/internal@^1.0.10": "1.0.12", + "jsr:@std/http@^1.0.21": "1.0.23", + "jsr:@std/internal@^1.0.12": "1.0.12", "jsr:@std/json@^1.0.2": "1.0.2", "jsr:@std/jsonc@^1.0.2": "1.0.2", "jsr:@std/media-types@^1.1.0": "1.1.0", - "jsr:@std/path@1": "1.1.2", - "jsr:@std/path@^1.1.1": "1.1.2", - "jsr:@std/path@^1.1.2": "1.1.2", - "jsr:@std/semver@^1.0.6": "1.0.6", - "jsr:@std/uuid@^1.0.9": "1.0.9", - "jsr:@valibot/valibot@^1.1.0": "1.1.0", + "jsr:@std/path@1": "1.1.4", + "jsr:@std/path@^1.1.1": "1.1.4", + "jsr:@std/path@^1.1.2": "1.1.4", + "jsr:@std/path@^1.1.4": "1.1.4", + "jsr:@std/semver@^1.0.6": "1.0.7", + "jsr:@std/uuid@^1.0.9": "1.1.0", + "jsr:@valibot/valibot@^1.2.0": "1.2.0", "npm:@babel/core@^7.28.0": "7.28.5", "npm:@babel/preset-react@^7.27.1": "7.28.5_@babel+core@7.28.5", "npm:@mjackson/node-fetch-server@0.7": "0.7.0", "npm:@opentelemetry/api@^1.9.0": "1.9.0", - "npm:@preact/signals@^2.2.1": "2.3.2_preact@10.27.2", - "npm:@preact/signals@^2.3.2": "2.3.2_preact@10.27.2", - "npm:@prefresh/vite@^2.4.8": "2.4.11_preact@10.27.2_vite@7.1.12__picomatch@4.0.3_@types+node@24.2.0", - "npm:@tailwindcss/vite@^4.1.16": "4.1.16_vite@7.1.12__picomatch@4.0.3_@types+node@24.2.0", + "npm:@preact/signals@^2.2.1": "2.5.1_preact@10.28.2", + "npm:@preact/signals@^2.5.1": "2.5.1_preact@10.28.2", + "npm:@prefresh/vite@^2.4.8": "2.4.11_preact@10.28.2_vite@7.3.1__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", + "npm:@tailwindcss/vite@^4.1.18": "4.1.18_vite@7.3.1__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", "npm:@types/node@*": "24.2.0", "npm:esbuild-wasm@~0.25.11": "0.25.12", "npm:esbuild@0.25.7": "0.25.7", @@ -47,22 +48,23 @@ "npm:marked-footnote@^1.2.0": "1.4.0_marked@12.0.2", "npm:marked-gfm-heading-id@^3.1.0": "3.2.0_marked@12.0.2", "npm:marked@12": "12.0.2", - "npm:preact-render-to-string@^6.6.3": "6.6.3_preact@10.27.2", - "npm:preact@^10.27.0": "10.27.2", - "npm:preact@^10.27.2": "10.27.2", + "npm:preact-render-to-string@^6.6.3": "6.6.5_preact@10.28.2", + "npm:preact@^10.27.0": "10.28.2", + "npm:preact@^10.27.2": "10.28.2", + "npm:preact@^10.28.2": "10.28.2", "npm:prismjs@^1.29.0": "1.30.0", - "npm:rollup@^4.50.0": "4.52.5", + "npm:rollup@^4.50.0": "4.55.1", "npm:sanitize-html@^2.13.0": "2.17.0", - "npm:tailwindcss@^4.1.16": "4.1.16", - "npm:vite@*": "7.1.12_picomatch@4.0.3_@types+node@24.2.0", - "npm:vite@^7.1.12": "7.1.12_picomatch@4.0.3_@types+node@24.2.0", - "npm:vite@^7.1.4": "7.1.12_picomatch@4.0.3_@types+node@24.2.0" + "npm:tailwindcss@^4.1.18": "4.1.18", + "npm:vite@*": "7.3.1_@types+node@24.2.0_picomatch@4.0.3", + "npm:vite@^7.1.4": "7.3.1_@types+node@24.2.0_picomatch@4.0.3", + "npm:vite@^7.3.1": "7.3.1_@types+node@24.2.0_picomatch@4.0.3" }, "jsr": { - "@deno/esbuild-plugin@1.2.0": { - "integrity": "04ddd0fca9416d8a2866263928a53b9d5ed08dfca064d64504a0aaf9800c709e", + "@deno/esbuild-plugin@1.2.1": { + "integrity": "df629467913adc1f960149fdfa3a3430ba8c20381c310fba096db244e6c3c9f6", "dependencies": [ - "jsr:@deno/loader@~0.3.3", + "jsr:@deno/loader@~0.3.10", "jsr:@std/path@^1.1.1", "npm:esbuild@~0.25.5" ] @@ -82,8 +84,8 @@ "npm:sanitize-html" ] }, - "@deno/loader@0.3.9": { - "integrity": "703d44656f7da0fa4a4a7f8a5105b5b41320821286508c2967b4252a00a2506f" + "@deno/loader@0.3.11": { + "integrity": "7c62f4f09cdfc34e66ba25b5a775a1830cbb5266b3e39f67b0f620c75484df8d" }, "@denosaurs/emoji@0.3.1": { "integrity": "b0aed5f55dec99e83da7c9637fe0a36d1d6252b7c99deaaa3fc5dea3fcf3da8b" @@ -94,8 +96,8 @@ "jsr:@std/encoding" ] }, - "@fresh/core@2.1.4": { - "integrity": "ce6321efe9ec5de38631a52a79f1691e37805c11df8b96472c788805e5e3a214", + "@fresh/core@2.2.0": { + "integrity": "b3c00f82288a2c4c8ec85e4abb67b080b366ec5971860f2f2898eb281ea1a80f", "dependencies": [ "jsr:@deno/esbuild-plugin", "jsr:@fresh/build-id", @@ -118,12 +120,12 @@ "npm:preact@^10.27.2" ] }, - "@fresh/plugin-vite@1.0.7": { - "integrity": "0dd7048f7c5d5cf7f62b29b4653a18cfa4dc81e074c4c0ab9e8166cce639cbeb", + "@fresh/plugin-vite@1.0.8": { + "integrity": "5780d842ed82e4cbccd93dd8ba2d54bf59dff5aee65921134aab15a4cd457c56", "dependencies": [ "jsr:@deno/loader@~0.3.2", "jsr:@fresh/core@2", - "jsr:@fresh/core@^2.1.4", + "jsr:@fresh/core@^2.2.0", "jsr:@std/dotenv", "jsr:@std/fmt@^1.0.7", "jsr:@std/path@1", @@ -138,8 +140,8 @@ "@std/bytes@1.0.6": { "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" }, - "@std/dotenv@0.225.5": { - "integrity": "9ce6f9d0ec3311f74a32535aa1b8c62ed88b1ab91b7f0815797d77a6f60c922f" + "@std/dotenv@0.225.6": { + "integrity": "1d6f9db72f565bd26790fa034c26e45ecb260b5245417be76c2279e5734c421b" }, "@std/encoding@1.0.10": { "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" @@ -147,17 +149,17 @@ "@std/fmt@1.0.8": { "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" }, - "@std/fs@1.0.19": { - "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", + "@std/fs@1.0.21": { + "integrity": "d720fe1056d78d43065a4d6e0eeb2b19f34adb8a0bc7caf3a4dbf1d4178252cd", "dependencies": [ - "jsr:@std/path@^1.1.1" + "jsr:@std/path@^1.1.4" ] }, "@std/html@1.0.5": { "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" }, - "@std/http@1.0.21": { - "integrity": "abb5c747651ee6e3ea6139858fd9b1810d2c97f53a5e6722f3b6d27a6d263edc", + "@std/http@1.0.23": { + "integrity": "6634e9e034c589bf35101c1b5ee5bbf052a5987abca20f903e58bdba85c80dee", "dependencies": [ "jsr:@std/encoding" ] @@ -177,23 +179,23 @@ "@std/media-types@1.1.0": { "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" }, - "@std/path@1.1.2": { - "integrity": "c0b13b97dfe06546d5e16bf3966b1cadf92e1cc83e56ba5476ad8b498d9e3038", + "@std/path@1.1.4": { + "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", "dependencies": [ "jsr:@std/internal" ] }, - "@std/semver@1.0.6": { - "integrity": "b7c98ae2843547cf3f7ac37f3995889e6e4cee0a97b57b57f17f62722843303c" + "@std/semver@1.0.7": { + "integrity": "7d5f65391762dc4358abde80fc3354086ddb40101f140295e60f290c138887d0" }, - "@std/uuid@1.0.9": { - "integrity": "44b627bf2d372fe1bd099e2ad41b2be41a777fc94e62a3151006895a037f1642", + "@std/uuid@1.1.0": { + "integrity": "6268db2ccf172849c9be80763354ca305d49ef4af41fe995623d44fcc3f7457c", "dependencies": [ "jsr:@std/bytes" ] }, - "@valibot/valibot@1.1.0": { - "integrity": "2617f02b532011b8140926899d420a3e1bbb0fcb7cdf8e7b669df89e7edd7f5f" + "@valibot/valibot@1.2.0": { + "integrity": "61c118a4d027ed55912caf381c78f0a178f335f46ad0c4bcb136498dc1ef2285" } }, "npm": { @@ -378,266 +380,266 @@ "@babel/helper-validator-identifier" ] }, - "@esbuild/aix-ppc64@0.25.12": { - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "os": ["aix"], - "cpu": ["ppc64"] - }, "@esbuild/aix-ppc64@0.25.7": { "integrity": "sha512-uD0kKFHh6ETr8TqEtaAcV+dn/2qnYbH/+8wGEdY70Qf7l1l/jmBUbrmQqwiPKAQE6cOQ7dTj6Xr0HzQDGHyceQ==", "os": ["aix"], "cpu": ["ppc64"] }, - "@esbuild/android-arm64@0.25.12": { - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "os": ["android"], - "cpu": ["arm64"] + "@esbuild/aix-ppc64@0.27.2": { + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "os": ["aix"], + "cpu": ["ppc64"] }, "@esbuild/android-arm64@0.25.7": { "integrity": "sha512-p0ohDnwyIbAtztHTNUTzN5EGD/HJLs1bwysrOPgSdlIA6NDnReoVfoCyxG6W1d85jr2X80Uq5KHftyYgaK9LPQ==", "os": ["android"], "cpu": ["arm64"] }, - "@esbuild/android-arm@0.25.12": { - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "@esbuild/android-arm64@0.27.2": { + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "os": ["android"], - "cpu": ["arm"] + "cpu": ["arm64"] }, "@esbuild/android-arm@0.25.7": { "integrity": "sha512-Jhuet0g1k9rAJHrXGIh7sFknFuT4sfytYZpZpuZl7YKDhnPByVAm5oy2LEBmMbuYf3ejWVYCc2seX81Mk+madA==", "os": ["android"], "cpu": ["arm"] }, - "@esbuild/android-x64@0.25.12": { - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "@esbuild/android-arm@0.27.2": { + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "os": ["android"], - "cpu": ["x64"] + "cpu": ["arm"] }, "@esbuild/android-x64@0.25.7": { "integrity": "sha512-mMxIJFlSgVK23HSsII3ZX9T2xKrBCDGyk0qiZnIW10LLFFtZLkFD6imZHu7gUo2wkNZwS9Yj3mOtZD3ZPcjCcw==", "os": ["android"], "cpu": ["x64"] }, - "@esbuild/darwin-arm64@0.25.12": { - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "os": ["darwin"], - "cpu": ["arm64"] + "@esbuild/android-x64@0.27.2": { + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "os": ["android"], + "cpu": ["x64"] }, "@esbuild/darwin-arm64@0.25.7": { "integrity": "sha512-jyOFLGP2WwRwxM8F1VpP6gcdIJc8jq2CUrURbbTouJoRO7XCkU8GdnTDFIHdcifVBT45cJlOYsZ1kSlfbKjYUQ==", "os": ["darwin"], "cpu": ["arm64"] }, - "@esbuild/darwin-x64@0.25.12": { - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "@esbuild/darwin-arm64@0.27.2": { + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "os": ["darwin"], - "cpu": ["x64"] + "cpu": ["arm64"] }, "@esbuild/darwin-x64@0.25.7": { "integrity": "sha512-m9bVWqZCwQ1BthruifvG64hG03zzz9gE2r/vYAhztBna1/+qXiHyP9WgnyZqHgGeXoimJPhAmxfbeU+nMng6ZA==", "os": ["darwin"], "cpu": ["x64"] }, - "@esbuild/freebsd-arm64@0.25.12": { - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "os": ["freebsd"], - "cpu": ["arm64"] + "@esbuild/darwin-x64@0.27.2": { + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "os": ["darwin"], + "cpu": ["x64"] }, "@esbuild/freebsd-arm64@0.25.7": { "integrity": "sha512-Bss7P4r6uhr3kDzRjPNEnTm/oIBdTPRNQuwaEFWT/uvt6A1YzK/yn5kcx5ZxZ9swOga7LqeYlu7bDIpDoS01bA==", "os": ["freebsd"], "cpu": ["arm64"] }, - "@esbuild/freebsd-x64@0.25.12": { - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "@esbuild/freebsd-arm64@0.27.2": { + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "os": ["freebsd"], - "cpu": ["x64"] + "cpu": ["arm64"] }, "@esbuild/freebsd-x64@0.25.7": { "integrity": "sha512-S3BFyjW81LXG7Vqmr37ddbThrm3A84yE7ey/ERBlK9dIiaWgrjRlre3pbG7txh1Uaxz8N7wGGQXmC9zV+LIpBQ==", "os": ["freebsd"], "cpu": ["x64"] }, - "@esbuild/linux-arm64@0.25.12": { - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "os": ["linux"], - "cpu": ["arm64"] + "@esbuild/freebsd-x64@0.27.2": { + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@esbuild/linux-arm64@0.25.7": { "integrity": "sha512-HfQZQqrNOfS1Okn7PcsGUqHymL1cWGBslf78dGvtrj8q7cN3FkapFgNA4l/a5lXDwr7BqP2BSO6mz9UremNPbg==", "os": ["linux"], "cpu": ["arm64"] }, - "@esbuild/linux-arm@0.25.12": { - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "@esbuild/linux-arm64@0.27.2": { + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "os": ["linux"], - "cpu": ["arm"] + "cpu": ["arm64"] }, "@esbuild/linux-arm@0.25.7": { "integrity": "sha512-JZMIci/1m5vfQuhKoFXogCKVYVfYQmoZJg8vSIMR4TUXbF+0aNlfXH3DGFEFMElT8hOTUF5hisdZhnrZO/bkDw==", "os": ["linux"], "cpu": ["arm"] }, - "@esbuild/linux-ia32@0.25.12": { - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "@esbuild/linux-arm@0.27.2": { + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "os": ["linux"], - "cpu": ["ia32"] + "cpu": ["arm"] }, "@esbuild/linux-ia32@0.25.7": { "integrity": "sha512-9Jex4uVpdeofiDxnwHRgen+j6398JlX4/6SCbbEFEXN7oMO2p0ueLN+e+9DdsdPLUdqns607HmzEFnxwr7+5wQ==", "os": ["linux"], "cpu": ["ia32"] }, - "@esbuild/linux-loong64@0.25.12": { - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "@esbuild/linux-ia32@0.27.2": { + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "os": ["linux"], - "cpu": ["loong64"] + "cpu": ["ia32"] }, "@esbuild/linux-loong64@0.25.7": { "integrity": "sha512-TG1KJqjBlN9IHQjKVUYDB0/mUGgokfhhatlay8aZ/MSORMubEvj/J1CL8YGY4EBcln4z7rKFbsH+HeAv0d471w==", "os": ["linux"], "cpu": ["loong64"] }, - "@esbuild/linux-mips64el@0.25.12": { - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "@esbuild/linux-loong64@0.27.2": { + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "os": ["linux"], - "cpu": ["mips64el"] + "cpu": ["loong64"] }, "@esbuild/linux-mips64el@0.25.7": { "integrity": "sha512-Ty9Hj/lx7ikTnhOfaP7ipEm/ICcBv94i/6/WDg0OZ3BPBHhChsUbQancoWYSO0WNkEiSW5Do4febTTy4x1qYQQ==", "os": ["linux"], "cpu": ["mips64el"] }, - "@esbuild/linux-ppc64@0.25.12": { - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "@esbuild/linux-mips64el@0.27.2": { + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "os": ["linux"], - "cpu": ["ppc64"] + "cpu": ["mips64el"] }, "@esbuild/linux-ppc64@0.25.7": { "integrity": "sha512-MrOjirGQWGReJl3BNQ58BLhUBPpWABnKrnq8Q/vZWWwAB1wuLXOIxS2JQ1LT3+5T+3jfPh0tyf5CpbyQHqnWIQ==", "os": ["linux"], "cpu": ["ppc64"] }, - "@esbuild/linux-riscv64@0.25.12": { - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "@esbuild/linux-ppc64@0.27.2": { + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "os": ["linux"], - "cpu": ["riscv64"] + "cpu": ["ppc64"] }, "@esbuild/linux-riscv64@0.25.7": { "integrity": "sha512-9pr23/pqzyqIZEZmQXnFyqp3vpa+KBk5TotfkzGMqpw089PGm0AIowkUppHB9derQzqniGn3wVXgck19+oqiOw==", "os": ["linux"], "cpu": ["riscv64"] }, - "@esbuild/linux-s390x@0.25.12": { - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "@esbuild/linux-riscv64@0.27.2": { + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "os": ["linux"], - "cpu": ["s390x"] + "cpu": ["riscv64"] }, "@esbuild/linux-s390x@0.25.7": { "integrity": "sha512-4dP11UVGh9O6Y47m8YvW8eoA3r8qL2toVZUbBKyGta8j6zdw1cn9F/Rt59/Mhv0OgY68pHIMjGXWOUaykCnx+w==", "os": ["linux"], "cpu": ["s390x"] }, - "@esbuild/linux-x64@0.25.12": { - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "@esbuild/linux-s390x@0.27.2": { + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "os": ["linux"], - "cpu": ["x64"] + "cpu": ["s390x"] }, "@esbuild/linux-x64@0.25.7": { "integrity": "sha512-ghJMAJTdw/0uhz7e7YnpdX1xVn7VqA0GrWrAO2qKMuqbvgHT2VZiBv1BQ//VcHsPir4wsL3P2oPggfKPzTKoCA==", "os": ["linux"], "cpu": ["x64"] }, - "@esbuild/netbsd-arm64@0.25.12": { - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "os": ["netbsd"], - "cpu": ["arm64"] + "@esbuild/linux-x64@0.27.2": { + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "os": ["linux"], + "cpu": ["x64"] }, "@esbuild/netbsd-arm64@0.25.7": { "integrity": "sha512-bwXGEU4ua45+u5Ci/a55B85KWaDSRS8NPOHtxy2e3etDjbz23wlry37Ffzapz69JAGGc4089TBo+dGzydQmydg==", "os": ["netbsd"], "cpu": ["arm64"] }, - "@esbuild/netbsd-x64@0.25.12": { - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "@esbuild/netbsd-arm64@0.27.2": { + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "os": ["netbsd"], - "cpu": ["x64"] + "cpu": ["arm64"] }, "@esbuild/netbsd-x64@0.25.7": { "integrity": "sha512-tUZRvLtgLE5OyN46sPSYlgmHoBS5bx2URSrgZdW1L1teWPYVmXh+QN/sKDqkzBo/IHGcKcHLKDhBeVVkO7teEA==", "os": ["netbsd"], "cpu": ["x64"] }, - "@esbuild/openbsd-arm64@0.25.12": { - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "os": ["openbsd"], - "cpu": ["arm64"] + "@esbuild/netbsd-x64@0.27.2": { + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "os": ["netbsd"], + "cpu": ["x64"] }, "@esbuild/openbsd-arm64@0.25.7": { "integrity": "sha512-bTJ50aoC+WDlDGBReWYiObpYvQfMjBNlKztqoNUL0iUkYtwLkBQQeEsTq/I1KyjsKA5tyov6VZaPb8UdD6ci6Q==", "os": ["openbsd"], "cpu": ["arm64"] }, - "@esbuild/openbsd-x64@0.25.12": { - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "@esbuild/openbsd-arm64@0.27.2": { + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "os": ["openbsd"], - "cpu": ["x64"] + "cpu": ["arm64"] }, "@esbuild/openbsd-x64@0.25.7": { "integrity": "sha512-TA9XfJrgzAipFUU895jd9j2SyDh9bbNkK2I0gHcvqb/o84UeQkBpi/XmYX3cO1q/9hZokdcDqQxIi6uLVrikxg==", "os": ["openbsd"], "cpu": ["x64"] }, - "@esbuild/openharmony-arm64@0.25.12": { - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "os": ["openharmony"], - "cpu": ["arm64"] + "@esbuild/openbsd-x64@0.27.2": { + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "os": ["openbsd"], + "cpu": ["x64"] }, "@esbuild/openharmony-arm64@0.25.7": { "integrity": "sha512-5VTtExUrWwHHEUZ/N+rPlHDwVFQ5aME7vRJES8+iQ0xC/bMYckfJ0l2n3yGIfRoXcK/wq4oXSItZAz5wslTKGw==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@esbuild/sunos-x64@0.25.12": { - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "os": ["sunos"], - "cpu": ["x64"] + "@esbuild/openharmony-arm64@0.27.2": { + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "os": ["openharmony"], + "cpu": ["arm64"] }, "@esbuild/sunos-x64@0.25.7": { "integrity": "sha512-umkbn7KTxsexhv2vuuJmj9kggd4AEtL32KodkJgfhNOHMPtQ55RexsaSrMb+0+jp9XL4I4o2y91PZauVN4cH3A==", "os": ["sunos"], "cpu": ["x64"] }, - "@esbuild/win32-arm64@0.25.12": { - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "os": ["win32"], - "cpu": ["arm64"] + "@esbuild/sunos-x64@0.27.2": { + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "os": ["sunos"], + "cpu": ["x64"] }, "@esbuild/win32-arm64@0.25.7": { "integrity": "sha512-j20JQGP/gz8QDgzl5No5Gr4F6hurAZvtkFxAKhiv2X49yi/ih8ECK4Y35YnjlMogSKJk931iNMcd35BtZ4ghfw==", "os": ["win32"], "cpu": ["arm64"] }, - "@esbuild/win32-ia32@0.25.12": { - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "@esbuild/win32-arm64@0.27.2": { + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "os": ["win32"], - "cpu": ["ia32"] + "cpu": ["arm64"] }, "@esbuild/win32-ia32@0.25.7": { "integrity": "sha512-4qZ6NUfoiiKZfLAXRsvFkA0hoWVM+1y2bSHXHkpdLAs/+r0LgwqYohmfZCi985c6JWHhiXP30mgZawn/XrqAkQ==", "os": ["win32"], "cpu": ["ia32"] }, - "@esbuild/win32-x64@0.25.12": { - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "@esbuild/win32-ia32@0.27.2": { + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "os": ["win32"], - "cpu": ["x64"] + "cpu": ["ia32"] }, "@esbuild/win32-x64@0.25.7": { "integrity": "sha512-FaPsAHTwm+1Gfvn37Eg3E5HIpfR3i6x1AIcla/MkqAIupD4BW3MrSeUqfoTzwwJhk3WE2/KqUn4/eenEJC76VA==", "os": ["win32"], "cpu": ["x64"] }, + "@esbuild/win32-x64@0.27.2": { + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "os": ["win32"], + "cpu": ["x64"] + }, "@jridgewell/gen-mapping@0.3.13": { "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dependencies": [ @@ -674,8 +676,8 @@ "@preact/signals-core@1.12.1": { "integrity": "sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==" }, - "@preact/signals@2.3.2_preact@10.27.2": { - "integrity": "sha512-Q22avIn4z0BQnmFeo6Y5HCnJTo8VufN84zN51OtqeNgZOVCYgdwEOcJKVX1x/IrjRVxUnOy6Ubn7H5aVFujXaQ==", + "@preact/signals@2.5.1_preact@10.28.2": { + "integrity": "sha512-VPjk5YFt7i11Fi4UK0tzaEe5xLwfhUxXL3l89ocxQ5aPz7bRo8M5+N73LjBMPklyXKYKz6YsNo4Smp8n6nplng==", "dependencies": [ "@preact/signals-core", "preact" @@ -684,8 +686,8 @@ "@prefresh/babel-plugin@0.5.2": { "integrity": "sha512-AOl4HG6dAxWkJ5ndPHBgBa49oo/9bOiJuRDKHLSTyH+Fd9x00shTXpdiTj1W41l6oQIwUOAgJeHMn4QwIDpHkA==" }, - "@prefresh/core@1.5.8_preact@10.27.2": { - "integrity": "sha512-T7HMpakS1iPVCFZvfDLMGyrWAcO3toUN9/RkJUqqoRr/vNhQrZgHjidfhq3awDzAQtw1emDWH8dsOeu0DWqtgA==", + "@prefresh/core@1.5.9_preact@10.28.2": { + "integrity": "sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==", "dependencies": [ "preact" ] @@ -693,19 +695,7 @@ "@prefresh/utils@1.2.1": { "integrity": "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==" }, - "@prefresh/vite@2.4.11_preact@10.27.2_vite@7.1.12__picomatch@4.0.3": { - "integrity": "sha512-/XjURQqdRiCG3NpMmWqE9kJwrg9IchIOWHzulCfqg2sRe/8oQ1g5De7xrk9lbqPIQLn7ntBkKdqWXIj4E9YXyg==", - "dependencies": [ - "@babel/core", - "@prefresh/babel-plugin", - "@prefresh/core", - "@prefresh/utils", - "@rollup/pluginutils", - "preact", - "vite@7.1.12_picomatch@4.0.3" - ] - }, - "@prefresh/vite@2.4.11_preact@10.27.2_vite@7.1.12__picomatch@4.0.3_@types+node@24.2.0": { + "@prefresh/vite@2.4.11_preact@10.28.2_vite@7.3.1__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { "integrity": "sha512-/XjURQqdRiCG3NpMmWqE9kJwrg9IchIOWHzulCfqg2sRe/8oQ1g5De7xrk9lbqPIQLn7ntBkKdqWXIj4E9YXyg==", "dependencies": [ "@babel/core", @@ -714,7 +704,7 @@ "@prefresh/utils", "@rollup/pluginutils", "preact", - "vite@7.1.12_picomatch@4.0.3_@types+node@24.2.0" + "vite" ] }, "@rollup/pluginutils@4.2.1": { @@ -724,118 +714,133 @@ "picomatch@2.3.1" ] }, - "@rollup/rollup-android-arm-eabi@4.52.5": { - "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "@rollup/rollup-android-arm-eabi@4.55.1": { + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", "os": ["android"], "cpu": ["arm"] }, - "@rollup/rollup-android-arm64@4.52.5": { - "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "@rollup/rollup-android-arm64@4.55.1": { + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", "os": ["android"], "cpu": ["arm64"] }, - "@rollup/rollup-darwin-arm64@4.52.5": { - "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "@rollup/rollup-darwin-arm64@4.55.1": { + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", "os": ["darwin"], "cpu": ["arm64"] }, - "@rollup/rollup-darwin-x64@4.52.5": { - "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "@rollup/rollup-darwin-x64@4.55.1": { + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", "os": ["darwin"], "cpu": ["x64"] }, - "@rollup/rollup-freebsd-arm64@4.52.5": { - "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "@rollup/rollup-freebsd-arm64@4.55.1": { + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", "os": ["freebsd"], "cpu": ["arm64"] }, - "@rollup/rollup-freebsd-x64@4.52.5": { - "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "@rollup/rollup-freebsd-x64@4.55.1": { + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", "os": ["freebsd"], "cpu": ["x64"] }, - "@rollup/rollup-linux-arm-gnueabihf@4.52.5": { - "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "@rollup/rollup-linux-arm-gnueabihf@4.55.1": { + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", "os": ["linux"], "cpu": ["arm"] }, - "@rollup/rollup-linux-arm-musleabihf@4.52.5": { - "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "@rollup/rollup-linux-arm-musleabihf@4.55.1": { + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", "os": ["linux"], "cpu": ["arm"] }, - "@rollup/rollup-linux-arm64-gnu@4.52.5": { - "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "@rollup/rollup-linux-arm64-gnu@4.55.1": { + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", "os": ["linux"], "cpu": ["arm64"] }, - "@rollup/rollup-linux-arm64-musl@4.52.5": { - "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "@rollup/rollup-linux-arm64-musl@4.55.1": { + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", "os": ["linux"], "cpu": ["arm64"] }, - "@rollup/rollup-linux-loong64-gnu@4.52.5": { - "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "@rollup/rollup-linux-loong64-gnu@4.55.1": { + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", "os": ["linux"], "cpu": ["loong64"] }, - "@rollup/rollup-linux-ppc64-gnu@4.52.5": { - "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "@rollup/rollup-linux-loong64-musl@4.55.1": { + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-ppc64-gnu@4.55.1": { + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", "os": ["linux"], "cpu": ["ppc64"] }, - "@rollup/rollup-linux-riscv64-gnu@4.52.5": { - "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "@rollup/rollup-linux-ppc64-musl@4.55.1": { + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-riscv64-gnu@4.55.1": { + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", "os": ["linux"], "cpu": ["riscv64"] }, - "@rollup/rollup-linux-riscv64-musl@4.52.5": { - "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "@rollup/rollup-linux-riscv64-musl@4.55.1": { + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", "os": ["linux"], "cpu": ["riscv64"] }, - "@rollup/rollup-linux-s390x-gnu@4.52.5": { - "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "@rollup/rollup-linux-s390x-gnu@4.55.1": { + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", "os": ["linux"], "cpu": ["s390x"] }, - "@rollup/rollup-linux-x64-gnu@4.52.5": { - "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "@rollup/rollup-linux-x64-gnu@4.55.1": { + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", "os": ["linux"], "cpu": ["x64"] }, - "@rollup/rollup-linux-x64-musl@4.52.5": { - "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "@rollup/rollup-linux-x64-musl@4.55.1": { + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", "os": ["linux"], "cpu": ["x64"] }, - "@rollup/rollup-openharmony-arm64@4.52.5": { - "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "@rollup/rollup-openbsd-x64@4.55.1": { + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-openharmony-arm64@4.55.1": { + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@rollup/rollup-win32-arm64-msvc@4.52.5": { - "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "@rollup/rollup-win32-arm64-msvc@4.55.1": { + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", "os": ["win32"], "cpu": ["arm64"] }, - "@rollup/rollup-win32-ia32-msvc@4.52.5": { - "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "@rollup/rollup-win32-ia32-msvc@4.55.1": { + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", "os": ["win32"], "cpu": ["ia32"] }, - "@rollup/rollup-win32-x64-gnu@4.52.5": { - "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "@rollup/rollup-win32-x64-gnu@4.55.1": { + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", "os": ["win32"], "cpu": ["x64"] }, - "@rollup/rollup-win32-x64-msvc@4.52.5": { - "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "@rollup/rollup-win32-x64-msvc@4.55.1": { + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", "os": ["win32"], "cpu": ["x64"] }, - "@tailwindcss/node@4.1.16": { - "integrity": "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==", + "@tailwindcss/node@4.1.18": { + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", "dependencies": [ "@jridgewell/remapping", "enhanced-resolve", @@ -846,67 +851,67 @@ "tailwindcss" ] }, - "@tailwindcss/oxide-android-arm64@4.1.16": { - "integrity": "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==", + "@tailwindcss/oxide-android-arm64@4.1.18": { + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", "os": ["android"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-darwin-arm64@4.1.16": { - "integrity": "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==", + "@tailwindcss/oxide-darwin-arm64@4.1.18": { + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", "os": ["darwin"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-darwin-x64@4.1.16": { - "integrity": "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==", + "@tailwindcss/oxide-darwin-x64@4.1.18": { + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", "os": ["darwin"], "cpu": ["x64"] }, - "@tailwindcss/oxide-freebsd-x64@4.1.16": { - "integrity": "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==", + "@tailwindcss/oxide-freebsd-x64@4.1.18": { + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", "os": ["freebsd"], "cpu": ["x64"] }, - "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16": { - "integrity": "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==", + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18": { + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", "os": ["linux"], "cpu": ["arm"] }, - "@tailwindcss/oxide-linux-arm64-gnu@4.1.16": { - "integrity": "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==", + "@tailwindcss/oxide-linux-arm64-gnu@4.1.18": { + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", "os": ["linux"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-linux-arm64-musl@4.1.16": { - "integrity": "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==", + "@tailwindcss/oxide-linux-arm64-musl@4.1.18": { + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", "os": ["linux"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-linux-x64-gnu@4.1.16": { - "integrity": "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==", + "@tailwindcss/oxide-linux-x64-gnu@4.1.18": { + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", "os": ["linux"], "cpu": ["x64"] }, - "@tailwindcss/oxide-linux-x64-musl@4.1.16": { - "integrity": "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==", + "@tailwindcss/oxide-linux-x64-musl@4.1.18": { + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", "os": ["linux"], "cpu": ["x64"] }, - "@tailwindcss/oxide-wasm32-wasi@4.1.16": { - "integrity": "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==", + "@tailwindcss/oxide-wasm32-wasi@4.1.18": { + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", "cpu": ["wasm32"] }, - "@tailwindcss/oxide-win32-arm64-msvc@4.1.16": { - "integrity": "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==", + "@tailwindcss/oxide-win32-arm64-msvc@4.1.18": { + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", "os": ["win32"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-win32-x64-msvc@4.1.16": { - "integrity": "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==", + "@tailwindcss/oxide-win32-x64-msvc@4.1.18": { + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", "os": ["win32"], "cpu": ["x64"] }, - "@tailwindcss/oxide@4.1.16": { - "integrity": "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==", + "@tailwindcss/oxide@4.1.18": { + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", "optionalDependencies": [ "@tailwindcss/oxide-android-arm64", "@tailwindcss/oxide-darwin-arm64", @@ -922,22 +927,13 @@ "@tailwindcss/oxide-win32-x64-msvc" ] }, - "@tailwindcss/vite@4.1.16_vite@7.1.12__picomatch@4.0.3": { - "integrity": "sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg==", - "dependencies": [ - "@tailwindcss/node", - "@tailwindcss/oxide", - "tailwindcss", - "vite@7.1.12_picomatch@4.0.3" - ] - }, - "@tailwindcss/vite@4.1.16_vite@7.1.12__picomatch@4.0.3_@types+node@24.2.0": { - "integrity": "sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg==", + "@tailwindcss/vite@4.1.18_vite@7.3.1__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", "dependencies": [ "@tailwindcss/node", "@tailwindcss/oxide", "tailwindcss", - "vite@7.1.12_picomatch@4.0.3_@types+node@24.2.0" + "vite" ] }, "@types/estree@1.0.8": { @@ -949,12 +945,12 @@ "undici-types" ] }, - "baseline-browser-mapping@2.8.24": { - "integrity": "sha512-uUhTRDPXamakPyghwrUcjaGvvBqGrWvBHReoiULMIpOJVM9IYzQh83Xk2Onx5HlGI2o10NNCzcs9TG/S3TkwrQ==", + "baseline-browser-mapping@2.9.14": { + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", "bin": true }, - "browserslist@4.27.0": { - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "browserslist@4.28.1": { + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dependencies": [ "baseline-browser-mapping", "caniuse-lite", @@ -964,8 +960,8 @@ ], "bin": true }, - "caniuse-lite@1.0.30001753": { - "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==" + "caniuse-lite@1.0.30001763": { + "integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==" }, "commander@8.3.0": { "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" @@ -1010,11 +1006,11 @@ "domhandler" ] }, - "electron-to-chromium@1.5.244": { - "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==" + "electron-to-chromium@1.5.267": { + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==" }, - "enhanced-resolve@5.18.3": { - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "enhanced-resolve@5.18.4": { + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "dependencies": [ "graceful-fs", "tapable" @@ -1027,39 +1023,6 @@ "integrity": "sha512-rZqkjL3Y6FwLpSHzLnaEy8Ps6veCNo1kZa9EOfJvmWtBq5dJH4iVjfmOO6Mlkv9B0tt9WFPFmb/VxlgJOnueNg==", "bin": true }, - "esbuild@0.25.12": { - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "optionalDependencies": [ - "@esbuild/aix-ppc64@0.25.12", - "@esbuild/android-arm@0.25.12", - "@esbuild/android-arm64@0.25.12", - "@esbuild/android-x64@0.25.12", - "@esbuild/darwin-arm64@0.25.12", - "@esbuild/darwin-x64@0.25.12", - "@esbuild/freebsd-arm64@0.25.12", - "@esbuild/freebsd-x64@0.25.12", - "@esbuild/linux-arm@0.25.12", - "@esbuild/linux-arm64@0.25.12", - "@esbuild/linux-ia32@0.25.12", - "@esbuild/linux-loong64@0.25.12", - "@esbuild/linux-mips64el@0.25.12", - "@esbuild/linux-ppc64@0.25.12", - "@esbuild/linux-riscv64@0.25.12", - "@esbuild/linux-s390x@0.25.12", - "@esbuild/linux-x64@0.25.12", - "@esbuild/netbsd-arm64@0.25.12", - "@esbuild/netbsd-x64@0.25.12", - "@esbuild/openbsd-arm64@0.25.12", - "@esbuild/openbsd-x64@0.25.12", - "@esbuild/openharmony-arm64@0.25.12", - "@esbuild/sunos-x64@0.25.12", - "@esbuild/win32-arm64@0.25.12", - "@esbuild/win32-ia32@0.25.12", - "@esbuild/win32-x64@0.25.12" - ], - "scripts": true, - "bin": true - }, "esbuild@0.25.7": { "integrity": "sha512-daJB0q2dmTzo90L9NjRaohhRWrCzYxWNFTjEi72/h+p5DcY3yn4MacWfDakHmaBaDzDiuLJsCh0+6LK/iX+c+Q==", "optionalDependencies": [ @@ -1093,6 +1056,39 @@ "scripts": true, "bin": true }, + "esbuild@0.27.2": { + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "optionalDependencies": [ + "@esbuild/aix-ppc64@0.27.2", + "@esbuild/android-arm@0.27.2", + "@esbuild/android-arm64@0.27.2", + "@esbuild/android-x64@0.27.2", + "@esbuild/darwin-arm64@0.27.2", + "@esbuild/darwin-x64@0.27.2", + "@esbuild/freebsd-arm64@0.27.2", + "@esbuild/freebsd-x64@0.27.2", + "@esbuild/linux-arm@0.27.2", + "@esbuild/linux-arm64@0.27.2", + "@esbuild/linux-ia32@0.27.2", + "@esbuild/linux-loong64@0.27.2", + "@esbuild/linux-mips64el@0.27.2", + "@esbuild/linux-ppc64@0.27.2", + "@esbuild/linux-riscv64@0.27.2", + "@esbuild/linux-s390x@0.27.2", + "@esbuild/linux-x64@0.27.2", + "@esbuild/netbsd-arm64@0.27.2", + "@esbuild/netbsd-x64@0.27.2", + "@esbuild/openbsd-arm64@0.27.2", + "@esbuild/openbsd-x64@0.27.2", + "@esbuild/openharmony-arm64@0.27.2", + "@esbuild/sunos-x64@0.27.2", + "@esbuild/win32-arm64@0.27.2", + "@esbuild/win32-ia32@0.27.2", + "@esbuild/win32-x64@0.27.2" + ], + "scripts": true, + "bin": true + }, "escalade@3.2.0": { "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, @@ -1302,20 +1298,20 @@ "source-map-js" ] }, - "preact-render-to-string@6.6.3_preact@10.27.2": { - "integrity": "sha512-7oHG7jzjriqsFPkSPiPnzrQ0GcxFm6wOkYWNdStK5Ks9YlWSQQXKGBRAX4nKDdqX7HAQuRvI4pZNZMycK4WwDw==", + "preact-render-to-string@6.6.5_preact@10.28.2": { + "integrity": "sha512-O6MHzYNIKYaiSX3bOw0gGZfEbOmlIDtDfWwN1JJdc/T3ihzRT6tGGSEWE088dWrEDGa1u7101q+6fzQnO9XCPA==", "dependencies": [ "preact" ] }, - "preact@10.27.2": { - "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==" + "preact@10.28.2": { + "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==" }, "prismjs@1.30.0": { "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==" }, - "rollup@4.52.5": { - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "rollup@4.55.1": { + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", "dependencies": [ "@types/estree" ], @@ -1331,12 +1327,15 @@ "@rollup/rollup-linux-arm64-gnu", "@rollup/rollup-linux-arm64-musl", "@rollup/rollup-linux-loong64-gnu", + "@rollup/rollup-linux-loong64-musl", "@rollup/rollup-linux-ppc64-gnu", + "@rollup/rollup-linux-ppc64-musl", "@rollup/rollup-linux-riscv64-gnu", "@rollup/rollup-linux-riscv64-musl", "@rollup/rollup-linux-s390x-gnu", "@rollup/rollup-linux-x64-gnu", "@rollup/rollup-linux-x64-musl", + "@rollup/rollup-openbsd-x64", "@rollup/rollup-openharmony-arm64", "@rollup/rollup-win32-arm64-msvc", "@rollup/rollup-win32-ia32-msvc", @@ -1364,8 +1363,8 @@ "source-map-js@1.2.1": { "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, - "tailwindcss@4.1.16": { - "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==" + "tailwindcss@4.1.18": { + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==" }, "tapable@2.3.0": { "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==" @@ -1380,8 +1379,8 @@ "undici-types@7.10.0": { "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" }, - "update-browserslist-db@1.1.4_browserslist@4.27.0": { - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "update-browserslist-db@1.2.3_browserslist@4.28.1": { + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dependencies": [ "browserslist", "escalade", @@ -1389,26 +1388,11 @@ ], "bin": true }, - "vite@7.1.12_picomatch@4.0.3": { - "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", - "dependencies": [ - "esbuild@0.25.12", - "fdir", - "picomatch@4.0.3", - "postcss", - "rollup", - "tinyglobby" - ], - "optionalDependencies": [ - "fsevents" - ], - "bin": true - }, - "vite@7.1.12_picomatch@4.0.3_@types+node@24.2.0": { - "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "vite@7.3.1_@types+node@24.2.0_picomatch@4.0.3": { + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dependencies": [ "@types/node", - "esbuild@0.25.12", + "esbuild@0.27.2", "fdir", "picomatch@4.0.3", "postcss", @@ -1471,14 +1455,14 @@ "workspace": { "dependencies": [ "jsr:@deno/gfm@0.11", - "jsr:@fresh/core@^2.1.4", - "jsr:@fresh/plugin-vite@^1.0.7", - "jsr:@valibot/valibot@^1.1.0", - "npm:@preact/signals@^2.3.2", - "npm:@tailwindcss/vite@^4.1.16", - "npm:preact@^10.27.2", - "npm:tailwindcss@^4.1.16", - "npm:vite@^7.1.12" + "jsr:@fresh/core@^2.2.0", + "jsr:@fresh/plugin-vite@^1.0.8", + "jsr:@valibot/valibot@^1.2.0", + "npm:@preact/signals@^2.5.1", + "npm:@tailwindcss/vite@^4.1.18", + "npm:preact@^10.28.2", + "npm:tailwindcss@^4.1.18", + "npm:vite@^7.3.1" ] } } diff --git a/islands/ViewNote.tsx b/islands/ViewNote.tsx index f07664d..2dd702b 100644 --- a/islands/ViewNote.tsx +++ b/islands/ViewNote.tsx @@ -14,6 +14,7 @@ import { ViewNoteSchema, viewNoteSchema } from '../lib/validation/note.ts'; import * as v from '@valibot/valibot'; import ErrorPage from '../components/ErrorPage.tsx'; import PasswordToggle from '../components/PasswordToggle.tsx'; +import CopyButton from '../components/CopyButton.tsx'; // Constants for messages const MESSAGES = { @@ -269,13 +270,7 @@ function DisplayDecryptedNote( {/* Content section */}
- - - +

Content

diff --git a/lib/rate-limiting/src/hashing.ts b/lib/rate-limiting/src/hashing.ts index 2936fa5..0b0e8e8 100644 --- a/lib/rate-limiting/src/hashing.ts +++ b/lib/rate-limiting/src/hashing.ts @@ -7,7 +7,10 @@ * const hash = await generateHMACSHA256('myInput', Deno.env.get('ARC_SECRET')!); * console.log(hash); // Outputs the HMAC SHA-256 hash in base64 format */ -export async function generateHMACSHA256(input: string, secret: string): Promise { +export async function generateHMACSHA256( + input: string, + secret: string, +): Promise { const encoder = new TextEncoder(); const key = await crypto.subtle.importKey( 'raw', @@ -16,6 +19,10 @@ export async function generateHMACSHA256(input: string, secret: string): Promise false, ['sign'], ); - const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(input)); + const signature = await crypto.subtle.sign( + 'HMAC', + key, + encoder.encode(input), + ); return btoa(String.fromCharCode(...new Uint8Array(signature))); } diff --git a/lib/services/database-service.ts b/lib/services/database-service.ts deleted file mode 100644 index 86eb821..0000000 --- a/lib/services/database-service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NoteDatabase } from '../database/note-database.ts'; -import { defaultLogger } from '../logging.ts'; - -const databasePath = Deno.env.get('DATABASE_PATH'); - -let _noteDatabase: NoteDatabase | null = null; - -export async function getNoteDatabase() { - if (!_noteDatabase) { - defaultLogger.log(`Database Path Source: ${databasePath ? 'env' : 'default'}`); - _noteDatabase = await new NoteDatabase(databasePath).init(); - } - return _noteDatabase; -} diff --git a/main.ts b/main.ts index f31c835..9c4b7e0 100644 --- a/main.ts +++ b/main.ts @@ -2,13 +2,20 @@ import { App, cors, csp, csrf, staticFiles } from 'fresh'; import { headers } from './middleware.ts'; import { ArcRateLimiter } from './lib/rate-limiting/src/arc-rate-limiter.ts'; import { ORIGIN, State } from './lib/types/common.ts'; +import { NoteDatabase } from './lib/database/note-database.ts'; +import { defaultLogger } from './lib/logging.ts'; const serverSecret = Deno.env.get('ARC_SECRET'); +const databasePath = Deno.env.get('DATABASE_PATH'); if (!serverSecret) { throw new Error('ARC_SECRET environment variable is not set'); } +defaultLogger.log(`Database Path Source: ${databasePath ? 'env' : 'default'}`); + +export const noteDatabase: NoteDatabase = await new NoteDatabase(databasePath).init(); + // Configure rate limiter: 15 requests per minute, 5 min block duration const rateLimiter = new ArcRateLimiter({ maxRequests: 15, diff --git a/routes/[id].tsx b/routes/[id].tsx index 7213caf..5a4e828 100644 --- a/routes/[id].tsx +++ b/routes/[id].tsx @@ -3,8 +3,8 @@ import { Note } from '../lib/types.ts'; import Header from '../components/Header.tsx'; import ViewEncryptedNote from '../islands/ViewNote.tsx'; import { HttpError } from 'fresh'; -import { getNoteDatabase } from '../lib/services/database-service.ts'; import { State } from '../lib/types/common.ts'; +import { noteDatabase } from '../main.ts'; interface NotePageProps { note: Note; @@ -18,8 +18,7 @@ export const handler = { throw new HttpError(404); } - const db = await getNoteDatabase(); - const note = await db.getNoteById(id); + const note = await noteDatabase.getNoteById(id); if (!note) { throw new HttpError(404); @@ -37,8 +36,7 @@ export const handler = { if (!id) { throw new HttpError(404); } - const db = await getNoteDatabase(); - const note = await db.getNoteById(id); + const note = await noteDatabase.getNoteById(id); if (!note) { throw new HttpError(404); diff --git a/routes/api/notes.ts b/routes/api/notes.ts index f403c1e..f5bf652 100644 --- a/routes/api/notes.ts +++ b/routes/api/notes.ts @@ -2,28 +2,26 @@ import { createNoteSchema } from '../../lib/validation/note.ts'; import { formatExpiration, Note } from '../../lib/types.ts'; import * as v from '@valibot/valibot'; import { Context } from 'fresh'; -import { getNoteDatabase } from '../../lib/services/database-service.ts'; import * as bcrypt from 'bcrypt'; import { State } from '../../lib/types/common.ts'; +import { noteDatabase } from '../../main.ts'; /* used for client side note creation and encryption - * This endpoint handles only POST requests. - * - POST: Creates a new note with the provided content, IV, password, and expiration time. - * - * Note: The content should be encrypted before sending to this endpoint and the password should be hashed with PBKDF2 - * on the client side for security, then securely hashed with bcrypt on the server for storage. - * - * rate-limiting (ARC - Anonymous Rate-Limited Credentials): - * - Limit: 10 requests per minute per client - * - Block duration: 5 minutes for rate limit violations - * - Privacy-preserving: Uses anonymous tokens with daily rotation - * - No IP address storage: Only hashed, rotated tokens are kept - */ + * This endpoint handles only POST requests. + * - POST: Creates a new note with the provided content, IV, password, and expiration time. + * + * Note: The content should be encrypted before sending to this endpoint and the password should be hashed with PBKDF2 + * on the client side for security, then securely hashed with bcrypt on the server for storage. + * + * rate-limiting (ARC - Anonymous Rate-Limited Credentials): + * - Limit: 10 requests per minute per client + * - Block duration: 5 minutes for rate limit violations + * - Privacy-preserving: Uses anonymous tokens with daily rotation + * - No IP address storage: Only hashed, rotated tokens are kept + */ export const handler = { async POST(ctx: Context) { - const db = await getNoteDatabase(); - try { const { content, iv, password, expiresIn, manualDeletion } = await ctx.req.json(); @@ -42,7 +40,7 @@ export const handler = { ); } - const noteId = await db.generateNoteId(); + const noteId = await noteDatabase.generateNoteId(); const hasPassword = password && password.trim() !== ''; // if password is provided, hash it with bcrypt (password should be PBKDF2 hashed on client before sending) @@ -58,7 +56,7 @@ export const handler = { manualDeletion: manualDeletion, }; - const insertResult = await db.insertNote(result); + const insertResult = await noteDatabase.insertNote(result); if (!insertResult.success) { return new Response( diff --git a/routes/api/notes/[id].ts b/routes/api/notes/[id].ts index a9cd59c..700a45d 100644 --- a/routes/api/notes/[id].ts +++ b/routes/api/notes/[id].ts @@ -1,14 +1,13 @@ import { Note } from '../../../lib/types.ts'; import { Context } from 'fresh'; -import { getNoteDatabase } from '../../../lib/services/database-service.ts'; import * as bcrypt from 'bcrypt'; import { State } from '../../../lib/types/common.ts'; +import { noteDatabase } from '../../../main.ts'; export const handler = async (ctx: Context): Promise => { if (ctx.req.method !== 'POST' && ctx.req.method !== 'DELETE') { return new Response('Method not allowed', { status: 405 }); } - const db = await getNoteDatabase(); const id = ctx.params.id; if (!id) { @@ -16,7 +15,7 @@ export const handler = async (ctx: Context): Promise => { } if (ctx.req.method === 'POST') { - const note = await db.getNoteById(id); + const note = await noteDatabase.getNoteById(id); const { passwordHash } = await ctx.req.json(); if (!note || !passwordHash) { @@ -29,7 +28,7 @@ export const handler = async (ctx: Context): Promise => { // If the note doesn't require manual deletion, delete it to ensure it has been destroyed if (!note.manualDeletion) { - await db.deleteNote(id); + await noteDatabase.deleteNote(id); } return new Response( @@ -45,7 +44,7 @@ export const handler = async (ctx: Context): Promise => { }, ); } else if (ctx.req.method === 'DELETE') { - const note = await db.getNoteById(id); + const note = await noteDatabase.getNoteById(id); const { passwordHash } = await ctx.req.json(); if (!note) { return new Response('Note not found', { status: 404 }); @@ -54,7 +53,7 @@ export const handler = async (ctx: Context): Promise => { if (note.password && !compareHash(passwordHash, note.password)) { return new Response('Invalid password or auth key', { status: 403 }); } - await db.deleteNote(id); + await noteDatabase.deleteNote(id); return new Response( JSON.stringify({ message: 'Note deleted successfully', diff --git a/tests/arc-rate-limiter_test.ts b/tests/arc-rate-limiter_test.ts index 2c31f38..c69badd 100644 --- a/tests/arc-rate-limiter_test.ts +++ b/tests/arc-rate-limiter_test.ts @@ -18,6 +18,7 @@ const defaultRateLimitOptions = { enablePeriodicCleanup: false, }; +Deno.env.set('ARC_SECRET', defaultRateLimitOptions.serverSecret); Deno.test('ARC Rate Limiter - basic functionality', async () => { const rateLimiter = new ArcRateLimiter(defaultRateLimitOptions); const app = new App() diff --git a/tests/main_test.ts b/tests/main_test.ts index 35881b1..ff2c4e4 100644 --- a/tests/main_test.ts +++ b/tests/main_test.ts @@ -21,6 +21,8 @@ export class TestDataFactory { }; } +Deno.env.set('ARC_SECRET', 'super-secret'); + // Test suite for basic HTTP functionality Deno.test({ name: 'HTTP - Basic functionality', From 9921430595f5ee4aa2dfe5591ce5097801c90b65 Mon Sep 17 00:00:00 2001 From: Emil Krebs <68400102+emilkrebs@users.noreply.github.com> Date: Sat, 10 Jan 2026 01:41:15 +0100 Subject: [PATCH 2/5] Update main.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- main.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/main.ts b/main.ts index 9c4b7e0..b7b2d7e 100644 --- a/main.ts +++ b/main.ts @@ -14,8 +14,16 @@ if (!serverSecret) { defaultLogger.log(`Database Path Source: ${databasePath ? 'env' : 'default'}`); -export const noteDatabase: NoteDatabase = await new NoteDatabase(databasePath).init(); +let noteDatabase: NoteDatabase; +try { + noteDatabase = await new NoteDatabase(databasePath).init(); +} catch (error) { + defaultLogger.error('Failed to initialize NoteDatabase', error); + throw error; +} + +export { noteDatabase }; // Configure rate limiter: 15 requests per minute, 5 min block duration const rateLimiter = new ArcRateLimiter({ maxRequests: 15, From f6731904f8aedb3b2de9307d697e1d2bcb3800c3 Mon Sep 17 00:00:00 2001 From: Emil Krebs <68400102+emilkrebs@users.noreply.github.com> Date: Sat, 10 Jan 2026 01:41:22 +0100 Subject: [PATCH 3/5] Update tests/main_test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/main_test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/main_test.ts b/tests/main_test.ts index ff2c4e4..f53c17a 100644 --- a/tests/main_test.ts +++ b/tests/main_test.ts @@ -21,7 +21,8 @@ export class TestDataFactory { }; } -Deno.env.set('ARC_SECRET', 'super-secret'); +const testArcSecret = crypto.randomUUID(); +Deno.env.set('ARC_SECRET', testArcSecret); // Test suite for basic HTTP functionality Deno.test({ From 37256dec493d8d4658072c9135ed8761c5408424 Mon Sep 17 00:00:00 2001 From: emilkrebs Date: Sat, 10 Jan 2026 01:57:46 +0100 Subject: [PATCH 4/5] improve code quality to reduce main blocking threads --- deno.lock | 3 +- lib/database/note-database.ts | 26 +++++++------- main.ts | 9 +++-- routes/[id].tsx | 17 --------- routes/api/notes.ts | 8 ++--- routes/api/notes/[id].ts | 68 +++++++++++++++++------------------ 6 files changed, 55 insertions(+), 76 deletions(-) diff --git a/deno.lock b/deno.lock index a6d5de2..935ef7d 100644 --- a/deno.lock +++ b/deno.lock @@ -1450,7 +1450,8 @@ "https://deno.land/x/bcrypt@v0.4.1/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7", "https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7", "https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/bcrypt.ts": "ec221648cc6453ea5e3803bc817c01157dada06aa6f7a0ba6b9f87aae32b21e2", - "https://deno.land/x/bcrypt@v0.4.1/src/main.ts": "08d201b289c8d9c46f8839c69cd6625b213863db29775c7a200afc3b540e64f8" + "https://deno.land/x/bcrypt@v0.4.1/src/main.ts": "08d201b289c8d9c46f8839c69cd6625b213863db29775c7a200afc3b540e64f8", + "https://deno.land/x/bcrypt@v0.4.1/src/worker.ts": "5a73bdfee9c9e622f47c9733d374b627dce52fb3ec1e74c8226698b3fc57ffac" }, "workspace": { "dependencies": [ diff --git a/lib/database/note-database.ts b/lib/database/note-database.ts index 2ef7dd4..4e07c77 100644 --- a/lib/database/note-database.ts +++ b/lib/database/note-database.ts @@ -116,23 +116,21 @@ export class NoteDatabase { async generateNoteId(): Promise { if (!this.kv) throw new Error('Database not initialized'); - let id = ''; - let note = null; - let attempts = 0; - let length = 10; // Start with a length of 10 characters - - do { - attempts++; - if (attempts > 10) { - length++; - } - id = Math.random().toString(36).substring(2, length + 2); + const MAX_ATTEMPTS = 5; + const BASE_LENGTH = 12; // Start with longer IDs to reduce collisions + + for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + const length = BASE_LENGTH + Math.floor(attempt / 2); // Increase length after failed attempts + const id = crypto.randomUUID().replace(/-/g, '').substring(0, length); const res = await this.kv.get(['note', id]); - note = res.value; - } while (note); + if (!res.value) { + return id; + } + } - return id; + // Fallback to timestamp-based ID if all attempts fail + return `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`; } async clearAllNotes(): Promise { diff --git a/main.ts b/main.ts index b7b2d7e..1858bfd 100644 --- a/main.ts +++ b/main.ts @@ -7,23 +7,20 @@ import { defaultLogger } from './lib/logging.ts'; const serverSecret = Deno.env.get('ARC_SECRET'); const databasePath = Deno.env.get('DATABASE_PATH'); +let noteDatabase: NoteDatabase; if (!serverSecret) { throw new Error('ARC_SECRET environment variable is not set'); } -defaultLogger.log(`Database Path Source: ${databasePath ? 'env' : 'default'}`); - -let noteDatabase: NoteDatabase; - try { noteDatabase = await new NoteDatabase(databasePath).init(); + defaultLogger.log(`Database Path Source: ${databasePath ? 'env' : 'default'}`); } catch (error) { defaultLogger.error('Failed to initialize NoteDatabase', error); throw error; } -export { noteDatabase }; // Configure rate limiter: 15 requests per minute, 5 min block duration const rateLimiter = new ArcRateLimiter({ maxRequests: 15, @@ -33,6 +30,8 @@ const rateLimiter = new ArcRateLimiter({ serverSecret, }); +export { noteDatabase }; + export const app = new App() .use(staticFiles()) .use(cors({ diff --git a/routes/[id].tsx b/routes/[id].tsx index 5a4e828..80d1e40 100644 --- a/routes/[id].tsx +++ b/routes/[id].tsx @@ -28,23 +28,6 @@ export const handler = { // The client will handle password input and decryption entirely return { data: { note, message: 'Note found - the client will handle decryption' } }; }, - - async POST(ctx: Context) { - // For backward compatibility, POST requests should also render the client-side component - // All password validation and decryption now happens client-side - const { id } = ctx.params; - if (!id) { - throw new HttpError(404); - } - const note = await noteDatabase.getNoteById(id); - - if (!note) { - throw new HttpError(404); - } - - // Always render the client-side component - password validation is now client-side - return { data: { note, message: 'Note found - the client will handle decryption' } }; - }, }; export default function NotePage({ data }: { data: NotePageProps }) { diff --git a/routes/api/notes.ts b/routes/api/notes.ts index f5bf652..d731136 100644 --- a/routes/api/notes.ts +++ b/routes/api/notes.ts @@ -44,7 +44,7 @@ export const handler = { const hasPassword = password && password.trim() !== ''; // if password is provided, hash it with bcrypt (password should be PBKDF2 hashed on client before sending) - const passwordHash = hasPassword ? generateHash(password) : undefined; + const passwordHash = hasPassword ? await generateHash(password) : undefined; // check if content is encrypted const result: Note = { @@ -94,7 +94,7 @@ export const handler = { }, }; -function generateHash(password: string): string { - const salt = bcrypt.genSaltSync(12); - return bcrypt.hashSync(password, salt); +async function generateHash(password: string): Promise { + const salt = await bcrypt.genSalt(12); + return await bcrypt.hash(password, salt); } diff --git a/routes/api/notes/[id].ts b/routes/api/notes/[id].ts index 700a45d..4af8ebe 100644 --- a/routes/api/notes/[id].ts +++ b/routes/api/notes/[id].ts @@ -4,6 +4,26 @@ import * as bcrypt from 'bcrypt'; import { State } from '../../../lib/types/common.ts'; import { noteDatabase } from '../../../main.ts'; +async function validateNoteAccess(id: string, passwordHash?: string): Promise<{ note: Note | null; error?: Response }> { + const note = await noteDatabase.getNoteById(id); + + if (!note) { + return { + note: null, + error: new Response('Note not found', { status: 404 }), + }; + } + + if (note.password && passwordHash && !(await compareHash(passwordHash, note.password))) { + return { + note: null, + error: new Response('Invalid password or auth key', { status: 403 }), + }; + } + + return { note }; +} + export const handler = async (ctx: Context): Promise => { if (ctx.req.method !== 'POST' && ctx.req.method !== 'DELETE') { return new Response('Method not allowed', { status: 405 }); @@ -14,62 +34,40 @@ export const handler = async (ctx: Context): Promise => { return new Response('Note ID is required', { status: 400 }); } - if (ctx.req.method === 'POST') { - const note = await noteDatabase.getNoteById(id); - const { passwordHash } = await ctx.req.json(); - - if (!note || !passwordHash) { - return new Response('Note not found or password hash missing', { status: 404 }); - } + const { passwordHash } = await ctx.req.json(); + const { note, error } = await validateNoteAccess(id, passwordHash); - if (note.password && !compareHash(passwordHash, note.password)) { - return new Response('Invalid password or auth key', { status: 403 }); - } + if (error) return error; + if (!note) return new Response('Note not found', { status: 404 }); - // If the note doesn't require manual deletion, delete it to ensure it has been destroyed + if (ctx.req.method === 'POST') { + // Auto-delete non-manual notes after viewing if (!note.manualDeletion) { await noteDatabase.deleteNote(id); } return new Response( - JSON.stringify({ - id: note.id, - content: note.content, - iv: note.iv, - expiresIn: note.expiresIn, - manualDeletion: note.manualDeletion, - } as Note), + JSON.stringify(note), { status: 200, + headers: { 'Content-Type': 'application/json' }, }, ); - } else if (ctx.req.method === 'DELETE') { - const note = await noteDatabase.getNoteById(id); - const { passwordHash } = await ctx.req.json(); - if (!note) { - return new Response('Note not found', { status: 404 }); - } - - if (note.password && !compareHash(passwordHash, note.password)) { - return new Response('Invalid password or auth key', { status: 403 }); - } + } else { // DELETE await noteDatabase.deleteNote(id); return new Response( - JSON.stringify({ - message: 'Note deleted successfully', - }), + JSON.stringify({ message: 'Note deleted successfully' }), { status: 200, + headers: { 'Content-Type': 'application/json' }, }, ); - } else { - return new Response('Method not allowed', { status: 405 }); } }; -function compareHash(plainText: string, hash: string): boolean { +async function compareHash(plainText: string, hash: string): Promise { try { - return bcrypt.compareSync(plainText, hash); + return await bcrypt.compare(plainText, hash); } catch (error) { console.error('Error comparing hash:', error); return false; From 41cfc6f450a2afbf20f8590c5e5c31fb64cdfc29 Mon Sep 17 00:00:00 2001 From: Emil Krebs <68400102+emilkrebs@users.noreply.github.com> Date: Sat, 10 Jan 2026 02:01:09 +0100 Subject: [PATCH 5/5] Update lib/database/note-database.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/database/note-database.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/database/note-database.ts b/lib/database/note-database.ts index 4e07c77..3b861d5 100644 --- a/lib/database/note-database.ts +++ b/lib/database/note-database.ts @@ -129,8 +129,12 @@ export class NoteDatabase { } } - // Fallback to timestamp-based ID if all attempts fail - return `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`; + // Fallback to cryptographically secure random ID if all attempts fail + const fallbackId = ( + crypto.randomUUID().replace(/-/g, '') + + crypto.randomUUID().replace(/-/g, '') + ).substring(0, 20); + return fallbackId; } async clearAllNotes(): Promise {