@rotorsoft/act 1.23.6 → 1.23.7

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/index.cjs CHANGED
@@ -2044,84 +2044,8 @@ var PASS_FACTORIES = {
2044
2044
  "clock-anomalies": make_clock_anomalies_pass
2045
2045
  };
2046
2046
 
2047
- // src/internal/autoclose-config.ts
2048
- var import_zod5 = require("zod");
2049
- var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
2050
- var DEFAULT_CLOSE_BATCH_SIZE = 64;
2051
- var DEFAULT_CLOSE_YIELD_MS = 0;
2052
- var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
2053
- function is_valid_time_zone(tz) {
2054
- try {
2055
- new Intl.DateTimeFormat("en-US", { timeZone: tz });
2056
- return true;
2057
- } catch {
2058
- return false;
2059
- }
2060
- }
2061
- var AutocloseWindowSchema = import_zod5.z.object({
2062
- start: import_zod5.z.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
2063
- end: import_zod5.z.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
2064
- timeZone: import_zod5.z.string().refine(is_valid_time_zone, {
2065
- message: "autocloseWindow.timeZone must be a valid IANA time zone"
2066
- }).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
2067
- }).refine((w) => w.start !== w.end, {
2068
- message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
2069
- });
2070
- var AutocloseOptionsSchema = import_zod5.z.object({
2071
- autocloseCycleMinutes: import_zod5.z.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
2072
- closeBatchSize: import_zod5.z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
2073
- closeYieldMs: import_zod5.z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
2074
- closeOnError: import_zod5.z.boolean().default(false),
2075
- autocloseWindow: AutocloseWindowSchema.optional()
2076
- });
2077
- function resolveAutocloseConfig(options) {
2078
- return AutocloseOptionsSchema.parse({
2079
- autocloseCycleMinutes: options?.autocloseCycleMinutes,
2080
- closeBatchSize: options?.closeBatchSize,
2081
- closeYieldMs: options?.closeYieldMs,
2082
- closeOnError: options?.closeOnError,
2083
- autocloseWindow: options?.autocloseWindow
2084
- });
2085
- }
2086
- function hour_in_zone(now, timeZone) {
2087
- const parts = new Intl.DateTimeFormat("en-US", {
2088
- timeZone,
2089
- hour: "2-digit",
2090
- hourCycle: "h23"
2091
- }).formatToParts(now);
2092
- return Number(parts.find((p) => p.type === "hour")?.value);
2093
- }
2094
- function hour_in_range(hour, start, end) {
2095
- return start < end ? hour >= start && hour < end : hour >= start || hour < end;
2096
- }
2097
- function is_dst_gap_open(now, timeZone, start) {
2098
- const hour = hour_in_zone(now, timeZone);
2099
- if (hour <= start) return false;
2100
- const prev = hour_in_zone(new Date(now.getTime() - 36e5), timeZone);
2101
- return prev === start - 1 && hour === start + 1;
2102
- }
2103
- function in_autoclose_window(window, now) {
2104
- if (!window) return true;
2105
- const hour = hour_in_zone(now, window.timeZone);
2106
- return hour_in_range(hour, window.start, window.end) || is_dst_gap_open(now, window.timeZone, window.start);
2107
- }
2108
- function next_window_open(window, now) {
2109
- const start_ms = now.getTime();
2110
- for (let h = 0; h <= 48; h++) {
2111
- const candidate = new Date(start_ms + h * 36e5);
2112
- if (hour_in_zone(candidate, window.timeZone) === window.start)
2113
- return candidate;
2114
- const boundary = new Date(
2115
- candidate.getTime() - candidate.getTime() % 36e5
2116
- );
2117
- if (is_dst_gap_open(boundary, window.timeZone, window.start))
2118
- return boundary;
2119
- }
2120
- return new Date(start_ms + 864e5);
2121
- }
2122
-
2123
2047
  // src/internal/autoclose-policy.ts
2124
- var import_zod6 = require("zod");
2048
+ var import_zod5 = require("zod");
2125
2049
  var DAY_MS = 864e5;
