@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.
- package/README.md +12 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act-builder.d.ts +60 -20
- package/dist/@types/act-builder.d.ts.map +1 -1
- package/dist/@types/act.d.ts +75 -57
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/event-sourcing.d.ts +12 -12
- package/dist/@types/event-sourcing.d.ts.map +1 -1
- package/dist/@types/merge.d.ts +1 -1
- package/dist/@types/merge.d.ts.map +1 -1
- package/dist/@types/projection-builder.d.ts +22 -22
- package/dist/@types/projection-builder.d.ts.map +1 -1
- package/dist/@types/slice-builder.d.ts +31 -27
- package/dist/@types/slice-builder.d.ts.map +1 -1
- package/dist/@types/state-builder.d.ts +35 -33
- package/dist/@types/state-builder.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +75 -66
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/errors.d.ts +15 -14
- package/dist/@types/types/errors.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +41 -22
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/@types/types/registry.d.ts +15 -15
- package/dist/@types/types/registry.d.ts.map +1 -1
- package/dist/@types/types/schemas.d.ts +7 -7
- package/dist/@types/types/schemas.d.ts.map +1 -1
- package/dist/index.cjs +117 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +117 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -55,12 +55,12 @@ var ZodEmpty = z.record(z.string(), z.never());
|
|
|
55
55
|
var ActorSchema = z.object({
|
|
56
56
|
id: z.string(),
|
|
57
57
|
name: z.string()
|
|
58
|
-
}).readonly();
|
|
58
|
+
}).loose().readonly();
|
|
59
59
|
var TargetSchema = z.object({
|
|
60
60
|
stream: z.string(),
|
|
61
61
|
actor: ActorSchema,
|
|
62
62
|
expectedVersion: z.number().optional()
|
|
63
|
-
}).readonly();
|
|
63
|
+
}).loose().readonly();
|
|
64
64
|
var CausationEventSchema = z.object({
|
|
65
65
|
id: z.number(),
|
|
66
66
|
name: z.string(),
|
|
@@ -697,13 +697,16 @@ var Act = class {
|
|
|
697
697
|
dispose(() => {
|
|
698
698
|
this._emitter.removeAllListeners();
|
|
699
699
|
this.stop_correlations();
|
|
700
|
+
this.stop_settling();
|
|
700
701
|
return Promise.resolve();
|
|
701
702
|
});
|
|
702
703
|
}
|
|
703
704
|
_emitter = new EventEmitter();
|
|
704
705
|
_drain_locked = false;
|
|
705
706
|
_drain_lag2lead_ratio = 0.5;
|
|
706
|
-
|
|
707
|
+
_correlation_timer = void 0;
|
|
708
|
+
_settle_timer = void 0;
|
|
709
|
+
_settling = false;
|
|
707
710
|
emit(event, args) {
|
|
708
711
|
return this._emitter.emit(event, args);
|
|
709
712
|
}
|
|
@@ -726,7 +729,7 @@ var Act = class {
|
|
|
726
729
|
* 5. Applies events to create new state
|
|
727
730
|
* 6. Commits events to the store with optimistic concurrency control
|
|
728
731
|
*
|
|
729
|
-
* @template
|
|
732
|
+
* @template TKey - Action name from registered actions
|
|
730
733
|
* @param action - The name of the action to execute
|
|
731
734
|
* @param target - Target specification with stream ID and actor context
|
|
732
735
|
* @param payload - Action payload matching the action's schema
|
|
@@ -953,7 +956,7 @@ var Act = class {
|
|
|
953
956
|
/**
|
|
954
957
|
* Processes pending reactions by draining uncommitted events from the event store.
|
|
955
958
|
*
|
|
956
|
-
*
|
|
959
|
+
* Runs a single drain cycle:
|
|
957
960
|
* 1. Polls the store for streams with uncommitted events
|
|
958
961
|
* 2. Leases streams to prevent concurrent processing
|
|
959
962
|
* 3. Fetches events for each leased stream
|
|
@@ -963,7 +966,8 @@ var Act = class {
|
|
|
963
966
|
* Drain uses a dual-frontier strategy to balance processing of new streams (lagging)
|
|
964
967
|
* vs active streams (leading). The ratio adapts based on event pressure.
|
|
965
968
|
*
|
|
966
|
-
* Call
|
|
969
|
+
* Call `correlate()` before `drain()` to discover target streams. For a higher-level
|
|
970
|
+
* API that handles debouncing, correlation, and signaling automatically, use {@link settle}.
|
|
967
971
|
*
|
|
968
972
|
* @param options - Drain configuration options
|
|
969
973
|
* @param options.streamLimit - Maximum number of streams to process per cycle (default: 10)
|
|
@@ -971,46 +975,20 @@ var Act = class {
|
|
|
971
975
|
* @param options.leaseMillis - Lease duration in milliseconds (default: 10000)
|
|
972
976
|
* @returns Drain statistics with fetched, leased, acked, and blocked counts
|
|
973
977
|
*
|
|
974
|
-
* @example
|
|
978
|
+
* @example In tests and scripts
|
|
975
979
|
* ```typescript
|
|
976
|
-
* // Process reactions after each action
|
|
977
980
|
* await app.do("createUser", target, payload);
|
|
981
|
+
* await app.correlate();
|
|
978
982
|
* await app.drain();
|
|
979
983
|
* ```
|
|
980
984
|
*
|
|
981
|
-
* @example
|
|
985
|
+
* @example In production, prefer settle()
|
|
982
986
|
* ```typescript
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
* const result = await app.drain({
|
|
986
|
-
* streamLimit: 20,
|
|
987
|
-
* eventLimit: 50
|
|
988
|
-
* });
|
|
989
|
-
* if (result.acked.length) {
|
|
990
|
-
* console.log(`Processed ${result.acked.length} streams`);
|
|
991
|
-
* }
|
|
992
|
-
* } catch (error) {
|
|
993
|
-
* console.error("Drain error:", error);
|
|
994
|
-
* }
|
|
995
|
-
* }, 5000); // Every 5 seconds
|
|
996
|
-
* ```
|
|
997
|
-
*
|
|
998
|
-
* @example With lifecycle listeners
|
|
999
|
-
* ```typescript
|
|
1000
|
-
* app.on("acked", (leases) => {
|
|
1001
|
-
* console.log(`Acknowledged ${leases.length} streams`);
|
|
1002
|
-
* });
|
|
1003
|
-
*
|
|
1004
|
-
* app.on("blocked", (blocked) => {
|
|
1005
|
-
* console.error(`Blocked ${blocked.length} streams due to errors`);
|
|
1006
|
-
* blocked.forEach(({ stream, error }) => {
|
|
1007
|
-
* console.error(`Stream ${stream}: ${error}`);
|
|
1008
|
-
* });
|
|
1009
|
-
* });
|
|
1010
|
-
*
|
|
1011
|
-
* await app.drain();
|
|
987
|
+
* await app.do("CreateItem", target, input);
|
|
988
|
+
* app.settle(); // debounced correlate→drain, emits "settled"
|
|
1012
989
|
* ```
|
|
1013
990
|
*
|
|
991
|
+
* @see {@link settle} for debounced correlate→drain with lifecycle events
|
|
1014
992
|
* @see {@link correlate} for dynamic stream discovery
|
|
1015
993
|
* @see {@link start_correlations} for automatic correlation
|
|
1016
994
|
*/
|
|
@@ -1239,10 +1217,10 @@ var Act = class {
|
|
|
1239
1217
|
* @see {@link stop_correlations} to stop the worker
|
|
1240
1218
|
*/
|
|
1241
1219
|
start_correlations(query = {}, frequency = 1e4, callback) {
|
|
1242
|
-
if (this.
|
|
1220
|
+
if (this._correlation_timer) return false;
|
|
1243
1221
|
const limit = query.limit || 100;
|
|
1244
1222
|
let after = query.after || -1;
|
|
1245
|
-
this.
|
|
1223
|
+
this._correlation_timer = setInterval(
|
|
1246
1224
|
() => this.correlate({ ...query, after, limit }).then((result) => {
|
|
1247
1225
|
after = result.last_id;
|
|
1248
1226
|
if (callback && result.leased.length) callback(result.leased);
|
|
@@ -1269,11 +1247,77 @@ var Act = class {
|
|
|
1269
1247
|
* @see {@link start_correlations}
|
|
1270
1248
|
*/
|
|
1271
1249
|
stop_correlations() {
|
|
1272
|
-
if (this.
|
|
1273
|
-
clearInterval(this.
|
|
1274
|
-
this.
|
|
1250
|
+
if (this._correlation_timer) {
|
|
1251
|
+
clearInterval(this._correlation_timer);
|
|
1252
|
+
this._correlation_timer = void 0;
|
|
1275
1253
|
}
|
|
1276
1254
|
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Cancels any pending or active settle cycle.
|
|
1257
|
+
*
|
|
1258
|
+
* @see {@link settle}
|
|
1259
|
+
*/
|
|
1260
|
+
stop_settling() {
|
|
1261
|
+
if (this._settle_timer) {
|
|
1262
|
+
clearTimeout(this._settle_timer);
|
|
1263
|
+
this._settle_timer = void 0;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Debounced, non-blocking correlate→drain cycle.
|
|
1268
|
+
*
|
|
1269
|
+
* Call this after `app.do()` to schedule a background drain. Multiple rapid
|
|
1270
|
+
* calls within the debounce window are coalesced into a single cycle. Runs
|
|
1271
|
+
* correlate→drain in a loop until the system reaches a consistent state,
|
|
1272
|
+
* then emits the `"settled"` lifecycle event.
|
|
1273
|
+
*
|
|
1274
|
+
* @param options - Settle configuration options
|
|
1275
|
+
* @param options.debounceMs - Debounce window in milliseconds (default: 10)
|
|
1276
|
+
* @param options.correlate - Query filter for correlation scans (default: `{ after: -1, limit: 100 }`)
|
|
1277
|
+
* @param options.maxPasses - Maximum correlate→drain loops (default: 5)
|
|
1278
|
+
* @param options.streamLimit - Maximum streams per drain cycle (default: 10)
|
|
1279
|
+
* @param options.eventLimit - Maximum events per stream (default: 10)
|
|
1280
|
+
* @param options.leaseMillis - Lease duration in milliseconds (default: 10000)
|
|
1281
|
+
*
|
|
1282
|
+
* @example API mutations
|
|
1283
|
+
* ```typescript
|
|
1284
|
+
* await app.do("CreateItem", target, input);
|
|
1285
|
+
* app.settle(); // non-blocking, returns immediately
|
|
1286
|
+
*
|
|
1287
|
+
* app.on("settled", (drain) => {
|
|
1288
|
+
* // notify SSE clients, invalidate caches, etc.
|
|
1289
|
+
* });
|
|
1290
|
+
* ```
|
|
1291
|
+
*
|
|
1292
|
+
* @see {@link drain} for single synchronous drain cycles
|
|
1293
|
+
* @see {@link correlate} for manual correlation
|
|
1294
|
+
*/
|
|
1295
|
+
settle(options = {}) {
|
|
1296
|
+
const {
|
|
1297
|
+
debounceMs = 10,
|
|
1298
|
+
correlate: correlateQuery = { after: -1, limit: 100 },
|
|
1299
|
+
maxPasses = 5,
|
|
1300
|
+
...drainOptions
|
|
1301
|
+
} = options;
|
|
1302
|
+
if (this._settle_timer) clearTimeout(this._settle_timer);
|
|
1303
|
+
this._settle_timer = setTimeout(() => {
|
|
1304
|
+
this._settle_timer = void 0;
|
|
1305
|
+
if (this._settling) return;
|
|
1306
|
+
this._settling = true;
|
|
1307
|
+
(async () => {
|
|
1308
|
+
let lastDrain;
|
|
1309
|
+
for (let i = 0; i < maxPasses; i++) {
|
|
1310
|
+
const { leased } = await this.correlate(correlateQuery);
|
|
1311
|
+
if (leased.length === 0 && i > 0) break;
|
|
1312
|
+
lastDrain = await this.drain(drainOptions);
|
|
1313
|
+
if (!lastDrain.acked.length && !lastDrain.blocked.length) break;
|
|
1314
|
+
}
|
|
1315
|
+
if (lastDrain) this.emit("settled", lastDrain);
|
|
1316
|
+
})().catch((err) => logger.error(err)).finally(() => {
|
|
1317
|
+
this._settling = false;
|
|
1318
|
+
});
|
|
1319
|
+
}, debounceMs);
|
|
1320
|
+
}
|
|
1277
1321
|
};
|
|
1278
1322
|
|
|
1279
1323
|
// src/merge.ts
|
|
@@ -1412,7 +1456,18 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
1412
1456
|
},
|
|
1413
1457
|
withProjection: (proj) => {
|
|
1414
1458
|
mergeProjection(proj, registry.events);
|
|
1415
|
-
return act(
|
|
1459
|
+
return act(
|
|
1460
|
+
states,
|
|
1461
|
+
registry,
|
|
1462
|
+
pendingProjections
|
|
1463
|
+
);
|
|
1464
|
+
},
|
|
1465
|
+
withActor: () => {
|
|
1466
|
+
return act(
|
|
1467
|
+
states,
|
|
1468
|
+
registry,
|
|
1469
|
+
pendingProjections
|
|
1470
|
+
);
|
|
1416
1471
|
},
|
|
1417
1472
|
on: (event) => ({
|
|
1418
1473
|
do: (handler, options) => {
|
|
@@ -1449,7 +1504,10 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
1449
1504
|
for (const proj of pendingProjections) {
|
|
1450
1505
|
mergeProjection(proj, registry.events);
|
|
1451
1506
|
}
|
|
1452
|
-
return new Act(
|
|
1507
|
+
return new Act(
|
|
1508
|
+
registry,
|
|
1509
|
+
states
|
|
1510
|
+
);
|
|
1453
1511
|
},
|
|
1454
1512
|
events: registry.events
|
|
1455
1513
|
};
|
|
@@ -1531,7 +1589,12 @@ function slice(states = /* @__PURE__ */ new Map(), actions = {}, events = {}, pr
|
|
|
1531
1589
|
},
|
|
1532
1590
|
withProjection: (proj) => {
|
|
1533
1591
|
projections.push(proj);
|
|
1534
|
-
return slice(
|
|
1592
|
+
return slice(
|
|
1593
|
+
states,
|
|
1594
|
+
actions,
|
|
1595
|
+
events,
|
|
1596
|
+
projections
|
|
1597
|
+
);
|
|
1535
1598
|
},
|
|
1536
1599
|
on: (event) => ({
|
|
1537
1600
|
do: (handler, options) => {
|
|
@@ -1627,7 +1690,10 @@ function action_builder(state2) {
|
|
|
1627
1690
|
const schema = entry[action2];
|
|
1628
1691
|
if (action2 in state2.actions)
|
|
1629
1692
|
throw new Error(`Duplicate action "${action2}"`);
|
|
1630
|
-
const actions = {
|
|
1693
|
+
const actions = {
|
|
1694
|
+
...state2.actions,
|
|
1695
|
+
[action2]: schema
|
|
1696
|
+
};
|
|
1631
1697
|
const on = { ...state2.on };
|
|
1632
1698
|
const _given = { ...state2.given };
|
|
1633
1699
|
function given(rules) {
|
|
@@ -1651,7 +1717,10 @@ function action_builder(state2) {
|
|
|
1651
1717
|
return { given, emit };
|
|
1652
1718
|
},
|
|
1653
1719
|
snap(snap2) {
|
|
1654
|
-
return action_builder({
|
|
1720
|
+
return action_builder({
|
|
1721
|
+
...state2,
|
|
1722
|
+
snap: snap2
|
|
1723
|
+
});
|
|
1655
1724
|
},
|
|
1656
1725
|
build() {
|
|
1657
1726
|
return state2;
|