@rotorsoft/act 0.14.0 → 0.16.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.
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/types/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEhD;;;;;GAKG;AAEH;;GAEG;AACH,eAAO,MAAM,QAAQ,sCAAkC,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,WAAW;;;kBAKX,CAAC;AAEd;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;kBAMZ,CAAC;AAEd;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;iBAI/B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;kBAQf,CAAC;AAEd;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;kBAQnB,CAAC;AAEd;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC;IACjE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC;IAClE,KAAK,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;CAC/B,CAAC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;kBAaX,CAAC"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/types/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEhD;;;;;GAKG;AAEH;;GAEG;AACH,eAAO,MAAM,QAAQ,sCAAkC,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,WAAW;;;kBAMX,CAAC;AAEd;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;kBAOZ,CAAC;AAEd;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;iBAI/B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;kBAQf,CAAC;AAEd;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;kBAQnB,CAAC;AAEd;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC;IACjE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC;IAClE,KAAK,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;CAC/B,CAAC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;kBAaX,CAAC"}
package/dist/index.cjs CHANGED
@@ -122,12 +122,12 @@ var ZodEmpty = import_zod.z.record(import_zod.z.string(), import_zod.z.never());
122
122
  var ActorSchema = import_zod.z.object({
123
123
  id: import_zod.z.string(),
124
124
  name: import_zod.z.string()
125
- }).readonly();
125
+ }).loose().readonly();
126
126
  var TargetSchema = import_zod.z.object({
127
127
  stream: import_zod.z.string(),
128
128
  actor: ActorSchema,
129
129
  expectedVersion: import_zod.z.number().optional()
130
- }).readonly();
130
+ }).loose().readonly();
131
131
  var CausationEventSchema = import_zod.z.object({
132
132
  id: import_zod.z.number(),
133
133
  name: import_zod.z.string(),
@@ -764,13 +764,16 @@ var Act = class {
764
764
  dispose(() => {
765
765
  this._emitter.removeAllListeners();
766
766
  this.stop_correlations();
767
+ this.stop_settling();
767
768
  return Promise.resolve();
768
769
  });
769
770
  }
770
771
  _emitter = new import_events.default();
771
772
  _drain_locked = false;
772
773
  _drain_lag2lead_ratio = 0.5;
773
- _correlation_interval = void 0;
774
+ _correlation_timer = void 0;
775
+ _settle_timer = void 0;
776
+ _settling = false;
774
777
  emit(event, args) {
775
778
  return this._emitter.emit(event, args);
776
779
  }
@@ -793,7 +796,7 @@ var Act = class {
793
796
  * 5. Applies events to create new state
794
797
  * 6. Commits events to the store with optimistic concurrency control
795
798
  *
796
- * @template K - Action name from registered actions
799
+ * @template TKey - Action name from registered actions
797
800
  * @param action - The name of the action to execute
798
801
  * @param target - Target specification with stream ID and actor context
799
802
  * @param payload - Action payload matching the action's schema
@@ -1020,7 +1023,7 @@ var Act = class {
1020
1023
  /**
1021
1024
  * Processes pending reactions by draining uncommitted events from the event store.
1022
1025
  *
1023
- * The drain process:
1026
+ * Runs a single drain cycle:
1024
1027
  * 1. Polls the store for streams with uncommitted events
1025
1028
  * 2. Leases streams to prevent concurrent processing
1026
1029
  * 3. Fetches events for each leased stream
@@ -1030,7 +1033,8 @@ var Act = class {
1030
1033
  * Drain uses a dual-frontier strategy to balance processing of new streams (lagging)
1031
1034
  * vs active streams (leading). The ratio adapts based on event pressure.
1032
1035
  *
1033
- * Call this method periodically in a background loop, or after committing events.
1036
+ * Call `correlate()` before `drain()` to discover target streams. For a higher-level
1037
+ * API that handles debouncing, correlation, and signaling automatically, use {@link settle}.
1034
1038
  *
1035
1039
  * @param options - Drain configuration options
1036
1040
  * @param options.streamLimit - Maximum number of streams to process per cycle (default: 10)
@@ -1038,46 +1042,20 @@ var Act = class {
1038
1042
  * @param options.leaseMillis - Lease duration in milliseconds (default: 10000)
1039
1043
  * @returns Drain statistics with fetched, leased, acked, and blocked counts
1040
1044
  *
1041
- * @example Basic drain loop
1045
+ * @example In tests and scripts
1042
1046
  * ```typescript
1043
- * // Process reactions after each action
1044
1047
  * await app.do("createUser", target, payload);
1048
+ * await app.correlate();
1045
1049
  * await app.drain();
1046
1050
  * ```
1047
1051
  *
1048
- * @example Background drain worker
1052
+ * @example In production, prefer settle()
1049
1053
  * ```typescript
1050
- * setInterval(async () => {
1051
- * try {
1052
- * const result = await app.drain({
1053
- * streamLimit: 20,
1054
- * eventLimit: 50
1055
- * });
1056
- * if (result.acked.length) {
1057
- * console.log(`Processed ${result.acked.length} streams`);
1058
- * }
1059
- * } catch (error) {
1060
- * console.error("Drain error:", error);
1061
- * }
1062
- * }, 5000); // Every 5 seconds
1063
- * ```
1064
- *
1065
- * @example With lifecycle listeners
1066
- * ```typescript
1067
- * app.on("acked", (leases) => {
1068
- * console.log(`Acknowledged ${leases.length} streams`);
1069
- * });
1070
- *
1071
- * app.on("blocked", (blocked) => {
1072
- * console.error(`Blocked ${blocked.length} streams due to errors`);
1073
- * blocked.forEach(({ stream, error }) => {
1074
- * console.error(`Stream ${stream}: ${error}`);
1075
- * });
1076
- * });
1077
- *
1078
- * await app.drain();
1054
+ * await app.do("CreateItem", target, input);
1055
+ * app.settle(); // debounced correlate→drain, emits "settled"
1079
1056
  * ```
1080
1057
  *
1058
+ * @see {@link settle} for debounced correlate→drain with lifecycle events
1081
1059
  * @see {@link correlate} for dynamic stream discovery
1082
1060
  * @see {@link start_correlations} for automatic correlation
1083
1061
  */
@@ -1306,10 +1284,10 @@ var Act = class {
1306
1284
  * @see {@link stop_correlations} to stop the worker
1307
1285
  */
1308
1286
  start_correlations(query = {}, frequency = 1e4, callback) {
1309
- if (this._correlation_interval) return false;
1287
+ if (this._correlation_timer) return false;
1310
1288
  const limit = query.limit || 100;
1311
1289
  let after = query.after || -1;
1312
- this._correlation_interval = setInterval(
1290
+ this._correlation_timer = setInterval(
1313
1291
  () => this.correlate({ ...query, after, limit }).then((result) => {
1314
1292
  after = result.last_id;
1315
1293
  if (callback && result.leased.length) callback(result.leased);
@@ -1336,11 +1314,77 @@ var Act = class {
1336
1314
  * @see {@link start_correlations}
1337
1315
  */
1338
1316
  stop_correlations() {
1339
- if (this._correlation_interval) {
1340
- clearInterval(this._correlation_interval);
1341
- this._correlation_interval = void 0;
1317
+ if (this._correlation_timer) {
1318
+ clearInterval(this._correlation_timer);
1319
+ this._correlation_timer = void 0;
1342
1320
  }
1343
1321
  }
1322
+ /**
1323
+ * Cancels any pending or active settle cycle.
1324
+ *
1325
+ * @see {@link settle}
1326
+ */
1327
+ stop_settling() {
1328
+ if (this._settle_timer) {
1329
+ clearTimeout(this._settle_timer);
1330
+ this._settle_timer = void 0;
1331
+ }
1332
+ }
1333
+ /**
1334
+ * Debounced, non-blocking correlate→drain cycle.
1335
+ *
1336
+ * Call this after `app.do()` to schedule a background drain. Multiple rapid
1337
+ * calls within the debounce window are coalesced into a single cycle. Runs
1338
+ * correlate→drain in a loop until the system reaches a consistent state,
1339
+ * then emits the `"settled"` lifecycle event.
1340
+ *
1341
+ * @param options - Settle configuration options
1342
+ * @param options.debounceMs - Debounce window in milliseconds (default: 10)
1343
+ * @param options.correlate - Query filter for correlation scans (default: `{ after: -1, limit: 100 }`)
1344
+ * @param options.maxPasses - Maximum correlate→drain loops (default: 5)
1345
+ * @param options.streamLimit - Maximum streams per drain cycle (default: 10)
1346
+ * @param options.eventLimit - Maximum events per stream (default: 10)
1347
+ * @param options.leaseMillis - Lease duration in milliseconds (default: 10000)
1348
+ *
1349
+ * @example API mutations
1350
+ * ```typescript
1351
+ * await app.do("CreateItem", target, input);
1352
+ * app.settle(); // non-blocking, returns immediately
1353
+ *
1354
+ * app.on("settled", (drain) => {
1355
+ * // notify SSE clients, invalidate caches, etc.
1356
+ * });
1357
+ * ```
1358
+ *
1359
+ * @see {@link drain} for single synchronous drain cycles
1360
+ * @see {@link correlate} for manual correlation
1361
+ */
1362
+ settle(options = {}) {
1363
+ const {
1364
+ debounceMs = 10,
1365
+ correlate: correlateQuery = { after: -1, limit: 100 },
1366
+ maxPasses = 5,
1367
+ ...drainOptions
1368
+ } = options;
1369
+ if (this._settle_timer) clearTimeout(this._settle_timer);
1370
+ this._settle_timer = setTimeout(() => {
1371
+ this._settle_timer = void 0;
1372
+ if (this._settling) return;
1373
+ this._settling = true;
1374
+ (async () => {
1375
+ let lastDrain;
1376
+ for (let i = 0; i < maxPasses; i++) {
1377
+ const { leased } = await this.correlate(correlateQuery);
1378
+ if (leased.length === 0 && i > 0) break;
1379
+ lastDrain = await this.drain(drainOptions);
1380
+ if (!lastDrain.acked.length && !lastDrain.blocked.length) break;
1381
+ }
1382
+ if (lastDrain) this.emit("settled", lastDrain);
1383
+ })().catch((err) => logger.error(err)).finally(() => {
1384
+ this._settling = false;
1385
+ });
1386
+ }, debounceMs);
1387
+ }
1344
1388
  };
1345
1389
 
1346
1390
  // src/merge.ts
@@ -1479,7 +1523,18 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
1479
1523
  },
1480
1524
  withProjection: (proj) => {
1481
1525
  mergeProjection(proj, registry.events);
1482
- return act(states, registry, pendingProjections);
1526
+ return act(
1527
+ states,
1528
+ registry,
1529
+ pendingProjections
1530
+ );
1531
+ },
1532
+ withActor: () => {
1533
+ return act(
1534
+ states,
1535
+ registry,
1536
+ pendingProjections
1537
+ );
1483
1538
  },
1484
1539
  on: (event) => ({
1485
1540
  do: (handler, options) => {
@@ -1516,7 +1571,10 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
1516
1571
  for (const proj of pendingProjections) {
1517
1572
  mergeProjection(proj, registry.events);
1518
1573
  }
1519
- return new Act(registry, states);
1574
+ return new Act(
1575
+ registry,
1576
+ states
1577
+ );
1520
1578
  },
1521
1579
  events: registry.events
1522
1580
  };
@@ -1598,7 +1656,12 @@ function slice(states = /* @__PURE__ */ new Map(), actions = {}, events = {}, pr
1598
1656
  },
1599
1657
  withProjection: (proj) => {
1600
1658
  projections.push(proj);
1601
- return slice(states, actions, events, projections);
1659
+ return slice(
1660
+ states,
1661
+ actions,
1662
+ events,
1663
+ projections
1664
+ );
1602
1665
  },
1603
1666
  on: (event) => ({
1604
1667
  do: (handler, options) => {
@@ -1694,7 +1757,10 @@ function action_builder(state2) {
1694
1757
  const schema = entry[action2];
1695
1758
  if (action2 in state2.actions)
1696
1759
  throw new Error(`Duplicate action "${action2}"`);
1697
- const actions = { ...state2.actions, [action2]: schema };
1760
+ const actions = {
1761
+ ...state2.actions,
1762
+ [action2]: schema
1763
+ };
1698
1764
  const on = { ...state2.on };
1699
1765
  const _given = { ...state2.given };
1700
1766
  function given(rules) {
@@ -1718,7 +1784,10 @@ function action_builder(state2) {
1718
1784
  return { given, emit };
1719
1785
  },
1720
1786
  snap(snap2) {
1721
- return action_builder({ ...state2, snap: snap2 });
1787
+ return action_builder({
1788
+ ...state2,
1789
+ snap: snap2
1790
+ });
1722
1791
  },
1723
1792
  build() {
1724
1793
  return state2;