diff --git a/.changeset/fix-history-and-react-selectors.md b/.changeset/fix-history-and-react-selectors.md new file mode 100644 index 0000000..93726ef --- /dev/null +++ b/.changeset/fix-history-and-react-selectors.md @@ -0,0 +1,5 @@ +--- +'@codebelt/classy-store': patch +--- + +Restore mutable nested state after undo/redo while preserving reactive collection methods and keeping recorded snapshots unchanged. Refresh React selections when selectors or equality functions change, even when the store has not mutated. Update the library's development dependencies to their latest stable versions and add regression coverage for both fixes. diff --git a/.changeset/synchronous-react-subscriptions.md b/.changeset/synchronous-react-subscriptions.md new file mode 100644 index 0000000..55e4c86 --- /dev/null +++ b/.changeset/synchronous-react-subscriptions.md @@ -0,0 +1,9 @@ +--- +"@codebelt/classy-store": minor +--- + +React subscriptions now always notify synchronously, fixing controlled-input caret jumps without configuration. Remove the `sync` option from `useClassyStore` and hooks returned by `createStoreHook`, and remove imports of `UseClassyStoreOptions`. Selector options still accept `isEqual`. Ordinary `subscribe()` callbacks remain microtask-batched and retain their existing `sync` option. + +React selectors may run after each mutation; several writes are not an atomic transaction. React can batch the resulting renders within an event handler. Auto-tracked reads are isolated per render so conditional and abandoned renders do not retain stale dependencies, including for class and null-prototype snapshots. Unchanged tracked references remain stable across parent renders. + +Persistence hydration restores mutable nested defaults and custom merged values while preserving prototypes and user accessors. Add real-browser input regressions, lifecycle and SSR coverage, and packed-package checks for React 18 and 19. diff --git a/website-fumadocs/.gitignore b/website-fumadocs/.gitignore new file mode 100644 index 0000000..a710b82 --- /dev/null +++ b/website-fumadocs/.gitignore @@ -0,0 +1,27 @@ +# deps +/node_modules + +# generated content +.source + +# test & build +/coverage +/.next/ +/out/ +/build +/public/demos +*.tsbuildinfo + +# misc +.DS_Store +*.pem +/.pnp +.pnp.js +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# others +.env*.local +.vercel +next-env.d.ts diff --git a/website-fumadocs/README.md b/website-fumadocs/README.md new file mode 100644 index 0000000..d32710d --- /dev/null +++ b/website-fumadocs/README.md @@ -0,0 +1,32 @@ +# Classy Store Fumadocs Prototype + +This is an isolated Fumadocs version of the Classy Store documentation site. +The existing Docusaurus site remains in `../website`; this folder is a prototype +to iterate on before replacing or moving anything. + +The app uses Next.js static export and Fumadocs MDX. Its content is copied from +`../website/docs`, so edit these copies while working on the prototype. + +## Development + +```bash +bun run dev +``` + +Open http://localhost:3000. The examples bundle is rebuilt from +`../examples/react` when the dev server starts. + +## Static output + +```bash +bun run build +``` + +For a GitHub Pages project site at `codebelt.github.io/classy-store`, use: + +```bash +bun run build:pages +``` + +The exported files are written to `out/`. `public/.nojekyll` keeps GitHub Pages +from dropping Next.js's `_next` assets. diff --git a/website-fumadocs/app/(home)/layout.tsx b/website-fumadocs/app/(home)/layout.tsx new file mode 100644 index 0000000..5421ee4 --- /dev/null +++ b/website-fumadocs/app/(home)/layout.tsx @@ -0,0 +1,6 @@ +import {HomeLayout} from 'fumadocs-ui/layouts/home'; +import {baseOptions} from '@/lib/layout.shared'; + +export default function Layout({children}: LayoutProps<'/'>) { + return {children}; +} diff --git a/website-fumadocs/app/(home)/page.tsx b/website-fumadocs/app/(home)/page.tsx new file mode 100644 index 0000000..569c7fc --- /dev/null +++ b/website-fumadocs/app/(home)/page.tsx @@ -0,0 +1,81 @@ +import Link from 'next/link'; + +export default function HomePage() { + return ( +
+
+
+
+

+ Reactive state, made straightforward +

+

+ State management that reads like TypeScript. +

+

+ Define state and actions in plain classes. Classy Store handles + reactivity, computed getters, and framework subscriptions for you. +

+
+ + Read the docs + + + Explore examples + +
+
+ +
+            {`import { createClassyStore } from '@codebelt/classy-store';
+import { useClassyStore } from '@codebelt/classy-store/react';
+
+class CounterStore {
+  count = 0;
+
+  get doubled() {
+    return this.count * 2;
+  }
+
+  increment() {
+    this.count++;
+  }
+}
+
+const counter = createClassyStore(new CounterStore());
+const count = useClassyStore(counter, (state) => state.count);`}
+          
+
+
+ +
+
+

A class is the store

+

+ Fields hold state, methods update it, and getters are memoized + computed values. +

+
+
+

Use your framework

+

+ Bindings are available for React, Vue, Svelte, Solid, and Angular. +

+
+
+

Useful tools included

+

+ Persist state, inspect it with Redux DevTools, subscribe to a single + key, or add undo and redo. +

+
+
+
+ ); +} diff --git a/website-fumadocs/app/api/search/route.ts b/website-fumadocs/app/api/search/route.ts new file mode 100644 index 0000000..d0c255f --- /dev/null +++ b/website-fumadocs/app/api/search/route.ts @@ -0,0 +1,9 @@ +import {createFromSource} from 'fumadocs-core/search/server'; +import {source} from '@/lib/source'; + +export const revalidate = false; + +export const {staticGET: GET} = createFromSource(source, { + // https://docs.orama.com/docs/orama-js/supported-languages + language: 'english', +}); diff --git a/website-fumadocs/app/docs/[[...slug]]/page.tsx b/website-fumadocs/app/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000..20429ad --- /dev/null +++ b/website-fumadocs/app/docs/[[...slug]]/page.tsx @@ -0,0 +1,67 @@ +import { + DocsBody, + DocsDescription, + DocsPage, + DocsTitle, + MarkdownCopyButton, + ViewOptionsPopover, +} from 'fumadocs-ui/layouts/docs/page'; +import {createRelativeLink} from 'fumadocs-ui/mdx'; +import type {Metadata} from 'next'; +import {notFound} from 'next/navigation'; +import {getMDXComponents} from '@/components/mdx'; +import {getPageImageUrl, getPageMarkdownUrl, gitConfig} from '@/lib/shared'; +import {source} from '@/lib/source'; + +export default async function Page(props: PageProps<'/docs/[[...slug]]'>) { + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); + + const MDX = page.data.body; + const markdownUrl = getPageMarkdownUrl(page).url; + + return ( + + {page.data.title} + + {page.data.description} + +
+ + +
+ + + +
+ ); +} + +export async function generateStaticParams() { + return source.generateParams(); +} + +export async function generateMetadata( + props: PageProps<'/docs/[[...slug]]'>, +): Promise { + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); + + return { + title: page.data.title, + description: page.data.description, + openGraph: { + images: getPageImageUrl(page).url, + }, + }; +} diff --git a/website-fumadocs/app/docs/layout.tsx b/website-fumadocs/app/docs/layout.tsx new file mode 100644 index 0000000..a968657 --- /dev/null +++ b/website-fumadocs/app/docs/layout.tsx @@ -0,0 +1,11 @@ +import {DocsLayout} from 'fumadocs-ui/layouts/docs'; +import {baseOptions} from '@/lib/layout.shared'; +import {source} from '@/lib/source'; + +export default function Layout({children}: LayoutProps<'/docs'>) { + return ( + + {children} + + ); +} diff --git a/website-fumadocs/app/global.css b/website-fumadocs/app/global.css new file mode 100644 index 0000000..c80ae57 --- /dev/null +++ b/website-fumadocs/app/global.css @@ -0,0 +1,17 @@ +@import "tailwindcss"; +@import "fumadocs-ui/css/neutral.css"; +@import "fumadocs-ui/css/preset.css"; + +html { + scrollbar-gutter: stable; + font-family: + ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; +} + +html > body[data-scroll-locked] { + /* biome-ignore lint/complexity/noImportantStyles: Reset scroll-lock compensation while scrollbar-gutter reserves the space. */ + margin-right: 0px !important; + /* biome-ignore lint/complexity/noImportantStyles: Reset the matching scroll-lock compensation value. */ + --removed-body-scroll-bar-size: 0px !important; +} diff --git a/website-fumadocs/app/icon.ico b/website-fumadocs/app/icon.ico new file mode 100644 index 0000000..c01d54b Binary files /dev/null and b/website-fumadocs/app/icon.ico differ diff --git a/website-fumadocs/app/layout.tsx b/website-fumadocs/app/layout.tsx new file mode 100644 index 0000000..9f55831 --- /dev/null +++ b/website-fumadocs/app/layout.tsx @@ -0,0 +1,23 @@ +import type {Metadata} from 'next'; +import {Provider} from '@/components/provider'; +import './global.css'; + +export const metadata: Metadata = { + metadataBase: new URL('https://codebelt.github.io/classy-store/'), + title: { + default: 'Classy Store', + template: '%s | Classy Store', + }, + description: + 'Class-based reactive state management for React, Vue, Svelte, Solid, and Angular.', +}; + +export default function Layout({children}: LayoutProps<'/'>) { + return ( + + + {children} + + + ); +} diff --git a/website-fumadocs/app/llms-full.txt/route.ts b/website-fumadocs/app/llms-full.txt/route.ts new file mode 100644 index 0000000..a8bef12 --- /dev/null +++ b/website-fumadocs/app/llms-full.txt/route.ts @@ -0,0 +1,8 @@ +import {withDocsBasePath} from '@/lib/shared'; +import {docsLlms} from '@/lib/source'; + +export const revalidate = false; + +export async function GET() { + return new Response(withDocsBasePath(await docsLlms.full())); +} diff --git a/website-fumadocs/app/llms.mdx/docs/[[...slug]]/route.ts b/website-fumadocs/app/llms.mdx/docs/[[...slug]]/route.ts new file mode 100644 index 0000000..fc7e566 --- /dev/null +++ b/website-fumadocs/app/llms.mdx/docs/[[...slug]]/route.ts @@ -0,0 +1,27 @@ +import {notFound} from 'next/navigation'; +import {getPageMarkdownUrl} from '@/lib/shared'; +import {docsLlms, source} from '@/lib/source'; + +export const revalidate = false; + +export async function GET( + _req: Request, + {params}: RouteContext<'/llms.mdx/docs/[[...slug]]'>, +) { + const {slug} = await params; + // remove the appended "content.md" + const page = source.getPage(slug?.slice(0, -1)); + if (!page) notFound(); + + return new Response(await docsLlms.page(page), { + headers: { + 'Content-Type': 'text/markdown', + }, + }); +} + +export function generateStaticParams() { + return source.getPages().map((page) => ({ + slug: getPageMarkdownUrl(page).segments, + })); +} diff --git a/website-fumadocs/app/llms.txt/route.ts b/website-fumadocs/app/llms.txt/route.ts new file mode 100644 index 0000000..9fe3122 --- /dev/null +++ b/website-fumadocs/app/llms.txt/route.ts @@ -0,0 +1,8 @@ +import {withDocsBasePath} from '@/lib/shared'; +import {docsLlms} from '@/lib/source'; + +export const revalidate = false; + +export async function GET() { + return new Response(withDocsBasePath(await docsLlms.index())); +} diff --git a/website-fumadocs/app/og/docs/[...slug]/route.tsx b/website-fumadocs/app/og/docs/[...slug]/route.tsx new file mode 100644 index 0000000..f9990df --- /dev/null +++ b/website-fumadocs/app/og/docs/[...slug]/route.tsx @@ -0,0 +1,28 @@ +import {generateOGImage} from 'fumadocs-ui/og'; +import {notFound} from 'next/navigation'; +import {appName, getPageImageUrl} from '@/lib/shared'; +import {source} from '@/lib/source'; + +export const revalidate = false; + +export async function GET( + _req: Request, + {params}: RouteContext<'/og/docs/[...slug]'>, +) { + const {slug} = await params; + const page = source.getPage(slug.slice(0, -1)); + if (!page) notFound(); + + return generateOGImage({ + title: page.data.title, + description: page.data.description, + site: appName, + }); +} + +export function generateStaticParams() { + return source.getPages().map((page) => ({ + lang: page.locale, + slug: getPageImageUrl(page).segments, + })); +} diff --git a/website-fumadocs/bun.lock b/website-fumadocs/bun.lock new file mode 100644 index 0000000..8fd8a5f --- /dev/null +++ b/website-fumadocs/bun.lock @@ -0,0 +1,1174 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "website-fumadocs", + "dependencies": { + "cn": "^0.5.0", + "fumadocs-core": "16.17.7", + "fumadocs-mdx": "15.4.9", + "fumadocs-ui": "npm:@fumadocs/base-ui@16.17.7", + "lucide-react": "^1.59.0", + "mermaid": "^12.2.0", + "next": "16.4.2", + "react": "^19.3.1", + "react-dom": "^19.3.1", + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.3.3", + "@types/mdx": "^2.0.14", + "@types/node": "^26.6.5", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "postcss": "^8.5.31", + "serve": "^14.2.6", + "tailwindcss": "^4.3.3", + "typescript": "^7.0.2", + }, + }, + }, + "packages": { + "@alloc/quick-lru": ["@alloc/quick-lru@5.3.0", "", {}, "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA=="], + + "@antfu/install-pkg": ["@antfu/install-pkg@2.1.0", "", { "dependencies": { "package-manager-detector": "^1.8.0", "tinyexec": "^1.3.1" } }, "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw=="], + + "@babel/runtime": ["@babel/runtime@8.0.7", "", {}, "sha512-k0hagDDq/6w4v+0Sp4wFvIz/yswH5O7Va6aRTnD7N62Tnborp4X+YB+H3XUcWVSR4aE47WPEXiIIZchBs0GmYg=="], + + "@base-ui/react": ["@base-ui/react@1.9.0", "", { "dependencies": { "@babel/runtime": "^7.29.7 || ^8.0.0", "@base-ui/utils": "0.5.0", "@floating-ui/react-dom": "^2.1.9", "@floating-ui/utils": "^0.2.12", "use-sync-external-store": "^1.7.0" }, "peerDependencies": { "@date-fns/tz": "^1.2.0", "@types/react": "^17 || ^18 || ^19", "date-fns": "^4.0.0", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@date-fns/tz", "@types/react", "date-fns"] }, "sha512-S8CSYosASBKtpL8SWUfS78rFElfLijiCgA7oHOK22jCJ95FULsYyKwBNFOgudt0RA+/7yNc7F81bOvQnRgbBiQ=="], + + "@base-ui/utils": ["@base-ui/utils@0.5.0", "", { "dependencies": { "@babel/runtime": "^7.29.7 || ^8.0.0", "@floating-ui/utils": "^0.2.12", "reselect": "^5.3.0", "use-sync-external-store": "^1.7.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-dYGGcEttjkYiMoa5nHm8vLaJ2UcuTWxo3BFAwOYXeRg/q7UVVvA6cacEIXEs3FgBfK1CGtW1ptElqs7wsx+t9A=="], + + "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], + + "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@13.2.0", "", { "dependencies": { "@chevrotain/gast": "13.2.0", "@chevrotain/types": "13.2.0" } }, "sha512-bA9dvWlhAUcHkNwU+8w9yiUxJPlj/Lm3VqS7MD+NbIR/7LmpNLuK/AWNFdCo/T3SK5ND0/UHf3IJvl/Jn/i6yg=="], + + "@chevrotain/gast": ["@chevrotain/gast@13.2.0", "", { "dependencies": { "@chevrotain/types": "13.2.0" } }, "sha512-bCARdAsG+eEbOveSXcOAC7pVxA04I1ionzeMOwIYf5NX7SaS7zgn4Dgug2BSqoL+iyKeMNSorQjsFSHJHsCNwg=="], + + "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@13.2.0", "", {}, "sha512-kxpUkLtiaK09ph+dib+Yvm+3sWSnuwlo25gqfKRgTPNmF6H7uW4gVkMjaRdtnN783S9sM6vUNWh9CsxYX4QwKw=="], + + "@chevrotain/types": ["@chevrotain/types@13.2.0", "", {}, "sha512-7qrYHRlKfWf2AvBfJi5eVOrKAkbxvMFsy/iIHTGIiApNcEecCaVIdR+mIQhyCBsTDJxBz4JWXHMo6krbhR9hVg=="], + + "@chevrotain/utils": ["@chevrotain/utils@13.2.0", "", {}, "sha512-SzKYY68rseOrb9KILVzPsl6SjkQtdWw01SRGB5aCVI5kFcHltQsOYV3JB0Ffst72DgkvHCyDQ0NLwxtmCCf97Q=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], + + "@floating-ui/core": ["@floating-ui/core@1.8.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.8.0", "", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="], + + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.9", "", { "dependencies": { "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="], + + "@fuma-translate/react": ["@fuma-translate/react@1.0.2", "", { "peerDependencies": { "@types/react": "*", "react": "^19.2.0", "react-dom": "^19.2.0" }, "optionalPeers": ["@types/react"] }, "sha512-uOiOtBx3nRXR8Nu1GzBf1tApgF1FErDBTHxRIAQeyQdyOoZbrNRN6H4kDCWObY4qyGeGbHydG0DHzgeUgFDMIw=="], + + "@fumadocs/tailwind": ["@fumadocs/tailwind@0.1.4", "", { "peerDependencies": { "tailwindcss": "^4.0.0" }, "optionalPeers": ["tailwindcss"] }, "sha512-1LAmuroJ6TJoQodyODRhMC9ThAkfrV2N5vZjaj4KKs+dpF2bs0d/2z0604iMKBLv2BKstR7zE9TUa/L0pxGnhA=="], + + "@fumari/image-size": ["@fumari/image-size@0.1.1", "", {}, "sha512-iaiV2wyzJ4mW358NX4CX3yGit+WjKYzpaQ+rldK9BjL/5jFpeV6ruWnZVq7MEyMn3Gd0DLi8g/79NZAAQaGscw=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.1.7", "", { "dependencies": { "@antfu/install-pkg": "^2.0.1", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w=="], + + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.4" }, "os": "darwin", "cpu": "x64" }, "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw=="], + + "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.5", "", { "dependencies": { "@img/sharp-wasm32": "0.35.5" }, "os": "freebsd" }, "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.4", "", { "os": "linux", "cpu": "arm" }, "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.4", "", { "os": "linux", "cpu": "none" }, "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.4" }, "os": "linux", "cpu": "arm" }, "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.4" }, "os": "linux", "cpu": "arm64" }, "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.4" }, "os": "linux", "cpu": "none" }, "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.4" }, "os": "linux", "cpu": "s390x" }, "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.4" }, "os": "linux", "cpu": "x64" }, "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" }, "os": "linux", "cpu": "arm64" }, "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.4" }, "os": "linux", "cpu": "x64" }, "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.5", "", { "dependencies": { "@emnapi/runtime": "^1.11.3" } }, "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ=="], + + "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.5", "", { "dependencies": { "@img/sharp-wasm32": "0.35.5" }, "cpu": "none" }, "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.5", "", { "os": "win32", "cpu": "x64" }, "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], + + "@mermaid-js/parser": ["@mermaid-js/parser@2.0.1", "", { "dependencies": { "@chevrotain/types": "~13.2.0" } }, "sha512-j37dj/h6p6pots1L7pLPPNuABEEu9WP4OYg9pW1g+nYG331j0+3canE9OagtYdytYvdSAMG+2fKU5cxep6LgjA=="], + + "@next/env": ["@next/env@16.4.2", "", {}, "sha512-nTUTFH7G5/J/fmpkjVLegOkqyKOP0CEsJcMsyO4UCYeN1su+YMW/MfaSErnJskL7XM+UDK6aTvslxsRCyya/sA=="], + + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.4.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ONkhFvZNFWlsEHOM/81AtxdyuJP2KRYKYR0JxPwvcu1AuPMMMgcL+wW2luldJvMtZwKtUxoSkCychW5wIeoYSw=="], + + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.4.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-luPCMbOsCoaF+kXJEUjmjmOZg1lC3BflhmUu+ZmW4tBu2vW/4AXak8JBFvePizGkxVmNiAw/r9ra+6UviCWtWQ=="], + + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-xhwFTAncJyaCNy5KqKKuKaIIdCy1/1ZV1s5S1G0hhT7wGMkDTlfJ6aQbfRFnIG/s3/kDrxQ8aycVe+c5QnP8ow=="], + + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-T1PNmfbPcxr5ndtWUL/3wQtLXsz808lq6sc2GrNT5k2oquF7UyqZddHSuAewHVsWsGXe6nsl9HoN1YNriVjuSg=="], + + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-N9cDUOCGtkJG5PNKDFZUF3xXndrJJcpLp121uAGjIGvO/U2p9tfdNOM9htj7+5f72l0m8rMw/cvYacUJBQoudg=="], + + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-yY2uh1YhX6ufuYy2FCbw1dqblbhI57GLYAJfCkqD9auTv6pLrH67Uccjc3QMKBeCYAG6lkgu08ExmX1bbjejjQ=="], + + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.4.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-j/ArWRIHpMMB6wYLMhr2puTguBt70lNU/Cmjl9p6dTDE8DGknYJMP9PdwPk2dqpCNL/KIX9yfngDvIesxJPmsg=="], + + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.4.2", "", { "os": "win32", "cpu": "x64" }, "sha512-AA4edyWHDko3NCSo0x6GomsdgFb0oR1J3MXU1b5xt6FRFg4AucrMPW3S0hCe/9onU0O1BbpGQBsqo1r0Gneu1g=="], + + "@shikijs/core": ["@shikijs/core@4.5.0", "", { "dependencies": { "@shikijs/primitive": "4.5.0", "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5", "hast-util-to-html": "^9.0.5" } }, "sha512-6CIgwq+bt2JZ3OSjb/nBkkwEf/CfpHcbam+Mg5Pllco7HAGRQ2/EFhZrrJtbyNTgKwknfVS+llsXGygiDGwJWw=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-Ed6UdEj84LyYkwnxM5CvSMWAD+id9ExTYri0/+x41U9zrgNifPSS9lZm0JqBP0VfYI8yr8tY5nbNq9+p9R0kFA=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-dKVmbJecVdB9recl8a/CU4jdpNXyjJQrIcesIkpS70s5HS02ohpnCSpMUbotPnEUzbAcLQ+S4hwZYbkBBiTcJw=="], + + "@shikijs/langs": ["@shikijs/langs@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0" } }, "sha512-wwBTqwduXSz0X4v9Qa2EWeFaYJEw1clCncWjoRNQRbVDu3SPRTRWucnouYapKj3b8Q+sk/41Vbc4B5c8nuYzLA=="], + + "@shikijs/primitive": ["@shikijs/primitive@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-UM7ShlA8oz0OWxLLO8BmO9Ip6fVqdkt8yF0EH7nOCqHfrx1eEAzF0ZUVK7L+I8NEgeu1PEWSRlxaiPPeCb4iDg=="], + + "@shikijs/themes": ["@shikijs/themes@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0" } }, "sha512-zGb87H3UBbpuI4yoh5UguB1XbU+2BDU3kflM7UfZ+Cy2hWo9lBtqjSVK20gmLY6ukYl040hAypGXMgud9vul8Q=="], + + "@shikijs/types": ["@shikijs/types@4.5.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.1.0", "", {}, "sha512-cVBuhDQjBZaNMTwBquYfPydrUP+E3hE6oYGasx8h6ZrODIM7/r+gxLDXdd66oszbrggZHEntdE6j+5gYIVyMWQ=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@swc/helpers": ["@swc/helpers@0.5.23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.3", "", { "os": "android", "cpu": "arm64" }, "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.3", "", { "dependencies": { "@emnapi/core": "^1.11.1", "@emnapi/runtime": "^1.11.1", "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="], + + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.3", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "postcss": "^8.5.16", "tailwindcss": "4.3.3" } }, "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg=="], + + "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], + + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="], + + "@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="], + + "@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], + + "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], + + "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="], + + "@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="], + + "@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], + + "@types/d3-geo": ["@types/d3-geo@3.1.1", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w=="], + + "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="], + + "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="], + + "@types/d3-random": ["@types/d3-random@3.0.4", "", {}, "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.12", "", {}, "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw=="], + + "@types/d3-shape": ["@types/d3-shape@3.2.0", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.9", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-0sE1406XBYJGiqD3AusTl9ZqC//2mIXix51tbom25gDCA8ri4xnSZg28CaSE8Srl6FClqABUKfsn0qghgdepMA=="], + + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], + + "@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/mdx": ["@types/mdx@2.0.14", "", {}, "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@26.6.5", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg=="], + + "@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="], + + "@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.4.0", "", {}, "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ=="], + + "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="], + + "@yuku-core/binding-android-arm64": ["@yuku-core/binding-android-arm64@0.19.0", "", { "os": "android", "cpu": "arm64" }, "sha512-wdL2XmTiZixAOaHk9Z0JJZ8u9Qm8d333Zjpc6gaxXFX+UR5BlsfxZ7a2+2Rm71pamppsPYGUGFq2oVLbh6DISw=="], + + "@yuku-core/binding-darwin-arm64": ["@yuku-core/binding-darwin-arm64@0.19.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7sNJa6XJhyetnTy64jw3NEi4YgUcezHk1G5GH9FSvXAuC8wIlbaOwT9lgq5qo5sspjrZJQlBR1TWflP49uRpMw=="], + + "@yuku-core/binding-darwin-x64": ["@yuku-core/binding-darwin-x64@0.19.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-pTC7dYEc59isdyizc3nsuyFNBu2LU5wMYZ1be6UMr59PY/AJNtO1aBLWV0MgL/z7C1nv/2sX4TNjGgKLoeevJw=="], + + "@yuku-core/binding-freebsd-x64": ["@yuku-core/binding-freebsd-x64@0.19.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xrZjF6XOEwyLOiDIfrwDdRCS6ghxcJeirNC1KivQhVcUwlRonaU79S3/Uiyx3lBylPnT8qKJ3/aMT+H52nGTJQ=="], + + "@yuku-core/binding-linux-arm-gnu": ["@yuku-core/binding-linux-arm-gnu@0.19.0", "", { "os": "linux", "cpu": "arm" }, "sha512-0OyIH53C0I1CbADHn9FSdCfX5qbpeLwJhmOVhonFJNYheybOnkSJ50qGMfTJIb8y5zrwqvLs7FVBvz6Fk8maZg=="], + + "@yuku-core/binding-linux-arm-musl": ["@yuku-core/binding-linux-arm-musl@0.19.0", "", { "os": "linux", "cpu": "arm" }, "sha512-z/iPfkCOCSXE34VXNt0uP1ORSW+4mEa9b5CiJcWZeqZH08g49bFU/jRM2T04f+Wpw5mJ6HIzNBiojxeAbrHjeg=="], + + "@yuku-core/binding-linux-arm64-gnu": ["@yuku-core/binding-linux-arm64-gnu@0.19.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jLNTi+gzaRwBJ+gwhCJ+rDb5dmx/8hEBx93UWd7hjMSqPTL1D1wvVSvXVAldV5kALvju7b0/m51EHEhs4+I1fA=="], + + "@yuku-core/binding-linux-arm64-musl": ["@yuku-core/binding-linux-arm64-musl@0.19.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YwyPIdW/rN2Fmv0MTFWi47FaB3F9g+2b+sJISyAw83Yd60RWH4T3Nx6XkAHHVN+uKJNBAd6So7/BVihSiFYvuQ=="], + + "@yuku-core/binding-linux-x64-gnu": ["@yuku-core/binding-linux-x64-gnu@0.19.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/vskaAXVX3ESR43FiTdfiYS7z1b9uoQJwVQ/0RgUHgN2sWoAyCZs+aX4CgU7eKGIHTVfp9VFwv+c+2XchYiEtQ=="], + + "@yuku-core/binding-linux-x64-musl": ["@yuku-core/binding-linux-x64-musl@0.19.0", "", { "os": "linux", "cpu": "x64" }, "sha512-FuXL5igeSKib9Rye02TdH+D6SQezTyGxIATMu1Qsn/QywrzwpGl35OLpS2C1x5vpGLeVE2XgiLd5mVPFHmIglQ=="], + + "@yuku-core/binding-win32-arm64": ["@yuku-core/binding-win32-arm64@0.19.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-YuJAe7zEKT1Xa7LuHlhGJpPnYQeVtvo0rZvNjJRqkH0hQGOw4C8QeP30Tg5VKGzZfo2x8CjhjEhKt4TmLwfmdg=="], + + "@yuku-core/binding-win32-x64": ["@yuku-core/binding-win32-x64@0.19.0", "", { "os": "win32", "cpu": "x64" }, "sha512-o8iY/mXwlRkedp0LNTr4+TEfXTPQwoNZmv6k+1WIAd1ZXEn8xJJTBCtB0osaVvdZo/x/cNmV16UrWrrGpM5Mvw=="], + + "@yuku-toolchain/types": ["@yuku-toolchain/types@0.19.0", "", {}, "sha512-tFkNbsP4WXX9UMUC5syJGdK9dPMLK0PPaIuXCcCYH75sZ8MrebfBZ2XvCILRTw8AgLivSSCfDa4R67iYBDa4Kg=="], + + "@zeit/schemas": ["@zeit/schemas@2.36.0", "", {}, "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg=="], + + "acorn": ["acorn@8.19.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + + "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], + + "ansi-regex": ["ansi-regex@6.4.0", "", {}, "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "arch": ["arch@2.2.0", "", {}, "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="], + + "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], + + "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.30", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-YJs6Pz17RMNv8HoyKREtCY38E3BV1nPkWVmIb+XN1uHC1Vqt3KZCH/uJH9ZKRoiKb/icZ6tvehyee6RZNbFfRA=="], + + "boxen": ["boxen@7.0.0", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^7.0.0", "chalk": "^5.0.1", "cli-boxes": "^3.0.0", "string-width": "^5.1.2", "type-fest": "^2.13.0", "widest-line": "^4.0.1", "wrap-ansi": "^8.0.1" } }, "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg=="], + + "brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], + + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "camelcase": ["camelcase@7.0.1", "", {}, "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001817", "", {}, "sha512-uZ1JG80BwhtMalIAGPxPfmNhHyd/B/nTdr3hITWlRamSyAdUStP1Gop01e7SD5q6J90ixJvWDbvmiFVfDgn4bw=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chalk": ["chalk@5.0.1", "", {}, "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w=="], + + "chalk-template": ["chalk-template@0.4.0", "", { "dependencies": { "chalk": "^4.1.2" } }, "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + + "chevrotain": ["chevrotain@13.2.0", "", { "dependencies": { "@chevrotain/cst-dts-gen": "13.2.0", "@chevrotain/gast": "13.2.0", "@chevrotain/regexp-to-ast": "13.2.0", "@chevrotain/types": "13.2.0", "@chevrotain/utils": "13.2.0" } }, "sha512-GTouk5YzgBxlTPCaDn5q0q2EJKkrYmnPF+HmF8v0Qo4r3iyPG7HJivkN1wthCXZcm3sIcZFpGTRovLPNdJNRCA=="], + + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + + "clipboardy": ["clipboardy@3.0.0", "", { "dependencies": { "arch": "^2.2.0", "execa": "^5.1.1", "is-wsl": "^2.2.0" } }, "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "cn": ["cn@0.5.0", "", { "bin": { "cn": "bin/cn.mjs" } }, "sha512-JGEixZEpfFXm1P5uXwvEobOFLlHwIWi5v4tjd1ZwfiYmcEilkuD2l+054rvJcYCR1B+uVThHeUbgmNz1FwuxuA=="], + + "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@15.0.0", "", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="], + + "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="], + + "compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="], + + "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "content-disposition": ["content-disposition@0.5.2", "", {}, "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA=="], + + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "cytoscape": ["cytoscape@3.34.4", "", {}, "sha512-msY99tMY8iWTg+sIPGfSqDcPNOt4xcj+v/K/7qUFaCUxxlqnMkUYLBjRUzn9W5CU/R2hrmtJa5+UtovrEM2ctg=="], + + "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], + + "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="], + + "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], + + "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], + + "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], + + "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], + + "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="], + + "dayjs": ["dayjs@1.11.23", "", {}, "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ=="], + + "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "dompurify": ["dompurify@3.4.17", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "elkjs": ["elkjs@0.9.3", "", {}, "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ=="], + + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "enhanced-resolve": ["enhanced-resolve@5.26.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw=="], + + "es-toolkit": ["es-toolkit@1.52.0", "", {}, "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA=="], + + "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], + + "esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="], + + "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], + + "estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="], + + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + + "estree-util-scope": ["estree-util-scope@1.0.1", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0" } }, "sha512-B0np3dcdxqILX5e9nEi5/Fr4K7gL4oYFVPV1zRa2e9wRCbQoZZNWOZFYyoInvXUPJXBXjss+QXlWLJChDEHDkA=="], + + "estree-util-to-js": ["estree-util-to-js@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "astring": "^1.8.0", "source-map": "^0.7.0" } }, "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg=="], + + "estree-util-value-to-estree": ["estree-util-value-to-estree@3.5.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ=="], + + "estree-util-visit": ["estree-util-visit@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" } }, "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.9", "", {}, "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "framer-motion": ["framer-motion@14.4.0", "", { "dependencies": { "motion-dom": "14.4.0", "motion-utils": "14.1.1", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-HtO1fxord2rnXYRyr1laG4uY7+OtgwDc9Szcg1wlMq/LF7BO3/S7a+KEo691U7pSi+i0ueRuRqaD1UaLEmZHuQ=="], + + "fumadocs-core": ["fumadocs-core@16.17.7", "", { "dependencies": { "@fumari/image-size": "^0.1.1", "estree-util-value-to-estree": "^3.5.0", "github-slugger": "^2.0.0", "hast-util-from-dom": "^5.0.1", "hast-util-to-estree": "^3.1.3", "hast-util-to-jsx-runtime": "^2.3.6", "mdast-util-mdx": "^3.0.0", "micromark": "^4.0.2", "micromark-extension-gfm-table": "^2.1.2", "npm-to-yarn": "3.2.0", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-rehype": "^11.1.2", "scroll-into-view-if-needed": "^3.1.0", "shiki": "^4.5.0", "tinyglobby": "^0.2.17", "unified": "^11.0.5", "vfile": "^6.0.3", "yaml": "^2.9.1", "zbsearch": "^4.0.1" }, "peerDependencies": { "@mdx-js/mdx": "*", "@mixedbread/sdk": "0.x.x", "@modelcontextprotocol/server": "2.x.x", "@orama/core": "1.x.x", "@oramacloud/client": "2.x.x", "@tanstack/react-router": "1.x.x", "@types/estree-jsx": "*", "@types/hast": "*", "@types/mdast": "*", "@types/react": "*", "algoliasearch": "5.x.x", "flexsearch": "*", "lucide-react": "*", "meilisearch": ">=0.50.0 <1.0.0", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "7.x.x || 8.x.x", "waku": "*", "zod": "4.x.x" }, "optionalPeers": ["@mdx-js/mdx", "@mixedbread/sdk", "@modelcontextprotocol/server", "@orama/core", "@oramacloud/client", "@tanstack/react-router", "@types/estree-jsx", "@types/hast", "@types/mdast", "@types/react", "algoliasearch", "flexsearch", "lucide-react", "meilisearch", "next", "react", "react-dom", "react-router", "waku", "zod"] }, "sha512-o8uR1JuFgAHrUSbX/hzThOsvW8tFXfV4vlrWf7VeFd+lbQsRqwr8R7gvs96Vwwzn5xSDfgg0boQalyv1llJAQQ=="], + + "fumadocs-mdx": ["fumadocs-mdx@15.4.9", "", { "dependencies": { "@mdx-js/mdx": "^3.1.1", "@standard-schema/spec": "^1.1.0", "chokidar": "^5.0.0", "esbuild": "^0.28.2", "estree-util-value-to-estree": "^3.5.0", "github-slugger": "^2.0.0", "magic-string": "^1.4.3", "mdast-util-mdx": "^3.0.0", "picocolors": "^1.1.1", "picomatch": "^4.0.7", "tinyexec": "^1.3.1", "tinyglobby": "^0.2.17", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "vfile": "^6.0.3", "yaml": "^2.9.1", "yuku-analyzer": "^0.19.0", "zod": "^4.6.7" }, "peerDependencies": { "@fumadocs/satteri": "0.x.x", "@types/mdast": "*", "@types/mdx": "*", "@types/react": "*", "fumadocs-core": "^16.17.0", "mdast-util-directive": "*", "next": "^15.3.0 || ^16.0.0", "react": "^19.2.0", "rolldown": "*", "satteri": "^0.10.5", "vite": "7.x.x || 8.x.x" }, "optionalPeers": ["@fumadocs/satteri", "@types/mdast", "@types/mdx", "@types/react", "mdast-util-directive", "next", "react", "rolldown", "satteri", "vite"], "bin": { "fumadocs-mdx": "./bin.js" } }, "sha512-vsZGzpGsWptlBAX8sWb7R87KIt9+3GhJa5GpkwJqBv/OaN9QGul34hJikKdq6C8pT2XOwKMB499lvj31JaHbGg=="], + + "fumadocs-ui": ["@fumadocs/base-ui@16.17.7", "", { "dependencies": { "@base-ui/react": "^1.8.0", "@fuma-translate/react": "^1.0.2", "@fumadocs/tailwind": "0.1.4", "class-variance-authority": "^0.7.1", "cn": "^0.4.0", "hast-util-to-jsx-runtime": "^2.3.6", "lucide-react": "^1.50.0", "motion": "^14.0.0", "next-themes": "^0.4.6", "react-remove-scroll": "^2.7.2", "scroll-into-view-if-needed": "^3.1.0", "shiki": "^4.5.0" }, "peerDependencies": { "@types/mdx": "*", "@types/react": "*", "fumadocs-core": "16.17.7", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "takumi-js": "*" }, "optionalPeers": ["@types/mdx", "@types/react", "next", "takumi-js"] }, "sha512-t9Am0mxPrGuAG9BeC/ACLhNbBtxXbc+JIbTTzr7j5apnCFRQSDxGaZT9/+BRWG6DqltnsccGqGB1x3kPz7WaYw=="], + + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hast-util-from-dom": ["hast-util-from-dom@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hastscript": "^9.0.0", "web-namespaces": "^2.0.0" } }, "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + + "hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "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" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hastscript": ["hastscript@9.0.1", "", { "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" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + + "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + + "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-port-reachable": ["is-port-reachable@4.0.0", "", {}, "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "katex": ["katex@0.18.11", "", { "dependencies": { "commander": "^15.0.0" }, "bin": { "katex": "cli.js" } }, "sha512-yvyz/2VMKqoho/35Hat5w37wIEdWJ5ElVMidTAlWrxr5uW4aBGsEpveTmKh18/zw08v/7lGswCPon4OhqmqrTA=="], + + "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + + "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "lucide-react": ["lucide-react@1.60.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-UqxOW0FgAfDyb9VmjKb6/BkOzW6yfhf/QNboKtS2NDsscnNRW2CDzlyhbMKBQ1ontmcxuIDqHJ2AVgFR3NI+GA=="], + + "magic-string": ["magic-string@1.4.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0" } }, "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ=="], + + "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.3", "", { "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" } }, "sha512-xPgpDNl0/OXHsI7TlaIs22lWnH3KlpvZdXG1ET/m/YT2Hhdkx8lJV4kLphE6l9simyM4KPDFBwas08BgEob6Jw=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.1.0", "", { "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" } }, "sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "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" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "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" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "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" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "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" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-mdx": ["mdast-util-mdx@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "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" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.2.0", "", { "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-character": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "mermaid": ["mermaid@12.2.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^2.0.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "chevrotain": "~13.2.0", "cytoscape": "^3.34.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.21", "dompurify": "^3.4.12", "elkjs": "^0.9.3", "es-toolkit": "^1.45.1", "katex": "^0.18.0", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-w7nr/SFrLO7BaVTs3dQTvYSV6nYSBj0Yy9DjBhsIJtAcHqRzXSq4Pg32ujHZ/AdWsAMNEvvhbm5dt9E2tOiHzw=="], + + "micromark": ["micromark@4.0.3", "", { "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-edit-map": "^1.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" } }, "sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.4", "", { "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.1.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-edit-map": "^1.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.3" } }, "sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "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" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "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" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "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" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "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" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.2", "", { "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" } }, "sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "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" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-extension-mdx-expression": ["micromark-extension-mdx-expression@3.0.1", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q=="], + + "micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.2", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ=="], + + "micromark-extension-mdx-md": ["micromark-extension-mdx-md@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ=="], + + "micromark-extension-mdxjs": ["micromark-extension-mdxjs@3.0.0", "", { "dependencies": { "acorn": "^8.0.0", "acorn-jsx": "^5.0.0", "micromark-extension-mdx-expression": "^3.0.0", "micromark-extension-mdx-jsx": "^3.0.0", "micromark-extension-mdx-md": "^2.0.0", "micromark-extension-mdxjs-esm": "^3.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ=="], + + "micromark-extension-mdxjs-esm": ["micromark-extension-mdxjs-esm@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-mdx-expression": ["micromark-factory-mdx-expression@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ=="], + + "micromark-factory-space": ["micromark-factory-space@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "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" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "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" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "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" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-edit-map": ["micromark-util-edit-map@1.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-events-to-acorn": ["micromark-util-events-to-acorn@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.3", "", {}, "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@2.1.18", "", { "dependencies": { "mime-db": "~1.33.0" } }, "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "motion": ["motion@14.4.0", "", { "dependencies": { "framer-motion": "14.4.0", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-JJTIdeNjtF2zi/py0ndRKxQF+8z2pcDUgJYXul7zqbhMvyTLRsPFR9ifE/j4cFCpCMlehQVJkTwd7BZFiPKgQg=="], + + "motion-dom": ["motion-dom@14.4.0", "", { "dependencies": { "motion-utils": "14.1.1" } }, "sha512-1q8RyHSdr5COpzrv3JgJ9XRjOO88ZN9CwflpkGgyO/SR/SNRP/dD6EU8SEo092kcKY34IzHC5diJPKrQRMA8lQ=="], + + "motion-utils": ["motion-utils@14.1.1", "", {}, "sha512-3VvN5Uvf8B6qG+OfOui6Nht5EWp+vLuU3IqcAj7ItZGKqtmTjCebl5sGZF7/08gEHRnEl1AP/fIwbcEyVrcQGQ=="], + + "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "nanoid": ["nanoid@3.3.20", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ=="], + + "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], + + "next": ["next@16.4.2", "", { "dependencies": { "@next/env": "16.4.2", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.23", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.4.2", "@next/swc-darwin-x64": "16.4.2", "@next/swc-linux-arm64-gnu": "16.4.2", "@next/swc-linux-arm64-musl": "16.4.2", "@next/swc-linux-x64-gnu": "16.4.2", "@next/swc-linux-x64-musl": "16.4.2", "@next/swc-win32-arm64-msvc": "16.4.2", "@next/swc-win32-x64-msvc": "16.4.2", "sharp": "^0.35.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-r+oBJojfefUjOiWkBhXgLS+igxX/7BWyK59uHnyOyplMSLtBZ+QLP3okrdYTbrfrbrmNTVWLBPaHDX9KRaIt3w=="], + + "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], + + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "npm-to-yarn": ["npm-to-yarn@3.2.0", "", {}, "sha512-K1HmQeZT2HrjpsR6KgqbN2FAXL2NrJJmNUSD9ck7HGTVu1JKXox8n9SB+tjbU8m8JGLF4OscrroPepew/L7/Xw=="], + + "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="], + + "package-manager-detector": ["package-manager-detector@1.9.0", "", {}, "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA=="], + + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + + "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + + "path-is-inside": ["path-is-inside@1.0.2", "", {}, "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-to-regexp": ["path-to-regexp@3.3.0", "", {}, "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], + + "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], + + "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], + + "postcss": ["postcss@8.5.31", "", { "dependencies": { "nanoid": "^3.3.20", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg=="], + + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + + "range-parser": ["range-parser@1.2.0", "", {}, "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "react": ["react@19.3.1", "", {}, "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw=="], + + "react-dom": ["react-dom@19.3.1", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.1" } }, "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ=="], + + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], + + "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], + + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + + "readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], + + "recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="], + + "recma-jsx": ["recma-jsx@1.0.1", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="], + + "recma-parse": ["recma-parse@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "esast-util-from-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ=="], + + "recma-stringify": ["recma-stringify@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g=="], + + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "registry-auth-token": ["registry-auth-token@3.3.2", "", { "dependencies": { "rc": "^1.1.6", "safe-buffer": "^5.0.1" } }, "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ=="], + + "registry-url": ["registry-url@3.1.0", "", { "dependencies": { "rc": "^1.0.1" } }, "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA=="], + + "rehype-recma": ["rehype-recma@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "hast-util-to-estree": "^3.0.0" } }, "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw=="], + + "remark": ["remark@15.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "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" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "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" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "reselect": ["reselect@5.3.0", "", {}, "sha512-XGoLeRAVzUTcJ1qkxPQhDJyIZ5d6zzZD9nT7AEZOaaU9UbWclhycElmhO+VD5bFeLuzhPBaOV2oXC8uG35ZSpg=="], + + "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], + + "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], + + "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="], + + "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "serve": ["serve@14.2.6", "", { "dependencies": { "@zeit/schemas": "2.36.0", "ajv": "8.18.0", "arg": "5.0.2", "boxen": "7.0.0", "chalk": "5.0.1", "chalk-template": "0.4.0", "clipboardy": "3.0.0", "compression": "1.8.1", "is-port-reachable": "4.0.0", "serve-handler": "6.1.7", "update-check": "1.5.4" }, "bin": { "serve": "build/main.js" } }, "sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q=="], + + "serve-handler": ["serve-handler@6.1.7", "", { "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", "mime-types": "2.1.18", "minimatch": "3.1.5", "path-is-inside": "1.0.2", "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg=="], + + "sharp": ["sharp@0.35.5", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.5", "@img/sharp-darwin-x64": "0.35.5", "@img/sharp-freebsd-wasm32": "0.35.5", "@img/sharp-libvips-darwin-arm64": "1.3.4", "@img/sharp-libvips-darwin-x64": "1.3.4", "@img/sharp-libvips-linux-arm": "1.3.4", "@img/sharp-libvips-linux-arm64": "1.3.4", "@img/sharp-libvips-linux-ppc64": "1.3.4", "@img/sharp-libvips-linux-riscv64": "1.3.4", "@img/sharp-libvips-linux-s390x": "1.3.4", "@img/sharp-libvips-linux-x64": "1.3.4", "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", "@img/sharp-libvips-linuxmusl-x64": "1.3.4", "@img/sharp-linux-arm": "0.35.5", "@img/sharp-linux-arm64": "0.35.5", "@img/sharp-linux-ppc64": "0.35.5", "@img/sharp-linux-riscv64": "0.35.5", "@img/sharp-linux-s390x": "0.35.5", "@img/sharp-linux-x64": "0.35.5", "@img/sharp-linuxmusl-arm64": "0.35.5", "@img/sharp-linuxmusl-x64": "0.35.5", "@img/sharp-webcontainers-wasm32": "0.35.5", "@img/sharp-win32-arm64": "0.35.5", "@img/sharp-win32-ia32": "0.35.5", "@img/sharp-win32-x64": "0.35.5" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shiki": ["shiki@4.5.0", "", { "dependencies": { "@shikijs/core": "4.5.0", "@shikijs/engine-javascript": "4.5.0", "@shikijs/engine-oniguruma": "4.5.0", "@shikijs/langs": "4.5.0", "@shikijs/themes": "4.5.0", "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ=="], + + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + + "source-map-js": ["source-map-js@1.2.2", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "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.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + + "stylis": ["stylis@4.4.0", "", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "tinyexec": ["tinyexec@1.3.1", "", {}, "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], + + "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], + + "undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], + + "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=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="], + + "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "update-check": ["update-check@1.5.4", "", { "dependencies": { "registry-auth-token": "3.3.2", "registry-url": "3.1.0" } }, "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ=="], + + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], + + "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + + "use-sync-external-store": ["use-sync-external-store@1.7.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A=="], + + "uuid": ["uuid@14.0.3", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw=="], + + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "widest-line": ["widest-line@4.0.1", "", { "dependencies": { "string-width": "^5.0.1" } }, "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="], + + "yuku-analyzer": ["yuku-analyzer@0.19.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.19.0", "yuku-ast": "^0.19.0", "yuku-core": "0.19.0" } }, "sha512-Iu7DV5cWOQ3ERSA7oBquz/RT5IEGsUyUeCivYs9lQaZ4nSjM31yD17mH616JKcTyVoVWYivsFZ+twT3UyBosTA=="], + + "yuku-ast": ["yuku-ast@0.19.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.19.0" } }, "sha512-PyCsiScaelhxBGzL8dyeJkLqvRZycoh78R31Vrt/WqYrm0hMFoKwIw/X7Dx5mBKLWAMlV9I9HEyBOHnj2gZVyw=="], + + "yuku-core": ["yuku-core@0.19.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.19.0" }, "optionalDependencies": { "@yuku-core/binding-android-arm64": "0.19.0", "@yuku-core/binding-darwin-arm64": "0.19.0", "@yuku-core/binding-darwin-x64": "0.19.0", "@yuku-core/binding-freebsd-x64": "0.19.0", "@yuku-core/binding-linux-arm-gnu": "0.19.0", "@yuku-core/binding-linux-arm-musl": "0.19.0", "@yuku-core/binding-linux-arm64-gnu": "0.19.0", "@yuku-core/binding-linux-arm64-musl": "0.19.0", "@yuku-core/binding-linux-x64-gnu": "0.19.0", "@yuku-core/binding-linux-x64-musl": "0.19.0", "@yuku-core/binding-win32-arm64": "0.19.0", "@yuku-core/binding-win32-x64": "0.19.0" } }, "sha512-kYK9e1XdfirNhkipzW0wmMcSWcVF/CWnuugodfVYRCUwvoBefweL9HzB96OiKEhsx78V5WbUxbJqlizMX869Qg=="], + + "zbsearch": ["zbsearch@4.0.1", "", {}, "sha512-lmnEEnb93QQcNv7qARDr7jBVPxYdZbZ9XdZ0dbZ0qaOvGne4jJMW4fjwPfVaUKQXv3FNWvGbDwRDgyaSb1pc8w=="], + + "zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@tailwindcss/node/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" }, "bundled": true }, "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "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=="], + + "chalk-template/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], + + "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], + + "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + + "fumadocs-ui/cn": ["cn@0.4.0", "", { "bin": { "cn": "bin/cn.mjs" } }, "sha512-qOAeUhPwPzBxx6jaeQdo646XIKliKHc2j1ZF7Ngb1o0pJZKH5gq8FkA0aVdUdPFUDXNXyUCXpCtoJISo6dhizg=="], + + "micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mime-types/mime-db": ["mime-db@1.33.0", "", {}, "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="], + + "next/postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="], + + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "serve-handler/bytes": ["bytes@3.0.0", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="], + + "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=="], + + "chalk-template/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], + + "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + + "micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/website-fumadocs/components/mdx.tsx b/website-fumadocs/components/mdx.tsx new file mode 100644 index 0000000..d213b8d --- /dev/null +++ b/website-fumadocs/components/mdx.tsx @@ -0,0 +1,17 @@ +import defaultMdxComponents from 'fumadocs-ui/mdx'; +import type {MDXComponents} from 'mdx/types'; +import {Mermaid} from './mermaid'; + +export function getMDXComponents(components?: MDXComponents) { + return { + ...defaultMdxComponents, + Mermaid, + ...components, + } satisfies MDXComponents; +} + +export const useMDXComponents = getMDXComponents; + +declare global { + type MDXProvidedComponents = ReturnType; +} diff --git a/website-fumadocs/components/mermaid.tsx b/website-fumadocs/components/mermaid.tsx new file mode 100644 index 0000000..ff28ac9 --- /dev/null +++ b/website-fumadocs/components/mermaid.tsx @@ -0,0 +1,46 @@ +// biome-ignore-all lint/security/noDangerouslySetInnerHtml: Mermaid sanitizes generated SVG at securityLevel strict, and React must insert the SVG markup to render it. +'use client'; + +import {useTheme} from 'next-themes'; +import {useEffect, useId, useState} from 'react'; + +export function Mermaid({chart}: {chart: string}) { + const id = useId().replaceAll(':', ''); + const {resolvedTheme} = useTheme(); + const [svg, setSvg] = useState(); + + useEffect(() => { + let active = true; + + void import('mermaid').then(async ({default: mermaid}) => { + mermaid.initialize({ + startOnLoad: false, + securityLevel: 'strict', + fontFamily: 'inherit', + theme: resolvedTheme === 'dark' ? 'dark' : 'default', + }); + + const rendered = await mermaid.render(`classy-store-${id}`, chart); + if (active) setSvg(rendered.svg); + }); + + return () => { + active = false; + }; + }, [chart, id, resolvedTheme]); + + if (!svg) { + return ( +
+        {chart}
+      
+ ); + } + + return ( +
+ ); +} diff --git a/website-fumadocs/components/provider.tsx b/website-fumadocs/components/provider.tsx new file mode 100644 index 0000000..aa430db --- /dev/null +++ b/website-fumadocs/components/provider.tsx @@ -0,0 +1,8 @@ +'use client'; +import {RootProvider} from 'fumadocs-ui/provider/next'; +import type {ReactNode} from 'react'; +import SearchDialog from '@/components/search'; + +export function Provider({children}: {children: ReactNode}) { + return {children}; +} diff --git a/website-fumadocs/components/search.tsx b/website-fumadocs/components/search.tsx new file mode 100644 index 0000000..b7ece42 --- /dev/null +++ b/website-fumadocs/components/search.tsx @@ -0,0 +1,35 @@ +'use client'; +import {useStaticSearch} from 'fumadocs-core/search/client'; +import { + SearchDialog, + SearchDialogClose, + SearchDialogContent, + SearchDialogHeader, + SearchDialogIcon, + SearchDialogInput, + SearchDialogList, + SearchDialogOverlay, + type SharedProps, +} from 'fumadocs-ui/components/dialog/search'; +import {useI18n} from 'fumadocs-ui/contexts/i18n'; + +export default function DefaultSearchDialog(props: SharedProps) { + const {locale} = useI18n(); // (optional) for i18n + const search = useStaticSearch({ + locale, + }); + + return ( + + + + + + + + + + + + ); +} diff --git a/website-fumadocs/content/docs/ARCHITECTURE.md b/website-fumadocs/content/docs/ARCHITECTURE.md new file mode 100644 index 0000000..a9f91d2 --- /dev/null +++ b/website-fumadocs/content/docs/ARCHITECTURE.md @@ -0,0 +1,518 @@ +--- +title: "Classy Store — Architecture" +description: "Understand proxy tracking, immutable snapshots, computed getters, and framework bindings." +--- + +Internal design documentation for contributors and maintainers. + +## Three-Layer Architecture + +The library is organized into three distinct layers, each with a single responsibility: + +```mermaid +flowchart TB + subgraph userCode ["User Code"] + ClassDef["class MyStore { fields, methods, getters }"] + end + + subgraph layer1 ["Layer 1: Write Proxy (core.ts)"] + storeFn["createClassyStore(instance)"] + SetTrap["SET trap: forward write → bump version → notify subscribers"] + GetTrap["GET trap: return value, bind methods, lazy-wrap nested objects, memoize getters"] + DeleteTrap["DELETE trap: same as SET"] + Batch["queueMicrotask: coalesce synchronous mutations → 1 notification"] + SyncNotify["sync listeners: notify immediately per mutation"] + DeepProxy["Lazy deep proxy: nested objects/arrays wrapped on first access"] + end + + subgraph layer2 ["Layer 2: Snapshot (snapshot.ts)"] + SnapshotFn["snapshot(proxy): immutable deep copy"] + StructuralShare["Structural sharing: unchanged refs reused across snapshots"] + Freeze["Object.freeze: enforces immutability"] + VersionCache["Version-stamped cache: skip rebuild if version unchanged"] + GetterEval["Getters: memoized per-snapshot + cross-snapshot caching"] + end + + subgraph layer3 ["Layer 3: React Hook (react.ts)"] + UseStore["useClassyStore(store, selector?)"] + UseLocalStore["useLocalStore(factory)"] + USES["useSyncExternalStore"] + ProxyCompare["proxy-compare: track which snapshot props selector reads"] + IsChanged["isChanged(prev, next, affected): fine-grained diff"] + SelectorMode["Selector mode: Object.is or custom isEqual"] + end + + subgraph component ["React Component"] + Render["Re-render ONLY when accessed snapshot props change"] + end + + ClassDef --> storeFn + storeFn --> SetTrap + storeFn --> GetTrap + storeFn --> DeleteTrap + SetTrap --> SyncNotify + SetTrap --> Batch + Batch --> SnapshotFn + GetTrap --> DeepProxy + SnapshotFn --> StructuralShare + StructuralShare --> Freeze + Freeze --> VersionCache + VersionCache --> GetterEval + UseStore --> USES + USES --> VersionCache + VersionCache --> ProxyCompare + ProxyCompare --> IsChanged + IsChanged --> Render + SelectorMode --> Render +``` + +## File Map + +``` +src/ +├── index.ts # Barrel export: createClassyStore, snapshot, subscribe, getVersion, shallowEqual, Snapshot +├── collections/ +│ ├── collections.ts # ReactiveMap and ReactiveSet implementations +│ ├── index.ts # Collections barrel: reactiveMap, reactiveSet, ReactiveMap, ReactiveSet +│ └── collections.test.ts # tests: ReactiveMap, ReactiveSet, class store integration +├── core/ +│ ├── core.ts # Layer 1: Write Proxy — createClassyStore(), subscribe(), getVersion() +│ ├── core.test.ts # tests: mutations, batching, methods, getters, arrays +│ └── computed.test.tsx # tests: write proxy + snapshot memoization, useClassyStore integration +├── react/ +│ ├── react.ts # Layer 3: React Hook — useClassyStore(), useLocalStore() +│ ├── react.test.tsx # tests: selector mode, auto-tracked mode, re-render control +│ └── react.behavior.test.tsx # tests: batching, set-then-revert, async, multi-component, unmount +├── snapshot/ +│ ├── snapshot.ts # Layer 2: Immutable Snapshots — snapshot() +│ └── snapshot.test.ts # tests: freezing, caching, structural sharing, getters +├── utils/ +│ ├── index.ts # Barrel export for @codebelt/classy-store/utils: persist, devtools, subscribeKey, withHistory +│ ├── devtools/ +│ │ ├── devtools.ts # devtools() utility: Redux DevTools integration, time-travel debugging +│ │ └── devtools.test.ts # tests: connect, disconnect, state sync, time-travel +│ ├── equality/ +│ │ ├── equality.ts # shallowEqual +│ │ └── equality.test.ts # tests for shallowEqual +│ ├── history/ +│ │ ├── history.ts # withHistory() utility: undo/redo via snapshot stack, pause/resume, configurable limits +│ │ └── history.test.ts # tests: undo, redo, limits, pause/resume +│ ├── internal/ +│ │ ├── internal.ts # Internal helpers: isPlainObject, canProxy, findGetterDescriptor, PROXYABLE +│ │ └── internal.test.ts # tests for internal helpers +│ ├── persist/ +│ │ ├── persist.ts # persist() utility: storage, transforms, versioning, cross-tab sync +│ │ └── persist.test.ts # tests: round-trip, transforms, debounce, migration, merge, SSR, cross-tab +│ ├── subscribe-key/ +│ │ ├── subscribe-key.ts # subscribeKey() utility: single-property subscription with prev/current values +│ │ └── subscribe-key.test.ts # tests: single key changes, prev/current values +├── types.ts # Shared types: Snapshot, StoreInternal, DepEntry, ComputedEntry +package.json +tsconfig.json +tsdown.config.ts +bunfig.toml # Preload happy-dom for React hook tests +happydom.ts # happy-dom global registrator +README.md # Usage guide +ARCHITECTURE.md # This file +TUTORIAL.md # Step-by-step tutorial +PERSIST_TUTORIAL.md # Persist utility tutorial +PERSIST_ARCHITECTURE.md # Persist utility internals +``` + +## Layer 1: Write Proxy (`core.ts`) + +### Overview + +The `createClassyStore()` function wraps a class instance in an ES6 Proxy. All mutations — property writes, array operations, nested object changes — are intercepted. Subscribers are batched by default into a single notification per microtask, while React always uses the synchronous path. Other code can opt into that path when it must observe each mutation immediately. + +### Data Flow: Mutation → Notification + +```mermaid +sequenceDiagram + participant User as User Code + participant Proxy as Write Proxy + participant Target as Raw Target + participant Internal as StoreInternal + participant Sync as Sync Subscribers + participant Micro as queueMicrotask + participant Batched as Batched Subscribers + + User->>Proxy: store.count = 5 + Proxy->>Target: Reflect.set(target, 'count', 5) + Proxy->>Internal: bumpVersion(internal) up to root + Proxy->>Internal: snapshotCache = null (invalidate) + Proxy->>Sync: notify sync listeners immediately + Proxy->>Micro: schedule batched notify (if not already scheduled) + Note over Micro: Coalesces all sync mutations + User->>Proxy: store.name = 'new' + Proxy->>Target: Reflect.set(target, 'name', 'new') + Proxy->>Internal: bumpVersion (version increments again) + Proxy->>Sync: notify sync listeners again + Note over Micro: Still same microtask batch + Micro->>Batched: notify batched listeners once +``` + +### Internal State Storage + +All internal bookkeeping is stored in a `WeakMap`: + +```typescript +type StoreInternal = { + target: object; // Raw class instance + version: number; // Monotonically increasing + batchedListeners: Set<() => void>; // Subscribers notified once per microtask + syncListeners: Set<() => void>; // Subscribers notified immediately per mutation + childProxies: Map; // Cached child proxies + childInternals: Map; + parent: StoreInternal | null; // For version propagation + notifyScheduled: boolean; // Batch dedup flag + computedCache: Map; // Memoized getter cache +}; +``` + +### Proxy Traps + +**SET trap:** +1. Compare old and new values with `Object.is` — noop if equal +2. Clean up child proxy if the property is being replaced +3. Forward write to raw target via `Reflect.set` +4. Bump version for this node and all ancestors +5. Notify sync listeners immediately, then schedule batched listeners via `queueMicrotask` (deduped) + +**GET trap (priority order):** +1. **Memoized getter detection** — walk prototype chain with `Object.getOwnPropertyDescriptor`. If a getter is found, call `evaluateComputed()` which checks dependency validity and returns the cached result or re-evaluates with dependency tracking. +2. **Method binding** — if value is a function, bind to proxy so `this.count++` in methods goes through the SET trap. Bound methods are cached. +3. **Nested objects/arrays** — if value passes `canProxy()` (plain object or array), lazily wrap in a child proxy. Child proxies are cached in `childProxies` Map. Also records a dependency if a getter is currently being tracked. +4. **Primitives** — return as-is. Also records a dependency if a getter is currently being tracked. + +**DELETE trap:** Same pattern as SET — clean up child proxy, delete from target, bump version, notify sync listeners, and schedule batched listeners. + +### Batching via `queueMicrotask` + +Array operations like `push()` trigger multiple SET traps (element + length). For ordinary listeners, a dirty flag + `queueMicrotask` coalesces these mutations into one notification: + +``` +store.items.push('a') + → SET trap for index 0 → scheduleNotify (flags, queues microtask) + → SET trap for length → scheduleNotify (already flagged, skips) + → microtask fires → notifies listeners ONCE +``` + +Subscribers opt into immediate timing with `{sync: true}`. Sync subscribers run after version propagation and before the batched microtask. They are intentionally not deduped: two writes mean two sync notifications. Every React hook uses this timing so `useSyncExternalStore` observes the new value during the input event, preserving controlled-input caret position. Selectors can run after each write; React batches rendering within event handlers. + +### Version Propagation + +When a nested property mutates, versions bump from the mutated node up to the root: + +```mermaid +graph TD + Root["Root (v=10 → v=13)"] --> User["user (v=11 → v=12)"] + Root --> Settings["settings (v=5, unchanged)"] + User --> Name["name = 'Bob' (mutation here, v=12)"] + User --> Age["age = 30 (unchanged)"] +``` + +This enables structural sharing: when the snapshot layer sees that `settings` has the same version, it returns the cached snapshot reference. + +## Layer 2: Immutable Snapshots (`snapshot.ts`) + +### Overview + +`snapshot(proxy)` creates a deeply frozen, plain-JS copy of the store state. Unchanged sub-trees reuse previous snapshot references (structural sharing). + +### Snapshot Creation Flow + +```mermaid +flowchart TD + Start["snapshot(proxy)"] --> CheckCache{"Version match\nin snapshotCache?"} + CheckCache -->|Yes| ReturnCached["Return cached snapshot (O(1))"] + CheckCache -->|No| CreateSnap["Create new snapshot object"] + CreateSnap --> IterateKeys["For each own key"] + IterateKeys --> CheckChild{"Has child\ninternal?"} + CheckChild -->|Yes| Recurse["Recurse: createSnapshotRecursive(child)"] + CheckChild -->|No| CheckProxy{"Value is\nplain obj/array?"} + CheckProxy -->|Yes| DeepClone["deepFreezeClone (cached by identity)"] + CheckProxy -->|No| CopyValue["Copy as-is (primitive/function)"] + Recurse --> AssignProp["Assign to snapshot"] + DeepClone --> AssignProp + CopyValue --> AssignProp + AssignProp --> IterateKeys + IterateKeys -->|Done| Freeze["Object.freeze(snapshot)"] + Freeze --> Cache["Cache: snapshotCache.set(target, [version, snap])"] + Cache --> Return["Return frozen snapshot"] +``` + +### Two Cache Strategies + +1. **Tracked sub-trees** (`snapshotCache` — `WeakMap`): + Objects that have been accessed through the proxy have a `StoreInternal` with version tracking. The snapshot recurses into them and uses version-stamped caching for structural sharing. + +2. **Untracked sub-trees** (`untrackedCache` — `WeakMap`): + Objects never accessed through the proxy are deep-cloned and frozen. The clone is cached by the raw object's identity. Since untracked objects can't be mutated through the proxy, the cache is always fresh. + +### Structural Sharing Example + +``` +Snapshot v1: Snapshot v2 (after user.name = 'Bob'): +┌──────────┐ ┌──────────┐ +│ root │ │ root │ (new object) +│ user ────┼──→ {name:'Alice'} │ user ────┼──→ {name:'Bob'} (new) +│ settings─┼──→ {theme:'dark'} │ settings─┼──→ {theme:'dark'} (SAME ref ===) +└──────────┘ └──────────┘ +``` + +`snap2.settings === snap1.settings` because the `settings` sub-tree's version didn't change → cache hit → same reference. + +### Getter Evaluation (Memoized) + +Snapshots preserve the prototype chain and install lazy-memoizing getters on each snapshot object. Getter results are cached at two levels: + +1. **Per-snapshot cache** — repeated access to the same getter on the same snapshot returns the identical reference. +2. **Cross-snapshot cache** — if the properties the getter reads are structurally the same across snapshots (via reference equality from structural sharing), the previous result is returned. + +```typescript +class Store { + count = 5; + get doubled() { return this.count * 2; } +} + +const snap = snapshot(createClassyStore(new Store())); +snap.doubled; // 10 — computed once, cached +snap.doubled; // 10 — same reference (per-snapshot cache hit) +``` + +## Computed Getter Memoization + +Class getters are automatically memoized at two layers — the write proxy and the snapshot — without any user-facing API changes. This is analogous to MobX's `@computed`, but implemented transparently via Proxy. + +### Write Proxy Memoization (`core.ts`) + +When a getter is accessed on the write proxy, `evaluateComputed()` runs the getter with dependency tracking. A module-level tracker stack records which properties the getter reads on `this`: + +```mermaid +flowchart TD + GetterAccess["store.filtered accessed"] + CheckDeps{"Dependencies\nchanged?"} + ReturnCached["Return cached value"] + Recompute["Re-run getter with\ndependency tracking"] + CacheResult["Cache result + deps"] + + GetterAccess --> CheckDeps + CheckDeps -->|No| ReturnCached + CheckDeps -->|Yes| Recompute + Recompute --> CacheResult + CacheResult --> ReturnCached +``` + +**Dependency types:** +- **Sub-tree dep** (`kind: 'version'`): the getter read a nested object/array. Validated by comparing `internal.version` — O(1) per dep. +- **Primitive dep** (`kind: 'value'`): the getter read a primitive. Validated by `Object.is(target[prop], recordedValue)` — O(1) per dep. + +**Nested getter support:** when getter A reads getter B, B pushes its own frame onto the tracker stack. If B returns cached, A's deps include B's deps (propagated). If B recomputes, the underlying reads naturally propagate to A's tracker. + +### Snapshot Memoization (`snapshot.ts`) + +Since snapshots are frozen, a getter's result can never change for a given snapshot. But more importantly, getter results are stable **across** snapshots when dependencies haven't changed: + +```mermaid +flowchart TD + SnapGetter["snap.filtered accessed"] + PerSnapCheck{"Per-snapshot\ncache hit?"} + PerSnapReturn["Return cached ref"] + CrossSnapCheck{"Cross-snapshot\ndeps unchanged?"} + CrossSnapReturn["Return prev result"] + Compute["Run getter with dep tracking,\nrecord which this.X were read"] + CacheBoth["Cache in per-snapshot + cross-snapshot"] + + SnapGetter --> PerSnapCheck + PerSnapCheck -->|Yes| PerSnapReturn + PerSnapCheck -->|No| CrossSnapCheck + CrossSnapCheck -->|Yes| CrossSnapReturn + CrossSnapCheck -->|No| Compute + Compute --> CacheBoth +``` + +**Cross-snapshot mechanism:** +1. First evaluation: a lightweight tracking Proxy records which `this` properties the getter reads and their values. +2. On subsequent snapshots: for each recorded property, check `Object.is(currentSnap[prop], previousValue)`. +3. Structural sharing guarantees that unchanged sub-trees have the same reference — so if `this.todos` hasn't changed, the reference comparison passes and the cached result is returned. +4. For getters reading other getters (e.g., `get filteredCount() { return this.filtered.length; }`): the inner getter is itself memoized, so it returns a stable reference, which the outer getter's dep check recognizes as unchanged. + +**Impact on `useClassyStore`:** The existing hook benefits automatically — no changes needed: +- **Selector mode:** `useClassyStore(store, (state) => state.filtered)` gets a stable reference from the memoized snapshot getter. `Object.is` correctly detects "no change" without `shallowEqual`. +- **Auto-tracked mode:** `proxy-compare`'s `isChanged` gets stable references from snapshot getters, reducing false positives. + +## Layer 3: React Hook (`react.ts`) + +### Overview + +`useClassyStore` uses `useSyncExternalStore` for tear-free React integration. React subscriptions always notify synchronously, allowing controlled inputs to observe updates during their event. The React API has no timing option; ordinary core subscriptions remain batched. It supports two modes: + +### Server rendering and hydration + +`prepareStoreForHydration(store)` captures an immutable snapshot in a WeakMap keyed by the exact store proxy. It returns the same mutable proxy and preserves the first capture on repeated calls. Capturing is explicit at bootstrap, before rendering or persistence, because a delayed Suspense boundary may not call its hook until after the live store changes. + +Prepared stores supply that snapshot through `getServerSnapshot`; ordinary `getSnapshot` still reads live state. Each selector has separate server and live caches. When React reads the hydration result, it can seed the live cache with that result so unchanged inputs and equal selections retain their references without overwriting committed selections during rendering. Client-only mounts never evaluate the server selector. + +The capture stays available for the store's lifetime, including later boundaries or roots. It is never cleared when the first consumer mounts. Unprepared stores keep the existing current-snapshot behavior. Core mutation timing, ordinary subscriptions, and `snapshot(store)` are unchanged. + +### Mode 1: Selector + +```typescript +const count = useClassyStore(myStore, (state) => state.count); +``` + +```mermaid +sequenceDiagram + participant React as React (useSyncExternalStore) + participant Hook as useClassyStore + participant Snap as snapshot() + participant Selector as selector(snap) + + React->>Hook: getSnapshot() + Hook->>Snap: snapshot(proxy) + Snap-->>Hook: frozen snapshot + Hook->>Hook: Same snapshot ref? → return cached result + Hook->>Selector: selector(snapshot) + Selector-->>Hook: selected value + Hook->>Hook: Object.is(prev, next)? + alt Same value + Hook-->>React: return cached (no re-render) + else Different value + Hook-->>React: return new value (re-render) + end +``` + +**Equality chain:** +1. Same store, selector, equality function, and snapshot reference? → reuse the cached result (O(1)) +2. Run selector → compare result with `Object.is` (or `options.isEqual`) +3. Same result → return previous reference (no re-render) + +**Controlled input timing:** + +```typescript +const name = useClassyStore(formStore, (state) => state.name); +``` + +The hook always calls the core `subscribe()` with `{sync: true}`. Ordinary listeners still batch with `queueMicrotask`. This timing is independent of React's own render scheduling and does not provide atomic transactions across several writes. + +### Mode 2: Auto-tracked (selectorless) + +```typescript +const snap = useClassyStore(myStore); +// snap.count — tracked +// snap.user.name — tracked +``` + +Each render creates its own `affected` map and raw-snapshot getter; the hook retains a proxy cache across renders. `useSyncExternalStore` receives a cached raw snapshot: unchanged tracked values return the previous snapshot for that render. After reading it, the hook wraps the snapshot with `createProxy` to record the component's property reads. + +A parent re-render starts from current store data and a render-local `TrackingReadMap`. It lazily copies committed operation records for reused immutable snapshot objects, including structurally shared children. This retains reads skipped by React Compiler or manual memoization. Sets are copied before recording new reads, so suspended and abandoned renders cannot change committed dependencies. An effect publishes only committed reads into a WeakMap; old snapshot objects can be collected without retaining a chain of prior render maps. + +The adapter follows the operation-record/Set layout of the pinned `proxy-compare` 3 dependency without hardcoding its private field names. Compiled regressions cover value reads, `in`, `Object.hasOwn`, and key enumeration. Reused snapshot objects conservatively retain prior committed dependencies, so changing a conditional branch may cause an extra render before that object's identity changes. Explicit selectors provide selected-value equality when stricter filtering is needed. + +Snapshot creation marks only library-created snapshot nodes with `markToTrack`, including class and null-prototype objects. This enables field tracking without altering opaque leaves such as native maps and dates. Nodes containing object-valued symbol fields use whole-node comparison to avoid a frozen-symbol limitation in `proxy-compare` 3; updates remain reactive with coarser render filtering. + +**How `proxy-compare` works:** +- `createProxy(snapshot, affected)` records properties read into the render's map; committed records are retained separately with weak snapshot keys. +- `isChanged(previousSnapshot, nextSnapshot, affected)` checks whether a tracked property differs. +- Unchanged tracked properties suppress subscription-driven renders; `snapshot()` retains its existing identity cache and structural sharing. + +## Dependency Graph + +```mermaid +graph LR + react["react.ts"] --> core["core.ts"] + react --> snapshot["snapshot.ts"] + react --> proxyCompare["proxy-compare"] + snapshot --> core + snapshot --> types["types.ts"] + snapshot --> internal["utils/internal/internal.ts"] + core --> types + core --> internal + index["index.ts"] --> core + index --> snapshot + index --> react + index --> internal + index --> types + + subgraph utilities ["Utilities (utils/)"] + persist["persist.ts"] + devtools["devtools.ts"] + subscribeKey["subscribe-key.ts"] + withHistory["history.ts"] + end + + persist --> core + persist --> snapshot + persist --> internal + devtools --> core + devtools --> snapshot + subscribeKey --> core + subscribeKey --> snapshot + withHistory --> core + withHistory --> snapshot +``` + +## Key Design Decisions + +### Why ES6 Proxy instead of `Object.defineProperty`? + +ES6 Proxy supports intercepting property creation, deletion, and arbitrary key access — not just known properties. This is essential for arrays (index access) and dynamic objects. All modern browsers and Node.js/Bun support Proxy natively. + +### Why `queueMicrotask` for batching? + +`queueMicrotask` runs after the current synchronous code but before the next paint/timer. For ordinary subscribers, this means: +- Multiple mutations in the same event handler → 1 notification +- Array `push` (which triggers multiple SET traps) → 1 notification +- No risk of missing updates (unlike `requestAnimationFrame`) + +### Why structural sharing in snapshots? + +Without structural sharing, `Object.is(prevSelector, nextSelector)` would always return `false` for object/array selectors because each snapshot creates new objects. Structural sharing ensures unchanged sub-trees maintain reference equality, making selectors efficient without custom equality functions. + +### Why `proxy-compare` for auto-tracking? + +`proxy-compare` (~1KB, maintained by the creator of Valtio/Zustand/Jotai) provides battle-tested property access tracking. It wraps a frozen object in a Proxy that records reads, then efficiently diffs only the accessed properties between snapshots. This eliminates the need for manual selectors in many cases. + +### Why `useSyncExternalStore` instead of `useState` + `useEffect`? + +`useSyncExternalStore` is React's official API for external stores. It: +- Prevents tearing (concurrent mode safe) +- Integrates with React's scheduler +- Works with server-side rendering (via `getServerSnapshot`) +- Is the same API used by Zustand, Redux, and Valtio + +### Why `WeakMap` for internal state? + +Storing internals in a `WeakMap` instead of on the proxy itself: +- Doesn't pollute the user's object with library metadata +- Allows garbage collection when the store is no longer referenced +- Prevents conflicts with user-defined properties + +### Why subclass inheritance works + +No special handling is needed — three mechanisms in the existing design make it work automatically: + +1. **Methods** — The GET trap uses `Reflect.get(target, prop)`, which naturally walks the JavaScript prototype chain. Methods from any ancestor class are found and bound to the proxy via `.bind(receiver)`, so `this` mutations inside inherited methods go through the SET trap. **Important:** Arrow function properties (`increment = () => {}`) are own properties whose `this` is lexically bound to the raw instance at construction time. `.bind()` is a no-op on arrow functions, so mutations inside them bypass the proxy entirely. Always use prototype methods (`increment() {}`) for reactive mutations. + +2. **Getters** — `findGetterDescriptor()` (in `utils.ts`) walks the prototype chain with `Object.getOwnPropertyDescriptor` and returns the first (most-derived) getter found. For snapshots, `collectGetters()` (in `snapshot.ts`) walks the full chain using a `seen` Set to ensure overridden getters are collected only once — the most-derived version wins. + +3. **Snapshot prototype** — `createSnapshotRecursive()` creates snapshot objects with `Object.create(Object.getPrototypeOf(target))`, preserving the full prototype chain. This means `snapshot(store) instanceof DerivedClass` is `true`, and `installMemoizedGetters()` installs getters from all levels with cross-snapshot memoization. + +`super.method()` calls also work because they use the language-level prototype dispatch mechanism, which resolves to the parent method with `this` still bound to the proxy. + +## Performance Characteristics + +| Operation | Complexity | Notes | +|-----------|-----------|-------| +| Property read | O(1) | Direct Proxy GET trap | +| Property write | O(depth) | Versions bump from node to root | +| Notification | O(listeners) | Ordinary listeners once per microtask; React listeners per mutation | +| Getter (cache hit) | O(deps) | Check deps validity, return cached | +| Getter (cache miss) | O(getter body) | Re-evaluates with dep tracking | +| Snapshot (cache hit) | O(1) | Version check only | +| Snapshot (cache miss) | O(changed branches) | Structural sharing skips unchanged | +| Snapshot getter (same snap) | O(1) | Per-snapshot cache hit | +| Snapshot getter (cross-snap) | O(deps) | Reference comparison via structural sharing | +| Selector equality | O(1) for primitives | `Object.is` check | +| Auto-track diff | O(accessed props) | `proxy-compare` checks only read props | diff --git a/website-fumadocs/content/docs/DEVTOOLS_TUTORIAL.md b/website-fumadocs/content/docs/DEVTOOLS_TUTORIAL.md new file mode 100644 index 0000000..3429607 --- /dev/null +++ b/website-fumadocs/content/docs/DEVTOOLS_TUTORIAL.md @@ -0,0 +1,207 @@ +--- +title: "DevTools Tutorial" +description: "Connect Classy Store to Redux DevTools for inspection and time-travel debugging." +--- + +`devtools()` connects a store proxy to the [Redux DevTools](https://github.com/reduxjs/redux-devtools) browser extension for state inspection and time-travel debugging. Every mutation sends a snapshot to the DevTools panel. You can inspect state, jump to any point in history, and the store updates in real-time. + +## Prerequisites + +Install the Redux DevTools extension for your browser: + +- [Chrome](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) +- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/) +- [Edge](https://microsoftedge.microsoft.com/addons/detail/redux-devtools/nnkgneoiohoecpdiaponcejilbhhikei) + +## Getting Started + +### 1. Create a store + +```ts +import {createClassyStore} from '@codebelt/classy-store'; + +class CounterStore { + count = 0; + step = 1; + + get doubled() { + return this.count * 2; + } + + increment() { + this.count += this.step; + } + + decrement() { + this.count -= this.step; + } + + setStep(step: number) { + this.step = step; + } +} + +export const counterStore = createClassyStore(new CounterStore()); +``` + +### 2. Connect to DevTools + +```ts +import {devtools} from '@codebelt/classy-store/utils'; + +const disconnect = devtools(counterStore, {name: 'CounterStore'}); +``` + +Open the Redux DevTools panel in your browser. You'll see `CounterStore` in the instance selector with the initial state: `{ count: 0, step: 1 }`. + +### 3. Mutate and observe + +```ts +counterStore.increment(); +// DevTools shows: action "STORE_UPDATE", state { count: 1, step: 1 } + +counterStore.increment(); +// DevTools shows: action "STORE_UPDATE", state { count: 2, step: 1 } + +counterStore.setStep(5); +counterStore.increment(); +// DevTools shows: action "STORE_UPDATE", state { count: 7, step: 5 } +``` + +Each batched mutation appears as a new entry in the DevTools timeline. + +### 4. Time-travel + +Click any previous entry in the DevTools timeline. The store proxy updates to match that state — your React components re-render, side effects fire, everything stays in sync. Click "Jump to State" or "Jump to Action" to navigate through history. + +### 5. Disconnect when done + +```ts +disconnect(); +``` + +After disconnecting, mutations are no longer sent to DevTools and time-travel messages are ignored. + +## Signature + +```ts +function devtools( + proxyStore: T, + options?: DevtoolsOptions, +): () => void; +``` + +### Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `name` | `string` | `'ClassyStore'` | Display name in the DevTools panel | +| `enabled` | `boolean` | `true` | Set to `false` to disable (returns a noop) | + +### Return value + +A dispose function `() => void` that disconnects from DevTools and unsubscribes from the store. + +## How It Works + +1. **Connect:** calls `window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name })` to create a DevTools connection. +2. **Init:** sends the initial snapshot via `connection.init(snapshot(store))`. +3. **Subscribe:** uses `subscribe(store, ...)` to listen for mutations. On each batched mutation, sends `snapshot(store)` to DevTools via `connection.send()`. +4. **Time-travel:** listens for `DISPATCH` messages from DevTools. When a `JUMP_TO_STATE` or `JUMP_TO_ACTION` message arrives, parses the state and applies it back to the store proxy — skipping getters and methods (same pattern as `persist()`). +5. **Dispose:** unsubscribes from the store and disconnects from DevTools. + +## Use Cases + +### Multiple stores + +Connect each store with a unique name to distinguish them in the DevTools panel: + +```ts +import {createClassyStore} from '@codebelt/classy-store'; +import {devtools} from '@codebelt/classy-store/utils'; + +const authStore = createClassyStore(new AuthStore()); +const todoStore = createClassyStore(new TodoStore()); +const uiStore = createClassyStore(new UiStore()); + +devtools(authStore, {name: 'AuthStore'}); +devtools(todoStore, {name: 'TodoStore'}); +devtools(uiStore, {name: 'UiStore'}); +``` + +Each store appears as a separate instance in the DevTools dropdown. + +### Development-only + +Disable DevTools in production by tying the `enabled` option to your build environment: + +```ts +devtools(counterStore, { + name: 'CounterStore', + enabled: import.meta.env.DEV, +}); +``` + +When `enabled` is `false`, `devtools()` returns a noop immediately — no DevTools connection is created, no subscriptions are added, and the function has zero runtime cost. + +### Conditional connection + +Connect DevTools only when the extension is available: + +```ts +const disconnect = devtools(counterStore, {name: 'CounterStore'}); + +// If the extension isn't installed, `disconnect` is a noop. +// No errors, no warnings — the store works normally. +``` + +`devtools()` checks for `window.__REDUX_DEVTOOLS_EXTENSION__` internally. If it's not available (missing extension, SSR, Node.js), it returns a noop without throwing. + +### Late disposal on unmount (React) + +```tsx +import {useEffect} from 'react'; +import {devtools} from '@codebelt/classy-store/utils'; + +function App() { + useEffect(() => { + const disconnect = devtools(counterStore, {name: 'CounterStore'}); + return () => disconnect(); + }, []); + + return ; +} +``` + +## Time-Travel Details + +When you click a previous state in the DevTools timeline, the extension sends a message like: + +```json +{ + "type": "DISPATCH", + "payload": {"type": "JUMP_TO_STATE"}, + "state": "{\"count\": 0, \"step\": 1}" +} +``` + +The `devtools()` utility: + +1. Parses `message.state` via `JSON.parse()`. +2. Iterates over the keys of the parsed state. +3. **Skips getters** — detected via `findGetterDescriptor()` on the prototype chain. Getters like `doubled` are computed values that recompute from the restored data automatically. +4. **Skips methods** — detected via `typeof value === 'function'`. +5. Assigns data properties directly to the store proxy: `proxy[key] = value`. +6. The SET trap fires, triggers reactivity, and your components update. + +During time-travel, the utility temporarily pauses sending updates to DevTools to avoid echoing the applied state back as a new action. + +## Quick Reference + +| What you want | How to do it | +|---|---| +| Connect a store | `devtools(store, {name: 'MyStore'})` | +| Disconnect | `const dispose = devtools(...); dispose()` | +| Disable in production | `enabled: import.meta.env.DEV` | +| Multiple stores | Call `devtools()` once per store with unique names | +| Use without extension | Works safely — returns a noop if extension is missing | diff --git a/website-fumadocs/content/docs/HISTORY_TUTORIAL.md b/website-fumadocs/content/docs/HISTORY_TUTORIAL.md new file mode 100644 index 0000000..2e6b827 --- /dev/null +++ b/website-fumadocs/content/docs/HISTORY_TUTORIAL.md @@ -0,0 +1,323 @@ +--- +title: "withHistory Tutorial" +description: "Add undo, redo, pause, and resume behavior to a Classy Store instance." +--- + +`withHistory()` adds undo/redo capability to any store proxy. It maintains a stack of snapshots captured on each mutation. Call `undo()` to go back, `redo()` to go forward, and `pause()`/`resume()` to batch operations that shouldn't create individual history entries. + +## Getting Started + +### 1. Create a store + +```ts +import {createClassyStore} from '@codebelt/classy-store'; + +class DrawingStore { + color = '#000000'; + strokeWidth = 2; + points: { x: number; y: number }[] = []; + + addPoint(x: number, y: number) { + this.points = [...this.points, { x, y }]; + } + + setColor(color: string) { + this.color = color; + } + + setStrokeWidth(width: number) { + this.strokeWidth = width; + } + + clear() { + this.points = []; + } +} + +export const drawingStore = createClassyStore(new DrawingStore()); +``` + +### 2. Attach history + +```ts +import {withHistory} from '@codebelt/classy-store/utils'; + +const history = withHistory(drawingStore); +``` + +That's it. Every mutation is now recorded. The initial state is captured as `history[0]`. + +### 3. Undo and redo + +```ts +drawingStore.setColor('#ff0000'); +drawingStore.setStrokeWidth(5); +// history: [initial, {color: '#ff0000'}, {strokeWidth: 5}] + +history.undo(); +// strokeWidth is back to 2 + +history.undo(); +// color is back to '#000000' + +history.redo(); +// color is '#ff0000' again +``` + +### 4. Check availability + +```ts +history.canUndo; // true if there's a previous state +history.canRedo; // true if there's a next state (after an undo) +``` + +Use these to enable/disable undo/redo buttons: + +```tsx +function UndoRedoButtons() { + const snap = useClassyStore(drawingStore); + + return ( +
+ + +
+ ); +} +``` + +### 5. Clean up + +```ts +history.dispose(); +``` + +After disposing, mutations are no longer recorded and the snapshot stack is cleared. + +## Signature + +```ts +function withHistory( + proxyStore: T, + options?: { limit?: number }, +): HistoryHandle; +``` + +### Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `limit` | `number` | `100` | Maximum number of history entries | + +### HistoryHandle + +| Property/Method | Type | Description | +|-----------------|------|-------------| +| `undo()` | `() => void` | Restore the previous state | +| `redo()` | `() => void` | Restore the next state (after an undo) | +| `canUndo` | `readonly boolean` | Whether there is a previous state | +| `canRedo` | `readonly boolean` | Whether there is a next state | +| `pause()` | `() => void` | Stop recording history entries | +| `resume()` | `() => void` | Resume recording history entries | +| `dispose()` | `() => void` | Unsubscribe and clean up | + +## How It Works + +1. **Init:** captures an initial snapshot as `history[0]` and sets `pointer = 0`. +2. **Record:** on each `subscribe()` callback (when not paused), captures a new snapshot, truncates any redo entries after the current pointer, pushes the snapshot, and enforces the limit by shifting from the front. +3. **Undo:** decrements the pointer, pauses recording, applies `history[pointer]` back to the store proxy (skipping getters and methods), and resumes recording. +4. **Redo:** increments the pointer, pauses recording, applies `history[pointer]`, and resumes. +5. **State restoration:** iterates own enumerable keys of the snapshot, skips keys with getter descriptors on the prototype chain, and assigns values to the proxy. This triggers SET traps and reactivity — your components update automatically. + +Undo and redo themselves do **not** create new history entries because recording is paused during the restore operation. + +## Use Cases + +### Text editor with undo + +```ts +class EditorStore { + content = ''; + cursorPosition = 0; + + type(text: string) { + this.content = + this.content.slice(0, this.cursorPosition) + + text + + this.content.slice(this.cursorPosition); + this.cursorPosition += text.length; + } + + backspace() { + if (this.cursorPosition === 0) return; + this.content = + this.content.slice(0, this.cursorPosition - 1) + + this.content.slice(this.cursorPosition); + this.cursorPosition--; + } +} + +const editorStore = createClassyStore(new EditorStore()); +const history = withHistory(editorStore, { limit: 200 }); + +// Cmd+Z / Ctrl+Z +document.addEventListener('keydown', (e) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'z') { + e.preventDefault(); + if (e.shiftKey) { + history.redo(); + } else { + history.undo(); + } + } +}); +``` + +### Form with reset capability + +```ts +class FormStore { + name = ''; + email = ''; + message = ''; + + setField(field: 'name' | 'email' | 'message', value: string) { + this[field] = value; + } +} + +const formStore = createClassyStore(new FormStore()); +const history = withHistory(formStore); + +// User fills in the form... +formStore.setField('name', 'Alice'); +formStore.setField('email', 'alice@example.com'); + +// Undo last change +history.undo(); +// email is back to '' + +// Undo all changes +while (history.canUndo) { + history.undo(); +} +// All fields are back to their initial values +``` + +### Batch operations with pause/resume + +When performing multiple related mutations that should count as a single history entry, use `pause()` and `resume()`: + +```ts +class SpreadsheetStore { + cells: Record = {}; + + setCell(id: string, value: string) { + this.cells = { ...this.cells, [id]: value }; + } +} + +const spreadsheetStore = createClassyStore(new SpreadsheetStore()); +const history = withHistory(spreadsheetStore); + +// Paste a block of cells — should be a single undo step +function pasteBlock(data: Record) { + history.pause(); + + for (const [id, value] of Object.entries(data)) { + spreadsheetStore.setCell(id, value); + } + + history.resume(); + + // Manually trigger a snapshot capture by making a final + // mutation after resume, or just let the next user action + // create the history entry. +} +``` + +### Canvas with limited history + +For memory-sensitive applications, set a lower limit: + +```ts +const history = withHistory(canvasStore, { limit: 50 }); + +// Only the last 50 states are kept. +// Older entries are shifted off the front of the stack. +// Memory usage stays bounded. +``` + +## Undo/Redo and Branching + +When you undo and then make a new mutation, the redo history is discarded: + +``` +Initial state: { count: 0 } + +Mutation 1: count = 1 → history: [0, 1] +Mutation 2: count = 2 → history: [0, 1, 2] +Mutation 3: count = 3 → history: [0, 1, 2, 3] + +Undo → history: [0, 1, 2, 3], pointer at 2 (count = 2) +Undo → history: [0, 1, 2, 3], pointer at 1 (count = 1) + +New mutation: count = 99 → history: [0, 1, 99], pointer at 2 + (entries 2 and 3 are gone — no redo available) +``` + +This is the standard behavior for undo systems (like text editors). Making a new change from a past state creates a new branch and discards the old future. + +## Combining with Other Utilities + +### withHistory + persist + +Persist the store's current state while maintaining undo history in memory: + +```ts +import {persist, withHistory} from '@codebelt/classy-store/utils'; + +const todoStore = createClassyStore(new TodoStore()); + +// Persist to localStorage (survives page reload) +persist(todoStore, { name: 'todos' }); + +// Undo/redo in memory (resets on page reload) +const history = withHistory(todoStore); +``` + +History is in-memory only — it doesn't persist across page loads. The persisted state is always the latest state (not the undo pointer position). + +### withHistory + devtools + +Use both for a rich debugging experience: + +```ts +import {devtools, withHistory} from '@codebelt/classy-store/utils'; + +const store = createClassyStore(new MyStore()); + +devtools(store, { name: 'MyStore' }); +const history = withHistory(store); + +// DevTools shows every mutation including undo/redo restores. +// Time-travel in DevTools is independent of withHistory's stack. +``` + +## Quick Reference + +| What you want | How to do it | +|---|---| +| Add undo/redo | `const h = withHistory(store)` | +| Undo | `h.undo()` | +| Redo | `h.redo()` | +| Check if undoable | `h.canUndo` | +| Check if redoable | `h.canRedo` | +| Limit history size | `withHistory(store, { limit: 50 })` | +| Batch as single step | `h.pause(); /* mutations */; h.resume()` | +| Stop tracking | `h.dispose()` | +| Undo all | `while (h.canUndo) h.undo()` | diff --git a/website-fumadocs/content/docs/PERSIST_ARCHITECTURE.md b/website-fumadocs/content/docs/PERSIST_ARCHITECTURE.md new file mode 100644 index 0000000..564522d --- /dev/null +++ b/website-fumadocs/content/docs/PERSIST_ARCHITECTURE.md @@ -0,0 +1,303 @@ +--- +title: "Persist Utility -- Architecture" +description: "Understand how persistence saves, restores, migrates, and synchronizes store data." +--- + +Internal design documentation for the `persist()` utility in `@codebelt/classy-store/utils`. + +## Design Principles + +1. **Standalone utility, not middleware.** `persist` is a plain function that consumes two existing public APIs (`subscribe` and `snapshot`) and nothing else. No changes to `core.ts`, `snapshot.ts`, or `useClassyStore` were needed. + +2. **Tree-shakeable.** `persist` lives in a separate entry point (`@codebelt/classy-store/utils`). Applications that don't use persistence pay zero cost -- the code is never bundled. + +3. **Storage-agnostic.** The `StorageAdapter` interface accepts sync (`localStorage`, `sessionStorage`) and async (`AsyncStorage`, `localForage`) implementations through the same type. `persist` handles both transparently via `await`. + +4. **Class-aware.** The utility understands class stores: getters and methods are automatically excluded from persistence. Only source-of-truth data properties are saved. This is a key differentiator from libraries like Zustand where `partialize` requires manual exclusion. + +## How It Hooks Into the Existing Architecture + +`persist` sits beside the existing three-layer architecture as an independent consumer: + +```mermaid +flowchart LR + subgraph existingCore ["Existing Core (unchanged)"] + Mutation["store.prop = x"] --> Batch["queueMicrotask notify"] + Batch --> Listeners["subscribe callbacks"] + end + + subgraph persistLayer ["persist() utility"] + Listeners --> Debounce["debounce timer"] + Debounce --> Snap["snapshot(proxy)"] + Snap --> Pick["pick properties"] + Pick --> Serialize["per-property serialize"] + Serialize --> Envelope["JSON.stringify envelope"] + Envelope --> Storage["storage.setItem()"] + end + + subgraph hydration ["Hydration (init / cross-tab / manual)"] + Read["storage.getItem()"] --> Parse["JSON.parse"] + Parse --> Migrate["version check + migrate"] + Migrate --> Deserialize["per-property deserialize"] + Deserialize --> Merge["merge strategy"] + Merge --> Apply["proxy[key] = value"] + Apply --> Mutation + end +``` + +The key insight: writing `proxy[key] = value` during hydration flows through the existing SET trap in `core.ts`, which handles child proxy cleanup, version bumping, and notification scheduling automatically. The persist utility doesn't need to know anything about the proxy internals. + +## File Structure + +``` +src/ + index.ts # Existing barrel (unchanged) + utils/ + index.ts # Barrel: persist, devtools, subscribeKey, withHistory + persist/ + persist.ts # persist(), types, and all logic + persist.test.ts # tests with mock storage adapters +package.json # "./utils" export entry +tsdown.config.ts # 'src/utils/index.ts' in entry array +``` + +## Internal State + +Each `persist()` call creates a closure with the following internal state: + +| Variable | Type | Purpose | +|---|---|---| +| `disposed` | `boolean` | Guards against writes after `unsubscribe()` | +| `debounceTimer` | `ReturnType \| null` | Active debounce timer (cancelled on save/unsubscribe) | +| `hydratedFlag` | `boolean` | Synchronous hydration status | +| `expiredFlag` | `boolean` | Set to `true` when hydration encounters expired data | +| `resolveHydrated` | `() => void` | Resolves the `hydrated` promise | +| `rejectHydrated` | `(error) => void` | Rejects the `hydrated` promise on error | +| `resolvedProps` | `Array<{key, transform?}>` | Normalized property list with optional transforms | +| `transformMap` | `Map` | Fast lookup from key to transform | +| `propKeys` | `string[]` | Flat list of property keys to persist | + +No global mutable state. Multiple `persist()` calls on different stores (or even the same store with different keys) are fully independent. + +## Save Flow + +Triggered by `subscribe(proxy, callback)` firing after each batched mutation: + +``` +1. subscribe callback fires +2. if disposed → return (guard) +3. if debounce > 0: + cancel existing timer + schedule new timer → goto step 4 after delay + else: + goto step 4 immediately +4. snapshot(proxy) → frozen immutable copy +5. for each key in propKeys: + read value from snapshot + if transform exists → call transform.serialize(value) + assign to state object +6. wrap in envelope: { version, state } +7. if expireIn is set → stamp expiresAt = Date.now() + expireIn +8. JSON.stringify(envelope) +9. await storage.setItem(name, json) +``` + +### Property Resolution + +On initialization, `resolveProperties()` determines which keys to persist: + +- **If `properties` is provided:** each entry is either a string key (used as-is) or a `PropertyTransform` descriptor (key extracted, transform stored in `transformMap`). +- **If `properties` is omitted:** takes a snapshot of the store, iterates `Object.keys()`, and excludes: + - **Getters:** detected via `findGetterDescriptor()` walking the prototype chain with `Object.getOwnPropertyDescriptor`. + - **Methods:** detected via `typeof value === 'function'` on the proxy. + +This resolution happens once at `persist()` call time, not on every save. + +### Storage Envelope + +Data is stored as a JSON string with a version wrapper: + +```json +{ + "version": 1, + "state": { + "todos": [{"text": "Buy milk", "done": false}], + "filter": "all" + }, + "expiresAt": 1700000000000 +} +``` + +The `version` field is always present (default `0`). The `state` field contains only the selected properties, post-serialization-transform. The `expiresAt` field is a Unix epoch timestamp (milliseconds) and is only present when `expireIn` is set. It is refreshed on every write. Envelopes without `expiresAt` are treated as "never expires". + +## Restore Flow (Hydration) + +Triggered in three scenarios: +1. **Auto-hydration on init** (unless `skipHydration: true`) +2. **Manual rehydrate** via `handle.rehydrate()` +3. **Cross-tab sync** via `window.storage` event + +All three call the same `applyPersistedState(raw)` function: + +``` +1. JSON.parse(raw) → envelope +2. Validate envelope shape (object with .state) + if invalid → return silently (corrupted data) +3. if envelope.expiresAt exists AND Date.now() >= expiresAt: + set expiredFlag = true + if clearOnExpire → storage.removeItem(name) + return (skip hydration) +4. if envelope.version !== options.version AND migrate exists: + state = migrate(state, envelope.version) + if transform exists → state[key] = transform.deserialize(state[key]) +6. Build currentState from snapshot (only propKeys) +7. Apply merge strategy: + 'shallow' / 'replace': { ...currentState, ...persistedState } + custom function: merge(persistedState, currentState) +8. for each key in propKeys: + if key in mergedState → proxy[key] = mergedState[key] + (goes through SET trap → reactivity → batched notification) +9. Set hydratedFlag = true, resolve hydrated promise +``` + +### Merge Strategies + +| Strategy | Behavior | +|---|---| +| `'shallow'` (default) | `{ ...current, ...persisted }` -- persisted keys overwrite, new keys keep defaults | +| `'replace'` | Same as shallow at top level. Conceptually signals full replacement for nested objects. | +| Custom `fn` | `fn(persisted, current)` -- full control. Enables deep merge or selective overrides. | + +Both `'shallow'` and `'replace'` produce the same result for flat stores. The distinction is semantic and affects how consumers think about nested object handling. + +### Error Handling + +- **Corrupted JSON** in storage (`JSON.parse` throws): caught silently, hydration completes with no state changes. +- **Invalid envelope** (missing `.state` or not an object): detected by shape check, skipped silently. +- **Async errors** during `storage.getItem()`: caught, hydrated promise rejects, `hydratedFlag` still set to `true`. + +## Cross-Tab Sync + +When `syncTabs: true` is set, the utility listens for the `window.storage` event: + +```mermaid +sequenceDiagram + participant TabA as Tab A + participant LS as localStorage + participant Event as window 'storage' event + participant TabB as Tab B (this instance) + + TabA->>LS: setItem('my-store', newJSON) + LS->>Event: StorageEvent { key: 'my-store', newValue: newJSON } + Event->>TabB: onStorageEvent fires + TabB->>TabB: if key !== name → ignore + TabB->>TabB: if newValue is null → ignore (cleared) + TabB->>TabB: applyPersistedState(newValue) + Note over TabB: proxy[key] = value → SET trap → reactivity +``` + +Key properties of `window.storage`: +- Fires in **other** same-origin tabs only (not the tab that wrote). This prevents infinite loops. +- Only works with `localStorage` -- not `sessionStorage`, not async adapters. +- The event payload includes `key`, `oldValue`, `newValue`, and `storageArea`. + +### Auto-detection + +If `syncTabs` is not explicitly set: +- `true` when the storage adapter is literally `globalThis.localStorage` (checked via identity comparison). +- `false` for everything else (`sessionStorage`, `AsyncStorage`, custom adapters). + +## Cleanup + +`handle.unsubscribe()` performs three cleanup steps: + +``` +1. Set disposed = true (guards all future writes) +2. Cancel pending debounce timer (if any) +3. Call unsubscribe from store mutations (remove subscribe callback) +4. Remove window 'storage' event listener (if syncTabs was active) +``` + +After `unsubscribe()`: +- Store mutations no longer trigger writes to storage. +- Cross-tab changes to the storage key are ignored. +- `save()` becomes a no-op. +- `clear()` and `rehydrate()` still work (they operate on storage directly, not on subscriptions). + +## PersistHandle API + +| Method/Property | Behavior | +|---|---| +| `unsubscribe()` | Full cleanup: unsubscribe + cancel debounce + remove storage listener. Idempotent. | +| `hydrated` | Promise that resolves when initial hydration completes. Rejects on async storage errors. | +| `isHydrated` | Getter returning `boolean`. Becomes `true` after hydration resolves or rejects. | +| `isExpired` | Getter returning `boolean`. `true` when the last hydration found expired data (requires `expireIn`). Re-evaluated on every `rehydrate()` call. | +| `save()` | Cancel pending debounce, write current state immediately. No-op if disposed. | +| `clear()` | Call `storage.removeItem(name)`. Works even after unsubscribe. | +| `rehydrate()` | Re-read from storage, apply to store. Also resolves `hydrated` if not yet resolved (for `skipHydration` flows). | + +## Dependency Graph + +```mermaid +graph LR + persist["utils/persist.ts"] --> subscribe["core.ts: subscribe()"] + persist --> snapshot["snapshot.ts: snapshot()"] + persist --> findGetter["utils.ts: findGetterDescriptor()"] + utilsIndex["utils/index.ts"] --> persist +``` + +`persist` has **no dependency** on `useClassyStore`, `types.ts`, or `collections.ts`. It only uses: +- `subscribe()` -- to listen for store mutations +- `snapshot()` -- to create an immutable copy for serialization +- `findGetterDescriptor()` -- to detect class getters during property resolution + +## Performance Characteristics + +| Operation | Complexity | Notes | +|---|---|---| +| Property resolution | O(keys) | Once at init, not per-save | +| Save (per mutation batch) | O(propKeys) | Snapshot + pick + serialize + JSON.stringify | +| Debounced save | O(1) per mutation | Timer reset; actual save is O(propKeys) when timer fires | +| Hydration | O(propKeys) | JSON.parse + deserialize + merge + assign | +| Cross-tab sync | O(propKeys) | Same as hydration, triggered by storage event | +| `save()` | O(propKeys) | Same as auto-save, synchronous trigger | +| `unsubscribe()` | O(1) | Flag + timer cancel + listener removal | + +## Design Decisions + +### Why a function, not a decorator or middleware? + +Decorators require class modifications and don't compose well with the existing `createClassyStore()` wrapping. Middleware requires an interception layer that doesn't exist in the architecture (the proxy IS the middleware). A standalone function that takes a proxy and returns a handle is the simplest, most composable API. + +### Why per-property transforms instead of global serialize/deserialize? + +Global `serialize`/`deserialize` (as in Zustand's `createJSONStorage`) operates on the entire state blob. This makes it awkward when only one property needs special handling (e.g., a single `Date` field among ten string fields). Per-property transforms are more ergonomic for class-based stores where each property has a known type. + +### Why exclude getters automatically? + +Class getters are derived/computed values that recompute from persisted data properties. Persisting a getter result would be redundant and risk restoring a stale value that contradicts the actual data. For example, if `get remaining()` returns 3 but the persisted `todos` array has 5 incomplete items, the restored getter value would be wrong until something triggers a recompute. By excluding getters, we persist only the inputs and let the outputs recompute naturally. + +### Why a hydrated Promise instead of lifecycle hooks? + +Zustand uses `onRehydrateStorage` (a callback), Pinia uses `beforeHydrate`/`afterHydrate` hooks. A Promise is more composable: it works with `async`/`await`, can be passed to React Suspense boundaries, and doesn't require a registration pattern. The `isHydrated` boolean covers the synchronous check case. + +### Why debounce instead of throttle? + +Debounce waits for a quiet period after the last mutation. Throttle writes at regular intervals. For persistence, debounce is more appropriate: it coalesces bursts of mutations (like typing) into a single write, while throttle would write mid-burst with potentially incomplete state. `save()` provides the escape hatch for "write right now" scenarios. + +| Group | What's covered | +|---|---| +| Basic round-trip | Save/restore, versioned envelope, getter exclusion, method exclusion | +| Properties option | Persist specific properties, restore only specified properties | +| Per-property transforms | Date round-trip, ReactiveMap round-trip | +| Debounce | Coalescing, save bypass | +| Version migration | Migration call, version match skip | +| Merge strategies | Shallow, replace, custom function | +| skipHydration | No auto-hydrate, manual rehydrate, promise resolution | +| Unsubscribe | Stop writes, cancel debounce | +| Flush/clear/rehydrate | Immediate write, remove data, in-memory preservation, re-read | +| Hydration state | Promise instance, resolution timing | +| Async storage | Async write, async hydrate | +| Cross-tab sync | Correct key, wrong key, post-unsubscribe, disabled | +| expireIn / TTL | Normal hydration, expired skip, clearOnExpire, default leave-in-storage, cross-tab reject, no-expiresAt envelope, TTL reset on write, rehydrate re-check | +| Edge cases | Empty storage, corrupted JSON, invalid envelope, no-storage error, multiple persists | diff --git a/website-fumadocs/content/docs/PERSIST_TUTORIAL.md b/website-fumadocs/content/docs/PERSIST_TUTORIAL.md new file mode 100644 index 0000000..bc1da7d --- /dev/null +++ b/website-fumadocs/content/docs/PERSIST_TUTORIAL.md @@ -0,0 +1,631 @@ +--- +title: "Classy Store Persist Utility" +description: "Persist, restore, migrate, and synchronize store state across browser sessions." +--- + +`persist()` saves your store's state to storage and restores it on page load. It's a standalone utility function that works with any `createClassyStore()` instance. + +## Getting Started + +### 1. Create a store + +```ts +// todoStore.ts +import {createClassyStore} from '@codebelt/classy-store'; + +class TodoStore { + filter: 'all' | 'active' | 'done' = 'all'; + todos: {text: string; done: boolean}[] = []; + + get remaining() { + return this.todos.filter((todo) => !todo.done).length; + } + + addTodo(text: string) { + this.todos.push({text, done: false}); + } + + toggle(index: number) { + this.todos[index].done = !this.todos[index]!.done; + } +} + +export const todoStore = createClassyStore(new TodoStore()); +``` + +### 2. Persist it + +```ts +// todoStore.ts +import {createClassyStore} from '@codebelt/classy-store'; +import {persist} from '@codebelt/classy-store/utils'; + +// ... (Include TodoStore setup from Step 1) + +// Saves all store properties to localStorage +const handle = persist(todoStore, { + name: 'todo-store', +}); +``` + +**What gets stored in storage:** + +```json +// In localStorage the key will be `todo-store` and the value will be: +{ + "version": 0, + "state": { + "todos": [], + "filter": "all" + }, + "expiresAt": 300000 // Only present if you set the expireIn option (5 minutes) +} +``` + +On every mutation, `todos` and `filter` are saved to `localStorage`. On next page load, they're restored automatically. The getter `remaining` and the methods `addTodo`/`toggle` are **not** persisted -- getters are derived values that recompute from the persisted data, and methods are functions that don't belong in storage. + +### Choosing What to Persist + +By default, `persist()` saves all own data properties of the store -- everything that isn't a getter or a method. You can narrow this with the `properties` option: + +```ts +persist(todoStore, { + name: 'todo-store', + properties: ['todos'], // Only persist todos, not filter +}); +``` + +Now `filter` resets to its class default (`'all'`) on every page load, while `todos` are restored from storage. + +### Why getters are excluded + +Class getters like `get remaining()` are derived values. They compute their results from the data property (`todos`) every time they're accessed. Persisting a getter result would be redundant and risks restoring a stale value that contradicts the underlying data. + +### Configuration and Handle Overview + +Here is a quick overview of every configuration option and handle method. You won't need most of them day-to-day, but they're available when you do. + +```ts +import {persist} from '@codebelt/classy-store/utils'; + +const handle = persist(todoStore, { + name: 'TodoStore', // Unique storage key (required) + properties: [], // Keys or transform descriptors to persist (default: all own data properties) + storage: localStorage, // Storage adapter with getItem/setItem/removeItem (default: localStorage) + debounce: 500, // Coalesce rapid mutations into one write, in ms (default: 0) + version: 0, // Schema version number; triggers migrate when mismatched (default: 0) + migrate: (state, v) => {}, // Called when stored version ≠ current; must return state in current shape + merge: 'shallow', // How to merge persisted state on hydration: 'shallow' | 'replace' | custom fn + skipHydration: true, // Don't hydrate on init; call handle.rehydrate() manually (default: false) + syncTabs: true, // Re-hydrate when another tab writes to the same key (localStorage only) + expireIn: 1000, // TTL in ms; expired data is skipped on hydration. Resets on every write + clearOnExpire: false, // Auto-remove storage key when expired data is found (default: false) +}); + +handle.isHydrated; // Whether hydration from storage has completed +handle.isExpired; // True if last hydration found expired data (requires expireIn) +await handle.hydrated; // Promise that resolves when initial hydration is complete +await handle.save(); // Manually write to storage (bypasses debounce) +await handle.clear(); // Remove this store's persisted data from storage +await handle.rehydrate(); // Manually re-hydrate the store from storage +handle.unsubscribe(); // Stop persisting: unsubscribe + cancel debounce + remove storage listener +``` + +--- + +## Configuration Properties + +### `properties` (Per-Property Transforms) + +Some values aren't JSON-safe. `Date` objects become strings, and custom classes vanish entirely after `JSON.stringify` + `JSON.parse`. Transforms handle the round-trip through the `properties` option. + +```ts +class UserStore { + name = ''; + birthdate = new Date(); + + get isAdult() { + const cutoff = new Date(); + cutoff.setFullYear(cutoff.getFullYear() - 18); + return this.birthdate <= cutoff; + } +} + +const userStore = createClassyStore(new UserStore()); + +persist(userStore, { + name: 'user-store', + properties: [ + 'name', // plain key -- string, no transform needed + { + key: 'birthdate', + serialize: (date) => date.toISOString(), // Date → string + deserialize: (stored) => new Date(stored as string), // string → Date + }, + ], +}); +``` + +**In storage:** + +```json +{ + "version": 0, + "state": { + "name": "Robert", + "birthdate": "2026-02-14T00:00:00.000Z" + } +} +``` + +On restore, `deserialize` converts the ISO string back into a `Date` object before assigning it to `userStore.birthdate`. + +*(If you need to persist `Map` or `Set` objects, see the [Reactive Collections Tutorial](./REACTIVE_COLLECTIONS_TUTORIAL.md).)* + +### `storage` + +`persist()` handles different storage adapters transparently. The default is `localStorage`. Any object with `getItem`, `setItem`, and `removeItem` (sync or async) works. + +**sessionStorage (Tab-Scoped)** + +Use `sessionStorage` for state that should reset when the tab closes: + +```ts +persist(uiStore, { + name: 'ui-state', + storage: sessionStorage, + properties: ['sidebarOpen', 'activeTab'], + // syncTabs defaults to false; opt in only for localStorage +}); +``` + +**Async Storage (React Native)** + +`AsyncStorage` from React Native works out of the box: + +```ts +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const handle = persist(todoStore, { + name: 'todo-store', + storage: AsyncStorage, + syncTabs: false, // no window.storage in React Native +}); + +await handle.hydrated; +``` + +### `syncTabs` (Cross-Tab Synchronization) + +Opt in to keep `localStorage`-backed state in sync across browser tabs. When another tab writes to the same key, this tab re-hydrates from the new value: + +```ts +persist(authStore, { + name: 'auth', + storage: localStorage, + syncTabs: true, +}); + +// Tab A: user logs out +authStore.token = null; +authStore.user = null; +// --> writes to localStorage + +// Tab B: automatically receives the update +// --> authStore.token and authStore.user become null +``` + +This works via the browser's `window.storage` event, which fires in all **other** same-origin tabs. The persist utility listens for changes to its storage key and re-hydrates when it detects a write from another tab. + +Disable it when you don't want cross-tab sync: + +```ts +persist(uiStore, { + name: 'ui', + syncTabs: false, +}); +``` + +Cross-tab sync only works with `localStorage`. It does not fire for `sessionStorage`, `AsyncStorage`, or custom adapters. + +### `expireIn` & `clearOnExpire` (Expiration / TTL) + +Use `expireIn` to set a time-to-live (in milliseconds) on persisted data. After the TTL elapses, stored data is skipped during hydration and the store keeps its class defaults. + +```ts +persist(sessionStore, { + name: 'session', + expireIn: 900_000, // 15 minutes +}); +``` + +**The TTL resets on every write.** As long as mutations keep happening (an active user session), the data stays fresh. The countdown only matters when the store is hydrated from storage after a period of inactivity (e.g., a page reload). + +**Checking expiration** + +The handle exposes an `isExpired` flag that becomes `true` when hydration encounters expired data: + +```ts +const handle = persist(sessionStore, { + name: 'session', + expireIn: 900_000, +}); + +await handle.hydrated; + +if (handle.isExpired) { + // Stored session expired — redirect to login +} +``` + +By default, expired data is skipped but left in storage. Set `clearOnExpire: true` to automatically remove the key: + +```ts +persist(sessionStore, { + name: 'session', + expireIn: 900_000, + clearOnExpire: true, // Remove key from storage when expired +}); +``` + +### `skipHydration` (SSR / Next.js Support) + +`persist()` requires a `storage` adapter. For SSR (Next.js, etc.) where module init runs on the server, supply an adapter that falls back to in-memory when `localStorage` is undefined and gate hydration on the client with `skipHydration: true`: + +```ts +// _storage.ts -- shared SSR-safe adapter +const memory = new Map(); +export const ssrSafeLocalStorage = { + getItem: (k: string) => (typeof localStorage !== 'undefined' ? localStorage.getItem(k) : memory.get(k) ?? null), + setItem: (k: string, v: string) => { + if (typeof localStorage !== 'undefined') localStorage.setItem(k, v); + else memory.set(k, v); + }, + removeItem: (k: string) => { + if (typeof localStorage !== 'undefined') localStorage.removeItem(k); + else memory.delete(k); + }, +}; + +// store.ts -- runs on both server and client +export const handle = persist(todoStore, { + name: 'todo-store', + storage: ssrSafeLocalStorage, + skipHydration: true, +}); +``` + +Then hydrate manually in a `useEffect` (which only runs on the client): + +```tsx +import {useEffect} from 'react'; +import {handle} from './store'; + +function App() { + useEffect(() => { + handle.rehydrate(); + }, []); + + return ; +} +``` + +#### The hydration mismatch problem + +In SSR frameworks like Next.js, the server renders HTML using the store's class defaults (e.g., `theme = 'light'`). When the client hydrates, if `persist()` has already restored a different value from `localStorage` (e.g., `theme = 'dark'`), React sees a mismatch between the server-rendered HTML and the client state. This causes a **hydration error**. + +Setting `skipHydration: true` prevents `persist()` from reading `localStorage` until you explicitly call `rehydrate()` — letting you control *when* the stored state is applied and avoid the mismatch. + +#### `StoreHydrator` component pattern + +When you have multiple persisted stores, calling `rehydrate()` in individual components is repetitive. A better pattern is a single `StoreHydrator` component that gates rendering until all stores are hydrated: + +```tsx +// StoreHydrator.tsx +'use client'; + +import {type ReactNode, useEffect, useState} from 'react'; +import {settingsPersist} from './stores/settings-store'; +import {todoPersist} from './stores/todo-store'; + +// Collect all persist handles that use skipHydration: true +const persists = [settingsPersist, todoPersist]; + +export function StoreHydrator({children}: {children: ReactNode}) { + const [hydrated, setHydrated] = useState(false); + + useEffect(() => { + Promise.all(persists.map((p) => p.rehydrate())).then(() => + setHydrated(true), + ); + }, []); + + if (!hydrated) { + return null; // or a loading spinner + } + + return <>{children}; +} +``` + +Wrap your app layout with `StoreHydrator` so nothing renders until `localStorage` values have been applied: + +```tsx +// layout.tsx (Next.js App Router) +import {StoreHydrator} from '@/components/StoreHydrator'; + +export default function RootLayout({children}: {children: React.ReactNode}) { + return ( + + + + +
{children}
+
+ + + ); +} +``` + +**How it works:** + +1. The server renders the page using each store's class defaults — no `localStorage` access. +2. On the client, `StoreHydrator` mounts and calls `rehydrate()` on every persist handle. +3. Until all handles resolve, `StoreHydrator` renders `null` (or a loading indicator), preventing any child component from reading stale default values. +4. Once hydration completes, children render with the correct `localStorage`-restored state — no mismatch. + +### `debounce` (Debouncing Writes) + +For stores that mutate frequently (form inputs, drag positions, real-time data), debouncing prevents excessive writes: + +```ts +persist(formStore, { + name: 'form-draft', + debounce: 500, // Write at most once every 500ms +}); + +// Rapid mutations: +formStore.name = 'J'; +formStore.name = 'Jo'; +formStore.name = 'John'; +// One write to storage, 500ms after the last mutation. +``` + +### `version`, `migrate`, & `merge` (Version Migration and Strategies) + +When the store's shape changes between releases, versioning and migration prevent data loss: + +```ts +persist(todoStore, { + name: 'todo-store', + version: 2, + migrate: (state, oldVersion) => { + if (oldVersion === 0) { + // v0 stored todos as `items` + return {...state, todos: state.items, items: undefined}; + } + if (oldVersion === 1) { + // v1 todos were plain strings, v2 todos are objects + return { + ...state, + todos: (state.todos as string[]).map((text) => ({text, done: false})), + }; + } + return state; + }, +}); +``` + +**Merge Strategies** + +When you add new properties to a store, old persisted data won't include them. The `merge` strategy controls how persisted state combines with the store's current state. + +Imagine your store adds a new `tags` property, but old persisted data only has `todos` and `filter`: + +```ts +// Current store class (v2 — added `tags`) +class TodoStore { + filter: 'all' | 'active' | 'done' = 'all'; + todos: {text: string; done: boolean}[] = []; + tags: string[] = ['work', 'personal']; // new property with defaults +} + +// Old persisted data in storage (no `tags` key): +// { "version": 1, "state": { "filter": "done", "todos": [{"text": "Buy milk", "done": false}] } } +``` + +**`'shallow'`** (default) — persisted values overwrite current values one key at a time. Properties not in storage keep their default value: + +```ts +persist(todoStore, { + name: 'todo-store', + storage: localStorage, + merge: 'shallow', +}); + +// Result after hydration: +// filter → "done" (from storage) +// todos → [{text: "Buy milk", done: false}] (from storage) +// tags → ["work", "personal"] (kept class default) +``` + +**Custom function** — for full control, pass a function that receives `(persisted, current)` and returns the merged state: + +```ts +persist(todoStore, { + name: 'todo-store', + merge: (persisted, current) => { + return { + ...current, // start with class defaults + ...persisted, // overwrite with stored values + tags: [ // custom: combine stored + default tags + ...new Set([ + ...(persisted.tags as string[] ?? []), + ...(current.tags as string[] ?? []), + ]), + ], + }; + }, +}); +``` + +--- + +## The Handle + +### `isHydrated` & `hydrated` + +If you need to know when the stored data has been applied to the store: + +```ts +// Async -- await the promise +await handle.hydrated; +console.log('Restored:', todoStore.todos); + +// Sync check +if (handle.isHydrated) { + // safe to read persisted state +} +``` + +For `localStorage` (synchronous), hydration resolves almost immediately. For async storage adapters like `AsyncStorage`, the promise resolves after the async read completes. + +### `clear()` (Clear stored data) + +```ts +// Remove the stored data without affecting in-memory state +await handle.clear(); + +// The store still has its current state in memory. +// Next page load starts with class defaults (nothing in storage). +``` + +### `save()` (Manual save) + +When you need to save immediately (e.g., before the user navigates away): + +```ts +const handle = persist(formStore, { + name: 'form-draft', + debounce: 1000, +}); + +// Force save before page unload +window.addEventListener('beforeunload', () => { + handle.save(); +}); +``` + +`save()` cancels the pending debounce timer and writes the current state immediately. + +### `unsubscribe()` (Stop persisting) + +```ts +const handle = persist(todoStore, {name: 'todo-store'}); + +// Later: stop all persistence (e.g., on logout) +handle.unsubscribe(); + +// Mutations after this point are NOT written to storage. +// Cross-tab sync listener is also removed. +// Pending debounce timer is cancelled. +todoStore.addTodo('This is not persisted'); +``` + +--- + +## Multiple Persistence Targets + +Call `persist()` multiple times with different options to persist different parts of a store to different locations: + +```ts +const todoStore = createClassyStore(new TodoStore()); + +// Persist todos to localStorage (survives page close) +persist(todoStore, { + name: 'todo-items', + properties: ['todos'], +}); + +// Persist UI filter to sessionStorage (resets on tab close) +persist(todoStore, { + name: 'todo-filter', + storage: sessionStorage, + properties: ['filter'], +}); +``` + +## Putting It All Together + +Here's a complete example combining several features: + +```ts +import {createClassyStore} from '@codebelt/classy-store'; +import {persist} from '@codebelt/classy-store/utils'; + +class AppStore { + theme: 'light' | 'dark' = 'light'; + language = 'en'; + lastLogin = new Date(0); + + setTheme(theme: 'light' | 'dark') { + this.theme = theme; + } +} + +const appStore = createClassyStore(new AppStore()); + +const handle = persist(appStore, { + name: 'app-store', + version: 1, + debounce: 300, + + properties: [ + 'theme', + 'language', + { + key: 'lastLogin', + serialize: (date) => date.toISOString(), + deserialize: (stored) => new Date(stored as string), + }, + ], + + migrate: (state, oldVersion) => { + if (oldVersion === 0) { + // v0 didn't have language + return {...state, language: 'en'}; + } + return state; + }, +}); + +// Wait for hydration, then start the app +await handle.hydrated; +console.log(`Welcome back! Theme: ${appStore.theme}`); +``` + +## Quick Reference + +| Feature | Code Example | +|---|---| +| **Persist everything** | `persist(myStore, {name: 'key'})` | +| **Persist specific properties** | `properties: ['count', 'name']` | +| **Handle Dates & Objects** | `{key: 'date', serialize: (d) => d.toISOString(), deserialize: (state) => new Date(s)}` | +| **Handle Reactive collections** | `users: reactiveMap()` (supported natively) | +| **Use sessionStorage** | `storage: sessionStorage` | +| **Use AsyncStorage** | `storage: AsyncStorage, syncTabs: false` | +| **Disable cross-tab sync** | `syncTabs: false` | +| **Expire payload after time**| `expireIn: 900_000` | +| **Auto-clear expired data** | `clearOnExpire: true` | +| **SSR & manual hydration** | `skipHydration: true` + `handle.rehydrate()` | +| **Debounce storage writes**| `debounce: 500` | +| **Migrate schema versions**| `version: 2, migrate: (state, old) => { ... }` | +| **Merge strategy** | `merge: 'shallow'` \| `'replace'` \| `(persisted, current) => merged` | +| **Check hydration** | `handle.isHydrated` | +| **Wait for hydration** | `await handle.hydrated` | +| **Clear stored data** | `await handle.clear()` | +| **Force immediate save** | `await handle.save()` | +| **Re-hydrate manually** | `await handle.rehydrate()` | +| **Stop persisting** | `handle.unsubscribe()` | +| **Check if data expired** | `handle.isExpired` | diff --git a/website-fumadocs/content/docs/REACTIVE_COLLECTIONS_TUTORIAL.md b/website-fumadocs/content/docs/REACTIVE_COLLECTIONS_TUTORIAL.md new file mode 100644 index 0000000..6961453 --- /dev/null +++ b/website-fumadocs/content/docs/REACTIVE_COLLECTIONS_TUTORIAL.md @@ -0,0 +1,88 @@ +--- +title: "Reactive Collections" +description: "Use reactiveMap and reactiveSet collections inside Classy Store instances." +--- + +Classy Store provides `ReactiveMap` and `ReactiveSet` to help with persisting `Map` and `Set` collections. Both collections are backed by plain arrays under the hood, but they expose the exact same methods and APIs as their native counterparts. + +> [!NOTE] +> **Performance Tip:** You should use native `Map` and `Set` in the store for better performance in most cases. `ReactiveMap` and `ReactiveSet` should only be used if you explicitly need to persist a `Map` or `Set` to storage using the `persist()` utility. + +## Why Reactive Collections? + +Native `Map` and `Set` objects are not JSON-serializable by default, making them incompatible with `localStorage` and `sessionStorage`. When you `JSON.stringify` a `Map` or `Set`, it usually becomes an empty object `{}`. + +`ReactiveMap` and `ReactiveSet` are backed by internal structures that allow them to be persisted correctly when combined with the `serialize` and `deserialize` options in the `persist` utility. + +## Persisting a ReactiveMap + +When persisting a `ReactiveMap`, you do not need to provide custom `serialize` or `deserialize` logic. The reactive collection is backed by an internal structure the `persist` utility can natively stringify and parse. + +```ts +import {createClassyStore} from '@codebelt/classy-store'; +import {reactiveMap} from '@codebelt/classy-store/collections'; +import {persist} from '@codebelt/classy-store/utils'; + +class UserStore { + users = reactiveMap(); + + addUser(id: string, name: string, role: string) { + this.users.set(id, {name, role}); + } +} + +const userStore = createClassyStore(new UserStore()); + +persist(userStore, { + name: 'user-store', +}); +``` + +**What gets stored in storage:** + +```json +{ + "version": 0, + "state": { + "users": [ + ["u1", { "name": "Alice", "role": "admin" }], + ["u2", { "name": "Bob", "role": "viewer" }] + ] + } +} +``` + +## Persisting a ReactiveSet + +The same pattern works for `ReactiveSet`. You can simply stringify it by referring to the property directly. + +```ts +import {createClassyStore} from '@codebelt/classy-store'; +import {reactiveSet} from '@codebelt/classy-store/collections'; +import {persist} from '@codebelt/classy-store/utils'; + +class TagStore { + tags = reactiveSet(); + + addTag(tag: string) { + this.tags.add(tag); + } +} + +const tagStore = createClassyStore(new TagStore()); + +persist(tagStore, { + name: 'tag-store', +}); +``` + +**What gets stored in storage:** + +```json +{ + "version": 0, + "state": { + "tags": ["typescript", "javascript", "react"] + } +} +``` diff --git a/website-fumadocs/content/docs/REACT_SSR.md b/website-fumadocs/content/docs/REACT_SSR.md new file mode 100644 index 0000000..6797f56 --- /dev/null +++ b/website-fumadocs/content/docs/REACT_SSR.md @@ -0,0 +1,107 @@ +--- +title: "React server rendering and hydration" +description: "Server rendering, request isolation, and hydration patterns for Classy Store in React." +--- + +Create one store for each server request and reconstruct a client store from the same initial data. Pass the store through props or React context. A module-level store on the server shares mutable state across requests and can expose one user's data to another. + +Call `prepareStoreForHydration(store)` after initializing the store with the data used to render the page, on both the server and client. It captures that initial snapshot before persistence or other live updates begin. All direct and bound hooks use the captured data during server rendering and client hydration, then read the current live store. React requires the initial server and client data to match; see [React's external-store SSR guidance](https://react.dev/reference/react/useSyncExternalStore#adding-support-for-server-rendering). + +## Initialize from request data + +Keep the serialized payload limited to the data the client needs. Reconstruct the class so its methods and getters remain available: + +```ts +// profileStore.ts +import {createClassyStore} from '@codebelt/classy-store'; +import {prepareStoreForHydration} from '@codebelt/classy-store/react'; + +export type ProfileData = { + name: string; + visits: number; +}; + +export class ProfileStore { + name: string; + visits: number; + + constructor(initialData: ProfileData) { + this.name = initialData.name; + this.visits = initialData.visits; + } + + recordVisit() { + this.visits++; + } +} + +export function createProfileStore(initialData: ProfileData) { + return prepareStoreForHydration( + createClassyStore(new ProfileStore(initialData)), + ); +} +``` + +```tsx +// Profile.tsx +import {useClassyStore} from '@codebelt/classy-store/react'; +import type {ProfileStore} from './profileStore'; + +export function Profile({store}: {store: ProfileStore}) { + const state = useClassyStore(store); + return ( + + ); +} +``` + +For each request, render ``. Transfer `initialData` using the framework's serialization facility and initialize the browser store from that same payload before hydration. If implementing the HTML transport yourself, use safe JSON serialization for the embedding context; do not interpolate raw JSON into an executable script. Class instances, functions, and native collection types need an explicit data representation. + +For a store owned by a component, `prepareStoreForHydration(useLocalStore(() => new ProfileStore(initialData)))` captures its initial state while keeping its identity stable across renders. Preparation is idempotent. Keep the factory pure: React Strict Mode can evaluate it more than once in development. Changes to `initialData` after mounting do not automatically reset the store or its captured snapshot. + +## Restore persistence while other boundaries hydrate + +Browser storage may contain values different from the server-rendered payload. Prepare the store before starting persistence, attach persistence in an effect, and unsubscribe when the component unmounts. The effect can restore storage even while a Suspense boundary is still waiting to hydrate: + +```tsx +// PersistedProfile.tsx +import {useEffect} from 'react'; +import {prepareStoreForHydration, useLocalStore} from '@codebelt/classy-store/react'; +import {persist} from '@codebelt/classy-store/utils'; +import {Profile} from './Profile'; +import {type ProfileData, ProfileStore} from './profileStore'; + +export function PersistedProfile({initialData}: {initialData: ProfileData}) { + const store = prepareStoreForHydration( + useLocalStore(() => new ProfileStore(initialData)), + ); + + useEffect(() => { + const persistence = persist(store, { + name: 'profile', + storage: localStorage, + skipHydration: true, + }); + void persistence.rehydrate(); + return () => persistence.unsubscribe(); + }, [store]); + + return ; +} +``` + +For example, the server renders `Server:1`, persistence restores `Saved:8`, and a delayed boundary then hydrates. The boundary first reads `Server:1` from the captured snapshot, reuses the server DOM, and then updates to `Saved:8` from the live store. It does not need an earlier hook read to establish the initial data. This applies to completed server HTML with selective hydration and to streamed Suspense content. + +## Snapshot lifetime and scope + +- Capture after loading the initial request data, before rendering or starting persistence. Preparation cannot recover earlier data if the store has already changed. +- Repeated calls return the same mutable proxy and retain the first snapshot. The capture is kept for the store's lifetime because another boundary or root may hydrate later. Use a fresh store for a different request or initial payload. +- Prepare the exact proxy passed to `useClassyStore` or `createStoreHook`. If a hook subscribes directly to a nested proxy, prepare that proxy separately. +- Capture follows ordinary `snapshot()` semantics. Plain objects, arrays, and supported class state are immutable snapshots; opaque leaves such as native `Date`, `Map`, and `Set` retain their identity. Replace those fields instead of mutating their native objects in place. +- Selectors read captured data during hydration and live data afterward. Their caches are separate, while equal selected results preserve reference identity at the handoff. + +Client-only apps need no preparation. `createRoot` reads current state even for a prepared store. Unprepared stores retain the existing behavior of reading current state for server rendering and hydration, so keep them unchanged until every consumer hydrates if you do not use preparation. Neither preparation nor hydration resets the live store; `snapshot(store)` continues to return current data. + +React subscriptions notify synchronously by default, including after hydration. Ordinary `subscribe(store, listener)` callbacks remain batched. The React hooks do not have a `sync` option. A normal sequence of mutations in an event can be batched into one React commit, but selectors may run after each mutation; this is not a transaction API. diff --git a/website-fumadocs/content/docs/SUBSCRIBE_KEY_TUTORIAL.md b/website-fumadocs/content/docs/SUBSCRIBE_KEY_TUTORIAL.md new file mode 100644 index 0000000..ea7e310 --- /dev/null +++ b/website-fumadocs/content/docs/SUBSCRIBE_KEY_TUTORIAL.md @@ -0,0 +1,204 @@ +--- +title: "subscribeKey Tutorial" +description: "Subscribe to one store property and respond only when that value changes." +--- + +`subscribeKey()` subscribes to changes on a single property of a store proxy. It's the property-level equivalent of `subscribe()` — instead of firing on every store mutation, it only fires when the specific key you're watching actually changes. + +## Getting Started + +### 1. Create a store + +```ts +import {createClassyStore} from '@codebelt/classy-store'; + +class SettingsStore { + theme: 'light' | 'dark' = 'light'; + fontSize = 14; + language = 'en'; + + setTheme(theme: 'light' | 'dark') { + this.theme = theme; + } + + setFontSize(size: number) { + this.fontSize = size; + } +} + +export const settingsStore = createClassyStore(new SettingsStore()); +``` + +### 2. Watch a single key + +```ts +import {subscribeKey} from '@codebelt/classy-store/utils'; + +const unsub = subscribeKey(settingsStore, 'theme', (value, previousValue) => { + console.log(`Theme changed from "${previousValue}" to "${value}"`); + document.documentElement.setAttribute('data-theme', value); +}); +``` + +Now only `theme` changes trigger the callback. Mutations to `fontSize` or `language` are ignored entirely. + +### 3. Unsubscribe when done + +```ts +unsub(); +``` + +## Signature + +```ts +function subscribeKey( + proxyStore: T, + key: K, + callback: (value: Snapshot[K], previousValue: Snapshot[K]) => void, +): () => void; +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `proxyStore` | `T` | A reactive proxy created by `createClassyStore()` | +| `key` | `keyof T` | The property to watch | +| `callback` | `(value, previousValue) => void` | Called when the watched property changes | +| **Returns** | `() => void` | Unsubscribe function | + +## How It Works + +Under the hood, `subscribeKey` wraps the existing `subscribe()` + `snapshot()` APIs: + +1. Captures an initial snapshot and stores `snapshot[key]` as the previous value. +2. On each store mutation, takes a new snapshot and compares `snapshot[key]` with the previous value via `Object.is()`. +3. If different, fires the callback with `(currentValue, previousValue)` and updates the stored previous value. + +Because `snapshot()` uses structural sharing, unchanged sub-trees return the same reference. This makes the `Object.is()` comparison efficient — no deep equality checks are needed. + +## Use Cases + +### Reacting to auth state changes + +```ts +class AuthStore { + token: string | null = null; + user: { name: string; role: string } | null = null; + + login(token: string, user: { name: string; role: string }) { + this.token = token; + this.user = user; + } + + logout() { + this.token = null; + this.user = null; + } +} + +const authStore = createClassyStore(new AuthStore()); + +// Redirect on login/logout +subscribeKey(authStore, 'token', (token, previousToken) => { + if (token && !previousToken) { + router.push('/dashboard'); + } else if (!token && previousToken) { + router.push('/login'); + } +}); +``` + +### Syncing with external systems + +```ts +class PlayerStore { + volume = 0.8; + track: string | null = null; + playing = false; + + setVolume(v: number) { + this.volume = v; + } + + play(track: string) { + this.track = track; + this.playing = true; + } +} + +const playerStore = createClassyStore(new PlayerStore()); + +// Sync volume slider with the Web Audio API +subscribeKey(playerStore, 'volume', (volume) => { + audioContext.gainNode.gain.value = volume; +}); + +// Update document title when track changes +subscribeKey(playerStore, 'track', (track) => { + document.title = track ? `Playing: ${track}` : 'Music Player'; +}); +``` + +### Logging specific changes + +```ts +class CartStore { + items: { id: string; qty: number }[] = []; + coupon: string | null = null; + + get total() { + return this.items.reduce((sum, item) => sum + item.qty, 0); + } + + addItem(id: string) { + this.items.push({ id, qty: 1 }); + } + + applyCoupon(code: string) { + this.coupon = code; + } +} + +const cartStore = createClassyStore(new CartStore()); + +// Only log when coupon changes — not on every item add +subscribeKey(cartStore, 'coupon', (coupon, prev) => { + analytics.track('coupon_changed', { from: prev, to: coupon }); +}); +``` + +## subscribeKey vs subscribe + +| | `subscribe()` | `subscribeKey()` | +|---|---|---| +| Fires on | Any mutation in the store | Only when the watched key changes | +| Callback args | `()` (no arguments) | `(value, previousValue)` | +| Use case | General side effects | Property-specific reactions | +| Overhead | Minimal | One snapshot comparison per mutation | + +Use `subscribe()` when you need to react to any change. Use `subscribeKey()` when you only care about one property and want to avoid running your callback unnecessarily. + +## Multiple Keys + +To watch multiple keys independently, call `subscribeKey` once per key: + +```ts +const unsub1 = subscribeKey(store, 'theme', handleThemeChange); +const unsub2 = subscribeKey(store, 'language', handleLanguageChange); + +// Clean up both +function dispose() { + unsub1(); + unsub2(); +} +``` + +Each subscription is independent — they don't interfere with each other. + +## Quick Reference + +| What you want | How to do it | +|---|---| +| Watch a single property | `subscribeKey(store, 'key', (val, prev) => { ... })` | +| Stop watching | `const unsub = subscribeKey(...); unsub()` | +| Watch multiple properties | Call `subscribeKey()` once per key | +| Get both old and new value | Callback receives `(value, previousValue)` | diff --git a/website-fumadocs/content/docs/TUTORIAL.md b/website-fumadocs/content/docs/TUTORIAL.md new file mode 100644 index 0000000..e6e0ba0 --- /dev/null +++ b/website-fumadocs/content/docs/TUTORIAL.md @@ -0,0 +1,612 @@ +--- +title: "Classy Store Tutorial" +description: "Build a Classy Store app from a class definition through selectors, snapshots, and persistence." +--- + +`Classy Store` is a reactive state library for React, Vue, Svelte, Solid, and Angular built on ES proxies. You define state as plain classes, wrap them with `createClassyStore()`, and read them with the binding for your framework. There are no Providers, no observers, no reducers, and no extra TypeScript interfaces to define your state shape — just classes and a hook. The library is ~3.5 KB gzipped, batches ordinary subscriptions while notifying React synchronously, and uses structural sharing for efficient change detection. + +## Quick Start + +**1. Define a class:** + +```ts +// stores.ts +import {createClassyStore} from '@codebelt/classy-store'; + +class Counter { + count = 0; + + increment() { + this.count++; + } +} + +export const counterStore = createClassyStore(new Counter()); +``` + +**2. Use it in a component:** + +```tsx +// Counter.tsx +import {useClassyStore} from '@codebelt/classy-store/react'; +import {counterStore} from './stores'; + +export function Counter() { + const count = useClassyStore(counterStore, (state) => state.count); + return ; +} +``` + +That's it. No Provider wrapping your app. The store is a module-level singleton — import it wherever you need it. + +## Defining Stores + +A store is any class instance wrapped with `createClassyStore()`. State lives as properties, mutations are plain assignments, and computed values are `get` accessors. + +```ts +import {createClassyStore} from '@codebelt/classy-store'; + +class TodoStore { + items: { id: number; text: string; done: boolean }[] = []; + filter: 'all' | 'active' | 'done' = 'all'; + + get remaining() { + return this.items.filter((item) => !item.done).length; + } + + get filtered() { + if (this.filter === 'active') return this.items.filter((item) => !item.done); + if (this.filter === 'done') return this.items.filter((item) => item.done); + return this.items; + } + + add(text: string) { + this.items.push({id: Date.now(), text, done: false}); + } + + toggle(id: number) { + const item = this.items.find((todo) => todo.id === id); + if (item) item.done = !item.done; + } + + remove(id: number) { + this.items = this.items.filter((todo) => todo.id !== id); + } +} + +export const todoStore = createClassyStore(new TodoStore()); +``` + +Key points: + +- Methods mutate state by assigning to `this`. The proxy intercepts every write. +- `get` accessors work as computed values — they run against the proxied state, so they're always up to date. +- Nested objects and arrays (like `items` and each item inside it) are automatically wrapped in child proxies. `item.done = true` triggers a notification just like a top-level write. + +## Reading State in React + +`useClassyStore` has two modes. Pick the right one and your components only re-render when they need to. + +### Selector mode + +```ts +const count = useClassyStore(counterStore, (state) => state.count); +``` + +The selector receives an immutable snapshot and returns the slice you need. The component re-renders only when the selected value changes (compared with `Object.is`). + +> **When to use:** Default choice. Best for render performance because it's explicit about what the component depends on. + +### Auto-tracked mode + +```ts +const snap = useClassyStore(counterStore); +// just use snap.count, snap.name, etc. in your JSX +``` + +No selector — you get back a tracking proxy over the snapshot. The library records which properties your component reads during render and only re-renders when one of those properties changes. + +> **When to use:** When you'd need 3+ selectors in one component, when prototyping, or when the shape of data you access is dynamic. + +### Custom equality with `shallowEqual` + +By default, `useClassyStore` compares the selector's return value with `Object.is`, which is a reference check. This works perfectly for primitives and for selecting existing properties directly — structural sharing means that if `todos` didn't change, the snapshot returns the **same frozen array reference**, so `Object.is` returns `true` and the component skips the re-render. + +```ts +// ✅ No shallowEqual needed — structural sharing keeps the reference stable +const count = useClassyStore(todoStore, (state) => state.items.length); +const todos = useClassyStore(todoStore, (state) => state.items); +``` + +The problem shows up when your selector **derives a new value** — calling `.filter()`, `.map()`, or using object spread always allocates a new array or object, even when the underlying data hasn't changed. `Object.is` sees a different reference and triggers a re-render. + +`shallowEqual` fixes this by comparing one level deep: for arrays it checks length + each element with `Object.is`, and for objects it checks key count + each value with `Object.is`. It only works on plain objects and arrays — not on `Map`, `Set`, or class instances. + +Without `shallowEqual` — `.filter()` creates a new array every time the snapshot updates, even if the todo items haven't changed: + +```ts +import {useClassyStore} from '@codebelt/classy-store/react'; + +// ❌ New array reference every snapshot → unnecessary re-renders +const active = useClassyStore(todoStore, (state) => state.items.filter((item) => !item.done)); +``` + +With `shallowEqual` — compares the array contents, not the reference: + +```ts +import {shallowEqual} from '@codebelt/classy-store'; +import {useClassyStore} from '@codebelt/classy-store/react'; + +// ✅ Only re-renders when the filtered items actually change +const active = useClassyStore( + todoStore, + (state) => state.items.filter((item) => !item.done), + {isEqual: shallowEqual}, +); +``` + +**Getters do NOT need `shallowEqual`.** Class getters benefit from cross-snapshot memoization: the library tracks which `this` properties a getter reads and caches the result. On subsequent snapshots, if those properties are structurally the same (same reference via structural sharing), the cached result is returned. This means getter results are stable across snapshots -- `Object.is` works out of the box: + +```ts +// ✅ No shallowEqual needed — cross-snapshot memoization keeps getter results stable +const filtered = useClassyStore(todoStore, (state) => state.filtered); + +// ✅ Primitives from getters also work fine +const remaining = useClassyStore(todoStore, (state) => state.remaining); +``` + +`shallowEqual` is only needed when the **selector itself** derives a new value (via `.filter()`, `.map()`, object spread, etc.) — not when selecting a getter that does the derivation internally. + +> **When to use:** When your selector (not a getter) derives a new object or array inline. You don't need it when selecting existing properties, primitives, or class getters — structural sharing, cross-snapshot memoization, and `Object.is` already handle those. + +### Decision guide + +- **Use selectors by default.** They're explicit and produce the fewest re-renders. +- **Use auto-tracked mode** when you'd need 3+ selectors in one component or when you're exploring the API. +- **Add `shallowEqual`** when your selector returns objects/arrays and you're seeing unnecessary re-renders. + +### Controlled inputs + +React subscriptions always notify synchronously. React sees a field's updated value during the input event, so typing in the middle of a controlled input preserves its caret. Selector, auto-tracked, and bound hooks all use the same timing without configuration: + +```tsx +class ProfileFormStore { + name = ''; + + setName(value: string) { + this.name = value; + } +} + +const profileFormStore = createClassyStore(new ProfileFormStore()); + +function NameInput() { + const name = useClassyStore(profileFormStore, (state) => state.name); + + return ( + profileFormStore.setName(event.target.value)} + /> + ); +} +``` + +When upgrading, remove the React `sync` option and `UseClassyStoreOptions` imports. Keep `isEqual` options if you use custom selector equality. The low-level `subscribe()` API retains its existing batching and optional `sync` setting. + +## Batching + +Ordinary `subscribe()` callbacks batch notifications in a microtask. React subscriptions instead notify after each mutation. React batches the resulting renders within an event handler, so a method that updates several fields can commit their final state together. This is not a transaction: selectors and getters may run between writes. + +```ts +class FormStore { + name = ''; + email = ''; + submitted = false; + count = 0; + + submit(name: string, email: string) { + this.name = name; // immediately notifies React + this.email = email; // immediately notifies React + this.submitted = true; // React batches renders in an event handler + } + + incrementMany(n: number) { + for (let i = 0; i < n; i++) { + this.count++; // each write notifies React + } + } +} +``` + +For ordinary subscribers, `incrementMany(100)` produces one notification. React may check its selected value on every write; use efficient selectors and avoid depending on an exact render count outside an event handler. + +Mutations separated by `await` can commit separately, allowing loading and result states to appear — see [Async Patterns](#async-patterns) below. + +## Snapshots + +`snapshot()` creates a deeply-frozen, immutable copy of the store's current state. + +```ts +import {snapshot} from '@codebelt/classy-store'; +import {counterStore} from './stores'; + +const snap = snapshot(counterStore); +console.log(snap.count); // read-only +snap.count = 5; // throws in strict mode +``` + +> **When to use:** Logging, debugging, serialization, passing state to non-React code (e.g., a canvas renderer, a Web Worker message). Inside React, `useClassyStore` calls `snapshot()` for you — you rarely need it directly. + +Snapshots use structural sharing: unchanged sub-trees return the same frozen reference. If you only mutated `store.name`, then `snapshot(store).settings === previousSnapshot.settings`. This is why selectors on nested objects are efficient — if the sub-tree didn't change, `Object.is` returns `true` and the component skips the re-render. + +## Subscribing Outside React + +`subscribe()` registers a callback that fires once per batched mutation. It returns an unsubscribe function. + +```ts +import {subscribe, snapshot} from '@codebelt/classy-store'; +import {counterStore} from './stores'; + +const unsub = subscribe(counterStore, () => { + const snap = snapshot(counterStore); + localStorage.setItem('counter', JSON.stringify(snap.count)); +}); + +// later: unsub(); +``` + +> **When to use:** Analytics events, `localStorage` persistence, WebSocket bridges, or any side effect that should react to store changes but isn't a React component. + +## Nested State & Structural Sharing + +Deep objects and arrays are automatically reactive. You don't need to do anything special. + +```ts +class DocStore { + metadata = {title: 'Untitled', author: 'Anonymous'}; + sections = [ + {id: 1, heading: 'Intro', body: 'Hello'}, + {id: 2, heading: 'Methods', body: 'We used proxies.'}, + ]; +} + +const docStore = createClassyStore(new DocStore()); + +// Deep mutation — triggers a notification +docStore.metadata.title = 'My Document'; + +// Array element mutation — also triggers a notification +docStore.sections[0].body = 'Updated intro'; +``` + +While modifying nested properties directly through the proxy works, the same best practice applies here: prefer store methods over inline mutations. When nested updates are scattered across components, it becomes difficult to trace how deeply nested state changes. Methods give you a single place to look. + +Structural sharing means that when you mutate `metadata.title`, the snapshot for `sections` is reused from the previous snapshot (same reference). A component selecting `(state) => state.sections` won't re-render because its selected value hasn't changed. + +## Collections + +Native `Map` and `Set` aren't plain objects — the proxy can't intercept their internal methods. Use `reactiveMap()` and `reactiveSet()` instead. + +```ts +import {createClassyStore} from '@codebelt/classy-store'; +import {reactiveMap} from '@codebelt/classy-store/collections'; + +class UserStore { + users = reactiveMap(); + + get count() { + return this.users.size; + } + + addUser(id: string, name: string) { + this.users.set(id, {name, role: 'viewer'}); + } + + removeUser(id: string) { + this.users.delete(id); + } +} + +export const userStore = createClassyStore(new UserStore()); +``` + +`ReactiveMap` and `ReactiveSet` mirror the native API (`get`, `set`, `has`, `delete`, `clear`, `forEach`, iteration) but are backed by plain arrays so the proxy can track mutations. + +> **When to use:** Whenever you'd reach for `Map` or `Set` inside a store. You *can* use native `Map`/`Set`, but only the reference is reactive — mutations like `.set()` or `.add()` won't trigger updates. You'd have to replace the entire Map/Set (e.g., `this.users = new Map(this.users)`) to notify subscribers. `reactiveMap()` and `reactiveSet()` avoid that by making individual operations reactive. + +## Class Inheritance + +Subclasses work out of the box — no special API or configuration needed. Methods, getters, and properties from all inheritance levels are fully reactive. + +```ts +import {createClassyStore} from '@codebelt/classy-store'; + +class BaseStore { + loading = false; + error: string | null = null; + + get hasError() { + return this.error !== null; + } + + setLoading(value: boolean) { + this.loading = value; + } + + setError(message: string | null) { + this.error = message; + } +} + +class UserStore extends BaseStore { + users: { id: string; name: string }[] = []; + + get activeCount() { + return this.users.length; + } + + async fetchUsers() { + this.setLoading(true); // base method — reactive + this.setError(null); + try { + const res = await fetch('/api/users'); + this.users = await res.json(); + } catch (error) { + this.setError(error instanceof Error ? error.message : 'Failed'); + } finally { + this.setLoading(false); + } + } +} + +export const userStore = createClassyStore(new UserStore()); +``` + +How it works: + +- **Methods** from any ancestor class are found via the prototype chain and bound to the proxy. `this.setLoading(true)` inside `fetchUsers` goes through the SET trap just like a method defined on `UserStore` itself. +- **Getters** from all levels are automatically memoized. If `UserStore` overrides a base getter, the most-derived version wins. Non-overridden base getters work as-is. +- **`super` calls** work correctly — `super.setLoading(true)` dispatches through the prototype chain with `this` still bound to the proxy, so mutations are reactive. +- **`snapshot()`** preserves the full prototype chain, so `snapshot(userStore) instanceof UserStore` and `instanceof BaseStore` are both `true`. + +> **When to use:** When multiple stores share common state or behavior. Extract the shared parts into a base class and extend it. The store proxy handles the rest. + +## Async Patterns + +Async methods work naturally. React is notified as each field changes, and pending asynchronous work lets it commit the loading state before the result arrives. + +```ts +class PostStore { + posts: { id: number; title: string }[] = []; + loading = false; + error: string | null = null; + + async fetchPosts() { + this.loading = true; // notify React that loading started + this.error = null; + try { + const res = await fetch('/api/posts'); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + this.posts = await res.json(); // notify React that data arrived + } catch (error) { + this.error = error instanceof Error ? error.message : 'Unknown error'; + } finally { + this.loading = false; // notify React that loading ended + } + } +} +``` + +A component selecting `loading` can show a spinner while the request is pending and remove it when the request finishes. React controls the exact render scheduling. + +## Local Stores + +By default, stores are module-level singletons — shared across your entire app. For component-scoped state that is garbage collected on unmount, use `useLocalStore`. + +### Basic usage + +`useLocalStore` creates a reactive store scoped to the component's lifetime. Each component instance gets its own isolated store. When the component unmounts, the store is garbage collected. + +```tsx +import {useLocalStore, useClassyStore} from '@codebelt/classy-store/react'; + +class CounterStore { + count = 0; + get doubled() { return this.count * 2; } + increment() { this.count++; } +} + +function Counter() { + const store = useLocalStore(() => new CounterStore()); + const count = useClassyStore(store, (state) => state.count); + + return ; +} +``` + +The factory initializes component state. Subsequent re-renders reuse the same store instance; keep the initializer pure because React StrictMode can invoke it more than once in development. + +### Persisting a local store + +`persist()` subscribes to the store, which keeps a reference alive. You must call `handle.unsubscribe()` on unmount to allow garbage collection. + +```tsx +import {useEffect} from 'react'; +import {useLocalStore, useClassyStore} from '@codebelt/classy-store/react'; +import {persist} from '@codebelt/classy-store/utils'; + +class FormStore { + name = ''; + email = ''; + setName(v: string) { this.name = v; } + setEmail(v: string) { this.email = v; } +} + +function EditProfile() { + const store = useLocalStore(() => new FormStore()); + // Auto-tracked mode — this component reads both name and email (see Decision guide). + const snap = useClassyStore(store); + + useEffect(() => { + const handle = persist(store, { name: 'edit-profile-draft' }); + return () => handle.unsubscribe(); + }, [store]); + + return ( +
+ store.setName(e.target.value)} /> + store.setEmail(e.target.value)} /> +
+ ); +} +``` + +The `useEffect` cleanup ensures the persist subscription is removed and the store can be garbage collected when the component unmounts. + +## Creating a Bound Hook with `createStoreHook` + +When you have a module-level store, you typically also export a typed hook for it: + +```ts +// Before — manual boilerplate for each store +import {createClassyStore} from '@codebelt/classy-store'; +import {useClassyStore} from '@codebelt/classy-store/react'; +import type {Snapshot} from '@codebelt/classy-store'; + +export const catalogStore = createClassyStore(new CatalogPageStore()); + +export function useCatalogStore( + selector: (snap: Snapshot) => S, +): S { + return useClassyStore(catalogStore, selector); +} +``` + +`createStoreHook` does this in one line: + +```ts +// After — one-liner +import {createClassyStore} from '@codebelt/classy-store'; +import {createStoreHook} from '@codebelt/classy-store/react'; + +export const catalogStore = createClassyStore(new CatalogPageStore()); +export const useCatalogStore = createStoreHook(catalogStore); +``` + +The returned hook supports both selector and auto-tracked modes, plus custom `isEqual` — identical to `useClassyStore`, but with the store already bound: + +```tsx +// Selector mode +const count = useCatalogStore((state) => state.count); + +// Auto-tracked mode +const snap = useCatalogStore(); + +// Custom equality +const data = useCatalogStore( + (state) => ({count: state.count, loading: state.loading}), + {isEqual: shallowEqual}, +); +``` + +`createStoreHook` validates its argument immediately — if you pass something that isn't a store proxy, it throws at module load time rather than on first render. + +## Tips & Gotchas + +### Mutate through methods, not from components + +While you *can* write `counterStore.count = 5` directly from a component, you shouldn't. Centralizing mutations in store methods keeps your logic in one place and makes it easier to find, refactor, and test. + +```ts +// ✅ Good — mutation logic lives in the store +counterStore.increment(); + +// ❌ Avoid — mutation scattered across components +counterStore.count++; +``` + +When every state change goes through a method, you can search for callers of that method to understand how your state changes. When state is set directly from dozens of components, tracking down bugs becomes much harder. + +### Don't use arrow functions for methods + +Arrow function properties (`handler = () => {}`) are own properties whose `this` is lexically bound to the raw class instance at construction time. `.bind()` cannot override an arrow function's `this`, so when the proxy's GET trap binds it to the proxy receiver, the bind is silently ignored. Mutations inside the arrow function bypass the SET trap entirely — no notifications, no reactivity. + +```ts +class Store { + count = 0; + + // ❌ Arrow function — `this` is the raw object, mutations are invisible + increment = () => { + this.count++; // bypasses the proxy SET trap + }; + + // ✅ Prototype method — `this` is bound to the proxy by the GET trap + increment() { + this.count++; // goes through the proxy SET trap → reactive + } +} +``` + +### Don't destructure the store outside `useClassyStore` + +Destructuring copies the primitive value at that moment and breaks the proxy connection. + +```ts +// ❌ Breaks reactivity — `count` is just the number 0 +const {count} = counterStore; + +// ✅ Always access through the proxy +counterStore.count; +``` + +### Non-proxyable types + +Native `Date`, `RegExp`, `Map`, and `Set` use internal slots that proxies can't intercept. For `Map` and `Set`, use `reactiveMap()` and `reactiveSet()`. For `Date`, store timestamps as numbers and construct `Date` objects in getters or at render time. + +### Set-then-revert and loop updates + +React checks subscriptions after each write. Setting a field and reverting it in one event can still schedule rendering, even though the final displayed value is unchanged. Neither selector mode nor auto-tracked mode promises to avoid this work. + +Likewise, a loop with a thousand writes sends a thousand synchronous React notifications. React can batch their renders within an event handler, but selectors may still run on each mutation. Ordinary `subscribe()` callbacks receive one microtask notification for that loop. For large updates, calculate the final value first when intermediate writes serve no purpose. + +### Auto-tracked references + +Each render gets its own read set. This keeps a suspended or abandoned render from changing the committed component's dependencies, and allows conditional reads to start from current store state. A shared proxy cache preserves unchanged tracked references across parent re-renders. Raw `snapshot(store)` caching and structural sharing are unchanged. + +### Snapshot caching + +Calling `snapshot()` multiple times without intervening mutations returns the same frozen object — an O(1) cache hit. There's no cost to calling it liberally. + +```ts +import {snapshot} from '@codebelt/classy-store'; + +const a = snapshot(counterStore); +const b = snapshot(counterStore); +a === b; // true — same cached object + +counterStore.increment(); + +const c = snapshot(counterStore); +a === c; // false — new snapshot after mutation +``` + +## Persistence + +`persist()` saves your store's state to `localStorage` (or any storage adapter) and restores it on page load. It's a standalone utility function — import it from `@codebelt/classy-store/utils`. + +```ts +import {persist} from '@codebelt/classy-store/utils'; + +const handle = persist(todoStore, { + name: 'todo-store', +}); + +// State survives page refreshes. +// Getters and methods are automatically excluded. +// Cross-tab sync is enabled by default. +``` + +The persist utility supports per-property transforms (for `Date`, `ReactiveMap`, etc.), debounced writes, schema versioning with migration, merge strategies, TTL expiration (`expireIn`), SSR-safe `skipHydration`, and cross-tab synchronization. + +For the full walkthrough, see the **[Persist Tutorial](./PERSIST_TUTORIAL.md)**. diff --git a/website-fumadocs/content/docs/index.md b/website-fumadocs/content/docs/index.md new file mode 100644 index 0000000..0d35a77 --- /dev/null +++ b/website-fumadocs/content/docs/index.md @@ -0,0 +1,683 @@ +--- +title: "Classy Store" +description: "Class-based reactive state management for React, Vue, Svelte, Solid, and Angular." +--- + +Class-based reactive state management for React, Vue, Svelte, Solid, and Angular. Write plain TypeScript classes — get fine-grained reactivity, immutable snapshots, and zero-boilerplate hooks. + +``` +~2.3 KB gzipped · ES6 Proxy · useSyncExternalStore · proxy-compare +``` + +## Features + +- **Class-based stores** — plain classes with fields, methods, and getters +- **No wrappers** — no `observer()`, no `Provider`, no HOCs, no decorators +- **Fine-grained reactivity** — components only re-render when properties they read change +- **Immutable snapshots** — structural sharing keeps unchanged sub-trees reference-equal +- **Memoized computed values** — class getters are automatically cached, recompute only when deps change +- **Batched subscriptions** — ordinary listeners coalesce mutations in a microtask; React receives updates synchronously +- **Type-safe** — full TypeScript inference from your class definitions +- **Framework bindings** — first-class integrations for React, Vue, Svelte, Solid, and Angular +- **Two hook modes** — explicit selector or automatic property tracking (React) +- **Reactive collections** — `reactiveMap()` and `reactiveSet()` for Map/Set-like state +- **Persistence** — `persist()` utility with transforms, versioning, migration, debounce, cross-tab sync, TTL expiration, and SSR support +- **DevTools** — `devtools()` connects to Redux DevTools for state inspection and time-travel debugging +- **Property subscriptions** — `subscribeKey()` watches a single property for changes with previous/current values +- **Undo/Redo** — `withHistory()` adds undo/redo via a snapshot stack with pause/resume and configurable limits + +## Installation + +```bash +bun add @codebelt/classy-store +``` + +All framework peer dependencies are optional — install only what your project uses: + +| Framework | Peer Dependency | Import | +|-----------|----------------|--------| +| React | `react >= 18.0.0` | `@codebelt/classy-store/react` | +| Vue | `vue >= 3.0.0` | `@codebelt/classy-store/vue` | +| Svelte | `svelte >= 4.0.0` | `@codebelt/classy-store/svelte` | +| Solid | `solid-js >= 1.0.0` | `@codebelt/classy-store/solid` | +| Angular | `@angular/core >= 17.0.0` | `@codebelt/classy-store/angular` | + +## Quick Start + +### 1. Define a store class + +```typescript +class TodoStore { + todos: Todo[] = []; + filter: 'all' | 'done' | 'pending' = 'all'; + + // Getter = computed value + get filtered() { + if (this.filter === 'all') return this.todos; + return this.todos.filter((todo) => + this.filter === 'done' ? todo.done : !todo.done, + ); + } + + get remaining() { + return this.todos.filter((todo) => !todo.done).length; + } + + addTodo(text: string) { + this.todos.push({text, done: false}); + } + + toggle(index: number) { + this.todos[index]!.done = !this.todos[index]!.done; + } +} +``` + +### 2. Create a reactive store + +```typescript +import {createClassyStore} from '@codebelt/classy-store'; + +const todoStore = createClassyStore(new TodoStore()); +``` + +### 3. Use in React components + +```tsx +import {useClassyStore} from '@codebelt/classy-store/react'; + +// Selector mode: explicit control over what triggers re-renders +function TodoCount() { + const remaining = useClassyStore(todoStore, (state) => state.remaining); + return {remaining} left; +} + +// Auto-tracked mode: reads are tracked automatically +function TodoList() { + const snap = useClassyStore(todoStore); + return ( +
    + {snap.filtered.map((todo, index) => ( +
  • {todo.text}
  • + ))} +
