wenay-common2 1.0.56 → 1.0.58
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/lib/Common/ObserveAll2/reactive2.js +47 -17
- package/lib/Common/ObserveAll2/store-replay.d.ts +17 -36
- package/lib/Common/ObserveAll2/store-replay.js +12 -0
- package/lib/Common/events/replay-conflate.d.ts +1 -0
- package/lib/Common/events/replay-conflate.js +6 -2
- package/lib/Common/events/replay-listen.d.ts +19 -3
- package/lib/Common/events/replay-listen.js +65 -3
- package/lib/Common/events/replay-wire.d.ts +27 -26
- package/lib/Common/events/replay-wire.js +113 -15
- package/lib/Common/rcp/listen-deep.d.ts +16 -2
- package/lib/Common/rcp/rpc-client.d.ts +3 -1
- package/lib/Common/rcp/rpc-server-auto.d.ts +9 -1
- package/lib/Common/rcp/rpc-server-auto.js +144 -7
- package/package.json +1 -1
|
@@ -33,7 +33,9 @@ function reactive(root, opts = {}) {
|
|
|
33
33
|
const { drain = 'immediate', depth = Infinity, eager = false } = opts;
|
|
34
34
|
const fire = scheduler(drain);
|
|
35
35
|
const eng = {
|
|
36
|
-
live: 0, pathLive: 0, dirty: new Set(), dirtyPaths: [],
|
|
36
|
+
live: 0, pathLive: 0, dirty: new Set(), dirtyPaths: [],
|
|
37
|
+
dirtyPathKeys: new Set(), pathKey: createPathKeyer(),
|
|
38
|
+
scheduled: false, waiters: new Set(), depth,
|
|
37
39
|
schedule() {
|
|
38
40
|
if (eng.scheduled)
|
|
39
41
|
return;
|
|
@@ -44,6 +46,8 @@ function reactive(root, opts = {}) {
|
|
|
44
46
|
eng.dirty.clear();
|
|
45
47
|
const dirtyPaths = eng.dirtyPaths;
|
|
46
48
|
eng.dirtyPaths = [];
|
|
49
|
+
eng.dirtyPathKeys = new Set();
|
|
50
|
+
eng.pathKey = createPathKeyer();
|
|
47
51
|
let err;
|
|
48
52
|
for (const n of batch) {
|
|
49
53
|
for (const cb of [...n.subs]) {
|
|
@@ -112,7 +116,6 @@ function makeNode(target, parent, path, level, eng) {
|
|
|
112
116
|
v = toRaw(v);
|
|
113
117
|
if (Object.is(node.target[k], v))
|
|
114
118
|
return true;
|
|
115
|
-
const dirtyPath = dirtyPathFor(node, k);
|
|
116
119
|
node.target[k] = v;
|
|
117
120
|
if (Array.isArray(proxyTarget) && k == "length")
|
|
118
121
|
proxyTarget.length = v;
|
|
@@ -120,7 +123,7 @@ function makeNode(target, parent, path, level, eng) {
|
|
|
120
123
|
if (kid)
|
|
121
124
|
rebind(kid, v);
|
|
122
125
|
if (eng.live > 0)
|
|
123
|
-
bubble(node,
|
|
126
|
+
bubble(node, k);
|
|
124
127
|
return true;
|
|
125
128
|
},
|
|
126
129
|
defineProperty(_, k, d) {
|
|
@@ -136,7 +139,6 @@ function makeNode(target, parent, path, level, eng) {
|
|
|
136
139
|
}
|
|
137
140
|
const v = node.target[k];
|
|
138
141
|
if (!Object.is(old, v)) {
|
|
139
|
-
const dirtyPath = dirtyPathFor(node, k);
|
|
140
142
|
const kid = node.kids.get(k);
|
|
141
143
|
if (kid) {
|
|
142
144
|
if (isReactiveObj(v))
|
|
@@ -148,14 +150,13 @@ function makeNode(target, parent, path, level, eng) {
|
|
|
148
150
|
}
|
|
149
151
|
}
|
|
150
152
|
if (eng.live > 0)
|
|
151
|
-
bubble(node,
|
|
153
|
+
bubble(node, k);
|
|
152
154
|
}
|
|
153
155
|
return true;
|
|
154
156
|
},
|
|
155
157
|
deleteProperty(_, k) {
|
|
156
158
|
if (!(k in node.target))
|
|
157
159
|
return true;
|
|
158
|
-
const dirtyPath = dirtyPathFor(node, k);
|
|
159
160
|
delete node.target[k];
|
|
160
161
|
const kid = node.kids.get(k);
|
|
161
162
|
if (kid) {
|
|
@@ -164,7 +165,7 @@ function makeNode(target, parent, path, level, eng) {
|
|
|
164
165
|
detachTree(kid);
|
|
165
166
|
}
|
|
166
167
|
if (eng.live > 0)
|
|
167
|
-
bubble(node,
|
|
168
|
+
bubble(node, k);
|
|
168
169
|
return true;
|
|
169
170
|
},
|
|
170
171
|
has(_, k) { return k in node.target; },
|
|
@@ -191,10 +192,10 @@ function makeNode(target, parent, path, level, eng) {
|
|
|
191
192
|
});
|
|
192
193
|
return node;
|
|
193
194
|
}
|
|
194
|
-
function bubble(from,
|
|
195
|
+
function bubble(from, key) {
|
|
195
196
|
const eng = from.eng;
|
|
196
|
-
if (eng.pathLive > 0
|
|
197
|
-
addDirtyPath(eng,
|
|
197
|
+
if (eng.pathLive > 0)
|
|
198
|
+
addDirtyPath(eng, dirtyPathFor(from, key));
|
|
198
199
|
for (let n = from; n && n.active; n = n.parent)
|
|
199
200
|
if (n.subs.size || n.pathSubs.size)
|
|
200
201
|
eng.dirty.add(n);
|
|
@@ -224,26 +225,55 @@ function markChanged(node) {
|
|
|
224
225
|
function dirtyPathFor(node, key) {
|
|
225
226
|
return Array.isArray(node.target) ? [...node.path] : [...node.path, key];
|
|
226
227
|
}
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
function createPathKeyer() {
|
|
229
|
+
let symIds = null;
|
|
230
|
+
return function pathKey(path) {
|
|
231
|
+
let out = '';
|
|
232
|
+
for (const p of path) {
|
|
233
|
+
if (typeof p == 'symbol') {
|
|
234
|
+
symIds ??= new Map();
|
|
235
|
+
let id = symIds.get(p);
|
|
236
|
+
if (id == null) {
|
|
237
|
+
id = symIds.size;
|
|
238
|
+
symIds.set(p, id);
|
|
239
|
+
}
|
|
240
|
+
out += 'y' + id + '|';
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
const s = String(p);
|
|
244
|
+
out += (typeof p)[0] + s.length + ':' + s + '|';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
};
|
|
230
249
|
}
|
|
231
|
-
function
|
|
232
|
-
|
|
250
|
+
function addDirtyPath(eng, path) {
|
|
251
|
+
const k = eng.pathKey(path);
|
|
252
|
+
if (eng.dirtyPathKeys.has(k))
|
|
253
|
+
return;
|
|
254
|
+
eng.dirtyPathKeys.add(k);
|
|
255
|
+
eng.dirtyPaths.push(path);
|
|
233
256
|
}
|
|
234
257
|
function startsWithPath(path, prefix) {
|
|
235
258
|
return prefix.length <= path.length && prefix.every((k, i) => Object.is(k, path[i]));
|
|
236
259
|
}
|
|
237
260
|
function pathsForNode(node, dirtyPaths) {
|
|
238
261
|
const out = [];
|
|
262
|
+
const seen = new Set();
|
|
263
|
+
const pathKey = createPathKeyer();
|
|
239
264
|
for (const path of dirtyPaths) {
|
|
240
265
|
let next = null;
|
|
241
266
|
if (startsWithPath(path, node.path))
|
|
242
267
|
next = path.slice(node.path.length);
|
|
243
268
|
else if (startsWithPath(node.path, path))
|
|
244
269
|
next = [];
|
|
245
|
-
if (next
|
|
246
|
-
|
|
270
|
+
if (next == null)
|
|
271
|
+
continue;
|
|
272
|
+
const k = pathKey(next);
|
|
273
|
+
if (seen.has(k))
|
|
274
|
+
continue;
|
|
275
|
+
seen.add(k);
|
|
276
|
+
out.push(next);
|
|
247
277
|
}
|
|
248
278
|
return out;
|
|
249
279
|
}
|
|
@@ -1,36 +1,12 @@
|
|
|
1
1
|
import { Store, StorePatch } from './store';
|
|
2
|
-
import { ReplayListenOptions } from '../events/replay-listen';
|
|
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';
|
|
5
5
|
export type StoreReplayOpts = Pick<ReplayListenOptions<[StorePatch]>, 'history' | 'getSince' | 'onJournal' | 'now'>;
|
|
6
6
|
export declare function storePatchKey(patch: StorePatch): string | null;
|
|
7
7
|
export declare function exposeStoreReplay<T extends object>(store: Store<T>, opts?: StoreReplayOpts): {
|
|
8
8
|
api: {
|
|
9
|
-
replay:
|
|
10
|
-
line: {
|
|
11
|
-
func: import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>;
|
|
12
|
-
isRun: () => boolean;
|
|
13
|
-
run: () => void;
|
|
14
|
-
close: () => void;
|
|
15
|
-
eventClose: (cb: () => void) => () => void;
|
|
16
|
-
onClose: (cb: () => void) => () => void;
|
|
17
|
-
removeEventClose: (cb: () => void) => void;
|
|
18
|
-
on: import("../..").ListenOn<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>;
|
|
19
|
-
off: (k: (string | symbol) | import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]> | null) => void;
|
|
20
|
-
addListen: (cb: import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>, opts?: {
|
|
21
|
-
cbClose?: () => void;
|
|
22
|
-
key?: string | symbol;
|
|
23
|
-
}) => () => void;
|
|
24
|
-
removeListen: (k: (string | symbol) | import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]> | null) => void;
|
|
25
|
-
once: (cb: import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>, opts?: {
|
|
26
|
-
key?: string | symbol;
|
|
27
|
-
}) => () => void;
|
|
28
|
-
count: () => number;
|
|
29
|
-
readonly getAllKeys: (string | symbol)[];
|
|
30
|
-
};
|
|
31
|
-
since: (seq: number) => import("../events/replay-listen").ReplayEvent<[StorePatch]>[] | null;
|
|
32
|
-
keyframe: () => import("../events/replay-listen").ReplayEvent<[StorePatch]> | null;
|
|
33
|
-
};
|
|
9
|
+
replay: import("../events/replay-wire").ReplayExpose<[StorePatch]>;
|
|
34
10
|
get(): T;
|
|
35
11
|
get<M extends import("./store").StoreMask<T>>(mask: M): import("./store").StorePick<T, M>;
|
|
36
12
|
set(path: import("./store").StorePath, value: any): void;
|
|
@@ -43,30 +19,34 @@ export declare function exposeStoreReplay<T extends object>(store: Store<T>, opt
|
|
|
43
19
|
replay: {
|
|
44
20
|
func: import("../..").Listener<[StorePatch]>;
|
|
45
21
|
head: () => number;
|
|
46
|
-
|
|
22
|
+
isStale: () => boolean;
|
|
23
|
+
lastTs: () => number;
|
|
24
|
+
close: () => void;
|
|
25
|
+
getSince: (seq: number) => ReplayEvent<[StorePatch]>[] | undefined;
|
|
47
26
|
line: {
|
|
48
|
-
func: import("../..").Listener<[
|
|
27
|
+
func: import("../..").Listener<[ReplayEvent<[StorePatch]>]>;
|
|
49
28
|
isRun: () => boolean;
|
|
50
29
|
run: () => void;
|
|
51
30
|
close: () => void;
|
|
52
31
|
eventClose: (cb: () => void) => () => void;
|
|
53
32
|
onClose: (cb: () => void) => () => void;
|
|
54
33
|
removeEventClose: (cb: () => void) => void;
|
|
55
|
-
on: import("../..").ListenOn<[
|
|
56
|
-
off: (k: (string | symbol) | import("../..").Listener<[
|
|
57
|
-
addListen: (cb: import("../..").Listener<[
|
|
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?: {
|
|
58
37
|
cbClose?: () => void;
|
|
59
38
|
key?: string | symbol;
|
|
60
39
|
}) => () => void;
|
|
61
|
-
removeListen: (k: (string | symbol) | import("../..").Listener<[
|
|
62
|
-
once: (cb: import("../..").Listener<[
|
|
40
|
+
removeListen: (k: (string | symbol) | import("../..").Listener<[ReplayEvent<[StorePatch]>]> | null) => void;
|
|
41
|
+
once: (cb: import("../..").Listener<[ReplayEvent<[StorePatch]>]>, opts?: {
|
|
63
42
|
key?: string | symbol;
|
|
64
43
|
}) => () => void;
|
|
65
44
|
count: () => number;
|
|
66
45
|
readonly getAllKeys: (string | symbol)[];
|
|
67
46
|
};
|
|
68
47
|
hasKeyframe: boolean;
|
|
69
|
-
keyframe: () =>
|
|
48
|
+
keyframe: () => ReplayEvent<[StorePatch]> | undefined;
|
|
49
|
+
frame: (sinceSeq: number, hint?: unknown) => ReplayEvent<[StorePatch]>[];
|
|
70
50
|
on: import("../events/replay-listen").ListenOnReplay<[StorePatch]>;
|
|
71
51
|
addListen: (cb: import("../..").Listener<[StorePatch]>, opts?: {
|
|
72
52
|
cbClose?: () => void;
|
|
@@ -75,7 +55,7 @@ export declare function exposeStoreReplay<T extends object>(store: Store<T>, opt
|
|
|
75
55
|
since?: number;
|
|
76
56
|
onSeq?: (seq: number) => void;
|
|
77
57
|
}) => () => void;
|
|
78
|
-
removeListen: (k: import("../..").Listener<[StorePatch]> |
|
|
58
|
+
removeListen: (k: (string | symbol) | import("../..").Listener<[StorePatch]> | null) => void;
|
|
79
59
|
once: (cb: import("../..").Listener<[StorePatch]>, opts?: {
|
|
80
60
|
key?: string | symbol;
|
|
81
61
|
current?: import("../..").ListenCurrent<[StorePatch]> | undefined;
|
|
@@ -83,7 +63,6 @@ export declare function exposeStoreReplay<T extends object>(store: Store<T>, opt
|
|
|
83
63
|
getAllKeys: (string | symbol)[];
|
|
84
64
|
isRun: () => boolean;
|
|
85
65
|
run: () => void;
|
|
86
|
-
close: () => void;
|
|
87
66
|
eventClose: (cb: () => void) => () => void;
|
|
88
67
|
onClose: (cb: () => void) => () => void;
|
|
89
68
|
removeEventClose: (cb: () => void) => void;
|
|
@@ -95,6 +74,8 @@ export declare function exposeStoreReplay<T extends object>(store: Store<T>, opt
|
|
|
95
74
|
export declare function syncStoreReplay<T extends object>(store: Store<T>, remote: ReplayRemote<[StorePatch]>, opts?: ReplaySubscribeOpts): (() => void) & {
|
|
96
75
|
ready: Promise<void>;
|
|
97
76
|
seq: () => number;
|
|
77
|
+
isStale: () => boolean;
|
|
78
|
+
lastTs: () => number;
|
|
98
79
|
};
|
|
99
80
|
export declare function storeReplayAt<T extends object>(storage: ReplayStorage<[StorePatch]>, at?: {
|
|
100
81
|
seq?: number;
|
|
@@ -21,9 +21,21 @@ function storePatchKey(patch) {
|
|
|
21
21
|
return null;
|
|
22
22
|
return JSON.stringify(patch.path);
|
|
23
23
|
}
|
|
24
|
+
function condensePatchTail(tail) {
|
|
25
|
+
const held = new Map();
|
|
26
|
+
for (const ev of tail) {
|
|
27
|
+
const k = storePatchKey(ev.event[0]);
|
|
28
|
+
if (k == null)
|
|
29
|
+
return tail;
|
|
30
|
+
held.delete(k);
|
|
31
|
+
held.set(k, ev);
|
|
32
|
+
}
|
|
33
|
+
return [...held.values()];
|
|
34
|
+
}
|
|
24
35
|
function exposeStoreReplay(store, opts = {}) {
|
|
25
36
|
const [emitPatch, lineApi] = (0, replay_listen_1.UseReplayListen)({
|
|
26
37
|
current: () => [{ path: [], exists: true, value: store.snapshot() }],
|
|
38
|
+
frame: condensePatchTail,
|
|
27
39
|
history: opts.getSince ? undefined : (opts.history ?? 1024),
|
|
28
40
|
getSince: opts.getSince,
|
|
29
41
|
onJournal: opts.onJournal,
|
|
@@ -33,6 +33,7 @@ export declare function conflateReplay<T>(replay: ListenReplayApi<T>, opts: Conf
|
|
|
33
33
|
};
|
|
34
34
|
since: (seq: number) => ReplayEvent<NormalizeTuple<T>>[] | null;
|
|
35
35
|
keyframe: () => ReplayEvent<NormalizeTuple<T>> | null;
|
|
36
|
+
frame: (seq: number, hint?: unknown) => ReplayEvent<NormalizeTuple<T>>[];
|
|
36
37
|
};
|
|
37
38
|
close: () => void;
|
|
38
39
|
stats: () => {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.conflateReplay = conflateReplay;
|
|
4
4
|
const Listen3_1 = require("./Listen3");
|
|
5
|
-
const replay_wire_1 = require("./replay-wire");
|
|
6
5
|
function conflateReplay(replay, opts) {
|
|
7
6
|
const { pending, highWater, lowWater = 0, pollMs = 25, keyOf, maxKeys = 1024 } = opts;
|
|
8
7
|
if (!replay.hasKeyframe)
|
|
@@ -92,7 +91,12 @@ function conflateReplay(replay, opts) {
|
|
|
92
91
|
gate.close();
|
|
93
92
|
}
|
|
94
93
|
return {
|
|
95
|
-
api: {
|
|
94
|
+
api: {
|
|
95
|
+
line: gate,
|
|
96
|
+
since: (seq) => replay.getSince(seq) ?? null,
|
|
97
|
+
keyframe: () => replay.keyframe() ?? null,
|
|
98
|
+
frame: (seq, hint) => replay.frame(seq, hint),
|
|
99
|
+
},
|
|
96
100
|
close,
|
|
97
101
|
stats: () => ({ conflating, dropped, keyframes, coalesced, flushes }),
|
|
98
102
|
};
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { Listener, ListenApi, ListenCurrent, ListenCurrentProvider, ListenOnBrand, ListenOptions, NormalizeTuple } from './Listen3';
|
|
2
2
|
type key = string | symbol;
|
|
3
3
|
type cbClose = () => void;
|
|
4
|
+
export declare const IS_REPLAY_LISTEN: unique symbol;
|
|
5
|
+
export declare function isReplayListen(obj: any): obj is ListenReplayApi<any>;
|
|
4
6
|
export type ReplayEvent<Z extends any[] = any[]> = {
|
|
5
7
|
seq: number;
|
|
6
8
|
ts: number;
|
|
7
9
|
event: Z;
|
|
8
10
|
};
|
|
11
|
+
export type StaleInfo = {
|
|
12
|
+
stale: boolean;
|
|
13
|
+
lastTs: number;
|
|
14
|
+
age: number;
|
|
15
|
+
};
|
|
9
16
|
export type ReplayListenOptions<Z extends any[]> = {
|
|
10
|
-
current?: ListenCurrentProvider<Z
|
|
17
|
+
current?: ListenCurrentProvider<Z> | 'last';
|
|
18
|
+
frame?: (tail: ReplayEvent<Z>[], hint?: unknown) => ReplayEvent<Z>[];
|
|
11
19
|
history?: number;
|
|
12
20
|
getSince?: (seq: number) => ReplayEvent<Z>[] | undefined;
|
|
13
21
|
onJournal?: (ev: ReplayEvent<Z>) => void;
|
|
14
22
|
now?: () => number;
|
|
23
|
+
staleMs?: number;
|
|
24
|
+
onStale?: (info: StaleInfo) => void;
|
|
15
25
|
};
|
|
16
26
|
type ReplayOnOptions<Z extends any[]> = {
|
|
17
27
|
cbClose?: cbClose;
|
|
@@ -24,6 +34,9 @@ export type ListenOnReplay<Z extends any[] = any[]> = ((cb: Listener<Z>, opts?:
|
|
|
24
34
|
export declare function withReplayListen<T>(base: ListenApi<T>, options?: ReplayListenOptions<NormalizeTuple<T>>): {
|
|
25
35
|
func: Listener<NormalizeTuple<T>>;
|
|
26
36
|
head: () => number;
|
|
37
|
+
isStale: () => boolean;
|
|
38
|
+
lastTs: () => number;
|
|
39
|
+
close: () => void;
|
|
27
40
|
getSince: (seq: number) => ReplayEvent<NormalizeTuple<T>>[] | undefined;
|
|
28
41
|
line: {
|
|
29
42
|
func: Listener<[ReplayEvent<NormalizeTuple<T>>]>;
|
|
@@ -48,6 +61,7 @@ export declare function withReplayListen<T>(base: ListenApi<T>, options?: Replay
|
|
|
48
61
|
};
|
|
49
62
|
hasKeyframe: boolean;
|
|
50
63
|
keyframe: () => ReplayEvent<NormalizeTuple<T>> | undefined;
|
|
64
|
+
frame: (sinceSeq: number, hint?: unknown) => ReplayEvent<NormalizeTuple<T>>[];
|
|
51
65
|
on: ListenOnReplay<NormalizeTuple<T>>;
|
|
52
66
|
addListen: (cb: Listener<NormalizeTuple<T>>, opts?: ReplayOnOptions<NormalizeTuple<T>>) => () => void;
|
|
53
67
|
removeListen: (k: Listener<NormalizeTuple<T>> | null | key) => void;
|
|
@@ -58,7 +72,6 @@ export declare function withReplayListen<T>(base: ListenApi<T>, options?: Replay
|
|
|
58
72
|
getAllKeys: key[];
|
|
59
73
|
isRun: () => boolean;
|
|
60
74
|
run: () => void;
|
|
61
|
-
close: () => void;
|
|
62
75
|
eventClose: (cb: () => void) => () => void;
|
|
63
76
|
onClose: (cb: () => void) => () => void;
|
|
64
77
|
removeEventClose: (cb: () => void) => void;
|
|
@@ -70,6 +83,9 @@ export type ReplayListenUseOptions<T> = ListenOptions<T> & ReplayListenOptions<N
|
|
|
70
83
|
export declare function UseReplayListen<T>(options?: ReplayListenUseOptions<T>): readonly [(...a: NormalizeTuple<T>) => void, {
|
|
71
84
|
func: Listener<NormalizeTuple<T>>;
|
|
72
85
|
head: () => number;
|
|
86
|
+
isStale: () => boolean;
|
|
87
|
+
lastTs: () => number;
|
|
88
|
+
close: () => void;
|
|
73
89
|
getSince: (seq: number) => ReplayEvent<NormalizeTuple<T>>[] | undefined;
|
|
74
90
|
line: {
|
|
75
91
|
func: Listener<[ReplayEvent<NormalizeTuple<T>>]>;
|
|
@@ -94,6 +110,7 @@ export declare function UseReplayListen<T>(options?: ReplayListenUseOptions<T>):
|
|
|
94
110
|
};
|
|
95
111
|
hasKeyframe: boolean;
|
|
96
112
|
keyframe: () => ReplayEvent<NormalizeTuple<T>> | undefined;
|
|
113
|
+
frame: (sinceSeq: number, hint?: unknown) => ReplayEvent<NormalizeTuple<T>>[];
|
|
97
114
|
on: ListenOnReplay<NormalizeTuple<T>>;
|
|
98
115
|
addListen: (cb: Listener<NormalizeTuple<T>>, opts?: ReplayOnOptions<NormalizeTuple<T>>) => () => void;
|
|
99
116
|
removeListen: (k: key | Listener<NormalizeTuple<T>> | null) => void;
|
|
@@ -104,7 +121,6 @@ export declare function UseReplayListen<T>(options?: ReplayListenUseOptions<T>):
|
|
|
104
121
|
getAllKeys: key[];
|
|
105
122
|
isRun: () => boolean;
|
|
106
123
|
run: () => void;
|
|
107
|
-
close: () => void;
|
|
108
124
|
eventClose: (cb: () => void) => () => void;
|
|
109
125
|
onClose: (cb: () => void) => () => void;
|
|
110
126
|
removeEventClose: (cb: () => void) => void;
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IS_REPLAY_LISTEN = void 0;
|
|
4
|
+
exports.isReplayListen = isReplayListen;
|
|
3
5
|
exports.withReplayListen = withReplayListen;
|
|
4
6
|
exports.UseReplayListen = UseReplayListen;
|
|
5
7
|
const Listen3_1 = require("./Listen3");
|
|
8
|
+
exports.IS_REPLAY_LISTEN = Symbol.for('isReplayListen');
|
|
9
|
+
function isReplayListen(obj) {
|
|
10
|
+
return !!obj && typeof obj == 'object' && Object.prototype.hasOwnProperty.call(obj, exports.IS_REPLAY_LISTEN);
|
|
11
|
+
}
|
|
6
12
|
function withReplayListen(base, options = {}) {
|
|
7
|
-
const { current, history = 0, getSince, onJournal, now = Date.now } = options;
|
|
13
|
+
const { current: currentOpt, frame: condense, history = 0, getSince, onJournal, now = Date.now, staleMs, onStale } = options;
|
|
14
|
+
let lastEv;
|
|
15
|
+
const current = currentOpt == 'last' ? () => lastEv?.event : currentOpt;
|
|
8
16
|
let head = 0;
|
|
9
17
|
const ring = [];
|
|
10
18
|
let emitting = null;
|
|
@@ -30,13 +38,54 @@ function withReplayListen(base, options = {}) {
|
|
|
30
38
|
return c();
|
|
31
39
|
return c ? current?.() : undefined;
|
|
32
40
|
}
|
|
41
|
+
let lastTs = 0;
|
|
42
|
+
let staleFlag = false;
|
|
43
|
+
let staleTimer = null;
|
|
44
|
+
function stopStaleTimer() {
|
|
45
|
+
if (staleTimer) {
|
|
46
|
+
clearTimeout(staleTimer);
|
|
47
|
+
staleTimer = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function reportStale(stale) {
|
|
51
|
+
staleFlag = stale;
|
|
52
|
+
try {
|
|
53
|
+
onStale({ stale, lastTs, age: now() - lastTs });
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
setTimeout(function rethrowOnStale() { throw e; }, 0);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function checkStale() {
|
|
60
|
+
staleTimer = null;
|
|
61
|
+
const age = now() - lastTs;
|
|
62
|
+
if (age >= staleMs)
|
|
63
|
+
reportStale(true);
|
|
64
|
+
else
|
|
65
|
+
armStaleTimer(staleMs - age);
|
|
66
|
+
}
|
|
67
|
+
function armStaleTimer(delay) {
|
|
68
|
+
if (staleTimer || !onStale || staleMs == null)
|
|
69
|
+
return;
|
|
70
|
+
staleTimer = setTimeout(checkStale, delay);
|
|
71
|
+
staleTimer.unref?.();
|
|
72
|
+
}
|
|
73
|
+
function touchStale(ts) {
|
|
74
|
+
lastTs = ts;
|
|
75
|
+
if (staleFlag)
|
|
76
|
+
reportStale(false);
|
|
77
|
+
armStaleTimer(staleMs);
|
|
78
|
+
}
|
|
33
79
|
const api = {
|
|
34
80
|
...base,
|
|
35
81
|
func: function emitJournaled(...e) {
|
|
36
82
|
const ev = { seq: ++head, ts: now(), event: e };
|
|
37
83
|
if (history > 0)
|
|
38
84
|
ring[(ev.seq - 1) % history] = ev;
|
|
85
|
+
if (currentOpt == 'last')
|
|
86
|
+
lastEv = ev;
|
|
39
87
|
onJournal?.(ev);
|
|
88
|
+
touchStale(ev.ts);
|
|
40
89
|
line.func(ev);
|
|
41
90
|
const prev = emitting;
|
|
42
91
|
emitting = ev;
|
|
@@ -48,6 +97,9 @@ function withReplayListen(base, options = {}) {
|
|
|
48
97
|
}
|
|
49
98
|
},
|
|
50
99
|
head: () => head,
|
|
100
|
+
isStale: () => staleMs != null && head > 0 && now() - lastTs >= staleMs,
|
|
101
|
+
lastTs: () => lastTs,
|
|
102
|
+
close: function closeReplay() { stopStaleTimer(); base.close(); },
|
|
51
103
|
getSince: journalSince,
|
|
52
104
|
line,
|
|
53
105
|
hasKeyframe: current != null,
|
|
@@ -55,6 +107,15 @@ function withReplayListen(base, options = {}) {
|
|
|
55
107
|
const m = current?.();
|
|
56
108
|
return m ? { seq: head, ts: now(), event: m } : undefined;
|
|
57
109
|
},
|
|
110
|
+
frame: function frameSince(sinceSeq, hint) {
|
|
111
|
+
const tail = journalSince(sinceSeq);
|
|
112
|
+
if (tail)
|
|
113
|
+
return condense ? condense(tail, hint) : tail;
|
|
114
|
+
const m = current?.();
|
|
115
|
+
if (m)
|
|
116
|
+
return [{ seq: head, ts: now(), event: m }];
|
|
117
|
+
throw new Error(`replay frame(${sinceSeq}): journal evicted and no keyframe (sacred line)`);
|
|
118
|
+
},
|
|
58
119
|
on: ((cb, { cbClose, key, current: cur, since, onSeq } = {}) => {
|
|
59
120
|
if (since == null) {
|
|
60
121
|
const off = base.on(cb, { cbClose, key });
|
|
@@ -120,14 +181,15 @@ function withReplayListen(base, options = {}) {
|
|
|
120
181
|
},
|
|
121
182
|
get getAllKeys() { return base.getAllKeys; },
|
|
122
183
|
};
|
|
184
|
+
Object.defineProperty(api, exports.IS_REPLAY_LISTEN, { value: true });
|
|
123
185
|
(0, Listen3_1.registerListenOn)(api.on, api);
|
|
124
186
|
return api;
|
|
125
187
|
}
|
|
126
188
|
function UseReplayListen(options = {}) {
|
|
127
|
-
const { current, history, getSince, onJournal, now, ...listenOptions } = options;
|
|
189
|
+
const { current, frame, history, getSince, onJournal, now, staleMs, onStale, ...listenOptions } = options;
|
|
128
190
|
let t;
|
|
129
191
|
const base = (0, Listen3_1.funcListenCallbackBase)((e) => { t = e; }, { fast: true, ...listenOptions });
|
|
130
|
-
const listen = withReplayListen(base, { current, history, getSince, onJournal, now });
|
|
192
|
+
const listen = withReplayListen(base, { current, frame, history, getSince, onJournal, now, staleMs, onStale });
|
|
131
193
|
base.run();
|
|
132
194
|
t = listen.func;
|
|
133
195
|
return [t, listen];
|
|
@@ -1,29 +1,18 @@
|
|
|
1
|
-
import { Listener } from './Listen3';
|
|
2
|
-
import { ListenReplayApi, ReplayEvent } from './replay-listen';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
key?: string | symbol;
|
|
17
|
-
}) => () => void;
|
|
18
|
-
removeListen: (k: (string | symbol) | Listener<[ReplayEvent<import("./Listen3").NormalizeTuple<T>>]> | null) => void;
|
|
19
|
-
once: (cb: Listener<[ReplayEvent<import("./Listen3").NormalizeTuple<T>>]>, opts?: {
|
|
20
|
-
key?: string | symbol;
|
|
21
|
-
}) => () => void;
|
|
22
|
-
count: () => number;
|
|
23
|
-
readonly getAllKeys: (string | symbol)[];
|
|
24
|
-
};
|
|
25
|
-
since: (seq: number) => ReplayEvent<import("./Listen3").NormalizeTuple<T>>[] | null;
|
|
26
|
-
keyframe: () => ReplayEvent<import("./Listen3").NormalizeTuple<T>> | null;
|
|
1
|
+
import { Listener, NormalizeTuple } from './Listen3';
|
|
2
|
+
import { ListenReplayApi, ReplayEvent, StaleInfo } from './replay-listen';
|
|
3
|
+
import { conflateReplay, ConflateOpts } from './replay-conflate';
|
|
4
|
+
export type ReplayExpose<T> = {
|
|
5
|
+
line: ListenReplayApi<T>['line'];
|
|
6
|
+
since: (seq: number) => ReplayEvent<NormalizeTuple<T>>[] | null;
|
|
7
|
+
keyframe: () => ReplayEvent<NormalizeTuple<T>> | null;
|
|
8
|
+
frame: ListenReplayApi<T>['frame'];
|
|
9
|
+
};
|
|
10
|
+
export declare function exposeReplay<T>(replay: ListenReplayApi<T>): ReplayExpose<T>;
|
|
11
|
+
export declare function exposeReplay<T>(replay: ListenReplayApi<T>, opts: {
|
|
12
|
+
conflate: ConflateOpts<NormalizeTuple<T>>;
|
|
13
|
+
}): ReplayExpose<T> & {
|
|
14
|
+
close: () => void;
|
|
15
|
+
stats: ReturnType<typeof conflateReplay<T>>['stats'];
|
|
27
16
|
};
|
|
28
17
|
export type ReplayRemote<Z extends any[] = any[]> = {
|
|
29
18
|
line: {
|
|
@@ -31,13 +20,25 @@ export type ReplayRemote<Z extends any[] = any[]> = {
|
|
|
31
20
|
};
|
|
32
21
|
since: (seq: number) => Promise<ReplayEvent<Z>[] | null | undefined> | ReplayEvent<Z>[] | null | undefined;
|
|
33
22
|
keyframe: () => Promise<ReplayEvent<Z> | null | undefined> | ReplayEvent<Z> | null | undefined;
|
|
23
|
+
frame?: (seq: number, hint?: unknown) => Promise<ReplayEvent<Z>[] | null | undefined> | ReplayEvent<Z>[] | null | undefined;
|
|
24
|
+
frameLine?: {
|
|
25
|
+
on: (cb: (ev: ReplayEvent<Z>) => void) => any;
|
|
26
|
+
};
|
|
34
27
|
};
|
|
35
28
|
export type ReplaySubscribeOpts = {
|
|
36
29
|
since?: number;
|
|
37
30
|
onSeq?: (seq: number) => void;
|
|
38
31
|
onError?: (e: any) => void;
|
|
32
|
+
staleMs?: number;
|
|
33
|
+
onStale?: (info: StaleInfo) => void;
|
|
34
|
+
skewMs?: number;
|
|
35
|
+
now?: () => number;
|
|
36
|
+
policy?: 'queue' | 'frame';
|
|
37
|
+
hint?: unknown;
|
|
39
38
|
};
|
|
40
39
|
export declare function replaySubscribe<Z extends any[]>(remote: ReplayRemote<Z>, cb: Listener<Z>, opts?: ReplaySubscribeOpts): (() => void) & {
|
|
41
40
|
ready: Promise<void>;
|
|
42
41
|
seq: () => number;
|
|
42
|
+
isStale: () => boolean;
|
|
43
|
+
lastTs: () => number;
|
|
43
44
|
};
|
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.exposeReplay = exposeReplay;
|
|
4
4
|
exports.replaySubscribe = replaySubscribe;
|
|
5
|
-
|
|
5
|
+
const replay_conflate_1 = require("./replay-conflate");
|
|
6
|
+
function exposeReplayPlain(replay) {
|
|
6
7
|
return {
|
|
7
8
|
line: replay.line,
|
|
8
9
|
since: (seq) => replay.getSince(seq) ?? null,
|
|
9
10
|
keyframe: () => replay.keyframe() ?? null,
|
|
11
|
+
frame: (seq, hint) => replay.frame(seq, hint),
|
|
10
12
|
};
|
|
11
13
|
}
|
|
14
|
+
function exposeReplay(replay, opts) {
|
|
15
|
+
if (!opts?.conflate)
|
|
16
|
+
return exposeReplayPlain(replay);
|
|
17
|
+
const gated = (0, replay_conflate_1.conflateReplay)(replay, opts.conflate);
|
|
18
|
+
return { ...gated.api, close: gated.close, stats: gated.stats };
|
|
19
|
+
}
|
|
12
20
|
function unsubscribeHandle(handle) {
|
|
13
21
|
if (typeof handle == 'function') {
|
|
14
22
|
handle();
|
|
@@ -20,19 +28,87 @@ function unsubscribeHandle(handle) {
|
|
|
20
28
|
handle.unsubscribe();
|
|
21
29
|
}
|
|
22
30
|
function replaySubscribe(remote, cb, opts = {}) {
|
|
23
|
-
const { since = -1, onSeq, onError } = opts;
|
|
31
|
+
const { since = -1, onSeq, onError, staleMs, onStale, skewMs = 0, now = Date.now, policy = 'queue', hint } = opts;
|
|
24
32
|
let lastDelivered = since;
|
|
25
33
|
let replaying = true;
|
|
26
34
|
let closed = false;
|
|
27
35
|
const queue = [];
|
|
36
|
+
let lastTs = 0;
|
|
37
|
+
let lastArrival = now();
|
|
38
|
+
let staleFlag = false;
|
|
39
|
+
let staleTimer = null;
|
|
40
|
+
function stopStaleTimer() {
|
|
41
|
+
if (staleTimer) {
|
|
42
|
+
clearTimeout(staleTimer);
|
|
43
|
+
staleTimer = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function reportStale(stale) {
|
|
47
|
+
staleFlag = stale;
|
|
48
|
+
if (!onStale)
|
|
49
|
+
return;
|
|
50
|
+
try {
|
|
51
|
+
onStale({ stale, lastTs, age: now() - (lastTs || lastArrival) });
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
setTimeout(function rethrowOnStale() { throw e; }, 0);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function checkArrivalGap() {
|
|
58
|
+
staleTimer = null;
|
|
59
|
+
if (closed)
|
|
60
|
+
return;
|
|
61
|
+
const gap = now() - lastArrival;
|
|
62
|
+
if (gap >= staleMs) {
|
|
63
|
+
if (!staleFlag)
|
|
64
|
+
reportStale(true);
|
|
65
|
+
}
|
|
66
|
+
else
|
|
67
|
+
armStaleTimer(staleMs - gap);
|
|
68
|
+
}
|
|
69
|
+
function armStaleTimer(delay) {
|
|
70
|
+
if (staleTimer || !onStale || staleMs == null || closed)
|
|
71
|
+
return;
|
|
72
|
+
staleTimer = setTimeout(checkArrivalGap, delay);
|
|
73
|
+
staleTimer.unref?.();
|
|
74
|
+
}
|
|
75
|
+
function assessStale() {
|
|
76
|
+
if (staleMs == null || closed)
|
|
77
|
+
return;
|
|
78
|
+
const tsStale = lastTs > 0 && now() - lastTs > staleMs + skewMs;
|
|
79
|
+
if (tsStale != staleFlag)
|
|
80
|
+
reportStale(tsStale);
|
|
81
|
+
if (tsStale)
|
|
82
|
+
stopStaleTimer();
|
|
83
|
+
else
|
|
84
|
+
armStaleTimer(staleMs);
|
|
85
|
+
}
|
|
86
|
+
armStaleTimer(staleMs);
|
|
28
87
|
function deliver(ev) {
|
|
29
88
|
if (closed || ev.seq <= lastDelivered)
|
|
30
89
|
return;
|
|
31
90
|
lastDelivered = ev.seq;
|
|
91
|
+
lastTs = ev.ts;
|
|
92
|
+
lastArrival = now();
|
|
32
93
|
cb(...ev.event);
|
|
33
94
|
onSeq?.(ev.seq);
|
|
95
|
+
if (!replaying)
|
|
96
|
+
assessStale();
|
|
34
97
|
}
|
|
35
|
-
const
|
|
98
|
+
const liveLine = policy == 'frame' && remote.frameLine ? remote.frameLine : remote.line;
|
|
99
|
+
const handle = liveLine.on(function liveTap(ev) {
|
|
100
|
+
if (ev == null || typeof ev.seq != 'number') {
|
|
101
|
+
if (closed)
|
|
102
|
+
return;
|
|
103
|
+
const err = new Error('replaySubscribe: line ended by server (' + String(ev) + ')');
|
|
104
|
+
off();
|
|
105
|
+
if (onError)
|
|
106
|
+
onError(err);
|
|
107
|
+
else
|
|
108
|
+
setTimeout(function rethrowLineEnd() { throw err; }, 0);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
lastArrival = now();
|
|
36
112
|
if (replaying)
|
|
37
113
|
queue.push(ev);
|
|
38
114
|
else
|
|
@@ -40,21 +116,39 @@ function replaySubscribe(remote, cb, opts = {}) {
|
|
|
40
116
|
});
|
|
41
117
|
async function catchUp() {
|
|
42
118
|
try {
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
119
|
+
let done = false;
|
|
120
|
+
if (since >= 0 && remote.frame) {
|
|
121
|
+
const envs = await remote.frame(since, hint);
|
|
122
|
+
if (closed)
|
|
123
|
+
return;
|
|
124
|
+
if (envs) {
|
|
125
|
+
if (envs.length) {
|
|
126
|
+
lastDelivered = envs[0].seq - 1;
|
|
127
|
+
for (const ev of envs)
|
|
128
|
+
deliver(ev);
|
|
129
|
+
}
|
|
130
|
+
done = true;
|
|
131
|
+
}
|
|
49
132
|
}
|
|
50
|
-
|
|
51
|
-
const
|
|
133
|
+
if (!done) {
|
|
134
|
+
const tail = since >= 0 ? await remote.since(since) : null;
|
|
52
135
|
if (closed)
|
|
53
136
|
return;
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
137
|
+
if (tail) {
|
|
138
|
+
for (const ev of tail)
|
|
139
|
+
deliver(ev);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const kf = await remote.keyframe();
|
|
143
|
+
if (closed)
|
|
144
|
+
return;
|
|
145
|
+
if (kf) {
|
|
146
|
+
lastDelivered = kf.seq;
|
|
147
|
+
lastTs = kf.ts;
|
|
148
|
+
lastArrival = now();
|
|
149
|
+
cb(...kf.event);
|
|
150
|
+
onSeq?.(kf.seq);
|
|
151
|
+
}
|
|
58
152
|
}
|
|
59
153
|
}
|
|
60
154
|
}
|
|
@@ -68,6 +162,7 @@ function replaySubscribe(remote, cb, opts = {}) {
|
|
|
68
162
|
while (queue.length)
|
|
69
163
|
deliver(queue.shift());
|
|
70
164
|
replaying = false;
|
|
165
|
+
assessStale();
|
|
71
166
|
}
|
|
72
167
|
}
|
|
73
168
|
const ready = catchUp();
|
|
@@ -75,10 +170,13 @@ function replaySubscribe(remote, cb, opts = {}) {
|
|
|
75
170
|
if (closed)
|
|
76
171
|
return;
|
|
77
172
|
closed = true;
|
|
173
|
+
stopStaleTimer();
|
|
78
174
|
unsubscribeHandle(handle);
|
|
79
175
|
}
|
|
80
176
|
return Object.assign(off, {
|
|
81
177
|
ready,
|
|
82
178
|
seq: () => lastDelivered,
|
|
179
|
+
isStale: () => staleFlag || (staleMs != null && now() - lastArrival >= staleMs),
|
|
180
|
+
lastTs: () => lastTs,
|
|
83
181
|
});
|
|
84
182
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ListenOn } from "../events/Listen";
|
|
2
|
+
import type { ReplayEvent } from "../events/replay-listen";
|
|
2
3
|
import { listenSocket, listenSocketFirst, listenSocketAll, listenSocketSmart, type tSubHandle } from "./listen-socket";
|
|
3
4
|
type WithSubHandle<R> = R extends {
|
|
4
5
|
callback: (...a: infer A) => any;
|
|
@@ -11,8 +12,21 @@ type Obj = Record<string, any>;
|
|
|
11
12
|
type InferArgs<T> = T extends {
|
|
12
13
|
addListen: (cb: (...args: infer R) => void, ...rest: any[]) => any;
|
|
13
14
|
} ? R : never;
|
|
15
|
+
export type ReplaySocketListen<Z extends any[]> = WithSubHandle<ReturnType<typeof listenSocket<Z>>> & {
|
|
16
|
+
line: WithSubHandle<ReturnType<typeof listenSocket<[ReplayEvent<Z>]>>>;
|
|
17
|
+
frameLine: WithSubHandle<ReturnType<typeof listenSocket<[ReplayEvent<Z>]>>>;
|
|
18
|
+
since: (seq: number) => Promise<ReplayEvent<Z>[] | null>;
|
|
19
|
+
keyframe: () => Promise<ReplayEvent<Z> | null>;
|
|
20
|
+
frame: (seq: number, hint?: unknown) => Promise<ReplayEvent<Z>[]>;
|
|
21
|
+
};
|
|
22
|
+
type IsReplayMember<V> = V extends {
|
|
23
|
+
addListen: Function;
|
|
24
|
+
getSince: Function;
|
|
25
|
+
keyframe: Function;
|
|
26
|
+
line: object;
|
|
27
|
+
} ? true : false;
|
|
14
28
|
export type DeepSocketListen<T> = {
|
|
15
|
-
[K in keyof T]: T[K] extends {
|
|
29
|
+
[K in keyof T]: IsReplayMember<T[K]> extends true ? ReplaySocketListen<InferArgs<T[K]>> : T[K] extends {
|
|
16
30
|
addListen: Function;
|
|
17
31
|
} ? WithSubHandle<ReturnType<typeof listenSocket<InferArgs<T[K]>>>> : T[K] extends ListenOn<infer Z> ? WithSubHandle<ReturnType<typeof listenSocket<Z>>> : T[K] extends (...a: any[]) => any ? T[K] : T[K] extends Promise<any> ? T[K] : T[K] extends typeof Promise ? T[K] : T[K] extends object ? DeepSocketListen<T[K]> : T[K];
|
|
18
32
|
};
|
|
@@ -27,7 +41,7 @@ export type DeepSocketListenAll<T> = {
|
|
|
27
41
|
} ? ReturnType<typeof listenSocketAll<InferArgs<T[K]>>> : T[K] extends ListenOn<infer Z> ? ReturnType<typeof listenSocketAll<Z>> : T[K] extends (...a: any[]) => any ? T[K] : T[K] extends Promise<any> ? T[K] : T[K] extends typeof Promise ? T[K] : T[K] extends object ? DeepSocketListenAll<T[K]> : T[K];
|
|
28
42
|
};
|
|
29
43
|
export type DeepSocketListenSmart<T> = {
|
|
30
|
-
[K in keyof T]: NonNullable<T[K]> extends {
|
|
44
|
+
[K in keyof T]: IsReplayMember<NonNullable<T[K]>> extends true ? ReplaySocketListen<InferArgs<NonNullable<T[K]>>> | Extract<T[K], undefined | null> : NonNullable<T[K]> extends {
|
|
31
45
|
addListen: Function;
|
|
32
46
|
} ? ReturnType<typeof listenSocketSmart<InferArgs<NonNullable<T[K]>>>> | Extract<T[K], undefined | null> : NonNullable<T[K]> extends ListenOn<infer Z> ? ReturnType<typeof listenSocketSmart<Z>> : NonNullable<T[K]> extends (...a: any[]) => any ? T[K] : NonNullable<T[K]> extends Promise<any> ? T[K] : NonNullable<T[K]> extends typeof Promise ? T[K] : NonNullable<T[K]> extends object ? DeepSocketListenSmart<T[K]> : T[K];
|
|
33
47
|
};
|
|
@@ -3,7 +3,9 @@ import { type RpcLimits } from "./rpc-limits";
|
|
|
3
3
|
import { makeOff } from "./rpc-off";
|
|
4
4
|
import { type RpcOpt } from "./rpc-caps";
|
|
5
5
|
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
|
|
6
|
-
export type DeepDataOnly<T> = T extends Function ? never : T extends
|
|
6
|
+
export type DeepDataOnly<T> = T extends Function ? never : T extends readonly any[] ? {
|
|
7
|
+
[I in keyof T]: DeepDataOnly<T[I]>;
|
|
8
|
+
} : T extends object ? {
|
|
7
9
|
[K in keyof T as T[K] extends Function ? never : K]: DeepDataOnly<T[K]>;
|
|
8
10
|
} : T;
|
|
9
11
|
export type ClientAPIAll<T> = {
|
|
@@ -3,7 +3,13 @@ import { type PromiseServerHooks, type RpcLimits, type RpcServerAuth, type RpcOp
|
|
|
3
3
|
import { DeepSocketListen } from "./listen-deep";
|
|
4
4
|
import { SocketTmpl } from "./rpc-protocol";
|
|
5
5
|
type ListenCallbackBase<T extends any[] = any[]> = ReturnType<typeof funcListenCallbackBase<T>>;
|
|
6
|
-
export
|
|
6
|
+
export type RpcReplayOpts = {
|
|
7
|
+
pending?: () => number;
|
|
8
|
+
highWater?: number;
|
|
9
|
+
lowWater?: number;
|
|
10
|
+
pollMs?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function createRpcServerAuto<T extends object>({ socket, object: target, socketKey: key, debug, hooks, disconnectListen, limits, auth, maxPerListen, throttle, opt, replay, replayOpts }: {
|
|
7
13
|
socket: SocketTmpl;
|
|
8
14
|
object: T;
|
|
9
15
|
socketKey: string;
|
|
@@ -15,6 +21,8 @@ export declare function createRpcServerAuto<T extends object>({ socket, object:
|
|
|
15
21
|
maxPerListen?: number;
|
|
16
22
|
throttle?: number;
|
|
17
23
|
opt?: RpcOpt;
|
|
24
|
+
replay?: false | "auto" | "force";
|
|
25
|
+
replayOpts?: RpcReplayOpts;
|
|
18
26
|
}): {
|
|
19
27
|
api: {
|
|
20
28
|
subscriptions: () => {
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createRpcServerAuto = createRpcServerAuto;
|
|
4
4
|
const Listen_1 = require("../events/Listen");
|
|
5
|
+
const replay_listen_1 = require("../events/replay-listen");
|
|
5
6
|
const listen_socket_1 = require("./listen-socket");
|
|
6
7
|
const rpc_server_1 = require("./rpc-server");
|
|
7
8
|
const rpc_protocol_1 = require("./rpc-protocol");
|
|
8
|
-
function createRpcServerAuto({ socket, object: target, socketKey: key, debug, hooks, disconnectListen, limits, auth, maxPerListen, throttle, opt }) {
|
|
9
|
+
function createRpcServerAuto({ socket, object: target, socketKey: key, debug, hooks, disconnectListen, limits, auth, maxPerListen, throttle, opt, replay = "auto", replayOpts }) {
|
|
9
10
|
const cache = new WeakMap();
|
|
10
11
|
const registry = new Map();
|
|
11
12
|
function unsubscribeAllActive() {
|
|
@@ -15,7 +16,8 @@ function createRpcServerAuto({ socket, object: target, socketKey: key, debug, ho
|
|
|
15
16
|
}
|
|
16
17
|
registry.clear();
|
|
17
18
|
}
|
|
18
|
-
function getListenSocket(parent, disconnectListen) {
|
|
19
|
+
function getListenSocket(parent, disconnectListen, nodeOpt) {
|
|
20
|
+
const nodeThrottle = nodeOpt ? nodeOpt.throttle : throttle;
|
|
19
21
|
let result = cache.get(parent);
|
|
20
22
|
if (!result) {
|
|
21
23
|
const subs = new Map();
|
|
@@ -27,7 +29,7 @@ function createRpcServerAuto({ socket, object: target, socketKey: key, debug, ho
|
|
|
27
29
|
if (!registry.has(parent))
|
|
28
30
|
registry.set(parent, { subs });
|
|
29
31
|
subs.get(z)?.off();
|
|
30
|
-
const w = (0, listen_socket_1.listenSocket)(parent, { addListenClose: disconnectListen, throttle });
|
|
32
|
+
const w = (0, listen_socket_1.listenSocket)(parent, { addListenClose: disconnectListen, throttle: nodeThrottle });
|
|
31
33
|
subs.set(z, w);
|
|
32
34
|
const done = w.on(z);
|
|
33
35
|
done.then(() => {
|
|
@@ -46,7 +48,7 @@ function createRpcServerAuto({ socket, object: target, socketKey: key, debug, ho
|
|
|
46
48
|
if (!registry.has(parent))
|
|
47
49
|
registry.set(parent, { subs });
|
|
48
50
|
subs.get(z)?.off();
|
|
49
|
-
const w = (0, listen_socket_1.listenSocket)(parent, { addListenClose: disconnectListen, throttle });
|
|
51
|
+
const w = (0, listen_socket_1.listenSocket)(parent, { addListenClose: disconnectListen, throttle: nodeThrottle });
|
|
50
52
|
let fired = false;
|
|
51
53
|
const oneShot = (...a) => {
|
|
52
54
|
if (fired)
|
|
@@ -79,6 +81,137 @@ function createRpcServerAuto({ socket, object: target, socketKey: key, debug, ho
|
|
|
79
81
|
}
|
|
80
82
|
return result;
|
|
81
83
|
}
|
|
84
|
+
function isReplayNode(obj) {
|
|
85
|
+
if (replay == false || obj == null || typeof obj != "object")
|
|
86
|
+
return false;
|
|
87
|
+
if (Object.prototype.hasOwnProperty.call(obj, replay_listen_1.IS_REPLAY_LISTEN))
|
|
88
|
+
return true;
|
|
89
|
+
return replay == "force"
|
|
90
|
+
&& (0, Listen_1.isListenCallback)(obj)
|
|
91
|
+
&& typeof obj.getSince == "function"
|
|
92
|
+
&& typeof obj.keyframe == "function"
|
|
93
|
+
&& !!obj.line && typeof obj.line == "object";
|
|
94
|
+
}
|
|
95
|
+
function lineFrame(parent, seq, hint) {
|
|
96
|
+
if (typeof parent.frame == "function")
|
|
97
|
+
return parent.frame(seq, hint);
|
|
98
|
+
const tail = parent.getSince(seq);
|
|
99
|
+
if (tail)
|
|
100
|
+
return tail;
|
|
101
|
+
const kf = parent.keyframe();
|
|
102
|
+
if (kf)
|
|
103
|
+
return [kf];
|
|
104
|
+
throw new Error(`replay frame(${seq}): journal evicted and no keyframe (sacred line)`);
|
|
105
|
+
}
|
|
106
|
+
const gateClosers = new Set();
|
|
107
|
+
function closeAllGates() { for (const c of [...gateClosers])
|
|
108
|
+
c(); }
|
|
109
|
+
let gatesHooked = false;
|
|
110
|
+
function hookGateTeardown() {
|
|
111
|
+
if (gatesHooked || !disconnectListen)
|
|
112
|
+
return;
|
|
113
|
+
gatesHooked = true;
|
|
114
|
+
const dl = disconnectListen;
|
|
115
|
+
if (typeof dl.on == "function")
|
|
116
|
+
dl.on(closeAllGates);
|
|
117
|
+
else
|
|
118
|
+
dl.addListen(closeAllGates);
|
|
119
|
+
}
|
|
120
|
+
function gatedLineNode(parent) {
|
|
121
|
+
const { pending: pendingOpt, highWater = Infinity, lowWater = 0, pollMs = 25 } = replayOpts ?? {};
|
|
122
|
+
const pending = pendingOpt ?? (() => socket?.conn?.writeBuffer?.length ?? 0);
|
|
123
|
+
const out = (0, Listen_1.funcListenCallbackBase)(() => { });
|
|
124
|
+
out.run();
|
|
125
|
+
let lastSent = typeof parent.head == "function" ? parent.head() : 0;
|
|
126
|
+
let gated = false;
|
|
127
|
+
let closed = false;
|
|
128
|
+
let timer = null;
|
|
129
|
+
function stopPoll() { if (timer) {
|
|
130
|
+
clearInterval(timer);
|
|
131
|
+
timer = null;
|
|
132
|
+
} }
|
|
133
|
+
function startPoll() {
|
|
134
|
+
if (timer || closed)
|
|
135
|
+
return;
|
|
136
|
+
timer = setInterval(recoverIfDrained, pollMs);
|
|
137
|
+
timer.unref?.();
|
|
138
|
+
}
|
|
139
|
+
function close() {
|
|
140
|
+
if (closed)
|
|
141
|
+
return;
|
|
142
|
+
closed = true;
|
|
143
|
+
stopPoll();
|
|
144
|
+
offLine();
|
|
145
|
+
gateClosers.delete(close);
|
|
146
|
+
}
|
|
147
|
+
function fail(e) {
|
|
148
|
+
if (debug)
|
|
149
|
+
console.error("[rpc replay gate] frame recovery failed:", e);
|
|
150
|
+
const emitStop = !closed;
|
|
151
|
+
close();
|
|
152
|
+
if (emitStop)
|
|
153
|
+
out.func(rpc_protocol_1.RPC_STOP);
|
|
154
|
+
out.close?.();
|
|
155
|
+
}
|
|
156
|
+
function recoverIfDrained() {
|
|
157
|
+
if (!gated || closed)
|
|
158
|
+
return;
|
|
159
|
+
if (pending() > lowWater)
|
|
160
|
+
return;
|
|
161
|
+
gated = false;
|
|
162
|
+
stopPoll();
|
|
163
|
+
let envs;
|
|
164
|
+
try {
|
|
165
|
+
envs = lineFrame(parent, lastSent);
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
fail(e);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
for (const ev of envs) {
|
|
172
|
+
if (ev.seq > lastSent)
|
|
173
|
+
lastSent = ev.seq;
|
|
174
|
+
out.func(ev);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const offLine = parent.line.on(function gateForward(ev) {
|
|
178
|
+
if (closed)
|
|
179
|
+
return;
|
|
180
|
+
if (!gated && pending() > highWater) {
|
|
181
|
+
gated = true;
|
|
182
|
+
startPoll();
|
|
183
|
+
}
|
|
184
|
+
if (gated) {
|
|
185
|
+
recoverIfDrained();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
lastSent = ev.seq;
|
|
189
|
+
out.func(ev);
|
|
190
|
+
});
|
|
191
|
+
gateClosers.add(close);
|
|
192
|
+
hookGateTeardown();
|
|
193
|
+
return getListenSocket(out, disconnectListen, { throttle: undefined });
|
|
194
|
+
}
|
|
195
|
+
const replayCache = new WeakMap();
|
|
196
|
+
function getReplayExpose(parent) {
|
|
197
|
+
let node = replayCache.get(parent);
|
|
198
|
+
if (node)
|
|
199
|
+
return node;
|
|
200
|
+
const legacy = getListenSocket(parent, disconnectListen);
|
|
201
|
+
const lineNode = getListenSocket(parent.line, disconnectListen, { throttle: undefined });
|
|
202
|
+
const frameLineNode = replayOpts?.highWater != null ? gatedLineNode(parent) : lineNode;
|
|
203
|
+
node = {
|
|
204
|
+
...legacy,
|
|
205
|
+
line: lineNode,
|
|
206
|
+
frameLine: frameLineNode,
|
|
207
|
+
since: (seq) => parent.getSince(seq) ?? null,
|
|
208
|
+
keyframe: () => parent.keyframe() ?? null,
|
|
209
|
+
frame: (seq, hint) => lineFrame(parent, seq, hint),
|
|
210
|
+
};
|
|
211
|
+
node[rpc_protocol_1.IS_RPC_LISTEN] = true;
|
|
212
|
+
replayCache.set(parent, node);
|
|
213
|
+
return node;
|
|
214
|
+
}
|
|
82
215
|
const api = {
|
|
83
216
|
subscriptions: () => Array.from(registry, ([parent, e], i) => ({
|
|
84
217
|
key: parent?.constructor?.name ? `${parent.constructor.name}#${i}` : `listen#${i}`,
|
|
@@ -89,12 +222,16 @@ function createRpcServerAuto({ socket, object: target, socketKey: key, debug, ho
|
|
|
89
222
|
socket, object: target, socketKey: key, debug, limits, auth, opt,
|
|
90
223
|
hooks: {
|
|
91
224
|
...hooks,
|
|
92
|
-
onDispose: () => { unsubscribeAllActive(); hooks?.onDispose?.(); },
|
|
225
|
+
onDispose: () => { closeAllGates(); unsubscribeAllActive(); hooks?.onDispose?.(); },
|
|
93
226
|
resolveTransform: (obj) => {
|
|
227
|
+
if (isReplayNode(obj))
|
|
228
|
+
return getReplayExpose(obj);
|
|
94
229
|
if ((0, Listen_1.isListenCallback)(obj))
|
|
95
230
|
return getListenSocket(obj, disconnectListen);
|
|
96
|
-
if ((0, Listen_1.isListenOn)(obj))
|
|
97
|
-
|
|
231
|
+
if ((0, Listen_1.isListenOn)(obj)) {
|
|
232
|
+
const byOn = (0, Listen_1.getListenByOn)(obj);
|
|
233
|
+
return isReplayNode(byOn) ? getReplayExpose(byOn) : getListenSocket(byOn, disconnectListen);
|
|
234
|
+
}
|
|
98
235
|
return obj;
|
|
99
236
|
},
|
|
100
237
|
},
|