@rotorsoft/act 1.10.2 → 1.12.0

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.js CHANGED
@@ -486,57 +486,24 @@ var PASS_FACTORIES = {
486
486
  "clock-anomalies": make_clock_anomalies_pass
487
487
  };
488
488
 
489
- // src/internal/build-classify.ts
490
- var ALL_LANES = /* @__PURE__ */ Symbol("act-1103/all-lanes");
491
- function classify_registry(registry, states) {
492
- const statics = /* @__PURE__ */ new Map();
493
- const reactive_events = /* @__PURE__ */ new Set();
494
- const event_to_lanes = /* @__PURE__ */ new Map();
495
- let has_dynamic_resolvers = false;
496
- for (const [name, register] of Object.entries(registry.events)) {
497
- if (register.reactions.size > 0) reactive_events.add(name);
498
- for (const reaction of register.reactions.values()) {
499
- if (typeof reaction.resolver === "function") {
500
- has_dynamic_resolvers = true;
501
- event_to_lanes.set(name, ALL_LANES);
502
- } else {
503
- const { target, source, priority = 0, lane } = reaction.resolver;
504
- const lane_name = lane ?? "default";
505
- const existing_lanes = event_to_lanes.get(name);
506
- if (existing_lanes !== ALL_LANES) {
507
- const set = existing_lanes ?? /* @__PURE__ */ new Set();
508
- set.add(lane_name);
509
- event_to_lanes.set(name, set);
510
- }
511
- const key = `${target}|${source ?? ""}`;
512
- const existing = statics.get(key);
513
- if (!existing) {
514
- statics.set(key, { stream: target, source, priority, lane });
515
- } else {
516
- if ((existing.lane ?? void 0) !== (lane ?? void 0))
517
- throw new Error(
518
- `Stream "${target}" has conflicting lane assignments ("${existing.lane ?? "default"}" vs "${lane ?? "default"}")`
519
- );
520
- if (priority > existing.priority) {
521
- statics.set(key, { ...existing, priority });
522
- }
523
- }
524
- }
525
- }
526
- }
527
- const event_to_state = /* @__PURE__ */ new Map();
528
- for (const merged of states.values()) {
529
- for (const event_name of Object.keys(merged.events)) {
530
- event_to_state.set(event_name, merged);
531
- }
532
- }
533
- return {
534
- static_targets: [...statics.values()],
535
- has_dynamic_resolvers,
536
- reactive_events,
537
- event_to_state,
538
- event_to_lanes
539
- };
489
+ // src/internal/autoclose-config.ts
490
+ import { z } from "zod";
491
+ var DEFAULT_AUTOCLOSE_CYCLE_MS = 6e4;
492
+ var DEFAULT_CLOSE_BATCH_SIZE = 64;
493
+ var DEFAULT_CLOSE_YIELD_MS = 0;
494
+ var AutocloseOptionsSchema = z.object({
495
+ autocloseCycleMs: z.number().min(1e4).max(36e5).default(DEFAULT_AUTOCLOSE_CYCLE_MS),
496
+ closeBatchSize: z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
497
+ closeYieldMs: z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
498
+ closeOnError: z.boolean().default(false)
499
+ });
500
+ function resolveAutocloseConfig(options) {
501
+ return AutocloseOptionsSchema.parse({
502
+ autocloseCycleMs: options?.autocloseCycleMs,
503
+ closeBatchSize: options?.closeBatchSize,
504
+ closeYieldMs: options?.closeYieldMs,
505
+ closeOnError: options?.closeOnError
506
+ });
540
507
  }
541
508
 
542
509
  // src/internal/close-cycle.ts
@@ -686,6 +653,303 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
686
653
  return truncated;
687
654
  }
688
655
 
