commonswarm 0.1.58 → 0.1.60
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/cswarm.cjs +488 -438
- package/package.json +1 -1
package/cswarm.cjs
CHANGED
|
@@ -30841,6 +30841,336 @@ function idlePollHelpSentence(defaultMs = IDLE_POLL_DEFAULT_MS) {
|
|
|
30841
30841
|
return `listen start --poll-interval sets how long the listener waits after an empty claim (default ${formatIdlePollDuration(defaultMs)}). A whole number plus s or m (for example ${idlePollDurationHint()}), ${idlePollBoundSentence()}. Empty polls double that wait up to ${IDLE_POLL_MAX_LABEL}; any delivery resets it to the configured interval.`;
|
|
30842
30842
|
}
|
|
30843
30843
|
|
|
30844
|
+
// src/listener/wake.ts
|
|
30845
|
+
var LISTENER_RECONCILE_POLL_MS = 3e5;
|
|
30846
|
+
var WAKE_COALESCE_MS = 1e3;
|
|
30847
|
+
var WAKE_CLAIMS_PER_MINUTE_BUDGET = 50;
|
|
30848
|
+
var WAKE_RATE_LIMIT_POLL_MS = 6e4;
|
|
30849
|
+
var REALTIME_SUBSCRIBE_STATUS = {
|
|
30850
|
+
SUBSCRIBED: "SUBSCRIBED",
|
|
30851
|
+
CHANNEL_ERROR: "CHANNEL_ERROR",
|
|
30852
|
+
CLOSED: "CLOSED",
|
|
30853
|
+
TIMED_OUT: "TIMED_OUT"
|
|
30854
|
+
};
|
|
30855
|
+
var LISTENER_WAKE_MODES = ["push", "poll"];
|
|
30856
|
+
var LISTENER_WAKE_MODE_PUSH = LISTENER_WAKE_MODES[0];
|
|
30857
|
+
var LISTENER_WAKE_MODE_POLL = LISTENER_WAKE_MODES[1];
|
|
30858
|
+
var LISTENER_WAKE_MODE_SET = new Set(
|
|
30859
|
+
LISTENER_WAKE_MODES
|
|
30860
|
+
);
|
|
30861
|
+
var WAKE_ERROR_CODES = [
|
|
30862
|
+
"channel_error",
|
|
30863
|
+
"closed",
|
|
30864
|
+
"timed_out",
|
|
30865
|
+
"rate_limited",
|
|
30866
|
+
"wake_budget"
|
|
30867
|
+
];
|
|
30868
|
+
var WAKE_ERROR_CODE_SET = new Set(WAKE_ERROR_CODES);
|
|
30869
|
+
var WAKE_ERROR_CODE_WAKE_BUDGET = WAKE_ERROR_CODES.find(
|
|
30870
|
+
(code) => code === "wake_budget"
|
|
30871
|
+
);
|
|
30872
|
+
var LISTENER_WAKE_STATUS_KEYS = [
|
|
30873
|
+
"mode",
|
|
30874
|
+
"subscribedAt",
|
|
30875
|
+
"reconnects",
|
|
30876
|
+
"lastWakeAt",
|
|
30877
|
+
"lastReconcileAt",
|
|
30878
|
+
"errorCode",
|
|
30879
|
+
"topicRotatedAt",
|
|
30880
|
+
"rateLimited"
|
|
30881
|
+
];
|
|
30882
|
+
var LISTENER_WAKE_SENSITIVE_KEYS = [
|
|
30883
|
+
"topic",
|
|
30884
|
+
"wakeTopic",
|
|
30885
|
+
"wake_topic"
|
|
30886
|
+
];
|
|
30887
|
+
function defaultRealtime(target2) {
|
|
30888
|
+
const client = createClient(target2.url, target2.anonKey, {
|
|
30889
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
30890
|
+
});
|
|
30891
|
+
return client.realtime;
|
|
30892
|
+
}
|
|
30893
|
+
function isSubscribeStatus(value) {
|
|
30894
|
+
return value === REALTIME_SUBSCRIBE_STATUS.SUBSCRIBED || value === REALTIME_SUBSCRIBE_STATUS.CHANNEL_ERROR || value === REALTIME_SUBSCRIBE_STATUS.CLOSED || value === REALTIME_SUBSCRIBE_STATUS.TIMED_OUT;
|
|
30895
|
+
}
|
|
30896
|
+
function wakeErrorCodeFromSubscribeStatus(status) {
|
|
30897
|
+
switch (status) {
|
|
30898
|
+
case REALTIME_SUBSCRIBE_STATUS.CHANNEL_ERROR:
|
|
30899
|
+
return "channel_error";
|
|
30900
|
+
case REALTIME_SUBSCRIBE_STATUS.CLOSED:
|
|
30901
|
+
return "closed";
|
|
30902
|
+
case REALTIME_SUBSCRIBE_STATUS.TIMED_OUT:
|
|
30903
|
+
return "timed_out";
|
|
30904
|
+
default:
|
|
30905
|
+
return null;
|
|
30906
|
+
}
|
|
30907
|
+
}
|
|
30908
|
+
function emptyListenerWakeStatus() {
|
|
30909
|
+
return {
|
|
30910
|
+
mode: LISTENER_WAKE_MODE_POLL,
|
|
30911
|
+
subscribedAt: null,
|
|
30912
|
+
reconnects: 0,
|
|
30913
|
+
lastWakeAt: null,
|
|
30914
|
+
lastReconcileAt: null,
|
|
30915
|
+
errorCode: null,
|
|
30916
|
+
topicRotatedAt: null,
|
|
30917
|
+
rateLimited: false
|
|
30918
|
+
};
|
|
30919
|
+
}
|
|
30920
|
+
function listenerWakePersistWorthy(previous, next, lastPersistMs, nowMs) {
|
|
30921
|
+
if (previous === void 0) return true;
|
|
30922
|
+
if (previous.mode !== next.mode || previous.rateLimited !== next.rateLimited || previous.errorCode !== next.errorCode || previous.reconnects !== next.reconnects || previous.subscribedAt !== next.subscribedAt || previous.topicRotatedAt !== next.topicRotatedAt || previous.lastReconcileAt !== next.lastReconcileAt) {
|
|
30923
|
+
return true;
|
|
30924
|
+
}
|
|
30925
|
+
if (previous.lastWakeAt !== next.lastWakeAt) {
|
|
30926
|
+
return nowMs - lastPersistMs >= WAKE_COALESCE_MS;
|
|
30927
|
+
}
|
|
30928
|
+
return false;
|
|
30929
|
+
}
|
|
30930
|
+
function listenerWakeStatusSentence(wake, pollIntervalMs, lastWakeLabel) {
|
|
30931
|
+
if (wake.mode === LISTENER_WAKE_MODE_PUSH) {
|
|
30932
|
+
const last = lastWakeLabel === null ? "no wake yet" : `last wake ${lastWakeLabel}`;
|
|
30933
|
+
return `${LISTENER_WAKE_MODE_PUSH} (Realtime), ${last}, reconcile every ${formatIdlePollDuration(LISTENER_RECONCILE_POLL_MS)}.`;
|
|
30934
|
+
}
|
|
30935
|
+
if (wake.errorCode === WAKE_ERROR_CODE_WAKE_BUDGET) {
|
|
30936
|
+
return `Subscribed; claims paused until the minute clears (${WAKE_ERROR_CODE_WAKE_BUDGET}); polling every ${formatIdlePollDuration(pollIntervalMs)} meanwhile.`;
|
|
30937
|
+
}
|
|
30938
|
+
const code = wake.errorCode ?? "disconnected";
|
|
30939
|
+
return `${LISTENER_WAKE_MODE_POLL} every ${formatIdlePollDuration(pollIntervalMs)}. Realtime not connected (${code}).`;
|
|
30940
|
+
}
|
|
30941
|
+
var WakeSubscriber = class {
|
|
30942
|
+
now;
|
|
30943
|
+
target;
|
|
30944
|
+
createRealtime;
|
|
30945
|
+
realtime = null;
|
|
30946
|
+
channel = null;
|
|
30947
|
+
topic = null;
|
|
30948
|
+
waiter = null;
|
|
30949
|
+
pending = null;
|
|
30950
|
+
connectionState = "disconnected";
|
|
30951
|
+
subscribedAt = null;
|
|
30952
|
+
reconnects = 0;
|
|
30953
|
+
lastWakeAt = null;
|
|
30954
|
+
lastReconcileAt = null;
|
|
30955
|
+
lastErrorCode = null;
|
|
30956
|
+
topicRotatedAt = null;
|
|
30957
|
+
rateLimitedUntil = 0;
|
|
30958
|
+
lastClaimAt = 0;
|
|
30959
|
+
wakeClaimTimes = [];
|
|
30960
|
+
closed = false;
|
|
30961
|
+
everSubscribed = false;
|
|
30962
|
+
constructor(options) {
|
|
30963
|
+
this.target = options.target;
|
|
30964
|
+
this.now = options.now ?? Date.now;
|
|
30965
|
+
this.createRealtime = options.createRealtime ?? defaultRealtime;
|
|
30966
|
+
}
|
|
30967
|
+
get state() {
|
|
30968
|
+
return this.connectionState;
|
|
30969
|
+
}
|
|
30970
|
+
get hasTopic() {
|
|
30971
|
+
return this.topic !== null;
|
|
30972
|
+
}
|
|
30973
|
+
snapshot(nowMs = this.now()) {
|
|
30974
|
+
const serverLimited = nowMs < this.rateLimitedUntil;
|
|
30975
|
+
const overBudget = this.overWakeBudget(nowMs);
|
|
30976
|
+
const rateLimited = serverLimited || overBudget;
|
|
30977
|
+
const mode3 = this.connectionState === "subscribed" && !rateLimited ? LISTENER_WAKE_MODE_PUSH : LISTENER_WAKE_MODE_POLL;
|
|
30978
|
+
const errorCode = serverLimited ? "rate_limited" : overBudget ? WAKE_ERROR_CODE_WAKE_BUDGET : this.lastErrorCode;
|
|
30979
|
+
return {
|
|
30980
|
+
mode: mode3,
|
|
30981
|
+
subscribedAt: this.subscribedAt,
|
|
30982
|
+
reconnects: this.reconnects,
|
|
30983
|
+
lastWakeAt: this.lastWakeAt,
|
|
30984
|
+
lastReconcileAt: this.lastReconcileAt,
|
|
30985
|
+
errorCode,
|
|
30986
|
+
topicRotatedAt: this.topicRotatedAt,
|
|
30987
|
+
rateLimited
|
|
30988
|
+
};
|
|
30989
|
+
}
|
|
30990
|
+
noteReconcile(nowMs = this.now()) {
|
|
30991
|
+
this.lastReconcileAt = new Date(nowMs).toISOString();
|
|
30992
|
+
}
|
|
30993
|
+
noteClaim(nowMs = this.now()) {
|
|
30994
|
+
this.lastClaimAt = nowMs;
|
|
30995
|
+
}
|
|
30996
|
+
noteWakeClaim(nowMs = this.now()) {
|
|
30997
|
+
this.noteClaim(nowMs);
|
|
30998
|
+
this.wakeClaimTimes.push(nowMs);
|
|
30999
|
+
this.trimWakeClaims(nowMs);
|
|
31000
|
+
}
|
|
31001
|
+
coalescingRemainingMs(nowMs = this.now()) {
|
|
31002
|
+
if (this.lastClaimAt <= 0) return 0;
|
|
31003
|
+
return Math.max(0, this.lastClaimAt + WAKE_COALESCE_MS - nowMs);
|
|
31004
|
+
}
|
|
31005
|
+
overWakeBudget(nowMs = this.now()) {
|
|
31006
|
+
this.trimWakeClaims(nowMs);
|
|
31007
|
+
return this.wakeClaimTimes.length >= WAKE_CLAIMS_PER_MINUTE_BUDGET;
|
|
31008
|
+
}
|
|
31009
|
+
canClaimOnWake(nowMs = this.now()) {
|
|
31010
|
+
if (nowMs < this.rateLimitedUntil) return false;
|
|
31011
|
+
if (this.coalescingRemainingMs(nowMs) > 0) return false;
|
|
31012
|
+
return !this.overWakeBudget(nowMs);
|
|
31013
|
+
}
|
|
31014
|
+
markRateLimited(nowMs = this.now()) {
|
|
31015
|
+
this.rateLimitedUntil = nowMs + WAKE_RATE_LIMIT_POLL_MS;
|
|
31016
|
+
this.lastErrorCode = "rate_limited";
|
|
31017
|
+
this.emitPending("state");
|
|
31018
|
+
}
|
|
31019
|
+
setTopic(topic) {
|
|
31020
|
+
if (this.closed) return;
|
|
31021
|
+
if (!isWakeTopic(topic)) {
|
|
31022
|
+
throw new Error("wake topic is malformed");
|
|
31023
|
+
}
|
|
31024
|
+
if (this.topic === topic) return;
|
|
31025
|
+
const rotated = this.topic !== null;
|
|
31026
|
+
void this.detachChannel();
|
|
31027
|
+
this.topic = topic;
|
|
31028
|
+
if (rotated) {
|
|
31029
|
+
this.topicRotatedAt = new Date(this.now()).toISOString();
|
|
31030
|
+
}
|
|
31031
|
+
this.connect();
|
|
31032
|
+
}
|
|
31033
|
+
next(options) {
|
|
31034
|
+
if (this.waiter !== null) {
|
|
31035
|
+
throw new Error("wake next() already has a waiter");
|
|
31036
|
+
}
|
|
31037
|
+
if (options.signal?.aborted) {
|
|
31038
|
+
return Promise.resolve("deadline");
|
|
31039
|
+
}
|
|
31040
|
+
const nowMs = this.now();
|
|
31041
|
+
if (this.pending !== null) {
|
|
31042
|
+
if (!(this.pending === "wake" && this.wakeClaimPaused(nowMs))) {
|
|
31043
|
+
const reason = this.pending;
|
|
31044
|
+
this.pending = null;
|
|
31045
|
+
return Promise.resolve(reason);
|
|
31046
|
+
}
|
|
31047
|
+
}
|
|
31048
|
+
if (nowMs >= options.until) {
|
|
31049
|
+
return Promise.resolve("deadline");
|
|
31050
|
+
}
|
|
31051
|
+
return new Promise((resolve3) => {
|
|
31052
|
+
const delay2 = Math.max(0, options.until - this.now());
|
|
31053
|
+
const timer2 = setTimeout(() => this.finishWait("deadline"), delay2);
|
|
31054
|
+
const onAbort = () => this.finishWait("deadline");
|
|
31055
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
31056
|
+
this.waiter = {
|
|
31057
|
+
resolve: resolve3,
|
|
31058
|
+
timer: timer2,
|
|
31059
|
+
onAbort,
|
|
31060
|
+
signal: options.signal
|
|
31061
|
+
};
|
|
31062
|
+
});
|
|
31063
|
+
}
|
|
31064
|
+
async close() {
|
|
31065
|
+
this.closed = true;
|
|
31066
|
+
this.topic = null;
|
|
31067
|
+
this.finishWait("deadline");
|
|
31068
|
+
await this.detachChannel();
|
|
31069
|
+
try {
|
|
31070
|
+
this.realtime?.disconnect?.();
|
|
31071
|
+
} catch {
|
|
31072
|
+
}
|
|
31073
|
+
this.realtime = null;
|
|
31074
|
+
this.connectionState = "disconnected";
|
|
31075
|
+
}
|
|
31076
|
+
trimWakeClaims(nowMs) {
|
|
31077
|
+
const minuteStart = Math.floor(nowMs / 6e4) * 6e4;
|
|
31078
|
+
this.wakeClaimTimes = this.wakeClaimTimes.filter((ts) => ts >= minuteStart);
|
|
31079
|
+
}
|
|
31080
|
+
/** Client budget or a server 429: do not claim on wake; poll covers the window. */
|
|
31081
|
+
wakeClaimPaused(nowMs) {
|
|
31082
|
+
return nowMs < this.rateLimitedUntil || this.overWakeBudget(nowMs);
|
|
31083
|
+
}
|
|
31084
|
+
emitPending(reason) {
|
|
31085
|
+
if (this.waiter !== null) {
|
|
31086
|
+
if (reason === "wake" && this.wakeClaimPaused(this.now())) {
|
|
31087
|
+
this.pending = "wake";
|
|
31088
|
+
return;
|
|
31089
|
+
}
|
|
31090
|
+
this.finishWait(reason);
|
|
31091
|
+
return;
|
|
31092
|
+
}
|
|
31093
|
+
if (reason === "wake" || this.pending !== "wake") {
|
|
31094
|
+
this.pending = reason;
|
|
31095
|
+
}
|
|
31096
|
+
}
|
|
31097
|
+
finishWait(reason) {
|
|
31098
|
+
const waiter = this.waiter;
|
|
31099
|
+
if (waiter === null) return;
|
|
31100
|
+
this.waiter = null;
|
|
31101
|
+
if (waiter.timer !== null) clearTimeout(waiter.timer);
|
|
31102
|
+
if (waiter.signal && waiter.onAbort) {
|
|
31103
|
+
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
31104
|
+
}
|
|
31105
|
+
waiter.resolve(reason);
|
|
31106
|
+
}
|
|
31107
|
+
connect() {
|
|
31108
|
+
if (this.closed || this.topic === null) return;
|
|
31109
|
+
if (this.realtime === null) {
|
|
31110
|
+
this.realtime = this.createRealtime(this.target);
|
|
31111
|
+
void this.realtime.setAuth(this.target.anonKey);
|
|
31112
|
+
}
|
|
31113
|
+
const topic = this.topic;
|
|
31114
|
+
this.connectionState = "connecting";
|
|
31115
|
+
this.lastErrorCode = null;
|
|
31116
|
+
const channel = this.realtime.channel(topic, {
|
|
31117
|
+
config: { private: true }
|
|
31118
|
+
});
|
|
31119
|
+
this.channel = channel;
|
|
31120
|
+
channel.on("broadcast", { event: WAKE_EVENT }, () => {
|
|
31121
|
+
this.lastWakeAt = new Date(this.now()).toISOString();
|
|
31122
|
+
this.emitPending("wake");
|
|
31123
|
+
});
|
|
31124
|
+
channel.subscribe((status) => {
|
|
31125
|
+
this.onSubscribeStatus(status);
|
|
31126
|
+
});
|
|
31127
|
+
}
|
|
31128
|
+
onSubscribeStatus(status) {
|
|
31129
|
+
if (this.closed) return;
|
|
31130
|
+
if (!isSubscribeStatus(status)) return;
|
|
31131
|
+
const wasSubscribed = this.connectionState === "subscribed";
|
|
31132
|
+
if (status === REALTIME_SUBSCRIBE_STATUS.SUBSCRIBED) {
|
|
31133
|
+
this.connectionState = "subscribed";
|
|
31134
|
+
this.subscribedAt = new Date(this.now()).toISOString();
|
|
31135
|
+
this.lastErrorCode = null;
|
|
31136
|
+
if (this.everSubscribed && !wasSubscribed) this.reconnects += 1;
|
|
31137
|
+
this.everSubscribed = true;
|
|
31138
|
+
if (!wasSubscribed) this.emitPending("state");
|
|
31139
|
+
return;
|
|
31140
|
+
}
|
|
31141
|
+
const code = wakeErrorCodeFromSubscribeStatus(status);
|
|
31142
|
+
if (status === REALTIME_SUBSCRIBE_STATUS.CLOSED) {
|
|
31143
|
+
this.connectionState = "disconnected";
|
|
31144
|
+
} else {
|
|
31145
|
+
this.connectionState = "errored";
|
|
31146
|
+
}
|
|
31147
|
+
this.lastErrorCode = code;
|
|
31148
|
+
this.subscribedAt = null;
|
|
31149
|
+
if (wasSubscribed) this.emitPending("state");
|
|
31150
|
+
}
|
|
31151
|
+
async detachChannel() {
|
|
31152
|
+
const channel = this.channel;
|
|
31153
|
+
this.channel = null;
|
|
31154
|
+
if (channel === null) return;
|
|
31155
|
+
try {
|
|
31156
|
+
await channel.unsubscribe();
|
|
31157
|
+
} catch {
|
|
31158
|
+
}
|
|
31159
|
+
try {
|
|
31160
|
+
await this.realtime?.removeChannel?.(channel);
|
|
31161
|
+
} catch {
|
|
31162
|
+
}
|
|
31163
|
+
if (this.channel !== null) return;
|
|
31164
|
+
if (this.connectionState === "subscribed") {
|
|
31165
|
+
this.connectionState = "disconnected";
|
|
31166
|
+
this.subscribedAt = null;
|
|
31167
|
+
}
|
|
31168
|
+
}
|
|
31169
|
+
};
|
|
31170
|
+
function createWakeSubscriber(options) {
|
|
31171
|
+
return new WakeSubscriber(options);
|
|
31172
|
+
}
|
|
31173
|
+
|
|
30844
31174
|
// src/cloud/arrival-watch.ts
|
|
30845
31175
|
var UUID_RE11 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
30846
31176
|
var CURSOR_MAX_BYTES = 4 * 1024;
|
|
@@ -31102,10 +31432,15 @@ function assertCursorPage(page) {
|
|
|
31102
31432
|
async function runArrivalWatch(options) {
|
|
31103
31433
|
const pollMs = options.pollMs ?? ARRIVAL_WATCH_POLL_MS2;
|
|
31104
31434
|
const random = options.random ?? Math.random;
|
|
31435
|
+
const now = options.now ?? Date.now;
|
|
31436
|
+
const reconcileMs = options.reconcileMs ?? LISTENER_RECONCILE_POLL_MS;
|
|
31437
|
+
const wake = options.wake;
|
|
31105
31438
|
let emptyIdleStreak = 0;
|
|
31106
31439
|
let cursor = await options.store.read();
|
|
31107
31440
|
let baseline = cursor === void 0;
|
|
31108
31441
|
let attempt = 0;
|
|
31442
|
+
let reconcileDueAt = now();
|
|
31443
|
+
let pendingKind = "other";
|
|
31109
31444
|
const cancelled = () => options.signal?.aborted === true;
|
|
31110
31445
|
const wait = async (ms) => {
|
|
31111
31446
|
if (options.sleep) {
|
|
@@ -31133,6 +31468,52 @@ async function runArrivalWatch(options) {
|
|
|
31133
31468
|
if (!hadDelivery) emptyIdleStreak += 1;
|
|
31134
31469
|
await wait(intervalMs);
|
|
31135
31470
|
};
|
|
31471
|
+
const pushMode = () => wake !== void 0 && wake.hasTopic && wake.snapshot(now()).mode === LISTENER_WAKE_MODE_PUSH;
|
|
31472
|
+
const waitCapMs = () => {
|
|
31473
|
+
if (pushMode()) return reconcileMs;
|
|
31474
|
+
return nextIdlePollMs(pollMs, emptyIdleStreak, IDLE_POLL_MAX_MS);
|
|
31475
|
+
};
|
|
31476
|
+
const applyWakeHint = (page) => {
|
|
31477
|
+
const topic = page.wake?.topic;
|
|
31478
|
+
if (topic === void 0 || wake === void 0) return;
|
|
31479
|
+
try {
|
|
31480
|
+
wake.setTopic(topic);
|
|
31481
|
+
} catch {
|
|
31482
|
+
}
|
|
31483
|
+
};
|
|
31484
|
+
const waitForTrigger = async (hadDelivery) => {
|
|
31485
|
+
if (hadDelivery) emptyIdleStreak = 0;
|
|
31486
|
+
if (wake === void 0 || !wake.hasTopic) {
|
|
31487
|
+
pendingKind = "other";
|
|
31488
|
+
await idleWait(hadDelivery);
|
|
31489
|
+
return;
|
|
31490
|
+
}
|
|
31491
|
+
const cap = waitCapMs();
|
|
31492
|
+
const until = Math.min(reconcileDueAt, now() + cap);
|
|
31493
|
+
const reason = await wake.next({
|
|
31494
|
+
until,
|
|
31495
|
+
...options.signal ? { signal: options.signal } : {}
|
|
31496
|
+
});
|
|
31497
|
+
if (cancelled()) return;
|
|
31498
|
+
if (reason === "wake" && pushMode()) {
|
|
31499
|
+
const coalesceMs = wake.coalescingRemainingMs(now());
|
|
31500
|
+
if (coalesceMs > 0) await wait(coalesceMs);
|
|
31501
|
+
pendingKind = pushMode() ? "wake" : "other";
|
|
31502
|
+
return;
|
|
31503
|
+
}
|
|
31504
|
+
pendingKind = "other";
|
|
31505
|
+
if (!hadDelivery && reason === "deadline" && !pushMode()) {
|
|
31506
|
+
emptyIdleStreak += 1;
|
|
31507
|
+
}
|
|
31508
|
+
};
|
|
31509
|
+
const noteCycle = () => {
|
|
31510
|
+
if (pendingKind === "wake") {
|
|
31511
|
+
wake?.noteClaim(now());
|
|
31512
|
+
return;
|
|
31513
|
+
}
|
|
31514
|
+
reconcileDueAt = now() + reconcileMs;
|
|
31515
|
+
wake?.noteReconcile(now());
|
|
31516
|
+
};
|
|
31136
31517
|
while (!cancelled()) {
|
|
31137
31518
|
try {
|
|
31138
31519
|
const page = await options.readPage({
|
|
@@ -31141,6 +31522,7 @@ async function runArrivalWatch(options) {
|
|
|
31141
31522
|
limit: baseline ? 1 : SIGNAL_FOLLOW_PAGE_LIMIT
|
|
31142
31523
|
});
|
|
31143
31524
|
assertCursorPage(page);
|
|
31525
|
+
applyWakeHint(page);
|
|
31144
31526
|
if (page.signals.some(
|
|
31145
31527
|
(row) => row.workspace_id !== options.workspaceId || !(signalAddressesAgent(row, options.principalId) || row.to === null && row.to_agent === null)
|
|
31146
31528
|
)) {
|
|
@@ -31158,7 +31540,8 @@ async function runArrivalWatch(options) {
|
|
|
31158
31540
|
await options.store.write(cursor);
|
|
31159
31541
|
baseline = false;
|
|
31160
31542
|
if (cancelled()) break;
|
|
31161
|
-
|
|
31543
|
+
noteCycle();
|
|
31544
|
+
await waitForTrigger(false);
|
|
31162
31545
|
continue;
|
|
31163
31546
|
}
|
|
31164
31547
|
const emittedSignals = [];
|
|
@@ -31172,10 +31555,14 @@ async function runArrivalWatch(options) {
|
|
|
31172
31555
|
if (emittedSignals.length > 0) {
|
|
31173
31556
|
await options.afterEmitBatch?.(emittedSignals);
|
|
31174
31557
|
}
|
|
31175
|
-
if (cancelled()) break;
|
|
31176
|
-
const fullPage = page.rawCount >= SIGNAL_FOLLOW_PAGE_LIMIT;
|
|
31177
|
-
if (fullPage)
|
|
31178
|
-
|
|
31558
|
+
if (cancelled()) break;
|
|
31559
|
+
const fullPage = page.rawCount >= SIGNAL_FOLLOW_PAGE_LIMIT;
|
|
31560
|
+
if (fullPage) {
|
|
31561
|
+
await wait(0);
|
|
31562
|
+
continue;
|
|
31563
|
+
}
|
|
31564
|
+
noteCycle();
|
|
31565
|
+
await waitForTrigger(emittedSignals.length > 0);
|
|
31179
31566
|
} catch (error) {
|
|
31180
31567
|
if (cancelled()) break;
|
|
31181
31568
|
const http = followHttpDetails(error);
|
|
@@ -37357,453 +37744,113 @@ function parseEntry(value, rejectUnknownKeys) {
|
|
|
37357
37744
|
}
|
|
37358
37745
|
function parseFile(raw, rejectUnknownKeys = false) {
|
|
37359
37746
|
let value;
|
|
37360
|
-
try {
|
|
37361
|
-
value = JSON.parse(raw);
|
|
37362
|
-
} catch {
|
|
37363
|
-
throw new Error("stored pending-for-main queue is malformed");
|
|
37364
|
-
}
|
|
37365
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
37366
|
-
throw new Error("stored pending-for-main queue is malformed");
|
|
37367
|
-
}
|
|
37368
|
-
const row = value;
|
|
37369
|
-
if (rejectUnknownKeys && Object.keys(row).some((key2) => !QUEUE_KEYS.has(key2)) || row.version !== 1 || !Array.isArray(row.entries) || row.entries.length > LISTENER_MAIN_QUEUE_MAX || !(row.droppedCount === void 0 || typeof row.droppedCount === "number" && Number.isSafeInteger(row.droppedCount) && row.droppedCount >= 0)) {
|
|
37370
|
-
throw new Error("stored pending-for-main queue is malformed");
|
|
37371
|
-
}
|
|
37372
|
-
const entries = row.entries.map((entry) => parseEntry(entry, rejectUnknownKeys));
|
|
37373
|
-
if (new Set(entries.map((entry) => entry.signalId)).size !== entries.length) {
|
|
37374
|
-
throw new Error("stored pending-for-main queue repeats a signal");
|
|
37375
|
-
}
|
|
37376
|
-
return { version: 1, entries, droppedCount: row.droppedCount ?? 0 };
|
|
37377
|
-
}
|
|
37378
|
-
var FilePendingMainQueue = class {
|
|
37379
|
-
path;
|
|
37380
|
-
directory;
|
|
37381
|
-
constructor(instanceDirectory) {
|
|
37382
|
-
if (!(0, import_node_path15.isAbsolute)(instanceDirectory)) {
|
|
37383
|
-
throw new Error("pending-for-main directory must be absolute");
|
|
37384
|
-
}
|
|
37385
|
-
this.directory = instanceDirectory;
|
|
37386
|
-
this.path = (0, import_node_path15.join)(instanceDirectory, QUEUE_FILE);
|
|
37387
|
-
}
|
|
37388
|
-
async readUnlocked() {
|
|
37389
|
-
const raw = await readSecureJsonFile(this.path, MAX_QUEUE_BYTES);
|
|
37390
|
-
return raw === null ? { version: 1, entries: [], droppedCount: 0 } : parseFile(raw);
|
|
37391
|
-
}
|
|
37392
|
-
async writeUnlocked(file) {
|
|
37393
|
-
const canonical = parseFile(JSON.stringify(file), true);
|
|
37394
|
-
await writeSecureJsonFile(this.path, JSON.stringify(canonical));
|
|
37395
|
-
}
|
|
37396
|
-
async read() {
|
|
37397
|
-
return [...(await this.readUnlocked()).entries];
|
|
37398
|
-
}
|
|
37399
|
-
async count() {
|
|
37400
|
-
return (await this.readUnlocked()).entries.length;
|
|
37401
|
-
}
|
|
37402
|
-
async stats() {
|
|
37403
|
-
const file = await this.readUnlocked();
|
|
37404
|
-
return { count: file.entries.length, droppedCount: file.droppedCount };
|
|
37405
|
-
}
|
|
37406
|
-
async enqueue(entry) {
|
|
37407
|
-
const checked = parseEntry(entry, true);
|
|
37408
|
-
return await withFileLock(this.directory, QUEUE_LOCK, async () => {
|
|
37409
|
-
const file = await this.readUnlocked();
|
|
37410
|
-
if (file.entries.some((item) => item.signalId === checked.signalId)) {
|
|
37411
|
-
return {
|
|
37412
|
-
count: file.entries.length,
|
|
37413
|
-
added: false,
|
|
37414
|
-
droppedOldest: false,
|
|
37415
|
-
droppedCount: file.droppedCount
|
|
37416
|
-
};
|
|
37417
|
-
}
|
|
37418
|
-
file.entries.push(checked);
|
|
37419
|
-
const droppedOldest = file.entries.length > LISTENER_MAIN_QUEUE_MAX;
|
|
37420
|
-
if (droppedOldest) {
|
|
37421
|
-
file.entries.shift();
|
|
37422
|
-
file.droppedCount += 1;
|
|
37423
|
-
}
|
|
37424
|
-
await this.writeUnlocked(file);
|
|
37425
|
-
return {
|
|
37426
|
-
count: file.entries.length,
|
|
37427
|
-
added: true,
|
|
37428
|
-
droppedOldest,
|
|
37429
|
-
droppedCount: file.droppedCount
|
|
37430
|
-
};
|
|
37431
|
-
});
|
|
37432
|
-
}
|
|
37433
|
-
async remove(signalIds, lockTimeoutMs) {
|
|
37434
|
-
if (signalIds.size === 0) return await this.count();
|
|
37435
|
-
return await withFileLock(this.directory, QUEUE_LOCK, async () => {
|
|
37436
|
-
const file = await this.readUnlocked();
|
|
37437
|
-
const entries = file.entries.filter((entry) => !signalIds.has(entry.signalId));
|
|
37438
|
-
if (entries.length !== file.entries.length) {
|
|
37439
|
-
await this.writeUnlocked({
|
|
37440
|
-
version: 1,
|
|
37441
|
-
entries,
|
|
37442
|
-
droppedCount: file.droppedCount
|
|
37443
|
-
});
|
|
37444
|
-
}
|
|
37445
|
-
return entries.length;
|
|
37446
|
-
}, lockTimeoutMs === void 0 ? {} : { timeoutMs: lockTimeoutMs });
|
|
37447
|
-
}
|
|
37448
|
-
};
|
|
37449
|
-
function pendingMainEntry(signal, principalId, provenance, now, options = {}) {
|
|
37450
|
-
if (signal.kind !== "ask" && signal.kind !== "note") {
|
|
37451
|
-
throw new Error("only directed asks and notes can enter the pending-for-main queue");
|
|
37452
|
-
}
|
|
37453
|
-
return parseEntry({
|
|
37454
|
-
signalId: signal.id,
|
|
37455
|
-
workspaceId: signal.workspace_id,
|
|
37456
|
-
principalId,
|
|
37457
|
-
fromId: signal.from,
|
|
37458
|
-
fromKind: signal.from_kind,
|
|
37459
|
-
kind: signal.kind,
|
|
37460
|
-
senderName: provenance.senderName,
|
|
37461
|
-
body: signal.body,
|
|
37462
|
-
...(signal.attachments?.length ?? 0) > 0 ? { attachmentCount: signal.attachments.length } : {},
|
|
37463
|
-
createdAt: signal.created_at,
|
|
37464
|
-
queuedAt: new Date(now).toISOString(),
|
|
37465
|
-
...options.observationPending ? { observationPending: true } : {}
|
|
37466
|
-
}, true);
|
|
37467
|
-
}
|
|
37468
|
-
|
|
37469
|
-
// src/listener/wake.ts
|
|
37470
|
-
var LISTENER_RECONCILE_POLL_MS = 3e5;
|
|
37471
|
-
var WAKE_COALESCE_MS = 1e3;
|
|
37472
|
-
var WAKE_CLAIMS_PER_MINUTE_BUDGET = 50;
|
|
37473
|
-
var WAKE_RATE_LIMIT_POLL_MS = 6e4;
|
|
37474
|
-
var REALTIME_SUBSCRIBE_STATUS = {
|
|
37475
|
-
SUBSCRIBED: "SUBSCRIBED",
|
|
37476
|
-
CHANNEL_ERROR: "CHANNEL_ERROR",
|
|
37477
|
-
CLOSED: "CLOSED",
|
|
37478
|
-
TIMED_OUT: "TIMED_OUT"
|
|
37479
|
-
};
|
|
37480
|
-
var LISTENER_WAKE_MODES = ["push", "poll"];
|
|
37481
|
-
var LISTENER_WAKE_MODE_PUSH = LISTENER_WAKE_MODES[0];
|
|
37482
|
-
var LISTENER_WAKE_MODE_POLL = LISTENER_WAKE_MODES[1];
|
|
37483
|
-
var LISTENER_WAKE_MODE_SET = new Set(
|
|
37484
|
-
LISTENER_WAKE_MODES
|
|
37485
|
-
);
|
|
37486
|
-
var WAKE_ERROR_CODES = [
|
|
37487
|
-
"channel_error",
|
|
37488
|
-
"closed",
|
|
37489
|
-
"timed_out",
|
|
37490
|
-
"rate_limited",
|
|
37491
|
-
"wake_budget"
|
|
37492
|
-
];
|
|
37493
|
-
var WAKE_ERROR_CODE_SET = new Set(WAKE_ERROR_CODES);
|
|
37494
|
-
var WAKE_ERROR_CODE_WAKE_BUDGET = WAKE_ERROR_CODES.find(
|
|
37495
|
-
(code) => code === "wake_budget"
|
|
37496
|
-
);
|
|
37497
|
-
var LISTENER_WAKE_STATUS_KEYS = [
|
|
37498
|
-
"mode",
|
|
37499
|
-
"subscribedAt",
|
|
37500
|
-
"reconnects",
|
|
37501
|
-
"lastWakeAt",
|
|
37502
|
-
"lastReconcileAt",
|
|
37503
|
-
"errorCode",
|
|
37504
|
-
"topicRotatedAt",
|
|
37505
|
-
"rateLimited"
|
|
37506
|
-
];
|
|
37507
|
-
var LISTENER_WAKE_SENSITIVE_KEYS = [
|
|
37508
|
-
"topic",
|
|
37509
|
-
"wakeTopic",
|
|
37510
|
-
"wake_topic"
|
|
37511
|
-
];
|
|
37512
|
-
function defaultRealtime(target2) {
|
|
37513
|
-
const client = createClient(target2.url, target2.anonKey, {
|
|
37514
|
-
auth: { persistSession: false, autoRefreshToken: false }
|
|
37515
|
-
});
|
|
37516
|
-
return client.realtime;
|
|
37517
|
-
}
|
|
37518
|
-
function isSubscribeStatus(value) {
|
|
37519
|
-
return value === REALTIME_SUBSCRIBE_STATUS.SUBSCRIBED || value === REALTIME_SUBSCRIBE_STATUS.CHANNEL_ERROR || value === REALTIME_SUBSCRIBE_STATUS.CLOSED || value === REALTIME_SUBSCRIBE_STATUS.TIMED_OUT;
|
|
37520
|
-
}
|
|
37521
|
-
function wakeErrorCodeFromSubscribeStatus(status) {
|
|
37522
|
-
switch (status) {
|
|
37523
|
-
case REALTIME_SUBSCRIBE_STATUS.CHANNEL_ERROR:
|
|
37524
|
-
return "channel_error";
|
|
37525
|
-
case REALTIME_SUBSCRIBE_STATUS.CLOSED:
|
|
37526
|
-
return "closed";
|
|
37527
|
-
case REALTIME_SUBSCRIBE_STATUS.TIMED_OUT:
|
|
37528
|
-
return "timed_out";
|
|
37529
|
-
default:
|
|
37530
|
-
return null;
|
|
37531
|
-
}
|
|
37532
|
-
}
|
|
37533
|
-
function emptyListenerWakeStatus() {
|
|
37534
|
-
return {
|
|
37535
|
-
mode: LISTENER_WAKE_MODE_POLL,
|
|
37536
|
-
subscribedAt: null,
|
|
37537
|
-
reconnects: 0,
|
|
37538
|
-
lastWakeAt: null,
|
|
37539
|
-
lastReconcileAt: null,
|
|
37540
|
-
errorCode: null,
|
|
37541
|
-
topicRotatedAt: null,
|
|
37542
|
-
rateLimited: false
|
|
37543
|
-
};
|
|
37544
|
-
}
|
|
37545
|
-
function listenerWakePersistWorthy(previous, next, lastPersistMs, nowMs) {
|
|
37546
|
-
if (previous === void 0) return true;
|
|
37547
|
-
if (previous.mode !== next.mode || previous.rateLimited !== next.rateLimited || previous.errorCode !== next.errorCode || previous.reconnects !== next.reconnects || previous.subscribedAt !== next.subscribedAt || previous.topicRotatedAt !== next.topicRotatedAt || previous.lastReconcileAt !== next.lastReconcileAt) {
|
|
37548
|
-
return true;
|
|
37549
|
-
}
|
|
37550
|
-
if (previous.lastWakeAt !== next.lastWakeAt) {
|
|
37551
|
-
return nowMs - lastPersistMs >= WAKE_COALESCE_MS;
|
|
37552
|
-
}
|
|
37553
|
-
return false;
|
|
37554
|
-
}
|
|
37555
|
-
function listenerWakeStatusSentence(wake, pollIntervalMs, lastWakeLabel) {
|
|
37556
|
-
if (wake.mode === LISTENER_WAKE_MODE_PUSH) {
|
|
37557
|
-
const last = lastWakeLabel === null ? "no wake yet" : `last wake ${lastWakeLabel}`;
|
|
37558
|
-
return `${LISTENER_WAKE_MODE_PUSH} (Realtime), ${last}, reconcile every ${formatIdlePollDuration(LISTENER_RECONCILE_POLL_MS)}.`;
|
|
37559
|
-
}
|
|
37560
|
-
if (wake.errorCode === WAKE_ERROR_CODE_WAKE_BUDGET) {
|
|
37561
|
-
return `Subscribed; claims paused until the minute clears (${WAKE_ERROR_CODE_WAKE_BUDGET}); polling every ${formatIdlePollDuration(pollIntervalMs)} meanwhile.`;
|
|
37562
|
-
}
|
|
37563
|
-
const code = wake.errorCode ?? "disconnected";
|
|
37564
|
-
return `${LISTENER_WAKE_MODE_POLL} every ${formatIdlePollDuration(pollIntervalMs)}. Realtime not connected (${code}).`;
|
|
37565
|
-
}
|
|
37566
|
-
var WakeSubscriber = class {
|
|
37567
|
-
now;
|
|
37568
|
-
target;
|
|
37569
|
-
createRealtime;
|
|
37570
|
-
realtime = null;
|
|
37571
|
-
channel = null;
|
|
37572
|
-
topic = null;
|
|
37573
|
-
waiter = null;
|
|
37574
|
-
pending = null;
|
|
37575
|
-
connectionState = "disconnected";
|
|
37576
|
-
subscribedAt = null;
|
|
37577
|
-
reconnects = 0;
|
|
37578
|
-
lastWakeAt = null;
|
|
37579
|
-
lastReconcileAt = null;
|
|
37580
|
-
lastErrorCode = null;
|
|
37581
|
-
topicRotatedAt = null;
|
|
37582
|
-
rateLimitedUntil = 0;
|
|
37583
|
-
lastClaimAt = 0;
|
|
37584
|
-
wakeClaimTimes = [];
|
|
37585
|
-
closed = false;
|
|
37586
|
-
everSubscribed = false;
|
|
37587
|
-
constructor(options) {
|
|
37588
|
-
this.target = options.target;
|
|
37589
|
-
this.now = options.now ?? Date.now;
|
|
37590
|
-
this.createRealtime = options.createRealtime ?? defaultRealtime;
|
|
37591
|
-
}
|
|
37592
|
-
get state() {
|
|
37593
|
-
return this.connectionState;
|
|
37594
|
-
}
|
|
37595
|
-
get hasTopic() {
|
|
37596
|
-
return this.topic !== null;
|
|
37597
|
-
}
|
|
37598
|
-
snapshot(nowMs = this.now()) {
|
|
37599
|
-
const serverLimited = nowMs < this.rateLimitedUntil;
|
|
37600
|
-
const overBudget = this.overWakeBudget(nowMs);
|
|
37601
|
-
const rateLimited = serverLimited || overBudget;
|
|
37602
|
-
const mode3 = this.connectionState === "subscribed" && !rateLimited ? LISTENER_WAKE_MODE_PUSH : LISTENER_WAKE_MODE_POLL;
|
|
37603
|
-
const errorCode = serverLimited ? "rate_limited" : overBudget ? WAKE_ERROR_CODE_WAKE_BUDGET : this.lastErrorCode;
|
|
37604
|
-
return {
|
|
37605
|
-
mode: mode3,
|
|
37606
|
-
subscribedAt: this.subscribedAt,
|
|
37607
|
-
reconnects: this.reconnects,
|
|
37608
|
-
lastWakeAt: this.lastWakeAt,
|
|
37609
|
-
lastReconcileAt: this.lastReconcileAt,
|
|
37610
|
-
errorCode,
|
|
37611
|
-
topicRotatedAt: this.topicRotatedAt,
|
|
37612
|
-
rateLimited
|
|
37613
|
-
};
|
|
37747
|
+
try {
|
|
37748
|
+
value = JSON.parse(raw);
|
|
37749
|
+
} catch {
|
|
37750
|
+
throw new Error("stored pending-for-main queue is malformed");
|
|
37614
37751
|
}
|
|
37615
|
-
|
|
37616
|
-
|
|
37752
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
37753
|
+
throw new Error("stored pending-for-main queue is malformed");
|
|
37617
37754
|
}
|
|
37618
|
-
|
|
37619
|
-
|
|
37755
|
+
const row = value;
|
|
37756
|
+
if (rejectUnknownKeys && Object.keys(row).some((key2) => !QUEUE_KEYS.has(key2)) || row.version !== 1 || !Array.isArray(row.entries) || row.entries.length > LISTENER_MAIN_QUEUE_MAX || !(row.droppedCount === void 0 || typeof row.droppedCount === "number" && Number.isSafeInteger(row.droppedCount) && row.droppedCount >= 0)) {
|
|
37757
|
+
throw new Error("stored pending-for-main queue is malformed");
|
|
37620
37758
|
}
|
|
37621
|
-
|
|
37622
|
-
|
|
37623
|
-
|
|
37624
|
-
this.trimWakeClaims(nowMs);
|
|
37759
|
+
const entries = row.entries.map((entry) => parseEntry(entry, rejectUnknownKeys));
|
|
37760
|
+
if (new Set(entries.map((entry) => entry.signalId)).size !== entries.length) {
|
|
37761
|
+
throw new Error("stored pending-for-main queue repeats a signal");
|
|
37625
37762
|
}
|
|
37626
|
-
|
|
37627
|
-
|
|
37628
|
-
|
|
37763
|
+
return { version: 1, entries, droppedCount: row.droppedCount ?? 0 };
|
|
37764
|
+
}
|
|
37765
|
+
var FilePendingMainQueue = class {
|
|
37766
|
+
path;
|
|
37767
|
+
directory;
|
|
37768
|
+
constructor(instanceDirectory) {
|
|
37769
|
+
if (!(0, import_node_path15.isAbsolute)(instanceDirectory)) {
|
|
37770
|
+
throw new Error("pending-for-main directory must be absolute");
|
|
37771
|
+
}
|
|
37772
|
+
this.directory = instanceDirectory;
|
|
37773
|
+
this.path = (0, import_node_path15.join)(instanceDirectory, QUEUE_FILE);
|
|
37629
37774
|
}
|
|
37630
|
-
|
|
37631
|
-
this.
|
|
37632
|
-
return
|
|
37775
|
+
async readUnlocked() {
|
|
37776
|
+
const raw = await readSecureJsonFile(this.path, MAX_QUEUE_BYTES);
|
|
37777
|
+
return raw === null ? { version: 1, entries: [], droppedCount: 0 } : parseFile(raw);
|
|
37633
37778
|
}
|
|
37634
|
-
|
|
37635
|
-
|
|
37636
|
-
|
|
37637
|
-
return !this.overWakeBudget(nowMs);
|
|
37779
|
+
async writeUnlocked(file) {
|
|
37780
|
+
const canonical = parseFile(JSON.stringify(file), true);
|
|
37781
|
+
await writeSecureJsonFile(this.path, JSON.stringify(canonical));
|
|
37638
37782
|
}
|
|
37639
|
-
|
|
37640
|
-
this.
|
|
37641
|
-
this.lastErrorCode = "rate_limited";
|
|
37642
|
-
this.emitPending("state");
|
|
37783
|
+
async read() {
|
|
37784
|
+
return [...(await this.readUnlocked()).entries];
|
|
37643
37785
|
}
|
|
37644
|
-
|
|
37645
|
-
|
|
37646
|
-
if (!isWakeTopic(topic)) {
|
|
37647
|
-
throw new Error("wake topic is malformed");
|
|
37648
|
-
}
|
|
37649
|
-
if (this.topic === topic) return;
|
|
37650
|
-
const rotated = this.topic !== null;
|
|
37651
|
-
void this.detachChannel();
|
|
37652
|
-
this.topic = topic;
|
|
37653
|
-
if (rotated) {
|
|
37654
|
-
this.topicRotatedAt = new Date(this.now()).toISOString();
|
|
37655
|
-
}
|
|
37656
|
-
this.connect();
|
|
37786
|
+
async count() {
|
|
37787
|
+
return (await this.readUnlocked()).entries.length;
|
|
37657
37788
|
}
|
|
37658
|
-
|
|
37659
|
-
|
|
37660
|
-
|
|
37661
|
-
|
|
37662
|
-
|
|
37663
|
-
|
|
37664
|
-
|
|
37665
|
-
|
|
37666
|
-
|
|
37667
|
-
|
|
37668
|
-
|
|
37669
|
-
|
|
37670
|
-
|
|
37789
|
+
async stats() {
|
|
37790
|
+
const file = await this.readUnlocked();
|
|
37791
|
+
return { count: file.entries.length, droppedCount: file.droppedCount };
|
|
37792
|
+
}
|
|
37793
|
+
async enqueue(entry) {
|
|
37794
|
+
const checked = parseEntry(entry, true);
|
|
37795
|
+
return await withFileLock(this.directory, QUEUE_LOCK, async () => {
|
|
37796
|
+
const file = await this.readUnlocked();
|
|
37797
|
+
if (file.entries.some((item) => item.signalId === checked.signalId)) {
|
|
37798
|
+
return {
|
|
37799
|
+
count: file.entries.length,
|
|
37800
|
+
added: false,
|
|
37801
|
+
droppedOldest: false,
|
|
37802
|
+
droppedCount: file.droppedCount
|
|
37803
|
+
};
|
|
37671
37804
|
}
|
|
37672
|
-
|
|
37673
|
-
|
|
37674
|
-
|
|
37675
|
-
|
|
37676
|
-
|
|
37677
|
-
|
|
37678
|
-
|
|
37679
|
-
|
|
37680
|
-
|
|
37681
|
-
|
|
37682
|
-
|
|
37683
|
-
|
|
37684
|
-
}
|
|
37685
|
-
resolve3(reason);
|
|
37686
|
-
};
|
|
37687
|
-
const delay2 = Math.max(0, options.until - this.now());
|
|
37688
|
-
const timer2 = setTimeout(() => finish("deadline"), delay2);
|
|
37689
|
-
const onAbort = () => finish("deadline");
|
|
37690
|
-
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
37691
|
-
this.waiter = {
|
|
37692
|
-
resolve: finish,
|
|
37693
|
-
timer: timer2,
|
|
37694
|
-
onAbort,
|
|
37695
|
-
signal: options.signal
|
|
37805
|
+
file.entries.push(checked);
|
|
37806
|
+
const droppedOldest = file.entries.length > LISTENER_MAIN_QUEUE_MAX;
|
|
37807
|
+
if (droppedOldest) {
|
|
37808
|
+
file.entries.shift();
|
|
37809
|
+
file.droppedCount += 1;
|
|
37810
|
+
}
|
|
37811
|
+
await this.writeUnlocked(file);
|
|
37812
|
+
return {
|
|
37813
|
+
count: file.entries.length,
|
|
37814
|
+
added: true,
|
|
37815
|
+
droppedOldest,
|
|
37816
|
+
droppedCount: file.droppedCount
|
|
37696
37817
|
};
|
|
37697
37818
|
});
|
|
37698
37819
|
}
|
|
37699
|
-
async
|
|
37700
|
-
|
|
37701
|
-
this.
|
|
37702
|
-
|
|
37703
|
-
|
|
37704
|
-
|
|
37705
|
-
|
|
37706
|
-
|
|
37707
|
-
|
|
37708
|
-
|
|
37709
|
-
|
|
37710
|
-
}
|
|
37711
|
-
trimWakeClaims(nowMs) {
|
|
37712
|
-
const minuteStart = Math.floor(nowMs / 6e4) * 6e4;
|
|
37713
|
-
this.wakeClaimTimes = this.wakeClaimTimes.filter((ts) => ts >= minuteStart);
|
|
37714
|
-
}
|
|
37715
|
-
/** Client budget or a server 429: do not claim on wake; poll covers the window. */
|
|
37716
|
-
wakeClaimPaused(nowMs) {
|
|
37717
|
-
return nowMs < this.rateLimitedUntil || this.overWakeBudget(nowMs);
|
|
37718
|
-
}
|
|
37719
|
-
emitPending(reason) {
|
|
37720
|
-
if (this.waiter !== null) {
|
|
37721
|
-
if (reason === "wake" && this.wakeClaimPaused(this.now())) {
|
|
37722
|
-
this.pending = "wake";
|
|
37723
|
-
return;
|
|
37820
|
+
async remove(signalIds, lockTimeoutMs) {
|
|
37821
|
+
if (signalIds.size === 0) return await this.count();
|
|
37822
|
+
return await withFileLock(this.directory, QUEUE_LOCK, async () => {
|
|
37823
|
+
const file = await this.readUnlocked();
|
|
37824
|
+
const entries = file.entries.filter((entry) => !signalIds.has(entry.signalId));
|
|
37825
|
+
if (entries.length !== file.entries.length) {
|
|
37826
|
+
await this.writeUnlocked({
|
|
37827
|
+
version: 1,
|
|
37828
|
+
entries,
|
|
37829
|
+
droppedCount: file.droppedCount
|
|
37830
|
+
});
|
|
37724
37831
|
}
|
|
37725
|
-
|
|
37726
|
-
|
|
37727
|
-
}
|
|
37728
|
-
if (reason === "wake" || this.pending !== "wake") {
|
|
37729
|
-
this.pending = reason;
|
|
37730
|
-
}
|
|
37731
|
-
}
|
|
37732
|
-
finishWait(reason) {
|
|
37733
|
-
const waiter = this.waiter;
|
|
37734
|
-
if (waiter === null) return;
|
|
37735
|
-
this.waiter = null;
|
|
37736
|
-
if (waiter.timer !== null) clearTimeout(waiter.timer);
|
|
37737
|
-
if (waiter.signal && waiter.onAbort) {
|
|
37738
|
-
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
37739
|
-
}
|
|
37740
|
-
waiter.resolve(reason);
|
|
37741
|
-
}
|
|
37742
|
-
connect() {
|
|
37743
|
-
if (this.closed || this.topic === null) return;
|
|
37744
|
-
if (this.realtime === null) {
|
|
37745
|
-
this.realtime = this.createRealtime(this.target);
|
|
37746
|
-
void this.realtime.setAuth(this.target.anonKey);
|
|
37747
|
-
}
|
|
37748
|
-
const topic = this.topic;
|
|
37749
|
-
this.connectionState = "connecting";
|
|
37750
|
-
this.lastErrorCode = null;
|
|
37751
|
-
const channel = this.realtime.channel(topic, {
|
|
37752
|
-
config: { private: true }
|
|
37753
|
-
});
|
|
37754
|
-
this.channel = channel;
|
|
37755
|
-
channel.on("broadcast", { event: WAKE_EVENT }, () => {
|
|
37756
|
-
this.lastWakeAt = new Date(this.now()).toISOString();
|
|
37757
|
-
this.emitPending("wake");
|
|
37758
|
-
});
|
|
37759
|
-
channel.subscribe((status) => {
|
|
37760
|
-
this.onSubscribeStatus(status);
|
|
37761
|
-
});
|
|
37762
|
-
}
|
|
37763
|
-
onSubscribeStatus(status) {
|
|
37764
|
-
if (this.closed) return;
|
|
37765
|
-
if (!isSubscribeStatus(status)) return;
|
|
37766
|
-
const wasSubscribed = this.connectionState === "subscribed";
|
|
37767
|
-
if (status === REALTIME_SUBSCRIBE_STATUS.SUBSCRIBED) {
|
|
37768
|
-
this.connectionState = "subscribed";
|
|
37769
|
-
this.subscribedAt = new Date(this.now()).toISOString();
|
|
37770
|
-
this.lastErrorCode = null;
|
|
37771
|
-
if (this.everSubscribed && !wasSubscribed) this.reconnects += 1;
|
|
37772
|
-
this.everSubscribed = true;
|
|
37773
|
-
if (!wasSubscribed) this.emitPending("state");
|
|
37774
|
-
return;
|
|
37775
|
-
}
|
|
37776
|
-
const code = wakeErrorCodeFromSubscribeStatus(status);
|
|
37777
|
-
if (status === REALTIME_SUBSCRIBE_STATUS.CLOSED) {
|
|
37778
|
-
this.connectionState = "disconnected";
|
|
37779
|
-
} else {
|
|
37780
|
-
this.connectionState = "errored";
|
|
37781
|
-
}
|
|
37782
|
-
this.lastErrorCode = code;
|
|
37783
|
-
this.subscribedAt = null;
|
|
37784
|
-
if (wasSubscribed) this.emitPending("state");
|
|
37785
|
-
}
|
|
37786
|
-
async detachChannel() {
|
|
37787
|
-
const channel = this.channel;
|
|
37788
|
-
this.channel = null;
|
|
37789
|
-
if (channel === null) return;
|
|
37790
|
-
try {
|
|
37791
|
-
await channel.unsubscribe();
|
|
37792
|
-
} catch {
|
|
37793
|
-
}
|
|
37794
|
-
try {
|
|
37795
|
-
await this.realtime?.removeChannel?.(channel);
|
|
37796
|
-
} catch {
|
|
37797
|
-
}
|
|
37798
|
-
if (this.channel !== null) return;
|
|
37799
|
-
if (this.connectionState === "subscribed") {
|
|
37800
|
-
this.connectionState = "disconnected";
|
|
37801
|
-
this.subscribedAt = null;
|
|
37802
|
-
}
|
|
37832
|
+
return entries.length;
|
|
37833
|
+
}, lockTimeoutMs === void 0 ? {} : { timeoutMs: lockTimeoutMs });
|
|
37803
37834
|
}
|
|
37804
37835
|
};
|
|
37805
|
-
function
|
|
37806
|
-
|
|
37836
|
+
function pendingMainEntry(signal, principalId, provenance, now, options = {}) {
|
|
37837
|
+
if (signal.kind !== "ask" && signal.kind !== "note") {
|
|
37838
|
+
throw new Error("only directed asks and notes can enter the pending-for-main queue");
|
|
37839
|
+
}
|
|
37840
|
+
return parseEntry({
|
|
37841
|
+
signalId: signal.id,
|
|
37842
|
+
workspaceId: signal.workspace_id,
|
|
37843
|
+
principalId,
|
|
37844
|
+
fromId: signal.from,
|
|
37845
|
+
fromKind: signal.from_kind,
|
|
37846
|
+
kind: signal.kind,
|
|
37847
|
+
senderName: provenance.senderName,
|
|
37848
|
+
body: signal.body,
|
|
37849
|
+
...(signal.attachments?.length ?? 0) > 0 ? { attachmentCount: signal.attachments.length } : {},
|
|
37850
|
+
createdAt: signal.created_at,
|
|
37851
|
+
queuedAt: new Date(now).toISOString(),
|
|
37852
|
+
...options.observationPending ? { observationPending: true } : {}
|
|
37853
|
+
}, true);
|
|
37807
37854
|
}
|
|
37808
37855
|
|
|
37809
37856
|
// src/listener/runtime.ts
|
|
@@ -43662,8 +43709,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
43662
43709
|
]);
|
|
43663
43710
|
var UUID_RE23 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
43664
43711
|
function packageVersion() {
|
|
43665
|
-
if ("0.1.
|
|
43666
|
-
return "0.1.
|
|
43712
|
+
if ("0.1.60".length > 0) {
|
|
43713
|
+
return "0.1.60";
|
|
43667
43714
|
}
|
|
43668
43715
|
try {
|
|
43669
43716
|
const value = JSON.parse(
|
|
@@ -46427,6 +46474,7 @@ async function runInboxNotifyCommand(args) {
|
|
|
46427
46474
|
principalId
|
|
46428
46475
|
);
|
|
46429
46476
|
await acquireArrivalWatchLock(lockPath);
|
|
46477
|
+
const wake = createWakeSubscriber({ target: cloud });
|
|
46430
46478
|
try {
|
|
46431
46479
|
const retryNotices = createArrivalRetryNoticePolicy();
|
|
46432
46480
|
let renderedBearer = selected.bearer;
|
|
@@ -46440,6 +46488,7 @@ async function runInboxNotifyCommand(args) {
|
|
|
46440
46488
|
principalId,
|
|
46441
46489
|
store: cursorStore,
|
|
46442
46490
|
signal: controller.signal,
|
|
46491
|
+
wake,
|
|
46443
46492
|
readPage: async ({ after, baseline, limit }) => {
|
|
46444
46493
|
const token = selected.session ? await selected.session.bearer() : selected.bearer;
|
|
46445
46494
|
renderedBearer = token;
|
|
@@ -46500,6 +46549,7 @@ async function runInboxNotifyCommand(args) {
|
|
|
46500
46549
|
process.off("SIGINT", stop);
|
|
46501
46550
|
process.off("SIGTERM", stop);
|
|
46502
46551
|
httpClient.close();
|
|
46552
|
+
await wake.close();
|
|
46503
46553
|
await releaseArrivalWatchLock(lockPath);
|
|
46504
46554
|
}
|
|
46505
46555
|
}
|