wenay-common2 1.0.58 → 1.0.62
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 +8 -8
- package/lib/Common/{ObserveAll2 → Observe}/index.d.ts +1 -0
- package/lib/Common/{ObserveAll2 → Observe}/index.js +1 -0
- package/lib/Common/Observe/reactive2.d.ts +21 -0
- package/lib/Common/{ObserveAll2 → Observe}/reactive2.js +6 -6
- package/lib/Common/Observe/store-offline.d.ts +88 -0
- package/lib/Common/Observe/store-offline.js +278 -0
- package/lib/Common/{ObserveAll2 → Observe}/store-replay.d.ts +20 -39
- package/lib/Common/{ObserveAll2 → Observe}/store-replay.js +16 -1
- package/lib/Common/{ObserveAll2 → Observe}/store.d.ts +8 -0
- package/lib/Common/{ObserveAll2 → Observe}/store.js +57 -15
- package/lib/Common/async/promiseProgress.d.ts +12 -0
- package/lib/Common/async/promiseProgress.js +29 -0
- package/lib/Common/events/Listen3.d.ts +81 -162
- package/lib/Common/events/Listen3.js +95 -105
- package/lib/Common/events/SocketBuffer.d.ts +13 -33
- package/lib/Common/events/SocketBuffer.js +8 -8
- package/lib/Common/events/SocketServerHook.d.ts +5 -45
- package/lib/Common/events/SocketServerHook.js +1 -1
- package/lib/Common/events/joinListens.d.ts +6 -7
- package/lib/Common/events/joinListens.js +3 -4
- package/lib/Common/events/mapListen.d.ts +4 -0
- package/lib/Common/events/mapListen.js +25 -0
- package/lib/Common/events/replay-conflate.d.ts +1 -21
- package/lib/Common/events/replay-conflate.js +4 -4
- package/lib/Common/events/replay-listen.d.ts +19 -65
- package/lib/Common/events/replay-listen.js +8 -11
- package/lib/Common/rcp/createRpcServerAutoWithProtocolDetection.d.ts +3 -3
- package/lib/Common/rcp/createRpcServerAutoWithProtocolDetection.js +10 -10
- package/lib/Common/rcp/listen-deep.d.ts +12 -12
- package/lib/Common/rcp/listen-deep.js +1 -1
- package/lib/Common/rcp/listen-socket.d.ts +13 -13
- package/lib/Common/rcp/listen-socket.js +4 -3
- package/lib/Common/rcp/rpc-client-auto.d.ts +2 -2
- package/lib/Common/rcp/rpc-client-auto.js +3 -2
- package/lib/Common/rcp/rpc-client.d.ts +3 -2
- package/lib/Common/rcp/rpc-client.js +6 -4
- package/lib/Common/rcp/rpc-index.d.ts +2 -1
- package/lib/Common/rcp/rpc-index.js +3 -2
- package/lib/Common/rcp/rpc-path.d.ts +1 -0
- package/lib/Common/rcp/rpc-path.js +6 -0
- package/lib/Common/rcp/rpc-server-auto.d.ts +2 -2
- package/lib/Common/rcp/rpc-server-auto.js +7 -11
- package/lib/Common/rcp/rpc-server.js +11 -7
- package/lib/client.d.ts +1 -1
- package/lib/client.js +1 -1
- package/lib/index.d.ts +3 -4
- package/lib/index.js +4 -5
- package/package.json +2 -3
- package/lib/Common/ObserveAll2/reactive2.d.ts +0 -61
- package/lib/Common/async/PromiseArrayListen.d.ts +0 -14
- package/lib/Common/async/PromiseArrayListen.js +0 -37
- package/lib/Common/events/Listen2.d.ts +0 -1
- package/lib/Common/events/Listen2.js +0 -17
- package/lib/Common/events/UseListenTransform.d.ts +0 -24
- package/lib/Common/events/UseListenTransform.js +0 -28
- package/lib/Common/rcp/test.d.ts +0 -1
- package/lib/Common/rcp/test.js +0 -79
package/README.md
CHANGED
|
@@ -18,13 +18,13 @@ A set of common utilities and components for TypeScript/Node.js projects.
|
|
|
18
18
|
|
|
19
19
|
### ⏱️ **async/** — Async Utilities
|
|
20
20
|
- **`waitRun.ts`** — throttle/debounce (`enhancedWaitRun`), async queues (`createAsyncQueue`, `queueRun`), task queue with readiness control (`createTaskQueue`)
|
|
21
|
-
- **`
|
|
21
|
+
- **`promiseProgress.ts`** — processing an array of promises with success/error subscriptions
|
|
22
22
|
- **`createIterableObject.ts`** — proxy for iterable objects (readonly/read-write)
|
|
23
23
|
|
|
24
24
|
### 🔔 **events/** — Events and Subscriptions
|
|
25
|
-
- **`Listen.ts`** — subscription system (`
|
|
25
|
+
- **`Listen.ts`** — subscription system (`listen`, `createListen`), `isListenCallback` check
|
|
26
26
|
- **`event.ts`** — event handler collections (`CObjectEventsArr`, `CObjectEventsList`)
|
|
27
|
-
- **`SocketBuffer.ts`** — socket callback buffering (`
|
|
27
|
+
- **`SocketBuffer.ts`** — socket callback buffering (`socketBuffer`, `listenSnapshot`)
|
|
28
28
|
- **`SocketServerHook.ts`** — tag-based subscription wrappers for socket communication (`SocketServerHook`, `WebSocketServerHook`)
|
|
29
29
|
- **`joinListens.ts`** — merging multiple subscription streams into one (`joinListens`)
|
|
30
30
|
- **`listen-socket.ts`** — bridge between event system and RPC (`listenSocket`, `listenSocketFirst`, `listenSocketAll`, `listenSocketSmart`)
|
|
@@ -92,11 +92,11 @@ Bidirectional, strongly-typed RPC protocol over sockets (Socket.IO or similar).
|
|
|
92
92
|
|
|
93
93
|
### 2.1 Socket Connection
|
|
94
94
|
```typescript
|
|
95
|
-
import { createRpcServerAuto,
|
|
95
|
+
import { createRpcServerAuto, listen } from "wenay-common2";
|
|
96
96
|
|
|
97
97
|
io.sockets.on('connection', (socket) => {
|
|
98
98
|
// 1. Create unsubscribe trigger for memory cleanup
|
|
99
|
-
const [stop, listenStop] =
|
|
99
|
+
const [stop, listenStop] = listen<[]>();
|
|
100
100
|
socket.on('disconnect', stop);
|
|
101
101
|
|
|
102
102
|
// 2. Initialize RPC channel on this socket
|
|
@@ -113,10 +113,10 @@ io.sockets.on('connection', (socket) => {
|
|
|
113
113
|
### 2.2 Building API Object (Facade)
|
|
114
114
|
The object is traversed by the server to build a "Schema" that is sent to the client.
|
|
115
115
|
```typescript
|
|
116
|
-
import { noStrict,
|
|
116
|
+
import { noStrict, listen } from "wenay-common2";
|
|
117
117
|
|
|
118
118
|
// Create pub/sub event system
|
|
119
|
-
const [sendEvent, listenEvent] =
|
|
119
|
+
const [sendEvent, listenEvent] = listen<[string]>();
|
|
120
120
|
|
|
121
121
|
export function buildFacade(client) {
|
|
122
122
|
const role = (...roles) => hasRole(client, roles) ? true : null;
|
|
@@ -233,7 +233,7 @@ api.space.admin.logAction("clicked");
|
|
|
233
233
|
```
|
|
234
234
|
|
|
235
235
|
### 3.4 Client Subscriptions (Listen)
|
|
236
|
-
`createRpcServerAuto` exposes server `
|
|
236
|
+
`createRpcServerAuto` exposes server `listen` values as RPC Listen nodes. New code uses `on`/`once` and keeps the returned `off` handle. For TypeScript, project `client.func` to `DeepSocketListen<ServerFacade>`; this mirrors the runtime shape and keeps event argument types.
|
|
237
237
|
```typescript
|
|
238
238
|
import type { DeepSocketListen } from "wenay-common2";
|
|
239
239
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type Fn = () => void;
|
|
2
|
+
export type ReactiveChange = {
|
|
3
|
+
paths: PropertyKey[][];
|
|
4
|
+
};
|
|
5
|
+
type PathUpdateFn = (change: ReactiveChange) => void;
|
|
6
|
+
type Drain = 'micro' | 'immediate' | number | ((flush: Fn) => void);
|
|
7
|
+
export type Opts = {
|
|
8
|
+
drain?: Drain;
|
|
9
|
+
depth?: number;
|
|
10
|
+
eager?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function reactive<T extends object>(root: T, opts?: Opts): T;
|
|
13
|
+
export declare function isReactive(p: any): boolean;
|
|
14
|
+
export declare function toRaw<T>(p: T): T;
|
|
15
|
+
export declare function onUpdate(p: any, cb: Fn): () => void;
|
|
16
|
+
export declare function onUpdatePaths(p: any, cb: PathUpdateFn): () => void;
|
|
17
|
+
export declare function flushReactive(p: any): Promise<void>;
|
|
18
|
+
export declare function listenUpdate(p: any): import("../events/Listen3").ListenApi<[]>;
|
|
19
|
+
export declare function listenUpdatePaths(p: any): import("../events/Listen3").ListenApi<[ReactiveChange]>;
|
|
20
|
+
export type Reactive<T extends object> = T;
|
|
21
|
+
export {};
|
|
@@ -351,22 +351,22 @@ function flushReactive(p) {
|
|
|
351
351
|
return new Promise(resolve => { eng.waiters.add(resolve); });
|
|
352
352
|
}
|
|
353
353
|
function listenUpdate(p) {
|
|
354
|
-
const listen = (0, Listen_1.
|
|
354
|
+
const listen = (0, Listen_1.createListen)((emit) => onUpdate(p, () => emit()), {
|
|
355
355
|
event: (type, count, api) => {
|
|
356
|
-
if (type == "add" && count == 1 && !api.
|
|
356
|
+
if (type == "add" && count == 1 && !api.isRunning())
|
|
357
357
|
api.run();
|
|
358
|
-
if (type == "remove" && count == 0 && api.
|
|
358
|
+
if (type == "remove" && count == 0 && api.isRunning())
|
|
359
359
|
api.close();
|
|
360
360
|
},
|
|
361
361
|
});
|
|
362
362
|
return listen;
|
|
363
363
|
}
|
|
364
364
|
function listenUpdatePaths(p) {
|
|
365
|
-
const listen = (0, Listen_1.
|
|
365
|
+
const listen = (0, Listen_1.createListen)((emit) => onUpdatePaths(p, change => emit(change)), {
|
|
366
366
|
event: (type, count, api) => {
|
|
367
|
-
if (type == "add" && count == 1 && !api.
|
|
367
|
+
if (type == "add" && count == 1 && !api.isRunning())
|
|
368
368
|
api.run();
|
|
369
|
-
if (type == "remove" && count == 0 && api.
|
|
369
|
+
if (type == "remove" && count == 0 && api.isRunning())
|
|
370
370
|
api.close();
|
|
371
371
|
},
|
|
372
372
|
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ReplayRemote, ReplaySubscribeOpts } from '../events/replay-wire';
|
|
2
|
+
import { createStore, Store, StorePatch } from './store';
|
|
3
|
+
export type OfflineStorage = {
|
|
4
|
+
read<T>(key: string): Promise<T | undefined>;
|
|
5
|
+
write<T>(key: string, value: T): Promise<void>;
|
|
6
|
+
remove(key: string): Promise<void>;
|
|
7
|
+
transaction?<R>(fn: (tx: OfflineStorage) => Promise<R>): Promise<R>;
|
|
8
|
+
};
|
|
9
|
+
export type OfflineStoreRecord<T extends object> = {
|
|
10
|
+
version: number;
|
|
11
|
+
seq: number;
|
|
12
|
+
snapshot: T;
|
|
13
|
+
savedAt: number;
|
|
14
|
+
};
|
|
15
|
+
export type OfflineStoreStatus = {
|
|
16
|
+
ready: boolean;
|
|
17
|
+
syncing: boolean;
|
|
18
|
+
offline: boolean;
|
|
19
|
+
stale: boolean;
|
|
20
|
+
saving: boolean;
|
|
21
|
+
seq: number;
|
|
22
|
+
savedAt?: number;
|
|
23
|
+
error?: unknown;
|
|
24
|
+
};
|
|
25
|
+
export type PersistStoreOpts = {
|
|
26
|
+
key: string;
|
|
27
|
+
storage: OfflineStorage;
|
|
28
|
+
version?: number;
|
|
29
|
+
seq?: number;
|
|
30
|
+
savedAt?: number;
|
|
31
|
+
debounceMs?: number;
|
|
32
|
+
now?: () => number;
|
|
33
|
+
onError?: (error: unknown) => void;
|
|
34
|
+
onStatus?: (status: OfflineStoreStatus) => void;
|
|
35
|
+
};
|
|
36
|
+
export type CreateOfflineStoreOpts<T extends object> = {
|
|
37
|
+
key: string;
|
|
38
|
+
remote?: ReplayRemote<[StorePatch]>;
|
|
39
|
+
initial: T;
|
|
40
|
+
storage: OfflineStorage;
|
|
41
|
+
version?: number;
|
|
42
|
+
debounceMs?: number;
|
|
43
|
+
mode?: 'snapshot' | 'topLevel';
|
|
44
|
+
migrate?: (oldSnapshot: unknown, fromVersion: number, toVersion: number) => T | Promise<T>;
|
|
45
|
+
now?: () => number;
|
|
46
|
+
storeOpts?: Parameters<typeof createStore<T>>[1];
|
|
47
|
+
syncOpts?: ReplaySubscribeOpts;
|
|
48
|
+
onError?: (error: unknown) => void;
|
|
49
|
+
onStatus?: (status: OfflineStoreStatus) => void;
|
|
50
|
+
};
|
|
51
|
+
export type PersistedStoreControl = ReturnType<typeof persistStore>;
|
|
52
|
+
export type OfflineStore<T extends object> = Store<T> & {
|
|
53
|
+
ready: Promise<void>;
|
|
54
|
+
close(): void;
|
|
55
|
+
flush(): Promise<void>;
|
|
56
|
+
status(): OfflineStoreStatus;
|
|
57
|
+
statusListen: PersistedStoreControl['statusListen'];
|
|
58
|
+
reconnect(remote: ReplayRemote<[StorePatch]>, opts?: ReplaySubscribeOpts): Promise<void>;
|
|
59
|
+
};
|
|
60
|
+
export declare function createMemoryOfflineStorage(initial?: Record<string, unknown>): OfflineStorage & {
|
|
61
|
+
dump(): Record<string, unknown>;
|
|
62
|
+
};
|
|
63
|
+
export declare function persistStore<T extends object>(store: Store<T>, opts: PersistStoreOpts): {
|
|
64
|
+
flush: () => Promise<void>;
|
|
65
|
+
forceFlush: () => Promise<void>;
|
|
66
|
+
close: () => void;
|
|
67
|
+
setSeq: (nextSeq: number) => void;
|
|
68
|
+
seq: () => number;
|
|
69
|
+
status: () => OfflineStoreStatus;
|
|
70
|
+
statusListen: {
|
|
71
|
+
on: import("../events/Listen3").ListenOnCurrent<[OfflineStoreStatus]>;
|
|
72
|
+
once: (cb: import("../events/Listen3").Listener<[OfflineStoreStatus]>, opts?: {
|
|
73
|
+
key?: import("../events/Listen3").ListenKey;
|
|
74
|
+
current?: import("../events/Listen3").ListenCurrent<[OfflineStoreStatus]> | undefined;
|
|
75
|
+
}) => import("../events/Listen3").ListenOff;
|
|
76
|
+
emit: import("../events/Listen3").Listener<[OfflineStoreStatus]>;
|
|
77
|
+
has(key: import("../events/Listen3").ListenKey): boolean;
|
|
78
|
+
off(keyOrCallback: import("../events/Listen3").ListenKey | import("../events/Listen3").Listener<[OfflineStoreStatus]> | null): void;
|
|
79
|
+
close(): void;
|
|
80
|
+
count(): number;
|
|
81
|
+
keys(): import("../events/Listen3").ListenKey[];
|
|
82
|
+
isRunning(): boolean;
|
|
83
|
+
run(): void;
|
|
84
|
+
onClose(cb: () => void): import("../events/Listen3").ListenOff;
|
|
85
|
+
};
|
|
86
|
+
setSyncStatus: (patch: Partial<OfflineStoreStatus>) => void;
|
|
87
|
+
};
|
|
88
|
+
export declare function createOfflineStore<T extends object>(opts: CreateOfflineStoreOpts<T>): Promise<OfflineStore<T>>;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMemoryOfflineStorage = createMemoryOfflineStorage;
|
|
4
|
+
exports.persistStore = persistStore;
|
|
5
|
+
exports.createOfflineStore = createOfflineStore;
|
|
6
|
+
const common_1 = require("../core/common");
|
|
7
|
+
const Listen3_1 = require("../events/Listen3");
|
|
8
|
+
const store_1 = require("./store");
|
|
9
|
+
const store_replay_1 = require("./store-replay");
|
|
10
|
+
function isRecord(v) {
|
|
11
|
+
return !!v && typeof v == 'object' && typeof v.version == 'number'
|
|
12
|
+
&& typeof v.seq == 'number' && 'snapshot' in v;
|
|
13
|
+
}
|
|
14
|
+
function cloneValue(value) {
|
|
15
|
+
return (0, common_1.clone)(value);
|
|
16
|
+
}
|
|
17
|
+
function createMemoryOfflineStorage(initial) {
|
|
18
|
+
const data = new Map();
|
|
19
|
+
for (const [k, v] of Object.entries(initial ?? {}))
|
|
20
|
+
data.set(k, cloneValue(v));
|
|
21
|
+
const api = {
|
|
22
|
+
async read(key) {
|
|
23
|
+
return data.has(key) ? cloneValue(data.get(key)) : undefined;
|
|
24
|
+
},
|
|
25
|
+
async write(key, value) {
|
|
26
|
+
data.set(key, cloneValue(value));
|
|
27
|
+
},
|
|
28
|
+
async remove(key) {
|
|
29
|
+
data.delete(key);
|
|
30
|
+
},
|
|
31
|
+
async transaction(fn) {
|
|
32
|
+
return fn(api);
|
|
33
|
+
},
|
|
34
|
+
dump() {
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const [k, v] of data)
|
|
37
|
+
out[k] = cloneValue(v);
|
|
38
|
+
return out;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
return api;
|
|
42
|
+
}
|
|
43
|
+
function emitError(opts, error) {
|
|
44
|
+
if (opts.onError)
|
|
45
|
+
opts.onError(error);
|
|
46
|
+
else
|
|
47
|
+
setTimeout(function rethrowOfflineStoreError() { throw error; }, 0);
|
|
48
|
+
}
|
|
49
|
+
function copyStatus(status) {
|
|
50
|
+
return { ...status };
|
|
51
|
+
}
|
|
52
|
+
function persistStore(store, opts) {
|
|
53
|
+
const { key, storage, version = 1, debounceMs = 250, now = Date.now, onStatus } = opts;
|
|
54
|
+
let seq = opts.seq ?? -1;
|
|
55
|
+
let savedAt = opts.savedAt;
|
|
56
|
+
let closed = false;
|
|
57
|
+
let dirty = false;
|
|
58
|
+
let timer = null;
|
|
59
|
+
let chain = Promise.resolve();
|
|
60
|
+
const status = {
|
|
61
|
+
ready: true,
|
|
62
|
+
syncing: false,
|
|
63
|
+
offline: false,
|
|
64
|
+
stale: false,
|
|
65
|
+
saving: false,
|
|
66
|
+
seq,
|
|
67
|
+
savedAt,
|
|
68
|
+
};
|
|
69
|
+
const [emitStatus, statusListen] = (0, Listen3_1.listenStore)({
|
|
70
|
+
current: () => [copyStatus(status)],
|
|
71
|
+
});
|
|
72
|
+
function updateStatus(patch = {}) {
|
|
73
|
+
Object.assign(status, patch, { seq, savedAt });
|
|
74
|
+
const snap = copyStatus(status);
|
|
75
|
+
emitStatus(snap);
|
|
76
|
+
onStatus?.(snap);
|
|
77
|
+
}
|
|
78
|
+
function clearTimer() {
|
|
79
|
+
if (timer)
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
timer = null;
|
|
82
|
+
}
|
|
83
|
+
function scheduleSave() {
|
|
84
|
+
if (closed)
|
|
85
|
+
return;
|
|
86
|
+
dirty = true;
|
|
87
|
+
clearTimer();
|
|
88
|
+
timer = setTimeout(function flushOfflineStoreTimer() {
|
|
89
|
+
timer = null;
|
|
90
|
+
flush().catch(e => emitError(opts, e));
|
|
91
|
+
}, Math.max(0, debounceMs));
|
|
92
|
+
timer.unref?.();
|
|
93
|
+
}
|
|
94
|
+
async function writeRecord(record) {
|
|
95
|
+
if (storage.transaction) {
|
|
96
|
+
await storage.transaction(async function writeOfflineStoreTx(tx) {
|
|
97
|
+
await tx.write(key, record);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
await storage.write(key, record);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function runFlush(force = false) {
|
|
105
|
+
if (closed)
|
|
106
|
+
return;
|
|
107
|
+
if (!dirty && !force)
|
|
108
|
+
return;
|
|
109
|
+
dirty = false;
|
|
110
|
+
updateStatus({ saving: true, error: undefined });
|
|
111
|
+
const nextSavedAt = now();
|
|
112
|
+
const record = {
|
|
113
|
+
version,
|
|
114
|
+
seq,
|
|
115
|
+
snapshot: store.snapshot(),
|
|
116
|
+
savedAt: nextSavedAt,
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
await writeRecord(record);
|
|
120
|
+
savedAt = nextSavedAt;
|
|
121
|
+
updateStatus({ saving: false, error: undefined });
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
updateStatus({ saving: false, error: e });
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
if (dirty && !closed)
|
|
129
|
+
scheduleSave();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function flush() {
|
|
133
|
+
clearTimer();
|
|
134
|
+
const task = chain.catch(() => { }).then(function flushOfflineStore() { return runFlush(false); });
|
|
135
|
+
chain = task.catch(() => { });
|
|
136
|
+
return task;
|
|
137
|
+
}
|
|
138
|
+
function forceFlush() {
|
|
139
|
+
clearTimer();
|
|
140
|
+
dirty = true;
|
|
141
|
+
const task = chain.catch(() => { }).then(function forceFlushOfflineStore() { return runFlush(true); });
|
|
142
|
+
chain = task.catch(() => { });
|
|
143
|
+
return task;
|
|
144
|
+
}
|
|
145
|
+
function setSeq(nextSeq) {
|
|
146
|
+
if (nextSeq == seq)
|
|
147
|
+
return;
|
|
148
|
+
seq = nextSeq;
|
|
149
|
+
updateStatus();
|
|
150
|
+
scheduleSave();
|
|
151
|
+
}
|
|
152
|
+
const offStore = store.listenPaths().on(function persistStoreChange() {
|
|
153
|
+
scheduleSave();
|
|
154
|
+
});
|
|
155
|
+
function close() {
|
|
156
|
+
if (closed)
|
|
157
|
+
return;
|
|
158
|
+
closed = true;
|
|
159
|
+
clearTimer();
|
|
160
|
+
offStore();
|
|
161
|
+
statusListen.close();
|
|
162
|
+
}
|
|
163
|
+
function setSyncStatus(patch) {
|
|
164
|
+
updateStatus(patch);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
flush,
|
|
168
|
+
forceFlush,
|
|
169
|
+
close,
|
|
170
|
+
setSeq,
|
|
171
|
+
seq: () => seq,
|
|
172
|
+
status: () => copyStatus(status),
|
|
173
|
+
statusListen,
|
|
174
|
+
setSyncStatus,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async function loadSnapshot(opts) {
|
|
178
|
+
const { key, storage, initial, version = 1, migrate } = opts;
|
|
179
|
+
const raw = await storage.read(key);
|
|
180
|
+
if (raw == null)
|
|
181
|
+
return { snapshot: initial, seq: -1 };
|
|
182
|
+
if (isRecord(raw)) {
|
|
183
|
+
if (raw.version == version)
|
|
184
|
+
return { snapshot: raw.snapshot, seq: raw.seq, savedAt: raw.savedAt };
|
|
185
|
+
if (migrate)
|
|
186
|
+
return {
|
|
187
|
+
snapshot: await migrate(raw.snapshot, raw.version, version),
|
|
188
|
+
seq: raw.seq,
|
|
189
|
+
savedAt: raw.savedAt,
|
|
190
|
+
};
|
|
191
|
+
return { snapshot: initial, seq: -1 };
|
|
192
|
+
}
|
|
193
|
+
if (migrate)
|
|
194
|
+
return { snapshot: await migrate(raw, 0, version), seq: -1 };
|
|
195
|
+
return { snapshot: initial, seq: -1 };
|
|
196
|
+
}
|
|
197
|
+
async function createOfflineStore(opts) {
|
|
198
|
+
const { remote, storeOpts, syncOpts = {}, onStatus } = opts;
|
|
199
|
+
if (opts.mode && opts.mode != 'snapshot')
|
|
200
|
+
throw new Error('createOfflineStore: only snapshot mode is implemented');
|
|
201
|
+
let loaded;
|
|
202
|
+
try {
|
|
203
|
+
loaded = await loadSnapshot(opts);
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
emitError(opts, e);
|
|
207
|
+
loaded = { snapshot: opts.initial, seq: -1 };
|
|
208
|
+
}
|
|
209
|
+
const store = (0, store_1.createStore)(loaded.snapshot, storeOpts);
|
|
210
|
+
const persist = persistStore(store, {
|
|
211
|
+
key: opts.key,
|
|
212
|
+
storage: opts.storage,
|
|
213
|
+
version: opts.version,
|
|
214
|
+
seq: loaded.seq,
|
|
215
|
+
savedAt: loaded.savedAt,
|
|
216
|
+
debounceMs: opts.debounceMs,
|
|
217
|
+
now: opts.now,
|
|
218
|
+
onError: opts.onError,
|
|
219
|
+
onStatus,
|
|
220
|
+
});
|
|
221
|
+
let sub = null;
|
|
222
|
+
let ready = Promise.resolve();
|
|
223
|
+
async function reconnect(nextRemote, nextSyncOpts = syncOpts) {
|
|
224
|
+
sub?.();
|
|
225
|
+
persist.setSyncStatus({ syncing: true, offline: false, ready: false, error: undefined });
|
|
226
|
+
const { onSeq, onError, onStale, since, ...rest } = nextSyncOpts;
|
|
227
|
+
const effectiveSince = since ?? (persist.seq() >= 0 ? persist.seq() : undefined);
|
|
228
|
+
try {
|
|
229
|
+
sub = (0, store_replay_1.syncStoreReplay)(store, nextRemote, {
|
|
230
|
+
...rest,
|
|
231
|
+
since: effectiveSince,
|
|
232
|
+
onSeq: function offlineStoreOnSeq(seq) {
|
|
233
|
+
persist.setSeq(seq);
|
|
234
|
+
onSeq?.(seq);
|
|
235
|
+
},
|
|
236
|
+
onError: function offlineStoreOnError(error) {
|
|
237
|
+
persist.setSyncStatus({ offline: true, syncing: false, error });
|
|
238
|
+
onError?.(error);
|
|
239
|
+
if (!onError)
|
|
240
|
+
opts.onError?.(error);
|
|
241
|
+
},
|
|
242
|
+
onStale: onStale && function offlineStoreOnStale(info) {
|
|
243
|
+
persist.setSyncStatus({ stale: info.stale });
|
|
244
|
+
onStale(info);
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
ready = sub.ready.then(function offlineStoreReady() {
|
|
248
|
+
persist.setSyncStatus({ ready: true, syncing: false });
|
|
249
|
+
});
|
|
250
|
+
await ready;
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
persist.setSyncStatus({ ready: true, syncing: false, offline: true, error: e });
|
|
254
|
+
emitError(opts, e);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (remote) {
|
|
258
|
+
ready = reconnect(remote);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
persist.setSyncStatus({ ready: true, syncing: false, offline: true });
|
|
262
|
+
}
|
|
263
|
+
function close() {
|
|
264
|
+
sub?.();
|
|
265
|
+
persist.close();
|
|
266
|
+
}
|
|
267
|
+
function flush() {
|
|
268
|
+
return persist.flush();
|
|
269
|
+
}
|
|
270
|
+
return Object.assign(store, {
|
|
271
|
+
get ready() { return ready; },
|
|
272
|
+
close,
|
|
273
|
+
flush,
|
|
274
|
+
status: persist.status,
|
|
275
|
+
statusListen: persist.statusListen,
|
|
276
|
+
reconnect,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Store, StorePatch } from './store';
|
|
1
|
+
import { Store, StorePatch, StoreDrain, StoreEachCtx } from './store';
|
|
2
2
|
import { ReplayListenOptions, ReplayEvent } from '../events/replay-listen';
|
|
3
3
|
import { ReplayRemote, ReplaySubscribeOpts } from '../events/replay-wire';
|
|
4
4
|
import { ReplayStorage } from '../events/replay-history';
|
|
@@ -17,57 +17,28 @@ export declare function exposeStoreReplay<T extends object>(store: Store<T>, opt
|
|
|
17
17
|
changedData?: any;
|
|
18
18
|
};
|
|
19
19
|
replay: {
|
|
20
|
-
|
|
20
|
+
emit: import("../..").Listener<[StorePatch]>;
|
|
21
21
|
head: () => number;
|
|
22
22
|
isStale: () => boolean;
|
|
23
23
|
lastTs: () => number;
|
|
24
24
|
close: () => void;
|
|
25
25
|
getSince: (seq: number) => ReplayEvent<[StorePatch]>[] | undefined;
|
|
26
|
-
line:
|
|
27
|
-
func: import("../..").Listener<[ReplayEvent<[StorePatch]>]>;
|
|
28
|
-
isRun: () => boolean;
|
|
29
|
-
run: () => void;
|
|
30
|
-
close: () => void;
|
|
31
|
-
eventClose: (cb: () => void) => () => void;
|
|
32
|
-
onClose: (cb: () => void) => () => void;
|
|
33
|
-
removeEventClose: (cb: () => void) => void;
|
|
34
|
-
on: import("../..").ListenOn<[ReplayEvent<[StorePatch]>]>;
|
|
35
|
-
off: (k: (string | symbol) | import("../..").Listener<[ReplayEvent<[StorePatch]>]> | null) => void;
|
|
36
|
-
addListen: (cb: import("../..").Listener<[ReplayEvent<[StorePatch]>]>, opts?: {
|
|
37
|
-
cbClose?: () => void;
|
|
38
|
-
key?: string | symbol;
|
|
39
|
-
}) => () => void;
|
|
40
|
-
removeListen: (k: (string | symbol) | import("../..").Listener<[ReplayEvent<[StorePatch]>]> | null) => void;
|
|
41
|
-
once: (cb: import("../..").Listener<[ReplayEvent<[StorePatch]>]>, opts?: {
|
|
42
|
-
key?: string | symbol;
|
|
43
|
-
}) => () => void;
|
|
44
|
-
count: () => number;
|
|
45
|
-
readonly getAllKeys: (string | symbol)[];
|
|
46
|
-
};
|
|
26
|
+
line: import("../..").ListenApi<[ReplayEvent<[StorePatch]>]>;
|
|
47
27
|
hasKeyframe: boolean;
|
|
48
28
|
keyframe: () => ReplayEvent<[StorePatch]> | undefined;
|
|
49
29
|
frame: (sinceSeq: number, hint?: unknown) => ReplayEvent<[StorePatch]>[];
|
|
50
30
|
on: import("../events/replay-listen").ListenOnReplay<[StorePatch]>;
|
|
51
|
-
addListen: (cb: import("../..").Listener<[StorePatch]>, opts?: {
|
|
52
|
-
cbClose?: () => void;
|
|
53
|
-
key?: string | symbol;
|
|
54
|
-
current?: import("../..").ListenCurrent<[StorePatch]> | undefined;
|
|
55
|
-
since?: number;
|
|
56
|
-
onSeq?: (seq: number) => void;
|
|
57
|
-
}) => () => void;
|
|
58
|
-
removeListen: (k: (string | symbol) | import("../..").Listener<[StorePatch]> | null) => void;
|
|
59
31
|
once: (cb: import("../..").Listener<[StorePatch]>, opts?: {
|
|
60
32
|
key?: string | symbol;
|
|
61
33
|
current?: import("../..").ListenCurrent<[StorePatch]> | undefined;
|
|
62
34
|
}) => () => void;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
count: () => number;
|
|
35
|
+
has(key: import("../..").ListenKey): boolean;
|
|
36
|
+
off(keyOrCallback: import("../..").ListenKey | import("../..").Listener<[StorePatch]> | null): void;
|
|
37
|
+
count(): number;
|
|
38
|
+
keys(): import("../..").ListenKey[];
|
|
39
|
+
isRunning(): boolean;
|
|
40
|
+
run(): void;
|
|
41
|
+
onClose(cb: () => void): import("../..").ListenOff;
|
|
71
42
|
};
|
|
72
43
|
close: () => void;
|
|
73
44
|
};
|
|
@@ -77,6 +48,16 @@ export declare function syncStoreReplay<T extends object>(store: Store<T>, remot
|
|
|
77
48
|
isStale: () => boolean;
|
|
78
49
|
lastTs: () => number;
|
|
79
50
|
};
|
|
51
|
+
export declare function syncStoreReplayEach<T extends object>(remote: ReplayRemote<[StorePatch]>, cb: (key: string, value: T[keyof T] | undefined, ctx: StoreEachCtx) => void, opts?: ReplaySubscribeOpts & {
|
|
52
|
+
drain?: StoreDrain;
|
|
53
|
+
initial?: T;
|
|
54
|
+
}): (() => void) & {
|
|
55
|
+
store: Store<T>;
|
|
56
|
+
ready: Promise<void>;
|
|
57
|
+
seq: () => number;
|
|
58
|
+
isStale: () => boolean;
|
|
59
|
+
lastTs: () => number;
|
|
60
|
+
};
|
|
80
61
|
export declare function storeReplayAt<T extends object>(storage: ReplayStorage<[StorePatch]>, at?: {
|
|
81
62
|
seq?: number;
|
|
82
63
|
ts?: number;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.storePatchKey = storePatchKey;
|
|
4
4
|
exports.exposeStoreReplay = exposeStoreReplay;
|
|
5
5
|
exports.syncStoreReplay = syncStoreReplay;
|
|
6
|
+
exports.syncStoreReplayEach = syncStoreReplayEach;
|
|
6
7
|
exports.storeReplayAt = storeReplayAt;
|
|
7
8
|
const store_1 = require("./store");
|
|
8
9
|
const replay_listen_1 = require("../events/replay-listen");
|
|
@@ -33,7 +34,7 @@ function condensePatchTail(tail) {
|
|
|
33
34
|
return [...held.values()];
|
|
34
35
|
}
|
|
35
36
|
function exposeStoreReplay(store, opts = {}) {
|
|
36
|
-
const [emitPatch, lineApi] = (0, replay_listen_1.
|
|
37
|
+
const [emitPatch, lineApi] = (0, replay_listen_1.replayListen)({
|
|
37
38
|
current: () => [{ path: [], exists: true, value: store.snapshot() }],
|
|
38
39
|
frame: condensePatchTail,
|
|
39
40
|
history: opts.getSince ? undefined : (opts.history ?? 1024),
|
|
@@ -54,6 +55,20 @@ function exposeStoreReplay(store, opts = {}) {
|
|
|
54
55
|
function syncStoreReplay(store, remote, opts = {}) {
|
|
55
56
|
return (0, replay_wire_1.replaySubscribe)(remote, function applyLine(patch) { (0, store_1.applyStorePatch)(store, patch); }, opts);
|
|
56
57
|
}
|
|
58
|
+
function syncStoreReplayEach(remote, cb, opts = {}) {
|
|
59
|
+
const { drain, initial, ...wireOpts } = opts;
|
|
60
|
+
const store = (0, store_1.createStore)((initial ?? {}), drain !== undefined ? { drain } : {});
|
|
61
|
+
const offEach = store.each().on(cb);
|
|
62
|
+
const sub = syncStoreReplay(store, remote, wireOpts);
|
|
63
|
+
function off() { offEach(); sub(); }
|
|
64
|
+
return Object.assign(off, {
|
|
65
|
+
store,
|
|
66
|
+
ready: sub.ready,
|
|
67
|
+
seq: sub.seq,
|
|
68
|
+
isStale: sub.isStale,
|
|
69
|
+
lastTs: sub.lastTs,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
57
72
|
function storeReplayAt(storage, at = {}) {
|
|
58
73
|
const envelopes = (0, replay_history_1.openHistory)(storage).at(at);
|
|
59
74
|
if (!envelopes)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createListen } from "../events/Listen";
|
|
1
2
|
import { listenUpdate, listenUpdatePaths, reactive, ReactiveChange } from "./reactive2";
|
|
2
3
|
export type StorePath = readonly PropertyKey[];
|
|
3
4
|
export type StoreDrain = "micro" | "immediate" | number | ((flush: () => void) => void);
|
|
@@ -67,6 +68,12 @@ export type StoreSelectionCtx<T, M> = {
|
|
|
67
68
|
mask: M;
|
|
68
69
|
paths: PropertyKey[][];
|
|
69
70
|
};
|
|
71
|
+
export type StoreEachOpts = {
|
|
72
|
+
depth?: number;
|
|
73
|
+
};
|
|
74
|
+
export type StoreEachCtx = {
|
|
75
|
+
path: PropertyKey[];
|
|
76
|
+
};
|
|
70
77
|
export type Store<T extends object> = {
|
|
71
78
|
readonly state: T;
|
|
72
79
|
readonly node: StoreNode<T>;
|
|
@@ -76,6 +83,7 @@ export type Store<T extends object> = {
|
|
|
76
83
|
on(cb: (value: T, ctx: StoreCtx<T>) => void, opts?: StoreSubOpts): () => void;
|
|
77
84
|
once(cb: (value: T, ctx: StoreCtx<T>) => void, opts?: StoreSubOpts): () => void;
|
|
78
85
|
update<M extends StoreMask<T>>(mask: M, opts?: StoreSubOpts): StoreSelection<T, M>;
|
|
86
|
+
each(opts?: StoreEachOpts): ReturnType<typeof createListen<[key: string, value: T[keyof T] | undefined, ctx: StoreEachCtx]>>;
|
|
79
87
|
listen(): ReturnType<typeof listenUpdate>;
|
|
80
88
|
listenPaths(): ReturnType<typeof listenUpdatePaths>;
|
|
81
89
|
count(): number;
|