+ ); +} + +// Actions: call methods directly on the store +function AddButton() { + return ; +} +``` + +## API Reference + +### `createClassyStore(instance)` + +Wraps a class instance in a reactive Proxy. Mutations notify React immediately; ordinary subscribers are batched via `queueMicrotask`. + +```typescript +const myStore = createClassyStore(new MyClass()); +``` + +- **Methods** are automatically bound so `this` mutations go through the proxy +- **Getters** are automatically memoized — they only recompute when a dependency changes (like MobX `@computed`) +- **Nested objects/arrays** are lazily deep-proxied on first access + +### `useClassyStore(store, selector?, options?)` + +React hook that subscribes to store changes via `useSyncExternalStore`. + +```typescript +type UseClassyStoreSelectorOptions = { + isEqual?: (previous: S, next: S) => boolean; +}; +``` + +**Selector mode:** + +```typescript +const count = useClassyStore(myStore, (state) => state.count); +const user = useClassyStore(myStore, (state) => state.user); +const items = useClassyStore(myStore, (state) => state.items); +``` + +The selector receives an immutable snapshot. Re-renders only when the selected value changes via `Object.is` by default, or `options.isEqual`. + +**Auto-tracked mode:** + +```typescript +const snap = useClassyStore(myStore); +// Access snap.count, snap.user.name, etc. +// Only re-renders when accessed properties change +``` + +Returns a tracking proxy. Properties your component reads are automatically tracked. Committed reads remain associated with each reused immutable snapshot object, so memoized calculations and children stay reactive even when they skip property reads on a later render. Abandoned renders do not publish their reads. Unchanged snapshots and nested tracked values retain their references across parent re-renders. + +**Custom equality:** + +```typescript +import {shallowEqual} from '@codebelt/classy-store'; +import {useClassyStore} from '@codebelt/classy-store/react'; + +const userData = useClassyStore(myStore, (state) => ({ + name: state.user.name, + role: state.user.role, +}), {isEqual: shallowEqual}); +``` + +**Controlled input timing:** + +```tsx +const name = useClassyStore(formStore, (state) => state.name); + +return ( + formStore.setName(event.target.value)} + /> +); +``` + +React subscriptions always notify synchronously, including selector, auto-tracked, and bound hooks. There is no React `sync` option. This lets React observe controlled-field changes during the input event and preserve caret position. Ordinary `subscribe()` callbacks remain batched. + +Migration: remove `{sync: true}` or `{sync: false}` from React hook calls and remove imports of `UseClassyStoreOptions`. Selector options still support `isEqual`. Synchronous notification does not make a method transactional: selectors can observe each write, while React batches rendering within event handlers. + +### `prepareStoreForHydration(store)` + +Captures the initial snapshot for server rendering and client hydration, returning the same mutable store proxy: + +```ts +import {createClassyStore} from '@codebelt/classy-store'; +import {prepareStoreForHydration} from '@codebelt/classy-store/react'; + +const store = prepareStoreForHydration( + createClassyStore(new ProfileStore(initialData)), +); +``` + +Call on each request's store and its client counterpart using matching initial data, before rendering or starting persistence. Direct and bound hooks hydrate from the captured snapshot even if persistence updates the live store before a Suspense boundary hydrates. After hydration they read current state normally. + +Preparation is idempotent, retains the first snapshot for the store's lifetime, and applies to the exact proxy passed in. Client-only apps need no preparation. See [React SSR and hydration](./REACT_SSR.md) for streaming, request isolation, nested proxies, and snapshot leaf semantics. + +### `useLocalStore(factory)` + +Creates a component-scoped reactive store. Each component instance gets its own isolated store, garbage collected on unmount. + +```tsx +import {useLocalStore, useClassyStore} from '@codebelt/classy-store/react'; + +class CounterStore { + count = 0; + increment() { this.count++; } +} + +function Counter() { + const store = useLocalStore(() => new CounterStore()); + const count = useClassyStore(store, (state) => state.count); + + return ; +} +``` + +Subsequent re-renders reuse the same store instance. Keep the factory pure: React StrictMode can invoke the initializer more than once in development. + +> See the [Local Stores](./TUTORIAL.md#local-stores) section in the Tutorial for persistence patterns and more examples. + +### `createStoreHook(store)` + +Creates a pre-bound React hook for a specific store proxy. Eliminates the boilerplate of writing a typed wrapper around `useClassyStore` for every store instance. + +```tsx +import {createClassyStore} from '@codebelt/classy-store'; +import {createStoreHook} from '@codebelt/classy-store/react'; + +class CatalogPageStore { + items: string[] = []; + loading = false; + + get count() { return this.items.length; } + + addItem(item: string) { this.items.push(item); } +} + +const catalogStore = createClassyStore(new CatalogPageStore()); +export const useCatalogStore = createStoreHook(catalogStore); +``` + +The returned hook supports both selector mode and auto-tracked mode — identical to `useClassyStore`, but with the store already bound: + +```tsx +// Selector mode +function ItemCount() { + const count = useCatalogStore((state) => state.count); + return {count} items; +} + +// Auto-tracked mode +function ItemList() { + const snap = useCatalogStore(); + return
    {snap.items.map((item) =>
  • {item}
  • )}
