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.
@@ -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: [], scheduled: false, waiters: new Set(), depth,
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, dirtyPath);
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, dirtyPath);
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, dirtyPath);
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, dirtyPath) {
195
+ function bubble(from, key) {
195
196
  const eng = from.eng;
196
- if (eng.pathLive > 0 && dirtyPath)
197
- addDirtyPath(eng, dirtyPath);
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 addDirtyPath(eng, path) {
228
- if (!eng.dirtyPaths.some(p => samePath(p, path)))
229
- eng.dirtyPaths.push([...path]);
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 samePath(a, b) {
232
- return a.length == b.length && a.every((k, i) => Object.is(k, b[i]));
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 && !out.some(p => samePath(p, next)))
246
- out.push(next);
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
- getSince: (seq: number) => import("../events/replay-listen").ReplayEvent<[StorePatch]>[] | undefined;
22
+ isStale: () => boolean;
23
+ lastTs: () => number;
24
+ close: () => void;
25
+ getSince: (seq: number) => ReplayEvent<[StorePatch]>[] | undefined;
47
26
  line: {
48
- func: import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>;
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<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>;
56
- off: (k: (string | symbol) | import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]> | null) => void;
57
- addListen: (cb: import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>, opts?: {
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<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]> | null) => void;
62
- once: (cb: import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>, opts?: {
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: () => import("../events/replay-listen").ReplayEvent<[StorePatch]> | undefined;
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]> | (string | symbol) | null) => void;
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: { ...(0, replay_wire_1.exposeReplay)(replay), line: gate },
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
- export declare function exposeReplay<T>(replay: ListenReplayApi<T>): {
4
- line: {
5
- func: Listener<[ReplayEvent<import("./Listen3").NormalizeTuple<T>>]>;
6
- isRun: () => boolean;
7
- run: () => void;
8
- close: () => void;
9
- eventClose: (cb: () => void) => () => void;
10
- onClose: (cb: () => void) => () => void;
11
- removeEventClose: (cb: () => void) => void;
12
- on: import("./Listen3").ListenOn<[ReplayEvent<import("./Listen3").NormalizeTuple<T>>]>;
13
- off: (k: (string | symbol) | Listener<[ReplayEvent<import("./Listen3").NormalizeTuple<T>>]> | null) => void;
14
- addListen: (cb: Listener<[ReplayEvent<import("./Listen3").NormalizeTuple<T>>]>, opts?: {
15
- cbClose?: () => void;
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
- function exposeReplay(replay) {
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 handle = remote.line.on(function liveTap(ev) {
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
- const tail = since >= 0 ? await remote.since(since) : null;
44
- if (closed)
45
- return;
46
- if (tail) {
47
- for (const ev of tail)
48
- deliver(ev);
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
- else {
51
- const kf = await remote.keyframe();
133
+ if (!done) {
134
+ const tail = since >= 0 ? await remote.since(since) : null;
52
135
  if (closed)
53
136
  return;
54
- if (kf) {
55
- lastDelivered = kf.seq;
56
- cb(...kf.event);
57
- onSeq?.(kf.seq);
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 Array<infer U> ? Array<DeepDataOnly<U>> : T extends object ? {
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 declare function createRpcServerAuto<T extends object>({ socket, object: target, socketKey: key, debug, hooks, disconnectListen, limits, auth, maxPerListen, throttle, opt }: {
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
- return getListenSocket((0, Listen_1.getListenByOn)(obj), disconnectListen);
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
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wenay-common2",
3
- "version": "1.0.56",
3
+ "version": "1.0.58",
4
4
  "description": "Common library",
5
5
  "strict": true,
6
6
  "main": "lib/index.js",