@rotorsoft/act 1.23.0 → 1.23.2

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.
Files changed (38) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/@types/act.d.ts +18 -0
  3. package/dist/@types/act.d.ts.map +1 -1
  4. package/dist/@types/adapters/console-logger.d.ts.map +1 -1
  5. package/dist/@types/adapters/in-memory-cache.d.ts.map +1 -1
  6. package/dist/@types/adapters/in-memory-store.d.ts +10 -0
  7. package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
  8. package/dist/@types/config.d.ts +5 -5
  9. package/dist/@types/csv.d.ts.map +1 -1
  10. package/dist/@types/internal/builder-utils.d.ts.map +1 -1
  11. package/dist/@types/internal/circuit-breaker.d.ts.map +1 -1
  12. package/dist/@types/internal/close-cycle.d.ts +16 -0
  13. package/dist/@types/internal/close-cycle.d.ts.map +1 -1
  14. package/dist/@types/internal/close-signal.d.ts.map +1 -1
  15. package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
  16. package/dist/@types/internal/defer-signal.d.ts.map +1 -1
  17. package/dist/@types/internal/defer-timer.d.ts.map +1 -1
  18. package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
  19. package/dist/@types/internal/drain.d.ts.map +1 -1
  20. package/dist/@types/internal/lru-map.d.ts.map +1 -1
  21. package/dist/@types/internal/merge.d.ts.map +1 -1
  22. package/dist/@types/internal/settle.d.ts.map +1 -1
  23. package/dist/@types/ports.d.ts +1 -1
  24. package/dist/@types/ports.d.ts.map +1 -1
  25. package/dist/@types/types/errors.d.ts.map +1 -1
  26. package/dist/@types/types/index.d.ts.map +1 -1
  27. package/dist/@types/utils.d.ts.map +1 -1
  28. package/dist/{chunk-YVLB5MMB.js → chunk-4HYYZOEH.js} +9 -2
  29. package/dist/chunk-4HYYZOEH.js.map +1 -0
  30. package/dist/index.cjs +70 -15
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.js +63 -15
  33. package/dist/index.js.map +1 -1
  34. package/dist/test/index.cjs +8 -1
  35. package/dist/test/index.cjs.map +1 -1
  36. package/dist/test/index.js +1 -1
  37. package/package.json +2 -2
  38. package/dist/chunk-YVLB5MMB.js.map +0 -1
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  sleep,
21
21
  store,
22
22
  validate
23
- } from "./chunk-YVLB5MMB.js";
23
+ } from "./chunk-4HYYZOEH.js";
24
24
  import {
25
25
  ActorSchema,
26
26
  CausationEventSchema,
@@ -1216,21 +1216,37 @@ async function run_windowed_closes(windowed, deps, skipped) {
1216
1216
  windowed.map((t) => t.stream),
1217
1217
  deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE
1218
1218
  ) : /* @__PURE__ */ new Map();
1219
+ const with_lock = deps.with_stream_lock ?? ((_stream, work) => work());
1220
+ const truncated = /* @__PURE__ */ new Map();
1219
1221
  for (const t of windowed) {
1220
- if (t.archive) await t.archive();
1221
- }
1222
- const truncated = await store().truncate(
1223
- windowed.map((t) => ({
1224
- stream: t.stream,
1225
- before: t.before,
1226
- max_id: min_at.get(t.stream)
1227
- }))
1228
- );
1229
- for (const t of windowed) {
1230
- if (!truncated.has(t.stream)) skipped.push(t.stream);
1222
+ const entry = await with_lock(t.stream, async () => {
1223
+ const max_id = min_at.get(t.stream);
1224
+ if (!await windowed_prune_pending(t.stream, t.before, max_id))
1225
+ return void 0;
1226
+ if (t.archive) await t.archive();
1227
+ const result = await store().truncate([
1228
+ { stream: t.stream, before: t.before, max_id }
1229
+ ]);
1230
+ return result.get(t.stream);
1231
+ });
1232
+ if (entry) truncated.set(t.stream, entry);
1233
+ else skipped.push(t.stream);
1231
1234
  }
1232
1235
  return truncated;
1233
1236
  }
