@rotorsoft/act 1.32.14 → 1.32.16
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 +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 -31
- 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 +44 -88
- 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/projection-fold.d.ts +2 -2
- package/dist/@types/internal/report-once.d.ts +3 -3
- package/dist/@types/internal/sensitive.d.ts +2 -2
- package/dist/@types/internal/settle.d.ts +6 -19
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +3 -3
- package/dist/@types/scoped.d.ts +6 -6
- package/dist/@types/scoped.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +23 -25
- package/dist/@types/types/action.d.ts.map +1 -1
- 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 +15 -20
- package/dist/@types/types/reaction.d.ts.map +1 -1
- 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-FVJFR7GH.js → chunk-JRTZGWVS.js} +1 -1
- package/dist/chunk-JRTZGWVS.js.map +1 -0
- package/dist/{chunk-JPPO3LZ3.js → chunk-PZP5ZEZ4.js} +9 -9
- package/dist/chunk-PZP5ZEZ4.js.map +1 -0
- package/dist/index.cjs +152 -372
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +147 -367
- 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-FVJFR7GH.js.map +0 -1
- package/dist/chunk-JPPO3LZ3.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)
|
|
@@ -3344,7 +3319,7 @@ var CorrelateCycle = class {
|
|
|
3344
3319
|
this._initialized = true;
|
|
3345
3320
|
}
|
|
3346
3321
|
async _run_init() {
|
|
3347
|
-
const { watermark, correlated_at } = await
|
|
3322
|
+
const { watermark, correlated_at } = await this._cd.subscribe([
|
|
3348
3323
|
...this._static_targets
|
|
3349
3324
|
]);
|
|
3350
3325
|
this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - this._cold_start_back_scan);
|
|
@@ -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) {
|
|
@@ -4464,7 +4391,16 @@ function warn_misrouted(seen, stream, handler, event) {
|
|
|
4464
4391
|
`Reaction "${handler}" on "${event}" resolved to target "${stream}", which a projection already serves. A target is served by one batch handler or one state projection, so this reaction can never run \u2014 and delivering to it would hand the projection an aggregate from another stream. Skipping it. The equivalent static \`.to({ target })\` is rejected at build; a dynamic resolver's target is only knowable here.`
|
|
4465
4392
|
);
|
|
4466
4393
|
}
|
|
4467
|
-
async function run_drain_cycle(
|
|
4394
|
+
async function run_drain_cycle(deps, cycle) {
|
|
4395
|
+
const { ops, registry, batch_handlers, handle, handle_batch, lane } = deps;
|
|
4396
|
+
const {
|
|
4397
|
+
lagging,
|
|
4398
|
+
leading,
|
|
4399
|
+
eventLimit,
|
|
4400
|
+
leaseMillis,
|
|
4401
|
+
misrouted,
|
|
4402
|
+
store_failing
|
|
4403
|
+
} = cycle;
|
|
4468
4404
|
const leased = await ops.claim(
|
|
4469
4405
|
lagging,
|
|
4470
4406
|
leading,
|
|
@@ -4536,7 +4472,7 @@ async function run_drain_cycle(ops, registry, batch_handlers, misrouted, store_f
|
|
|
4536
4472
|
const blocked = await ops.block(
|
|
4537
4473
|
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
4538
4474
|
);
|
|
4539
|
-
if (blocked.length) on_blocked(blocked);
|
|
4475
|
+
if (blocked.length) deps.on_blocked(blocked);
|
|
4540
4476
|
const submitted = handled.flatMap((h, i) => {
|
|
4541
4477
|
const advance = h.handled > 0 ? h.acked_at : leased[i].at;
|
|
4542
4478
|
return h.defer !== void 0 ? { ...h.lease, at: advance, due: h.defer, retry: -1 } : h.next_attempt_at !== void 0 ? { ...h.lease, at: advance, due: h.next_attempt_at } : h.handled > 0 || !h.error ? { ...h.lease, at: h.acked_at } : [];
|
|
@@ -4561,32 +4497,25 @@ var DrainController = class {
|
|
|
4561
4497
|
_locked = false;
|
|
4562
4498
|
_ratio = 0.5;
|
|
4563
4499
|
/**
|
|
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.
|
|
4500
|
+
* Local wake for streams this worker parked (backoff or defer). The
|
|
4501
|
+
* schedule itself is persisted in the store; this only re-arms the drain
|
|
4502
|
+
* at the earliest pending visit.
|
|
4570
4503
|
*/
|
|
4571
4504
|
_defer = new DeferTimer(() => {
|
|
4572
4505
|
this._armed = true;
|
|
4573
4506
|
});
|
|
4574
|
-
/** Worker timer
|
|
4507
|
+
/** Worker timer. Set when `start()` is active, undefined otherwise. */
|
|
4575
4508
|
_worker;
|
|
4576
4509
|
/**
|
|
4577
|
-
* Misroutings this controller has reported
|
|
4510
|
+
* Misroutings this controller has reported. A resolver returning a
|
|
4578
4511
|
* projection's target does so for every matching event; one line per event
|
|
4579
4512
|
* would bury the signal it exists to raise.
|
|
4580
4513
|
*/
|
|
4581
4514
|
_misrouted = /* @__PURE__ */ new Set();
|
|
4582
4515
|
_stopped = false;
|
|
4583
4516
|
/**
|
|
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.
|
|
4517
|
+
* Resolves when the cycle in flight finishes, so a graceful shutdown can
|
|
4518
|
+
* wait for it. Never rejects.
|
|
4590
4519
|
*/
|
|
4591
4520
|
_inflight;
|
|
4592
4521
|
_inflight_done;
|
|
@@ -4603,16 +4532,9 @@ var DrainController = class {
|
|
|
4603
4532
|
this._armed = true;
|
|
4604
4533
|
}
|
|
4605
4534
|
/**
|
|
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.
|
|
4535
|
+
* Re-seed a persisted `deferred_at` into the local wake at cold start, so
|
|
4536
|
+
* an idle deferred stream re-arms at its due-time with no new commit.
|
|
4537
|
+
* Seeds collapse into one timer; the earliest wins.
|
|
4616
4538
|
*/
|
|
4617
4539
|
seed_defer(stream, at) {
|
|
4618
4540
|
this._defer.set(stream, at);
|
|
@@ -4622,31 +4544,21 @@ var DrainController = class {
|
|
|
4622
4544
|
get armed() {
|
|
4623
4545
|
return this._armed;
|
|
4624
4546
|
}
|
|
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
|
-
*/
|
|
4547
|
+
/** The cycle in flight, or `undefined` when idle. */
|
|
4631
4548
|
get inflight() {
|
|
4632
4549
|
return this._inflight;
|
|
4633
4550
|
}
|
|
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
|
-
*/
|
|
4551
|
+
/** This lane's configured lease, or `undefined`; the shutdown grace basis. */
|
|
4640
4552
|
get lease_millis() {
|
|
4641
4553
|
return this._deps.defaults?.leaseMillis;
|
|
4642
4554
|
}
|
|
4643
|
-
/** Lane this controller drains (undefined
|
|
4555
|
+
/** Lane this controller drains (undefined spans all lanes). */
|
|
4644
4556
|
get lane() {
|
|
4645
4557
|
return this._deps.lane;
|
|
4646
4558
|
}
|
|
4647
4559
|
/**
|
|
4648
4560
|
* Start a per-lane worker that drains at the lane's `cycleMs`
|
|
4649
|
-
* cadence
|
|
4561
|
+
* cadence. When armed, the worker calls `drain()` on every
|
|
4650
4562
|
* tick and re-schedules; when not armed, it still re-schedules at
|
|
4651
4563
|
* `cycleMs` so a future `arm()` is picked up on the next tick.
|
|
4652
4564
|
*
|
|
@@ -4691,21 +4603,14 @@ var DrainController = class {
|
|
|
4691
4603
|
});
|
|
4692
4604
|
const lagging = Math.ceil(streamLimit * this._ratio);
|
|
4693
4605
|
const leading = streamLimit - lagging;
|
|
4694
|
-
const cycle = await run_drain_cycle(
|
|
4695
|
-
this._deps.ops,
|
|
4696
|
-
this._deps.registry,
|
|
4697
|
-
this._deps.batch_handlers,
|
|
4698
|
-
this._misrouted,
|
|
4699
|
-
this._deps.breaker.failing,
|
|
4700
|
-
this._deps.handle,
|
|
4701
|
-
this._deps.handle_batch,
|
|
4606
|
+
const cycle = await run_drain_cycle(this._deps, {
|
|
4702
4607
|
lagging,
|
|
4703
4608
|
leading,
|
|
4704
4609
|
eventLimit,
|
|
4705
4610
|
leaseMillis,
|
|
4706
|
-
|
|
4707
|
-
this._deps.
|
|
4708
|
-
);
|
|
4611
|
+
misrouted: this._misrouted,
|
|
4612
|
+
store_failing: this._deps.breaker.failing
|
|
4613
|
+
});
|
|
4709
4614
|
if (!cycle) {
|
|
4710
4615
|
this._deps.breaker.passed();
|
|
4711
4616
|
this._armed = false;
|
|
@@ -4833,26 +4738,13 @@ var SettleLoop = class {
|
|
|
4833
4738
|
_timer = void 0;
|
|
4834
4739
|
_running = false;
|
|
4835
4740
|
/**
|
|
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.
|
|
4741
|
+
* Resolves when the cycle in flight finishes, so shutdown can wait for
|
|
4742
|
+
* it (`stop()` cancels scheduling only). Never rejects.
|
|
4846
4743
|
*/
|
|
4847
4744
|
_inflight;
|
|
4848
4745
|
/**
|
|
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.
|
|
4746
|
+
* A wake-up that fired while a cycle was running. The running cycle's
|
|
4747
|
+
* `finally` schedules one more pass with these options, so it isn't lost.
|
|
4856
4748
|
*/
|
|
4857
4749
|
_pending = void 0;
|
|
4858
4750
|
_deps;
|
|
@@ -4924,7 +4816,7 @@ var SettleLoop = class {
|
|
|
4924
4816
|
}, debounceMs);
|
|
4925
4817
|
}
|
|
4926
4818
|
/**
|
|
4927
|
-
* The cycle currently in flight, or `undefined` when idle
|
|
4819
|
+
* The cycle currently in flight, or `undefined` when idle. A
|
|
4928
4820
|
* graceful shutdown awaits this alongside the drain controllers so a
|
|
4929
4821
|
* settle parked in `correlate` does not resume after teardown.
|
|
4930
4822
|
*/
|
|
@@ -5070,17 +4962,17 @@ function validate_only_lanes(options, lanes) {
|
|
|
5070
4962
|
}
|
|
5071
4963
|
var Act = class {
|
|
5072
4964
|
_emitter = new import_node_events.default();
|
|
5073
|
-
/**
|
|
4965
|
+
/** Circuit breaker shared by all drain lanes. */
|
|
5074
4966
|
_breaker;
|
|
5075
|
-
/**
|
|
4967
|
+
/** Whether this instance subscribes to `Store.notify`. */
|
|
5076
4968
|
_listen;
|
|
5077
|
-
/**
|
|
4969
|
+
/** Whether this instance runs reactions (drain, settle, correlate). */
|
|
5078
4970
|
_drain;
|
|
5079
4971
|
/** Event names with at least one registered reaction (computed at build time) */
|
|
5080
4972
|
_reactive_events;
|
|
5081
4973
|
/** One DrainController per active lane, keyed by lane name. */
|
|
5082
4974
|
_drain_controllers;
|
|
5083
|
-
/** Correlation
|
|
4975
|
+
/** Correlation: lazy init and the scan that marks work. */
|
|
5084
4976
|
_correlate;
|
|
5085
4977
|
/** Debounced correlate→drain catch-up loop. */
|
|
5086
4978
|
_settle;
|
|
@@ -5110,26 +5002,10 @@ var Act = class {
|
|
|
5110
5002
|
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
5111
5003
|
* via {@link ActLifecycleEvents}.
|
|
5112
5004
|
*
|
|
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.
|
|
5005
|
+
* Each listener is contained on its own: a throwing listener is logged,
|
|
5006
|
+
* the remaining listeners still run, and the caller (`do()`, `forget()`,
|
|
5007
|
+
* `close()`, the drain) never sees the throw, since its durable work has
|
|
5008
|
+
* already landed.
|
|
5133
5009
|
*
|
|
5134
5010
|
* Uses `rawListeners` so `once` wrappers still de-register themselves, and
|
|
5135
5011
|
* returns "had listeners" to preserve the `EventEmitter.emit` contract.
|
|
@@ -5190,29 +5066,23 @@ var Act = class {
|
|
|
5190
5066
|
*/
|
|
5191
5067
|
_event_to_state;
|
|
5192
5068
|
/**
|
|
5193
|
-
* Event-name → lane fan-in for selective arming
|
|
5069
|
+
* Event-name → lane fan-in for selective arming. Built by
|
|
5194
5070
|
* `classify_registry` once per build. `"all"` means at least one of
|
|
5195
5071
|
* the event's reactions is a dynamic resolver (lane opaque until
|
|
5196
5072
|
* runtime); a `Set<string>` lists the static lanes only that event's
|
|
5197
5073
|
* reactions target.
|
|
5198
5074
|
*/
|
|
5199
5075
|
_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
|
-
*/
|
|
5076
|
+
/** What `internal/audit.ts` needs, built once at construction. */
|
|
5207
5077
|
_audit_deps;
|
|
5208
5078
|
/** Logger resolved at construction time (after user port configuration) */
|
|
5209
5079
|
_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
5080
|
/** This Act's ports: its own bag, or the singleton adapters. */
|
|
5215
5081
|
_ports;
|
|
5082
|
+
/**
|
|
5083
|
+
* Runs a body inside this Act's ports frame, so internal
|
|
5084
|
+
* `store()`/`cache()` calls resolve to `_ports`.
|
|
5085
|
+
*/
|
|
5216
5086
|
_scoped;
|
|
5217
5087
|
/**
|
|
5218
5088
|
* Correlation-id generator for originating actions. Bound at
|
|
@@ -5231,24 +5101,18 @@ var Act = class {
|
|
|
5231
5101
|
/** Reaction dispatchers built once and handed to run_drain_cycle each cycle. */
|
|
5232
5102
|
_handle;
|
|
5233
5103
|
_handle_batch;
|
|
5234
|
-
/** Declared drain lanes
|
|
5104
|
+
/** Declared drain lanes. */
|
|
5235
5105
|
_lanes;
|
|
5236
5106
|
/**
|
|
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.
|
|
5107
|
+
* Per-stream close serialization tails, so a manual close and an
|
|
5108
|
+
* autoclose of the same stream never prune concurrently (the manual path
|
|
5109
|
+
* has no drain lease to exclude it). Entries are dropped once settled.
|
|
5246
5110
|
*/
|
|
5247
5111
|
_close_locks = /* @__PURE__ */ new Map();
|
|
5248
5112
|
/**
|
|
5249
|
-
* Run `work` under the per-stream close lock
|
|
5250
|
-
* critical sections for the same stream (windowed prunes
|
|
5251
|
-
*
|
|
5113
|
+
* Run `work` under the per-stream close lock. Serializes close
|
|
5114
|
+
* critical sections for the same stream (windowed prunes and full
|
|
5115
|
+
* closes) while letting different streams proceed in parallel.
|
|
5252
5116
|
*/
|
|
5253
5117
|
_with_close_lock(stream, work) {
|
|
5254
5118
|
const prev = this._close_locks.get(stream) ?? Promise.resolve();
|
|
@@ -5275,11 +5139,11 @@ var Act = class {
|
|
|
5275
5139
|
* @param states Merged map of state name → state definition
|
|
5276
5140
|
* @param batch_handlers Static-target projection batch handlers (target → handler)
|
|
5277
5141
|
* @param options Tuning knobs — see {@link ActOptions}
|
|
5278
|
-
* @param lanes Declared drain lanes
|
|
5279
|
-
* these from `.withLane(...)` calls
|
|
5280
|
-
*
|
|
5142
|
+
* @param lanes Declared drain lanes. The builder collects
|
|
5143
|
+
* these from `.withLane(...)` calls; one `DrainController` runs
|
|
5144
|
+
* per lane, plus the implicit `"default"`.
|
|
5281
5145
|
* @param patch_fn The per-event patch step selected once by the builder
|
|
5282
|
-
* from `ActOptions.validateFoldedState`
|
|
5146
|
+
* from `ActOptions.validateFoldedState` — `bare_patch` by
|
|
5283
5147
|
* default, `validating_patch` when the flag is on. The builder uses
|
|
5284
5148
|
* the same value for its projection-fold handlers, so there is a
|
|
5285
5149
|
* single selection site. Defaults to `bare_patch` for direct
|
|
@@ -5294,16 +5158,15 @@ var Act = class {
|
|
|
5294
5158
|
this._ports = options.scoped ?? default_scope();
|
|
5295
5159
|
const ports = this._ports;
|
|
5296
5160
|
this._scoped = make_run_scoped({
|
|
5297
|
-
//
|
|
5298
|
-
//
|
|
5299
|
-
// whichever adapters happened to be installed at construction and
|
|
5300
|
-
// ignore a later `store(...)` / `cache(...)`.
|
|
5161
|
+
// Getters, not a spread: the singletons resolve lazily, so a later
|
|
5162
|
+
// `store(...)` / `cache(...)` still takes effect.
|
|
5301
5163
|
get store() {
|
|
5302
5164
|
return ports.store;
|
|
5303
5165
|
},
|
|
5304
5166
|
get cache() {
|
|
5305
5167
|
return ports.cache;
|
|
5306
5168
|
},
|
|
5169
|
+
// Parsed per build, so a bad window throws on every build.
|
|
5307
5170
|
autoclose_window: resolveAutocloseConfig(options).autocloseWindow
|
|
5308
5171
|
});
|
|
5309
5172
|
this._correlator = options.correlator ?? default_correlator;
|
|
@@ -5311,8 +5174,6 @@ var Act = class {
|
|
|
5311
5174
|
this._cd = build_drain(this._logger);
|
|
5312
5175
|
this._handle = build_handle({
|
|
5313
5176
|
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
5177
|
reaction_scope: make_reaction_scope({
|
|
5317
5178
|
do: this._bound_do,
|
|
5318
5179
|
load: this._bound_load,
|
|
@@ -5347,16 +5208,8 @@ var Act = class {
|
|
|
5347
5208
|
resolveCircuitBreakerConfig(options.circuitBreaker),
|
|
5348
5209
|
{
|
|
5349
5210
|
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()`.
|
|
5211
|
+
// When the cooldown elapses, settle once: its scan or claim is the
|
|
5212
|
+
// half-open probe that closes or re-opens the breaker.
|
|
5360
5213
|
on_retry: () => {
|
|
5361
5214
|
this.settle({ debounceMs: 0 });
|
|
5362
5215
|
}
|
|
@@ -5364,12 +5217,9 @@ var Act = class {
|
|
|
5364
5217
|
);
|
|
5365
5218
|
}
|
|
5366
5219
|
/**
|
|
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).
|
|
5220
|
+
* One DrainController per active lane, plus the implicit "default" unless
|
|
5221
|
+
* `onlyLanes` excludes it. Apps that declare no lanes pass no lane filter
|
|
5222
|
+
* to `claim`.
|
|
5373
5223
|
*/
|
|
5374
5224
|
_build_drain_controllers(options, lanes) {
|
|
5375
5225
|
const all_lanes = ["default", ...lanes.map((l) => l.name)];
|
|
@@ -5388,11 +5238,7 @@ var Act = class {
|
|
|
5388
5238
|
handle_batch: this._handle_batch,
|
|
5389
5239
|
on_acked: (acked) => this.emit("acked", acked),
|
|
5390
5240
|
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.
|
|
5241
|
+
// Reaction-requested close: the same machinery as `app.close`.
|
|
5396
5242
|
on_close: async (targets) => {
|
|
5397
5243
|
const close_actor = { id: "$close", name: "close" };
|
|
5398
5244
|
const result = await run_close_cycle(targets, {
|
|
@@ -5409,12 +5255,7 @@ var Act = class {
|
|
|
5409
5255
|
this.emit("closed", result);
|
|
5410
5256
|
},
|
|
5411
5257
|
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
5258
|
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
5259
|
lane: single_default_lane ? void 0 : name,
|
|
5419
5260
|
defaults: cfg && {
|
|
5420
5261
|
streamLimit: cfg.streamLimit,
|
|
@@ -5428,17 +5269,9 @@ var Act = class {
|
|
|
5428
5269
|
return controllers;
|
|
5429
5270
|
}
|
|
5430
5271
|
/**
|
|
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.
|
|
5272
|
+
* Startup advisory naming declared lanes that `onlyLanes` leaves without a
|
|
5273
|
+
* controller here. One process can't check that the cluster covers every
|
|
5274
|
+
* lane, so it reports its own gaps.
|
|
5442
5275
|
*/
|
|
5443
5276
|
_advise_orphaned_lanes(options, lanes) {
|
|
5444
5277
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
@@ -5452,25 +5285,15 @@ var Act = class {
|
|
|
5452
5285
|
`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
5286
|
);
|
|
5454
5287
|
}
|
|
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
|
-
*/
|
|
5288
|
+
/** Audit deps, built after the controllers so the lane set is final. */
|
|
5461
5289
|
_build_audit_deps() {
|
|
5462
5290
|
return {
|
|
5463
5291
|
store: store2,
|
|
5464
5292
|
logger: this._logger,
|
|
5465
5293
|
event_to_state: this._event_to_state,
|
|
5466
5294
|
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.
|
|
5295
|
+
// Declared lanes, not this process's controllers: a lane another
|
|
5296
|
+
// worker drains is not `unknown-lane`.
|
|
5474
5297
|
declared_lanes: /* @__PURE__ */ new Set(["default", ...this._lanes.map((l) => l.name)]),
|
|
5475
5298
|
routed_events: new Set(this._event_to_lanes.keys())
|
|
5476
5299
|
};
|
|
@@ -5486,10 +5309,8 @@ var Act = class {
|
|
|
5486
5309
|
static_targets: classification.static_targets,
|
|
5487
5310
|
cd: this._cd,
|
|
5488
5311
|
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.
|
|
5312
|
+
// Declared lanes (not active ones): a lane another process claims is
|
|
5313
|
+
// still a valid destination.
|
|
5493
5314
|
declared_lanes: /* @__PURE__ */ new Set([
|
|
5494
5315
|
"default",
|
|
5495
5316
|
...this._lanes.map((l) => l.name)
|
|
@@ -5497,26 +5318,14 @@ var Act = class {
|
|
|
5497
5318
|
on_init: () => {
|
|
5498
5319
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
5499
5320
|
},
|
|
5500
|
-
// Cold-start defer re-seed (#1221). Skipped on writer-only instances
|
|
5501
|
-
// (`drain: false`) — they run no local controllers to re-arm.
|
|
5502
5321
|
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
5503
5322
|
});
|
|
5504
5323
|
}
|
|
5505
5324
|
/**
|
|
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.
|
|
5325
|
+
* At cold start, re-seed each lane controller's local wake from the
|
|
5326
|
+
* persisted future `deferred_at`, so an idle deferred stream (an
|
|
5327
|
+
* autoclose waiting out its window) still fires on time. Streams on lanes
|
|
5328
|
+
* this instance doesn't run are skipped.
|
|
5520
5329
|
*/
|
|
5521
5330
|
async _seed_persisted_defers() {
|
|
5522
5331
|
const now = Date.now();
|
|
@@ -5530,10 +5339,6 @@ var Act = class {
|
|
|
5530
5339
|
_build_settle(options) {
|
|
5531
5340
|
return new SettleLoop(
|
|
5532
5341
|
{
|
|
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
5342
|
init: () => this._scoped(() => this._correlate.init()),
|
|
5538
5343
|
checkpoint: () => this._correlate.checkpoint,
|
|
5539
5344
|
correlate: (q) => this._correlate_scanned(q, true),
|
|
@@ -5551,19 +5356,11 @@ var Act = class {
|
|
|
5551
5356
|
* already in flight a bounded chance to finish, then remove lifecycle
|
|
5552
5357
|
* listeners and tear down the cross-process notify subscription.
|
|
5553
5358
|
*
|
|
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.
|
|
5359
|
+
* Scheduling stops first, then in-flight cycles get up to `graceMs` to
|
|
5360
|
+
* reach their `ack` (an abandoned handler holds its lease until it
|
|
5361
|
+
* expires). Listeners come off after the wait, so they still see work that
|
|
5362
|
+
* finished during it. The budget is a ceiling, not a delay; when it runs
|
|
5363
|
+
* out, teardown proceeds anyway.
|
|
5567
5364
|
*
|
|
5568
5365
|
* Idempotent — repeated calls return the same promise, and the first
|
|
5569
5366
|
* call's `graceMs` is the one that applies. Registered automatically with
|
|
@@ -5596,26 +5393,10 @@ var Act = class {
|
|
|
5596
5393
|
* whichever comes first. Cycle promises never reject (`drain()` contains
|
|
5597
5394
|
* its own errors), so this never throws.
|
|
5598
5395
|
*
|
|
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.
|
|
5396
|
+
* An omitted budget is the largest `leaseMillis` among lanes with a cycle
|
|
5397
|
+
* in flight (the drain's fallback for lanes that pinned none, or pinned
|
|
5398
|
+
* 0, which is the same as none), with an in-flight settle counting on the
|
|
5399
|
+
* fallback, capped at the maximum. Idle lanes don't count.
|
|
5619
5400
|
*/
|
|
5620
5401
|
_derive_grace_ms(running, settling) {
|
|
5621
5402
|
let max = settling ? DEFAULT_SHUTDOWN_GRACE_MS : 0;
|
|
@@ -5888,7 +5669,7 @@ var Act = class {
|
|
|
5888
5669
|
}
|
|
5889
5670
|
/**
|
|
5890
5671
|
* Wipe the sensitive-data payload for every event on the stream — see
|
|
5891
|
-
* {@link IAct.forget}.
|
|
5672
|
+
* {@link IAct.forget}.
|
|
5892
5673
|
*
|
|
5893
5674
|
* Throws on adapters without `Store.forget_pii`, invalidates the cache
|
|
5894
5675
|
* entry for the stream, emits the `forgotten` lifecycle event with the
|
|
@@ -5929,7 +5710,7 @@ var Act = class {
|
|
|
5929
5710
|
*
|
|
5930
5711
|
* Call `correlate()` before `drain()`. It is not only how dynamic targets
|
|
5931
5712
|
* are discovered: a stream is claimable while `at < correlated_at`, and
|
|
5932
|
-
* `correlate` is the only component that raises that mark
|
|
5713
|
+
* `correlate` is the only component that raises that mark, so a
|
|
5933
5714
|
* commit no correlate has seen is not drainable — including for static
|
|
5934
5715
|
* targets, which were served by a probe of the event log before. For a
|
|
5935
5716
|
* higher-level API that handles debouncing, correlation, and signaling
|
|
@@ -5962,14 +5743,14 @@ var Act = class {
|
|
|
5962
5743
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
5963
5744
|
return this._scoped(() => this._drain_all(options));
|
|
5964
5745
|
}
|
|
5965
|
-
/** Arm every active lane controller
|
|
5746
|
+
/** Arm every active lane controller. */
|
|
5966
5747
|
_arm_all() {
|
|
5967
5748
|
this._correlate.arm();
|
|
5968
5749
|
for (const c of this._drain_controllers.values()) c.arm();
|
|
5969
5750
|
}
|
|
5970
5751
|
/**
|
|
5971
5752
|
* Arm only the lane controllers whose reactions match the supplied
|
|
5972
|
-
* event names (
|
|
5753
|
+
* event names (selective arming). Events with any dynamic
|
|
5973
5754
|
* resolver fall back to `_arm_all()` via the `"all"` sentinel — the
|
|
5974
5755
|
* resolver's lane isn't known until correlate runs the function.
|
|
5975
5756
|
* Events with no reactions are skipped; `_event_to_lanes` doesn't
|
|
@@ -6073,7 +5854,7 @@ var Act = class {
|
|
|
6073
5854
|
return { subscribed, last_id };
|
|
6074
5855
|
}
|
|
6075
5856
|
/**
|
|
6076
|
-
* `correlate` plus whether the pass actually read the store
|
|
5857
|
+
* `correlate` plus whether the pass actually read the store.
|
|
6077
5858
|
*
|
|
6078
5859
|
* The settle loop needs that extra bit to decide whether the pass carries a
|
|
6079
5860
|
* circuit-breaker health signal, and a disarmed pass carries none. It stays
|
|
@@ -6334,7 +6115,7 @@ var Act = class {
|
|
|
6334
6115
|
});
|
|
6335
6116
|
}
|
|
6336
6117
|
/**
|
|
6337
|
-
* Operator-driven store audit
|
|
6118
|
+
* Operator-driven store audit.
|
|
6338
6119
|
*
|
|
6339
6120
|
* Walks the connected store and yields per-category findings —
|
|
6340
6121
|
* each tagged with the remediation it suggests. Same operator-
|
|
@@ -6386,7 +6167,7 @@ var Act = class {
|
|
|
6386
6167
|
* Bulk-update scheduling priority for streams matching `filter`.
|
|
6387
6168
|
*
|
|
6388
6169
|
* Operator-grade override of the `claim()` lagging-frontier
|
|
6389
|
-
* ordering
|
|
6170
|
+
* ordering. Useful when a long-running replay needs to
|
|
6390
6171
|
* jump ahead of other lagging streams, or when a no-longer-urgent
|
|
6391
6172
|
* job should yield slots back to the rest. Build-time priorities
|
|
6392
6173
|
* (set via the resolver's `priority` field) are subject to a
|
|
@@ -6483,12 +6264,12 @@ var Act = class {
|
|
|
6483
6264
|
*/
|
|
6484
6265
|
/**
|
|
6485
6266
|
* After a close, forget any target whose subscription row the truncate
|
|
6486
|
-
* removed, so a later correlate can re-subscribe it
|
|
6267
|
+
* removed, so a later correlate can re-subscribe it. Restart
|
|
6487
6268
|
* targets keep their row, so only fully-retired streams are forgotten.
|
|
6488
6269
|
*/
|
|
6489
6270
|
/**
|
|
6490
6271
|
* Advance correlation until the read cursor reaches `until`, and report
|
|
6491
|
-
* where it landed
|
|
6272
|
+
* where it landed. Used by the close cycle's safety probe, which
|
|
6492
6273
|
* cannot judge a subscription's pending work over events correlate has
|
|
6493
6274
|
* not resolved yet.
|
|
6494
6275
|
*
|
|
@@ -6514,13 +6295,12 @@ var Act = class {
|
|
|
6514
6295
|
* A tombstone seed means the stream was retired; a snapshot seed means it
|
|
6515
6296
|
* was restarted and is still consuming.
|
|
6516
6297
|
*
|
|
6517
|
-
* `truncate` no longer removes the subscription row
|
|
6298
|
+
* `truncate` no longer removes the subscription row, so this is no
|
|
6518
6299
|
* longer repairing damage the close itself did. It still matters, because
|
|
6519
6300
|
* the row can disappear later: reclaiming retired subscriptions is an
|
|
6520
6301
|
* operator job now, and that `DELETE` can land while this process is
|
|
6521
6302
|
* 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
|
|
6303
|
+
* an operator removed, which is a silent-no-delivery failure — a reaction whose target is named after the stream would never
|
|
6524
6304
|
* be re-registered.
|
|
6525
6305
|
*/
|
|
6526
6306
|
_forget_closed_subscriptions(result) {
|
|
@@ -6795,7 +6575,7 @@ function reaction_on(event, events, builder) {
|
|
|
6795
6575
|
const reaction = {
|
|
6796
6576
|
handler: schedule ? make_deferred(handler, schedule) : handler,
|
|
6797
6577
|
resolver: _this_,
|
|
6798
|
-
//
|
|
6578
|
+
// Validate the whole bag at the declaration site so a bad
|
|
6799
6579
|
// `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
|
|
6800
6580
|
// not a NaN gate on the first retry.
|
|
6801
6581
|
options: resolveReactionConfig({
|
|
@@ -6997,7 +6777,7 @@ function act() {
|
|
|
6997
6777
|
spec.flush,
|
|
6998
6778
|
spec.config,
|
|
6999
6779
|
patch_fn,
|
|
7000
|
-
// Head loads strip sensitive keys like the warm path
|
|
6780
|
+
// Head loads strip sensitive keys like the warm path.
|
|
7001
6781
|
registry.sensitive_fields
|
|
7002
6782
|
)
|
|
7003
6783
|
)
|
|
@@ -7503,7 +7283,7 @@ var CsvFile = class {
|
|
|
7503
7283
|
name: fields[1],
|
|
7504
7284
|
// Revive dates like every Store adapter does — a Date in an event
|
|
7505
7285
|
// payload must not depend on whether it came back through a store or
|
|
7506
|
-
// through a CSV restore
|
|
7286
|
+
// through a CSV restore. The `created` column below has
|
|
7507
7287
|
// always been parsed explicitly; the JSON columns were missed.
|
|
7508
7288
|
data: JSON.parse(fields[2]),
|
|
7509
7289
|
stream: fields[3],
|