@rotorsoft/act 1.22.6 → 1.22.8
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 +31 -0
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +11 -1
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/defer-timer.d.ts +5 -1
- package/dist/@types/internal/defer-timer.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +13 -0
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +3 -3
- package/dist/@types/types/ports.d.ts +15 -0
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/schemas.d.ts +17 -6
- package/dist/@types/types/schemas.d.ts.map +1 -1
- package/dist/{chunk-2RZSKBZZ.js → chunk-LDWS6I2C.js} +1 -1
- package/dist/chunk-LDWS6I2C.js.map +1 -0
- package/dist/{chunk-CWBI4NQD.js → chunk-YVLB5MMB.js} +10 -5
- package/dist/chunk-YVLB5MMB.js.map +1 -0
- package/dist/index.cjs +101 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +94 -5
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +8 -3
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +2 -2
- package/dist/types/index.cjs +20 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +3 -1
- package/package.json +2 -2
- package/dist/chunk-2RZSKBZZ.js.map +0 -1
- package/dist/chunk-CWBI4NQD.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
sleep,
|
|
21
21
|
store,
|
|
22
22
|
validate
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-YVLB5MMB.js";
|
|
24
24
|
import {
|
|
25
25
|
ActorSchema,
|
|
26
26
|
CausationEventSchema,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
pii_split,
|
|
46
46
|
pii_strip,
|
|
47
47
|
sensitive
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-LDWS6I2C.js";
|
|
49
49
|
|
|
50
50
|
// src/signals.ts
|
|
51
51
|
process.once("SIGINT", async (arg) => {
|
|
@@ -1406,6 +1406,16 @@ var CorrelateCycle = class {
|
|
|
1406
1406
|
_has_dynamic_resolvers;
|
|
1407
1407
|
_cd;
|
|
1408
1408
|
_on_init;
|
|
1409
|
+
/**
|
|
1410
|
+
* Async cold-start hook (#1221). Runs once, after the sync `on_init`,
|
|
1411
|
+
* inside the same `init()` await. The orchestrator uses it to re-seed the
|
|
1412
|
+
* process-local defer timers from the store's persisted `deferred_at` so
|
|
1413
|
+
* an idle deferred stream re-arms its drain across a restart. Kept
|
|
1414
|
+
* separate from `on_init` because seeding is an async store read; `init`
|
|
1415
|
+
* already awaits, so folding it in here preserves the "runs exactly once"
|
|
1416
|
+
* guarantee without a second gate on the Act side.
|
|
1417
|
+
*/
|
|
1418
|
+
_on_init_async;
|
|
1409
1419
|
/**
|
|
1410
1420
|
* Scope runner (#1191). The periodic `start_polling` timer fires
|
|
1411
1421
|
* outside any caller frame, so its `correlate()` must be re-wrapped in
|
|
@@ -1414,7 +1424,7 @@ var CorrelateCycle = class {
|
|
|
1414
1424
|
* `_scoped` (identity for a non-scoped Act), so it's required.
|
|
1415
1425
|
*/
|
|
1416
1426
|
_run_scoped;
|
|
1417
|
-
constructor(registry, static_targets, has_dynamic_resolvers, cd, maxSubscribedStreams, on_init, run_scoped) {
|
|
1427
|
+
constructor(registry, static_targets, has_dynamic_resolvers, cd, maxSubscribedStreams, on_init, run_scoped, on_init_async) {
|
|
1418
1428
|
this._subscribed = new LruSet(maxSubscribedStreams);
|
|
1419
1429
|
this._registry = registry;
|
|
1420
1430
|
this._static_targets = static_targets;
|
|
@@ -1422,6 +1432,7 @@ var CorrelateCycle = class {
|
|
|
1422
1432
|
this._cd = cd;
|
|
1423
1433
|
this._on_init = on_init;
|
|
1424
1434
|
this._run_scoped = run_scoped;
|
|
1435
|
+
this._on_init_async = on_init_async;
|
|
1425
1436
|
}
|
|
1426
1437
|
/** Last correlated event id. */
|
|
1427
1438
|
get checkpoint() {
|
|
@@ -1443,6 +1454,7 @@ var CorrelateCycle = class {
|
|
|
1443
1454
|
for (const { stream } of this._static_targets) {
|
|
1444
1455
|
this._subscribed.add(stream);
|
|
1445
1456
|
}
|
|
1457
|
+
await this._on_init_async?.();
|
|
1446
1458
|
}
|
|
1447
1459
|
/**
|
|
1448
1460
|
* Discover dynamic-resolver targets in the events past the checkpoint
|
|
@@ -2316,6 +2328,22 @@ var DrainController = class {
|
|
|
2316
2328
|
arm() {
|
|
2317
2329
|
this._armed = true;
|
|
2318
2330
|
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Re-seed a persisted defer schedule into the process-local timer at cold
|
|
2333
|
+
* start (#1221). The `_defer` map lives in worker memory and is empty
|
|
2334
|
+
* after a restart; a stream deferred to a future due-time (e.g. an idle
|
|
2335
|
+
* autoclose aggregate) is durable in the store's `deferred_at` but has
|
|
2336
|
+
* nothing in memory to re-arm the drain at the due-time. The orchestrator
|
|
2337
|
+
* reads the persisted `deferred_at` for this controller's lane and calls
|
|
2338
|
+
* this to park the stream + (re)schedule the shared wake — so the drain
|
|
2339
|
+
* re-arms at the due-time with no intervening commit. `schedule()`
|
|
2340
|
+
* collapses many seeds into one timer, so callers may seed in a loop and
|
|
2341
|
+
* let the earliest due-time win.
|
|
2342
|
+
*/
|
|
2343
|
+
seed_defer(stream, at) {
|
|
2344
|
+
this._defer.set(stream, at);
|
|
2345
|
+
this._defer.schedule();
|
|
2346
|
+
}
|
|
2319
2347
|
/** Read-only flag — true while a commit / reset is unprocessed. */
|
|
2320
2348
|
get armed() {
|
|
2321
2349
|
return this._armed;
|
|
@@ -2864,6 +2892,7 @@ var Act = class {
|
|
|
2864
2892
|
this._drain = options.drain !== false;
|
|
2865
2893
|
this._breaker = this._build_breaker(options);
|
|
2866
2894
|
this._drain_controllers = this._build_drain_controllers(options, lanes);
|
|
2895
|
+
this._advise_orphaned_lanes(options, lanes);
|
|
2867
2896
|
this._audit_deps = this._build_audit_deps();
|
|
2868
2897
|
this._correlate = this._build_correlate(options, classification);
|
|
2869
2898
|
this._settle = this._build_settle(options);
|
|
@@ -2956,6 +2985,31 @@ var Act = class {
|
|
|
2956
2985
|
}
|
|
2957
2986
|
return controllers;
|
|
2958
2987
|
}
|
|
2988
|
+
/**
|
|
2989
|
+
* Orphaned-lane startup advisory (#1220). When `onlyLanes` is set, this
|
|
2990
|
+
* instance builds a controller only for its slice of the declared lane
|
|
2991
|
+
* universe — every OTHER declared lane's stream is persisted (correlate
|
|
2992
|
+
* subscribes all static targets regardless of `onlyLanes`) but never
|
|
2993
|
+
* claimed here. If no peer worker deploys with those lanes in ITS
|
|
2994
|
+
* `onlyLanes`, their reactions accumulate forever, silently. A single
|
|
2995
|
+
* process can't verify the cluster invariant `∪ onlyLanes ⊇ declared
|
|
2996
|
+
* lanes`, so we surface the per-instance signal — "these declared lanes
|
|
2997
|
+
* have no controller here" — the same way the deprecated-event advisory
|
|
2998
|
+
* surfaces legacy events. No advisory when `onlyLanes` is unset (every
|
|
2999
|
+
* lane gets a controller) or covers every declared lane.
|
|
3000
|
+
*/
|
|
3001
|
+
_advise_orphaned_lanes(options, lanes) {
|
|
3002
|
+
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
3003
|
+
const active = new Set(this._drain_controllers.keys());
|
|
3004
|
+
const orphaned = ["default", ...lanes.map((l) => l.name)].filter(
|
|
3005
|
+
(name) => !active.has(name)
|
|
3006
|
+
);
|
|
3007
|
+
if (orphaned.length === 0) return;
|
|
3008
|
+
const list = orphaned.map((name) => `"${name}"`).join(", ");
|
|
3009
|
+
this._logger.info(
|
|
3010
|
+
`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.`
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
2959
3013
|
/**
|
|
2960
3014
|
* Audit deps bag. Snapshotted after registry classification and
|
|
2961
3015
|
* drain-controller build so the audit module sees the finalized lane
|
|
@@ -2969,7 +3023,14 @@ var Act = class {
|
|
|
2969
3023
|
event_to_state: this._event_to_state,
|
|
2970
3024
|
states: this._states,
|
|
2971
3025
|
known_events: new Set(this._event_to_state.keys()),
|
|
2972
|
-
|
|
3026
|
+
// The DECLARED lane universe — the implicit "default" plus every
|
|
3027
|
+
// `.withLane(...)` name — NOT `_drain_controllers.keys()` (#1224).
|
|
3028
|
+
// An `onlyLanes`-filtered instance builds a controller only for its
|
|
3029
|
+
// slice of lanes, so keying off the active controller set would flag
|
|
3030
|
+
// a stream correctly assigned to an excluded-but-declared lane (one
|
|
3031
|
+
// another worker drains) as `unknown-lane`. The audit reports what's
|
|
3032
|
+
// structurally routable across the cluster, not what this process runs.
|
|
3033
|
+
declared_lanes: /* @__PURE__ */ new Set(["default", ...this._lanes.map((l) => l.name)]),
|
|
2973
3034
|
routed_events: new Set(this._event_to_lanes.keys())
|
|
2974
3035
|
};
|
|
2975
3036
|
}
|
|
@@ -2990,9 +3051,36 @@ var Act = class {
|
|
|
2990
3051
|
},
|
|
2991
3052
|
// Re-scope the background `start_correlations` timer so its
|
|
2992
3053
|
// correlate resolves the scoped ports, not the singleton (#1191).
|
|
2993
|
-
this._scoped
|
|
3054
|
+
this._scoped,
|
|
3055
|
+
// Cold-start defer re-seed (#1221). Skipped on writer-only instances
|
|
3056
|
+
// (`drain: false`) — they run no local controllers to re-arm.
|
|
3057
|
+
this._drain ? () => this._seed_persisted_defers() : void 0
|
|
2994
3058
|
);
|
|
2995
3059
|
}
|
|
3060
|
+
/**
|
|
3061
|
+
* Re-seed every active lane controller's process-local defer timer from
|
|
3062
|
+
* the store's persisted `deferred_at` (#1221). Runs once at cold start,
|
|
3063
|
+
* inside `CorrelateCycle.init`, after static targets are subscribed.
|
|
3064
|
+
*
|
|
3065
|
+
* The defer timer is worker memory: empty after a restart. A stream
|
|
3066
|
+
* deferred to a future due-time (the classic case: an idle autoclose
|
|
3067
|
+
* aggregate that deferred its terminal close) is durable in the store but
|
|
3068
|
+
* has nothing in memory to re-arm the drain — the controller disarms on
|
|
3069
|
+
* the first empty claim and, since the aggregate is idle, no commit ever
|
|
3070
|
+
* re-arms it. Reading the persisted schedule and seeding the owning lane's
|
|
3071
|
+
* timer restores the wake, so the close fires at the due-time.
|
|
3072
|
+
*
|
|
3073
|
+
* Streams whose lane has no controller on this instance (excluded by
|
|
3074
|
+
* `onlyLanes`) are skipped — a peer worker owns that lane's timer.
|
|
3075
|
+
*/
|
|
3076
|
+
async _seed_persisted_defers() {
|
|
3077
|
+
const now = Date.now();
|
|
3078
|
+
await store().query_streams((pos) => {
|
|
3079
|
+
if (pos.deferred_at === void 0 || pos.deferred_at <= now) return;
|
|
3080
|
+
const controller = this._drain_controllers.get(pos.lane ?? "default");
|
|
3081
|
+
controller?.seed_defer(pos.stream, pos.deferred_at);
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
2996
3084
|
/** Settle loop driving correlate + drain to quiescence. */
|
|
2997
3085
|
_build_settle(options) {
|
|
2998
3086
|
return new SettleLoop(
|
|
@@ -4615,6 +4703,7 @@ export {
|
|
|
4615
4703
|
extend,
|
|
4616
4704
|
is_literal_source,
|
|
4617
4705
|
log,
|
|
4706
|
+
pii_fields,
|
|
4618
4707
|
port,
|
|
4619
4708
|
projection,
|
|
4620
4709
|
resolveAutocloseConfig,
|