@interop/was-react 0.1.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/LICENSE.md +20 -0
- package/README.md +410 -0
- package/dist/auth/chapi.d.ts +43 -0
- package/dist/auth/chapi.d.ts.map +1 -0
- package/dist/auth/chapi.js +107 -0
- package/dist/auth/chapi.js.map +1 -0
- package/dist/auth/loginFlow.d.ts +93 -0
- package/dist/auth/loginFlow.d.ts.map +1 -0
- package/dist/auth/loginFlow.js +149 -0
- package/dist/auth/loginFlow.js.map +1 -0
- package/dist/auth/loginRequest.d.ts +66 -0
- package/dist/auth/loginRequest.d.ts.map +1 -0
- package/dist/auth/loginRequest.js +83 -0
- package/dist/auth/loginRequest.js.map +1 -0
- package/dist/auth/verifyResponse.d.ts +52 -0
- package/dist/auth/verifyResponse.d.ts.map +1 -0
- package/dist/auth/verifyResponse.js +138 -0
- package/dist/auth/verifyResponse.js.map +1 -0
- package/dist/auth/verifyResponse.test.d.ts +2 -0
- package/dist/auth/verifyResponse.test.d.ts.map +1 -0
- package/dist/auth/verifyResponse.test.js +282 -0
- package/dist/auth/verifyResponse.test.js.map +1 -0
- package/dist/auth/walletRequestTypes.d.ts +143 -0
- package/dist/auth/walletRequestTypes.d.ts.map +1 -0
- package/dist/auth/walletRequestTypes.js +2 -0
- package/dist/auth/walletRequestTypes.js.map +1 -0
- package/dist/config.d.ts +150 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +27 -0
- package/dist/config.js.map +1 -0
- package/dist/dev/cli.d.ts +16 -0
- package/dist/dev/cli.d.ts.map +1 -0
- package/dist/dev/cli.js +122 -0
- package/dist/dev/cli.js.map +1 -0
- package/dist/dev/cli.test.d.ts +2 -0
- package/dist/dev/cli.test.d.ts.map +1 -0
- package/dist/dev/cli.test.js +39 -0
- package/dist/dev/cli.test.js.map +1 -0
- package/dist/dev/index.d.ts +10 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +10 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/provisionDevGrants.d.ts +73 -0
- package/dist/dev/provisionDevGrants.d.ts.map +1 -0
- package/dist/dev/provisionDevGrants.js +176 -0
- package/dist/dev/provisionDevGrants.js.map +1 -0
- package/dist/grants.d.ts +59 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +82 -0
- package/dist/grants.js.map +1 -0
- package/dist/grants.test.d.ts +2 -0
- package/dist/grants.test.d.ts.map +1 -0
- package/dist/grants.test.js +79 -0
- package/dist/grants.test.js.map +1 -0
- package/dist/identity/agents.d.ts +99 -0
- package/dist/identity/agents.d.ts.map +1 -0
- package/dist/identity/agents.js +132 -0
- package/dist/identity/agents.js.map +1 -0
- package/dist/identity/appSession.d.ts +78 -0
- package/dist/identity/appSession.d.ts.map +1 -0
- package/dist/identity/appSession.js +93 -0
- package/dist/identity/appSession.js.map +1 -0
- package/dist/identity/documentLoader.d.ts +20 -0
- package/dist/identity/documentLoader.d.ts.map +1 -0
- package/dist/identity/documentLoader.js +99 -0
- package/dist/identity/documentLoader.js.map +1 -0
- package/dist/identity/initAppSession.d.ts +27 -0
- package/dist/identity/initAppSession.d.ts.map +1 -0
- package/dist/identity/initAppSession.js +30 -0
- package/dist/identity/initAppSession.js.map +1 -0
- package/dist/identity/seedCredential.d.ts +74 -0
- package/dist/identity/seedCredential.d.ts.map +1 -0
- package/dist/identity/seedCredential.js +165 -0
- package/dist/identity/seedCredential.js.map +1 -0
- package/dist/identity/seedCredential.test.d.ts +2 -0
- package/dist/identity/seedCredential.test.d.ts.map +1 -0
- package/dist/identity/seedCredential.test.js +205 -0
- package/dist/identity/seedCredential.test.js.map +1 -0
- package/dist/identity/seedStore.d.ts +27 -0
- package/dist/identity/seedStore.d.ts.map +1 -0
- package/dist/identity/seedStore.js +74 -0
- package/dist/identity/seedStore.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/mui/ProtectedRoute.d.ts +10 -0
- package/dist/mui/ProtectedRoute.d.ts.map +1 -0
- package/dist/mui/ProtectedRoute.js +54 -0
- package/dist/mui/ProtectedRoute.js.map +1 -0
- package/dist/mui/ReconnectBanner.d.ts +2 -0
- package/dist/mui/ReconnectBanner.d.ts.map +1 -0
- package/dist/mui/ReconnectBanner.js +20 -0
- package/dist/mui/ReconnectBanner.js.map +1 -0
- package/dist/mui/SyncStatusChip.d.ts +2 -0
- package/dist/mui/SyncStatusChip.d.ts.map +1 -0
- package/dist/mui/SyncStatusChip.js +27 -0
- package/dist/mui/SyncStatusChip.js.map +1 -0
- package/dist/mui/index.d.ts +12 -0
- package/dist/mui/index.d.ts.map +1 -0
- package/dist/mui/index.js +12 -0
- package/dist/mui/index.js.map +1 -0
- package/dist/react/WasSessionProvider.d.ts +36 -0
- package/dist/react/WasSessionProvider.d.ts.map +1 -0
- package/dist/react/WasSessionProvider.js +35 -0
- package/dist/react/WasSessionProvider.js.map +1 -0
- package/dist/react/hooks.d.ts +66 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +121 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/index.d.ts +11 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +11 -0
- package/dist/react/index.js.map +1 -0
- package/dist/session/appReadyStore.d.ts +10 -0
- package/dist/session/appReadyStore.d.ts.map +1 -0
- package/dist/session/appReadyStore.js +22 -0
- package/dist/session/appReadyStore.js.map +1 -0
- package/dist/session/authStore.d.ts +71 -0
- package/dist/session/authStore.d.ts.map +1 -0
- package/dist/session/authStore.js +347 -0
- package/dist/session/authStore.js.map +1 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +10 -0
- package/dist/session/index.js.map +1 -0
- package/dist/storage/entityStore.d.ts +51 -0
- package/dist/storage/entityStore.d.ts.map +1 -0
- package/dist/storage/entityStore.js +78 -0
- package/dist/storage/entityStore.js.map +1 -0
- package/dist/storage/localStore.d.ts +144 -0
- package/dist/storage/localStore.d.ts.map +1 -0
- package/dist/storage/localStore.js +289 -0
- package/dist/storage/localStore.js.map +1 -0
- package/dist/storage/rehydrate.d.ts +56 -0
- package/dist/storage/rehydrate.d.ts.map +1 -0
- package/dist/storage/rehydrate.js +87 -0
- package/dist/storage/rehydrate.js.map +1 -0
- package/dist/storage/rehydrate.test.d.ts +2 -0
- package/dist/storage/rehydrate.test.d.ts.map +1 -0
- package/dist/storage/rehydrate.test.js +172 -0
- package/dist/storage/rehydrate.test.js.map +1 -0
- package/dist/storage/storageManager.d.ts +39 -0
- package/dist/storage/storageManager.d.ts.map +1 -0
- package/dist/storage/storageManager.js +68 -0
- package/dist/storage/storageManager.js.map +1 -0
- package/dist/storage/syncController.d.ts +79 -0
- package/dist/storage/syncController.d.ts.map +1 -0
- package/dist/storage/syncController.js +189 -0
- package/dist/storage/syncController.js.map +1 -0
- package/dist/storage/syncStatusStore.d.ts +17 -0
- package/dist/storage/syncStatusStore.d.ts.map +1 -0
- package/dist/storage/syncStatusStore.js +19 -0
- package/dist/storage/syncStatusStore.js.map +1 -0
- package/dist/storage/wasRemoteStore.d.ts +73 -0
- package/dist/storage/wasRemoteStore.d.ts.map +1 -0
- package/dist/storage/wasRemoteStore.js +76 -0
- package/dist/storage/wasRemoteStore.js.map +1 -0
- package/dist/storage/wasSync.d.ts +42 -0
- package/dist/storage/wasSync.d.ts.map +1 -0
- package/dist/storage/wasSync.js +34 -0
- package/dist/storage/wasSync.js.map +1 -0
- package/dist/sync/changesQuery.d.ts +44 -0
- package/dist/sync/changesQuery.d.ts.map +1 -0
- package/dist/sync/changesQuery.js +61 -0
- package/dist/sync/changesQuery.js.map +1 -0
- package/dist/sync/changesQuery.test.d.ts +2 -0
- package/dist/sync/changesQuery.test.d.ts.map +1 -0
- package/dist/sync/changesQuery.test.js +145 -0
- package/dist/sync/changesQuery.test.js.map +1 -0
- package/dist/sync/docCipher.d.ts +68 -0
- package/dist/sync/docCipher.d.ts.map +1 -0
- package/dist/sync/docCipher.js +89 -0
- package/dist/sync/docCipher.js.map +1 -0
- package/dist/sync/feedMasterPort.d.ts +30 -0
- package/dist/sync/feedMasterPort.d.ts.map +1 -0
- package/dist/sync/feedMasterPort.js +58 -0
- package/dist/sync/feedMasterPort.js.map +1 -0
- package/dist/sync/index.d.ts +21 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +21 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/lww.d.ts +29 -0
- package/dist/sync/lww.d.ts.map +1 -0
- package/dist/sync/lww.js +28 -0
- package/dist/sync/lww.js.map +1 -0
- package/dist/sync/lww.test.d.ts +2 -0
- package/dist/sync/lww.test.d.ts.map +1 -0
- package/dist/sync/lww.test.js +28 -0
- package/dist/sync/lww.test.js.map +1 -0
- package/dist/sync/lwwConflictHandler.d.ts +49 -0
- package/dist/sync/lwwConflictHandler.d.ts.map +1 -0
- package/dist/sync/lwwConflictHandler.js +65 -0
- package/dist/sync/lwwConflictHandler.js.map +1 -0
- package/dist/sync/lwwConflictHandler.test.d.ts +2 -0
- package/dist/sync/lwwConflictHandler.test.d.ts.map +1 -0
- package/dist/sync/lwwConflictHandler.test.js +78 -0
- package/dist/sync/lwwConflictHandler.test.js.map +1 -0
- package/dist/sync/pushWrites.d.ts +57 -0
- package/dist/sync/pushWrites.d.ts.map +1 -0
- package/dist/sync/pushWrites.js +159 -0
- package/dist/sync/pushWrites.js.map +1 -0
- package/dist/sync/pushWrites.test.d.ts +2 -0
- package/dist/sync/pushWrites.test.d.ts.map +1 -0
- package/dist/sync/pushWrites.test.js +287 -0
- package/dist/sync/pushWrites.test.js.map +1 -0
- package/dist/sync/syncedDocSchema.d.ts +22 -0
- package/dist/sync/syncedDocSchema.d.ts.map +1 -0
- package/dist/sync/syncedDocSchema.js +26 -0
- package/dist/sync/syncedDocSchema.js.map +1 -0
- package/dist/sync/types.d.ts +194 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +36 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/sync/wasReplication.d.ts +47 -0
- package/dist/sync/wasReplication.d.ts.map +1 -0
- package/dist/sync/wasReplication.js +55 -0
- package/dist/sync/wasReplication.js.map +1 -0
- package/dist/sync/wasSyncPort.d.ts +45 -0
- package/dist/sync/wasSyncPort.d.ts.map +1 -0
- package/dist/sync/wasSyncPort.js +170 -0
- package/dist/sync/wasSyncPort.js.map +1 -0
- package/package.json +142 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The app-ready gate: a tiny shared zustand store the auth store flips once the
|
|
6
|
+
* session seed has opened the local store and hydrated every entity store. The
|
|
7
|
+
* router gate (`ProtectedRoute` / the `useAppReady` hook) waits on it before
|
|
8
|
+
* rendering the app.
|
|
9
|
+
*
|
|
10
|
+
* This is only the ready-flag store extracted from the app-side bootstrap; the
|
|
11
|
+
* dev-mode `initApp` open/hydrate driver stays app-side. In wallet mode the auth
|
|
12
|
+
* store owns the open/hydrate ordering and reuses this same gate.
|
|
13
|
+
*/
|
|
14
|
+
import { create } from 'zustand';
|
|
15
|
+
export const useAppReady = create(set => ({
|
|
16
|
+
ready: false,
|
|
17
|
+
error: null,
|
|
18
|
+
setReady: () => set({ ready: true, error: null }),
|
|
19
|
+
setError: message => set({ error: message }),
|
|
20
|
+
reset: () => set({ ready: false, error: null })
|
|
21
|
+
}));
|
|
22
|
+
//# sourceMappingURL=appReadyStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appReadyStore.js","sourceRoot":"","sources":["../../src/session/appReadyStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;GASG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAUhC,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAgB,GAAG,CAAC,EAAE,CAAC,CAAC;IACvD,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,IAAI;IACX,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACjD,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC5C,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;CAChD,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The wallet-mode auth store (zustand): owns the session lifecycle -- hot
|
|
6
|
+
* restore (zero popups), Login With Wallet, logout, and the expired-access
|
|
7
|
+
* reconnect -- plus the open/hydrate/sync ordering.
|
|
8
|
+
*
|
|
9
|
+
* On a successful login/restore it opens the encrypted {@link LocalStore} from
|
|
10
|
+
* the session seed (a per-controller database name, so two wallet users on one
|
|
11
|
+
* browser never collide), hydrates the entity stores, flips the shared
|
|
12
|
+
* `useAppReady` gate, and starts WAS replication from the granted zcaps.
|
|
13
|
+
*
|
|
14
|
+
* `status` is the router gate: `ProtectedRoute` waits for the restore attempt to
|
|
15
|
+
* settle before deciding between the app and the login page.
|
|
16
|
+
*
|
|
17
|
+
* The library cannot bind a module-level store to app config, so this is a
|
|
18
|
+
* FACTORY: {@link createAuthStore} captures the app's {@link WasAppConfig} and
|
|
19
|
+
* {@link StoreRegistry} once (the React provider calls it once) and returns a
|
|
20
|
+
* vanilla zustand store the hooks consume through context.
|
|
21
|
+
*/
|
|
22
|
+
import { type StoreApi } from 'zustand/vanilla';
|
|
23
|
+
import type { RxStorage } from 'rxdb/plugins/core';
|
|
24
|
+
import { type StoreRegistry, type WasAppConfig } from '../config.js';
|
|
25
|
+
import { type SeedStore } from '../identity/seedStore.js';
|
|
26
|
+
import { type LoginPhase } from '../auth/loginFlow.js';
|
|
27
|
+
export type AuthStatus = 'idle' | 'restoring' | 'unauthenticated' | 'authenticating' | 'authenticated';
|
|
28
|
+
export interface AuthState {
|
|
29
|
+
status: AuthStatus;
|
|
30
|
+
/** The current login phase, for the login page's progress line. */
|
|
31
|
+
phase: LoginPhase | null;
|
|
32
|
+
error: string | null;
|
|
33
|
+
controllerDid: string | null;
|
|
34
|
+
/** ISO expiry of the current grant set (earliest zcap expiry). */
|
|
35
|
+
expires: string | null;
|
|
36
|
+
/** A live 401/403 was seen mid-session: show the reconnect banner. */
|
|
37
|
+
accessExpired: boolean;
|
|
38
|
+
reconnecting: boolean;
|
|
39
|
+
/** Hot restore from the persisted session; falls to `unauthenticated`. */
|
|
40
|
+
restore: () => Promise<void>;
|
|
41
|
+
/** Full Login With Wallet (first-run or returning). */
|
|
42
|
+
login: () => Promise<void>;
|
|
43
|
+
/** Re-run the grants flow with the existing seed (expired access). */
|
|
44
|
+
reconnect: () => Promise<void>;
|
|
45
|
+
logout: () => Promise<void>;
|
|
46
|
+
notifyAccessExpired: () => void;
|
|
47
|
+
}
|
|
48
|
+
/** The vanilla zustand store returned by {@link createAuthStore}. */
|
|
49
|
+
export type WasAuthStore = StoreApi<AuthState>;
|
|
50
|
+
/**
|
|
51
|
+
* Builds the wallet-mode auth store bound to an app's config and store registry.
|
|
52
|
+
* Call once (the React provider does) and share the returned store through
|
|
53
|
+
* context; the hooks read it via `useStore`.
|
|
54
|
+
*
|
|
55
|
+
* @param options {object}
|
|
56
|
+
* @param options.config {WasAppConfig} the app-wide configuration
|
|
57
|
+
* @param options.registry {StoreRegistry} the per-collection hydrate/patch
|
|
58
|
+
* handlers the rehydrate mechanism drives
|
|
59
|
+
* @param [options.seedStore] {SeedStore} the session IndexedDB persistence
|
|
60
|
+
* (defaults to one bound to `<dbName>-session`; inject a fake for tests)
|
|
61
|
+
* @param [options.storage] {RxStorage} the RxDB storage for the local store
|
|
62
|
+
* (defaults to Dexie/IndexedDB; inject a fake for tests)
|
|
63
|
+
* @returns {WasAuthStore}
|
|
64
|
+
*/
|
|
65
|
+
export declare function createAuthStore({ config, registry, seedStore, storage }: {
|
|
66
|
+
config: WasAppConfig;
|
|
67
|
+
registry: StoreRegistry;
|
|
68
|
+
seedStore?: SeedStore;
|
|
69
|
+
storage?: RxStorage<unknown, unknown>;
|
|
70
|
+
}): WasAuthStore;
|
|
71
|
+
//# sourceMappingURL=authStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authStore.d.ts","sourceRoot":"","sources":["../../src/session/authStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAElD,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAA;AAIrB,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAO1E,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,sBAAsB,CAAA;AAsB7B,MAAM,MAAM,UAAU,GACpB,MAAM,GAAG,WAAW,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,eAAe,CAAA;AAU/E,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,UAAU,CAAA;IAClB,mEAAmE;IACnE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,kEAAkE;IAClE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,sEAAsE;IACtE,aAAa,EAAE,OAAO,CAAA;IACtB,YAAY,EAAE,OAAO,CAAA;IACrB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,uDAAuD;IACvD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,sEAAsE;IACtE,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,mBAAmB,EAAE,MAAM,IAAI,CAAA;CAChC;AAED,qEAAqE;AACrE,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAA;AAE9C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,QAAQ,EACR,SAAS,EACT,OAAO,EACR,EAAE;IACD,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,EAAE,aAAa,CAAA;IACvB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;CACtC,GAAG,YAAY,CAkUf"}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The wallet-mode auth store (zustand): owns the session lifecycle -- hot
|
|
6
|
+
* restore (zero popups), Login With Wallet, logout, and the expired-access
|
|
7
|
+
* reconnect -- plus the open/hydrate/sync ordering.
|
|
8
|
+
*
|
|
9
|
+
* On a successful login/restore it opens the encrypted {@link LocalStore} from
|
|
10
|
+
* the session seed (a per-controller database name, so two wallet users on one
|
|
11
|
+
* browser never collide), hydrates the entity stores, flips the shared
|
|
12
|
+
* `useAppReady` gate, and starts WAS replication from the granted zcaps.
|
|
13
|
+
*
|
|
14
|
+
* `status` is the router gate: `ProtectedRoute` waits for the restore attempt to
|
|
15
|
+
* settle before deciding between the app and the login page.
|
|
16
|
+
*
|
|
17
|
+
* The library cannot bind a module-level store to app config, so this is a
|
|
18
|
+
* FACTORY: {@link createAuthStore} captures the app's {@link WasAppConfig} and
|
|
19
|
+
* {@link StoreRegistry} once (the React provider calls it once) and returns a
|
|
20
|
+
* vanilla zustand store the hooks consume through context.
|
|
21
|
+
*/
|
|
22
|
+
import { createStore } from 'zustand/vanilla';
|
|
23
|
+
import { DEFAULT_DB_NAME, DEFAULT_EXPIRY_WARNING_MS, DEFAULT_EXPIRY_WATCH_MS } from '../config.js';
|
|
24
|
+
import { createDocumentLoader } from '../identity/documentLoader.js';
|
|
25
|
+
import { initAppSession } from '../identity/initAppSession.js';
|
|
26
|
+
import { createSeedStore } from '../identity/seedStore.js';
|
|
27
|
+
import { clearAppSession, persistAppSession, restoreAppSession, isNearExpiry } from '../identity/appSession.js';
|
|
28
|
+
import { loginWithWallet, requestGrants, LoginCancelledError } from '../auth/loginFlow.js';
|
|
29
|
+
import { parseGrants } from '../grants.js';
|
|
30
|
+
import { LocalStore } from '../storage/localStore.js';
|
|
31
|
+
import { clearLocalStore, hasStore, requireStore, setLocalStore } from '../storage/storageManager.js';
|
|
32
|
+
import { clearAllEntityStores, hydrateAll, patchFromChange } from '../storage/rehydrate.js';
|
|
33
|
+
import { startWasSync } from '../storage/wasSync.js';
|
|
34
|
+
import { createSyncController } from '../storage/syncController.js';
|
|
35
|
+
import { useSyncStatusStore } from '../storage/syncStatusStore.js';
|
|
36
|
+
import { useAppReady } from './appReadyStore.js';
|
|
37
|
+
/**
|
|
38
|
+
* Builds the wallet-mode auth store bound to an app's config and store registry.
|
|
39
|
+
* Call once (the React provider does) and share the returned store through
|
|
40
|
+
* context; the hooks read it via `useStore`.
|
|
41
|
+
*
|
|
42
|
+
* @param options {object}
|
|
43
|
+
* @param options.config {WasAppConfig} the app-wide configuration
|
|
44
|
+
* @param options.registry {StoreRegistry} the per-collection hydrate/patch
|
|
45
|
+
* handlers the rehydrate mechanism drives
|
|
46
|
+
* @param [options.seedStore] {SeedStore} the session IndexedDB persistence
|
|
47
|
+
* (defaults to one bound to `<dbName>-session`; inject a fake for tests)
|
|
48
|
+
* @param [options.storage] {RxStorage} the RxDB storage for the local store
|
|
49
|
+
* (defaults to Dexie/IndexedDB; inject a fake for tests)
|
|
50
|
+
* @returns {WasAuthStore}
|
|
51
|
+
*/
|
|
52
|
+
export function createAuthStore({ config, registry, seedStore, storage }) {
|
|
53
|
+
const dbName = config.dbName ?? DEFAULT_DB_NAME;
|
|
54
|
+
const sessionStore = seedStore ?? createSeedStore({ dbName: `${dbName}-session` });
|
|
55
|
+
const documentLoader = createDocumentLoader(config.wasServerUrl !== undefined
|
|
56
|
+
? { wasServerUrl: config.wasServerUrl }
|
|
57
|
+
: {});
|
|
58
|
+
// The login flow consumes only the WAS collection ids; this config layer owns
|
|
59
|
+
// the `{ key, id }` registry.
|
|
60
|
+
const loginConfig = {
|
|
61
|
+
appOrigin: config.appOrigin,
|
|
62
|
+
appName: config.appName,
|
|
63
|
+
collections: config.collections.map(collection => collection.id),
|
|
64
|
+
credential: config.credential,
|
|
65
|
+
documentLoader,
|
|
66
|
+
...(config.wasServerUrl !== undefined && {
|
|
67
|
+
wasServerUrl: config.wasServerUrl
|
|
68
|
+
}),
|
|
69
|
+
...(config.mediatorBase !== undefined && {
|
|
70
|
+
mediatorBase: config.mediatorBase
|
|
71
|
+
})
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* How close to grant expiry the reconnect banner is raised proactively (so the
|
|
75
|
+
* user re-grants before a live request fails). Wallet grants default to a long
|
|
76
|
+
* TTL, so a short lead time never fires spuriously mid-session.
|
|
77
|
+
*/
|
|
78
|
+
const warningMs = config.expiry?.warningMs ?? DEFAULT_EXPIRY_WARNING_MS;
|
|
79
|
+
/** Poll interval for the near-expiry watch (grant expiry is coarse-grained). */
|
|
80
|
+
const watchMs = config.expiry?.watchMs ?? DEFAULT_EXPIRY_WATCH_MS;
|
|
81
|
+
let expiryTimer;
|
|
82
|
+
// The per-session controller: single-use, stopped on logout and replaced on a
|
|
83
|
+
// reconnect (started once per grant set).
|
|
84
|
+
let controller = null;
|
|
85
|
+
/** Stops the near-expiry watch (logout / re-grant). */
|
|
86
|
+
function disarmExpiryWatch() {
|
|
87
|
+
if (expiryTimer) {
|
|
88
|
+
clearInterval(expiryTimer);
|
|
89
|
+
expiryTimer = undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Watches the session's earliest grant expiry and raises the reconnect banner
|
|
94
|
+
* once it is within `warningMs` (or already past). Checks immediately, then on
|
|
95
|
+
* a coarse interval; re-armed with the fresh expiry after a reconnect.
|
|
96
|
+
*/
|
|
97
|
+
function armExpiryWatch(expires) {
|
|
98
|
+
disarmExpiryWatch();
|
|
99
|
+
const check = () => {
|
|
100
|
+
if (isNearExpiry(expires, warningMs)) {
|
|
101
|
+
store.getState().notifyAccessExpired();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
check();
|
|
105
|
+
expiryTimer = setInterval(check, watchMs);
|
|
106
|
+
}
|
|
107
|
+
/** A stable, RxDB-safe database name per controller DID (FNV-1a hex). */
|
|
108
|
+
function dbNameForController(controllerDid) {
|
|
109
|
+
let hash = 0x811c9dc5;
|
|
110
|
+
for (let i = 0; i < controllerDid.length; i++) {
|
|
111
|
+
hash ^= controllerDid.charCodeAt(i);
|
|
112
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
113
|
+
}
|
|
114
|
+
return `${dbName}-${hash.toString(16).padStart(8, '0')}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Starts a fresh per-session controller replicating the granted collections,
|
|
118
|
+
* wiring reactive per-doc store patching and the auth-error (401/403) signal.
|
|
119
|
+
*/
|
|
120
|
+
function beginSync({ parsed, zcapClient }) {
|
|
121
|
+
controller = createSyncController({
|
|
122
|
+
collections: config.collections,
|
|
123
|
+
...(config.sync && { sync: config.sync })
|
|
124
|
+
});
|
|
125
|
+
return startWasSync({
|
|
126
|
+
parsed,
|
|
127
|
+
zcapClient,
|
|
128
|
+
localStore: requireStore(),
|
|
129
|
+
syncController: controller,
|
|
130
|
+
onRemoteChange: (key, event) => void patchFromChange(registry, key, event),
|
|
131
|
+
onAuthError: () => store.getState().notifyAccessExpired()
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Opens storage + hydrates + starts sync for a validated session, persists it,
|
|
136
|
+
* and flips the ready gate. Shared by login and restore.
|
|
137
|
+
*/
|
|
138
|
+
async function activateSession(session) {
|
|
139
|
+
if (!hasStore()) {
|
|
140
|
+
const local = await LocalStore.init({
|
|
141
|
+
seed: session.seed,
|
|
142
|
+
collections: config.collections,
|
|
143
|
+
dbName: dbNameForController(session.identity.controllerDid),
|
|
144
|
+
...(storage && { storage })
|
|
145
|
+
});
|
|
146
|
+
setLocalStore(local);
|
|
147
|
+
}
|
|
148
|
+
await hydrateAll(registry);
|
|
149
|
+
useAppReady.getState().setReady();
|
|
150
|
+
await persistAppSession({
|
|
151
|
+
session: {
|
|
152
|
+
seed: session.seed,
|
|
153
|
+
controllerDid: session.identity.controllerDid,
|
|
154
|
+
serverUrl: session.parsed.serverUrl,
|
|
155
|
+
spaceId: session.parsed.spaceId,
|
|
156
|
+
grants: session.grants,
|
|
157
|
+
expires: session.expires
|
|
158
|
+
},
|
|
159
|
+
store: sessionStore
|
|
160
|
+
});
|
|
161
|
+
// Replication starts in the background; a down server never blocks entry.
|
|
162
|
+
void beginSync({
|
|
163
|
+
parsed: session.parsed,
|
|
164
|
+
zcapClient: session.identity.zcapClient
|
|
165
|
+
}).catch(err => console.warn('WAS sync failed to start:', err));
|
|
166
|
+
armExpiryWatch(session.expires);
|
|
167
|
+
}
|
|
168
|
+
/** Tears down storage + sync + entity stores (logout and re-login paths). */
|
|
169
|
+
async function deactivateSession() {
|
|
170
|
+
disarmExpiryWatch();
|
|
171
|
+
if (controller) {
|
|
172
|
+
await controller.stop();
|
|
173
|
+
controller = null;
|
|
174
|
+
}
|
|
175
|
+
if (hasStore()) {
|
|
176
|
+
try {
|
|
177
|
+
await requireStore().close();
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
console.warn('Error closing the local store:', err);
|
|
181
|
+
}
|
|
182
|
+
clearLocalStore();
|
|
183
|
+
}
|
|
184
|
+
clearAllEntityStores(registry);
|
|
185
|
+
useSyncStatusStore.getState().reset();
|
|
186
|
+
useAppReady.getState().reset();
|
|
187
|
+
}
|
|
188
|
+
// Declared last so `prefer-const` is satisfied; the lifecycle closures above
|
|
189
|
+
// only dereference `store` at call time, by which point it is assigned.
|
|
190
|
+
const store = createStore()((set, get) => ({
|
|
191
|
+
status: 'idle',
|
|
192
|
+
phase: null,
|
|
193
|
+
error: null,
|
|
194
|
+
controllerDid: null,
|
|
195
|
+
expires: null,
|
|
196
|
+
accessExpired: false,
|
|
197
|
+
reconnecting: false,
|
|
198
|
+
restore: async () => {
|
|
199
|
+
if (get().status !== 'idle') {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
set({ status: 'restoring' });
|
|
203
|
+
try {
|
|
204
|
+
const restored = await restoreAppSession({ store: sessionStore });
|
|
205
|
+
if (!restored) {
|
|
206
|
+
set({ status: 'unauthenticated' });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const identity = await initAppSession({ seed: restored.seed });
|
|
210
|
+
if (identity.controllerDid !== restored.controllerDid) {
|
|
211
|
+
// A corrupt record; treat as logged out.
|
|
212
|
+
await clearAppSession({ store: sessionStore });
|
|
213
|
+
set({ status: 'unauthenticated' });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const parsed = parseGrants(restored.grants);
|
|
217
|
+
if (config.wasServerUrl !== undefined &&
|
|
218
|
+
parsed.serverUrl !== config.wasServerUrl) {
|
|
219
|
+
await clearAppSession({ store: sessionStore });
|
|
220
|
+
set({ status: 'unauthenticated' });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
await activateSession({
|
|
224
|
+
seed: restored.seed,
|
|
225
|
+
identity,
|
|
226
|
+
parsed,
|
|
227
|
+
grants: restored.grants,
|
|
228
|
+
expires: restored.expires
|
|
229
|
+
});
|
|
230
|
+
set({
|
|
231
|
+
status: 'authenticated',
|
|
232
|
+
controllerDid: identity.controllerDid,
|
|
233
|
+
expires: restored.expires,
|
|
234
|
+
error: null
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
console.warn('Session restore failed:', err);
|
|
239
|
+
set({ status: 'unauthenticated' });
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
login: async () => {
|
|
243
|
+
const { status } = get();
|
|
244
|
+
if (status === 'authenticating' || status === 'authenticated') {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
set({ status: 'authenticating', error: null, phase: 'probing' });
|
|
248
|
+
try {
|
|
249
|
+
const outcome = await loginWithWallet({
|
|
250
|
+
config: loginConfig,
|
|
251
|
+
onPhase: phase => set({ phase })
|
|
252
|
+
});
|
|
253
|
+
await activateSession({
|
|
254
|
+
seed: outcome.seed,
|
|
255
|
+
identity: outcome.identity,
|
|
256
|
+
parsed: outcome.parsed,
|
|
257
|
+
grants: outcome.grants,
|
|
258
|
+
expires: outcome.expires
|
|
259
|
+
});
|
|
260
|
+
set({
|
|
261
|
+
status: 'authenticated',
|
|
262
|
+
controllerDid: outcome.identity.controllerDid,
|
|
263
|
+
expires: outcome.expires,
|
|
264
|
+
phase: null,
|
|
265
|
+
error: null,
|
|
266
|
+
accessExpired: false
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
const message = err instanceof LoginCancelledError
|
|
271
|
+
? err.message
|
|
272
|
+
: err instanceof Error
|
|
273
|
+
? `Login failed: ${err.message}`
|
|
274
|
+
: 'Login failed.';
|
|
275
|
+
set({ status: 'unauthenticated', phase: null, error: message });
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
reconnect: async () => {
|
|
279
|
+
const { reconnecting, status } = get();
|
|
280
|
+
if (reconnecting || status !== 'authenticated') {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
set({ reconnecting: true, error: null });
|
|
284
|
+
try {
|
|
285
|
+
const restored = await restoreAppSession({ store: sessionStore });
|
|
286
|
+
// The seed survives grant expiry; only the grants need renewing. A
|
|
287
|
+
// missing seed means the session is unrecoverable in place.
|
|
288
|
+
const seed = restored?.seed;
|
|
289
|
+
if (!seed) {
|
|
290
|
+
await get().logout();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const identity = await initAppSession({ seed });
|
|
294
|
+
const checked = await requestGrants({ identity, config: loginConfig });
|
|
295
|
+
if (controller) {
|
|
296
|
+
await controller.stop();
|
|
297
|
+
controller = null;
|
|
298
|
+
}
|
|
299
|
+
await persistAppSession({
|
|
300
|
+
session: {
|
|
301
|
+
seed,
|
|
302
|
+
controllerDid: identity.controllerDid,
|
|
303
|
+
serverUrl: checked.parsed.serverUrl,
|
|
304
|
+
spaceId: checked.parsed.spaceId,
|
|
305
|
+
grants: checked.grants,
|
|
306
|
+
expires: checked.expires
|
|
307
|
+
},
|
|
308
|
+
store: sessionStore
|
|
309
|
+
});
|
|
310
|
+
void beginSync({
|
|
311
|
+
parsed: checked.parsed,
|
|
312
|
+
zcapClient: identity.zcapClient
|
|
313
|
+
}).catch(err => console.warn('WAS sync failed to restart:', err));
|
|
314
|
+
armExpiryWatch(checked.expires);
|
|
315
|
+
set({
|
|
316
|
+
accessExpired: false,
|
|
317
|
+
expires: checked.expires,
|
|
318
|
+
reconnecting: false
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
const message = err instanceof Error ? err.message : 'Reconnect failed.';
|
|
323
|
+
set({ reconnecting: false, error: message });
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
logout: async () => {
|
|
327
|
+
await deactivateSession();
|
|
328
|
+
await clearAppSession({ store: sessionStore });
|
|
329
|
+
set({
|
|
330
|
+
status: 'unauthenticated',
|
|
331
|
+
phase: null,
|
|
332
|
+
error: null,
|
|
333
|
+
controllerDid: null,
|
|
334
|
+
expires: null,
|
|
335
|
+
accessExpired: false,
|
|
336
|
+
reconnecting: false
|
|
337
|
+
});
|
|
338
|
+
},
|
|
339
|
+
notifyAccessExpired: () => {
|
|
340
|
+
if (!get().accessExpired) {
|
|
341
|
+
set({ accessExpired: true });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}));
|
|
345
|
+
return store;
|
|
346
|
+
}
|
|
347
|
+
//# sourceMappingURL=authStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authStore.js","sourceRoot":"","sources":["../../src/session/authStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The session lifecycle layer: the wallet-mode auth store factory and the shared
|
|
6
|
+
* app-ready gate.
|
|
7
|
+
*/
|
|
8
|
+
export { createAuthStore, type AuthStatus, type AuthState, type WasAuthStore } from './authStore.js';
|
|
9
|
+
export { useAppReady } from './appReadyStore.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;GAGG;AACH,OAAO,EACL,eAAe,EACf,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The session lifecycle layer: the wallet-mode auth store factory and the shared
|
|
6
|
+
* app-ready gate.
|
|
7
|
+
*/
|
|
8
|
+
export { createAuthStore } from './authStore.js';
|
|
9
|
+
export { useAppReady } from './appReadyStore.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;GAGG;AACH,OAAO,EACL,eAAe,EAIhB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* A generic zustand store over one localStore collection. Holds the decrypted
|
|
6
|
+
* payloads as a `Map<uuid, Doc>` and exposes the CRUD verbs that (1) persist
|
|
7
|
+
* through the encrypted {@link LocalStore} and (2) patch the in-memory Map. UI
|
|
8
|
+
* reads through selectors and applies the app's own comparators/filters; this
|
|
9
|
+
* layer stays domain-agnostic so every entity shares it.
|
|
10
|
+
*
|
|
11
|
+
* Reactivity note: local writes patch the Map optimistically after the localStore
|
|
12
|
+
* write resolves. Pulled remote changes are applied per-doc by the sync layer
|
|
13
|
+
* through `patch` / `drop` (no whole-collection re-hydrate), keeping multi-device
|
|
14
|
+
* edits and tombstones live without re-hydrate storms.
|
|
15
|
+
*/
|
|
16
|
+
import { type UseBoundStore, type StoreApi } from 'zustand';
|
|
17
|
+
export interface EntityStore<T extends {
|
|
18
|
+
id: string;
|
|
19
|
+
}> {
|
|
20
|
+
/** Decrypted payloads keyed by logical uuid. */
|
|
21
|
+
byId: Map<string, T>;
|
|
22
|
+
hydrated: boolean;
|
|
23
|
+
/** Decrypt every live row of the collection into the Map. */
|
|
24
|
+
hydrate: () => Promise<void>;
|
|
25
|
+
/** Encrypt+insert a new doc, then add it to the Map. */
|
|
26
|
+
insert: (doc: T) => Promise<void>;
|
|
27
|
+
/** Re-encrypt a doc in place (sequence+1), then replace it in the Map. */
|
|
28
|
+
update: (doc: T) => Promise<void>;
|
|
29
|
+
/** Tombstone a doc, then drop it from the Map. */
|
|
30
|
+
remove: (uuid: string) => Promise<void>;
|
|
31
|
+
/** Replace the whole Map (used by hydrate and, later, the sync stream). */
|
|
32
|
+
replaceAll: (docs: T[]) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Upsert one already-decrypted doc into the Map WITHOUT persisting (the sync
|
|
35
|
+
* stream owns the persisted row already). Used for per-doc reactive patching
|
|
36
|
+
* of pulled/conflict-resolved remote changes.
|
|
37
|
+
*/
|
|
38
|
+
patch: (doc: T) => void;
|
|
39
|
+
/** Drop one doc from the Map WITHOUT persisting (remote tombstone patch). */
|
|
40
|
+
drop: (uuid: string) => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Builds a zustand hook for the collection whose localStore key is `collectionKey`.
|
|
44
|
+
*
|
|
45
|
+
* @param collectionKey {string}
|
|
46
|
+
* @returns {UseBoundStore<StoreApi<EntityStore<T>>>}
|
|
47
|
+
*/
|
|
48
|
+
export declare function createEntityStore<T extends {
|
|
49
|
+
id: string;
|
|
50
|
+
}>(collectionKey: string): UseBoundStore<StoreApi<EntityStore<T>>>;
|
|
51
|
+
//# sourceMappingURL=entityStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entityStore.d.ts","sourceRoot":"","sources":["../../src/storage/entityStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;GAWG;AACH,OAAO,EAAU,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAA;AAGnE,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE;IACnD,gDAAgD;IAChD,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,wDAAwD;IACxD,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,0EAA0E;IAC1E,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,kDAAkD;IAClD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,2EAA2E;IAC3E,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,IAAI,CAAA;IAC/B;;;;OAIG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAA;IACvB,6EAA6E;IAC7E,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACxD,aAAa,EAAE,MAAM,GACpB,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAqDzC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* A generic zustand store over one localStore collection. Holds the decrypted
|
|
6
|
+
* payloads as a `Map<uuid, Doc>` and exposes the CRUD verbs that (1) persist
|
|
7
|
+
* through the encrypted {@link LocalStore} and (2) patch the in-memory Map. UI
|
|
8
|
+
* reads through selectors and applies the app's own comparators/filters; this
|
|
9
|
+
* layer stays domain-agnostic so every entity shares it.
|
|
10
|
+
*
|
|
11
|
+
* Reactivity note: local writes patch the Map optimistically after the localStore
|
|
12
|
+
* write resolves. Pulled remote changes are applied per-doc by the sync layer
|
|
13
|
+
* through `patch` / `drop` (no whole-collection re-hydrate), keeping multi-device
|
|
14
|
+
* edits and tombstones live without re-hydrate storms.
|
|
15
|
+
*/
|
|
16
|
+
import { create } from 'zustand';
|
|
17
|
+
import { requireStore } from './storageManager.js';
|
|
18
|
+
/**
|
|
19
|
+
* Builds a zustand hook for the collection whose localStore key is `collectionKey`.
|
|
20
|
+
*
|
|
21
|
+
* @param collectionKey {string}
|
|
22
|
+
* @returns {UseBoundStore<StoreApi<EntityStore<T>>>}
|
|
23
|
+
*/
|
|
24
|
+
export function createEntityStore(collectionKey) {
|
|
25
|
+
return create(set => ({
|
|
26
|
+
byId: new Map(),
|
|
27
|
+
hydrated: false,
|
|
28
|
+
hydrate: async () => {
|
|
29
|
+
const docs = await requireStore().listEntities(collectionKey);
|
|
30
|
+
set({ byId: new Map(docs.map(d => [d.id, d])), hydrated: true });
|
|
31
|
+
},
|
|
32
|
+
insert: async (doc) => {
|
|
33
|
+
await requireStore().insertEntity(collectionKey, doc);
|
|
34
|
+
set(state => {
|
|
35
|
+
const byId = new Map(state.byId);
|
|
36
|
+
byId.set(doc.id, doc);
|
|
37
|
+
return { byId };
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
update: async (doc) => {
|
|
41
|
+
await requireStore().updateEntity(collectionKey, doc);
|
|
42
|
+
set(state => {
|
|
43
|
+
const byId = new Map(state.byId);
|
|
44
|
+
byId.set(doc.id, doc);
|
|
45
|
+
return { byId };
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
remove: async (uuid) => {
|
|
49
|
+
await requireStore().deleteEntity(collectionKey, uuid);
|
|
50
|
+
set(state => {
|
|
51
|
+
const byId = new Map(state.byId);
|
|
52
|
+
byId.delete(uuid);
|
|
53
|
+
return { byId };
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
replaceAll: docs => {
|
|
57
|
+
set({ byId: new Map(docs.map(d => [d.id, d])), hydrated: true });
|
|
58
|
+
},
|
|
59
|
+
patch: doc => {
|
|
60
|
+
set(state => {
|
|
61
|
+
const byId = new Map(state.byId);
|
|
62
|
+
byId.set(doc.id, doc);
|
|
63
|
+
return { byId };
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
drop: uuid => {
|
|
67
|
+
set(state => {
|
|
68
|
+
if (!state.byId.has(uuid)) {
|
|
69
|
+
return state;
|
|
70
|
+
}
|
|
71
|
+
const byId = new Map(state.byId);
|
|
72
|
+
byId.delete(uuid);
|
|
73
|
+
return { byId };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=entityStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entityStore.js","sourceRoot":"","sources":["../../src/storage/entityStore.ts"],"names":[],"mappings":"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"}
|