@rotorsoft/act 1.6.0 → 1.7.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 +59 -0
- package/dist/@types/types/action.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.js
CHANGED
|
@@ -873,6 +873,29 @@ var subscribe = (streams) => store().subscribe(streams);
|
|
|
873
873
|
|
|
874
874
|
// src/internal/event-sourcing.ts
|
|
875
875
|
import { patch } from "@rotorsoft/act-patch";
|
|
876
|
+
|
|
877
|
+
// src/internal/backoff.ts
|
|
878
|
+
function computeBackoffDelay(retry, opts) {
|
|
879
|
+
if (!opts || opts.baseMs <= 0) return 0;
|
|
880
|
+
const r = Math.max(0, retry);
|
|
881
|
+
let delay;
|
|
882
|
+
switch (opts.strategy) {
|
|
883
|
+
case "fixed":
|
|
884
|
+
delay = opts.baseMs;
|
|
885
|
+
break;
|
|
886
|
+
case "linear":
|
|
887
|
+
delay = opts.baseMs * (r + 1);
|
|
888
|
+
break;
|
|
889
|
+
case "exponential":
|
|
890
|
+
delay = opts.baseMs * 2 ** r;
|
|
891
|
+
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
892
|
+
break;
|
|
893
|
+
}
|
|
894
|
+
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
895
|
+
return Math.max(0, Math.floor(delay));
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// src/internal/event-sourcing.ts
|
|
876
899
|
var DEFAULT_BATCH = 500;
|
|
877
900
|
async function snap(snapshot) {
|
|
878
901
|
try {
|
|
@@ -1080,113 +1103,126 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1080
1103
|
const { stream, expectedVersion, actor } = target;
|
|
1081
1104
|
if (!stream) throw new Error("Missing target stream");
|
|
1082
1105
|
const validated = skipValidation ? payload : validate(action2, payload, me.actions[action2]);
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
warned.
|
|
1114
|
-
|
|
1115
|
-
|
|
1106
|
+
const opts = me.options?.[action2];
|
|
1107
|
+
const maxRetries = opts?.maxRetries ?? 0;
|
|
1108
|
+
for (let attempt = 0; ; attempt++) {
|
|
1109
|
+
try {
|
|
1110
|
+
const snapshot = await load(me, stream);
|
|
1111
|
+
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
1112
|
+
throw new StreamClosedError(stream);
|
|
1113
|
+
const expected = expectedVersion ?? snapshot.event?.version;
|
|
1114
|
+
if (me.given) {
|
|
1115
|
+
const invariants = me.given[action2] || [];
|
|
1116
|
+
invariants.forEach(({ valid, description }) => {
|
|
1117
|
+
if (!valid(snapshot.state, actor))
|
|
1118
|
+
throw new InvariantError(
|
|
1119
|
+
action2,
|
|
1120
|
+
validated,
|
|
1121
|
+
target,
|
|
1122
|
+
snapshot,
|
|
1123
|
+
description
|
|
1124
|
+
);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
const result = me.on[action2](validated, snapshot, target);
|
|
1128
|
+
if (!result) return [snapshot];
|
|
1129
|
+
if (Array.isArray(result) && result.length === 0) {
|
|
1130
|
+
return [snapshot];
|
|
1131
|
+
}
|
|
1132
|
+
const tuples = Array.isArray(result[0]) ? result : [result];
|
|
1133
|
+
const deprecated = me._deprecated;
|
|
1134
|
+
if (deprecated && deprecated.size > 0) {
|
|
1135
|
+
const me_ = me;
|
|
1136
|
+
const warned = me_._warned ?? (me_._warned = /* @__PURE__ */ new Set());
|
|
1137
|
+
for (const [name] of tuples) {
|
|
1138
|
+
const evt = name;
|
|
1139
|
+
if (deprecated.has(evt) && !warned.has(evt)) {
|
|
1140
|
+
warned.add(evt);
|
|
1141
|
+
log().warn(
|
|
1142
|
+
`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)`
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const emitted = tuples.map(([name, data]) => ({
|
|
1148
|
+
name,
|
|
1149
|
+
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
1150
|
+
}));
|
|
1151
|
+
const meta = {
|
|
1152
|
+
correlation: reactingTo?.meta.correlation || correlator({
|
|
1153
|
+
action: action2,
|
|
1154
|
+
state: me.name,
|
|
1155
|
+
stream,
|
|
1156
|
+
actor: target.actor
|
|
1157
|
+
}),
|
|
1158
|
+
causation: {
|
|
1159
|
+
action: {
|
|
1160
|
+
name: action2,
|
|
1161
|
+
...target
|
|
1162
|
+
// payload intentionally omitted: it can be large or contain PII,
|
|
1163
|
+
// and callers correlate via the correlation id when they need it.
|
|
1164
|
+
},
|
|
1165
|
+
event: reactingTo ? {
|
|
1166
|
+
id: reactingTo.id,
|
|
1167
|
+
name: reactingTo.name,
|
|
1168
|
+
stream: reactingTo.stream
|
|
1169
|
+
} : void 0
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
let committed;
|
|
1173
|
+
try {
|
|
1174
|
+
committed = await store().commit(
|
|
1175
|
+
stream,
|
|
1176
|
+
emitted,
|
|
1177
|
+
meta,
|
|
1178
|
+
// Reactions skip optimistic concurrency: they always append against the
|
|
1179
|
+
// current head. Stream leasing already serializes concurrent reactions,
|
|
1180
|
+
// and forcing version checks here would turn ordinary catch-up into
|
|
1181
|
+
// spurious retries.
|
|
1182
|
+
reactingTo ? void 0 : expected
|
|
1116
1183
|
);
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
if (error instanceof ConcurrencyError) {
|
|
1186
|
+
await cache().invalidate(stream);
|
|
1187
|
+
}
|
|
1188
|
+
throw error;
|
|
1189
|
+
}
|
|
1190
|
+
let { state: state2, patches } = snapshot;
|
|
1191
|
+
const snapshots = committed.map((event) => {
|
|
1192
|
+
const p = me.patch[event.name](event, state2);
|
|
1193
|
+
state2 = patch(state2, p);
|
|
1194
|
+
patches++;
|
|
1195
|
+
return {
|
|
1196
|
+
event,
|
|
1197
|
+
state: state2,
|
|
1198
|
+
version: event.version,
|
|
1199
|
+
patches,
|
|
1200
|
+
snaps: snapshot.snaps,
|
|
1201
|
+
patch: p,
|
|
1202
|
+
cache_hit: snapshot.cache_hit,
|
|
1203
|
+
replayed: snapshot.replayed
|
|
1204
|
+
};
|
|
1205
|
+
});
|
|
1206
|
+
const last = snapshots.at(-1);
|
|
1207
|
+
const snapped = me.snap?.(last);
|
|
1208
|
+
cache().set(stream, {
|
|
1209
|
+
state: last.state,
|
|
1210
|
+
version: last.event.version,
|
|
1211
|
+
event_id: last.event.id,
|
|
1212
|
+
patches: snapped ? 0 : last.patches,
|
|
1213
|
+
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
1214
|
+
}).catch((err) => log().error(err));
|
|
1215
|
+
if (snapped) void snap(last);
|
|
1216
|
+
return snapshots;
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
if (!(error instanceof ConcurrencyError)) throw error;
|
|
1219
|
+
if (attempt >= maxRetries) throw error;
|
|
1220
|
+
if (opts?.backoff) {
|
|
1221
|
+
const delayMs = computeBackoffDelay(attempt, opts.backoff);
|
|
1222
|
+
if (delayMs > 0) await sleep(delayMs);
|
|
1117
1223
|
}
|
|
1118
1224
|
}
|
|
1119
1225
|
}
|
|
1120
|
-
const emitted = tuples.map(([name, data]) => ({
|
|
1121
|
-
name,
|
|
1122
|
-
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
1123
|
-
}));
|
|
1124
|
-
const meta = {
|
|
1125
|
-
correlation: reactingTo?.meta.correlation || correlator({
|
|
1126
|
-
action: action2,
|
|
1127
|
-
state: me.name,
|
|
1128
|
-
stream,
|
|
1129
|
-
actor: target.actor
|
|
1130
|
-
}),
|
|
1131
|
-
causation: {
|
|
1132
|
-
action: {
|
|
1133
|
-
name: action2,
|
|
1134
|
-
...target
|
|
1135
|
-
// payload intentionally omitted: it can be large or contain PII,
|
|
1136
|
-
// and callers correlate via the correlation id when they need it.
|
|
1137
|
-
},
|
|
1138
|
-
event: reactingTo ? {
|
|
1139
|
-
id: reactingTo.id,
|
|
1140
|
-
name: reactingTo.name,
|
|
1141
|
-
stream: reactingTo.stream
|
|
1142
|
-
} : void 0
|
|
1143
|
-
}
|
|
1144
|
-
};
|
|
1145
|
-
let committed;
|
|
1146
|
-
try {
|
|
1147
|
-
committed = await store().commit(
|
|
1148
|
-
stream,
|
|
1149
|
-
emitted,
|
|
1150
|
-
meta,
|
|
1151
|
-
// Reactions skip optimistic concurrency: they always append against the
|
|
1152
|
-
// current head. Stream leasing already serializes concurrent reactions,
|
|
1153
|
-
// and forcing version checks here would turn ordinary catch-up into
|
|
1154
|
-
// spurious retries.
|
|
1155
|
-
reactingTo ? void 0 : expected
|
|
1156
|
-
);
|
|
1157
|
-
} catch (error) {
|
|
1158
|
-
if (error instanceof ConcurrencyError) {
|
|
1159
|
-
await cache().invalidate(stream);
|
|
1160
|
-
}
|
|
1161
|
-
throw error;
|
|
1162
|
-
}
|
|
1163
|
-
let { state: state2, patches } = snapshot;
|
|
1164
|
-
const snapshots = committed.map((event) => {
|
|
1165
|
-
const p = me.patch[event.name](event, state2);
|
|
1166
|
-
state2 = patch(state2, p);
|
|
1167
|
-
patches++;
|
|
1168
|
-
return {
|
|
1169
|
-
event,
|
|
1170
|
-
state: state2,
|
|
1171
|
-
version: event.version,
|
|
1172
|
-
patches,
|
|
1173
|
-
snaps: snapshot.snaps,
|
|
1174
|
-
patch: p,
|
|
1175
|
-
cache_hit: snapshot.cache_hit,
|
|
1176
|
-
replayed: snapshot.replayed
|
|
1177
|
-
};
|
|
1178
|
-
});
|
|
1179
|
-
const last = snapshots.at(-1);
|
|
1180
|
-
const snapped = me.snap?.(last);
|
|
1181
|
-
cache().set(stream, {
|
|
1182
|
-
state: last.state,
|
|
1183
|
-
version: last.event.version,
|
|
1184
|
-
event_id: last.event.id,
|
|
1185
|
-
patches: snapped ? 0 : last.patches,
|
|
1186
|
-
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
1187
|
-
}).catch((err) => log().error(err));
|
|
1188
|
-
if (snapped) void snap(last);
|
|
1189
|
-
return snapshots;
|
|
1190
1226
|
}
|
|
1191
1227
|
|
|
1192
1228
|
// src/internal/tracing.ts
|
|
@@ -1720,27 +1756,6 @@ var _this_ = ({ stream }) => ({
|
|
|
1720
1756
|
target: stream
|
|
1721
1757
|
});
|
|
1722
1758
|
|
|
1723
|
-
// src/internal/backoff.ts
|
|
1724
|
-
function computeBackoffDelay(retry, opts) {
|
|
1725
|
-
if (!opts || opts.baseMs <= 0) return 0;
|
|
1726
|
-
const r = Math.max(0, retry);
|
|
1727
|
-
let delay;
|
|
1728
|
-
switch (opts.strategy) {
|
|
1729
|
-
case "fixed":
|
|
1730
|
-
delay = opts.baseMs;
|
|
1731
|
-
break;
|
|
1732
|
-
case "linear":
|
|
1733
|
-
delay = opts.baseMs * (r + 1);
|
|
1734
|
-
break;
|
|
1735
|
-
case "exponential":
|
|
1736
|
-
delay = opts.baseMs * 2 ** r;
|
|
1737
|
-
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
1738
|
-
break;
|
|
1739
|
-
}
|
|
1740
|
-
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
1741
|
-
return Math.max(0, Math.floor(delay));
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
1759
|
// src/internal/reactions.ts
|
|
1745
1760
|
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
1746
1761
|
if (!error) return { lease, handled, acked_at: at };
|
|
@@ -3272,7 +3287,7 @@ function state(entry) {
|
|
|
3272
3287
|
function action_builder(state2) {
|
|
3273
3288
|
const internal = state2;
|
|
3274
3289
|
const builder = {
|
|
3275
|
-
on(entry) {
|
|
3290
|
+
on(entry, options) {
|
|
3276
3291
|
const keys = Object.keys(entry);
|
|
3277
3292
|
if (keys.length !== 1) throw new Error(".on() requires exactly one key");
|
|
3278
3293
|
const action2 = keys[0];
|
|
@@ -3280,6 +3295,10 @@ function action_builder(state2) {
|
|
|
3280
3295
|
if (action2 in internal.actions)
|
|
3281
3296
|
throw new Error(`Duplicate action "${action2}"`);
|
|
3282
3297
|
internal.actions[action2] = schema;
|
|
3298
|
+
if (options) {
|
|
3299
|
+
internal.options ??= {};
|
|
3300
|
+
internal.options[action2] = options;
|
|
3301
|
+
}
|
|
3283
3302
|
function given(rules) {
|
|
3284
3303
|
internal.given ??= {};
|
|
3285
3304
|
internal.given[action2] = rules;
|