@rotorsoft/act 1.32.25 → 1.32.27
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 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +5 -151
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +0 -21
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts +0 -25
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/builders/build-classify.d.ts +4 -4
- package/dist/@types/builders/build-classify.d.ts.map +1 -1
- package/dist/@types/builders/projection-builder.d.ts.map +1 -1
- package/dist/@types/builders/slice-builder.d.ts +1 -12
- package/dist/@types/builders/slice-builder.d.ts.map +1 -1
- package/dist/@types/builders/state-builder.d.ts +0 -189
- package/dist/@types/builders/state-builder.d.ts.map +1 -1
- package/dist/@types/config.d.ts +0 -27
- package/dist/@types/config.d.ts.map +1 -1
- package/dist/@types/internal/close-cycle.d.ts +2 -1
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +0 -7
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/test/sandbox.d.ts +0 -14
- package/dist/@types/test/sandbox.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +0 -27
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/errors.d.ts +1 -92
- package/dist/@types/types/errors.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +0 -70
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +0 -33
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/{chunk-QXW3JV2H.js → chunk-5FXT63KF.js} +2 -2
- package/dist/chunk-5FXT63KF.js.map +1 -0
- package/dist/{chunk-XSG2NW4J.js → chunk-F7K7SIG6.js} +1 -1
- package/dist/chunk-F7K7SIG6.js.map +1 -0
- package/dist/index.cjs +79 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +81 -167
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +2 -2
- package/dist/test/index.js.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +1 -1
- package/package.json +1 -4
- package/dist/chunk-QXW3JV2H.js.map +0 -1
- package/dist/chunk-XSG2NW4J.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
sleep,
|
|
27
27
|
store,
|
|
28
28
|
validate
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-5FXT63KF.js";
|
|
30
30
|
import {
|
|
31
31
|
ActorSchema,
|
|
32
32
|
CausationEventSchema,
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
pii_split,
|
|
54
54
|
pii_strip,
|
|
55
55
|
sensitive
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-F7K7SIG6.js";
|
|
57
57
|
|
|
58
58
|
// src/signals.ts
|
|
59
59
|
process.once("SIGINT", async (arg) => {
|
|
@@ -81,10 +81,8 @@ var ALL_LANES = /* @__PURE__ */ Symbol("act-1103/all-lanes");
|
|
|
81
81
|
function classify_registry(registry, states) {
|
|
82
82
|
const statics = /* @__PURE__ */ new Map();
|
|
83
83
|
const target_lanes = /* @__PURE__ */ new Map();
|
|
84
|
-
const reactive_events = /* @__PURE__ */ new Set();
|
|
85
84
|
const event_to_lanes = /* @__PURE__ */ new Map();
|
|
86
85
|
for (const [name, register] of Object.entries(registry.events)) {
|
|
87
|
-
if (register.reactions.size > 0) reactive_events.add(name);
|
|
88
86
|
for (const reaction of register.reactions.values()) {
|
|
89
87
|
if (typeof reaction.resolver === "function") {
|
|
90
88
|
event_to_lanes.set(name, ALL_LANES);
|
|
@@ -122,7 +120,6 @@ function classify_registry(registry, states) {
|
|
|
122
120
|
}
|
|
123
121
|
return {
|
|
124
122
|
static_targets: [...statics.values()],
|
|
125
|
-
reactive_events,
|
|
126
123
|
event_to_state,
|
|
127
124
|
event_to_lanes
|
|
128
125
|
};
|
|
@@ -1279,7 +1276,7 @@ async function run_full_closes(full, target_map, deps, skipped) {
|
|
|
1279
1276
|
}
|
|
1280
1277
|
const safe = await partition_by_safety(
|
|
1281
1278
|
stream_info,
|
|
1282
|
-
deps.
|
|
1279
|
+
deps.reactive,
|
|
1283
1280
|
skipped,
|
|
1284
1281
|
deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE,
|
|
1285
1282
|
deps.catch_up_correlation
|
|
@@ -1316,8 +1313,8 @@ async function run_full_closes(full, target_map, deps, skipped) {
|
|
|
1316
1313
|
);
|
|
1317
1314
|
}
|
|
1318
1315
|
async function run_windowed_closes(windowed, deps, skipped) {
|
|
1319
|
-
const checkpoint = deps.
|
|
1320
|
-
const min_at = deps.
|
|
1316
|
+
const checkpoint = deps.reactive ? await deps.catch_up_correlation(Number.MAX_SAFE_INTEGER) : -1;
|
|
1317
|
+
const min_at = deps.reactive ? await probe_min_watermarks(
|
|
1321
1318
|
windowed.map((t) => t.stream),
|
|
1322
1319
|
deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE,
|
|
1323
1320
|
checkpoint
|
|
@@ -1405,8 +1402,8 @@ async function scan_stream_heads(streams) {
|
|
|
1405
1402
|
}
|
|
1406
1403
|
return out;
|
|
1407
1404
|
}
|
|
1408
|
-
async function partition_by_safety(stream_info,
|
|
1409
|
-
if (
|
|
1405
|
+
async function partition_by_safety(stream_info, reactive, skipped, page_size, catch_up_correlation2) {
|
|
1406
|
+
if (!reactive) return [...stream_info.keys()];
|
|
1410
1407
|
let needed = -1;
|
|
1411
1408
|
for (const info of stream_info.values())
|
|
1412
1409
|
needed = Math.max(needed, info.max_id);
|
|
@@ -1692,13 +1689,25 @@ var CorrelateCycle = class {
|
|
|
1692
1689
|
const after = Math.max(this._checkpoint, query.after || -1);
|
|
1693
1690
|
const correlated = /* @__PURE__ */ new Map();
|
|
1694
1691
|
let last_id = after;
|
|
1692
|
+
let stopped = false;
|
|
1695
1693
|
const found = await store().query(
|
|
1696
1694
|
(event) => {
|
|
1697
|
-
|
|
1695
|
+
if (stopped) return;
|
|
1698
1696
|
const register = this._registry.events[event.name];
|
|
1699
1697
|
if (register) {
|
|
1700
1698
|
for (const reaction of register.reactions.values()) {
|
|
1701
|
-
|
|
1699
|
+
let resolved;
|
|
1700
|
+
try {
|
|
1701
|
+
resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
stopped = true;
|
|
1704
|
+
report_once(
|
|
1705
|
+
this._reported,
|
|
1706
|
+
`resolver|${reaction.handler.name}|${event.id}`,
|
|
1707
|
+
`Resolver of reaction "${reaction.handler.name}" threw on ${String(event.name)} #${event.id} (stream "${event.stream}"): ${String(error)}. Correlation stops before this event until the resolver stops throwing; work already found keeps draining.`
|
|
1708
|
+
);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1702
1711
|
if (!resolved) continue;
|
|
1703
1712
|
let lane = resolved.lane;
|
|
1704
1713
|
if (lane !== void 0 && !this._declared_lanes.has(lane)) {
|
|
@@ -1747,6 +1756,7 @@ var CorrelateCycle = class {
|
|
|
1747
1756
|
correlated.set(resolved.target, entry);
|
|
1748
1757
|
}
|
|
1749
1758
|
}
|
|
1759
|
+
last_id = event.id;
|
|
1750
1760
|
},
|
|
1751
1761
|
// Resolvers see `pii: null`: correlate runs actor-less, and one
|
|
1752
1762
|
// undecryptable row would otherwise stop every stream's reactions.
|
|
@@ -1788,7 +1798,7 @@ var CorrelateCycle = class {
|
|
|
1788
1798
|
return { subscribed, last_id, marked, scanned: true };
|
|
1789
1799
|
}
|
|
1790
1800
|
this._checkpoint = last_id;
|
|
1791
|
-
this._armed = found === query.limit;
|
|
1801
|
+
this._armed = stopped || found === query.limit;
|
|
1792
1802
|
return { subscribed: 0, last_id, marked: 0, scanned: true };
|
|
1793
1803
|
}
|
|
1794
1804
|
/**
|
|
@@ -2672,6 +2682,22 @@ function fetch_failure_policy(registry) {
|
|
|
2672
2682
|
}
|
|
2673
2683
|
return { blockOnError, maxRetries };
|
|
2674
2684
|
}
|
|
2685
|
+
function resolver_failed(lease, { error, options }) {
|
|
2686
|
+
log().error(error);
|
|
2687
|
+
const block2 = options.blockOnError && lease.retry >= options.maxRetries;
|
|
2688
|
+
return {
|
|
2689
|
+
lease,
|
|
2690
|
+
handled: 0,
|
|
2691
|
+
acked_at: lease.at,
|
|
2692
|
+
error,
|
|
2693
|
+
...block2 ? { block: true } : {
|
|
2694
|
+
next_attempt_at: Date.now() + compute_backoff_delay(
|
|
2695
|
+
lease.retry,
|
|
2696
|
+
options.backoff ?? DEFAULT_BACKOFF
|
|
2697
|
+
)
|
|
2698
|
+
}
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2675
2701
|
function warn_misrouted(seen, stream, handler, event) {
|
|
2676
2702
|
report_once(
|
|
2677
2703
|
seen,
|
|
@@ -2705,13 +2731,23 @@ async function run_drain_cycle(deps, cycle) {
|
|
|
2705
2731
|
);
|
|
2706
2732
|
for (const f of fetched) {
|
|
2707
2733
|
const { stream, events } = f;
|
|
2734
|
+
let failed;
|
|
2708
2735
|
const payloads = events.flatMap((event) => {
|
|
2709
2736
|
const register = registry.events[event.name];
|
|
2710
2737
|
if (!register) return [];
|
|
2711
2738
|
return [...register.reactions.values()].filter((reaction) => {
|
|
2712
2739
|
const resolver = reaction.resolver;
|
|
2713
2740
|
const dynamic = typeof resolver === "function";
|
|
2714
|
-
|
|
2741
|
+
let resolved;
|
|
2742
|
+
try {
|
|
2743
|
+
resolved = dynamic ? resolver(event) : resolver;
|
|
2744
|
+
} catch (error) {
|
|
2745
|
+
failed ??= {
|
|
2746
|
+
error: `Resolver of reaction "${reaction.handler.name}" threw on ${String(event.name)} #${event.id} (stream "${event.stream}"): ${String(error)}`,
|
|
2747
|
+
options: reaction.options
|
|
2748
|
+
};
|
|
2749
|
+
return false;
|
|
2750
|
+
}
|
|
2715
2751
|
if (!resolved || resolved.target !== stream) return false;
|
|
2716
2752
|
if (!dynamic || !batch_handlers.has(stream)) return true;
|
|
2717
2753
|
warn_misrouted(
|
|
@@ -2723,7 +2759,7 @@ async function run_drain_cycle(deps, cycle) {
|
|
|
2723
2759
|
return false;
|
|
2724
2760
|
}).map((reaction) => ({ ...reaction, event }));
|
|
2725
2761
|
});
|
|
2726
|
-
fetch_map.set(stream, { fetch: f, payloads });
|
|
2762
|
+
fetch_map.set(stream, { fetch: f, payloads, failed });
|
|
2727
2763
|
}
|
|
2728
2764
|
const handled = await Promise.all(
|
|
2729
2765
|
leased.map((lease) => {
|
|
@@ -2744,6 +2780,8 @@ async function run_drain_cycle(deps, cycle) {
|
|
|
2744
2780
|
};
|
|
2745
2781
|
return Promise.resolve(failed);
|
|
2746
2782
|
}
|
|
2783
|
+
if (entry.failed)
|
|
2784
|
+
return Promise.resolve(resolver_failed(lease, entry.failed));
|
|
2747
2785
|
const at = entry.fetch.events.at(-1)?.id || fetch_window_at;
|
|
2748
2786
|
const { payloads } = entry;
|
|
2749
2787
|
const exhausted = budget_exhausted(
|
|
@@ -3223,7 +3261,8 @@ var Act = class {
|
|
|
3223
3261
|
_breaker;
|
|
3224
3262
|
_listen;
|
|
3225
3263
|
_drain;
|
|
3226
|
-
|
|
3264
|
+
/** Whether any event has a reaction; nothing arms without one. */
|
|
3265
|
+
_reactive;
|
|
3227
3266
|
_drain_controllers;
|
|
3228
3267
|
_correlate;
|
|
3229
3268
|
_settle;
|
|
@@ -3353,7 +3392,7 @@ var Act = class {
|
|
|
3353
3392
|
});
|
|
3354
3393
|
this._handle_batch = build_handle_batch(this._logger);
|
|
3355
3394
|
const classification = classify_registry(this.registry, this._states);
|
|
3356
|
-
this.
|
|
3395
|
+
this._reactive = classification.event_to_lanes.size > 0;
|
|
3357
3396
|
this._event_to_state = classification.event_to_state;
|
|
3358
3397
|
this._event_to_lanes = classification.event_to_lanes;
|
|
3359
3398
|
this._listen = options.listen !== false;
|
|
@@ -3400,8 +3439,18 @@ var Act = class {
|
|
|
3400
3439
|
on_acked: (acked) => this.emit("acked", acked),
|
|
3401
3440
|
on_blocked: (blocked) => this.emit("blocked", blocked),
|
|
3402
3441
|
// Reaction-requested close: the same machinery as `app.close`.
|
|
3442
|
+
// A store failure reaches the breaker; anything else (the archive
|
|
3443
|
+
// callback is app code) is logged here, never counted.
|
|
3403
3444
|
on_close: async (targets) => {
|
|
3404
|
-
|
|
3445
|
+
try {
|
|
3446
|
+
await this._run_close(targets);
|
|
3447
|
+
} catch (error) {
|
|
3448
|
+
if (error instanceof StoreError) throw error;
|
|
3449
|
+
this._logger.error(
|
|
3450
|
+
error,
|
|
3451
|
+
`Reaction-requested close of ${targets.map((t) => `"${t.stream}"`).join(", ")} failed and was not retried; close it with app.close(...) once the cause is fixed.`
|
|
3452
|
+
);
|
|
3453
|
+
}
|
|
3405
3454
|
},
|
|
3406
3455
|
breaker: this._breaker,
|
|
3407
3456
|
run_scoped: this._scoped,
|
|
@@ -3450,9 +3499,8 @@ var Act = class {
|
|
|
3450
3499
|
declared_lanes: new Set(this._declared_lanes),
|
|
3451
3500
|
// Built after the controllers, so their keys are the active lanes.
|
|
3452
3501
|
shard: this._drain_controllers.size < this._declared_lanes.length ? [...this._drain_controllers.keys()] : void 0,
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
},
|
|
3502
|
+
// Only reached on a draining app: writer-only ones never correlate.
|
|
3503
|
+
on_init: () => this._arm_all(),
|
|
3456
3504
|
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
3457
3505
|
});
|
|
3458
3506
|
}
|
|
@@ -3549,7 +3597,7 @@ var Act = class {
|
|
|
3549
3597
|
}
|
|
3550
3598
|
/** Failures are logged, never thrown: `notify` is a hint. */
|
|
3551
3599
|
async _wire_notify(s) {
|
|
3552
|
-
if (this.
|
|
3600
|
+
if (!this._reactive) return void 0;
|
|
3553
3601
|
if (!s.notify) return void 0;
|
|
3554
3602
|
if (!this._listen) return void 0;
|
|
3555
3603
|
try {
|
|
@@ -3609,46 +3657,6 @@ var Act = class {
|
|
|
3609
3657
|
* console.log(snapshots[0].state.count); // Current count after increment
|
|
3610
3658
|
* ```
|
|
3611
3659
|
*
|
|
3612
|
-
* @example With error handling
|
|
3613
|
-
* ```typescript
|
|
3614
|
-
* try {
|
|
3615
|
-
* await app.do(
|
|
3616
|
-
* "withdraw",
|
|
3617
|
-
* { stream: "account-123", actor: { id: "user1", name: "Alice" } },
|
|
3618
|
-
* { amount: 1000 }
|
|
3619
|
-
* );
|
|
3620
|
-
* } catch (error) {
|
|
3621
|
-
* if (error instanceof InvariantError) {
|
|
3622
|
-
* console.error("Business rule violated:", error.description);
|
|
3623
|
-
* } else if (error instanceof ConcurrencyError) {
|
|
3624
|
-
* console.error("Concurrent modification detected, retry...");
|
|
3625
|
-
* } else if (error instanceof ValidationError) {
|
|
3626
|
-
* console.error("Invalid payload:", error.details);
|
|
3627
|
-
* }
|
|
3628
|
-
* }
|
|
3629
|
-
* ```
|
|
3630
|
-
*
|
|
3631
|
-
* @example Reaction triggering another action (reactingTo auto-injected)
|
|
3632
|
-
* ```typescript
|
|
3633
|
-
* const app = act()
|
|
3634
|
-
* .withState(Order)
|
|
3635
|
-
* .withState(Inventory)
|
|
3636
|
-
* .on("OrderPlaced")
|
|
3637
|
-
* .do(async function reduceInventory(event, _stream, app) {
|
|
3638
|
-
* // Inside reaction handlers, reactingTo is auto-injected when omitted.
|
|
3639
|
-
* // The triggering event is used by default, maintaining the correlation chain.
|
|
3640
|
-
* await app.do(
|
|
3641
|
-
* "reduceStock",
|
|
3642
|
-
* { stream: "inventory-1", actor: { id: "sys", name: "system" } },
|
|
3643
|
-
* { amount: event.data.items.length }
|
|
3644
|
-
* );
|
|
3645
|
-
* // To use a different correlation, pass reactingTo explicitly:
|
|
3646
|
-
* // await app.do("reduceStock", target, payload, { reactingTo: customEvent });
|
|
3647
|
-
* })
|
|
3648
|
-
* .to("inventory-1")
|
|
3649
|
-
* .build();
|
|
3650
|
-
* ```
|
|
3651
|
-
*
|
|
3652
3660
|
* @see {@link Target} for target structure
|
|
3653
3661
|
* @see {@link Snapshot} for return value structure
|
|
3654
3662
|
* @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
|
|
@@ -3664,10 +3672,9 @@ var Act = class {
|
|
|
3664
3672
|
payload,
|
|
3665
3673
|
do_options
|
|
3666
3674
|
);
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
);
|
|
3675
|
+
this._arm_for_event_names(
|
|
3676
|
+
snapshots.map((s) => s.event.name)
|
|
3677
|
+
);
|
|
3671
3678
|
this.emit("committed", snapshots);
|
|
3672
3679
|
return snapshots;
|
|
3673
3680
|
});
|
|
@@ -3714,26 +3721,6 @@ var Act = class {
|
|
|
3714
3721
|
* console.log(`Found ${count} events from ${first?.id} to ${last?.id}`);
|
|
3715
3722
|
* ```
|
|
3716
3723
|
*
|
|
3717
|
-
* @example Query specific event types
|
|
3718
|
-
* ```typescript
|
|
3719
|
-
* const { count } = await app.query(
|
|
3720
|
-
* { name: "UserCreated", limit: 100 },
|
|
3721
|
-
* (event) => {
|
|
3722
|
-
* console.log("User created:", event.data.email);
|
|
3723
|
-
* }
|
|
3724
|
-
* );
|
|
3725
|
-
* ```
|
|
3726
|
-
*
|
|
3727
|
-
* @example Query events in time range
|
|
3728
|
-
* ```typescript
|
|
3729
|
-
* const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
3730
|
-
* const { count } = await app.query({
|
|
3731
|
-
* created_after: yesterday,
|
|
3732
|
-
* stream: "user-123"
|
|
3733
|
-
* });
|
|
3734
|
-
* console.log(`User had ${count} events in last 24 hours`);
|
|
3735
|
-
* ```
|
|
3736
|
-
*
|
|
3737
3724
|
* @see {@link query_array} for loading events into memory
|
|
3738
3725
|
*/
|
|
3739
3726
|
async query(query, callback) {
|
|
@@ -3765,14 +3752,6 @@ var Act = class {
|
|
|
3765
3752
|
* events.forEach(event => console.log(event.name, event.data));
|
|
3766
3753
|
* ```
|
|
3767
3754
|
*
|
|
3768
|
-
* @example Get recent events
|
|
3769
|
-
* ```typescript
|
|
3770
|
-
* const recent = await app.query_array({
|
|
3771
|
-
* stream: "user-123",
|
|
3772
|
-
* limit: 10
|
|
3773
|
-
* });
|
|
3774
|
-
* ```
|
|
3775
|
-
*
|
|
3776
3755
|
* @see {@link query} for large result sets
|
|
3777
3756
|
*/
|
|
3778
3757
|
async query_array(query) {
|
|
@@ -3843,12 +3822,6 @@ var Act = class {
|
|
|
3843
3822
|
* await app.drain();
|
|
3844
3823
|
* ```
|
|
3845
3824
|
*
|
|
3846
|
-
* @example In production, prefer settle()
|
|
3847
|
-
* ```typescript
|
|
3848
|
-
* await app.do("CreateItem", target, input);
|
|
3849
|
-
* app.settle(); // debounced correlate→drain, emits "settled"
|
|
3850
|
-
* ```
|
|
3851
|
-
*
|
|
3852
3825
|
* @see {@link settle} for debounced correlate→drain with lifecycle events
|
|
3853
3826
|
* @see {@link correlate} for dynamic stream discovery
|
|
3854
3827
|
* @see {@link start_correlations} for automatic correlation
|
|
@@ -3860,6 +3833,7 @@ var Act = class {
|
|
|
3860
3833
|
return this._scoped(() => this._drain_all(options));
|
|
3861
3834
|
}
|
|
3862
3835
|
_arm_all() {
|
|
3836
|
+
if (!this._reactive) return;
|
|
3863
3837
|
this._correlate.arm();
|
|
3864
3838
|
for (const c of this._drain_controllers.values()) c.arm();
|
|
3865
3839
|
}
|
|
@@ -3915,37 +3889,20 @@ var Act = class {
|
|
|
3915
3889
|
* on reaction resolvers. It then registers these streams so they'll be picked up by
|
|
3916
3890
|
* the next drain cycle.
|
|
3917
3891
|
*
|
|
3918
|
-
* @param query - Query filter to scan for new correlations
|
|
3919
3892
|
* @param query - Scan filter — see {@link Query} for fields (typically
|
|
3920
3893
|
* `{ after: <event-id>, limit: <count> }`)
|
|
3921
|
-
* @returns
|
|
3894
|
+
* @returns The number of newly subscribed streams and the last scanned event id
|
|
3922
3895
|
*
|
|
3923
3896
|
* @example Manual correlation
|
|
3924
3897
|
* ```typescript
|
|
3925
3898
|
* // Scan for new streams
|
|
3926
|
-
* const {
|
|
3927
|
-
* console.log(`
|
|
3899
|
+
* const { subscribed, last_id } = await app.correlate({ after: 0, limit: 100 });
|
|
3900
|
+
* console.log(`Subscribed ${subscribed} new streams`);
|
|
3928
3901
|
*
|
|
3929
3902
|
* // Save last_id for next scan
|
|
3930
3903
|
* await saveCheckpoint(last_id);
|
|
3931
3904
|
* ```
|
|
3932
3905
|
*
|
|
3933
|
-
* @example Dynamic stream creation
|
|
3934
|
-
* ```typescript
|
|
3935
|
-
* const app = act()
|
|
3936
|
-
* .withState(User)
|
|
3937
|
-
* .withState(UserStats)
|
|
3938
|
-
* .on("UserLoggedIn")
|
|
3939
|
-
* .do(async (event) => ["incrementLoginCount", {}])
|
|
3940
|
-
* .to((event) => ({
|
|
3941
|
-
* target: `stats-${event.stream}` // Dynamic target per user
|
|
3942
|
-
* }))
|
|
3943
|
-
* .build();
|
|
3944
|
-
*
|
|
3945
|
-
* // Discover stats streams as users log in
|
|
3946
|
-
* await app.correlate();
|
|
3947
|
-
* ```
|
|
3948
|
-
*
|
|
3949
3906
|
* @see {@link start_correlations} for automatic periodic correlation
|
|
3950
3907
|
* @see {@link stop_correlations} to stop automatic correlation
|
|
3951
3908
|
*/
|
|
@@ -3958,8 +3915,7 @@ var Act = class {
|
|
|
3958
3915
|
if (!this._drain) return { subscribed: 0, last_id: -1, scanned: false };
|
|
3959
3916
|
return this._scoped(async () => {
|
|
3960
3917
|
const { subscribed, last_id, marked, scanned } = await this._correlate.correlate(query, lease);
|
|
3961
|
-
if (
|
|
3962
|
-
this._arm_all();
|
|
3918
|
+
if (subscribed > 0 || marked > 0) this._arm_all();
|
|
3963
3919
|
if (subscribed > 0) this._on_discovered?.(subscribed);
|
|
3964
3920
|
return { subscribed, last_id, scanned };
|
|
3965
3921
|
});
|
|
@@ -4051,12 +4007,6 @@ var Act = class {
|
|
|
4051
4007
|
* app.settle({ eventLimit: 1000 }); // emits "settled" when fully replayed
|
|
4052
4008
|
* ```
|
|
4053
4009
|
*
|
|
4054
|
-
* @example Rebuild a projection (tests / scripts)
|
|
4055
|
-
* ```typescript
|
|
4056
|
-
* await app.reset(["my-projection"]);
|
|
4057
|
-
* await app.drain({ eventLimit: 1000 }); // small streams: one pass is enough
|
|
4058
|
-
* ```
|
|
4059
|
-
*
|
|
4060
4010
|
* @see {@link Store.reset} for the underlying store primitive
|
|
4061
4011
|
* @see {@link settle} for the debounced full-catch-up loop
|
|
4062
4012
|
*/
|
|
@@ -4065,7 +4015,7 @@ var Act = class {
|
|
|
4065
4015
|
const count = await store().reset(input);
|
|
4066
4016
|
for (const handler of this._batch_handlers.values())
|
|
4067
4017
|
handler[FOLD_RESET]?.();
|
|
4068
|
-
if (count > 0
|
|
4018
|
+
if (count > 0) this._arm_all();
|
|
4069
4019
|
return count;
|
|
4070
4020
|
});
|
|
4071
4021
|
}
|
|
@@ -4099,7 +4049,7 @@ var Act = class {
|
|
|
4099
4049
|
async unblock(input) {
|
|
4100
4050
|
return this._scoped(async () => {
|
|
4101
4051
|
const count = await store().unblock(input);
|
|
4102
|
-
if (count > 0
|
|
4052
|
+
if (count > 0) this._arm_all();
|
|
4103
4053
|
return count;
|
|
4104
4054
|
});
|
|
4105
4055
|
}
|
|
@@ -4135,11 +4085,6 @@ var Act = class {
|
|
|
4135
4085
|
* await cache().clear(); // operator's responsibility
|
|
4136
4086
|
* ```
|
|
4137
4087
|
*
|
|
4138
|
-
* @example Copy one store into another
|
|
4139
|
-
* ```typescript
|
|
4140
|
-
* await app.restore(pg, {}, sqlite);
|
|
4141
|
-
* ```
|
|
4142
|
-
*
|
|
4143
4088
|
* @see {@link Store.restore} for the underlying driver-pattern primitive.
|
|
4144
4089
|
*/
|
|
4145
4090
|
async restore(source, opts = {}, sink) {
|
|
@@ -4278,16 +4223,6 @@ var Act = class {
|
|
|
4278
4223
|
* await app.prioritize({ stream: "^proj-orders$", stream_exact: false }, 10);
|
|
4279
4224
|
* ```
|
|
4280
4225
|
*
|
|
4281
|
-
* @example Drop all audit projections to background
|
|
4282
|
-
* ```typescript
|
|
4283
|
-
* await app.prioritize({ source: "^audit-" }, -5);
|
|
4284
|
-
* ```
|
|
4285
|
-
*
|
|
4286
|
-
* @example Reset everyone to default
|
|
4287
|
-
* ```typescript
|
|
4288
|
-
* await app.prioritize({}, 0);
|
|
4289
|
-
* ```
|
|
4290
|
-
*
|
|
4291
4226
|
* @see {@link Store.prioritize} for the underlying primitive
|
|
4292
4227
|
* @see {@link claim} for how priority biases scheduling
|
|
4293
4228
|
*/
|
|
@@ -4298,7 +4233,7 @@ var Act = class {
|
|
|
4298
4233
|
async _run_close(targets) {
|
|
4299
4234
|
const close_actor = { id: "$close", name: "close" };
|
|
4300
4235
|
const result = await run_close_cycle(targets, {
|
|
4301
|
-
|
|
4236
|
+
reactive: this._reactive,
|
|
4302
4237
|
catch_up_correlation: (until) => catch_up_correlation(
|
|
4303
4238
|
this._correlate,
|
|
4304
4239
|
(limit) => this.correlate({ limit }),
|
|
@@ -4351,27 +4286,6 @@ var Act = class {
|
|
|
4351
4286
|
* { stream: "order-456" },
|
|
4352
4287
|
* ]);
|
|
4353
4288
|
* ```
|
|
4354
|
-
*
|
|
4355
|
-
* @example Close with restart (state loaded automatically after guard)
|
|
4356
|
-
* ```typescript
|
|
4357
|
-
* await app.close([
|
|
4358
|
-
* { stream: "counter-1", restart: true },
|
|
4359
|
-
* { stream: "counter-2" }, // tombstoned
|
|
4360
|
-
* ]);
|
|
4361
|
-
* ```
|
|
4362
|
-
*
|
|
4363
|
-
* @example Windowed close — keep the last 180 days of real events
|
|
4364
|
-
* ```typescript
|
|
4365
|
-
* const cutoff = new Date();
|
|
4366
|
-
* cutoff.setDate(cutoff.getDate() - 180);
|
|
4367
|
-
* await app.close([
|
|
4368
|
-
* {
|
|
4369
|
-
* stream: "ledger-acme",
|
|
4370
|
-
* before: cutoff,
|
|
4371
|
-
* archive: async () => { await archiveToS3("ledger-acme", cutoff); },
|
|
4372
|
-
* },
|
|
4373
|
-
* ]);
|
|
4374
|
-
* ```
|
|
4375
4289
|
*/
|
|
4376
4290
|
async close(targets) {
|
|
4377
4291
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|