656
+ // src/internal/correlator.ts
657
+ import { randomInt } from "crypto";
658
+ var BASE = 36;
659
+ var SEG_WIDTH = 4;
660
+ var SEG_SPACE = BASE ** SEG_WIDTH;
661
+ function seg(n) {
662
+ return n.toString(BASE).padStart(SEG_WIDTH, "0");
663
+ }
664
+ var default_correlator = ({ state: state2, action: action2 }) => {
665
+ const s = state2.slice(0, SEG_WIDTH).toLowerCase();
666
+ const a = action2.slice(0, SEG_WIDTH).toLowerCase();
667
+ const ts = seg(Date.now() % SEG_SPACE);
668
+ const rnd = seg(randomInt(SEG_SPACE));
669
+ return `${s}-${a}-${ts}${rnd}`;
670
+ };
671
+ function close_correlation(correlator, actor) {
672
+ return correlator({
673
+ state: "$close",
674
+ action: "close",
675
+ stream: "$close",
676
+ actor
677
+ });
678
+ }
679
+
680
+ // src/internal/autoclose-cycle.ts
681
+ function yield_between_batches(ms) {
682
+ if (ms <= 0) return Promise.resolve();
683
+ return new Promise((resolve) => setTimeout(resolve, ms));
684
+ }
685
+ async function run_autoclose_cycle(deps) {
686
+ const s = store();
687
+ let inspected = 0;
688
+ let evaluated = 0;
689
+ let predicate_errors = 0;
690
+ const truncated = /* @__PURE__ */ new Map();
691
+ const skipped = [];
692
+ const stats = await s.query_stats(
693
+ {},
694
+ { count: true, exclude: [TOMBSTONE_EVENT] }
695
+ );
696
+ const flush_batch = async (candidates2) => {
697
+ if (candidates2.length === 0) return;
698
+ const result = await run_close_cycle(candidates2, {
699
+ reactive_events_size: deps.reactive_events_size,
700
+ event_to_state: deps.event_to_state,
701
+ load: deps.load,
702
+ tombstone: deps.tombstone,
703
+ logger: deps.logger,
704
+ correlation: deps.correlation
705
+ });
706
+ for (const [stream, entry] of result.truncated) {
707
+ truncated.set(stream, entry);
708
+ }
709
+ for (const stream of result.skipped) skipped.push(stream);
710
+ await yield_between_batches(deps.config.closeYieldMs);
711
+ };
712
+ let candidates = [];
713
+ for (const [stream, { head, count }] of stats) {
714
+ inspected += 1;
715
+ const owner = deps.event_to_state.get(head.name);
716
+ if (!owner) continue;
717
+ const predicate = deps.autoclose_policy(owner.name);
718
+ if (!predicate) continue;
719
+ let close = false;
720
+ try {
721
+ close = predicate(stream, head, count ?? 0);
722
+ evaluated += 1;
723
+ } catch (err) {
724
+ predicate_errors += 1;
725
+ deps.logger.error(
726
+ `Autoclose predicate for state "${owner.name}" threw on stream "${stream}": ${err instanceof Error ? err.message : String(err)}`
727
+ );
728
+ close = deps.config.closeOnError;
729
+ }
730
+ if (!close) continue;
731
+ const archiver = deps.autoclose_archiver(owner.name);
732
+ const archive = archiver ? () => archiver(stream, head) : void 0;
733
+ candidates.push({ stream, archive });
734
+ if (candidates.length >= deps.config.closeBatchSize) {
735
+ await flush_batch(candidates);
736
+ candidates = [];
737
+ }
738
+ }
739
+ await flush_batch(candidates);
740
+ return {
741
+ inspected,
742
+ evaluated,
743
+ predicate_errors,
744
+ close_result: { truncated, skipped }
745
+ };
746
+ }
747
+ var AutocloseController = class {
748
+ deps;
749
+ _timer;
750
+ /**
751
+ * Reentrancy guard. The interval fires the next tick on cadence
752
+ * regardless of how long the previous one took; this flag drops
753
+ * overlapping ticks so the cycle stays sequential per controller.
754
+ */
755
+ _running = false;
756
+ constructor(deps) {
757
+ this.deps = deps;
758
+ }
759
+ /**
760
+ * Start the cycle ticker. Returns `false` if already running so
761
+ * accidental double-start doesn't stack timers (matches
762
+ * `_correlate.start_polling` semantics).
763
+ */
764
+ start() {
765
+ if (this._timer) return false;
766
+ this._timer = setInterval(() => {
767
+ this.run_once().catch((err) => {
768
+ this.deps.logger.error(
769
+ `Autoclose cycle errored: ${err instanceof Error ? err.message : String(err)}`
770
+ );
771
+ });
772
+ }, this.deps.config.autocloseCycleMs);
773
+ this._timer.unref();
774
+ return true;
775
+ }
776
+ /**
777
+ * Stop the cycle ticker. Idempotent.
778
+ */
779
+ stop() {
780
+ if (this._timer) {
781
+ clearInterval(this._timer);
782
+ this._timer = void 0;
783
+ }
784
+ }
785
+ /**
786
+ * Whether the ticker is currently running.
787
+ */
788
+ get is_running() {
789
+ return this._timer !== void 0;
790
+ }
791
+ /**
792
+ * Run one cycle synchronously. The controller's interval callback
793
+ * delegates here; tests can invoke it directly to deterministically
794
+ * exercise the cycle without spinning real timers.
795
+ *
796
+ * Overlapping invocations short-circuit: if a previous tick is
797
+ * still running, this one drops and returns `null`. The orchestrator
798
+ * never sees a queue of pending ticks regardless of how slow the
799
+ * predicate / archive / truncate are.
800
+ */
801
+ async run_once() {
802
+ if (this._running) return null;
803
+ this._running = true;
804
+ try {
805
+ const correlation = close_correlation(
806
+ this.deps.correlator,
807
+ this.deps.close_actor
808
+ );
809
+ const result = await run_autoclose_cycle({
810
+ autoclose_policy: this.deps.autoclose_policy,
811
+ autoclose_archiver: this.deps.autoclose_archiver,
812
+ event_to_state: this.deps.event_to_state,
813
+ reactive_events_size: this.deps.reactive_events_size,
814
+ load: this.deps.load,
815
+ tombstone: this.deps.tombstone,
816
+ logger: this.deps.logger,
817
+ config: this.deps.config,
818
+ correlation
819
+ });
820
+ if (result.close_result.truncated.size > 0) {
821
+ this.deps.on_closed(result.close_result);
822
+ }
823
+ return result;
824
+ } finally {
825
+ this._running = false;
826
+ }
827
+ }
828
+ };
829
+
830
+ // src/internal/autoclose-policy.ts
831
+ import { z as z2 } from "zod";
832
+ var MIN_AFTER_MS = 6e4;
833
+ var AfterSchema = z2.object({
834
+ days: z2.number({ message: "autocloses: after.days must be a number" }).positive("autocloses: after.days must be > 0")
835
+ }).refine((o) => o.days * 864e5 >= MIN_AFTER_MS, {
836
+ message: `autocloses: after resolves to < ${MIN_AFTER_MS}ms (one minute), which is too short to be a meaningful retention window`
837
+ });
838
+ var IsSchema = z2.union([
839
+ z2.string().min(1, "autocloses: is must be a non-empty event name"),
840
+ z2.array(
841
+ z2.string().min(1, "autocloses: is entries must be non-empty event names")
842
+ ).min(1, "autocloses: is must include at least one event name").readonly()
843
+ ]);
844
+ var ReachesSchema = z2.number({ message: "autocloses: reaches must be a number" }).int("autocloses: reaches must be an integer").min(1, "autocloses: reaches must be >= 1");
845
+ var OrBlockSchema = z2.object({
846
+ after: AfterSchema.optional(),
847
+ is: IsSchema.optional(),
848
+ reaches: ReachesSchema.optional()
849
+ }).strict().refine(
850
+ (o) => o.after !== void 0 || o.is !== void 0 || o.reaches !== void 0,
851
+ {
852
+ message: "autocloses: `or` block must include at least one of after / is / reaches"
853
+ }
854
+ );
855
+ var AutoclosePolicySchema = z2.object({
856
+ after: AfterSchema.optional(),
857
+ is: IsSchema.optional(),
858
+ reaches: ReachesSchema.optional(),
859
+ or: OrBlockSchema.optional()
860
+ }).strict().refine(
861
+ (o) => o.after !== void 0 || o.is !== void 0 || o.reaches !== void 0 || o.or !== void 0,
862
+ {
863
+ message: "autocloses: at least one of after / is / reaches / or must be specified \u2014 empty `{}` is a misconfiguration"
864
+ }
865
+ );
866
+ function compile_after(after) {
867
+ const after_ms = after.days * 864e5;
868
+ return (_stream, head) => Date.now() - head.created.getTime() >= after_ms;
869
+ }
870
+ function compile_is(is) {
871
+ const set = new Set(typeof is === "string" ? [is] : is);
872
+ return (_stream, head) => set.has(head.name);
873
+ }
874
+ function compile_reaches(reaches) {
875
+ return (_stream, _head, count) => count >= reaches;
876
+ }
877
+ function compile_autoclose_policy(options) {
878
+ const parsed = AutoclosePolicySchema.parse(options);
879
+ const and_preds = [];
880
+ if (parsed.after) and_preds.push(compile_after(parsed.after));
881
+ if (parsed.is) and_preds.push(compile_is(parsed.is));
882
+ if (parsed.reaches) and_preds.push(compile_reaches(parsed.reaches));
883
+ const or_preds = [];
884
+ if (parsed.or) {
885
+ if (parsed.or.after) or_preds.push(compile_after(parsed.or.after));
886
+ if (parsed.or.is) or_preds.push(compile_is(parsed.or.is));
887
+ if (parsed.or.reaches) or_preds.push(compile_reaches(parsed.or.reaches));
888
+ }
889
+ return (stream, head, count) => {
890
+ if (and_preds.length > 0 && and_preds.every((p) => p(stream, head, count))) {
891
+ return true;
892
+ }
893
+ if (or_preds.some((p) => p(stream, head, count))) {
894
+ return true;
895
+ }
896
+ return false;
897
+ };
898
+ }
899
+
900
+ // src/internal/build-classify.ts
901
+ var ALL_LANES = /* @__PURE__ */ Symbol("act-1103/all-lanes");
902
+ function classify_registry(registry, states) {
903
+ const statics = /* @__PURE__ */ new Map();
904
+ const reactive_events = /* @__PURE__ */ new Set();
905
+ const event_to_lanes = /* @__PURE__ */ new Map();
906
+ let has_dynamic_resolvers = false;
907
+ for (const [name, register] of Object.entries(registry.events)) {
908
+ if (register.reactions.size > 0) reactive_events.add(name);
909
+ for (const reaction of register.reactions.values()) {
910
+ if (typeof reaction.resolver === "function") {
911
+ has_dynamic_resolvers = true;
912
+ event_to_lanes.set(name, ALL_LANES);
913
+ } else {
914
+ const { target, source, priority = 0, lane } = reaction.resolver;
915
+ const lane_name = lane ?? "default";
916
+ const existing_lanes = event_to_lanes.get(name);
917
+ if (existing_lanes !== ALL_LANES) {
918
+ const set = existing_lanes ?? /* @__PURE__ */ new Set();
919
+ set.add(lane_name);
920
+ event_to_lanes.set(name, set);
921
+ }
922
+ const key = `${target}|${source ?? ""}`;
923
+ const existing = statics.get(key);
924
+ if (!existing) {
925
+ statics.set(key, { stream: target, source, priority, lane });
926
+ } else {
927
+ if ((existing.lane ?? void 0) !== (lane ?? void 0))
928
+ throw new Error(
929
+ `Stream "${target}" has conflicting lane assignments ("${existing.lane ?? "default"}" vs "${lane ?? "default"}")`
930
+ );
931
+ if (priority > existing.priority) {
932
+ statics.set(key, { ...existing, priority });
933
+ }
934
+ }
935
+ }
936
+ }
937
+ }
938
+ const event_to_state = /* @__PURE__ */ new Map();
939
+ for (const merged of states.values()) {
940
+ for (const event_name of Object.keys(merged.events)) {
941
+ event_to_state.set(event_name, merged);
942
+ }
943
+ }
944
+ return {
945
+ static_targets: [...statics.values()],
946
+ has_dynamic_resolvers,
947
+ reactive_events,
948
+ event_to_state,
949
+ event_to_lanes
950
+ };
951
+ }
952
+
689
953
  // src/internal/correlate-cycle.ts
