@rotorsoft/act 1.32.19 → 1.32.21
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/internal/correlate-cycle.d.ts +18 -1
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/defer-timer.d.ts +15 -5
- package/dist/@types/internal/defer-timer.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +3 -0
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/index.cjs +89 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +89 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1665,13 +1665,14 @@ var CorrelateCycle = class {
|
|
|
1665
1665
|
cd,
|
|
1666
1666
|
max_subscribed_streams,
|
|
1667
1667
|
declared_lanes,
|
|
1668
|
+
shard,
|
|
1668
1669
|
on_init,
|
|
1669
1670
|
on_init_async,
|
|
1670
1671
|
cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN,
|
|
1671
1672
|
lease_millis = DEFAULT_CORRELATION_LEASE_MS
|
|
1672
1673
|
}) {
|
|
1673
1674
|
this._lease_millis = lease_millis;
|
|
1674
|
-
this._key = registry_key(registry.events);
|
|
1675
|
+
this._key = shard ? `${registry_key(registry.events)}:${[...shard].sort().join(",")}` : registry_key(registry.events);
|
|
1675
1676
|
this._dynamic_subscriptions = new LruMap(max_subscribed_streams);
|
|
1676
1677
|
this._registry = registry;
|
|
1677
1678
|
this._declared_lanes = declared_lanes;
|
|
@@ -1838,6 +1839,8 @@ var CorrelateCycle = class {
|
|
|
1838
1839
|
priority: carried.priority,
|
|
1839
1840
|
lane: carried.lane,
|
|
1840
1841
|
upgraded,
|
|
1842
|
+
missed: !recorded,
|
|
1843
|
+
handler: reaction.handler.name,
|
|
1841
1844
|
correlated_at: void 0
|
|
1842
1845
|
};
|
|
1843
1846
|
const seen_in_scan = correlated.has(resolved.target);
|
|
@@ -1856,6 +1859,7 @@ var CorrelateCycle = class {
|
|
|
1856
1859
|
entry.priority = carried.priority;
|
|
1857
1860
|
entry.lane = carried.lane;
|
|
1858
1861
|
entry.upgraded = upgraded;
|
|
1862
|
+
entry.handler = reaction.handler.name;
|
|
1859
1863
|
}
|
|
1860
1864
|
if (this._in_fetch_window(resolved.source, event.stream))
|
|
1861
1865
|
entry.correlated_at = event.id;
|
|
@@ -1869,6 +1873,7 @@ var CorrelateCycle = class {
|
|
|
1869
1873
|
// actor-less, so plaintext here would be un-gated disclosure.
|
|
1870
1874
|
{ ...query, after, with_pii: false }
|
|
1871
1875
|
);
|
|
1876
|
+
if (this._declared_lanes.size > 1) await this._recall(correlated);
|
|
1872
1877
|
const streams = [];
|
|
1873
1878
|
for (const [stream, entry] of correlated) {
|
|
1874
1879
|
if (entry.upgraded || entry.correlated_at !== void 0)
|
|
@@ -1907,6 +1912,50 @@ var CorrelateCycle = class {
|
|
|
1907
1912
|
this._armed = found === query.limit;
|
|
1908
1913
|
return { subscribed: 0, last_id, marked: 0, scanned: true };
|
|
1909
1914
|
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Give each target this scan had no record of the record its row holds.
|
|
1917
|
+
*
|
|
1918
|
+
* A target with no record is either new or forgotten. Its row, when it
|
|
1919
|
+
* has one, says what lane and priority it was subscribed at; a resolution
|
|
1920
|
+
* that doesn't beat that priority carries the row's values, exactly as if
|
|
1921
|
+
* the record had never been evicted. One read per target with no record,
|
|
1922
|
+
* and only when more than one lane is declared: with one lane there is no
|
|
1923
|
+
* lane to lose, and the store already keeps the higher priority.
|
|
1924
|
+
*/
|
|
1925
|
+
async _recall(correlated) {
|
|
1926
|
+
const missed = [...correlated].filter(([, entry]) => entry.missed);
|
|
1927
|
+
await Promise.all(
|
|
1928
|
+
missed.map(async ([stream, entry]) => {
|
|
1929
|
+
let row;
|
|
1930
|
+
await store().query_streams(
|
|
1931
|
+
(position) => {
|
|
1932
|
+
row = position;
|
|
1933
|
+
},
|
|
1934
|
+
{ stream, stream_exact: true, limit: 1 }
|
|
1935
|
+
);
|
|
1936
|
+
if (!row) return;
|
|
1937
|
+
this._dynamic_subscriptions.set(stream, {
|
|
1938
|
+
floor: row.priority,
|
|
1939
|
+
priority: row.priority,
|
|
1940
|
+
lane: row.lane
|
|
1941
|
+
});
|
|
1942
|
+
if (entry.priority > row.priority) return;
|
|
1943
|
+
const held_lane = row.lane ?? DEFAULT_LANE;
|
|
1944
|
+
const resolved_lane = entry.lane ?? DEFAULT_LANE;
|
|
1945
|
+
if (entry.priority === row.priority && held_lane !== resolved_lane)
|
|
1946
|
+
report_lane_conflict(
|
|
1947
|
+
this._reported,
|
|
1948
|
+
entry.handler,
|
|
1949
|
+
stream,
|
|
1950
|
+
held_lane,
|
|
1951
|
+
resolved_lane
|
|
1952
|
+
);
|
|
1953
|
+
entry.priority = row.priority;
|
|
1954
|
+
entry.lane = row.lane;
|
|
1955
|
+
entry.upgraded = false;
|
|
1956
|
+
})
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1910
1959
|
/**
|
|
1911
1960
|
* Hand the correlation lease back early.
|
|
1912
1961
|
*
|
|
@@ -2066,9 +2115,8 @@ var DeferTimer = class {
|
|
|
2066
2115
|
_timer;
|
|
2067
2116
|
_on_wake;
|
|
2068
2117
|
/**
|
|
2069
|
-
* @param on_wake - invoked once each time the earliest due-time elapses
|
|
2070
|
-
*
|
|
2071
|
-
* for whatever is still parked.
|
|
2118
|
+
* @param on_wake - invoked once each time the earliest due-time elapses.
|
|
2119
|
+
* The consumer re-schedules for whatever is still parked.
|
|
2072
2120
|
*/
|
|
2073
2121
|
constructor(on_wake) {
|
|
2074
2122
|
this._on_wake = on_wake;
|
|
@@ -2098,6 +2146,16 @@ var DeferTimer = class {
|
|
|
2098
2146
|
delete(stream) {
|
|
2099
2147
|
this._due.delete(stream);
|
|
2100
2148
|
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Drop every entry due at or before `before`. The drain calls it when a
|
|
2151
|
+
* pass that started at `before` claimed nothing: whatever was due by then
|
|
2152
|
+
* was handled elsewhere (another worker) or has no work left, so waking
|
|
2153
|
+
* for it again would spin.
|
|
2154
|
+
*/
|
|
2155
|
+
prune(before) {
|
|
2156
|
+
for (const [stream, at] of this._due)
|
|
2157
|
+
if (at <= before) this._due.delete(stream);
|
|
2158
|
+
}
|
|
2101
2159
|
/**
|
|
2102
2160
|
* (Re)schedule the wake timer at the earliest pending due-time. Idempotent
|
|
2103
2161
|
* — collapses many parked streams into a single timer. A no-op clears any
|
|
@@ -2120,9 +2178,6 @@ var DeferTimer = class {
|
|
|
2120
2178
|
);
|
|
2121
2179
|
this._timer = setTimeout(() => {
|
|
2122
2180
|
this._timer = void 0;
|
|
2123
|
-
const now = Date.now();
|
|
2124
|
-
for (const [stream, at] of this._due)
|
|
2125
|
-
if (at <= now) this._due.delete(stream);
|
|
2126
2181
|
this._on_wake();
|
|
2127
2182
|
}, delay);
|
|
2128
2183
|
this._timer.unref();
|
|
@@ -2891,6 +2946,9 @@ var DrainController = class {
|
|
|
2891
2946
|
* schedule itself is persisted in the store; this runs the drain at the
|
|
2892
2947
|
* earliest pending visit. A parked stream already carries its mark, so
|
|
2893
2948
|
* the drain alone finds it, and on an idle app nothing else would.
|
|
2949
|
+
* Every drain re-aims it when it ends, and a due entry stays until a pass
|
|
2950
|
+
* handles it or a pass that started after it came due claims nothing, so
|
|
2951
|
+
* the wake keeps draining until every due stream is reached.
|
|
2894
2952
|
* `stop()` cancels the timer and a stopped drain never re-schedules it,
|
|
2895
2953
|
* so a wake never runs after a stop.
|
|
2896
2954
|
*/
|
|
@@ -2988,15 +3046,16 @@ var DrainController = class {
|
|
|
2988
3046
|
const streamLimit = d.streamLimit ?? options.streamLimit ?? DEFAULT_STREAM_LIMIT;
|
|
2989
3047
|
const eventLimit = d.eventLimit ?? options.eventLimit ?? DEFAULT_EVENT_LIMIT;
|
|
2990
3048
|
const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? DEFAULT_LEASE_MILLIS;
|
|
3049
|
+
if (this._deps.breaker.state(Date.now()) === "open")
|
|
3050
|
+
return EMPTY_DRAIN;
|
|
2991
3051
|
try {
|
|
2992
|
-
if (this._deps.breaker.state(Date.now()) === "open")
|
|
2993
|
-
return EMPTY_DRAIN;
|
|
2994
3052
|
this._locked = true;
|
|
2995
3053
|
this._inflight = new Promise((done) => {
|
|
2996
3054
|
this._inflight_done = done;
|
|
2997
3055
|
});
|
|
2998
3056
|
const lagging = Math.ceil(streamLimit * this._ratio);
|
|
2999
3057
|
const leading = streamLimit - lagging;
|
|
3058
|
+
const started = Date.now();
|
|
3000
3059
|
const cycle = await run_drain_cycle(this._deps, {
|
|
3001
3060
|
lagging,
|
|
3002
3061
|
leading,
|
|
@@ -3006,6 +3065,7 @@ var DrainController = class {
|
|
|
3006
3065
|
store_failing: this._deps.breaker.failing
|
|
3007
3066
|
});
|
|
3008
3067
|
if (!cycle) {
|
|
3068
|
+
this._defer.prune(started);
|
|
3009
3069
|
this._deps.breaker.passed();
|
|
3010
3070
|
this._armed = false;
|
|
3011
3071
|
return EMPTY_DRAIN;
|
|
@@ -3671,6 +3731,8 @@ var Act = class {
|
|
|
3671
3731
|
// Declared lanes (not active ones): a lane another process claims is
|
|
3672
3732
|
// still a valid destination.
|
|
3673
3733
|
declared_lanes: new Set(this._declared_lanes),
|
|
3734
|
+
// Built after the controllers, so their keys are the active lanes.
|
|
3735
|
+
shard: this._drain_controllers.size < this._declared_lanes.length ? [...this._drain_controllers.keys()] : void 0,
|
|
3674
3736
|
on_init: () => {
|
|
3675
3737
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
3676
3738
|
},
|
|
@@ -4367,11 +4429,11 @@ var Act = class {
|
|
|
4367
4429
|
});
|
|
4368
4430
|
}
|
|
4369
4431
|
/**
|
|
4370
|
-
* Atomically wipe the store and rebuild it from an
|
|
4371
|
-
*
|
|
4372
|
-
* `
|
|
4373
|
-
*
|
|
4374
|
-
*
|
|
4432
|
+
* Atomically wipe the store and rebuild it from an event source. The
|
|
4433
|
+
* framework owns iteration, validation, `drop_snapshots` filtering,
|
|
4434
|
+
* `on_progress`, and the per-call `old → new` causation remap; the
|
|
4435
|
+
* adapter's {@link Store.restore} driver supplies the transaction
|
|
4436
|
+
* lifecycle and per-event insert.
|
|
4375
4437
|
*
|
|
4376
4438
|
* Throws if the adapter has no restore capability. Throws on the
|
|
4377
4439
|
* first invalid event (negative version, malformed `created`) with
|
|
@@ -4379,33 +4441,30 @@ var Act = class {
|
|
|
4379
4441
|
* the adapter means a failing restore leaves the store byte-for-byte
|
|
4380
4442
|
* unchanged.
|
|
4381
4443
|
*
|
|
4382
|
-
* @param source -
|
|
4383
|
-
* rather than buffered so
|
|
4384
|
-
* Each event's original `id`
|
|
4385
|
-
* never written through —
|
|
4444
|
+
* @param source - Any {@link EventSource}: a `CsvFile` backup or
|
|
4445
|
+
* another store. Read in batches rather than buffered, so
|
|
4446
|
+
* multi-million-event backups don't OOM. Each event's original `id`
|
|
4447
|
+
* is used as a causation lookup key but never written through —
|
|
4448
|
+
* adapters renumber densely.
|
|
4386
4449
|
* @param opts - {@link ScanOptions}. `drop_snapshots` skips
|
|
4387
4450
|
* `__snapshot__` events (counted in the result); `on_progress`
|
|
4388
4451
|
* fires once per event.
|
|
4452
|
+
* @param sink - Where the events land. Defaults to this Act's store.
|
|
4389
4453
|
* @returns {@link ScanResult} with `kept`, `duration_ms`, and
|
|
4390
4454
|
* `dropped` per-category counters.
|
|
4391
4455
|
*
|
|
4392
|
-
* @example
|
|
4456
|
+
* @example Restore a CSV backup into the connected store
|
|
4393
4457
|
* ```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), {});
|
|
4458
|
+
* const result = await app.restore(new CsvFile({ path: "./backup.csv" }));
|
|
4405
4459
|
* console.log(`Restored ${result.kept} events in ${result.duration_ms}ms`);
|
|
4406
4460
|
* await cache().clear(); // operator's responsibility
|
|
4407
4461
|
* ```
|
|
4408
4462
|
*
|
|
4463
|
+
* @example Copy one store into another
|
|
4464
|
+
* ```typescript
|
|
4465
|
+
* await app.restore(pg, {}, sqlite);
|
|
4466
|
+
* ```
|
|
4467
|
+
*
|
|
4409
4468
|
* @see {@link Store.restore} for the underlying driver-pattern primitive.
|
|
4410
4469
|
*/
|
|
4411
4470
|
async restore(source, opts = {}, sink) {
|