@rotorsoft/act 1.31.1 → 1.31.3
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 +3 -3
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/{internal → builders}/build-classify.d.ts +1 -1
- package/dist/@types/{internal → builders}/build-classify.d.ts.map +1 -1
- package/dist/@types/builders/builder-base.d.ts +1 -1
- package/dist/@types/{internal → builders}/builder-utils.d.ts +1 -1
- package/dist/@types/{internal → builders}/builder-utils.d.ts.map +1 -1
- package/dist/@types/{internal → builders}/merge.d.ts +1 -1
- package/dist/@types/{internal → builders}/merge.d.ts.map +1 -1
- package/dist/@types/builders/projection-builder.d.ts.map +1 -1
- package/dist/@types/{internal/reactions.d.ts → builders/reaction-builder.d.ts} +15 -14
- package/dist/@types/builders/reaction-builder.d.ts.map +1 -0
- package/dist/@types/builders/slice-builder.d.ts +7 -0
- package/dist/@types/builders/slice-builder.d.ts.map +1 -1
- package/dist/@types/{internal/disposers.d.ts → disposers.d.ts} +1 -1
- package/dist/@types/disposers.d.ts.map +1 -0
- package/dist/@types/internal/defer-signal.d.ts +1 -1
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +4 -7
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +2 -10
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/scoped.d.ts +86 -0
- package/dist/@types/scoped.d.ts.map +1 -0
- package/dist/{chunk-YUJ7ACSP.js → chunk-IBLR35N5.js} +27 -9
- package/dist/chunk-IBLR35N5.js.map +1 -0
- package/dist/index.cjs +601 -601
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +582 -595
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +5 -4
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +2 -2
- package/dist/@types/internal/disposers.d.ts.map +0 -1
- package/dist/@types/internal/reacting.d.ts +0 -17
- package/dist/@types/internal/reacting.d.ts.map +0 -1
- package/dist/@types/internal/reactions.d.ts.map +0 -1
- package/dist/chunk-YUJ7ACSP.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -77,7 +77,6 @@ __export(index_exports, {
|
|
|
77
77
|
port: () => port,
|
|
78
78
|
projection: () => projection,
|
|
79
79
|
resolveAutocloseConfig: () => resolveAutocloseConfig,
|
|
80
|
-
scoped: () => scoped,
|
|
81
80
|
sensitive: () => sensitive,
|
|
82
81
|
sleep: () => sleep,
|
|
83
82
|
slice: () => slice,
|
|
@@ -87,9 +86,6 @@ __export(index_exports, {
|
|
|
87
86
|
});
|
|
88
87
|
module.exports = __toCommonJS(index_exports);
|
|
89
88
|
|
|
90
|
-
// src/ports.ts
|
|
91
|
-
var import_node_async_hooks = require("async_hooks");
|
|
92
|
-
|
|
93
89
|
// src/adapters/console-logger.ts
|
|
94
90
|
var LEVEL_VALUES = {
|
|
95
91
|
fatal: 60,
|
|
@@ -1576,7 +1572,7 @@ var InMemoryStore = class {
|
|
|
1576
1572
|
}
|
|
1577
1573
|
};
|
|
1578
1574
|
|
|
1579
|
-
// src/
|
|
1575
|
+
// src/disposers.ts
|
|
1580
1576
|
var entries = [];
|
|
1581
1577
|
var prune = () => {
|
|
1582
1578
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
@@ -1606,8 +1602,27 @@ var run_disposers = async () => {
|
|
|
1606
1602
|
}
|
|
1607
1603
|
};
|
|
1608
1604
|
|
|
1609
|
-
// src/
|
|
1605
|
+
// src/scoped.ts
|
|
1606
|
+
var import_node_async_hooks = require("async_hooks");
|
|
1610
1607
|
var scoped = new import_node_async_hooks.AsyncLocalStorage();
|
|
1608
|
+
var reacting = new import_node_async_hooks.AsyncLocalStorage();
|
|
1609
|
+
function make_run_scoped(bag) {
|
|
1610
|
+
return bag ? (fn) => scoped.run(bag, fn) : (fn) => fn();
|
|
1611
|
+
}
|
|
1612
|
+
function current_ports() {
|
|
1613
|
+
return scoped.getStore();
|
|
1614
|
+
}
|
|
1615
|
+
function run_reacting(event, fn) {
|
|
1616
|
+
return reacting.run(event, fn);
|
|
1617
|
+
}
|
|
1618
|
+
function current_reacting() {
|
|
1619
|
+
return reacting.getStore();
|
|
1620
|
+
}
|
|
1621
|
+
function make_reaction_scope(app) {
|
|
1622
|
+
return { app, run: run_reacting };
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// src/ports.ts
|
|
1611
1626
|
var ExitCodes = ["ERROR", "EXIT"];
|
|
1612
1627
|
var adapters = /* @__PURE__ */ new Map();
|
|
1613
1628
|
function port(injector) {
|
|
@@ -1631,13 +1646,13 @@ var _store = port(function store(adapter) {
|
|
|
1631
1646
|
return adapter ?? new InMemoryStore();
|
|
1632
1647
|
});
|
|
1633
1648
|
var store2 = ((adapter) => {
|
|
1634
|
-
return
|
|
1649
|
+
return current_ports()?.store ?? _store(adapter);
|
|
1635
1650
|
});
|
|
1636
1651
|
var _cache = port(function cache(adapter) {
|
|
1637
1652
|
return adapter ?? new InMemoryCache();
|
|
1638
1653
|
});
|
|
1639
1654
|
var cache2 = ((adapter) => {
|
|
1640
|
-
return
|
|
1655
|
+
return current_ports()?.cache ?? _cache(adapter);
|
|
1641
1656
|
});
|
|
1642
1657
|
async function disposeAndExit(code = "EXIT") {
|
|
1643
1658
|
if (code === "ERROR" && config().env === "production") {
|
|
@@ -1683,6 +1698,58 @@ process.once("unhandledRejection", async (arg) => {
|
|
|
1683
1698
|
// src/act.ts
|
|
1684
1699
|
var import_node_events = __toESM(require("events"), 1);
|
|
1685
1700
|
|
|
1701
|
+
// src/builders/build-classify.ts
|
|
1702
|
+
var ALL_LANES = /* @__PURE__ */ Symbol("act-1103/all-lanes");
|
|
1703
|
+
function classify_registry(registry, states) {
|
|
1704
|
+
const statics = /* @__PURE__ */ new Map();
|
|
1705
|
+
const target_lanes = /* @__PURE__ */ new Map();
|
|
1706
|
+
const reactive_events = /* @__PURE__ */ new Set();
|
|
1707
|
+
const event_to_lanes = /* @__PURE__ */ new Map();
|
|
1708
|
+
for (const [name, register] of Object.entries(registry.events)) {
|
|
1709
|
+
if (register.reactions.size > 0) reactive_events.add(name);
|
|
1710
|
+
for (const reaction of register.reactions.values()) {
|
|
1711
|
+
if (typeof reaction.resolver === "function") {
|
|
1712
|
+
event_to_lanes.set(name, ALL_LANES);
|
|
1713
|
+
} else {
|
|
1714
|
+
const { target, source, priority = 0, lane } = reaction.resolver;
|
|
1715
|
+
const lane_name = lane ?? "default";
|
|
1716
|
+
const existing_lanes = event_to_lanes.get(name);
|
|
1717
|
+
if (existing_lanes !== ALL_LANES) {
|
|
1718
|
+
const set = existing_lanes ?? /* @__PURE__ */ new Set();
|
|
1719
|
+
set.add(lane_name);
|
|
1720
|
+
event_to_lanes.set(name, set);
|
|
1721
|
+
}
|
|
1722
|
+
if (!target_lanes.has(target)) {
|
|
1723
|
+
target_lanes.set(target, lane);
|
|
1724
|
+
} else if ((target_lanes.get(target) ?? void 0) !== (lane ?? void 0)) {
|
|
1725
|
+
throw new Error(
|
|
1726
|
+
`Stream "${target}" has conflicting lane assignments ("${target_lanes.get(target) ?? "default"}" vs "${lane ?? "default"}")`
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
const key = `${target}|${source ?? ""}`;
|
|
1730
|
+
const existing = statics.get(key);
|
|
1731
|
+
if (!existing) {
|
|
1732
|
+
statics.set(key, { stream: target, source, priority, lane });
|
|
1733
|
+
} else if (priority > existing.priority) {
|
|
1734
|
+
statics.set(key, { ...existing, priority });
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
const event_to_state = /* @__PURE__ */ new Map();
|
|
1740
|
+
for (const merged of states.values()) {
|
|
1741
|
+
for (const event_name of Object.keys(merged.events)) {
|
|
1742
|
+
event_to_state.set(event_name, merged);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return {
|
|
1746
|
+
static_targets: [...statics.values()],
|
|
1747
|
+
reactive_events,
|
|
1748
|
+
event_to_state,
|
|
1749
|
+
event_to_lanes
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1686
1753
|
// src/internal/event-versions.ts
|
|
1687
1754
|
var VERSION_SUFFIX = /^(.+?)_v(\d+)$/;
|
|
1688
1755
|
function parse(name) {
|
|
@@ -2434,432 +2501,43 @@ function synthesize_autoclose_reactions(registry, states, config2) {
|
|
|
2434
2501
|
}
|
|
2435
2502
|
}
|
|
2436
2503
|
|
|
2437
|
-
// src/internal/
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
const
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
event_to_lanes.set(name, set);
|
|
2457
|
-
}
|
|
2458
|
-
if (!target_lanes.has(target)) {
|
|
2459
|
-
target_lanes.set(target, lane);
|
|
2460
|
-
} else if ((target_lanes.get(target) ?? void 0) !== (lane ?? void 0)) {
|
|
2461
|
-
throw new Error(
|
|
2462
|
-
`Stream "${target}" has conflicting lane assignments ("${target_lanes.get(target) ?? "default"}" vs "${lane ?? "default"}")`
|
|
2463
|
-
);
|
|
2464
|
-
}
|
|
2465
|
-
const key = `${target}|${source ?? ""}`;
|
|
2466
|
-
const existing = statics.get(key);
|
|
2467
|
-
if (!existing) {
|
|
2468
|
-
statics.set(key, { stream: target, source, priority, lane });
|
|
2469
|
-
} else if (priority > existing.priority) {
|
|
2470
|
-
statics.set(key, { ...existing, priority });
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
const event_to_state = /* @__PURE__ */ new Map();
|
|
2476
|
-
for (const merged of states.values()) {
|
|
2477
|
-
for (const event_name of Object.keys(merged.events)) {
|
|
2478
|
-
event_to_state.set(event_name, merged);
|
|
2504
|
+
// src/internal/backoff.ts
|
|
2505
|
+
function compute_backoff_delay(retry, opts) {
|
|
2506
|
+
if (!opts || opts.baseMs <= 0) return 0;
|
|
2507
|
+
const r = Math.max(0, retry);
|
|
2508
|
+
let delay;
|
|
2509
|
+
switch (opts.strategy) {
|
|
2510
|
+
case "fixed":
|
|
2511
|
+
delay = opts.baseMs;
|
|
2512
|
+
break;
|
|
2513
|
+
case "linear":
|
|
2514
|
+
delay = opts.baseMs * (r + 1);
|
|
2515
|
+
break;
|
|
2516
|
+
case "exponential":
|
|
2517
|
+
delay = opts.baseMs * 2 ** r;
|
|
2518
|
+
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
2519
|
+
break;
|
|
2520
|
+
default: {
|
|
2521
|
+
const _never = opts.strategy;
|
|
2522
|
+
throw new Error(`unknown backoff strategy: ${String(_never)}`);
|
|
2479
2523
|
}
|
|
2480
2524
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
reactive_events,
|
|
2484
|
-
event_to_state,
|
|
2485
|
-
event_to_lanes
|
|
2486
|
-
};
|
|
2525
|
+
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
2526
|
+
return Math.max(0, Math.floor(delay));
|
|
2487
2527
|
}
|
|
2488
2528
|
|
|
2489
|
-
// src/internal/
|
|
2490
|
-
var
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
});
|
|
2502
|
-
function resolveReactionConfig(options) {
|
|
2503
|
-
return ReactionOptionsSchema.parse(options);
|
|
2504
|
-
}
|
|
2505
|
-
var ActionOptionsSchema = import_zod6.z.object({
|
|
2506
|
-
maxRetries: import_zod6.z.number().int().min(0).optional(),
|
|
2507
|
-
backoff: BackoffOptionsSchema.optional()
|
|
2508
|
-
});
|
|
2509
|
-
function resolveActionConfig(options) {
|
|
2510
|
-
return ActionOptionsSchema.parse(options);
|
|
2511
|
-
}
|
|
2512
|
-
var LaneConfigSchema = import_zod6.z.object({
|
|
2513
|
-
name: import_zod6.z.string().min(1),
|
|
2514
|
-
leaseMillis: import_zod6.z.number().min(0).optional(),
|
|
2515
|
-
streamLimit: import_zod6.z.number().int().min(0).optional(),
|
|
2516
|
-
cycleMs: import_zod6.z.number().min(0).optional()
|
|
2517
|
-
});
|
|
2518
|
-
function resolveLaneConfig(options) {
|
|
2519
|
-
LaneConfigSchema.parse(options);
|
|
2520
|
-
return options;
|
|
2521
|
-
}
|
|
2522
|
-
var DrainOptionsSchema = import_zod6.z.object({
|
|
2523
|
-
streamLimit: import_zod6.z.number().int().min(0).optional(),
|
|
2524
|
-
eventLimit: import_zod6.z.number().int().min(0).optional(),
|
|
2525
|
-
leaseMillis: import_zod6.z.number().min(0).optional()
|
|
2526
|
-
});
|
|
2527
|
-
function resolveDrainConfig(options) {
|
|
2528
|
-
if (options === void 0) return void 0;
|
|
2529
|
-
DrainOptionsSchema.parse(options);
|
|
2530
|
-
return options;
|
|
2531
|
-
}
|
|
2532
|
-
var SettleOptionsSchema = DrainOptionsSchema.extend({
|
|
2533
|
-
debounceMs: import_zod6.z.number().min(0).optional(),
|
|
2534
|
-
// `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
|
|
2535
|
-
// a non-negative integer — `0` is legal and means "run no passes".
|
|
2536
|
-
maxPasses: import_zod6.z.number().int().min(0).optional()
|
|
2537
|
-
}).loose();
|
|
2538
|
-
function resolveSettleConfig(options) {
|
|
2539
|
-
if (options === void 0) return void 0;
|
|
2540
|
-
SettleOptionsSchema.parse(options);
|
|
2541
|
-
return options;
|
|
2542
|
-
}
|
|
2543
|
-
var MAX_SHUTDOWN_GRACE_MS = 3e4;
|
|
2544
|
-
var DEFAULT_SHUTDOWN_GRACE_MS = 1e4;
|
|
2545
|
-
var ShutdownOptionsSchema = import_zod6.z.object({
|
|
2546
|
-
// `0` is legal and is exactly today's behavior: stop scheduling and
|
|
2547
|
-
// return without waiting.
|
|
2548
|
-
graceMs: import_zod6.z.number().min(0).optional()
|
|
2549
|
-
}).loose();
|
|
2550
|
-
function resolveShutdownConfig(options) {
|
|
2551
|
-
if (options === void 0) return void 0;
|
|
2552
|
-
ShutdownOptionsSchema.parse(options);
|
|
2553
|
-
return options;
|
|
2554
|
-
}
|
|
2555
|
-
var ActOptionsSchema = import_zod6.z.object({
|
|
2556
|
-
maxSubscribedStreams: import_zod6.z.number().int().min(1).optional(),
|
|
2557
|
-
settleDebounceMs: import_zod6.z.number().int().min(0).optional()
|
|
2558
|
-
}).loose();
|
|
2559
|
-
function resolveActConfig(options) {
|
|
2560
|
-
if (options === void 0) return void 0;
|
|
2561
|
-
ActOptionsSchema.parse(options);
|
|
2562
|
-
return options;
|
|
2563
|
-
}
|
|
2564
|
-
var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
|
|
2565
|
-
var DEFAULT_CLOSE_BATCH_SIZE = 64;
|
|
2566
|
-
var DEFAULT_CLOSE_YIELD_MS = 0;
|
|
2567
|
-
var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
|
|
2568
|
-
function is_valid_time_zone(tz) {
|
|
2569
|
-
try {
|
|
2570
|
-
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
2571
|
-
return true;
|
|
2572
|
-
} catch {
|
|
2573
|
-
return false;
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
var AutocloseWindowSchema = import_zod6.z.object({
|
|
2577
|
-
start: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
|
|
2578
|
-
end: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
|
|
2579
|
-
timeZone: import_zod6.z.string().refine(is_valid_time_zone, {
|
|
2580
|
-
message: "autocloseWindow.timeZone must be a valid IANA time zone"
|
|
2581
|
-
}).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
|
|
2582
|
-
}).refine((w) => w.start !== w.end, {
|
|
2583
|
-
message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
|
|
2584
|
-
});
|
|
2585
|
-
var AutocloseOptionsSchema = import_zod6.z.object({
|
|
2586
|
-
autocloseCycleMinutes: import_zod6.z.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
|
|
2587
|
-
closeBatchSize: import_zod6.z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
|
|
2588
|
-
closeYieldMs: import_zod6.z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
|
|
2589
|
-
closeOnError: import_zod6.z.boolean().default(false),
|
|
2590
|
-
autocloseWindow: AutocloseWindowSchema.optional()
|
|
2591
|
-
});
|
|
2592
|
-
function resolveAutocloseConfig(options) {
|
|
2593
|
-
return AutocloseOptionsSchema.parse({
|
|
2594
|
-
autocloseCycleMinutes: options?.autocloseCycleMinutes,
|
|
2595
|
-
closeBatchSize: options?.closeBatchSize,
|
|
2596
|
-
closeYieldMs: options?.closeYieldMs,
|
|
2597
|
-
closeOnError: options?.closeOnError,
|
|
2598
|
-
autocloseWindow: options?.autocloseWindow
|
|
2599
|
-
});
|
|
2600
|
-
}
|
|
2601
|
-
var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
|
|
2602
|
-
var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
|
|
2603
|
-
var CircuitBreakerOptionsSchema = import_zod6.z.object({
|
|
2604
|
-
failureThreshold: import_zod6.z.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
|
|
2605
|
-
cooldownMs: import_zod6.z.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
|
|
2606
|
-
});
|
|
2607
|
-
var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
|
|
2608
|
-
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
2609
|
-
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
2610
|
-
var FoldOptionsSchema = import_zod6.z.object({
|
|
2611
|
-
flushEvery: import_zod6.z.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
2612
|
-
maxCachedStates: import_zod6.z.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
2613
|
-
});
|
|
2614
|
-
function resolveFoldConfig(options) {
|
|
2615
|
-
return FoldOptionsSchema.parse(options);
|
|
2616
|
-
}
|
|
2617
|
-
|
|
2618
|
-
// src/internal/defer-config.ts
|
|
2619
|
-
var import_zod7 = require("zod");
|
|
2620
|
-
var DeferDurationSchema = import_zod7.z.object({
|
|
2621
|
-
days: import_zod7.z.number().positive().optional(),
|
|
2622
|
-
hours: import_zod7.z.number().positive().optional(),
|
|
2623
|
-
minutes: import_zod7.z.number().positive().optional()
|
|
2624
|
-
}).strict().refine((d) => d.days != null || d.hours != null || d.minutes != null, {
|
|
2625
|
-
message: "defer: a duration needs at least one of days, hours, or minutes"
|
|
2626
|
-
});
|
|
2627
|
-
var DeferWhenSchema = import_zod7.z.object({
|
|
2628
|
-
after: DeferDurationSchema.optional(),
|
|
2629
|
-
at: import_zod7.z.date().optional()
|
|
2630
|
-
}).strict().refine((w) => w.after === void 0 !== (w.at === void 0), {
|
|
2631
|
-
message: "defer: specify exactly one of `after` or `at`"
|
|
2632
|
-
});
|
|
2633
|
-
function duration_ms(d) {
|
|
2634
|
-
return (d.days ?? 0) * 864e5 + (d.hours ?? 0) * 36e5 + (d.minutes ?? 0) * 6e4;
|
|
2635
|
-
}
|
|
2636
|
-
function resolve_defer_at(when, event) {
|
|
2637
|
-
const parsed = DeferWhenSchema.parse(when);
|
|
2638
|
-
if (parsed.after) return event.created.getTime() + duration_ms(parsed.after);
|
|
2639
|
-
return parsed.at.getTime();
|
|
2640
|
-
}
|
|
2641
|
-
function assert_defer_when(when) {
|
|
2642
|
-
DeferWhenSchema.parse(when);
|
|
2643
|
-
}
|
|
2644
|
-
function make_deferred(handler, schedule) {
|
|
2645
|
-
const call = handler;
|
|
2646
|
-
const deferred = async (event, stream, app) => {
|
|
2647
|
-
const when = typeof schedule === "function" ? schedule(event) : schedule;
|
|
2648
|
-
if (Date.now() < resolve_defer_at(when, event)) throw new DeferSignal(when);
|
|
2649
|
-
return call(event, stream, app);
|
|
2650
|
-
};
|
|
2651
|
-
Object.defineProperty(deferred, "name", { value: handler.name });
|
|
2652
|
-
return deferred;
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
// src/internal/merge.ts
|
|
2656
|
-
var import_zod8 = require("zod");
|
|
2657
|
-
function base_type_name(zodType) {
|
|
2658
|
-
let t = zodType;
|
|
2659
|
-
while (typeof t.unwrap === "function") {
|
|
2660
|
-
t = t.unwrap();
|
|
2661
|
-
}
|
|
2662
|
-
return t.constructor.name;
|
|
2663
|
-
}
|
|
2664
|
-
function merge_schemas(existing, incoming, state_name) {
|
|
2665
|
-
if (existing instanceof import_zod8.ZodObject && incoming instanceof import_zod8.ZodObject) {
|
|
2666
|
-
const existing_shape = existing.shape;
|
|
2667
|
-
const incoming_shape = incoming.shape;
|
|
2668
|
-
for (const key of Object.keys(incoming_shape)) {
|
|
2669
|
-
if (key in existing_shape) {
|
|
2670
|
-
const existing_base = base_type_name(existing_shape[key]);
|
|
2671
|
-
const incoming_base = base_type_name(incoming_shape[key]);
|
|
2672
|
-
if (existing_base !== incoming_base) {
|
|
2673
|
-
throw new Error(
|
|
2674
|
-
`Schema conflict in "${state_name}": key "${key}" has type "${existing_base}" but incoming partial declares "${incoming_base}"`
|
|
2675
|
-
);
|
|
2676
|
-
}
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
2679
|
-
return existing.extend(incoming_shape);
|
|
2680
|
-
}
|
|
2681
|
-
return existing;
|
|
2682
|
-
}
|
|
2683
|
-
function merge_inits(existing, incoming) {
|
|
2684
|
-
return () => ({ ...existing(), ...incoming() });
|
|
2685
|
-
}
|
|
2686
|
-
function register_state(state2, states, actions, events) {
|
|
2687
|
-
const existing = states.get(state2.name);
|
|
2688
|
-
if (existing) {
|
|
2689
|
-
merge_into_existing(state2, existing, states, actions, events);
|
|
2690
|
-
} else {
|
|
2691
|
-
register_new_state(state2, states, actions, events);
|
|
2692
|
-
}
|
|
2693
|
-
}
|
|
2694
|
-
function register_new_state(state2, states, actions, events) {
|
|
2695
|
-
states.set(state2.name, state2);
|
|
2696
|
-
for (const name of Object.keys(state2.actions)) {
|
|
2697
|
-
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
2698
|
-
actions[name] = state2;
|
|
2699
|
-
}
|
|
2700
|
-
for (const name of Object.keys(state2.events)) {
|
|
2701
|
-
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
2702
|
-
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
function merge_into_existing(state2, existing, states, actions, events) {
|
|
2706
|
-
for (const name of Object.keys(state2.actions)) {
|
|
2707
|
-
if (existing.actions[name] === state2.actions[name]) continue;
|
|
2708
|
-
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
2709
|
-
}
|
|
2710
|
-
for (const name of Object.keys(state2.events)) {
|
|
2711
|
-
if (existing.events[name] === state2.events[name]) continue;
|
|
2712
|
-
if (existing.events[name]) {
|
|
2713
|
-
throw new Error(
|
|
2714
|
-
`Event "${name}" in state "${state2.name}" is declared with different Zod schemas across slices. Cross-slice event schemas must reference the same instance \u2014 extract a shared schema (e.g. \`export const ${name} = z.object({ ... })\` in a shared module) and import it in every slice that declares it.`
|
|
2715
|
-
);
|
|
2716
|
-
}
|
|
2717
|
-
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
2718
|
-
}
|
|
2719
|
-
const merged_patch = merge_patches(existing.patch, state2.patch, state2.name);
|
|
2720
|
-
const merged = {
|
|
2721
|
-
...existing,
|
|
2722
|
-
state: merge_schemas(existing.state, state2.state, state2.name),
|
|
2723
|
-
init: merge_inits(existing.init, state2.init),
|
|
2724
|
-
events: { ...existing.events, ...state2.events },
|
|
2725
|
-
actions: { ...existing.actions, ...state2.actions },
|
|
2726
|
-
patch: merged_patch,
|
|
2727
|
-
on: { ...existing.on, ...state2.on },
|
|
2728
|
-
given: { ...existing.given, ...state2.given },
|
|
2729
|
-
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
2730
|
-
throw new Error(
|
|
2731
|
-
`Duplicate snap strategy for state "${state2.name}"`
|
|
2732
|
-
);
|
|
2733
|
-
})() : state2.snap || existing.snap
|
|
2734
|
-
};
|
|
2735
|
-
states.set(state2.name, merged);
|
|
2736
|
-
for (const name of Object.keys(merged.actions)) {
|
|
2737
|
-
actions[name] = merged;
|
|
2738
|
-
}
|
|
2739
|
-
for (const name of Object.keys(state2.events)) {
|
|
2740
|
-
if (events[name]) continue;
|
|
2741
|
-
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
function merge_patches(existing, incoming, state_name) {
|
|
2745
|
-
const merged = { ...existing };
|
|
2746
|
-
for (const name of Object.keys(incoming)) {
|
|
2747
|
-
const existing_p = existing[name];
|
|
2748
|
-
const incoming_p = incoming[name];
|
|
2749
|
-
if (!existing_p) {
|
|
2750
|
-
merged[name] = incoming_p;
|
|
2751
|
-
continue;
|
|
2752
|
-
}
|
|
2753
|
-
const existing_is_default = existing_p._passthrough;
|
|
2754
|
-
const incoming_is_default = incoming_p._passthrough;
|
|
2755
|
-
if (!existing_is_default && !incoming_is_default && existing_p !== incoming_p) {
|
|
2756
|
-
throw new Error(
|
|
2757
|
-
`Duplicate custom patch for event "${name}" in state "${state_name}"`
|
|
2758
|
-
);
|
|
2759
|
-
}
|
|
2760
|
-
if (existing_is_default && !incoming_is_default) {
|
|
2761
|
-
merged[name] = incoming_p;
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
|
-
return merged;
|
|
2765
|
-
}
|
|
2766
|
-
function merge_event_register(target, source) {
|
|
2767
|
-
for (const [event_name, source_reg] of Object.entries(source)) {
|
|
2768
|
-
const target_reg = target[event_name];
|
|
2769
|
-
if (!target_reg) continue;
|
|
2770
|
-
for (const [name, reaction] of source_reg.reactions) {
|
|
2771
|
-
const existing = target_reg.reactions.get(name);
|
|
2772
|
-
if (existing !== void 0 && existing !== reaction)
|
|
2773
|
-
throw new Error(
|
|
2774
|
-
`Duplicate reaction "${name}" for event "${event_name}". Reaction handlers are keyed by function name; rename one of them.`
|
|
2775
|
-
);
|
|
2776
|
-
target_reg.reactions.set(name, reaction);
|
|
2777
|
-
}
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
|
-
function merge_projection(proj, events) {
|
|
2781
|
-
for (const event_name of Object.keys(proj.events)) {
|
|
2782
|
-
const proj_register = proj.events[event_name];
|
|
2783
|
-
const existing = events[event_name];
|
|
2784
|
-
if (!existing) {
|
|
2785
|
-
events[event_name] = {
|
|
2786
|
-
schema: proj_register.schema,
|
|
2787
|
-
reactions: new Map(proj_register.reactions)
|
|
2788
|
-
};
|
|
2789
|
-
} else {
|
|
2790
|
-
for (const [name, reaction] of proj_register.reactions) {
|
|
2791
|
-
if ([...existing.reactions.values()].includes(reaction)) continue;
|
|
2792
|
-
let key = name;
|
|
2793
|
-
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
2794
|
-
existing.reactions.set(key, reaction);
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2797
|
-
}
|
|
2798
|
-
}
|
|
2799
|
-
var _this_ = ({ stream }) => ({
|
|
2800
|
-
source: stream,
|
|
2801
|
-
target: stream
|
|
2802
|
-
});
|
|
2803
|
-
|
|
2804
|
-
// src/internal/builder-utils.ts
|
|
2805
|
-
function register_lane(config2, lanes) {
|
|
2806
|
-
if (config2.name === DEFAULT_LANE)
|
|
2807
|
-
throw new Error(`Lane "${DEFAULT_LANE}" is reserved`);
|
|
2808
|
-
if (lanes.some((l) => l.name === config2.name))
|
|
2809
|
-
throw new Error(`Lane "${config2.name}" was already declared`);
|
|
2810
|
-
lanes.push(config2);
|
|
2811
|
-
}
|
|
2812
|
-
function reaction_on(event, events, builder) {
|
|
2813
|
-
const register = (handler, options, schedule) => {
|
|
2814
|
-
if (!handler.name)
|
|
2815
|
-
throw new Error(
|
|
2816
|
-
`Reaction handler for "${String(event)}" must be a named function`
|
|
2817
|
-
);
|
|
2818
|
-
if (events[event].reactions.has(handler.name))
|
|
2819
|
-
throw new Error(
|
|
2820
|
-
`Duplicate reaction "${handler.name}" for event "${String(event)}". Reaction handlers are keyed by function name; rename one of them.`
|
|
2821
|
-
);
|
|
2822
|
-
if (schedule && typeof schedule !== "function") assert_defer_when(schedule);
|
|
2823
|
-
const reaction = {
|
|
2824
|
-
handler: schedule ? make_deferred(handler, schedule) : handler,
|
|
2825
|
-
resolver: _this_,
|
|
2826
|
-
// #1269: validate the whole bag at the declaration site so a bad
|
|
2827
|
-
// `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
|
|
2828
|
-
// not a NaN gate on the first retry.
|
|
2829
|
-
options: resolveReactionConfig({
|
|
2830
|
-
blockOnError: options?.blockOnError ?? true,
|
|
2831
|
-
maxRetries: options?.maxRetries ?? 3,
|
|
2832
|
-
backoff: options?.backoff
|
|
2833
|
-
})
|
|
2834
|
-
};
|
|
2835
|
-
events[event].reactions.set(handler.name, reaction);
|
|
2836
|
-
return Object.assign(builder, {
|
|
2837
|
-
to(resolver) {
|
|
2838
|
-
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
2839
|
-
return builder;
|
|
2840
|
-
}
|
|
2841
|
-
});
|
|
2842
|
-
};
|
|
2843
|
-
return {
|
|
2844
|
-
do: (handler, options) => register(handler, options),
|
|
2845
|
-
defer: (schedule) => ({
|
|
2846
|
-
do: (handler, options) => register(handler, options, schedule)
|
|
2847
|
-
})
|
|
2848
|
-
};
|
|
2849
|
-
}
|
|
2850
|
-
|
|
2851
|
-
// src/internal/circuit-breaker.ts
|
|
2852
|
-
var CircuitBreaker = class {
|
|
2853
|
-
_failures = 0;
|
|
2854
|
-
_opened_at;
|
|
2855
|
-
_wake;
|
|
2856
|
-
_threshold;
|
|
2857
|
-
_cooldown_ms;
|
|
2858
|
-
_hooks;
|
|
2859
|
-
constructor(config2, hooks = {}) {
|
|
2860
|
-
this._threshold = config2.failureThreshold;
|
|
2861
|
-
this._cooldown_ms = config2.cooldownMs;
|
|
2862
|
-
this._hooks = hooks;
|
|
2529
|
+
// src/internal/circuit-breaker.ts
|
|
2530
|
+
var CircuitBreaker = class {
|
|
2531
|
+
_failures = 0;
|
|
2532
|
+
_opened_at;
|
|
2533
|
+
_wake;
|
|
2534
|
+
_threshold;
|
|
2535
|
+
_cooldown_ms;
|
|
2536
|
+
_hooks;
|
|
2537
|
+
constructor(config2, hooks = {}) {
|
|
2538
|
+
this._threshold = config2.failureThreshold;
|
|
2539
|
+
this._cooldown_ms = config2.cooldownMs;
|
|
2540
|
+
this._hooks = hooks;
|
|
2863
2541
|
}
|
|
2864
2542
|
/** Current state given the wall-clock `now`. */
|
|
2865
2543
|
state(now) {
|
|
@@ -3218,6 +2896,135 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
|
|
|
3218
2896
|
return truncated;
|
|
3219
2897
|
}
|
|
3220
2898
|
|
|
2899
|
+
// src/internal/config.ts
|
|
2900
|
+
var import_zod6 = require("zod");
|
|
2901
|
+
var BackoffOptionsSchema = import_zod6.z.object({
|
|
2902
|
+
strategy: import_zod6.z.enum(["fixed", "linear", "exponential"]),
|
|
2903
|
+
baseMs: import_zod6.z.number().min(0),
|
|
2904
|
+
maxMs: import_zod6.z.number().gt(0).optional(),
|
|
2905
|
+
jitter: import_zod6.z.boolean().optional()
|
|
2906
|
+
});
|
|
2907
|
+
var ReactionOptionsSchema = import_zod6.z.object({
|
|
2908
|
+
blockOnError: import_zod6.z.boolean(),
|
|
2909
|
+
maxRetries: import_zod6.z.number().int().min(0),
|
|
2910
|
+
backoff: BackoffOptionsSchema.optional()
|
|
2911
|
+
});
|
|
2912
|
+
function resolveReactionConfig(options) {
|
|
2913
|
+
return ReactionOptionsSchema.parse(options);
|
|
2914
|
+
}
|
|
2915
|
+
var ActionOptionsSchema = import_zod6.z.object({
|
|
2916
|
+
maxRetries: import_zod6.z.number().int().min(0).optional(),
|
|
2917
|
+
backoff: BackoffOptionsSchema.optional()
|
|
2918
|
+
});
|
|
2919
|
+
function resolveActionConfig(options) {
|
|
2920
|
+
return ActionOptionsSchema.parse(options);
|
|
2921
|
+
}
|
|
2922
|
+
var LaneConfigSchema = import_zod6.z.object({
|
|
2923
|
+
name: import_zod6.z.string().min(1),
|
|
2924
|
+
leaseMillis: import_zod6.z.number().min(0).optional(),
|
|
2925
|
+
streamLimit: import_zod6.z.number().int().min(0).optional(),
|
|
2926
|
+
cycleMs: import_zod6.z.number().min(0).optional()
|
|
2927
|
+
});
|
|
2928
|
+
function resolveLaneConfig(options) {
|
|
2929
|
+
LaneConfigSchema.parse(options);
|
|
2930
|
+
return options;
|
|
2931
|
+
}
|
|
2932
|
+
var DrainOptionsSchema = import_zod6.z.object({
|
|
2933
|
+
streamLimit: import_zod6.z.number().int().min(0).optional(),
|
|
2934
|
+
eventLimit: import_zod6.z.number().int().min(0).optional(),
|
|
2935
|
+
leaseMillis: import_zod6.z.number().min(0).optional()
|
|
2936
|
+
});
|
|
2937
|
+
function resolveDrainConfig(options) {
|
|
2938
|
+
if (options === void 0) return void 0;
|
|
2939
|
+
DrainOptionsSchema.parse(options);
|
|
2940
|
+
return options;
|
|
2941
|
+
}
|
|
2942
|
+
var SettleOptionsSchema = DrainOptionsSchema.extend({
|
|
2943
|
+
debounceMs: import_zod6.z.number().min(0).optional(),
|
|
2944
|
+
// `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
|
|
2945
|
+
// a non-negative integer — `0` is legal and means "run no passes".
|
|
2946
|
+
maxPasses: import_zod6.z.number().int().min(0).optional()
|
|
2947
|
+
}).loose();
|
|
2948
|
+
function resolveSettleConfig(options) {
|
|
2949
|
+
if (options === void 0) return void 0;
|
|
2950
|
+
SettleOptionsSchema.parse(options);
|
|
2951
|
+
return options;
|
|
2952
|
+
}
|
|
2953
|
+
var MAX_SHUTDOWN_GRACE_MS = 3e4;
|
|
2954
|
+
var DEFAULT_SHUTDOWN_GRACE_MS = 1e4;
|
|
2955
|
+
var ShutdownOptionsSchema = import_zod6.z.object({
|
|
2956
|
+
// `0` is legal and is exactly today's behavior: stop scheduling and
|
|
2957
|
+
// return without waiting.
|
|
2958
|
+
graceMs: import_zod6.z.number().min(0).optional()
|
|
2959
|
+
}).loose();
|
|
2960
|
+
function resolveShutdownConfig(options) {
|
|
2961
|
+
if (options === void 0) return void 0;
|
|
2962
|
+
ShutdownOptionsSchema.parse(options);
|
|
2963
|
+
return options;
|
|
2964
|
+
}
|
|
2965
|
+
var ActOptionsSchema = import_zod6.z.object({
|
|
2966
|
+
maxSubscribedStreams: import_zod6.z.number().int().min(1).optional(),
|
|
2967
|
+
settleDebounceMs: import_zod6.z.number().int().min(0).optional()
|
|
2968
|
+
}).loose();
|
|
2969
|
+
function resolveActConfig(options) {
|
|
2970
|
+
if (options === void 0) return void 0;
|
|
2971
|
+
ActOptionsSchema.parse(options);
|
|
2972
|
+
return options;
|
|
2973
|
+
}
|
|
2974
|
+
var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
|
|
2975
|
+
var DEFAULT_CLOSE_BATCH_SIZE = 64;
|
|
2976
|
+
var DEFAULT_CLOSE_YIELD_MS = 0;
|
|
2977
|
+
var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
|
|
2978
|
+
function is_valid_time_zone(tz) {
|
|
2979
|
+
try {
|
|
2980
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
2981
|
+
return true;
|
|
2982
|
+
} catch {
|
|
2983
|
+
return false;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
var AutocloseWindowSchema = import_zod6.z.object({
|
|
2987
|
+
start: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
|
|
2988
|
+
end: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
|
|
2989
|
+
timeZone: import_zod6.z.string().refine(is_valid_time_zone, {
|
|
2990
|
+
message: "autocloseWindow.timeZone must be a valid IANA time zone"
|
|
2991
|
+
}).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
|
|
2992
|
+
}).refine((w) => w.start !== w.end, {
|
|
2993
|
+
message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
|
|
2994
|
+
});
|
|
2995
|
+
var AutocloseOptionsSchema = import_zod6.z.object({
|
|
2996
|
+
autocloseCycleMinutes: import_zod6.z.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
|
|
2997
|
+
closeBatchSize: import_zod6.z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
|
|
2998
|
+
closeYieldMs: import_zod6.z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
|
|
2999
|
+
closeOnError: import_zod6.z.boolean().default(false),
|
|
3000
|
+
autocloseWindow: AutocloseWindowSchema.optional()
|
|
3001
|
+
});
|
|
3002
|
+
function resolveAutocloseConfig(options) {
|
|
3003
|
+
return AutocloseOptionsSchema.parse({
|
|
3004
|
+
autocloseCycleMinutes: options?.autocloseCycleMinutes,
|
|
3005
|
+
closeBatchSize: options?.closeBatchSize,
|
|
3006
|
+
closeYieldMs: options?.closeYieldMs,
|
|
3007
|
+
closeOnError: options?.closeOnError,
|
|
3008
|
+
autocloseWindow: options?.autocloseWindow
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
3011
|
+
var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
|
|
3012
|
+
var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
|
|
3013
|
+
var CircuitBreakerOptionsSchema = import_zod6.z.object({
|
|
3014
|
+
failureThreshold: import_zod6.z.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
|
|
3015
|
+
cooldownMs: import_zod6.z.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
|
|
3016
|
+
});
|
|
3017
|
+
var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
|
|
3018
|
+
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
3019
|
+
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
3020
|
+
var FoldOptionsSchema = import_zod6.z.object({
|
|
3021
|
+
flushEvery: import_zod6.z.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
3022
|
+
maxCachedStates: import_zod6.z.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
3023
|
+
});
|
|
3024
|
+
function resolveFoldConfig(options) {
|
|
3025
|
+
return FoldOptionsSchema.parse(options);
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3221
3028
|
// src/internal/correlate-cycle.ts
|
|
3222
3029
|
var import_node_crypto = require("crypto");
|
|
3223
3030
|
var DEFAULT_COLD_START_BACK_SCAN = 1e4;
|
|
@@ -3612,6 +3419,43 @@ function close_correlation(correlator, actor) {
|
|
|
3612
3419
|
});
|
|
3613
3420
|
}
|
|
3614
3421
|
|
|
3422
|
+
// src/internal/defer-config.ts
|
|
3423
|
+
var import_zod7 = require("zod");
|
|
3424
|
+
var DeferDurationSchema = import_zod7.z.object({
|
|
3425
|
+
days: import_zod7.z.number().positive().optional(),
|
|
3426
|
+
hours: import_zod7.z.number().positive().optional(),
|
|
3427
|
+
minutes: import_zod7.z.number().positive().optional()
|
|
3428
|
+
}).strict().refine((d) => d.days != null || d.hours != null || d.minutes != null, {
|
|
3429
|
+
message: "defer: a duration needs at least one of days, hours, or minutes"
|
|
3430
|
+
});
|
|
3431
|
+
var DeferWhenSchema = import_zod7.z.object({
|
|
3432
|
+
after: DeferDurationSchema.optional(),
|
|
3433
|
+
at: import_zod7.z.date().optional()
|
|
3434
|
+
}).strict().refine((w) => w.after === void 0 !== (w.at === void 0), {
|
|
3435
|
+
message: "defer: specify exactly one of `after` or `at`"
|
|
3436
|
+
});
|
|
3437
|
+
function duration_ms(d) {
|
|
3438
|
+
return (d.days ?? 0) * 864e5 + (d.hours ?? 0) * 36e5 + (d.minutes ?? 0) * 6e4;
|
|
3439
|
+
}
|
|
3440
|
+
function resolve_defer_at(when, event) {
|
|
3441
|
+
const parsed = DeferWhenSchema.parse(when);
|
|
3442
|
+
if (parsed.after) return event.created.getTime() + duration_ms(parsed.after);
|
|
3443
|
+
return parsed.at.getTime();
|
|
3444
|
+
}
|
|
3445
|
+
function assert_defer_when(when) {
|
|
3446
|
+
DeferWhenSchema.parse(when);
|
|
3447
|
+
}
|
|
3448
|
+
function make_deferred(handler, schedule) {
|
|
3449
|
+
const call = handler;
|
|
3450
|
+
const deferred = async (event, stream, app) => {
|
|
3451
|
+
const when = typeof schedule === "function" ? schedule(event) : schedule;
|
|
3452
|
+
if (Date.now() < resolve_defer_at(when, event)) throw new DeferSignal(when);
|
|
3453
|
+
return call(event, stream, app);
|
|
3454
|
+
};
|
|
3455
|
+
Object.defineProperty(deferred, "name", { value: handler.name });
|
|
3456
|
+
return deferred;
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3615
3459
|
// src/internal/drain-cycle.ts
|
|
3616
3460
|
var import_node_crypto3 = require("crypto");
|
|
3617
3461
|
|
|
@@ -3739,37 +3583,6 @@ var subscribe = (streams, correlated_at, correlator) => store2().subscribe(strea
|
|
|
3739
3583
|
|
|
3740
3584
|
// src/internal/event-sourcing.ts
|
|
3741
3585
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
3742
|
-
|
|
3743
|
-
// src/internal/backoff.ts
|
|
3744
|
-
function compute_backoff_delay(retry, opts) {
|
|
3745
|
-
if (!opts || opts.baseMs <= 0) return 0;
|
|
3746
|
-
const r = Math.max(0, retry);
|
|
3747
|
-
let delay;
|
|
3748
|
-
switch (opts.strategy) {
|
|
3749
|
-
case "fixed":
|
|
3750
|
-
delay = opts.baseMs;
|
|
3751
|
-
break;
|
|
3752
|
-
case "linear":
|
|
3753
|
-
delay = opts.baseMs * (r + 1);
|
|
3754
|
-
break;
|
|
3755
|
-
case "exponential":
|
|
3756
|
-
delay = opts.baseMs * 2 ** r;
|
|
3757
|
-
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
3758
|
-
break;
|
|
3759
|
-
default: {
|
|
3760
|
-
const _never = opts.strategy;
|
|
3761
|
-
throw new Error(`unknown backoff strategy: ${String(_never)}`);
|
|
3762
|
-
}
|
|
3763
|
-
}
|
|
3764
|
-
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
3765
|
-
return Math.max(0, Math.floor(delay));
|
|
3766
|
-
}
|
|
3767
|
-
|
|
3768
|
-
// src/internal/reacting.ts
|
|
3769
|
-
var import_node_async_hooks2 = require("async_hooks");
|
|
3770
|
-
var reacting = new import_node_async_hooks2.AsyncLocalStorage();
|
|
3771
|
-
|
|
3772
|
-
// src/internal/event-sourcing.ts
|
|
3773
3586
|
var bare_patch = (_me, state2, partial) => (0, import_act_patch.patch)(state2, partial);
|
|
3774
3587
|
var validating_patch = (me, state2, partial, event) => {
|
|
3775
3588
|
const next = (0, import_act_patch.patch)(state2, partial);
|
|
@@ -4026,7 +3839,7 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
4026
3839
|
async function action(me, action2, target, payload, options, patch_fn = bare_patch) {
|
|
4027
3840
|
const { stream, expectedVersion, actor } = target;
|
|
4028
3841
|
if (!stream) throw new Error("Missing target stream");
|
|
4029
|
-
const reactingTo = options?.reactingTo
|
|
3842
|
+
const reactingTo = options?.reactingTo;
|
|
4030
3843
|
const correlator = options?.correlator ?? default_correlator;
|
|
4031
3844
|
const validated = validate(action2, payload, me.actions[action2]);
|
|
4032
3845
|
const opts = me.options?.[action2];
|
|
@@ -4675,131 +4488,6 @@ function make_fold_handler(me, flush, config2, patch_fn = bare_patch, sensitive_
|
|
|
4675
4488
|
return handler;
|
|
4676
4489
|
}
|
|
4677
4490
|
|
|
4678
|
-
// src/internal/reactions.ts
|
|
4679
|
-
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
4680
|
-
if (!error) return { lease, handled, acked_at: at };
|
|
4681
|
-
logger.error(error);
|
|
4682
|
-
const non_retryable = error instanceof NonRetryableError;
|
|
4683
|
-
const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
|
|
4684
|
-
if (block2)
|
|
4685
|
-
logger.error(
|
|
4686
|
-
non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
4687
|
-
);
|
|
4688
|
-
const next_attempt_at = !block2 && options.backoff ? Date.now() + compute_backoff_delay(lease.retry, options.backoff) : void 0;
|
|
4689
|
-
return {
|
|
4690
|
-
lease,
|
|
4691
|
-
handled,
|
|
4692
|
-
acked_at: at,
|
|
4693
|
-
error: error.message,
|
|
4694
|
-
block: block2,
|
|
4695
|
-
next_attempt_at,
|
|
4696
|
-
failed_at
|
|
4697
|
-
};
|
|
4698
|
-
}
|
|
4699
|
-
function build_handle(deps) {
|
|
4700
|
-
const {
|
|
4701
|
-
logger,
|
|
4702
|
-
bound_do,
|
|
4703
|
-
bound_load,
|
|
4704
|
-
bound_query,
|
|
4705
|
-
bound_query_array,
|
|
4706
|
-
bound_forget
|
|
4707
|
-
} = deps;
|
|
4708
|
-
return async (lease, payloads) => {
|
|
4709
|
-
if (payloads.length === 0) return { lease, handled: 0, acked_at: lease.at };
|
|
4710
|
-
const stream = lease.stream;
|
|
4711
|
-
const last_index_of = /* @__PURE__ */ new Map();
|
|
4712
|
-
for (let i = 0; i < payloads.length; i++)
|
|
4713
|
-
last_index_of.set(payloads[i].event.id, i);
|
|
4714
|
-
let at = lease.at;
|
|
4715
|
-
let handled = 0;
|
|
4716
|
-
if (lease.retry > 0)
|
|
4717
|
-
logger.warn(
|
|
4718
|
-
`Retrying ${stream}@${payloads.at(0).event.id} (${lease.retry}).`
|
|
4719
|
-
);
|
|
4720
|
-
const scoped_app = {
|
|
4721
|
-
do: bound_do,
|
|
4722
|
-
load: bound_load,
|
|
4723
|
-
query: bound_query,
|
|
4724
|
-
query_array: bound_query_array,
|
|
4725
|
-
forget: bound_forget
|
|
4726
|
-
};
|
|
4727
|
-
for (let i = 0; i < payloads.length; i++) {
|
|
4728
|
-
const payload = payloads[i];
|
|
4729
|
-
const { event, handler } = payload;
|
|
4730
|
-
try {
|
|
4731
|
-
await reacting.run(
|
|
4732
|
-
event,
|
|
4733
|
-
() => handler(event, stream, scoped_app)
|
|
4734
|
-
);
|
|
4735
|
-
if (last_index_of.get(event.id) === i) {
|
|
4736
|
-
at = event.id;
|
|
4737
|
-
handled++;
|
|
4738
|
-
}
|
|
4739
|
-
} catch (error) {
|
|
4740
|
-
if (error instanceof DeferSignal)
|
|
4741
|
-
return {
|
|
4742
|
-
lease,
|
|
4743
|
-
handled,
|
|
4744
|
-
acked_at: at,
|
|
4745
|
-
defer: resolve_defer_at(error.when, event)
|
|
4746
|
-
};
|
|
4747
|
-
if (error instanceof CloseSignal)
|
|
4748
|
-
return {
|
|
4749
|
-
lease,
|
|
4750
|
-
handled: handled + 1,
|
|
4751
|
-
// Advance to the live head the handler evaluated against (when
|
|
4752
|
-
// provided) so the close-cycle guard sees this reaction caught up.
|
|
4753
|
-
acked_at: error.at ?? event.id,
|
|
4754
|
-
// Close the signalled stream (the autoclose reaction's aggregate,
|
|
4755
|
-
// which differs from its synthetic lease stream); a self-closing
|
|
4756
|
-
// user reaction omits it and closes its own lease stream. A
|
|
4757
|
-
// carried `before` makes it a windowed close (prune, not retire).
|
|
4758
|
-
close: {
|
|
4759
|
-
stream: error.stream ?? stream,
|
|
4760
|
-
archive: error.archive,
|
|
4761
|
-
before: error.before
|
|
4762
|
-
}
|
|
4763
|
-
};
|
|
4764
|
-
return finalize(
|
|
4765
|
-
lease,
|
|
4766
|
-
handled,
|
|
4767
|
-
at,
|
|
4768
|
-
error,
|
|
4769
|
-
payload.options,
|
|
4770
|
-
logger,
|
|
4771
|
-
event.id
|
|
4772
|
-
);
|
|
4773
|
-
}
|
|
4774
|
-
}
|
|
4775
|
-
return finalize(lease, handled, at, void 0, payloads[0].options, logger);
|
|
4776
|
-
};
|
|
4777
|
-
}
|
|
4778
|
-
function build_handle_batch(logger) {
|
|
4779
|
-
return async (lease, payloads, batchHandler) => {
|
|
4780
|
-
const stream = lease.stream;
|
|
4781
|
-
const events = payloads.map(
|
|
4782
|
-
(p) => p.event
|
|
4783
|
-
);
|
|
4784
|
-
const options = payloads[0].options;
|
|
4785
|
-
if (lease.retry > 0)
|
|
4786
|
-
logger.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
|
|
4787
|
-
try {
|
|
4788
|
-
await batchHandler(events, stream);
|
|
4789
|
-
return finalize(
|
|
4790
|
-
lease,
|
|
4791
|
-
events.length,
|
|
4792
|
-
events.at(-1).id,
|
|
4793
|
-
void 0,
|
|
4794
|
-
options,
|
|
4795
|
-
logger
|
|
4796
|
-
);
|
|
4797
|
-
} catch (error) {
|
|
4798
|
-
return finalize(lease, 0, lease.at, error, options, logger);
|
|
4799
|
-
}
|
|
4800
|
-
};
|
|
4801
|
-
}
|
|
4802
|
-
|
|
4803
4491
|
// src/internal/settle.ts
|
|
4804
4492
|
var SettleLoop = class {
|
|
4805
4493
|
_timer = void 0;
|
|
@@ -4910,8 +4598,119 @@ var SettleLoop = class {
|
|
|
4910
4598
|
clearTimeout(this._timer);
|
|
4911
4599
|
this._timer = void 0;
|
|
4912
4600
|
}
|
|
4913
|
-
}
|
|
4914
|
-
};
|
|
4601
|
+
}
|
|
4602
|
+
};
|
|
4603
|
+
|
|
4604
|
+
// src/builders/reaction-builder.ts
|
|
4605
|
+
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
4606
|
+
if (!error) return { lease, handled, acked_at: at };
|
|
4607
|
+
logger.error(error);
|
|
4608
|
+
const non_retryable = error instanceof NonRetryableError;
|
|
4609
|
+
const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
|
|
4610
|
+
if (block2)
|
|
4611
|
+
logger.error(
|
|
4612
|
+
non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
4613
|
+
);
|
|
4614
|
+
const next_attempt_at = !block2 && options.backoff ? Date.now() + compute_backoff_delay(lease.retry, options.backoff) : void 0;
|
|
4615
|
+
return {
|
|
4616
|
+
lease,
|
|
4617
|
+
handled,
|
|
4618
|
+
acked_at: at,
|
|
4619
|
+
error: error.message,
|
|
4620
|
+
block: block2,
|
|
4621
|
+
next_attempt_at,
|
|
4622
|
+
failed_at
|
|
4623
|
+
};
|
|
4624
|
+
}
|
|
4625
|
+
function build_handle(deps) {
|
|
4626
|
+
const { logger, reaction_scope } = deps;
|
|
4627
|
+
return async (lease, payloads) => {
|
|
4628
|
+
if (payloads.length === 0) return { lease, handled: 0, acked_at: lease.at };
|
|
4629
|
+
const stream = lease.stream;
|
|
4630
|
+
const last_index_of = /* @__PURE__ */ new Map();
|
|
4631
|
+
for (let i = 0; i < payloads.length; i++)
|
|
4632
|
+
last_index_of.set(payloads[i].event.id, i);
|
|
4633
|
+
let at = lease.at;
|
|
4634
|
+
let handled = 0;
|
|
4635
|
+
if (lease.retry > 0)
|
|
4636
|
+
logger.warn(
|
|
4637
|
+
`Retrying ${stream}@${payloads.at(0).event.id} (${lease.retry}).`
|
|
4638
|
+
);
|
|
4639
|
+
for (let i = 0; i < payloads.length; i++) {
|
|
4640
|
+
const payload = payloads[i];
|
|
4641
|
+
const { event, handler } = payload;
|
|
4642
|
+
try {
|
|
4643
|
+
await reaction_scope.run(
|
|
4644
|
+
event,
|
|
4645
|
+
() => handler(event, stream, reaction_scope.app)
|
|
4646
|
+
);
|
|
4647
|
+
if (last_index_of.get(event.id) === i) {
|
|
4648
|
+
at = event.id;
|
|
4649
|
+
handled++;
|
|
4650
|
+
}
|
|
4651
|
+
} catch (error) {
|
|
4652
|
+
if (error instanceof DeferSignal)
|
|
4653
|
+
return {
|
|
4654
|
+
lease,
|
|
4655
|
+
handled,
|
|
4656
|
+
acked_at: at,
|
|
4657
|
+
defer: resolve_defer_at(error.when, event)
|
|
4658
|
+
};
|
|
4659
|
+
if (error instanceof CloseSignal)
|
|
4660
|
+
return {
|
|
4661
|
+
lease,
|
|
4662
|
+
handled: handled + 1,
|
|
4663
|
+
// Advance to the live head the handler evaluated against (when
|
|
4664
|
+
// provided) so the close-cycle guard sees this reaction caught up.
|
|
4665
|
+
acked_at: error.at ?? event.id,
|
|
4666
|
+
// Close the signalled stream (the autoclose reaction's aggregate,
|
|
4667
|
+
// which differs from its synthetic lease stream); a self-closing
|
|
4668
|
+
// user reaction omits it and closes its own lease stream. A
|
|
4669
|
+
// carried `before` makes it a windowed close (prune, not retire).
|
|
4670
|
+
close: {
|
|
4671
|
+
stream: error.stream ?? stream,
|
|
4672
|
+
archive: error.archive,
|
|
4673
|
+
before: error.before
|
|
4674
|
+
}
|
|
4675
|
+
};
|
|
4676
|
+
return finalize(
|
|
4677
|
+
lease,
|
|
4678
|
+
handled,
|
|
4679
|
+
at,
|
|
4680
|
+
error,
|
|
4681
|
+
payload.options,
|
|
4682
|
+
logger,
|
|
4683
|
+
event.id
|
|
4684
|
+
);
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
return finalize(lease, handled, at, void 0, payloads[0].options, logger);
|
|
4688
|
+
};
|
|
4689
|
+
}
|
|
4690
|
+
function build_handle_batch(logger) {
|
|
4691
|
+
return async (lease, payloads, batchHandler) => {
|
|
4692
|
+
const stream = lease.stream;
|
|
4693
|
+
const events = payloads.map(
|
|
4694
|
+
(p) => p.event
|
|
4695
|
+
);
|
|
4696
|
+
const options = payloads[0].options;
|
|
4697
|
+
if (lease.retry > 0)
|
|
4698
|
+
logger.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
|
|
4699
|
+
try {
|
|
4700
|
+
await batchHandler(events, stream);
|
|
4701
|
+
return finalize(
|
|
4702
|
+
lease,
|
|
4703
|
+
events.length,
|
|
4704
|
+
events.at(-1).id,
|
|
4705
|
+
void 0,
|
|
4706
|
+
options,
|
|
4707
|
+
logger
|
|
4708
|
+
);
|
|
4709
|
+
} catch (error) {
|
|
4710
|
+
return finalize(lease, 0, lease.at, error, options, logger);
|
|
4711
|
+
}
|
|
4712
|
+
};
|
|
4713
|
+
}
|
|
4915
4714
|
|
|
4916
4715
|
// src/act.ts
|
|
4917
4716
|
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
@@ -5144,17 +4943,21 @@ var Act = class {
|
|
|
5144
4943
|
this._batch_handlers = batch_handlers;
|
|
5145
4944
|
this._lanes = lanes;
|
|
5146
4945
|
validate_only_lanes(options, lanes);
|
|
5147
|
-
this._scoped =
|
|
4946
|
+
this._scoped = make_run_scoped(options.scoped);
|
|
5148
4947
|
this._correlator = options.correlator ?? default_correlator;
|
|
5149
4948
|
this._es = build_es(this._logger, this._correlator, patch_fn);
|
|
5150
4949
|
this._cd = build_drain(this._logger);
|
|
5151
4950
|
this._handle = build_handle({
|
|
5152
4951
|
logger: this._logger,
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
4952
|
+
// The orchestrator owns ambient context; `build_handle` only asks for
|
|
4953
|
+
// the triggering event to be in scope while the handler runs.
|
|
4954
|
+
reaction_scope: make_reaction_scope({
|
|
4955
|
+
do: this._bound_do,
|
|
4956
|
+
load: this._bound_load,
|
|
4957
|
+
query: this._bound_query,
|
|
4958
|
+
query_array: this._bound_query_array,
|
|
4959
|
+
forget: this._bound_forget
|
|
4960
|
+
})
|
|
5158
4961
|
});
|
|
5159
4962
|
this._handle_batch = build_handle_batch(this._logger);
|
|
5160
4963
|
const classification = classify_registry(this.registry, this._states);
|
|
@@ -5202,15 +5005,15 @@ var Act = class {
|
|
|
5202
5005
|
* One DrainController per active lane. The implicit "default" lane is
|
|
5203
5006
|
* always present unless onlyLanes excludes it. Each controller filters
|
|
5204
5007
|
* its claim() by its lane name; the legacy single-controller path is the
|
|
5205
|
-
*
|
|
5206
|
-
*
|
|
5207
|
-
*
|
|
5008
|
+
* no-lane-declared case with `lane: undefined` deps so claim() doesn't
|
|
5009
|
+
* filter (preserves the single-lane SQL planner shape for apps that never
|
|
5010
|
+
* call withLane).
|
|
5208
5011
|
*/
|
|
5209
5012
|
_build_drain_controllers(options, lanes) {
|
|
5210
5013
|
const all_lanes = ["default", ...lanes.map((l) => l.name)];
|
|
5211
5014
|
const only_set = options.onlyLanes && options.onlyLanes.length > 0 ? new Set(options.onlyLanes) : void 0;
|
|
5212
5015
|
const active_lanes = only_set ? all_lanes.filter((n) => only_set.has(n)) : all_lanes;
|
|
5213
|
-
const single_default_lane =
|
|
5016
|
+
const single_default_lane = lanes.length === 0;
|
|
5214
5017
|
const controllers = /* @__PURE__ */ new Map();
|
|
5215
5018
|
for (const name of active_lanes) {
|
|
5216
5019
|
const cfg = lanes.find((l) => l.name === name);
|
|
@@ -5573,13 +5376,15 @@ var Act = class {
|
|
|
5573
5376
|
* @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
|
|
5574
5377
|
*/
|
|
5575
5378
|
async do(action2, target, payload, options) {
|
|
5379
|
+
const reacting_to = options?.reactingTo ?? current_reacting();
|
|
5380
|
+
const do_options = reacting_to === options?.reactingTo ? options : { ...options, reactingTo: reacting_to };
|
|
5576
5381
|
return this._scoped(async () => {
|
|
5577
5382
|
const snapshots = await this._es.action(
|
|
5578
5383
|
this.registry.actions[action2],
|
|
5579
5384
|
action2,
|
|
5580
5385
|
target,
|
|
5581
5386
|
payload,
|
|
5582
|
-
|
|
5387
|
+
do_options
|
|
5583
5388
|
);
|
|
5584
5389
|
if (this._reactive_events.size > 0)
|
|
5585
5390
|
this._arm_for_event_names(
|
|
@@ -6412,6 +6217,202 @@ var Act = class {
|
|
|
6412
6217
|
}
|
|
6413
6218
|
};
|
|
6414
6219
|
|
|
6220
|
+
// src/builders/merge.ts
|
|
6221
|
+
var import_zod8 = require("zod");
|
|
6222
|
+
function base_type_name(zodType) {
|
|
6223
|
+
let t = zodType;
|
|
6224
|
+
while (typeof t.unwrap === "function") {
|
|
6225
|
+
t = t.unwrap();
|
|
6226
|
+
}
|
|
6227
|
+
return t.constructor.name;
|
|
6228
|
+
}
|
|
6229
|
+
function merge_schemas(existing, incoming, state_name) {
|
|
6230
|
+
if (existing instanceof import_zod8.ZodObject && incoming instanceof import_zod8.ZodObject) {
|
|
6231
|
+
const existing_shape = existing.shape;
|
|
6232
|
+
const incoming_shape = incoming.shape;
|
|
6233
|
+
for (const key of Object.keys(incoming_shape)) {
|
|
6234
|
+
if (key in existing_shape) {
|
|
6235
|
+
const existing_base = base_type_name(existing_shape[key]);
|
|
6236
|
+
const incoming_base = base_type_name(incoming_shape[key]);
|
|
6237
|
+
if (existing_base !== incoming_base) {
|
|
6238
|
+
throw new Error(
|
|
6239
|
+
`Schema conflict in "${state_name}": key "${key}" has type "${existing_base}" but incoming partial declares "${incoming_base}"`
|
|
6240
|
+
);
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6243
|
+
}
|
|
6244
|
+
return existing.extend(incoming_shape);
|
|
6245
|
+
}
|
|
6246
|
+
return existing;
|
|
6247
|
+
}
|
|
6248
|
+
function merge_inits(existing, incoming) {
|
|
6249
|
+
return () => ({ ...existing(), ...incoming() });
|
|
6250
|
+
}
|
|
6251
|
+
function register_state(state2, states, actions, events) {
|
|
6252
|
+
const existing = states.get(state2.name);
|
|
6253
|
+
if (existing) {
|
|
6254
|
+
merge_into_existing(state2, existing, states, actions, events);
|
|
6255
|
+
} else {
|
|
6256
|
+
register_new_state(state2, states, actions, events);
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
function register_new_state(state2, states, actions, events) {
|
|
6260
|
+
states.set(state2.name, state2);
|
|
6261
|
+
for (const name of Object.keys(state2.actions)) {
|
|
6262
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
6263
|
+
actions[name] = state2;
|
|
6264
|
+
}
|
|
6265
|
+
for (const name of Object.keys(state2.events)) {
|
|
6266
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
6267
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
function merge_into_existing(state2, existing, states, actions, events) {
|
|
6271
|
+
for (const name of Object.keys(state2.actions)) {
|
|
6272
|
+
if (existing.actions[name] === state2.actions[name]) continue;
|
|
6273
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
6274
|
+
}
|
|
6275
|
+
for (const name of Object.keys(state2.events)) {
|
|
6276
|
+
if (existing.events[name] === state2.events[name]) continue;
|
|
6277
|
+
if (existing.events[name]) {
|
|
6278
|
+
throw new Error(
|
|
6279
|
+
`Event "${name}" in state "${state2.name}" is declared with different Zod schemas across slices. Cross-slice event schemas must reference the same instance \u2014 extract a shared schema (e.g. \`export const ${name} = z.object({ ... })\` in a shared module) and import it in every slice that declares it.`
|
|
6280
|
+
);
|
|
6281
|
+
}
|
|
6282
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
6283
|
+
}
|
|
6284
|
+
const merged_patch = merge_patches(existing.patch, state2.patch, state2.name);
|
|
6285
|
+
const merged = {
|
|
6286
|
+
...existing,
|
|
6287
|
+
state: merge_schemas(existing.state, state2.state, state2.name),
|
|
6288
|
+
init: merge_inits(existing.init, state2.init),
|
|
6289
|
+
events: { ...existing.events, ...state2.events },
|
|
6290
|
+
actions: { ...existing.actions, ...state2.actions },
|
|
6291
|
+
patch: merged_patch,
|
|
6292
|
+
on: { ...existing.on, ...state2.on },
|
|
6293
|
+
given: { ...existing.given, ...state2.given },
|
|
6294
|
+
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
6295
|
+
throw new Error(
|
|
6296
|
+
`Duplicate snap strategy for state "${state2.name}"`
|
|
6297
|
+
);
|
|
6298
|
+
})() : state2.snap || existing.snap
|
|
6299
|
+
};
|
|
6300
|
+
states.set(state2.name, merged);
|
|
6301
|
+
for (const name of Object.keys(merged.actions)) {
|
|
6302
|
+
actions[name] = merged;
|
|
6303
|
+
}
|
|
6304
|
+
for (const name of Object.keys(state2.events)) {
|
|
6305
|
+
if (events[name]) continue;
|
|
6306
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
function merge_patches(existing, incoming, state_name) {
|
|
6310
|
+
const merged = { ...existing };
|
|
6311
|
+
for (const name of Object.keys(incoming)) {
|
|
6312
|
+
const existing_p = existing[name];
|
|
6313
|
+
const incoming_p = incoming[name];
|
|
6314
|
+
if (!existing_p) {
|
|
6315
|
+
merged[name] = incoming_p;
|
|
6316
|
+
continue;
|
|
6317
|
+
}
|
|
6318
|
+
const existing_is_default = existing_p._passthrough;
|
|
6319
|
+
const incoming_is_default = incoming_p._passthrough;
|
|
6320
|
+
if (!existing_is_default && !incoming_is_default && existing_p !== incoming_p) {
|
|
6321
|
+
throw new Error(
|
|
6322
|
+
`Duplicate custom patch for event "${name}" in state "${state_name}"`
|
|
6323
|
+
);
|
|
6324
|
+
}
|
|
6325
|
+
if (existing_is_default && !incoming_is_default) {
|
|
6326
|
+
merged[name] = incoming_p;
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
return merged;
|
|
6330
|
+
}
|
|
6331
|
+
function merge_event_register(target, source) {
|
|
6332
|
+
for (const [event_name, source_reg] of Object.entries(source)) {
|
|
6333
|
+
const target_reg = target[event_name];
|
|
6334
|
+
if (!target_reg) continue;
|
|
6335
|
+
for (const [name, reaction] of source_reg.reactions) {
|
|
6336
|
+
const existing = target_reg.reactions.get(name);
|
|
6337
|
+
if (existing !== void 0 && existing !== reaction)
|
|
6338
|
+
throw new Error(
|
|
6339
|
+
`Duplicate reaction "${name}" for event "${event_name}". Reaction handlers are keyed by function name; rename one of them.`
|
|
6340
|
+
);
|
|
6341
|
+
target_reg.reactions.set(name, reaction);
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
function merge_projection(proj, events) {
|
|
6346
|
+
for (const event_name of Object.keys(proj.events)) {
|
|
6347
|
+
const proj_register = proj.events[event_name];
|
|
6348
|
+
const existing = events[event_name];
|
|
6349
|
+
if (!existing) {
|
|
6350
|
+
events[event_name] = {
|
|
6351
|
+
schema: proj_register.schema,
|
|
6352
|
+
reactions: new Map(proj_register.reactions)
|
|
6353
|
+
};
|
|
6354
|
+
} else {
|
|
6355
|
+
for (const [name, reaction] of proj_register.reactions) {
|
|
6356
|
+
if ([...existing.reactions.values()].includes(reaction)) continue;
|
|
6357
|
+
let key = name;
|
|
6358
|
+
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
6359
|
+
existing.reactions.set(key, reaction);
|
|
6360
|
+
}
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
var _this_ = ({ stream }) => ({
|
|
6365
|
+
source: stream,
|
|
6366
|
+
target: stream
|
|
6367
|
+
});
|
|
6368
|
+
|
|
6369
|
+
// src/builders/builder-utils.ts
|
|
6370
|
+
function register_lane(config2, lanes) {
|
|
6371
|
+
if (config2.name === DEFAULT_LANE)
|
|
6372
|
+
throw new Error(`Lane "${DEFAULT_LANE}" is reserved`);
|
|
6373
|
+
if (lanes.some((l) => l.name === config2.name))
|
|
6374
|
+
throw new Error(`Lane "${config2.name}" was already declared`);
|
|
6375
|
+
lanes.push(config2);
|
|
6376
|
+
}
|
|
6377
|
+
function reaction_on(event, events, builder) {
|
|
6378
|
+
const register = (handler, options, schedule) => {
|
|
6379
|
+
if (!handler.name)
|
|
6380
|
+
throw new Error(
|
|
6381
|
+
`Reaction handler for "${String(event)}" must be a named function`
|
|
6382
|
+
);
|
|
6383
|
+
if (events[event].reactions.has(handler.name))
|
|
6384
|
+
throw new Error(
|
|
6385
|
+
`Duplicate reaction "${handler.name}" for event "${String(event)}". Reaction handlers are keyed by function name; rename one of them.`
|
|
6386
|
+
);
|
|
6387
|
+
if (schedule && typeof schedule !== "function") assert_defer_when(schedule);
|
|
6388
|
+
const reaction = {
|
|
6389
|
+
handler: schedule ? make_deferred(handler, schedule) : handler,
|
|
6390
|
+
resolver: _this_,
|
|
6391
|
+
// #1269: validate the whole bag at the declaration site so a bad
|
|
6392
|
+
// `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
|
|
6393
|
+
// not a NaN gate on the first retry.
|
|
6394
|
+
options: resolveReactionConfig({
|
|
6395
|
+
blockOnError: options?.blockOnError ?? true,
|
|
6396
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
6397
|
+
backoff: options?.backoff
|
|
6398
|
+
})
|
|
6399
|
+
};
|
|
6400
|
+
events[event].reactions.set(handler.name, reaction);
|
|
6401
|
+
return Object.assign(builder, {
|
|
6402
|
+
to(resolver) {
|
|
6403
|
+
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
6404
|
+
return builder;
|
|
6405
|
+
}
|
|
6406
|
+
});
|
|
6407
|
+
};
|
|
6408
|
+
return {
|
|
6409
|
+
do: (handler, options) => register(handler, options),
|
|
6410
|
+
defer: (schedule) => ({
|
|
6411
|
+
do: (handler, options) => register(handler, options, schedule)
|
|
6412
|
+
})
|
|
6413
|
+
};
|
|
6414
|
+
}
|
|
6415
|
+
|
|
6415
6416
|
// src/builders/act-builder.ts
|
|
6416
6417
|
function register_batch_handler(proj, batch_handlers) {
|
|
6417
6418
|
if (!proj.batchHandler || !proj.target) return;
|
|
@@ -7197,7 +7198,6 @@ function write_line(writer, line) {
|
|
|
7197
7198
|
port,
|
|
7198
7199
|
projection,
|
|
7199
7200
|
resolveAutocloseConfig,
|
|
7200
|
-
scoped,
|
|
7201
7201
|
sensitive,
|
|
7202
7202
|
sleep,
|
|
7203
7203
|
slice,
|