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