@rotorsoft/act 1.6.0 → 1.8.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/.tsbuildinfo +1 -1
- package/dist/@types/builders/state-builder.d.ts +23 -2
- package/dist/@types/builders/state-builder.d.ts.map +1 -1
- package/dist/@types/internal/backoff.d.ts +1 -1
- package/dist/@types/internal/backoff.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +11 -1
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +79 -0
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +30 -0
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +12 -39
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/index.cjs +144 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +144 -125
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -2123,6 +2123,29 @@ var subscribe = (streams) => store2().subscribe(streams);
|
|
|
2123
2123
|
|
|
2124
2124
|
// src/internal/event-sourcing.ts
|
|
2125
2125
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
2126
|
+
|
|
2127
|
+
// src/internal/backoff.ts
|
|
2128
|
+
function computeBackoffDelay(retry, opts) {
|
|
2129
|
+
if (!opts || opts.baseMs <= 0) return 0;
|
|
2130
|
+
const r = Math.max(0, retry);
|
|
2131
|
+
let delay;
|
|
2132
|
+
switch (opts.strategy) {
|
|
2133
|
+
case "fixed":
|
|
2134
|
+
delay = opts.baseMs;
|
|
2135
|
+
break;
|
|
2136
|
+
case "linear":
|
|
2137
|
+
delay = opts.baseMs * (r + 1);
|
|
2138
|
+
break;
|
|
2139
|
+
case "exponential":
|
|
2140
|
+
delay = opts.baseMs * 2 ** r;
|
|
2141
|
+
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
2145
|
+
return Math.max(0, Math.floor(delay));
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// src/internal/event-sourcing.ts
|
|
2126
2149
|
var DEFAULT_BATCH = 500;
|
|
2127
2150
|
async function snap(snapshot) {
|
|
2128
2151
|
try {
|
|
@@ -2330,113 +2353,126 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2330
2353
|
const { stream, expectedVersion, actor } = target;
|
|
2331
2354
|
if (!stream) throw new Error("Missing target stream");
|
|
2332
2355
|
const validated = skipValidation ? payload : validate(action2, payload, me.actions[action2]);
|
|
2333
|
-
const
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
warned.
|
|
2364
|
-
|
|
2365
|
-
|
|
2356
|
+
const opts = me.options?.[action2];
|
|
2357
|
+
const maxRetries = opts?.maxRetries ?? 0;
|
|
2358
|
+
for (let attempt = 0; ; attempt++) {
|
|
2359
|
+
try {
|
|
2360
|
+
const snapshot = await load(me, stream);
|
|
2361
|
+
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
2362
|
+
throw new StreamClosedError(stream);
|
|
2363
|
+
const expected = expectedVersion ?? snapshot.event?.version;
|
|
2364
|
+
if (me.given) {
|
|
2365
|
+
const invariants = me.given[action2] || [];
|
|
2366
|
+
invariants.forEach(({ valid, description }) => {
|
|
2367
|
+
if (!valid(snapshot.state, actor))
|
|
2368
|
+
throw new InvariantError(
|
|
2369
|
+
action2,
|
|
2370
|
+
validated,
|
|
2371
|
+
target,
|
|
2372
|
+
snapshot,
|
|
2373
|
+
description
|
|
2374
|
+
);
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
const result = me.on[action2](validated, snapshot, target);
|
|
2378
|
+
if (!result) return [snapshot];
|
|
2379
|
+
if (Array.isArray(result) && result.length === 0) {
|
|
2380
|
+
return [snapshot];
|
|
2381
|
+
}
|
|
2382
|
+
const tuples = Array.isArray(result[0]) ? result : [result];
|
|
2383
|
+
const deprecated = me._deprecated;
|
|
2384
|
+
if (deprecated && deprecated.size > 0) {
|
|
2385
|
+
const me_ = me;
|
|
2386
|
+
const warned = me_._warned ?? (me_._warned = /* @__PURE__ */ new Set());
|
|
2387
|
+
for (const [name] of tuples) {
|
|
2388
|
+
const evt = name;
|
|
2389
|
+
if (deprecated.has(evt) && !warned.has(evt)) {
|
|
2390
|
+
warned.add(evt);
|
|
2391
|
+
log().warn(
|
|
2392
|
+
`Action "${String(action2)}" emitted deprecated event "${evt}". A newer version exists in the registry \u2014 update the action's .emit() to target the current version. (warned once per process)`
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
const emitted = tuples.map(([name, data]) => ({
|
|
2398
|
+
name,
|
|
2399
|
+
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
2400
|
+
}));
|
|
2401
|
+
const meta = {
|
|
2402
|
+
correlation: reactingTo?.meta.correlation || correlator({
|
|
2403
|
+
action: action2,
|
|
2404
|
+
state: me.name,
|
|
2405
|
+
stream,
|
|
2406
|
+
actor: target.actor
|
|
2407
|
+
}),
|
|
2408
|
+
causation: {
|
|
2409
|
+
action: {
|
|
2410
|
+
name: action2,
|
|
2411
|
+
...target
|
|
2412
|
+
// payload intentionally omitted: it can be large or contain PII,
|
|
2413
|
+
// and callers correlate via the correlation id when they need it.
|
|
2414
|
+
},
|
|
2415
|
+
event: reactingTo ? {
|
|
2416
|
+
id: reactingTo.id,
|
|
2417
|
+
name: reactingTo.name,
|
|
2418
|
+
stream: reactingTo.stream
|
|
2419
|
+
} : void 0
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
2422
|
+
let committed;
|
|
2423
|
+
try {
|
|
2424
|
+
committed = await store2().commit(
|
|
2425
|
+
stream,
|
|
2426
|
+
emitted,
|
|
2427
|
+
meta,
|
|
2428
|
+
// Reactions skip optimistic concurrency: they always append against the
|
|
2429
|
+
// current head. Stream leasing already serializes concurrent reactions,
|
|
2430
|
+
// and forcing version checks here would turn ordinary catch-up into
|
|
2431
|
+
// spurious retries.
|
|
2432
|
+
reactingTo ? void 0 : expected
|
|
2366
2433
|
);
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
if (error instanceof ConcurrencyError) {
|
|
2436
|
+
await cache2().invalidate(stream);
|
|
2437
|
+
}
|
|
2438
|
+
throw error;
|
|
2439
|
+
}
|
|
2440
|
+
let { state: state2, patches } = snapshot;
|
|
2441
|
+
const snapshots = committed.map((event) => {
|
|
2442
|
+
const p = me.patch[event.name](event, state2);
|
|
2443
|
+
state2 = (0, import_act_patch.patch)(state2, p);
|
|
2444
|
+
patches++;
|
|
2445
|
+
return {
|
|
2446
|
+
event,
|
|
2447
|
+
state: state2,
|
|
2448
|
+
version: event.version,
|
|
2449
|
+
patches,
|
|
2450
|
+
snaps: snapshot.snaps,
|
|
2451
|
+
patch: p,
|
|
2452
|
+
cache_hit: snapshot.cache_hit,
|
|
2453
|
+
replayed: snapshot.replayed
|
|
2454
|
+
};
|
|
2455
|
+
});
|
|
2456
|
+
const last = snapshots.at(-1);
|
|
2457
|
+
const snapped = me.snap?.(last);
|
|
2458
|
+
cache2().set(stream, {
|
|
2459
|
+
state: last.state,
|
|
2460
|
+
version: last.event.version,
|
|
2461
|
+
event_id: last.event.id,
|
|
2462
|
+
patches: snapped ? 0 : last.patches,
|
|
2463
|
+
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
2464
|
+
}).catch((err) => log().error(err));
|
|
2465
|
+
if (snapped) void snap(last);
|
|
2466
|
+
return snapshots;
|
|
2467
|
+
} catch (error) {
|
|
2468
|
+
if (!(error instanceof ConcurrencyError)) throw error;
|
|
2469
|
+
if (attempt >= maxRetries) throw error;
|
|
2470
|
+
if (opts?.backoff) {
|
|
2471
|
+
const delayMs = computeBackoffDelay(attempt, opts.backoff);
|
|
2472
|
+
if (delayMs > 0) await sleep(delayMs);
|
|
2367
2473
|
}
|
|
2368
2474
|
}
|
|
2369
2475
|
}
|
|
2370
|
-
const emitted = tuples.map(([name, data]) => ({
|
|
2371
|
-
name,
|
|
2372
|
-
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
2373
|
-
}));
|
|
2374
|
-
const meta = {
|
|
2375
|
-
correlation: reactingTo?.meta.correlation || correlator({
|
|
2376
|
-
action: action2,
|
|
2377
|
-
state: me.name,
|
|
2378
|
-
stream,
|
|
2379
|
-
actor: target.actor
|
|
2380
|
-
}),
|
|
2381
|
-
causation: {
|
|
2382
|
-
action: {
|
|
2383
|
-
name: action2,
|
|
2384
|
-
...target
|
|
2385
|
-
// payload intentionally omitted: it can be large or contain PII,
|
|
2386
|
-
// and callers correlate via the correlation id when they need it.
|
|
2387
|
-
},
|
|
2388
|
-
event: reactingTo ? {
|
|
2389
|
-
id: reactingTo.id,
|
|
2390
|
-
name: reactingTo.name,
|
|
2391
|
-
stream: reactingTo.stream
|
|
2392
|
-
} : void 0
|
|
2393
|
-
}
|
|
2394
|
-
};
|
|
2395
|
-
let committed;
|
|
2396
|
-
try {
|
|
2397
|
-
committed = await store2().commit(
|
|
2398
|
-
stream,
|
|
2399
|
-
emitted,
|
|
2400
|
-
meta,
|
|
2401
|
-
// Reactions skip optimistic concurrency: they always append against the
|
|
2402
|
-
// current head. Stream leasing already serializes concurrent reactions,
|
|
2403
|
-
// and forcing version checks here would turn ordinary catch-up into
|
|
2404
|
-
// spurious retries.
|
|
2405
|
-
reactingTo ? void 0 : expected
|
|
2406
|
-
);
|
|
2407
|
-
} catch (error) {
|
|
2408
|
-
if (error instanceof ConcurrencyError) {
|
|
2409
|
-
await cache2().invalidate(stream);
|
|
2410
|
-
}
|
|
2411
|
-
throw error;
|
|
2412
|
-
}
|
|
2413
|
-
let { state: state2, patches } = snapshot;
|
|
2414
|
-
const snapshots = committed.map((event) => {
|
|
2415
|
-
const p = me.patch[event.name](event, state2);
|
|
2416
|
-
state2 = (0, import_act_patch.patch)(state2, p);
|
|
2417
|
-
patches++;
|
|
2418
|
-
return {
|
|
2419
|
-
event,
|
|
2420
|
-
state: state2,
|
|
2421
|
-
version: event.version,
|
|
2422
|
-
patches,
|
|
2423
|
-
snaps: snapshot.snaps,
|
|
2424
|
-
patch: p,
|
|
2425
|
-
cache_hit: snapshot.cache_hit,
|
|
2426
|
-
replayed: snapshot.replayed
|
|
2427
|
-
};
|
|
2428
|
-
});
|
|
2429
|
-
const last = snapshots.at(-1);
|
|
2430
|
-
const snapped = me.snap?.(last);
|
|
2431
|
-
cache2().set(stream, {
|
|
2432
|
-
state: last.state,
|
|
2433
|
-
version: last.event.version,
|
|
2434
|
-
event_id: last.event.id,
|
|
2435
|
-
patches: snapped ? 0 : last.patches,
|
|
2436
|
-
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
2437
|
-
}).catch((err) => log().error(err));
|
|
2438
|
-
if (snapped) void snap(last);
|
|
2439
|
-
return snapshots;
|
|
2440
2476
|
}
|
|
2441
2477
|
|
|
2442
2478
|
// src/internal/tracing.ts
|
|
@@ -2970,27 +3006,6 @@ var _this_ = ({ stream }) => ({
|
|
|
2970
3006
|
target: stream
|
|
2971
3007
|
});
|
|
2972
3008
|
|
|
2973
|
-
// src/internal/backoff.ts
|
|
2974
|
-
function computeBackoffDelay(retry, opts) {
|
|
2975
|
-
if (!opts || opts.baseMs <= 0) return 0;
|
|
2976
|
-
const r = Math.max(0, retry);
|
|
2977
|
-
let delay;
|
|
2978
|
-
switch (opts.strategy) {
|
|
2979
|
-
case "fixed":
|
|
2980
|
-
delay = opts.baseMs;
|
|
2981
|
-
break;
|
|
2982
|
-
case "linear":
|
|
2983
|
-
delay = opts.baseMs * (r + 1);
|
|
2984
|
-
break;
|
|
2985
|
-
case "exponential":
|
|
2986
|
-
delay = opts.baseMs * 2 ** r;
|
|
2987
|
-
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
2988
|
-
break;
|
|
2989
|
-
}
|
|
2990
|
-
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
2991
|
-
return Math.max(0, Math.floor(delay));
|
|
2992
|
-
}
|
|
2993
|
-
|
|
2994
3009
|
// src/internal/reactions.ts
|
|
2995
3010
|
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
2996
3011
|
if (!error) return { lease, handled, acked_at: at };
|
|
@@ -4522,7 +4537,7 @@ function state(entry) {
|
|
|
4522
4537
|
function action_builder(state2) {
|
|
4523
4538
|
const internal = state2;
|
|
4524
4539
|
const builder = {
|
|
4525
|
-
on(entry) {
|
|
4540
|
+
on(entry, options) {
|
|
4526
4541
|
const keys = Object.keys(entry);
|
|
4527
4542
|
if (keys.length !== 1) throw new Error(".on() requires exactly one key");
|
|
4528
4543
|
const action2 = keys[0];
|
|
@@ -4530,6 +4545,10 @@ function action_builder(state2) {
|
|
|
4530
4545
|
if (action2 in internal.actions)
|
|
4531
4546
|
throw new Error(`Duplicate action "${action2}"`);
|
|
4532
4547
|
internal.actions[action2] = schema;
|
|
4548
|
+
if (options) {
|
|
4549
|
+
internal.options ??= {};
|
|
4550
|
+
internal.options[action2] = options;
|
|
4551
|
+
}
|
|
4533
4552
|
function given(rules) {
|
|
4534
4553
|
internal.given ??= {};
|
|
4535
4554
|
internal.given[action2] = rules;
|