@columenlabs/lumen-ui 0.1.120 → 0.1.121

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/dist/index.d.ts CHANGED
@@ -76,6 +76,7 @@ export { type McpServer, McpSettings } from './mcp-settings.js';
76
76
  export { lumenToast } from './lumen-toast.js';
77
77
  export { LumenShell, type LumenShellProps, type ShellUser, StatusItem, firstName, initials, maskAmount, useAmountsHidden, useApp, useShell, } from './shell.js';
78
78
  export { apiJson } from './api-fetch.js';
79
+ export { savedAnswers, type SavedAnswersOptions, type SavedAnswersPersister, } from './saved-answers.js';
79
80
  export { cn } from './lib/utils.js';
80
81
  export { type HealthProbe, StatusHealth } from './status-health.js';
81
82
  export { type StatusMessage, type StatusMessageTone, StatusMessages, groupStatusMessages, } from './status-messages.js';
package/dist/index.js CHANGED
@@ -83,6 +83,7 @@ export { McpSettings } from './mcp-settings.js';
83
83
  export { lumenToast } from './lumen-toast.js';
84
84
  export { LumenShell, StatusItem, firstName, initials, maskAmount, useAmountsHidden, useApp, useShell, } from './shell.js';
85
85
  export { apiJson } from './api-fetch.js';
86
+ export { savedAnswers, } from './saved-answers.js';
86
87
  export { cn } from './lib/utils.js';
87
88
  export { StatusHealth } from './status-health.js';
88
89
  export { StatusMessages, groupStatusMessages, } from './status-messages.js';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Saved answers: a react-query persister that keeps an app's answers in IndexedDB, so a page
3
+ * paints at once from the last visit. Pass it to `PersistQueryClientProvider` as `persister`.
4
+ *
5
+ * Nothing waits on the device for good: a read that has not answered within `restoreMs` (another
6
+ * tab holding a write, a broken store) gives up and the app asks the server, and an aborted or
7
+ * blocked store fails instead of hanging. The provider shows nothing until the restore settles,
8
+ * so a hung read would otherwise leave the app blank. Writes go out at most once a second, as one
9
+ * record kept as an object (no JSON round trip).
10
+ */
11
+ /** The shape `@tanstack/react-query-persist-client` expects; kept structural so the kit needs no extra package. */
12
+ export interface SavedAnswersPersister<C = unknown> {
13
+ persistClient: (client: C) => void;
14
+ restoreClient: () => Promise<C | undefined>;
15
+ removeClient: () => Promise<void> | void;
16
+ }
17
+ export interface SavedAnswersOptions {
18
+ /** The IndexedDB database name; one per app. */
19
+ name: string;
20
+ /** How long the first paint waits for the saved answers before asking the server (ms). Default 1500. */
21
+ restoreMs?: number;
22
+ /** How long writes wait to batch (ms). Default 1000. */
23
+ writeMs?: number;
24
+ }
25
+ export declare function savedAnswers<C = unknown>({ name, restoreMs, writeMs, }: SavedAnswersOptions): SavedAnswersPersister<C>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Saved answers: a react-query persister that keeps an app's answers in IndexedDB, so a page
3
+ * paints at once from the last visit. Pass it to `PersistQueryClientProvider` as `persister`.
4
+ *
5
+ * Nothing waits on the device for good: a read that has not answered within `restoreMs` (another
6
+ * tab holding a write, a broken store) gives up and the app asks the server, and an aborted or
7
+ * blocked store fails instead of hanging. The provider shows nothing until the restore settles,
8
+ * so a hung read would otherwise leave the app blank. Writes go out at most once a second, as one
9
+ * record kept as an object (no JSON round trip).
10
+ */
11
+ const KEY = 'queries';
12
+ export function savedAnswers({ name, restoreMs = 1500, writeMs = 1000, }) {
13
+ // No IndexedDB (a test runner, a locked-down browser): nothing is saved, every load asks the server.
14
+ if (typeof indexedDB === 'undefined')
15
+ return {
16
+ persistClient: () => { },
17
+ restoreClient: async () => undefined,
18
+ removeClient: () => { },
19
+ };
20
+ let db;
21
+ const open = () => {
22
+ if (!db)
23
+ db = new Promise((resolve, reject) => {
24
+ const req = indexedDB.open(name, 1);
25
+ req.onupgradeneeded = () => req.result.createObjectStore('cache');
26
+ req.onsuccess = () => resolve(req.result);
27
+ req.onerror = () => reject(req.error);
28
+ req.onblocked = () => reject(new Error(`${name}: saved answers blocked`));
29
+ }).catch((e) => {
30
+ // A failed open is tried again on the next read or write.
31
+ db = undefined;
32
+ throw e;
33
+ });
34
+ return db;
35
+ };
36
+ const run = (mode, op) => open().then((d) => new Promise((resolve, reject) => {
37
+ const tx = d.transaction('cache', mode);
38
+ const req = op(tx.objectStore('cache'));
39
+ tx.oncomplete = () => resolve(req.result);
40
+ tx.onerror = () => reject(tx.error);
41
+ // An aborted transaction (storage full, the browser gave up) fires only abort, never error.
42
+ tx.onabort = () => reject(tx.error ?? new Error(`${name}: saved answers aborted`));
43
+ }));
44
+ let next;
45
+ let timer;
46
+ return {
47
+ persistClient: (client) => {
48
+ next = client;
49
+ timer ??= setTimeout(() => {
50
+ timer = undefined;
51
+ void run('readwrite', (s) => s.put(next, KEY)).catch(() => { });
52
+ }, writeMs);
53
+ },
54
+ restoreClient: () => Promise.race([
55
+ run('readonly', (s) => s.get(KEY)).catch(() => undefined),
56
+ new Promise((resolve) => setTimeout(() => resolve(undefined), restoreMs)),
57
+ ]),
58
+ removeClient: () => run('readwrite', (s) => s.delete(KEY)).then(() => { }, () => { }),
59
+ };
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.120",
3
+ "version": "0.1.121",
4
4
  "description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -49,7 +49,7 @@
49
49
  "@codemirror/lang-javascript": "6.2.5",
50
50
  "@codemirror/lang-json": "6.0.2",
51
51
  "@codemirror/lang-sql": "6.10.0",
52
- "@columenlabs/lumen-ui-types": "0.1.120",
52
+ "@columenlabs/lumen-ui-types": "0.1.121",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",