690
954
  var CorrelateCycle = class {
691
955
  _checkpoint = -1;
@@ -814,30 +1078,6 @@ var CorrelateCycle = class {
814
1078
  }
815
1079
  };
816
1080
 
817
- // src/internal/correlator.ts
818
- import { randomInt } from "crypto";
819
- var BASE = 36;
820
- var SEG_WIDTH = 4;
821
- var SEG_SPACE = BASE ** SEG_WIDTH;
822
- function seg(n) {
823
- return n.toString(BASE).padStart(SEG_WIDTH, "0");
824
- }
825
- var default_correlator = ({ state: state2, action: action2 }) => {
826
- const s = state2.slice(0, SEG_WIDTH).toLowerCase();
827
- const a = action2.slice(0, SEG_WIDTH).toLowerCase();
828
- const ts = seg(Date.now() % SEG_SPACE);
829
- const rnd = seg(randomInt(SEG_SPACE));
830
- return `${s}-${a}-${ts}${rnd}`;
831
- };
832
- function close_correlation(correlator, actor) {
833
- return correlator({
834
- state: "$close",
835
- action: "close",
836
- stream: "$close",
837
- actor
838
- });
839
- }
840
-
841
1081
  // src/internal/drain-cycle.ts
842
1082
  import { randomUUID } from "crypto";
