@rotorsoft/act 1.32.11 → 1.32.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +31 -100
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +8 -0
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/builder-utils.d.ts.map +1 -1
- package/dist/@types/builders/projection-builder.d.ts.map +1 -1
- package/dist/@types/internal/autoclose-reaction.d.ts +1 -1
- package/dist/@types/internal/autoclose-reaction.d.ts.map +1 -1
- package/dist/@types/internal/config.d.ts +25 -4
- package/dist/@types/internal/config.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +1 -19
- 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/internal/index.d.ts +1 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/{chunk-UVFP2BQ3.js → chunk-FLAGQMWE.js} +35 -58
- package/dist/chunk-FLAGQMWE.js.map +1 -0
- package/dist/index.cjs +235 -316
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +202 -260
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +34 -57
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +2 -6
- package/dist/chunk-UVFP2BQ3.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -912,17 +912,34 @@ var InMemoryStore = class {
|
|
|
912
912
|
this._streams = /* @__PURE__ */ new Map();
|
|
913
913
|
}
|
|
914
914
|
in_query(query, e) {
|
|
915
|
-
if (query.stream)
|
|
916
|
-
|
|
917
|
-
if (e.stream !== query.stream) return false;
|
|
918
|
-
} else if (!RegExp(query.stream).test(e.stream)) return false;
|
|
919
|
-
}
|
|
915
|
+
if (query.stream && !this._stream_matches(query.stream, query, e.stream))
|
|
916
|
+
return false;
|
|
920
917
|
if (query.names && !query.names.includes(e.name)) return false;
|
|
921
918
|
if (query.correlation && e.meta?.correlation !== query.correlation)
|
|
922
919
|
return false;
|
|
923
920
|
if (e.name === SNAP_EVENT && !query.with_snaps) return false;
|
|
921
|
+
if (query.created_after && e.created <= query.created_after) return false;
|
|
922
|
+
if (query.created_before && e.created >= query.created_before) return false;
|
|
924
923
|
return true;
|
|
925
924
|
}
|
|
925
|
+
_stream_matches(pattern, query, stream) {
|
|
926
|
+
return query.stream_exact ? stream === pattern : RegExp(pattern).test(stream);
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Index of the latest snapshot of the queried stream, or -1. `with_snaps`
|
|
930
|
+
* on an exact single stream resumes there, so earlier events aren't read.
|
|
931
|
+
* The orchestrator asks only for unbounded current-state loads, so the
|
|
932
|
+
* floor applies whenever asked; an explicit `after` wins over it.
|
|
933
|
+
*/
|
|
934
|
+
_snap_floor(query) {
|
|
935
|
+
if (!query?.with_snaps || !query.stream_exact || query.stream === void 0 || query.after !== void 0)
|
|
936
|
+
return -1;
|
|
937
|
+
for (let j = this._events.length - 1; j >= 0; j--) {
|
|
938
|
+
const e = this._events[j];
|
|
939
|
+
if (e.stream === query.stream && e.name === SNAP_EVENT) return j;
|
|
940
|
+
}
|
|
941
|
+
return -1;
|
|
942
|
+
}
|
|
926
943
|
/**
|
|
927
944
|
* Query events in the store, optionally filtered by query options.
|
|
928
945
|
* @param callback - Function to call for each event.
|
|
@@ -931,59 +948,19 @@ var InMemoryStore = class {
|
|
|
931
948
|
*/
|
|
932
949
|
async query(callback, query) {
|
|
933
950
|
await sleep();
|
|
951
|
+
const floor = this._snap_floor(query);
|
|
952
|
+
const lo = floor >= 0 ? floor : this._first_index_after(query?.after ?? -1);
|
|
953
|
+
const hi = query?.before !== void 0 ? this._first_index_after(query.before - 1) : this._events.length;
|
|
954
|
+
const with_pii = query?.with_pii !== false;
|
|
934
955
|
let count = 0;
|
|
935
|
-
let
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
if (query?.backward) {
|
|
946
|
-
const floor_id = floor_index >= 0 ? this._events[floor_index].id : -1;
|
|
947
|
-
let i = (query?.before !== void 0 ? this._first_index_after(query.before - 1) : this._events.length) - 1;
|
|
948
|
-
while (i >= 0) {
|
|
949
|
-
const e = this._events[i--];
|
|
950
|
-
if (query && !this.in_query(query, e)) continue;
|
|
951
|
-
if (query?.created_before && e.created >= query.created_before)
|
|
952
|
-
continue;
|
|
953
|
-
if (query.after !== void 0 && e.id <= query.after) break;
|
|
954
|
-
if (floor_id >= 0 && e.id < floor_id) break;
|
|
955
|
-
if (query.created_after && e.created <= query.created_after) continue;
|
|
956
|
-
await Promise.resolve(
|
|
957
|
-
callback(
|
|
958
|
-
this._with_pii(
|
|
959
|
-
e,
|
|
960
|
-
query?.with_pii !== false
|
|
961
|
-
)
|
|
962
|
-
)
|
|
963
|
-
);
|
|
964
|
-
count++;
|
|
965
|
-
if (query?.limit && count >= query.limit) break;
|
|
966
|
-
}
|
|
967
|
-
} else {
|
|
968
|
-
let i = floor_index >= 0 ? floor_index : this._first_index_after(query?.after ?? -1);
|
|
969
|
-
while (i < this._events.length) {
|
|
970
|
-
const e = this._events[i++];
|
|
971
|
-
if (query && !this.in_query(query, e)) continue;
|
|
972
|
-
if (query?.created_after && e.created <= query.created_after) continue;
|
|
973
|
-
if (query?.before !== void 0 && e.id >= query.before) break;
|
|
974
|
-
if (query?.created_before && e.created >= query.created_before)
|
|
975
|
-
continue;
|
|
976
|
-
await Promise.resolve(
|
|
977
|
-
callback(
|
|
978
|
-
this._with_pii(
|
|
979
|
-
e,
|
|
980
|
-
query?.with_pii !== false
|
|
981
|
-
)
|
|
982
|
-
)
|
|
983
|
-
);
|
|
984
|
-
count++;
|
|
985
|
-
if (query?.limit && count >= query.limit) break;
|
|
986
|
-
}
|
|
956
|
+
for (let k = 0; k < hi - lo; k++) {
|
|
957
|
+
const e = this._events[query?.backward ? hi - 1 - k : lo + k];
|
|
958
|
+
if (query && !this.in_query(query, e)) continue;
|
|
959
|
+
await Promise.resolve(
|
|
960
|
+
callback(this._with_pii(e, with_pii))
|
|
961
|
+
);
|
|
962
|
+
count++;
|
|
963
|
+
if (query?.limit && count >= query.limit) break;
|
|
987
964
|
}
|
|
988
965
|
return count;
|
|
989
966
|
}
|
|
@@ -2509,6 +2486,151 @@ var CloseSignal = class extends Error {
|
|
|
2509
2486
|
}
|
|
2510
2487
|
};
|
|
2511
2488
|
|
|
2489
|
+
// src/internal/config.ts
|
|
2490
|
+
var import_zod6 = require("zod");
|
|
2491
|
+
var BackoffOptionsSchema = import_zod6.z.object({
|
|
2492
|
+
strategy: import_zod6.z.enum(["fixed", "linear", "exponential"]),
|
|
2493
|
+
baseMs: import_zod6.z.number().min(0),
|
|
2494
|
+
maxMs: import_zod6.z.number().gt(0).optional(),
|
|
2495
|
+
jitter: import_zod6.z.boolean().optional()
|
|
2496
|
+
});
|
|
2497
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
2498
|
+
var DEFAULT_BLOCK_ON_ERROR = true;
|
|
2499
|
+
var ReactionOptionsSchema = import_zod6.z.object({
|
|
2500
|
+
blockOnError: import_zod6.z.boolean(),
|
|
2501
|
+
maxRetries: import_zod6.z.number().int().min(0),
|
|
2502
|
+
backoff: BackoffOptionsSchema.optional()
|
|
2503
|
+
});
|
|
2504
|
+
function resolveReactionConfig(options) {
|
|
2505
|
+
return ReactionOptionsSchema.parse(options);
|
|
2506
|
+
}
|
|
2507
|
+
var ActionOptionsSchema = import_zod6.z.object({
|
|
2508
|
+
maxRetries: import_zod6.z.number().int().min(0).optional(),
|
|
2509
|
+
backoff: BackoffOptionsSchema.optional()
|
|
2510
|
+
});
|
|
2511
|
+
function resolveActionConfig(options) {
|
|
2512
|
+
return ActionOptionsSchema.parse(options);
|
|
2513
|
+
}
|
|
2514
|
+
var LaneConfigSchema = import_zod6.z.object({
|
|
2515
|
+
name: import_zod6.z.string().min(1),
|
|
2516
|
+
leaseMillis: import_zod6.z.number().min(0).optional(),
|
|
2517
|
+
streamLimit: import_zod6.z.number().int().min(0).optional(),
|
|
2518
|
+
cycleMs: import_zod6.z.number().min(0).optional()
|
|
2519
|
+
});
|
|
2520
|
+
function resolveLaneConfig(options) {
|
|
2521
|
+
LaneConfigSchema.parse(options);
|
|
2522
|
+
return options;
|
|
2523
|
+
}
|
|
2524
|
+
var DEFAULT_STREAM_LIMIT = 10;
|
|
2525
|
+
var DEFAULT_EVENT_LIMIT = 10;
|
|
2526
|
+
var DEFAULT_LEASE_MILLIS = 1e4;
|
|
2527
|
+
var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
|
|
2528
|
+
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
2529
|
+
var DrainOptionsSchema = import_zod6.z.object({
|
|
2530
|
+
streamLimit: import_zod6.z.number().int().min(0).optional(),
|
|
2531
|
+
eventLimit: import_zod6.z.number().int().min(0).optional(),
|
|
2532
|
+
leaseMillis: import_zod6.z.number().min(0).optional()
|
|
2533
|
+
});
|
|
2534
|
+
function resolveDrainConfig(options) {
|
|
2535
|
+
if (options === void 0) return void 0;
|
|
2536
|
+
DrainOptionsSchema.parse(options);
|
|
2537
|
+
return options;
|
|
2538
|
+
}
|
|
2539
|
+
var SettleOptionsSchema = DrainOptionsSchema.extend({
|
|
2540
|
+
debounceMs: import_zod6.z.number().min(0).optional(),
|
|
2541
|
+
// `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
|
|
2542
|
+
// a non-negative integer — `0` is legal and means "run no passes".
|
|
2543
|
+
//
|
|
2544
|
+
// `Infinity` is accepted explicitly. It is the documented default
|
|
2545
|
+
// (`Act.settle`'s doc-comment and `SettleOptions.maxPasses` both name it),
|
|
2546
|
+
// and `settle.ts` destructures `maxPasses = Infinity`, so the one value the
|
|
2547
|
+
// docs advertise was the one value `z.number().int()` refused — Zod 4
|
|
2548
|
+
// rejects `±Infinity`, and an integer check cannot pass it either. Wrapper
|
|
2549
|
+
// code normalizing an optional cap (`maxPasses: opts.maxPasses ?? Infinity`)
|
|
2550
|
+
// is the natural way to hit it. Accepting it also honors this module's own
|
|
2551
|
+
// rule that a value which works today is never newly rejected (#1711).
|
|
2552
|
+
maxPasses: import_zod6.z.union([import_zod6.z.number().int().min(0), import_zod6.z.literal(Number.POSITIVE_INFINITY)]).optional()
|
|
2553
|
+
}).loose();
|
|
2554
|
+
function resolveSettleConfig(options) {
|
|
2555
|
+
if (options === void 0) return void 0;
|
|
2556
|
+
SettleOptionsSchema.parse(options);
|
|
2557
|
+
return options;
|
|
2558
|
+
}
|
|
2559
|
+
var MAX_SHUTDOWN_GRACE_MS = 3e4;
|
|
2560
|
+
var DEFAULT_SHUTDOWN_GRACE_MS = DEFAULT_LEASE_MILLIS;
|
|
2561
|
+
var ShutdownOptionsSchema = import_zod6.z.object({
|
|
2562
|
+
// `0` is legal and is exactly today's behavior: stop scheduling and
|
|
2563
|
+
// return without waiting.
|
|
2564
|
+
graceMs: import_zod6.z.number().min(0).optional()
|
|
2565
|
+
}).loose();
|
|
2566
|
+
function resolveShutdownConfig(options) {
|
|
2567
|
+
if (options === void 0) return void 0;
|
|
2568
|
+
ShutdownOptionsSchema.parse(options);
|
|
2569
|
+
return options;
|
|
2570
|
+
}
|
|
2571
|
+
var ActOptionsSchema = import_zod6.z.object({
|
|
2572
|
+
maxSubscribedStreams: import_zod6.z.number().int().min(1).optional(),
|
|
2573
|
+
settleDebounceMs: import_zod6.z.number().int().min(0).optional()
|
|
2574
|
+
}).loose();
|
|
2575
|
+
function resolveActConfig(options) {
|
|
2576
|
+
if (options === void 0) return void 0;
|
|
2577
|
+
ActOptionsSchema.parse(options);
|
|
2578
|
+
return options;
|
|
2579
|
+
}
|
|
2580
|
+
var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
|
|
2581
|
+
var DEFAULT_CLOSE_BATCH_SIZE = 64;
|
|
2582
|
+
var DEFAULT_CLOSE_YIELD_MS = 0;
|
|
2583
|
+
var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
|
|
2584
|
+
function is_valid_time_zone(tz) {
|
|
2585
|
+
try {
|
|
2586
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
2587
|
+
return true;
|
|
2588
|
+
} catch {
|
|
2589
|
+
return false;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
var AutocloseWindowSchema = import_zod6.z.object({
|
|
2593
|
+
start: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
|
|
2594
|
+
end: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
|
|
2595
|
+
timeZone: import_zod6.z.string().refine(is_valid_time_zone, {
|
|
2596
|
+
message: "autocloseWindow.timeZone must be a valid IANA time zone"
|
|
2597
|
+
}).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
|
|
2598
|
+
}).refine((w) => w.start !== w.end, {
|
|
2599
|
+
message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
|
|
2600
|
+
});
|
|
2601
|
+
var AutocloseOptionsSchema = import_zod6.z.object({
|
|
2602
|
+
autocloseCycleMinutes: import_zod6.z.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
|
|
2603
|
+
closeBatchSize: import_zod6.z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
|
|
2604
|
+
closeYieldMs: import_zod6.z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
|
|
2605
|
+
closeOnError: import_zod6.z.boolean().default(false),
|
|
2606
|
+
autocloseWindow: AutocloseWindowSchema.optional()
|
|
2607
|
+
});
|
|
2608
|
+
function resolveAutocloseConfig(options) {
|
|
2609
|
+
return AutocloseOptionsSchema.parse({
|
|
2610
|
+
autocloseCycleMinutes: options?.autocloseCycleMinutes,
|
|
2611
|
+
closeBatchSize: options?.closeBatchSize,
|
|
2612
|
+
closeYieldMs: options?.closeYieldMs,
|
|
2613
|
+
closeOnError: options?.closeOnError,
|
|
2614
|
+
autocloseWindow: options?.autocloseWindow
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
|
|
2618
|
+
var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
|
|
2619
|
+
var CircuitBreakerOptionsSchema = import_zod6.z.object({
|
|
2620
|
+
failureThreshold: import_zod6.z.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
|
|
2621
|
+
cooldownMs: import_zod6.z.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
|
|
2622
|
+
});
|
|
2623
|
+
var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
|
|
2624
|
+
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
2625
|
+
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
2626
|
+
var FoldOptionsSchema = import_zod6.z.object({
|
|
2627
|
+
flushEvery: import_zod6.z.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
2628
|
+
maxCachedStates: import_zod6.z.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
2629
|
+
});
|
|
2630
|
+
function resolveFoldConfig(options) {
|
|
2631
|
+
return FoldOptionsSchema.parse(options);
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2512
2634
|
// src/internal/defer-signal.ts
|
|
2513
2635
|
var DeferSignal = class extends Error {
|
|
2514
2636
|
/**
|
|
@@ -2545,7 +2667,7 @@ function synthesize_autoclose_reactions(registry, states, read_window) {
|
|
|
2545
2667
|
}),
|
|
2546
2668
|
// Never block on autoclose: a transient query/store error should retry,
|
|
2547
2669
|
// not quarantine the synthetic stream.
|
|
2548
|
-
options: { blockOnError: false, maxRetries:
|
|
2670
|
+
options: { blockOnError: false, maxRetries: DEFAULT_MAX_RETRIES },
|
|
2549
2671
|
handler: async (event) => {
|
|
2550
2672
|
const aggregate = event.stream;
|
|
2551
2673
|
const window = read_window();
|
|
@@ -3032,144 +3154,6 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
|
|
|
3032
3154
|
return truncated;
|
|
3033
3155
|
}
|
|
3034
3156
|
|
|
3035
|
-
// src/internal/config.ts
|
|
3036
|
-
var import_zod6 = require("zod");
|
|
3037
|
-
var BackoffOptionsSchema = import_zod6.z.object({
|
|
3038
|
-
strategy: import_zod6.z.enum(["fixed", "linear", "exponential"]),
|
|
3039
|
-
baseMs: import_zod6.z.number().min(0),
|
|
3040
|
-
maxMs: import_zod6.z.number().gt(0).optional(),
|
|
3041
|
-
jitter: import_zod6.z.boolean().optional()
|
|
3042
|
-
});
|
|
3043
|
-
var ReactionOptionsSchema = import_zod6.z.object({
|
|
3044
|
-
blockOnError: import_zod6.z.boolean(),
|
|
3045
|
-
maxRetries: import_zod6.z.number().int().min(0),
|
|
3046
|
-
backoff: BackoffOptionsSchema.optional()
|
|
3047
|
-
});
|
|
3048
|
-
function resolveReactionConfig(options) {
|
|
3049
|
-
return ReactionOptionsSchema.parse(options);
|
|
3050
|
-
}
|
|
3051
|
-
var ActionOptionsSchema = import_zod6.z.object({
|
|
3052
|
-
maxRetries: import_zod6.z.number().int().min(0).optional(),
|
|
3053
|
-
backoff: BackoffOptionsSchema.optional()
|
|
3054
|
-
});
|
|
3055
|
-
function resolveActionConfig(options) {
|
|
3056
|
-
return ActionOptionsSchema.parse(options);
|
|
3057
|
-
}
|
|
3058
|
-
var LaneConfigSchema = import_zod6.z.object({
|
|
3059
|
-
name: import_zod6.z.string().min(1),
|
|
3060
|
-
leaseMillis: import_zod6.z.number().min(0).optional(),
|
|
3061
|
-
streamLimit: import_zod6.z.number().int().min(0).optional(),
|
|
3062
|
-
cycleMs: import_zod6.z.number().min(0).optional()
|
|
3063
|
-
});
|
|
3064
|
-
function resolveLaneConfig(options) {
|
|
3065
|
-
LaneConfigSchema.parse(options);
|
|
3066
|
-
return options;
|
|
3067
|
-
}
|
|
3068
|
-
var DrainOptionsSchema = import_zod6.z.object({
|
|
3069
|
-
streamLimit: import_zod6.z.number().int().min(0).optional(),
|
|
3070
|
-
eventLimit: import_zod6.z.number().int().min(0).optional(),
|
|
3071
|
-
leaseMillis: import_zod6.z.number().min(0).optional()
|
|
3072
|
-
});
|
|
3073
|
-
function resolveDrainConfig(options) {
|
|
3074
|
-
if (options === void 0) return void 0;
|
|
3075
|
-
DrainOptionsSchema.parse(options);
|
|
3076
|
-
return options;
|
|
3077
|
-
}
|
|
3078
|
-
var SettleOptionsSchema = DrainOptionsSchema.extend({
|
|
3079
|
-
debounceMs: import_zod6.z.number().min(0).optional(),
|
|
3080
|
-
// `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
|
|
3081
|
-
// a non-negative integer — `0` is legal and means "run no passes".
|
|
3082
|
-
//
|
|
3083
|
-
// `Infinity` is accepted explicitly. It is the documented default
|
|
3084
|
-
// (`Act.settle`'s doc-comment and `SettleOptions.maxPasses` both name it),
|
|
3085
|
-
// and `settle.ts` destructures `maxPasses = Infinity`, so the one value the
|
|
3086
|
-
// docs advertise was the one value `z.number().int()` refused — Zod 4
|
|
3087
|
-
// rejects `±Infinity`, and an integer check cannot pass it either. Wrapper
|
|
3088
|
-
// code normalizing an optional cap (`maxPasses: opts.maxPasses ?? Infinity`)
|
|
3089
|
-
// is the natural way to hit it. Accepting it also honors this module's own
|
|
3090
|
-
// rule that a value which works today is never newly rejected (#1711).
|
|
3091
|
-
maxPasses: import_zod6.z.union([import_zod6.z.number().int().min(0), import_zod6.z.literal(Number.POSITIVE_INFINITY)]).optional()
|
|
3092
|
-
}).loose();
|
|
3093
|
-
function resolveSettleConfig(options) {
|
|
3094
|
-
if (options === void 0) return void 0;
|
|
3095
|
-
SettleOptionsSchema.parse(options);
|
|
3096
|
-
return options;
|
|
3097
|
-
}
|
|
3098
|
-
var MAX_SHUTDOWN_GRACE_MS = 3e4;
|
|
3099
|
-
var DEFAULT_SHUTDOWN_GRACE_MS = 1e4;
|
|
3100
|
-
var ShutdownOptionsSchema = import_zod6.z.object({
|
|
3101
|
-
// `0` is legal and is exactly today's behavior: stop scheduling and
|
|
3102
|
-
// return without waiting.
|
|
3103
|
-
graceMs: import_zod6.z.number().min(0).optional()
|
|
3104
|
-
}).loose();
|
|
3105
|
-
function resolveShutdownConfig(options) {
|
|
3106
|
-
if (options === void 0) return void 0;
|
|
3107
|
-
ShutdownOptionsSchema.parse(options);
|
|
3108
|
-
return options;
|
|
3109
|
-
}
|
|
3110
|
-
var ActOptionsSchema = import_zod6.z.object({
|
|
3111
|
-
maxSubscribedStreams: import_zod6.z.number().int().min(1).optional(),
|
|
3112
|
-
settleDebounceMs: import_zod6.z.number().int().min(0).optional()
|
|
3113
|
-
}).loose();
|
|
3114
|
-
function resolveActConfig(options) {
|
|
3115
|
-
if (options === void 0) return void 0;
|
|
3116
|
-
ActOptionsSchema.parse(options);
|
|
3117
|
-
return options;
|
|
3118
|
-
}
|
|
3119
|
-
var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
|
|
3120
|
-
var DEFAULT_CLOSE_BATCH_SIZE = 64;
|
|
3121
|
-
var DEFAULT_CLOSE_YIELD_MS = 0;
|
|
3122
|
-
var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
|
|
3123
|
-
function is_valid_time_zone(tz) {
|
|
3124
|
-
try {
|
|
3125
|
-
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
3126
|
-
return true;
|
|
3127
|
-
} catch {
|
|
3128
|
-
return false;
|
|
3129
|
-
}
|
|
3130
|
-
}
|
|
3131
|
-
var AutocloseWindowSchema = import_zod6.z.object({
|
|
3132
|
-
start: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
|
|
3133
|
-
end: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
|
|
3134
|
-
timeZone: import_zod6.z.string().refine(is_valid_time_zone, {
|
|
3135
|
-
message: "autocloseWindow.timeZone must be a valid IANA time zone"
|
|
3136
|
-
}).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
|
|
3137
|
-
}).refine((w) => w.start !== w.end, {
|
|
3138
|
-
message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
|
|
3139
|
-
});
|
|
3140
|
-
var AutocloseOptionsSchema = import_zod6.z.object({
|
|
3141
|
-
autocloseCycleMinutes: import_zod6.z.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
|
|
3142
|
-
closeBatchSize: import_zod6.z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
|
|
3143
|
-
closeYieldMs: import_zod6.z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
|
|
3144
|
-
closeOnError: import_zod6.z.boolean().default(false),
|
|
3145
|
-
autocloseWindow: AutocloseWindowSchema.optional()
|
|
3146
|
-
});
|
|
3147
|
-
function resolveAutocloseConfig(options) {
|
|
3148
|
-
return AutocloseOptionsSchema.parse({
|
|
3149
|
-
autocloseCycleMinutes: options?.autocloseCycleMinutes,
|
|
3150
|
-
closeBatchSize: options?.closeBatchSize,
|
|
3151
|
-
closeYieldMs: options?.closeYieldMs,
|
|
3152
|
-
closeOnError: options?.closeOnError,
|
|
3153
|
-
autocloseWindow: options?.autocloseWindow
|
|
3154
|
-
});
|
|
3155
|
-
}
|
|
3156
|
-
var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
|
|
3157
|
-
var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
|
|
3158
|
-
var CircuitBreakerOptionsSchema = import_zod6.z.object({
|
|
3159
|
-
failureThreshold: import_zod6.z.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
|
|
3160
|
-
cooldownMs: import_zod6.z.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
|
|
3161
|
-
});
|
|
3162
|
-
var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
|
|
3163
|
-
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
3164
|
-
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
3165
|
-
var FoldOptionsSchema = import_zod6.z.object({
|
|
3166
|
-
flushEvery: import_zod6.z.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
3167
|
-
maxCachedStates: import_zod6.z.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
3168
|
-
});
|
|
3169
|
-
function resolveFoldConfig(options) {
|
|
3170
|
-
return FoldOptionsSchema.parse(options);
|
|
3171
|
-
}
|
|
3172
|
-
|
|
3173
3157
|
// src/internal/correlate-cycle.ts
|
|
3174
3158
|
var import_node_crypto = require("crypto");
|
|
3175
3159
|
|
|
@@ -3247,7 +3231,6 @@ var CorrelateCycle = class {
|
|
|
3247
3231
|
_armed = true;
|
|
3248
3232
|
/** In-flight init, memoized for single-flight and cleared on failure. */
|
|
3249
3233
|
_init_promise;
|
|
3250
|
-
_timer = void 0;
|
|
3251
3234
|
// Dynamically discovered targets → what each was last subscribed at,
|
|
3252
3235
|
// bounded by `maxSubscribedStreams`. The static half lives in
|
|
3253
3236
|
// `_static_subscriptions` below, which is deliberately not evictable.
|
|
@@ -3291,14 +3274,6 @@ var CorrelateCycle = class {
|
|
|
3291
3274
|
* guarantee without a second gate on the Act side.
|
|
3292
3275
|
*/
|
|
3293
3276
|
_on_init_async;
|
|
3294
|
-
/**
|
|
3295
|
-
* Scope runner (#1191). The periodic `start_polling` timer fires
|
|
3296
|
-
* outside any caller frame, so its `correlate()` must be re-wrapped in
|
|
3297
|
-
* the Act's `_scoped` bag or `store()`/`cache()` resolve to the
|
|
3298
|
-
* singleton for a scoped Act. The orchestrator always threads its
|
|
3299
|
-
* `_scoped` (identity for a non-scoped Act), so it's required.
|
|
3300
|
-
*/
|
|
3301
|
-
_run_scoped;
|
|
3302
3277
|
/**
|
|
3303
3278
|
* Tail re-scan window applied to the cold-start checkpoint (ACT-1207).
|
|
3304
3279
|
* See {@link DEFAULT_COLD_START_BACK_SCAN}. Constructor arg (not a
|
|
@@ -3319,7 +3294,6 @@ var CorrelateCycle = class {
|
|
|
3319
3294
|
cd,
|
|
3320
3295
|
max_subscribed_streams,
|
|
3321
3296
|
declared_lanes,
|
|
3322
|
-
run_scoped,
|
|
3323
3297
|
on_init,
|
|
3324
3298
|
on_init_async,
|
|
3325
3299
|
cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN,
|
|
@@ -3333,7 +3307,6 @@ var CorrelateCycle = class {
|
|
|
3333
3307
|
this._static_targets = static_targets;
|
|
3334
3308
|
this._cd = cd;
|
|
3335
3309
|
this._on_init = on_init;
|
|
3336
|
-
this._run_scoped = run_scoped;
|
|
3337
3310
|
this._on_init_async = on_init_async;
|
|
3338
3311
|
this._cold_start_back_scan = cold_start_back_scan;
|
|
3339
3312
|
}
|
|
@@ -3581,33 +3554,6 @@ var CorrelateCycle = class {
|
|
|
3581
3554
|
this._armed = found === query.limit;
|
|
3582
3555
|
return { subscribed: 0, last_id, marked: 0, scanned: true };
|
|
3583
3556
|
}
|
|
3584
|
-
/**
|
|
3585
|
-
* Start a periodic correlation worker. Returns false if one is already
|
|
3586
|
-
* running. Errors from `correlate()` are routed through `log()` so they
|
|
3587
|
-
* land in the configured logger (the timer keeps running on failure).
|
|
3588
|
-
*/
|
|
3589
|
-
start_polling(query = {}, frequency = 1e4, callback) {
|
|
3590
|
-
if (this._timer) return false;
|
|
3591
|
-
const limit = query.limit || 100;
|
|
3592
|
-
this._timer = setInterval(
|
|
3593
|
-
() => this._run_scoped(() => {
|
|
3594
|
-
this.arm();
|
|
3595
|
-
return this.correlate(
|
|
3596
|
-
{
|
|
3597
|
-
...query,
|
|
3598
|
-
after: this._checkpoint,
|
|
3599
|
-
limit
|
|
3600
|
-
},
|
|
3601
|
-
true
|
|
3602
|
-
);
|
|
3603
|
-
}).then((result) => {
|
|
3604
|
-
if (callback && result.subscribed) callback(result.subscribed);
|
|
3605
|
-
}).catch((err) => log().error(err)),
|
|
3606
|
-
frequency
|
|
3607
|
-
);
|
|
3608
|
-
return true;
|
|
3609
|
-
}
|
|
3610
|
-
/** Stop the periodic correlation worker. Idempotent. */
|
|
3611
3557
|
/**
|
|
3612
3558
|
* Hand the correlation lease back early.
|
|
3613
3559
|
*
|
|
@@ -3638,12 +3584,6 @@ var CorrelateCycle = class {
|
|
|
3638
3584
|
);
|
|
3639
3585
|
}
|
|
3640
3586
|
}
|
|
3641
|
-
stop_polling() {
|
|
3642
|
-
if (this._timer) {
|
|
3643
|
-
clearInterval(this._timer);
|
|
3644
|
-
this._timer = void 0;
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
3587
|
};
|
|
3648
3588
|
|
|
3649
3589
|
// src/internal/correlator.ts
|
|
@@ -4741,9 +4681,9 @@ var DrainController = class {
|
|
|
4741
4681
|
if (this._deps.breaker.state(Date.now()) === "open")
|
|
4742
4682
|
return EMPTY_DRAIN;
|
|
4743
4683
|
const d = this._deps.defaults ?? {};
|
|
4744
|
-
const streamLimit = d.streamLimit ?? options.streamLimit ??
|
|
4745
|
-
const eventLimit = d.eventLimit ?? options.eventLimit ??
|
|
4746
|
-
const leaseMillis = d.leaseMillis ?? options.leaseMillis ??
|
|
4684
|
+
const streamLimit = d.streamLimit ?? options.streamLimit ?? DEFAULT_STREAM_LIMIT;
|
|
4685
|
+
const eventLimit = d.eventLimit ?? options.eventLimit ?? DEFAULT_EVENT_LIMIT;
|
|
4686
|
+
const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? DEFAULT_LEASE_MILLIS;
|
|
4747
4687
|
try {
|
|
4748
4688
|
this._locked = true;
|
|
4749
4689
|
this._inflight = new Promise((done) => {
|
|
@@ -5117,10 +5057,8 @@ function build_handle_batch(logger) {
|
|
|
5117
5057
|
}
|
|
5118
5058
|
|
|
5119
5059
|
// src/act.ts
|
|
5120
|
-
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
5121
5060
|
var CLOSE_CATCH_UP_LIMIT = 1e3;
|
|
5122
5061
|
var CLOSE_CATCH_UP_PASSES = 20;
|
|
5123
|
-
var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
|
|
5124
5062
|
function validate_only_lanes(options, lanes) {
|
|
5125
5063
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
5126
5064
|
const declared = /* @__PURE__ */ new Set(["default", ...lanes.map((l) => l.name)]);
|
|
@@ -5146,6 +5084,10 @@ var Act = class {
|
|
|
5146
5084
|
_correlate;
|
|
5147
5085
|
/** Debounced correlate→drain catch-up loop. */
|
|
5148
5086
|
_settle;
|
|
5087
|
+
/** Timer behind {@link start_correlations}. */
|
|
5088
|
+
_poll;
|
|
5089
|
+
/** The deprecated `start_correlations` callback, while the poll runs. */
|
|
5090
|
+
_on_discovered;
|
|
5149
5091
|
/**
|
|
5150
5092
|
* Disposer for the cross-process notify subscription, set up eagerly
|
|
5151
5093
|
* during construction. Held as a promise because the subscription
|
|
@@ -5555,9 +5497,6 @@ var Act = class {
|
|
|
5555
5497
|
on_init: () => {
|
|
5556
5498
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
5557
5499
|
},
|
|
5558
|
-
// Re-scope the background `start_correlations` timer so its
|
|
5559
|
-
// correlate resolves the scoped ports, not the singleton (#1191).
|
|
5560
|
-
run_scoped: this._scoped,
|
|
5561
5500
|
// Cold-start defer re-seed (#1221). Skipped on writer-only instances
|
|
5562
5501
|
// (`drain: false`) — they run no local controllers to re-arm.
|
|
5563
5502
|
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
@@ -6147,86 +6086,63 @@ var Act = class {
|
|
|
6147
6086
|
const { subscribed, last_id, marked, scanned } = await this._correlate.correlate(query, lease);
|
|
6148
6087
|
if ((subscribed > 0 || marked > 0) && this._reactive_events.size > 0)
|
|
6149
6088
|
this._arm_all();
|
|
6089
|
+
if (subscribed > 0) this._on_discovered?.(subscribed);
|
|
6150
6090
|
return { subscribed, last_id, scanned };
|
|
6151
6091
|
});
|
|
6152
6092
|
}
|
|
6153
6093
|
/**
|
|
6154
|
-
* Starts
|
|
6155
|
-
*
|
|
6156
|
-
*
|
|
6157
|
-
*
|
|
6158
|
-
*
|
|
6159
|
-
*
|
|
6160
|
-
*
|
|
6161
|
-
*
|
|
6094
|
+
* Starts a background poll that settles on a timer: each tick looks for
|
|
6095
|
+
* new events, subscribes the streams their reactions target, and drains
|
|
6096
|
+
* them.
|
|
6097
|
+
*
|
|
6098
|
+
* Use it when this process can miss commits — another process writes to a
|
|
6099
|
+
* store without `notify`, or a notification is lost. It is the safety net
|
|
6100
|
+
* under `notify`: a missed wakeup costs at most one `frequency` of latency.
|
|
6101
|
+
*
|
|
6102
|
+
* Only one poll runs per Act instance. On a writer-only instance
|
|
6103
|
+
* (`drain: false`) each tick is a no-op, like `settle()`.
|
|
6104
|
+
*
|
|
6105
|
+
* @param query - Only `limit` is used: the most events each correlate pass
|
|
6106
|
+
* reads (default 100). The scan always starts at the correlation
|
|
6107
|
+
* checkpoint, so `after` is ignored.
|
|
6108
|
+
* @param frequency - Milliseconds between ticks (default: 10000)
|
|
6109
|
+
* @param callback - Called with the number of newly subscribed streams
|
|
6110
|
+
* after each correlate pass that found some.
|
|
6111
|
+
* @deprecated Listen to the `"settled"` lifecycle event instead.
|
|
6112
|
+
* @returns `true` if the poll started, `false` if one is already running
|
|
6162
6113
|
*
|
|
6163
|
-
*
|
|
6164
|
-
*
|
|
6165
|
-
* @param query - Query filter for correlation scans — see {@link Query}
|
|
6166
|
-
* (typically `{ after: -1, limit: 100 }`)
|
|
6167
|
-
* @param frequency - Correlation frequency in milliseconds (default: 10000)
|
|
6168
|
-
* @param callback - Optional callback invoked with newly discovered streams
|
|
6169
|
-
* @returns `true` if worker started, `false` if already running
|
|
6170
|
-
*
|
|
6171
|
-
* @example Start automatic correlation
|
|
6114
|
+
* @example
|
|
6172
6115
|
* ```typescript
|
|
6173
|
-
*
|
|
6174
|
-
*
|
|
6175
|
-
* { after: 0, limit: 100 },
|
|
6176
|
-
* 5000,
|
|
6177
|
-
* (leased) => {
|
|
6178
|
-
* console.log(`Discovered ${leased.length} new streams`);
|
|
6179
|
-
* }
|
|
6180
|
-
* );
|
|
6181
|
-
*
|
|
6182
|
-
* // Later, stop it
|
|
6116
|
+
* app.start_correlations({ limit: 100 }, 5000);
|
|
6117
|
+
* // later
|
|
6183
6118
|
* app.stop_correlations();
|
|
6184
6119
|
* ```
|
|
6185
6120
|
*
|
|
6186
|
-
* @
|
|
6187
|
-
*
|
|
6188
|
-
* // Load last checkpoint
|
|
6189
|
-
* const lastId = await loadCheckpoint();
|
|
6190
|
-
*
|
|
6191
|
-
* app.start_correlations(
|
|
6192
|
-
* { after: lastId, limit: 100 },
|
|
6193
|
-
* 10000,
|
|
6194
|
-
* async (leased) => {
|
|
6195
|
-
* // Save checkpoint for next restart
|
|
6196
|
-
* if (leased.length) {
|
|
6197
|
-
* const maxId = Math.max(...leased.map(l => l.at));
|
|
6198
|
-
* await saveCheckpoint(maxId);
|
|
6199
|
-
* }
|
|
6200
|
-
* }
|
|
6201
|
-
* );
|
|
6202
|
-
* ```
|
|
6203
|
-
*
|
|
6204
|
-
* @see {@link correlate} for manual one-time correlation
|
|
6205
|
-
* @see {@link stop_correlations} to stop the worker
|
|
6121
|
+
* @see {@link settle} for what each tick runs
|
|
6122
|
+
* @see {@link stop_correlations} to stop the poll
|
|
6206
6123
|
*/
|
|
6207
6124
|
start_correlations(query = {}, frequency = 1e4, callback) {
|
|
6208
|
-
|
|
6209
|
-
|
|
6125
|
+
if (this._poll) return false;
|
|
6126
|
+
this._on_discovered = callback;
|
|
6127
|
+
const correlate = { limit: query.limit || 100 };
|
|
6128
|
+
this._poll = setInterval(() => {
|
|
6129
|
+
this._correlate.arm();
|
|
6130
|
+
this.settle({ debounceMs: 0, correlate });
|
|
6131
|
+
}, frequency);
|
|
6132
|
+
return true;
|
|
6210
6133
|
}
|
|
6211
6134
|
/**
|
|
6212
|
-
* Stops the
|
|
6213
|
-
*
|
|
6214
|
-
* Call this to stop the background correlation worker started by {@link start_correlations}.
|
|
6215
|
-
* This is automatically called when the Act instance is disposed.
|
|
6216
|
-
*
|
|
6217
|
-
* @example
|
|
6218
|
-
* ```typescript
|
|
6219
|
-
* // Start correlation
|
|
6220
|
-
* app.start_correlations();
|
|
6221
|
-
*
|
|
6222
|
-
* // Later, stop it
|
|
6223
|
-
* app.stop_correlations();
|
|
6224
|
-
* ```
|
|
6135
|
+
* Stops the poll started by {@link start_correlations}. Idempotent, and
|
|
6136
|
+
* called by `shutdown()`.
|
|
6225
6137
|
*
|
|
6226
6138
|
* @see {@link start_correlations}
|
|
6227
6139
|
*/
|
|
6228
6140
|
stop_correlations() {
|
|
6229
|
-
this.
|
|
6141
|
+
if (this._poll) {
|
|
6142
|
+
clearInterval(this._poll);
|
|
6143
|
+
this._poll = void 0;
|
|
6144
|
+
}
|
|
6145
|
+
this._on_discovered = void 0;
|
|
6230
6146
|
void this._scoped(() => this._correlate.release_correlation());
|
|
6231
6147
|
}
|
|
6232
6148
|
/**
|
|
@@ -6883,8 +6799,8 @@ function reaction_on(event, events, builder) {
|
|
|
6883
6799
|
// `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
|
|
6884
6800
|
// not a NaN gate on the first retry.
|
|
6885
6801
|
options: resolveReactionConfig({
|
|
6886
|
-
blockOnError: options?.blockOnError ??
|
|
6887
|
-
maxRetries: options?.maxRetries ??
|
|
6802
|
+
blockOnError: options?.blockOnError ?? DEFAULT_BLOCK_ON_ERROR,
|
|
6803
|
+
maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
6888
6804
|
backoff: options?.backoff
|
|
6889
6805
|
})
|
|
6890
6806
|
};
|
|
@@ -7275,8 +7191,8 @@ function _projection(target) {
|
|
|
7275
7191
|
handler,
|
|
7276
7192
|
resolver: default_resolver ?? _this_,
|
|
7277
7193
|
options: {
|
|
7278
|
-
blockOnError:
|
|
7279
|
-
maxRetries:
|
|
7194
|
+
blockOnError: DEFAULT_BLOCK_ON_ERROR,
|
|
7195
|
+
maxRetries: DEFAULT_MAX_RETRIES
|
|
7280
7196
|
}
|
|
7281
7197
|
};
|
|
7282
7198
|
const register = events[event];
|
|
@@ -7349,7 +7265,10 @@ function _projection(target) {
|
|
|
7349
7265
|
{
|
|
7350
7266
|
handler: noop2,
|
|
7351
7267
|
resolver: { target },
|
|
7352
|
-
options: {
|
|
7268
|
+
options: {
|
|
7269
|
+
blockOnError: DEFAULT_BLOCK_ON_ERROR,
|
|
7270
|
+
maxRetries: DEFAULT_MAX_RETRIES
|
|
7271
|
+
}
|
|
7353
7272
|
}
|
|
7354
7273
|
]
|
|
7355
7274
|
])
|