@rotorsoft/act 0.6.1 → 0.6.3
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 +36 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +3 -8
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/InMemoryStore.d.ts +21 -6
- package/dist/@types/adapters/InMemoryStore.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +3 -3
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +35 -14
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/@types/types/registry.d.ts +9 -0
- package/dist/@types/types/registry.d.ts.map +1 -1
- package/dist/index.cjs +169 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +169 -131
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -262,71 +262,84 @@ async function sleep(ms) {
|
|
|
262
262
|
|
|
263
263
|
// src/adapters/InMemoryStore.ts
|
|
264
264
|
var InMemoryStream = class {
|
|
265
|
-
stream;
|
|
266
|
-
source;
|
|
267
|
-
at = -1;
|
|
268
|
-
retry = -1;
|
|
269
|
-
blocked = false;
|
|
270
|
-
error = "";
|
|
271
|
-
leased_at = void 0;
|
|
272
|
-
leased_by = void 0;
|
|
273
|
-
leased_until = void 0;
|
|
274
265
|
constructor(stream, source) {
|
|
275
266
|
this.stream = stream;
|
|
276
267
|
this.source = source;
|
|
277
268
|
}
|
|
269
|
+
_at = -1;
|
|
270
|
+
_retry = -1;
|
|
271
|
+
_blocked = false;
|
|
272
|
+
_error = "";
|
|
273
|
+
_leased_by = void 0;
|
|
274
|
+
_leased_until = void 0;
|
|
278
275
|
get is_avaliable() {
|
|
279
|
-
return !this.
|
|
276
|
+
return !this._blocked && (!this._leased_until || this._leased_until <= /* @__PURE__ */ new Date());
|
|
277
|
+
}
|
|
278
|
+
get at() {
|
|
279
|
+
return this._at;
|
|
280
280
|
}
|
|
281
281
|
/**
|
|
282
282
|
* Attempt to lease this stream for processing.
|
|
283
|
-
* @param
|
|
284
|
-
* @param by - The lease holder.
|
|
283
|
+
* @param lease - The lease request.
|
|
285
284
|
* @param millis - Lease duration in milliseconds.
|
|
286
285
|
* @returns The granted lease or undefined if blocked.
|
|
287
286
|
*/
|
|
288
|
-
lease(
|
|
289
|
-
if (this.is_avaliable
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
287
|
+
lease(lease, millis) {
|
|
288
|
+
if (this.is_avaliable) {
|
|
289
|
+
if (millis > 0) {
|
|
290
|
+
this._leased_by = lease.by;
|
|
291
|
+
this._leased_until = new Date(Date.now() + millis);
|
|
292
|
+
this._retry = this._retry + 1;
|
|
293
|
+
}
|
|
294
294
|
return {
|
|
295
295
|
stream: this.stream,
|
|
296
296
|
source: this.source,
|
|
297
|
-
at,
|
|
298
|
-
by,
|
|
299
|
-
retry: this.
|
|
297
|
+
at: lease.at,
|
|
298
|
+
by: lease.by,
|
|
299
|
+
retry: this._retry,
|
|
300
|
+
lagging: lease.lagging
|
|
300
301
|
};
|
|
301
302
|
}
|
|
302
303
|
}
|
|
303
304
|
/**
|
|
304
305
|
* Acknowledge completion of processing for this stream.
|
|
305
|
-
* @param
|
|
306
|
-
* @param by - Lease holder that processed the watermark.
|
|
306
|
+
* @param lease - The lease request.
|
|
307
307
|
*/
|
|
308
|
-
ack(
|
|
309
|
-
if (this.
|
|
310
|
-
this.
|
|
311
|
-
this.
|
|
312
|
-
this.
|
|
313
|
-
this.
|
|
314
|
-
|
|
315
|
-
|
|
308
|
+
ack(lease) {
|
|
309
|
+
if (this._leased_by === lease.by) {
|
|
310
|
+
this._leased_by = void 0;
|
|
311
|
+
this._leased_until = void 0;
|
|
312
|
+
this._at = lease.at;
|
|
313
|
+
this._retry = -1;
|
|
314
|
+
return {
|
|
315
|
+
stream: this.stream,
|
|
316
|
+
source: this.source,
|
|
317
|
+
at: this._at,
|
|
318
|
+
by: lease.by,
|
|
319
|
+
retry: this._retry,
|
|
320
|
+
lagging: lease.lagging
|
|
321
|
+
};
|
|
316
322
|
}
|
|
317
|
-
return false;
|
|
318
323
|
}
|
|
319
324
|
/**
|
|
320
325
|
* Block a stream for processing after failing to process and reaching max retries with blocking enabled.
|
|
326
|
+
* @param lease - The lease request.
|
|
321
327
|
* @param error Blocked error message.
|
|
322
328
|
*/
|
|
323
|
-
block(
|
|
324
|
-
if (this.
|
|
325
|
-
this.
|
|
326
|
-
this.
|
|
327
|
-
return
|
|
329
|
+
block(lease, error) {
|
|
330
|
+
if (this._leased_by === lease.by) {
|
|
331
|
+
this._blocked = true;
|
|
332
|
+
this._error = error;
|
|
333
|
+
return {
|
|
334
|
+
stream: this.stream,
|
|
335
|
+
source: this.source,
|
|
336
|
+
at: this._at,
|
|
337
|
+
by: this._leased_by,
|
|
338
|
+
retry: this._retry,
|
|
339
|
+
error: this._error,
|
|
340
|
+
lagging: lease.lagging
|
|
341
|
+
};
|
|
328
342
|
}
|
|
329
|
-
return false;
|
|
330
343
|
}
|
|
331
344
|
};
|
|
332
345
|
var InMemoryStore = class {
|
|
@@ -358,6 +371,15 @@ var InMemoryStore = class {
|
|
|
358
371
|
this._events.length = 0;
|
|
359
372
|
this._streams = /* @__PURE__ */ new Map();
|
|
360
373
|
}
|
|
374
|
+
in_query(query, e) {
|
|
375
|
+
if (query.stream && !RegExp(`^${query.stream}$`).test(e.stream))
|
|
376
|
+
return false;
|
|
377
|
+
if (query.names && !query.names.includes(e.name)) return false;
|
|
378
|
+
if (query.correlation && e.meta?.correlation !== query.correlation)
|
|
379
|
+
return false;
|
|
380
|
+
if (e.name === SNAP_EVENT && !query.with_snaps) return false;
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
361
383
|
/**
|
|
362
384
|
* Query events in the store, optionally filtered by query options.
|
|
363
385
|
* @param callback - Function to call for each event.
|
|
@@ -366,30 +388,32 @@ var InMemoryStore = class {
|
|
|
366
388
|
*/
|
|
367
389
|
async query(callback, query) {
|
|
368
390
|
await sleep();
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
391
|
+
let count = 0;
|
|
392
|
+
if (query?.backward) {
|
|
393
|
+
let i = (query?.before || this._events.length) - 1;
|
|
394
|
+
while (i >= 0) {
|
|
395
|
+
const e = this._events[i--];
|
|
396
|
+
if (query && !this.in_query(query, e)) continue;
|
|
397
|
+
if (query?.created_before && e.created >= query.created_before)
|
|
398
|
+
continue;
|
|
399
|
+
if (query.after && e.id <= query.after) break;
|
|
400
|
+
if (query.created_after && e.created <= query.created_after) break;
|
|
401
|
+
callback(e);
|
|
402
|
+
count++;
|
|
403
|
+
if (query?.limit && count >= query.limit) break;
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
let i = (query?.after ?? -1) + 1;
|
|
407
|
+
while (i < this._events.length) {
|
|
408
|
+
const e = this._events[i++];
|
|
409
|
+
if (query && !this.in_query(query, e)) continue;
|
|
410
|
+
if (query?.created_after && e.created <= query.created_after) continue;
|
|
411
|
+
if (query?.before && e.id >= query.before) break;
|
|
412
|
+
if (query?.created_before && e.created >= query.created_before) break;
|
|
413
|
+
callback(e);
|
|
414
|
+
count++;
|
|
415
|
+
if (query?.limit && count >= query.limit) break;
|
|
416
|
+
}
|
|
393
417
|
}
|
|
394
418
|
return count;
|
|
395
419
|
}
|
|
@@ -431,13 +455,25 @@ var InMemoryStore = class {
|
|
|
431
455
|
}
|
|
432
456
|
/**
|
|
433
457
|
* Polls the store for unblocked streams needing processing, ordered by lease watermark ascending.
|
|
434
|
-
* @param
|
|
435
|
-
* @param
|
|
458
|
+
* @param lagging - Max number of streams to poll in ascending order.
|
|
459
|
+
* @param leading - Max number of streams to poll in descending order.
|
|
436
460
|
* @returns The polled streams.
|
|
437
461
|
*/
|
|
438
|
-
async poll(
|
|
462
|
+
async poll(lagging, leading) {
|
|
439
463
|
await sleep();
|
|
440
|
-
|
|
464
|
+
const a = [...this._streams.values()].filter((s) => s.is_avaliable).sort((a2, b2) => a2.at - b2.at).slice(0, lagging).map(({ stream, source, at }) => ({
|
|
465
|
+
stream,
|
|
466
|
+
source,
|
|
467
|
+
at,
|
|
468
|
+
lagging: true
|
|
469
|
+
}));
|
|
470
|
+
const b = [...this._streams.values()].filter((s) => s.is_avaliable).sort((a2, b2) => b2.at - a2.at).slice(0, leading).map(({ stream, source, at }) => ({
|
|
471
|
+
stream,
|
|
472
|
+
source,
|
|
473
|
+
at,
|
|
474
|
+
lagging: false
|
|
475
|
+
}));
|
|
476
|
+
return [...a, ...b];
|
|
441
477
|
}
|
|
442
478
|
/**
|
|
443
479
|
* Lease streams for processing (e.g., for distributed consumers).
|
|
@@ -447,10 +483,11 @@ var InMemoryStore = class {
|
|
|
447
483
|
*/
|
|
448
484
|
async lease(leases, millis) {
|
|
449
485
|
await sleep();
|
|
450
|
-
return leases.map((
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
486
|
+
return leases.map((l) => {
|
|
487
|
+
if (!this._streams.has(l.stream)) {
|
|
488
|
+
this._streams.set(l.stream, new InMemoryStream(l.stream, l.source));
|
|
489
|
+
}
|
|
490
|
+
return this._streams.get(l.stream)?.lease(l, millis);
|
|
454
491
|
}).filter((l) => !!l);
|
|
455
492
|
}
|
|
456
493
|
/**
|
|
@@ -459,9 +496,7 @@ var InMemoryStore = class {
|
|
|
459
496
|
*/
|
|
460
497
|
async ack(leases) {
|
|
461
498
|
await sleep();
|
|
462
|
-
return leases.filter(
|
|
463
|
-
(lease) => this._streams.get(lease.stream)?.ack(lease.at, lease.by)
|
|
464
|
-
);
|
|
499
|
+
return leases.map((l) => this._streams.get(l.stream)?.ack(l)).filter((l) => !!l);
|
|
465
500
|
}
|
|
466
501
|
/**
|
|
467
502
|
* Block a stream for processing after failing to process and reaching max retries with blocking enabled.
|
|
@@ -470,9 +505,7 @@ var InMemoryStore = class {
|
|
|
470
505
|
*/
|
|
471
506
|
async block(leases) {
|
|
472
507
|
await sleep();
|
|
473
|
-
return leases.filter(
|
|
474
|
-
(lease) => this._streams.get(lease.stream)?.block(lease.by, lease.error)
|
|
475
|
-
);
|
|
508
|
+
return leases.map((l) => this._streams.get(l.stream)?.block(l, l.error)).filter((l) => !!l);
|
|
476
509
|
}
|
|
477
510
|
};
|
|
478
511
|
|
|
@@ -732,6 +765,7 @@ var Act = class {
|
|
|
732
765
|
}
|
|
733
766
|
_emitter = new import_events.default();
|
|
734
767
|
_drain_locked = false;
|
|
768
|
+
_drain_lag2lead_ratio = 0.5;
|
|
735
769
|
_correlation_interval = void 0;
|
|
736
770
|
emit(event, args) {
|
|
737
771
|
return this._emitter.emit(event, args);
|
|
@@ -834,7 +868,7 @@ var Act = class {
|
|
|
834
868
|
* @returns The lease with results
|
|
835
869
|
*/
|
|
836
870
|
async handle(lease, payloads) {
|
|
837
|
-
if (payloads.length === 0) return { lease, at: lease.at };
|
|
871
|
+
if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
|
|
838
872
|
const stream = lease.stream;
|
|
839
873
|
let at = payloads.at(0).event.id, handled = 0;
|
|
840
874
|
lease.retry > 0 && logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
@@ -850,6 +884,7 @@ var Act = class {
|
|
|
850
884
|
block && logger.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
851
885
|
return {
|
|
852
886
|
lease,
|
|
887
|
+
handled,
|
|
853
888
|
at,
|
|
854
889
|
// only report error when nothing was handled
|
|
855
890
|
error: handled === 0 ? error.message : void 0,
|
|
@@ -857,7 +892,7 @@ var Act = class {
|
|
|
857
892
|
};
|
|
858
893
|
}
|
|
859
894
|
}
|
|
860
|
-
return { lease, at };
|
|
895
|
+
return { lease, handled, at };
|
|
861
896
|
}
|
|
862
897
|
/**
|
|
863
898
|
* Drains and processes events from the store, triggering reactions and updating state.
|
|
@@ -872,42 +907,36 @@ var Act = class {
|
|
|
872
907
|
async drain({
|
|
873
908
|
streamLimit = 10,
|
|
874
909
|
eventLimit = 10,
|
|
875
|
-
leaseMillis = 1e4
|
|
876
|
-
descending = false
|
|
910
|
+
leaseMillis = 1e4
|
|
877
911
|
} = {}) {
|
|
878
912
|
if (!this._drain_locked) {
|
|
879
913
|
try {
|
|
880
914
|
this._drain_locked = true;
|
|
881
|
-
const
|
|
915
|
+
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
916
|
+
const leading = streamLimit - lagging;
|
|
917
|
+
const polled = await store().poll(lagging, leading);
|
|
882
918
|
const fetched = await Promise.all(
|
|
883
|
-
polled.map(async ({ stream, source, at }) => {
|
|
919
|
+
polled.map(async ({ stream, source, at, lagging: lagging2 }) => {
|
|
884
920
|
const events = await this.query_array({
|
|
885
921
|
stream: source,
|
|
886
922
|
after: at,
|
|
887
923
|
limit: eventLimit
|
|
888
924
|
});
|
|
889
|
-
return { stream, source, events };
|
|
925
|
+
return { stream, source, at, lagging: lagging2, events };
|
|
890
926
|
})
|
|
891
927
|
);
|
|
892
|
-
fetched.length
|
|
893
|
-
|
|
894
|
-
([last_at2, count2], { events }) => [
|
|
895
|
-
Math.max(last_at2, events.at(-1)?.id || 0),
|
|
896
|
-
count2 + events.length
|
|
897
|
-
],
|
|
898
|
-
[0, 0]
|
|
899
|
-
);
|
|
900
|
-
if (count > 0) {
|
|
928
|
+
if (fetched.length) {
|
|
929
|
+
tracer.fetched(fetched);
|
|
901
930
|
const leases = /* @__PURE__ */ new Map();
|
|
902
|
-
fetched.
|
|
931
|
+
const fetch_window_at = fetched.reduce(
|
|
932
|
+
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
933
|
+
0
|
|
934
|
+
);
|
|
935
|
+
fetched.forEach(({ stream, lagging: lagging2, events }) => {
|
|
903
936
|
const payloads = events.flatMap((event) => {
|
|
904
|
-
const register = this.registry.events[event.name];
|
|
905
|
-
if (!register) return [];
|
|
937
|
+
const register = this.registry.events[event.name] || [];
|
|
906
938
|
return [...register.reactions.values()].filter((reaction) => {
|
|
907
|
-
const resolved = typeof reaction.resolver === "function" ? (
|
|
908
|
-
// @ts-expect-error index by key
|
|
909
|
-
reaction.resolver(event)
|
|
910
|
-
) : reaction.resolver;
|
|
939
|
+
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
911
940
|
return resolved && resolved.target === stream;
|
|
912
941
|
}).map((reaction) => ({ ...reaction, event }));
|
|
913
942
|
});
|
|
@@ -915,43 +944,51 @@ var Act = class {
|
|
|
915
944
|
lease: {
|
|
916
945
|
stream,
|
|
917
946
|
by: (0, import_crypto2.randomUUID)(),
|
|
918
|
-
at: events.at(-1)?.id ||
|
|
919
|
-
//
|
|
920
|
-
retry: 0
|
|
947
|
+
at: events.at(-1)?.id || fetch_window_at,
|
|
948
|
+
// ff when no matching events
|
|
949
|
+
retry: 0,
|
|
950
|
+
lagging: lagging2
|
|
921
951
|
},
|
|
922
952
|
// @ts-expect-error indexed by key
|
|
923
953
|
payloads
|
|
924
954
|
});
|
|
925
955
|
});
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
956
|
+
const leased = await store().lease(
|
|
957
|
+
[...leases.values()].map(({ lease }) => lease),
|
|
958
|
+
leaseMillis
|
|
959
|
+
);
|
|
960
|
+
tracer.leased(leased);
|
|
961
|
+
const handled = await Promise.all(
|
|
962
|
+
leased.map(
|
|
963
|
+
(lease) => this.handle(lease, leases.get(lease.stream).payloads)
|
|
964
|
+
)
|
|
965
|
+
);
|
|
966
|
+
const [lagging_handled, leading_handled] = handled.reduce(
|
|
967
|
+
([lagging_handled2, leading_handled2], { lease, handled: handled2 }) => [
|
|
968
|
+
lagging_handled2 + (lease.lagging ? handled2 : 0),
|
|
969
|
+
leading_handled2 + (lease.lagging ? 0 : handled2)
|
|
970
|
+
],
|
|
971
|
+
[0, 0]
|
|
972
|
+
);
|
|
973
|
+
const lagging_avg = lagging > 0 ? lagging_handled / lagging : 0;
|
|
974
|
+
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
975
|
+
const total = lagging_avg + leading_avg;
|
|
976
|
+
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
977
|
+
const acked = await store().ack(
|
|
978
|
+
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
979
|
+
);
|
|
980
|
+
if (acked.length) {
|
|
981
|
+
tracer.acked(acked);
|
|
982
|
+
this.emit("acked", acked);
|
|
983
|
+
}
|
|
984
|
+
const blocked = await store().block(
|
|
985
|
+
handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
|
|
986
|
+
);
|
|
987
|
+
if (blocked.length) {
|
|
988
|
+
tracer.blocked(blocked);
|
|
989
|
+
this.emit("blocked", blocked);
|
|
954
990
|
}
|
|
991
|
+
return { fetched, leased, acked, blocked };
|
|
955
992
|
}
|
|
956
993
|
} catch (error) {
|
|
957
994
|
logger.error(error);
|
|
@@ -959,7 +996,7 @@ var Act = class {
|
|
|
959
996
|
this._drain_locked = false;
|
|
960
997
|
}
|
|
961
998
|
}
|
|
962
|
-
return { leased: [], acked: [], blocked: [] };
|
|
999
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
963
1000
|
}
|
|
964
1001
|
/**
|
|
965
1002
|
* Correlates streams using reaction resolvers.
|
|
@@ -987,6 +1024,7 @@ var Act = class {
|
|
|
987
1024
|
by: (0, import_crypto2.randomUUID)(),
|
|
988
1025
|
at: 0,
|
|
989
1026
|
retry: 0,
|
|
1027
|
+
lagging: true,
|
|
990
1028
|
payloads
|
|
991
1029
|
}));
|
|
992
1030
|
const leased = await store().lease(leases, 0);
|