@rotorsoft/act 1.0.0 → 1.2.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 +44 -1
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +13 -0
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +17 -1
- 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/types/action.d.ts +77 -0
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +59 -0
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/{chunk-VC6MSVC3.js → chunk-J6NDEEXC.js} +46 -1
- package/dist/{chunk-VC6MSVC3.js.map → chunk-J6NDEEXC.js.map} +1 -1
- package/dist/index.cjs +160 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +116 -1
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +47 -2
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +3 -3
- package/dist/test/index.js.map +1 -1
- package/package.json +2 -2
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-J6NDEEXC.js";
|
|
23
23
|
import {
|
|
24
24
|
ActorSchema,
|
|
25
25
|
CausationEventSchema,
|
|
@@ -899,6 +899,60 @@ async function tombstone(stream, expectedVersion, correlation) {
|
|
|
899
899
|
throw error;
|
|
900
900
|
}
|
|
901
901
|
}
|
|
902
|
+
function isValid(event) {
|
|
903
|
+
if (event.version < 0) return false;
|
|
904
|
+
if (!(event.created instanceof Date) || Number.isNaN(event.created.getTime()))
|
|
905
|
+
return false;
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
async function scan(source, opts = {}, callback) {
|
|
909
|
+
const { drop_snapshots = false, on_progress } = opts;
|
|
910
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
911
|
+
let kept = 0;
|
|
912
|
+
let droppedSnapshots = 0;
|
|
913
|
+
let processed = 0;
|
|
914
|
+
for await (const event of source) {
|
|
915
|
+
processed++;
|
|
916
|
+
if (!isValid(event)) throw new Error(`Invalid event at index ${processed}`);
|
|
917
|
+
if (on_progress) on_progress({ processed });
|
|
918
|
+
if (drop_snapshots && event.name === SNAP_EVENT) {
|
|
919
|
+
droppedSnapshots++;
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (!callback) {
|
|
923
|
+
kept++;
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
let remapped = event;
|
|
927
|
+
const causedBy = event.meta.causation.event?.id;
|
|
928
|
+
if (causedBy !== void 0) {
|
|
929
|
+
const newCausedBy = idMap.get(causedBy);
|
|
930
|
+
if (newCausedBy !== void 0 && newCausedBy !== causedBy) {
|
|
931
|
+
remapped = {
|
|
932
|
+
...event,
|
|
933
|
+
meta: {
|
|
934
|
+
...event.meta,
|
|
935
|
+
causation: {
|
|
936
|
+
...event.meta.causation,
|
|
937
|
+
event: { ...event.meta.causation.event, id: newCausedBy }
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
const newId = await callback(remapped);
|
|
944
|
+
idMap.set(event.id, newId);
|
|
945
|
+
kept++;
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
kept,
|
|
949
|
+
dropped: {
|
|
950
|
+
closed_streams: 0,
|
|
951
|
+
snapshots: droppedSnapshots,
|
|
952
|
+
empty_streams: 0
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
}
|
|
902
956
|
async function load(me, stream, callback, asOf) {
|
|
903
957
|
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
904
958
|
const cached = timeTravel ? void 0 : await cache().get(stream);
|
|
@@ -2533,6 +2587,67 @@ var Act = class {
|
|
|
2533
2587
|
return count;
|
|
2534
2588
|
});
|
|
2535
2589
|
}
|
|
2590
|
+
/**
|
|
2591
|
+
* Atomically wipe the store and rebuild it from an async stream of
|
|
2592
|
+
* committed events. The framework owns iteration, validation,
|
|
2593
|
+
* `drop_snapshots` filtering, `on_progress`, and the per-call
|
|
2594
|
+
* `old → new` causation remap; the adapter's {@link Store.restore}
|
|
2595
|
+
* driver supplies the transaction lifecycle and per-event insert.
|
|
2596
|
+
*
|
|
2597
|
+
* Throws if the adapter has no restore capability. Throws on the
|
|
2598
|
+
* first invalid event (negative version, malformed `created`) with
|
|
2599
|
+
* the running index in the message; atomic transaction rollback in
|
|
2600
|
+
* the adapter means a failing restore leaves the store byte-for-byte
|
|
2601
|
+
* unchanged.
|
|
2602
|
+
*
|
|
2603
|
+
* @param source - Async stream of events in target order. Streamed
|
|
2604
|
+
* rather than buffered so multi-million-event backups don't OOM.
|
|
2605
|
+
* Each event's original `id` is used as a causation lookup key but
|
|
2606
|
+
* never written through — adapters renumber densely.
|
|
2607
|
+
* @param opts - {@link ScanOptions}. `drop_snapshots` skips
|
|
2608
|
+
* `__snapshot__` events (counted in the result); `on_progress`
|
|
2609
|
+
* fires once per event.
|
|
2610
|
+
* @returns {@link ScanResult} with `kept`, `duration_ms`, and
|
|
2611
|
+
* `dropped` per-category counters.
|
|
2612
|
+
*
|
|
2613
|
+
* @example Round-trip a CSV backup
|
|
2614
|
+
* ```typescript
|
|
2615
|
+
* async function* parseCsv(blob: string) {
|
|
2616
|
+
* for (const line of blob.split("\n").slice(1)) {
|
|
2617
|
+
* const [id, name, data, stream, version, created, meta] = parse(line);
|
|
2618
|
+
* yield {
|
|
2619
|
+
* id: +id, name, data: JSON.parse(data), stream,
|
|
2620
|
+
* version: +version, created: new Date(created),
|
|
2621
|
+
* meta: JSON.parse(meta),
|
|
2622
|
+
* };
|
|
2623
|
+
* }
|
|
2624
|
+
* }
|
|
2625
|
+
* const result = await app.restore(parseCsv(csvBlob), {});
|
|
2626
|
+
* console.log(`Restored ${result.kept} events in ${result.duration_ms}ms`);
|
|
2627
|
+
* await cache().clear(); // operator's responsibility
|
|
2628
|
+
* ```
|
|
2629
|
+
*
|
|
2630
|
+
* @see {@link Store.restore} for the underlying driver-pattern primitive.
|
|
2631
|
+
*/
|
|
2632
|
+
async restore(source, opts = {}) {
|
|
2633
|
+
return this._scoped(async () => {
|
|
2634
|
+
const started = Date.now();
|
|
2635
|
+
if (opts.dry_run) {
|
|
2636
|
+
const partial = await scan(source, opts);
|
|
2637
|
+
return { ...partial, duration_ms: Date.now() - started };
|
|
2638
|
+
}
|
|
2639
|
+
const s = store();
|
|
2640
|
+
if (!s.restore) throw new Error("adapter has no restore capability");
|
|
2641
|
+
let kept = 0;
|
|
2642
|
+
let dropped = { closed_streams: 0, snapshots: 0, empty_streams: 0 };
|
|
2643
|
+
await s.restore(async (callback) => {
|
|
2644
|
+
const partial = await scan(source, opts, callback);
|
|
2645
|
+
kept = partial.kept;
|
|
2646
|
+
dropped = partial.dropped;
|
|
2647
|
+
});
|
|
2648
|
+
return { kept, dropped, duration_ms: Date.now() - started };
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2536
2651
|
/**
|
|
2537
2652
|
* Return every currently-blocked stream position. Convenience wrapper
|
|
2538
2653
|
* around `store().query_streams(cb, { blocked: true })` for the common
|