@cotal-ai/core 0.61.0 → 0.63.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/connector-config.d.ts +31 -1
- package/dist/connector-config.d.ts.map +1 -1
- package/dist/connector-config.js +63 -7
- package/dist/connector-config.js.map +1 -1
- package/dist/connector-setup.d.ts +11 -0
- package/dist/connector-setup.d.ts.map +1 -1
- package/dist/endpoint-reconcile.d.ts.map +1 -1
- package/dist/endpoint-reconcile.js +20 -15
- package/dist/endpoint-reconcile.js.map +1 -1
- package/dist/endpoint-serve-kv.d.ts +4 -3
- package/dist/endpoint-serve-kv.d.ts.map +1 -1
- package/dist/endpoint-serve-kv.js +5 -4
- package/dist/endpoint-serve-kv.js.map +1 -1
- package/dist/endpoint-service.d.ts +13 -6
- package/dist/endpoint-service.d.ts.map +1 -1
- package/dist/endpoint-service.js +20 -8
- package/dist/endpoint-service.js.map +1 -1
- package/dist/endpoint.d.ts +38 -4
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +127 -34
- package/dist/endpoint.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/issued-authority.d.ts +22 -0
- package/dist/issued-authority.d.ts.map +1 -1
- package/dist/issued-authority.js +46 -1
- package/dist/issued-authority.js.map +1 -1
- package/dist/kv-scan.d.ts +9 -0
- package/dist/kv-scan.d.ts.map +1 -1
- package/dist/kv-scan.js +5 -2
- package/dist/kv-scan.js.map +1 -1
- package/dist/managed-handoff.d.ts +48 -0
- package/dist/managed-handoff.d.ts.map +1 -0
- package/dist/managed-handoff.js +137 -0
- package/dist/managed-handoff.js.map +1 -0
- package/dist/provision.d.ts +4 -0
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +27 -37
- package/dist/provision.js.map +1 -1
- package/dist/remote-manager-authority.d.ts +129 -1
- package/dist/remote-manager-authority.d.ts.map +1 -1
- package/dist/remote-manager-authority.js +192 -31
- package/dist/remote-manager-authority.js.map +1 -1
- package/dist/runtime.d.ts +56 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +24 -1
- package/dist/runtime.js.map +1 -1
- package/dist/subjects.d.ts +3 -0
- package/dist/subjects.d.ts.map +1 -1
- package/dist/subjects.js +5 -0
- package/dist/subjects.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/endpoint.js
CHANGED
|
@@ -245,6 +245,12 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
245
245
|
* EXPECTED async permission violation that joinChannel turns into a clean throw, so watchStatus
|
|
246
246
|
* suppresses it rather than surfacing a spurious connection error. */
|
|
247
247
|
confirmingChatSubs = new Set();
|
|
248
|
+
/** Delete subjects of this endpoint's own consumers whose refusal watchStatus has not consumed yet
|
|
249
|
+
* (see {@link deleteOwnConsumer}). */
|
|
250
|
+
ownConsumerDeletes = new Set();
|
|
251
|
+
/** `$JS.API.CONSUMER.DELETE.<stream>.<prefix>_` for each of this connection's own KV watches,
|
|
252
|
+
* running membership scans and running history reads (see {@link recordOwnWatchConsumer}). */
|
|
253
|
+
ownWatchDeletePrefixes = new Set();
|
|
248
254
|
/** True until the first successful connect completes its boot backfill — distinguishes first-connect
|
|
249
255
|
* (backfill the boot channels' history) from a reconnect (reopen the core-subs, no re-backfill).
|
|
250
256
|
* Persists across reconnect (NOT connection-scoped). Replaces the legacy chat-durable consumed-cursor
|
|
@@ -296,6 +302,10 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
296
302
|
presenceRebindAt = 0;
|
|
297
303
|
status = "idle";
|
|
298
304
|
activity;
|
|
305
|
+
/** When {@link status} or {@link activity} last changed. Heartbeats republish it unchanged. */
|
|
306
|
+
statusSince = Date.now();
|
|
307
|
+
/** When {@link activity} last changed. A status change leaves it alone. */
|
|
308
|
+
activitySince;
|
|
299
309
|
/** Last harness-reported work progress. Carried by the next heartbeat, never published per event. */
|
|
300
310
|
activeAt;
|
|
301
311
|
condition;
|
|
@@ -1207,6 +1217,8 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
1207
1217
|
this.chatSubs.clear();
|
|
1208
1218
|
this.chatSubDenied.clear();
|
|
1209
1219
|
this.confirmingChatSubs.clear();
|
|
1220
|
+
this.ownConsumerDeletes.clear();
|
|
1221
|
+
this.ownWatchDeletePrefixes.clear();
|
|
1210
1222
|
this.roster.clear();
|
|
1211
1223
|
// #1356: the presence-refusal record is connection-scoped like everything else torn down here.
|
|
1212
1224
|
// It says "the broker on THIS connection refuses writes to this bucket", so it cannot outlive the
|
|
@@ -2246,10 +2258,14 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2246
2258
|
}
|
|
2247
2259
|
}
|
|
2248
2260
|
async setActivity(activity) {
|
|
2261
|
+
if (activity !== this.activity)
|
|
2262
|
+
this.statusSince = this.activitySince = Date.now();
|
|
2249
2263
|
this.activity = activity;
|
|
2250
2264
|
await this.publishPresence();
|
|
2251
2265
|
}
|
|
2252
2266
|
async setStatus(status) {
|
|
2267
|
+
if (status !== this.status)
|
|
2268
|
+
this.statusSince = Date.now();
|
|
2253
2269
|
this.status = status;
|
|
2254
2270
|
await this.publishPresence();
|
|
2255
2271
|
}
|
|
@@ -2525,7 +2541,17 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2525
2541
|
// measured at 30-34s for 89 entries against a mesh at 534ms RTT. `liveKvEntries` is ~3 round
|
|
2526
2542
|
// trips regardless of N, and (unlike the old loop) refuses to return a truncated view rather
|
|
2527
2543
|
// than reporting a partial roster as the whole one.
|
|
2528
|
-
|
|
2544
|
+
let scanPrefix;
|
|
2545
|
+
const entries = await liveKvEntries(kv, {
|
|
2546
|
+
onConsumer: (info) => { scanPrefix = this.recordOwnWatchConsumer(info); },
|
|
2547
|
+
deleteOwnConsumer: (stream, name, del) => this.deleteOwnConsumer(stream, name, del),
|
|
2548
|
+
}).finally(() => {
|
|
2549
|
+
// The scan's own last delete goes out after nats.js closed its consumer, so every predecessor
|
|
2550
|
+
// delete the library sent is answered ahead of it on this connection.
|
|
2551
|
+
if (scanPrefix)
|
|
2552
|
+
this.ownWatchDeletePrefixes.delete(scanPrefix);
|
|
2553
|
+
});
|
|
2554
|
+
for (const e of entries) {
|
|
2529
2555
|
if (e.key === MEMBERSHIP_FEED_KEY) {
|
|
2530
2556
|
try {
|
|
2531
2557
|
asOf = e.json().observedAt;
|
|
@@ -2594,6 +2620,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2594
2620
|
const cc = kv._buildCC(">", KvWatchInclude.LastValue, { headers_only: false });
|
|
2595
2621
|
const consumer = await kv.js.consumers.getPushConsumer(kv.stream, cc);
|
|
2596
2622
|
const info = await consumer.info(true);
|
|
2623
|
+
this.recordOwnWatchConsumer(info);
|
|
2597
2624
|
// The broker resource exists now. Record its identity BEFORE consume() so a concurrent stop or
|
|
2598
2625
|
// connection close always leaves enough state for strict cleanup or fresh-epoch retry.
|
|
2599
2626
|
watch.consumer = consumer;
|
|
@@ -2649,19 +2676,72 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2649
2676
|
watch.resolveStop = undefined;
|
|
2650
2677
|
watch.rejectStop = undefined;
|
|
2651
2678
|
}
|
|
2652
|
-
/**
|
|
2679
|
+
/** Run one delete of a consumer this endpoint created. A profile without the delete row is refused
|
|
2680
|
+
* by design (#691), and nats.js reports a refused request twice: as the request's rejection, which
|
|
2681
|
+
* the caller handles, and on the connection status, which watchStatus would emit as an `error`.
|
|
2682
|
+
* A refused subject stays recorded until watchStatus drops that echo, since the two settle in
|
|
2683
|
+
* either order. */
|
|
2684
|
+
async deleteOwnConsumer(stream, name, del) {
|
|
2685
|
+
const subject = `$JS.API.CONSUMER.DELETE.${stream}.${name}`;
|
|
2686
|
+
this.ownConsumerDeletes.add(subject);
|
|
2687
|
+
let refused = false;
|
|
2688
|
+
try {
|
|
2689
|
+
return await del();
|
|
2690
|
+
}
|
|
2691
|
+
catch (err) {
|
|
2692
|
+
refused = isPublishPermissionDenied(err);
|
|
2693
|
+
throw err;
|
|
2694
|
+
}
|
|
2695
|
+
finally {
|
|
2696
|
+
if (!refused)
|
|
2697
|
+
this.ownConsumerDeletes.delete(subject);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
/** Record the consumer behind one of this connection's own KV watches, membership scans or history
|
|
2701
|
+
* reads, and return the delete-subject prefix it recorded. nats.js names it `<prefix>_<serial>` and,
|
|
2702
|
+
* rebuilding it after a stall or a sequence gap, deletes the predecessor itself with no hook before
|
|
2703
|
+
* the send, so a profile without the delete row (#691) has that delete refused. A watch's prefix
|
|
2704
|
+
* stays recorded for the connection because a retired watch's last rebuild can be refused after
|
|
2705
|
+
* the watch has stopped; {@link readMembership} and {@link drainWindow} retire theirs after their own
|
|
2706
|
+
* last delete, which goes out once nats.js has stopped the consumer and so is answered after every
|
|
2707
|
+
* predecessor delete. */
|
|
2708
|
+
recordOwnWatchConsumer({ stream_name, name }) {
|
|
2709
|
+
const prefix = `$JS.API.CONSUMER.DELETE.${stream_name}.${name.slice(0, name.lastIndexOf("_") + 1)}`;
|
|
2710
|
+
this.ownWatchDeletePrefixes.add(prefix);
|
|
2711
|
+
return prefix;
|
|
2712
|
+
}
|
|
2713
|
+
/** Whether `subject` deletes `<prefix>_<serial>` of a consumer {@link recordOwnWatchConsumer} recorded. */
|
|
2714
|
+
isOwnWatchDelete(subject) {
|
|
2715
|
+
const cut = subject.lastIndexOf("_") + 1;
|
|
2716
|
+
return /^\d+$/.test(subject.slice(cut)) && this.ownWatchDeletePrefixes.has(subject.slice(0, cut));
|
|
2717
|
+
}
|
|
2718
|
+
/** Delete a history reader's own ephemeral consumer. A refused delete is the designed outcome for
|
|
2719
|
+
* the elevated profile, which holds no stream-wide CONSUMER.DELETE (#691): the broker reaps the
|
|
2720
|
+
* consumer at its inactive threshold. */
|
|
2721
|
+
async deleteReaderConsumer(consumer) {
|
|
2722
|
+
const { stream_name, name } = await consumer.info(true);
|
|
2723
|
+
try {
|
|
2724
|
+
await this.deleteOwnConsumer(stream_name, name, () => consumer.delete());
|
|
2725
|
+
}
|
|
2726
|
+
catch (e) {
|
|
2727
|
+
if (!isJetStreamMissing(e, JetStreamApiCodes.ConsumerNotFound) && !isPublishPermissionDenied(e))
|
|
2728
|
+
throw e;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
/** Delete one membership-watch consumer, swallowing ONLY already-gone and a refused delete. */
|
|
2653
2732
|
async deleteMembershipConsumer(jsm, stream, name) {
|
|
2654
2733
|
try {
|
|
2655
|
-
return await jsm.consumers.delete(stream, name);
|
|
2734
|
+
return await this.deleteOwnConsumer(stream, name, () => jsm.consumers.delete(stream, name));
|
|
2656
2735
|
}
|
|
2657
2736
|
catch (err) {
|
|
2658
|
-
if (
|
|
2737
|
+
if (membershipConsumerReleased(err))
|
|
2659
2738
|
return true;
|
|
2660
2739
|
throw err;
|
|
2661
2740
|
}
|
|
2662
2741
|
}
|
|
2663
|
-
/** Stop the local iterator AND delete its ordered consumer. The admin/observer
|
|
2664
|
-
*
|
|
2742
|
+
/** Stop the local iterator AND delete its ordered consumer. The admin/observer profile holds no
|
|
2743
|
+
* consumer delete on this bucket (#691), so its refused delete leaves the consumer to the broker's
|
|
2744
|
+
* five-minute inactive threshold. */
|
|
2665
2745
|
async disarmMembershipWatch(watch) {
|
|
2666
2746
|
const iter = watch.iter;
|
|
2667
2747
|
const consumer = watch.consumer;
|
|
@@ -2673,14 +2753,15 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2673
2753
|
catch { /* already closed */ }
|
|
2674
2754
|
if (consumer) {
|
|
2675
2755
|
try {
|
|
2676
|
-
const
|
|
2756
|
+
const { stream_name, name } = await consumer.info(true);
|
|
2757
|
+
const deleted = await this.deleteOwnConsumer(stream_name, name, () => consumer.delete());
|
|
2677
2758
|
if (deleted) {
|
|
2678
2759
|
watch.consumerStream = undefined;
|
|
2679
2760
|
watch.consumerName = undefined;
|
|
2680
2761
|
}
|
|
2681
2762
|
}
|
|
2682
2763
|
catch (err) {
|
|
2683
|
-
if (
|
|
2764
|
+
if (membershipConsumerReleased(err)) {
|
|
2684
2765
|
watch.consumerStream = undefined;
|
|
2685
2766
|
watch.consumerName = undefined;
|
|
2686
2767
|
}
|
|
@@ -3049,13 +3130,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3049
3130
|
throw new Error(`history: the broker reported messages on ${subjectLabel(subjects)} but delivered none - the read was cut short, not empty`);
|
|
3050
3131
|
}
|
|
3051
3132
|
finally {
|
|
3052
|
-
|
|
3053
|
-
await consumer.delete();
|
|
3054
|
-
}
|
|
3055
|
-
catch (e) {
|
|
3056
|
-
if (!isJetStreamMissing(e, JetStreamApiCodes.ConsumerNotFound))
|
|
3057
|
-
throw e;
|
|
3058
|
-
}
|
|
3133
|
+
await this.deleteReaderConsumer(consumer);
|
|
3059
3134
|
}
|
|
3060
3135
|
}
|
|
3061
3136
|
/** The newest stream sequence matching `subject`, or 0 when the subject has no messages.
|
|
@@ -3095,13 +3170,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3095
3170
|
throw new Error(`history: the broker reported messages on ${subjectLabel(subjects)} but delivered none - the read was cut short, not empty`);
|
|
3096
3171
|
}
|
|
3097
3172
|
finally {
|
|
3098
|
-
|
|
3099
|
-
await consumer.delete();
|
|
3100
|
-
}
|
|
3101
|
-
catch (e) {
|
|
3102
|
-
if (!isJetStreamMissing(e, JetStreamApiCodes.ConsumerNotFound))
|
|
3103
|
-
throw e;
|
|
3104
|
-
}
|
|
3173
|
+
await this.deleteReaderConsumer(consumer);
|
|
3105
3174
|
}
|
|
3106
3175
|
}
|
|
3107
3176
|
/** Drain every message matching `subject` with sequence in `[start, ceiling]`, oldest-first.
|
|
@@ -3112,6 +3181,8 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3112
3181
|
signal?.throwIfAborted();
|
|
3113
3182
|
const out = [];
|
|
3114
3183
|
const consumer = await js.consumers.get(stream, { filter_subjects: subjects, opt_start_seq: start });
|
|
3184
|
+
// A delivery-sequence gap makes nats.js rebuild this consumer and delete the predecessor itself.
|
|
3185
|
+
const readerPrefix = this.recordOwnWatchConsumer(await consumer.info(true));
|
|
3115
3186
|
try {
|
|
3116
3187
|
// A freshly created consumer already carries its ConsumerInfo, so read the CACHED copy: the
|
|
3117
3188
|
// explicit uncached `info()` this used to call was a round trip for data we already had.
|
|
@@ -3174,13 +3245,9 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3174
3245
|
// DMs, and the single-channel routes still make one each.
|
|
3175
3246
|
// Left alone that accumulates consumers on the broker until the thresholds expire, and the
|
|
3176
3247
|
// resulting resource exhaustion would land in streamHistory's catch and read as empty history.
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
}
|
|
3180
|
-
catch (e) {
|
|
3181
|
-
if (!isJetStreamMissing(e, JetStreamApiCodes.ConsumerNotFound))
|
|
3182
|
-
throw e;
|
|
3183
|
-
}
|
|
3248
|
+
// The observer and admin profiles may not delete (#691), so the dashboard's readers do live out
|
|
3249
|
+
// that threshold; only a profile holding the delete row reclaims them here.
|
|
3250
|
+
await this.deleteReaderConsumer(consumer).finally(() => { this.ownWatchDeletePrefixes.delete(readerPrefix); });
|
|
3184
3251
|
}
|
|
3185
3252
|
}
|
|
3186
3253
|
// ---- internals -----------------------------------------------------------
|
|
@@ -3224,6 +3291,12 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3224
3291
|
// and turns into a clean throw — it is not a connection error to surface.
|
|
3225
3292
|
if (s.error instanceof PermissionViolationError && this.confirmingChatSubs.has(s.error.subject))
|
|
3226
3293
|
continue;
|
|
3294
|
+
// The echo of a refused delete of this endpoint's own consumer: one its caller already handled,
|
|
3295
|
+
// or the predecessor nats.js deleted while rebuilding one of this connection's watches, scans or
|
|
3296
|
+
// history reads.
|
|
3297
|
+
if (s.error instanceof PermissionViolationError && s.error.operation === "publish"
|
|
3298
|
+
&& (this.ownConsumerDeletes.delete(s.error.subject) || this.isOwnWatchDelete(s.error.subject)))
|
|
3299
|
+
continue;
|
|
3227
3300
|
this.emit("error", describeStatusError(s.error));
|
|
3228
3301
|
}
|
|
3229
3302
|
})().catch((e) => {
|
|
@@ -5838,14 +5911,17 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
5838
5911
|
condition: this.condition,
|
|
5839
5912
|
environment: this.environment,
|
|
5840
5913
|
activity: this.activity,
|
|
5914
|
+
statusSince: this.statusSince,
|
|
5915
|
+
activitySince: this.activitySince,
|
|
5841
5916
|
activeAt: this.activeAt,
|
|
5842
5917
|
attention: this.attentionMode,
|
|
5843
5918
|
channelModes: this.channelModes,
|
|
5844
5919
|
ts: Date.now(),
|
|
5845
5920
|
};
|
|
5846
|
-
// Wire contract (SPEC §6): an OFFLINE record must not carry the advisory attention fields
|
|
5847
|
-
// the publisher — this covers stop(), setStatus("offline"), and any future
|
|
5848
|
-
// the raw KV record is compliant, not only the observer-side roster
|
|
5921
|
+
// Wire contract (SPEC §6): an OFFLINE record must not carry the advisory attention fields or
|
|
5922
|
+
// `statusSince`. Scrub at the publisher — this covers stop(), setStatus("offline"), and any future
|
|
5923
|
+
// offline publish site, so the raw KV record is compliant, not only the observer-side roster
|
|
5924
|
+
// materialization.
|
|
5849
5925
|
const record = this.status === "offline" ? this.toOffline(p) : p;
|
|
5850
5926
|
// #1356: this put can still be in flight when a teardown runs, because a rebind reaches
|
|
5851
5927
|
// publishPresence through onPresenceBucketEmpty and no teardown awaits that flight. Take the same
|
|
@@ -5968,6 +6044,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
5968
6044
|
let hydrated;
|
|
5969
6045
|
this.presenceSnapshot = new Promise((resolve) => { hydrated = resolve; });
|
|
5970
6046
|
const iter = await this.kv.watch();
|
|
6047
|
+
this.recordOwnWatchConsumer(await kvWatchConsumer(iter).info(true));
|
|
5971
6048
|
if (epoch !== this.presenceEpoch) {
|
|
5972
6049
|
try {
|
|
5973
6050
|
iter.stop();
|
|
@@ -6065,6 +6142,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
6065
6142
|
if (!this.channelKv)
|
|
6066
6143
|
return;
|
|
6067
6144
|
const iter = await this.channelKv.watch();
|
|
6145
|
+
this.recordOwnWatchConsumer(await kvWatchConsumer(iter).info(true));
|
|
6068
6146
|
this.channelWatchIter = iter;
|
|
6069
6147
|
void (async () => {
|
|
6070
6148
|
for await (const e of iter)
|
|
@@ -6236,9 +6314,11 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
6236
6314
|
}
|
|
6237
6315
|
/** Materialize an OFFLINE presence record: drop the advisory attention fields. An offline peer must
|
|
6238
6316
|
* not show a stale `[focus]` or "locally muted #x" hint — SPEC: attention removed on offline sweep,
|
|
6239
|
-
* channel modes reset on restart.
|
|
6317
|
+
* channel modes reset on restart. `statusSince` goes too: it dated the live status, and an observer
|
|
6318
|
+
* that derives offline from a stale heartbeat does not know when the peer left. card/activity/ts
|
|
6319
|
+
* are kept. */
|
|
6240
6320
|
toOffline(p) {
|
|
6241
|
-
return { ...p, status: "offline", attention: undefined, channelModes: undefined };
|
|
6321
|
+
return { ...p, status: "offline", statusSince: undefined, attention: undefined, channelModes: undefined };
|
|
6242
6322
|
}
|
|
6243
6323
|
/** Mark a known peer offline (on KV delete/purge), keeping it in the roster. */
|
|
6244
6324
|
markOffline(id) {
|
|
@@ -6676,6 +6756,19 @@ export function isPermissionDenied(e) {
|
|
|
6676
6756
|
function isJetStreamMissing(e, ...codes) {
|
|
6677
6757
|
return e instanceof JetStreamApiError && codes.includes(e.code);
|
|
6678
6758
|
}
|
|
6759
|
+
/** The ordered consumer behind a KV watch, which nats.js keeps on the iterator and the KV types hide. */
|
|
6760
|
+
function kvWatchConsumer(iter) {
|
|
6761
|
+
const consumer = iter._data;
|
|
6762
|
+
if (!consumer)
|
|
6763
|
+
throw new Error("KV watch carries no consumer - the pinned nats.js changed shape");
|
|
6764
|
+
return consumer;
|
|
6765
|
+
}
|
|
6766
|
+
/** A membership-watch delete that leaves nothing to retry: the consumer is already gone, or the broker
|
|
6767
|
+
* refused the delete because the reading profile holds no stream-wide CONSUMER.DELETE (#691) and
|
|
6768
|
+
* reaps the consumer at its inactive threshold. */
|
|
6769
|
+
function membershipConsumerReleased(err) {
|
|
6770
|
+
return err.code === 404 || /(consumer|stream) not found/i.test(err.message) || isPublishPermissionDenied(err);
|
|
6771
|
+
}
|
|
6679
6772
|
/** The signal's exact abort reason, or the platform-standard AbortError when none was supplied. */
|
|
6680
6773
|
function abortReason(signal) {
|
|
6681
6774
|
return signal?.reason instanceof Error
|