@rotorsoft/act 1.32.18 → 1.32.20
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/README.md +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +18 -21
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/builders/reaction-builder.d.ts.map +1 -1
- package/dist/@types/internal/config.d.ts +6 -0
- package/dist/@types/internal/config.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +7 -1
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/defer-timer.d.ts +6 -12
- package/dist/@types/internal/defer-timer.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +5 -2
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +1 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +3 -3
- package/dist/index.cjs +41 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +41 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -859,6 +859,10 @@ var BackoffOptionsSchema = z2.object({
|
|
|
859
859
|
maxMs: z2.number().gt(0).optional(),
|
|
860
860
|
jitter: z2.boolean().optional()
|
|
861
861
|
});
|
|
862
|
+
var DEFAULT_BACKOFF = {
|
|
863
|
+
strategy: "fixed",
|
|
864
|
+
baseMs: 1e4
|
|
865
|
+
};
|
|
862
866
|
var DEFAULT_MAX_RETRIES = 3;
|
|
863
867
|
var DEFAULT_BLOCK_ON_ERROR = true;
|
|
864
868
|
var ReactionOptionsSchema = z2.object({
|
|
@@ -1661,13 +1665,14 @@ var CorrelateCycle = class {
|
|
|
1661
1665
|
cd,
|
|
1662
1666
|
max_subscribed_streams,
|
|
1663
1667
|
declared_lanes,
|
|
1668
|
+
shard,
|
|
1664
1669
|
on_init,
|
|
1665
1670
|
on_init_async,
|
|
1666
1671
|
cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN,
|
|
1667
1672
|
lease_millis = DEFAULT_CORRELATION_LEASE_MS
|
|
1668
1673
|
}) {
|
|
1669
1674
|
this._lease_millis = lease_millis;
|
|
1670
|
-
this._key = registry_key(registry.events);
|
|
1675
|
+
this._key = shard ? `${registry_key(registry.events)}:${[...shard].sort().join(",")}` : registry_key(registry.events);
|
|
1671
1676
|
this._dynamic_subscriptions = new LruMap(max_subscribed_streams);
|
|
1672
1677
|
this._registry = registry;
|
|
1673
1678
|
this._declared_lanes = declared_lanes;
|
|
@@ -2063,9 +2068,8 @@ var DeferTimer = class {
|
|
|
2063
2068
|
_on_wake;
|
|
2064
2069
|
/**
|
|
2065
2070
|
* @param on_wake - invoked once each time the earliest due-time elapses,
|
|
2066
|
-
* after the come-due entries have been removed.
|
|
2067
|
-
*
|
|
2068
|
-
* tick).
|
|
2071
|
+
* after the come-due entries have been removed. The consumer re-schedules
|
|
2072
|
+
* for whatever is still parked.
|
|
2069
2073
|
*/
|
|
2070
2074
|
constructor(on_wake) {
|
|
2071
2075
|
this._on_wake = on_wake;
|
|
@@ -2118,14 +2122,9 @@ var DeferTimer = class {
|
|
|
2118
2122
|
this._timer = setTimeout(() => {
|
|
2119
2123
|
this._timer = void 0;
|
|
2120
2124
|
const now = Date.now();
|
|
2121
|
-
let came_due = false;
|
|
2122
2125
|
for (const [stream, at] of this._due)
|
|
2123
|
-
if (at <= now)
|
|
2124
|
-
this._due.delete(stream);
|
|
2125
|
-
came_due = true;
|
|
2126
|
-
}
|
|
2126
|
+
if (at <= now) this._due.delete(stream);
|
|
2127
2127
|
this._on_wake();
|
|
2128
|
-
if (!came_due && this._due.size > 0) this.schedule();
|
|
2129
2128
|
}, delay);
|
|
2130
2129
|
this._timer.unref();
|
|
2131
2130
|
}
|
|
@@ -2842,7 +2841,7 @@ async function run_drain_cycle(deps, cycle) {
|
|
|
2842
2841
|
handled: 0,
|
|
2843
2842
|
acked_at: lease.at,
|
|
2844
2843
|
error,
|
|
2845
|
-
...block2 ? { block: true } : {}
|
|
2844
|
+
...block2 ? { block: true } : { next_attempt_at: Date.now() + DEFAULT_BACKOFF.baseMs }
|
|
2846
2845
|
};
|
|
2847
2846
|
return Promise.resolve(failed);
|
|
2848
2847
|
}
|
|
@@ -2890,11 +2889,15 @@ var DrainController = class {
|
|
|
2890
2889
|
_ratio = 0.5;
|
|
2891
2890
|
/**
|
|
2892
2891
|
* Local wake for streams this worker parked (backoff or defer). The
|
|
2893
|
-
* schedule itself is persisted in the store; this
|
|
2894
|
-
*
|
|
2892
|
+
* schedule itself is persisted in the store; this runs the drain at the
|
|
2893
|
+
* earliest pending visit. A parked stream already carries its mark, so
|
|
2894
|
+
* the drain alone finds it, and on an idle app nothing else would.
|
|
2895
|
+
* `stop()` cancels the timer and a stopped drain never re-schedules it,
|
|
2896
|
+
* so a wake never runs after a stop.
|
|
2895
2897
|
*/
|
|
2896
2898
|
_defer = new DeferTimer(() => {
|
|
2897
2899
|
this._armed = true;
|
|
2900
|
+
void this._deps.run_scoped(() => this.drain());
|
|
2898
2901
|
});
|
|
2899
2902
|
/** Worker timer. Set when `start()` is active, undefined otherwise. */
|
|
2900
2903
|
_worker;
|
|
@@ -2982,13 +2985,13 @@ var DrainController = class {
|
|
|
2982
2985
|
async drain(options = {}) {
|
|
2983
2986
|
if (!this._armed) return EMPTY_DRAIN;
|
|
2984
2987
|
if (this._locked) return EMPTY_DRAIN;
|
|
2985
|
-
if (this._deps.breaker.state(Date.now()) === "open")
|
|
2986
|
-
return EMPTY_DRAIN;
|
|
2987
2988
|
const d = this._deps.defaults ?? {};
|
|
2988
2989
|
const streamLimit = d.streamLimit ?? options.streamLimit ?? DEFAULT_STREAM_LIMIT;
|
|
2989
2990
|
const eventLimit = d.eventLimit ?? options.eventLimit ?? DEFAULT_EVENT_LIMIT;
|
|
2990
2991
|
const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? DEFAULT_LEASE_MILLIS;
|
|
2991
2992
|
try {
|
|
2993
|
+
if (this._deps.breaker.state(Date.now()) === "open")
|
|
2994
|
+
return EMPTY_DRAIN;
|
|
2992
2995
|
this._locked = true;
|
|
2993
2996
|
this._inflight = new Promise((done) => {
|
|
2994
2997
|
this._inflight_done = done;
|
|
@@ -3006,7 +3009,6 @@ var DrainController = class {
|
|
|
3006
3009
|
if (!cycle) {
|
|
3007
3010
|
this._deps.breaker.passed();
|
|
3008
3011
|
this._armed = false;
|
|
3009
|
-
if (this._defer.size > 0) this._defer.schedule();
|
|
3010
3012
|
return EMPTY_DRAIN;
|
|
3011
3013
|
}
|
|
3012
3014
|
const { leased, fetched, handled, acked, blocked, closeable } = cycle;
|
|
@@ -3015,11 +3017,9 @@ var DrainController = class {
|
|
|
3015
3017
|
for (const lease of acked) this._defer.delete(lease.stream);
|
|
3016
3018
|
for (const lease of blocked) this._defer.delete(lease.stream);
|
|
3017
3019
|
for (const h of handled) {
|
|
3018
|
-
const
|
|
3019
|
-
const next = h.defer ?? retry_at;
|
|
3020
|
+
const next = h.defer ?? h.next_attempt_at;
|
|
3020
3021
|
if (next !== void 0) this._defer.set(h.lease.stream, next);
|
|
3021
3022
|
}
|
|
3022
|
-
if (this._defer.size > 0) this._defer.schedule();
|
|
3023
3023
|
if (acked.length) this._deps.on_acked(acked);
|
|
3024
3024
|
if (closeable.length) await this._deps.on_close(closeable);
|
|
3025
3025
|
this._deps.breaker.passed();
|
|
@@ -3030,6 +3030,7 @@ var DrainController = class {
|
|
|
3030
3030
|
this._deps.breaker.failed(Date.now(), error);
|
|
3031
3031
|
return EMPTY_DRAIN;
|
|
3032
3032
|
} finally {
|
|
3033
|
+
if (!this._stopped) this._defer.schedule();
|
|
3033
3034
|
this._locked = false;
|
|
3034
3035
|
this._inflight = void 0;
|
|
3035
3036
|
this._inflight_done?.();
|
|
@@ -3235,7 +3236,7 @@ function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
|
3235
3236
|
logger.error(
|
|
3236
3237
|
non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
3237
3238
|
);
|
|
3238
|
-
const next_attempt_at =
|
|
3239
|
+
const next_attempt_at = block2 ? void 0 : Date.now() + compute_backoff_delay(lease.retry, options.backoff ?? DEFAULT_BACKOFF);
|
|
3239
3240
|
return {
|
|
3240
3241
|
lease,
|
|
3241
3242
|
handled,
|
|
@@ -3671,6 +3672,8 @@ var Act = class {
|
|
|
3671
3672
|
// Declared lanes (not active ones): a lane another process claims is
|
|
3672
3673
|
// still a valid destination.
|
|
3673
3674
|
declared_lanes: new Set(this._declared_lanes),
|
|
3675
|
+
// Built after the controllers, so their keys are the active lanes.
|
|
3676
|
+
shard: this._drain_controllers.size < this._declared_lanes.length ? [...this._drain_controllers.keys()] : void 0,
|
|
3674
3677
|
on_init: () => {
|
|
3675
3678
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
3676
3679
|
},
|
|
@@ -4367,11 +4370,11 @@ var Act = class {
|
|
|
4367
4370
|
});
|
|
4368
4371
|
}
|
|
4369
4372
|
/**
|
|
4370
|
-
* Atomically wipe the store and rebuild it from an
|
|
4371
|
-
*
|
|
4372
|
-
* `
|
|
4373
|
-
*
|
|
4374
|
-
*
|
|
4373
|
+
* Atomically wipe the store and rebuild it from an event source. The
|
|
4374
|
+
* framework owns iteration, validation, `drop_snapshots` filtering,
|
|
4375
|
+
* `on_progress`, and the per-call `old → new` causation remap; the
|
|
4376
|
+
* adapter's {@link Store.restore} driver supplies the transaction
|
|
4377
|
+
* lifecycle and per-event insert.
|
|
4375
4378
|
*
|
|
4376
4379
|
* Throws if the adapter has no restore capability. Throws on the
|
|
4377
4380
|
* first invalid event (negative version, malformed `created`) with
|
|
@@ -4379,33 +4382,30 @@ var Act = class {
|
|
|
4379
4382
|
* the adapter means a failing restore leaves the store byte-for-byte
|
|
4380
4383
|
* unchanged.
|
|
4381
4384
|
*
|
|
4382
|
-
* @param source -
|
|
4383
|
-
* rather than buffered so
|
|
4384
|
-
* Each event's original `id`
|
|
4385
|
-
* never written through —
|
|
4385
|
+
* @param source - Any {@link EventSource}: a `CsvFile` backup or
|
|
4386
|
+
* another store. Read in batches rather than buffered, so
|
|
4387
|
+
* multi-million-event backups don't OOM. Each event's original `id`
|
|
4388
|
+
* is used as a causation lookup key but never written through —
|
|
4389
|
+
* adapters renumber densely.
|
|
4386
4390
|
* @param opts - {@link ScanOptions}. `drop_snapshots` skips
|
|
4387
4391
|
* `__snapshot__` events (counted in the result); `on_progress`
|
|
4388
4392
|
* fires once per event.
|
|
4393
|
+
* @param sink - Where the events land. Defaults to this Act's store.
|
|
4389
4394
|
* @returns {@link ScanResult} with `kept`, `duration_ms`, and
|
|
4390
4395
|
* `dropped` per-category counters.
|
|
4391
4396
|
*
|
|
4392
|
-
* @example
|
|
4397
|
+
* @example Restore a CSV backup into the connected store
|
|
4393
4398
|
* ```typescript
|
|
4394
|
-
*
|
|
4395
|
-
* for (const line of blob.split("\n").slice(1)) {
|
|
4396
|
-
* const [id, name, data, stream, version, created, meta] = parse(line);
|
|
4397
|
-
* yield {
|
|
4398
|
-
* id: +id, name, data: JSON.parse(data), stream,
|
|
4399
|
-
* version: +version, created: new Date(created),
|
|
4400
|
-
* meta: JSON.parse(meta),
|
|
4401
|
-
* };
|
|
4402
|
-
* }
|
|
4403
|
-
* }
|
|
4404
|
-
* const result = await app.restore(parseCsv(csvBlob), {});
|
|
4399
|
+
* const result = await app.restore(new CsvFile({ path: "./backup.csv" }));
|
|
4405
4400
|
* console.log(`Restored ${result.kept} events in ${result.duration_ms}ms`);
|
|
4406
4401
|
* await cache().clear(); // operator's responsibility
|
|
4407
4402
|
* ```
|
|
4408
4403
|
*
|
|
4404
|
+
* @example Copy one store into another
|
|
4405
|
+
* ```typescript
|
|
4406
|
+
* await app.restore(pg, {}, sqlite);
|
|
4407
|
+
* ```
|
|
4408
|
+
*
|
|
4409
4409
|
* @see {@link Store.restore} for the underlying driver-pattern primitive.
|
|
4410
4410
|
*/
|
|
4411
4411
|
async restore(source, opts = {}, sink) {
|