@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.cjs
CHANGED
|
@@ -51,6 +51,8 @@ __export(index_exports, {
|
|
|
51
51
|
NonRetryableError: () => NonRetryableError,
|
|
52
52
|
PackageSchema: () => PackageSchema,
|
|
53
53
|
QuerySchema: () => QuerySchema,
|
|
54
|
+
REDACTED: () => REDACTED,
|
|
55
|
+
SHREDDED: () => SHREDDED,
|
|
54
56
|
SNAP_EVENT: () => SNAP_EVENT,
|
|
55
57
|
StreamClosedError: () => StreamClosedError,
|
|
56
58
|
TOMBSTONE_EVENT: () => TOMBSTONE_EVENT,
|
|
@@ -67,6 +69,7 @@ __export(index_exports, {
|
|
|
67
69
|
port: () => port,
|
|
68
70
|
projection: () => projection,
|
|
69
71
|
scoped: () => scoped,
|
|
72
|
+
sensitive: () => sensitive,
|
|
70
73
|
sleep: () => sleep,
|
|
71
74
|
slice: () => slice,
|
|
72
75
|
state: () => state,
|
|
@@ -380,55 +383,150 @@ var NonRetryableError = class extends Error {
|
|
|
380
383
|
};
|
|
381
384
|
|
|
382
385
|
// src/utils.ts
|
|
383
|
-
var
|
|
386
|
+
var import_zod4 = require("zod");
|
|
384
387
|
|
|
385
388
|
// src/config.ts
|
|
386
389
|
var fs = __toESM(require("fs"), 1);
|
|
387
|
-
var
|
|
390
|
+
var import_zod3 = require("zod");
|
|
388
391
|
|
|
389
392
|
// src/types/schemas.ts
|
|
393
|
+
var import_zod2 = require("zod");
|
|
394
|
+
|
|
395
|
+
// src/internal/sensitive.ts
|
|
390
396
|
var import_zod = require("zod");
|
|
391
|
-
var
|
|
392
|
-
var
|
|
393
|
-
|
|
394
|
-
|
|
397
|
+
var REDACTED = "[REDACTED]";
|
|
398
|
+
var SHREDDED = "[SHREDDED]";
|
|
399
|
+
var _registry = import_zod.z.registry();
|
|
400
|
+
function is_pii(schema) {
|
|
401
|
+
let cur = schema;
|
|
402
|
+
while (true) {
|
|
403
|
+
if (_registry.has(cur)) return true;
|
|
404
|
+
const inner = cur._def?.innerType;
|
|
405
|
+
if (!inner || inner === cur) return false;
|
|
406
|
+
cur = inner;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function pii_fields(schema) {
|
|
410
|
+
const shape = schema.shape;
|
|
411
|
+
if (!shape || typeof shape !== "object") return [];
|
|
412
|
+
const fields = [];
|
|
413
|
+
for (const key of Object.keys(shape)) {
|
|
414
|
+
if (is_pii(shape[key])) fields.push(key);
|
|
415
|
+
}
|
|
416
|
+
return fields;
|
|
417
|
+
}
|
|
418
|
+
function pii_split(emitted, fields) {
|
|
419
|
+
const rec = emitted.data;
|
|
420
|
+
const clean = {};
|
|
421
|
+
const pii = {};
|
|
422
|
+
for (const k of Object.keys(rec)) {
|
|
423
|
+
if (fields.includes(k)) pii[k] = rec[k];
|
|
424
|
+
else clean[k] = rec[k];
|
|
425
|
+
}
|
|
426
|
+
return { name: emitted.name, data: clean, pii };
|
|
427
|
+
}
|
|
428
|
+
function pii_merge(event, fields) {
|
|
429
|
+
const data = event.data;
|
|
430
|
+
const pii = event.pii;
|
|
431
|
+
if (pii != null) {
|
|
432
|
+
return {
|
|
433
|
+
...event,
|
|
434
|
+
data: { ...data, ...pii }
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const shredded = { ...data };
|
|
438
|
+
for (const f of fields) shredded[f] = SHREDDED;
|
|
439
|
+
return {
|
|
440
|
+
...event,
|
|
441
|
+
data: shredded
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function pii_gate(event, fields, predicate, actor) {
|
|
445
|
+
const data = event.data;
|
|
446
|
+
if (event.pii == null) {
|
|
447
|
+
const shredded = { ...data };
|
|
448
|
+
for (const f of fields) shredded[f] = SHREDDED;
|
|
449
|
+
return {
|
|
450
|
+
...event,
|
|
451
|
+
data: shredded
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
const allowed = !!actor && !!predicate && predicate(event, actor);
|
|
455
|
+
if (allowed) {
|
|
456
|
+
return {
|
|
457
|
+
...event,
|
|
458
|
+
data: {
|
|
459
|
+
...data,
|
|
460
|
+
...event.pii
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const redacted = { ...data };
|
|
465
|
+
for (const f of fields) redacted[f] = REDACTED;
|
|
466
|
+
return {
|
|
467
|
+
...event,
|
|
468
|
+
data: redacted
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function pii_strip(event, fields) {
|
|
472
|
+
const data = event.data;
|
|
473
|
+
const stripped = {};
|
|
474
|
+
for (const k of Object.keys(data)) {
|
|
475
|
+
if (!fields.includes(k)) stripped[k] = data[k];
|
|
476
|
+
}
|
|
477
|
+
const { pii: _drop_pii, ...rest } = event;
|
|
478
|
+
return {
|
|
479
|
+
...rest,
|
|
480
|
+
data: stripped
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/types/schemas.ts
|
|
485
|
+
var ZodEmpty = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.never());
|
|
486
|
+
function sensitive(schema) {
|
|
487
|
+
_registry.add(schema, { sensitive: true });
|
|
488
|
+
return schema;
|
|
489
|
+
}
|
|
490
|
+
var ActorSchema = import_zod2.z.object({
|
|
491
|
+
id: import_zod2.z.string(),
|
|
492
|
+
name: import_zod2.z.string()
|
|
395
493
|
}).loose().readonly();
|
|
396
|
-
var TargetSchema =
|
|
397
|
-
stream:
|
|
494
|
+
var TargetSchema = import_zod2.z.object({
|
|
495
|
+
stream: import_zod2.z.string(),
|
|
398
496
|
actor: ActorSchema,
|
|
399
|
-
expectedVersion:
|
|
497
|
+
expectedVersion: import_zod2.z.number().optional()
|
|
400
498
|
}).loose().readonly();
|
|
401
|
-
var CausationEventSchema =
|
|
402
|
-
id:
|
|
403
|
-
name:
|
|
404
|
-
stream:
|
|
499
|
+
var CausationEventSchema = import_zod2.z.object({
|
|
500
|
+
id: import_zod2.z.number(),
|
|
501
|
+
name: import_zod2.z.string(),
|
|
502
|
+
stream: import_zod2.z.string()
|
|
405
503
|
});
|
|
406
|
-
var EventMetaSchema =
|
|
407
|
-
correlation:
|
|
408
|
-
causation:
|
|
409
|
-
action: TargetSchema.and(
|
|
504
|
+
var EventMetaSchema = import_zod2.z.object({
|
|
505
|
+
correlation: import_zod2.z.string(),
|
|
506
|
+
causation: import_zod2.z.object({
|
|
507
|
+
action: TargetSchema.and(import_zod2.z.object({ name: import_zod2.z.string() })).optional(),
|
|
410
508
|
event: CausationEventSchema.optional()
|
|
411
509
|
})
|
|
412
510
|
}).readonly();
|
|
413
|
-
var CommittedMetaSchema =
|
|
414
|
-
id:
|
|
415
|
-
stream:
|
|
416
|
-
version:
|
|
417
|
-
created:
|
|
511
|
+
var CommittedMetaSchema = import_zod2.z.object({
|
|
512
|
+
id: import_zod2.z.number(),
|
|
513
|
+
stream: import_zod2.z.string(),
|
|
514
|
+
version: import_zod2.z.number(),
|
|
515
|
+
created: import_zod2.z.date(),
|
|
418
516
|
meta: EventMetaSchema
|
|
419
517
|
}).readonly();
|
|
420
|
-
var QuerySchema =
|
|
421
|
-
stream:
|
|
422
|
-
names:
|
|
423
|
-
before:
|
|
424
|
-
after:
|
|
425
|
-
limit:
|
|
426
|
-
created_before:
|
|
427
|
-
created_after:
|
|
428
|
-
backward:
|
|
429
|
-
correlation:
|
|
430
|
-
with_snaps:
|
|
431
|
-
stream_exact:
|
|
518
|
+
var QuerySchema = import_zod2.z.object({
|
|
519
|
+
stream: import_zod2.z.string().optional(),
|
|
520
|
+
names: import_zod2.z.string().array().optional(),
|
|
521
|
+
before: import_zod2.z.number().optional(),
|
|
522
|
+
after: import_zod2.z.number().optional(),
|
|
523
|
+
limit: import_zod2.z.number().optional(),
|
|
524
|
+
created_before: import_zod2.z.date().optional(),
|
|
525
|
+
created_after: import_zod2.z.date().optional(),
|
|
526
|
+
backward: import_zod2.z.boolean().optional(),
|
|
527
|
+
correlation: import_zod2.z.string().optional(),
|
|
528
|
+
with_snaps: import_zod2.z.boolean().optional(),
|
|
529
|
+
stream_exact: import_zod2.z.boolean().optional()
|
|
432
530
|
}).readonly();
|
|
433
531
|
|
|
434
532
|
// src/types/index.ts
|
|
@@ -448,13 +546,13 @@ var LogLevels = [
|
|
|
448
546
|
];
|
|
449
547
|
|
|
450
548
|
// src/config.ts
|
|
451
|
-
var PackageSchema =
|
|
452
|
-
name:
|
|
453
|
-
version:
|
|
454
|
-
description:
|
|
455
|
-
author:
|
|
456
|
-
license:
|
|
457
|
-
dependencies:
|
|
549
|
+
var PackageSchema = import_zod3.z.object({
|
|
550
|
+
name: import_zod3.z.string().min(1),
|
|
551
|
+
version: import_zod3.z.string().min(1),
|
|
552
|
+
description: import_zod3.z.string().min(1).optional(),
|
|
553
|
+
author: import_zod3.z.object({ name: import_zod3.z.string().min(1), email: import_zod3.z.string().optional() }).optional().or(import_zod3.z.string().min(1)).optional(),
|
|
554
|
+
license: import_zod3.z.string().min(1).optional(),
|
|
555
|
+
dependencies: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.string()).optional()
|
|
458
556
|
});
|
|
459
557
|
var FALLBACK_PACKAGE = {
|
|
460
558
|
name: "act-fallback",
|
|
@@ -472,10 +570,10 @@ var getPackage = () => {
|
|
|
472
570
|
};
|
|
473
571
|
var pkgLoadError;
|
|
474
572
|
var BaseSchema = PackageSchema.extend({
|
|
475
|
-
env:
|
|
476
|
-
logLevel:
|
|
477
|
-
logSingleLine:
|
|
478
|
-
sleepMs:
|
|
573
|
+
env: import_zod3.z.enum(Environments),
|
|
574
|
+
logLevel: import_zod3.z.enum(LogLevels),
|
|
575
|
+
logSingleLine: import_zod3.z.boolean(),
|
|
576
|
+
sleepMs: import_zod3.z.number().int().min(0).max(5e3)
|
|
479
577
|
});
|
|
480
578
|
var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
|
|
481
579
|
var env = NODE_ENV || "development";
|
|
@@ -506,8 +604,8 @@ var validate = (target, payload, schema) => {
|
|
|
506
604
|
try {
|
|
507
605
|
return schema ? schema.parse(payload) : payload;
|
|
508
606
|
} catch (error) {
|
|
509
|
-
if (error instanceof
|
|
510
|
-
throw new ValidationError(target, payload, (0,
|
|
607
|
+
if (error instanceof import_zod4.ZodError) {
|
|
608
|
+
throw new ValidationError(target, payload, (0, import_zod4.prettifyError)(error));
|
|
511
609
|
}
|
|
512
610
|
throw new ValidationError(target, payload, error);
|
|
513
611
|
}
|
|
@@ -687,11 +785,24 @@ var InMemoryStore = class {
|
|
|
687
785
|
_maxEventIdByStream = /* @__PURE__ */ new Map();
|
|
688
786
|
// global max non-snapshot event id — fast pre-check for source-less streams in claim()
|
|
689
787
|
_maxNonSnapEventId = -1;
|
|
788
|
+
// stream → (event_id → cloned sensitive payload). Two-level so `forget_pii`
|
|
789
|
+
// is O(1) — drop the inner Map for the stream and the wipe is done — mirroring
|
|
790
|
+
// the `DELETE WHERE stream = ?` scope that durable adapters get from their
|
|
791
|
+
// stream index. Entries exist only for events committed with a non-null
|
|
792
|
+
// `pii` field; absence means "no PII" (returned as `null` on load).
|
|
793
|
+
_pii = /* @__PURE__ */ new Map();
|
|
690
794
|
_resetIndexes() {
|
|
691
795
|
this._events.length = 0;
|
|
692
796
|
this._streamVersions.clear();
|
|
693
797
|
this._maxEventIdByStream.clear();
|
|
694
798
|
this._maxNonSnapEventId = -1;
|
|
799
|
+
this._pii.clear();
|
|
800
|
+
}
|
|
801
|
+
// Attach the isolated PII payload (or null) to an event before handing it to
|
|
802
|
+
// a caller. Allocation-free for events without PII — by far the common case.
|
|
803
|
+
_withPii(e) {
|
|
804
|
+
const pii = this._pii.get(e.stream)?.get(e.id);
|
|
805
|
+
return pii ? { ...e, pii } : e;
|
|
695
806
|
}
|
|
696
807
|
/**
|
|
697
808
|
* Dispose of the store and clear all events.
|
|
@@ -747,7 +858,9 @@ var InMemoryStore = class {
|
|
|
747
858
|
continue;
|
|
748
859
|
if (query.after && e.id <= query.after) break;
|
|
749
860
|
if (query.created_after && e.created <= query.created_after) break;
|
|
750
|
-
await Promise.resolve(
|
|
861
|
+
await Promise.resolve(
|
|
862
|
+
callback(this._withPii(e))
|
|
863
|
+
);
|
|
751
864
|
count++;
|
|
752
865
|
if (query?.limit && count >= query.limit) break;
|
|
753
866
|
}
|
|
@@ -759,7 +872,9 @@ var InMemoryStore = class {
|
|
|
759
872
|
if (query?.created_after && e.created <= query.created_after) continue;
|
|
760
873
|
if (query?.before && e.id >= query.before) break;
|
|
761
874
|
if (query?.created_before && e.created >= query.created_before) break;
|
|
762
|
-
await Promise.resolve(
|
|
875
|
+
await Promise.resolve(
|
|
876
|
+
callback(this._withPii(e))
|
|
877
|
+
);
|
|
763
878
|
count++;
|
|
764
879
|
if (query?.limit && count >= query.limit) break;
|
|
765
880
|
}
|
|
@@ -788,7 +903,7 @@ var InMemoryStore = class {
|
|
|
788
903
|
}
|
|
789
904
|
let version = currentVersion + 1;
|
|
790
905
|
let lastNonSnapId = -1;
|
|
791
|
-
const committed = msgs.map(({ name, data }) => {
|
|
906
|
+
const committed = msgs.map(({ name, data, pii }) => {
|
|
792
907
|
const c = {
|
|
793
908
|
id: this._events.length,
|
|
794
909
|
stream,
|
|
@@ -799,9 +914,17 @@ var InMemoryStore = class {
|
|
|
799
914
|
meta
|
|
800
915
|
};
|
|
801
916
|
this._events.push(c);
|
|
917
|
+
if (pii != null) {
|
|
918
|
+
let perStream = this._pii.get(stream);
|
|
919
|
+
if (!perStream) {
|
|
920
|
+
perStream = /* @__PURE__ */ new Map();
|
|
921
|
+
this._pii.set(stream, perStream);
|
|
922
|
+
}
|
|
923
|
+
perStream.set(c.id, structuredClone(pii));
|
|
924
|
+
}
|
|
802
925
|
if (name !== SNAP_EVENT) lastNonSnapId = c.id;
|
|
803
926
|
version++;
|
|
804
|
-
return c;
|
|
927
|
+
return this._withPii(c);
|
|
805
928
|
});
|
|
806
929
|
this._streamVersions.set(stream, version - 1);
|
|
807
930
|
if (lastNonSnapId >= 0) {
|
|
@@ -982,6 +1105,21 @@ var InMemoryStore = class {
|
|
|
982
1105
|
* @param input - Stream names or a filter selecting the streams to unblock.
|
|
983
1106
|
* @returns Count of streams that were actually flipped (were blocked).
|
|
984
1107
|
*/
|
|
1108
|
+
/**
|
|
1109
|
+
* Wipe the sensitive-data payload for every event on the stream — see
|
|
1110
|
+
* {@link Store.forget_pii}. O(1) drop of the stream's inner Map; the size of
|
|
1111
|
+
* that Map is the count of events that had PII. Idempotent: a second call
|
|
1112
|
+
* finds no inner Map and returns `0`.
|
|
1113
|
+
*
|
|
1114
|
+
* @param stream - Target stream.
|
|
1115
|
+
* @returns Count of events whose isolated PII payload was deleted.
|
|
1116
|
+
*/
|
|
1117
|
+
async forget_pii(stream) {
|
|
1118
|
+
await sleep();
|
|
1119
|
+
const count = this._pii.get(stream)?.size ?? 0;
|
|
1120
|
+
this._pii.delete(stream);
|
|
1121
|
+
return count;
|
|
1122
|
+
}
|
|
985
1123
|
async unblock(input) {
|
|
986
1124
|
await sleep();
|
|
987
1125
|
let count = 0;
|
|
@@ -2294,7 +2432,7 @@ async function scan(source, opts = {}, callback) {
|
|
|
2294
2432
|
}
|
|
2295
2433
|
};
|
|
2296
2434
|
}
|
|
2297
|
-
async function load(me, stream, callback, asOf) {
|
|
2435
|
+
async function load(me, stream, callback, asOf, actor) {
|
|
2298
2436
|
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
2299
2437
|
const cached = timeTravel ? void 0 : await cache2().get(stream);
|
|
2300
2438
|
const cache_hit = !!cached;
|
|
@@ -2306,15 +2444,16 @@ async function load(me, stream, callback, asOf) {
|
|
|
2306
2444
|
let event;
|
|
2307
2445
|
await store2().query(
|
|
2308
2446
|
(e) => {
|
|
2309
|
-
event = e;
|
|
2310
2447
|
version = e.version;
|
|
2448
|
+
const typed = e;
|
|
2449
|
+
event = me.view(typed, actor);
|
|
2311
2450
|
if (e.name === SNAP_EVENT) {
|
|
2312
2451
|
state2 = e.data;
|
|
2313
2452
|
snaps++;
|
|
2314
2453
|
patches = 0;
|
|
2315
2454
|
replayed++;
|
|
2316
2455
|
} else if (me.patch[e.name]) {
|
|
2317
|
-
state2 = (0, import_act_patch.patch)(state2, me.patch[e.name](
|
|
2456
|
+
state2 = (0, import_act_patch.patch)(state2, me.patch[e.name](typed, state2));
|
|
2318
2457
|
patches++;
|
|
2319
2458
|
replayed++;
|
|
2320
2459
|
} else if (e.name !== TOMBSTONE_EVENT) {
|
|
@@ -2357,7 +2496,13 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2357
2496
|
const maxRetries = opts?.maxRetries ?? 0;
|
|
2358
2497
|
for (let attempt = 0; ; attempt++) {
|
|
2359
2498
|
try {
|
|
2360
|
-
const snapshot = await load(
|
|
2499
|
+
const snapshot = await load(
|
|
2500
|
+
me,
|
|
2501
|
+
stream,
|
|
2502
|
+
void 0,
|
|
2503
|
+
void 0,
|
|
2504
|
+
target.actor
|
|
2505
|
+
);
|
|
2361
2506
|
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
2362
2507
|
throw new StreamClosedError(stream);
|
|
2363
2508
|
const expected = expectedVersion ?? snapshot.event?.version;
|
|
@@ -2394,10 +2539,10 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2394
2539
|
}
|
|
2395
2540
|
}
|
|
2396
2541
|
}
|
|
2397
|
-
const emitted = tuples.map(([name, data]) =>
|
|
2398
|
-
name,
|
|
2399
|
-
|
|
2400
|
-
})
|
|
2542
|
+
const emitted = tuples.map(([name, data]) => {
|
|
2543
|
+
const validated2 = skipValidation ? data : validate(name, data, me.events[name]);
|
|
2544
|
+
return me.message({ name, data: validated2 });
|
|
2545
|
+
});
|
|
2401
2546
|
const meta = {
|
|
2402
2547
|
correlation: reactingTo?.meta.correlation || correlator({
|
|
2403
2548
|
action: action2,
|
|
@@ -2409,8 +2554,8 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2409
2554
|
action: {
|
|
2410
2555
|
name: action2,
|
|
2411
2556
|
...target
|
|
2412
|
-
// payload intentionally omitted
|
|
2413
|
-
//
|
|
2557
|
+
// payload intentionally omitted from causation metadata —
|
|
2558
|
+
// callers correlate via the correlation id when they need it.
|
|
2414
2559
|
},
|
|
2415
2560
|
event: reactingTo ? {
|
|
2416
2561
|
id: reactingTo.id,
|
|
@@ -2443,7 +2588,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2443
2588
|
state2 = (0, import_act_patch.patch)(state2, p);
|
|
2444
2589
|
patches++;
|
|
2445
2590
|
return {
|
|
2446
|
-
event,
|
|
2591
|
+
event: me.view(event, target.actor),
|
|
2447
2592
|
state: state2,
|
|
2448
2593
|
version: event.version,
|
|
2449
2594
|
patches,
|
|
@@ -2528,7 +2673,7 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
2528
2673
|
return result;
|
|
2529
2674
|
});
|
|
2530
2675
|
function buildEs(logger, correlator = defaultCorrelator) {
|
|
2531
|
-
const
|
|
2676
|
+
const bound_action = (me, actionName, target, payload, reactingTo, skipValidation = false) => action(
|
|
2532
2677
|
me,
|
|
2533
2678
|
actionName,
|
|
2534
2679
|
target,
|
|
@@ -2541,7 +2686,7 @@ function buildEs(logger, correlator = defaultCorrelator) {
|
|
|
2541
2686
|
return {
|
|
2542
2687
|
snap,
|
|
2543
2688
|
load,
|
|
2544
|
-
action:
|
|
2689
|
+
action: bound_action,
|
|
2545
2690
|
tombstone
|
|
2546
2691
|
};
|
|
2547
2692
|
}
|
|
@@ -2571,7 +2716,7 @@ function buildEs(logger, correlator = defaultCorrelator) {
|
|
|
2571
2716
|
);
|
|
2572
2717
|
}),
|
|
2573
2718
|
action: traced(
|
|
2574
|
-
|
|
2719
|
+
bound_action,
|
|
2575
2720
|
(snapshots, _me, _action, target) => {
|
|
2576
2721
|
const committed = snapshots.filter((s) => s.event);
|
|
2577
2722
|
if (committed.length) {
|
|
@@ -2864,7 +3009,7 @@ var DrainController = class {
|
|
|
2864
3009
|
};
|
|
2865
3010
|
|
|
2866
3011
|
// src/internal/merge.ts
|
|
2867
|
-
var
|
|
3012
|
+
var import_zod5 = require("zod");
|
|
2868
3013
|
function baseTypeName(zodType) {
|
|
2869
3014
|
let t = zodType;
|
|
2870
3015
|
while (typeof t.unwrap === "function") {
|
|
@@ -2873,7 +3018,7 @@ function baseTypeName(zodType) {
|
|
|
2873
3018
|
return t.constructor.name;
|
|
2874
3019
|
}
|
|
2875
3020
|
function mergeSchemas(existing, incoming, stateName) {
|
|
2876
|
-
if (existing instanceof
|
|
3021
|
+
if (existing instanceof import_zod5.ZodObject && incoming instanceof import_zod5.ZodObject) {
|
|
2877
3022
|
const existingShape = existing.shape;
|
|
2878
3023
|
const incomingShape = incoming.shape;
|
|
2879
3024
|
for (const key of Object.keys(incomingShape)) {
|
|
@@ -3028,7 +3173,14 @@ function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
|
3028
3173
|
};
|
|
3029
3174
|
}
|
|
3030
3175
|
function buildHandle(deps) {
|
|
3031
|
-
const {
|
|
3176
|
+
const {
|
|
3177
|
+
logger,
|
|
3178
|
+
bound_do,
|
|
3179
|
+
bound_load,
|
|
3180
|
+
bound_query,
|
|
3181
|
+
bound_query_array,
|
|
3182
|
+
bound_forget
|
|
3183
|
+
} = deps;
|
|
3032
3184
|
return async (lease, payloads) => {
|
|
3033
3185
|
if (payloads.length === 0) return { lease, handled: 0, acked_at: lease.at };
|
|
3034
3186
|
const stream = lease.stream;
|
|
@@ -3037,14 +3189,15 @@ function buildHandle(deps) {
|
|
|
3037
3189
|
if (lease.retry > 0)
|
|
3038
3190
|
logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
3039
3191
|
const scopedApp = {
|
|
3040
|
-
do:
|
|
3041
|
-
load:
|
|
3042
|
-
query:
|
|
3043
|
-
query_array:
|
|
3192
|
+
do: bound_do,
|
|
3193
|
+
load: bound_load,
|
|
3194
|
+
query: bound_query,
|
|
3195
|
+
query_array: bound_query_array,
|
|
3196
|
+
forget: bound_forget
|
|
3044
3197
|
};
|
|
3045
3198
|
for (const payload of payloads) {
|
|
3046
3199
|
const { event, handler } = payload;
|
|
3047
|
-
scopedApp.do = (action2, target, actionPayload, reactingTo, skipValidation) =>
|
|
3200
|
+
scopedApp.do = (action2, target, actionPayload, reactingTo, skipValidation) => bound_do(
|
|
3048
3201
|
action2,
|
|
3049
3202
|
target,
|
|
3050
3203
|
actionPayload,
|
|
@@ -3073,7 +3226,9 @@ function buildHandle(deps) {
|
|
|
3073
3226
|
function buildHandleBatch(logger) {
|
|
3074
3227
|
return async (lease, payloads, batchHandler) => {
|
|
3075
3228
|
const stream = lease.stream;
|
|
3076
|
-
const events = payloads.map(
|
|
3229
|
+
const events = payloads.map(
|
|
3230
|
+
(p) => p.event
|
|
3231
|
+
);
|
|
3077
3232
|
const options = payloads[0].options;
|
|
3078
3233
|
if (lease.retry > 0)
|
|
3079
3234
|
logger.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
|
|
@@ -3256,6 +3411,7 @@ var Act = class {
|
|
|
3256
3411
|
_bound_load = this.load.bind(this);
|
|
3257
3412
|
_bound_query = this.query.bind(this);
|
|
3258
3413
|
_bound_query_array = this.query_array.bind(this);
|
|
3414
|
+
_bound_forget = this.forget.bind(this);
|
|
3259
3415
|
/** Reaction dispatchers built once and handed to runDrainCycle each cycle. */
|
|
3260
3416
|
_handle;
|
|
3261
3417
|
_handle_batch;
|
|
@@ -3301,10 +3457,11 @@ var Act = class {
|
|
|
3301
3457
|
this._cd = buildDrain(this._logger);
|
|
3302
3458
|
this._handle = buildHandle({
|
|
3303
3459
|
logger: this._logger,
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3460
|
+
bound_do: this._bound_do,
|
|
3461
|
+
bound_load: this._bound_load,
|
|
3462
|
+
bound_query: this._bound_query,
|
|
3463
|
+
bound_query_array: this._bound_query_array,
|
|
3464
|
+
bound_forget: this._bound_forget
|
|
3308
3465
|
});
|
|
3309
3466
|
this._handle_batch = buildHandleBatch(this._logger);
|
|
3310
3467
|
const {
|
|
@@ -3536,7 +3693,7 @@ var Act = class {
|
|
|
3536
3693
|
return snapshots;
|
|
3537
3694
|
});
|
|
3538
3695
|
}
|
|
3539
|
-
async load(stateOrName, stream, callback, asOf) {
|
|
3696
|
+
async load(stateOrName, stream, callback, asOf, actor) {
|
|
3540
3697
|
return this._scoped(async () => {
|
|
3541
3698
|
let merged;
|
|
3542
3699
|
if (typeof stateOrName === "string") {
|
|
@@ -3546,7 +3703,7 @@ var Act = class {
|
|
|
3546
3703
|
} else {
|
|
3547
3704
|
merged = this._states.get(stateOrName.name) || stateOrName;
|
|
3548
3705
|
}
|
|
3549
|
-
return await this._es.load(merged, stream, callback, asOf);
|
|
3706
|
+
return await this._es.load(merged, stream, callback, asOf, actor);
|
|
3550
3707
|
});
|
|
3551
3708
|
}
|
|
3552
3709
|
/**
|
|
@@ -3640,6 +3797,34 @@ var Act = class {
|
|
|
3640
3797
|
return events;
|
|
3641
3798
|
});
|
|
3642
3799
|
}
|
|
3800
|
+
/**
|
|
3801
|
+
* Wipe the sensitive-data payload for every event on the stream — see
|
|
3802
|
+
* {@link IAct.forget}. Application-level half of #566.
|
|
3803
|
+
*
|
|
3804
|
+
* Throws on adapters without `Store.forget_pii`, invalidates the cache
|
|
3805
|
+
* entry for the stream, emits the `forgotten` lifecycle event with the
|
|
3806
|
+
* row count. Idempotent: a second call returns `{eventCount: 0}` and
|
|
3807
|
+
* does NOT re-emit.
|
|
3808
|
+
*
|
|
3809
|
+
* @param stream - Target stream.
|
|
3810
|
+
* @returns `{eventCount}` — number of events whose PII column was wiped.
|
|
3811
|
+
*/
|
|
3812
|
+
async forget(stream) {
|
|
3813
|
+
return this._scoped(async () => {
|
|
3814
|
+
const s = store2();
|
|
3815
|
+
if (!s.forget_pii) {
|
|
3816
|
+
throw new Error(
|
|
3817
|
+
`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).`
|
|
3818
|
+
);
|
|
3819
|
+
}
|
|
3820
|
+
const eventCount = await s.forget_pii(stream);
|
|
3821
|
+
await cache2().invalidate(stream);
|
|
3822
|
+
if (eventCount > 0) {
|
|
3823
|
+
this.emit("forgotten", { stream, at: /* @__PURE__ */ new Date(), eventCount });
|
|
3824
|
+
}
|
|
3825
|
+
return { eventCount };
|
|
3826
|
+
});
|
|
3827
|
+
}
|
|
3643
3828
|
/**
|
|
3644
3829
|
* Processes pending reactions by draining uncommitted events from the event store.
|
|
3645
3830
|
*
|
|
@@ -4240,9 +4425,13 @@ function validateLaneReferences(registry, lanes) {
|
|
|
4240
4425
|
}
|
|
4241
4426
|
function act() {
|
|
4242
4427
|
const states = /* @__PURE__ */ new Map();
|
|
4428
|
+
const _sf = /* @__PURE__ */ new Map();
|
|
4429
|
+
const _dp = /* @__PURE__ */ new Map();
|
|
4243
4430
|
const registry = {
|
|
4244
4431
|
actions: {},
|
|
4245
|
-
events: {}
|
|
4432
|
+
events: {},
|
|
4433
|
+
sensitive_fields: (eventName) => _sf.get(eventName) ?? [],
|
|
4434
|
+
disclosure_predicate: (stateName) => _dp.get(stateName) ?? null
|
|
4246
4435
|
};
|
|
4247
4436
|
const pendingProjections = [];
|
|
4248
4437
|
const batchHandlers = /* @__PURE__ */ new Map();
|
|
@@ -4353,6 +4542,58 @@ function act() {
|
|
|
4353
4542
|
}
|
|
4354
4543
|
finalizeDeprecations();
|
|
4355
4544
|
validateLaneReferences(registry, lanes);
|
|
4545
|
+
for (const [eventName, reg] of Object.entries(
|
|
4546
|
+
registry.events
|
|
4547
|
+
)) {
|
|
4548
|
+
const fields = pii_fields(reg.schema);
|
|
4549
|
+
if (fields.length === 0) continue;
|
|
4550
|
+
_sf.set(eventName, fields);
|
|
4551
|
+
for (const [name, reaction] of reg.reactions) {
|
|
4552
|
+
const inner = reaction.handler;
|
|
4553
|
+
const wrapped = (event, stream, app) => inner(pii_strip(event, fields), stream, app);
|
|
4554
|
+
Object.defineProperty(wrapped, "name", { value: inner.name });
|
|
4555
|
+
reaction.handler = wrapped;
|
|
4556
|
+
reg.reactions.set(name, reaction);
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
for (const state2 of states.values()) {
|
|
4560
|
+
if (state2.disclose) _dp.set(state2.name, state2.disclose);
|
|
4561
|
+
const fields_by_event = /* @__PURE__ */ new Map();
|
|
4562
|
+
for (const eventName of Object.keys(state2.events)) {
|
|
4563
|
+
const fields = _sf.get(eventName);
|
|
4564
|
+
if (fields) fields_by_event.set(eventName, fields);
|
|
4565
|
+
}
|
|
4566
|
+
if (fields_by_event.size === 0) continue;
|
|
4567
|
+
if (state2.snap) {
|
|
4568
|
+
const offending = [...fields_by_event.keys()];
|
|
4569
|
+
throw new Error(
|
|
4570
|
+
`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.`
|
|
4571
|
+
);
|
|
4572
|
+
}
|
|
4573
|
+
const disclose = state2.disclose ?? null;
|
|
4574
|
+
state2.view = (event, actor) => {
|
|
4575
|
+
const fields = fields_by_event.get(event.name);
|
|
4576
|
+
return fields ? pii_gate(event, fields, disclose, actor) : event;
|
|
4577
|
+
};
|
|
4578
|
+
state2.message = (validated) => {
|
|
4579
|
+
const fields = fields_by_event.get(validated.name);
|
|
4580
|
+
return fields ? pii_split(validated, fields) : validated;
|
|
4581
|
+
};
|
|
4582
|
+
for (const [eventName, fields] of fields_by_event) {
|
|
4583
|
+
const original = state2.patch[eventName];
|
|
4584
|
+
state2.patch[eventName] = (event, s) => original(pii_merge(event, fields), s);
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
for (const [target, original] of batchHandlers) {
|
|
4588
|
+
const wrapped = async (events, stream) => {
|
|
4589
|
+
const stripped = events.map((e) => {
|
|
4590
|
+
const f = _sf.get(e.name);
|
|
4591
|
+
return f ? pii_strip(e, f) : e;
|
|
4592
|
+
});
|
|
4593
|
+
return original(stripped, stream);
|
|
4594
|
+
};
|
|
4595
|
+
batchHandlers.set(target, wrapped);
|
|
4596
|
+
}
|
|
4356
4597
|
_built = true;
|
|
4357
4598
|
}
|
|
4358
4599
|
return new Act(
|
|
@@ -4520,7 +4761,12 @@ function state(entry) {
|
|
|
4520
4761
|
name,
|
|
4521
4762
|
init,
|
|
4522
4763
|
patch: defaultPatch,
|
|
4523
|
-
on: {}
|
|
4764
|
+
on: {},
|
|
4765
|
+
// Step delegates initialized as identity. `act().build()`
|
|
4766
|
+
// overrides on states with `sensitive(...)` events to bake in
|
|
4767
|
+
// the gate / split.
|
|
4768
|
+
view: (event) => event,
|
|
4769
|
+
message: (validated) => validated
|
|
4524
4770
|
};
|
|
4525
4771
|
const builder = action_builder(internal);
|
|
4526
4772
|
return Object.assign(builder, {
|
|
@@ -4572,6 +4818,10 @@ function action_builder(state2) {
|
|
|
4572
4818
|
internal.snap = snap2;
|
|
4573
4819
|
return builder;
|
|
4574
4820
|
},
|
|
4821
|
+
discloses(disclose) {
|
|
4822
|
+
internal.disclose = disclose;
|
|
4823
|
+
return builder;
|
|
4824
|
+
},
|
|
4575
4825
|
build() {
|
|
4576
4826
|
return internal;
|
|
4577
4827
|
}
|
|
@@ -4762,6 +5012,8 @@ function writeLine(writer, line) {
|
|
|
4762
5012
|
NonRetryableError,
|
|
4763
5013
|
PackageSchema,
|
|
4764
5014
|
QuerySchema,
|
|
5015
|
+
REDACTED,
|
|
5016
|
+
SHREDDED,
|
|
4765
5017
|
SNAP_EVENT,
|
|
4766
5018
|
StreamClosedError,
|
|
4767
5019
|
TOMBSTONE_EVENT,
|
|
@@ -4778,6 +5030,7 @@ function writeLine(writer, line) {
|
|
|
4778
5030
|
port,
|
|
4779
5031
|
projection,
|
|
4780
5032
|
scoped,
|
|
5033
|
+
sensitive,
|
|
4781
5034
|
sleep,
|
|
4782
5035
|
slice,
|
|
4783
5036
|
state,
|