; +} + +// Custom equality +import {shallowEqual} from '@codebelt/classy-store'; + +function Summary() { + const data = useCatalogStore( + (state) => ({count: state.count, loading: state.loading}), + {isEqual: shallowEqual}, + ); + return
{data.loading ? 'Loading...' : `${data.count} items`}
; +} +``` + +Validates at creation time — throws if the argument is not a store proxy. + +### `snapshot(store)` + +Creates a deeply frozen, immutable snapshot of the current state. Used internally by `useClassyStore` but also available directly. + +```typescript +import {snapshot} from '@codebelt/classy-store'; + +const snap = snapshot(myStore); +// snap is deeply frozen — mutations throw +// Structural sharing: unchanged sub-trees === previous snapshot +``` + +### `subscribe(store, callback, options?)` + +Low-level subscription API. Returns an unsubscribe function. By default, the callback fires once per batched mutation after the microtask flush. Pass `{sync: true}` to notify immediately on each mutation. + +```typescript +import {subscribe} from '@codebelt/classy-store'; + +const unsub = subscribe(myStore, () => { + console.log('Store changed'); +}); + +const unsubSync = subscribe( + myStore, + () => { + console.log('Store changed synchronously'); + }, + {sync: true}, +); + +// Later: unsub(); +``` + +### `getVersion(store)` + +Returns the current version number of a store proxy. Versions are monotonically increasing and bump on any mutation in the store's subtree (child mutations propagate up to the root). Useful for debugging, testing whether a store has changed, or custom cache invalidation. + +```typescript +import {getVersion} from '@codebelt/classy-store'; + +const v1 = getVersion(myStore); +myStore.count++; +// After microtask: +const v2 = getVersion(myStore); +// v2 > v1 +``` + +### `shallowEqual(a, b)` + +Shallow equality comparison for objects and arrays. Useful as a custom `isEqual` for selectors that return derived objects. + +### `reactiveMap(initial?)` + +Creates a reactive Map-like collection backed by a plain array. Use inside a `createClassyStore()` for full reactivity. Import from `@codebelt/classy-store/collections`. + +```typescript +import {createClassyStore} from '@codebelt/classy-store'; +import {reactiveMap} from '@codebelt/classy-store/collections'; +import {useClassyStore} from '@codebelt/classy-store/react'; + +class UserStore { + users = reactiveMap(); + + addUser(id: string, name: string, role: string) { + this.users.set(id, {name, role}); + } + + removeUser(id: string) { + this.users.delete(id); + } +} + +const userStore = createClassyStore(new UserStore()); + +function UserList() { + const snap = useClassyStore(userStore, (state) => [...state.users.entries()]); + return ( +
    + {snap.map(([id, user]) => ( +
  • {user.name} ({user.role})
  • + ))} +
