@rotorsoft/act 0.21.0 → 0.22.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
@@ -427,18 +427,22 @@ var InMemoryStore = class {
427
427
  /**
428
428
  * Registers streams for event processing.
429
429
  * @param streams - Streams to register with optional source.
430
- * @returns Number of newly registered streams.
430
+ * @returns subscribed count and current max watermark.
431
431
  */
432
432
  async subscribe(streams) {
433
433
  await sleep();
434
- let count = 0;
434
+ let subscribed = 0;
435
435
  for (const { stream, source } of streams) {
436
436
  if (!this._streams.has(stream)) {
437
437
  this._streams.set(stream, new InMemoryStream(stream, source));
438
- count++;
438
+ subscribed++;
439
439
  }
440
440
  }
441
- return count;
441
+ let watermark = -1;
442
+ for (const s of this._streams.values()) {
443
+ if (s.at > watermark) watermark = s.at;
444
+ }
445
+ return { subscribed, watermark };
442
446
  }
443
447
  /**
444
448
  * Acknowledge completion of processing for leased streams.
@@ -725,15 +729,21 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
725
729
  // src/act.ts
726
730
  var tracer = build_tracer(config().logLevel);
727
731
  var Act = class {
728
- /**
729
- * Create a new Act orchestrator.
730
- *
731
- * @param registry The registry of state, event, and action schemas
732
- * @param states Map of state names to their (potentially merged) state definitions
733
- */
734
732
  constructor(registry, _states = /* @__PURE__ */ new Map()) {
735
733
  this.registry = registry;
736
734
  this._states = _states;
735
+ const statics = [];
736
+ for (const register of Object.values(this.registry.events)) {
737
+ for (const reaction of register.reactions.values()) {
738
+ if (typeof reaction.resolver === "function") {
739
+ this._has_dynamic_resolvers = true;
740
+ } else if (reaction.resolver) {
741
+ const r = reaction.resolver;
742
+ statics.push({ stream: r.target, source: r.source });
743
+ }
744
+ }
745
+ }
746
+ this._static_targets = statics;
737
747
  dispose(() => {
738
748
  this._emitter.removeAllListeners();
739
749
  this.stop_correlations();
@@ -747,6 +757,10 @@ var Act = class {
747
757
  _correlation_timer = void 0;
748
758
  _settle_timer = void 0;
749
759
  _settling = false;
760
+ _correlation_checkpoint = -1;
761
+ _subscribed_statics = /* @__PURE__ */ new Set();
762
+ _has_dynamic_resolvers = false;
763
+ _correlation_initialized = false;
750
764
  emit(event, args) {
751
765
  return this._emitter.emit(event, args);
752
766
  }
@@ -758,6 +772,14 @@ var Act = class {
758
772
  this._emitter.off(event, listener);
759
773
  return this;
760
774
  }
775
+ /**
776
+ * Create a new Act orchestrator.
777
+ *
778
+ * @param registry The registry of state, event, and action schemas
779
+ * @param states Map of state names to their (potentially merged) state definitions
780
+ */
781
+ /** Static resolver targets collected at build time */
782
+ _static_targets;
761
783
  /**
762
784
  * Executes an action on a state instance, committing resulting events.
763
785
  *
@@ -1167,37 +1189,67 @@ var Act = class {
1167
1189
  * @see {@link start_correlations} for automatic periodic correlation
1168
1190
  * @see {@link stop_correlations} to stop automatic correlation
1169
1191
  */
1192
+ /**
1193
+ * Initialize correlation state on first call.
1194
+ * - Reads max(at) from store as cold-start checkpoint
1195
+ * - Subscribes static resolver targets (idempotent upsert)
1196
+ * - Populates the subscribed statics set
1197
+ * @internal
1198
+ */
1199
+ async _init_correlation() {
1200
+ if (this._correlation_initialized) return;
1201
+ this._correlation_initialized = true;
1202
+ const { watermark } = await store().subscribe(this._static_targets);
1203
+ this._correlation_checkpoint = watermark;
1204
+ for (const { stream } of this._static_targets) {
1205
+ this._subscribed_statics.add(stream);
1206
+ }
1207
+ }
1170
1208
  async correlate(query = { after: -1, limit: 10 }) {
1209
+ await this._init_correlation();
1210
+ if (!this._has_dynamic_resolvers)
1211
+ return { subscribed: 0, last_id: this._correlation_checkpoint };
1212
+ const after = Math.max(this._correlation_checkpoint, query.after || -1);
1171
1213
  const correlated = /* @__PURE__ */ new Map();
1172
- let last_id = query.after || -1;
1173
- await store().query((event) => {
1174
- last_id = event.id;
1175
- const register = this.registry.events[event.name];
1176
- if (register) {
1177
- for (const reaction of register.reactions.values()) {
1178
- const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
1179
- if (resolved) {
1180
- const entry = correlated.get(resolved.target) || {
1181
- source: resolved.source,
1182
- payloads: []
1183
- };
1184
- entry.payloads.push({
1185
- ...reaction,
1186
- source: resolved.source,
1187
- event
1188
- });
1189
- correlated.set(resolved.target, entry);
1214
+ let last_id = after;
1215
+ await store().query(
1216
+ (event) => {
1217
+ last_id = event.id;
1218
+ const register = this.registry.events[event.name];
1219
+ if (register) {
1220
+ for (const reaction of register.reactions.values()) {
1221
+ if (typeof reaction.resolver !== "function") continue;
1222
+ const resolved = reaction.resolver(event);
1223
+ if (resolved && !this._subscribed_statics.has(resolved.target)) {
1224
+ const entry = correlated.get(resolved.target) || {
1225
+ source: resolved.source,
1226
+ payloads: []
1227
+ };
1228
+ entry.payloads.push({
1229
+ ...reaction,
1230
+ source: resolved.source,
1231
+ event
1232
+ });
1233
+ correlated.set(resolved.target, entry);
1234
+ }
1190
1235
  }
1191
1236
  }
1192
- }
1193
- }, query);
1237
+ },
1238
+ { ...query, after }
1239
+ );
1240
+ this._correlation_checkpoint = last_id;
1194
1241
  if (correlated.size) {
1195
1242
  const streams = [...correlated.entries()].map(([stream, { source }]) => ({
1196
1243
  stream,
1197
1244
  source
1198
1245
  }));
1199
- const subscribed = await store().subscribe(streams);
1200
- subscribed && tracer.correlated(streams);
1246
+ const { subscribed } = await store().subscribe(streams);
1247
+ if (subscribed) {
1248
+ tracer.correlated(streams);
1249
+ for (const { stream } of streams) {
1250
+ this._subscribed_statics.add(stream);
1251
+ }
1252
+ }
1201
1253
  return { subscribed, last_id };
1202
1254
  }
1203
1255
  return { subscribed: 0, last_id };
@@ -1260,10 +1312,8 @@ var Act = class {
1260
1312
  start_correlations(query = {}, frequency = 1e4, callback) {
1261
1313
  if (this._correlation_timer) return false;
1262
1314
  const limit = query.limit || 100;
1263
- let after = query.after || -1;
1264
1315
  this._correlation_timer = setInterval(
1265
- () => this.correlate({ ...query, after, limit }).then((result) => {
1266
- after = result.last_id;
1316
+ () => this.correlate({ ...query, after: this._correlation_checkpoint, limit }).then((result) => {
1267
1317
  if (callback && result.subscribed) callback(result.subscribed);
1268
1318
  }).catch(console.error),
1269
1319
  frequency
@@ -1346,9 +1396,13 @@ var Act = class {
1346
1396
  if (this._settling) return;
1347
1397
  this._settling = true;
1348
1398
  (async () => {
1399
+ await this._init_correlation();
1349
1400
  let lastDrain;
1350
1401
  for (let i = 0; i < maxPasses; i++) {
1351
- const { subscribed } = await this.correlate(correlateQuery);
1402
+ const { subscribed } = await this.correlate({
1403
+ ...correlateQuery,
1404
+ after: this._correlation_checkpoint
1405
+ });
1352
1406
  if (subscribed === 0 && i > 0) break;
1353
1407
  lastDrain = await this.drain(drainOptions);
1354
1408
  if (!lastDrain.acked.length && !lastDrain.blocked.length) break;