1237
+ async function windowed_prune_pending(stream, before, max_id) {
1238
+ let boundary_id;
1239
+ let min_id;
1240
+ await store().query(
1241
+ (event) => {
1242
+ if (min_id === void 0 || event.id < min_id) min_id = event.id;
1243
+ if (event.name === SNAP_EVENT && event.created < before && (max_id === void 0 || event.id <= max_id) && (boundary_id === void 0 || event.id > boundary_id))
1244
+ boundary_id = event.id;
1245
+ },
1246
+ { stream, stream_exact: true, with_snaps: true, after: -1 }
1247
+ );
1248
+ return boundary_id !== void 0 && min_id < boundary_id;
1249
+ }
1234
1250
  async function probe_min_watermarks(streams, page_size) {
1235
1251
  const min_at = /* @__PURE__ */ new Map();
1236
1252
  const source_regex = /* @__PURE__ */ new Map();
@@ -1505,8 +1521,10 @@ var CorrelateCycle = class {
1505
1521
  lane: resolved.lane,
1506
1522
  payloads: []
1507
1523
  };
1508
- if (incoming_priority > entry.priority)
1524
+ if (incoming_priority > entry.priority) {
1509
1525
  entry.priority = incoming_priority;
1526
+ entry.lane = resolved.lane;
1527
+ }
1510
1528
  entry.payloads.push({
1511
1529
  ...reaction,
1512
1530
  source: resolved.source,
@@ -2911,6 +2929,34 @@ var Act = class {
2911
2929
  _handle_batch;
2912
2930
  /** Declared drain lanes (ACT-1103). */
2913
2931
  _lanes;
2932
+ /**
2933
+ * Per-stream close serialization tails (#1222). Chains each stream's
2934
+ * windowed-close work behind the previous one so a manual
2935
+ * `app.close([{stream, before}])` and an autoclose windowed close for
2936
+ * the same stream never run their guard-free prune concurrently — the
2937
+ * manual path bypasses the `__autoclose__:X` drain lease that would
2938
+ * otherwise exclude them, so without this both closers archive the
2939
+ * same prefix. Process-local: both racers run on the same Act
2940
+ * instance. Entries are dropped once their tail resolves so the map
2941
+ * doesn't grow with distinct stream names.
2942
+ */
2943
+ _close_locks = /* @__PURE__ */ new Map();
2944
+ /**
2945
+ * Run `work` under the per-stream close lock (#1222). Serializes
2946
+ * windowed-close critical sections for the same stream while letting
2947
+ * different streams proceed in parallel.
2948
+ */
2949
+ _with_close_lock(stream, work) {
2950
+ const prev = this._close_locks.get(stream) ?? Promise.resolve();
2951
+ const next = prev.then(work, work);
2952
+ this._close_locks.set(stream, next);
2953
+ const cleanup = () => {
2954
+ if (this._close_locks.get(stream) === next)
2955
+ this._close_locks.delete(stream);
2956
+ };
2957
+ next.then(cleanup, cleanup);
2958
+ return next;
2959
+ }
2914
2960
  /** Drain lanes declared via `.withLane(...)`. Implicit default not included. */
2915
2961
  get lanes() {
2916
2962
  return this._lanes;
@@ -3032,7 +3078,8 @@ var Act = class {
3032
3078
  load: this._es.load,
3033
3079
  tombstone: this._es.tombstone,
3034
3080
  logger: this._logger,
3035
- correlation: close_correlation(this._correlator, close_actor)
3081
+ correlation: close_correlation(this._correlator, close_actor),
3082
+ with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
3036
3083
  });
3037
3084
  this.emit("closed", result);
3038
3085
  },
@@ -4022,7 +4069,8 @@ var Act = class {
4022
4069
  load: this._es.load,
4023
4070
  tombstone: this._es.tombstone,
4024
4071
  logger: this._logger,
4025
- correlation: close_correlation(this._correlator, close_actor)
4072
+ correlation: close_correlation(this._correlator, close_actor),
4073
+ with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
4026
4074
  });
4027
4075
  this.emit("closed", result);
4028
4076
  return result;