@weasel-js/labkit 1.4.4 → 1.5.1
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/README.md +54 -6
- package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CJNMS6mz.d.ts} +1 -1
- package/dist/_dts/{PrefsForm.d-DDHFANkc.d.ts → PrefsForm.d-DPy9I8It.d.ts} +4 -0
- package/dist/_dts/Select.d-Gwr8fLpK.d.ts +453 -0
- package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-B4okC2lX.d.ts} +291 -31
- package/dist/_dts/{index-Blv9uQzu.d.ts → index-BNdz-tNd.d.ts} +105 -17
- package/dist/_dts/index-CSJoj4LC.d.ts +248 -0
- package/dist/_dts/{types-B9_zrHmb.d.ts → types-DzoNY1pO.d.ts} +91 -4
- package/dist/_dts/usePanZoom-Pzf9f2jh.d.ts +160 -0
- package/dist/_dts/useTrialState-uLlmaccn.d.ts +212 -0
- package/dist/canvas/index.d.ts +18 -129
- package/dist/canvas/index.js +4 -2
- package/dist/chrome/index.d.ts +5 -93
- package/dist/chrome/index.js +10 -6
- package/dist/chunk-7PFH3CNM.js +150 -0
- package/dist/chunk-7PFH3CNM.js.map +1 -0
- package/dist/{chunk-I6JCVE24.js → chunk-CEIUBPPW.js} +143 -17
- package/dist/chunk-CEIUBPPW.js.map +1 -0
- package/dist/chunk-EH7UEQV7.js +590 -0
- package/dist/chunk-EH7UEQV7.js.map +1 -0
- package/dist/{chunk-MOM3GOVY.js → chunk-EHGVROVV.js} +125 -27
- package/dist/chunk-EHGVROVV.js.map +1 -0
- package/dist/{chunk-TJ7QY3OC.js → chunk-ERFMC6WU.js} +160 -78
- package/dist/chunk-ERFMC6WU.js.map +1 -0
- package/dist/{chunk-CTRKTLYZ.js → chunk-H6ZAOWNE.js} +11 -11
- package/dist/chunk-H6ZAOWNE.js.map +1 -0
- package/dist/{chunk-64ZCN3DA.js → chunk-IHUVQ3TX.js} +250 -160
- package/dist/chunk-IHUVQ3TX.js.map +1 -0
- package/dist/{chunk-FQMJUVHJ.js → chunk-IJJUK4FU.js} +53 -46
- package/dist/chunk-IJJUK4FU.js.map +1 -0
- package/dist/chunk-IXTQI7JX.js +737 -0
- package/dist/chunk-IXTQI7JX.js.map +1 -0
- package/dist/{chunk-BKFVHKJH.js → chunk-MDVTK5U3.js} +4 -4
- package/dist/{chunk-BKFVHKJH.js.map → chunk-MDVTK5U3.js.map} +1 -1
- package/dist/chunk-PHK4W562.js +72 -0
- package/dist/chunk-PHK4W562.js.map +1 -0
- package/dist/chunk-PJ7WEQBJ.js +18 -0
- package/dist/chunk-PJ7WEQBJ.js.map +1 -0
- package/dist/chunk-RWNDDKOH.js +682 -0
- package/dist/chunk-RWNDDKOH.js.map +1 -0
- package/dist/chunk-T3EC2DXD.js +12628 -0
- package/dist/chunk-T3EC2DXD.js.map +1 -0
- package/dist/{chunk-SMHP6XZ4.js → chunk-UN4U4P63.js} +10 -8
- package/dist/chunk-UN4U4P63.js.map +1 -0
- package/dist/chunk-WIG6XHJ7.js +35 -0
- package/dist/chunk-WIG6XHJ7.js.map +1 -0
- package/dist/chunk-Z76KEZAX.js +15 -0
- package/dist/chunk-Z76KEZAX.js.map +1 -0
- package/dist/chunk-ZDHY47EV.js +363 -0
- package/dist/chunk-ZDHY47EV.js.map +1 -0
- package/dist/config/index.d.ts +276 -0
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -0
- package/dist/controls/index.d.ts +3 -4
- package/dist/controls/index.js +4 -3
- package/dist/dragdrop/index.d.ts +3 -5
- package/dist/index.d.ts +266 -413
- package/dist/index.js +1775 -2251
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +4 -6
- package/dist/layers/index.js +2 -2
- package/dist/loupe/index.d.ts +69 -20
- package/dist/loupe/index.js +2 -2
- package/dist/passthrough/weasel-ui.d.ts +175 -309
- package/dist/passthrough/weasel-ui.js +1 -1
- package/dist/primitives/index.d.ts +55 -6
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +13 -8
- package/dist/state/index.js +18 -13
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +223 -76
- package/dist/surface/index.d.ts +44 -7
- package/dist/surface/index.js +2 -1
- package/dist/ui/layers/index.js +1 -1
- package/dist/undo/index.d.ts +3 -5
- package/package.json +15 -8
- package/src/annotations/AnnotationOverlay.tsx +56 -5
- package/src/annotations/Annotations.less +4 -0
- package/src/annotations/Annotations.overlay.test.tsx +105 -6
- package/src/annotations/drawOne.test.ts +1 -1
- package/src/annotations/drawOne.ts +2 -2
- package/src/annotations/paint.test.ts +2 -2
- package/src/annotations/paint.ts +1 -1
- package/src/annotations/preload.ts +6 -0
- package/src/annotations/store.test.ts +18 -0
- package/src/annotations/store.ts +5 -4
- package/src/annotations/svgNodes.test.ts +2 -2
- package/src/annotations/svgNodes.ts +1 -0
- package/src/annotations/toolMap.test.ts +9 -5
- package/src/annotations/toolMap.ts +6 -3
- package/src/annotations/types.ts +6 -3
- package/src/canvas/CameraWheelContext.ts +12 -0
- package/src/canvas/CanvasStack.stories.tsx +1 -1
- package/src/canvas/CanvasStack.test.tsx +25 -0
- package/src/canvas/CanvasStack.tsx +31 -4
- package/src/canvas/Stage.less +18 -0
- package/src/canvas/Stage.test.tsx +186 -0
- package/src/canvas/Stage.tsx +134 -0
- package/src/canvas/camera.test.ts +17 -2
- package/src/canvas/camera.ts +9 -9
- package/src/canvas/index.ts +4 -0
- package/src/canvas/usePanZoom.test.ts +28 -0
- package/src/canvas/usePanZoom.ts +21 -9
- package/src/chrome/ChromeRegions.stories.tsx +1 -4
- package/src/chrome/LabChrome.tsx +130 -0
- package/src/chrome/builtins.test.ts +1 -0
- package/src/chrome/builtins.tsx +21 -31
- package/src/chrome/index.ts +16 -0
- package/src/chrome/labRegions.test.tsx +208 -0
- package/src/chrome/labTypes.ts +50 -0
- package/src/chrome/merge.ts +7 -9
- package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
- package/src/chrome/regions/PaletteRegion.tsx +16 -9
- package/src/chrome/regions/SidebarRegion.stories.tsx +44 -0
- package/src/chrome/regions/SidebarRegion.tsx +25 -13
- package/src/chrome/regions/StatusRegion.tsx +15 -8
- package/src/chrome/regions/ToolbarRegion.tsx +34 -16
- package/src/chrome/types.ts +50 -20
- package/src/config/auto.test.ts +12 -0
- package/src/config/auto.ts +15 -0
- package/src/config/autoConfig.test.ts +107 -0
- package/src/config/autoConfig.ts +153 -0
- package/src/config/builder.test.ts +42 -0
- package/src/config/builder.ts +68 -17
- package/src/config/declarationEmit.fixture.ts +11 -0
- package/src/config/declarationEmit.test.ts +51 -0
- package/src/config/entry.test.ts +46 -0
- package/src/config/index.ts +16 -1
- package/src/config/nodeClasses.test.ts +18 -0
- package/src/config/path.ts +7 -2
- package/src/config/resolve.test.ts +25 -0
- package/src/config/resolve.ts +17 -1
- package/src/config/types.ts +34 -2
- package/src/config/useResolvedConfig.ts +26 -0
- package/src/controls/AutoControls.stories.tsx +88 -0
- package/src/controls/ControlPanel.stories.tsx +42 -1
- package/src/controls/ControlPanel.test.tsx +530 -4
- package/src/controls/ControlPanel.tsx +562 -82
- package/src/fake-indexeddb-auto.d.ts +5 -0
- package/src/index.test.ts +7 -0
- package/src/index.ts +66 -5
- package/src/instrument/serializers.test.ts +22 -0
- package/src/instrument/serializers.ts +40 -1
- package/src/instrument/types.ts +25 -1
- package/src/lab/Lab.chrome.test.tsx +16 -0
- package/src/lab/Lab.less +4 -13
- package/src/lab/Lab.persist.test.tsx +147 -0
- package/src/lab/Lab.stories.tsx +1 -3
- package/src/lab/Lab.surface.test.tsx +71 -3
- package/src/lab/Lab.test.tsx +168 -16
- package/src/lab/Lab.tsx +434 -129
- package/src/lab/LabContext.ts +8 -0
- package/src/lab/LabFit.stories.less +18 -0
- package/src/lab/LabFit.stories.tsx +219 -0
- package/src/lab/LabFullChrome.stories.tsx +1 -4
- package/src/lab/LabHeader.test.tsx +40 -9
- package/src/lab/LabHeader.tsx +7 -14
- package/src/lab/LabPalette.tsx +13 -26
- package/src/lab/LabRoot.test.tsx +56 -0
- package/src/lab/LabRoot.tsx +47 -0
- package/src/lab/LabShell.less +73 -15
- package/src/lab/LabShell.stories.tsx +50 -1
- package/src/lab/LabShell.tsx +18 -27
- package/src/lab/LabSwitcher.less +18 -11
- package/src/lab/LabSwitcher.test.tsx +14 -9
- package/src/lab/LabSwitcher.tsx +8 -4
- package/src/lab/Workspace.less +1 -1
- package/src/lab/Workspace.stories.tsx +1 -1
- package/src/lab/Workspace.surface.test.tsx +1 -0
- package/src/lab/fitCheck.test.ts +57 -0
- package/src/lab/fitCheck.ts +125 -0
- package/src/lab/index.ts +2 -0
- package/src/layers/LayerList.stories.tsx +1 -1
- package/src/loupe/AGENTS.md +17 -5
- package/src/loupe/Loupe.less +1 -1
- package/src/loupe/LoupeGestures.tsx +67 -0
- package/src/loupe/TrialLoupe.tsx +43 -28
- package/src/loupe/canvasLens.ts +1 -1
- package/src/loupe/index.ts +4 -0
- package/src/loupe/loupeActions.ts +98 -0
- package/src/loupe/types.ts +1 -1
- package/src/loupe/useLoupe.test.tsx +60 -3
- package/src/loupe/useLoupe.ts +29 -49
- package/src/passthrough/weasel-ui.ts +9 -0
- package/src/primitives/FloatingPanel.stories.tsx +14 -2
- package/src/primitives/FloatingPanel.test.tsx +47 -16
- package/src/primitives/FloatingPanel.tsx +11 -25
- package/src/primitives/FpsMeter.stories.tsx +1 -1
- package/src/primitives/JobProgress.stories.tsx +1 -1
- package/src/primitives/Legend.stories.tsx +1 -1
- package/src/primitives/ScaleIndicator.stories.tsx +1 -1
- package/src/primitives/Sidebar.less +7 -0
- package/src/primitives/Sidebar.stories.tsx +1 -1
- package/src/primitives/Split.less +14 -0
- package/src/primitives/Split.test.tsx +133 -0
- package/src/primitives/Split.tsx +175 -0
- package/src/primitives/StatusBar.stories.tsx +1 -1
- package/src/primitives/Toolbar.stories.tsx +1 -1
- package/src/primitives/ZoomControl.tsx +5 -2
- package/src/primitives/index.ts +2 -0
- package/src/specimen/Specimen.less +95 -0
- package/src/specimen/Specimen.stories.tsx +13 -0
- package/src/specimen/Specimen.test.tsx +13 -0
- package/src/specimen/Specimen.tsx +888 -0
- package/src/state/Persistence.stories.tsx +75 -0
- package/src/state/Persistence.tsx +33 -0
- package/src/state/SingletonExperiment.test.tsx +47 -53
- package/src/state/SingletonExperiment.tsx +24 -13
- package/src/state/adapterContract.ts +116 -0
- package/src/state/adapters.test.ts +113 -61
- package/src/state/adapters.ts +328 -69
- package/src/state/document.test.ts +71 -96
- package/src/state/document.ts +66 -52
- package/src/state/helpers.test.ts +1 -1
- package/src/state/index.ts +8 -0
- package/src/state/labRecords.test.ts +105 -0
- package/src/state/labRecords.ts +160 -0
- package/src/state/openLabStore.test.ts +658 -0
- package/src/state/openLabStore.ts +403 -0
- package/src/state/records.test.ts +234 -0
- package/src/state/records.ts +219 -0
- package/src/state/store.test.ts +169 -635
- package/src/state/store.ts +146 -163
- package/src/state/toolSlot.test.ts +1 -1
- package/src/state/types.ts +65 -17
- package/src/state/useOpenOnce.ts +68 -0
- package/src/state/usePersistedState.test.tsx +125 -0
- package/src/state/usePersistedState.ts +66 -0
- package/src/state/useTrialState.test.tsx +87 -3
- package/src/state/useTrialState.ts +9 -1
- package/src/state/view.test.ts +49 -15
- package/src/state/view.ts +18 -1
- package/src/styles.less +3 -0
- package/src/surface/AGENTS.md +39 -7
- package/src/surface/SurfaceContext.ts +26 -3
- package/src/surface/index.ts +2 -0
- package/src/surface/useSurfaceTile.test.tsx +1 -0
- package/src/surface/useSurfaceTile.ts +6 -5
- package/src/surface/useTiledSurface.test.tsx +231 -1
- package/src/surface/useTiledSurface.ts +76 -15
- package/src/theme/Interstellar.stories.tsx +9 -9
- package/src/theme/interstellar.test.ts +17 -6
- package/src/theme/interstellar.theme.json +180 -0
- package/src/theme/interstellar.ts +8 -5
- package/src/tools/labTool.ts +11 -0
- package/src/trial/Trial.annotations.persist.test.tsx +100 -58
- package/src/trial/Trial.annotations.test.tsx +59 -7
- package/src/trial/Trial.canvas.test.tsx +32 -0
- package/src/trial/Trial.config.test.tsx +75 -2
- package/src/trial/Trial.job.test.tsx +3 -3
- package/src/trial/Trial.less +5 -26
- package/src/trial/Trial.stories.tsx +5 -3
- package/src/trial/Trial.test.tsx +31 -4
- package/src/trial/Trial.trialId.test.tsx +23 -1
- package/src/trial/Trial.tsx +139 -52
- package/src/trial/TrialBody.tsx +17 -141
- package/src/trial/TrialChrome.tsx +24 -7
- package/src/trial/trialOps.test.ts +101 -1
- package/src/trial/trialOps.ts +67 -7
- package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
- package/dist/_dts/types-x92Kfeme.d.ts +0 -62
- package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
- package/dist/chunk-2P6PP5N4.js +0 -553
- package/dist/chunk-2P6PP5N4.js.map +0 -1
- package/dist/chunk-64ZCN3DA.js.map +0 -1
- package/dist/chunk-CTRKTLYZ.js.map +0 -1
- package/dist/chunk-E44SU6XS.js +0 -50
- package/dist/chunk-E44SU6XS.js.map +0 -1
- package/dist/chunk-FQMJUVHJ.js.map +0 -1
- package/dist/chunk-I6JCVE24.js.map +0 -1
- package/dist/chunk-MOM3GOVY.js.map +0 -1
- package/dist/chunk-SMHP6XZ4.js.map +0 -1
- package/dist/chunk-TJ7QY3OC.js.map +0 -1
- package/dist/chunk-U3IYHIAE.js +0 -343
- package/dist/chunk-U3IYHIAE.js.map +0 -1
- package/dist/chunk-W3ECWC2K.js +0 -7335
- package/dist/chunk-W3ECWC2K.js.map +0 -1
- package/src/theme/interstellar.tokens.json +0 -61
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { StorageAdapter, StorageChange } from './types';
|
|
2
|
+
|
|
3
|
+
/** One record changing, as a cache listener hears it. `value` is `undefined`
|
|
4
|
+
* for a delete. `local` changes were made through this cache; `remote` ones
|
|
5
|
+
* came from another writer. */
|
|
6
|
+
export interface RecordChange {
|
|
7
|
+
name: string;
|
|
8
|
+
value: unknown;
|
|
9
|
+
origin: 'local' | 'remote';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Every record under one prefix, held in memory: the only thing that talks to
|
|
14
|
+
* a `StorageAdapter`. Names are keys with the prefix removed.
|
|
15
|
+
*
|
|
16
|
+
* Writes land in memory at once and reach storage on a debounce. The newest
|
|
17
|
+
* write to a record wins: another writer's change to a record with a write
|
|
18
|
+
* still queued here is ignored, since the queued write lands after it.
|
|
19
|
+
*/
|
|
20
|
+
export interface RecordCache {
|
|
21
|
+
readonly prefix: string;
|
|
22
|
+
/** False once writing is off — the records could not be read, or were
|
|
23
|
+
* written by a newer labkit — so nothing here can overwrite them. */
|
|
24
|
+
readonly writable: boolean;
|
|
25
|
+
has(name: string): boolean;
|
|
26
|
+
get(name: string): unknown;
|
|
27
|
+
/** Every record whose name starts with `namePrefix`. */
|
|
28
|
+
entries(namePrefix?: string): [string, unknown][];
|
|
29
|
+
set(name: string, value: unknown): void;
|
|
30
|
+
delete(name: string): void;
|
|
31
|
+
subscribe(listener: (changes: RecordChange[]) => void): () => void;
|
|
32
|
+
/** Send queued writes now. */
|
|
33
|
+
flush(): Promise<void>;
|
|
34
|
+
/** Send queued writes and stop hearing other writers. */
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The parts of a cache only its opener uses. */
|
|
39
|
+
export interface OwnedRecordCache extends RecordCache {
|
|
40
|
+
/** Hold these records without writing them back. */
|
|
41
|
+
seed(entries: Iterable<[string, unknown]>): void;
|
|
42
|
+
stopWriting(): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Options for `createRecordCache` and `openRecords`. */
|
|
46
|
+
export interface RecordCacheOptions {
|
|
47
|
+
storage: StorageAdapter;
|
|
48
|
+
prefix: string;
|
|
49
|
+
/** Trailing debounce on writes. Default 300 ms. */
|
|
50
|
+
debounceMs?: number;
|
|
51
|
+
/** The longest a write waits under continuous change. Default 1000 ms. */
|
|
52
|
+
maxWaitMs?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const DELETED = Symbol('deleted');
|
|
56
|
+
|
|
57
|
+
/** A cache holding `initial`, with nothing read from storage. */
|
|
58
|
+
export function createRecordCache(
|
|
59
|
+
options: RecordCacheOptions & { initial?: Iterable<[string, unknown]>; writable?: boolean },
|
|
60
|
+
): OwnedRecordCache {
|
|
61
|
+
const { storage, prefix } = options;
|
|
62
|
+
const debounceMs = options.debounceMs ?? 300;
|
|
63
|
+
const maxWaitMs = options.maxWaitMs ?? 1000;
|
|
64
|
+
const values = new Map<string, unknown>(options.initial ?? []);
|
|
65
|
+
const queued = new Map<string, unknown>();
|
|
66
|
+
const listeners = new Set<(changes: RecordChange[]) => void>();
|
|
67
|
+
let writable = options.writable ?? true;
|
|
68
|
+
let closed = false;
|
|
69
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
70
|
+
let firstQueuedAt: number | null = null;
|
|
71
|
+
|
|
72
|
+
const emit = (changes: RecordChange[]): void => {
|
|
73
|
+
for (const listener of [...listeners]) listener(changes);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const schedule = (): void => {
|
|
77
|
+
if (!writable || closed) return;
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
firstQueuedAt ??= now;
|
|
80
|
+
if (timer) clearTimeout(timer);
|
|
81
|
+
const wait = Math.max(0, Math.min(debounceMs, firstQueuedAt + maxWaitMs - now));
|
|
82
|
+
timer = setTimeout(() => {
|
|
83
|
+
timer = null;
|
|
84
|
+
void flush();
|
|
85
|
+
}, wait);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
async function flush(): Promise<void> {
|
|
89
|
+
if (timer) clearTimeout(timer);
|
|
90
|
+
timer = null;
|
|
91
|
+
firstQueuedAt = null;
|
|
92
|
+
if (!writable || queued.size === 0) return;
|
|
93
|
+
const batch = [...queued];
|
|
94
|
+
queued.clear();
|
|
95
|
+
await Promise.all(
|
|
96
|
+
batch.map(async ([name, value]) => {
|
|
97
|
+
const key = prefix + name;
|
|
98
|
+
try {
|
|
99
|
+
if (value === DELETED) await storage.delete(key);
|
|
100
|
+
else await storage.set(key, value);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.warn(`[labkit] could not write "${key}"; keeping it in memory`, error);
|
|
103
|
+
}
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const applyRemote = (changes: StorageChange[]): void => {
|
|
109
|
+
const applied: RecordChange[] = [];
|
|
110
|
+
for (const [key, value] of changes) {
|
|
111
|
+
if (!key.startsWith(prefix)) continue;
|
|
112
|
+
const name = key.slice(prefix.length);
|
|
113
|
+
if (queued.has(name)) continue;
|
|
114
|
+
if (value === undefined) {
|
|
115
|
+
if (!values.has(name)) continue;
|
|
116
|
+
values.delete(name);
|
|
117
|
+
} else {
|
|
118
|
+
values.set(name, value);
|
|
119
|
+
}
|
|
120
|
+
applied.push({ name, value, origin: 'remote' });
|
|
121
|
+
}
|
|
122
|
+
if (applied.length > 0) emit(applied);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const unsubscribe = writable ? storage.subscribe?.(prefix, applyRemote) : undefined;
|
|
126
|
+
|
|
127
|
+
const onPageHide = (): void => {
|
|
128
|
+
void flush();
|
|
129
|
+
};
|
|
130
|
+
const onVisibility = (): void => {
|
|
131
|
+
if (document.visibilityState === 'hidden') void flush();
|
|
132
|
+
};
|
|
133
|
+
if (typeof window !== 'undefined') {
|
|
134
|
+
window.addEventListener('pagehide', onPageHide);
|
|
135
|
+
document.addEventListener('visibilitychange', onVisibility);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const cache: OwnedRecordCache & { applyRemote: typeof applyRemote } = {
|
|
139
|
+
prefix,
|
|
140
|
+
get writable() {
|
|
141
|
+
return writable;
|
|
142
|
+
},
|
|
143
|
+
has: (name) => values.has(name),
|
|
144
|
+
get: (name) => values.get(name),
|
|
145
|
+
entries: (namePrefix = '') => [...values].filter(([name]) => name.startsWith(namePrefix)),
|
|
146
|
+
set: (name, value) => {
|
|
147
|
+
values.set(name, value);
|
|
148
|
+
queued.set(name, value);
|
|
149
|
+
emit([{ name, value, origin: 'local' }]);
|
|
150
|
+
schedule();
|
|
151
|
+
},
|
|
152
|
+
delete: (name) => {
|
|
153
|
+
if (!values.has(name) && !queued.has(name)) return;
|
|
154
|
+
values.delete(name);
|
|
155
|
+
queued.set(name, DELETED);
|
|
156
|
+
emit([{ name, value: undefined, origin: 'local' }]);
|
|
157
|
+
schedule();
|
|
158
|
+
},
|
|
159
|
+
subscribe: (listener) => {
|
|
160
|
+
listeners.add(listener);
|
|
161
|
+
return () => {
|
|
162
|
+
listeners.delete(listener);
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
flush,
|
|
166
|
+
close: async () => {
|
|
167
|
+
if (closed) return;
|
|
168
|
+
await flush();
|
|
169
|
+
closed = true;
|
|
170
|
+
unsubscribe?.();
|
|
171
|
+
if (typeof window !== 'undefined') {
|
|
172
|
+
window.removeEventListener('pagehide', onPageHide);
|
|
173
|
+
document.removeEventListener('visibilitychange', onVisibility);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
seed: (entries) => {
|
|
177
|
+
for (const [name, value] of entries) values.set(name, value);
|
|
178
|
+
},
|
|
179
|
+
stopWriting: () => {
|
|
180
|
+
writable = false;
|
|
181
|
+
queued.clear();
|
|
182
|
+
if (timer) clearTimeout(timer);
|
|
183
|
+
timer = null;
|
|
184
|
+
unsubscribe?.();
|
|
185
|
+
},
|
|
186
|
+
applyRemote,
|
|
187
|
+
};
|
|
188
|
+
return cache;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Read every record under `prefix` and hold them. A failed read opens the
|
|
192
|
+
* cache empty with writing off, so it never overwrites what it could not
|
|
193
|
+
* read. */
|
|
194
|
+
export async function openRecords(options: RecordCacheOptions): Promise<OwnedRecordCache> {
|
|
195
|
+
// Listen before listing, so a change landing between the two is not lost.
|
|
196
|
+
const early: StorageChange[] = [];
|
|
197
|
+
const stopEarly = options.storage.subscribe?.(options.prefix, (c) => early.push(...c));
|
|
198
|
+
let listed: [string, unknown][];
|
|
199
|
+
try {
|
|
200
|
+
listed = await options.storage.list(options.prefix);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
stopEarly?.();
|
|
203
|
+
console.warn(
|
|
204
|
+
`[labkit] could not read "${options.prefix}"; opening empty and not persisting`,
|
|
205
|
+
error,
|
|
206
|
+
);
|
|
207
|
+
return createRecordCache({ ...options, writable: false });
|
|
208
|
+
}
|
|
209
|
+
const cache = createRecordCache({
|
|
210
|
+
...options,
|
|
211
|
+
initial: listed.map(([key, value]): [string, unknown] => [
|
|
212
|
+
key.slice(options.prefix.length),
|
|
213
|
+
value,
|
|
214
|
+
]),
|
|
215
|
+
});
|
|
216
|
+
stopEarly?.();
|
|
217
|
+
(cache as unknown as { applyRemote: (c: StorageChange[]) => void }).applyRemote(early);
|
|
218
|
+
return cache;
|
|
219
|
+
}
|