2126
2050
  var MIN_AFTER_DAYS = 1 / 1440;
2127
2051
  var MIN_KEEP_DAYS = 1;
@@ -2131,21 +2055,21 @@ function days_after(date, days) {
2131
2055
  function days_before_now(days) {
2132
2056
  return new Date(Date.now() - days * DAY_MS);
2133
2057
  }
2134
- var window_schema = (field, min_days, floor) => import_zod6.z.object({
2135
- days: import_zod6.z.number({ message: `autocloses: ${field}.days must be a number` }).positive(`autocloses: ${field}.days must be > 0`)
2058
+ var window_schema = (field, min_days, floor) => import_zod5.z.object({
2059
+ days: import_zod5.z.number({ message: `autocloses: ${field}.days must be a number` }).positive(`autocloses: ${field}.days must be > 0`)
2136
2060
  }).refine((o) => o.days >= min_days, {
2137
2061
  message: `autocloses: ${field} resolves to < ${floor}, which is too short to be a meaningful retention window`
2138
2062
  });
2139
2063
  var AfterSchema = window_schema("after", MIN_AFTER_DAYS, "one minute");
2140
2064
  var KeepSchema = window_schema("keep", MIN_KEEP_DAYS, "one day");
2141
- var IsSchema = import_zod6.z.union([
2142
- import_zod6.z.string().min(1, "autocloses: is must be a non-empty event name"),
2143
- import_zod6.z.array(
2144
- import_zod6.z.string().min(1, "autocloses: is entries must be non-empty event names")
2065
+ var IsSchema = import_zod5.z.union([
2066
+ import_zod5.z.string().min(1, "autocloses: is must be a non-empty event name"),
2067
+ import_zod5.z.array(
2068
+ import_zod5.z.string().min(1, "autocloses: is entries must be non-empty event names")
2145
2069
  ).min(1, "autocloses: is must include at least one event name").readonly()
2146
2070
  ]);
2147
- var ReachesSchema = import_zod6.z.number({ message: "autocloses: reaches must be a number" }).int("autocloses: reaches must be an integer").min(1, "autocloses: reaches must be >= 1");
2148
- var OrBlockSchema = import_zod6.z.object({
2071
+ var ReachesSchema = import_zod5.z.number({ message: "autocloses: reaches must be a number" }).int("autocloses: reaches must be an integer").min(1, "autocloses: reaches must be >= 1");
2072
+ var OrBlockSchema = import_zod5.z.object({
2149
2073
  after: AfterSchema.optional(),
2150
2074
  is: IsSchema.optional(),
2151
2075
  reaches: ReachesSchema.optional()
@@ -2155,7 +2079,7 @@ var OrBlockSchema = import_zod6.z.object({
2155
2079
  message: "autocloses: `or` block must include at least one of after / is / reaches"
2156
2080
  }
2157
2081
  );
2158
- var AutoclosePolicySchema = import_zod6.z.object({
2082
+ var AutoclosePolicySchema = import_zod5.z.object({
2159
2083
  after: AfterSchema.optional(),
2160
2084
  is: IsSchema.optional(),
2161
2085
  reaches: ReachesSchema.optional(),
@@ -2211,6 +2135,44 @@ function compile_autoclose_policy(options) {
2211
2135
  };
2212
2136
  }
2213
2137
 
2138
+ // src/internal/autoclose-window.ts
2139
+ function hour_in_zone(now, timeZone) {
2140
+ const parts = new Intl.DateTimeFormat("en-US", {
2141
+ timeZone,
2142
+ hour: "2-digit",
2143
+ hourCycle: "h23"
2144
+ }).formatToParts(now);
2145
+ return Number(parts.find((p) => p.type === "hour")?.value);
2146
+ }
2147
+ function hour_in_range(hour, start, end) {
2148
+ return start < end ? hour >= start && hour < end : hour >= start || hour < end;
2149
+ }
2150
+ function is_dst_gap_open(now, timeZone, start) {
2151
+ const hour = hour_in_zone(now, timeZone);
2152
+ if (hour <= start) return false;
2153
+ const prev = hour_in_zone(new Date(now.getTime() - 36e5), timeZone);
2154
+ return prev === start - 1 && hour === start + 1;
2155
+ }
2156
+ function in_autoclose_window(window, now) {
2157
+ if (!window) return true;
2158
+ const hour = hour_in_zone(now, window.timeZone);
2159
+ return hour_in_range(hour, window.start, window.end) || is_dst_gap_open(now, window.timeZone, window.start);
2160
+ }
2161
+ function next_window_open(window, now) {
2162
+ const start_ms = now.getTime();
2163
+ for (let h = 0; h <= 48; h++) {
2164
+ const candidate = new Date(start_ms + h * 36e5);
2165
+ if (hour_in_zone(candidate, window.timeZone) === window.start)
2166
+ return candidate;
2167
+ const boundary = new Date(
2168
+ candidate.getTime() - candidate.getTime() % 36e5
2169
+ );
2170
+ if (is_dst_gap_open(boundary, window.timeZone, window.start))
2171
+ return boundary;
2172
+ }
2173
+ return new Date(start_ms + 864e5);
2174
+ }
2175
+
2214
2176
  // src/internal/close-signal.ts
2215
2177
  var CloseSignal = class extends Error {
2216
2178
  /**
@@ -2391,6 +2353,123 @@ function classify_registry(registry, states) {
2391
2353
  };
2392
2354
  }
2393
2355
 
2356
+ // src/internal/config.ts
2357
+ var import_zod6 = require("zod");
2358
+ var BackoffOptionsSchema = import_zod6.z.object({
2359
+ strategy: import_zod6.z.enum(["fixed", "linear", "exponential"]),
2360
+ baseMs: import_zod6.z.number().min(0),
2361
+ maxMs: import_zod6.z.number().gt(0).optional(),
2362
+ jitter: import_zod6.z.boolean().optional()
2363
+ });
2364
+ var ReactionOptionsSchema = import_zod6.z.object({
2365
+ blockOnError: import_zod6.z.boolean(),
2366
+ maxRetries: import_zod6.z.number().int().min(0),
2367
+ backoff: BackoffOptionsSchema.optional()
2368
+ });
2369
+ function resolveReactionConfig(options) {
2370
+ return ReactionOptionsSchema.parse(options);
2371
+ }
2372
+ var ActionOptionsSchema = import_zod6.z.object({
2373
+ maxRetries: import_zod6.z.number().int().min(0).optional(),
2374
+ backoff: BackoffOptionsSchema.optional()
2375
+ });
2376
+ function resolveActionConfig(options) {
2377
+ return ActionOptionsSchema.parse(options);
2378
+ }
2379
+ var LaneConfigSchema = import_zod6.z.object({
2380
+ name: import_zod6.z.string().min(1),
2381
+ leaseMillis: import_zod6.z.number().min(0).optional(),
2382
+ streamLimit: import_zod6.z.number().int().min(0).optional(),
2383
+ cycleMs: import_zod6.z.number().min(0).optional()
2384
+ });
2385
+ function resolveLaneConfig(options) {
2386
+ LaneConfigSchema.parse(options);
2387
+ return options;
2388
+ }
2389
+ var DrainOptionsSchema = import_zod6.z.object({
2390
+ streamLimit: import_zod6.z.number().int().min(0).optional(),
2391
+ eventLimit: import_zod6.z.number().int().min(0).optional(),
2392
+ leaseMillis: import_zod6.z.number().min(0).optional()
2393
+ });
2394
+ function resolveDrainConfig(options) {
2395
+ if (options === void 0) return void 0;
2396
+ DrainOptionsSchema.parse(options);
2397
+ return options;
2398
+ }
2399
+ var SettleOptionsSchema = DrainOptionsSchema.extend({
2400
+ debounceMs: import_zod6.z.number().min(0).optional(),
2401
+ // `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
2402
+ // a non-negative integer — `0` is legal and means "run no passes".
2403
+ maxPasses: import_zod6.z.number().int().min(0).optional()
2404
+ }).loose();
2405
+ function resolveSettleConfig(options) {
2406
+ if (options === void 0) return void 0;
2407
+ SettleOptionsSchema.parse(options);
2408
+ return options;
2409
+ }
2410
+ var ActOptionsSchema = import_zod6.z.object({
2411
+ maxSubscribedStreams: import_zod6.z.number().int().min(1).optional(),
2412
+ settleDebounceMs: import_zod6.z.number().int().min(0).optional()
2413
+ }).loose();
2414
+ function resolveActConfig(options) {
2415
+ if (options === void 0) return void 0;
2416
+ ActOptionsSchema.parse(options);
2417
+ return options;
2418
+ }
2419
+ var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
2420
+ var DEFAULT_CLOSE_BATCH_SIZE = 64;
2421
+ var DEFAULT_CLOSE_YIELD_MS = 0;
2422
+ var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
2423
+ function is_valid_time_zone(tz) {
2424
+ try {
2425
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
2426
+ return true;
2427
+ } catch {
2428
+ return false;
2429
+ }
2430
+ }
2431
+ var AutocloseWindowSchema = import_zod6.z.object({
2432
+ start: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
2433
+ end: import_zod6.z.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
2434
+ timeZone: import_zod6.z.string().refine(is_valid_time_zone, {
2435
+ message: "autocloseWindow.timeZone must be a valid IANA time zone"
2436
+ }).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
2437
+ }).refine((w) => w.start !== w.end, {
2438
+ message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
2439
+ });
2440
+ var AutocloseOptionsSchema = import_zod6.z.object({
2441
+ autocloseCycleMinutes: import_zod6.z.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
2442
+ closeBatchSize: import_zod6.z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
2443
+ closeYieldMs: import_zod6.z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
2444
+ closeOnError: import_zod6.z.boolean().default(false),
2445
+ autocloseWindow: AutocloseWindowSchema.optional()
2446
+ });
2447
+ function resolveAutocloseConfig(options) {
2448
+ return AutocloseOptionsSchema.parse({
2449
+ autocloseCycleMinutes: options?.autocloseCycleMinutes,
2450
+ closeBatchSize: options?.closeBatchSize,
2451
+ closeYieldMs: options?.closeYieldMs,
2452
+ closeOnError: options?.closeOnError,
2453
+ autocloseWindow: options?.autocloseWindow
2454
+ });
2455
+ }
2456
+ var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
2457
+ var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
2458
+ var CircuitBreakerOptionsSchema = import_zod6.z.object({
2459
+ failureThreshold: import_zod6.z.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
2460
+ cooldownMs: import_zod6.z.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
2461
+ });
2462
+ var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
2463
+ var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
2464
+ var DEFAULT_MAX_CACHED_STATES = 1e4;
2465
+ var FoldOptionsSchema = import_zod6.z.object({
2466
+ flushEvery: import_zod6.z.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
2467
+ maxCachedStates: import_zod6.z.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
2468
+ });
2469
+ function resolveFoldConfig(options) {
2470
+ return FoldOptionsSchema.parse(options);
2471
+ }
2472
+
2394
2473
  // src/internal/defer-config.ts
2395
2474
  var import_zod7 = require("zod");
2396
2475
  var DeferDurationSchema = import_zod7.z.object({
@@ -2598,11 +2677,14 @@ function reaction_on(event, events, builder) {
2598
2677
  const reaction = {
2599
2678
  handler: schedule ? make_deferred(handler, schedule) : handler,
2600
2679
  resolver: _this_,
2601
- options: {
2680
+ // #1269: validate the whole bag at the declaration site so a bad
2681
+ // `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
2682
+ // not a NaN gate on the first retry.
2683
+ options: resolveReactionConfig({
2602
2684
  blockOnError: options?.blockOnError ?? true,
2603
2685
  maxRetries: options?.maxRetries ?? 3,
2604
2686
  backoff: options?.backoff
2605
- }
2687
+ })
2606
2688
  };
2607
2689
  events[event].reactions.set(handler.name, reaction);
2608
2690
  return Object.assign(builder, {
@@ -2621,14 +2703,6 @@ function reaction_on(event, events, builder) {
2621
2703
  }
2622
2704
 
2623
2705
  // src/internal/circuit-breaker.ts
2624
- var import_zod9 = require("zod");
2625
- var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
2626
- var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
2627
- var CircuitBreakerOptionsSchema = import_zod9.z.object({
2628
- failureThreshold: import_zod9.z.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
2629
- cooldownMs: import_zod9.z.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
2630
- });
2631
- var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
2632
2706
  var CircuitBreaker = class {
2633
2707
  _failures = 0;
2634
2708
  _opened_at;
@@ -3296,6 +3370,10 @@ function compute_backoff_delay(retry, opts) {
3296
3370
  delay = opts.baseMs * 2 ** r;
3297
3371
  if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
3298
3372
  break;
3373
+ default: {
3374
+ const _never = opts.strategy;
3375
+ throw new Error(`unknown backoff strategy: ${String(_never)}`);
3376
+ }
3299
3377
  }
3300
3378
  if (opts.jitter) delay = delay * (0.5 + Math.random());
3301
3379
  return Math.max(0, Math.floor(delay));
@@ -4052,16 +4130,6 @@ var DrainController = class {
4052
4130
  };
4053
4131
 
4054
4132
  // src/internal/projection-fold.ts
4055
- var import_zod10 = require("zod");
4056
- var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
4057
- var DEFAULT_MAX_CACHED_STATES = 1e4;
4058
- var FoldOptionsSchema = import_zod10.z.object({
4059
- flushEvery: import_zod10.z.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
4060
- maxCachedStates: import_zod10.z.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
4061
- });
4062
- function resolveFoldConfig(options) {
4063
- return FoldOptionsSchema.parse(options);
4064
- }
4065
4133
  function make_fold_handler(me, flush, config2, patch_fn = bare_patch) {
4066
4134
  const cache3 = /* @__PURE__ */ new Map();
4067
4135
  const row = ({ dirty: _, ...row2 }) => row2;
@@ -5088,6 +5156,7 @@ var Act = class {
5088
5156
  * @see {@link start_correlations} for automatic correlation
5089
5157
  */
5090
5158
  async drain(options = {}) {
5159
+ resolveDrainConfig(options);
5091
5160
  if (!this._drain)
5092
5161
  return { fetched: [], leased: [], acked: [], blocked: [] };
5093
5162
  return this._scoped(() => this._drain_all(options));
@@ -5650,6 +5719,7 @@ var Act = class {
5650
5719
  * @see {@link correlate} for manual correlation
5651
5720
  */
5652
5721
  settle(options = {}) {
5722
+ resolveSettleConfig(options);
5653
5723
  if (!this._drain) return;
5654
5724
  this._settle.schedule(options);
5655
5725
  }
@@ -5770,11 +5840,12 @@ function act() {
5770
5840
  },
5771
5841
  withActor: () => builder,
5772
5842
  withLane: (config2) => {
5773
- register_lane(config2, lanes);
5843
+ register_lane(resolveLaneConfig(config2), lanes);
5774
5844
  return builder;
5775
5845
  },
5776
5846
  on: (event) => reaction_on(event, registry.events, builder),
5777
5847
  build: (options) => {
5848
+ resolveActConfig(options);
5778
5849
  const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
5779
5850
  if (!_built) {
5780
5851
  for (const proj of pending_projections) {
@@ -6091,6 +6162,7 @@ function action_builder(state2) {
6091
6162
  throw new Error(`Duplicate action "${action2}"`);
6092
6163
  internal.actions[action2] = schema;
6093
6164
  if (options) {
6165
+ resolveActionConfig(options);
6094
6166
  internal.options ??= {};
6095
6167
  internal.options[action2] = options;
6096
6168
  }