@rotorsoft/act 1.2.0 → 1.4.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 +2 -2
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/csv.d.ts +41 -0
- package/dist/@types/csv.d.ts.map +1 -0
- package/dist/@types/index.d.ts +1 -0
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +12 -3
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +59 -6
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/audit.d.ts +2 -1
- package/dist/@types/types/audit.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +2 -2
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/{chunk-J6NDEEXC.js → chunk-XKCTGUW2.js} +3 -3
- package/dist/chunk-XKCTGUW2.js.map +1 -0
- package/dist/index.cjs +230 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +228 -40
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +2 -2
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-J6NDEEXC.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
CommittedMetaSchema: () => CommittedMetaSchema,
|
|
37
37
|
ConcurrencyError: () => ConcurrencyError,
|
|
38
38
|
ConsoleLogger: () => ConsoleLogger,
|
|
39
|
+
CsvFile: () => CsvFile,
|
|
39
40
|
DEFAULT_LANE: () => DEFAULT_LANE,
|
|
40
41
|
DEFAULT_MAX_SUBSCRIBED_STREAMS: () => DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
41
42
|
DEFAULT_SETTLE_DEBOUNCE_MS: () => DEFAULT_SETTLE_DEBOUNCE_MS,
|
|
@@ -717,7 +718,7 @@ var InMemoryStore = class {
|
|
|
717
718
|
continue;
|
|
718
719
|
if (query.after && e.id <= query.after) break;
|
|
719
720
|
if (query.created_after && e.created <= query.created_after) break;
|
|
720
|
-
callback(e);
|
|
721
|
+
await Promise.resolve(callback(e));
|
|
721
722
|
count++;
|
|
722
723
|
if (query?.limit && count >= query.limit) break;
|
|
723
724
|
}
|
|
@@ -729,7 +730,7 @@ var InMemoryStore = class {
|
|
|
729
730
|
if (query?.created_after && e.created <= query.created_after) continue;
|
|
730
731
|
if (query?.before && e.id >= query.before) break;
|
|
731
732
|
if (query?.created_before && e.created >= query.created_before) break;
|
|
732
|
-
callback(e);
|
|
733
|
+
await Promise.resolve(callback(e));
|
|
733
734
|
count++;
|
|
734
735
|
if (query?.limit && count >= query.limit) break;
|
|
735
736
|
}
|
|
@@ -2088,6 +2089,7 @@ var subscribe = (streams) => store2().subscribe(streams);
|
|
|
2088
2089
|
|
|
2089
2090
|
// src/internal/event-sourcing.ts
|
|
2090
2091
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
2092
|
+
var DEFAULT_BATCH = 500;
|
|
2091
2093
|
async function snap(snapshot) {
|
|
2092
2094
|
try {
|
|
2093
2095
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -2119,7 +2121,7 @@ async function tombstone(stream, expectedVersion, correlation) {
|
|
|
2119
2121
|
throw error;
|
|
2120
2122
|
}
|
|
2121
2123
|
}
|
|
2122
|
-
function
|
|
2124
|
+
function is_valid(event) {
|
|
2123
2125
|
if (event.version < 0) return false;
|
|
2124
2126
|
if (!(event.created instanceof Date) || Number.isNaN(event.created.getTime()))
|
|
2125
2127
|
return false;
|
|
@@ -2127,48 +2129,70 @@ function isValid(event) {
|
|
|
2127
2129
|
}
|
|
2128
2130
|
async function scan(source, opts = {}, callback) {
|
|
2129
2131
|
const { drop_snapshots = false, on_progress } = opts;
|
|
2130
|
-
const
|
|
2132
|
+
const limit = opts.batch_size ?? DEFAULT_BATCH;
|
|
2133
|
+
const id_map = /* @__PURE__ */ new Map();
|
|
2131
2134
|
let kept = 0;
|
|
2132
|
-
let
|
|
2135
|
+
let dropped_snaps = 0;
|
|
2133
2136
|
let processed = 0;
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2137
|
+
let at;
|
|
2138
|
+
let max_id;
|
|
2139
|
+
const probed = await source.query(
|
|
2140
|
+
(e) => {
|
|
2141
|
+
max_id = e.id;
|
|
2142
|
+
},
|
|
2143
|
+
{ backward: true, limit: 1 }
|
|
2144
|
+
);
|
|
2145
|
+
if (probed !== 1) max_id = void 0;
|
|
2146
|
+
while (true) {
|
|
2147
|
+
let got = 0;
|
|
2148
|
+
let id;
|
|
2149
|
+
await source.query(
|
|
2150
|
+
async (event) => {
|
|
2151
|
+
got++;
|
|
2152
|
+
id = event.id;
|
|
2153
|
+
processed++;
|
|
2154
|
+
if (!is_valid(event))
|
|
2155
|
+
throw new Error(`Invalid event at index ${processed}`);
|
|
2156
|
+
if (on_progress) on_progress({ processed, id: event.id, max_id });
|
|
2157
|
+
if (drop_snapshots && event.name === SNAP_EVENT) {
|
|
2158
|
+
dropped_snaps++;
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
if (!callback) {
|
|
2162
|
+
kept++;
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
let remapped = event;
|
|
2166
|
+
const caused_by = event.meta.causation.event?.id;
|
|
2167
|
+
if (caused_by !== void 0) {
|
|
2168
|
+
const new_caused_by = id_map.get(caused_by);
|
|
2169
|
+
if (new_caused_by !== void 0 && new_caused_by !== caused_by) {
|
|
2170
|
+
remapped = {
|
|
2171
|
+
...event,
|
|
2172
|
+
meta: {
|
|
2173
|
+
...event.meta,
|
|
2174
|
+
causation: {
|
|
2175
|
+
...event.meta.causation,
|
|
2176
|
+
event: { ...event.meta.causation.event, id: new_caused_by }
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
};
|
|
2159
2180
|
}
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2181
|
+
}
|
|
2182
|
+
const new_id = await callback(remapped);
|
|
2183
|
+
id_map.set(event.id, new_id);
|
|
2184
|
+
kept++;
|
|
2185
|
+
},
|
|
2186
|
+
{ after: at, limit }
|
|
2187
|
+
);
|
|
2188
|
+
if (got !== limit) break;
|
|
2189
|
+
at = id;
|
|
2166
2190
|
}
|
|
2167
2191
|
return {
|
|
2168
2192
|
kept,
|
|
2169
2193
|
dropped: {
|
|
2170
2194
|
closed_streams: 0,
|
|
2171
|
-
snapshots:
|
|
2195
|
+
snapshots: dropped_snaps,
|
|
2172
2196
|
empty_streams: 0
|
|
2173
2197
|
}
|
|
2174
2198
|
};
|
|
@@ -3849,18 +3873,21 @@ var Act = class {
|
|
|
3849
3873
|
*
|
|
3850
3874
|
* @see {@link Store.restore} for the underlying driver-pattern primitive.
|
|
3851
3875
|
*/
|
|
3852
|
-
async restore(source, opts = {}) {
|
|
3876
|
+
async restore(source, opts = {}, sink) {
|
|
3853
3877
|
return this._scoped(async () => {
|
|
3854
3878
|
const started = Date.now();
|
|
3855
3879
|
if (opts.dry_run) {
|
|
3856
3880
|
const partial = await scan(source, opts);
|
|
3857
3881
|
return { ...partial, duration_ms: Date.now() - started };
|
|
3858
3882
|
}
|
|
3859
|
-
const
|
|
3860
|
-
|
|
3883
|
+
const target = sink ?? (() => {
|
|
3884
|
+
const s = store2();
|
|
3885
|
+
if (!s.restore) throw new Error("adapter has no restore capability");
|
|
3886
|
+
return s;
|
|
3887
|
+
})();
|
|
3861
3888
|
let kept = 0;
|
|
3862
3889
|
let dropped = { closed_streams: 0, snapshots: 0, empty_streams: 0 };
|
|
3863
|
-
await
|
|
3890
|
+
await target.restore(async (callback) => {
|
|
3864
3891
|
const partial = await scan(source, opts, callback);
|
|
3865
3892
|
kept = partial.kept;
|
|
3866
3893
|
dropped = partial.dropped;
|
|
@@ -4433,6 +4460,167 @@ function action_builder(state2) {
|
|
|
4433
4460
|
};
|
|
4434
4461
|
return builder;
|
|
4435
4462
|
}
|
|
4463
|
+
|
|
4464
|
+
// src/csv.ts
|
|
4465
|
+
var import_node_fs = require("fs");
|
|
4466
|
+
var import_node_readline = require("readline");
|
|
4467
|
+
var CSV_COLUMNS = [
|
|
4468
|
+
"id",
|
|
4469
|
+
"name",
|
|
4470
|
+
"data",
|
|
4471
|
+
"stream",
|
|
4472
|
+
"version",
|
|
4473
|
+
"created",
|
|
4474
|
+
"meta"
|
|
4475
|
+
];
|
|
4476
|
+
var CsvFile = class {
|
|
4477
|
+
path;
|
|
4478
|
+
blob;
|
|
4479
|
+
constructor(options) {
|
|
4480
|
+
if ("path" in options) {
|
|
4481
|
+
this.path = options.path;
|
|
4482
|
+
this.blob = null;
|
|
4483
|
+
} else {
|
|
4484
|
+
this.path = null;
|
|
4485
|
+
this.blob = options.blob;
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
async query(callback, _filter) {
|
|
4489
|
+
const lines = this.blob !== null ? linesFromBlob(this.blob) : linesFromFile(this.path);
|
|
4490
|
+
let count = 0;
|
|
4491
|
+
let header = null;
|
|
4492
|
+
for await (const line of lines) {
|
|
4493
|
+
if (!line.trim()) continue;
|
|
4494
|
+
const fields = parseCsvLine(line);
|
|
4495
|
+
if (!header) {
|
|
4496
|
+
header = fields;
|
|
4497
|
+
const expected = CSV_COLUMNS.join(",");
|
|
4498
|
+
if (header.join(",") !== expected)
|
|
4499
|
+
throw new Error(`Invalid CSV header. Expected: ${expected}`);
|
|
4500
|
+
continue;
|
|
4501
|
+
}
|
|
4502
|
+
if (fields.length !== CSV_COLUMNS.length)
|
|
4503
|
+
throw new Error(
|
|
4504
|
+
`Row ${count + 1}: expected ${CSV_COLUMNS.length} fields, got ${fields.length}`
|
|
4505
|
+
);
|
|
4506
|
+
const event = {
|
|
4507
|
+
id: Number.parseInt(fields[0], 10),
|
|
4508
|
+
name: fields[1],
|
|
4509
|
+
data: JSON.parse(fields[2]),
|
|
4510
|
+
stream: fields[3],
|
|
4511
|
+
version: Number.parseInt(fields[4], 10),
|
|
4512
|
+
created: new Date(fields[5]),
|
|
4513
|
+
meta: JSON.parse(fields[6])
|
|
4514
|
+
};
|
|
4515
|
+
await Promise.resolve(callback(event));
|
|
4516
|
+
count++;
|
|
4517
|
+
}
|
|
4518
|
+
if (header === null)
|
|
4519
|
+
throw new Error("CSV must have a header and at least one row");
|
|
4520
|
+
return count;
|
|
4521
|
+
}
|
|
4522
|
+
async restore(driver) {
|
|
4523
|
+
if (this.path === null)
|
|
4524
|
+
throw new Error(
|
|
4525
|
+
"CsvFile in blob mode is read-only \u2014 provide `path` to write"
|
|
4526
|
+
);
|
|
4527
|
+
const writer = (0, import_node_fs.createWriteStream)(this.path, {
|
|
4528
|
+
flags: "w",
|
|
4529
|
+
encoding: "utf8"
|
|
4530
|
+
});
|
|
4531
|
+
let nextId = 1;
|
|
4532
|
+
try {
|
|
4533
|
+
await writeLine(writer, CSV_COLUMNS.join(","));
|
|
4534
|
+
await driver(async (event) => {
|
|
4535
|
+
const id = nextId++;
|
|
4536
|
+
const row = [
|
|
4537
|
+
String(id),
|
|
4538
|
+
csvEscape(event.name),
|
|
4539
|
+
csvEscape(JSON.stringify(event.data)),
|
|
4540
|
+
csvEscape(event.stream),
|
|
4541
|
+
String(event.version),
|
|
4542
|
+
event.created.toISOString(),
|
|
4543
|
+
csvEscape(JSON.stringify(event.meta))
|
|
4544
|
+
].join(",");
|
|
4545
|
+
await writeLine(writer, row);
|
|
4546
|
+
return id;
|
|
4547
|
+
});
|
|
4548
|
+
} finally {
|
|
4549
|
+
await new Promise((resolve) => writer.end(resolve));
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
async dispose() {
|
|
4553
|
+
}
|
|
4554
|
+
};
|
|
4555
|
+
async function* linesFromFile(path) {
|
|
4556
|
+
const stream = (0, import_node_fs.createReadStream)(path, { encoding: "utf8" });
|
|
4557
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
4558
|
+
input: stream,
|
|
4559
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
4560
|
+
});
|
|
4561
|
+
try {
|
|
4562
|
+
for await (const line of rl) yield line;
|
|
4563
|
+
} finally {
|
|
4564
|
+
rl.close();
|
|
4565
|
+
stream.close();
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
async function* linesFromBlob(blob) {
|
|
4569
|
+
let start = 0;
|
|
4570
|
+
while (start < blob.length) {
|
|
4571
|
+
const nl = blob.indexOf("\n", start);
|
|
4572
|
+
const end = nl === -1 ? blob.length : nl;
|
|
4573
|
+
yield blob.slice(start, end);
|
|
4574
|
+
start = nl === -1 ? blob.length : nl + 1;
|
|
4575
|
+
await Promise.resolve();
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
function parseCsvLine(line) {
|
|
4579
|
+
const fields = [];
|
|
4580
|
+
let i = 0;
|
|
4581
|
+
while (i < line.length) {
|
|
4582
|
+
if (line[i] === '"') {
|
|
4583
|
+
let value = "";
|
|
4584
|
+
i++;
|
|
4585
|
+
while (i < line.length) {
|
|
4586
|
+
if (line[i] === '"' && line[i + 1] === '"') {
|
|
4587
|
+
value += '"';
|
|
4588
|
+
i += 2;
|
|
4589
|
+
} else if (line[i] === '"') {
|
|
4590
|
+
i++;
|
|
4591
|
+
break;
|
|
4592
|
+
} else {
|
|
4593
|
+
value += line[i++];
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
fields.push(value);
|
|
4597
|
+
if (line[i] === ",") i++;
|
|
4598
|
+
} else {
|
|
4599
|
+
const next = line.indexOf(",", i);
|
|
4600
|
+
if (next === -1) {
|
|
4601
|
+
fields.push(line.slice(i));
|
|
4602
|
+
i = line.length;
|
|
4603
|
+
} else {
|
|
4604
|
+
fields.push(line.slice(i, next));
|
|
4605
|
+
i = next + 1;
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
return fields;
|
|
4610
|
+
}
|
|
4611
|
+
function csvEscape(value) {
|
|
4612
|
+
if (/[",\n\r]/.test(value)) return `"${value.replace(/"/g, '""')}"`;
|
|
4613
|
+
return value;
|
|
4614
|
+
}
|
|
4615
|
+
function writeLine(writer, line) {
|
|
4616
|
+
return new Promise((resolve, reject) => {
|
|
4617
|
+
writer.write(`${line}
|
|
4618
|
+
`, (err) => {
|
|
4619
|
+
if (err) reject(err);
|
|
4620
|
+
else resolve();
|
|
4621
|
+
});
|
|
4622
|
+
});
|
|
4623
|
+
}
|
|
4436
4624
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4437
4625
|
0 && (module.exports = {
|
|
4438
4626
|
Act,
|
|
@@ -4441,6 +4629,7 @@ function action_builder(state2) {
|
|
|
4441
4629
|
CommittedMetaSchema,
|
|
4442
4630
|
ConcurrencyError,
|
|
4443
4631
|
ConsoleLogger,
|
|
4632
|
+
CsvFile,
|
|
4444
4633
|
DEFAULT_LANE,
|
|
4445
4634
|
DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
4446
4635
|
DEFAULT_SETTLE_DEBOUNCE_MS,
|