@rotorsoft/act 1.32.13 → 1.32.15
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/act.d.ts +98 -152
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +3 -3
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts +3 -3
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/builders/build-classify.d.ts +1 -1
- package/dist/@types/builders/builder-base.d.ts +3 -3
- package/dist/@types/builders/builder-utils.d.ts +1 -1
- package/dist/@types/builders/event-builder.d.ts +2 -12
- package/dist/@types/builders/event-builder.d.ts.map +1 -1
- package/dist/@types/builders/merge.d.ts +1 -1
- package/dist/@types/builders/reaction-builder.d.ts +1 -1
- package/dist/@types/builders/slice-builder.d.ts +2 -2
- package/dist/@types/builders/state-builder.d.ts +3 -4
- package/dist/@types/builders/state-builder.d.ts.map +1 -1
- package/dist/@types/csv.d.ts +1 -1
- package/dist/@types/disposers.d.ts.map +1 -1
- package/dist/@types/internal/audit.d.ts +12 -42
- package/dist/@types/internal/audit.d.ts.map +1 -1
- package/dist/@types/internal/autoclose-policy.d.ts +3 -3
- package/dist/@types/internal/autoclose-reaction.d.ts +2 -2
- package/dist/@types/internal/autoclose-window.d.ts +1 -1
- package/dist/@types/internal/close-cycle.d.ts +10 -39
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/close-signal.d.ts +3 -3
- package/dist/@types/internal/config.d.ts +3 -3
- package/dist/@types/internal/config.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +39 -86
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/correlator.d.ts +1 -1
- package/dist/@types/internal/date-reviver.d.ts +1 -1
- package/dist/@types/internal/defer-config.d.ts +2 -2
- package/dist/@types/internal/defer-signal.d.ts +2 -2
- package/dist/@types/internal/defer-timer.d.ts +3 -3
- package/dist/@types/internal/drain-cycle.d.ts +35 -82
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +8 -19
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/event-versions.d.ts +1 -1
- package/dist/@types/internal/index.d.ts +5 -6
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/internal/projection-fold.d.ts +2 -2
- package/dist/@types/internal/report-once.d.ts +3 -3
- package/dist/@types/internal/sensitive.d.ts +2 -28
- package/dist/@types/internal/sensitive.d.ts.map +1 -1
- package/dist/@types/internal/settle.d.ts +6 -19
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/internal/walk-streams.d.ts +0 -2
- package/dist/@types/internal/walk-streams.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +3 -3
- package/dist/@types/scoped.d.ts +6 -16
- package/dist/@types/scoped.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +19 -19
- package/dist/@types/types/audit.d.ts +1 -1
- package/dist/@types/types/ports.d.ts +22 -26
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +8 -8
- package/dist/@types/types/registry.d.ts +1 -1
- package/dist/@types/types/schemas.d.ts +2 -2
- package/dist/@types/utils.d.ts +1 -1
- package/dist/{chunk-BI3UNNVA.js → chunk-JRTZGWVS.js} +1 -1
- package/dist/chunk-JRTZGWVS.js.map +1 -0
- package/dist/{chunk-FLAGQMWE.js → chunk-PZP5ZEZ4.js} +9 -9
- package/dist/chunk-PZP5ZEZ4.js.map +1 -0
- package/dist/index.cjs +137 -359
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +132 -354
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +7 -7
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +2 -2
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-BI3UNNVA.js.map +0 -1
- package/dist/chunk-FLAGQMWE.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -635,12 +635,12 @@ var InMemoryStream = class {
|
|
|
635
635
|
_leased_until = void 0;
|
|
636
636
|
_priority = 0;
|
|
637
637
|
_lane = DEFAULT_LANE;
|
|
638
|
-
// Persisted next-visit time
|
|
638
|
+
// Persisted next-visit time. When set and still in the future, the
|
|
639
639
|
// stream is held out of `claim` entirely — so a deferred reaction is not
|
|
640
640
|
// re-claimed (and `retry` is never bumped) until its due-time passes. Unlike
|
|
641
641
|
// in-process backoff, this is durable store state shared across workers.
|
|
642
642
|
_deferred_at = void 0;
|
|
643
|
-
// Work mark
|
|
643
|
+
// Work mark: highest event id observed to resolve to this target.
|
|
644
644
|
// `undefined` means UNKNOWN — the row predates the mark, so `claim` falls
|
|
645
645
|
// back to the legacy has-work probe rather than treating it as "no work".
|
|
646
646
|
_correlated_at = void 0;
|
|
@@ -686,7 +686,7 @@ var InMemoryStream = class {
|
|
|
686
686
|
this._priority = priority;
|
|
687
687
|
}
|
|
688
688
|
get is_available() {
|
|
689
|
-
return !this._blocked && (!this._leased_until || this._leased_until <= /* @__PURE__ */ new Date()) && // A stream deferred to a future time is not claimable until due
|
|
689
|
+
return !this._blocked && (!this._leased_until || this._leased_until <= /* @__PURE__ */ new Date()) && // A stream deferred to a future time is not claimable until due.
|
|
690
690
|
(!this._deferred_at || this._deferred_at <= Date.now());
|
|
691
691
|
}
|
|
692
692
|
/**
|
|
@@ -716,7 +716,7 @@ var InMemoryStream = class {
|
|
|
716
716
|
get leased_until() {
|
|
717
717
|
return this._leased_until;
|
|
718
718
|
}
|
|
719
|
-
/** Persisted next-visit time
|
|
719
|
+
/** Persisted next-visit time, or undefined when no active defer. */
|
|
720
720
|
get deferred_at() {
|
|
721
721
|
return this._deferred_at;
|
|
722
722
|
}
|
|
@@ -831,16 +831,16 @@ var InMemoryStore = class {
|
|
|
831
831
|
_next_id = 0;
|
|
832
832
|
// stored stream positions and other metadata
|
|
833
833
|
_streams = /* @__PURE__ */ new Map();
|
|
834
|
-
/** Correlate checkpoint
|
|
834
|
+
/** Correlate checkpoint: how far the log has been READ. */
|
|
835
835
|
_correlated_at = -1;
|
|
836
836
|
/**
|
|
837
|
-
* Per-correlator checkpoint and lease
|
|
837
|
+
* Per-correlator checkpoint and lease, keyed by correlator.
|
|
838
838
|
*
|
|
839
839
|
* Keyed rather than singular because correlators that look for different
|
|
840
840
|
* things read the log for different reasons: sharing one position lets a
|
|
841
841
|
* partial-behaviour worker inherit another's, and sharing one lease lets
|
|
842
842
|
* one starve the other. Callers that supply no correlator use
|
|
843
|
-
* `_correlated_at` instead,
|
|
843
|
+
* `_correlated_at` instead, the shared checkpoint.
|
|
844
844
|
*/
|
|
845
845
|
_correlators = /* @__PURE__ */ new Map();
|
|
846
846
|
// last committed version per stream — O(1) replacement for filter-on-commit
|
|
@@ -2470,7 +2470,7 @@ var CloseSignal = class extends Error {
|
|
|
2470
2470
|
*/
|
|
2471
2471
|
at;
|
|
2472
2472
|
/**
|
|
2473
|
-
* Windowed close
|
|
2473
|
+
* Windowed close: prune events older than this cutoff behind
|
|
2474
2474
|
* the closest safe snapshot instead of retiring the stream. Thrown by
|
|
2475
2475
|
* the autoclose reaction of a `.autocloses({ keep })` state; flows
|
|
2476
2476
|
* into {@link CloseTarget.before}. Omitted → a full close.
|
|
@@ -2548,7 +2548,7 @@ var SettleOptionsSchema = DrainOptionsSchema.extend({
|
|
|
2548
2548
|
// rejects `±Infinity`, and an integer check cannot pass it either. Wrapper
|
|
2549
2549
|
// code normalizing an optional cap (`maxPasses: opts.maxPasses ?? Infinity`)
|
|
2550
2550
|
// is the natural way to hit it. Accepting it also honors this module's own
|
|
2551
|
-
// rule that a value which works today is never newly rejected
|
|
2551
|
+
// rule that a value which works today is never newly rejected.
|
|
2552
2552
|
maxPasses: import_zod6.z.union([import_zod6.z.number().int().min(0), import_zod6.z.literal(Number.POSITIVE_INFINITY)]).optional()
|
|
2553
2553
|
}).loose();
|
|
2554
2554
|
function resolveSettleConfig(options) {
|
|
@@ -3190,7 +3190,7 @@ var CorrelateCycle = class {
|
|
|
3190
3190
|
_checkpoint = -1;
|
|
3191
3191
|
_initialized = false;
|
|
3192
3192
|
/**
|
|
3193
|
-
* This worker's identity for the correlation lease
|
|
3193
|
+
* This worker's identity for the correlation lease. A per-instance
|
|
3194
3194
|
* UUID, matching the drain's convention, so a renewal is recognised as the
|
|
3195
3195
|
* same holder and a restarted process never inherits a stale claim.
|
|
3196
3196
|
*/
|
|
@@ -3206,24 +3206,15 @@ var CorrelateCycle = class {
|
|
|
3206
3206
|
* When this worker's correlation lease runs out, as a local clock reading,
|
|
3207
3207
|
* or 0 when it holds none.
|
|
3208
3208
|
*
|
|
3209
|
-
*
|
|
3210
|
-
*
|
|
3211
|
-
*
|
|
3212
|
-
* The act-sqlite perf gate caught that as a 1.5x regression on
|
|
3213
|
-
* correlate+drain, which is the shape an embedded app runs constantly.
|
|
3214
|
-
*
|
|
3215
|
-
* Believing this while the store disagrees is safe in the one direction it
|
|
3216
|
-
* can fail. A worker that scans without really holding the lease produces
|
|
3217
|
-
* the duplicate scan that existed before the lease, and the marks are
|
|
3218
|
-
* idempotent — so a stale belief costs work, never correctness.
|
|
3209
|
+
* Cached so the holder doesn't ask the store on every pass. A stale belief
|
|
3210
|
+
* can only cause a duplicate scan, and marks are idempotent, so it costs
|
|
3211
|
+
* work, never correctness.
|
|
3219
3212
|
*/
|
|
3220
3213
|
_lease_until = 0;
|
|
3221
3214
|
/**
|
|
3222
|
-
* Whether a scan might find anything
|
|
3223
|
-
*
|
|
3224
|
-
*
|
|
3225
|
-
* equivalent, so a settle pass always scanned, including the final pass
|
|
3226
|
-
* whose only job is to confirm nothing changed (#1510).
|
|
3215
|
+
* Whether a scan might find anything, like the drain's flag: a commit or
|
|
3216
|
+
* notification raises it, a short page lowers it, and a disarmed scan
|
|
3217
|
+
* returns without touching the store.
|
|
3227
3218
|
*
|
|
3228
3219
|
* Starts armed: the log may already hold events this process has never
|
|
3229
3220
|
* correlated, and only a scan can find out.
|
|
@@ -3232,15 +3223,9 @@ var CorrelateCycle = class {
|
|
|
3232
3223
|
/** In-flight init, memoized for single-flight and cleared on failure. */
|
|
3233
3224
|
_init_promise;
|
|
3234
3225
|
// Dynamically discovered targets → what each was last subscribed at,
|
|
3235
|
-
// bounded by `maxSubscribedStreams`.
|
|
3236
|
-
//
|
|
3237
|
-
//
|
|
3238
|
-
// Every scan re-subscribes the
|
|
3239
|
-
// targets it resolved (that is how the work mark lands), so this no longer
|
|
3240
|
-
// decides *whether* a target is sent — it decides *what* is sent with it:
|
|
3241
|
-
// a resolution raises priority/lane only when it beats the recorded floor,
|
|
3242
|
-
// and otherwise re-sends the row's own values so the mark changes nothing
|
|
3243
|
-
// else. See {@link Subscription}.
|
|
3226
|
+
// bounded by `maxSubscribedStreams`. It decides *what* a scan sends with a
|
|
3227
|
+
// target: priority/lane only when the resolution beats the recorded floor,
|
|
3228
|
+
// otherwise the row's own values. See {@link Subscription}.
|
|
3244
3229
|
_dynamic_subscriptions;
|
|
3245
3230
|
/**
|
|
3246
3231
|
* What each static target was subscribed at by `init`. A plain map, never
|
|
@@ -3248,14 +3233,8 @@ var CorrelateCycle = class {
|
|
|
3248
3233
|
* is already bounded by the registry and costs nothing the registry does
|
|
3249
3234
|
* not already hold.
|
|
3250
3235
|
*
|
|
3251
|
-
*
|
|
3252
|
-
*
|
|
3253
|
-
* churn of dynamic targets could evict a static record, and the next
|
|
3254
|
-
* dynamic resolution to that target found no record, took the
|
|
3255
|
-
* first-discovery branch, and re-subscribed the target with its own
|
|
3256
|
-
* priority and lane — silently re-laning a stream whose lane the
|
|
3257
|
-
* build-time subscribe owns, and starving it wherever `onlyLanes` had
|
|
3258
|
-
* provisioned a worker for the declared lane.
|
|
3236
|
+
* Kept out of the LRU so eviction can never drop a static target's
|
|
3237
|
+
* `+Infinity` floor and let a dynamic resolution re-lane it.
|
|
3259
3238
|
*/
|
|
3260
3239
|
_static_subscriptions = /* @__PURE__ */ new Map();
|
|
3261
3240
|
/** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
|
|
@@ -3265,17 +3244,13 @@ var CorrelateCycle = class {
|
|
|
3265
3244
|
_cd;
|
|
3266
3245
|
_on_init;
|
|
3267
3246
|
/**
|
|
3268
|
-
* Async cold-start hook
|
|
3269
|
-
*
|
|
3270
|
-
*
|
|
3271
|
-
* an idle deferred stream re-arms its drain across a restart. Kept
|
|
3272
|
-
* separate from `on_init` because seeding is an async store read; `init`
|
|
3273
|
-
* already awaits, so folding it in here preserves the "runs exactly once"
|
|
3274
|
-
* guarantee without a second gate on the Act side.
|
|
3247
|
+
* Async cold-start hook, run once after `on_init` inside `init()`. The
|
|
3248
|
+
* orchestrator re-seeds its defer timers from the persisted `deferred_at`
|
|
3249
|
+
* here, so an idle deferred stream re-arms across a restart.
|
|
3275
3250
|
*/
|
|
3276
3251
|
_on_init_async;
|
|
3277
3252
|
/**
|
|
3278
|
-
* Tail re-scan window applied to the cold-start checkpoint
|
|
3253
|
+
* Tail re-scan window applied to the cold-start checkpoint.
|
|
3279
3254
|
* See {@link DEFAULT_COLD_START_BACK_SCAN}. Constructor arg (not a
|
|
3280
3255
|
* public option) so tests can shrink it; defaults otherwise.
|
|
3281
3256
|
*/
|
|
@@ -3327,7 +3302,7 @@ var CorrelateCycle = class {
|
|
|
3327
3302
|
* - Reads the durable correlate checkpoint (and max(at)) from the store,
|
|
3328
3303
|
* flooring a first boot at `watermark - back_scan` so an event
|
|
3329
3304
|
* committed-but-not-correlated before a crash is re-scanned on
|
|
3330
|
-
* restart instead of skipped
|
|
3305
|
+
* restart instead of skipped
|
|
3331
3306
|
* - Subscribes static resolver targets (idempotent upsert)
|
|
3332
3307
|
* - Populates the subscribed-streams LRU
|
|
3333
3308
|
* - Fires `on_init` once (Act uses this to flag a cold-start drain)
|
|
@@ -3358,21 +3333,6 @@ var CorrelateCycle = class {
|
|
|
3358
3333
|
}
|
|
3359
3334
|
await this._on_init_async?.();
|
|
3360
3335
|
}
|
|
3361
|
-
/**
|
|
3362
|
-
* Forget targets whose subscription rows no longer exist, so a later
|
|
3363
|
-
* scan can re-subscribe them.
|
|
3364
|
-
*
|
|
3365
|
-
* A full close deletes the closed stream's subscription row. The
|
|
3366
|
-
* in-process dedup would otherwise still believe the target is
|
|
3367
|
-
* subscribed and never re-issue `subscribe()`, silently stopping
|
|
3368
|
-
* delivery for any reaction whose target is named after the stream —
|
|
3369
|
-
* the documented per-aggregate shape `.to(e => ({target: e.stream}))`
|
|
3370
|
-
* makes those two namespaces collide by construction (#1398).
|
|
3371
|
-
*
|
|
3372
|
-
* Static targets are left alone: their record lives in
|
|
3373
|
-
* `_static_subscriptions`, which this never touches, so they stay pinned
|
|
3374
|
-
* at +Infinity and the dynamic path never re-opens them.
|
|
3375
|
-
*/
|
|
3376
3336
|
/**
|
|
3377
3337
|
* Start correlation over after the event log was replaced wholesale
|
|
3378
3338
|
* (`restore`). The restored log is renumbered from the start and its
|
|
@@ -3381,7 +3341,7 @@ var CorrelateCycle = class {
|
|
|
3381
3341
|
* and the init latch makes the next pass run cold-start again: static
|
|
3382
3342
|
* targets are re-subscribed, the checkpoint is re-read from the store
|
|
3383
3343
|
* (which `restore` reset), and dynamic targets are rediscovered by the
|
|
3384
|
-
* scan
|
|
3344
|
+
* scan.
|
|
3385
3345
|
*/
|
|
3386
3346
|
restart() {
|
|
3387
3347
|
this._initialized = false;
|
|
@@ -3391,6 +3351,11 @@ var CorrelateCycle = class {
|
|
|
3391
3351
|
this._static_subscriptions.clear();
|
|
3392
3352
|
this._armed = true;
|
|
3393
3353
|
}
|
|
3354
|
+
/**
|
|
3355
|
+
* Forget dynamic targets whose subscription rows no longer exist (a full
|
|
3356
|
+
* close deletes the row), so a later scan re-subscribes them. Static
|
|
3357
|
+
* targets keep their records.
|
|
3358
|
+
*/
|
|
3394
3359
|
forget_subscribed(streams) {
|
|
3395
3360
|
for (const stream of streams) this._dynamic_subscriptions.delete(stream);
|
|
3396
3361
|
}
|
|
@@ -3415,7 +3380,7 @@ var CorrelateCycle = class {
|
|
|
3415
3380
|
* Scan the events past the checkpoint, resolve each to its target
|
|
3416
3381
|
* streams, and record what it found through `cd.subscribe` — new dynamic
|
|
3417
3382
|
* targets get registered, and every target an event resolved to gets its
|
|
3418
|
-
* **work mark** raised to that event's id
|
|
3383
|
+
* **work mark** raised to that event's id.
|
|
3419
3384
|
*
|
|
3420
3385
|
* Both resolver kinds are walked. A static target is already subscribed
|
|
3421
3386
|
* at init, but marking it is what makes it claimable without probing the
|
|
@@ -3502,18 +3467,10 @@ var CorrelateCycle = class {
|
|
|
3502
3467
|
}
|
|
3503
3468
|
}
|
|
3504
3469
|
},
|
|
3505
|
-
// Decline the sensitive payload:
|
|
3506
|
-
//
|
|
3507
|
-
//
|
|
3508
|
-
//
|
|
3509
|
-
// decrypt failure here strands the whole app with nothing blocked and
|
|
3510
|
-
// nothing naming the row (#1675).
|
|
3511
|
-
//
|
|
3512
|
-
// A dynamic `.to(event => …)` resolver therefore sees `pii: null`.
|
|
3513
|
-
// That is deliberate and is itself a fix: correlate runs actor-less,
|
|
3514
|
-
// so handing a resolver decrypted plaintext is un-gated disclosure —
|
|
3515
|
-
// the defect #1673 corrected on the drain return and #1277 before it.
|
|
3516
|
-
// `data` already carries no sensitive keys; nothing merges them here.
|
|
3470
|
+
// Decline the sensitive payload: the scan only needs name/stream/id,
|
|
3471
|
+
// and one undecryptable row would otherwise stop every stream's
|
|
3472
|
+
// reactions. Resolvers therefore see `pii: null`; correlate runs
|
|
3473
|
+
// actor-less, so plaintext here would be un-gated disclosure.
|
|
3517
3474
|
{ ...query, after, with_pii: false }
|
|
3518
3475
|
);
|
|
3519
3476
|
const streams = [];
|
|
@@ -3557,18 +3514,9 @@ var CorrelateCycle = class {
|
|
|
3557
3514
|
/**
|
|
3558
3515
|
* Hand the correlation lease back early.
|
|
3559
3516
|
*
|
|
3560
|
-
*
|
|
3561
|
-
*
|
|
3562
|
-
*
|
|
3563
|
-
* holder renews, and renewing to a millisecond is a release in all but
|
|
3564
|
-
* name.
|
|
3565
|
-
*
|
|
3566
|
-
* Without this a worker that stops cleanly still blocks discovery for the
|
|
3567
|
-
* rest of its lease — invisible in a long-lived deployment, very visible
|
|
3568
|
-
* anywhere Acts are created and dropped inside one process.
|
|
3569
|
-
*
|
|
3570
|
-
* Best-effort: failing here costs the lease's remaining lifetime, which is
|
|
3571
|
-
* what would have happened had the process died instead.
|
|
3517
|
+
* The port has no release verb; re-acquiring as the same holder with a
|
|
3518
|
+
* zero duration releases it. Best-effort: a failure costs the lease's
|
|
3519
|
+
* remaining lifetime, as a crash would.
|
|
3572
3520
|
*/
|
|
3573
3521
|
async release_correlation() {
|
|
3574
3522
|
try {
|
|
@@ -4054,26 +4002,11 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
4054
4002
|
{
|
|
4055
4003
|
stream,
|
|
4056
4004
|
stream_exact: true,
|
|
4057
|
-
//
|
|
4058
|
-
//
|
|
4059
|
-
//
|
|
4060
|
-
// a
|
|
4061
|
-
//
|
|
4062
|
-
// for the whole system; the stores apply the floor whenever asked and
|
|
4063
|
-
// never re-derive it (RFC 1274).
|
|
4064
|
-
//
|
|
4065
|
-
// The warm path resumes from `after`, and MUST also carry `with_snaps`
|
|
4066
|
-
// (#1345): `after` and `with_snaps` are independent store conditions —
|
|
4067
|
-
// `after` bounds the scan (id > cached.event_id), `with_snaps` keeps
|
|
4068
|
-
// `__snapshot__` rows in the result. A stale (lagging/cross-process)
|
|
4069
|
-
// cache checkpoint can sit below a newer `__snapshot__` boundary, and a
|
|
4070
|
-
// windowed close (`app.close`/`.autocloses({keep})`) may have pruned the
|
|
4071
|
-
// domain events between the checkpoint and that snapshot. Without
|
|
4072
|
-
// `with_snaps` the rebaselining snapshot is filtered out and the fold
|
|
4073
|
-
// silently applies the surviving tail on top of stale state (wrong
|
|
4074
|
-
// count, yet `version` still reports the true head — the concurrency
|
|
4075
|
-
// guard passes). With it, the snapshot in the after-window rebaselines
|
|
4076
|
-
// the fold; when no snapshot falls in the window it is a no-op.
|
|
4005
|
+
// `with_snaps` on every non-time-travel load. Cold, it resumes from the
|
|
4006
|
+
// latest snapshot (this is the only place that decides it; stores just
|
|
4007
|
+
// apply it). Warm, it keeps a snapshot above a stale checkpoint in the
|
|
4008
|
+
// result, so a windowed close that pruned the events in between can't
|
|
4009
|
+
// make the fold apply the tail onto stale state.
|
|
4077
4010
|
...cached ? { after: cached.event_id, with_snaps: true } : { ...time_travel ? {} : { with_snaps: true }, ...asOf }
|
|
4078
4011
|
}
|
|
4079
4012
|
);
|
|
@@ -4100,7 +4033,7 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
4100
4033
|
// Head event id, captured atomically with `state`: the last replayed
|
|
4101
4034
|
// event's id on a cache miss, or the cached checkpoint's event_id on a
|
|
4102
4035
|
// warm hit with no new events. Consumers read this instead of a
|
|
4103
|
-
// separate cache lookup that could race a concurrent commit
|
|
4036
|
+
// separate cache lookup that could race a concurrent commit.
|
|
4104
4037
|
id: event?.id ?? cached?.event_id ?? -1,
|
|
4105
4038
|
patches,
|
|
4106
4039
|
snaps,
|
|
@@ -4178,14 +4111,8 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
|
|
|
4178
4111
|
stream,
|
|
4179
4112
|
emitted,
|
|
4180
4113
|
meta,
|
|
4181
|
-
// Reactions skip the
|
|
4182
|
-
//
|
|
4183
|
-
// and forcing version checks here would turn ordinary catch-up into
|
|
4184
|
-
// spurious retries. An expectedVersion the caller passed explicitly is
|
|
4185
|
-
// still honored — dropping a guard the caller asked for is silent data
|
|
4186
|
-
// loss, and the reaction context can reach further than the handler
|
|
4187
|
-
// (detached timers, settle cycles) where the caller never intended a
|
|
4188
|
-
// reaction's semantics at all.
|
|
4114
|
+
// Reactions skip the inferred version guard (leases already
|
|
4115
|
+
// serialize them); an explicit expectedVersion is still honored.
|
|
4189
4116
|
reactingTo ? expectedVersion : expected
|
|
4190
4117
|
);
|
|
4191
4118
|
} catch (error) {
|
|
@@ -4561,32 +4488,25 @@ var DrainController = class {
|
|
|
4561
4488
|
_locked = false;
|
|
4562
4489
|
_ratio = 0.5;
|
|
4563
4490
|
/**
|
|
4564
|
-
*
|
|
4565
|
-
*
|
|
4566
|
-
*
|
|
4567
|
-
* process memory — per-worker pacing by design (see {@link BackoffOptions}
|
|
4568
|
-
* for the multi-worker trade-off). Its wake re-arms drain at the earliest
|
|
4569
|
-
* pending visit.
|
|
4491
|
+
* Local wake for streams this worker parked (backoff or defer). The
|
|
4492
|
+
* schedule itself is persisted in the store; this only re-arms the drain
|
|
4493
|
+
* at the earliest pending visit.
|
|
4570
4494
|
*/
|
|
4571
4495
|
_defer = new DeferTimer(() => {
|
|
4572
4496
|
this._armed = true;
|
|
4573
4497
|
});
|
|
4574
|
-
/** Worker timer
|
|
4498
|
+
/** Worker timer. Set when `start()` is active, undefined otherwise. */
|
|
4575
4499
|
_worker;
|
|
4576
4500
|
/**
|
|
4577
|
-
* Misroutings this controller has reported
|
|
4501
|
+
* Misroutings this controller has reported. A resolver returning a
|
|
4578
4502
|
* projection's target does so for every matching event; one line per event
|
|
4579
4503
|
* would bury the signal it exists to raise.
|
|
4580
4504
|
*/
|
|
4581
4505
|
_misrouted = /* @__PURE__ */ new Set();
|
|
4582
4506
|
_stopped = false;
|
|
4583
4507
|
/**
|
|
4584
|
-
* Resolves when the cycle
|
|
4585
|
-
*
|
|
4586
|
-
* the overlap guard; this answers "tell me when it is done" for a graceful
|
|
4587
|
-
* shutdown, which needs to await the handler rather than abandon it
|
|
4588
|
-
* mid-`await` with the stream still leased. Never rejects — `drain()`
|
|
4589
|
-
* contains its own errors — so awaiting it is always safe.
|
|
4508
|
+
* Resolves when the cycle in flight finishes, so a graceful shutdown can
|
|
4509
|
+
* wait for it. Never rejects.
|
|
4590
4510
|
*/
|
|
4591
4511
|
_inflight;
|
|
4592
4512
|
_inflight_done;
|
|
@@ -4603,16 +4523,9 @@ var DrainController = class {
|
|
|
4603
4523
|
this._armed = true;
|
|
4604
4524
|
}
|
|
4605
4525
|
/**
|
|
4606
|
-
* Re-seed a persisted
|
|
4607
|
-
*
|
|
4608
|
-
*
|
|
4609
|
-
* autoclose aggregate) is durable in the store's `deferred_at` but has
|
|
4610
|
-
* nothing in memory to re-arm the drain at the due-time. The orchestrator
|
|
4611
|
-
* reads the persisted `deferred_at` for this controller's lane and calls
|
|
4612
|
-
* this to park the stream + (re)schedule the shared wake — so the drain
|
|
4613
|
-
* re-arms at the due-time with no intervening commit. `schedule()`
|
|
4614
|
-
* collapses many seeds into one timer, so callers may seed in a loop and
|
|
4615
|
-
* let the earliest due-time win.
|
|
4526
|
+
* Re-seed a persisted `deferred_at` into the local wake at cold start, so
|
|
4527
|
+
* an idle deferred stream re-arms at its due-time with no new commit.
|
|
4528
|
+
* Seeds collapse into one timer; the earliest wins.
|
|
4616
4529
|
*/
|
|
4617
4530
|
seed_defer(stream, at) {
|
|
4618
4531
|
this._defer.set(stream, at);
|
|
@@ -4622,31 +4535,21 @@ var DrainController = class {
|
|
|
4622
4535
|
get armed() {
|
|
4623
4536
|
return this._armed;
|
|
4624
4537
|
}
|
|
4625
|
-
/**
|
|
4626
|
-
* The cycle currently in flight, or `undefined` when idle (#1442). A
|
|
4627
|
-
* graceful shutdown awaits this so an in-flight handler reaches its `ack`
|
|
4628
|
-
* — which releases the stream's lease — instead of being abandoned with
|
|
4629
|
-
* the lease held until it expires.
|
|
4630
|
-
*/
|
|
4538
|
+
/** The cycle in flight, or `undefined` when idle. */
|
|
4631
4539
|
get inflight() {
|
|
4632
4540
|
return this._inflight;
|
|
4633
4541
|
}
|
|
4634
|
-
/**
|
|
4635
|
-
* This lane's configured lease budget, or `undefined` when the lane didn't
|
|
4636
|
-
* pin one. It is the operator's own statement of how long a handler may
|
|
4637
|
-
* legitimately hold a stream, which makes it the right basis for a
|
|
4638
|
-
* shutdown grace budget (#1442).
|
|
4639
|
-
*/
|
|
4542
|
+
/** This lane's configured lease, or `undefined`; the shutdown grace basis. */
|
|
4640
4543
|
get lease_millis() {
|
|
4641
4544
|
return this._deps.defaults?.leaseMillis;
|
|
4642
4545
|
}
|
|
4643
|
-
/** Lane this controller drains (undefined
|
|
4546
|
+
/** Lane this controller drains (undefined spans all lanes). */
|
|
4644
4547
|
get lane() {
|
|
4645
4548
|
return this._deps.lane;
|
|
4646
4549
|
}
|
|
4647
4550
|
/**
|
|
4648
4551
|
* Start a per-lane worker that drains at the lane's `cycleMs`
|
|
4649
|
-
* cadence
|
|
4552
|
+
* cadence. When armed, the worker calls `drain()` on every
|
|
4650
4553
|
* tick and re-schedules; when not armed, it still re-schedules at
|
|
4651
4554
|
* `cycleMs` so a future `arm()` is picked up on the next tick.
|
|
4652
4555
|
*
|
|
@@ -4833,26 +4736,13 @@ var SettleLoop = class {
|
|
|
4833
4736
|
_timer = void 0;
|
|
4834
4737
|
_running = false;
|
|
4835
4738
|
/**
|
|
4836
|
-
* Resolves when the cycle
|
|
4837
|
-
*
|
|
4838
|
-
* re-arm bookkeeping; this answers "tell me when it is done" for a
|
|
4839
|
-
* graceful shutdown.
|
|
4840
|
-
*
|
|
4841
|
-
* `stop()` only cancels *scheduling* — a cycle already inside its
|
|
4842
|
-
* correlate → drain loop keeps going, and because `DrainController.drain`
|
|
4843
|
-
* does not consult `_stopped`, it can claim a stream after teardown
|
|
4844
|
-
* returned and after the store adapter was disposed. Never rejects: the
|
|
4845
|
-
* cycle's own `catch` contains its errors, so awaiting this is safe.
|
|
4739
|
+
* Resolves when the cycle in flight finishes, so shutdown can wait for
|
|
4740
|
+
* it (`stop()` cancels scheduling only). Never rejects.
|
|
4846
4741
|
*/
|
|
4847
4742
|
_inflight;
|
|
4848
4743
|
/**
|
|
4849
|
-
*
|
|
4850
|
-
*
|
|
4851
|
-
* the wake-up isn't dropped — a commit landing during the final
|
|
4852
|
-
* no-progress drain pass would otherwise leave armed controllers with
|
|
4853
|
-
* nothing to re-drain on an instance with no lane `cycleMs` and no
|
|
4854
|
-
* polling. Carries the options of the dropped call so the re-armed
|
|
4855
|
-
* pass honors its `debounceMs`/`maxPasses`/drain overrides.
|
|
4744
|
+
* A wake-up that fired while a cycle was running. The running cycle's
|
|
4745
|
+
* `finally` schedules one more pass with these options, so it isn't lost.
|
|
4856
4746
|
*/
|
|
4857
4747
|
_pending = void 0;
|
|
4858
4748
|
_deps;
|
|
@@ -4924,7 +4814,7 @@ var SettleLoop = class {
|
|
|
4924
4814
|
}, debounceMs);
|
|
4925
4815
|
}
|
|
4926
4816
|
/**
|
|
4927
|
-
* The cycle currently in flight, or `undefined` when idle
|
|
4817
|
+
* The cycle currently in flight, or `undefined` when idle. A
|
|
4928
4818
|
* graceful shutdown awaits this alongside the drain controllers so a
|
|
4929
4819
|
* settle parked in `correlate` does not resume after teardown.
|
|
4930
4820
|
*/
|
|
@@ -5070,17 +4960,17 @@ function validate_only_lanes(options, lanes) {
|
|
|
5070
4960
|
}
|
|
5071
4961
|
var Act = class {
|
|
5072
4962
|
_emitter = new import_node_events.default();
|
|
5073
|
-
/**
|
|
4963
|
+
/** Circuit breaker shared by all drain lanes. */
|
|
5074
4964
|
_breaker;
|
|
5075
|
-
/**
|
|
4965
|
+
/** Whether this instance subscribes to `Store.notify`. */
|
|
5076
4966
|
_listen;
|
|
5077
|
-
/**
|
|
4967
|
+
/** Whether this instance runs reactions (drain, settle, correlate). */
|
|
5078
4968
|
_drain;
|
|
5079
4969
|
/** Event names with at least one registered reaction (computed at build time) */
|
|
5080
4970
|
_reactive_events;
|
|
5081
4971
|
/** One DrainController per active lane, keyed by lane name. */
|
|
5082
4972
|
_drain_controllers;
|
|
5083
|
-
/** Correlation
|
|
4973
|
+
/** Correlation: lazy init and the scan that marks work. */
|
|
5084
4974
|
_correlate;
|
|
5085
4975
|
/** Debounced correlate→drain catch-up loop. */
|
|
5086
4976
|
_settle;
|
|
@@ -5110,26 +5000,10 @@ var Act = class {
|
|
|
5110
5000
|
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
5111
5001
|
* via {@link ActLifecycleEvents}.
|
|
5112
5002
|
*
|
|
5113
|
-
*
|
|
5114
|
-
*
|
|
5115
|
-
*
|
|
5116
|
-
*
|
|
5117
|
-
*
|
|
5118
|
-
* - Wrapping the *emit* rather than each *listener* still let the first
|
|
5119
|
-
* thrower abort the rest: `EventEmitter.emit` stops dispatching on the
|
|
5120
|
-
* first exception, so a second `app.on("acked", …)` never ran. The
|
|
5121
|
-
* documented "the remaining sinks still fire" was false whenever an event
|
|
5122
|
-
* had more than one listener. Same lesson as #1423, where guarding the
|
|
5123
|
-
* loop instead of each callback left every later SSE subscriber unserved.
|
|
5124
|
-
* - Only *some* call sites wrapped at all. The drain contained `acked` and
|
|
5125
|
-
* `blocked`; `committed`, `forgotten` and `close()`'s `closed` did not, so
|
|
5126
|
-
* a throwing listener rejected `do()`, `forget()` and `close()` **after**
|
|
5127
|
-
* their durable work had already landed. A caller retrying a "failed"
|
|
5128
|
-
* `do()` writes the event twice, since the framework has no dedup by
|
|
5129
|
-
* design.
|
|
5130
|
-
*
|
|
5131
|
-
* Containing here makes it a property of emitting, so a new lifecycle event
|
|
5132
|
-
* cannot reintroduce the gap by omission.
|
|
5003
|
+
* Each listener is contained on its own: a throwing listener is logged,
|
|
5004
|
+
* the remaining listeners still run, and the caller (`do()`, `forget()`,
|
|
5005
|
+
* `close()`, the drain) never sees the throw, since its durable work has
|
|
5006
|
+
* already landed.
|
|
5133
5007
|
*
|
|
5134
5008
|
* Uses `rawListeners` so `once` wrappers still de-register themselves, and
|
|
5135
5009
|
* returns "had listeners" to preserve the `EventEmitter.emit` contract.
|
|
@@ -5190,29 +5064,23 @@ var Act = class {
|
|
|
5190
5064
|
*/
|
|
5191
5065
|
_event_to_state;
|
|
5192
5066
|
/**
|
|
5193
|
-
* Event-name → lane fan-in for selective arming
|
|
5067
|
+
* Event-name → lane fan-in for selective arming. Built by
|
|
5194
5068
|
* `classify_registry` once per build. `"all"` means at least one of
|
|
5195
5069
|
* the event's reactions is a dynamic resolver (lane opaque until
|
|
5196
5070
|
* runtime); a `Set<string>` lists the static lanes only that event's
|
|
5197
5071
|
* reactions target.
|
|
5198
5072
|
*/
|
|
5199
5073
|
_event_to_lanes;
|
|
5200
|
-
/**
|
|
5201
|
-
* Audit dependency bag (#723). Built once at construction; held as
|
|
5202
|
-
* an immutable snapshot of the registry state the audit module
|
|
5203
|
-
* needs. Lives in `internal/audit.ts` — this orchestrator never
|
|
5204
|
-
* carries audit logic, only the deps + a one-liner that hands them
|
|
5205
|
-
* over.
|
|
5206
|
-
*/
|
|
5074
|
+
/** What `internal/audit.ts` needs, built once at construction. */
|
|
5207
5075
|
_audit_deps;
|
|
5208
5076
|
/** Logger resolved at construction time (after user port configuration) */
|
|
5209
5077
|
_logger = log();
|
|
5210
|
-
/** Wraps a public-method body so internal `store()`/`cache()` resolve to the
|
|
5211
|
-
* per-Act ports (ACT-501). No-op when the Act is unscoped — so the singleton
|
|
5212
|
-
* path keeps reading fresh `store()`/`cache()` per call, which matters for
|
|
5213
|
-
* tests that dispose and re-seed mid-suite. */
|
|
5214
5078
|
/** This Act's ports: its own bag, or the singleton adapters. */
|
|
5215
5079
|
_ports;
|
|
5080
|
+
/**
|
|
5081
|
+
* Runs a body inside this Act's ports frame, so internal
|
|
5082
|
+
* `store()`/`cache()` calls resolve to `_ports`.
|
|
5083
|
+
*/
|
|
5216
5084
|
_scoped;
|
|
5217
5085
|
/**
|
|
5218
5086
|
* Correlation-id generator for originating actions. Bound at
|
|
@@ -5231,24 +5099,18 @@ var Act = class {
|
|
|
5231
5099
|
/** Reaction dispatchers built once and handed to run_drain_cycle each cycle. */
|
|
5232
5100
|
_handle;
|
|
5233
5101
|
_handle_batch;
|
|
5234
|
-
/** Declared drain lanes
|
|
5102
|
+
/** Declared drain lanes. */
|
|
5235
5103
|
_lanes;
|
|
5236
5104
|
/**
|
|
5237
|
-
* Per-stream close serialization tails
|
|
5238
|
-
*
|
|
5239
|
-
*
|
|
5240
|
-
* the same stream never run their guard-free prune concurrently — the
|
|
5241
|
-
* manual path bypasses the `__autoclose__:X` drain lease that would
|
|
5242
|
-
* otherwise exclude them, so without this both closers archive the
|
|
5243
|
-
* same prefix. Process-local: both racers run on the same Act
|
|
5244
|
-
* instance. Entries are dropped once their tail resolves so the map
|
|
5245
|
-
* doesn't grow with distinct stream names.
|
|
5105
|
+
* Per-stream close serialization tails, so a manual close and an
|
|
5106
|
+
* autoclose of the same stream never prune concurrently (the manual path
|
|
5107
|
+
* has no drain lease to exclude it). Entries are dropped once settled.
|
|
5246
5108
|
*/
|
|
5247
5109
|
_close_locks = /* @__PURE__ */ new Map();
|
|
5248
5110
|
/**
|
|
5249
|
-
* Run `work` under the per-stream close lock
|
|
5250
|
-
* critical sections for the same stream (windowed prunes
|
|
5251
|
-
*
|
|
5111
|
+
* Run `work` under the per-stream close lock. Serializes close
|
|
5112
|
+
* critical sections for the same stream (windowed prunes and full
|
|
5113
|
+
* closes) while letting different streams proceed in parallel.
|
|
5252
5114
|
*/
|
|
5253
5115
|
_with_close_lock(stream, work) {
|
|
5254
5116
|
const prev = this._close_locks.get(stream) ?? Promise.resolve();
|
|
@@ -5275,11 +5137,11 @@ var Act = class {
|
|
|
5275
5137
|
* @param states Merged map of state name → state definition
|
|
5276
5138
|
* @param batch_handlers Static-target projection batch handlers (target → handler)
|
|
5277
5139
|
* @param options Tuning knobs — see {@link ActOptions}
|
|
5278
|
-
* @param lanes Declared drain lanes
|
|
5279
|
-
* these from `.withLane(...)` calls
|
|
5280
|
-
*
|
|
5140
|
+
* @param lanes Declared drain lanes. The builder collects
|
|
5141
|
+
* these from `.withLane(...)` calls; one `DrainController` runs
|
|
5142
|
+
* per lane, plus the implicit `"default"`.
|
|
5281
5143
|
* @param patch_fn The per-event patch step selected once by the builder
|
|
5282
|
-
* from `ActOptions.validateFoldedState`
|
|
5144
|
+
* from `ActOptions.validateFoldedState` — `bare_patch` by
|
|
5283
5145
|
* default, `validating_patch` when the flag is on. The builder uses
|
|
5284
5146
|
* the same value for its projection-fold handlers, so there is a
|
|
5285
5147
|
* single selection site. Defaults to `bare_patch` for direct
|
|
@@ -5294,16 +5156,15 @@ var Act = class {
|
|
|
5294
5156
|
this._ports = options.scoped ?? default_scope();
|
|
5295
5157
|
const ports = this._ports;
|
|
5296
5158
|
this._scoped = make_run_scoped({
|
|
5297
|
-
//
|
|
5298
|
-
//
|
|
5299
|
-
// whichever adapters happened to be installed at construction and
|
|
5300
|
-
// ignore a later `store(...)` / `cache(...)`.
|
|
5159
|
+
// Getters, not a spread: the singletons resolve lazily, so a later
|
|
5160
|
+
// `store(...)` / `cache(...)` still takes effect.
|
|
5301
5161
|
get store() {
|
|
5302
5162
|
return ports.store;
|
|
5303
5163
|
},
|
|
5304
5164
|
get cache() {
|
|
5305
5165
|
return ports.cache;
|
|
5306
5166
|
},
|
|
5167
|
+
// Parsed per build, so a bad window throws on every build.
|
|
5307
5168
|
autoclose_window: resolveAutocloseConfig(options).autocloseWindow
|
|
5308
5169
|
});
|
|
5309
5170
|
this._correlator = options.correlator ?? default_correlator;
|
|
@@ -5311,8 +5172,6 @@ var Act = class {
|
|
|
5311
5172
|
this._cd = build_drain(this._logger);
|
|
5312
5173
|
this._handle = build_handle({
|
|
5313
5174
|
logger: this._logger,
|
|
5314
|
-
// The orchestrator owns ambient context; `build_handle` only asks for
|
|
5315
|
-
// the triggering event to be in scope while the handler runs.
|
|
5316
5175
|
reaction_scope: make_reaction_scope({
|
|
5317
5176
|
do: this._bound_do,
|
|
5318
5177
|
load: this._bound_load,
|
|
@@ -5347,16 +5206,8 @@ var Act = class {
|
|
|
5347
5206
|
resolveCircuitBreakerConfig(options.circuitBreaker),
|
|
5348
5207
|
{
|
|
5349
5208
|
on_error: (error, circuit) => this._emit_error(error, circuit),
|
|
5350
|
-
//
|
|
5351
|
-
//
|
|
5352
|
-
// The wake fires `settle()`, which (in half-open) runs a real store
|
|
5353
|
-
// probe: for a dynamic-resolver app the probe is settle's correlate
|
|
5354
|
-
// (a store scan); for a static-reaction app correlate is a no-op that
|
|
5355
|
-
// records no health, so the probe is settle's DRAIN claim — either
|
|
5356
|
-
// way one success closes the breaker and every loop resumes, a
|
|
5357
|
-
// failure re-opens it and reschedules the wake. Settle does NOT close
|
|
5358
|
-
// the breaker off a no-op correlate (#1329) — only a real store op
|
|
5359
|
-
// (correlate scan or drain claim) records `passed()`.
|
|
5209
|
+
// When the cooldown elapses, settle once: its scan or claim is the
|
|
5210
|
+
// half-open probe that closes or re-opens the breaker.
|
|
5360
5211
|
on_retry: () => {
|
|
5361
5212
|
this.settle({ debounceMs: 0 });
|
|
5362
5213
|
}
|
|
@@ -5364,12 +5215,9 @@ var Act = class {
|
|
|
5364
5215
|
);
|
|
5365
5216
|
}
|
|
5366
5217
|
/**
|
|
5367
|
-
* One DrainController per active lane
|
|
5368
|
-
*
|
|
5369
|
-
*
|
|
5370
|
-
* no-lane-declared case with `lane: undefined` deps so claim() doesn't
|
|
5371
|
-
* filter (preserves the single-lane SQL planner shape for apps that never
|
|
5372
|
-
* call withLane).
|
|
5218
|
+
* One DrainController per active lane, plus the implicit "default" unless
|
|
5219
|
+
* `onlyLanes` excludes it. Apps that declare no lanes pass no lane filter
|
|
5220
|
+
* to `claim`.
|
|
5373
5221
|
*/
|
|
5374
5222
|
_build_drain_controllers(options, lanes) {
|
|
5375
5223
|
const all_lanes = ["default", ...lanes.map((l) => l.name)];
|
|
@@ -5388,11 +5236,7 @@ var Act = class {
|
|
|
5388
5236
|
handle_batch: this._handle_batch,
|
|
5389
5237
|
on_acked: (acked) => this.emit("acked", acked),
|
|
5390
5238
|
on_blocked: (blocked) => this.emit("blocked", blocked),
|
|
5391
|
-
// Reaction-requested close
|
|
5392
|
-
// `app.close` (tombstone guard + archive + atomic truncate) for the
|
|
5393
|
-
// targets a handler signalled via `CloseSignal`. No `correlate()`
|
|
5394
|
-
// here — the drain that produced these targets has already
|
|
5395
|
-
// correlated.
|
|
5239
|
+
// Reaction-requested close: the same machinery as `app.close`.
|
|
5396
5240
|
on_close: async (targets) => {
|
|
5397
5241
|
const close_actor = { id: "$close", name: "close" };
|
|
5398
5242
|
const result = await run_close_cycle(targets, {
|
|
@@ -5409,12 +5253,7 @@ var Act = class {
|
|
|
5409
5253
|
this.emit("closed", result);
|
|
5410
5254
|
},
|
|
5411
5255
|
breaker: this._breaker,
|
|
5412
|
-
// Re-scope the per-lane worker's auto-start ticks so their drain
|
|
5413
|
-
// resolves the scoped ports, not the singleton (#1191).
|
|
5414
5256
|
run_scoped: this._scoped,
|
|
5415
|
-
// Pass lane only when a true per-lane controller is active.
|
|
5416
|
-
// The all-lanes (single default) case keeps lane=undefined so
|
|
5417
|
-
// adapter SQL collapses to the single-lane shape.
|
|
5418
5257
|
lane: single_default_lane ? void 0 : name,
|
|
5419
5258
|
defaults: cfg && {
|
|
5420
5259
|
streamLimit: cfg.streamLimit,
|
|
@@ -5428,17 +5267,9 @@ var Act = class {
|
|
|
5428
5267
|
return controllers;
|
|
5429
5268
|
}
|
|
5430
5269
|
/**
|
|
5431
|
-
*
|
|
5432
|
-
*
|
|
5433
|
-
*
|
|
5434
|
-
* subscribes all static targets regardless of `onlyLanes`) but never
|
|
5435
|
-
* claimed here. If no peer worker deploys with those lanes in ITS
|
|
5436
|
-
* `onlyLanes`, their reactions accumulate forever, silently. A single
|
|
5437
|
-
* process can't verify the cluster invariant `∪ onlyLanes ⊇ declared
|
|
5438
|
-
* lanes`, so we surface the per-instance signal — "these declared lanes
|
|
5439
|
-
* have no controller here" — the same way the deprecated-event advisory
|
|
5440
|
-
* surfaces legacy events. No advisory when `onlyLanes` is unset (every
|
|
5441
|
-
* lane gets a controller) or covers every declared lane.
|
|
5270
|
+
* Startup advisory naming declared lanes that `onlyLanes` leaves without a
|
|
5271
|
+
* controller here. One process can't check that the cluster covers every
|
|
5272
|
+
* lane, so it reports its own gaps.
|
|
5442
5273
|
*/
|
|
5443
5274
|
_advise_orphaned_lanes(options, lanes) {
|
|
5444
5275
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
@@ -5452,25 +5283,15 @@ var Act = class {
|
|
|
5452
5283
|
`Act declared ${orphaned.length} orphaned lane(s) on this instance: ${list}. onlyLanes excludes them, so no DrainController claims their streams here \u2014 their reactions accumulate un-drained unless a peer worker deploys with these lanes in its onlyLanes. Ensure the cluster invariant holds: the union of every worker's onlyLanes must cover every declared lane. See docs/docs/guides/production-checklist.md \xA7 Sizing lanes.`
|
|
5453
5284
|
);
|
|
5454
5285
|
}
|
|
5455
|
-
/**
|
|
5456
|
-
* Audit deps bag. Snapshotted after registry classification and
|
|
5457
|
-
* drain-controller build so the audit module sees the finalized lane
|
|
5458
|
-
* set. Held as an immutable bag — the orchestrator never carries audit
|
|
5459
|
-
* logic itself, only this typed contract.
|
|
5460
|
-
*/
|
|
5286
|
+
/** Audit deps, built after the controllers so the lane set is final. */
|
|
5461
5287
|
_build_audit_deps() {
|
|
5462
5288
|
return {
|
|
5463
5289
|
store: store2,
|
|
5464
5290
|
logger: this._logger,
|
|
5465
5291
|
event_to_state: this._event_to_state,
|
|
5466
5292
|
states: this._states,
|
|
5467
|
-
//
|
|
5468
|
-
//
|
|
5469
|
-
// An `onlyLanes`-filtered instance builds a controller only for its
|
|
5470
|
-
// slice of lanes, so keying off the active controller set would flag
|
|
5471
|
-
// a stream correctly assigned to an excluded-but-declared lane (one
|
|
5472
|
-
// another worker drains) as `unknown-lane`. The audit reports what's
|
|
5473
|
-
// structurally routable across the cluster, not what this process runs.
|
|
5293
|
+
// Declared lanes, not this process's controllers: a lane another
|
|
5294
|
+
// worker drains is not `unknown-lane`.
|
|
5474
5295
|
declared_lanes: /* @__PURE__ */ new Set(["default", ...this._lanes.map((l) => l.name)]),
|
|
5475
5296
|
routed_events: new Set(this._event_to_lanes.keys())
|
|
5476
5297
|
};
|
|
@@ -5486,10 +5307,8 @@ var Act = class {
|
|
|
5486
5307
|
static_targets: classification.static_targets,
|
|
5487
5308
|
cd: this._cd,
|
|
5488
5309
|
max_subscribed_streams: options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
5489
|
-
//
|
|
5490
|
-
//
|
|
5491
|
-
// not active: `onlyLanes` shrinks this process's controllers, but a
|
|
5492
|
-
// lane another process claims is still a valid destination.
|
|
5310
|
+
// Declared lanes (not active ones): a lane another process claims is
|
|
5311
|
+
// still a valid destination.
|
|
5493
5312
|
declared_lanes: /* @__PURE__ */ new Set([
|
|
5494
5313
|
"default",
|
|
5495
5314
|
...this._lanes.map((l) => l.name)
|
|
@@ -5497,26 +5316,14 @@ var Act = class {
|
|
|
5497
5316
|
on_init: () => {
|
|
5498
5317
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
5499
5318
|
},
|
|
5500
|
-
// Cold-start defer re-seed (#1221). Skipped on writer-only instances
|
|
5501
|
-
// (`drain: false`) — they run no local controllers to re-arm.
|
|
5502
5319
|
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
5503
5320
|
});
|
|
5504
5321
|
}
|
|
5505
5322
|
/**
|
|
5506
|
-
*
|
|
5507
|
-
*
|
|
5508
|
-
*
|
|
5509
|
-
*
|
|
5510
|
-
* The defer timer is worker memory: empty after a restart. A stream
|
|
5511
|
-
* deferred to a future due-time (the classic case: an idle autoclose
|
|
5512
|
-
* aggregate that deferred its terminal close) is durable in the store but
|
|
5513
|
-
* has nothing in memory to re-arm the drain — the controller disarms on
|
|
5514
|
-
* the first empty claim and, since the aggregate is idle, no commit ever
|
|
5515
|
-
* re-arms it. Reading the persisted schedule and seeding the owning lane's
|
|
5516
|
-
* timer restores the wake, so the close fires at the due-time.
|
|
5517
|
-
*
|
|
5518
|
-
* Streams whose lane has no controller on this instance (excluded by
|
|
5519
|
-
* `onlyLanes`) are skipped — a peer worker owns that lane's timer.
|
|
5323
|
+
* At cold start, re-seed each lane controller's local wake from the
|
|
5324
|
+
* persisted future `deferred_at`, so an idle deferred stream (an
|
|
5325
|
+
* autoclose waiting out its window) still fires on time. Streams on lanes
|
|
5326
|
+
* this instance doesn't run are skipped.
|
|
5520
5327
|
*/
|
|
5521
5328
|
async _seed_persisted_defers() {
|
|
5522
5329
|
const now = Date.now();
|
|
@@ -5530,10 +5337,6 @@ var Act = class {
|
|
|
5530
5337
|
_build_settle(options) {
|
|
5531
5338
|
return new SettleLoop(
|
|
5532
5339
|
{
|
|
5533
|
-
// Scope the init like every other store-touching path — a bare
|
|
5534
|
-
// `this._correlate.init()` runs `store().subscribe(...)` against
|
|
5535
|
-
// the singleton for a scoped Act, so static targets never land on
|
|
5536
|
-
// the scoped store and `_initialized` then blocks a retry (#1191).
|
|
5537
5340
|
init: () => this._scoped(() => this._correlate.init()),
|
|
5538
5341
|
checkpoint: () => this._correlate.checkpoint,
|
|
5539
5342
|
correlate: (q) => this._correlate_scanned(q, true),
|
|
@@ -5551,19 +5354,11 @@ var Act = class {
|
|
|
5551
5354
|
* already in flight a bounded chance to finish, then remove lifecycle
|
|
5552
5355
|
* listeners and tear down the cross-process notify subscription.
|
|
5553
5356
|
*
|
|
5554
|
-
*
|
|
5555
|
-
*
|
|
5556
|
-
*
|
|
5557
|
-
*
|
|
5558
|
-
*
|
|
5559
|
-
* (which #1418 then redelivers). Listeners come off *after* that wait, not
|
|
5560
|
-
* before, so an `acked` / `blocked` subscriber still observes the work
|
|
5561
|
-
* that completed during the grace window.
|
|
5562
|
-
*
|
|
5563
|
-
* The budget is a ceiling, not a delay — teardown continues the moment the
|
|
5564
|
-
* last in-flight cycle finishes. When it is exhausted, teardown proceeds
|
|
5565
|
-
* anyway: one stuck handler must not hang a deploy, which is the failure
|
|
5566
|
-
* mode an unbounded wait would trade for.
|
|
5357
|
+
* Scheduling stops first, then in-flight cycles get up to `graceMs` to
|
|
5358
|
+
* reach their `ack` (an abandoned handler holds its lease until it
|
|
5359
|
+
* expires). Listeners come off after the wait, so they still see work that
|
|
5360
|
+
* finished during it. The budget is a ceiling, not a delay; when it runs
|
|
5361
|
+
* out, teardown proceeds anyway.
|
|
5567
5362
|
*
|
|
5568
5363
|
* Idempotent — repeated calls return the same promise, and the first
|
|
5569
5364
|
* call's `graceMs` is the one that applies. Registered automatically with
|
|
@@ -5581,7 +5376,7 @@ var Act = class {
|
|
|
5581
5376
|
this.stop_correlations();
|
|
5582
5377
|
const disposer = await this._notify_disposer;
|
|
5583
5378
|
if (disposer) await disposer();
|
|
5584
|
-
this.
|
|
5379
|
+
this._settle.stop();
|
|
5585
5380
|
this._breaker.stop();
|
|
5586
5381
|
for (const c of this._drain_controllers.values()) c.stop();
|
|
5587
5382
|
await this._await_inflight(options?.graceMs);
|
|
@@ -5596,26 +5391,10 @@ var Act = class {
|
|
|
5596
5391
|
* whichever comes first. Cycle promises never reject (`drain()` contains
|
|
5597
5392
|
* its own errors), so this never throws.
|
|
5598
5393
|
*
|
|
5599
|
-
* An omitted budget is
|
|
5600
|
-
* in flight
|
|
5601
|
-
*
|
|
5602
|
-
*
|
|
5603
|
-
* lane that pinned no lease contributes `drain()`'s own fallback, and the
|
|
5604
|
-
* whole thing is capped so a long-leased lane cannot hold a deploy open.
|
|
5605
|
-
* Idle lanes do not count — nothing is running on them to wait for.
|
|
5606
|
-
*
|
|
5607
|
-
* An in-flight settle counts the same way, on the same fallback (#1617).
|
|
5608
|
-
* It is waited on like any cycle, and deriving from the lanes alone gave
|
|
5609
|
-
* it a budget of `0` whenever it was the only thing running — which
|
|
5610
|
-
* returned before the wait it was about to be added to.
|
|
5611
|
-
*
|
|
5612
|
-
* A lane configured `leaseMillis: 0` takes the fallback rather than its
|
|
5613
|
-
* own value, which is why this coalesces on falsy and not just on absent
|
|
5614
|
-
* (#1647). A zero-length lease expires the instant it is granted, so it
|
|
5615
|
-
* has no answer to offer for "how long may a handler hold this stream" —
|
|
5616
|
-
* it is the pinned-no-lease case spelled with a number, and #1617 already
|
|
5617
|
-
* settled what that case is worth. Taking it literally derived a budget
|
|
5618
|
-
* of `0` and abandoned the very cycle this had just found running.
|
|
5394
|
+
* An omitted budget is the largest `leaseMillis` among lanes with a cycle
|
|
5395
|
+
* in flight (the drain's fallback for lanes that pinned none, or pinned
|
|
5396
|
+
* 0, which is the same as none), with an in-flight settle counting on the
|
|
5397
|
+
* fallback, capped at the maximum. Idle lanes don't count.
|
|
5619
5398
|
*/
|
|
5620
5399
|
_derive_grace_ms(running, settling) {
|
|
5621
5400
|
let max = settling ? DEFAULT_SHUTDOWN_GRACE_MS : 0;
|
|
@@ -5888,7 +5667,7 @@ var Act = class {
|
|
|
5888
5667
|
}
|
|
5889
5668
|
/**
|
|
5890
5669
|
* Wipe the sensitive-data payload for every event on the stream — see
|
|
5891
|
-
* {@link IAct.forget}.
|
|
5670
|
+
* {@link IAct.forget}.
|
|
5892
5671
|
*
|
|
5893
5672
|
* Throws on adapters without `Store.forget_pii`, invalidates the cache
|
|
5894
5673
|
* entry for the stream, emits the `forgotten` lifecycle event with the
|
|
@@ -5929,7 +5708,7 @@ var Act = class {
|
|
|
5929
5708
|
*
|
|
5930
5709
|
* Call `correlate()` before `drain()`. It is not only how dynamic targets
|
|
5931
5710
|
* are discovered: a stream is claimable while `at < correlated_at`, and
|
|
5932
|
-
* `correlate` is the only component that raises that mark
|
|
5711
|
+
* `correlate` is the only component that raises that mark, so a
|
|
5933
5712
|
* commit no correlate has seen is not drainable — including for static
|
|
5934
5713
|
* targets, which were served by a probe of the event log before. For a
|
|
5935
5714
|
* higher-level API that handles debouncing, correlation, and signaling
|
|
@@ -5962,14 +5741,14 @@ var Act = class {
|
|
|
5962
5741
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
5963
5742
|
return this._scoped(() => this._drain_all(options));
|
|
5964
5743
|
}
|
|
5965
|
-
/** Arm every active lane controller
|
|
5744
|
+
/** Arm every active lane controller. */
|
|
5966
5745
|
_arm_all() {
|
|
5967
5746
|
this._correlate.arm();
|
|
5968
5747
|
for (const c of this._drain_controllers.values()) c.arm();
|
|
5969
5748
|
}
|
|
5970
5749
|
/**
|
|
5971
5750
|
* Arm only the lane controllers whose reactions match the supplied
|
|
5972
|
-
* event names (
|
|
5751
|
+
* event names (selective arming). Events with any dynamic
|
|
5973
5752
|
* resolver fall back to `_arm_all()` via the `"all"` sentinel — the
|
|
5974
5753
|
* resolver's lane isn't known until correlate runs the function.
|
|
5975
5754
|
* Events with no reactions are skipped; `_event_to_lanes` doesn't
|
|
@@ -6073,7 +5852,7 @@ var Act = class {
|
|
|
6073
5852
|
return { subscribed, last_id };
|
|
6074
5853
|
}
|
|
6075
5854
|
/**
|
|
6076
|
-
* `correlate` plus whether the pass actually read the store
|
|
5855
|
+
* `correlate` plus whether the pass actually read the store.
|
|
6077
5856
|
*
|
|
6078
5857
|
* The settle loop needs that extra bit to decide whether the pass carries a
|
|
6079
5858
|
* circuit-breaker health signal, and a disarmed pass carries none. It stays
|
|
@@ -6334,7 +6113,7 @@ var Act = class {
|
|
|
6334
6113
|
});
|
|
6335
6114
|
}
|
|
6336
6115
|
/**
|
|
6337
|
-
* Operator-driven store audit
|
|
6116
|
+
* Operator-driven store audit.
|
|
6338
6117
|
*
|
|
6339
6118
|
* Walks the connected store and yields per-category findings —
|
|
6340
6119
|
* each tagged with the remediation it suggests. Same operator-
|
|
@@ -6386,7 +6165,7 @@ var Act = class {
|
|
|
6386
6165
|
* Bulk-update scheduling priority for streams matching `filter`.
|
|
6387
6166
|
*
|
|
6388
6167
|
* Operator-grade override of the `claim()` lagging-frontier
|
|
6389
|
-
* ordering
|
|
6168
|
+
* ordering. Useful when a long-running replay needs to
|
|
6390
6169
|
* jump ahead of other lagging streams, or when a no-longer-urgent
|
|
6391
6170
|
* job should yield slots back to the rest. Build-time priorities
|
|
6392
6171
|
* (set via the resolver's `priority` field) are subject to a
|
|
@@ -6483,12 +6262,12 @@ var Act = class {
|
|
|
6483
6262
|
*/
|
|
6484
6263
|
/**
|
|
6485
6264
|
* After a close, forget any target whose subscription row the truncate
|
|
6486
|
-
* removed, so a later correlate can re-subscribe it
|
|
6265
|
+
* removed, so a later correlate can re-subscribe it. Restart
|
|
6487
6266
|
* targets keep their row, so only fully-retired streams are forgotten.
|
|
6488
6267
|
*/
|
|
6489
6268
|
/**
|
|
6490
6269
|
* Advance correlation until the read cursor reaches `until`, and report
|
|
6491
|
-
* where it landed
|
|
6270
|
+
* where it landed. Used by the close cycle's safety probe, which
|
|
6492
6271
|
* cannot judge a subscription's pending work over events correlate has
|
|
6493
6272
|
* not resolved yet.
|
|
6494
6273
|
*
|
|
@@ -6514,13 +6293,12 @@ var Act = class {
|
|
|
6514
6293
|
* A tombstone seed means the stream was retired; a snapshot seed means it
|
|
6515
6294
|
* was restarted and is still consuming.
|
|
6516
6295
|
*
|
|
6517
|
-
* `truncate` no longer removes the subscription row
|
|
6296
|
+
* `truncate` no longer removes the subscription row, so this is no
|
|
6518
6297
|
* longer repairing damage the close itself did. It still matters, because
|
|
6519
6298
|
* the row can disappear later: reclaiming retired subscriptions is an
|
|
6520
6299
|
* operator job now, and that `DELETE` can land while this process is
|
|
6521
6300
|
* running. Forgetting here keeps the in-process view from outliving a row
|
|
6522
|
-
* an operator removed, which is
|
|
6523
|
-
* described — a reaction whose target is named after the stream would never
|
|
6301
|
+
* an operator removed, which is a silent-no-delivery failure — a reaction whose target is named after the stream would never
|
|
6524
6302
|
* be re-registered.
|
|
6525
6303
|
*/
|
|
6526
6304
|
_forget_closed_subscriptions(result) {
|
|
@@ -6795,7 +6573,7 @@ function reaction_on(event, events, builder) {
|
|
|
6795
6573
|
const reaction = {
|
|
6796
6574
|
handler: schedule ? make_deferred(handler, schedule) : handler,
|
|
6797
6575
|
resolver: _this_,
|
|
6798
|
-
//
|
|
6576
|
+
// Validate the whole bag at the declaration site so a bad
|
|
6799
6577
|
// `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
|
|
6800
6578
|
// not a NaN gate on the first retry.
|
|
6801
6579
|
options: resolveReactionConfig({
|
|
@@ -6997,7 +6775,7 @@ function act() {
|
|
|
6997
6775
|
spec.flush,
|
|
6998
6776
|
spec.config,
|
|
6999
6777
|
patch_fn,
|
|
7000
|
-
// Head loads strip sensitive keys like the warm path
|
|
6778
|
+
// Head loads strip sensitive keys like the warm path.
|
|
7001
6779
|
registry.sensitive_fields
|
|
7002
6780
|
)
|
|
7003
6781
|
)
|
|
@@ -7503,7 +7281,7 @@ var CsvFile = class {
|
|
|
7503
7281
|
name: fields[1],
|
|
7504
7282
|
// Revive dates like every Store adapter does — a Date in an event
|
|
7505
7283
|
// payload must not depend on whether it came back through a store or
|
|
7506
|
-
// through a CSV restore
|
|
7284
|
+
// through a CSV restore. The `created` column below has
|
|
7507
7285
|
// always been parsed explicitly; the JSON columns were missed.
|
|
7508
7286
|
data: JSON.parse(fields[2]),
|
|
7509
7287
|
stream: fields[3],
|