diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bc54724..e32c75c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,6 +5,9 @@ on: branches: [main] paths: - "docs/**" + - "server/openapi.yml" + - "frontend/ds/openapi.yml" + - "frontend/s2/openapi.yml" - ".github/workflows/docs.yml" workflow_dispatch: diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 93bdb73..29c8ad8 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,4 +1,6 @@ import { defineConfig } from 'vitepress'; +import { useSidebar } from 'vitepress-openapi'; +import { adminSpec, dsSpec, s2Spec } from './specs'; function sideItem(label: string, link: string, path: string) { return { @@ -7,6 +9,102 @@ function sideItem(label: string, link: string, path: string) { }; } +function operationItems(spec: object, prefix: string) { + return useSidebar({ spec, linkPrefix: prefix }) + .generateSidebarGroups({ linkPrefix: prefix }) + .flatMap((group) => group.items ?? []); +} + +const docsSidebar = [ + sideItem( + 'Introduction', + '/introduction', + '', + ), + sideItem( + 'Quickstart', + '/quickstart', + '', + ), + sideItem( + 'Benchmark', + '/benchmark', + '', + ), + { + ...sideItem( + 'Protocol', + '/protocol', + '', + ), + collapsed: false, + items: [ + { + ...sideItem( + 'Durable Streams', + '/protocol/durable-streams', + '', + ), + collapsed: true, + items: operationItems(dsSpec, '/protocol/durable-streams/'), + }, + { + ...sideItem( + 'S2', + '/protocol/s2', + '', + ), + collapsed: true, + items: operationItems(s2Spec, '/protocol/s2/'), + }, + ], + }, + { + ...sideItem( + 'Deployment', + '/deployment', + '', + ), + collapsed: false, + items: [ + sideItem( + 'AWS', + '/deployment/aws', + '', + ), + sideItem( + 'Fly.io', + '/deployment/fly', + '', + ), + ], + }, + { + ...sideItem( + 'Operations', + '/operations', + '', + ), + collapsed: false, + items: [ + sideItem( + 'Durability', + '/operations/durability', + '', + ), + { + ...sideItem( + 'Admin', + '/operations/api', + '', + ), + collapsed: true, + items: operationItems(adminSpec, '/operations/api/'), + }, + ], + }, +]; + export default defineConfig({ title: 'Stream Stack', description: 'A durable stream engine on object storage.', @@ -16,6 +114,11 @@ export default defineConfig({ srcDir: 'pages', vite: { publicDir: 'assets', + server: { + fs: { + allow: ['..'], + }, + }, }, markdown: { theme: 'github-dark', @@ -35,43 +138,7 @@ export default defineConfig({ { text: 'Home', link: '/' }, { text: 'Docs', link: '/introduction' }, ], - sidebar: [ - sideItem( - 'Introduction', - '/introduction', - '', - ), - sideItem( - 'Quickstart', - '/quickstart', - '', - ), - sideItem( - 'Benchmark', - '/benchmark', - '', - ), - { - ...sideItem( - 'Deployment', - '/deployment', - '', - ), - collapsed: false, - items: [ - sideItem( - 'AWS', - '/deployment/aws', - '', - ), - sideItem( - 'Fly.io', - '/deployment/fly', - '', - ), - ], - }, - ], + sidebar: docsSidebar, search: { provider: 'local', }, @@ -86,4 +153,9 @@ export default defineConfig({ copyright: '© 2026 Modak Labs. Apache 2.0.', }, }, + transformPageData(pageData) { + if (pageData.params?.pageTitle) { + pageData.title = pageData.params.pageTitle; + } + }, }); diff --git a/docs/.vitepress/specs.ts b/docs/.vitepress/specs.ts new file mode 100644 index 0000000..fe1ca53 --- /dev/null +++ b/docs/.vitepress/specs.ts @@ -0,0 +1,14 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { resolve } from 'node:path'; +import { parseSpec } from 'vitepress-openapi'; + +const streamstack = resolve(fileURLToPath(new URL('../..', import.meta.url))); + +function loadSpec(rel: string) { + return parseSpec(readFileSync(resolve(streamstack, rel), 'utf8')); +} + +export const dsSpec = loadSpec('frontend/ds/openapi.yml'); +export const s2Spec = loadSpec('frontend/s2/openapi.yml'); +export const adminSpec = loadSpec('server/openapi.yml'); diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index 5ccbef4..d9c8ea4 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -234,14 +234,12 @@ } .VPDoc h1 { - font-size: clamp(2.25rem, 5vw, 3.5rem); - line-height: 1.05; + font-size: clamp(1.7rem, 3vw, 2.05rem); + line-height: 1.15; } -.vp-doc h2:first-of-type { - margin-top: 1.25rem; - padding-top: 0; - border-top: none; +.vp-doc h2 { + margin-top: 2rem; } .vp-doc div[class*="language-"] { diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 2300b26..4382c2e 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -1,11 +1,13 @@ import DefaultTheme from 'vitepress/theme'; import { h } from 'vue'; import type { Theme } from 'vitepress'; +import { theme as openapiTheme } from 'vitepress-openapi/client'; import ColorSwitch from './ColorSwitch.vue'; import HeroDither from './HeroDither.vue'; import HomeChips from './HomeChips.vue'; import HomeBento from './HomeBento.vue'; import SiteLogo from './SiteLogo.vue'; +import 'vitepress-openapi/dist/style.css'; import './custom.css'; export default { @@ -21,4 +23,7 @@ export default { h('p', { class: 'ss-doc-copyright' }, '© 2026 Modak Labs. Apache 2.0.'), }); }, + enhanceApp({ app }) { + openapiTheme.enhanceApp({ app }); + }, } satisfies Theme; diff --git a/docs/package-lock.json b/docs/package-lock.json index ba85e1c..771a306 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -6,7 +6,8 @@ "": { "name": "streamstack-docs", "dependencies": { - "@paper-design/shaders": "0.0.80" + "@paper-design/shaders": "0.0.80", + "vitepress-openapi": "^0.2.4" }, "devDependencies": { "vitepress": "^1.6.4" @@ -16,7 +17,6 @@ "version": "1.22.0", "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.22.0.tgz", "integrity": "sha512-BFR6zNowNKcY7Ou7TaJc9QWexES4YKPbmf/OTFofpdsdhz4x6q0lbxp3duO0EHnyrN7rE4ba/TSXuY+BDGu4+g==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -32,7 +32,6 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", @@ -43,7 +42,6 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.17.7" @@ -56,7 +54,6 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.17.7" @@ -70,7 +67,6 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", - "dev": true, "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -81,7 +77,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.56.0.tgz", "integrity": "sha512-7r4Z3NC7yU1oAQVWJNA2HX7tX481F3pJvCGyLIXiTdBcthz4Q/o21jwcMYDFkuI92UWTNBQQmHYgwHo1zS5dzg==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -97,7 +92,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.56.0.tgz", "integrity": "sha512-avmjXQSq+jadFO8Xl2em05/uQdQnEmHsJyOAdVbZkmVgpMfxL12aJwVVfGNwYr9nulcpuJN1X0lTaQ5wxuNGcA==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -113,7 +107,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.56.0.tgz", "integrity": "sha512-v2TPStUhY//ripPjIVclZ8AWc7DEGooXULZGFlFu37zNatgHjw34oZZ+OSbbc/YHO+xZwPl62I1k8xH1m4S2eg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14.0.0" @@ -123,7 +116,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.56.0.tgz", "integrity": "sha512-P0ehROpM4Sem3Sqo5x2cKPgj67D3G3jy0rh1Amwkcvsfr6tkvIcdCmerieanqTF7NxUMPNFLkpIFeMO8Rpa50w==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -139,7 +131,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.56.0.tgz", "integrity": "sha512-SXK3Vn3WVxyzbm31oePZBJkp1wpOyuWdd4B/Pv7n0aXDxmeSWhC1R1FC1517mMrFAIaPH4Rt0x6RUe7ZNjz8FA==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -155,7 +146,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.56.0.tgz", "integrity": "sha512-5+ZdX8garFnmycnZgKhtXHePEaLj5zqDxI/0lkhhluzCcvTn0/PvvTirTg8hHYetQHvn7GDyeAiqTAieMvMW4A==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -171,7 +161,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.56.0.tgz", "integrity": "sha512-+mKUdYvqOi0BcvpAEyCEw49vSBptufIcfibtHz2bdr1pI789M46Yt0uQEk/sxtK3teh71OQvVFHaTDzShUWewQ==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -187,7 +176,6 @@ "version": "1.56.0", "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.56.0.tgz", "integrity": "sha512-9g/zj+AZx5moFcdFIrYQoVrueXivjUcc3MQHtCYT8WhIuk1lUh1AyEhvJCS0XBZld09cLvd1AZ3BvDBpVpX2UA==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -203,7 +191,6 @@ "version": "1.56.0", "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.56.0.tgz", "integrity": "sha512-Qf3Sr6f9A9uxCZUf3MXS0d2b877uYzEB5yxqpVGXAhcJnBCQjrRRon0KvefpGkxy+BshrIJs96OUoMtGqXTFDA==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -219,7 +206,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.56.0.tgz", "integrity": "sha512-GXWG1rWc5wu8hY4N33Y3b6ernY6sAdAvmKWN/zHAiACOx40WnpG0TVX5YazCAr/9gOYGInSiM2A0y2jy2xbiDA==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0", @@ -235,7 +221,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.56.0.tgz", "integrity": "sha512-7t24cBxaInS3mZb7ddEaZT/tp6q+/aR4YttsQVyP1/i+LmwPR34atO35KjaLFCcRVrlP7sYOAqkCfg6lIRB+ew==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0" @@ -248,7 +233,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.56.0.tgz", "integrity": "sha512-R7ePHgVYmDFjZpvrsVAfbDz/d4RxKAYZ5/vgLfIsCVRZRryjWl/3INOxpOICzitehQ5FjNtNjcLQTrmHPTcHBQ==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0" @@ -261,7 +245,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.56.0.tgz", "integrity": "sha512-PIOUXlSnrqM0S+WOgDRb4RzotydJH7ZoT6tOyL7tAO7qJOfvX5wsEW8Pe+PMKMwvuI4/gIyK9cg2H7lJXqnc4Q==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/client-common": "5.56.0" @@ -274,7 +257,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -284,7 +266,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -294,7 +275,6 @@ "version": "7.29.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.8" @@ -310,7 +290,6 @@ "version": "7.29.8", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -324,14 +303,12 @@ "version": "3.8.2", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", - "dev": true, "license": "MIT" }, "node_modules/@docsearch/js": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", - "dev": true, "license": "MIT", "dependencies": { "@docsearch/react": "3.8.2", @@ -342,7 +319,6 @@ "version": "3.8.2", "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.17.7", @@ -378,7 +354,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -395,7 +370,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -412,7 +386,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -429,7 +402,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -446,7 +418,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -463,7 +434,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -480,7 +450,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -497,7 +466,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -514,7 +482,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -531,7 +498,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -548,7 +514,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -565,7 +530,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -582,7 +546,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -599,7 +562,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -616,7 +578,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -633,7 +594,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -650,7 +610,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -667,7 +626,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -684,7 +642,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -701,7 +658,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -718,7 +674,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -735,7 +690,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -752,7 +706,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -762,11 +715,72 @@ "node": ">=12" } }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@floating-ui/vue": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.11.tgz", + "integrity": "sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6", + "@floating-ui/utils": "^0.2.11", + "vue-demi": ">=0.13.0" + } + }, + "node_modules/@floating-ui/vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@iconify-json/simple-icons": { "version": "1.2.93", "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.93.tgz", "integrity": "sha512-/XhANjfGYOuqvSR3TmUnkQkINvQ4GVjVuukvymRbxtVFBvIq/yiXJqCDycKcQPT401OYT9H2vIY6ihAlz1QIAw==", - "dev": true, "license": "CC0-1.0", "dependencies": { "@iconify/types": "*" @@ -776,14 +790,30 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "dev": true, "license": "MIT" }, + "node_modules/@internationalized/date": { + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.3.tgz", + "integrity": "sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz", + "integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@napi-rs/lzma-linux-x64-gnu": { @@ -793,7 +823,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -816,7 +845,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -830,7 +858,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -844,7 +871,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -858,7 +884,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -872,7 +897,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -886,7 +910,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -900,7 +923,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -914,7 +936,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -928,7 +949,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -942,7 +962,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -956,7 +975,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -970,7 +988,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -984,7 +1001,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -998,7 +1014,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1012,7 +1027,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1026,7 +1040,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1040,7 +1053,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1054,7 +1066,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1068,7 +1079,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1082,7 +1092,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1096,7 +1105,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1110,7 +1118,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1124,7 +1131,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1138,7 +1144,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1152,7 +1157,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1163,7 +1167,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/engine-javascript": "2.5.0", @@ -1178,7 +1181,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "2.5.0", @@ -1190,7 +1192,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "2.5.0", @@ -1201,7 +1202,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "2.5.0" @@ -1211,7 +1211,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "2.5.0" @@ -1221,7 +1220,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/core": "2.5.0", @@ -1232,7 +1230,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -1243,21 +1240,53 @@ "version": "10.0.2", "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "dev": true, "license": "MIT" }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.7", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.7.tgz", + "integrity": "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.35", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.35.tgz", + "integrity": "sha512-lOfSPvgPdlaH6Qy+CyIc3XpycitaSQ9GECndGpTuDiu+uDA1am+90yWXwzDSd/20ZM196ggWJLS+Qb6WjVd/OA==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, "node_modules/@types/hast": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "*" @@ -1267,14 +1296,12 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/markdown-it": { "version": "14.1.2", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "dev": true, "license": "MIT", "dependencies": { "@types/linkify-it": "^5", @@ -1285,7 +1312,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "*" @@ -1295,35 +1321,30 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true, "license": "MIT" }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/web-bluetooth": { "version": "0.0.21", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", - "dev": true, "license": "MIT" }, "node_modules/@ungap/structured-clone": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", - "dev": true, "license": "ISC" }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", - "dev": true, "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" @@ -1337,7 +1358,6 @@ "version": "3.5.41", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz", "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.8", @@ -1351,7 +1371,6 @@ "version": "3.5.41", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-core": "3.5.41", @@ -1362,7 +1381,6 @@ "version": "3.5.41", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.8", @@ -1380,7 +1398,6 @@ "version": "3.5.41", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.41", @@ -1391,7 +1408,6 @@ "version": "7.7.10", "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.10.tgz", "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", - "dev": true, "license": "MIT", "dependencies": { "@vue/devtools-kit": "^7.7.10" @@ -1401,7 +1417,6 @@ "version": "7.7.10", "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", - "dev": true, "license": "MIT", "dependencies": { "@vue/devtools-shared": "^7.7.10", @@ -1417,7 +1432,6 @@ "version": "7.7.10", "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", - "dev": true, "license": "MIT", "dependencies": { "rfdc": "^1.4.1" @@ -1427,7 +1441,6 @@ "version": "3.5.41", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz", "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", - "dev": true, "license": "MIT", "dependencies": { "@vue/shared": "3.5.41" @@ -1437,7 +1450,6 @@ "version": "3.5.41", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz", "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", - "dev": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.41", @@ -1448,7 +1460,6 @@ "version": "3.5.41", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", - "dev": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.41", @@ -1461,7 +1472,6 @@ "version": "3.5.41", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz", "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-ssr": "3.5.41", @@ -1473,14 +1483,12 @@ "version": "3.5.41", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", - "dev": true, "license": "MIT" }, "node_modules/@vueuse/core": { "version": "12.8.2", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.21", @@ -1496,7 +1504,6 @@ "version": "12.8.2", "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", - "dev": true, "license": "MIT", "dependencies": { "@vueuse/core": "12.8.2", @@ -1563,7 +1570,6 @@ "version": "12.8.2", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -1573,7 +1579,6 @@ "version": "12.8.2", "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", - "dev": true, "license": "MIT", "dependencies": { "vue": "^3.5.13" @@ -1586,7 +1591,6 @@ "version": "5.56.0", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.56.0.tgz", "integrity": "sha512-PrqppUmhT4ENdas2pH9caE7efUcxy6EcSFhWzosiVuQBzu2tQ5yLTI6jwomT/1cuBnivzGfxiJCqDNN9FRRh+Q==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/abtesting": "1.22.0", @@ -1608,11 +1612,22 @@ "node": ">= 14.0.0" } }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/birpc": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -1622,7 +1637,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -1633,7 +1647,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -1644,18 +1657,37 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -1666,7 +1698,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", - "dev": true, "license": "MIT", "dependencies": { "is-what": "^5.2.0" @@ -1682,14 +1713,18 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1699,7 +1734,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dev": true, "license": "MIT", "dependencies": { "dequal": "^2.0.0" @@ -1713,14 +1747,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "dev": true, "license": "MIT" }, "node_modules/entities": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -1733,7 +1765,6 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -1772,14 +1803,12 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, "license": "MIT" }, "node_modules/focus-trap": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", - "dev": true, "license": "MIT", "dependencies": { "tabbable": "^6.4.0" @@ -1789,7 +1818,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -1804,7 +1832,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -1828,7 +1855,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -1842,14 +1868,12 @@ "version": "5.5.3", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "dev": true, "license": "MIT" }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -1860,7 +1884,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1869,11 +1892,20 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/lucide-vue-next": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-1.0.0.tgz", + "integrity": "sha512-V6SPvx1IHTj/UY+FrIYWV5faISsPSb8BnWSFDxAtezWKvWc9ZZ40PDrdu1/Qb5vg4lHWr1hs1BAMGVGm6V1Xdg==", + "deprecated": "Package deprecated. Please use @lucide/vue instead.", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -1883,14 +1915,18 @@ "version": "8.11.1", "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/markdown-it-link-attributes": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz", + "integrity": "sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ==", "license": "MIT" }, "node_modules/mdast-util-to-hast": { "version": "13.2.1", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -1912,7 +1948,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -1933,7 +1968,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -1950,7 +1984,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -1972,7 +2005,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -1989,7 +2021,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -2006,21 +2037,18 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", - "dev": true, "license": "MIT" }, "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", - "dev": true, "funding": [ { "type": "github", @@ -2035,11 +2063,16 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "license": "MIT" + }, "node_modules/oniguruma-to-es": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex-xs": "^1.0.0", @@ -2051,21 +2084,18 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/postcss": { "version": "8.5.26", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2094,7 +2124,6 @@ "version": "10.29.8", "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz", "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -2113,7 +2142,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -2124,7 +2152,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", - "dev": true, "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" @@ -2134,7 +2161,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "dev": true, "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" @@ -2144,21 +2170,81 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "dev": true, "license": "MIT" }, + "node_modules/reka-ui": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.10.3.tgz", + "integrity": "sha512-nJGZbwcha8AcP2wbnjodfzsciTKBqp1mIzepC9Pi2xDI/YK3++ej3vpJOSPyxqrkq1Oorj4K+BdJkbVCV6x6ag==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.13", + "@floating-ui/vue": "^1.1.6", + "@internationalized/date": "^3.5.0", + "@internationalized/number": "^3.5.0", + "@tanstack/vue-virtual": "^3.12.0", + "@vueuse/core": "^14.1.0", + "@vueuse/shared": "^14.1.0", + "aria-hidden": "^1.2.4", + "defu": "^6.1.5", + "ohash": "^2.0.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/zernonia" + }, + "peerDependencies": { + "vue": ">= 3.4.0" + } + }, + "node_modules/reka-ui/node_modules/@vueuse/core": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.4.0.tgz", + "integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.4.0", + "@vueuse/shared": "14.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/reka-ui/node_modules/@vueuse/metadata": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.4.0.tgz", + "integrity": "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/reka-ui/node_modules/@vueuse/shared": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.4.0.tgz", + "integrity": "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, "license": "MIT" }, "node_modules/rollup": { "version": "4.62.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.9" @@ -2204,7 +2290,6 @@ "version": "2.17.3", "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "dev": true, "license": "MIT", "peer": true }, @@ -2212,7 +2297,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/core": "2.5.0", @@ -2229,7 +2313,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2239,7 +2322,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -2250,7 +2332,6 @@ "version": "14.0.1", "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2260,7 +2341,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dev": true, "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", @@ -2275,7 +2355,6 @@ "version": "2.2.6", "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", - "dev": true, "license": "MIT", "dependencies": { "copy-anything": "^4" @@ -2288,25 +2367,28 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", - "dev": true, "license": "MIT" }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -2320,7 +2402,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -2334,7 +2415,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -2348,7 +2428,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -2364,7 +2443,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -2379,7 +2457,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -2394,7 +2471,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -2409,7 +2485,6 @@ "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -2469,7 +2544,6 @@ "version": "1.6.4", "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", - "dev": true, "license": "MIT", "dependencies": { "@docsearch/css": "3.8.2", @@ -2507,11 +2581,69 @@ } } }, + "node_modules/vitepress-openapi": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/vitepress-openapi/-/vitepress-openapi-0.2.4.tgz", + "integrity": "sha512-wzZ4n++Ais5nUG9X7h5V0wF+uvfu17xEb2RpK8Mdp0blGHU56E7WbSzBY8yr3/K2k0164XxVn/r+zYZIvqBKYQ==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "^14.2.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-vue-next": "^1.0.0", + "markdown-it-link-attributes": "^4.0.1", + "reka-ui": "^2.9.5" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "vitepress": ">=1.0.0", + "vue": "^3.0.0" + } + }, + "node_modules/vitepress-openapi/node_modules/@vueuse/core": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.4.0.tgz", + "integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.4.0", + "@vueuse/shared": "14.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vitepress-openapi/node_modules/@vueuse/metadata": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.4.0.tgz", + "integrity": "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vitepress-openapi/node_modules/@vueuse/shared": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.4.0.tgz", + "integrity": "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/vue": { "version": "3.5.41", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz", "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.41", @@ -2533,7 +2665,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "dev": true, "license": "MIT", "funding": { "type": "github", diff --git a/docs/package.json b/docs/package.json index da61b3c..2c60a03 100644 --- a/docs/package.json +++ b/docs/package.json @@ -11,6 +11,7 @@ "vitepress": "^1.6.4" }, "dependencies": { - "@paper-design/shaders": "0.0.80" + "@paper-design/shaders": "0.0.80", + "vitepress-openapi": "^0.2.4" } } diff --git a/docs/pages/deployment.md b/docs/pages/deployment.md index 6d80035..4be10dd 100644 --- a/docs/pages/deployment.md +++ b/docs/pages/deployment.md @@ -1,6 +1,6 @@ # Deployment -Docker image: `harness/docker`. Deploy `StreamStack` on `local`, [aws](/deployment/aws), or [fly](/deployment/fly). Each node: raft, metadata, and the storage engine in one JVM. [Durable Streams](https://github.com/durable-streams/durable-streams) and [S2](https://github.com/s2-streamstore/s2) are protocol facades over the same engine. Pick one per deployment. +Docker image: `harness/docker`. Deploy `StreamStack` on `local`, [aws](/deployment/aws), or [fly](/deployment/fly). Each node: raft, metadata, and the storage engine in one JVM. [Durable Streams](/protocol/durable-streams) and [S2](/protocol/s2) are protocol facades over the same engine. Pick one per deployment. Storage configuration from the topo file (`TOPO`). `DATA_BUCKET` / `WAL_BUCKET` / `BUCKET_NAME` override the topo S3 URIs. @@ -37,48 +37,4 @@ URI forms: `0@s3://bucket?region=us-east-1`, `-2@file:///path`, `0@mem://bucket` `--topo` requires `--node-id`. CLI flags override topo values and export credentials in the environment that starts the process. -## Admin and health - -Every node serves an admin plane on a separate port, independent of the protocol facade. Default port is `9090`; set it per node with `adminPort` in the topo, `--admin-port` on the CLI, or `ADMIN_PORT` in the Docker image. Set `0` to disable. - -| Endpoint | Description | -|----------|-------------| -| `GET /health` | Liveness. `200 ok` as soon as the process is up, before raft and storage are ready | -| `GET /ready` | Readiness. `200` once raft has a leader, the node is registered, and storage started; `503` otherwise | -| `GET /admin/cluster` | Cluster and raft status: leader, applied index, stream and object counts | -| `GET /admin/nodes` | Registered nodes with advertised addresses and epochs | -| `GET /admin/streams/{name}` | Owner and metadata for a stream | -| `POST /admin/peers` | Add a raft peer: `{"peer": "host:port"}` (leader only) | -| `DELETE /admin/peers/{peer}` | Remove a raft peer (leader only) | -| `POST /admin/transfer-leader` | Transfer raft leadership: `{"peer": "host:port"}` (leader only) | -| `POST /admin/snapshot` | Trigger a metadata raft snapshot immediately | -| `GET /admin/snapshots` | Archived metadata snapshots in object storage, with archive health | - -The admin port also serves a read-only dashboard at `/` with cluster, node, and raft status. - -Point load balancer target-group health checks at `/ready`. On shutdown the node flips `/ready` to `503` first and waits `shutdownDrainSec` (topo config, default `0`) before closing, so the load balancer drains in-flight traffic. - -At startup the node verifies the storage and WAL buckets are reachable, retrying with backoff for up to ten attempts before failing; `/health` responds while it retries. - -## Metadata durability - -Stream data lives in object storage, but metadata (streams, object registry, KV) lives in each node's raft `dataDir`: a periodic local snapshot plus a bounded log. Raft replication covers single-node loss; the snapshot archive covers losing the data directories of a quorum. - -The raft leader archives every metadata snapshot to the storage bucket under `_streamstack/metadata/{clusterId}/snapshots/`, keeping the last five. Archival is asynchronous and never blocks the snapshot itself; failures are logged and reported on `GET /admin/snapshots` and the dashboard. Disable with `metadataArchive: false` in the topo config, `--metadata-archive false` on the CLI, or `METADATA_ARCHIVE=false` in the Docker image. Keep the `_streamstack/` key prefix reserved for StreamStack internals. - -Run production nodes on durable disks (EBS, not instance storage) for `dataDir`, with a stable DNS name per node. - -### Replacing a node - -- Disk survived: attach the volume to a replacement machine with the same `nodeId` and DNS name and start it. The node replays its local snapshot and log and rejoins. -- Disk lost: start a replacement with the same `nodeId` and DNS name and an empty `dataDir`. The raft leader streams its latest snapshot to the new peer and replays the log tail. If the address changed, fix membership with `POST /admin/peers` and `DELETE /admin/peers/{peer}`. - -### Restoring from the archive - -If a quorum of data directories is lost, bootstrap from the archive: - -1. Start node 1 with an empty `dataDir`, a single-peer topo, and `--restore-from-storage true` (or `RESTORE_FROM_STORAGE=true`). The node loads the latest archived snapshot before forming raft and persists it into a local raft snapshot right after election. -2. Verify with `GET /admin/cluster` and `GET /admin/streams/{name}`. -3. Add nodes 2 and 3 with empty data dirs via `POST /admin/peers`; they receive the snapshot from the leader. - -Restore only runs on a fresh `dataDir`; if local raft state exists it is skipped, and a log without a snapshot fails with instructions to wipe first. Data appended after the last archived snapshot keeps its bytes in the bucket but has no metadata entries; the snapshot interval (30s) bounds that window. \ No newline at end of file +Admin plane, health checks, and metadata restore: [Operations](/operations). \ No newline at end of file diff --git a/docs/pages/operations.md b/docs/pages/operations.md new file mode 100644 index 0000000..be35049 --- /dev/null +++ b/docs/pages/operations.md @@ -0,0 +1,11 @@ +# Operations + +Every node optionally serves an admin-plane on a separate port, independent of the protocol facade. Default port is `9090`. Set it per node with `adminPort` in the topo, `--admin-port` on the CLI, or `ADMIN_PORT` in the Docker image. Set `0` to disable. + +The admin APIs also power the dashboard with cluster, node, and raft status. + +## Health + +Point load-balancer target-group health checks at `/ready`. On shutdown the node flips `/ready` to `503` first and waits `shutdownDrainSec` (topo config, default `0`) before closing, so the load balancer drains in-flight traffic. + +At startup the node verifies the storage and WAL buckets are reachable, retrying with backoff for up to ten attempts before failing. `/health` responds while it retries. diff --git a/docs/pages/operations/api.md b/docs/pages/operations/api.md new file mode 100644 index 0000000..5a502a6 --- /dev/null +++ b/docs/pages/operations/api.md @@ -0,0 +1,8 @@ +--- +aside: false +outline: false +--- + +# Admin API + +Admin plane on a separate port from the protocol facade. Default port is `9090`. diff --git a/docs/pages/operations/api/[operationId].md b/docs/pages/operations/api/[operationId].md new file mode 100644 index 0000000..2f75be3 --- /dev/null +++ b/docs/pages/operations/api/[operationId].md @@ -0,0 +1,13 @@ +--- +aside: false +outline: false +--- + + + + diff --git a/docs/pages/operations/api/[operationId].paths.ts b/docs/pages/operations/api/[operationId].paths.ts new file mode 100644 index 0000000..6d3ede3 --- /dev/null +++ b/docs/pages/operations/api/[operationId].paths.ts @@ -0,0 +1,15 @@ +import { usePaths } from 'vitepress-openapi'; +import { adminSpec } from '../../../.vitepress/specs'; + +export default { + paths() { + return usePaths({ spec: adminSpec }) + .getPathsByVerbs() + .map(({ operationId, summary }) => ({ + params: { + operationId, + pageTitle: summary, + }, + })); + }, +}; diff --git a/docs/pages/operations/durability.md b/docs/pages/operations/durability.md new file mode 100644 index 0000000..e3528aa --- /dev/null +++ b/docs/pages/operations/durability.md @@ -0,0 +1,30 @@ +# Durability + +Stream data lives in object-storage, but metadata (streams, object registry, KV) lives in each node's raft `dataDir`: a periodic local snapshot and a bounded log. Raft replication covers single-node loss. The snapshot archive covers losing the data directories of a quorum. + +The raft leader archives every metadata snapshot to the storage bucket under `_streamstack/metadata/{clusterId}/snapshots/`, keeping the last five. Archival is asynchronous and never blocks the snapshot itself. + +::: info +Run production nodes on durable disks (EBS, not instance storage) for `dataDir`, with a stable DNS name per node. +::: + +Failures are logged and reported on `GET /admin/snapshots` and visible on the dashboard. Disable with `metadataArchive: false` in the topo config, `--metadata-archive false` on the CLI, or `METADATA_ARCHIVE=false` in the Docker image. + +## Replacing a node + +- Disk survived: attach the volume to a replacement machine with the same `nodeId`, DNS name and start it. The node replays its local snapshot, log and rejoins. +- Disk lost: start the replacement machine with the same `nodeId`, DNS name and an empty `dataDir`. The raft leader streams its latest snapshot to the new peer and replays the log tail. If the address changed, fix membership with `POST /admin/peers` and `DELETE /admin/peers/{peer}`. + +## Restoring from archive + +If a quorum of data directories is lost, bootstrap from the archive: + +- Start node 1 with an empty `dataDir`, a single-peer topo, and `--restore-from-storage true` (or `RESTORE_FROM_STORAGE=true`). The node loads the latest archived snapshot before forming raft and persists it into a local raft snapshot right after election. +- Verify with `GET /admin/cluster` and `GET /admin/streams/{name}`. +- Add nodes 2 and 3 with empty data dirs via `POST /admin/peers`. They receive the snapshot from the leader. + +Restore only runs on a fresh `dataDir`. If local raft state exists it is skipped, and a log without a snapshot fails with instructions to wipe first. + +On graceful shutdown the node flushes the `WAL` tail, commits it to metadata, and archives a final snapshot before raft stops, so acknowledged writes always survive a planned stop followed by disk loss. + +For a hard crash where the data directories are also lost, commits since the last archived snapshot (bounded by the `30s` snapshot interval) keep their bytes in the bucket but lose their metadata entries. A crash alone, with disks intact, loses nothing because the local raft log and WAL replay cover the tail. diff --git a/docs/pages/protocol.md b/docs/pages/protocol.md new file mode 100644 index 0000000..10676c7 --- /dev/null +++ b/docs/pages/protocol.md @@ -0,0 +1,10 @@ +# Protocol + +Storage, metadata, and protocol are three decoupled layers. The engine writes to object-storage and replicates metadata with `JRaft`. A protocol facade sits on top and speaks one client protocol. + +[Durable Streams](https://github.com/durable-streams/durable-streams) and [S2](https://github.com/s2-streamstore/s2) are the supported facades today. Pick one per deployment. Both talk to the same streams, WAL, and raft group. The HTTP port is `4437` by default. + +Specs: + +- [Durable Streams](/protocol/durable-streams) +- [S2](/protocol/s2) diff --git a/docs/pages/protocol/durable-streams.md b/docs/pages/protocol/durable-streams.md new file mode 100644 index 0000000..a0c187d --- /dev/null +++ b/docs/pages/protocol/durable-streams.md @@ -0,0 +1,8 @@ +--- +aside: false +outline: false +--- + +# Durable Streams + +The [Durable Streams](https://github.com/durable-streams/durable-streams) protocol facade. Default HTTP port is `4437`. Endpoints are in the sidebar. diff --git a/docs/pages/protocol/durable-streams/[operationId].md b/docs/pages/protocol/durable-streams/[operationId].md new file mode 100644 index 0000000..1a00b92 --- /dev/null +++ b/docs/pages/protocol/durable-streams/[operationId].md @@ -0,0 +1,13 @@ +--- +aside: false +outline: false +--- + + + + diff --git a/docs/pages/protocol/durable-streams/[operationId].paths.ts b/docs/pages/protocol/durable-streams/[operationId].paths.ts new file mode 100644 index 0000000..6cb3990 --- /dev/null +++ b/docs/pages/protocol/durable-streams/[operationId].paths.ts @@ -0,0 +1,15 @@ +import { usePaths } from 'vitepress-openapi'; +import { dsSpec } from '../../../.vitepress/specs'; + +export default { + paths() { + return usePaths({ spec: dsSpec }) + .getPathsByVerbs() + .map(({ operationId, summary }) => ({ + params: { + operationId, + pageTitle: summary, + }, + })); + }, +}; diff --git a/docs/pages/protocol/s2.md b/docs/pages/protocol/s2.md new file mode 100644 index 0000000..497c207 --- /dev/null +++ b/docs/pages/protocol/s2.md @@ -0,0 +1,8 @@ +--- +aside: false +outline: false +--- + +# S2 + +The [S2](https://github.com/s2-streamstore/s2) protocol facade. Default HTTP port is `4437`. Endpoints are in the sidebar. diff --git a/docs/pages/protocol/s2/[operationId].md b/docs/pages/protocol/s2/[operationId].md new file mode 100644 index 0000000..8039ec9 --- /dev/null +++ b/docs/pages/protocol/s2/[operationId].md @@ -0,0 +1,13 @@ +--- +aside: false +outline: false +--- + + + + diff --git a/docs/pages/protocol/s2/[operationId].paths.ts b/docs/pages/protocol/s2/[operationId].paths.ts new file mode 100644 index 0000000..636727f --- /dev/null +++ b/docs/pages/protocol/s2/[operationId].paths.ts @@ -0,0 +1,15 @@ +import { usePaths } from 'vitepress-openapi'; +import { s2Spec } from '../../../.vitepress/specs'; + +export default { + paths() { + return usePaths({ spec: s2Spec }) + .getPathsByVerbs() + .map(({ operationId, summary }) => ({ + params: { + operationId, + pageTitle: summary, + }, + })); + }, +}; diff --git a/metadata/src/main/java/io/streamstack/metadata/raft/MetadataNode.java b/metadata/src/main/java/io/streamstack/metadata/raft/MetadataNode.java index da60b22..058a473 100644 --- a/metadata/src/main/java/io/streamstack/metadata/raft/MetadataNode.java +++ b/metadata/src/main/java/io/streamstack/metadata/raft/MetadataNode.java @@ -364,6 +364,39 @@ public CompletableFuture removePeer(PeerId peer) { return future; } + public void archiveFinalSnapshot(long timeoutMs) throws InterruptedException { + if (Objects.isNull(snapshotArchive) || !node.isLeader()) { + return; + } + + long target = stateMachine.appliedIndex(); + + if (snapshotArchive.lastArchivedIndex() >= target) { + return; + } + + try { + triggerSnapshot(); + } catch (Exception e) { + LOGGER.warn("failed to trigger final metadata snapshot nodeId={}: {}", nodeId, e.getMessage()); + } + + long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs); + + while (System.nanoTime() < deadline) { + if (snapshotArchive.lastArchivedIndex() >= target) { + LOGGER.info("archived final metadata snapshot nodeId={} appliedIndex={}", nodeId, target); + return; + } + + Thread.sleep(50); + } + + LOGGER.warn( + "timed out archiving final metadata snapshot nodeId={} appliedIndex={} lastArchivedIndex={}", + nodeId, target, snapshotArchive.lastArchivedIndex()); + } + public void triggerSnapshot() throws InterruptedException { CompletableFuture future = new CompletableFuture<>(); diff --git a/server/openapi.yml b/server/openapi.yml new file mode 100644 index 0000000..01370f7 --- /dev/null +++ b/server/openapi.yml @@ -0,0 +1,409 @@ +openapi: 3.1.0 +info: + title: StreamStack Admin + version: "1.0" + license: + name: Apache 2.0 + description: Admin plane on a separate port from the protocol facade. Default port is 9090. +servers: + - url: http://127.0.0.1:9090 +tags: + - name: Health + - name: Cluster + - name: Membership + - name: Streams + - name: Snapshots +paths: + /health: + get: + operationId: health + tags: [Health] + summary: Liveness + description: Returns as soon as the process is up, before raft and storage are ready. + responses: + "200": + description: Alive + content: + text/plain: + schema: + type: string + example: ok + /ready: + get: + operationId: ready + tags: [Health] + summary: Readiness + description: Ready once raft has a leader, the node is registered, and storage started. + responses: + "200": + description: Ready + content: + application/json: + schema: + $ref: "#/components/schemas/Readiness" + "503": + description: Not ready + content: + application/json: + schema: + $ref: "#/components/schemas/Readiness" + /admin/cluster: + get: + operationId: cluster + tags: [Cluster] + summary: Cluster and raft status + responses: + "200": + description: Cluster status + content: + application/json: + schema: + $ref: "#/components/schemas/Cluster" + /admin/nodes: + get: + operationId: nodes + tags: [Cluster] + summary: Registered nodes + responses: + "200": + description: Nodes + content: + application/json: + schema: + $ref: "#/components/schemas/Nodes" + /admin/streams/{name}: + get: + operationId: stream + tags: [Streams] + summary: Stream owner and metadata + parameters: + - name: name + in: path + required: true + description: Stream path without the leading slash. Nested paths are allowed. + schema: + type: string + example: demo/orders + responses: + "200": + description: Stream + content: + application/json: + schema: + $ref: "#/components/schemas/Stream" + "404": + $ref: "#/components/responses/Error" + /admin/peers: + post: + operationId: addPeer + tags: [Membership] + summary: Add a raft peer + description: Leader only. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/PeerBody" + responses: + "200": + description: Added + content: + application/json: + schema: + type: object + required: [added] + properties: + added: + type: string + example: 127.0.0.1:8092 + "400": + $ref: "#/components/responses/Error" + "409": + $ref: "#/components/responses/NotLeader" + /admin/peers/{peer}: + delete: + operationId: removePeer + tags: [Membership] + summary: Remove a raft peer + description: Leader only. + parameters: + - name: peer + in: path + required: true + description: Raft peer as host:port. + schema: + type: string + example: 127.0.0.1:8092 + responses: + "200": + description: Removed + content: + application/json: + schema: + type: object + required: [removed] + properties: + removed: + type: string + "400": + $ref: "#/components/responses/Error" + "409": + $ref: "#/components/responses/NotLeader" + /admin/transfer-leader: + post: + operationId: transferLeader + tags: [Membership] + summary: Transfer raft leadership + description: Leader only. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/PeerBody" + responses: + "200": + description: Transferred + content: + application/json: + schema: + type: object + required: [transferredTo] + properties: + transferredTo: + type: string + "400": + $ref: "#/components/responses/Error" + "409": + $ref: "#/components/responses/NotLeader" + "500": + $ref: "#/components/responses/Error" + /admin/snapshot: + post: + operationId: triggerSnapshot + tags: [Snapshots] + summary: Trigger a metadata raft snapshot + responses: + "200": + description: Snapshot triggered + content: + application/json: + schema: + type: object + required: [appliedIndex] + properties: + appliedIndex: + type: integer + format: int64 + /admin/snapshots: + get: + operationId: snapshots + tags: [Snapshots] + summary: Archived metadata snapshots + responses: + "200": + description: Archive status + content: + application/json: + schema: + $ref: "#/components/schemas/SnapshotArchive" + "404": + $ref: "#/components/responses/Error" +components: + schemas: + Readiness: + type: object + required: [ready, started, leaderKnown, registered] + properties: + ready: + type: boolean + started: + type: boolean + leaderKnown: + type: boolean + registered: + type: boolean + Cluster: + type: object + required: + - clusterId + - nodeId + - nodeEpoch + - advertisedAddress + - registered + - raft + - streamCount + - objectCount + - destroyedObjectBacklog + properties: + clusterId: + type: string + nodeId: + type: integer + nodeEpoch: + type: integer + format: int64 + advertisedAddress: + type: string + registered: + type: boolean + raft: + $ref: "#/components/schemas/Raft" + streamCount: + type: integer + objectCount: + type: integer + destroyedObjectBacklog: + type: integer + Raft: + type: object + required: [isLeader, appliedIndex, applySuccessCount, applyFailCount] + properties: + leader: + type: string + nullable: true + isLeader: + type: boolean + appliedIndex: + type: integer + format: int64 + applySuccessCount: + type: integer + format: int64 + applyFailCount: + type: integer + format: int64 + peers: + type: array + nullable: true + items: + type: string + Nodes: + type: object + required: [nodes] + properties: + nodes: + type: array + items: + $ref: "#/components/schemas/Node" + Node: + type: object + required: [nodeId, nodeEpoch, local] + properties: + nodeId: + type: integer + nodeEpoch: + type: integer + format: int64 + advertisedAddress: + type: string + nullable: true + local: + type: boolean + Stream: + type: object + required: [name, ownerLocal] + properties: + name: + type: string + streamId: + type: integer + format: int64 + nullable: true + ownerLocal: + type: boolean + ownerNodeId: + type: integer + ownerAdvertisedAddress: + type: string + meta: + $ref: "#/components/schemas/StreamMeta" + StreamMeta: + type: object + properties: + streamId: + type: integer + format: int64 + contentType: + type: string + ttlSeconds: + type: integer + nullable: true + expiresAt: + type: string + nullable: true + startOffset: + type: string + nextOffset: + type: string + closed: + type: boolean + PeerBody: + type: object + required: [peer] + properties: + peer: + type: string + example: 127.0.0.1:8092 + SnapshotArchive: + type: object + required: [archiveSuccessCount, archiveFailureCount, lastArchivedIndex, snapshots] + properties: + archiveSuccessCount: + type: integer + format: int64 + archiveFailureCount: + type: integer + format: int64 + lastArchivedIndex: + type: integer + format: int64 + snapshots: + type: array + items: + $ref: "#/components/schemas/ArchivedSnapshot" + ArchivedSnapshot: + type: object + required: [key, appliedIndex, timestampMs, size] + properties: + key: + type: string + appliedIndex: + type: integer + format: int64 + timestampMs: + type: integer + format: int64 + size: + type: integer + format: int64 + Error: + type: object + required: [error] + properties: + error: + type: string + NotLeader: + type: object + required: [error] + properties: + error: + type: string + example: not raft leader + leader: + type: string + nullable: true + responses: + Error: + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + NotLeader: + description: Not raft leader + content: + application/json: + schema: + $ref: "#/components/schemas/NotLeader" diff --git a/server/src/main/java/io/streamstack/server/StreamStackNode.java b/server/src/main/java/io/streamstack/server/StreamStackNode.java index 233f5f2..336807c 100644 --- a/server/src/main/java/io/streamstack/server/StreamStackNode.java +++ b/server/src/main/java/io/streamstack/server/StreamStackNode.java @@ -48,6 +48,7 @@ public final class StreamStackNode implements AutoCloseable { private static final int STORAGE_READINESS_MAX_ATTEMPTS = 10; private static final long STORAGE_READINESS_INITIAL_BACKOFF_MS = 1_000; private static final long STORAGE_READINESS_MAX_BACKOFF_MS = 30_000; + private static final long FINAL_SNAPSHOT_ARCHIVE_TIMEOUT_MS = 15_000; private final ServerConfig config; private final SnapshotArchive snapshotArchive; @@ -305,7 +306,19 @@ public void drainBeforeShutdown() { @Override public void close() { ready.set(false); + closeStreamingLayer(); + archiveFinalSnapshot(); + closeObjectStorages(); + closeMetadata(); + if (Objects.nonNull(adminServer)) { + adminServer.close(); + } + + started.set(false); + } + + private void closeStreamingLayer() { try { for (var stream : new ArrayList<>(streamService.openStreamSnapshot())) { try { @@ -332,7 +345,17 @@ public void close() { storage.shutdown(); } catch (Exception ignored) { } + } + private void archiveFinalSnapshot() { + try { + metadataNode.archiveFinalSnapshot(FINAL_SNAPSHOT_ARCHIVE_TIMEOUT_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private void closeObjectStorages() { if (Objects.nonNull(walObjectStorage)) { try { walObjectStorage.close(); @@ -344,18 +367,14 @@ public void close() { objectStorage.close(); } catch (Exception ignored) { } + } + private void closeMetadata() { metadataNode.close(); if (Objects.nonNull(snapshotArchive)) { snapshotArchive.close(); } - - if (Objects.nonNull(adminServer)) { - adminServer.close(); - } - - started.set(false); } private record WalBundle(WriteAheadLog wal, ObjectStorage objectStorage) { diff --git a/server/src/test/java/io/streamstack/server/MetadataArchiveRestoreIntegrationTest.java b/server/src/test/java/io/streamstack/server/MetadataArchiveRestoreIntegrationTest.java index 2e83953..ad2b48c 100644 --- a/server/src/test/java/io/streamstack/server/MetadataArchiveRestoreIntegrationTest.java +++ b/server/src/test/java/io/streamstack/server/MetadataArchiveRestoreIntegrationTest.java @@ -79,6 +79,44 @@ void archivesSnapshotsAndRestoresMetadataFromStorage() throws Exception { } } + @Test + void acknowledgedTailSurvivesGracefulShutdownAndRestore() throws Exception { + Path dataDir = tempDir.resolve("data"); + Path objectDir = tempDir.resolve("objects"); + + try (StreamStackNode node = new StreamStackNode( + config(dataDir, objectDir, 1, false, freePort(), 104857600L))) { + node.start(); + StreamService services = node.service(); + + assertTrue(services.lifecycle().create(new CreateCommand( + "/streams/tail", "text/plain", null, null, false, new byte[0])).created()); + assertTrue(services.append().append(new AppendCommand( + "/streams/tail", + List.of("acked-tail".getBytes(StandardCharsets.UTF_8)), + "text/plain", + null, + null, + false)).applied()); + } + + deleteRecursively(dataDir); + + try (StreamStackNode restored = new StreamStackNode( + config(dataDir, objectDir, 2, true, freePort(), 104857600L))) { + restored.start(); + assertTrue(restored.metadataNode().restoredFromArchive()); + StreamService services = restored.service(); + StreamMeta head = services.lifecycle().head("/streams/tail").orElseThrow(); + + assertEquals(1, head.nextOffset().recordOffset()); + ReadResult batch = services.read().read("/streams/tail", OffsetToken.beginning(), 1024, 0); + + assertEquals(1, batch.records().size()); + assertEquals("acked-tail", new String(batch.records().get(0).payload(), StandardCharsets.UTF_8)); + } + } + @Test void restoreSkippedWhenLocalSnapshotExists() throws Exception { Path dataDir = tempDir.resolve("data"); @@ -108,6 +146,11 @@ void restoreFailsWithoutArchivedSnapshot() { private ServerConfig config(Path dataDir, Path objectDir, long nodeEpoch, boolean restore, int raftPort) throws Exception { + return config(dataDir, objectDir, nodeEpoch, restore, raftPort, 1L); + } + + private ServerConfig config(Path dataDir, Path objectDir, long nodeEpoch, boolean restore, int raftPort, + long walUploadThreshold) throws Exception { ServerConfig.Builder builder = ServerConfig.builder() .nodeId(1) .nodeEpoch(nodeEpoch) @@ -122,7 +165,7 @@ private ServerConfig config(Path dataDir, Path objectDir, long nodeEpoch, boolea .routingMode(RoutingMode.LOCAL_ALWAYS) .restoreFromStorage(restore); - builder.streamConfig().walUploadThreshold(1L); + builder.streamConfig().walUploadThreshold(walUploadThreshold); return builder.build(); }