wenay-common2 1.0.74 → 1.0.75

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,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.replayRouteSubscribe = replayRouteSubscribe;
4
+ const transport_lifecycle_1 = require("./transport-lifecycle");
4
5
  function unsubscribeHandle(handle) {
5
6
  if (typeof handle == 'function') {
6
7
  handle();
@@ -50,7 +51,8 @@ function replayRouteSubscribe(remote, cb, opts = {}) {
50
51
  let replaying = true;
51
52
  let lineError;
52
53
  const queue = [];
53
- const liveLine = policy == 'frame' && nextRemote.frameLine ? nextRemote.frameLine : nextRemote.line;
54
+ const frameLineState = (0, transport_lifecycle_1.getRpcMemberState)(nextRemote, 'frameLine');
55
+ const liveLine = policy == 'frame' && frameLineState != false && nextRemote.frameLine ? nextRemote.frameLine : nextRemote.line;
54
56
  const handle = liveLine.on(function liveTap(ev) {
55
57
  if (slotClosed)
56
58
  return;
@@ -76,7 +78,8 @@ function replayRouteSubscribe(remote, cb, opts = {}) {
76
78
  async function catchUp() {
77
79
  try {
78
80
  let done = false;
79
- if (since >= 0 && nextRemote.frame) {
81
+ const frameState = (0, transport_lifecycle_1.getRpcMemberState)(nextRemote, 'frame');
82
+ if (since >= 0 && frameState != false && nextRemote.frame) {
80
83
  const envs = await nextRemote.frame(since, hint);
81
84
  if (slotClosed)
82
85
  return;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.exposeReplay = exposeReplay;
4
4
  exports.replaySubscribe = replaySubscribe;
5
5
  const replay_conflate_1 = require("./replay-conflate");
6
+ const transport_lifecycle_1 = require("./transport-lifecycle");
6
7
  function exposeReplayPlain(replay) {
7
8
  return {
8
9
  line: replay.line,
@@ -29,10 +30,21 @@ function unsubscribeHandle(handle) {
29
30
  }
30
31
  function replaySubscribe(remote, cb, opts = {}) {
31
32
  const { since = -1, onSeq, onError, staleMs, onStale, skewMs = 0, now = Date.now, policy = 'queue', hint } = opts;
33
+ const lifecycle = (0, transport_lifecycle_1.getRpcTransportLifecycle)(remote);
32
34
  let lastDelivered = since;
33
35
  let replaying = true;
34
36
  let closed = false;
37
+ let recoveryGeneration = 0;
35
38
  const queue = [];
39
+ let readySettled = false;
40
+ let resolveReady = function resolveReadyLater() { };
41
+ const ready = new Promise(function waitUntilReady(resolve) { resolveReady = resolve; });
42
+ function settleReady() {
43
+ if (readySettled)
44
+ return;
45
+ readySettled = true;
46
+ resolveReady();
47
+ }
36
48
  let lastTs = 0;
37
49
  let lastArrival = now();
38
50
  let staleFlag = false;
@@ -95,17 +107,68 @@ function replaySubscribe(remote, cb, opts = {}) {
95
107
  if (!replaying)
96
108
  assessStale();
97
109
  }
98
- const liveLine = policy == 'frame' && remote.frameLine ? remote.frameLine : remote.line;
99
- const handle = liveLine.on(function liveTap(ev) {
110
+ function deliverSorted(events, allowReset) {
111
+ const sorted = [...events].sort((a, b) => a.seq - b.seq);
112
+ if (allowReset && sorted.length && sorted[0].seq <= lastDelivered) {
113
+ lastDelivered = sorted[0].seq - 1;
114
+ }
115
+ for (const event of sorted)
116
+ deliver(event);
117
+ }
118
+ function drainLiveQueue() {
119
+ while (queue.length) {
120
+ const batch = queue.splice(0).sort((a, b) => a.seq - b.seq);
121
+ for (const event of batch)
122
+ deliver(event);
123
+ }
124
+ }
125
+ const frameLineState = (0, transport_lifecycle_1.getRpcMemberState)(remote, 'frameLine');
126
+ const liveLine = policy == 'frame' && frameLineState != false && remote.frameLine ? remote.frameLine : remote.line;
127
+ let handle = null;
128
+ let offConnect = function noConnectListener() { };
129
+ let offDisconnect = function noDisconnectListener() { };
130
+ let offClose = function noCloseListener() { };
131
+ function closeSubscription() {
132
+ if (closed)
133
+ return false;
134
+ closed = true;
135
+ recoveryGeneration++;
136
+ queue.length = 0;
137
+ stopStaleTimer();
138
+ offConnect();
139
+ offDisconnect();
140
+ offClose();
141
+ unsubscribeHandle(handle);
142
+ settleReady();
143
+ return true;
144
+ }
145
+ function errorText(error) {
146
+ if (typeof error?.message == 'string')
147
+ return error.message;
148
+ if (typeof error?.error?.message == 'string')
149
+ return error.error.message;
150
+ return String(error);
151
+ }
152
+ function failRecovery(error, point, phase) {
153
+ const wrapped = new Error('replaySubscribe: ' + phase + ' from seq ' + point + ' failed: ' + errorText(error));
154
+ wrapped.cause = error;
155
+ if (!closeSubscription())
156
+ return;
157
+ if (onError) {
158
+ try {
159
+ onError(wrapped);
160
+ }
161
+ catch (caught) {
162
+ setTimeout(function rethrowOnError() { throw caught; }, 0);
163
+ }
164
+ }
165
+ else {
166
+ setTimeout(function rethrowRecoveryError() { throw wrapped; }, 0);
167
+ }
168
+ }
169
+ handle = liveLine.on(function liveTap(ev) {
100
170
  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);
171
+ failRecovery(new Error('line ended by server (' + String(ev) + ')'), lastDelivered, 'live line');
109
172
  return;
110
173
  }
111
174
  lastArrival = now();
@@ -114,64 +177,91 @@ function replaySubscribe(remote, cb, opts = {}) {
114
177
  else
115
178
  deliver(ev);
116
179
  });
117
- async function catchUp() {
180
+ if (typeof handle?.then == 'function') {
181
+ handle.then(function logicalLineEnded() {
182
+ if (!closed)
183
+ failRecovery(new Error('logical RPC line ended'), lastDelivered, 'live line');
184
+ }, function logicalLineFailed(error) {
185
+ if (!closed)
186
+ failRecovery(error, lastDelivered, 'live line');
187
+ });
188
+ }
189
+ function isCurrent(generation) {
190
+ return !closed && generation == recoveryGeneration;
191
+ }
192
+ async function catchUp(generation, point, initial) {
118
193
  try {
119
194
  let done = false;
120
- if (since >= 0 && remote.frame) {
121
- const envs = await remote.frame(since, hint);
122
- if (closed)
195
+ const frameState = (0, transport_lifecycle_1.getRpcMemberState)(remote, 'frame');
196
+ if (point >= 0 && frameState != false && remote.frame) {
197
+ const envelopes = await remote.frame(point, hint);
198
+ if (!isCurrent(generation))
123
199
  return;
124
- if (envs) {
125
- if (envs.length) {
126
- lastDelivered = envs[0].seq - 1;
127
- for (const ev of envs)
128
- deliver(ev);
129
- }
200
+ if (envelopes) {
201
+ deliverSorted(envelopes, initial);
130
202
  done = true;
131
203
  }
132
204
  }
133
205
  if (!done) {
134
- const tail = since >= 0 ? await remote.since(since) : null;
135
- if (closed)
206
+ const tail = point >= 0 ? await remote.since(point) : null;
207
+ if (!isCurrent(generation))
136
208
  return;
137
209
  if (tail) {
138
- for (const ev of tail)
139
- deliver(ev);
210
+ deliverSorted(tail, false);
140
211
  }
141
212
  else {
142
- const kf = await remote.keyframe();
143
- if (closed)
213
+ const keyframe = await remote.keyframe();
214
+ if (!isCurrent(generation))
144
215
  return;
145
- if (kf) {
146
- lastDelivered = kf.seq;
147
- lastTs = kf.ts;
148
- lastArrival = now();
149
- cb(...kf.event);
150
- onSeq?.(kf.seq);
216
+ if (keyframe) {
217
+ if (initial && keyframe.seq <= lastDelivered)
218
+ lastDelivered = keyframe.seq - 1;
219
+ deliver(keyframe);
220
+ }
221
+ else if (point >= 0) {
222
+ throw new Error('journal evicted or unavailable; no keyframe can cover the gap');
151
223
  }
152
224
  }
153
225
  }
154
- }
155
- catch (e) {
156
- if (onError)
157
- onError(e);
158
- else
159
- setTimeout(function rethrowCatchUp() { throw e; }, 0);
160
- }
161
- finally {
162
- while (queue.length)
163
- deliver(queue.shift());
226
+ if (!isCurrent(generation))
227
+ return;
228
+ drainLiveQueue();
164
229
  replaying = false;
165
230
  assessStale();
231
+ settleReady();
232
+ }
233
+ catch (error) {
234
+ if (!isCurrent(generation))
235
+ return;
236
+ failRecovery(error, point, initial ? 'initial catch-up' : 'reconnect catch-up');
166
237
  }
167
238
  }
168
- const ready = catchUp();
169
- function off() {
239
+ function startCatchUp(initial) {
170
240
  if (closed)
171
241
  return;
172
- closed = true;
173
- stopStaleTimer();
174
- unsubscribeHandle(handle);
242
+ replaying = true;
243
+ const generation = ++recoveryGeneration;
244
+ void catchUp(generation, lastDelivered, initial);
245
+ }
246
+ if (lifecycle) {
247
+ offDisconnect = lifecycle.onDisconnect(function replayTransportDisconnected() {
248
+ if (closed)
249
+ return;
250
+ replaying = true;
251
+ recoveryGeneration++;
252
+ queue.length = 0;
253
+ });
254
+ offConnect = lifecycle.onConnect(function replayTransportConnected() {
255
+ startCatchUp(!readySettled);
256
+ });
257
+ offClose = lifecycle.onClose(function replayTransportClosed() {
258
+ closeSubscription();
259
+ });
260
+ }
261
+ if (!lifecycle || lifecycle.connected())
262
+ startCatchUp(true);
263
+ function off() {
264
+ closeSubscription();
175
265
  }
176
266
  return Object.assign(off, {
177
267
  ready,
@@ -0,0 +1,23 @@
1
+ export declare const RPC_TRANSPORT_LIFECYCLE: unique symbol;
2
+ export declare const RPC_TRANSPORT_CONTROL: unique symbol;
3
+ export declare const RPC_MEMBER_LOOKUP: unique symbol;
4
+ export type RpcMemberLookup = (member: string) => boolean | undefined;
5
+ export declare function getRpcMemberState(remote: any, member: string): boolean | undefined;
6
+ export type TransportLifecycleApi = {
7
+ connected: () => boolean;
8
+ closed: () => boolean;
9
+ generation: () => number;
10
+ onConnect: (cb: (generation: number) => void) => () => void;
11
+ onDisconnect: (cb: (reason: string, generation: number) => void) => () => void;
12
+ onClose: (cb: (reason: string) => void) => () => void;
13
+ };
14
+ export declare function getRpcTransportLifecycle(remote: any): TransportLifecycleApi | undefined;
15
+ export type TransportLifecycleControl = {
16
+ connect: () => void;
17
+ disconnect: (reason: string) => void;
18
+ close: (reason: string) => void;
19
+ };
20
+ export declare function createTransportLifecycle(initialConnected?: boolean): {
21
+ api: TransportLifecycleApi;
22
+ control: TransportLifecycleControl;
23
+ };
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RPC_MEMBER_LOOKUP = exports.RPC_TRANSPORT_CONTROL = exports.RPC_TRANSPORT_LIFECYCLE = void 0;
4
+ exports.getRpcMemberState = getRpcMemberState;
5
+ exports.getRpcTransportLifecycle = getRpcTransportLifecycle;
6
+ exports.createTransportLifecycle = createTransportLifecycle;
7
+ exports.RPC_TRANSPORT_LIFECYCLE = Symbol.for('wenay-common2.rpc.transportLifecycle');
8
+ exports.RPC_TRANSPORT_CONTROL = Symbol.for('wenay-common2.rpc.transportControl');
9
+ exports.RPC_MEMBER_LOOKUP = Symbol.for('wenay-common2.rpc.memberLookup');
10
+ function getRpcMemberState(remote, member) {
11
+ let lookup;
12
+ try {
13
+ const candidate = remote?.[exports.RPC_MEMBER_LOOKUP];
14
+ if (typeof candidate != 'function')
15
+ return undefined;
16
+ if (Object.getOwnPropertyDescriptor(candidate, exports.RPC_MEMBER_LOOKUP)?.value != true)
17
+ return undefined;
18
+ lookup = candidate;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ return lookup(member);
24
+ }
25
+ function getRpcTransportLifecycle(remote) {
26
+ try {
27
+ const candidate = remote?.[exports.RPC_TRANSPORT_LIFECYCLE];
28
+ if (candidate == null || (typeof candidate != 'object' && typeof candidate != 'function'))
29
+ return undefined;
30
+ if (Object.getOwnPropertyDescriptor(candidate, exports.RPC_TRANSPORT_LIFECYCLE)?.value != true)
31
+ return undefined;
32
+ return candidate;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
38
+ function createTransportLifecycle(initialConnected = true) {
39
+ let online = initialConnected;
40
+ let terminal = false;
41
+ let generation = initialConnected ? 1 : 0;
42
+ const connectCbs = new Set();
43
+ const disconnectCbs = new Set();
44
+ const closeCbs = new Set();
45
+ function onConnect(cb) {
46
+ connectCbs.add(cb);
47
+ return function offConnect() { connectCbs.delete(cb); };
48
+ }
49
+ function onDisconnect(cb) {
50
+ disconnectCbs.add(cb);
51
+ return function offDisconnect() { disconnectCbs.delete(cb); };
52
+ }
53
+ function onClose(cb) {
54
+ closeCbs.add(cb);
55
+ return function offClose() { closeCbs.delete(cb); };
56
+ }
57
+ function connect() {
58
+ if (terminal || online)
59
+ return;
60
+ online = true;
61
+ generation++;
62
+ for (const cb of [...connectCbs])
63
+ cb(generation);
64
+ }
65
+ function disconnect(reason) {
66
+ if (terminal || !online)
67
+ return;
68
+ online = false;
69
+ for (const cb of [...disconnectCbs])
70
+ cb(reason, generation);
71
+ }
72
+ function close(reason) {
73
+ if (terminal)
74
+ return;
75
+ terminal = true;
76
+ online = false;
77
+ for (const cb of [...closeCbs])
78
+ cb(reason);
79
+ connectCbs.clear();
80
+ disconnectCbs.clear();
81
+ closeCbs.clear();
82
+ }
83
+ const api = {
84
+ connected: () => online,
85
+ closed: () => terminal,
86
+ generation: () => generation,
87
+ onConnect,
88
+ onDisconnect,
89
+ onClose,
90
+ };
91
+ Object.defineProperty(api, exports.RPC_TRANSPORT_LIFECYCLE, { value: true });
92
+ const control = { connect, disconnect, close };
93
+ return { api, control };
94
+ }