wenay-common2 1.0.57 → 1.0.61

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.
@@ -1,5 +1,5 @@
1
- import { Store, StorePatch } from './store';
2
- import { ReplayListenOptions } from '../events/replay-listen';
1
+ import { Store, StorePatch, StoreDrain, StoreEachCtx } from './store';
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) => import("../events/replay-listen").ReplayEvent<[StorePatch]>[] | undefined;
25
+ getSince: (seq: number) => ReplayEvent<[StorePatch]>[] | undefined;
26
26
  line: {
27
- func: import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>;
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<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]>;
35
- off: (k: (string | symbol) | import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]> | null) => void;
36
- 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?: {
37
37
  cbClose?: () => void;
38
38
  key?: string | symbol;
39
39
  }) => () => void;
40
- removeListen: (k: (string | symbol) | import("../..").Listener<[import("../events/replay-listen").ReplayEvent<[StorePatch]>]> | null) => void;
41
- 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?: {
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: () => import("../events/replay-listen").ReplayEvent<[StorePatch]> | undefined;
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]> | (string | symbol) | null) => void;
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;
@@ -76,6 +77,16 @@ export declare function syncStoreReplay<T extends object>(store: Store<T>, remot
76
77
  isStale: () => boolean;
77
78
  lastTs: () => number;
78
79
  };
