@rotorsoft/act 0.6.1 → 0.6.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.
package/dist/index.js CHANGED
@@ -197,21 +197,21 @@ async function sleep(ms) {
197
197
 
198
198
  // src/adapters/InMemoryStore.ts
199
199
  var InMemoryStream = class {
200
- stream;
201
- source;
202
- at = -1;
203
- retry = -1;
204
- blocked = false;
205
- error = "";
206
- leased_at = void 0;
207
- leased_by = void 0;
208
- leased_until = void 0;
209
200
  constructor(stream, source) {
210
201
  this.stream = stream;
211
202
  this.source = source;
212
203
  }
204
+ _at = -1;
205
+ _retry = -1;
206
+ _blocked = false;
207
+ _error = "";
208
+ _leased_by = void 0;
209
+ _leased_until = void 0;
213
210
  get is_avaliable() {
214
- return !this.blocked && (!this.leased_until || this.leased_until <= /* @__PURE__ */ new Date());
211
+ return !this._blocked && (!this._leased_until || this._leased_until <= /* @__PURE__ */ new Date());
212
+ }
213
+ get at() {
214
+ return this._at;
215
215
  }
216
216
  /**
217
217
  * Attempt to lease this stream for processing.
@@ -221,17 +221,18 @@ var InMemoryStream = class {
221
221
  * @returns The granted lease or undefined if blocked.
222
222
  */
223
223
  lease(at, by, millis) {
224
- if (this.is_avaliable && at > this.at) {
225
- this.leased_at = at;
226
- this.leased_by = by;
227
- this.leased_until = new Date(Date.now() + millis);
228
- millis > 0 && (this.retry = this.retry + 1);
224
+ if (this.is_avaliable) {
225
+ if (millis > 0) {
226
+ this._leased_by = by;
227
+ this._leased_until = new Date(Date.now() + millis);
228
+ this._retry = this._retry + 1;
229
+ }
229
230
  return {
230
231
  stream: this.stream,
231
232
  source: this.source,
232
233
  at,
233
234
  by,
234
- retry: this.retry
235
+ retry: this._retry
235
236
  };
236
237
  }
237
238
  }
@@ -241,27 +242,37 @@ var InMemoryStream = class {
241
242
  * @param by - Lease holder that processed the watermark.
242
243
  */
243
244
  ack(at, by) {
244
- if (this.leased_by === by && at >= this.at) {
245
- this.leased_at = void 0;
246
- this.leased_by = void 0;
247
- this.leased_until = void 0;
248
- this.at = at;
249
- this.retry = -1;
250
- return true;
245
+ if (this._leased_by === by) {
246
+ this._leased_by = void 0;
247
+ this._leased_until = void 0;
248
+ this._at = at;
249
+ this._retry = -1;
250
+ return {
251
+ stream: this.stream,
252
+ source: this.source,
253
+ at: this._at,
254
+ by,
255
+ retry: this._retry
256
+ };
251
257
  }
252
- return false;
253
258
  }
254
259
  /**
255
260
  * Block a stream for processing after failing to process and reaching max retries with blocking enabled.
256
261
  * @param error Blocked error message.
257
262
  */
258
263
  block(by, error) {
259
- if (this.leased_by === by) {
260
- this.blocked = true;
261
- this.error = error;
262
- return true;
264
+ if (this._leased_by === by) {
265
+ this._blocked = true;
266
+ this._error = error;
267
+ return {
268
+ stream: this.stream,
269
+ source: this.source,
270
+ at: this._at,
271
+ by: this._leased_by,
272
+ retry: this._retry,
273
+ error: this._error
274
+ };
263
275
  }
264
- return false;
265
276
  }
266
277
  };
267
278
  var InMemoryStore = class {
@@ -293,6 +304,15 @@ var InMemoryStore = class {
293
304
  this._events.length = 0;
294
305
  this._streams = /* @__PURE__ */ new Map();
295
306
  }
307
+ in_query(query, e) {
308
+ if (query.stream && !RegExp(`^${query.stream}$`).test(e.stream))
309
+ return false;
310
+ if (query.names && !query.names.includes(e.name)) return false;
311
+ if (query.correlation && e.meta?.correlation !== query.correlation)
312
+ return false;
313
+ if (e.name === SNAP_EVENT && !query.with_snaps) return false;
314
+ return true;
315
+ }
296
316
  /**
297
317
  * Query events in the store, optionally filtered by query options.
298
318
  * @param callback - Function to call for each event.
@@ -301,30 +321,32 @@ var InMemoryStore = class {
301
321
  */
302
322
  async query(callback, query) {
303
323
  await sleep();
304
- const {
305
- stream,
306
- names,
307
- before,
308
- after = -1,
309
- limit,
310
- created_before,
311
- created_after,
312
- correlation,
313
- with_snaps = false
314
- } = query || {};
315
- let i = after + 1, count = 0;
316
- while (i < this._events.length) {
317
- const e = this._events[i++];
318
- if (stream && !RegExp(`^${stream}$`).test(e.stream)) continue;
319
- if (names && !names.includes(e.name)) continue;
320
- if (correlation && e.meta?.correlation !== correlation) continue;
321
- if (created_after && e.created <= created_after) continue;
322
- if (e.name === SNAP_EVENT && !with_snaps) continue;
323
- if (before && e.id >= before) break;
324
- if (created_before && e.created >= created_before) break;
325
- callback(e);
326
- count++;
327
- if (limit && count >= limit) break;
324
+ let count = 0;
325
+ if (query?.backward) {
326
+ let i = (query?.before || this._events.length) - 1;
327
+ while (i >= 0) {
328
+ const e = this._events[i--];
329
+ if (query && !this.in_query(query, e)) continue;
330
+ if (query?.created_before && e.created >= query.created_before)
331
+ continue;
332
+ if (query.after && e.id <= query.after) break;
333
+ if (query.created_after && e.created <= query.created_after) break;
334
+ callback(e);
335
+ count++;
336
+ if (query?.limit && count >= query.limit) break;
337
+ }
338
+ } else {
339
+ let i = (query?.after ?? -1) + 1;
340
+ while (i < this._events.length) {
341
+ const e = this._events[i++];
342
+ if (query && !this.in_query(query, e)) continue;
343
+ if (query?.created_after && e.created <= query.created_after) continue;
344
+ if (query?.before && e.id >= query.before) break;
345
+ if (query?.created_before && e.created >= query.created_before) break;
346
+ callback(e);
347
+ count++;
348
+ if (query?.limit && count >= query.limit) break;
349
+ }
328
350
  }
329
351
  return count;
330
352
  }
@@ -366,13 +388,15 @@ var InMemoryStore = class {
366
388
  }
367
389
  /**
368
390
  * Polls the store for unblocked streams needing processing, ordered by lease watermark ascending.
369
- * @param limit - Maximum number of streams to poll.
370
- * @param descending - Whether to poll streams in descending order (aka poll the most advanced first).
391
+ * @param lagging - Max number of streams to poll in ascending order.
392
+ * @param leading - Max number of streams to poll in descending order.
371
393
  * @returns The polled streams.
372
394
  */
373
- async poll(limit, descending = false) {
395
+ async poll(lagging, leading) {
374
396
  await sleep();
375
- return [...this._streams.values()].filter((s) => s.is_avaliable).sort((a, b) => descending ? b.at - a.at : a.at - b.at).slice(0, limit).map(({ stream, source, at }) => ({ stream, source, at }));
397
+ const a = [...this._streams.values()].filter((s) => s.is_avaliable).sort((a2, b2) => a2.at - b2.at).slice(0, lagging).map(({ stream, source, at }) => ({ stream, source, at }));
398
+ const b = [...this._streams.values()].filter((s) => s.is_avaliable).sort((a2, b2) => b2.at - a2.at).slice(0, leading).map(({ stream, source, at }) => ({ stream, source, at }));
399
+ return [...a, ...b];
376
400
  }
377
401
  /**
378
402
  * Lease streams for processing (e.g., for distributed consumers).
@@ -382,10 +406,11 @@ var InMemoryStore = class {
382
406
  */
383
407
  async lease(leases, millis) {
384
408
  await sleep();
385
- return leases.map(({ stream, at, by, source }) => {
386
- const found = this._streams.get(stream) || // store new correlations
387
- this._streams.set(stream, new InMemoryStream(stream, source)).get(stream);
388
- return found.lease(at, by, millis);
409
+ return leases.map((l) => {
410
+ if (!this._streams.has(l.stream)) {
411
+ this._streams.set(l.stream, new InMemoryStream(l.stream, l.source));
412
+ }
413
+ return this._streams.get(l.stream)?.lease(l.at, l.by, millis);
389
414
  }).filter((l) => !!l);
390
415
  }
391
416
  /**
@@ -394,9 +419,7 @@ var InMemoryStore = class {
394
419
  */
395
420
  async ack(leases) {
396
421
  await sleep();
397
- return leases.filter(
398
- (lease) => this._streams.get(lease.stream)?.ack(lease.at, lease.by)
399
- );
422
+ return leases.map((l) => this._streams.get(l.stream)?.ack(l.at, l.by)).filter((l) => !!l);
400
423
  }
401
424
  /**
402
425
  * Block a stream for processing after failing to process and reaching max retries with blocking enabled.
@@ -405,9 +428,7 @@ var InMemoryStore = class {
405
428
  */
406
429
  async block(leases) {
407
430
  await sleep();
408
- return leases.filter(
409
- (lease) => this._streams.get(lease.stream)?.block(lease.by, lease.error)
410
- );
431
+ return leases.map((l) => this._streams.get(l.stream)?.block(l.by, l.error)).filter((l) => !!l);
411
432
  }
412
433
  };
413
434
 
@@ -807,13 +828,14 @@ var Act = class {
807
828
  async drain({
808
829
  streamLimit = 10,
809
830
  eventLimit = 10,
810
- leaseMillis = 1e4,
811
- descending = false
831
+ leaseMillis = 1e4
812
832
  } = {}) {
813
833
  if (!this._drain_locked) {
814
834
  try {
815
835
  this._drain_locked = true;
816
- const polled = await store().poll(streamLimit, descending);
836
+ const lagging = Math.ceil(streamLimit * 2 / 3);
837
+ const leading = streamLimit - lagging;
838
+ const polled = await store().poll(lagging, leading);
817
839
  const fetched = await Promise.all(
818
840
  polled.map(async ({ stream, source, at }) => {
819
841
  const events = await this.query_array({
@@ -821,28 +843,21 @@ var Act = class {
821
843
  after: at,
822
844
  limit: eventLimit
823
845
  });
824
- return { stream, source, events };
846
+ return { stream, source, at, events };
825
847
  })
826
848
  );
827
- fetched.length && tracer.fetched(fetched);
828
- const [last_at, count] = fetched.reduce(
829
- ([last_at2, count2], { events }) => [
830
- Math.max(last_at2, events.at(-1)?.id || 0),
831
- count2 + events.length
832
- ],
833
- [0, 0]
834
- );
835
- if (count > 0) {
849
+ if (fetched.length) {
850
+ tracer.fetched(fetched);
836
851
  const leases = /* @__PURE__ */ new Map();
852
+ const last_window_at = fetched.reduce(
853
+ (max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
854
+ 0
855
+ );
837
856
  fetched.forEach(({ stream, events }) => {
838
857
  const payloads = events.flatMap((event) => {
839
- const register = this.registry.events[event.name];
840
- if (!register) return [];
858
+ const register = this.registry.events[event.name] || [];
841
859
  return [...register.reactions.values()].filter((reaction) => {
842
- const resolved = typeof reaction.resolver === "function" ? (
843
- // @ts-expect-error index by key
844
- reaction.resolver(event)
845
- ) : reaction.resolver;
860
+ const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
846
861
  return resolved && resolved.target === stream;
847
862
  }).map((reaction) => ({ ...reaction, event }));
848
863
  });
@@ -850,43 +865,39 @@ var Act = class {
850
865
  lease: {
851
866
  stream,
852
867
  by: randomUUID2(),
853
- at: events.at(-1)?.id || last_at,
854
- // move the lease watermark forward when no events found in window
868
+ at: events.at(-1)?.id || last_window_at,
869
+ // ff when no matching events
855
870
  retry: 0
856
871
  },
857
872
  // @ts-expect-error indexed by key
858
873
  payloads
859
874
  });
860
875
  });
861
- if (leases.size) {
862
- const leased = await store().lease(
863
- [...leases.values()].map((l) => l.lease),
864
- leaseMillis
865
- );
866
- if (leased.length) {
867
- tracer.leased(leased);
868
- const handled = await Promise.all(
869
- leased.map(
870
- (lease) => this.handle(lease, leases.get(lease.stream).payloads)
871
- )
872
- );
873
- const acked = await store().ack(
874
- handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
875
- );
876
- if (acked.length) {
877
- tracer.acked(acked);
878
- this.emit("acked", acked);
879
- }
880
- const blocked = await store().block(
881
- handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
882
- );
883
- if (blocked.length) {
884
- tracer.blocked(blocked);
885
- this.emit("blocked", blocked);
886
- }
887
- return { leased, acked, blocked };
888
- }
876
+ const leased = await store().lease(
877
+ [...leases.values()].map((l) => l.lease),
878
+ leaseMillis
879
+ );
880
+ tracer.leased(leased);
881
+ const handled = await Promise.all(
882
+ leased.map(
883
+ (lease) => this.handle(lease, leases.get(lease.stream).payloads)
884
+ )
885
+ );
886
+ const acked = await store().ack(
887
+ handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
888
+ );
889
+ if (acked.length) {
890
+ tracer.acked(acked);
891
+ this.emit("acked", acked);
892
+ }
893
+ const blocked = await store().block(
894
+ handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
895
+ );
896
+ if (blocked.length) {
897
+ tracer.blocked(blocked);
898
+ this.emit("blocked", blocked);
889
899
  }
900
+ return { fetched, leased, acked, blocked };
890
901
  }
891
902
  } catch (error) {
892
903
  logger.error(error);
@@ -894,7 +905,7 @@ var Act = class {
894
905
  this._drain_locked = false;
895
906
  }
896
907
  }
897
- return { leased: [], acked: [], blocked: [] };
908
+ return { fetched: [], leased: [], acked: [], blocked: [] };
898
909
  }
899
910
  /**
900
911
  * Correlates streams using reaction resolvers.