@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.js
CHANGED
|
@@ -197,71 +197,84 @@ 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.
|
|
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.
|
|
218
|
-
* @param
|
|
219
|
-
* @param by - The lease holder.
|
|
218
|
+
* @param lease - The lease request.
|
|
220
219
|
* @param millis - Lease duration in milliseconds.
|
|
221
220
|
* @returns The granted lease or undefined if blocked.
|
|
222
221
|
*/
|
|
223
|
-
lease(
|
|
224
|
-
if (this.is_avaliable
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
222
|
+
lease(lease, millis) {
|
|
223
|
+
if (this.is_avaliable) {
|
|
224
|
+
if (millis > 0) {
|
|
225
|
+
this._leased_by = lease.by;
|
|
226
|
+
this._leased_until = new Date(Date.now() + millis);
|
|
227
|
+
this._retry = this._retry + 1;
|
|
228
|
+
}
|
|
229
229
|
return {
|
|
230
230
|
stream: this.stream,
|
|
231
231
|
source: this.source,
|
|
232
|
-
at,
|
|
233
|
-
by,
|
|
234
|
-
retry: this.
|
|
232
|
+
at: lease.at,
|
|
233
|
+
by: lease.by,
|
|
234
|
+
retry: this._retry,
|
|
235
|
+
lagging: lease.lagging
|
|
235
236
|
};
|
|
236
237
|
}
|
|
237
238
|
}
|
|
238
239
|
/**
|
|
239
240
|
* Acknowledge completion of processing for this stream.
|
|
240
|
-
* @param
|
|
241
|
-
* @param by - Lease holder that processed the watermark.
|
|
241
|
+
* @param lease - The lease request.
|
|
242
242
|
*/
|
|
243
|
-
ack(
|
|
244
|
-
if (this.
|
|
245
|
-
this.
|
|
246
|
-
this.
|
|
247
|
-
this.
|
|
248
|
-
this.
|
|
249
|
-
|
|
250
|
-
|
|
243
|
+
ack(lease) {
|
|
244
|
+
if (this._leased_by === lease.by) {
|
|
245
|
+
this._leased_by = void 0;
|
|
246
|
+
this._leased_until = void 0;
|
|
247
|
+
this._at = lease.at;
|
|
248
|
+
this._retry = -1;
|
|
249
|
+
return {
|
|
250
|
+
stream: this.stream,
|
|
251
|
+
source: this.source,
|
|
252
|
+
at: this._at,
|
|
253
|
+
by: lease.by,
|
|
254
|
+
retry: this._retry,
|
|
255
|
+
lagging: lease.lagging
|
|
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.
|
|
261
|
+
* @param lease - The lease request.
|
|
256
262
|
* @param error Blocked error message.
|
|
257
263
|
*/
|
|
258
|
-
block(
|
|
259
|
-
if (this.
|
|
260
|
-
this.
|
|
261
|
-
this.
|
|
262
|
-
return
|
|
264
|
+
block(lease, error) {
|
|
265
|
+
if (this._leased_by === lease.by) {
|
|
266
|
+
this._blocked = true;
|
|
267
|
+
this._error = error;
|
|
268
|
+
return {
|
|
269
|
+
stream: this.stream,
|
|
270
|
+
source: this.source,
|
|
271
|
+
at: this._at,
|
|
272
|
+
by: this._leased_by,
|
|
273
|
+
retry: this._retry,
|
|
274
|
+
error: this._error,
|
|
275
|
+
lagging: lease.lagging
|
|
276
|
+
};
|
|
263
277
|
}
|
|
264
|
-
return false;
|
|
265
278
|
}
|
|
266
279
|
};
|
|
267
280
|
var InMemoryStore = class {
|
|
@@ -293,6 +306,15 @@ var InMemoryStore = class {
|
|
|
293
306
|
this._events.length = 0;
|
|
294
307
|
this._streams = /* @__PURE__ */ new Map();
|
|
295
308
|
}
|
|
309
|
+
in_query(query, e) {
|
|
310
|
+
if (query.stream && !RegExp(`^${query.stream}$`).test(e.stream))
|
|
311
|
+
return false;
|
|
312
|
+
if (query.names && !query.names.includes(e.name)) return false;
|
|
313
|
+
if (query.correlation && e.meta?.correlation !== query.correlation)
|
|
314
|
+
return false;
|
|
315
|
+
if (e.name === SNAP_EVENT && !query.with_snaps) return false;
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
296
318
|
/**
|
|
297
319
|
* Query events in the store, optionally filtered by query options.
|
|
298
320
|
* @param callback - Function to call for each event.
|
|
@@ -301,30 +323,32 @@ var InMemoryStore = class {
|
|
|
301
323
|
*/
|
|
302
324
|
async query(callback, query) {
|
|
303
325
|
await sleep();
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
let count = 0;
|
|
327
|
+
if (query?.backward) {
|
|
328
|
+
let i = (query?.before || this._events.length) - 1;
|
|
329
|
+
while (i >= 0) {
|
|
330
|
+
const e = this._events[i--];
|
|
331
|
+
if (query && !this.in_query(query, e)) continue;
|
|
332
|
+
if (query?.created_before && e.created >= query.created_before)
|
|
333
|
+
continue;
|
|
334
|
+
if (query.after && e.id <= query.after) break;
|
|
335
|
+
if (query.created_after && e.created <= query.created_after) break;
|
|
336
|
+
callback(e);
|
|
337
|
+
count++;
|
|
338
|
+
if (query?.limit && count >= query.limit) break;
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
let i = (query?.after ?? -1) + 1;
|
|
342
|
+
while (i < this._events.length) {
|
|
343
|
+
const e = this._events[i++];
|
|
344
|
+
if (query && !this.in_query(query, e)) continue;
|
|
345
|
+
if (query?.created_after && e.created <= query.created_after) continue;
|
|
346
|
+
if (query?.before && e.id >= query.before) break;
|
|
347
|
+
if (query?.created_before && e.created >= query.created_before) break;
|
|
348
|
+
callback(e);
|
|
349
|
+
count++;
|
|
350
|
+
if (query?.limit && count >= query.limit) break;
|
|
351
|
+
}
|
|
328
352
|
}
|
|
329
353
|
return count;
|
|
330
354
|
}
|
|
@@ -366,13 +390,25 @@ var InMemoryStore = class {
|
|
|
366
390
|
}
|
|
367
391
|
/**
|
|
368
392
|
* Polls the store for unblocked streams needing processing, ordered by lease watermark ascending.
|
|
369
|
-
* @param
|
|
370
|
-
* @param
|
|
393
|
+
* @param lagging - Max number of streams to poll in ascending order.
|
|
394
|
+
* @param leading - Max number of streams to poll in descending order.
|
|
371
395
|
* @returns The polled streams.
|
|
372
396
|
*/
|
|
373
|
-
async poll(
|
|
397
|
+
async poll(lagging, leading) {
|
|
374
398
|
await sleep();
|
|
375
|
-
|
|
399
|
+
const a = [...this._streams.values()].filter((s) => s.is_avaliable).sort((a2, b2) => a2.at - b2.at).slice(0, lagging).map(({ stream, source, at }) => ({
|
|
400
|
+
stream,
|
|
401
|
+
source,
|
|
402
|
+
at,
|
|
403
|
+
lagging: true
|
|
404
|
+
}));
|
|
405
|
+
const b = [...this._streams.values()].filter((s) => s.is_avaliable).sort((a2, b2) => b2.at - a2.at).slice(0, leading).map(({ stream, source, at }) => ({
|
|
406
|
+
stream,
|
|
407
|
+
source,
|
|
408
|
+
at,
|
|
409
|
+
lagging: false
|
|
410
|
+
}));
|
|
411
|
+
return [...a, ...b];
|
|
376
412
|
}
|
|
377
413
|
/**
|
|
378
414
|
* Lease streams for processing (e.g., for distributed consumers).
|
|
@@ -382,10 +418,11 @@ var InMemoryStore = class {
|
|
|
382
418
|
*/
|
|
383
419
|
async lease(leases, millis) {
|
|
384
420
|
await sleep();
|
|
385
|
-
return leases.map((
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
421
|
+
return leases.map((l) => {
|
|
422
|
+
if (!this._streams.has(l.stream)) {
|
|
423
|
+
this._streams.set(l.stream, new InMemoryStream(l.stream, l.source));
|
|
424
|
+
}
|
|
425
|
+
return this._streams.get(l.stream)?.lease(l, millis);
|
|
389
426
|
}).filter((l) => !!l);
|
|
390
427
|
}
|
|
391
428
|
/**
|
|
@@ -394,9 +431,7 @@ var InMemoryStore = class {
|
|
|
394
431
|
*/
|
|
395
432
|
async ack(leases) {
|
|
396
433
|
await sleep();
|
|
397
|
-
return leases.filter(
|
|
398
|
-
(lease) => this._streams.get(lease.stream)?.ack(lease.at, lease.by)
|
|
399
|
-
);
|
|
434
|
+
return leases.map((l) => this._streams.get(l.stream)?.ack(l)).filter((l) => !!l);
|
|
400
435
|
}
|
|
401
436
|
/**
|
|
402
437
|
* Block a stream for processing after failing to process and reaching max retries with blocking enabled.
|
|
@@ -405,9 +440,7 @@ var InMemoryStore = class {
|
|
|
405
440
|
*/
|
|
406
441
|
async block(leases) {
|
|
407
442
|
await sleep();
|
|
408
|
-
return leases.filter(
|
|
409
|
-
(lease) => this._streams.get(lease.stream)?.block(lease.by, lease.error)
|
|
410
|
-
);
|
|
443
|
+
return leases.map((l) => this._streams.get(l.stream)?.block(l, l.error)).filter((l) => !!l);
|
|
411
444
|
}
|
|
412
445
|
};
|
|
413
446
|
|
|
@@ -667,6 +700,7 @@ var Act = class {
|
|
|
667
700
|
}
|
|
668
701
|
_emitter = new EventEmitter();
|
|
669
702
|
_drain_locked = false;
|
|
703
|
+
_drain_lag2lead_ratio = 0.5;
|
|
670
704
|
_correlation_interval = void 0;
|
|
671
705
|
emit(event, args) {
|
|
672
706
|
return this._emitter.emit(event, args);
|
|
@@ -769,7 +803,7 @@ var Act = class {
|
|
|
769
803
|
* @returns The lease with results
|
|
770
804
|
*/
|
|
771
805
|
async handle(lease, payloads) {
|
|
772
|
-
if (payloads.length === 0) return { lease, at: lease.at };
|
|
806
|
+
if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
|
|
773
807
|
const stream = lease.stream;
|
|
774
808
|
let at = payloads.at(0).event.id, handled = 0;
|
|
775
809
|
lease.retry > 0 && logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
@@ -785,6 +819,7 @@ var Act = class {
|
|
|
785
819
|
block && logger.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
786
820
|
return {
|
|
787
821
|
lease,
|
|
822
|
+
handled,
|
|
788
823
|
at,
|
|
789
824
|
// only report error when nothing was handled
|
|
790
825
|
error: handled === 0 ? error.message : void 0,
|
|
@@ -792,7 +827,7 @@ var Act = class {
|
|
|
792
827
|
};
|
|
793
828
|
}
|
|
794
829
|
}
|
|
795
|
-
return { lease, at };
|
|
830
|
+
return { lease, handled, at };
|
|
796
831
|
}
|
|
797
832
|
/**
|
|
798
833
|
* Drains and processes events from the store, triggering reactions and updating state.
|
|
@@ -807,42 +842,36 @@ var Act = class {
|
|
|
807
842
|
async drain({
|
|
808
843
|
streamLimit = 10,
|
|
809
844
|
eventLimit = 10,
|
|
810
|
-
leaseMillis = 1e4
|
|
811
|
-
descending = false
|
|
845
|
+
leaseMillis = 1e4
|
|
812
846
|
} = {}) {
|
|
813
847
|
if (!this._drain_locked) {
|
|
814
848
|
try {
|
|
815
849
|
this._drain_locked = true;
|
|
816
|
-
const
|
|
850
|
+
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
851
|
+
const leading = streamLimit - lagging;
|
|
852
|
+
const polled = await store().poll(lagging, leading);
|
|
817
853
|
const fetched = await Promise.all(
|
|
818
|
-
polled.map(async ({ stream, source, at }) => {
|
|
854
|
+
polled.map(async ({ stream, source, at, lagging: lagging2 }) => {
|
|
819
855
|
const events = await this.query_array({
|
|
820
856
|
stream: source,
|
|
821
857
|
after: at,
|
|
822
858
|
limit: eventLimit
|
|
823
859
|
});
|
|
824
|
-
return { stream, source, events };
|
|
860
|
+
return { stream, source, at, lagging: lagging2, events };
|
|
825
861
|
})
|
|
826
862
|
);
|
|
827
|
-
fetched.length
|
|
828
|
-
|
|
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) {
|
|
863
|
+
if (fetched.length) {
|
|
864
|
+
tracer.fetched(fetched);
|
|
836
865
|
const leases = /* @__PURE__ */ new Map();
|
|
837
|
-
fetched.
|
|
866
|
+
const fetch_window_at = fetched.reduce(
|
|
867
|
+
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
868
|
+
0
|
|
869
|
+
);
|
|
870
|
+
fetched.forEach(({ stream, lagging: lagging2, events }) => {
|
|
838
871
|
const payloads = events.flatMap((event) => {
|
|
839
|
-
const register = this.registry.events[event.name];
|
|
840
|
-
if (!register) return [];
|
|
872
|
+
const register = this.registry.events[event.name] || [];
|
|
841
873
|
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;
|
|
874
|
+
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
846
875
|
return resolved && resolved.target === stream;
|
|
847
876
|
}).map((reaction) => ({ ...reaction, event }));
|
|
848
877
|
});
|
|
@@ -850,43 +879,51 @@ var Act = class {
|
|
|
850
879
|
lease: {
|
|
851
880
|
stream,
|
|
852
881
|
by: randomUUID2(),
|
|
853
|
-
at: events.at(-1)?.id ||
|
|
854
|
-
//
|
|
855
|
-
retry: 0
|
|
882
|
+
at: events.at(-1)?.id || fetch_window_at,
|
|
883
|
+
// ff when no matching events
|
|
884
|
+
retry: 0,
|
|
885
|
+
lagging: lagging2
|
|
856
886
|
},
|
|
857
887
|
// @ts-expect-error indexed by key
|
|
858
888
|
payloads
|
|
859
889
|
});
|
|
860
890
|
});
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
891
|
+
const leased = await store().lease(
|
|
892
|
+
[...leases.values()].map(({ lease }) => lease),
|
|
893
|
+
leaseMillis
|
|
894
|
+
);
|
|
895
|
+
tracer.leased(leased);
|
|
896
|
+
const handled = await Promise.all(
|
|
897
|
+
leased.map(
|
|
898
|
+
(lease) => this.handle(lease, leases.get(lease.stream).payloads)
|
|
899
|
+
)
|
|
900
|
+
);
|
|
901
|
+
const [lagging_handled, leading_handled] = handled.reduce(
|
|
902
|
+
([lagging_handled2, leading_handled2], { lease, handled: handled2 }) => [
|
|
903
|
+
lagging_handled2 + (lease.lagging ? handled2 : 0),
|
|
904
|
+
leading_handled2 + (lease.lagging ? 0 : handled2)
|
|
905
|
+
],
|
|
906
|
+
[0, 0]
|
|
907
|
+
);
|
|
908
|
+
const lagging_avg = lagging > 0 ? lagging_handled / lagging : 0;
|
|
909
|
+
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
910
|
+
const total = lagging_avg + leading_avg;
|
|
911
|
+
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
912
|
+
const acked = await store().ack(
|
|
913
|
+
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
914
|
+
);
|
|
915
|
+
if (acked.length) {
|
|
916
|
+
tracer.acked(acked);
|
|
917
|
+
this.emit("acked", acked);
|
|
918
|
+
}
|
|
919
|
+
const blocked = await store().block(
|
|
920
|
+
handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
|
|
921
|
+
);
|
|
922
|
+
if (blocked.length) {
|
|
923
|
+
tracer.blocked(blocked);
|
|
924
|
+
this.emit("blocked", blocked);
|
|
889
925
|
}
|
|
926
|
+
return { fetched, leased, acked, blocked };
|
|
890
927
|
}
|
|
891
928
|
} catch (error) {
|
|
892
929
|
logger.error(error);
|
|
@@ -894,7 +931,7 @@ var Act = class {
|
|
|
894
931
|
this._drain_locked = false;
|
|
895
932
|
}
|
|
896
933
|
}
|
|
897
|
-
return { leased: [], acked: [], blocked: [] };
|
|
934
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
898
935
|
}
|
|
899
936
|
/**
|
|
900
937
|
* Correlates streams using reaction resolvers.
|
|
@@ -922,6 +959,7 @@ var Act = class {
|
|
|
922
959
|
by: randomUUID2(),
|
|
923
960
|
at: 0,
|
|
924
961
|
retry: 0,
|
|
962
|
+
lagging: true,
|
|
925
963
|
payloads
|
|
926
964
|
}));
|
|
927
965
|
const leased = await store().lease(leases, 0);
|