80
+ export declare function syncStoreReplayEach<T extends object>(remote: ReplayRemote<[StorePatch]>, cb: (key: string, value: T[keyof T] | undefined, ctx: StoreEachCtx) => void, opts?: ReplaySubscribeOpts & {
81
+ drain?: StoreDrain;
82
+ initial?: T;
83
+ }): (() => void) & {
84
+ store: Store<T>;
85
+ ready: Promise<void>;
86
+ seq: () => number;
87
+ isStale: () => boolean;
88
+ lastTs: () => number;
89
+ };
79
90
  export declare function storeReplayAt<T extends object>(storage: ReplayStorage<[StorePatch]>, at?: {
80
91
  seq?: number;
81
92
  ts?: number;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.storePatchKey = storePatchKey;
4
4
  exports.exposeStoreReplay = exposeStoreReplay;
5
5
  exports.syncStoreReplay = syncStoreReplay;
6
+ exports.syncStoreReplayEach = syncStoreReplayEach;
6
7
  exports.storeReplayAt = storeReplayAt;
7
8
  const store_1 = require("./store");
8
9
  const replay_listen_1 = require("../events/replay-listen");
@@ -21,9 +22,21 @@ function storePatchKey(patch) {
21
22
  return null;
22
23
  return JSON.stringify(patch.path);
23
24
  }
25
+ function condensePatchTail(tail) {
26
+ const held = new Map();
27
+ for (const ev of tail) {
28
+ const k = storePatchKey(ev.event[0]);
29
+ if (k == null)
30
+ return tail;
31
+ held.delete(k);
32
+ held.set(k, ev);
33
+ }
34
+ return [...held.values()];
35
+ }
24
36
  function exposeStoreReplay(store, opts = {}) {
25
37
  const [emitPatch, lineApi] = (0, replay_listen_1.UseReplayListen)({
26
38
  current: () => [{ path: [], exists: true, value: store.snapshot() }],
39
+ frame: condensePatchTail,
27
40
  history: opts.getSince ? undefined : (opts.history ?? 1024),
28
41
  getSince: opts.getSince,
29
42
  onJournal: opts.onJournal,
@@ -42,6 +55,20 @@ function exposeStoreReplay(store, opts = {}) {
42
55
  function syncStoreReplay(store, remote, opts = {}) {
43
56
  return (0, replay_wire_1.replaySubscribe)(remote, function applyLine(patch) { (0, store_1.applyStorePatch)(store, patch); }, opts);
44
57
  }
58
+ function syncStoreReplayEach(remote, cb, opts = {}) {
59
+ const { drain, initial, ...wireOpts } = opts;
60
+ const store = (0, store_1.createStore)((initial ?? {}), drain !== undefined ? { drain } : {});
61
+ const offEach = store.each().on(cb);
62
+ const sub = syncStoreReplay(store, remote, wireOpts);
63
+ function off() { offEach(); sub(); }
64
+ return Object.assign(off, {
65
+ store,
66
+ ready: sub.ready,
67
+ seq: sub.seq,
68
+ isStale: sub.isStale,
69
+ lastTs: sub.lastTs,
70
+ });
71
+ }
45
72
  function storeReplayAt(storage, at = {}) {
46
73
  const envelopes = (0, replay_history_1.openHistory)(storage).at(at);
47
74
  if (!envelopes)
@@ -1,3 +1,4 @@
1
+ import { funcListenCallbackBase } from "../events/Listen";
1
2
  import { listenUpdate, listenUpdatePaths, reactive, ReactiveChange } from "./reactive2";
2
3
  export type StorePath = readonly PropertyKey[];
3
4
  export type StoreDrain = "micro" | "immediate" | number | ((flush: () => void) => void);
@@ -67,6 +68,12 @@ export type StoreSelectionCtx<T, M> = {
67
68
  mask: M;
68
69
  paths: PropertyKey[][];
69
70
  };
71
+ export type StoreEachOpts = {
72
+ depth?: number;
73
+ };
74
+ export type StoreEachCtx = {
75
+ path: PropertyKey[];
76
+ };
70
77
  export type Store<T extends object> = {
71
78
  readonly state: T;
72
79
  readonly node: StoreNode<T>;
@@ -76,6 +83,7 @@ export type Store<T extends object> = {
76
83
  on(cb: (value: T, ctx: StoreCtx<T>) => void, opts?: StoreSubOpts): () => void;
77
84
  once(cb: (value: T, ctx: StoreCtx<T>) => void, opts?: StoreSubOpts): () => void;
78
85
  update<M extends StoreMask<T>>(mask: M, opts?: StoreSubOpts): StoreSelection<T, M>;
86
+ each(opts?: StoreEachOpts): ReturnType<typeof funcListenCallbackBase<[key: string, value: T[keyof T] | undefined, ctx: StoreEachCtx]>>;
79
87
  listen(): ReturnType<typeof listenUpdate>;
80
88
  listenPaths(): ReturnType<typeof listenUpdatePaths>;
81
89
  count(): number;
@@ -307,6 +307,48 @@ function createPatchesListen(store) {
307
307
  },
308
308
  });
309
309
  }
310
+ function createEachListen(store, opts = {}) {
311
+ if (opts.depth != null && opts.depth != 1)
312
+ throw new Error("store.each: only depth 1 is supported (reserved option)");
313
+ return (0, Listen_1.funcListenCallbackBase)((emit) => {
314
+ const known = new Set(Reflect.ownKeys((0, reactive2_1.toRaw)(store.state)));
315
+ function emitKey(key) {
316
+ const raw = (0, reactive2_1.toRaw)(store.state);
317
+ const exists = isObj(raw) && key in raw;
318
+ if (exists)
319
+ known.add(key);
320
+ else
321
+ known.delete(key);
322
+ emit(key, exists ? store.state[key] : undefined, { path: [key] });
323
+ }
324
+ const off = store.listenPaths().on(function eachStoreChange(change) {
325
+ const keys = new Set();
326
+ let root = false;
327
+ for (const path of change.paths) {
328
+ if (path.length == 0)
329
+ root = true;
330
+ else
331
+ keys.add(path[0]);
332
+ }
333
+ if (root) {
334
+ for (const key of Reflect.ownKeys((0, reactive2_1.toRaw)(store.state)))
335
+ keys.add(key);
336
+ for (const key of known)
337
+ keys.add(key);
338
+ }
339
+ for (const key of keys)
340
+ emitKey(key);
341
+ });
342
+ return off;
343
+ }, {
344
+ event: (type, count, api) => {
345
+ if (type == "add" && count == 1 && !api.isRun())
346
+ api.run();
347
+ if (type == "remove" && count == 0 && api.isRun())
348
+ api.close();
349
+ },
350
+ });
351
+ }
310
352
  function createChangedDataListen(store) {
311
353
  return (0, Listen_1.funcListenCallbackBase)((emit) => {
312
354
  const off = store.listenPaths().on((change) => {
@@ -458,6 +500,7 @@ function getNode(store, path) {
458
500
  store._nodeCache.set(k, proxy);
459
501
  return proxy;
460
502
  }
503
+ let warnedRootOnEach = false;
461
504
  function createSelection(store, base, mask, defaults = {}) {
462
505
  const fullPaths = maskPaths(mask, base);
463
506
  const rootNode = getNode(store, base);
@@ -486,6 +529,10 @@ function createSelection(store, base, mask, defaults = {}) {
486
529
  return off;
487
530
  },
488
531
  onEach(cb, opts = {}) {
532
+ if (fullPaths.some(p => p.length == base.length) && !warnedRootOnEach) {
533
+ warnedRootOnEach = true;
534
+ console.warn("store: update(true).onEach fires ONCE per drain window with the WHOLE value (per selected path, not per key). For per-changed-key delivery use store.each(); for a subset — an explicit key mask.");
535
+ }
489
536
  const o = { ...defaults, ...opts };
490
537
  const offs = fullPaths.map(p => subscribePath(store, p, cb, o, false));
491
538
  return () => { for (const off of offs)
@@ -508,6 +555,7 @@ function createStore(initial, opts = {}) {
508
555
  on: (cb, opts) => getNode(store, []).on(cb, opts),
509
556
  once: (cb, opts) => getNode(store, []).once(cb, opts),
510
557
  update: (mask, opts) => createSelection(store, [], mask, opts),
558
+ each: (opts) => createEachListen(store, opts),
511
559
  listen: () => (0, reactive2_1.listenUpdate)(state),
512
560
  listenPaths: () => (0, reactive2_1.listenUpdatePaths)(state),
513
561
  count: () => Array.from(store._counts.values()).reduce((a, b) => a + b, 0),
@@ -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 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
+ }
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
- const tail = since >= 0 ? await remote.since(since) : null;
107
- if (closed)
108
- return;
109
- if (tail) {
110
- for (const ev of tail)
111
- 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
+ }
112
132
  }
113
- else {
114
- const kf = await remote.keyframe();
133
+ if (!done) {
134
+ const tail = since >= 0 ? await remote.since(since) : null;
115
135
  if (closed)
116
136
  return;
117
- if (kf) {
118
- lastDelivered = kf.seq;
119
- lastTs = kf.ts;
120
- lastArrival = now();
121
- cb(...kf.event);
122
- 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
+ }
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;
@@ -8,11 +9,24 @@ type WithSubHandle<R> = R extends {
8
9
  once: (...a: A) => tSubHandle & Promise<void>;
9
10
  } : R;
10
11
  type Obj = Record<string, any>;
11
- type InferArgs<T> = T extends {
12
+ export 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
+ export 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
  };
@@ -2,16 +2,19 @@ import { type SocketTmpl } from "./rpc-protocol";
2
2
  import { type RpcLimits } from "./rpc-limits";
3
3
  import { makeOff } from "./rpc-off";
4
4
  import { type RpcOpt } from "./rpc-caps";
5
+ import type { IsReplayMember, InferArgs, ReplaySocketListen } from "./listen-deep";
5
6
  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 ? {
7
+ export type DeepDataOnly<T> = T extends Function ? never : T extends readonly any[] ? {
8
+ [I in keyof T]: DeepDataOnly<T[I]>;
9
+ } : T extends object ? {
7
10
  [K in keyof T as T[K] extends Function ? never : K]: DeepDataOnly<T[K]>;
8
11
  } : T;
9
12
  export type ClientAPIAll<T> = {
10
- [K in keyof T as T[K] extends Function ? K : T[K] extends object ? K : never]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<DeepDataOnly<UnwrapPromise<R>>> : T[K] extends object ? ClientAPIAll<T[K]> : never;
13
+ [K in keyof T as T[K] extends Function ? K : T[K] extends object ? K : never]: IsReplayMember<T[K]> extends true ? ReplaySocketListen<InferArgs<T[K]>> : T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<DeepDataOnly<UnwrapPromise<R>>> : T[K] extends object ? ClientAPIAll<T[K]> : never;
11
14
  };
12
15
  type NonFalsy<T> = Exclude<T, false | null | 0 | "" | undefined>;
13
16
  export type ClientAPIStrict<T> = {
14
- [K in keyof T as NonFalsy<T[K]> extends Function ? K : NonFalsy<T[K]> extends object ? K : never]: NonFalsy<T[K]> extends (...args: infer A) => infer R ? (...args: A) => Promise<DeepDataOnly<UnwrapPromise<R>>> : NonFalsy<T[K]> extends object ? ClientAPIStrict<NonFalsy<T[K]>> : never;
17
+ [K in keyof T as NonFalsy<T[K]> extends Function ? K : NonFalsy<T[K]> extends object ? K : never]: IsReplayMember<NonFalsy<T[K]>> extends true ? ReplaySocketListen<InferArgs<NonFalsy<T[K]>>> : NonFalsy<T[K]> extends (...args: infer A) => infer R ? (...args: A) => Promise<DeepDataOnly<UnwrapPromise<R>>> : NonFalsy<T[K]> extends object ? ClientAPIStrict<NonFalsy<T[K]>> : never;
15
18
  };
16
19
  export interface PipeArrayAPI<T> extends Promise<DeepDataOnly<T[]>> {
17
20
  [index: number]: PipeAPI<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.57",
3
+ "version": "1.0.61",
4
4
  "description": "Common library",
5
5
  "strict": true,
6
6
  "main": "lib/index.js",