@rotorsoft/act 1.8.0 → 1.10.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/act.d.ts +34 -2
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +12 -0
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/builders/state-builder.d.ts +31 -1
- package/dist/@types/builders/state-builder.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +7 -2
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +1 -0
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/internal/reactions.d.ts +5 -4
- package/dist/@types/internal/reactions.d.ts.map +1 -1
- package/dist/@types/internal/sensitive.d.ts +147 -0
- package/dist/@types/internal/sensitive.d.ts.map +1 -0
- package/dist/@types/internal/tracing.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +57 -0
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/registry.d.ts +9 -1
- package/dist/@types/types/registry.d.ts.map +1 -1
- package/dist/@types/types/schemas.d.ts +36 -0
- package/dist/@types/types/schemas.d.ts.map +1 -1
- package/dist/{chunk-I4L224TZ.js → chunk-3ZTFNAY7.js} +46 -6
- package/dist/chunk-3ZTFNAY7.js.map +1 -0
- package/dist/chunk-XSBT63QX.js +267 -0
- package/dist/chunk-XSBT63QX.js.map +1 -0
- package/dist/index.cjs +335 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +155 -32
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +106 -60
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +11 -11
- package/dist/test/index.js.map +1 -1
- package/dist/types/index.cjs +52 -34
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +9 -3
- package/package.json +2 -2
- package/dist/chunk-I4L224TZ.js.map +0 -1
- package/dist/chunk-PMAZTOSO.js +0 -164
- package/dist/chunk-PMAZTOSO.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
sleep,
|
|
20
20
|
store,
|
|
21
21
|
validate
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-3ZTFNAY7.js";
|
|
23
23
|
import {
|
|
24
24
|
ActorSchema,
|
|
25
25
|
CausationEventSchema,
|
|
@@ -32,11 +32,19 @@ import {
|
|
|
32
32
|
LogLevels,
|
|
33
33
|
NonRetryableError,
|
|
34
34
|
QuerySchema,
|
|
35
|
+
REDACTED,
|
|
36
|
+
SHREDDED,
|
|
35
37
|
StreamClosedError,
|
|
36
38
|
TargetSchema,
|
|
37
39
|
ValidationError,
|
|
38
|
-
ZodEmpty
|
|
39
|
-
|
|
40
|
+
ZodEmpty,
|
|
41
|
+
pii_fields,
|
|
42
|
+
pii_gate,
|
|
43
|
+
pii_merge,
|
|
44
|
+
pii_split,
|
|
45
|
+
pii_strip,
|
|
46
|
+
sensitive
|
|
47
|
+
} from "./chunk-XSBT63QX.js";
|
|
40
48
|
import "./chunk-5WRI5ZAA.js";
|
|
41
49
|
|
|
42
50
|
// src/signals.ts
|
|
@@ -1044,7 +1052,7 @@ async function scan(source, opts = {}, callback) {
|
|
|
1044
1052
|
}
|
|
1045
1053
|
};
|
|
1046
1054
|
}
|
|
1047
|
-
async function load(me, stream, callback, asOf) {
|
|
1055
|
+
async function load(me, stream, callback, asOf, actor) {
|
|
1048
1056
|
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
1049
1057
|
const cached = timeTravel ? void 0 : await cache().get(stream);
|
|
1050
1058
|
const cache_hit = !!cached;
|
|
@@ -1056,15 +1064,16 @@ async function load(me, stream, callback, asOf) {
|
|
|
1056
1064
|
let event;
|
|
1057
1065
|
await store().query(
|
|
1058
1066
|
(e) => {
|
|
1059
|
-
event = e;
|
|
1060
1067
|
version = e.version;
|
|
1068
|
+
const typed = e;
|
|
1069
|
+
event = me.view(typed, actor);
|
|
1061
1070
|
if (e.name === SNAP_EVENT) {
|
|
1062
1071
|
state2 = e.data;
|
|
1063
1072
|
snaps++;
|
|
1064
1073
|
patches = 0;
|
|
1065
1074
|
replayed++;
|
|
1066
1075
|
} else if (me.patch[e.name]) {
|
|
1067
|
-
state2 = patch(state2, me.patch[e.name](
|
|
1076
|
+
state2 = patch(state2, me.patch[e.name](typed, state2));
|
|
1068
1077
|
patches++;
|
|
1069
1078
|
replayed++;
|
|
1070
1079
|
} else if (e.name !== TOMBSTONE_EVENT) {
|
|
@@ -1107,7 +1116,13 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1107
1116
|
const maxRetries = opts?.maxRetries ?? 0;
|
|
1108
1117
|
for (let attempt = 0; ; attempt++) {
|
|
1109
1118
|
try {
|
|
1110
|
-
const snapshot = await load(
|
|
1119
|
+
const snapshot = await load(
|
|
1120
|
+
me,
|
|
1121
|
+
stream,
|
|
1122
|
+
void 0,
|
|
1123
|
+
void 0,
|
|
1124
|
+
target.actor
|
|
1125
|
+
);
|
|
1111
1126
|
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
1112
1127
|
throw new StreamClosedError(stream);
|
|
1113
1128
|
const expected = expectedVersion ?? snapshot.event?.version;
|
|
@@ -1144,10 +1159,10 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1144
1159
|
}
|
|
1145
1160
|
}
|
|
1146
1161
|
}
|
|
1147
|
-
const emitted = tuples.map(([name, data]) =>
|
|
1148
|
-
name,
|
|
1149
|
-
|
|
1150
|
-
})
|
|
1162
|
+
const emitted = tuples.map(([name, data]) => {
|
|
1163
|
+
const validated2 = skipValidation ? data : validate(name, data, me.events[name]);
|
|
1164
|
+
return me.message({ name, data: validated2 });
|
|
1165
|
+
});
|
|
1151
1166
|
const meta = {
|
|
1152
1167
|
correlation: reactingTo?.meta.correlation || correlator({
|
|
1153
1168
|
action: action2,
|
|
@@ -1159,8 +1174,8 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1159
1174
|
action: {
|
|
1160
1175
|
name: action2,
|
|
1161
1176
|
...target
|
|
1162
|
-
// payload intentionally omitted
|
|
1163
|
-
//
|
|
1177
|
+
// payload intentionally omitted from causation metadata —
|
|
1178
|
+
// callers correlate via the correlation id when they need it.
|
|
1164
1179
|
},
|
|
1165
1180
|
event: reactingTo ? {
|
|
1166
1181
|
id: reactingTo.id,
|
|
@@ -1193,7 +1208,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1193
1208
|
state2 = patch(state2, p);
|
|
1194
1209
|
patches++;
|
|
1195
1210
|
return {
|
|
1196
|
-
event,
|
|
1211
|
+
event: me.view(event, target.actor),
|
|
1197
1212
|
state: state2,
|
|
1198
1213
|
version: event.version,
|
|
1199
1214
|
patches,
|
|
@@ -1278,7 +1293,7 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
1278
1293
|
return result;
|
|
1279
1294
|
});
|
|
1280
1295
|
function buildEs(logger, correlator = defaultCorrelator) {
|
|
1281
|
-
const
|
|
1296
|
+
const bound_action = (me, actionName, target, payload, reactingTo, skipValidation = false) => action(
|
|
1282
1297
|
me,
|
|
1283
1298
|
actionName,
|
|
1284
1299
|
target,
|
|
@@ -1291,7 +1306,7 @@ function buildEs(logger, correlator = defaultCorrelator) {
|
|
|
1291
1306
|
return {
|
|
1292
1307
|
snap,
|
|
1293
1308
|
load,
|
|
1294
|
-
action:
|
|
1309
|
+
action: bound_action,
|
|
1295
1310
|
tombstone
|
|
1296
1311
|
};
|
|
1297
1312
|
}
|
|
@@ -1321,7 +1336,7 @@ function buildEs(logger, correlator = defaultCorrelator) {
|
|
|
1321
1336
|
);
|
|
1322
1337
|
}),
|
|
1323
1338
|
action: traced(
|
|
1324
|
-
|
|
1339
|
+
bound_action,
|
|
1325
1340
|
(snapshots, _me, _action, target) => {
|
|
1326
1341
|
const committed = snapshots.filter((s) => s.event);
|
|
1327
1342
|
if (committed.length) {
|
|
@@ -1778,7 +1793,14 @@ function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
|
1778
1793
|
};
|
|
1779
1794
|
}
|
|
1780
1795
|
function buildHandle(deps) {
|
|
1781
|
-
const {
|
|
1796
|
+
const {
|
|
1797
|
+
logger,
|
|
1798
|
+
bound_do,
|
|
1799
|
+
bound_load,
|
|
1800
|
+
bound_query,
|
|
1801
|
+
bound_query_array,
|
|
1802
|
+
bound_forget
|
|
1803
|
+
} = deps;
|
|
1782
1804
|
return async (lease, payloads) => {
|
|
1783
1805
|
if (payloads.length === 0) return { lease, handled: 0, acked_at: lease.at };
|
|
1784
1806
|
const stream = lease.stream;
|
|
@@ -1787,14 +1809,15 @@ function buildHandle(deps) {
|
|
|
1787
1809
|
if (lease.retry > 0)
|
|
1788
1810
|
logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
1789
1811
|
const scopedApp = {
|
|
1790
|
-
do:
|
|
1791
|
-
load:
|
|
1792
|
-
query:
|
|
1793
|
-
query_array:
|
|
1812
|
+
do: bound_do,
|
|
1813
|
+
load: bound_load,
|
|
1814
|
+
query: bound_query,
|
|
1815
|
+
query_array: bound_query_array,
|
|
1816
|
+
forget: bound_forget
|
|
1794
1817
|
};
|
|
1795
1818
|
for (const payload of payloads) {
|
|
1796
1819
|
const { event, handler } = payload;
|
|
1797
|
-
scopedApp.do = (action2, target, actionPayload, reactingTo, skipValidation) =>
|
|
1820
|
+
scopedApp.do = (action2, target, actionPayload, reactingTo, skipValidation) => bound_do(
|
|
1798
1821
|
action2,
|
|
1799
1822
|
target,
|
|
1800
1823
|
actionPayload,
|
|
@@ -1823,7 +1846,9 @@ function buildHandle(deps) {
|
|
|
1823
1846
|
function buildHandleBatch(logger) {
|
|
1824
1847
|
return async (lease, payloads, batchHandler) => {
|
|
1825
1848
|
const stream = lease.stream;
|
|
1826
|
-
const events = payloads.map(
|
|
1849
|
+
const events = payloads.map(
|
|
1850
|
+
(p) => p.event
|
|
1851
|
+
);
|
|
1827
1852
|
const options = payloads[0].options;
|
|
1828
1853
|
if (lease.retry > 0)
|
|
1829
1854
|
logger.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
|
|
@@ -2006,6 +2031,7 @@ var Act = class {
|
|
|
2006
2031
|
_bound_load = this.load.bind(this);
|
|
2007
2032
|
_bound_query = this.query.bind(this);
|
|
2008
2033
|
_bound_query_array = this.query_array.bind(this);
|
|
2034
|
+
_bound_forget = this.forget.bind(this);
|
|
2009
2035
|
/** Reaction dispatchers built once and handed to runDrainCycle each cycle. */
|
|
2010
2036
|
_handle;
|
|
2011
2037
|
_handle_batch;
|
|
@@ -2051,10 +2077,11 @@ var Act = class {
|
|
|
2051
2077
|
this._cd = buildDrain(this._logger);
|
|
2052
2078
|
this._handle = buildHandle({
|
|
2053
2079
|
logger: this._logger,
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2080
|
+
bound_do: this._bound_do,
|
|
2081
|
+
bound_load: this._bound_load,
|
|
2082
|
+
bound_query: this._bound_query,
|
|
2083
|
+
bound_query_array: this._bound_query_array,
|
|
2084
|
+
bound_forget: this._bound_forget
|
|
2058
2085
|
});
|
|
2059
2086
|
this._handle_batch = buildHandleBatch(this._logger);
|
|
2060
2087
|
const {
|
|
@@ -2286,7 +2313,7 @@ var Act = class {
|
|
|
2286
2313
|
return snapshots;
|
|
2287
2314
|
});
|
|
2288
2315
|
}
|
|
2289
|
-
async load(stateOrName, stream, callback, asOf) {
|
|
2316
|
+
async load(stateOrName, stream, callback, asOf, actor) {
|
|
2290
2317
|
return this._scoped(async () => {
|
|
2291
2318
|
let merged;
|
|
2292
2319
|
if (typeof stateOrName === "string") {
|
|
@@ -2296,7 +2323,7 @@ var Act = class {
|
|
|
2296
2323
|
} else {
|
|
2297
2324
|
merged = this._states.get(stateOrName.name) || stateOrName;
|
|
2298
2325
|
}
|
|
2299
|
-
return await this._es.load(merged, stream, callback, asOf);
|
|
2326
|
+
return await this._es.load(merged, stream, callback, asOf, actor);
|
|
2300
2327
|
});
|
|
2301
2328
|
}
|
|
2302
2329
|
/**
|
|
@@ -2390,6 +2417,34 @@ var Act = class {
|
|
|
2390
2417
|
return events;
|
|
2391
2418
|
});
|
|
2392
2419
|
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Wipe the sensitive-data payload for every event on the stream — see
|
|
2422
|
+
* {@link IAct.forget}. Application-level half of #566.
|
|
2423
|
+
*
|
|
2424
|
+
* Throws on adapters without `Store.forget_pii`, invalidates the cache
|
|
2425
|
+
* entry for the stream, emits the `forgotten` lifecycle event with the
|
|
2426
|
+
* row count. Idempotent: a second call returns `{eventCount: 0}` and
|
|
2427
|
+
* does NOT re-emit.
|
|
2428
|
+
*
|
|
2429
|
+
* @param stream - Target stream.
|
|
2430
|
+
* @returns `{eventCount}` — number of events whose PII column was wiped.
|
|
2431
|
+
*/
|
|
2432
|
+
async forget(stream) {
|
|
2433
|
+
return this._scoped(async () => {
|
|
2434
|
+
const s = store();
|
|
2435
|
+
if (!s.forget_pii) {
|
|
2436
|
+
throw new Error(
|
|
2437
|
+
`Store does not implement forget_pii \u2014 adapter cannot comply with sensitive-data erasure. Use an adapter that declares pii_isolation: true (e.g. @rotorsoft/act on the in-memory store).`
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
2440
|
+
const eventCount = await s.forget_pii(stream);
|
|
2441
|
+
await cache().invalidate(stream);
|
|
2442
|
+
if (eventCount > 0) {
|
|
2443
|
+
this.emit("forgotten", { stream, at: /* @__PURE__ */ new Date(), eventCount });
|
|
2444
|
+
}
|
|
2445
|
+
return { eventCount };
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2393
2448
|
/**
|
|
2394
2449
|
* Processes pending reactions by draining uncommitted events from the event store.
|
|
2395
2450
|
*
|
|
@@ -2990,9 +3045,13 @@ function validateLaneReferences(registry, lanes) {
|
|
|
2990
3045
|
}
|
|
2991
3046
|
function act() {
|
|
2992
3047
|
const states = /* @__PURE__ */ new Map();
|
|
3048
|
+
const _sf = /* @__PURE__ */ new Map();
|
|
3049
|
+
const _dp = /* @__PURE__ */ new Map();
|
|
2993
3050
|
const registry = {
|
|
2994
3051
|
actions: {},
|
|
2995
|
-
events: {}
|
|
3052
|
+
events: {},
|
|
3053
|
+
sensitive_fields: (eventName) => _sf.get(eventName) ?? [],
|
|
3054
|
+
disclosure_predicate: (stateName) => _dp.get(stateName) ?? null
|
|
2996
3055
|
};
|
|
2997
3056
|
const pendingProjections = [];
|
|
2998
3057
|
const batchHandlers = /* @__PURE__ */ new Map();
|
|
@@ -3103,6 +3162,58 @@ function act() {
|
|
|
3103
3162
|
}
|
|
3104
3163
|
finalizeDeprecations();
|
|
3105
3164
|
validateLaneReferences(registry, lanes);
|
|
3165
|
+
for (const [eventName, reg] of Object.entries(
|
|
3166
|
+
registry.events
|
|
3167
|
+
)) {
|
|
3168
|
+
const fields = pii_fields(reg.schema);
|
|
3169
|
+
if (fields.length === 0) continue;
|
|
3170
|
+
_sf.set(eventName, fields);
|
|
3171
|
+
for (const [name, reaction] of reg.reactions) {
|
|
3172
|
+
const inner = reaction.handler;
|
|
3173
|
+
const wrapped = (event, stream, app) => inner(pii_strip(event, fields), stream, app);
|
|
3174
|
+
Object.defineProperty(wrapped, "name", { value: inner.name });
|
|
3175
|
+
reaction.handler = wrapped;
|
|
3176
|
+
reg.reactions.set(name, reaction);
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
for (const state2 of states.values()) {
|
|
3180
|
+
if (state2.disclose) _dp.set(state2.name, state2.disclose);
|
|
3181
|
+
const fields_by_event = /* @__PURE__ */ new Map();
|
|
3182
|
+
for (const eventName of Object.keys(state2.events)) {
|
|
3183
|
+
const fields = _sf.get(eventName);
|
|
3184
|
+
if (fields) fields_by_event.set(eventName, fields);
|
|
3185
|
+
}
|
|
3186
|
+
if (fields_by_event.size === 0) continue;
|
|
3187
|
+
if (state2.snap) {
|
|
3188
|
+
const offending = [...fields_by_event.keys()];
|
|
3189
|
+
throw new Error(
|
|
3190
|
+
`State "${state2.name}" cannot snapshot \u2014 events {${offending.join(", ")}} carry sensitive fields. Snapshots write derived state into __snapshot__.data, which forget_pii cannot reach. Remove .snap() or remove sensitive(...) markers.`
|
|
3191
|
+
);
|
|
3192
|
+
}
|
|
3193
|
+
const disclose = state2.disclose ?? null;
|
|
3194
|
+
state2.view = (event, actor) => {
|
|
3195
|
+
const fields = fields_by_event.get(event.name);
|
|
3196
|
+
return fields ? pii_gate(event, fields, disclose, actor) : event;
|
|
3197
|
+
};
|
|
3198
|
+
state2.message = (validated) => {
|
|
3199
|
+
const fields = fields_by_event.get(validated.name);
|
|
3200
|
+
return fields ? pii_split(validated, fields) : validated;
|
|
3201
|
+
};
|
|
3202
|
+
for (const [eventName, fields] of fields_by_event) {
|
|
3203
|
+
const original = state2.patch[eventName];
|
|
3204
|
+
state2.patch[eventName] = (event, s) => original(pii_merge(event, fields), s);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
for (const [target, original] of batchHandlers) {
|
|
3208
|
+
const wrapped = async (events, stream) => {
|
|
3209
|
+
const stripped = events.map((e) => {
|
|
3210
|
+
const f = _sf.get(e.name);
|
|
3211
|
+
return f ? pii_strip(e, f) : e;
|
|
3212
|
+
});
|
|
3213
|
+
return original(stripped, stream);
|
|
3214
|
+
};
|
|
3215
|
+
batchHandlers.set(target, wrapped);
|
|
3216
|
+
}
|
|
3106
3217
|
_built = true;
|
|
3107
3218
|
}
|
|
3108
3219
|
return new Act(
|
|
@@ -3270,7 +3381,12 @@ function state(entry) {
|
|
|
3270
3381
|
name,
|
|
3271
3382
|
init,
|
|
3272
3383
|
patch: defaultPatch,
|
|
3273
|
-
on: {}
|
|
3384
|
+
on: {},
|
|
3385
|
+
// Step delegates initialized as identity. `act().build()`
|
|
3386
|
+
// overrides on states with `sensitive(...)` events to bake in
|
|
3387
|
+
// the gate / split.
|
|
3388
|
+
view: (event) => event,
|
|
3389
|
+
message: (validated) => validated
|
|
3274
3390
|
};
|
|
3275
3391
|
const builder = action_builder(internal);
|
|
3276
3392
|
return Object.assign(builder, {
|
|
@@ -3322,6 +3438,10 @@ function action_builder(state2) {
|
|
|
3322
3438
|
internal.snap = snap2;
|
|
3323
3439
|
return builder;
|
|
3324
3440
|
},
|
|
3441
|
+
discloses(disclose) {
|
|
3442
|
+
internal.disclose = disclose;
|
|
3443
|
+
return builder;
|
|
3444
|
+
},
|
|
3325
3445
|
build() {
|
|
3326
3446
|
return internal;
|
|
3327
3447
|
}
|
|
@@ -3511,6 +3631,8 @@ export {
|
|
|
3511
3631
|
NonRetryableError,
|
|
3512
3632
|
PackageSchema,
|
|
3513
3633
|
QuerySchema,
|
|
3634
|
+
REDACTED,
|
|
3635
|
+
SHREDDED,
|
|
3514
3636
|
SNAP_EVENT,
|
|
3515
3637
|
StreamClosedError,
|
|
3516
3638
|
TOMBSTONE_EVENT,
|
|
@@ -3527,6 +3649,7 @@ export {
|
|
|
3527
3649
|
port,
|
|
3528
3650
|
projection,
|
|
3529
3651
|
scoped,
|
|
3652
|
+
sensitive,
|
|
3530
3653
|
sleep,
|
|
3531
3654
|
slice,
|
|
3532
3655
|
state,
|