@ai-matrx/kit 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +132 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/autosave.cjs +92 -0
- package/dist/autosave.cjs.map +1 -0
- package/dist/autosave.d.cts +17 -0
- package/dist/autosave.d.ts +17 -0
- package/dist/autosave.js +72 -0
- package/dist/autosave.js.map +1 -0
- package/dist/clipboard.cjs +152 -0
- package/dist/clipboard.cjs.map +1 -0
- package/dist/clipboard.d.cts +17 -0
- package/dist/clipboard.d.ts +17 -0
- package/dist/clipboard.js +132 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/color-util.cjs +790 -0
- package/dist/color-util.cjs.map +1 -0
- package/dist/color-util.d.cts +235 -0
- package/dist/color-util.d.ts +235 -0
- package/dist/color-util.js +767 -0
- package/dist/color-util.js.map +1 -0
- package/dist/concurrency.cjs +59 -0
- package/dist/concurrency.cjs.map +1 -0
- package/dist/concurrency.d.cts +19 -0
- package/dist/concurrency.d.ts +19 -0
- package/dist/concurrency.js +38 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/confirm.cjs +344 -0
- package/dist/confirm.cjs.map +1 -0
- package/dist/confirm.d.cts +132 -0
- package/dist/confirm.d.ts +132 -0
- package/dist/confirm.js +312 -0
- package/dist/confirm.js.map +1 -0
- package/dist/delimiter-guard.cjs +172 -0
- package/dist/delimiter-guard.cjs.map +1 -0
- package/dist/delimiter-guard.d.cts +119 -0
- package/dist/delimiter-guard.d.ts +119 -0
- package/dist/delimiter-guard.js +151 -0
- package/dist/delimiter-guard.js.map +1 -0
- package/dist/drafts.cjs +223 -0
- package/dist/drafts.cjs.map +1 -0
- package/dist/drafts.d.cts +62 -0
- package/dist/drafts.d.ts +62 -0
- package/dist/drafts.js +201 -0
- package/dist/drafts.js.map +1 -0
- package/dist/idb-store.cjs +178 -0
- package/dist/idb-store.cjs.map +1 -0
- package/dist/idb-store.d.cts +111 -0
- package/dist/idb-store.d.ts +111 -0
- package/dist/idb-store.js +155 -0
- package/dist/idb-store.js.map +1 -0
- package/dist/idle-scheduler.cjs +268 -0
- package/dist/idle-scheduler.cjs.map +1 -0
- package/dist/idle-scheduler.d.cts +185 -0
- package/dist/idle-scheduler.d.ts +185 -0
- package/dist/idle-scheduler.js +246 -0
- package/dist/idle-scheduler.js.map +1 -0
- package/dist/index.cjs +3113 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +897 -0
- package/dist/index.d.ts +897 -0
- package/dist/index.js +3088 -0
- package/dist/index.js.map +1 -0
- package/dist/invalidation.cjs +64 -0
- package/dist/invalidation.cjs.map +1 -0
- package/dist/invalidation.d.cts +52 -0
- package/dist/invalidation.d.ts +52 -0
- package/dist/invalidation.js +43 -0
- package/dist/invalidation.js.map +1 -0
- package/dist/json-format.cjs +319 -0
- package/dist/json-format.cjs.map +1 -0
- package/dist/json-format.d.cts +148 -0
- package/dist/json-format.d.ts +148 -0
- package/dist/json-format.js +286 -0
- package/dist/json-format.js.map +1 -0
- package/dist/latest-request.cjs +36 -0
- package/dist/latest-request.cjs.map +1 -0
- package/dist/latest-request.d.cts +29 -0
- package/dist/latest-request.d.ts +29 -0
- package/dist/latest-request.js +16 -0
- package/dist/latest-request.js.map +1 -0
- package/dist/qr.cjs +109 -0
- package/dist/qr.cjs.map +1 -0
- package/dist/qr.d.cts +47 -0
- package/dist/qr.d.ts +47 -0
- package/dist/qr.js +78 -0
- package/dist/qr.js.map +1 -0
- package/dist/react-tree.cjs +69 -0
- package/dist/react-tree.cjs.map +1 -0
- package/dist/react-tree.d.cts +38 -0
- package/dist/react-tree.d.ts +38 -0
- package/dist/react-tree.js +38 -0
- package/dist/react-tree.js.map +1 -0
- package/dist/search-scoring.cjs +116 -0
- package/dist/search-scoring.cjs.map +1 -0
- package/dist/search-scoring.d.cts +77 -0
- package/dist/search-scoring.d.ts +77 -0
- package/dist/search-scoring.js +95 -0
- package/dist/search-scoring.js.map +1 -0
- package/dist/text-case.cjs +313 -0
- package/dist/text-case.cjs.map +1 -0
- package/dist/text-case.d.cts +53 -0
- package/dist/text-case.d.ts +53 -0
- package/dist/text-case.js +292 -0
- package/dist/text-case.js.map +1 -0
- package/dist/toast.cjs +74 -0
- package/dist/toast.cjs.map +1 -0
- package/dist/toast.d.cts +92 -0
- package/dist/toast.d.ts +92 -0
- package/dist/toast.js +54 -0
- package/dist/toast.js.map +1 -0
- package/dist/url-state.cjs +212 -0
- package/dist/url-state.cjs.map +1 -0
- package/dist/url-state.d.cts +90 -0
- package/dist/url-state.d.ts +90 -0
- package/dist/url-state.js +199 -0
- package/dist/url-state.js.map +1 -0
- package/package.json +286 -0
package/dist/drafts.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/drafts/use-durable-draft.ts
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
var PREFIX = "matrx:durable-draft:";
|
|
6
|
+
var warnedStorageUnavailable = false;
|
|
7
|
+
function storageWrite(storageKey, value) {
|
|
8
|
+
try {
|
|
9
|
+
if (value) window.localStorage.setItem(storageKey, value);
|
|
10
|
+
else window.localStorage.removeItem(storageKey);
|
|
11
|
+
} catch (err) {
|
|
12
|
+
if (!warnedStorageUnavailable) {
|
|
13
|
+
warnedStorageUnavailable = true;
|
|
14
|
+
console.warn(
|
|
15
|
+
"[useDurableDraft] localStorage unavailable \u2014 drafts survive only in memory this session",
|
|
16
|
+
err
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function useDurableDraft(key) {
|
|
22
|
+
const storageKey = PREFIX + key;
|
|
23
|
+
const [draft, setDraftState] = useState("");
|
|
24
|
+
const touchedKeyRef = useRef(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
let saved = null;
|
|
27
|
+
try {
|
|
28
|
+
saved = window.localStorage.getItem(storageKey);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
setDraftState(
|
|
32
|
+
(current) => touchedKeyRef.current === storageKey && current ? current : saved ?? ""
|
|
33
|
+
);
|
|
34
|
+
}, [storageKey]);
|
|
35
|
+
const setDraft = (value) => {
|
|
36
|
+
touchedKeyRef.current = storageKey;
|
|
37
|
+
setDraftState(value);
|
|
38
|
+
storageWrite(storageKey, value);
|
|
39
|
+
};
|
|
40
|
+
const clearDraft = () => {
|
|
41
|
+
touchedKeyRef.current = storageKey;
|
|
42
|
+
setDraftState("");
|
|
43
|
+
storageWrite(storageKey, "");
|
|
44
|
+
};
|
|
45
|
+
return { draft, setDraft, clearDraft };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/drafts/local-drafts.ts
|
|
49
|
+
var STORAGE_KEY = "matrx.local-drafts.v1";
|
|
50
|
+
var DRAFT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
51
|
+
var MAX_DRAFT_CHARS = 4e5;
|
|
52
|
+
var MAX_TOTAL_CHARS = 15e5;
|
|
53
|
+
var TRUNCATION_MARKER = "\n\n[\u2026 truncated by the local draft store \u2014 the note was too large to snapshot in full]";
|
|
54
|
+
var sources = /* @__PURE__ */ new Map();
|
|
55
|
+
var unloadListenerAttached = false;
|
|
56
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
57
|
+
var version = 0;
|
|
58
|
+
function subscribeDrafts(listener) {
|
|
59
|
+
listeners.add(listener);
|
|
60
|
+
return () => {
|
|
61
|
+
listeners.delete(listener);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function getDraftsVersion() {
|
|
65
|
+
return version;
|
|
66
|
+
}
|
|
67
|
+
function emitDraftsChanged() {
|
|
68
|
+
version += 1;
|
|
69
|
+
for (const listener of listeners) {
|
|
70
|
+
try {
|
|
71
|
+
listener();
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error("[LocalDrafts] subscriber threw:", err);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function registerDraftSource(id, collect) {
|
|
78
|
+
sources.set(id, collect);
|
|
79
|
+
attachUnloadListener();
|
|
80
|
+
return () => {
|
|
81
|
+
if (sources.get(id) === collect) sources.delete(id);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function attachUnloadListener() {
|
|
85
|
+
if (unloadListenerAttached || typeof window === "undefined") return;
|
|
86
|
+
unloadListenerAttached = true;
|
|
87
|
+
window.addEventListener("pagehide", () => {
|
|
88
|
+
captureDrafts("unload");
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function captureDrafts(reason) {
|
|
92
|
+
if (typeof window === "undefined") return [];
|
|
93
|
+
const collected = [];
|
|
94
|
+
for (const [id, collect] of sources) {
|
|
95
|
+
try {
|
|
96
|
+
collected.push(...collect());
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error("[LocalDrafts] draft source failed:", id, err);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (collected.length === 0) return [];
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
const written = collected.map((input) => ({
|
|
104
|
+
...input,
|
|
105
|
+
content: input.content.length > MAX_DRAFT_CHARS ? input.content.slice(0, MAX_DRAFT_CHARS) + TRUNCATION_MARKER : input.content,
|
|
106
|
+
key: draftKey(input.namespace, input.entityId),
|
|
107
|
+
capturedAt: now,
|
|
108
|
+
reason
|
|
109
|
+
}));
|
|
110
|
+
const existing = readAll().filter(
|
|
111
|
+
(d) => !written.some((w) => w.key === d.key)
|
|
112
|
+
);
|
|
113
|
+
writeAll([...written, ...existing]);
|
|
114
|
+
console.warn(
|
|
115
|
+
`[LocalDrafts] snapshotted ${written.length} unsaved item(s) to this browser (reason: ${reason}).`,
|
|
116
|
+
written.map((d) => `${d.key} (${d.content.length} chars)`)
|
|
117
|
+
);
|
|
118
|
+
return written;
|
|
119
|
+
}
|
|
120
|
+
function listDrafts(namespace, ownerId) {
|
|
121
|
+
if (!ownerId) return [];
|
|
122
|
+
return readAll().filter((d) => d.namespace === namespace && d.ownerId === ownerId).sort((a, b) => b.capturedAt - a.capturedAt);
|
|
123
|
+
}
|
|
124
|
+
function getDraft(namespace, entityId, ownerId) {
|
|
125
|
+
if (!ownerId) return null;
|
|
126
|
+
const key = draftKey(namespace, entityId);
|
|
127
|
+
return readAll().find((d) => d.key === key && d.ownerId === ownerId) ?? null;
|
|
128
|
+
}
|
|
129
|
+
function discardDraft(namespace, entityId) {
|
|
130
|
+
const key = draftKey(namespace, entityId);
|
|
131
|
+
const all = readAll();
|
|
132
|
+
const next = all.filter((d) => d.key !== key);
|
|
133
|
+
if (next.length !== all.length) writeAll(next);
|
|
134
|
+
}
|
|
135
|
+
function draftKey(namespace, entityId) {
|
|
136
|
+
return `${namespace}:${entityId}`;
|
|
137
|
+
}
|
|
138
|
+
function readAll() {
|
|
139
|
+
if (typeof window === "undefined") return [];
|
|
140
|
+
let raw = null;
|
|
141
|
+
try {
|
|
142
|
+
raw = window.localStorage.getItem(STORAGE_KEY);
|
|
143
|
+
} catch {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
if (!raw) return [];
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(raw);
|
|
149
|
+
if (!Array.isArray(parsed)) return [];
|
|
150
|
+
const cutoff = Date.now() - DRAFT_TTL_MS;
|
|
151
|
+
return parsed.filter(isLocalDraft).filter((d) => d.capturedAt >= cutoff);
|
|
152
|
+
} catch {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function writeAll(drafts) {
|
|
157
|
+
if (typeof window === "undefined") return;
|
|
158
|
+
const cutoff = Date.now() - DRAFT_TTL_MS;
|
|
159
|
+
const fresh = drafts.filter((d) => d.capturedAt >= cutoff).sort((a, b) => b.capturedAt - a.capturedAt);
|
|
160
|
+
const kept = [];
|
|
161
|
+
let total = 0;
|
|
162
|
+
for (const draft of fresh) {
|
|
163
|
+
if (total + draft.content.length > MAX_TOTAL_CHARS && kept.length > 0) {
|
|
164
|
+
console.warn(
|
|
165
|
+
"[LocalDrafts] draft budget exhausted \u2014 dropping older draft",
|
|
166
|
+
draft.key
|
|
167
|
+
);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
kept.push(draft);
|
|
171
|
+
total += draft.content.length;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(kept));
|
|
175
|
+
} catch (err) {
|
|
176
|
+
console.error("[LocalDrafts] failed to persist drafts:", err);
|
|
177
|
+
if (kept.length > 1) {
|
|
178
|
+
try {
|
|
179
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify([kept[0]]));
|
|
180
|
+
} catch {
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
emitDraftsChanged();
|
|
185
|
+
}
|
|
186
|
+
function isLocalDraft(value) {
|
|
187
|
+
if (typeof value !== "object" || value === null) return false;
|
|
188
|
+
const d = value;
|
|
189
|
+
return typeof d.key === "string" && typeof d.namespace === "string" && typeof d.entityId === "string" && typeof d.content === "string" && typeof d.capturedAt === "number";
|
|
190
|
+
}
|
|
191
|
+
export {
|
|
192
|
+
captureDrafts,
|
|
193
|
+
discardDraft,
|
|
194
|
+
getDraft,
|
|
195
|
+
getDraftsVersion,
|
|
196
|
+
listDrafts,
|
|
197
|
+
registerDraftSource,
|
|
198
|
+
subscribeDrafts,
|
|
199
|
+
useDurableDraft
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=drafts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/drafts/use-durable-draft.ts","../src/drafts/local-drafts.ts"],"sourcesContent":["\"use client\";\n\n// useDurableDraft — user-authored text that MUST survive anything.\n//\n// A composer draft held only in React state dies with the tab: a mobile\n// Safari reload, a crash, an error storm, a mis-tap on Back — and the user's\n// two-minute dictated rant is gone. Losing it once costs all trust\n// (Arman's ruling, 2026-08-16, after exactly that happened in the Vision\n// Interview room).\n//\n// This hook is write-through: every change lands in localStorage\n// synchronously, restore happens on mount, and the draft is removed ONLY\n// via clearDraft() — which callers may invoke only after the content has\n// durably landed somewhere the user can see (a DB row, a rendered turn).\n// A send that fails keeps the draft by construction.\n//\n// Storage failure (private mode, quota) never breaks typing — state still\n// works; the failure is logged loudly once so the degraded durability is\n// visible, not silent.\n\nimport { useEffect, useRef, useState } from \"react\";\n\nconst PREFIX = \"matrx:durable-draft:\";\n\nlet warnedStorageUnavailable = false;\n\nfunction storageWrite(storageKey: string, value: string): void {\n try {\n if (value) window.localStorage.setItem(storageKey, value);\n else window.localStorage.removeItem(storageKey);\n } catch (err) {\n if (!warnedStorageUnavailable) {\n warnedStorageUnavailable = true;\n console.warn(\n \"[useDurableDraft] localStorage unavailable — drafts survive only in memory this session\",\n err,\n );\n }\n }\n}\n\nexport function useDurableDraft(key: string): {\n draft: string;\n setDraft: (value: string) => void;\n clearDraft: () => void;\n} {\n const storageKey = PREFIX + key;\n const [draft, setDraftState] = useState(\"\");\n // Which key the user's live keystrokes belong to. Their typing beats a\n // stale saved copy ONLY for the same key — a key CHANGE always adopts the\n // new key's saved value (or empty), so a swapped entity id can never show\n // or send the previous entity's text.\n const touchedKeyRef = useRef<string | null>(null);\n\n useEffect(() => {\n // Restore runs on mount AND on every key change (localStorage is\n // unavailable during SSR, hence effect not render).\n let saved: string | null = null;\n try {\n saved = window.localStorage.getItem(storageKey);\n } catch {\n // Restore is best-effort; the write path warns once (above).\n }\n setDraftState((current) =>\n touchedKeyRef.current === storageKey && current ? current : (saved ?? \"\"),\n );\n }, [storageKey]);\n\n const setDraft = (value: string) => {\n touchedKeyRef.current = storageKey;\n setDraftState(value);\n storageWrite(storageKey, value);\n };\n\n const clearDraft = () => {\n touchedKeyRef.current = storageKey;\n setDraftState(\"\");\n storageWrite(storageKey, \"\");\n };\n\n return { draft, setDraft, clearDraft };\n}\n","// lib/local-drafts/localDrafts.ts\n//\n// THE LAST-RESORT COPY of unsaved in-memory work, in this browser.\n//\n// Nothing here is a persistence path — every feature still owns its real save.\n// This exists for the moment the app is about to LOSE in-memory edits and has\n// no way to persist them: the tab is being hard-stopped (auth identity drift),\n// the page is unloading, or a feature's saves have been failing so long that\n// the buffer is the only copy that exists. Snapshot first, block second.\n//\n// Written because of D132 (2026-08-08): a domain-wide auth cookie rotated\n// under an open /notes tab, ~14h of autosaves were RLS-filtered to 0 rows, and\n// the \"Account Changed\" overlay then forced a reload that threw the in-memory\n// buffer away. One note never reached the DB at all and is unrecoverable.\n//\n// Rules:\n// - A draft is offered back ONLY to the same `ownerId` that wrote it.\n// - Storage is best-effort: quota errors, private mode, and disabled storage\n// degrade to \"no draft\", never to a thrown error on a save path.\n// - Drafts expire (7 days) and are capped, oldest-first, so this can never\n// grow into a shadow database.\n\nimport type { DraftSource, LocalDraft, LocalDraftInput } from \"./types\";\n\nconst STORAGE_KEY = \"matrx.local-drafts.v1\";\n\n/** Drafts older than this are dropped on the next read/write. */\nconst DRAFT_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n/** A single draft larger than this is stored truncated (with a marker). */\nconst MAX_DRAFT_CHARS = 400_000;\n/** Total budget across all drafts; the oldest are dropped to fit. */\nconst MAX_TOTAL_CHARS = 1_500_000;\n\nconst TRUNCATION_MARKER =\n \"\\n\\n[… truncated by the local draft store — the note was too large to snapshot in full]\";\n\n// ── Sources ────────────────────────────────────────────────────────────────\n\nconst sources = new Map<string, DraftSource>();\nlet unloadListenerAttached = false;\n\n// ── Subscription ───────────────────────────────────────────────────────────\n//\n// A capture can happen while a recovery UI is already on screen (a save-failure\n// escalation for a note that is not the open tab). Without a notification that\n// strip would sit empty until a remount — i.e. the rescue exists and the user\n// is never told. `version` bumps on every write.\n\nconst listeners = new Set<() => void>();\nlet version = 0;\n\n/** Subscribe to draft-store writes. Pair with `getDraftsVersion` for `useSyncExternalStore`. */\nexport function subscribeDrafts(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\nexport function getDraftsVersion(): number {\n return version;\n}\n\nfunction emitDraftsChanged(): void {\n version += 1;\n for (const listener of listeners) {\n try {\n listener();\n } catch (err) {\n console.error(\"[LocalDrafts] subscriber threw:\", err);\n }\n }\n}\n\n/**\n * Register a collector for one feature's unsaved work. Re-registering the same\n * id replaces the previous collector (remounts are safe); the returned\n * unregister only removes the entry if it is still the one it installed.\n */\nexport function registerDraftSource(id: string, collect: DraftSource): () => void {\n sources.set(id, collect);\n attachUnloadListener();\n return () => {\n if (sources.get(id) === collect) sources.delete(id);\n };\n}\n\nfunction attachUnloadListener(): void {\n if (unloadListenerAttached || typeof window === \"undefined\") return;\n unloadListenerAttached = true;\n // `pagehide` fires in cases `beforeunload` does not (bfcache, mobile Safari).\n window.addEventListener(\"pagehide\", () => {\n captureDrafts(\"unload\");\n });\n}\n\n// ── Capture ────────────────────────────────────────────────────────────────\n\n/**\n * Walk every registered source and persist what they hand back.\n * Returns the drafts written (empty when nothing is unsaved).\n *\n * Call this BEFORE anything that discards in-memory state — a forced reload,\n * a blocking overlay, a hard sign-out.\n */\nexport function captureDrafts(reason: string): LocalDraft[] {\n if (typeof window === \"undefined\") return [];\n\n const collected: LocalDraftInput[] = [];\n for (const [id, collect] of sources) {\n try {\n collected.push(...collect());\n } catch (err) {\n console.error(\"[LocalDrafts] draft source failed:\", id, err);\n }\n }\n if (collected.length === 0) return [];\n\n const now = Date.now();\n const written: LocalDraft[] = collected.map((input) => ({\n ...input,\n content:\n input.content.length > MAX_DRAFT_CHARS\n ? input.content.slice(0, MAX_DRAFT_CHARS) + TRUNCATION_MARKER\n : input.content,\n key: draftKey(input.namespace, input.entityId),\n capturedAt: now,\n reason,\n }));\n\n const existing = readAll().filter(\n (d) => !written.some((w) => w.key === d.key),\n );\n writeAll([...written, ...existing]);\n\n console.warn(\n `[LocalDrafts] snapshotted ${written.length} unsaved item(s) to this browser (reason: ${reason}).`,\n written.map((d) => `${d.key} (${d.content.length} chars)`),\n );\n return written;\n}\n\n// ── Read / discard ─────────────────────────────────────────────────────────\n\n/** Every live draft in a namespace that belongs to `ownerId`, newest first. */\nexport function listDrafts(namespace: string, ownerId: string | null): LocalDraft[] {\n if (!ownerId) return [];\n return readAll()\n .filter((d) => d.namespace === namespace && d.ownerId === ownerId)\n .sort((a, b) => b.capturedAt - a.capturedAt);\n}\n\n/** The draft for one entity, if it belongs to `ownerId`. */\nexport function getDraft(\n namespace: string,\n entityId: string,\n ownerId: string | null,\n): LocalDraft | null {\n if (!ownerId) return null;\n const key = draftKey(namespace, entityId);\n return (\n readAll().find((d) => d.key === key && d.ownerId === ownerId) ?? null\n );\n}\n\n/** Drop one draft (restored, discarded by the user, or its entity saved). */\nexport function discardDraft(namespace: string, entityId: string): void {\n const key = draftKey(namespace, entityId);\n const all = readAll();\n const next = all.filter((d) => d.key !== key);\n if (next.length !== all.length) writeAll(next); // writeAll emits\n}\n\n// ── Storage ────────────────────────────────────────────────────────────────\n\nfunction draftKey(namespace: string, entityId: string): string {\n return `${namespace}:${entityId}`;\n}\n\nfunction readAll(): LocalDraft[] {\n if (typeof window === \"undefined\") return [];\n let raw: string | null = null;\n try {\n raw = window.localStorage.getItem(STORAGE_KEY);\n } catch {\n return []; // storage disabled / private mode — no drafts, never a throw\n }\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n const cutoff = Date.now() - DRAFT_TTL_MS;\n return parsed.filter(isLocalDraft).filter((d) => d.capturedAt >= cutoff);\n } catch {\n return [];\n }\n}\n\nfunction writeAll(drafts: LocalDraft[]): void {\n if (typeof window === \"undefined\") return;\n const cutoff = Date.now() - DRAFT_TTL_MS;\n const fresh = drafts\n .filter((d) => d.capturedAt >= cutoff)\n .sort((a, b) => b.capturedAt - a.capturedAt);\n\n // Newest-first budget: keep taking drafts until the char budget runs out.\n const kept: LocalDraft[] = [];\n let total = 0;\n for (const draft of fresh) {\n if (total + draft.content.length > MAX_TOTAL_CHARS && kept.length > 0) {\n console.warn(\n \"[LocalDrafts] draft budget exhausted — dropping older draft\",\n draft.key,\n );\n continue;\n }\n kept.push(draft);\n total += draft.content.length;\n }\n\n try {\n window.localStorage.setItem(STORAGE_KEY, JSON.stringify(kept));\n } catch (err) {\n // Quota exceeded: retry with only the newest draft before giving up —\n // one recovered note beats zero.\n console.error(\"[LocalDrafts] failed to persist drafts:\", err);\n if (kept.length > 1) {\n try {\n window.localStorage.setItem(STORAGE_KEY, JSON.stringify([kept[0]]));\n } catch {\n /* storage is unusable — nothing more we can do here */\n }\n }\n }\n\n emitDraftsChanged();\n}\n\nfunction isLocalDraft(value: unknown): value is LocalDraft {\n if (typeof value !== \"object\" || value === null) return false;\n const d = value as Record<string, unknown>;\n return (\n typeof d.key === \"string\" &&\n typeof d.namespace === \"string\" &&\n typeof d.entityId === \"string\" &&\n typeof d.content === \"string\" &&\n typeof d.capturedAt === \"number\"\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/idb-store.ts
|
|
21
|
+
var idb_store_exports = {};
|
|
22
|
+
__export(idb_store_exports, {
|
|
23
|
+
DBStoreManager: () => DBStoreManager,
|
|
24
|
+
FeatureStore: () => FeatureStore,
|
|
25
|
+
PublicStoreManager: () => PublicStoreManager,
|
|
26
|
+
_resetIdbStoreSingletons: () => _resetIdbStoreSingletons,
|
|
27
|
+
getIdbStoreSingleton: () => getIdbStoreSingleton
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(idb_store_exports);
|
|
30
|
+
|
|
31
|
+
// src/idb-store/store-manager.ts
|
|
32
|
+
var import_idb = require("idb");
|
|
33
|
+
var DBStoreManager = class {
|
|
34
|
+
db = null;
|
|
35
|
+
dbName;
|
|
36
|
+
version;
|
|
37
|
+
constructor(dbName, version) {
|
|
38
|
+
this.dbName = dbName;
|
|
39
|
+
this.version = version;
|
|
40
|
+
}
|
|
41
|
+
async initDB() {
|
|
42
|
+
if (this.db) return;
|
|
43
|
+
try {
|
|
44
|
+
this.db = await (0, import_idb.openDB)(this.dbName, this.version, {
|
|
45
|
+
upgrade: (db) => {
|
|
46
|
+
this.setupStores(db);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error("Failed to initialize database:", error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// `add`/`get` carry their own generic (like `query` below) so a subclass
|
|
55
|
+
// whose `T` covers one IDB object store (e.g. Recording) can still read/write
|
|
56
|
+
// a different store's record shape (e.g. RecordingChunk) without a cast —
|
|
57
|
+
// see the audio store in the original host, which manages both `recordings`
|
|
58
|
+
// and `chunks` stores.
|
|
59
|
+
async add(storeName, data) {
|
|
60
|
+
try {
|
|
61
|
+
if (!this.db) throw new Error("Database not initialized");
|
|
62
|
+
const id = await this.db.add(storeName, data);
|
|
63
|
+
return { data: id.toString(), error: null };
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return { data: null, error };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async get(storeName, id) {
|
|
69
|
+
try {
|
|
70
|
+
if (!this.db) throw new Error("Database not initialized");
|
|
71
|
+
const result = await this.db.get(storeName, id);
|
|
72
|
+
return { data: result, error: null };
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return { data: null, error };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async getAll(storeName) {
|
|
78
|
+
try {
|
|
79
|
+
if (!this.db) throw new Error("Database not initialized");
|
|
80
|
+
const result = await this.db.getAll(storeName);
|
|
81
|
+
return { data: result, error: null };
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return { data: null, error };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async update(storeName, id, data) {
|
|
87
|
+
try {
|
|
88
|
+
if (!this.db) throw new Error("Database not initialized");
|
|
89
|
+
const existing = await this.db.get(storeName, id);
|
|
90
|
+
if (!existing) throw new Error("Record not found");
|
|
91
|
+
const updated = { ...existing, ...data };
|
|
92
|
+
await this.db.put(storeName, updated);
|
|
93
|
+
return { data: true, error: null };
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return { data: null, error };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async delete(storeName, id) {
|
|
99
|
+
try {
|
|
100
|
+
if (!this.db) throw new Error("Database not initialized");
|
|
101
|
+
await this.db.delete(storeName, id);
|
|
102
|
+
return { data: true, error: null };
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return { data: null, error };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async query(storeName, indexName, query) {
|
|
108
|
+
try {
|
|
109
|
+
if (!this.db) throw new Error("Database not initialized");
|
|
110
|
+
const result = await this.db.getAllFromIndex(storeName, indexName, query);
|
|
111
|
+
return { data: result, error: null };
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return { data: null, error };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/idb-store/store-interface.ts
|
|
119
|
+
var PublicStoreManager = class extends DBStoreManager {
|
|
120
|
+
constructor(dbName, version) {
|
|
121
|
+
super(dbName, version);
|
|
122
|
+
}
|
|
123
|
+
addItem(storeName, data) {
|
|
124
|
+
return this.add(storeName, data);
|
|
125
|
+
}
|
|
126
|
+
getItem(storeName, id) {
|
|
127
|
+
return this.get(storeName, id);
|
|
128
|
+
}
|
|
129
|
+
getAllItems(storeName) {
|
|
130
|
+
return this.getAll(storeName);
|
|
131
|
+
}
|
|
132
|
+
updateItem(storeName, id, data) {
|
|
133
|
+
return this.update(storeName, id, data);
|
|
134
|
+
}
|
|
135
|
+
deleteItem(storeName, id) {
|
|
136
|
+
return this.delete(storeName, id);
|
|
137
|
+
}
|
|
138
|
+
queryItems(storeName, indexName, query) {
|
|
139
|
+
return this.query(storeName, indexName, query);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/idb-store/feature-store.ts
|
|
144
|
+
var FeatureStore = class extends PublicStoreManager {
|
|
145
|
+
storeName;
|
|
146
|
+
constructor(dbName, version, storeName) {
|
|
147
|
+
super(dbName, version);
|
|
148
|
+
this.storeName = storeName;
|
|
149
|
+
this.initDB().catch(() => {
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
getStoreName() {
|
|
153
|
+
return this.storeName;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/idb-store/singleton.ts
|
|
158
|
+
var STATE_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.kit.idb-store-state");
|
|
159
|
+
function getState() {
|
|
160
|
+
const holder = globalThis;
|
|
161
|
+
let state = holder[STATE_SLOT];
|
|
162
|
+
if (!state) {
|
|
163
|
+
state = { instances: /* @__PURE__ */ new Map() };
|
|
164
|
+
holder[STATE_SLOT] = state;
|
|
165
|
+
}
|
|
166
|
+
return state;
|
|
167
|
+
}
|
|
168
|
+
function getIdbStoreSingleton(key, create) {
|
|
169
|
+
const state = getState();
|
|
170
|
+
if (!state.instances.has(key)) {
|
|
171
|
+
state.instances.set(key, create());
|
|
172
|
+
}
|
|
173
|
+
return state.instances.get(key);
|
|
174
|
+
}
|
|
175
|
+
function _resetIdbStoreSingletons() {
|
|
176
|
+
getState().instances.clear();
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=idb-store.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/idb-store.ts","../src/idb-store/store-manager.ts","../src/idb-store/store-interface.ts","../src/idb-store/feature-store.ts","../src/idb-store/singleton.ts"],"sourcesContent":["/**\n * @ai-matrx/kit/idb-store — the typed, versioned IndexedDB wrapper.\n *\n * Three layers, ported from the matrx-frontend originals (`lib/idb/`):\n * - `DBStoreManager<T>` — the base: `openDB` with a `setupStores` upgrade\n * hook, and protected CRUD/query operations that never throw — every\n * operation resolves `AsyncResult<T>` (`{ data, error }`).\n * - `PublicStoreManager<T>` — the base with the CRUD surface public\n * (`addItem`/`getItem`/`getAllItems`/`updateItem`/`deleteItem`/`queryItems`),\n * for hosts driving arbitrary stores generically.\n * - `FeatureStore<T>` — one named object store per class; opens the DB from\n * the constructor (operations before init resolve with an error, never\n * throw).\n *\n * Singleton-per-store is the intended lifetime, and the slot for it is\n * `getIdbStoreSingleton(key, create)` — backed by `globalThis` under\n * `Symbol.for(\"ai-matrx.kit.idb-store-state\")`, never a class-static or\n * module variable (the dual ESM/CJS graph rule; see singleton.ts).\n *\n * Runtime dependency of this subpath (and only this subpath's bundles):\n * `idb` — the typed IndexedDB promise wrapper IS the engine here, and\n * subclass `setupStores` hooks receive its `IDBPDatabase`.\n *\n * Browser capability: requires IndexedDB at operation time. Importing is\n * inert (SSR-safe); construction opens the database.\n */\nexport { DBStoreManager } from \"./idb-store/store-manager\";\nexport type { AsyncResult } from \"./idb-store/store-manager\";\nexport { PublicStoreManager } from \"./idb-store/store-interface\";\nexport { FeatureStore } from \"./idb-store/feature-store\";\nexport {\n getIdbStoreSingleton,\n _resetIdbStoreSingletons,\n} from \"./idb-store/singleton\";\n","/**\n * @ai-matrx/kit/idb-store — base manager.\n *\n * Ported verbatim from matrx-frontend `lib/idb/store-manager.ts`, with ONE\n * structural inversion: the original documented a `protected static _instance`\n * convention (each subclass held its singleton on its own static field).\n * A class-static is module state — with this package built `splitting: false`\n * in dual ESM/CJS format the class is duplicated into the root bundle and the\n * `./idb-store` bundle, and CJS/ESM each instantiate their own module graph,\n * so a static field would silently split \"the\" singleton into up to four\n * instances racing the same IndexedDB database. The singleton slot therefore\n * lives on `globalThis` under `Symbol.for(\"ai-matrx.kit.idb-store-state\")`\n * instead — see `singleton.ts` (`getIdbStoreSingleton`). Never \"clean this\n * up\" back into a static field.\n */\n\nimport { openDB, IDBPDatabase } from \"idb\";\n\nexport type AsyncResult<T> = Promise<{ data: T | null; error: Error | null }>;\n\nexport abstract class DBStoreManager<T> {\n protected db: IDBPDatabase | null = null;\n protected dbName: string;\n protected version: number;\n\n protected constructor(dbName: string, version: number) {\n this.dbName = dbName;\n this.version = version;\n }\n\n protected abstract setupStores(db: IDBPDatabase): void;\n\n protected async initDB(): Promise<void> {\n if (this.db) return;\n\n try {\n this.db = await openDB(this.dbName, this.version, {\n upgrade: (db) => {\n this.setupStores(db);\n },\n });\n } catch (error) {\n console.error(\"Failed to initialize database:\", error);\n throw error;\n }\n }\n\n // `add`/`get` carry their own generic (like `query` below) so a subclass\n // whose `T` covers one IDB object store (e.g. Recording) can still read/write\n // a different store's record shape (e.g. RecordingChunk) without a cast —\n // see the audio store in the original host, which manages both `recordings`\n // and `chunks` stores.\n protected async add<TRecord = T>(storeName: string, data: TRecord): AsyncResult<string> {\n try {\n if (!this.db) throw new Error(\"Database not initialized\");\n const id = await this.db.add(storeName, data);\n return { data: id.toString(), error: null };\n } catch (error) {\n return { data: null, error: error as Error };\n }\n }\n\n protected async get<TRecord = T>(storeName: string, id: string): AsyncResult<TRecord> {\n try {\n if (!this.db) throw new Error(\"Database not initialized\");\n const result = await this.db.get(storeName, id);\n return { data: result as TRecord, error: null };\n } catch (error) {\n return { data: null, error: error as Error };\n }\n }\n\n protected async getAll(storeName: string): AsyncResult<T[]> {\n try {\n if (!this.db) throw new Error(\"Database not initialized\");\n const result = await this.db.getAll(storeName);\n return { data: result as T[], error: null };\n } catch (error) {\n return { data: null, error: error as Error };\n }\n }\n\n protected async update<U extends object>(storeName: string, id: number, data: Partial<U>): AsyncResult<boolean> {\n try {\n if (!this.db) throw new Error(\"Database not initialized\");\n const existing = await this.db.get(storeName, id);\n if (!existing) throw new Error(\"Record not found\");\n\n const updated = { ...existing, ...data };\n await this.db.put(storeName, updated);\n return { data: true, error: null };\n } catch (error) {\n return { data: null, error: error as Error };\n }\n }\n\n protected async delete(storeName: string, id: number): AsyncResult<boolean> {\n try {\n if (!this.db) throw new Error(\"Database not initialized\");\n await this.db.delete(storeName, id);\n return { data: true, error: null };\n } catch (error) {\n return { data: null, error: error as Error };\n }\n }\n\n protected async query<U>(\n storeName: string,\n indexName: string,\n query: IDBValidKey | IDBKeyRange\n ): AsyncResult<U[]> {\n try {\n if (!this.db) throw new Error(\"Database not initialized\");\n const result = await this.db.getAllFromIndex(storeName, indexName, query);\n return { data: result as U[], error: null };\n } catch (error) {\n return { data: null, error: error as Error };\n }\n }\n}\n","/**\n * Public CRUD surface over `DBStoreManager` — ported verbatim from\n * matrx-frontend `lib/idb/store-interface.ts`. The base keeps its operations\n * `protected` so feature stores expose intent-named methods; this subclass is\n * the escape hatch for hosts that want the raw generic surface (e.g. a\n * `useIDB(store)` hook driving arbitrary stores).\n *\n * NOTE (original API shape, kept verbatim): `getItem` takes a string id while\n * `updateItem`/`deleteItem` take a number id. With an `autoIncrement` key path\n * the generated keys are numbers, so reads use `String(id)` only against\n * string-keyed stores — this asymmetry is the original public contract.\n */\n\nimport { DBStoreManager, AsyncResult } from \"./store-manager\";\n\nexport abstract class PublicStoreManager<T> extends DBStoreManager<T> {\n constructor(dbName: string, version: number) {\n super(dbName, version);\n }\n\n public addItem(storeName: string, data: T): AsyncResult<string> {\n return this.add(storeName, data);\n }\n\n public getItem(storeName: string, id: string): AsyncResult<T> {\n return this.get(storeName, id);\n }\n\n public getAllItems(storeName: string): AsyncResult<T[]> {\n return this.getAll(storeName);\n }\n\n public updateItem<U extends object>(\n storeName: string,\n id: number,\n data: Partial<U>\n ): AsyncResult<boolean> {\n return this.update<U>(storeName, id, data);\n }\n\n public deleteItem(storeName: string, id: number): AsyncResult<boolean> {\n return this.delete(storeName, id);\n }\n\n public queryItems<U>(\n storeName: string,\n indexName: string,\n query: IDBValidKey | IDBKeyRange\n ): AsyncResult<U[]> {\n return this.query<U>(storeName, indexName, query);\n }\n}\n","/**\n * Convenience base for a store class bound to one named object store —\n * ported from matrx-frontend `lib/idb/feature-store.ts`.\n *\n * DEFECT FIX vs the original: the constructor kicks off `initDB()` without\n * awaiting it (by design — construction stays synchronous and operations\n * before init resolve `{ error: \"Database not initialized\" }`), but the\n * original left that floating promise unhandled, so a failed `openDB` (e.g.\n * private-mode storage denial) surfaced as an unhandled promise rejection on\n * top of the `initDB` console.error. The rejection is now absorbed here —\n * `initDB` has already screamed, and every operation still reports the\n * uninitialized state through its `AsyncResult` error.\n */\n\nimport { IDBPDatabase } from \"idb\";\nimport { PublicStoreManager } from \"./store-interface\";\n\nexport abstract class FeatureStore<T> extends PublicStoreManager<T> {\n protected storeName: string;\n\n protected constructor(dbName: string, version: number, storeName: string) {\n super(dbName, version);\n this.storeName = storeName;\n this.initDB().catch(() => {\n // Already logged loudly by initDB; operations report\n // \"Database not initialized\" through their AsyncResult.\n });\n }\n\n protected abstract override setupStores(db: IDBPDatabase): void;\n\n public getStoreName(): string {\n return this.storeName;\n }\n}\n","/**\n * The one place a store singleton may live.\n *\n * The original host pattern held each store's singleton on a\n * `protected static _instance` field of the store class. In this package that\n * is a hazard, not a convenience: built `splitting: false` in dual ESM/CJS\n * format, the class body is duplicated into the root bundle and the\n * `./idb-store` bundle, and the ESM and CJS graphs each instantiate their own\n * copy — a class-static would silently split \"the\" singleton into several\n * instances, each opening its own connection (and racing upgrades) against\n * the same IndexedDB database. So per-store instances live on `globalThis`\n * under `Symbol.for(\"ai-matrx.kit.idb-store-state\")`, keyed by a\n * caller-chosen name. Never \"clean this up\" into module or class state.\n *\n * Host usage (replaces the old static `getInstance` body):\n *\n * class AudioStore extends DBStoreManager<Recording> { ... }\n * export const audioStore = getIdbStoreSingleton(\"voiceNotesDB/audio\", () => new AudioStore());\n */\n\ninterface IdbStoreState {\n instances: Map<string, unknown>;\n}\n\nconst STATE_SLOT = Symbol.for(\"ai-matrx.kit.idb-store-state\");\n\nfunction getState(): IdbStoreState {\n const holder = globalThis as Record<symbol, IdbStoreState | undefined>;\n let state = holder[STATE_SLOT];\n if (!state) {\n state = { instances: new Map() };\n holder[STATE_SLOT] = state;\n }\n return state;\n}\n\n/**\n * Returns the one instance registered under `key`, creating it via `create`\n * on first call. The key should uniquely name the store (a good convention is\n * `\"<dbName>/<storeName>\"`) — two different classes registering the same key\n * is a caller bug and gets whichever registered first.\n */\nexport function getIdbStoreSingleton<T>(key: string, create: () => T): T {\n const state = getState();\n if (!state.instances.has(key)) {\n state.instances.set(key, create());\n }\n return state.instances.get(key) as T;\n}\n\n/** @internal Test-only: drop every registered store instance. */\nexport function _resetIdbStoreSingletons(): void {\n getState().instances.clear();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { IDBPDatabase } from 'idb';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @ai-matrx/kit/idb-store — base manager.
|
|
5
|
+
*
|
|
6
|
+
* Ported verbatim from matrx-frontend `lib/idb/store-manager.ts`, with ONE
|
|
7
|
+
* structural inversion: the original documented a `protected static _instance`
|
|
8
|
+
* convention (each subclass held its singleton on its own static field).
|
|
9
|
+
* A class-static is module state — with this package built `splitting: false`
|
|
10
|
+
* in dual ESM/CJS format the class is duplicated into the root bundle and the
|
|
11
|
+
* `./idb-store` bundle, and CJS/ESM each instantiate their own module graph,
|
|
12
|
+
* so a static field would silently split "the" singleton into up to four
|
|
13
|
+
* instances racing the same IndexedDB database. The singleton slot therefore
|
|
14
|
+
* lives on `globalThis` under `Symbol.for("ai-matrx.kit.idb-store-state")`
|
|
15
|
+
* instead — see `singleton.ts` (`getIdbStoreSingleton`). Never "clean this
|
|
16
|
+
* up" back into a static field.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type AsyncResult<T> = Promise<{
|
|
20
|
+
data: T | null;
|
|
21
|
+
error: Error | null;
|
|
22
|
+
}>;
|
|
23
|
+
declare abstract class DBStoreManager<T> {
|
|
24
|
+
protected db: IDBPDatabase | null;
|
|
25
|
+
protected dbName: string;
|
|
26
|
+
protected version: number;
|
|
27
|
+
protected constructor(dbName: string, version: number);
|
|
28
|
+
protected abstract setupStores(db: IDBPDatabase): void;
|
|
29
|
+
protected initDB(): Promise<void>;
|
|
30
|
+
protected add<TRecord = T>(storeName: string, data: TRecord): AsyncResult<string>;
|
|
31
|
+
protected get<TRecord = T>(storeName: string, id: string): AsyncResult<TRecord>;
|
|
32
|
+
protected getAll(storeName: string): AsyncResult<T[]>;
|
|
33
|
+
protected update<U extends object>(storeName: string, id: number, data: Partial<U>): AsyncResult<boolean>;
|
|
34
|
+
protected delete(storeName: string, id: number): AsyncResult<boolean>;
|
|
35
|
+
protected query<U>(storeName: string, indexName: string, query: IDBValidKey | IDBKeyRange): AsyncResult<U[]>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Public CRUD surface over `DBStoreManager` — ported verbatim from
|
|
40
|
+
* matrx-frontend `lib/idb/store-interface.ts`. The base keeps its operations
|
|
41
|
+
* `protected` so feature stores expose intent-named methods; this subclass is
|
|
42
|
+
* the escape hatch for hosts that want the raw generic surface (e.g. a
|
|
43
|
+
* `useIDB(store)` hook driving arbitrary stores).
|
|
44
|
+
*
|
|
45
|
+
* NOTE (original API shape, kept verbatim): `getItem` takes a string id while
|
|
46
|
+
* `updateItem`/`deleteItem` take a number id. With an `autoIncrement` key path
|
|
47
|
+
* the generated keys are numbers, so reads use `String(id)` only against
|
|
48
|
+
* string-keyed stores — this asymmetry is the original public contract.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
declare abstract class PublicStoreManager<T> extends DBStoreManager<T> {
|
|
52
|
+
constructor(dbName: string, version: number);
|
|
53
|
+
addItem(storeName: string, data: T): AsyncResult<string>;
|
|
54
|
+
getItem(storeName: string, id: string): AsyncResult<T>;
|
|
55
|
+
getAllItems(storeName: string): AsyncResult<T[]>;
|
|
56
|
+
updateItem<U extends object>(storeName: string, id: number, data: Partial<U>): AsyncResult<boolean>;
|
|
57
|
+
deleteItem(storeName: string, id: number): AsyncResult<boolean>;
|
|
58
|
+
queryItems<U>(storeName: string, indexName: string, query: IDBValidKey | IDBKeyRange): AsyncResult<U[]>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Convenience base for a store class bound to one named object store —
|
|
63
|
+
* ported from matrx-frontend `lib/idb/feature-store.ts`.
|
|
64
|
+
*
|
|
65
|
+
* DEFECT FIX vs the original: the constructor kicks off `initDB()` without
|
|
66
|
+
* awaiting it (by design — construction stays synchronous and operations
|
|
67
|
+
* before init resolve `{ error: "Database not initialized" }`), but the
|
|
68
|
+
* original left that floating promise unhandled, so a failed `openDB` (e.g.
|
|
69
|
+
* private-mode storage denial) surfaced as an unhandled promise rejection on
|
|
70
|
+
* top of the `initDB` console.error. The rejection is now absorbed here —
|
|
71
|
+
* `initDB` has already screamed, and every operation still reports the
|
|
72
|
+
* uninitialized state through its `AsyncResult` error.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
declare abstract class FeatureStore<T> extends PublicStoreManager<T> {
|
|
76
|
+
protected storeName: string;
|
|
77
|
+
protected constructor(dbName: string, version: number, storeName: string);
|
|
78
|
+
protected abstract setupStores(db: IDBPDatabase): void;
|
|
79
|
+
getStoreName(): string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The one place a store singleton may live.
|
|
84
|
+
*
|
|
85
|
+
* The original host pattern held each store's singleton on a
|
|
86
|
+
* `protected static _instance` field of the store class. In this package that
|
|
87
|
+
* is a hazard, not a convenience: built `splitting: false` in dual ESM/CJS
|
|
88
|
+
* format, the class body is duplicated into the root bundle and the
|
|
89
|
+
* `./idb-store` bundle, and the ESM and CJS graphs each instantiate their own
|
|
90
|
+
* copy — a class-static would silently split "the" singleton into several
|
|
91
|
+
* instances, each opening its own connection (and racing upgrades) against
|
|
92
|
+
* the same IndexedDB database. So per-store instances live on `globalThis`
|
|
93
|
+
* under `Symbol.for("ai-matrx.kit.idb-store-state")`, keyed by a
|
|
94
|
+
* caller-chosen name. Never "clean this up" into module or class state.
|
|
95
|
+
*
|
|
96
|
+
* Host usage (replaces the old static `getInstance` body):
|
|
97
|
+
*
|
|
98
|
+
* class AudioStore extends DBStoreManager<Recording> { ... }
|
|
99
|
+
* export const audioStore = getIdbStoreSingleton("voiceNotesDB/audio", () => new AudioStore());
|
|
100
|
+
*/
|
|
101
|
+
/**
|
|
102
|
+
* Returns the one instance registered under `key`, creating it via `create`
|
|
103
|
+
* on first call. The key should uniquely name the store (a good convention is
|
|
104
|
+
* `"<dbName>/<storeName>"`) — two different classes registering the same key
|
|
105
|
+
* is a caller bug and gets whichever registered first.
|
|
106
|
+
*/
|
|
107
|
+
declare function getIdbStoreSingleton<T>(key: string, create: () => T): T;
|
|
108
|
+
/** @internal Test-only: drop every registered store instance. */
|
|
109
|
+
declare function _resetIdbStoreSingletons(): void;
|
|
110
|
+
|
|
111
|
+
export { type AsyncResult, DBStoreManager, FeatureStore, PublicStoreManager, _resetIdbStoreSingletons, getIdbStoreSingleton };
|