From a38432a542e9c2440993f59257dd971e532a4023 Mon Sep 17 00:00:00 2001 From: scanash00 Date: Thu, 26 Mar 2026 12:43:52 +0000 Subject: [PATCH] new features, enhancements, and bug fixes --- backend/go.mod | 1 - backend/go.sum | 2 -- web/astro.config.mjs | 10 ++++++---- web/bun.lock | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------------------------------------- web/package-lock.json | 9674 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- web/package.json | 7 +++---- .github/workflows/deploy.yml | 22 ---------------------- web/src/App.tsx | 257 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- web/src/env.d.ts | 7 +++++++ web/src/middleware.ts | 20 +++++++++++++++++--- backend/cmd/server/main.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------- backend/internal/api/apikey.go | 6 +++--- backend/internal/api/handler.go | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- backend/internal/api/hydration.go | 307 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------- backend/internal/api/pds.go | 4 +++- backend/internal/db/db.go | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- backend/internal/db/pg_helpers.go | 22 ++++++++++++++++++++++ backend/internal/db/queries.go | 16 ++++++++-------- backend/internal/db/queries_annotations.go | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------------------------------------- backend/internal/db/queries_bookmarks.go | 364 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- backend/internal/db/queries_collections.go | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------- backend/internal/db/queries_highlights.go | 402 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ backend/internal/db/queries_history.go | 98 +++++++++++++++----------------------------------------------------------------------------------- backend/internal/db/queries_keys.go | 20 ++++++++++---------- backend/internal/db/queries_likes.go | 44 +++++++++++++++++++++++--------------------- backend/internal/db/queries_moderation.go | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------------- backend/internal/db/queries_notifications.go | 26 +++++++++++++------------- backend/internal/db/queries_recommendations.go | 62 +++++++++++++++++++++++++++----------------------------------- backend/internal/db/queries_replies.go | 109 ++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------- backend/internal/db/queries_search.go | 89 +++++++++++++++++++++++++++++++++++++++++------------------------------------------------ backend/internal/db/queries_sessions.go | 24 ++++++++++++------------ backend/internal/db/tags.go | 161 ++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------------------------- backend/internal/firehose/ingester.go | 33 +++++++++++++++++++++++++++------ backend/internal/recommendations/service.go | 6 ++++++ backend/internal/verification/verify.go | 10 +++++++++- extension/src/utils/overlay.ts | 21 +++++++++++---------- web/src/layouts/AppLayout.astro | 42 ++++++++++++++++++++++++++++++++++++++++++ web/src/layouts/BaseLayout.astro | 24 ++++++++++++++++++++++++ web/src/layouts/OGLayout.astro | 16 ---------------- web/src/lib/api.ts | 490 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/lib/metadataQueue.ts | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/pages/[...slug].astro | 10 ---------- web/src/pages/about.astro | 9 +++++++++ web/src/pages/annotations.astro | 12 ++++++++++++ web/src/pages/bookmarks.astro | 12 ++++++++++++ web/src/pages/discover.astro | 11 +++++++++++ web/src/pages/highlights.astro | 12 ++++++++++++ web/src/pages/home.astro | 12 ++++++++++++ web/src/pages/index.astro | 10 ++++++++-- web/src/pages/login.astro | 16 ++++++++++++++++ web/src/pages/new.astro | 18 ++++++++++++++++++ web/src/pages/notifications.astro | 14 ++++++++++++++ web/src/pages/og-image.ts | 562 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- web/src/pages/search.astro | 12 ++++++++++++ web/src/pages/settings.astro | 11 +++++++++++ web/src/routes/wrappers.tsx | 38 -------------------------------------- web/src/styles/global.css | 5 +++++ web/src/views/About.tsx | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------- web/src/components/common/Card.tsx | 59 +++++++++++++++++++++++++++++++++++------------------------ web/src/components/common/ProfileHoverCard.tsx | 13 ++++++------- web/src/components/common/RichText.tsx | 7 +++---- web/src/components/modals/SignUpModal.tsx | 3 +++ web/src/components/navigation/MobileNav.tsx | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------- web/src/components/navigation/RightSidebar.tsx | 28 +++++++++++++--------------- web/src/components/navigation/Sidebar.tsx | 72 +++++++++++++++++++++++++++++++++++++++++++++++++----------------------- web/src/components/ui/Button.tsx | 4 ++-- web/src/components/ui/Tabs.tsx | 4 ++-- web/src/pages/admin/moderation.astro | 14 ++++++++++++++ web/src/pages/collections/[rkey].astro | 12 ++++++++++++ web/src/pages/collections/index.astro | 11 +++++++++++ web/src/pages/profile/[did].astro | 16 ++++++++++++++++ web/src/pages/profile/index.astro | 8 ++++++++ web/src/views/auth/Login.tsx | 21 +++++++++------------ web/src/views/collections/CollectionDetail.tsx | 7 +++---- web/src/views/content/AnnotationDetail.tsx | 65 +++++++++++++++++++++++++++++++++-------------------------------- web/src/views/content/UrlPage.tsx | 16 ++++++++-------- web/src/views/content/UserUrlPage.tsx | 11 ++++++----- web/src/views/core/AdminModeration.tsx | 19 +++++++++---------- web/src/views/core/Discover.tsx | 2 +- web/src/views/core/Feed.tsx | 46 ++++++++++++++++++++++++---------------------- web/src/views/core/New.tsx | 39 ++++++++++++++++++++++----------------- web/src/views/core/Notifications.tsx | 30 +++++++++++++++--------------- web/src/views/core/Search.tsx | 15 +++++++++------ web/src/views/core/Settings.tsx | 13 ++++++------- web/src/views/profile/Profile.tsx | 7 +++---- web/src/pages/[handle]/annotation/[rkey].astro | 11 +++++++---- web/src/pages/[handle]/bookmark/[rkey].astro | 11 +++++++---- web/src/pages/[handle]/collection/[rkey].astro | 11 +++++++---- web/src/pages/[handle]/highlight/[rkey].astro | 11 +++++++---- web/src/pages/at/[did]/[rkey].astro | 9 ++++++--- 90 file(s) changed, 3130 insertion(s)(+), 12258 deletion(s)(-) diff --git a/backend/go.mod b/backend/go.mod --- a/backend/go.mod +++ b/backend/go.mod @@ -11,7 +11,6 @@ github.com/ipfs/go-cid v0.6.0 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 - github.com/mattn/go-sqlite3 v1.14.22 github.com/multiformats/go-multihash v0.2.3 ) diff --git a/backend/go.sum b/backend/go.sum --- a/backend/go.sum +++ b/backend/go.sum @@ -21,8 +21,6 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= diff --git a/web/astro.config.mjs b/web/astro.config.mjs --- a/web/astro.config.mjs +++ b/web/astro.config.mjs @@ -6,18 +6,20 @@ const API_PORT = process.env.API_PORT || 8081; -const isDev = process.env.NODE_ENV === "development"; - // https://astro.build/config export default defineConfig({ + output: "server", adapter: node({ mode: "standalone" }), integrations: [react(), tailwind()], + prefetch: { + prefetchAll: false, + defaultStrategy: "hover", + }, security: { - checkOrigin: false, + checkOrigin: true, }, vite: { ssr: { - noExternal: isDev ? /^(?!react|react-dom|react-router-dom|cookie)/ : true, external: ["@resvg/resvg-js"], }, build: { diff --git a/web/bun.lock b/web/bun.lock --- a/web/bun.lock +++ b/web/bun.lock @@ -1,17 +1,16 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "web", "dependencies": { - "@astrojs/node": "^9.5.2", - "@astrojs/react": "^4.4.2", + "@astrojs/node": "^10.0.3", + "@astrojs/react": "^5.0.1", "@astrojs/tailwind": "^6.0.2", "@nanostores/react": "^1.0.0", "@resvg/resvg-js": "^2.6.2", "@tailwindcss/vite": "^4.1.18", - "astro": "^5.17.1", + "astro": "^6.0.8", "autoprefixer": "^10.4.24", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -21,7 +20,6 @@ "postcss": "^8.5.6", "react": "^19.2.4", "react-dom": "^19.2.4", - "react-router-dom": "^7.13.0", "satori": "^0.19.2", "tailwind-merge": "^3.4.0", "tailwindcss": "^3.4.19", @@ -50,17 +48,17 @@ "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - "@astrojs/compiler": ["@astrojs/compiler@2.13.0", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="], + "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="], - "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.5", "", {}, "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA=="], + "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.8.0", "", { "dependencies": { "picomatch": "^4.0.3" } }, "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w=="], - "@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.10", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.19.0", "smol-toml": "^1.5.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A=="], + "@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.0.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-zAfLJmn07u9SlDNNHTpjv0RT4F8D4k54NR7ReRas8CO4OeGoqSvOuKwqCFg2/cqN3wHwdWlK/7Yv/lMXlhVIaw=="], - "@astrojs/node": ["@astrojs/node@9.5.2", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "send": "^1.2.1", "server-destroy": "^1.0.1" }, "peerDependencies": { "astro": "^5.14.3" } }, "sha512-85/x+FRwbNGDip1TzSGMiak31/6LvBhA8auqd9lLoHaM5XElk+uIfIr3KjJqucDojE0PtiLk1lMSwD9gd3YlGg=="], + "@astrojs/node": ["@astrojs/node@10.0.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "send": "^1.2.1", "server-destroy": "^1.0.1" }, "peerDependencies": { "astro": "^6.0.0" } }, "sha512-yWDPaXTOw34h9qNpxDBz1Xj5HudnyuWW2E8ZSegW6o8n+mKI3Yq/iLAUQfxA3h8wfaIRY/PCh3T2jLAys2SXeQ=="], - "@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="], + "@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="], - "@astrojs/react": ["@astrojs/react@4.4.2", "", { "dependencies": { "@vitejs/plugin-react": "^4.7.0", "ultrahtml": "^1.6.0", "vite": "^6.4.1" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ=="], + "@astrojs/react": ["@astrojs/react@5.0.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@vitejs/plugin-react": "^5.1.4", "devalue": "^5.6.3", "ultrahtml": "^1.6.0", "vite": "^7.3.1" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-gJgQfDUxyePk+UIzwCEtAq04SGbziwRNwOMYvkxLHEtZScSMvRnvQhDWAEMCjLwwEomoT92Tfm34xpD7XAAzOg=="], "@astrojs/tailwind": ["@astrojs/tailwind@6.0.2", "", { "dependencies": { "autoprefixer": "^10.4.21", "postcss": "^8.5.3", "postcss-load-config": "^4.0.2" }, "peerDependencies": { "astro": "^3.0.0 || ^4.0.0 || ^5.0.0", "tailwindcss": "^3.0.24" } }, "sha512-j3mhLNeugZq6A8dMNXVarUa8K6X9AW+QHU9u3lKNrPLMHhOQ0S7VeWhHwEeJFpEK1BTKEUY1U78VQv2gN6hNGg=="], @@ -106,59 +104,63 @@ "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="], + "@clack/core": ["@clack/core@1.1.0", "", { "dependencies": { "sisteransi": "^1.0.5" } }, "sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA=="], + + "@clack/prompts": ["@clack/prompts@1.1.0", "", { "dependencies": { "@clack/core": "1.1.0", "sisteransi": "^1.0.5" } }, "sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g=="], + "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], @@ -286,7 +288,7 @@ "@resvg/resvg-js-win32-x64-msvc": ["@resvg/resvg-js-win32-x64-msvc@2.6.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], @@ -340,17 +342,19 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], - "@shikijs/core": ["@shikijs/core@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA=="], + "@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], - "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw=="], + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="], - "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA=="], + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="], - "@shikijs/langs": ["@shikijs/langs@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0" } }, "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA=="], + "@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="], - "@shikijs/themes": ["@shikijs/themes@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0" } }, "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g=="], + "@shikijs/primitive": ["@shikijs/primitive@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw=="], - "@shikijs/types": ["@shikijs/types@3.22.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg=="], + "@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="], + + "@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="], "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], @@ -440,19 +444,13 @@ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - - "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], - - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], @@ -480,7 +478,7 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], - "astro": ["astro@5.17.1", "", { "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.5", "@astrojs/markdown-remark": "6.3.10", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.1", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.1.1", "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", "devalue": "^5.6.2", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.4.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.1", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.1", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-oD3tlxTaVWGq/Wfbqk6gxzVRz98xa/rYlpe+gU2jXJMSD01k6sEDL01ZlT8mVSYB/rMgnvIOfiQQ3BbLdN237A=="], + "astro": ["astro@6.0.8", "", { "dependencies": { "@astrojs/compiler": "^3.0.0", "@astrojs/internal-helpers": "0.8.0", "@astrojs/markdown-remark": "7.0.1", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.0.1", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyclip": "^0.1.6", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^7.3.1", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "bin/astro.mjs" } }, "sha512-DCPeb8GKOoFWh+8whB7Qi/kKWD/6NcQ9nd1QVNzJFxgHkea3WYrNroQRq4whmBdjhkYPTLS/1gmUAl2iA2Es2g=="], "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], @@ -494,8 +492,6 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="], - "base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="], @@ -503,8 +499,6 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - - "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], @@ -518,8 +512,6 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], - "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], "camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="], @@ -527,8 +519,6 @@ "caniuse-lite": ["caniuse-lite@1.0.30001768", "", {}, "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - - "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], @@ -540,8 +530,6 @@ "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], - "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], @@ -550,7 +538,7 @@ "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], - "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], @@ -614,9 +602,7 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], - - "devalue": ["devalue@5.6.2", "", {}, "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg=="], + "devalue": ["devalue@5.6.4", "", {}, "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], @@ -646,8 +632,6 @@ "emoji-picker-react": ["emoji-picker-react@4.18.0", "", { "dependencies": { "flairup": "1.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-vLTrLfApXAIciguGE57pXPWs9lPLBspbEpPMiUq03TIli2dHZBiB+aZ0R9/Wat0xmTfcd4AuEzQgSYxEZ8C88Q=="], - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], - "emoji-regex-xs": ["emoji-regex-xs@2.0.1", "", {}, "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -664,7 +648,7 @@ "es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="], - "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], @@ -674,7 +658,7 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], - "esbuild": ["esbuild@0.25.12", "", { "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" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -706,7 +690,7 @@ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], @@ -767,8 +751,6 @@ "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -838,8 +820,6 @@ "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -871,8 +851,6 @@ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], @@ -935,8 +913,6 @@ "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], @@ -1124,6 +1100,8 @@ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], @@ -1138,13 +1116,13 @@ "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], - "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], + "p-limit": ["p-limit@7.3.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], + "p-queue": ["p-queue@9.1.0", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw=="], - "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], + "p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="], "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], @@ -1196,8 +1174,6 @@ "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], - "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], - "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], @@ -1218,11 +1194,7 @@ "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], - - "react-router": ["react-router@7.13.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw=="], - - "react-router-dom": ["react-router-dom@7.13.0", "", { "dependencies": { "react-router": "7.13.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g=="], + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], @@ -1290,8 +1262,6 @@ "server-destroy": ["server-destroy@1.0.1", "", {}, "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ=="], - "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], - "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], @@ -1306,7 +1276,7 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "shiki": ["shiki@3.22.0", "", { "dependencies": { "@shikijs/core": "3.22.0", "@shikijs/engine-javascript": "3.22.0", "@shikijs/engine-oniguruma": "3.22.0", "@shikijs/langs": "3.22.0", "@shikijs/themes": "3.22.0", "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g=="], + "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], @@ -1328,8 +1298,6 @@ "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="], "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], @@ -1343,8 +1311,6 @@ "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], - - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], @@ -1365,6 +1331,8 @@ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + + "tinyclip": ["tinyclip@0.1.12", "", {}, "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA=="], "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], @@ -1387,8 +1355,6 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], @@ -1416,7 +1382,7 @@ "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], - "unifont": ["unifont@0.7.3", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA=="], + "unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="], "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], @@ -1450,9 +1416,9 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], + "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], - "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], + "vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="], "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], @@ -1468,11 +1434,7 @@ "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], - "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], @@ -1480,21 +1442,13 @@ "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], - "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], - - "yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="], - - "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], - - "zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="], "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], @@ -1503,8 +1457,6 @@ "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], @@ -1530,11 +1482,11 @@ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "astro/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "astro/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "astro/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -1568,16 +1520,10 @@ "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - - "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], } } diff --git a/web/package-lock.json b/web/package-lock.json deleted file mode 100644 --- a/web/package-lock.json +++ /dev/null @@ -1,9674 +0,0 @@ -{ - "name": "web", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "web", - "version": "0.0.1", - "dependencies": { - "@astrojs/node": "^9.5.2", - "@astrojs/react": "^4.4.2", - "@astrojs/tailwind": "^6.0.2", - "@nanostores/react": "^1.0.0", - "@resvg/resvg-js": "^2.6.2", - "@tailwindcss/vite": "^4.1.18", - "astro": "^5.17.1", - "autoprefixer": "^10.4.24", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "emoji-picker-react": "^4.18.0", - "lucide-react": "^0.563.0", - "nanostores": "^1.1.0", - "postcss": "^8.5.6", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-router-dom": "^7.13.0", - "satori": "^0.19.2", - "tailwind-merge": "^3.4.0", - "tailwindcss": "^3.4.19" - }, - "devDependencies": { - "@eslint/js": "^10.0.1", - "@types/node": "^25.2.3", - "@types/react": "^19.2.11", - "@types/react-dom": "^19.2.3", - "@typescript-eslint/eslint-plugin": "^8.54.0", - "@typescript-eslint/parser": "^8.54.0", - "eslint": "^10.0.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.5", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.5.0", - "globals": "^17.3.0", - "prettier": "^3.8.1", - "react-icons": "^5.5.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.54.0" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@astrojs/compiler": { - "version": "2.13.0", - "license": "MIT" - }, - "node_modules/@astrojs/internal-helpers": { - "version": "0.7.5", - "license": "MIT" - }, - "node_modules/@astrojs/markdown-remark": { - "version": "6.3.10", - "license": "MIT", - "dependencies": { - "@astrojs/internal-helpers": "0.7.5", - "@astrojs/prism": "3.3.0", - "github-slugger": "^2.0.0", - "hast-util-from-html": "^2.0.3", - "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.2.0", - "js-yaml": "^4.1.1", - "mdast-util-definitions": "^6.0.0", - "rehype-raw": "^7.0.0", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.1", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.2", - "remark-smartypants": "^3.0.2", - "shiki": "^3.19.0", - "smol-toml": "^1.5.2", - "unified": "^11.0.5", - "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.2", - "vfile": "^6.0.3" - } - }, - "node_modules/@astrojs/node": { - "version": "9.5.2", - "license": "MIT", - "dependencies": { - "@astrojs/internal-helpers": "0.7.5", - "send": "^1.2.1", - "server-destroy": "^1.0.1" - }, - "peerDependencies": { - "astro": "^5.14.3" - } - }, - "node_modules/@astrojs/prism": { - "version": "3.3.0", - "license": "MIT", - "dependencies": { - "prismjs": "^1.30.0" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" - } - }, - "node_modules/@astrojs/react": { - "version": "4.4.2", - "license": "MIT", - "dependencies": { - "@vitejs/plugin-react": "^4.7.0", - "ultrahtml": "^1.6.0", - "vite": "^6.4.1" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" - }, - "peerDependencies": { - "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", - "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", - "react": "^17.0.2 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@astrojs/tailwind": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.3", - "postcss-load-config": "^4.0.2" - }, - "peerDependencies": { - "astro": "^3.0.0 || ^4.0.0 || ^5.0.0", - "tailwindcss": "^3.0.24" - } - }, - "node_modules/@astrojs/telemetry": { - "version": "3.3.0", - "license": "MIT", - "dependencies": { - "ci-info": "^4.2.0", - "debug": "^4.4.0", - "dlv": "^1.1.3", - "dset": "^3.1.4", - "is-docker": "^3.0.0", - "is-wsl": "^3.1.0", - "which-pm-runs": "^1.1.0" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@capsizecss/unpack": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "fontkitten": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.23.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.1", - "debug": "^4.3.1", - "minimatch": "^10.1.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.5.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.1.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.6.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@img/colour": { - "version": "1.0.0", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nanostores/react": { - "version": "1.0.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": "^20.0.0 || >=22.0.0" - }, - "peerDependencies": { - "nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^1.0.0", - "react": ">=18.0.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@oslojs/encoding": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@resvg/resvg-js": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz", - "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==", - "license": "MPL-2.0", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@resvg/resvg-js-android-arm-eabi": "2.6.2", - "@resvg/resvg-js-android-arm64": "2.6.2", - "@resvg/resvg-js-darwin-arm64": "2.6.2", - "@resvg/resvg-js-darwin-x64": "2.6.2", - "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", - "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", - "@resvg/resvg-js-linux-arm64-musl": "2.6.2", - "@resvg/resvg-js-linux-x64-gnu": "2.6.2", - "@resvg/resvg-js-linux-x64-musl": "2.6.2", - "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", - "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", - "@resvg/resvg-js-win32-x64-msvc": "2.6.2" - } - }, - "node_modules/@resvg/resvg-js-android-arm-eabi": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz", - "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-android-arm64": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz", - "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-darwin-arm64": { - "version": "2.6.2", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-darwin-x64": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz", - "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz", - "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-linux-arm64-gnu": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz", - "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-linux-arm64-musl": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz", - "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-linux-x64-gnu": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz", - "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-linux-x64-musl": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz", - "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-win32-arm64-msvc": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz", - "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-win32-ia32-msvc": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz", - "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==", - "cpu": [ - "ia32" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@resvg/resvg-js-win32-x64-msvc": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz", - "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "license": "MIT" - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@shikijs/core": { - "version": "3.22.0", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.5" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "3.22.0", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.3.4" - } - }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "3.22.0", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "3.22.0", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "3.22.0", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0" - } - }, - "node_modules/@shikijs/types": { - "version": "3.22.0", - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "license": "MIT" - }, - "node_modules/@shuding/opentype.js": { - "version": "1.4.0-beta.0", - "license": "MIT", - "dependencies": { - "fflate": "^0.7.3", - "string.prototype.codepointat": "^0.2.1" - }, - "bin": { - "ot": "bin/ot" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.18", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.18" - } - }, - "node_modules/@tailwindcss/node/node_modules/jiti": { - "version": "2.6.1", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/@tailwindcss/node/node_modules/tailwindcss": { - "version": "4.1.18", - "license": "MIT" - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.18", - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-x64": "4.1.18", - "@tailwindcss/oxide-freebsd-x64": "4.1.18", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-x64-musl": "4.1.18", - "@tailwindcss/oxide-wasm32-wasi": "4.1.18", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", - "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.18", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", - "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", - "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", - "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.0", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", - "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.18", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "tailwindcss": "4.1.18" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@tailwindcss/vite/node_modules/tailwindcss": { - "version": "4.1.18", - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/@types/nlcst": { - "version": "2.0.3", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/node": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", - "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.11", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.54.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "license": "ISC" - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/string-width/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.9", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-iterate": { - "version": "2.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/astro": { - "version": "5.17.1", - "license": "MIT", - "dependencies": { - "@astrojs/compiler": "^2.13.0", - "@astrojs/internal-helpers": "0.7.5", - "@astrojs/markdown-remark": "6.3.10", - "@astrojs/telemetry": "3.3.0", - "@capsizecss/unpack": "^4.0.0", - "@oslojs/encoding": "^1.1.0", - "@rollup/pluginutils": "^5.3.0", - "acorn": "^8.15.0", - "aria-query": "^5.3.2", - "axobject-query": "^4.1.0", - "boxen": "8.0.1", - "ci-info": "^4.3.1", - "clsx": "^2.1.1", - "common-ancestor-path": "^1.0.1", - "cookie": "^1.1.1", - "cssesc": "^3.0.0", - "debug": "^4.4.3", - "deterministic-object-hash": "^2.0.2", - "devalue": "^5.6.2", - "diff": "^8.0.3", - "dlv": "^1.1.3", - "dset": "^3.1.4", - "es-module-lexer": "^1.7.0", - "esbuild": "^0.25.0", - "estree-walker": "^3.0.3", - "flattie": "^1.1.1", - "fontace": "~0.4.0", - "github-slugger": "^2.0.0", - "html-escaper": "3.0.3", - "http-cache-semantics": "^4.2.0", - "import-meta-resolve": "^4.2.0", - "js-yaml": "^4.1.1", - "magic-string": "^0.30.21", - "magicast": "^0.5.1", - "mrmime": "^2.0.1", - "neotraverse": "^0.6.18", - "p-limit": "^6.2.0", - "p-queue": "^8.1.1", - "package-manager-detector": "^1.6.0", - "piccolore": "^0.1.3", - "picomatch": "^4.0.3", - "prompts": "^2.4.2", - "rehype": "^13.0.2", - "semver": "^7.7.3", - "shiki": "^3.21.0", - "smol-toml": "^1.6.0", - "svgo": "^4.0.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tsconfck": "^3.1.6", - "ultrahtml": "^1.6.0", - "unifont": "~0.7.3", - "unist-util-visit": "^5.0.0", - "unstorage": "^1.17.4", - "vfile": "^6.0.3", - "vite": "^6.4.1", - "vitefu": "^1.1.1", - "xxhash-wasm": "^1.1.0", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.3", - "zod": "^3.25.76", - "zod-to-json-schema": "^3.25.1", - "zod-to-ts": "^1.2.0" - }, - "bin": { - "astro": "astro.js" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/astrodotbuild" - }, - "optionalDependencies": { - "sharp": "^0.34.0" - } - }, - "node_modules/astro/node_modules/semver": { - "version": "7.7.3", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.24", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001766", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/base-64": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "0.0.8", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "license": "ISC" - }, - "node_modules/boxen": { - "version": "8.0.1", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "8.0.0", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/camelize": { - "version": "1.0.1", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001768", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ci-info": { - "version": "4.4.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "11.1.0", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/common-ancestor-path": { - "version": "1.0.1", - "license": "ISC" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.1.1", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cookie-es": { - "version": "1.2.2", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crossws": { - "version": "0.3.5", - "license": "MIT", - "dependencies": { - "uncrypto": "^0.1.3" - } - }, - "node_modules/css-background-parser": { - "version": "0.1.0", - "license": "MIT" - }, - "node_modules/css-box-shadow": { - "version": "1.0.0-3", - "license": "MIT" - }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-gradient-parser": { - "version": "0.0.17", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/css-select": { - "version": "5.2.2", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "license": "MIT", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/css-tree": { - "version": "3.1.0", - "license": "MIT", - "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree/node_modules/mdn-data": { - "version": "2.0.28", - "license": "CC0-1.0" - }, - "node_modules/csstype": { - "version": "3.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/date-fns": { - "version": "4.1.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/defu": { - "version": "6.1.4", - "license": "MIT" - }, - "node_modules/depd": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/destr": { - "version": "2.0.5", - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/deterministic-object-hash": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "base-64": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/devalue": { - "version": "5.6.2", - "license": "MIT" - }, - "node_modules/devlop": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "license": "Apache-2.0" - }, - "node_modules/diff": { - "version": "8.0.3", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "license": "MIT" - }, - "node_modules/doctrine": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "4.5.0", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dset": { - "version": "3.1.4", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "license": "ISC" - }, - "node_modules/emoji-picker-react": { - "version": "4.18.0", - "license": "MIT", - "dependencies": { - "flairup": "1.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": ">=16" - } - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "license": "MIT" - }, - "node_modules/emoji-regex-xs": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.19.0", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "6.0.1", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-abstract": { - "version": "1.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.1", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.1.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.3.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.5", - "safe-array-concat": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "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" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "10.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.0", - "@eslint/config-helpers": "^0.5.2", - "@eslint/core": "^1.1.0", - "@eslint/plugin-kit": "^0.6.0", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.0", - "eslint-visitor-keys": "^5.0.0", - "espree": "^11.1.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.1.1", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.5", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.1", - "synckit": "^0.11.12" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.5.0", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=9" - } - }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-scope": { - "version": "9.1.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/espree": { - "version": "11.1.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "license": "MIT" - }, - "node_modules/extend": { - "version": "3.0.2", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.20.1", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fflate": { - "version": "0.7.4", - "license": "MIT" - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flairup": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "dev": true, - "license": "ISC" - }, - "node_modules/flattie": { - "version": "1.1.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/fontace": { - "version": "0.4.1", - "license": "MIT", - "dependencies": { - "fontkitten": "^1.0.2" - } - }, - "node_modules/fontkitten": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "tiny-inflate": "^1.0.3" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/github-slugger": { - "version": "2.0.0", - "license": "ISC" - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "17.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "license": "ISC" - }, - "node_modules/h3": { - "version": "1.15.5", - "license": "MIT", - "dependencies": { - "cookie-es": "^1.2.2", - "crossws": "^0.3.5", - "defu": "^6.1.4", - "destr": "^2.0.5", - "iron-webcrypto": "^1.2.1", - "node-mock-http": "^1.0.4", - "radix3": "^1.1.2", - "ufo": "^1.6.3", - "uncrypto": "^0.1.3" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hast-util-from-html": { - "version": "2.0.3", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.1.0", - "hast-util-from-parse5": "^8.0.0", - "parse5": "^7.0.0", - "vfile": "^6.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-is-element": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.1", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-text": { - "version": "4.0.2", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unist-util-find-after": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "9.0.1", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hermes-estree": { - "version": "0.25.1", - "dev": true, - "license": "MIT" - }, - "node_modules/hermes-parser": { - "version": "0.25.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, - "node_modules/hex-rgb": { - "version": "4.3.0", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/html-escaper": { - "version": "3.0.3", - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "license": "BSD-2-Clause" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore": { - "version": "7.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/iron-webcrypto": { - "version": "1.2.1", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/brc-dd" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.30.2", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/linebreak": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "base64-js": "0.0.8", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "11.2.5", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/lucide-react": { - "version": "0.563.0", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.5.2", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdast-util-definitions": { - "version": "6.0.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdn-data": { - "version": "2.12.2", - "license": "CC0-1.0" - }, - "node_modules/merge2": { - "version": "1.4.1", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/minimatch": { - "version": "10.1.2", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanostores": { - "version": "1.1.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": "^20.0.0 || >=22.0.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/neotraverse": { - "version": "0.6.18", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/nlcst-to-string": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "license": "MIT" - }, - "node_modules/node-mock-http": { - "version": "1.0.4", - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.27", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.9", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ofetch": { - "version": "1.5.1", - "license": "MIT", - "dependencies": { - "destr": "^2.0.5", - "node-fetch-native": "^1.6.7", - "ufo": "^1.6.1" - } - }, - "node_modules/ohash": { - "version": "2.0.11", - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/oniguruma-parser": { - "version": "0.12.1", - "license": "MIT" - }, - "node_modules/oniguruma-to-es": { - "version": "4.3.4", - "license": "MIT", - "dependencies": { - "oniguruma-parser": "^0.12.1", - "regex": "^6.0.1", - "regex-recursion": "^6.0.2" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate/node_modules/p-limit/node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "8.1.1", - "license": "MIT", - "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "6.1.4", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-manager-detector": { - "version": "1.6.0", - "license": "MIT" - }, - "node_modules/pako": { - "version": "0.2.9", - "license": "MIT" - }, - "node_modules/parse-css-color": { - "version": "0.2.1", - "license": "MIT", - "dependencies": { - "color-name": "^1.1.4", - "hex-rgb": "^4.1.0" - } - }, - "node_modules/parse-latin": { - "version": "7.0.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "@types/unist": "^3.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-modify-children": "^4.0.0", - "unist-util-visit-children": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "license": "MIT" - }, - "node_modules/piccolore": { - "version": "0.1.3", - "license": "ISC" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-import/node_modules/resolve": { - "version": "1.22.11", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/postcss-js": { - "version": "4.1.0", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.1", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/prismjs": { - "version": "1.30.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/property-information": { - "version": "7.1.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/radix3": { - "version": "1.1.2", - "license": "MIT" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/react": { - "version": "19.2.4", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.4", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, - "node_modules/react-icons": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "dev": true, - "license": "MIT" - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "7.13.0", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-router-dom": { - "version": "7.13.0", - "license": "MIT", - "dependencies": { - "react-router": "7.13.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regex": { - "version": "6.1.0", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "license": "MIT" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rehype": { - "version": "13.0.2", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "rehype-parse": "^9.0.0", - "rehype-stringify": "^10.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-parse": { - "version": "9.0.1", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-html": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-raw": { - "version": "7.0.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-stringify": { - "version": "10.0.1", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-to-html": "^9.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype": { - "version": "11.1.2", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-smartypants": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "retext": "^9.0.0", - "retext-smartypants": "^6.0.0", - "unified": "^11.0.4", - "unist-util-visit": "^5.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/resolve": { - "version": "2.0.0-next.5", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retext": { - "version": "9.0.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "retext-latin": "^4.0.0", - "retext-stringify": "^4.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext-latin": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "parse-latin": "^7.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext-smartypants": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext-stringify": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "nlcst-to-string": "^4.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.57.1", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/satori": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/satori/-/satori-0.19.2.tgz", - "integrity": "sha512-71plFHWcq6WJBM5sf/n0eHOmTBiKLUB/G8du7SmLTTLHKEKrV3TPHGKcEVIoyjnbhnjvu9HhLyF9MATB/zzL7g==", - "license": "MPL-2.0", - "dependencies": { - "@shuding/opentype.js": "1.4.0-beta.0", - "css-background-parser": "^0.1.0", - "css-box-shadow": "1.0.0-3", - "css-gradient-parser": "^0.0.17", - "css-to-react-native": "^3.0.0", - "emoji-regex-xs": "^2.0.1", - "escape-html": "^1.0.3", - "linebreak": "^1.1.0", - "parse-css-color": "^0.2.1", - "postcss-value-parser": "^4.2.0", - "yoga-layout": "^3.2.1" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/sax": { - "version": "1.4.4", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "1.2.1", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/server-destroy": { - "version": "1.0.1", - "license": "ISC" - }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "license": "MIT" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "license": "ISC" - }, - "node_modules/sharp": { - "version": "0.34.5", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.2", - "semver": "^7.7.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" - } - }, - "node_modules/sharp/node_modules/semver": { - "version": "7.7.3", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shiki": { - "version": "3.22.0", - "license": "MIT", - "dependencies": { - "@shikijs/core": "3.22.0", - "@shikijs/engine-javascript": "3.22.0", - "@shikijs/engine-oniguruma": "3.22.0", - "@shikijs/langs": "3.22.0", - "@shikijs/themes": "3.22.0", - "@shikijs/types": "3.22.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "license": "MIT" - }, - "node_modules/smol-toml": { - "version": "1.6.0", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 18" - }, - "funding": { - "url": "https://github.com/sponsors/cyyynthia" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string-width": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string.prototype.codepointat": { - "version": "0.2.1", - "license": "MIT" - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/sucrase": { - "version": "3.35.1", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svgo": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "commander": "^11.1.0", - "css-select": "^5.1.0", - "css-tree": "^3.0.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.1.1", - "sax": "^1.4.1" - }, - "bin": { - "svgo": "bin/svgo.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/synckit": { - "version": "0.11.12", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/tailwind-merge": { - "version": "3.4.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.19", - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss/node_modules/chokidar": { - "version": "3.6.0", - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tailwindcss/node_modules/jiti": { - "version": "1.21.7", - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/tailwindcss/node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tailwindcss/node_modules/readdirp": { - "version": "3.6.0", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/tailwindcss/node_modules/resolve": { - "version": "1.22.11", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.3.0", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ts-api-utils": { - "version": "2.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "license": "Apache-2.0" - }, - "node_modules/tsconfck": { - "version": "3.1.6", - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "license": "0BSD", - "optional": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "4.41.0", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.54.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "license": "MIT" - }, - "node_modules/ultrahtml": { - "version": "1.6.0", - "license": "MIT" - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/uncrypto": { - "version": "0.1.3", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicode-trie": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "node_modules/unified": { - "version": "11.0.5", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unifont": { - "version": "0.7.3", - "license": "MIT", - "dependencies": { - "css-tree": "^3.1.0", - "ofetch": "^1.5.1", - "ohash": "^2.0.11" - } - }, - "node_modules/unist-util-find-after": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-modify-children": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "array-iterate": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-children": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unstorage": { - "version": "1.17.4", - "license": "MIT", - "dependencies": { - "anymatch": "^3.1.3", - "chokidar": "^5.0.0", - "destr": "^2.0.5", - "h3": "^1.15.5", - "lru-cache": "^11.2.0", - "node-fetch-native": "^1.6.7", - "ofetch": "^1.5.1", - "ufo": "^1.6.3" - }, - "peerDependencies": { - "@azure/app-configuration": "^1.8.0", - "@azure/cosmos": "^4.2.0", - "@azure/data-tables": "^13.3.0", - "@azure/identity": "^4.6.0", - "@azure/keyvault-secrets": "^4.9.0", - "@azure/storage-blob": "^12.26.0", - "@capacitor/preferences": "^6 || ^7 || ^8", - "@deno/kv": ">=0.9.0", - "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", - "@planetscale/database": "^1.19.0", - "@upstash/redis": "^1.34.3", - "@vercel/blob": ">=0.27.1", - "@vercel/functions": "^2.2.12 || ^3.0.0", - "@vercel/kv": "^1 || ^2 || ^3", - "aws4fetch": "^1.0.20", - "db0": ">=0.2.1", - "idb-keyval": "^6.2.1", - "ioredis": "^5.4.2", - "uploadthing": "^7.4.4" - }, - "peerDependenciesMeta": { - "@azure/app-configuration": { - "optional": true - }, - "@azure/cosmos": { - "optional": true - }, - "@azure/data-tables": { - "optional": true - }, - "@azure/identity": { - "optional": true - }, - "@azure/keyvault-secrets": { - "optional": true - }, - "@azure/storage-blob": { - "optional": true - }, - "@capacitor/preferences": { - "optional": true - }, - "@deno/kv": { - "optional": true - }, - "@netlify/blobs": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/blob": { - "optional": true - }, - "@vercel/functions": { - "optional": true - }, - "@vercel/kv": { - "optional": true - }, - "aws4fetch": { - "optional": true - }, - "db0": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "uploadthing": { - "optional": true - } - } - }, - "node_modules/unstorage/node_modules/chokidar": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/unstorage/node_modules/chokidar/node_modules/readdirp": { - "version": "5.0.0", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/vfile": { - "version": "6.0.3", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "6.4.1", - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitefu": { - "version": "1.1.1", - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-pm-runs": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/widest-line": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/xxhash-wasm": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/yallist": { - "version": "3.1.1", - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.2", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "1.2.2", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yocto-spinner": { - "version": "0.2.3", - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoga-layout": { - "version": "3.2.1", - "license": "MIT" - }, - "node_modules/zod": { - "version": "3.25.76", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25 || ^4" - } - }, - "node_modules/zod-to-ts": { - "version": "1.2.0", - "peerDependencies": { - "typescript": "^4.9.4 || ^5.0.2", - "zod": "^3" - } - }, - "node_modules/zod-validation-error": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/web/package.json b/web/package.json --- a/web/package.json +++ b/web/package.json @@ -10,13 +10,13 @@ "lint": "eslint 'src/**/*.{ts,tsx,js,jsx}' --fix" }, "dependencies": { - "@astrojs/node": "^9.5.2", - "@astrojs/react": "^4.4.2", + "@astrojs/node": "^10.0.3", + "@astrojs/react": "^5.0.1", "@astrojs/tailwind": "^6.0.2", "@nanostores/react": "^1.0.0", "@resvg/resvg-js": "^2.6.2", "@tailwindcss/vite": "^4.1.18", - "astro": "^5.17.1", + "astro": "^6.0.8", "autoprefixer": "^10.4.24", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -26,7 +26,6 @@ "postcss": "^8.5.6", "react": "^19.2.4", "react-dom": "^19.2.4", - "react-router-dom": "^7.13.0", "satori": "^0.19.2", "tailwind-merge": "^3.4.0", "tailwindcss": "^3.4.19" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Deploy - -on: - workflow_run: - workflows: ["Build and Publish Docker Image"] - types: - - completed - branches: - - main - -jobs: - deploy: - runs-on: blacksmith-2vcpu-ubuntu-2404 - if: ${{ github.event.workflow_run.conclusion == 'success' }} - steps: - - name: Deploy to Dokku - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.DOKKU_HOST }} - username: deploy - key: ${{ secrets.DEPLOY_KEY }} - script: echo deploy \ No newline at end of file diff --git a/web/src/App.tsx b/web/src/App.tsx deleted file mode 100644 --- a/web/src/App.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import React from "react"; -import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; -import { initAuth, $user } from "./store/auth"; -import { loadPreferences } from "./store/preferences"; -import { useStore } from "@nanostores/react"; - -import AppLayout from "./layouts/AppLayout"; -import Feed from "./views/core/Feed"; -import Login from "./views/auth/Login"; -import Notifications from "./views/core/Notifications"; -import Collections from "./views/collections/Collections"; -import Settings from "./views/core/Settings"; -import NewAnnotationPage from "./views/core/New"; -import MasonryFeed from "./components/feed/MasonryFeed"; -import { - ProfileWrapper, - SelfProfileWrapper, - CollectionDetailWrapper, - AnnotationDetailWrapper, - UserUrlWrapper, - UrlWrapper, -} from "./routes/wrappers"; -import About from "./views/About"; -import AdminModeration from "./views/core/AdminModeration"; -import Search from "./views/core/Search"; -import Discover from "./views/core/Discover"; - -function RootRoute() { - const user = useStore($user); - - if (user) { - return ; - } - - return ; -} - -export default function App() { - React.useEffect(() => { - initAuth(); - loadPreferences(); - }, []); - - return ( - - - } /> - } /> - } /> - Redirecting...} /> - - - - - } - /> - } /> - - - - - } - /> - - - - - } - /> - - - - - } - /> - - - - } - /> - - - - } - /> - - - - - } - /> - - - - } - /> - - - - } - /> - - - - - } - /> - - - - } - /> - - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - - } - /> - - - - - } - /> - - - - } - /> - - } /> - - - ); -} diff --git a/web/src/env.d.ts b/web/src/env.d.ts new file mode 100644 --- /dev/null +++ b/web/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare namespace App { + interface Locals { + user: import("./types").UserProfile | null; + } +} diff --git a/web/src/middleware.ts b/web/src/middleware.ts --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -1,6 +1,7 @@ import type { APIContext } from "astro"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; +import { getSession } from "./lib/api"; const API_PORT = process.env.API_PORT || 8081; const API_URL = process.env.API_URL || `http://localhost:${API_PORT}`; @@ -26,9 +27,11 @@ } export async function onRequest( - { request, url }: APIContext, + context: APIContext, next: () => Promise, ): Promise { + const { request, url, locals } = context; + if (url.pathname === "/favicon.ico") { try { const file = await readFile( @@ -49,10 +52,21 @@ (p) => url.pathname.startsWith(p) || url.pathname === p.replace(/\/$/, ""), ); - if (!shouldProxy) { - return next(); + if (shouldProxy) { + return proxyToBackend(request, url); } + const cookie = request.headers.get("cookie") || ""; + if (cookie.includes("margin_session")) { + locals.user = await getSession(cookie); + } else { + locals.user = null; + } + + return next(); +} + +async function proxyToBackend(request: Request, url: URL): Promise { const origin = request.headers.get("origin"); if (request.method === "OPTIONS" && isExtensionOrigin(origin)) { diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -5,6 +5,7 @@ "net/http" "os" "os/signal" + "strings" "syscall" "time" @@ -27,7 +28,11 @@ func main() { godotenv.Load("../.env", ".env") - database, err := db.New(getEnv("DATABASE_URL", "margin.db")) + dsn := os.Getenv("DATABASE_URL") + if dsn == "" { + logger.Fatal("DATABASE_URL environment variable is required") + } + database, err := db.New(dsn) if err != nil { logger.Fatal("Failed to connect to database: %v", err) } @@ -70,29 +75,51 @@ firehose.RelayURL = getEnv("BLOCK_RELAY_URL", "wss://jetstream2.us-east.bsky.network/subscribe") logger.Info("Firehose URL: %s", firehose.RelayURL) + backfillCtx, backfillCancel := context.WithCancel(context.Background()) + defer backfillCancel() + if recService.IsEnabled() { ingester.SetOnAnnotation(recService.OnAnnotation) ingester.SetOnDocument(recService.OnDocument) - go func() { - logger.Info("Starting recommendation backfill...") - if err := recService.BackfillDocumentEmbeddings(200); err != nil { - logger.Error("Document embedding backfill error: %v", err) - } - annCount, err := recService.BackfillAnnotationEmbeddings(200) - if err != nil { - logger.Error("Annotation embedding backfill error: %v", err) - } - hlCount, err := recService.BackfillHighlightEmbeddings(200) - if err != nil { - logger.Error("Highlight embedding backfill error: %v", err) - } - profileCount, err := recService.RebuildAllProfiles() - if err != nil { - logger.Error("Profile rebuild error: %v", err) - } - logger.Info("Recommendation backfill complete (annotations: %d, highlights: %d, profiles: %d)", annCount, hlCount, profileCount) - }() + if getEnv("DISABLE_BACKFILL", "") == "" { + go func() { + time.Sleep(5 * time.Second) + select { + case <-backfillCtx.Done(): + return + default: + } + logger.Info("Starting recommendation backfill...") + if err := recService.BackfillDocumentEmbeddings(200); err != nil { + logger.Error("Document embedding backfill error: %v", err) + } + if backfillCtx.Err() != nil { + return + } + annCount, err := recService.BackfillAnnotationEmbeddings(200) + if err != nil { + logger.Error("Annotation embedding backfill error: %v", err) + } + if backfillCtx.Err() != nil { + return + } + hlCount, err := recService.BackfillHighlightEmbeddings(200) + if err != nil { + logger.Error("Highlight embedding backfill error: %v", err) + } + if backfillCtx.Err() != nil { + return + } + profileCount, err := recService.RebuildAllProfiles() + if err != nil { + logger.Error("Profile rebuild error: %v", err) + } + logger.Info("Recommendation backfill complete (annotations: %d, highlights: %d, profiles: %d)", annCount, hlCount, profileCount) + }() + } else { + logger.Info("Recommendation backfill disabled (DISABLE_BACKFILL is set)") + } } go func() { @@ -111,7 +138,17 @@ r.Use(middleware.Throttle(100)) r.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{"https://*", "http://*", "chrome-extension://*"}, + AllowOriginFunc: func(r *http.Request, origin string) bool { + if strings.HasPrefix(origin, "chrome-extension://") || + strings.HasPrefix(origin, "moz-extension://") || + strings.HasPrefix(origin, "safari-web-extension://") { + return true + } + if baseURL := os.Getenv("BASE_URL"); baseURL != "" { + return origin == baseURL + } + return false + }, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "X-Session-Token"}, ExposedHeaders: []string{"Link"}, @@ -174,6 +211,7 @@ <-quit logger.Infoln("Shutting down server...") + backfillCancel() ingester.Stop() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) diff --git a/backend/internal/api/apikey.go b/backend/internal/api/apikey.go --- a/backend/internal/api/apikey.go +++ b/backend/internal/api/apikey.go @@ -482,13 +482,13 @@ } func (h *APIKeyHandler) getSessionByDID(did string) (*SessionData, error) { - rows, err := h.db.Query(h.db.Rebind(` + rows, err := h.db.Query(` SELECT id, did, handle, access_token, refresh_token, COALESCE(dpop_key, '') FROM sessions - WHERE did = ? AND expires_at > ? + WHERE did = $1 AND expires_at > $2 ORDER BY created_at DESC LIMIT 1 - `), did, time.Now()) + `, did, time.Now()) if err != nil { return nil, err } diff --git a/backend/internal/api/handler.go b/backend/internal/api/handler.go --- a/backend/internal/api/handler.go +++ b/backend/internal/api/handler.go @@ -10,6 +10,7 @@ "sort" "strconv" "strings" + "sync" "time" "github.com/go-chi/chi/v5" @@ -22,6 +23,59 @@ "margin.at/internal/xrpc" ) +type urlMetaCacheEntry struct { + data map[string]string + expiresAt time.Time +} + +type urlMetaCache struct { + mu sync.RWMutex + entries map[string]urlMetaCacheEntry + inflight sync.Map +} + +type singleflight struct { + wg sync.WaitGroup + data map[string]string + err error +} + +func newURLMetaCache() *urlMetaCache { + c := &urlMetaCache{entries: make(map[string]urlMetaCacheEntry)} + go c.evictLoop() + return c +} + +func (c *urlMetaCache) get(key string) (map[string]string, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + e, ok := c.entries[key] + if !ok || time.Now().After(e.expiresAt) { + return nil, false + } + return e.data, true +} + +func (c *urlMetaCache) set(key string, data map[string]string, ttl time.Duration) { + c.mu.Lock() + defer c.mu.Unlock() + c.entries[key] = urlMetaCacheEntry{data: data, expiresAt: time.Now().Add(ttl)} +} + +func (c *urlMetaCache) evictLoop() { + ticker := time.NewTicker(5 * time.Minute) + for range ticker.C { + c.mu.Lock() + now := time.Now() + for k, e := range c.entries { + if now.After(e.expiresAt) { + delete(c.entries, k) + } + } + c.mu.Unlock() + } +} + type Handler struct { db *db.DB annotationService *AnnotationService @@ -30,6 +84,8 @@ syncService *internal_sync.Service moderation *ModerationHandler recommendations *recommendations.Service + metaCache *urlMetaCache + metaSem chan struct{} } func NewHandler(database *db.DB, annotationService *AnnotationService, refresher *TokenRefresher, syncService *internal_sync.Service, recService *recommendations.Service) *Handler { @@ -41,6 +97,8 @@ syncService: syncService, moderation: NewModerationHandler(database, refresher), recommendations: recService, + metaCache: newURLMetaCache(), + metaSem: make(chan struct{}, 5), } } @@ -346,9 +404,47 @@ } } - authAnnos, _ := hydrateAnnotations(h.db, annotations, viewerDID) - authHighs, _ := hydrateHighlights(h.db, highlights, viewerDID) - authBooks, _ := hydrateBookmarks(h.db, bookmarks, viewerDID) + allDIDs := make(map[string]bool) + for _, a := range annotations { + allDIDs[a.AuthorDID] = true + } + for _, h := range highlights { + allDIDs[h.AuthorDID] = true + } + for _, b := range bookmarks { + allDIDs[b.AuthorDID] = true + } + for _, ci := range collectionItems { + allDIDs[ci.AuthorDID] = true + } + didSlice := make([]string, 0, len(allDIDs)) + for did := range allDIDs { + didSlice = append(didSlice, did) + } + profiles := fetchProfilesForDIDs(h.db, didSlice) + shared := &hydrationData{profiles: profiles} + + var ( + authAnnos []APIAnnotation + authHighs []APIHighlight + authBooks []APIBookmark + authCollectionItems []APICollectionItem + wg sync.WaitGroup + ) + + wg.Add(3) + go func() { + defer wg.Done() + authAnnos, _ = hydrateAnnotationsWithData(h.db, annotations, viewerDID, shared) + }() + go func() { + defer wg.Done() + authHighs, _ = hydrateHighlightsWithData(h.db, highlights, viewerDID, shared) + }() + go func() { + defer wg.Done() + authBooks, _ = hydrateBookmarksWithData(h.db, bookmarks, viewerDID, shared) + }() if len(collectionItems) > 0 { var sembleURIs []string @@ -362,9 +458,14 @@ defer cancel() ensureSembleCardsIndexed(ctx, h.db, sembleURIs) } + wg.Add(1) + go func() { + defer wg.Done() + authCollectionItems, _ = hydrateCollectionItemsWithData(h.db, collectionItems, viewerDID, shared) + }() } - authCollectionItems, _ := hydrateCollectionItems(h.db, collectionItems, viewerDID) + wg.Wait() collectionItemURIs := make(map[string]string) for _, ci := range authCollectionItems { @@ -1194,20 +1295,90 @@ return } - client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Get(targetURL) - if err != nil { + if cached, ok := h.metaCache.get(targetURL); ok { w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{"title": "", "error": "failed to fetch"}) + w.Header().Set("X-Cache", "HIT") + json.NewEncoder(w).Encode(cached) return + } + + sfVal, loaded := h.metaCache.inflight.LoadOrStore(targetURL, &singleflight{}) + sf := sfVal.(*singleflight) + if loaded { + sf.wg.Wait() + w.Header().Set("Content-Type", "application/json") + w.Header().Set("X-Cache", "DEDUP") + if sf.data != nil { + json.NewEncoder(w).Encode(sf.data) + } else { + json.NewEncoder(w).Encode(map[string]string{"title": "", "error": "failed to fetch"}) + } + return + } + + sf.wg.Add(1) + defer func() { + sf.wg.Done() + go func() { + time.Sleep(100 * time.Millisecond) + h.metaCache.inflight.Delete(targetURL) + }() + }() + + select { + case h.metaSem <- struct{}{}: + defer func() { <-h.metaSem }() + case <-r.Context().Done(): + sf.data = map[string]string{"title": "", "error": "timeout"} + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(sf.data) + return + } + + data := h.fetchURLMetadata(r.Context(), targetURL) + sf.data = data + + ttl := 1 * time.Hour + if data["title"] == "" && data["error"] != "" { + ttl = 2 * time.Minute + } + h.metaCache.set(targetURL, data, ttl) + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "public, max-age=3600") + json.NewEncoder(w).Encode(data) +} + +func (h *Handler) fetchURLMetadata(ctx context.Context, targetURL string) map[string]string { + ctx, cancel := context.WithTimeout(ctx, 4*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "GET", targetURL, nil) + if err != nil { + return map[string]string{"title": "", "error": "invalid url"} + } + req.Header.Set("User-Agent", "Margin/1.0 (metadata fetcher)") + req.Header.Set("Accept", "text/html") + + client := &http.Client{ + Timeout: 4 * time.Second, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + if len(via) >= 3 { + return fmt.Errorf("too many redirects") + } + return nil + }, + } + + resp, err := client.Do(req) + if err != nil { + return map[string]string{"title": "", "error": "failed to fetch"} } defer resp.Body.Close() - body, err := io.ReadAll(io.LimitReader(resp.Body, 500*1024)) + body, err := io.ReadAll(io.LimitReader(resp.Body, 256*1024)) if err != nil { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{"title": ""}) - return + return map[string]string{"title": ""} } content := string(body) @@ -1310,15 +1481,12 @@ } } - data := map[string]string{ + return map[string]string{ "title": title, "description": description, "image": image, "icon": favicon, } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(data) } func (h *Handler) GetNotifications(w http.ResponseWriter, r *http.Request) { diff --git a/backend/internal/api/hydration.go b/backend/internal/api/hydration.go --- a/backend/internal/api/hydration.go +++ b/backend/internal/api/hydration.go @@ -19,6 +19,8 @@ var ( Cache ProfileCache = NewInMemoryCache(5 * time.Minute) ConstellationClient *constellation.Client = constellation.NewClient() // Enabled by default + + bskyHTTPClient = &http.Client{Timeout: 5 * time.Second} ) func init() { @@ -168,6 +170,11 @@ ReadAt *time.Time `json:"readAt,omitempty"` } +type hydrationData struct { + profiles map[string]Author + subscribedLabelers []string +} + func fetchCounts(ctx context.Context, database *db.DB, uris []string, viewerDID string) (likeCounts, replyCounts map[string]int, viewerLikes map[string]bool) { likeCounts = make(map[string]int) replyCounts = make(map[string]int) @@ -177,12 +184,39 @@ return } + var wg sync.WaitGroup + var mu sync.Mutex + if database != nil { - likeCounts, _ = database.GetLikeCounts(uris) - replyCounts, _ = database.GetReplyCounts(uris) + wg.Add(2) + go func() { + defer wg.Done() + if lc, err := database.GetLikeCounts(uris); err == nil { + mu.Lock() + likeCounts = lc + mu.Unlock() + } + }() + go func() { + defer wg.Done() + if rc, err := database.GetReplyCounts(uris); err == nil { + mu.Lock() + replyCounts = rc + mu.Unlock() + } + }() if viewerDID != "" { - viewerLikes, _ = database.GetViewerLikes(viewerDID, uris) + wg.Add(1) + go func() { + defer wg.Done() + if vl, err := database.GetViewerLikes(viewerDID, uris); err == nil { + mu.Lock() + viewerLikes = vl + mu.Unlock() + } + }() } + wg.Wait() } if ConstellationClient != nil && len(uris) <= 5 { @@ -205,28 +239,99 @@ return } +func fetchEngagementData(database *db.DB, uris []string, authorDIDs []string, viewerDID string) ( + likeCounts, replyCounts map[string]int, + viewerLikes map[string]bool, + uriLabels, didLabels map[string][]db.ContentLabel, + editTimes map[string]time.Time, +) { + likeCounts = make(map[string]int) + replyCounts = make(map[string]int) + viewerLikes = make(map[string]bool) + uriLabels = make(map[string][]db.ContentLabel) + didLabels = make(map[string][]db.ContentLabel) + editTimes = make(map[string]time.Time) + + if len(uris) == 0 { + return + } + + subscribedLabelers := getSubscribedLabelers(database, viewerDID) + labelerDIDs := appendUnique(subscribedLabelers, authorDIDs) + + var wg sync.WaitGroup + var mu sync.Mutex + + wg.Add(1) + go func() { + defer wg.Done() + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + lc, rc, vl := fetchCounts(ctx, database, uris, viewerDID) + mu.Lock() + likeCounts = lc + replyCounts = rc + viewerLikes = vl + mu.Unlock() + }() + + wg.Add(1) + go func() { + defer wg.Done() + if ul, err := database.GetContentLabelsForURIs(uris, labelerDIDs); err == nil { + mu.Lock() + uriLabels = ul + mu.Unlock() + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + if dl, err := database.GetContentLabelsForDIDs(authorDIDs, labelerDIDs); err == nil { + mu.Lock() + didLabels = dl + mu.Unlock() + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + if et, err := database.GetLatestEditTimes(uris); err == nil { + mu.Lock() + editTimes = et + mu.Unlock() + } + }() + + wg.Wait() + return +} + func hydrateAnnotations(database *db.DB, annotations []db.Annotation, viewerDID string) ([]APIAnnotation, error) { + return hydrateAnnotationsWithData(database, annotations, viewerDID, nil) +} + +func hydrateAnnotationsWithData(database *db.DB, annotations []db.Annotation, viewerDID string, shared *hydrationData) ([]APIAnnotation, error) { if len(annotations) == 0 { return []APIAnnotation{}, nil } - profiles := fetchProfilesForDIDs(database, collectDIDs(annotations, func(a db.Annotation) string { return a.AuthorDID })) + var profiles map[string]Author + if shared != nil && shared.profiles != nil { + profiles = shared.profiles + } else { + profiles = fetchProfilesForDIDs(database, collectDIDs(annotations, func(a db.Annotation) string { return a.AuthorDID })) + } uris := make([]string, len(annotations)) for i, a := range annotations { uris[i] = a.URI } - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - likeCounts, replyCounts, viewerLikes := fetchCounts(ctx, database, uris, viewerDID) - - subscribedLabelers := getSubscribedLabelers(database, viewerDID) authorDIDs := collectDIDs(annotations, func(a db.Annotation) string { return a.AuthorDID }) - labelerDIDs := appendUnique(subscribedLabelers, authorDIDs) - uriLabels, _ := database.GetContentLabelsForURIs(uris, labelerDIDs) - didLabels, _ := database.GetContentLabelsForDIDs(authorDIDs, labelerDIDs) - editTimes, _ := database.GetLatestEditTimes(uris) + + likeCounts, replyCounts, viewerLikes, uriLabels, didLabels, editTimes := fetchEngagementData(database, uris, authorDIDs, viewerDID) result := make([]APIAnnotation, len(annotations)) for i, a := range annotations { @@ -303,27 +408,28 @@ } func hydrateHighlights(database *db.DB, highlights []db.Highlight, viewerDID string) ([]APIHighlight, error) { + return hydrateHighlightsWithData(database, highlights, viewerDID, nil) +} + +func hydrateHighlightsWithData(database *db.DB, highlights []db.Highlight, viewerDID string, shared *hydrationData) ([]APIHighlight, error) { if len(highlights) == 0 { return []APIHighlight{}, nil } - profiles := fetchProfilesForDIDs(database, collectDIDs(highlights, func(h db.Highlight) string { return h.AuthorDID })) + var profiles map[string]Author + if shared != nil && shared.profiles != nil { + profiles = shared.profiles + } else { + profiles = fetchProfilesForDIDs(database, collectDIDs(highlights, func(h db.Highlight) string { return h.AuthorDID })) + } uris := make([]string, len(highlights)) for i, h := range highlights { uris[i] = h.URI } - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - likeCounts, replyCounts, viewerLikes := fetchCounts(ctx, database, uris, viewerDID) - - subscribedLabelers := getSubscribedLabelers(database, viewerDID) authorDIDs := collectDIDs(highlights, func(h db.Highlight) string { return h.AuthorDID }) - labelerDIDs := appendUnique(subscribedLabelers, authorDIDs) - uriLabels, _ := database.GetContentLabelsForURIs(uris, labelerDIDs) - didLabels, _ := database.GetContentLabelsForDIDs(authorDIDs, labelerDIDs) - editTimes, _ := database.GetLatestEditTimes(uris) + + likeCounts, replyCounts, viewerLikes, uriLabels, didLabels, editTimes := fetchEngagementData(database, uris, authorDIDs, viewerDID) result := make([]APIHighlight, len(highlights)) for i, h := range highlights { @@ -385,27 +491,28 @@ } func hydrateBookmarks(database *db.DB, bookmarks []db.Bookmark, viewerDID string) ([]APIBookmark, error) { + return hydrateBookmarksWithData(database, bookmarks, viewerDID, nil) +} + +func hydrateBookmarksWithData(database *db.DB, bookmarks []db.Bookmark, viewerDID string, shared *hydrationData) ([]APIBookmark, error) { if len(bookmarks) == 0 { return []APIBookmark{}, nil } - profiles := fetchProfilesForDIDs(database, collectDIDs(bookmarks, func(b db.Bookmark) string { return b.AuthorDID })) + var profiles map[string]Author + if shared != nil && shared.profiles != nil { + profiles = shared.profiles + } else { + profiles = fetchProfilesForDIDs(database, collectDIDs(bookmarks, func(b db.Bookmark) string { return b.AuthorDID })) + } uris := make([]string, len(bookmarks)) for i, b := range bookmarks { uris[i] = b.URI } - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - likeCounts, replyCounts, viewerLikes := fetchCounts(ctx, database, uris, viewerDID) - - subscribedLabelers := getSubscribedLabelers(database, viewerDID) authorDIDs := collectDIDs(bookmarks, func(b db.Bookmark) string { return b.AuthorDID }) - labelerDIDs := appendUnique(subscribedLabelers, authorDIDs) - uriLabels, _ := database.GetContentLabelsForURIs(uris, labelerDIDs) - didLabels, _ := database.GetContentLabelsForDIDs(authorDIDs, labelerDIDs) - editTimes, _ := database.GetLatestEditTimes(uris) + + likeCounts, replyCounts, viewerLikes, uriLabels, didLabels, editTimes := fetchEngagementData(database, uris, authorDIDs, viewerDID) result := make([]APIBookmark, len(bookmarks)) for i, b := range bookmarks { @@ -504,14 +611,46 @@ func fetchProfilesForDIDs(database *db.DB, dids []string) map[string]Author { profiles := make(map[string]Author) - missingDIDs := make([]string, 0) + if len(dids) == 0 { + return profiles + } + missingDIDs := make([]string, 0) for _, did := range dids { if author, ok := Cache.Get(did); ok { profiles[did] = author } else { missingDIDs = append(missingDIDs, did) } + } + + if len(missingDIDs) == 0 { + return profiles + } + + if database != nil { + marginProfiles, err := database.GetProfilesByDIDs(missingDIDs) + if err == nil { + for did, mp := range marginProfiles { + author := Author{DID: did} + if mp.DisplayName != nil && *mp.DisplayName != "" { + author.DisplayName = *mp.DisplayName + } + if mp.Avatar != nil && *mp.Avatar != "" { + author.Avatar = getProxiedAvatarURL(did, *mp.Avatar) + } + profiles[did] = author + Cache.Set(did, author) + } + } + + stillMissing := make([]string, 0) + for _, did := range missingDIDs { + if _, ok := profiles[did]; !ok { + stillMissing = append(stillMissing, did) + } + } + missingDIDs = stillMissing } if len(missingDIDs) > 0 { @@ -532,10 +671,11 @@ fetched, err := fetchProfiles(actors) if err == nil { mu.Lock() - defer mu.Unlock() for k, v := range fetched { profiles[k] = v + Cache.Set(k, v) } + mu.Unlock() } }(batch) } @@ -548,11 +688,8 @@ for did, mp := range marginProfiles { author, exists := profiles[did] if !exists { - author = Author{ - DID: did, - } + author = Author{DID: did} } - if mp.DisplayName != nil && *mp.DisplayName != "" { author.DisplayName = *mp.DisplayName } @@ -560,7 +697,6 @@ author.Avatar = getProxiedAvatarURL(did, *mp.Avatar) } profiles[did] = author - Cache.Set(did, author) } } @@ -579,7 +715,7 @@ q.Add("actors", did) } - resp, err := http.Get(config.Get().BskyGetProfilesURL() + "?" + q.Encode()) + resp, err := bskyHTTPClient.Get(config.Get().BskyGetProfilesURL() + "?" + q.Encode()) if err != nil { logger.Error("Hydration fetch error: %v", err) return nil, err @@ -618,11 +754,20 @@ } func hydrateCollectionItems(database *db.DB, items []db.CollectionItem, viewerDID string) ([]APICollectionItem, error) { + return hydrateCollectionItemsWithData(database, items, viewerDID, nil) +} + +func hydrateCollectionItemsWithData(database *db.DB, items []db.CollectionItem, viewerDID string, shared *hydrationData) ([]APICollectionItem, error) { if len(items) == 0 { return []APICollectionItem{}, nil } - profiles := fetchProfilesForDIDs(database, collectDIDs(items, func(i db.CollectionItem) string { return i.AuthorDID })) + var profiles map[string]Author + if shared != nil && shared.profiles != nil { + profiles = shared.profiles + } else { + profiles = fetchProfilesForDIDs(database, collectDIDs(items, func(i db.CollectionItem) string { return i.AuthorDID })) + } var collectionURIs []string var annotationURIs []string @@ -647,7 +792,6 @@ if len(collectionURIs) > 0 { colls, err := database.GetCollectionsByURIs(collectionURIs) if err == nil { - collProfiles := fetchProfilesForDIDs(database, collectDIDs(colls, func(c db.Collection) string { return c.AuthorDID })) for _, coll := range colls { icon := "" if coll.Icon != nil { @@ -662,7 +806,7 @@ Name: coll.Name, Description: desc, Icon: icon, - Creator: collProfiles[coll.AuthorDID], + Creator: profiles[coll.AuthorDID], CreatedAt: coll.CreatedAt, IndexedAt: coll.IndexedAt, } @@ -670,38 +814,65 @@ } } - annotationsMap := make(map[string]APIAnnotation) + var ( + annotationsMap = make(map[string]APIAnnotation) + highlightsMap = make(map[string]APIHighlight) + bookmarksMap = make(map[string]APIBookmark) + wg sync.WaitGroup + mu sync.Mutex + ) + + nestedShared := &hydrationData{profiles: profiles} + if len(annotationURIs) > 0 { - rawAnnos, err := database.GetAnnotationsByURIs(annotationURIs) - if err == nil { - hydrated, _ := hydrateAnnotations(database, rawAnnos, viewerDID) - for _, a := range hydrated { - annotationsMap[a.ID] = a + wg.Add(1) + go func() { + defer wg.Done() + rawAnnos, err := database.GetAnnotationsByURIs(annotationURIs) + if err == nil { + hydrated, _ := hydrateAnnotationsWithData(database, rawAnnos, viewerDID, nestedShared) + mu.Lock() + for _, a := range hydrated { + annotationsMap[a.ID] = a + } + mu.Unlock() } - } + }() } - highlightsMap := make(map[string]APIHighlight) if len(highlightURIs) > 0 { - rawHighlights, err := database.GetHighlightsByURIs(highlightURIs) - if err == nil { - hydrated, _ := hydrateHighlights(database, rawHighlights, viewerDID) - for _, h := range hydrated { - highlightsMap[h.ID] = h + wg.Add(1) + go func() { + defer wg.Done() + rawHighlights, err := database.GetHighlightsByURIs(highlightURIs) + if err == nil { + hydrated, _ := hydrateHighlightsWithData(database, rawHighlights, viewerDID, nestedShared) + mu.Lock() + for _, h := range hydrated { + highlightsMap[h.ID] = h + } + mu.Unlock() } - } + }() } - bookmarksMap := make(map[string]APIBookmark) if len(bookmarkURIs) > 0 { - rawBookmarks, err := database.GetBookmarksByURIs(bookmarkURIs) - if err == nil { - hydrated, _ := hydrateBookmarks(database, rawBookmarks, viewerDID) - for _, b := range hydrated { - bookmarksMap[b.ID] = b + wg.Add(1) + go func() { + defer wg.Done() + rawBookmarks, err := database.GetBookmarksByURIs(bookmarkURIs) + if err == nil { + hydrated, _ := hydrateBookmarksWithData(database, rawBookmarks, viewerDID, nestedShared) + mu.Lock() + for _, b := range hydrated { + bookmarksMap[b.ID] = b + } + mu.Unlock() } - } + }() } + + wg.Wait() var result []APICollectionItem for _, item := range items { diff --git a/backend/internal/api/pds.go b/backend/internal/api/pds.go --- a/backend/internal/api/pds.go +++ b/backend/internal/api/pds.go @@ -11,6 +11,8 @@ "margin.at/internal/xrpc" ) +var pdsClient = &http.Client{Timeout: 10 * time.Second} + func (h *Handler) FetchLatestUserRecords(r *http.Request, did string, collection string, limit int) ([]interface{}, error) { session, err := h.refresher.GetSessionWithAutoRefresh(r) if err != nil { @@ -25,7 +27,7 @@ req, _ := http.NewRequestWithContext(r.Context(), "GET", url, nil) req.Header.Set("Authorization", "Bearer "+client.AccessToken) - resp, err := http.DefaultClient.Do(req) + resp, err := pdsClient.Do(req) if err != nil { return fmt.Errorf("failed to fetch %s: %w", collection, err) } diff --git a/backend/internal/db/db.go b/backend/internal/db/db.go --- a/backend/internal/db/db.go +++ b/backend/internal/db/db.go @@ -8,12 +8,10 @@ "time" _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" ) type DB struct { *sql.DB - driver string } type Annotation struct { @@ -204,48 +202,28 @@ } func New(dsn string) (*DB, error) { - driver := "sqlite3" - if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { - driver = "postgres" + if !strings.HasPrefix(dsn, "postgres://") && !strings.HasPrefix(dsn, "postgresql://") { + return nil, fmt.Errorf("only PostgreSQL is supported, DSN must start with postgres:// or postgresql://") } - db, err := sql.Open(driver, dsn) + db, err := sql.Open("postgres", dsn) if err != nil { return nil, fmt.Errorf("failed to open database connection: %w", err) } - if driver == "sqlite3" { - if _, err := db.Exec("PRAGMA journal_mode=WAL;"); err != nil { - return nil, fmt.Errorf("failed to set WAL mode: %w", err) - } - db.Exec("PRAGMA synchronous=NORMAL;") - db.Exec("PRAGMA busy_timeout=5000;") - db.Exec("PRAGMA cache_size=-2000;") - db.Exec("PRAGMA foreign_keys=ON;") - - db.SetMaxOpenConns(25) - db.SetMaxIdleConns(25) - db.SetConnMaxLifetime(5 * time.Minute) - } else { - db.SetMaxOpenConns(50) - db.SetMaxIdleConns(25) - db.SetConnMaxLifetime(10 * time.Minute) - } + db.SetMaxOpenConns(25) + db.SetMaxIdleConns(10) + db.SetConnMaxLifetime(5 * time.Minute) + db.SetConnMaxIdleTime(2 * time.Minute) if err := db.Ping(); err != nil { return nil, fmt.Errorf("failed to ping database: %w", err) } - return &DB{DB: db, driver: driver}, nil + return &DB{DB: db}, nil } func (db *DB) Migrate() error { - - dateType := "DATETIME" - if db.driver == "postgres" { - dateType = "TIMESTAMP" - } - _, err := db.Exec(` CREATE TABLE IF NOT EXISTS annotations ( uri TEXT PRIMARY KEY, @@ -259,8 +237,8 @@ target_title TEXT, selector_json TEXT, tags_json TEXT, - created_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL, + created_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL, cid TEXT )`) if err != nil { @@ -272,6 +250,8 @@ db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_author_did ON annotations(author_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_motivation ON annotations(motivation)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_created_at ON annotations(created_at DESC)`) + db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_author_created ON annotations(author_did, created_at DESC)`) + db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_uri_pattern ON annotations(uri text_pattern_ops)`) db.Exec(`CREATE TABLE IF NOT EXISTS highlights ( uri TEXT PRIMARY KEY, @@ -282,13 +262,15 @@ selector_json TEXT, color TEXT, tags_json TEXT, - created_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL, + created_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL, cid TEXT )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_target_hash ON highlights(target_hash)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_author_did ON highlights(author_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_created_at ON highlights(created_at DESC)`) + db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_author_created ON highlights(author_did, created_at DESC)`) + db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_uri_pattern ON highlights(uri text_pattern_ops)`) db.Exec(`CREATE TABLE IF NOT EXISTS bookmarks ( uri TEXT PRIMARY KEY, @@ -298,13 +280,15 @@ title TEXT, description TEXT, tags_json TEXT, - created_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL, + created_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL, cid TEXT )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_source_hash ON bookmarks(source_hash)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_author_did ON bookmarks(author_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_created_at ON bookmarks(created_at DESC)`) + db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_author_created ON bookmarks(author_did, created_at DESC)`) + db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_uri_pattern ON bookmarks(uri text_pattern_ops)`) db.Exec(`CREATE TABLE IF NOT EXISTS replies ( uri TEXT PRIMARY KEY, @@ -313,20 +297,21 @@ root_uri TEXT NOT NULL, text TEXT NOT NULL, format TEXT DEFAULT 'text/plain', - created_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL, + created_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL, cid TEXT )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_replies_parent_uri ON replies(parent_uri)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_replies_root_uri ON replies(root_uri)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_replies_created_at ON replies(created_at DESC)`) + db.Exec(`CREATE INDEX IF NOT EXISTS idx_replies_author_did ON replies(author_did)`) db.Exec(`CREATE TABLE IF NOT EXISTS likes ( uri TEXT PRIMARY KEY, author_did TEXT NOT NULL, subject_uri TEXT NOT NULL, - created_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL + created_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_likes_subject_uri ON likes(subject_uri)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_likes_author_did ON likes(author_did)`) @@ -338,8 +323,8 @@ name TEXT NOT NULL, description TEXT, icon TEXT, - created_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL + created_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_collections_author_did ON collections(author_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_collections_created_at ON collections(created_at DESC)`) @@ -350,8 +335,8 @@ collection_uri TEXT NOT NULL, annotation_uri TEXT NOT NULL, position INTEGER DEFAULT 0, - created_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL + created_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_collection_items_collection ON collection_items(collection_uri)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_collection_items_annotation ON collection_items(annotation_uri)`) @@ -364,49 +349,46 @@ access_token TEXT NOT NULL, refresh_token TEXT NOT NULL, dpop_key TEXT, - created_at ` + dateType + ` NOT NULL, - expires_at ` + dateType + ` NOT NULL + created_at TIMESTAMP NOT NULL, + expires_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_sessions_did ON sessions(did)`) - - autoInc := "INTEGER PRIMARY KEY AUTOINCREMENT" - if db.driver == "postgres" { - autoInc = "SERIAL PRIMARY KEY" - } + db.Exec(`CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at)`) db.Exec(`CREATE TABLE IF NOT EXISTS edit_history ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, uri TEXT NOT NULL, record_type TEXT NOT NULL, previous_content TEXT NOT NULL, previous_cid TEXT, - edited_at ` + dateType + ` NOT NULL + edited_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_edit_history_uri ON edit_history(uri)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_edit_history_edited_at ON edit_history(edited_at DESC)`) db.Exec(`CREATE TABLE IF NOT EXISTS notifications ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, recipient_did TEXT NOT NULL, actor_did TEXT NOT NULL, type TEXT NOT NULL, subject_uri TEXT NOT NULL, - created_at ` + dateType + ` NOT NULL, - read_at ` + dateType + ` + created_at TIMESTAMP NOT NULL, + read_at TIMESTAMP )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_notifications_recipient ON notifications(recipient_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_notifications_created_at ON notifications(created_at DESC)`) + db.Exec(`CREATE INDEX IF NOT EXISTS idx_notifications_unread ON notifications(recipient_did) WHERE read_at IS NULL`) db.Exec(`CREATE TABLE IF NOT EXISTS api_keys ( id TEXT PRIMARY KEY, owner_did TEXT NOT NULL, name TEXT NOT NULL, key_hash TEXT NOT NULL, - created_at ` + dateType + ` NOT NULL, - last_used_at ` + dateType + `, + created_at TIMESTAMP NOT NULL, + last_used_at TIMESTAMP, uri TEXT, cid TEXT, - indexed_at ` + dateType + ` DEFAULT CURRENT_TIMESTAMP + indexed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_owner ON api_keys(owner_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash)`) @@ -419,8 +401,8 @@ bio TEXT, website TEXT, links_json TEXT, - created_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL, + created_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL, cid TEXT )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_profiles_author_did ON profiles(author_did)`) @@ -432,8 +414,8 @@ subscribed_labelers TEXT, label_preferences TEXT, disable_external_link_warning BOOLEAN, - created_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL, + created_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL, cid TEXT )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_preferences_author_did ON preferences(author_did)`) @@ -443,39 +425,39 @@ db.Exec(`CREATE TABLE IF NOT EXISTS cursors ( id TEXT PRIMARY KEY, last_cursor BIGINT NOT NULL, - updated_at ` + dateType + ` NOT NULL + updated_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE TABLE IF NOT EXISTS blocks ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, actor_did TEXT NOT NULL, subject_did TEXT NOT NULL, - created_at ` + dateType + ` NOT NULL, + created_at TIMESTAMP NOT NULL, UNIQUE(actor_did, subject_did) )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_blocks_actor ON blocks(actor_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_blocks_subject ON blocks(subject_did)`) db.Exec(`CREATE TABLE IF NOT EXISTS mutes ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, actor_did TEXT NOT NULL, subject_did TEXT NOT NULL, - created_at ` + dateType + ` NOT NULL, + created_at TIMESTAMP NOT NULL, UNIQUE(actor_did, subject_did) )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_mutes_actor ON mutes(actor_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_mutes_subject ON mutes(subject_did)`) db.Exec(`CREATE TABLE IF NOT EXISTS moderation_reports ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, reporter_did TEXT NOT NULL, subject_did TEXT NOT NULL, subject_uri TEXT, reason_type TEXT NOT NULL, reason_text TEXT, status TEXT NOT NULL DEFAULT 'pending', - created_at ` + dateType + ` NOT NULL, - resolved_at ` + dateType + `, + created_at TIMESTAMP NOT NULL, + resolved_at TIMESTAMP, resolved_by TEXT )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_reports_status ON moderation_reports(status)`) @@ -483,23 +465,23 @@ db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_reports_reporter ON moderation_reports(reporter_did)`) db.Exec(`CREATE TABLE IF NOT EXISTS moderation_actions ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, report_id INTEGER NOT NULL, actor_did TEXT NOT NULL, action TEXT NOT NULL, comment TEXT, - created_at ` + dateType + ` NOT NULL + created_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_actions_report ON moderation_actions(report_id)`) db.Exec(`CREATE TABLE IF NOT EXISTS content_labels ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, src TEXT NOT NULL, uri TEXT NOT NULL, val TEXT NOT NULL, neg INTEGER NOT NULL DEFAULT 0, created_by TEXT NOT NULL, - created_at ` + dateType + ` NOT NULL + created_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_content_labels_uri ON content_labels(uri)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_content_labels_src ON content_labels(src)`) @@ -511,7 +493,7 @@ name TEXT NOT NULL, description TEXT, show_in_discover BOOLEAN NOT NULL DEFAULT true, - indexed_at ` + dateType + ` NOT NULL + indexed_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_publications_author ON publications(author_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_publications_url ON publications(url)`) @@ -526,8 +508,8 @@ text_content TEXT, tags_json TEXT, canonical_url TEXT, - published_at ` + dateType + ` NOT NULL, - indexed_at ` + dateType + ` NOT NULL + published_at TIMESTAMP NOT NULL, + indexed_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_documents_author ON documents(author_did)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_documents_site ON documents(site)`) @@ -544,26 +526,14 @@ return nil, nil } - query := `SELECT uri, author_did, display_name, bio, avatar, website, links_json, created_at, indexed_at FROM profiles WHERE author_did IN (` - args := make([]interface{}, len(dids)) placeholders := make([]string, len(dids)) - + args := make([]interface{}, len(dids)) for i, did := range dids { placeholders[i] = fmt.Sprintf("$%d", i+1) args[i] = did } - query += strings.Join(placeholders, ",") + ")" - - if db.driver == "sqlite3" { - query = strings.ReplaceAll(query, "$", "?") - - placeholders = make([]string, len(dids)) - for i := range dids { - placeholders[i] = "?" - } - query = `SELECT uri, author_did, display_name, bio, avatar, website, links_json, created_at, indexed_at FROM profiles WHERE author_did IN (` + strings.Join(placeholders, ",") + ")" - } + query := `SELECT uri, author_did, display_name, bio, avatar, website, links_json, created_at, indexed_at FROM profiles WHERE author_did IN (` + strings.Join(placeholders, ",") + ")" rows, err := db.Query(query, args...) if err != nil { @@ -597,10 +567,10 @@ func (db *DB) SetCursor(id string, cursor int64) error { query := ` - INSERT INTO cursors (id, last_cursor, updated_at) - VALUES ($1, $2, $3) - ON CONFLICT(id) DO UPDATE SET - last_cursor = EXCLUDED.last_cursor, + INSERT INTO cursors (id, last_cursor, updated_at) + VALUES ($1, $2, $3) + ON CONFLICT(id) DO UPDATE SET + last_cursor = EXCLUDED.last_cursor, updated_at = EXCLUDED.updated_at ` _, err := db.Exec(query, id, cursor, time.Now()) @@ -623,17 +593,17 @@ func (db *DB) UpsertProfile(p *Profile) error { query := ` - INSERT INTO profiles (uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - ON CONFLICT(uri) DO UPDATE SET + INSERT INTO profiles (uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + ON CONFLICT(uri) DO UPDATE SET display_name = EXCLUDED.display_name, avatar = EXCLUDED.avatar, - bio = EXCLUDED.bio, + bio = EXCLUDED.bio, website = EXCLUDED.website, links_json = EXCLUDED.links_json, indexed_at = EXCLUDED.indexed_at ` - _, err := db.Exec(db.Rebind(query), p.URI, p.AuthorDID, p.DisplayName, p.Avatar, p.Bio, p.Website, p.LinksJSON, p.CreatedAt, p.IndexedAt) + _, err := db.Exec(query, p.URI, p.AuthorDID, p.DisplayName, p.Avatar, p.Bio, p.Website, p.LinksJSON, p.CreatedAt, p.IndexedAt) return err } @@ -672,9 +642,9 @@ func (db *DB) UpsertPreferences(p *Preferences) error { query := ` - INSERT INTO preferences (uri, author_did, external_link_skipped_hostnames, subscribed_labelers, label_preferences, disable_external_link_warning, created_at, indexed_at, cid) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - ON CONFLICT(uri) DO UPDATE SET + INSERT INTO preferences (uri, author_did, external_link_skipped_hostnames, subscribed_labelers, label_preferences, disable_external_link_warning, created_at, indexed_at, cid) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + ON CONFLICT(uri) DO UPDATE SET external_link_skipped_hostnames = EXCLUDED.external_link_skipped_hostnames, subscribed_labelers = EXCLUDED.subscribed_labelers, label_preferences = EXCLUDED.label_preferences, @@ -682,7 +652,7 @@ indexed_at = EXCLUDED.indexed_at, cid = EXCLUDED.cid ` - _, err := db.Exec(db.Rebind(query), p.URI, p.AuthorDID, p.ExternalLinkSkippedHostnames, p.SubscribedLabelers, p.LabelPreferences, p.DisableExternalLinkWarning, p.CreatedAt, p.IndexedAt, p.CID) + _, err := db.Exec(query, p.URI, p.AuthorDID, p.ExternalLinkSkippedHostnames, p.SubscribedLabelers, p.LabelPreferences, p.DisableExternalLinkWarning, p.CreatedAt, p.IndexedAt, p.CID) return err } @@ -697,7 +667,7 @@ } func (db *DB) GetAPIKeyURIs(ownerDID string) ([]string, error) { - rows, err := db.Query(db.Rebind("SELECT uri FROM api_keys WHERE owner_did = ? AND uri IS NOT NULL AND uri != ''"), ownerDID) + rows, err := db.Query("SELECT uri FROM api_keys WHERE owner_did = $1 AND uri IS NOT NULL AND uri != ''", ownerDID) if err != nil { return nil, err } @@ -714,7 +684,7 @@ } func (db *DB) GetPreferenceURIs(did string) ([]string, error) { - rows, err := db.Query(db.Rebind("SELECT uri FROM preferences WHERE author_did = ? AND uri IS NOT NULL AND uri != ''"), did) + rows, err := db.Query("SELECT uri FROM preferences WHERE author_did = $1 AND uri IS NOT NULL AND uri != ''", did) if err != nil { return nil, err } @@ -731,22 +701,18 @@ } func (db *DB) runMigrations() { - dateType := "DATETIME" - if db.driver == "postgres" { - dateType = "TIMESTAMP" - } - db.Exec(`ALTER TABLE sessions ADD COLUMN dpop_key TEXT`) + db.Exec(`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS dpop_key TEXT`) - db.Exec(`ALTER TABLE annotations ADD COLUMN motivation TEXT`) - db.Exec(`ALTER TABLE annotations ADD COLUMN body_value TEXT`) - db.Exec(`ALTER TABLE annotations ADD COLUMN body_format TEXT DEFAULT 'text/plain'`) - db.Exec(`ALTER TABLE annotations ADD COLUMN body_uri TEXT`) - db.Exec(`ALTER TABLE annotations ADD COLUMN target_source TEXT`) - db.Exec(`ALTER TABLE annotations ADD COLUMN target_hash TEXT`) - db.Exec(`ALTER TABLE annotations ADD COLUMN target_title TEXT`) - db.Exec(`ALTER TABLE annotations ADD COLUMN selector_json TEXT`) - db.Exec(`ALTER TABLE annotations ADD COLUMN tags_json TEXT`) - db.Exec(`ALTER TABLE annotations ADD COLUMN cid TEXT`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS motivation TEXT`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS body_value TEXT`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS body_format TEXT DEFAULT 'text/plain'`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS body_uri TEXT`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS target_source TEXT`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS target_hash TEXT`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS target_title TEXT`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS selector_json TEXT`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS tags_json TEXT`) + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS cid TEXT`) db.Exec(`UPDATE annotations SET target_source = url WHERE target_source IS NULL AND url IS NOT NULL`) db.Exec(`UPDATE annotations SET target_hash = url_hash WHERE target_hash IS NULL AND url_hash IS NOT NULL`) @@ -754,46 +720,39 @@ db.Exec(`UPDATE annotations SET target_title = title WHERE target_title IS NULL AND title IS NOT NULL`) db.Exec(`UPDATE annotations SET motivation = 'commenting' WHERE motivation IS NULL`) - db.Exec(`ALTER TABLE profiles ADD COLUMN website TEXT`) - db.Exec(`ALTER TABLE profiles ADD COLUMN display_name TEXT`) - db.Exec(`ALTER TABLE profiles ADD COLUMN avatar TEXT`) + db.Exec(`ALTER TABLE profiles ADD COLUMN IF NOT EXISTS website TEXT`) + db.Exec(`ALTER TABLE profiles ADD COLUMN IF NOT EXISTS display_name TEXT`) + db.Exec(`ALTER TABLE profiles ADD COLUMN IF NOT EXISTS avatar TEXT`) - if db.driver == "postgres" { - db.Exec(`ALTER TABLE cursors ALTER COLUMN last_cursor TYPE BIGINT`) - } + db.Exec(`ALTER TABLE cursors ALTER COLUMN last_cursor TYPE BIGINT`) - db.Exec(`ALTER TABLE api_keys ADD COLUMN uri TEXT`) - db.Exec(`ALTER TABLE api_keys ADD COLUMN cid TEXT`) - db.Exec(`ALTER TABLE api_keys ADD COLUMN indexed_at ` + dateType + ` DEFAULT CURRENT_TIMESTAMP`) + db.Exec(`ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS uri TEXT`) + db.Exec(`ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS cid TEXT`) + db.Exec(`ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS indexed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP`) - db.migrateModeration(dateType) + db.migrateModeration() - db.Exec(`ALTER TABLE preferences ADD COLUMN subscribed_labelers TEXT`) - db.Exec(`ALTER TABLE preferences ADD COLUMN label_preferences TEXT`) - db.Exec(`ALTER TABLE preferences ADD COLUMN disable_external_link_warning BOOLEAN`) + db.Exec(`ALTER TABLE preferences ADD COLUMN IF NOT EXISTS subscribed_labelers TEXT`) + db.Exec(`ALTER TABLE preferences ADD COLUMN IF NOT EXISTS label_preferences TEXT`) + db.Exec(`ALTER TABLE preferences ADD COLUMN IF NOT EXISTS disable_external_link_warning BOOLEAN`) } -func (db *DB) migrateModeration(dateType string) { +func (db *DB) migrateModeration() { _, err := db.Exec(`SELECT subject_did FROM moderation_reports LIMIT 0`) if err != nil { db.Exec(`DROP TABLE IF EXISTS moderation_reports`) db.Exec(`DROP TABLE IF EXISTS moderation_actions`) - autoInc := "INTEGER PRIMARY KEY AUTOINCREMENT" - if db.driver == "postgres" { - autoInc = "SERIAL PRIMARY KEY" - } - db.Exec(`CREATE TABLE IF NOT EXISTS moderation_reports ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, reporter_did TEXT NOT NULL, subject_did TEXT NOT NULL, subject_uri TEXT, reason_type TEXT NOT NULL, reason_text TEXT, status TEXT NOT NULL DEFAULT 'pending', - created_at ` + dateType + ` NOT NULL, - resolved_at ` + dateType + `, + created_at TIMESTAMP NOT NULL, + resolved_at TIMESTAMP, resolved_by TEXT )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_reports_status ON moderation_reports(status)`) @@ -801,28 +760,24 @@ db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_reports_reporter ON moderation_reports(reporter_did)`) db.Exec(`CREATE TABLE IF NOT EXISTS moderation_actions ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, report_id INTEGER NOT NULL, actor_did TEXT NOT NULL, action TEXT NOT NULL, comment TEXT, - created_at ` + dateType + ` NOT NULL + created_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_actions_report ON moderation_actions(report_id)`) } - autoInc := "INTEGER PRIMARY KEY AUTOINCREMENT" - if db.driver == "postgres" { - autoInc = "SERIAL PRIMARY KEY" - } db.Exec(`CREATE TABLE IF NOT EXISTS content_labels ( - id ` + autoInc + `, + id SERIAL PRIMARY KEY, src TEXT NOT NULL, uri TEXT NOT NULL, val TEXT NOT NULL, neg INTEGER NOT NULL DEFAULT 0, created_by TEXT NOT NULL, - created_at ` + dateType + ` NOT NULL + created_at TIMESTAMP NOT NULL )`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_content_labels_uri ON content_labels(uri)`) db.Exec(`CREATE INDEX IF NOT EXISTS idx_content_labels_src ON content_labels(src)`) @@ -830,30 +785,6 @@ func (db *DB) Close() error { return db.DB.Close() -} - -func (db *DB) Rebind(query string) string { - if db.driver != "postgres" { - return query - } - - if !strings.Contains(query, "?") { - return query - } - - var builder strings.Builder - builder.Grow(len(query) + 20) - - paramCount := 1 - for _, r := range query { - if r == '?' { - fmt.Fprintf(&builder, "$%d", paramCount) - paramCount++ - } else { - builder.WriteRune(r) - } - } - return builder.String() } func ParseSelector(selectorJSON *string) (*Selector, error) { diff --git a/backend/internal/db/pg_helpers.go b/backend/internal/db/pg_helpers.go new file mode 100644 --- /dev/null +++ b/backend/internal/db/pg_helpers.go @@ -0,0 +1,22 @@ +package db + +import ( + "database/sql/driver" + "fmt" + "strings" +) + +type pqStringArray []string + +func (a pqStringArray) Value() (driver.Value, error) { + if a == nil { + return "{}", nil + } + parts := make([]string, len(a)) + for i, s := range a { + escaped := strings.ReplaceAll(s, "\\", "\\\\") + escaped = strings.ReplaceAll(escaped, "\"", "\\\"") + parts[i] = fmt.Sprintf(`"%s"`, escaped) + } + return "{" + strings.Join(parts, ",") + "}", nil +} diff --git a/backend/internal/db/queries.go b/backend/internal/db/queries.go --- a/backend/internal/db/queries.go +++ b/backend/internal/db/queries.go @@ -35,9 +35,9 @@ } func (db *DB) AnnotationExists(uri string) bool { - var count int - db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM annotations WHERE uri = ?`), uri).Scan(&count) - return count > 0 + var exists bool + db.QueryRow(`SELECT EXISTS(SELECT 1 FROM annotations WHERE uri = $1)`, uri).Scan(&exists) + return exists } func HashURL(rawURL string) string { @@ -71,17 +71,17 @@ func (db *DB) GetAuthorByURI(uri string) (string, error) { var authorDID string - err := db.QueryRow(db.Rebind(`SELECT author_did FROM annotations WHERE uri = ?`), uri).Scan(&authorDID) + err := db.QueryRow(`SELECT author_did FROM annotations WHERE uri = $1`, uri).Scan(&authorDID) if err == nil { return authorDID, nil } - err = db.QueryRow(db.Rebind(`SELECT author_did FROM highlights WHERE uri = ?`), uri).Scan(&authorDID) + err = db.QueryRow(`SELECT author_did FROM highlights WHERE uri = $1`, uri).Scan(&authorDID) if err == nil { return authorDID, nil } - err = db.QueryRow(db.Rebind(`SELECT author_did FROM bookmarks WHERE uri = ?`), uri).Scan(&authorDID) + err = db.QueryRow(`SELECT author_did FROM bookmarks WHERE uri = $1`, uri).Scan(&authorDID) if err == nil { return authorDID, nil } @@ -89,13 +89,13 @@ return "", fmt.Errorf("uri not found or no author") } -func buildPlaceholders(n int) string { +func buildPlaceholders(n, startAt int) string { if n == 0 { return "" } placeholders := make([]string, n) for i := range placeholders { - placeholders[i] = "?" + placeholders[i] = fmt.Sprintf("$%d", startAt+i) } return strings.Join(placeholders, ", ") } diff --git a/backend/internal/db/queries_annotations.go b/backend/internal/db/queries_annotations.go --- a/backend/internal/db/queries_annotations.go +++ b/backend/internal/db/queries_annotations.go @@ -5,32 +5,32 @@ ) func (db *DB) CreateAnnotation(a *Annotation) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO annotations (uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) ON CONFLICT(uri) DO UPDATE SET - motivation = excluded.motivation, - body_value = excluded.body_value, - body_format = excluded.body_format, - body_uri = excluded.body_uri, - target_source = excluded.target_source, - target_hash = excluded.target_hash, - target_title = excluded.target_title, - selector_json = excluded.selector_json, - tags_json = excluded.tags_json, - indexed_at = excluded.indexed_at, - cid = excluded.cid - `), a.URI, a.AuthorDID, a.Motivation, a.BodyValue, a.BodyFormat, a.BodyURI, a.TargetSource, a.TargetHash, a.TargetTitle, a.SelectorJSON, a.TagsJSON, a.CreatedAt, a.IndexedAt, a.CID) + motivation = EXCLUDED.motivation, + body_value = EXCLUDED.body_value, + body_format = EXCLUDED.body_format, + body_uri = EXCLUDED.body_uri, + target_source = EXCLUDED.target_source, + target_hash = EXCLUDED.target_hash, + target_title = EXCLUDED.target_title, + selector_json = EXCLUDED.selector_json, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid + `, a.URI, a.AuthorDID, a.Motivation, a.BodyValue, a.BodyFormat, a.BodyURI, a.TargetSource, a.TargetHash, a.TargetTitle, a.SelectorJSON, a.TagsJSON, a.CreatedAt, a.IndexedAt, a.CID) return err } func (db *DB) GetAnnotationByURI(uri string) (*Annotation, error) { var a Annotation - err := db.QueryRow(db.Rebind(` + err := db.QueryRow(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE uri = ? - `), uri).Scan(&a.URI, &a.AuthorDID, &a.Motivation, &a.BodyValue, &a.BodyFormat, &a.BodyURI, &a.TargetSource, &a.TargetHash, &a.TargetTitle, &a.SelectorJSON, &a.TagsJSON, &a.CreatedAt, &a.IndexedAt, &a.CID) + WHERE uri = $1 + `, uri).Scan(&a.URI, &a.AuthorDID, &a.Motivation, &a.BodyValue, &a.BodyFormat, &a.BodyURI, &a.TargetSource, &a.TargetHash, &a.TargetTitle, &a.SelectorJSON, &a.TagsJSON, &a.CreatedAt, &a.IndexedAt, &a.CID) if err != nil { return nil, err } @@ -38,13 +38,13 @@ } func (db *DB) GetAnnotationsByTargetHash(targetHash string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE target_hash = ? + WHERE target_hash = $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), targetHash, limit, offset) + LIMIT $2 OFFSET $3 + `, targetHash, limit, offset) if err != nil { return nil, err } @@ -54,13 +54,13 @@ } func (db *DB) GetAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE author_did = ? + WHERE author_did = $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, limit, offset) + LIMIT $2 OFFSET $3 + `, authorDID, limit, offset) if err != nil { return nil, err } @@ -70,13 +70,13 @@ } func (db *DB) GetMarginAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE author_did = ? AND uri NOT LIKE '%network.cosmik%' + WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, limit, offset) + LIMIT $2 OFFSET $3 + `, authorDID, limit, offset) if err != nil { return nil, err } @@ -86,13 +86,13 @@ } func (db *DB) GetSembleAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE author_did = ? AND uri LIKE '%network.cosmik%' + WHERE author_did = $1 AND uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, limit, offset) + LIMIT $2 OFFSET $3 + `, authorDID, limit, offset) if err != nil { return nil, err } @@ -102,13 +102,13 @@ } func (db *DB) GetAnnotationsByMotivation(motivation string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE motivation = ? + WHERE motivation = $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), motivation, limit, offset) + LIMIT $2 OFFSET $3 + `, motivation, limit, offset) if err != nil { return nil, err } @@ -118,12 +118,12 @@ } func (db *DB) GetRecentAnnotations(limit, offset int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } @@ -134,22 +134,22 @@ func (db *DB) GetPopularAnnotations(limit, offset int) ([]Annotation, error) { since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(db.Rebind(` - SELECT - a.uri, a.author_did, a.motivation, a.body_value, a.body_format, - a.body_uri, a.target_source, a.target_hash, a.target_title, + rows, err := db.Query(` + SELECT + a.uri, a.author_did, a.motivation, a.body_value, a.body_format, + a.body_uri, a.target_source, a.target_hash, a.target_title, a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid FROM annotations a - LEFT JOIN ( - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri - ) l ON l.subject_uri = a.uri - LEFT JOIN ( - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri - ) r ON r.root_uri = a.uri - WHERE a.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) > 0 - ORDER BY (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) DESC, a.created_at DESC - LIMIT ? OFFSET ? - `), since, limit, offset) + LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = a.uri + ) l ON true + LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = a.uri + ) r ON true + WHERE a.created_at > $1 AND (l.cnt + r.cnt) > 0 + ORDER BY (l.cnt + r.cnt) DESC, a.created_at DESC + LIMIT $2 OFFSET $3 + `, since, limit, offset) if err != nil { return nil, err } @@ -161,22 +161,18 @@ func (db *DB) GetShelvedAnnotations(limit, offset int) ([]Annotation, error) { olderThan := time.Now().AddDate(0, 0, -1) since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(db.Rebind(` - SELECT - a.uri, a.author_did, a.motivation, a.body_value, a.body_format, - a.body_uri, a.target_source, a.target_hash, a.target_title, + rows, err := db.Query(` + SELECT + a.uri, a.author_did, a.motivation, a.body_value, a.body_format, + a.body_uri, a.target_source, a.target_hash, a.target_title, a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid FROM annotations a - LEFT JOIN ( - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri - ) l ON l.subject_uri = a.uri - LEFT JOIN ( - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri - ) r ON r.root_uri = a.uri - WHERE a.created_at < ? AND a.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) = 0 + WHERE a.created_at < $1 AND a.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = a.uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = a.uri) ORDER BY RANDOM() - LIMIT ? OFFSET ? - `), olderThan, since, limit, offset) + LIMIT $3 OFFSET $4 + `, olderThan, since, limit, offset) if err != nil { return nil, err } @@ -186,13 +182,13 @@ } func (db *DB) GetMarginAnnotations(limit, offset int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations WHERE uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } @@ -202,13 +198,13 @@ } func (db *DB) GetSembleAnnotations(limit, offset int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations WHERE uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } @@ -218,14 +214,13 @@ } func (db *DB) GetAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE tags_json LIKE ? + WHERE tags_json::jsonb ? $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), pattern, limit, offset) + LIMIT $2 OFFSET $3 + `, tag, limit, offset) if err != nil { return nil, err } @@ -235,14 +230,13 @@ } func (db *DB) GetMarginAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' + WHERE tags_json::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), pattern, limit, offset) + LIMIT $2 OFFSET $3 + `, tag, limit, offset) if err != nil { return nil, err } @@ -252,14 +246,13 @@ } func (db *DB) GetSembleAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE tags_json LIKE ? AND uri LIKE '%network.cosmik%' + WHERE tags_json::jsonb ? $1 AND uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), pattern, limit, offset) + LIMIT $2 OFFSET $3 + `, tag, limit, offset) if err != nil { return nil, err } @@ -269,28 +262,27 @@ } func (db *DB) DeleteAnnotation(uri string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM annotations WHERE uri = ?`), uri) + _, err := db.Exec(`DELETE FROM annotations WHERE uri = $1`, uri) return err } func (db *DB) UpdateAnnotation(uri, bodyValue, tagsJSON, cid string) error { - _, err := db.Exec(db.Rebind(` - UPDATE annotations - SET body_value = ?, tags_json = ?, cid = ?, indexed_at = ? - WHERE uri = ? - `), bodyValue, tagsJSON, cid, time.Now(), uri) + _, err := db.Exec(` + UPDATE annotations + SET body_value = $1, tags_json = $2, cid = $3, indexed_at = $4 + WHERE uri = $5 + `, bodyValue, tagsJSON, cid, time.Now(), uri) return err } func (db *DB) GetAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE author_did = ? AND tags_json LIKE ? + WHERE author_did = $1 AND tags_json::jsonb ? $2 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, pattern, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, tag, limit, offset) if err != nil { return nil, err } @@ -300,14 +292,13 @@ } func (db *DB) GetMarginAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE author_did = ? AND tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, pattern, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, tag, limit, offset) if err != nil { return nil, err } @@ -317,14 +308,13 @@ } func (db *DB) GetSembleAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE author_did = ? AND tags_json LIKE ? AND uri LIKE '%network.cosmik%' + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, pattern, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, tag, limit, offset) if err != nil { return nil, err } @@ -334,13 +324,13 @@ } func (db *DB) GetAnnotationsByAuthorAndTargetHash(authorDID, targetHash string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE author_did = ? AND target_hash = ? + WHERE author_did = $1 AND target_hash = $2 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, targetHash, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, targetHash, limit, offset) if err != nil { return nil, err } @@ -354,18 +344,13 @@ return []Annotation{}, nil } - query := db.Rebind(` + query := ` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) - `) + WHERE uri = ANY($1) + ` - args := make([]interface{}, len(uris)) - for i, uri := range uris { - args[i] = uri - } - - rows, err := db.Query(query, args...) + rows, err := db.Query(query, pqStringArray(uris)) if err != nil { return nil, err } @@ -375,9 +360,9 @@ } func (db *DB) GetAnnotationURIs(authorDID string) ([]string, error) { - rows, err := db.Query(db.Rebind(` - SELECT uri FROM annotations WHERE author_did = ? - `), authorDID) + rows, err := db.Query(` + SELECT uri FROM annotations WHERE author_did = $1 + `, authorDID) if err != nil { return nil, err } diff --git a/backend/internal/db/queries_bookmarks.go b/backend/internal/db/queries_bookmarks.go --- a/backend/internal/db/queries_bookmarks.go +++ b/backend/internal/db/queries_bookmarks.go @@ -5,28 +5,28 @@ ) func (db *DB) CreateBookmark(b *Bookmark) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO bookmarks (uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT(uri) DO UPDATE SET - source = excluded.source, - source_hash = excluded.source_hash, - title = excluded.title, - description = excluded.description, - tags_json = excluded.tags_json, - indexed_at = excluded.indexed_at, - cid = excluded.cid - `), b.URI, b.AuthorDID, b.Source, b.SourceHash, b.Title, b.Description, b.TagsJSON, b.CreatedAt, b.IndexedAt, b.CID) + source = EXCLUDED.source, + source_hash = EXCLUDED.source_hash, + title = EXCLUDED.title, + description = EXCLUDED.description, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid + `, b.URI, b.AuthorDID, b.Source, b.SourceHash, b.Title, b.Description, b.TagsJSON, b.CreatedAt, b.IndexedAt, b.CID) return err } func (db *DB) GetBookmarkByURI(uri string) (*Bookmark, error) { var b Bookmark - err := db.QueryRow(db.Rebind(` + err := db.QueryRow(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE uri = ? - `), uri).Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID) + WHERE uri = $1 + `, uri).Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID) if err != nil { return nil, err } @@ -34,376 +34,254 @@ } func (db *DB) GetRecentBookmarks(limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetPopularBookmarks(limit, offset int) ([]Bookmark, error) { since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(db.Rebind(` - SELECT - b.uri, b.author_did, b.source, b.source_hash, b.title, + rows, err := db.Query(` + SELECT + b.uri, b.author_did, b.source, b.source_hash, b.title, b.description, b.tags_json, b.created_at, b.indexed_at, b.cid FROM bookmarks b - LEFT JOIN ( - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri - ) l ON l.subject_uri = b.uri - LEFT JOIN ( - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri - ) r ON r.root_uri = b.uri - WHERE b.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) > 0 - ORDER BY (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) DESC, b.created_at DESC - LIMIT ? OFFSET ? - `), since, limit, offset) + LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = b.uri + ) l ON true + LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = b.uri + ) r ON true + WHERE b.created_at > $1 AND (l.cnt + r.cnt) > 0 + ORDER BY (l.cnt + r.cnt) DESC, b.created_at DESC + LIMIT $2 OFFSET $3 + `, since, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetShelvedBookmarks(limit, offset int) ([]Bookmark, error) { olderThan := time.Now().AddDate(0, 0, -1) since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(db.Rebind(` - SELECT - b.uri, b.author_did, b.source, b.source_hash, b.title, + rows, err := db.Query(` + SELECT + b.uri, b.author_did, b.source, b.source_hash, b.title, b.description, b.tags_json, b.created_at, b.indexed_at, b.cid FROM bookmarks b - LEFT JOIN ( - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri - ) l ON l.subject_uri = b.uri - LEFT JOIN ( - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri - ) r ON r.root_uri = b.uri - WHERE b.created_at < ? AND b.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) = 0 + WHERE b.created_at < $1 AND b.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = b.uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = b.uri) ORDER BY RANDOM() - LIMIT ? OFFSET ? - `), olderThan, since, limit, offset) + LIMIT $3 OFFSET $4 + `, olderThan, since, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetMarginBookmarks(limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks WHERE uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetSembleBookmarks(limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks WHERE uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE tags_json LIKE ? + WHERE tags_json::jsonb ? $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), pattern, limit, offset) + LIMIT $2 OFFSET $3 + `, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetMarginBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' + WHERE tags_json::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), pattern, limit, offset) + LIMIT $2 OFFSET $3 + `, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetSembleBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE tags_json LIKE ? AND uri LIKE '%network.cosmik%' + WHERE tags_json::jsonb ? $1 AND uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), pattern, limit, offset) + LIMIT $2 OFFSET $3 + `, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE author_did = ? AND tags_json LIKE ? + WHERE author_did = $1 AND tags_json::jsonb ? $2 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, pattern, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetMarginBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE author_did = ? AND tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, pattern, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetSembleBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE author_did = ? AND tags_json LIKE ? AND uri LIKE '%network.cosmik%' + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, pattern, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE author_did = ? + WHERE author_did = $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, limit, offset) + LIMIT $2 OFFSET $3 + `, authorDID, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetMarginBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE author_did = ? AND uri NOT LIKE '%network.cosmik%' + WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, limit, offset) + LIMIT $2 OFFSET $3 + `, authorDID, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetSembleBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE author_did = ? AND uri LIKE '%network.cosmik%' + WHERE author_did = $1 AND uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, limit, offset) + LIMIT $2 OFFSET $3 + `, authorDID, limit, offset) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) DeleteBookmark(uri string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM bookmarks WHERE uri = ?`), uri) + _, err := db.Exec(`DELETE FROM bookmarks WHERE uri = $1`, uri) return err } func (db *DB) UpdateBookmark(uri, title, description, tagsJSON, cid string) error { - _, err := db.Exec(db.Rebind(` - UPDATE bookmarks - SET title = ?, description = ?, tags_json = ?, cid = ?, indexed_at = ? - WHERE uri = ? - `), title, description, tagsJSON, cid, time.Now(), uri) + _, err := db.Exec(` + UPDATE bookmarks + SET title = $1, description = $2, tags_json = $3, cid = $4, indexed_at = $5 + WHERE uri = $6 + `, title, description, tagsJSON, cid, time.Now(), uri) return err } @@ -412,38 +290,23 @@ return []Bookmark{}, nil } - query := db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) - `) - - args := make([]interface{}, len(uris)) - for i, uri := range uris { - args[i] = uri - } - - rows, err := db.Query(query, args...) + WHERE uri = ANY($1) + `, pqStringArray(uris)) if err != nil { return nil, err } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } func (db *DB) GetBookmarkURIs(authorDID string) ([]string, error) { - rows, err := db.Query(db.Rebind(` - SELECT uri FROM bookmarks WHERE author_did = ? - `), authorDID) + rows, err := db.Query(` + SELECT uri FROM bookmarks WHERE author_did = $1 + `, authorDID) if err != nil { return nil, err } @@ -461,18 +324,25 @@ } func (db *DB) GetBookmarksByTargetHash(targetHash string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE source_hash = ? + WHERE source_hash = $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), targetHash, limit, offset) + LIMIT $2 OFFSET $3 + `, targetHash, limit, offset) if err != nil { return nil, err } defer rows.Close() + return scanBookmarks(rows) +} + +func scanBookmarks(rows interface { + Next() bool + Scan(...interface{}) error +}) ([]Bookmark, error) { var bookmarks []Bookmark for rows.Next() { var b Bookmark diff --git a/backend/internal/db/queries_collections.go b/backend/internal/db/queries_collections.go --- a/backend/internal/db/queries_collections.go +++ b/backend/internal/db/queries_collections.go @@ -3,25 +3,25 @@ import "time" func (db *DB) CreateCollection(c *Collection) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO collections (uri, author_did, name, description, icon, created_at, indexed_at) - VALUES (?, ?, ?, ?, ?, ?, ?) + VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT(uri) DO UPDATE SET - name = excluded.name, - description = excluded.description, - icon = excluded.icon, - indexed_at = excluded.indexed_at - `), c.URI, c.AuthorDID, c.Name, c.Description, c.Icon, c.CreatedAt, c.IndexedAt) + name = EXCLUDED.name, + description = EXCLUDED.description, + icon = EXCLUDED.icon, + indexed_at = EXCLUDED.indexed_at + `, c.URI, c.AuthorDID, c.Name, c.Description, c.Icon, c.CreatedAt, c.IndexedAt) return err } func (db *DB) GetCollectionsByAuthor(authorDID string) ([]Collection, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, name, description, icon, created_at, indexed_at FROM collections - WHERE author_did = ? + WHERE author_did = $1 ORDER BY created_at DESC - `), authorDID) + `, authorDID) if err != nil { return nil, err } @@ -40,11 +40,11 @@ func (db *DB) GetCollectionByURI(uri string) (*Collection, error) { var c Collection - err := db.QueryRow(db.Rebind(` + err := db.QueryRow(` SELECT uri, author_did, name, description, icon, created_at, indexed_at FROM collections - WHERE uri = ? - `), uri).Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt) + WHERE uri = $1 + `, uri).Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt) if err != nil { return nil, err } @@ -52,30 +52,29 @@ } func (db *DB) DeleteCollection(uri string) error { - - db.Exec(db.Rebind(`DELETE FROM collection_items WHERE collection_uri = ?`), uri) - _, err := db.Exec(db.Rebind(`DELETE FROM collections WHERE uri = ?`), uri) + db.Exec(`DELETE FROM collection_items WHERE collection_uri = $1`, uri) + _, err := db.Exec(`DELETE FROM collections WHERE uri = $1`, uri) return err } func (db *DB) AddToCollection(item *CollectionItem) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO collection_items (uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at) - VALUES (?, ?, ?, ?, ?, ?, ?) + VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT(uri) DO UPDATE SET - position = excluded.position, - indexed_at = excluded.indexed_at - `), item.URI, item.AuthorDID, item.CollectionURI, item.AnnotationURI, item.Position, item.CreatedAt, item.IndexedAt) + position = EXCLUDED.position, + indexed_at = EXCLUDED.indexed_at + `, item.URI, item.AuthorDID, item.CollectionURI, item.AnnotationURI, item.Position, item.CreatedAt, item.IndexedAt) return err } func (db *DB) GetCollectionItems(collectionURI string) ([]CollectionItem, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at FROM collection_items - WHERE collection_uri = ? + WHERE collection_uri = $1 ORDER BY position ASC, created_at DESC - `), collectionURI) + `, collectionURI) if err != nil { return nil, err } @@ -93,127 +92,91 @@ } func (db *DB) RemoveFromCollection(uri string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM collection_items WHERE uri = ?`), uri) + _, err := db.Exec(`DELETE FROM collection_items WHERE uri = $1`, uri) return err } func (db *DB) GetRecentCollectionItems(limit, offset int) ([]CollectionItem, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at FROM collection_items ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } defer rows.Close() - var items []CollectionItem - for rows.Next() { - var item CollectionItem - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { - return nil, err - } - items = append(items, item) - } - return items, nil + return scanCollectionItems(rows) } func (db *DB) GetPopularCollectionItems(limit, offset int) ([]CollectionItem, error) { since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(db.Rebind(` - SELECT - c.uri, c.author_did, c.collection_uri, c.annotation_uri, + rows, err := db.Query(` + SELECT + c.uri, c.author_did, c.collection_uri, c.annotation_uri, c.position, c.created_at, c.indexed_at FROM collection_items c - LEFT JOIN ( - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri - ) l ON l.subject_uri = c.annotation_uri - LEFT JOIN ( - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri - ) r ON r.root_uri = c.annotation_uri - WHERE c.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) > 0 - ORDER BY (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) DESC, c.created_at DESC - LIMIT ? OFFSET ? - `), since, limit, offset) + LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = c.annotation_uri + ) l ON true + LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = c.annotation_uri + ) r ON true + WHERE c.created_at > $1 AND (l.cnt + r.cnt) > 0 + ORDER BY (l.cnt + r.cnt) DESC, c.created_at DESC + LIMIT $2 OFFSET $3 + `, since, limit, offset) if err != nil { return nil, err } defer rows.Close() - var items []CollectionItem - for rows.Next() { - var item CollectionItem - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { - return nil, err - } - items = append(items, item) - } - return items, nil + return scanCollectionItems(rows) } func (db *DB) GetShelvedCollectionItems(limit, offset int) ([]CollectionItem, error) { olderThan := time.Now().AddDate(0, 0, -1) since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(db.Rebind(` - SELECT - c.uri, c.author_did, c.collection_uri, c.annotation_uri, + rows, err := db.Query(` + SELECT + c.uri, c.author_did, c.collection_uri, c.annotation_uri, c.position, c.created_at, c.indexed_at FROM collection_items c - LEFT JOIN ( - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri - ) l ON l.subject_uri = c.annotation_uri - LEFT JOIN ( - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri - ) r ON r.root_uri = c.annotation_uri - WHERE c.created_at < ? AND c.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) = 0 + WHERE c.created_at < $1 AND c.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = c.annotation_uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = c.annotation_uri) ORDER BY RANDOM() - LIMIT ? OFFSET ? - `), olderThan, since, limit, offset) + LIMIT $3 OFFSET $4 + `, olderThan, since, limit, offset) if err != nil { return nil, err } defer rows.Close() - var items []CollectionItem - for rows.Next() { - var item CollectionItem - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { - return nil, err - } - items = append(items, item) - } - return items, nil + return scanCollectionItems(rows) } func (db *DB) GetCollectionItemsByAuthor(authorDID string) ([]CollectionItem, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at FROM collection_items - WHERE author_did = ? + WHERE author_did = $1 ORDER BY created_at DESC - `), authorDID) + `, authorDID) if err != nil { return nil, err } defer rows.Close() - var items []CollectionItem - for rows.Next() { - var item CollectionItem - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { - return nil, err - } - items = append(items, item) - } - return items, nil + return scanCollectionItems(rows) } func (db *DB) GetCollectionURIsForAnnotation(annotationURI string) ([]string, error) { - rows, err := db.Query(db.Rebind(` - SELECT collection_uri FROM collection_items WHERE annotation_uri = ? - `), annotationURI) + rows, err := db.Query(` + SELECT collection_uri FROM collection_items WHERE annotation_uri = $1 + `, annotationURI) if err != nil { return nil, err } @@ -235,19 +198,12 @@ return map[string]int{}, nil } - query := db.Rebind(` + rows, err := db.Query(` SELECT collection_uri, COUNT(*) FROM collection_items - WHERE collection_uri IN (` + buildPlaceholders(len(uris)) + `) + WHERE collection_uri = ANY($1) GROUP BY collection_uri - `) - - args := make([]interface{}, len(uris)) - for i, uri := range uris { - args[i] = uri - } - - rows, err := db.Query(query, args...) + `, pqStringArray(uris)) if err != nil { return nil, err } @@ -270,18 +226,11 @@ return []Collection{}, nil } - query := db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, name, description, icon, created_at, indexed_at FROM collections - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) - `) - - args := make([]interface{}, len(uris)) - for i, uri := range uris { - args[i] = uri - } - - rows, err := db.Query(query, args...) + WHERE uri = ANY($1) + `, pqStringArray(uris)) if err != nil { return nil, err } @@ -296,4 +245,19 @@ collections = append(collections, c) } return collections, nil +} + +func scanCollectionItems(rows interface { + Next() bool + Scan(...interface{}) error +}) ([]CollectionItem, error) { + var items []CollectionItem + for rows.Next() { + var item CollectionItem + if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { + return nil, err + } + items = append(items, item) + } + return items, nil } diff --git a/backend/internal/db/queries_highlights.go b/backend/internal/db/queries_highlights.go --- a/backend/internal/db/queries_highlights.go +++ b/backend/internal/db/queries_highlights.go @@ -5,29 +5,29 @@ ) func (db *DB) CreateHighlight(h *Highlight) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO highlights (uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT(uri) DO UPDATE SET - target_source = excluded.target_source, - target_hash = excluded.target_hash, - target_title = excluded.target_title, - selector_json = excluded.selector_json, - color = excluded.color, - tags_json = excluded.tags_json, - indexed_at = excluded.indexed_at, - cid = excluded.cid - `), h.URI, h.AuthorDID, h.TargetSource, h.TargetHash, h.TargetTitle, h.SelectorJSON, h.Color, h.TagsJSON, h.CreatedAt, h.IndexedAt, h.CID) + target_source = EXCLUDED.target_source, + target_hash = EXCLUDED.target_hash, + target_title = EXCLUDED.target_title, + selector_json = EXCLUDED.selector_json, + color = EXCLUDED.color, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid + `, h.URI, h.AuthorDID, h.TargetSource, h.TargetHash, h.TargetTitle, h.SelectorJSON, h.Color, h.TagsJSON, h.CreatedAt, h.IndexedAt, h.CID) return err } func (db *DB) GetHighlightByURI(uri string) (*Highlight, error) { var h Highlight - err := db.QueryRow(db.Rebind(` + err := db.QueryRow(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE uri = ? - `), uri).Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID) + WHERE uri = $1 + `, uri).Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID) if err != nil { return nil, err } @@ -35,424 +35,286 @@ } func (db *DB) GetRecentHighlights(limit, offset int) ([]Highlight, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetPopularHighlights(limit, offset int) ([]Highlight, error) { since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(db.Rebind(` - SELECT - h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, + rows, err := db.Query(` + SELECT + h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid FROM highlights h - LEFT JOIN ( - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri - ) l ON l.subject_uri = h.uri - LEFT JOIN ( - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri - ) r ON r.root_uri = h.uri - WHERE h.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) > 0 - ORDER BY (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) DESC, h.created_at DESC - LIMIT ? OFFSET ? - `), since, limit, offset) + LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = h.uri + ) l ON true + LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = h.uri + ) r ON true + WHERE h.created_at > $1 AND (l.cnt + r.cnt) > 0 + ORDER BY (l.cnt + r.cnt) DESC, h.created_at DESC + LIMIT $2 OFFSET $3 + `, since, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetShelvedHighlights(limit, offset int) ([]Highlight, error) { olderThan := time.Now().AddDate(0, 0, -1) since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(db.Rebind(` - SELECT - h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, + rows, err := db.Query(` + SELECT + h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid FROM highlights h - LEFT JOIN ( - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri - ) l ON l.subject_uri = h.uri - LEFT JOIN ( - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri - ) r ON r.root_uri = h.uri - WHERE h.created_at < ? AND h.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) = 0 + WHERE h.created_at < $1 AND h.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = h.uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = h.uri) ORDER BY RANDOM() - LIMIT ? OFFSET ? - `), olderThan, since, limit, offset) + LIMIT $3 OFFSET $4 + `, olderThan, since, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetMarginHighlights(limit, offset int) ([]Highlight, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights WHERE uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetSembleHighlights(limit, offset int) ([]Highlight, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights WHERE uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE tags_json LIKE ? + WHERE tags_json::jsonb ? $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), pattern, limit, offset) + LIMIT $2 OFFSET $3 + `, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetMarginHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' + WHERE tags_json::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), pattern, limit, offset) + LIMIT $2 OFFSET $3 + `, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetSembleHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE tags_json LIKE ? AND uri LIKE '%network.cosmik%' + WHERE tags_json::jsonb ? $1 AND uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), pattern, limit, offset) + LIMIT $2 OFFSET $3 + `, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE author_did = ? AND tags_json LIKE ? + WHERE author_did = $1 AND tags_json::jsonb ? $2 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, pattern, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetMarginHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE author_did = ? AND tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, pattern, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetSembleHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { - pattern := "%\"" + tag + "\"%" - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE author_did = ? AND tags_json LIKE ? AND uri LIKE '%network.cosmik%' + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, pattern, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, tag, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetHighlightsByTargetHash(targetHash string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE target_hash = ? + WHERE target_hash = $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), targetHash, limit, offset) + LIMIT $2 OFFSET $3 + `, targetHash, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE author_did = ? + WHERE author_did = $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, limit, offset) + LIMIT $2 OFFSET $3 + `, authorDID, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetMarginHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE author_did = ? AND uri NOT LIKE '%network.cosmik%' + WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, limit, offset) + LIMIT $2 OFFSET $3 + `, authorDID, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetSembleHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE author_did = ? AND uri LIKE '%network.cosmik%' + WHERE author_did = $1 AND uri LIKE '%network.cosmik%' ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, limit, offset) + LIMIT $2 OFFSET $3 + `, authorDID, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetHighlightsByAuthorAndTargetHash(authorDID, targetHash string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE author_did = ? AND target_hash = ? + WHERE author_did = $1 AND target_hash = $2 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), authorDID, targetHash, limit, offset) + LIMIT $3 OFFSET $4 + `, authorDID, targetHash, limit, offset) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) DeleteHighlight(uri string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM highlights WHERE uri = ?`), uri) + _, err := db.Exec(`DELETE FROM highlights WHERE uri = $1`, uri) return err } func (db *DB) UpdateHighlight(uri, color, tagsJSON, cid string) error { - _, err := db.Exec(db.Rebind(` - UPDATE highlights - SET color = ?, tags_json = ?, cid = ?, indexed_at = ? - WHERE uri = ? - `), color, tagsJSON, cid, time.Now(), uri) + _, err := db.Exec(` + UPDATE highlights + SET color = $1, tags_json = $2, cid = $3, indexed_at = $4 + WHERE uri = $5 + `, color, tagsJSON, cid, time.Now(), uri) return err } @@ -461,38 +323,23 @@ return []Highlight{}, nil } - query := db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) - `) - - args := make([]interface{}, len(uris)) - for i, uri := range uris { - args[i] = uri - } - - rows, err := db.Query(query, args...) + WHERE uri = ANY($1) + `, pqStringArray(uris)) if err != nil { return nil, err } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) GetHighlightURIs(authorDID string) ([]string, error) { - rows, err := db.Query(db.Rebind(` - SELECT uri FROM highlights WHERE author_did = ? - `), authorDID) + rows, err := db.Query(` + SELECT uri FROM highlights WHERE author_did = $1 + `, authorDID) if err != nil { return nil, err } @@ -507,4 +354,19 @@ uris = append(uris, uri) } return uris, nil +} + +func scanHighlights(rows interface { + Next() bool + Scan(...interface{}) error +}) ([]Highlight, error) { + var highlights []Highlight + for rows.Next() { + var h Highlight + if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { + return nil, err + } + highlights = append(highlights, h) + } + return highlights, nil } diff --git a/backend/internal/db/queries_history.go b/backend/internal/db/queries_history.go --- a/backend/internal/db/queries_history.go +++ b/backend/internal/db/queries_history.go @@ -7,20 +7,20 @@ ) func (db *DB) SaveEditHistory(uri, recordType, previousContent string, previousCID *string) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO edit_history (uri, record_type, previous_content, previous_cid, edited_at) - VALUES (?, ?, ?, ?, ?) - `), uri, recordType, previousContent, previousCID, time.Now()) + VALUES ($1, $2, $3, $4, $5) + `, uri, recordType, previousContent, previousCID, time.Now()) return err } func (db *DB) GetEditHistory(uri string) ([]EditHistory, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT id, uri, record_type, previous_content, previous_cid, edited_at FROM edit_history - WHERE uri = ? + WHERE uri = $1 ORDER BY edited_at DESC - `), uri) + `, uri) if err != nil { return nil, err } @@ -29,28 +29,9 @@ var history []EditHistory for rows.Next() { var h EditHistory - var editedAt interface{} - if err := rows.Scan(&h.ID, &h.URI, &h.RecordType, &h.PreviousContent, &h.PreviousCID, &editedAt); err != nil { + if err := rows.Scan(&h.ID, &h.URI, &h.RecordType, &h.PreviousContent, &h.PreviousCID, &h.EditedAt); err != nil { return nil, err } - - switch v := editedAt.(type) { - case time.Time: - h.EditedAt = v - case []byte: - parsed, err := parseTime(string(v)) - if err != nil { - return nil, err - } - h.EditedAt = parsed - case string: - parsed, err := parseTime(v) - if err != nil { - return nil, err - } - h.EditedAt = parsed - } - history = append(history, h) } return history, nil @@ -61,33 +42,21 @@ return nil, nil } - query := ` - SELECT uri, MAX(edited_at) as edited_at - FROM edit_history - WHERE uri IN (` - args := make([]interface{}, len(uris)) placeholders := make([]string, len(uris)) - + args := make([]interface{}, len(uris)) for i, uri := range uris { placeholders[i] = fmt.Sprintf("$%d", i+1) args[i] = uri } - query += strings.Join(placeholders, ",") + ") GROUP BY uri" - - if db.driver == "sqlite3" { - query = strings.ReplaceAll(query, "$", "?") - placeholders = make([]string, len(uris)) - for i := range uris { - placeholders[i] = "?" - } - query = ` + query := ` SELECT uri, MAX(edited_at) as edited_at FROM edit_history - WHERE uri IN (` + strings.Join(placeholders, ",") + ") GROUP BY uri" - } + WHERE uri IN (` + strings.Join(placeholders, ",") + `) + GROUP BY uri + ` - rows, err := db.Query(db.Rebind(query), args...) + rows, err := db.Query(query, args...) if err != nil { return nil, err } @@ -96,49 +65,12 @@ result := make(map[string]time.Time) for rows.Next() { var uri string - var editedAt interface{} + var editedAt time.Time if err := rows.Scan(&uri, &editedAt); err != nil { continue } - - var finalTime time.Time - switch v := editedAt.(type) { - case time.Time: - finalTime = v - case []byte: - parsed, err := parseTime(string(v)) - if err != nil { - continue - } - finalTime = parsed - case string: - parsed, err := parseTime(v) - if err != nil { - continue - } - finalTime = parsed - default: - continue - } - - result[uri] = finalTime + result[uri] = editedAt } return result, nil -} - -func parseTime(s string) (time.Time, error) { - formats := []string{ - time.RFC3339, - time.RFC3339Nano, - "2006-01-02 15:04:05.999999999-07:00", - "2006-01-02 15:04:05", - } - - for _, f := range formats { - if t, err := time.Parse(f, s); err == nil { - return t, nil - } - } - return time.Time{}, fmt.Errorf("could not parse time: %s", s) } diff --git a/backend/internal/db/queries_keys.go b/backend/internal/db/queries_keys.go --- a/backend/internal/db/queries_keys.go +++ b/backend/internal/db/queries_keys.go @@ -5,25 +5,25 @@ ) func (db *DB) CreateAPIKey(key *APIKey) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO api_keys (id, owner_did, name, key_hash, created_at, uri, cid) - VALUES (?, ?, ?, ?, ?, ?, ?) + VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, key_hash = EXCLUDED.key_hash, uri = EXCLUDED.uri, cid = EXCLUDED.cid - `), key.ID, key.OwnerDID, key.Name, key.KeyHash, key.CreatedAt, key.URI, key.CID) + `, key.ID, key.OwnerDID, key.Name, key.KeyHash, key.CreatedAt, key.URI, key.CID) return err } func (db *DB) GetAPIKeysByOwner(ownerDID string) ([]APIKey, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT id, owner_did, name, key_hash, created_at, last_used_at FROM api_keys - WHERE owner_did = ? + WHERE owner_did = $1 ORDER BY created_at DESC - `), ownerDID) + `, ownerDID) if err != nil { return nil, err } @@ -42,11 +42,11 @@ func (db *DB) GetAPIKeyByHash(keyHash string) (*APIKey, error) { var k APIKey - err := db.QueryRow(db.Rebind(` + err := db.QueryRow(` SELECT id, owner_did, name, key_hash, created_at, last_used_at FROM api_keys - WHERE key_hash = ? - `), keyHash).Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt) + WHERE key_hash = $1 + `, keyHash).Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt) if err != nil { return nil, err } @@ -54,6 +54,6 @@ } func (db *DB) UpdateAPIKeyLastUsed(id string) error { - _, err := db.Exec(db.Rebind(`UPDATE api_keys SET last_used_at = ? WHERE id = ?`), time.Now(), id) + _, err := db.Exec(`UPDATE api_keys SET last_used_at = $1 WHERE id = $2`, time.Now(), id) return err } diff --git a/backend/internal/db/queries_likes.go b/backend/internal/db/queries_likes.go --- a/backend/internal/db/queries_likes.go +++ b/backend/internal/db/queries_likes.go @@ -1,26 +1,28 @@ package db +import "fmt" + func (db *DB) CreateLike(l *Like) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO likes (uri, author_did, subject_uri, created_at, indexed_at) - VALUES (?, ?, ?, ?, ?) + VALUES ($1, $2, $3, $4, $5) ON CONFLICT(uri) DO NOTHING - `), l.URI, l.AuthorDID, l.SubjectURI, l.CreatedAt, l.IndexedAt) + `, l.URI, l.AuthorDID, l.SubjectURI, l.CreatedAt, l.IndexedAt) return err } func (db *DB) DeleteLike(uri string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM likes WHERE uri = ?`), uri) + _, err := db.Exec(`DELETE FROM likes WHERE uri = $1`, uri) return err } func (db *DB) GetLikesByAuthor(authorDID string) ([]Like, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, subject_uri, created_at, indexed_at FROM likes - WHERE author_did = ? + WHERE author_did = $1 ORDER BY created_at DESC - `), authorDID) + `, authorDID) if err != nil { return nil, err } @@ -39,17 +41,17 @@ func (db *DB) GetLikeCount(subjectURI string) (int, error) { var count int - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM likes WHERE subject_uri = ?`), subjectURI).Scan(&count) + err := db.QueryRow(`SELECT COUNT(*) FROM likes WHERE subject_uri = $1`, subjectURI).Scan(&count) return count, err } func (db *DB) GetLikeByUserAndSubject(userDID, subjectURI string) (*Like, error) { var like Like - err := db.QueryRow(db.Rebind(` + err := db.QueryRow(` SELECT uri, author_did, subject_uri, created_at, indexed_at FROM likes - WHERE author_did = ? AND subject_uri = ? - `), userDID, subjectURI).Scan(&like.URI, &like.AuthorDID, &like.SubjectURI, &like.CreatedAt, &like.IndexedAt) + WHERE author_did = $1 AND subject_uri = $2 + `, userDID, subjectURI).Scan(&like.URI, &like.AuthorDID, &like.SubjectURI, &like.CreatedAt, &like.IndexedAt) if err != nil { return nil, err } @@ -61,12 +63,12 @@ return map[string]int{}, nil } - query := db.Rebind(` - SELECT subject_uri, COUNT(*) - FROM likes - WHERE subject_uri IN (` + buildPlaceholders(len(subjectURIs)) + `) + query := ` + SELECT subject_uri, COUNT(*) + FROM likes + WHERE subject_uri IN (` + buildPlaceholders(len(subjectURIs), 1) + `) GROUP BY subject_uri - `) + ` args := make([]interface{}, len(subjectURIs)) for i, uri := range subjectURIs { @@ -97,11 +99,11 @@ return map[string]bool{}, nil } - query := db.Rebind(` - SELECT subject_uri - FROM likes - WHERE author_did = ? AND subject_uri IN (` + buildPlaceholders(len(subjectURIs)) + `) - `) + query := fmt.Sprintf(` + SELECT subject_uri + FROM likes + WHERE author_did = $1 AND subject_uri IN (%s) + `, buildPlaceholders(len(subjectURIs), 2)) args := make([]interface{}, len(subjectURIs)+1) args[0] = viewerDID diff --git a/backend/internal/db/queries_moderation.go b/backend/internal/db/queries_moderation.go --- a/backend/internal/db/queries_moderation.go +++ b/backend/internal/db/queries_moderation.go @@ -1,21 +1,26 @@ package db -import "time" +import ( + "fmt" + "strings" + "time" +) func (db *DB) CreateBlock(actorDID, subjectDID string) error { - query := `INSERT INTO blocks (actor_did, subject_did, created_at) VALUES (?, ?, ?) - ON CONFLICT(actor_did, subject_did) DO NOTHING` - _, err := db.Exec(db.Rebind(query), actorDID, subjectDID, time.Now()) + _, err := db.Exec(` + INSERT INTO blocks (actor_did, subject_did, created_at) VALUES ($1, $2, $3) + ON CONFLICT(actor_did, subject_did) DO NOTHING + `, actorDID, subjectDID, time.Now()) return err } func (db *DB) DeleteBlock(actorDID, subjectDID string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM blocks WHERE actor_did = ? AND subject_did = ?`), actorDID, subjectDID) + _, err := db.Exec(`DELETE FROM blocks WHERE actor_did = $1 AND subject_did = $2`, actorDID, subjectDID) return err } func (db *DB) GetBlocks(actorDID string) ([]Block, error) { - rows, err := db.Query(db.Rebind(`SELECT id, actor_did, subject_did, created_at FROM blocks WHERE actor_did = ? ORDER BY created_at DESC`), actorDID) + rows, err := db.Query(`SELECT id, actor_did, subject_did, created_at FROM blocks WHERE actor_did = $1 ORDER BY created_at DESC`, actorDID) if err != nil { return nil, err } @@ -33,19 +38,19 @@ } func (db *DB) IsBlocked(actorDID, subjectDID string) (bool, error) { - var count int - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM blocks WHERE actor_did = ? AND subject_did = ?`), actorDID, subjectDID).Scan(&count) - return count > 0, err + var exists bool + err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM blocks WHERE actor_did = $1 AND subject_did = $2)`, actorDID, subjectDID).Scan(&exists) + return exists, err } func (db *DB) IsBlockedEither(did1, did2 string) (bool, error) { - var count int - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM blocks WHERE (actor_did = ? AND subject_did = ?) OR (actor_did = ? AND subject_did = ?)`), did1, did2, did2, did1).Scan(&count) - return count > 0, err + var exists bool + err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM blocks WHERE (actor_did = $1 AND subject_did = $2) OR (actor_did = $2 AND subject_did = $1))`, did1, did2).Scan(&exists) + return exists, err } func (db *DB) GetBlockedDIDs(actorDID string) ([]string, error) { - rows, err := db.Query(db.Rebind(`SELECT subject_did FROM blocks WHERE actor_did = ?`), actorDID) + rows, err := db.Query(`SELECT subject_did FROM blocks WHERE actor_did = $1`, actorDID) if err != nil { return nil, err } @@ -63,7 +68,7 @@ } func (db *DB) GetBlockedByDIDs(actorDID string) ([]string, error) { - rows, err := db.Query(db.Rebind(`SELECT actor_did FROM blocks WHERE subject_did = ?`), actorDID) + rows, err := db.Query(`SELECT actor_did FROM blocks WHERE subject_did = $1`, actorDID) if err != nil { return nil, err } @@ -81,19 +86,20 @@ } func (db *DB) CreateMute(actorDID, subjectDID string) error { - query := `INSERT INTO mutes (actor_did, subject_did, created_at) VALUES (?, ?, ?) - ON CONFLICT(actor_did, subject_did) DO NOTHING` - _, err := db.Exec(db.Rebind(query), actorDID, subjectDID, time.Now()) + _, err := db.Exec(` + INSERT INTO mutes (actor_did, subject_did, created_at) VALUES ($1, $2, $3) + ON CONFLICT(actor_did, subject_did) DO NOTHING + `, actorDID, subjectDID, time.Now()) return err } func (db *DB) DeleteMute(actorDID, subjectDID string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM mutes WHERE actor_did = ? AND subject_did = ?`), actorDID, subjectDID) + _, err := db.Exec(`DELETE FROM mutes WHERE actor_did = $1 AND subject_did = $2`, actorDID, subjectDID) return err } func (db *DB) GetMutes(actorDID string) ([]Mute, error) { - rows, err := db.Query(db.Rebind(`SELECT id, actor_did, subject_did, created_at FROM mutes WHERE actor_did = ? ORDER BY created_at DESC`), actorDID) + rows, err := db.Query(`SELECT id, actor_did, subject_did, created_at FROM mutes WHERE actor_did = $1 ORDER BY created_at DESC`, actorDID) if err != nil { return nil, err } @@ -111,13 +117,13 @@ } func (db *DB) IsMuted(actorDID, subjectDID string) (bool, error) { - var count int - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM mutes WHERE actor_did = ? AND subject_did = ?`), actorDID, subjectDID).Scan(&count) - return count > 0, err + var exists bool + err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM mutes WHERE actor_did = $1 AND subject_did = $2)`, actorDID, subjectDID).Scan(&exists) + return exists, err } func (db *DB) GetMutedDIDs(actorDID string) ([]string, error) { - rows, err := db.Query(db.Rebind(`SELECT subject_did FROM mutes WHERE actor_did = ?`), actorDID) + rows, err := db.Query(`SELECT subject_did FROM mutes WHERE actor_did = $1`, actorDID) if err != nil { return nil, err } @@ -187,32 +193,31 @@ } func (db *DB) CreateReport(reporterDID, subjectDID string, subjectURI *string, reasonType string, reasonText *string) (int, error) { - query := `INSERT INTO moderation_reports (reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at) - VALUES (?, ?, ?, ?, ?, 'pending', ?)` - - result, err := db.Exec(db.Rebind(query), reporterDID, subjectDID, subjectURI, reasonType, reasonText, time.Now()) - if err != nil { - return 0, err - } - - id, err := result.LastInsertId() - return int(id), err + var id int + err := db.QueryRow(` + INSERT INTO moderation_reports (reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at) + VALUES ($1, $2, $3, $4, $5, 'pending', $6) + RETURNING id + `, reporterDID, subjectDID, subjectURI, reasonType, reasonText, time.Now()).Scan(&id) + return id, err } func (db *DB) GetReports(status string, limit, offset int) ([]ModerationReport, error) { query := `SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by FROM moderation_reports` args := []interface{}{} + paramIdx := 1 if status != "" { - query += ` WHERE status = ?` + query += ` WHERE status = $1` args = append(args, status) + paramIdx = 2 } - query += ` ORDER BY created_at DESC LIMIT ? OFFSET ?` + query += ` ORDER BY created_at DESC LIMIT $` + itoa(paramIdx) + ` OFFSET $` + itoa(paramIdx+1) args = append(args, limit, offset) - rows, err := db.Query(db.Rebind(query), args...) + rows, err := db.Query(query, args...) if err != nil { return nil, err } @@ -231,7 +236,7 @@ func (db *DB) GetReport(id int) (*ModerationReport, error) { var r ModerationReport - err := db.QueryRow(db.Rebind(`SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by FROM moderation_reports WHERE id = ?`), id).Scan( + err := db.QueryRow(`SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by FROM moderation_reports WHERE id = $1`, id).Scan( &r.ID, &r.ReporterDID, &r.SubjectDID, &r.SubjectURI, &r.ReasonType, &r.ReasonText, &r.Status, &r.CreatedAt, &r.ResolvedAt, &r.ResolvedBy, ) if err != nil { @@ -241,18 +246,17 @@ } func (db *DB) ResolveReport(id int, resolvedBy string, status string) error { - _, err := db.Exec(db.Rebind(`UPDATE moderation_reports SET status = ?, resolved_at = ?, resolved_by = ? WHERE id = ?`), status, time.Now(), resolvedBy, id) + _, err := db.Exec(`UPDATE moderation_reports SET status = $1, resolved_at = $2, resolved_by = $3 WHERE id = $4`, status, time.Now(), resolvedBy, id) return err } func (db *DB) CreateModerationAction(reportID int, actorDID, action string, comment *string) error { - query := `INSERT INTO moderation_actions (report_id, actor_did, action, comment, created_at) VALUES (?, ?, ?, ?, ?)` - _, err := db.Exec(db.Rebind(query), reportID, actorDID, action, comment, time.Now()) + _, err := db.Exec(`INSERT INTO moderation_actions (report_id, actor_did, action, comment, created_at) VALUES ($1, $2, $3, $4, $5)`, reportID, actorDID, action, comment, time.Now()) return err } func (db *DB) GetReportActions(reportID int) ([]ModerationAction, error) { - rows, err := db.Query(db.Rebind(`SELECT id, report_id, actor_did, action, comment, created_at FROM moderation_actions WHERE report_id = ? ORDER BY created_at DESC`), reportID) + rows, err := db.Query(`SELECT id, report_id, actor_did, action, comment, created_at FROM moderation_actions WHERE report_id = $1 ORDER BY created_at DESC`, reportID) if err != nil { return nil, err } @@ -273,22 +277,21 @@ query := `SELECT COUNT(*) FROM moderation_reports` args := []interface{}{} if status != "" { - query += ` WHERE status = ?` + query += ` WHERE status = $1` args = append(args, status) } var count int - err := db.QueryRow(db.Rebind(query), args...).Scan(&count) + err := db.QueryRow(query, args...).Scan(&count) return count, err } func (db *DB) CreateContentLabel(src, uri, val, createdBy string) error { - query := `INSERT INTO content_labels (src, uri, val, neg, created_by, created_at) VALUES (?, ?, ?, 0, ?, ?)` - _, err := db.Exec(db.Rebind(query), src, uri, val, createdBy, time.Now()) + _, err := db.Exec(`INSERT INTO content_labels (src, uri, val, neg, created_by, created_at) VALUES ($1, $2, $3, 0, $4, $5)`, src, uri, val, createdBy, time.Now()) return err } func (db *DB) SyncSelfLabels(authorDID, uri string, labels []string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM content_labels WHERE src = ? AND uri = ? AND created_by = ?`), authorDID, uri, authorDID) + _, err := db.Exec(`DELETE FROM content_labels WHERE src = $1 AND uri = $2 AND created_by = $3`, authorDID, uri, authorDID) if err != nil { return err } @@ -301,12 +304,12 @@ } func (db *DB) NegateContentLabel(id int) error { - _, err := db.Exec(db.Rebind(`UPDATE content_labels SET neg = 1 WHERE id = ?`), id) + _, err := db.Exec(`UPDATE content_labels SET neg = 1 WHERE id = $1`, id) return err } func (db *DB) DeleteContentLabel(id int) error { - _, err := db.Exec(db.Rebind(`DELETE FROM content_labels WHERE id = ?`), id) + _, err := db.Exec(`DELETE FROM content_labels WHERE id = $1`, id) return err } @@ -316,28 +319,18 @@ return result, nil } - placeholders := make([]string, len(uris)) - args := make([]interface{}, len(uris)) - for i, uri := range uris { - placeholders[i] = "?" - args[i] = uri - } - query := `SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels - WHERE uri IN (` + joinStrings(placeholders, ",") + `) AND neg = 0` + WHERE uri = ANY($1) AND neg = 0` + args := []interface{}{pqStringArray(uris)} if len(labelerDIDs) > 0 { - srcPlaceholders := make([]string, len(labelerDIDs)) - for i, did := range labelerDIDs { - srcPlaceholders[i] = "?" - args = append(args, did) - } - query += ` AND src IN (` + joinStrings(srcPlaceholders, ",") + `)` + query += ` AND src = ANY($2)` + args = append(args, pqStringArray(labelerDIDs)) } query += ` ORDER BY created_at DESC` - rows, err := db.Query(db.Rebind(query), args...) + rows, err := db.Query(query, args...) if err != nil { return result, err } @@ -359,28 +352,18 @@ return result, nil } - placeholders := make([]string, len(dids)) - args := make([]interface{}, len(dids)) - for i, did := range dids { - placeholders[i] = "?" - args[i] = did - } - query := `SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels - WHERE uri IN (` + joinStrings(placeholders, ",") + `) AND neg = 0` + WHERE uri = ANY($1) AND neg = 0` + args := []interface{}{pqStringArray(dids)} if len(labelerDIDs) > 0 { - srcPlaceholders := make([]string, len(labelerDIDs)) - for i, did := range labelerDIDs { - srcPlaceholders[i] = "?" - args = append(args, did) - } - query += ` AND src IN (` + joinStrings(srcPlaceholders, ",") + `)` + query += ` AND src = ANY($2)` + args = append(args, pqStringArray(labelerDIDs)) } query += ` ORDER BY created_at DESC` - rows, err := db.Query(db.Rebind(query), args...) + rows, err := db.Query(query, args...) if err != nil { return result, err } @@ -397,7 +380,7 @@ } func (db *DB) GetAllContentLabels(limit, offset int) ([]ContentLabel, error) { - rows, err := db.Query(db.Rebind(`SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels ORDER BY created_at DESC LIMIT ? OFFSET ?`), limit, offset) + rows, err := db.Query(`SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels ORDER BY created_at DESC LIMIT $1 OFFSET $2`, limit, offset) if err != nil { return nil, err } @@ -414,13 +397,6 @@ return labels, nil } -func joinStrings(strs []string, sep string) string { - result := "" - for i, s := range strs { - if i > 0 { - result += sep - } - result += s - } - return result +func itoa(i int) string { + return strings.Repeat("", 0) + fmt.Sprintf("%d", i) } diff --git a/backend/internal/db/queries_notifications.go b/backend/internal/db/queries_notifications.go --- a/backend/internal/db/queries_notifications.go +++ b/backend/internal/db/queries_notifications.go @@ -5,21 +5,21 @@ ) func (db *DB) CreateNotification(n *Notification) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO notifications (recipient_did, actor_did, type, subject_uri, created_at) - VALUES (?, ?, ?, ?, ?) - `), n.RecipientDID, n.ActorDID, n.Type, n.SubjectURI, n.CreatedAt) + VALUES ($1, $2, $3, $4, $5) + `, n.RecipientDID, n.ActorDID, n.Type, n.SubjectURI, n.CreatedAt) return err } func (db *DB) GetNotifications(recipientDID string, limit, offset int) ([]Notification, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT id, recipient_did, actor_did, type, subject_uri, created_at, read_at FROM notifications - WHERE recipient_did = ? + WHERE recipient_did = $1 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `), recipientDID, limit, offset) + LIMIT $2 OFFSET $3 + `, recipientDID, limit, offset) if err != nil { return nil, err } @@ -38,15 +38,15 @@ func (db *DB) GetUnreadNotificationCount(recipientDID string) (int, error) { var count int - err := db.QueryRow(db.Rebind(` - SELECT COUNT(*) FROM notifications WHERE recipient_did = ? AND read_at IS NULL - `), recipientDID).Scan(&count) + err := db.QueryRow(` + SELECT COUNT(*) FROM notifications WHERE recipient_did = $1 AND read_at IS NULL + `, recipientDID).Scan(&count) return count, err } func (db *DB) MarkNotificationsRead(recipientDID string) error { - _, err := db.Exec(db.Rebind(` - UPDATE notifications SET read_at = ? WHERE recipient_did = ? AND read_at IS NULL - `), time.Now(), recipientDID) + _, err := db.Exec(` + UPDATE notifications SET read_at = $1 WHERE recipient_did = $2 AND read_at IS NULL + `, time.Now(), recipientDID) return err } diff --git a/backend/internal/db/queries_recommendations.go b/backend/internal/db/queries_recommendations.go --- a/backend/internal/db/queries_recommendations.go +++ b/backend/internal/db/queries_recommendations.go @@ -55,16 +55,11 @@ } func (db *DB) MigrateRecommendations() error { - dateType := "TIMESTAMP" - if db.driver == "sqlite3" { - dateType = "DATETIME" - } - _, err := db.Exec(` CREATE TABLE IF NOT EXISTS document_embeddings ( document_uri TEXT PRIMARY KEY, embedding TEXT NOT NULL, - updated_at ` + dateType + ` NOT NULL + updated_at TIMESTAMP NOT NULL )`) if err != nil { return fmt.Errorf("create document_embeddings table: %w", err) @@ -76,7 +71,7 @@ author_did TEXT NOT NULL, document_uri TEXT, embedding TEXT NOT NULL, - updated_at ` + dateType + ` NOT NULL + updated_at TIMESTAMP NOT NULL )`) if err != nil { return fmt.Errorf("create annotation_embeddings table: %w", err) @@ -90,7 +85,7 @@ embedding TEXT NOT NULL, tag_affinities TEXT DEFAULT '{}', annotation_count INTEGER NOT NULL DEFAULT 0, - updated_at ` + dateType + ` NOT NULL + updated_at TIMESTAMP NOT NULL )`) if err != nil { return fmt.Errorf("create user_profiles table: %w", err) @@ -154,7 +149,7 @@ func (db *DB) GetDocumentByCanonicalURL(canonicalURL string) (*Document, error) { var d Document err := db.QueryRow( - `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at + `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at FROM documents WHERE canonical_url = $1`, canonicalURL, ).Scan(&d.URI, &d.AuthorDID, &d.Site, &d.Path, &d.Title, &d.Description, &d.TextContent, &d.TagsJSON, &d.CanonicalURL, &d.PublishedAt, &d.IndexedAt) @@ -167,7 +162,7 @@ func (db *DB) GetDocumentByURI(uri string) (*Document, error) { var d Document err := db.QueryRow( - `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at + `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at FROM documents WHERE uri = $1`, uri, ).Scan(&d.URI, &d.AuthorDID, &d.Site, &d.Path, &d.Title, &d.Description, &d.TextContent, &d.TagsJSON, &d.CanonicalURL, &d.PublishedAt, &d.IndexedAt) @@ -178,14 +173,14 @@ } func (db *DB) GetDocumentsWithoutEmbeddings(limit int) ([]Document, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at FROM documents d LEFT JOIN document_embeddings de ON d.uri = de.document_uri WHERE de.document_uri IS NULL ORDER BY d.indexed_at DESC - LIMIT ? - `), limit) + LIMIT $1 + `, limit) if err != nil { return nil, err } @@ -194,14 +189,14 @@ } func (db *DB) GetAnnotationsWithoutEmbeddings(limit int) ([]Annotation, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT a.uri, a.author_did, a.motivation, a.body_value, a.body_format, a.body_uri, a.target_source, a.target_hash, a.target_title, a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid FROM annotations a LEFT JOIN annotation_embeddings ae ON a.uri = ae.annotation_uri WHERE ae.annotation_uri IS NULL AND a.motivation IN ('commenting', 'highlighting') ORDER BY a.created_at DESC - LIMIT ? - `), limit) + LIMIT $1 + `, limit) if err != nil { return nil, err } @@ -219,14 +214,14 @@ } func (db *DB) GetHighlightsWithoutEmbeddings(limit int) ([]HighlightForEmbedding, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT h.uri, h.author_did, h.target_source, h.target_title, h.selector_json, h.tags_json FROM highlights h LEFT JOIN annotation_embeddings ae ON h.uri = ae.annotation_uri WHERE ae.annotation_uri IS NULL ORDER BY h.created_at DESC - LIMIT ? - `), limit) + LIMIT $1 + `, limit) if err != nil { return nil, err } @@ -276,12 +271,12 @@ } func (db *DB) GetRecentDocuments(limit, offset int) ([]Document, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at FROM documents ORDER BY published_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } @@ -290,14 +285,14 @@ } func (db *DB) GetPopularDocuments(limit, offset int) ([]Document, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at FROM documents d LEFT JOIN annotations a ON a.target_source = d.canonical_url GROUP BY d.uri ORDER BY COUNT(a.uri) DESC, d.published_at DESC - LIMIT ? OFFSET ? - `), limit, offset) + LIMIT $1 OFFSET $2 + `, limit, offset) if err != nil { return nil, err } @@ -386,7 +381,7 @@ func (db *DB) GetRecentAnnotationEmbeddingsByAuthor(authorDID string, limit int) ([]AnnotationEmbedding, error) { rows, err := db.Query( - db.Rebind(`SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = ? ORDER BY updated_at DESC LIMIT ?`), + `SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = $1 ORDER BY updated_at DESC LIMIT $2`, authorDID, limit, ) if err != nil { @@ -422,11 +417,8 @@ } func (db *DB) GetCandidateDocuments(userDID string, limit int) ([]CandidateDocument, error) { - // Note: We use NOT LIKE instead of !~* for cross-database compatibility and performance. - // The engagement count sub-select is also constrained to recent elements if possible, but - // for now we just optimize the regex and exact grouping. - rows, err := db.Query(db.Rebind(` - SELECT + rows, err := db.Query(` + SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.tags_json, d.canonical_url, d.published_at, de.embedding, COALESCE(eng.cnt, 0) AS engagement @@ -439,7 +431,7 @@ GROUP BY document_uri ) eng ON eng.document_uri = d.uri LEFT JOIN publications p ON d.site = p.uri OR d.site = p.url - WHERE d.author_did != ? + WHERE d.author_did != $1 AND (p.show_in_discover IS NULL OR p.show_in_discover = true) AND LENGTH(d.title) > 15 AND (LENGTH(COALESCE(d.description, '')) >= 30 OR LENGTH(COALESCE(d.text_content, '')) >= 100) @@ -453,11 +445,11 @@ AND LOWER(d.title) NOT LIKE '%placeholder%' AND d.uri NOT IN ( SELECT DISTINCT document_uri FROM annotation_embeddings - WHERE author_did = ? AND document_uri IS NOT NULL + WHERE author_did = $2 AND document_uri IS NOT NULL ) ORDER BY d.published_at DESC - LIMIT ? - `), userDID, userDID, limit) + LIMIT $3 + `, userDID, userDID, limit) if err != nil { return nil, fmt.Errorf("candidate query: %w", err) } diff --git a/backend/internal/db/queries_replies.go b/backend/internal/db/queries_replies.go --- a/backend/internal/db/queries_replies.go +++ b/backend/internal/db/queries_replies.go @@ -1,48 +1,40 @@ package db func (db *DB) CreateReply(r *Reply) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO replies (uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT(uri) DO UPDATE SET - text = excluded.text, - format = excluded.format, - indexed_at = excluded.indexed_at, - cid = excluded.cid - `), r.URI, r.AuthorDID, r.ParentURI, r.RootURI, r.Text, r.Format, r.CreatedAt, r.IndexedAt, r.CID) + text = EXCLUDED.text, + format = EXCLUDED.format, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid + `, r.URI, r.AuthorDID, r.ParentURI, r.RootURI, r.Text, r.Format, r.CreatedAt, r.IndexedAt, r.CID) return err } func (db *DB) GetRepliesByRoot(rootURI string) ([]Reply, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid FROM replies - WHERE root_uri = ? + WHERE root_uri = $1 ORDER BY created_at ASC - `), rootURI) + `, rootURI) if err != nil { return nil, err } defer rows.Close() - var replies []Reply - for rows.Next() { - var r Reply - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { - return nil, err - } - replies = append(replies, r) - } - return replies, nil + return scanReplies(rows) } func (db *DB) GetReplyByURI(uri string) (*Reply, error) { var r Reply - err := db.QueryRow(db.Rebind(` + err := db.QueryRow(` SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid FROM replies - WHERE uri = ? - `), uri).Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID) + WHERE uri = $1 + `, uri).Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID) if err != nil { return nil, err } @@ -50,59 +42,43 @@ } func (db *DB) DeleteReply(uri string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM replies WHERE uri = ?`), uri) + _, err := db.Exec(`DELETE FROM replies WHERE uri = $1`, uri) return err } func (db *DB) GetRepliesByAuthor(authorDID string) ([]Reply, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid FROM replies - WHERE author_did = ? + WHERE author_did = $1 ORDER BY created_at DESC - `), authorDID) + `, authorDID) if err != nil { return nil, err } defer rows.Close() - var replies []Reply - for rows.Next() { - var r Reply - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { - return nil, err - } - replies = append(replies, r) - } - return replies, nil + return scanReplies(rows) } func (db *DB) GetOrphanedRepliesByAuthor(authorDID string) ([]Reply, error) { - rows, err := db.Query(db.Rebind(` + rows, err := db.Query(` SELECT r.uri, r.author_did, r.parent_uri, r.root_uri, r.text, r.format, r.created_at, r.indexed_at, r.cid FROM replies r LEFT JOIN annotations a ON r.root_uri = a.uri - WHERE r.author_did = ? AND a.uri IS NULL - `), authorDID) + WHERE r.author_did = $1 AND a.uri IS NULL + `, authorDID) if err != nil { return nil, err } defer rows.Close() - var replies []Reply - for rows.Next() { - var r Reply - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { - return nil, err - } - replies = append(replies, r) - } - return replies, nil + return scanReplies(rows) } func (db *DB) GetReplyCount(rootURI string) (int, error) { var count int - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM replies WHERE root_uri = ?`), rootURI).Scan(&count) + err := db.QueryRow(`SELECT COUNT(*) FROM replies WHERE root_uri = $1`, rootURI).Scan(&count) return count, err } @@ -111,19 +87,14 @@ return map[string]int{}, nil } - query := db.Rebind(` - SELECT root_uri, COUNT(*) - FROM replies - WHERE root_uri IN (` + buildPlaceholders(len(rootURIs)) + `) + query := ` + SELECT root_uri, COUNT(*) + FROM replies + WHERE root_uri = ANY($1) GROUP BY root_uri - `) + ` - args := make([]interface{}, len(rootURIs)) - for i, uri := range rootURIs { - args[i] = uri - } - - rows, err := db.Query(query, args...) + rows, err := db.Query(query, pqStringArray(rootURIs)) if err != nil { return nil, err } @@ -147,23 +118,23 @@ return []Reply{}, nil } - query := db.Rebind(` + rows, err := db.Query(` SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid FROM replies - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) - `) - - args := make([]interface{}, len(uris)) - for i, uri := range uris { - args[i] = uri - } - - rows, err := db.Query(query, args...) + WHERE uri = ANY($1) + `, pqStringArray(uris)) if err != nil { return nil, err } defer rows.Close() + return scanReplies(rows) +} + +func scanReplies(rows interface { + Next() bool + Scan(...interface{}) error +}) ([]Reply, error) { var replies []Reply for rows.Next() { var r Reply diff --git a/backend/internal/db/queries_search.go b/backend/internal/db/queries_search.go --- a/backend/internal/db/queries_search.go +++ b/backend/internal/db/queries_search.go @@ -1,29 +1,38 @@ package db +import "strings" + +func escapeLike(s string) string { + s = strings.ReplaceAll(s, "\\", "\\\\") + s = strings.ReplaceAll(s, "%", "\\%") + s = strings.ReplaceAll(s, "_", "\\_") + return s +} + func (db *DB) SearchAnnotations(query string, authorDID string, limit, offset int) ([]Annotation, error) { - pattern := "%" + query + "%" + pattern := "%" + escapeLike(query) + "%" var baseQuery string var args []interface{} if authorDID != "" { - baseQuery = db.Rebind(` + baseQuery = ` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE author_did = ? - AND (body_value LIKE ? OR target_source LIKE ? OR target_title LIKE ? OR tags_json LIKE ? OR selector_json LIKE ?) + WHERE author_did = $1 + AND (body_value ILIKE $2 OR target_source ILIKE $3 OR target_title ILIKE $4 OR tags_json ILIKE $5 OR selector_json ILIKE $6) ORDER BY created_at DESC - LIMIT ? OFFSET ? - `) + LIMIT $7 OFFSET $8 + ` args = []interface{}{authorDID, pattern, pattern, pattern, pattern, pattern, limit, offset} } else { - baseQuery = db.Rebind(` + baseQuery = ` SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid FROM annotations - WHERE body_value LIKE ? OR target_source LIKE ? OR target_title LIKE ? OR tags_json LIKE ? OR selector_json LIKE ? + WHERE body_value ILIKE $1 OR target_source ILIKE $2 OR target_title ILIKE $3 OR tags_json ILIKE $4 OR selector_json ILIKE $5 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `) + LIMIT $6 OFFSET $7 + ` args = []interface{}{pattern, pattern, pattern, pattern, pattern, limit, offset} } @@ -37,29 +46,29 @@ } func (db *DB) SearchHighlights(query string, authorDID string, limit, offset int) ([]Highlight, error) { - pattern := "%" + query + "%" + pattern := "%" + escapeLike(query) + "%" var baseQuery string var args []interface{} if authorDID != "" { - baseQuery = db.Rebind(` + baseQuery = ` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE author_did = ? - AND (target_source LIKE ? OR target_title LIKE ? OR selector_json LIKE ? OR tags_json LIKE ?) + WHERE author_did = $1 + AND (target_source ILIKE $2 OR target_title ILIKE $3 OR selector_json ILIKE $4 OR tags_json ILIKE $5) ORDER BY created_at DESC - LIMIT ? OFFSET ? - `) + LIMIT $6 OFFSET $7 + ` args = []interface{}{authorDID, pattern, pattern, pattern, pattern, limit, offset} } else { - baseQuery = db.Rebind(` + baseQuery = ` SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid FROM highlights - WHERE target_source LIKE ? OR target_title LIKE ? OR selector_json LIKE ? OR tags_json LIKE ? + WHERE target_source ILIKE $1 OR target_title ILIKE $2 OR selector_json ILIKE $3 OR tags_json ILIKE $4 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `) + LIMIT $5 OFFSET $6 + ` args = []interface{}{pattern, pattern, pattern, pattern, limit, offset} } @@ -69,41 +78,33 @@ } defer rows.Close() - var highlights []Highlight - for rows.Next() { - var h Highlight - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { - return nil, err - } - highlights = append(highlights, h) - } - return highlights, nil + return scanHighlights(rows) } func (db *DB) SearchBookmarks(query string, authorDID string, limit, offset int) ([]Bookmark, error) { - pattern := "%" + query + "%" + pattern := "%" + escapeLike(query) + "%" var baseQuery string var args []interface{} if authorDID != "" { - baseQuery = db.Rebind(` + baseQuery = ` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE author_did = ? - AND (source LIKE ? OR title LIKE ? OR description LIKE ? OR tags_json LIKE ?) + WHERE author_did = $1 + AND (source ILIKE $2 OR title ILIKE $3 OR description ILIKE $4 OR tags_json ILIKE $5) ORDER BY created_at DESC - LIMIT ? OFFSET ? - `) + LIMIT $6 OFFSET $7 + ` args = []interface{}{authorDID, pattern, pattern, pattern, pattern, limit, offset} } else { - baseQuery = db.Rebind(` + baseQuery = ` SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid FROM bookmarks - WHERE source LIKE ? OR title LIKE ? OR description LIKE ? OR tags_json LIKE ? + WHERE source ILIKE $1 OR title ILIKE $2 OR description ILIKE $3 OR tags_json ILIKE $4 ORDER BY created_at DESC - LIMIT ? OFFSET ? - `) + LIMIT $5 OFFSET $6 + ` args = []interface{}{pattern, pattern, pattern, pattern, limit, offset} } @@ -113,13 +114,5 @@ } defer rows.Close() - var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) - } - return bookmarks, nil + return scanBookmarks(rows) } diff --git a/backend/internal/db/queries_sessions.go b/backend/internal/db/queries_sessions.go --- a/backend/internal/db/queries_sessions.go +++ b/backend/internal/db/queries_sessions.go @@ -5,33 +5,33 @@ ) func (db *DB) SaveSession(id, did, handle, accessToken, refreshToken, dpopKey string, expiresAt time.Time) error { - _, err := db.Exec(db.Rebind(` + _, err := db.Exec(` INSERT INTO sessions (id, did, handle, access_token, refresh_token, dpop_key, created_at, expires_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT(id) DO UPDATE SET - access_token = excluded.access_token, - refresh_token = excluded.refresh_token, - dpop_key = excluded.dpop_key, - expires_at = excluded.expires_at - `), id, did, handle, accessToken, refreshToken, dpopKey, time.Now(), expiresAt) + access_token = EXCLUDED.access_token, + refresh_token = EXCLUDED.refresh_token, + dpop_key = EXCLUDED.dpop_key, + expires_at = EXCLUDED.expires_at + `, id, did, handle, accessToken, refreshToken, dpopKey, time.Now(), expiresAt) return err } func (db *DB) GetSession(id string) (did, handle, accessToken, refreshToken, dpopKey string, err error) { - err = db.QueryRow(db.Rebind(` + err = db.QueryRow(` SELECT did, handle, access_token, refresh_token, COALESCE(dpop_key, '') FROM sessions - WHERE id = ? AND expires_at > ? - `), id, time.Now()).Scan(&did, &handle, &accessToken, &refreshToken, &dpopKey) + WHERE id = $1 AND expires_at > $2 + `, id, time.Now()).Scan(&did, &handle, &accessToken, &refreshToken, &dpopKey) return } func (db *DB) DeleteSession(id string) error { - _, err := db.Exec(db.Rebind(`DELETE FROM sessions WHERE id = ?`), id) + _, err := db.Exec(`DELETE FROM sessions WHERE id = $1`, id) return err } func (db *DB) DeleteExpiredSessions() error { - _, err := db.Exec(db.Rebind(`DELETE FROM sessions WHERE expires_at <= ?`), time.Now()) + _, err := db.Exec(`DELETE FROM sessions WHERE expires_at <= $1`, time.Now()) return err } diff --git a/backend/internal/db/tags.go b/backend/internal/db/tags.go --- a/backend/internal/db/tags.go +++ b/backend/internal/db/tags.go @@ -1,69 +1,30 @@ package db -import "database/sql" - -type TrendingTag struct { - Tag string `json:"tag"` - Count int `json:"count"` -} - func (db *DB) GetTrendingTags(limit int) ([]TrendingTag, error) { - var query string - if db.driver == "postgres" { - query = ` - SELECT tag, COUNT(*) as count FROM ( - SELECT value as tag, author_did - FROM annotations, json_array_elements_text(tags_json::json) as value - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - AND created_at > NOW() - INTERVAL '14 days' - UNION ALL - SELECT value as tag, author_did - FROM highlights, json_array_elements_text(tags_json::json) as value - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - AND created_at > NOW() - INTERVAL '14 days' - UNION ALL - SELECT value as tag, author_did - FROM bookmarks, json_array_elements_text(tags_json::json) as value - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - AND created_at > NOW() - INTERVAL '14 days' - ) combined - GROUP BY tag - HAVING COUNT(DISTINCT author_did) >= 3 - ORDER BY count DESC - LIMIT $1 - ` - } else { - query = ` - SELECT tag, COUNT(*) as count FROM ( - SELECT json_each.value as tag, author_did - FROM annotations, json_each(annotations.tags_json) - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - AND created_at > datetime('now', '-14 days') - UNION ALL - SELECT json_each.value as tag, author_did - FROM highlights, json_each(highlights.tags_json) - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - AND created_at > datetime('now', '-14 days') - UNION ALL - SELECT json_each.value as tag, author_did - FROM bookmarks, json_each(bookmarks.tags_json) - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - AND created_at > datetime('now', '-14 days') - ) combined - GROUP BY tag - HAVING COUNT(DISTINCT author_did) >= 3 - ORDER BY count DESC - LIMIT ? - ` - } + query := ` + SELECT tag, COUNT(*) as count FROM ( + SELECT value as tag, author_did + FROM annotations, json_array_elements_text(tags_json::json) as value + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + AND created_at > NOW() - INTERVAL '14 days' + UNION ALL + SELECT value as tag, author_did + FROM highlights, json_array_elements_text(tags_json::json) as value + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + AND created_at > NOW() - INTERVAL '14 days' + UNION ALL + SELECT value as tag, author_did + FROM bookmarks, json_array_elements_text(tags_json::json) as value + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + AND created_at > NOW() - INTERVAL '14 days' + ) combined + GROUP BY tag + HAVING COUNT(DISTINCT author_did) >= 3 + ORDER BY count DESC + LIMIT $1 + ` - var rows *sql.Rows - var err error - if db.driver == "postgres" { - rows, err = db.Query(query, limit) - } else { - rows, err = db.Query(db.Rebind(query), limit) - } + rows, err := db.Query(query, limit) if err != nil { return nil, err } @@ -90,60 +51,29 @@ } func (db *DB) GetUserTags(did string, limit int) ([]TrendingTag, error) { - var query string - if db.driver == "postgres" { - query = ` - SELECT tag, SUM(cnt) as count FROM ( - SELECT value as tag, COUNT(*) as cnt - FROM annotations, json_array_elements_text(tags_json::json) as value - WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - GROUP BY tag - UNION ALL - SELECT value as tag, COUNT(*) as cnt - FROM highlights, json_array_elements_text(tags_json::json) as value - WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - GROUP BY tag - UNION ALL - SELECT value as tag, COUNT(*) as cnt - FROM bookmarks, json_array_elements_text(tags_json::json) as value - WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - GROUP BY tag - ) combined + query := ` + SELECT tag, SUM(cnt) as count FROM ( + SELECT value as tag, COUNT(*) as cnt + FROM annotations, json_array_elements_text(tags_json::json) as value + WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' GROUP BY tag - ORDER BY count DESC - LIMIT $2 - ` - } else { - query = ` - SELECT tag, SUM(cnt) as count FROM ( - SELECT json_each.value as tag, COUNT(*) as cnt - FROM annotations, json_each(annotations.tags_json) - WHERE author_did = ? AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - GROUP BY tag - UNION ALL - SELECT json_each.value as tag, COUNT(*) as cnt - FROM highlights, json_each(highlights.tags_json) - WHERE author_did = ? AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - GROUP BY tag - UNION ALL - SELECT json_each.value as tag, COUNT(*) as cnt - FROM bookmarks, json_each(bookmarks.tags_json) - WHERE author_did = ? AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - GROUP BY tag - ) combined + UNION ALL + SELECT value as tag, COUNT(*) as cnt + FROM highlights, json_array_elements_text(tags_json::json) as value + WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' GROUP BY tag - ORDER BY count DESC - LIMIT ? - ` - } + UNION ALL + SELECT value as tag, COUNT(*) as cnt + FROM bookmarks, json_array_elements_text(tags_json::json) as value + WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + GROUP BY tag + ) combined + GROUP BY tag + ORDER BY count DESC + LIMIT $2 + ` - var rows *sql.Rows - var err error - if db.driver == "postgres" { - rows, err = db.Query(query, did, limit) - } else { - rows, err = db.Query(db.Rebind(query), did, did, did, limit) - } + rows, err := db.Query(query, did, limit) if err != nil { return nil, err } @@ -167,4 +97,9 @@ } return tags, nil +} + +type TrendingTag struct { + Tag string `json:"tag"` + Count int `json:"count"` } diff --git a/backend/internal/firehose/ingester.go b/backend/internal/firehose/ingester.go --- a/backend/internal/firehose/ingester.go +++ b/backend/internal/firehose/ingester.go @@ -57,15 +57,26 @@ currentRelayIdx int onAnnotation AnnotationCallback onDocument DocumentCallback + workerPool chan func() } type RecordHandler func(event *FirehoseEvent) func NewIngester(database *db.DB, syncService *internal_sync.Service) *Ingester { + pool := make(chan func(), 256) + for range 10 { + go func() { + for fn := range pool { + fn() + } + }() + } + i := &Ingester{ - db: database, - sync: syncService, - handlers: make(map[string]RecordHandler), + db: database, + sync: syncService, + handlers: make(map[string]RecordHandler), + workerPool: pool, } i.RegisterHandler(CollectionAnnotation, i.handleAnnotation) @@ -237,7 +248,11 @@ i.dispatchToHandler(firehoseEvent) - go i.triggerLazySync(event.Did) + did := event.Did + select { + case i.workerPool <- func() { i.triggerLazySync(did) }: + default: + } } case "delete": i.handleDelete(commit.Collection, uri) @@ -266,7 +281,9 @@ return } - _, err = i.sync.PerformSync(context.Background(), did, func(ctx context.Context, _ string) (*xrpc.Client, error) { + syncCtx, syncCancel := context.WithTimeout(context.Background(), 15*time.Second) + defer syncCancel() + _, err = i.sync.PerformSync(syncCtx, did, func(ctx context.Context, _ string) (*xrpc.Client, error) { return &xrpc.Client{ PDS: pds, }, nil @@ -434,7 +451,11 @@ } else { logger.Info("Indexed annotation from %s on %s", event.Repo, targetSource) if i.onAnnotation != nil { - go i.onAnnotation(uri, event.Repo, targetSource, bodyValuePtr, selectorJSONPtr, targetTitlePtr, tagsJSONPtr) + cb := i.onAnnotation + select { + case i.workerPool <- func() { cb(uri, event.Repo, targetSource, bodyValuePtr, selectorJSONPtr, targetTitlePtr, tagsJSONPtr) }: + default: + } } } } diff --git a/backend/internal/recommendations/service.go b/backend/internal/recommendations/service.go --- a/backend/internal/recommendations/service.go +++ b/backend/internal/recommendations/service.go @@ -374,6 +374,8 @@ if len(docs) < batchSize { break } + + time.Sleep(2 * time.Second) } if total > 0 { @@ -429,6 +431,8 @@ if len(anns) < batchSize { break } + + time.Sleep(2 * time.Second) } if total > 0 { @@ -484,6 +488,8 @@ if len(highlights) < batchSize { break } + + time.Sleep(2 * time.Second) } if total > 0 { diff --git a/backend/internal/verification/verify.go b/backend/internal/verification/verify.go --- a/backend/internal/verification/verify.go +++ b/backend/internal/verification/verify.go @@ -22,6 +22,8 @@ }, } +var verifySem = make(chan struct{}, 3) + var linkTagPattern = regexp.MustCompile(`]+rel=["']site\.standard\.document["'][^>]+href=["']([^"']+)["'][^>]*/?>|]+href=["']([^"']+)["'][^>]+rel=["']site\.standard\.document["'][^>]*/?>`) func VerifyPublication(pubURL, expectedURI string) error { @@ -84,7 +86,7 @@ return fmt.Errorf("document URL returned %d", resp.StatusCode) } - body, err := io.ReadAll(io.LimitReader(resp.Body, 256*1024)) + body, err := io.ReadAll(io.LimitReader(resp.Body, 16*1024)) if err != nil { return fmt.Errorf("failed to read document: %w", err) } @@ -107,6 +109,9 @@ func VerifyPublicationAsync(pubURL, uri string, onVerified func(string)) { go func() { + verifySem <- struct{}{} + defer func() { <-verifySem }() + if err := VerifyPublication(pubURL, uri); err != nil { return } @@ -119,6 +124,9 @@ func VerifyDocumentAsync(docURL, uri string, onVerified func(string)) { go func() { + verifySem <- struct{}{} + defer func() { <-verifySem }() + if err := VerifyDocument(docURL, uri); err != nil { return } diff --git a/extension/src/utils/overlay.ts b/extension/src/utils/overlay.ts --- a/extension/src/utils/overlay.ts +++ b/extension/src/utils/overlay.ts @@ -765,9 +765,9 @@ const marginLeft = i === 0 ? '0' : '-8px'; if (avatar) { - return ``; + return ``; } else { - return `
${handle[0]?.toUpperCase() || 'U'}
`; + return `
${escapeHtml(handle[0]?.toUpperCase() || 'U')}
`; } }) .join(''); @@ -916,9 +916,9 @@ const isOwned = currentUserDid && author.did === currentUserDid; const createdAt = item.createdAt ? formatRelativeTime(item.createdAt) : ''; - let avatarHtml = `
${handle[0]?.toUpperCase() || 'U'}
`; + let avatarHtml = `
${escapeHtml(handle[0]?.toUpperCase() || 'U')}
`; if (avatar) { - avatarHtml = ``; + avatarHtml = ``; } let bodyHtml = ''; @@ -928,25 +928,26 @@ bodyHtml = `
${escapeHtml(text)}
`; } + const safeId = escapeHtml(id || ''); const addNoteBtn = isHighlight && isOwned - ? `` + ? `` : ''; return ` -
+
${avatarHtml}
- @${handle} - ${createdAt ? `${createdAt}` : ''} + @${escapeHtml(handle)} + ${createdAt ? `${escapeHtml(createdAt)}` : ''}
${bodyHtml}
${addNoteBtn} - ${!isHighlight ? `` : ''} - + ${!isHighlight ? `` : ''} +
`; diff --git a/web/src/layouts/AppLayout.astro b/web/src/layouts/AppLayout.astro new file mode 100644 --- /dev/null +++ b/web/src/layouts/AppLayout.astro @@ -0,0 +1,42 @@ +--- +import BaseLayout from './BaseLayout.astro'; +import Sidebar from '../components/navigation/Sidebar'; +import RightSidebar from '../components/navigation/RightSidebar'; +import MobileNav from '../components/navigation/MobileNav'; +import type { UserProfile } from '../types'; + +interface Props { + title?: string; + description?: string; + image?: string; + user?: UserProfile | null; +} + +const { title, description, image, user = Astro.locals.user } = Astro.props; +--- + + +
+
+ +
+ +
+
+
+
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+
diff --git a/web/src/layouts/BaseLayout.astro b/web/src/layouts/BaseLayout.astro --- a/web/src/layouts/BaseLayout.astro +++ b/web/src/layouts/BaseLayout.astro @@ -1,4 +1,5 @@ --- +import { ClientRouter } from 'astro:transitions'; import '../styles/global.css'; interface Props { @@ -33,6 +34,29 @@ {title} + + + + diff --git a/web/src/layouts/OGLayout.astro b/web/src/layouts/OGLayout.astro deleted file mode 100644 --- a/web/src/layouts/OGLayout.astro +++ /dev/null @@ -1,16 +0,0 @@ ---- -import BaseLayout from './BaseLayout.astro'; -import App from '../App'; - -interface Props { - title?: string; - description?: string; - image?: string; -} - -const { title = 'Margin', description = 'Annotate the web', image = 'https://margin.at/og.png' } = Astro.props; ---- - - - - diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts new file mode 100644 --- /dev/null +++ b/web/src/lib/api.ts @@ -0,0 +1,490 @@ +import type { + AnnotationItem, + UserProfile, + FeedResponse, + Collection, + NotificationItem, + Target, + Selector, +} from "../types"; + +const API_URL = + process.env.API_URL || `http://localhost:${process.env.API_PORT || 8081}`; + +interface RawItem { + type?: string; + collectionUri?: string; + annotation?: RawItem; + highlight?: RawItem; + bookmark?: RawItem; + uri?: string; + id?: string; + cid?: string; + author?: UserProfile; + creator?: UserProfile; + collection?: { uri: string; name: string; icon?: string }; + context?: { uri: string; name: string; icon?: string }[]; + created?: string; + createdAt?: string; + target?: string | { source?: string; title?: string; selector?: Selector }; + url?: string; + targetUrl?: string; + title?: string; + selector?: Selector; + viewer?: { like?: string; [key: string]: unknown }; + viewerHasLiked?: boolean; + motivation?: string; + [key: string]: unknown; +} + +export function normalizeItem(raw: RawItem): AnnotationItem { + if (raw.type === "CollectionItem" || raw.collectionUri) { + const inner = raw.annotation || raw.highlight || raw.bookmark || {}; + const normalizedInner = normalizeItem(inner); + return { + ...normalizedInner, + uri: normalizedInner.uri || raw.uri || "", + cid: normalizedInner.cid || raw.cid || "", + author: (normalizedInner.author || + raw.author || + raw.creator) as UserProfile, + collection: raw.collection + ? { + uri: raw.collection.uri, + name: raw.collection.name, + icon: raw.collection.icon, + } + : undefined, + context: raw.context?.map((c) => ({ + uri: c.uri, + name: c.name, + icon: c.icon, + })), + addedBy: raw.creator || raw.author, + createdAt: + normalizedInner.createdAt || + raw.created || + raw.createdAt || + new Date().toISOString(), + collectionItemUri: raw.id || raw.uri, + }; + } + + let target: Target | undefined; + if (raw.target) { + if (typeof raw.target === "string") { + target = { source: raw.target, title: raw.title, selector: raw.selector }; + } else { + target = { + source: raw.target.source || "", + title: raw.target.title || raw.title, + selector: raw.target.selector || raw.selector, + }; + } + } + if (!target || !target.source) { + const url = + raw.url || + raw.targetUrl || + (typeof raw.target === "string" ? raw.target : raw.target?.source); + if (url) { + target = { + source: url, + title: + raw.title || + (typeof raw.target !== "string" ? raw.target?.title : undefined), + selector: + raw.selector || + (typeof raw.target !== "string" ? raw.target?.selector : undefined), + }; + } + } + + return { + ...raw, + uri: raw.id || raw.uri || "", + cid: raw.cid || "", + author: (raw.creator || raw.author) as UserProfile, + createdAt: raw.created || raw.createdAt || new Date().toISOString(), + target, + viewer: raw.viewer || { like: raw.viewerHasLiked ? "true" : undefined }, + motivation: raw.motivation || "highlighting", + parentUri: (raw as Record).inReplyTo as string | undefined, + }; +} + +async function serverFetch(path: string, cookie?: string): Promise { + const headers: Record = { + "Content-Type": "application/json", + }; + if (cookie) headers["Cookie"] = cookie; + return fetch(`${API_URL}${path}`, { headers }); +} + +const sessionCache = new Map(); + +export async function getSession(cookie: string): Promise { + try { + const cacheKey = cookie.match(/margin_session=([^;]+)/)?.[1] || ""; + const cached = sessionCache.get(cacheKey); + if (cached && Date.now() < cached.expires) { + return cached.user; + } + + const res = await serverFetch("/auth/session", cookie); + if (!res.ok) return null; + const data = await res.json(); + if (!data.authenticated && !data.did) return null; + + const profile: UserProfile = { + did: data.did, + handle: data.handle, + displayName: data.displayName, + avatar: data.avatar, + description: data.description, + website: data.website, + links: data.links, + followersCount: data.followersCount, + followsCount: data.followsCount, + postsCount: data.postsCount, + }; + + // Fetch bsky profile and margin profile in parallel with a 3s timeout + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 3000); + + const [bskyRes, marginRes] = await Promise.allSettled([ + fetch( + `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(data.did)}`, + { signal: controller.signal }, + ), + serverFetch(`/api/profile/${data.did}`, cookie), + ]); + + clearTimeout(timeout); + + if (bskyRes.status === "fulfilled" && bskyRes.value.ok) { + try { + const bsky = await bskyRes.value.json(); + if (bsky.avatar) profile.avatar = bsky.avatar; + if (bsky.displayName) profile.displayName = bsky.displayName; + } catch { + /* ignore */ + } + } + + if (marginRes.status === "fulfilled" && marginRes.value.ok) { + try { + const mp = await marginRes.value.json(); + if (mp?.description) profile.description = mp.description; + if (mp?.followersCount) profile.followersCount = mp.followersCount; + if (mp?.followsCount) profile.followsCount = mp.followsCount; + if (mp?.postsCount) profile.postsCount = mp.postsCount; + if (mp?.website) profile.website = mp.website; + if (mp?.links) profile.links = mp.links; + } catch { + /* ignore */ + } + } + + if (cacheKey) { + sessionCache.set(cacheKey, { + user: profile, + expires: Date.now() + 30_000, + }); + // Evict old entries + if (sessionCache.size > 100) { + const now = Date.now(); + for (const [k, v] of sessionCache) { + if (now > v.expires) sessionCache.delete(k); + } + } + } + + return profile; + } catch { + return null; + } +} + +export interface GetFeedParams { + type?: string; + limit?: number; + offset?: number; + motivation?: string; + tag?: string; + creator?: string; + source?: string; +} + +function groupFeedItems(items: AnnotationItem[]): AnnotationItem[] { + if (items.length === 0) return items; + const grouped: AnnotationItem[] = [items[0]]; + for (let i = 1; i < items.length; i++) { + const prev = grouped[grouped.length - 1]; + const curr = items[i]; + if ( + prev.collection && + curr.collection && + prev.uri === curr.uri && + prev.addedBy?.did === curr.addedBy?.did + ) { + if (!prev.context) prev.context = [prev.collection]; + if (!prev.context.find((c) => c.uri === curr.collection!.uri)) { + prev.context.push(curr.collection); + } + continue; + } + grouped.push(curr); + } + return grouped; +} + +export async function getFeed( + cookie: string, + params: GetFeedParams = {}, +): Promise { + const qs = new URLSearchParams(); + if (params.source) qs.append("source", params.source); + if (params.type) qs.append("type", params.type); + if (params.limit) qs.append("limit", params.limit.toString()); + if (params.offset) qs.append("offset", params.offset.toString()); + if (params.motivation) qs.append("motivation", params.motivation); + if (params.tag) qs.append("tag", params.tag); + if (params.creator) qs.append("creator", params.creator); + + const endpoint = params.source ? "/api/targets" : "/api/annotations/feed"; + try { + const res = await serverFetch(`${endpoint}?${qs.toString()}`, cookie); + if (!res.ok) return { items: [], hasMore: false, fetchedCount: 0 }; + const data = await res.json(); + const items = (data.items || []).map(normalizeItem); + return { + items: groupFeedItems(items), + hasMore: items.length >= (params.limit || 50), + fetchedCount: items.length, + }; + } catch { + return { items: [], hasMore: false, fetchedCount: 0 }; + } +} + +export async function searchItems( + cookie: string, + query: string, + options: { creator?: string; limit?: number; offset?: number } = {}, +): Promise { + const qs = new URLSearchParams(); + qs.append("q", query); + if (options.creator) qs.append("creator", options.creator); + if (options.limit) qs.append("limit", options.limit.toString()); + if (options.offset) qs.append("offset", options.offset.toString()); + + try { + const res = await serverFetch(`/api/search?${qs.toString()}`, cookie); + if (!res.ok) return { items: [], hasMore: false, fetchedCount: 0 }; + const data = await res.json(); + const items = (data.items || []).map(normalizeItem); + return { + items, + hasMore: items.length >= (options.limit || 50), + fetchedCount: items.length, + }; + } catch { + return { items: [], hasMore: false, fetchedCount: 0 }; + } +} + +export async function getAnnotation( + cookie: string, + uri: string, +): Promise { + try { + const res = await serverFetch( + `/api/annotation?uri=${encodeURIComponent(uri)}`, + cookie, + ); + if (!res.ok) return null; + const data = await res.json(); + return normalizeItem(data); + } catch { + return null; + } +} + +export async function getReplies( + cookie: string, + uri: string, +): Promise { + try { + const res = await serverFetch( + `/api/replies?uri=${encodeURIComponent(uri)}`, + cookie, + ); + if (!res.ok) return []; + const data = await res.json(); + return (data.items || []).map(normalizeItem); + } catch { + return []; + } +} + +export async function getProfile( + cookie: string, + did: string, +): Promise { + try { + const res = await serverFetch( + `/api/profile/${encodeURIComponent(did)}`, + cookie, + ); + if (!res.ok) return null; + return await res.json(); + } catch { + return null; + } +} + +export async function getCollections( + cookie: string, + author?: string, +): Promise { + const qs = author ? `?author=${encodeURIComponent(author)}` : ""; + try { + const res = await serverFetch(`/api/collections${qs}`, cookie); + if (!res.ok) return []; + const data = await res.json(); + return data.collections || data || []; + } catch { + return []; + } +} + +export async function getCollection( + cookie: string, + uri: string, +): Promise { + try { + const res = await serverFetch( + `/api/collection?uri=${encodeURIComponent(uri)}`, + cookie, + ); + if (!res.ok) return null; + return await res.json(); + } catch { + return null; + } +} + +export async function getCollectionItems( + cookie: string, + uri: string, +): Promise { + try { + const res = await serverFetch( + `/api/collections/${encodeURIComponent(uri)}/items`, + cookie, + ); + if (!res.ok) return []; + const data = await res.json(); + return (data.items || data || []).map(normalizeItem); + } catch { + return []; + } +} + +export async function getNotifications( + cookie: string, + limit = 50, + offset = 0, +): Promise<{ items: NotificationItem[]; hasMore: boolean }> { + try { + const res = await serverFetch( + `/api/notifications?limit=${limit}&offset=${offset}`, + cookie, + ); + if (!res.ok) return { items: [], hasMore: false }; + const data = await res.json(); + return { + items: data.notifications || [], + hasMore: (data.notifications || []).length >= limit, + }; + } catch { + return { items: [], hasMore: false }; + } +} + +export async function getTrendingTags( + limit = 10, +): Promise<{ tag: string; count: number }[]> { + try { + const res = await serverFetch(`/api/tags/trending?limit=${limit}`); + if (!res.ok) return []; + return await res.json(); + } catch { + return []; + } +} + +export async function getRecommendations( + cookie: string, + params: { sort?: string; limit?: number; offset?: number } = {}, +): Promise<{ items: AnnotationItem[]; hasMore: boolean }> { + const qs = new URLSearchParams(); + if (params.sort) qs.append("sort", params.sort); + if (params.limit) qs.append("limit", params.limit.toString()); + if (params.offset) qs.append("offset", params.offset.toString()); + + try { + const res = await serverFetch(`/api/documents?${qs.toString()}`, cookie); + if (!res.ok) return { items: [], hasMore: false }; + const data = await res.json(); + return { + items: data.items || [], + hasMore: (data.items || []).length >= (params.limit || 20), + }; + } catch { + return { items: [], hasMore: false }; + } +} + +export async function getByTarget( + cookie: string, + url: string, + limit = 50, + offset = 0, +): Promise<{ annotations: AnnotationItem[]; highlights: AnnotationItem[] }> { + try { + const res = await serverFetch( + `/api/targets?source=${encodeURIComponent(url)}&limit=${limit}&offset=${offset}`, + cookie, + ); + if (!res.ok) return { annotations: [], highlights: [] }; + const data = await res.json(); + const items = (data.items || []).map(normalizeItem); + return { + annotations: items.filter( + (i: AnnotationItem) => i.motivation === "commenting", + ), + highlights: items.filter( + (i: AnnotationItem) => i.motivation === "highlighting", + ), + }; + } catch { + return { annotations: [], highlights: [] }; + } +} + +export async function resolveHandle(handle: string): Promise { + if (handle.startsWith("did:")) return handle; + try { + const res = await fetch( + `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`, + ); + if (!res.ok) return null; + const data = await res.json(); + return data.did || null; + } catch { + return null; + } +} diff --git a/web/src/lib/metadataQueue.ts b/web/src/lib/metadataQueue.ts new file mode 100644 --- /dev/null +++ b/web/src/lib/metadataQueue.ts @@ -0,0 +1,63 @@ +type QueueEntry = { + url: string; + resolve: (data: Record | null) => void; +}; + +const MAX_CONCURRENT = 3; +const queue: QueueEntry[] = []; +let active = 0; +const inflight = new Map | null>>(); + +function processQueue() { + while (active < MAX_CONCURRENT && queue.length > 0) { + const entry = queue.shift()!; + active++; + + doFetch(entry.url) + .then(entry.resolve) + .finally(() => { + active--; + processQueue(); + }); + } +} + +function doFetch(url: string): Promise | null> { + const existing = inflight.get(url); + if (existing) return existing; + + const promise = fetch(`/api/url-metadata?url=${encodeURIComponent(url)}`) + .then((res) => (res.ok ? res.json() : null)) + .catch(() => null) + .finally(() => { + inflight.delete(url); + }); + + inflight.set(url, promise); + return promise; +} + +export function fetchMetadata( + url: string, +): Promise | null> { + try { + const cached = sessionStorage.getItem(`og:${url}`); + if (cached) return Promise.resolve(JSON.parse(cached)); + } catch { + /* ignore */ + } + + return new Promise((resolve) => { + queue.push({ url, resolve }); + processQueue(); + }).then((data) => { + if (data) { + try { + sessionStorage.setItem(`og:${url}`, JSON.stringify(data)); + } catch { + /* ignore */ + } + } + return data as Record | null; + }); +} diff --git a/web/src/pages/[...slug].astro b/web/src/pages/[...slug].astro deleted file mode 100644 --- a/web/src/pages/[...slug].astro +++ /dev/null @@ -1,10 +0,0 @@ ---- -export const prerender = false; - -import BaseLayout from '../layouts/BaseLayout.astro'; -import App from '../App'; ---- - - - - diff --git a/web/src/pages/about.astro b/web/src/pages/about.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/about.astro @@ -0,0 +1,9 @@ +--- + +import BaseLayout from '../layouts/BaseLayout.astro'; +import About from '../views/About'; +--- + + + + diff --git a/web/src/pages/annotations.astro b/web/src/pages/annotations.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/annotations.astro @@ -0,0 +1,12 @@ +--- + +import AppLayout from '../layouts/AppLayout.astro'; +import Feed from '../views/core/Feed'; + +const user = Astro.locals.user; +const tag = Astro.url.searchParams.get('tag') || undefined; +--- + + + + diff --git a/web/src/pages/bookmarks.astro b/web/src/pages/bookmarks.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/bookmarks.astro @@ -0,0 +1,12 @@ +--- + +import AppLayout from '../layouts/AppLayout.astro'; +import Feed from '../views/core/Feed'; + +const user = Astro.locals.user; +const tag = Astro.url.searchParams.get('tag') || undefined; +--- + + + + diff --git a/web/src/pages/discover.astro b/web/src/pages/discover.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/discover.astro @@ -0,0 +1,11 @@ +--- + +import AppLayout from '../layouts/AppLayout.astro'; +import Discover from '../views/core/Discover'; + +const user = Astro.locals.user; +--- + + + + diff --git a/web/src/pages/highlights.astro b/web/src/pages/highlights.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/highlights.astro @@ -0,0 +1,12 @@ +--- + +import AppLayout from '../layouts/AppLayout.astro'; +import Feed from '../views/core/Feed'; + +const user = Astro.locals.user; +const tag = Astro.url.searchParams.get('tag') || undefined; +--- + + + + diff --git a/web/src/pages/home.astro b/web/src/pages/home.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/home.astro @@ -0,0 +1,12 @@ +--- + +import AppLayout from '../layouts/AppLayout.astro'; +import Feed from '../views/core/Feed'; + +const user = Astro.locals.user; +const tag = Astro.url.searchParams.get('tag') || undefined; +--- + + + + diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro --- a/web/src/pages/index.astro +++ b/web/src/pages/index.astro @@ -1,8 +1,14 @@ --- + import BaseLayout from '../layouts/BaseLayout.astro'; -import App from '../App'; +import About from '../views/About'; + +const user = Astro.locals.user; +if (user) { + return Astro.redirect('/home'); +} --- - + diff --git a/web/src/pages/login.astro b/web/src/pages/login.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/login.astro @@ -0,0 +1,16 @@ +--- + +import BaseLayout from '../layouts/BaseLayout.astro'; +import Login from '../views/auth/Login'; + +const user = Astro.locals.user; +if (user) { + return Astro.redirect('/home'); +} + +const error = Astro.url.searchParams.get('error') || undefined; +--- + + + + diff --git a/web/src/pages/new.astro b/web/src/pages/new.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/new.astro @@ -0,0 +1,18 @@ +--- + +import AppLayout from '../layouts/AppLayout.astro'; +import NewAnnotationPage from '../views/core/New'; + +const user = Astro.locals.user; +if (!user) { + return Astro.redirect('/login'); +} + +const url = Astro.url.searchParams.get('url') || undefined; +const selector = Astro.url.searchParams.get('selector') || undefined; +const quote = Astro.url.searchParams.get('quote') || undefined; +--- + + + + diff --git a/web/src/pages/notifications.astro b/web/src/pages/notifications.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/notifications.astro @@ -0,0 +1,14 @@ +--- + +import AppLayout from '../layouts/AppLayout.astro'; +import Notifications from '../views/core/Notifications'; + +const user = Astro.locals.user; +if (!user) { + return Astro.redirect('/login'); +} +--- + + + + diff --git a/web/src/pages/og-image.ts b/web/src/pages/og-image.ts --- a/web/src/pages/og-image.ts +++ b/web/src/pages/og-image.ts @@ -4,8 +4,6 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; -export const prerender = false; - function getPublicDir(): string { if (import.meta.env.PROD) { return join(process.cwd(), "dist", "client"); @@ -144,7 +142,7 @@ quote: "", source: "", title: item.name || "Collection", - icon: item.icon || "📁", + icon: item.icon || "", description: item.description || "", color: "", }; @@ -158,7 +156,7 @@ function truncate(str: string, max: number): string { if (str.length <= max) return str; - return str.slice(0, max - 3) + "..."; + return str.slice(0, max - 3) + "\u2026"; } function extractBody(body: unknown): string { @@ -171,14 +169,27 @@ } const C = { - bg: "#f4f4f5", - text: "#18181b", - textSecondary: "#52525b", - textMuted: "#a1a1aa", - textFaint: "#d4d4d8", - primary: "#3b82f6", - primaryDark: "#2563eb", - border: "#e4e4e7", + bg: "#18181b", + bgCard: "#27272a", + text: "#fafafa", + textSecondary: "#a1a1aa", + textMuted: "#71717a", + textFaint: "#3f3f46", + border: "#3f3f46", +}; + +const typeAccent: Record = { + annotation: "#3b82f6", + highlight: "#facc15", + bookmark: "#22c55e", + collection: "#a78bfa", +}; + +const typeLabels: Record = { + annotation: "Annotation", + highlight: "Highlight", + bookmark: "Bookmark", + collection: "Collection", }; const namedColors: Record = { @@ -186,6 +197,9 @@ green: "#4ade80", red: "#f87171", blue: "#60a5fa", + purple: "#a78bfa", + pink: "#f472b6", + orange: "#fb923c", }; function resolveHighlightColor(color: string): string { @@ -194,19 +208,68 @@ return namedColors[color] || "#facc15"; } -const typeColors: Record = { - annotation: "#3b82f6", - highlight: "#facc15", - bookmark: "#22c55e", - collection: "#3b82f6", +function sanitizeColor(color: string): string { + if (/^#[0-9a-fA-F]{3,8}$/.test(color)) return color; + if (/^rgba?\([0-9,.\s]+\)$/.test(color)) return color; + return "#888888"; +} + +function coloredLogoUri(color: string): string { + const safe = sanitizeColor(color); + const publicDir = getPublicDir(); + const svg = readFileSync(join(publicDir, "logo.svg"), "utf-8"); + const recolored = svg.replace(/fill="[^"]*"/, `fill="${safe}"`); + return `data:image/svg+xml,${encodeURIComponent(recolored)}`; +} + +function logoElement(height: number, color: string): unknown | null { + const uri = coloredLogoUri(color); + if (!uri) return null; + const width = Math.round(height * (265 / 231)); + return { + type: "img", + props: { + src: uri, + width, + height, + style: { flexShrink: 0, opacity: 0.9 }, + }, + }; +} + +const lucideFileMap: Record = { + file: "file-text", + pin: "map-pin", + trending: "trending-up", }; -function lightTint(hex: string): string { - const r = parseInt(hex.slice(1, 3), 16); - const g = parseInt(hex.slice(3, 5), 16); - const b = parseInt(hex.slice(5, 7), 16); - const mix = (c: number) => Math.round(c * 0.12 + 255 * 0.88); - return `rgb(${mix(r)}, ${mix(g)}, ${mix(b)})`; +function lucideIconUri(iconName: string, color: string): string | null { + try { + const safe = sanitizeColor(color); + const fileName = lucideFileMap[iconName] || iconName; + const iconPath = join( + process.cwd(), + "node_modules", + "lucide-react", + "dist", + "esm", + "icons", + `${fileName}.js`, + ); + const src = readFileSync(iconPath, "utf-8"); + const paths = [...src.matchAll(/d:\s*"([^"]+)"/g)].map((m) => m[1]); + if (!paths.length) return null; + const pathEls = paths + .map( + (d) => + ``, + ) + .join(""); + const svg = `${pathEls}`; + return `data:image/svg+xml,${encodeURIComponent(svg)}`; + } catch { + return null; + } } function avatarElement(url: string, name: string, size: number): unknown { @@ -217,14 +280,18 @@ src: url, width: size, height: size, - style: { borderRadius: size / 2, flexShrink: 0 }, + style: { + borderRadius: size / 2, + flexShrink: 0, + border: `2px solid ${C.border}`, + }, }, }; } const letter = name[0] === "@" - ? name[1]?.toUpperCase() || "?" - : name[0]?.toUpperCase() || "?"; + ? (name[1] || "?").toUpperCase() + : (name[0] || "?").toUpperCase(); return { type: "div", props: { @@ -232,11 +299,12 @@ width: size, height: size, borderRadius: size / 2, - background: "#e4e4e7", + background: C.bgCard, + border: `2px solid ${C.border}`, display: "flex", alignItems: "center", justifyContent: "center", - color: "#71717a", + color: C.textMuted, fontSize: Math.round(size * 0.45), fontWeight: 700, flexShrink: 0, @@ -246,58 +314,63 @@ }; } -function coloredLogoUri(color: string): string { - const publicDir = getPublicDir(); - const svg = readFileSync(join(publicDir, "logo.svg"), "utf-8"); - const recolored = svg.replace(/fill="[^"]*"/, `fill="${color}"`); - return `data:image/svg+xml,${encodeURIComponent(recolored)}`; -} - -function logoElement(size: number, color: string): unknown { +function accentBar(color: string): unknown { return { - type: "img", + type: "div", props: { - src: coloredLogoUri(color), - width: size, - height: size, - style: { flexShrink: 0 }, + style: { + position: "absolute", + left: 0, + top: 0, + bottom: 0, + width: 5, + background: color, + borderRadius: "3px 0 0 3px", + }, }, }; } -const typeLabels: Record = { - annotation: "Annotation", - highlight: "Highlight", - bookmark: "Bookmark", - collection: "Collection", -}; - -function headerWithBadge(data: RecordData, accentColor: string): unknown { +function bgPattern(): unknown { return { type: "div", props: { - style: { display: "flex", alignItems: "center" }, + style: { + position: "absolute", + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundImage: + "radial-gradient(circle at 1px 1px, rgba(255,255,255,0.03) 1px, transparent 0)", + backgroundSize: "32px 32px", + }, + }, + }; +} + +function header(data: RecordData, accentColor: string): unknown { + return { + type: "div", + props: { + style: { display: "flex", alignItems: "center", width: "100%" }, children: [ - avatarElement(data.avatarURL, data.displayName, 48), + avatarElement(data.avatarURL, data.displayName || data.author, 64), { type: "div", props: { style: { display: "flex", flexDirection: "column", - marginLeft: 14, + marginLeft: 18, flex: 1, }, children: [ { type: "span", props: { - style: { - color: C.text, - fontSize: 22, - fontWeight: 600, - }, - children: data.displayName, + style: { color: C.text, fontSize: 28, fontWeight: 700 }, + children: truncate(data.displayName || data.author, 30), }, }, { @@ -305,8 +378,8 @@ props: { style: { color: C.textMuted, - fontSize: 17, - marginTop: 1, + fontSize: 20, + marginTop: 3, }, children: data.author, }, @@ -321,33 +394,25 @@ display: "flex", alignItems: "center", gap: 10, + background: "rgba(255,255,255,0.06)", + borderRadius: 24, + padding: "8px 18px 8px 14px", }, children: [ - logoElement(24, accentColor), - { - type: "span", - props: { - style: { - color: C.textFaint, - fontSize: 18, - }, - children: "|", - }, - }, + logoElement(22, accentColor), { type: "span", props: { style: { color: accentColor, - fontSize: 16, + fontSize: 18, fontWeight: 600, - textTransform: "uppercase" as const, - letterSpacing: 1, + letterSpacing: 0.5, }, children: typeLabels[data.type] || data.type, }, }, - ], + ].filter(Boolean) as unknown[], }, }, ], @@ -355,7 +420,7 @@ }; } -function footerSource(source?: string): unknown | null { +function footerEl(source: string): unknown | null { if (!source) return null; return { type: "div", @@ -364,13 +429,25 @@ display: "flex", alignItems: "center", marginTop: "auto", - paddingTop: 16, + paddingTop: 24, }, children: [ { + type: "div", + props: { + style: { + width: 8, + height: 8, + borderRadius: 4, + background: C.textFaint, + marginRight: 12, + }, + }, + }, + { type: "span", props: { - style: { color: C.textMuted, fontSize: 16 }, + style: { color: C.textMuted, fontSize: 20 }, children: source, }, }, @@ -379,27 +456,44 @@ }; } -function wrap(children: unknown[], bg?: string): unknown { +function card(children: unknown[], accentColor: string): unknown { return { type: "div", props: { style: { display: "flex", - flexDirection: "column", width: "100%", height: "100%", - background: bg || C.bg, - padding: "48px 64px", + background: C.bg, + padding: 0, fontFamily: "Inter", + position: "relative", }, - children, + children: [ + bgPattern(), + accentBar(accentColor), + { + type: "div", + props: { + style: { + display: "flex", + flexDirection: "column", + width: "100%", + height: "100%", + padding: "48px 56px 48px 60px", + position: "relative", + }, + children, + }, + }, + ], }, }; } function buildAnnotationImage(data: RecordData) { - const accent = typeColors.annotation; - const children: unknown[] = [headerWithBadge(data, accent)]; + const accent = typeAccent.annotation; + const children: unknown[] = [header(data, accent)]; if (data.text) { const len = data.text.length; @@ -408,13 +502,13 @@ props: { style: { color: C.text, - fontSize: len > 200 ? 26 : len > 100 ? 30 : 36, - fontWeight: 500, - lineHeight: 1.45, - marginTop: 32, + fontSize: len > 200 ? 32 : len > 100 ? 38 : 46, + fontWeight: 400, + lineHeight: 1.5, + marginTop: 36, overflow: "hidden", }, - children: truncate(data.text, 280), + children: truncate(data.text, 240), }, }); } @@ -423,7 +517,13 @@ children.push({ type: "div", props: { - style: { display: "flex", marginTop: 24 }, + style: { + display: "flex", + marginTop: 28, + background: "rgba(59, 130, 246, 0.06)", + borderRadius: 14, + padding: "20px 24px", + }, children: [ { type: "div", @@ -433,6 +533,7 @@ borderRadius: 2, background: accent, flexShrink: 0, + opacity: 0.7, }, }, }, @@ -441,13 +542,13 @@ props: { style: { color: C.textSecondary, - fontSize: 20, + fontSize: 24, lineHeight: 1.6, - paddingLeft: 20, + paddingLeft: 18, fontStyle: "italic", overflow: "hidden", }, - children: truncate(data.quote, 200), + children: truncate(data.quote, 180), }, }, ], @@ -455,112 +556,98 @@ }); } - const footer = footerSource(data.source); - if (footer) children.push(footer); - - return wrap(children, lightTint(accent)); + const f = footerEl(data.source); + if (f) children.push(f); + return card(children, accent); } function buildHighlightImage(data: RecordData) { const highlightColor = resolveHighlightColor(data.color); - const bgTint = lightTint(highlightColor); const quoteText = data.quote || data.text || "Highlighted passage"; const len = quoteText.length; - return { - type: "div", - props: { - style: { - display: "flex", - flexDirection: "column", - width: "100%", - height: "100%", - background: bgTint, - padding: "48px 64px", - fontFamily: "Inter", + const children: unknown[] = [ + header(data, highlightColor), + { + type: "div", + props: { + style: { + color: highlightColor, + fontSize: 120, + fontWeight: 700, + lineHeight: 1, + marginTop: 28, + opacity: 0.5, + }, + children: "\u201C", }, - children: [ - headerWithBadge(data, highlightColor), - { - type: "div", - props: { - style: { - color: highlightColor, - fontSize: 120, - fontWeight: 700, - lineHeight: 1, - marginTop: 28, - }, - children: "\u201C", - }, - }, - { - type: "div", - props: { - style: { - color: C.text, - fontSize: len > 150 ? 28 : len > 80 ? 34 : 42, - fontWeight: 600, - lineHeight: 1.4, - marginTop: -30, - overflow: "hidden", - }, - children: truncate(quoteText, 240), - }, - }, - data.text && data.quote - ? { - type: "div", - props: { - style: { - color: C.textSecondary, - fontSize: 20, - marginTop: 20, - }, - children: truncate(data.text, 80), - }, - } - : null, - { - type: "div", - props: { - style: { - display: "flex", - alignItems: "center", - marginTop: "auto", - paddingTop: 16, - }, - children: [ - data.source - ? { - type: "span", - props: { - style: { color: C.textMuted, fontSize: 16 }, - children: data.source, - }, - } - : null, - ].filter(Boolean), - }, - }, - ].filter(Boolean), }, - }; + { + type: "div", + props: { + style: { + color: C.text, + fontSize: len > 150 ? 32 : len > 80 ? 40 : 50, + fontWeight: 600, + lineHeight: 1.4, + marginTop: -28, + overflow: "hidden", + }, + children: truncate(quoteText, 200), + }, + }, + ]; + + if (data.text && data.quote) { + children.push({ + type: "div", + props: { + style: { color: C.textMuted, fontSize: 22, marginTop: 22 }, + children: truncate(data.text, 80), + }, + }); + } + + const f = footerEl(data.source); + if (f) children.push(f); + return card(children, highlightColor); } function buildBookmarkImage(data: RecordData) { - const children: unknown[] = [headerWithBadge(data, typeColors.bookmark)]; + const accent = typeAccent.bookmark; + const children: unknown[] = [header(data, accent)]; if (data.source) { children.push({ type: "div", props: { style: { - color: typeColors.bookmark, - fontSize: 18, - marginTop: 32, + display: "flex", + alignItems: "center", + marginTop: 36, + gap: 10, }, - children: data.source, + children: [ + { + type: "div", + props: { + style: { + width: 10, + height: 10, + borderRadius: 5, + background: accent, + opacity: 0.6, + }, + }, + }, + { + type: "span", + props: { + style: { color: accent, fontSize: 22, fontWeight: 500 }, + children: data.source, + }, + }, + ], }, }); } @@ -571,13 +658,13 @@ props: { style: { color: C.text, - fontSize: titleLen > 60 ? 34 : 42, + fontSize: titleLen > 60 ? 40 : 50, fontWeight: 700, - lineHeight: 1.25, - marginTop: data.source ? 10 : 32, + lineHeight: 1.3, + marginTop: data.source ? 14 : 36, overflow: "hidden", }, - children: truncate(data.text || "Untitled Bookmark", 90), + children: truncate(data.text || "Untitled Bookmark", 80), }, }); @@ -587,21 +674,62 @@ props: { style: { color: C.textSecondary, - fontSize: 22, + fontSize: 26, lineHeight: 1.5, - marginTop: 16, + marginTop: 18, overflow: "hidden", }, - children: truncate(data.quote, 180), + children: truncate(data.quote, 160), }, }); } - return wrap(children, lightTint(typeColors.bookmark)); + return card(children, accent); } function buildCollectionImage(data: RecordData) { - const children: unknown[] = [headerWithBadge(data, typeColors.collection)]; + const accent = typeAccent.collection; + const children: unknown[] = [header(data, accent)]; + + const iconChildren: unknown[] = []; + if (data.icon) { + if (data.icon.startsWith("icon:")) { + const iconName = data.icon.replace("icon:", ""); + const uri = lucideIconUri(iconName, accent); + if (uri) { + iconChildren.push({ + type: "img", + props: { + src: uri, + width: 64, + height: 64, + style: { flexShrink: 0 }, + }, + }); + } + } else { + iconChildren.push({ + type: "span", + props: { + style: { fontSize: 72, lineHeight: 1 }, + children: data.icon, + }, + }); + } + } + iconChildren.push({ + type: "span", + props: { + style: { + color: C.text, + fontSize: (data.title || "").length > 24 ? 44 : 56, + fontWeight: 700, + overflow: "hidden", + lineHeight: 1.2, + }, + children: truncate(data.title || "Collection", 36), + }, + }); children.push({ type: "div", @@ -609,27 +737,10 @@ style: { display: "flex", alignItems: "center", - gap: 20, - marginTop: 36, + gap: 24, + marginTop: 40, }, - children: [ - { - type: "span", - props: { style: { fontSize: 52 }, children: data.icon }, - }, - { - type: "span", - props: { - style: { - color: C.text, - fontSize: 44, - fontWeight: 700, - overflow: "hidden", - }, - children: truncate(data.title, 36), - }, - }, - ], + children: iconChildren, }, }); @@ -638,18 +749,41 @@ props: { style: { color: data.description ? C.textSecondary : C.textMuted, - fontSize: 24, + fontSize: 28, lineHeight: 1.5, - marginTop: 20, + marginTop: 22, overflow: "hidden", }, children: data.description - ? truncate(data.description, 180) + ? truncate(data.description, 160) : "A collection on Margin", }, }); - return wrap(children, lightTint(typeColors.collection)); + children.push({ + type: "div", + props: { + style: { + display: "flex", + alignItems: "center", + gap: 12, + marginTop: "auto", + paddingTop: 24, + }, + children: [ + logoElement(24, C.textFaint), + { + type: "span", + props: { + style: { color: C.textFaint, fontSize: 20 }, + children: "margin.at", + }, + }, + ].filter(Boolean) as unknown[], + }, + }); + + return card(children, accent); } export const GET: APIRoute = async ({ url }) => { diff --git a/web/src/pages/search.astro b/web/src/pages/search.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/search.astro @@ -0,0 +1,12 @@ +--- + +import AppLayout from '../layouts/AppLayout.astro'; +import Search from '../views/core/Search'; + +const user = Astro.locals.user; +const q = Astro.url.searchParams.get('q') || undefined; +--- + + + + diff --git a/web/src/pages/settings.astro b/web/src/pages/settings.astro new file mode 100644 --- /dev/null +++ b/web/src/pages/settings.astro @@ -0,0 +1,11 @@ +--- + +import AppLayout from '../layouts/AppLayout.astro'; +import Settings from '../views/core/Settings'; + +const user = Astro.locals.user; +--- + + + + diff --git a/web/src/routes/wrappers.tsx b/web/src/routes/wrappers.tsx deleted file mode 100644 --- a/web/src/routes/wrappers.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useStore } from "@nanostores/react"; -import React from "react"; -import { Navigate, useParams } from "react-router-dom"; -import { $user } from "../store/auth"; -import CollectionDetail from "../views/collections/CollectionDetail"; -import AnnotationDetail from "../views/content/AnnotationDetail"; -import UrlPage from "../views/content/UrlPage"; -import UserUrlPage from "../views/content/UserUrlPage"; -import Profile from "../views/profile/Profile"; - -export function ProfileWrapper() { - const { did } = useParams(); - if (!did) return ; - return ; -} - -export function SelfProfileWrapper() { - const user = useStore($user); - if (!user) return ; - return ; -} - -export function CollectionDetailWrapper() { - const { handle, rkey } = useParams(); - return ; -} - -export function AnnotationDetailWrapper() { - return ; -} - -export function UserUrlWrapper() { - return ; -} - -export function UrlWrapper() { - return ; -} diff --git a/web/src/styles/global.css b/web/src/styles/global.css --- a/web/src/styles/global.css +++ b/web/src/styles/global.css @@ -169,3 +169,8 @@ transform: scale(1); } } + +::view-transition-old(root), +::view-transition-new(root) { + animation: none; +} diff --git a/web/src/views/About.tsx b/web/src/views/About.tsx --- a/web/src/views/About.tsx +++ b/web/src/views/About.tsx @@ -1,6 +1,6 @@ import React from "react"; import { useStore } from "@nanostores/react"; -import { Link } from "react-router-dom"; + import { $theme, cycleTheme } from "../store/theme"; import { $user } from "../store/auth"; import { @@ -118,13 +118,21 @@ const ExtensionIcon = browser === "firefox" ? FaFirefox : browser === "edge" ? FaEdge : Chrome; const extensionLabel = - browser === "firefox" ? "Firefox" : browser === "edge" ? "Edge" : "Chrome"; + browser === "firefox" + ? "Firefox" + : browser === "edge" + ? "Edge" + : browser === "safari" + ? "Chrome" + : "Chrome"; const [isScrolled, setIsScrolled] = React.useState(false); React.useEffect(() => { const handleScroll = () => { - setIsScrolled(window.scrollY > 20); + setIsScrolled((prev) => + prev ? window.scrollY > 10 : window.scrollY > 50, + ); }; window.addEventListener("scroll", handleScroll, { passive: true }); return () => window.removeEventListener("scroll", handleScroll); @@ -135,48 +143,57 @@