@cotal-ai/core 0.68.0 → 0.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-file.d.ts.map +1 -1
- package/dist/agent-file.js +8 -6
- package/dist/agent-file.js.map +1 -1
- package/dist/connector-config.d.ts +4 -0
- package/dist/connector-config.d.ts.map +1 -1
- package/dist/connector-config.js +49 -0
- package/dist/connector-config.js.map +1 -1
- package/dist/endpoint-invoke.d.ts +23 -0
- package/dist/endpoint-invoke.d.ts.map +1 -1
- package/dist/endpoint-invoke.js +50 -4
- package/dist/endpoint-invoke.js.map +1 -1
- package/dist/endpoint-serve-kv.d.ts +0 -1
- package/dist/endpoint-serve-kv.d.ts.map +1 -1
- package/dist/endpoint-serve-kv.js +0 -1
- package/dist/endpoint-serve-kv.js.map +1 -1
- package/dist/endpoint-service.d.ts +12 -6
- package/dist/endpoint-service.d.ts.map +1 -1
- package/dist/endpoint-service.js +29 -23
- package/dist/endpoint-service.js.map +1 -1
- package/dist/endpoint-verbs.d.ts.map +1 -1
- package/dist/endpoint-verbs.js +2 -1
- package/dist/endpoint-verbs.js.map +1 -1
- package/dist/endpoint.d.ts +25 -19
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +81 -76
- package/dist/endpoint.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/kv-scan.d.ts +1 -3
- package/dist/kv-scan.d.ts.map +1 -1
- package/dist/kv-scan.js.map +1 -1
- package/dist/loopback.d.ts +8 -0
- package/dist/loopback.d.ts.map +1 -0
- package/dist/loopback.js +17 -0
- package/dist/loopback.js.map +1 -0
- package/dist/managed-handoff.d.ts.map +1 -1
- package/dist/managed-handoff.js +1 -18
- package/dist/managed-handoff.js.map +1 -1
- package/dist/presence-ages.d.ts +22 -0
- package/dist/presence-ages.d.ts.map +1 -0
- package/dist/presence-ages.js +33 -0
- package/dist/presence-ages.js.map +1 -0
- package/dist/provision.d.ts +0 -4
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +1 -11
- package/dist/provision.js.map +1 -1
- package/dist/remote-manager-authority.d.ts +8 -5
- package/dist/remote-manager-authority.d.ts.map +1 -1
- package/dist/remote-manager-authority.js.map +1 -1
- package/dist/secret-store.d.ts +6 -2
- package/dist/secret-store.d.ts.map +1 -1
- package/dist/secret-store.js +9 -6
- package/dist/secret-store.js.map +1 -1
- package/dist/streams.d.ts.map +1 -1
- package/dist/streams.js +0 -1
- package/dist/streams.js.map +1 -1
- package/dist/subjects.d.ts +5 -0
- package/dist/subjects.d.ts.map +1 -1
- package/dist/subjects.js +11 -0
- package/dist/subjects.js.map +1 -1
- package/package.json +1 -1
package/dist/endpoint.js
CHANGED
|
@@ -249,9 +249,6 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
249
249
|
* EXPECTED async permission violation that joinChannel turns into a clean throw, so watchStatus
|
|
250
250
|
* suppresses it rather than surfacing a spurious connection error. */
|
|
251
251
|
confirmingChatSubs = new Set();
|
|
252
|
-
/** Delete subjects of this endpoint's own consumers whose refusal watchStatus has not consumed yet
|
|
253
|
-
* (see {@link deleteOwnConsumer}). */
|
|
254
|
-
ownConsumerDeletes = new Set();
|
|
255
252
|
/** `$JS.API.CONSUMER.DELETE.<stream>.<prefix>_` for each of this connection's own KV watches,
|
|
256
253
|
* running membership scans and running history reads (see {@link recordOwnWatchConsumer}). */
|
|
257
254
|
ownWatchDeletePrefixes = new Set();
|
|
@@ -1222,7 +1219,6 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
1222
1219
|
this.chatSubs.clear();
|
|
1223
1220
|
this.chatSubDenied.clear();
|
|
1224
1221
|
this.confirmingChatSubs.clear();
|
|
1225
|
-
this.ownConsumerDeletes.clear();
|
|
1226
1222
|
this.ownWatchDeletePrefixes.clear();
|
|
1227
1223
|
this.roster.clear();
|
|
1228
1224
|
// #1356: the presence-refusal record is connection-scoped like everything else torn down here.
|
|
@@ -2017,6 +2013,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2017
2013
|
return () => { reconnectHandler = undefined; };
|
|
2018
2014
|
},
|
|
2019
2015
|
signal: abortController.signal,
|
|
2016
|
+
prepare: opts.prepare,
|
|
2020
2017
|
});
|
|
2021
2018
|
}
|
|
2022
2019
|
finally {
|
|
@@ -2079,10 +2076,22 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2079
2076
|
}
|
|
2080
2077
|
return invokeCommand(nc, this.space, service, command, args, { ...invokeOpts, signal });
|
|
2081
2078
|
};
|
|
2082
|
-
|
|
2079
|
+
// A `failed-precondition` from the resolve is its own refusal, raised before any command was
|
|
2080
|
+
// published, so resolving once more is a repair.
|
|
2081
|
+
const resolveFirst = async (signal = opts.signal) => {
|
|
2082
|
+
try {
|
|
2083
|
+
return await resolve(signal);
|
|
2084
|
+
}
|
|
2085
|
+
catch (e) {
|
|
2086
|
+
if (!(e instanceof EpEnvelopeError) || e.code !== "failed-precondition")
|
|
2087
|
+
throw e;
|
|
2088
|
+
return await resolve(signal);
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
const doInvoke = async (service, signal = opts.signal) => {
|
|
2083
2092
|
signal?.throwIfAborted();
|
|
2084
2093
|
try {
|
|
2085
|
-
let r = await invokeResolved(
|
|
2094
|
+
let r = await invokeResolved(service, signal);
|
|
2086
2095
|
// THE RESPONDER FENCED IT (§13.2 `ai.cotal.ep.bind-refused`): a class member saw the call
|
|
2087
2096
|
// was bound to a different incarnation and refused BEFORE running the command.
|
|
2088
2097
|
//
|
|
@@ -2149,8 +2158,6 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2149
2158
|
return r;
|
|
2150
2159
|
}
|
|
2151
2160
|
catch (e) {
|
|
2152
|
-
if (!(e instanceof EpEnvelopeError))
|
|
2153
|
-
throw e;
|
|
2154
2161
|
// DO NOT auto-retry a command a responder already ANSWERED. This path covers the responders
|
|
2155
2162
|
// WITHOUT the fence above: they ignore `bind`, run the command, and the error is raised
|
|
2156
2163
|
// afterwards, so core cannot tell a repair from a duplicate and the allowlist is the only
|
|
@@ -2183,23 +2190,20 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2183
2190
|
throw e;
|
|
2184
2191
|
return await invokeResolved(await resolve(signal), signal);
|
|
2185
2192
|
}
|
|
2186
|
-
|
|
2187
|
-
// was published, so re-resolving once is a repair. The `replyRefusedBeforeEffect` half keeps
|
|
2188
|
-
// out the refusal THIS method raises after a failed re-issue: it carries that same code, and
|
|
2189
|
-
// would otherwise fall into the re-resolve below as a THIRD attempt at a command whose
|
|
2190
|
-
// second could not even be resolved. A marker means the disposition is already decided,
|
|
2191
|
-
// whichever code carries it.
|
|
2192
|
-
if (e.code !== "failed-precondition" || replyRefusedBeforeEffect(e.toEpError()))
|
|
2193
|
-
throw e;
|
|
2194
|
-
this.resolvedServices.delete(endpoint);
|
|
2195
|
-
return await invokeResolved(await resolve(signal), signal);
|
|
2193
|
+
throw e;
|
|
2196
2194
|
}
|
|
2197
2195
|
};
|
|
2198
2196
|
// P2 item 2 (2b): a goal-bearing command (spawn/launch) follows its acceptance to the terminal so
|
|
2199
2197
|
// the caller still returns on the real outcome (UX unchanged); every other command replies directly.
|
|
2200
2198
|
if (!opts.follow)
|
|
2201
|
-
return doInvoke();
|
|
2202
|
-
|
|
2199
|
+
return doInvoke(await resolveFirst());
|
|
2200
|
+
// Resolved before the submission starts: the follow reports an unmarked failure inside the
|
|
2201
|
+
// submission as a command that may have run, and the resolve publishes none of this call.
|
|
2202
|
+
let service;
|
|
2203
|
+
return this.followServiceGoal(endpoint, (signal) => doInvoke(service, signal), opts.deadlineMs ?? 10_000, {
|
|
2204
|
+
signal: opts.signal,
|
|
2205
|
+
prepare: async (signal) => { service = await resolveFirst(signal); },
|
|
2206
|
+
});
|
|
2203
2207
|
}
|
|
2204
2208
|
/** Send a durable-membership request to the SERVER-SIDE delivery daemon (`ctl.delivery`) and await its
|
|
2205
2209
|
* reply. Unlike {@link requestControl}, the reply rides a subject UNDER `ctl.delivery.<id>.>` (not the
|
|
@@ -2562,7 +2566,6 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2562
2566
|
let scanPrefix;
|
|
2563
2567
|
const entries = await liveKvEntries(kv, {
|
|
2564
2568
|
onConsumer: (info) => { scanPrefix = this.recordOwnWatchConsumer(info); },
|
|
2565
|
-
deleteOwnConsumer: (stream, name, del) => this.deleteOwnConsumer(stream, name, del),
|
|
2566
2569
|
}).finally(() => {
|
|
2567
2570
|
// The scan's own last delete goes out after nats.js closed its consumer, so every predecessor
|
|
2568
2571
|
// delete the library sent is answered ahead of it on this connection.
|
|
@@ -2644,15 +2647,11 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2644
2647
|
watch.consumer = consumer;
|
|
2645
2648
|
watch.consumerStream = info.stream_name;
|
|
2646
2649
|
watch.consumerName = info.name;
|
|
2647
|
-
let pending = info.num_pending;
|
|
2648
2650
|
let iter;
|
|
2649
2651
|
try {
|
|
2650
|
-
iter = await consumer.consume({ callback: (
|
|
2651
|
-
const isUpdate = pending === 0 || --pending === 0;
|
|
2652
|
-
const entry = kv.jmToWatchEntry(msg, isUpdate);
|
|
2652
|
+
iter = await consumer.consume({ callback: () => {
|
|
2653
2653
|
if (!watch.stopped && watch.consumer === consumer)
|
|
2654
2654
|
watch.onChange();
|
|
2655
|
-
void entry;
|
|
2656
2655
|
} });
|
|
2657
2656
|
}
|
|
2658
2657
|
catch (err) {
|
|
@@ -2694,31 +2693,12 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2694
2693
|
watch.resolveStop = undefined;
|
|
2695
2694
|
watch.rejectStop = undefined;
|
|
2696
2695
|
}
|
|
2697
|
-
/** Run one delete of a consumer this endpoint created. A profile without the delete row is refused
|
|
2698
|
-
* by design (#691), and nats.js reports a refused request twice: as the request's rejection, which
|
|
2699
|
-
* the caller handles, and on the connection status, which watchStatus would emit as an `error`.
|
|
2700
|
-
* A refused subject stays recorded until watchStatus drops that echo, since the two settle in
|
|
2701
|
-
* either order. */
|
|
2702
|
-
async deleteOwnConsumer(stream, name, del) {
|
|
2703
|
-
const subject = `$JS.API.CONSUMER.DELETE.${stream}.${name}`;
|
|
2704
|
-
this.ownConsumerDeletes.add(subject);
|
|
2705
|
-
let refused = false;
|
|
2706
|
-
try {
|
|
2707
|
-
return await del();
|
|
2708
|
-
}
|
|
2709
|
-
catch (err) {
|
|
2710
|
-
refused = isPublishPermissionDenied(err);
|
|
2711
|
-
throw err;
|
|
2712
|
-
}
|
|
2713
|
-
finally {
|
|
2714
|
-
if (!refused)
|
|
2715
|
-
this.ownConsumerDeletes.delete(subject);
|
|
2716
|
-
}
|
|
2717
|
-
}
|
|
2718
2696
|
/** Record the consumer behind one of this connection's own KV watches, membership scans or history
|
|
2719
2697
|
* reads, and return the delete-subject prefix it recorded. nats.js names it `<prefix>_<serial>` and,
|
|
2720
2698
|
* rebuilding it after a stall or a sequence gap, deletes the predecessor itself with no hook before
|
|
2721
|
-
* the send, so a profile without the delete row (#691) has that delete refused. A
|
|
2699
|
+
* the send, so a profile without the delete row (#691) has that delete refused. A stalled link can
|
|
2700
|
+
* time that delete out before its refusal arrives, and nats.js then hands the refusal to no request,
|
|
2701
|
+
* so {@link trackRequestDenials} cannot own it and the consumer's name has to. A watch's prefix
|
|
2722
2702
|
* stays recorded for the connection because a retired watch's last rebuild can be refused after
|
|
2723
2703
|
* the watch has stopped; {@link readMembership} and {@link drainWindow} retire theirs after their own
|
|
2724
2704
|
* last delete, which goes out once nats.js has stopped the consumer and so is answered after every
|
|
@@ -2737,9 +2717,8 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2737
2717
|
* the elevated profile, which holds no stream-wide CONSUMER.DELETE (#691): the broker reaps the
|
|
2738
2718
|
* consumer at its inactive threshold. */
|
|
2739
2719
|
async deleteReaderConsumer(consumer) {
|
|
2740
|
-
const { stream_name, name } = await consumer.info(true);
|
|
2741
2720
|
try {
|
|
2742
|
-
await
|
|
2721
|
+
await consumer.delete();
|
|
2743
2722
|
}
|
|
2744
2723
|
catch (e) {
|
|
2745
2724
|
if (!isJetStreamMissing(e, JetStreamApiCodes.ConsumerNotFound) && !isPublishPermissionDenied(e))
|
|
@@ -2749,7 +2728,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2749
2728
|
/** Delete one membership-watch consumer, swallowing ONLY already-gone and a refused delete. */
|
|
2750
2729
|
async deleteMembershipConsumer(jsm, stream, name) {
|
|
2751
2730
|
try {
|
|
2752
|
-
return await
|
|
2731
|
+
return await jsm.consumers.delete(stream, name);
|
|
2753
2732
|
}
|
|
2754
2733
|
catch (err) {
|
|
2755
2734
|
if (membershipConsumerReleased(err))
|
|
@@ -2771,8 +2750,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2771
2750
|
catch { /* already closed */ }
|
|
2772
2751
|
if (consumer) {
|
|
2773
2752
|
try {
|
|
2774
|
-
const
|
|
2775
|
-
const deleted = await this.deleteOwnConsumer(stream_name, name, () => consumer.delete());
|
|
2753
|
+
const deleted = await consumer.delete();
|
|
2776
2754
|
if (deleted) {
|
|
2777
2755
|
watch.consumerStream = undefined;
|
|
2778
2756
|
watch.consumerName = undefined;
|
|
@@ -3271,15 +3249,18 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3271
3249
|
// ---- internals -----------------------------------------------------------
|
|
3272
3250
|
/**
|
|
3273
3251
|
* Surface the connection's async status errors on our `error` event. NATS reports
|
|
3274
|
-
* publish permission violations *only* here (subscription
|
|
3275
|
-
*
|
|
3276
|
-
* traffic and it just looks "absent".
|
|
3277
|
-
*
|
|
3252
|
+
* publish permission violations *only* here (subscription ones too), never on the
|
|
3253
|
+
* failing call — so without this an over-tight ACL silently drops the agent's
|
|
3254
|
+
* traffic and it just looks "absent". A denied request is the exception: nats.js also
|
|
3255
|
+
* rejects that request with the denial, so its caller owns it (see {@link trackRequestDenials}).
|
|
3256
|
+
* We annotate permission denials explicitly so a denial is never mistaken for absence
|
|
3257
|
+
* (which already has a benign cause: MCP reconnect).
|
|
3278
3258
|
*/
|
|
3279
3259
|
watchStatus() {
|
|
3280
3260
|
const nc = this.nc;
|
|
3281
3261
|
if (!nc)
|
|
3282
3262
|
return;
|
|
3263
|
+
const handedToRequest = this.trackRequestDenials(nc);
|
|
3283
3264
|
void (async () => {
|
|
3284
3265
|
for await (const s of nc.status()) {
|
|
3285
3266
|
// A rebuild can replace `this.nc` before the old iterator finishes. Late disconnect/close
|
|
@@ -3309,11 +3290,12 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3309
3290
|
// and turns into a clean throw — it is not a connection error to surface.
|
|
3310
3291
|
if (s.error instanceof PermissionViolationError && this.confirmingChatSubs.has(s.error.subject))
|
|
3311
3292
|
continue;
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
//
|
|
3293
|
+
if (s.error instanceof PermissionViolationError && handedToRequest.has(s.error))
|
|
3294
|
+
continue;
|
|
3295
|
+
// The predecessor nats.js deleted while rebuilding one of this connection's watches, scans or
|
|
3296
|
+
// history reads, whose refusal can arrive after that delete timed out.
|
|
3315
3297
|
if (s.error instanceof PermissionViolationError && s.error.operation === "publish"
|
|
3316
|
-
&&
|
|
3298
|
+
&& this.isOwnWatchDelete(s.error.subject))
|
|
3317
3299
|
continue;
|
|
3318
3300
|
this.emit("error", describeStatusError(s.error));
|
|
3319
3301
|
}
|
|
@@ -3337,6 +3319,29 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3337
3319
|
return;
|
|
3338
3320
|
this.emit("transport", { connected: true, server: nc.getServer() });
|
|
3339
3321
|
}
|
|
3322
|
+
/** Wrap `nc.request` to record each publish denial nats.js rejects one of this connection's requests
|
|
3323
|
+
* with. nats.js rejects the pending request on the denied subject with the denial and then dispatches
|
|
3324
|
+
* that same error instance on the connection status, so the caller that received it (an empty history
|
|
3325
|
+
* read, a refused consumer delete, nats.js's own delete of a rebuilt watch's predecessor) owns it and
|
|
3326
|
+
* the status loop skips it. nats.js settles the request before it dispatches the status and this
|
|
3327
|
+
* reaction sits directly on the promise it returns, so it runs before the status loop body, which is
|
|
3328
|
+
* further microtasks away behind the status iterator. Any other denial is a different instance and is
|
|
3329
|
+
* not recorded, including one on the same subject and one that arrives after its request timed out.
|
|
3330
|
+
* A refused request inbox is rejected into every pending request but breaks every later one, so it
|
|
3331
|
+
* is not recorded either. */
|
|
3332
|
+
trackRequestDenials(nc) {
|
|
3333
|
+
const handed = new WeakSet();
|
|
3334
|
+
const request = nc.request.bind(nc);
|
|
3335
|
+
nc.request = (subject, payload, opts) => {
|
|
3336
|
+
const reply = request(subject, payload, opts);
|
|
3337
|
+
reply.catch(({ cause }) => {
|
|
3338
|
+
if (cause instanceof PermissionViolationError && cause.operation === "publish")
|
|
3339
|
+
handed.add(cause);
|
|
3340
|
+
});
|
|
3341
|
+
return reply;
|
|
3342
|
+
};
|
|
3343
|
+
return handed;
|
|
3344
|
+
}
|
|
3340
3345
|
/** The error message for a guard that finds the endpoint unbound: "reconnecting" during a
|
|
3341
3346
|
* rebuild's null window OR an inter-retry backoff (so a concurrent op reports the real
|
|
3342
3347
|
* reason, not "not started" — `reestablishing` spans the whole retry loop incl. backoff),
|
|
@@ -6041,28 +6046,29 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
6041
6046
|
stuck: this.presenceWriteEscalated,
|
|
6042
6047
|
};
|
|
6043
6048
|
}
|
|
6044
|
-
/** Bind a presence watch on the current connection. Resolves
|
|
6045
|
-
* installed,
|
|
6046
|
-
* bind's iterator is released here and nothing is installed, because the
|
|
6047
|
-
* for it is gone and the epoch that replaced it binds its own watch through
|
|
6049
|
+
/** Bind a presence watch on the current connection. Resolves the bound consumer's info when
|
|
6050
|
+
* the watch was installed, undefined when the endpoint stopped or rebuilt while the bind was
|
|
6051
|
+
* in flight: that bind's iterator is released here and nothing is installed, because the
|
|
6052
|
+
* epoch that asked for it is gone and the epoch that replaced it binds its own watch through
|
|
6048
6053
|
* {@link connectAndBind}. Without this fence a bind that completes after {@link stop} would
|
|
6049
6054
|
* resurrect a watch on a stopped endpoint, and one that completes after a rebuild would
|
|
6050
6055
|
* overwrite the fresh epoch's watch with a dead-connection iterator. */
|
|
6051
6056
|
async startPresenceWatch() {
|
|
6052
6057
|
if (!this.kv)
|
|
6053
|
-
return
|
|
6058
|
+
return undefined;
|
|
6054
6059
|
const epoch = this.presenceEpoch;
|
|
6055
6060
|
let hydrated;
|
|
6056
6061
|
this.presenceSnapshot = new Promise((resolve) => { hydrated = resolve; });
|
|
6057
6062
|
const iter = await this.kv.watch();
|
|
6058
|
-
|
|
6063
|
+
const info = await kvWatchConsumer(iter).info(true);
|
|
6064
|
+
this.recordOwnWatchConsumer(info);
|
|
6059
6065
|
if (epoch !== this.presenceEpoch) {
|
|
6060
6066
|
try {
|
|
6061
6067
|
iter.stop();
|
|
6062
6068
|
}
|
|
6063
6069
|
catch { /* its connection may already be gone */ }
|
|
6064
6070
|
hydrated();
|
|
6065
|
-
return
|
|
6071
|
+
return undefined;
|
|
6066
6072
|
}
|
|
6067
6073
|
this.presenceWatchIter = iter;
|
|
6068
6074
|
void (async () => {
|
|
@@ -6083,7 +6089,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
6083
6089
|
}
|
|
6084
6090
|
hydrated();
|
|
6085
6091
|
})().catch((e) => this.emit("error", e));
|
|
6086
|
-
return
|
|
6092
|
+
return info;
|
|
6087
6093
|
}
|
|
6088
6094
|
/**
|
|
6089
6095
|
* Replace a presence watch that has gone silent past TTL while the connection is up. The new
|
|
@@ -6111,11 +6117,11 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
6111
6117
|
// one a held link never answers must leave the old watch in place: on a plain stall that
|
|
6112
6118
|
// watch is the one that recovers by itself, and its replay is still guarded against
|
|
6113
6119
|
// expired PUTs. Only a successfully bound watch retires its predecessor.
|
|
6114
|
-
const
|
|
6120
|
+
const info = await this.startPresenceWatch();
|
|
6115
6121
|
// Retired mid-bind (stop or rebuild moved the epoch): the late iterator is already
|
|
6116
6122
|
// released and the old watch was torn down by whoever moved the epoch. Nothing to
|
|
6117
6123
|
// retire, nothing to report.
|
|
6118
|
-
if (!
|
|
6124
|
+
if (!info)
|
|
6119
6125
|
return;
|
|
6120
6126
|
if (old && old !== this.presenceWatchIter) {
|
|
6121
6127
|
try {
|
|
@@ -6124,11 +6130,10 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
6124
6130
|
catch { /* already closed with its consumer */ }
|
|
6125
6131
|
}
|
|
6126
6132
|
// A bucket with no keys replays nothing, so the new watch cannot refresh
|
|
6127
|
-
// `lastPresenceWatchAt` by delivering. It IS current knowledge: nobody is present.
|
|
6128
|
-
//
|
|
6129
|
-
// from the same `info(true)
|
|
6130
|
-
|
|
6131
|
-
if (pending === 0)
|
|
6133
|
+
// `lastPresenceWatchAt` by delivering. It IS current knowledge: nobody is present. The
|
|
6134
|
+
// bind's consumer info carries the initial pending count for that one fact; nats.js's KV
|
|
6135
|
+
// watch placed the isUpdate marker from the same `info(true)`.
|
|
6136
|
+
if (info.num_pending === 0)
|
|
6132
6137
|
await this.onPresenceBucketEmpty();
|
|
6133
6138
|
this.emit("warning", new Error(`presence watch silent for ${silentMs}ms with the connection up; rebound it from the bucket's current state`));
|
|
6134
6139
|
}
|