+ ); +} +``` + +Supports: `.get()`, `.set()`, `.has()`, `.delete()`, `.clear()`, `.size`, `.keys()`, `.values()`, `.entries()`, `.forEach()`, `for...of`. + +### `reactiveSet(initial?)` + +Creates a reactive Set-like collection backed by a plain array. Use inside a `createClassyStore()` for full reactivity. Import from `@codebelt/classy-store/collections`. + +```typescript +import {createClassyStore} from '@codebelt/classy-store'; +import {reactiveSet} from '@codebelt/classy-store/collections'; +import {useClassyStore} from '@codebelt/classy-store/react'; + +class TagStore { + tags = reactiveSet(); + + addTag(tag: string) { + this.tags.add(tag); + } +} + +const tagStore = createClassyStore(new TagStore()); + +function TagList() { + const tags = useClassyStore(tagStore, (state) => [...state.tags]); + return tags.map(tag => {tag}); +} +``` + +Supports: `.add()`, `.has()`, `.delete()`, `.clear()`, `.size`, `.keys()`, `.values()`, `.entries()`, `.forEach()`, `for...of`. + +> **Note:** `reactiveMap()` and `reactiveSet()` are not real `Map`/`Set` instances — they emulate the API on top of plain arrays so the store proxy can track mutations. `instanceof Map` / `instanceof Set` will return `false`. + +### `Snapshot` + +TypeScript utility type that converts a store type to its deeply readonly snapshot equivalent. + +```typescript +import type {Snapshot} from '@codebelt/classy-store'; + +type MyStoreSnap = Snapshot; +// All properties are readonly, arrays become ReadonlyArray, etc. +``` + +## Collections (`@codebelt/classy-store/collections`) + +Reactive Map and Set types are available via a separate entry point: + +```typescript +import {reactiveMap, reactiveSet} from '@codebelt/classy-store/collections'; +``` + +## Utilities (`@codebelt/classy-store/utils`) + +Tree-shakeable utilities are available via a separate entry point: + +```typescript +import {persist, devtools, subscribeKey, withHistory} from '@codebelt/classy-store/utils'; +``` + +### `persist(store, options)` + +Persist store state to storage with transforms, versioning, migration, cross-tab sync, and SSR support. Getters and methods are automatically excluded. + +```typescript +import {persist} from '@codebelt/classy-store/utils'; + +const handle = persist(todoStore, {name: 'todo-store'}); +``` + +> See the [Persist Tutorial](./PERSIST_TUTORIAL.md) and [Persist Architecture](./PERSIST_ARCHITECTURE.md). + +### `devtools(store, options?)` + +Connect a store to Redux DevTools for state inspection and time-travel debugging. + +```typescript +import {devtools} from '@codebelt/classy-store/utils'; + +const disconnect = devtools(myStore, {name: 'MyStore'}); +``` + +> See the [DevTools Tutorial](./DEVTOOLS_TUTORIAL.md). + +### `subscribeKey(store, key, callback)` + +Subscribe to changes on a single property. Fires only when the watched key changes. + +```typescript +import {subscribeKey} from '@codebelt/classy-store/utils'; + +const unsub = subscribeKey(store, 'theme', (value, prev) => { + console.log(`Theme: ${prev} → ${value}`); +}); +``` + +> See the [subscribeKey Tutorial](./SUBSCRIBE_KEY_TUTORIAL.md). + +### `withHistory(store, options?)` + +Add undo/redo capability via a snapshot stack with pause/resume and configurable limits. + +```typescript +import {withHistory} from '@codebelt/classy-store/utils'; + +const history = withHistory(store, {limit: 100}); +history.undo(); +history.redo(); +``` + +> See the [withHistory Tutorial](./HISTORY_TUTORIAL.md). + +## Patterns + +### Multiple stores + +```typescript +const authStore = createClassyStore(new AuthStore()); +const uiStore = createClassyStore(new UiStore()); + +function Header() { + const user = useClassyStore(authStore, (state) => state.currentUser); + const theme = useClassyStore(uiStore, (state) => state.theme); + return
{user?.name}
; +} +``` + +### Class inheritance + +Subclasses work out of the box. Methods, getters, and `super` calls from any ancestor are fully reactive: + +```typescript +class BaseStore { + loading = false; + error: string | null = null; + + get hasError() { return this.error !== null; } + setLoading(value: boolean) { this.loading = value; } + setError(msg: string | null) { this.error = msg; } +} + +class UserStore extends BaseStore { + users: string[] = []; + + get count() { return this.users.length; } + + addUser(name: string) { + this.users.push(name); + } +} + +const userStore = createClassyStore(new UserStore()); + +// Base methods, derived methods, base getters, derived getters — all reactive. +// snapshot(userStore) instanceof UserStore === true +``` + +### Nested object mutations + +Nested objects are deeply reactive. Mutations at any depth trigger the correct re-renders: + +```typescript +class SettingsStore { + settings = { + theme: 'dark', + notifications: { + email: true, + push: false, + }, + }; + + togglePush() { + this.settings.notifications.push = !this.settings.notifications.push; + } +} + +const settingsStore = createClassyStore(new SettingsStore()); + +// Only re-renders when push notification setting changes +function PushToggle() { + const push = useClassyStore(settingsStore, (state) => state.settings.notifications.push); + return settingsStore.togglePush()} />; +} +``` + +### Array operations + +Arrays support all standard operations — `push`, `splice`, `pop`, `shift`, index assignment, etc. Ordinary subscribers receive one microtask notification for consecutive mutations: + +```typescript +class ListStore { + items: string[] = []; + + addMany(newItems: string[]) { + // Multiple pushes = one ordinary subscription notification + for (const item of newItems) { + this.items.push(item); + } + } + + removeAt(index: number) { + this.items.splice(index, 1); + } +} +``` + +### Computed getters (automatic memoization) + +Class getters are automatically memoized at two levels — no `computed()` wrapper needed: + +**Write proxy:** getters track which properties they read. The result is cached and only recomputes when a dependency changes: + +```typescript +class Store { + items = ['a', 'b', 'c']; + filter = 'all'; + + get filtered() { + // Only runs when `items` or `filter` changes. + // Accessing store.filtered multiple times returns the same reference. + if (this.filter === 'all') return this.items; + return this.items.filter(item => item === this.filter); + } + + get filteredCount() { + // Nested getters work: reads this.filtered (itself memoized) + return this.filtered.length; + } +} +``` + +**Snapshots:** getter results are stable across snapshots when dependencies haven't changed. This means selectors that return computed values work with `Object.is` by default — no `shallowEqual` needed: + +```typescript +// Stable reference across re-renders when items/filter haven't changed. +// No shallowEqual required! +const filtered = useClassyStore(myStore, (state) => state.filtered); +``` + +### Working with Date and RegExp + +`Date` and `RegExp` are **not proxied** — they are treated as opaque values. Replace them entirely to trigger an update: + +```typescript +class Store { + date = new Date(); + regex = /test/; + + updateDate() { + // ❌ Mutation: won't trigger update + // this.date.setFullYear(2025); + + // ✅ Replacement: triggers update + this.date = new Date(); + } +} +``` + +For `Map` and `Set` semantics, use [`reactiveMap()`](#reactivemapk-vinitial) and [`reactiveSet()`](#reactivesettinitial) instead of native `Map`/`Set`. + +## React Compiler compatibility + +Direct and bound hooks work in compiled consumers with either selectors or auto-tracking. The regression matrix compiles consumer components with React Compiler 1.0 using default inference, then runs them against the packed library on React 18.0, 18.3, and 19.3. Fixtures must report successful compilation with memo slots; silently skipped components fail the checks. The library does not require a `"use no memo"` directive or custom compiler configuration. + +Auto-tracking retains earlier committed reads while an immutable snapshot object is reused, including structurally shared children. This protects dependencies inside cached helpers, array calculations, and memoized children. After a conditional branch changes, an earlier read can still trigger an extra render until that snapshot object's identity changes. Use an explicit selector when exact selected-value equality matters. + +Read render data through `useClassyStore` or a bound hook. The original store proxy remains mutable and is intended for actions; reading it directly during render does not subscribe the component. Configure the compiler target for your React version; React 18 apps need the compiler runtime described in [React's target documentation](https://react.dev/reference/react-compiler/target). + +Run `bun run test:react:compiler` for the installed-package matrix and `bun run test:browser` for compiled controlled inputs and memoized children in Chromium, Firefox, and WebKit. + +## When to use each mode + +| Mode | Best for | How it works | +|------|----------|--------------| +| `useClassyStore(store, selector)` | Derived values, primitives, specific slices | Selector runs on snapshot, compared with `Object.is` | +| `useClassyStore(store)` | Components reading many props, rapid prototyping | `proxy-compare` tracks reads automatically | +| `useClassyStore(store, selector, {isEqual: shallowEqual})` | Selectors returning new objects/arrays | Shallow comparison prevents unnecessary re-renders | +| `useClassyStore(store, selector)` | Controlled form fields | Always notifies React during the input event turn | + +## Comparison with other libraries + +| Feature | Classy Store | Zustand | MobX | Valtio | +|---------|-----------------|---------|------|--------| +| Class-based stores | Yes | No | Yes | No | +| No observer/Provider | Yes | Yes | No | Yes | +| Auto-tracking | Yes | No | Yes (observer) | Yes | +| Selector mode | Yes | Yes | No | Manual | +| Memoized computed | Yes (auto-memoized) | Manual | Yes (computed) | No | +| Immutable snapshots | Yes | No | No | Yes | +| Structural sharing | Yes | N/A | N/A | Yes | +| Built-in persistence | Yes (per-property transforms, versioning, cross-tab sync) | Yes (middleware) | No (separate pkg) | No (manual) | +| DevTools integration | Yes (`devtools()`) | Yes (middleware) | Yes (separate pkg) | Yes (`devtools()`) | +| Undo/Redo | Yes (`withHistory()`) | No (manual) | No (manual) | No (manual) | +| Bundle size | ~2.3 KB gzip | ~1.2KB | ~16KB | ~3KB | + +## Vision + +I wanted state management that feels like writing plain TypeScript. + +- **A class is the store.** Define fields, methods, and getters — that's your state, your actions, and your derived values. The class *is* the type. TypeScript infers everything automatically. +- **Getters are computed values.** Write `get filtered()` and it's memoized with dependency tracking out of the box. It caches until a dependency changes. +- **Framework bindings that feel native.** React's `useClassyStore`, Vue's `useClassyStore` (ShallowRef), Svelte's `toSvelteStore`, Solid's `useClassyStore` (signal getter), Angular's `injectStore` — each integration matches the framework's reactive idioms. +- **Call methods directly.** `todoStore.addTodo('Buy milk')` — a real object with real methods, callable from anywhere. +- **Observe only what matters.** Components re-render when the specific properties they read change. + +Clean, simple, and type-safe. That was the whole idea. + +## Acknowledgements + +This library wouldn't exist without the ideas pioneered by these projects. Each one taught us something different, and we took the best of each: + +**[MobX](https://github.com/mobxjs/mobx)** — The OG of class-based reactive state. MobX proved that classes with fields, methods, and getters are the most natural way to model state. We took its `makeAutoObservable` philosophy — everything is reactive by default, no decorators or boilerplate — and its automatic computed memoization with fine-grained dependency tracking. MobX showed that getters should "just work" as cached derived values. + +**[Valtio](https://github.com/pmndrs/valtio)** — Daishi Kato's proxy-based masterpiece gave us the core architectural pattern: a mutable write proxy for ergonomic mutations paired with immutable snapshots for React integration. Valtio's structural sharing approach — where unchanged sub-trees keep the same frozen reference across snapshots — is what makes `Object.is` selectors efficient without custom equality. We also adopted its `proxy-compare` library for automatic property tracking in selectorless mode. + +**[Zustand](https://github.com/pmndrs/zustand)** — Also by Daishi Kato, Zustand set the standard for minimal, hook-first state management. Its selector pattern (`useClassyStore(store, s => s.count)`) with `Object.is` equality is what we use in selector mode. Zustand proved that you don't need Providers, context wrappers, or HOCs — just a hook and a store. Its focus on tiny bundle size pushed us to keep things lean. + +**[proxy-compare](https://github.com/dai-shi/proxy-compare)** — The ~1KB utility (also by Dai-shi) that powers our auto-tracked mode. It wraps frozen snapshot objects in a tracking proxy, recording which properties a component reads, then efficiently diffs only those properties between snapshots. This eliminates the need for manual selectors in most cases. + +**[React](https://react.dev)** — React 18's `useSyncExternalStore` is the foundation of our hook layer. It provides tear-free concurrent-mode-safe integration with external stores, and it's the same API used by Zustand, Redux, and Valtio under the hood. + +**[Claude 4.6 Opus](https://anthropic.com)** — Let's be real: this library was designed, architected, implemented, tested, and documented almost entirely by Claude 4.6 Opus (Anthropic) via [Cursor](https://cursor.com). From the three-layer proxy architecture to the memoized computed getters with dependency tracking, the cross-snapshot caching strategy, and tests — it was all pair-programmed with an AI that never gets tired of writing Proxy traps. The human brought the vision, the taste, and the "no, make it better" energy. Claude brought the code. diff --git a/website-fumadocs/content/docs/meta.json b/website-fumadocs/content/docs/meta.json new file mode 100644 index 0000000..c1fbeb5 --- /dev/null +++ b/website-fumadocs/content/docs/meta.json @@ -0,0 +1,18 @@ +{ + "title": "Classy Store", + "pages": [ + "index", + "---Learn---", + "TUTORIAL", + "REACT_SSR", + "REACTIVE_COLLECTIONS_TUTORIAL", + "---Utilities---", + "PERSIST_TUTORIAL", + "DEVTOOLS_TUTORIAL", + "SUBSCRIBE_KEY_TUTORIAL", + "HISTORY_TUTORIAL", + "---Architecture---", + "ARCHITECTURE", + "PERSIST_ARCHITECTURE" + ] +} diff --git a/website-fumadocs/lib/cn.ts b/website-fumadocs/lib/cn.ts new file mode 100644 index 0000000..fea7194 --- /dev/null +++ b/website-fumadocs/lib/cn.ts @@ -0,0 +1 @@ +export {cn} from 'cn'; diff --git a/website-fumadocs/lib/layout.shared.tsx b/website-fumadocs/lib/layout.shared.tsx new file mode 100644 index 0000000..a59ddf9 --- /dev/null +++ b/website-fumadocs/lib/layout.shared.tsx @@ -0,0 +1,15 @@ +import type {BaseLayoutProps} from 'fumadocs-ui/layouts/shared'; +import {appName, gitConfig} from './shared'; + +export function baseOptions(): BaseLayoutProps { + return { + nav: { + title: appName, + }, + links: [ + {text: 'Docs', url: '/docs', on: 'nav'}, + {text: 'Examples', url: '/demos/index.html', on: 'nav'}, + ], + githubUrl: `https://github.com/${gitConfig.user}/${gitConfig.repo}`, + }; +} diff --git a/website-fumadocs/lib/shared.ts b/website-fumadocs/lib/shared.ts new file mode 100644 index 0000000..4c1e356 --- /dev/null +++ b/website-fumadocs/lib/shared.ts @@ -0,0 +1,38 @@ +import {createGetUrl} from 'fumadocs-core/source'; + +export const appName = 'Classy Store'; +export const docsRoute = '/docs'; +export const docsImageRoute = '/og/docs'; +export const docsContentRoute = '/llms.mdx/docs'; + +export const gitConfig = { + user: 'codebelt', + repo: 'classy-store', + branch: 'main', +}; + +export function withDocsBasePath(markdown: string) { + const basePath = process.env.DOCS_BASE_PATH; + if (!basePath) return markdown; + + return markdown.replace( + /([[(])\/docs(?=[/)#\s]|$)/g, + (_, prefix: string) => `${prefix}${basePath}/docs`, + ); +} + +const getContentUrl = createGetUrl(docsContentRoute); + +export function getPageMarkdownUrl(page: {slugs: string[]; locale?: string}) { + const segments = [...page.slugs, 'content.md']; + + return {segments, url: getContentUrl(segments, page.locale)}; +} + +const getImageUrl = createGetUrl(docsImageRoute); + +export function getPageImageUrl(page: {slugs: string[]; locale?: string}) { + const segments = [...page.slugs, 'image.png']; + + return {segments, url: getImageUrl(segments, page.locale)}; +} diff --git a/website-fumadocs/lib/source.ts b/website-fumadocs/lib/source.ts new file mode 100644 index 0000000..5845d8f --- /dev/null +++ b/website-fumadocs/lib/source.ts @@ -0,0 +1,30 @@ +import {llms, loader} from 'fumadocs-core/source'; +import {metaSchema, pageSchema} from 'fumadocs-core/source/schema'; +import {defineDocs} from 'fumadocs-mdx/macro'; +import {docsRoute} from './shared'; + +const docs = defineDocs({ + dir: 'content/docs', + docs: { + schema: pageSchema, + postprocess: { + includeProcessedMarkdown: true, + }, + }, + meta: { + schema: metaSchema, + }, +}); + +// See https://fumadocs.dev/docs/headless/source-api for more info +export const source = loader({ + baseUrl: docsRoute, + source: docs.toFumadocsSource(), + plugins: [], +}); + +export const docsLlms = llms(source, { + renderPage: async (page) => `# ${page.data.title} (${page.url}) + +${await page.data.getText('processed')}`, +}); diff --git a/website-fumadocs/next.config.mjs b/website-fumadocs/next.config.mjs new file mode 100644 index 0000000..1046c5e --- /dev/null +++ b/website-fumadocs/next.config.mjs @@ -0,0 +1,13 @@ +import {createMDX} from 'fumadocs-mdx/next'; + +const withMDX = createMDX(); + +/** @type {import('next').NextConfig} */ +const config = { + output: 'export', + basePath: process.env.DOCS_BASE_PATH || '', + trailingSlash: true, + reactStrictMode: true, +}; + +export default withMDX(config); diff --git a/website-fumadocs/package.json b/website-fumadocs/package.json new file mode 100644 index 0000000..3fbbcca --- /dev/null +++ b/website-fumadocs/package.json @@ -0,0 +1,35 @@ +{ + "name": "website-fumadocs", + "version": "0.0.50", + "private": true, + "scripts": { + "build:demos": "cd ../examples/react && bun run build.ts --outdir=../../website-fumadocs/public/demos", + "build": "bun run build:demos && next build --webpack", + "build:pages": "bun run build:demos && DOCS_BASE_PATH=/classy-store next build --webpack", + "dev": "bun run build:demos && next dev", + "start": "serve out", + "types:check": "next typegen && tsc --noEmit" + }, + "dependencies": { + "cn": "^0.5.0", + "fumadocs-core": "16.17.7", + "fumadocs-mdx": "15.4.9", + "fumadocs-ui": "npm:@fumadocs/base-ui@16.17.7", + "lucide-react": "^1.59.0", + "mermaid": "^12.2.0", + "next": "16.4.2", + "react": "^19.3.1", + "react-dom": "^19.3.1" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.3.3", + "@types/mdx": "^2.0.14", + "@types/node": "^26.6.5", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "postcss": "^8.5.31", + "serve": "^14.2.6", + "tailwindcss": "^4.3.3", + "typescript": "^7.0.2" + } +} diff --git a/website-fumadocs/postcss.config.mjs b/website-fumadocs/postcss.config.mjs new file mode 100644 index 0000000..297374d --- /dev/null +++ b/website-fumadocs/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; + +export default config; diff --git a/website-fumadocs/public/.nojekyll b/website-fumadocs/public/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/website-fumadocs/source.config.ts b/website-fumadocs/source.config.ts new file mode 100644 index 0000000..842c138 --- /dev/null +++ b/website-fumadocs/source.config.ts @@ -0,0 +1,8 @@ +import {remarkMdxMermaid} from 'fumadocs-core/mdx-plugins'; +import {defineConfig} from 'fumadocs-mdx/config'; + +export default defineConfig({ + mdxOptions: { + remarkPlugins: [remarkMdxMermaid], + }, +}); diff --git a/website-fumadocs/tsconfig.json b/website-fumadocs/tsconfig.json new file mode 100644 index 0000000..67086f3 --- /dev/null +++ b/website-fumadocs/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "paths": { + "@/*": ["./*"] + }, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": ["node_modules"] +}