@rotorsoft/act 1.32.22 → 1.32.24
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/.tsbuildinfo +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +16 -10
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/audit.d.ts +5 -45
- package/dist/@types/internal/audit.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +23 -163
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +19 -129
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +21 -158
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/{chunk-T3CWXVGT.js → chunk-OG5O36AJ.js} +37 -87
- package/dist/chunk-OG5O36AJ.js.map +1 -0
- package/dist/index.cjs +65 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +30 -173
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +36 -86
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-T3CWXVGT.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1142,25 +1142,19 @@ var InMemoryStore = class {
|
|
|
1142
1142
|
*/
|
|
1143
1143
|
async defer(input, deferred_at) {
|
|
1144
1144
|
await sleep();
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
s.defer(deferred_at);
|
|
1159
|
-
count++;
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
return count;
|
|
1145
|
+
const selected = this._select(input);
|
|
1146
|
+
for (const s of selected) s.defer(deferred_at);
|
|
1147
|
+
return selected.length;
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* The streams named by `input`: an explicit list (de-duplicated, so a
|
|
1151
|
+
* repeated name counts once, matching the SQL adapters' set-based
|
|
1152
|
+
* `stream = ANY(...)`; unknown names are skipped) or a {@link StreamFilter}.
|
|
1153
|
+
*/
|
|
1154
|
+
_select(input) {
|
|
1155
|
+
if (Array.isArray(input))
|
|
1156
|
+
return [...new Set(input)].map((name) => this._streams.get(name)).filter((s) => s !== void 0);
|
|
1157
|
+
return [...this._streams.values()].filter(this._filter_predicate(input));
|
|
1164
1158
|
}
|
|
1165
1159
|
/**
|
|
1166
1160
|
* Build a predicate from a {@link StreamFilter}. Compiled regexes are
|
|
@@ -1196,37 +1190,10 @@ var InMemoryStore = class {
|
|
|
1196
1190
|
*/
|
|
1197
1191
|
async reset(input) {
|
|
1198
1192
|
await sleep();
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
const s = this._streams.get(name);
|
|
1203
|
-
if (s) {
|
|
1204
|
-
s.reset();
|
|
1205
|
-
count++;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
} else {
|
|
1209
|
-
const matches = this._filter_predicate(input);
|
|
1210
|
-
for (const s of this._streams.values()) {
|
|
1211
|
-
if (matches(s)) {
|
|
1212
|
-
s.reset();
|
|
1213
|
-
count++;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
return count;
|
|
1193
|
+
const selected = this._select(input);
|
|
1194
|
+
for (const s of selected) s.reset();
|
|
1195
|
+
return selected.length;
|
|
1218
1196
|
}
|
|
1219
|
-
/**
|
|
1220
|
-
* Clear the blocked flag (and retry / error / lease) on the matched
|
|
1221
|
-
* streams without touching the watermark. Streams that aren't blocked
|
|
1222
|
-
* at call time are silently skipped. Accepts either an explicit list
|
|
1223
|
-
* of names or a {@link StreamFilter}. The filter form always restricts
|
|
1224
|
-
* to blocked streams — passing `blocked: false` matches nothing.
|
|
1225
|
-
* See {@link Store.unblock}.
|
|
1226
|
-
*
|
|
1227
|
-
* @param input - Stream names or a filter selecting the streams to unblock.
|
|
1228
|
-
* @returns Count of streams that were actually flipped (were blocked).
|
|
1229
|
-
*/
|
|
1230
1197
|
/**
|
|
1231
1198
|
* Wipe the sensitive-data payload for every event on the stream — see
|
|
1232
1199
|
* {@link Store.forget_pii}. O(1) drop of the stream's inner Map; the size of
|
|
@@ -1242,21 +1209,23 @@ var InMemoryStore = class {
|
|
|
1242
1209
|
this._pii.delete(stream);
|
|
1243
1210
|
return count;
|
|
1244
1211
|
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Clear the blocked flag (and retry / error / lease) on the matched
|
|
1214
|
+
* streams without touching the watermark. Streams that aren't blocked
|
|
1215
|
+
* at call time are silently skipped. Accepts either an explicit list
|
|
1216
|
+
* of names or a {@link StreamFilter}. The filter form always restricts
|
|
1217
|
+
* to blocked streams — passing `blocked: false` matches nothing.
|
|
1218
|
+
* See {@link Store.unblock}.
|
|
1219
|
+
*
|
|
1220
|
+
* @param input - Stream names or a filter selecting the streams to unblock.
|
|
1221
|
+
* @returns Count of streams that were actually flipped (were blocked).
|
|
1222
|
+
*/
|
|
1245
1223
|
async unblock(input) {
|
|
1246
1224
|
await sleep();
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
if (s?.unblock()) count++;
|
|
1252
|
-
}
|
|
1253
|
-
} else {
|
|
1254
|
-
const matches = this._filter_predicate({ ...input, blocked: true });
|
|
1255
|
-
for (const s of this._streams.values()) {
|
|
1256
|
-
if (matches(s) && s.unblock()) count++;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
return count;
|
|
1225
|
+
const selected = this._select(
|
|
1226
|
+
Array.isArray(input) ? input : { ...input, blocked: true }
|
|
1227
|
+
);
|
|
1228
|
+
return selected.filter((s) => s.unblock()).length;
|
|
1260
1229
|
}
|
|
1261
1230
|
/**
|
|
1262
1231
|
* Bulk-update priority of streams matching `filter`. Mirrors
|
|
@@ -1269,16 +1238,9 @@ var InMemoryStore = class {
|
|
|
1269
1238
|
*/
|
|
1270
1239
|
async prioritize(filter, priority) {
|
|
1271
1240
|
await sleep();
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
if (!matches(s)) continue;
|
|
1276
|
-
if (s.priority !== priority) {
|
|
1277
|
-
s.set_priority(priority);
|
|
1278
|
-
count++;
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
return count;
|
|
1241
|
+
const changed = this._select(filter).filter((s) => s.priority !== priority);
|
|
1242
|
+
for (const s of changed) s.set_priority(priority);
|
|
1243
|
+
return changed.length;
|
|
1282
1244
|
}
|
|
1283
1245
|
/**
|
|
1284
1246
|
* Streams registered subscription positions to the callback, ordered by
|
|
@@ -1289,10 +1251,8 @@ var InMemoryStore = class {
|
|
|
1289
1251
|
await sleep();
|
|
1290
1252
|
const limit = query?.limit ?? 100;
|
|
1291
1253
|
const after = query?.after;
|
|
1292
|
-
const blocked = query?.blocked;
|
|
1293
1254
|
const source_matches = query?.source_matches;
|
|
1294
|
-
const
|
|
1295
|
-
const source_re = query?.source !== void 0 && !query.source_exact ? new RegExp(query.source) : void 0;
|
|
1255
|
+
const matches = this._filter_predicate(query ?? {});
|
|
1296
1256
|
const reverse_cache = /* @__PURE__ */ new Map();
|
|
1297
1257
|
const reverse_match = (source) => {
|
|
1298
1258
|
let re = reverse_cache.get(source);
|
|
@@ -1306,20 +1266,10 @@ var InMemoryStore = class {
|
|
|
1306
1266
|
let count = 0;
|
|
1307
1267
|
for (const s of sorted) {
|
|
1308
1268
|
if (after !== void 0 && s.stream <= after) continue;
|
|
1309
|
-
if (
|
|
1310
|
-
if (query.stream_exact ? s.stream !== query.stream : !stream_re.test(s.stream))
|
|
1311
|
-
continue;
|
|
1312
|
-
}
|
|
1313
|
-
if (query?.source !== void 0) {
|
|
1314
|
-
if (s.source === void 0) continue;
|
|
1315
|
-
if (query.source_exact ? s.source !== query.source : !source_re.test(s.source))
|
|
1316
|
-
continue;
|
|
1317
|
-
}
|
|
1269
|
+
if (!matches(s)) continue;
|
|
1318
1270
|
if (source_matches !== void 0) {
|
|
1319
1271
|
if (s.source && !reverse_match(s.source)) continue;
|
|
1320
1272
|
}
|
|
1321
|
-
if (blocked !== void 0 && s.blocked !== blocked) continue;
|
|
1322
|
-
if (query?.lane !== void 0 && s.lane !== query.lane) continue;
|
|
1323
1273
|
if (count >= limit) break;
|
|
1324
1274
|
callback({
|
|
1325
1275
|
stream: s.stream,
|
|
@@ -3178,71 +3128,27 @@ function report_lane_conflict(seen, handler, target, kept, dropped) {
|
|
|
3178
3128
|
var CorrelateCycle = class {
|
|
3179
3129
|
_checkpoint = -1;
|
|
3180
3130
|
_initialized = false;
|
|
3181
|
-
/**
|
|
3182
|
-
* This worker's identity for the correlation lease. A per-instance
|
|
3183
|
-
* UUID, matching the drain's convention, so a renewal is recognised as the
|
|
3184
|
-
* same holder and a restarted process never inherits a stale claim.
|
|
3185
|
-
*/
|
|
3131
|
+
/** Lease holder id; per instance, so a restart never inherits a stale claim. */
|
|
3186
3132
|
_by = (0, import_node_crypto.randomUUID)();
|
|
3187
|
-
/**
|
|
3188
|
-
* Which correlator this is. Computed once from the registry so identical
|
|
3189
|
-
* workers share a lease and unrelated applications never do.
|
|
3190
|
-
*/
|
|
3191
3133
|
_key;
|
|
3192
|
-
/**
|
|
3193
|
-
* When this worker's correlation lease runs out, as a local clock reading,
|
|
3194
|
-
* or 0 when it holds none.
|
|
3195
|
-
*
|
|
3196
|
-
* Cached so the holder doesn't ask the store on every pass. A stale belief
|
|
3197
|
-
* can only cause a duplicate scan, and marks are idempotent, so it costs
|
|
3198
|
-
* work, never correctness.
|
|
3199
|
-
*/
|
|
3134
|
+
/** Local expiry of the held lease (0 = none); stale only costs a duplicate scan. */
|
|
3200
3135
|
_lease_until = 0;
|
|
3201
|
-
/**
|
|
3202
|
-
* Whether a scan might find anything, like the drain's flag: a commit or
|
|
3203
|
-
* notification raises it, a short page lowers it, and a disarmed scan
|
|
3204
|
-
* returns without touching the store.
|
|
3205
|
-
*
|
|
3206
|
-
* Starts armed: the log may already hold events this process has never
|
|
3207
|
-
* correlated, and only a scan can find out.
|
|
3208
|
-
*/
|
|
3136
|
+
/** Raised by a commit or notification, lowered by a short page. Starts armed. */
|
|
3209
3137
|
_armed = true;
|
|
3210
|
-
/** In-flight init, memoized for single-flight and cleared on failure. */
|
|
3211
3138
|
_init_promise;
|
|
3212
|
-
//
|
|
3213
|
-
//
|
|
3214
|
-
// target: priority/lane only when the resolution beats the recorded floor,
|
|
3215
|
-
// otherwise the row's own values. See {@link Subscription}.
|
|
3139
|
+
// Bounded by `maxSubscribedStreams`; static records live apart so eviction
|
|
3140
|
+
// never drops their +Infinity floor.
|
|
3216
3141
|
_dynamic_subscriptions;
|
|
3217
|
-
/**
|
|
3218
|
-
* What each static target was subscribed at by `init`. A plain map, never
|
|
3219
|
-
* evicted: the collection is the build-time `_static_targets` list, so it
|
|
3220
|
-
* is already bounded by the registry and costs nothing the registry does
|
|
3221
|
-
* not already hold.
|
|
3222
|
-
*
|
|
3223
|
-
* Kept out of the LRU so eviction can never drop a static target's
|
|
3224
|
-
* `+Infinity` floor and let a dynamic resolution re-lane it.
|
|
3225
|
-
*/
|
|
3226
3142
|
_static_subscriptions = /* @__PURE__ */ new Map();
|
|
3227
|
-
/** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
|
|
3228
3143
|
_patterns = new LruMap(PATTERN_CACHE_SIZE);
|
|
3229
3144
|
_registry;
|
|
3230
3145
|
_static_targets;
|
|
3231
3146
|
_cd;
|
|
3232
3147
|
_on_init;
|
|
3233
|
-
/**
|
|
3234
|
-
* Async cold-start hook, run once after `on_init` inside `init()`. The
|
|
3235
|
-
* orchestrator re-seeds its defer timers from the persisted `deferred_at`
|
|
3236
|
-
* here, so an idle deferred stream re-arms across a restart.
|
|
3237
|
-
*/
|
|
3148
|
+
/** Runs once after `on_init`; re-seeds defer timers across a restart. */
|
|
3238
3149
|
_on_init_async;
|
|
3239
|
-
/** Lanes a controller exists for. See {@link CorrelateCycleDeps}. */
|
|
3240
3150
|
_declared_lanes;
|
|
3241
|
-
/**
|
|
3242
|
-
* Offending declarations already reported, so a resolver firing for every
|
|
3243
|
-
* matching event reports once. Owned by the instance rather than the
|
|
3244
|
-
* module: `internal/` holds no module-level state.
|
|
3245
|
-
*/
|
|
3151
|
+
/** Report-once keys; per instance because `internal/` holds no module state. */
|
|
3246
3152
|
_reported = /* @__PURE__ */ new Set();
|
|
3247
3153
|
constructor({
|
|
3248
3154
|
registry,
|
|
@@ -3263,28 +3169,13 @@ var CorrelateCycle = class {
|
|
|
3263
3169
|
this._on_init = on_init;
|
|
3264
3170
|
this._on_init_async = on_init_async;
|
|
3265
3171
|
}
|
|
3266
|
-
/** Last correlated event id. */
|
|
3267
3172
|
get checkpoint() {
|
|
3268
3173
|
return this._checkpoint;
|
|
3269
3174
|
}
|
|
3270
|
-
/**
|
|
3271
|
-
* Signal that a commit (local or remote) may have produced events this
|
|
3272
|
-
* process has not correlated. Cheap and idempotent — the orchestrator calls
|
|
3273
|
-
* it on every commit and every notification.
|
|
3274
|
-
*/
|
|
3175
|
+
/** A commit (local or remote) may have produced uncorrelated events. */
|
|
3275
3176
|
arm() {
|
|
3276
3177
|
this._armed = true;
|
|
3277
3178
|
}
|
|
3278
|
-
/**
|
|
3279
|
-
* Initialize correlation state on first call.
|
|
3280
|
-
* - Reads the durable correlate checkpoint (and max(at)) from the store,
|
|
3281
|
-
* flooring a first boot at `watermark - back_scan` so an event
|
|
3282
|
-
* committed-but-not-correlated before a crash is re-scanned on
|
|
3283
|
-
* restart instead of skipped
|
|
3284
|
-
* - Subscribes static resolver targets (idempotent upsert)
|
|
3285
|
-
* - Populates the subscribed-streams LRU
|
|
3286
|
-
* - Fires `on_init` once (Act uses this to flag a cold-start drain)
|
|
3287
|
-
*/
|
|
3288
3179
|
async init() {
|
|
3289
3180
|
if (this._initialized) return;
|
|
3290
3181
|
if (!this._init_promise) {
|
|
@@ -3311,16 +3202,7 @@ var CorrelateCycle = class {
|
|
|
3311
3202
|
}
|
|
3312
3203
|
await this._on_init_async?.();
|
|
3313
3204
|
}
|
|
3314
|
-
/**
|
|
3315
|
-
* Start correlation over after the event log was replaced wholesale
|
|
3316
|
-
* (`restore`). The restored log is renumbered from the start and its
|
|
3317
|
-
* subscription rows are gone, so the in-memory scan position and both
|
|
3318
|
-
* subscription records describe a log that no longer exists. Dropping them
|
|
3319
|
-
* and the init latch makes the next pass run cold-start again: static
|
|
3320
|
-
* targets are re-subscribed, the checkpoint is re-read from the store
|
|
3321
|
-
* (which `restore` reset), and dynamic targets are rediscovered by the
|
|
3322
|
-
* scan.
|
|
3323
|
-
*/
|
|
3205
|
+
/** After `restore` replaced the log: forget everything and cold-start again. */
|
|
3324
3206
|
restart() {
|
|
3325
3207
|
this._initialized = false;
|
|
3326
3208
|
this._init_promise = void 0;
|
|
@@ -3329,21 +3211,11 @@ var CorrelateCycle = class {
|
|
|
3329
3211
|
this._static_subscriptions.clear();
|
|
3330
3212
|
this._armed = true;
|
|
3331
3213
|
}
|
|
3332
|
-
/**
|
|
3333
|
-
* Forget dynamic targets whose subscription rows no longer exist (a full
|
|
3334
|
-
* close deletes the row), so a later scan re-subscribes them. Static
|
|
3335
|
-
* targets keep their records.
|
|
3336
|
-
*/
|
|
3214
|
+
/** Forget dynamic targets whose rows a full close deleted, so they re-subscribe. */
|
|
3337
3215
|
forget_subscribed(streams) {
|
|
3338
3216
|
for (const stream of streams) this._dynamic_subscriptions.delete(stream);
|
|
3339
3217
|
}
|
|
3340
|
-
/**
|
|
3341
|
-
* Would an event from `stream` be fetched for a target subscribed with
|
|
3342
|
-
* `source`? The subscription's source is the filter `fetch` queries with
|
|
3343
|
-
* — literal names by equality, patterns compiled as a `RegExp` — so an
|
|
3344
|
-
* event outside it is not work for that target and must not mark it.
|
|
3345
|
-
* No source means the target consumes every stream.
|
|
3346
|
-
*/
|
|
3218
|
+
/** Would the target's own `fetch` (filtered by `source`) return this event? */
|
|
3347
3219
|
_in_fetch_window(source, stream) {
|
|
3348
3220
|
if (source === void 0 || source === stream) return true;
|
|
3349
3221
|
if (is_literal_source(source)) return false;
|
|
@@ -3354,16 +3226,7 @@ var CorrelateCycle = class {
|
|
|
3354
3226
|
}
|
|
3355
3227
|
return pattern.test(stream);
|
|
3356
3228
|
}
|
|
3357
|
-
/**
|
|
3358
|
-
* Scan the events past the checkpoint, resolve each to its target
|
|
3359
|
-
* streams, and record what it found through `cd.subscribe` — new dynamic
|
|
3360
|
-
* targets get registered, and every target an event resolved to gets its
|
|
3361
|
-
* **work mark** raised to that event's id.
|
|
3362
|
-
*
|
|
3363
|
-
* Both resolver kinds are walked. A static target is already subscribed
|
|
3364
|
-
* at init, but marking it is what makes it claimable without probing the
|
|
3365
|
-
* event log, so the scan runs for every app.
|
|
3366
|
-
*/
|
|
3229
|
+
/** Scan past the checkpoint, subscribe new targets and raise their work marks. */
|
|
3367
3230
|
async correlate(query = { after: -1, limit: 10 }, lease = false) {
|
|
3368
3231
|
await this.init();
|
|
3369
3232
|
if (!this._armed)
|
|
@@ -3448,10 +3311,8 @@ var CorrelateCycle = class {
|
|
|
3448
3311
|
}
|
|
3449
3312
|
}
|
|
3450
3313
|
},
|
|
3451
|
-
//
|
|
3452
|
-
//
|
|
3453
|
-
// reactions. Resolvers therefore see `pii: null`; correlate runs
|
|
3454
|
-
// actor-less, so plaintext here would be un-gated disclosure.
|
|
3314
|
+
// Resolvers see `pii: null`: correlate runs actor-less, and one
|
|
3315
|
+
// undecryptable row would otherwise stop every stream's reactions.
|
|
3455
3316
|
{ ...query, after, with_pii: false }
|
|
3456
3317
|
);
|
|
3457
3318
|
if (this._declared_lanes.size > 1) await this._recall(correlated);
|
|
@@ -3494,14 +3355,9 @@ var CorrelateCycle = class {
|
|
|
3494
3355
|
return { subscribed: 0, last_id, marked: 0, scanned: true };
|
|
3495
3356
|
}
|
|
3496
3357
|
/**
|
|
3497
|
-
*
|
|
3498
|
-
*
|
|
3499
|
-
*
|
|
3500
|
-
* has one, says what lane and priority it was subscribed at; a resolution
|
|
3501
|
-
* that doesn't beat that priority carries the row's values, exactly as if
|
|
3502
|
-
* the record had never been evicted. One read per target with no record,
|
|
3503
|
-
* and only when more than one lane is declared: with one lane there is no
|
|
3504
|
-
* lane to lose, and the store already keeps the higher priority.
|
|
3358
|
+
* Read the row of each target this scan had no record of (new or evicted),
|
|
3359
|
+
* so a resolution that doesn't beat its priority keeps the row's lane.
|
|
3360
|
+
* Only called with more than one lane declared.
|
|
3505
3361
|
*/
|
|
3506
3362
|
async _recall(correlated) {
|
|
3507
3363
|
const missed = [...correlated].filter(([, entry]) => entry.missed);
|
|
@@ -3537,13 +3393,7 @@ var CorrelateCycle = class {
|
|
|
3537
3393
|
})
|
|
3538
3394
|
);
|
|
3539
3395
|
}
|
|
3540
|
-
/**
|
|
3541
|
-
* Hand the correlation lease back early.
|
|
3542
|
-
*
|
|
3543
|
-
* The port has no release verb; re-acquiring as the same holder with a
|
|
3544
|
-
* zero duration releases it. Best-effort: a failure costs the lease's
|
|
3545
|
-
* remaining lifetime, as a crash would.
|
|
3546
|
-
*/
|
|
3396
|
+
/** Release the lease early by re-acquiring it for zero ms. Best-effort. */
|
|
3547
3397
|
async release_correlation() {
|
|
3548
3398
|
try {
|
|
3549
3399
|
this._lease_until = 0;
|
|
@@ -4020,11 +3870,9 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
4020
3870
|
{
|
|
4021
3871
|
stream,
|
|
4022
3872
|
stream_exact: true,
|
|
4023
|
-
// `with_snaps` on every non-time-travel load
|
|
4024
|
-
// latest snapshot
|
|
4025
|
-
//
|
|
4026
|
-
// result, so a windowed close that pruned the events in between can't
|
|
4027
|
-
// make the fold apply the tail onto stale state.
|
|
3873
|
+
// `with_snaps` on every non-time-travel load: cold, it resumes from the
|
|
3874
|
+
// latest snapshot; warm, it keeps a snapshot above a stale checkpoint,
|
|
3875
|
+
// so a windowed close's prune can't fold the tail onto stale state.
|
|
4028
3876
|
...cached ? { after: cached.event_id, with_snaps: true } : { ...time_travel ? {} : { with_snaps: true }, ...asOf }
|
|
4029
3877
|
}
|
|
4030
3878
|
);
|
|
@@ -4048,10 +3896,7 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
4048
3896
|
event,
|
|
4049
3897
|
state: state2,
|
|
4050
3898
|
version: event?.version ?? cached?.version ?? -1,
|
|
4051
|
-
//
|
|
4052
|
-
// event's id on a cache miss, or the cached checkpoint's event_id on a
|
|
4053
|
-
// warm hit with no new events. Consumers read this instead of a
|
|
4054
|
-
// separate cache lookup that could race a concurrent commit.
|
|
3899
|
+
// Captured with `state`, so consumers never race a separate cache lookup.
|
|
4055
3900
|
id: event?.id ?? cached?.event_id ?? -1,
|
|
4056
3901
|
patches,
|
|
4057
3902
|
snaps,
|
|
@@ -4113,8 +3958,7 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
|
|
|
4113
3958
|
action: {
|
|
4114
3959
|
name: action2,
|
|
4115
3960
|
...target
|
|
4116
|
-
// payload intentionally omitted
|
|
4117
|
-
// callers correlate via the correlation id when they need it.
|
|
3961
|
+
// payload intentionally omitted
|
|
4118
3962
|
},
|
|
4119
3963
|
event: reactingTo ? {
|
|
4120
3964
|
id: reactingTo.id,
|
|
@@ -4517,61 +4361,35 @@ var DrainController = class {
|
|
|
4517
4361
|
_locked = false;
|
|
4518
4362
|
_ratio = 0.5;
|
|
4519
4363
|
/**
|
|
4520
|
-
* Local wake
|
|
4521
|
-
*
|
|
4522
|
-
*
|
|
4523
|
-
* the drain alone finds it, and on an idle app nothing else would.
|
|
4524
|
-
* Every drain re-aims it when it ends, and a due entry stays until a pass
|
|
4525
|
-
* handles it or a pass that started after it came due claims nothing, so
|
|
4526
|
-
* the wake keeps draining until every due stream is reached.
|
|
4527
|
-
* `stop()` cancels the timer and a stopped drain never re-schedules it,
|
|
4528
|
-
* so a wake never runs after a stop.
|
|
4364
|
+
* Local wake at the earliest parked (backoff or defer) visit; the schedule
|
|
4365
|
+
* itself is persisted. On an idle app nothing else would drain it. A due
|
|
4366
|
+
* entry stays until handled or until a later pass claims nothing.
|
|
4529
4367
|
*/
|
|
4530
4368
|
_defer = new DeferTimer(() => {
|
|
4531
4369
|
this._armed = true;
|
|
4532
4370
|
void this._deps.run_scoped(() => this.drain());
|
|
4533
4371
|
});
|
|
4534
|
-
/** Worker timer. Set when `start()` is active, undefined otherwise. */
|
|
4535
4372
|
_worker;
|
|
4536
|
-
/**
|
|
4537
|
-
* Misroutings this controller has reported. A resolver returning a
|
|
4538
|
-
* projection's target does so for every matching event; one line per event
|
|
4539
|
-
* would bury the signal it exists to raise.
|
|
4540
|
-
*/
|
|
4541
4373
|
_misrouted = /* @__PURE__ */ new Set();
|
|
4542
4374
|
_stopped = false;
|
|
4543
|
-
/**
|
|
4544
|
-
* Resolves when the cycle in flight finishes, so a graceful shutdown can
|
|
4545
|
-
* wait for it. Never rejects.
|
|
4546
|
-
*/
|
|
4375
|
+
/** Never rejects; graceful shutdown waits on it. */
|
|
4547
4376
|
_inflight;
|
|
4548
4377
|
_inflight_done;
|
|
4549
4378
|
_deps;
|
|
4550
4379
|
constructor(deps) {
|
|
4551
4380
|
this._deps = deps;
|
|
4552
4381
|
}
|
|
4553
|
-
/**
|
|
4554
|
-
* Signal that a commit (or reset / cold-start) may have produced work.
|
|
4555
|
-
* Subsequent `drain()` calls will run the pipeline; once the pipeline
|
|
4556
|
-
* settles to no-progress, the controller disarms itself.
|
|
4557
|
-
*/
|
|
4558
4382
|
arm() {
|
|
4559
4383
|
this._armed = true;
|
|
4560
4384
|
}
|
|
4561
|
-
/**
|
|
4562
|
-
* Re-seed a persisted `deferred_at` into the local wake at cold start, so
|
|
4563
|
-
* an idle deferred stream re-arms at its due-time with no new commit.
|
|
4564
|
-
* Seeds collapse into one timer; the earliest wins.
|
|
4565
|
-
*/
|
|
4385
|
+
/** Cold start: re-seed a persisted `deferred_at` into the local wake. */
|
|
4566
4386
|
seed_defer(stream, at) {
|
|
4567
4387
|
this._defer.set(stream, at);
|
|
4568
4388
|
this._defer.schedule();
|
|
4569
4389
|
}
|
|
4570
|
-
/** Read-only flag — true while a commit / reset is unprocessed. */
|
|
4571
4390
|
get armed() {
|
|
4572
4391
|
return this._armed;
|
|
4573
4392
|
}
|
|
4574
|
-
/** The cycle in flight, or `undefined` when idle. */
|
|
4575
4393
|
get inflight() {
|
|
4576
4394
|
return this._inflight;
|
|
4577
4395
|
}
|
|
@@ -4579,19 +4397,10 @@ var DrainController = class {
|
|
|
4579
4397
|
get lease_millis() {
|
|
4580
4398
|
return this._deps.defaults?.leaseMillis;
|
|
4581
4399
|
}
|
|
4582
|
-
/** Lane this controller drains (undefined spans all lanes). */
|
|
4583
4400
|
get lane() {
|
|
4584
4401
|
return this._deps.lane;
|
|
4585
4402
|
}
|
|
4586
|
-
/**
|
|
4587
|
-
* Start a per-lane worker that drains at the lane's `cycleMs`
|
|
4588
|
-
* cadence. When armed, the worker calls `drain()` on every
|
|
4589
|
-
* tick and re-schedules; when not armed, it still re-schedules at
|
|
4590
|
-
* `cycleMs` so a future `arm()` is picked up on the next tick.
|
|
4591
|
-
*
|
|
4592
|
-
* The setTimeout chain uses `unref()` so it doesn't keep the process
|
|
4593
|
-
* alive on its own.
|
|
4594
|
-
*/
|
|
4403
|
+
/** Drains every `cycleMs` while armed; the unref'd timer never holds the process. */
|
|
4595
4404
|
start(cycleMs) {
|
|
4596
4405
|
if (this._worker || this._stopped) return;
|
|
4597
4406
|
const run = this._deps.run_scoped;
|
|
@@ -4604,7 +4413,6 @@ var DrainController = class {
|
|
|
4604
4413
|
this._worker = setTimeout(tick, cycleMs);
|
|
4605
4414
|
this._worker.unref();
|
|
4606
4415
|
}
|
|
4607
|
-
/** Stop the per-lane worker. Idempotent. */
|
|
4608
4416
|
stop() {
|
|
4609
4417
|
this._stopped = true;
|
|
4610
4418
|
if (this._worker) {
|
|
@@ -4613,7 +4421,6 @@ var DrainController = class {
|
|
|
4613
4421
|
}
|
|
4614
4422
|
this._defer.stop();
|
|
4615
4423
|
}
|
|
4616
|
-
/** Run one drain pass. Short-circuits when not armed or already running. */
|
|
4617
4424
|
async drain(options = {}) {
|
|
4618
4425
|
if (!this._armed) return EMPTY_DRAIN;
|
|
4619
4426
|
if (this._locked) return EMPTY_DRAIN;
|