843
1083
 
@@ -1941,6 +2181,22 @@ var Act = class {
1941
2181
  registry;
1942
2182
  /** Map of state name → state definition; populated by the builder. */
1943
2183
  _states;
2184
+ /**
2185
+ * Resolved autoclose configuration (#837 / epic #802). Frozen at
2186
+ * `act().build()` via {@link resolveAutocloseConfig}.
2187
+ *
2188
+ * @internal
2189
+ */
2190
+ _autoclose_config;
2191
+ /**
2192
+ * App-level autoclose controller. `undefined` when no state declares
2193
+ * `.autocloses(...)` — the controller is never constructed, so
2194
+ * `start_correlations()` / `stop_correlations()` skip the autoclose
2195
+ * lifecycle and the orchestrator pays zero per-tick cost.
2196
+ *
2197
+ * @internal
2198
+ */
2199
+ _autoclose;
1944
2200
  /**
1945
2201
  * Emit a lifecycle event. The payload type is inferred from the event name
1946
2202
  * via {@link ActLifecycleEvents}.
@@ -2075,6 +2331,7 @@ var Act = class {
2075
2331
  this._reactive_events = reactive_events;
2076
2332
  this._listen = options.listen !== false;
2077
2333
  this._drain = options.drain !== false;
2334
+ this._autoclose_config = resolveAutocloseConfig(options);
2078
2335
  this._event_to_state = event_to_state;
2079
2336
  this._event_to_lanes = event_to_lanes;
2080
2337
  const all_lanes = ["default", ...lanes.map((l) => l.name)];
@@ -2138,6 +2395,26 @@ var Act = class {
2138
2395
  },
2139
2396
  options.settleDebounceMs ?? DEFAULT_SETTLE_DEBOUNCE_MS
2140
2397
  );
2398
+ const states_with_policy = [...this._states.values()].filter(
2399
+ (s) => s.autoclose
2400
+ );
2401
+ this._autoclose = states_with_policy.length ? new AutocloseController({
2402
+ // Internal cycle takes the loose `(string) => …` signature; the
2403
+ // public `registry.autoclose_policy` narrows to
2404
+ // `keyof TStateMap & string`. Thin lambdas bridge across the
2405
+ // narrowing — runtime semantics are unchanged.
2406
+ autoclose_policy: (name) => this.registry.autoclose_policy(name),
2407
+ autoclose_archiver: (name) => this.registry.autoclose_archiver(name),
2408
+ event_to_state: this._event_to_state,
2409
+ reactive_events_size: this._reactive_events.size,
2410
+ load: this._es.load,
2411
+ tombstone: this._es.tombstone,
2412
+ logger: this._logger,
2413
+ config: this._autoclose_config,
2414
+ correlator: this._correlator,
2415
+ close_actor: { id: "$autoclose", name: "autoclose" },
2416
+ on_closed: (result) => this.emit("closed", result)
2417
+ }) : void 0;
2141
2418
  this._notify_disposer = this._wire_notify(options.scoped?.store ?? store());
2142
2419
  dispose(() => this.shutdown());
2143
2420
  }
@@ -2629,7 +2906,9 @@ var Act = class {
2629
2906
  * @see {@link stop_correlations} to stop the worker
2630
2907
  */
2631
2908
  start_correlations(query = {}, frequency = 1e4, callback) {
2632
- return this._correlate.start_polling(query, frequency, callback);
2909
+ const started = this._correlate.start_polling(query, frequency, callback);
2910
+ this._autoclose?.start();
2911
+ return started;
2633
2912
  }
2634
2913
  /**
2635
2914
  * Stops the automatic correlation worker.
@@ -2650,6 +2929,7 @@ var Act = class {
2650
2929
  */
2651
2930
  stop_correlations() {
2652
2931
  this._correlate.stop_polling();
2932
+ this._autoclose?.stop();
2653
2933
  }
2654
2934
  /**
2655
2935
  * Cancels any pending or active settle cycle.
@@ -3034,13 +3314,17 @@ function act() {
3034
3314
  const _sf = /* @__PURE__ */ new Map();
3035
3315
  const _dp = /* @__PURE__ */ new Map();
3036
3316
  const _de = /* @__PURE__ */ new Map();
3317
+ const _ac = /* @__PURE__ */ new Map();
3318
+ const _aa = /* @__PURE__ */ new Map();
3037
3319
  const EMPTY_DEPRECATED = /* @__PURE__ */ new Set();
3038
3320
  const registry = {
3039
3321
  actions: {},
3040
3322
  events: {},
3041
3323
  sensitive_fields: (event_name) => _sf.get(event_name) ?? [],
3042
3324
  disclosure_predicate: (state_name) => _dp.get(state_name) ?? null,
3043
- deprecated_events: (state_name) => _de.get(state_name) ?? EMPTY_DEPRECATED
3325
+ deprecated_events: (state_name) => _de.get(state_name) ?? EMPTY_DEPRECATED,
3326
+ autoclose_policy: (state_name) => _ac.get(state_name) ?? null,
3327
+ autoclose_archiver: (state_name) => _aa.get(state_name) ?? null
3044
3328
  };
3045
3329
  const pending_projections = [];
3046
3330
  const batch_handlers = /* @__PURE__ */ new Map();
@@ -3167,6 +3451,8 @@ function act() {
3167
3451
  }
3168
3452
  for (const state2 of states.values()) {
3169
3453
  if (state2.disclose) _dp.set(state2.name, state2.disclose);
3454
+ if (state2.autoclose) _ac.set(state2.name, state2.autoclose);
3455
+ if (state2.archive) _aa.set(state2.name, state2.archive);
3170
3456
  const fields_by_event = /* @__PURE__ */ new Map();
3171
3457
  for (const event_name of Object.keys(state2.events)) {
3172
3458
  const fields = _sf.get(event_name);
@@ -3434,6 +3720,31 @@ function action_builder(state2) {
3434
3720
  internal.disclose = disclose;
3435
3721
  return builder;
3436
3722
  },
3723
+ autocloses(predicate_or_policy) {
3724
+ let predicate;
3725
+ if (typeof predicate_or_policy === "function") {
3726
+ predicate = predicate_or_policy;
3727
+ } else if (predicate_or_policy !== null && typeof predicate_or_policy === "object") {
3728
+ predicate = compile_autoclose_policy(
3729
+ predicate_or_policy
3730
+ );
3731
+ } else {
3732
+ throw new Error(
3733
+ ".autocloses(...) requires a function or a policy object; got " + typeof predicate_or_policy
3734
+ );
3735
+ }
3736
+ internal.autoclose = predicate;
3737
+ return builder;
3738
+ },
3739
+ archives(archive) {
3740
+ if (typeof archive !== "function") {
3741
+ throw new Error(
3742
+ ".archives(archive) requires a function; got " + typeof archive
3743
+ );
3744
+ }
3745
+ internal.archive = archive;
3746
+ return builder;
3747
+ },
3437
3748
  build() {
3438
3749
  return internal;
3439
3750
  }
@@ -3609,6 +3920,9 @@ export {
3609
3920
  ConcurrencyError,
3610
3921
  ConsoleLogger,
3611
3922
  CsvFile,
3923
+ DEFAULT_AUTOCLOSE_CYCLE_MS,
3924
+ DEFAULT_CLOSE_BATCH_SIZE,
3925
+ DEFAULT_CLOSE_YIELD_MS,
3612
3926
  DEFAULT_LANE,
3613
3927
  DEFAULT_MAX_SUBSCRIBED_STREAMS,
3614
3928
  DEFAULT_SETTLE_DEBOUNCE_MS,
@@ -3640,6 +3954,7 @@ export {
3640
3954
  log,
3641
3955
  port,
3642
3956
  projection,
3957
+ resolveAutocloseConfig,
3643
3958
  scoped,
3644
3959
  sensitive,
3645
3960
  sleep,