wenay-common2 1.0.57 → 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/store-replay.d.ts +11 -10
- 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 +1 -0
- package/lib/Common/events/replay-listen.d.ts +6 -1
- package/lib/Common/events/replay-listen.js +23 -3
- package/lib/Common/events/replay-wire.d.ts +7 -0
- package/lib/Common/events/replay-wire.js +45 -16
- 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
|
@@ -1,5 +1,5 @@
|
|
|
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'>;
|
|
@@ -22,30 +22,31 @@ export declare function exposeStoreReplay<T extends object>(store: Store<T>, opt
|
|
|
22
22
|
isStale: () => boolean;
|
|
23
23
|
lastTs: () => number;
|
|
24
24
|
close: () => void;
|
|
25
|
-
getSince: (seq: number) =>
|
|
25
|
+
getSince: (seq: number) => ReplayEvent<[StorePatch]>[] | undefined;
|
|
26
26
|
line: {
|
|
27
|
-
func: import("../..").Listener<[
|
|
27
|
+
func: import("../..").Listener<[ReplayEvent<[StorePatch]>]>;
|
|
28
28
|
isRun: () => boolean;
|
|
29
29
|
run: () => void;
|
|
30
30
|
close: () => void;
|
|
31
31
|
eventClose: (cb: () => void) => () => void;
|
|
32
32
|
onClose: (cb: () => void) => () => void;
|
|
33
33
|
removeEventClose: (cb: () => void) => void;
|
|
34
|
-
on: import("../..").ListenOn<[
|
|
35
|
-
off: (k: (string | symbol) | import("../..").Listener<[
|
|
36
|
-
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?: {
|
|
37
37
|
cbClose?: () => void;
|
|
38
38
|
key?: string | symbol;
|
|
39
39
|
}) => () => void;
|
|
40
|
-
removeListen: (k: (string | symbol) | import("../..").Listener<[
|
|
41
|
-
once: (cb: import("../..").Listener<[
|
|
40
|
+
removeListen: (k: (string | symbol) | import("../..").Listener<[ReplayEvent<[StorePatch]>]> | null) => void;
|
|
41
|
+
once: (cb: import("../..").Listener<[ReplayEvent<[StorePatch]>]>, opts?: {
|
|
42
42
|
key?: string | symbol;
|
|
43
43
|
}) => () => void;
|
|
44
44
|
count: () => number;
|
|
45
45
|
readonly getAllKeys: (string | symbol)[];
|
|
46
46
|
};
|
|
47
47
|
hasKeyframe: boolean;
|
|
48
|
-
keyframe: () =>
|
|
48
|
+
keyframe: () => ReplayEvent<[StorePatch]> | undefined;
|
|
49
|
+
frame: (sinceSeq: number, hint?: unknown) => ReplayEvent<[StorePatch]>[];
|
|
49
50
|
on: import("../events/replay-listen").ListenOnReplay<[StorePatch]>;
|
|
50
51
|
addListen: (cb: import("../..").Listener<[StorePatch]>, opts?: {
|
|
51
52
|
cbClose?: () => void;
|
|
@@ -54,7 +55,7 @@ export declare function exposeStoreReplay<T extends object>(store: Store<T>, opt
|
|
|
54
55
|
since?: number;
|
|
55
56
|
onSeq?: (seq: number) => void;
|
|
56
57
|
}) => () => void;
|
|
57
|
-
removeListen: (k: import("../..").Listener<[StorePatch]> |
|
|
58
|
+
removeListen: (k: (string | symbol) | import("../..").Listener<[StorePatch]> | null) => void;
|
|
58
59
|
once: (cb: import("../..").Listener<[StorePatch]>, opts?: {
|
|
59
60
|
key?: string | symbol;
|
|
60
61
|
current?: import("../..").ListenCurrent<[StorePatch]> | undefined;
|
|
@@ -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: () => {
|
|
@@ -95,6 +95,7 @@ function conflateReplay(replay, opts) {
|
|
|
95
95
|
line: gate,
|
|
96
96
|
since: (seq) => replay.getSince(seq) ?? null,
|
|
97
97
|
keyframe: () => replay.keyframe() ?? null,
|
|
98
|
+
frame: (seq, hint) => replay.frame(seq, hint),
|
|
98
99
|
},
|
|
99
100
|
close,
|
|
100
101
|
stats: () => ({ conflating, dropped, keyframes, coalesced, flushes }),
|
|
@@ -1,6 +1,8 @@
|
|
|
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;
|
|
@@ -12,7 +14,8 @@ export type StaleInfo = {
|
|
|
12
14
|
age: number;
|
|
13
15
|
};
|
|
14
16
|
export type ReplayListenOptions<Z extends any[]> = {
|
|
15
|
-
current?: ListenCurrentProvider<Z
|
|
17
|
+
current?: ListenCurrentProvider<Z> | 'last';
|
|
18
|
+
frame?: (tail: ReplayEvent<Z>[], hint?: unknown) => ReplayEvent<Z>[];
|
|
16
19
|
history?: number;
|
|
17
20
|
getSince?: (seq: number) => ReplayEvent<Z>[] | undefined;
|
|
18
21
|
onJournal?: (ev: ReplayEvent<Z>) => void;
|
|
@@ -58,6 +61,7 @@ export declare function withReplayListen<T>(base: ListenApi<T>, options?: Replay
|
|
|
58
61
|
};
|
|
59
62
|
hasKeyframe: boolean;
|
|
60
63
|
keyframe: () => ReplayEvent<NormalizeTuple<T>> | undefined;
|
|
64
|
+
frame: (sinceSeq: number, hint?: unknown) => ReplayEvent<NormalizeTuple<T>>[];
|
|
61
65
|
on: ListenOnReplay<NormalizeTuple<T>>;
|
|
62
66
|
addListen: (cb: Listener<NormalizeTuple<T>>, opts?: ReplayOnOptions<NormalizeTuple<T>>) => () => void;
|
|
63
67
|
removeListen: (k: Listener<NormalizeTuple<T>> | null | key) => void;
|
|
@@ -106,6 +110,7 @@ export declare function UseReplayListen<T>(options?: ReplayListenUseOptions<T>):
|
|
|
106
110
|
};
|
|
107
111
|
hasKeyframe: boolean;
|
|
108
112
|
keyframe: () => ReplayEvent<NormalizeTuple<T>> | undefined;
|
|
113
|
+
frame: (sinceSeq: number, hint?: unknown) => ReplayEvent<NormalizeTuple<T>>[];
|
|
109
114
|
on: ListenOnReplay<NormalizeTuple<T>>;
|
|
110
115
|
addListen: (cb: Listener<NormalizeTuple<T>>, opts?: ReplayOnOptions<NormalizeTuple<T>>) => () => void;
|
|
111
116
|
removeListen: (k: key | Listener<NormalizeTuple<T>> | null) => 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, staleMs, onStale } = 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;
|
|
@@ -74,6 +82,8 @@ function withReplayListen(base, options = {}) {
|
|
|
74
82
|
const ev = { seq: ++head, ts: now(), event: e };
|
|
75
83
|
if (history > 0)
|
|
76
84
|
ring[(ev.seq - 1) % history] = ev;
|
|
85
|
+
if (currentOpt == 'last')
|
|
86
|
+
lastEv = ev;
|
|
77
87
|
onJournal?.(ev);
|
|
78
88
|
touchStale(ev.ts);
|
|
79
89
|
line.func(ev);
|
|
@@ -97,6 +107,15 @@ function withReplayListen(base, options = {}) {
|
|
|
97
107
|
const m = current?.();
|
|
98
108
|
return m ? { seq: head, ts: now(), event: m } : undefined;
|
|
99
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
|
+
},
|
|
100
119
|
on: ((cb, { cbClose, key, current: cur, since, onSeq } = {}) => {
|
|
101
120
|
if (since == null) {
|
|
102
121
|
const off = base.on(cb, { cbClose, key });
|
|
@@ -162,14 +181,15 @@ function withReplayListen(base, options = {}) {
|
|
|
162
181
|
},
|
|
163
182
|
get getAllKeys() { return base.getAllKeys; },
|
|
164
183
|
};
|
|
184
|
+
Object.defineProperty(api, exports.IS_REPLAY_LISTEN, { value: true });
|
|
165
185
|
(0, Listen3_1.registerListenOn)(api.on, api);
|
|
166
186
|
return api;
|
|
167
187
|
}
|
|
168
188
|
function UseReplayListen(options = {}) {
|
|
169
|
-
const { current, history, getSince, onJournal, now, staleMs, onStale, ...listenOptions } = options;
|
|
189
|
+
const { current, frame, history, getSince, onJournal, now, staleMs, onStale, ...listenOptions } = options;
|
|
170
190
|
let t;
|
|
171
191
|
const base = (0, Listen3_1.funcListenCallbackBase)((e) => { t = e; }, { fast: true, ...listenOptions });
|
|
172
|
-
const listen = withReplayListen(base, { current, history, getSince, onJournal, now, staleMs, onStale });
|
|
192
|
+
const listen = withReplayListen(base, { current, frame, history, getSince, onJournal, now, staleMs, onStale });
|
|
173
193
|
base.run();
|
|
174
194
|
t = listen.func;
|
|
175
195
|
return [t, listen];
|
|
@@ -5,6 +5,7 @@ export type ReplayExpose<T> = {
|
|
|
5
5
|
line: ListenReplayApi<T>['line'];
|
|
6
6
|
since: (seq: number) => ReplayEvent<NormalizeTuple<T>>[] | null;
|
|
7
7
|
keyframe: () => ReplayEvent<NormalizeTuple<T>> | null;
|
|
8
|
+
frame: ListenReplayApi<T>['frame'];
|
|
8
9
|
};
|
|
9
10
|
export declare function exposeReplay<T>(replay: ListenReplayApi<T>): ReplayExpose<T>;
|
|
10
11
|
export declare function exposeReplay<T>(replay: ListenReplayApi<T>, opts: {
|
|
@@ -19,6 +20,10 @@ export type ReplayRemote<Z extends any[] = any[]> = {
|
|
|
19
20
|
};
|
|
20
21
|
since: (seq: number) => Promise<ReplayEvent<Z>[] | null | undefined> | ReplayEvent<Z>[] | null | undefined;
|
|
21
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
|
+
};
|
|
22
27
|
};
|
|
23
28
|
export type ReplaySubscribeOpts = {
|
|
24
29
|
since?: number;
|
|
@@ -28,6 +33,8 @@ export type ReplaySubscribeOpts = {
|
|
|
28
33
|
onStale?: (info: StaleInfo) => void;
|
|
29
34
|
skewMs?: number;
|
|
30
35
|
now?: () => number;
|
|
36
|
+
policy?: 'queue' | 'frame';
|
|
37
|
+
hint?: unknown;
|
|
31
38
|
};
|
|
32
39
|
export declare function replaySubscribe<Z extends any[]>(remote: ReplayRemote<Z>, cb: Listener<Z>, opts?: ReplaySubscribeOpts): (() => void) & {
|
|
33
40
|
ready: Promise<void>;
|
|
@@ -8,6 +8,7 @@ function exposeReplayPlain(replay) {
|
|
|
8
8
|
line: replay.line,
|
|
9
9
|
since: (seq) => replay.getSince(seq) ?? null,
|
|
10
10
|
keyframe: () => replay.keyframe() ?? null,
|
|
11
|
+
frame: (seq, hint) => replay.frame(seq, hint),
|
|
11
12
|
};
|
|
12
13
|
}
|
|
13
14
|
function exposeReplay(replay, opts) {
|
|
@@ -27,7 +28,7 @@ function unsubscribeHandle(handle) {
|
|
|
27
28
|
handle.unsubscribe();
|
|
28
29
|
}
|
|
29
30
|
function replaySubscribe(remote, cb, opts = {}) {
|
|
30
|
-
const { since = -1, onSeq, onError, staleMs, onStale, skewMs = 0, now = Date.now } = opts;
|
|
31
|
+
const { since = -1, onSeq, onError, staleMs, onStale, skewMs = 0, now = Date.now, policy = 'queue', hint } = opts;
|
|
31
32
|
let lastDelivered = since;
|
|
32
33
|
let replaying = true;
|
|
33
34
|
let closed = false;
|
|
@@ -94,7 +95,19 @@ function replaySubscribe(remote, cb, opts = {}) {
|
|
|
94
95
|
if (!replaying)
|
|
95
96
|
assessStale();
|
|
96
97
|
}
|
|
97
|
-
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
|
+
}
|
|
98
111
|
lastArrival = now();
|
|
99
112
|
if (replaying)
|
|
100
113
|
queue.push(ev);
|
|
@@ -103,23 +116,39 @@ function replaySubscribe(remote, cb, opts = {}) {
|
|
|
103
116
|
});
|
|
104
117
|
async function catchUp() {
|
|
105
118
|
try {
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
}
|
|
112
132
|
}
|
|
113
|
-
|
|
114
|
-
const
|
|
133
|
+
if (!done) {
|
|
134
|
+
const tail = since >= 0 ? await remote.since(since) : null;
|
|
115
135
|
if (closed)
|
|
116
136
|
return;
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
}
|
|
123
152
|
}
|
|
124
153
|
}
|
|
125
154
|
}
|
|
@@ -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
|
},
|