diff --git a/.prettierignore b/.prettierignore index 1b4be7b..a6428c2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,6 +3,7 @@ dist/ .astro/ playwright-report/ test-results/ +pnpm-lock.yaml packages/hyperdb/dist/ packages/hyperdb-doc/dist/ diff --git a/AGENTS.md b/AGENTS.md index 2c74469..4ec2c0d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,9 @@ - + ## Skill Loading Before substantial work: + - Skill check: run `pnpm dlx @tanstack/intent@latest list`, or use skills already listed in context. - Skill guidance: if one local skill clearly matches the task, run `pnpm dlx @tanstack/intent@latest load #` and follow the returned `SKILL.md`. - Monorepos: when working across packages, run the skill check from the workspace root and prefer the local skill for the package being changed. @@ -10,5 +11,4 @@ Before substantial work: When you do any changes to packages/hyperdb or packages/hyperdb-devtool, -make sure that you covered it at packages/hyperdb-doc. - +make sure that you covered it at packages/hyperdb-doc and root README.md. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/TODO.md b/TODO.md index ec9af13..1e461d8 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,10 @@ Github: -1. Setup description, tags, auto-releaser +1. DONE Setup description, tags, auto-releaser -Codesandbox: +stackblitz: -1. Add demo +1. DONE Add demo Devtool: @@ -13,29 +13,30 @@ Devtool: Doc: -1. MIGRATE TO DOCUSAURUS -1. Fix npm install/doc links +1. Maybe reframe it and remove mention about sync nature? amybe even async by default? +1. review index.mdx, start/\*(execpt why), index.md, in-memory-persistence.md 1. Add performance compare doc -1. DONE Remove wa-sqlite from hyperdb-lib. Move wa-sqlite support to recipe -1. Add screenshot of code to the home page -1. Add demo page with devtool -1. Name package is @hyperdb/hyperdb -1. Create @hyperdb/sync -1. Remove SyncDB from doc -1. Mention tanstack/db, .... -1. Generate icon, favicon -1. Check all links -1. Update "start here" links -1. Ask make beaturfil deign using frontend design skill +1. On index page - embed stackblitz 1. Maybe rmeove "$" restriction? -1. Add quick guide to past to llm -1. Create new repo and setup release flow with changeset -1. Beatiful design -1. Release and record codesandbox video to put it to landing page -1. adopt styling of trpc -1. move devtool to separate package -1. how to isolate devtool styling? +1. Add quick guide to paste to llm or skill +1. DONE Fix npm install/doc links +1. DONE Remove wa-sqlite from hyperdb-lib. Move wa-sqlite support to recipe +1. DONE Add screenshot of code to the home page +1. DONE Add demo page with devtool +1. DONE Name package is @hyperdb/hyperdb +1. DONE Remove SyncDB from doc +1. DONE Generate icon, favicon +1. DONE Check all links +1. DONE Update "start here" links +1. DONE Ask make beaturfil deign using frontend design skill +1. DONE Create new repo and setup release flow with changeset or skill +1. DONE Beatiful design +1. DONE Release and make codesandbox screenshot to put it to landing page +1. DONE adopt styling of trpc +1. DONE move devtool to separate package Others: + 1. Understand when normalisation happen. Does it happened in-mem? Indexeddb? When validation happen? 1. intent skills css tanstack support +1. On release - cp readme.md to hyperdb/readme.md diff --git a/packages/hyperdb-demo/package.json b/packages/hyperdb-demo/package.json index 3ffb59c..a35fb67 100644 --- a/packages/hyperdb-demo/package.json +++ b/packages/hyperdb-demo/package.json @@ -15,7 +15,8 @@ "@will-be-done/hyperdb-devtool": "workspace:*", "react": "19.2.7", "react-dom": "19.2.7", - "tailwindcss": "^4.3.1" + "tailwindcss": "^4.3.1", + "wa-sqlite": "^1.0.0" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/packages/hyperdb-demo/src/BenchmarkApp.tsx b/packages/hyperdb-demo/src/BenchmarkApp.tsx index d5386c3..eb5f514 100644 --- a/packages/hyperdb-demo/src/BenchmarkApp.tsx +++ b/packages/hyperdb-demo/src/BenchmarkApp.tsx @@ -1,15 +1,18 @@ import { - useDispatch as useHyperdbDispatch, - useSyncSelector, + useAsyncDispatch as useHyperdbDispatch, + useAsyncSelector, } from "@will-be-done/hyperdb/react"; import { clearWorkload, + EMPTY_DASHBOARD_SNAPSHOT, generateWorkload, getDashboardSnapshot, toggleTaskDone, } from "./db"; import { LIST_PAGE_SIZE, useBenchmarkState } from "./useBenchmarkState"; import type { ClearWorkloadResult, WorkloadResult } from "./workload"; +import { getStoredMode, setStoredMode, type StoreMode } from "./store-mode"; +import { usePersistence } from "./persistence-context"; const numberFormatter = new Intl.NumberFormat("en-US"); const durationFormatter = new Intl.NumberFormat("en-US", { @@ -57,34 +60,79 @@ export function BenchmarkApp() { isWorking, setIsWorking, } = benchmarkState; - const dashboard = useSyncSelector({ - selector: getDashboardSnapshot, - args: { - taskLimit, - projectLimit, - selectedProjectId: benchmarkState.selectedProjectId, - }, - }); + const dashboard = + useAsyncSelector({ + selector: getDashboardSnapshot, + args: { + taskLimit, + projectLimit, + selectedProjectId: benchmarkState.selectedProjectId, + }, + }) ?? EMPTY_DASHBOARD_SNAPSHOT; + + const storeMode = getStoredMode(); + const persistence = usePersistence(); + const handleStoreModeChange = ( + event: React.ChangeEvent, + ) => { + const nextMode = event.currentTarget.value as StoreMode; + if (nextMode === storeMode) return; + // The store is created once at startup, so swapping tiers means a reload. + // The choice is remembered in localStorage and applied on the next boot. + setStoredMode(nextMode); + window.location.reload(); + }; const queuedTasks = projectCount * tasksPerProject; const visibleTaskCount = dashboard.selectedProject ? Math.min(taskLimit, dashboard.selectedTaskCount) : 0; const visibleProjectCount = Math.min(projectLimit, dashboard.totalProjects); + const directDriver = + storeMode === "idb" || storeMode === "idb-inmem" + ? "IndexedDB" + : "WA-SQLite OPFS"; + const hybrid = storeMode === "idb-inmem" || storeMode === "wa-sqlite-inmem"; + const storageStatus = hybrid + ? { + dot: + persistence?.draining || persistence?.pendingBatches + ? "animate-blip bg-amber" + : "bg-green", + text: + persistence?.draining || persistence?.pendingBatches + ? `Saving... ${formatNumber(persistence.pendingOps)} ops queued` + : persistence?.lastDurationMs != null + ? `Saved ${formatNumber( + persistence.lastOpCount ?? 0, + )} ops in ${formatDuration(persistence.lastDurationMs)} ms` + : `${directDriver} mirrored behind in-memory reads/writes.`, + } + : { + dot: "bg-green", + text: `Direct async ${directDriver} driver; changes survive reloads.`, + }; const runMeasured = ( label: string, - workload: () => WorkloadResult | ClearWorkloadResult, + workload: () => Promise, ) => { setIsWorking(true); requestAnimationFrame(() => { - const startedAt = performance.now(); - const result = workload(); - const durationMs = performance.now() - startedAt; + void (async () => { + const startedAt = performance.now(); + try { + const result = await workload(); + const durationMs = performance.now() - startedAt; - setLastRun({ label, durationMs, result }); - setIsWorking(false); + setLastRun({ label, durationMs, result }); + } catch (error) { + console.error(`Failed to run ${label}`, error); + } finally { + setIsWorking(false); + } + })(); }); }; @@ -130,6 +178,26 @@ export function BenchmarkApp() { +
+ Driver + +
+ +

{storageStatus.text}

+
+
+