@rotorsoft/act 1.2.0 → 1.3.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 +21 -2
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +38 -1
- 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 +218 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +216 -6
- 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,51 @@ 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
|
+
async function* iterate(source, filter) {
|
|
2093
|
+
const state2 = {
|
|
2094
|
+
slot: null,
|
|
2095
|
+
onProduce: null,
|
|
2096
|
+
onConsume: null,
|
|
2097
|
+
done: false,
|
|
2098
|
+
error: void 0
|
|
2099
|
+
};
|
|
2100
|
+
const wakeProduce = () => {
|
|
2101
|
+
const fn = state2.onProduce;
|
|
2102
|
+
state2.onProduce = null;
|
|
2103
|
+
if (fn) fn();
|
|
2104
|
+
};
|
|
2105
|
+
void source.query((event) => {
|
|
2106
|
+
state2.slot = event;
|
|
2107
|
+
wakeProduce();
|
|
2108
|
+
return new Promise((resolve) => {
|
|
2109
|
+
state2.onConsume = () => resolve();
|
|
2110
|
+
});
|
|
2111
|
+
}, filter).then(
|
|
2112
|
+
() => {
|
|
2113
|
+
state2.done = true;
|
|
2114
|
+
wakeProduce();
|
|
2115
|
+
},
|
|
2116
|
+
(err) => {
|
|
2117
|
+
state2.error = err;
|
|
2118
|
+
state2.done = true;
|
|
2119
|
+
wakeProduce();
|
|
2120
|
+
}
|
|
2121
|
+
);
|
|
2122
|
+
while (true) {
|
|
2123
|
+
if (state2.slot === null && !state2.done)
|
|
2124
|
+
await new Promise((resolve) => {
|
|
2125
|
+
state2.onProduce = resolve;
|
|
2126
|
+
});
|
|
2127
|
+
if (state2.error) throw state2.error;
|
|
2128
|
+
if (state2.slot === null) return;
|
|
2129
|
+
const event = state2.slot;
|
|
2130
|
+
state2.slot = null;
|
|
2131
|
+
const fn = state2.onConsume;
|
|
2132
|
+
state2.onConsume = null;
|
|
2133
|
+
fn();
|
|
2134
|
+
yield event;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2091
2137
|
async function snap(snapshot) {
|
|
2092
2138
|
try {
|
|
2093
2139
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -2131,7 +2177,7 @@ async function scan(source, opts = {}, callback) {
|
|
|
2131
2177
|
let kept = 0;
|
|
2132
2178
|
let droppedSnapshots = 0;
|
|
2133
2179
|
let processed = 0;
|
|
2134
|
-
for await (const event of source) {
|
|
2180
|
+
for await (const event of iterate(source)) {
|
|
2135
2181
|
processed++;
|
|
2136
2182
|
if (!isValid(event)) throw new Error(`Invalid event at index ${processed}`);
|
|
2137
2183
|
if (on_progress) on_progress({ processed });
|
|
@@ -3849,18 +3895,21 @@ var Act = class {
|
|
|
3849
3895
|
*
|
|
3850
3896
|
* @see {@link Store.restore} for the underlying driver-pattern primitive.
|
|
3851
3897
|
*/
|
|
3852
|
-
async restore(source, opts = {}) {
|
|
3898
|
+
async restore(source, opts = {}, sink) {
|
|
3853
3899
|
return this._scoped(async () => {
|
|
3854
3900
|
const started = Date.now();
|
|
3855
3901
|
if (opts.dry_run) {
|
|
3856
3902
|
const partial = await scan(source, opts);
|
|
3857
3903
|
return { ...partial, duration_ms: Date.now() - started };
|
|
3858
3904
|
}
|
|
3859
|
-
const
|
|
3860
|
-
|
|
3905
|
+
const target = sink ?? (() => {
|
|
3906
|
+
const s = store2();
|
|
3907
|
+
if (!s.restore) throw new Error("adapter has no restore capability");
|
|
3908
|
+
return s;
|
|
3909
|
+
})();
|
|
3861
3910
|
let kept = 0;
|
|
3862
3911
|
let dropped = { closed_streams: 0, snapshots: 0, empty_streams: 0 };
|
|
3863
|
-
await
|
|
3912
|
+
await target.restore(async (callback) => {
|
|
3864
3913
|
const partial = await scan(source, opts, callback);
|
|
3865
3914
|
kept = partial.kept;
|
|
3866
3915
|
dropped = partial.dropped;
|
|
@@ -4433,6 +4482,167 @@ function action_builder(state2) {
|
|
|
4433
4482
|
};
|
|
4434
4483
|
return builder;
|
|
4435
4484
|
}
|
|
4485
|
+
|
|
4486
|
+
// src/csv.ts
|
|
4487
|
+
var import_node_fs = require("fs");
|
|
4488
|
+
var import_node_readline = require("readline");
|
|
4489
|
+
var CSV_COLUMNS = [
|
|
4490
|
+
"id",
|
|
4491
|
+
"name",
|
|
4492
|
+
"data",
|
|
4493
|
+
"stream",
|
|
4494
|
+
"version",
|
|
4495
|
+
"created",
|
|
4496
|
+
"meta"
|
|
4497
|
+
];
|
|
4498
|
+
var CsvFile = class {
|
|
4499
|
+
path;
|
|
4500
|
+
blob;
|
|
4501
|
+
constructor(options) {
|
|
4502
|
+
if ("path" in options) {
|
|
4503
|
+
this.path = options.path;
|
|
4504
|
+
this.blob = null;
|
|
4505
|
+
} else {
|
|
4506
|
+
this.path = null;
|
|
4507
|
+
this.blob = options.blob;
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
async query(callback, _filter) {
|
|
4511
|
+
const lines = this.blob !== null ? linesFromBlob(this.blob) : linesFromFile(this.path);
|
|
4512
|
+
let count = 0;
|
|
4513
|
+
let header = null;
|
|
4514
|
+
for await (const line of lines) {
|
|
4515
|
+
if (!line.trim()) continue;
|
|
4516
|
+
const fields = parseCsvLine(line);
|
|
4517
|
+
if (!header) {
|
|
4518
|
+
header = fields;
|
|
4519
|
+
const expected = CSV_COLUMNS.join(",");
|
|
4520
|
+
if (header.join(",") !== expected)
|
|
4521
|
+
throw new Error(`Invalid CSV header. Expected: ${expected}`);
|
|
4522
|
+
continue;
|
|
4523
|
+
}
|
|
4524
|
+
if (fields.length !== CSV_COLUMNS.length)
|
|
4525
|
+
throw new Error(
|
|
4526
|
+
`Row ${count + 1}: expected ${CSV_COLUMNS.length} fields, got ${fields.length}`
|
|
4527
|
+
);
|
|
4528
|
+
const event = {
|
|
4529
|
+
id: Number.parseInt(fields[0], 10),
|
|
4530
|
+
name: fields[1],
|
|
4531
|
+
data: JSON.parse(fields[2]),
|
|
4532
|
+
stream: fields[3],
|
|
4533
|
+
version: Number.parseInt(fields[4], 10),
|
|
4534
|
+
created: new Date(fields[5]),
|
|
4535
|
+
meta: JSON.parse(fields[6])
|
|
4536
|
+
};
|
|
4537
|
+
await Promise.resolve(callback(event));
|
|
4538
|
+
count++;
|
|
4539
|
+
}
|
|
4540
|
+
if (header === null)
|
|
4541
|
+
throw new Error("CSV must have a header and at least one row");
|
|
4542
|
+
return count;
|
|
4543
|
+
}
|
|
4544
|
+
async restore(driver) {
|
|
4545
|
+
if (this.path === null)
|
|
4546
|
+
throw new Error(
|
|
4547
|
+
"CsvFile in blob mode is read-only \u2014 provide `path` to write"
|
|
4548
|
+
);
|
|
4549
|
+
const writer = (0, import_node_fs.createWriteStream)(this.path, {
|
|
4550
|
+
flags: "w",
|
|
4551
|
+
encoding: "utf8"
|
|
4552
|
+
});
|
|
4553
|
+
let nextId = 1;
|
|
4554
|
+
try {
|
|
4555
|
+
await writeLine(writer, CSV_COLUMNS.join(","));
|
|
4556
|
+
await driver(async (event) => {
|
|
4557
|
+
const id = nextId++;
|
|
4558
|
+
const row = [
|
|
4559
|
+
String(id),
|
|
4560
|
+
csvEscape(event.name),
|
|
4561
|
+
csvEscape(JSON.stringify(event.data)),
|
|
4562
|
+
csvEscape(event.stream),
|
|
4563
|
+
String(event.version),
|
|
4564
|
+
event.created.toISOString(),
|
|
4565
|
+
csvEscape(JSON.stringify(event.meta))
|
|
4566
|
+
].join(",");
|
|
4567
|
+
await writeLine(writer, row);
|
|
4568
|
+
return id;
|
|
4569
|
+
});
|
|
4570
|
+
} finally {
|
|
4571
|
+
await new Promise((resolve) => writer.end(resolve));
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
async dispose() {
|
|
4575
|
+
}
|
|
4576
|
+
};
|
|
4577
|
+
async function* linesFromFile(path) {
|
|
4578
|
+
const stream = (0, import_node_fs.createReadStream)(path, { encoding: "utf8" });
|
|
4579
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
4580
|
+
input: stream,
|
|
4581
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
4582
|
+
});
|
|
4583
|
+
try {
|
|
4584
|
+
for await (const line of rl) yield line;
|
|
4585
|
+
} finally {
|
|
4586
|
+
rl.close();
|
|
4587
|
+
stream.close();
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
async function* linesFromBlob(blob) {
|
|
4591
|
+
let start = 0;
|
|
4592
|
+
while (start < blob.length) {
|
|
4593
|
+
const nl = blob.indexOf("\n", start);
|
|
4594
|
+
const end = nl === -1 ? blob.length : nl;
|
|
4595
|
+
yield blob.slice(start, end);
|
|
4596
|
+
start = nl === -1 ? blob.length : nl + 1;
|
|
4597
|
+
await Promise.resolve();
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
function parseCsvLine(line) {
|
|
4601
|
+
const fields = [];
|
|
4602
|
+
let i = 0;
|
|
4603
|
+
while (i < line.length) {
|
|
4604
|
+
if (line[i] === '"') {
|
|
4605
|
+
let value = "";
|
|
4606
|
+
i++;
|
|
4607
|
+
while (i < line.length) {
|
|
4608
|
+
if (line[i] === '"' && line[i + 1] === '"') {
|
|
4609
|
+
value += '"';
|
|
4610
|
+
i += 2;
|
|
4611
|
+
} else if (line[i] === '"') {
|
|
4612
|
+
i++;
|
|
4613
|
+
break;
|
|
4614
|
+
} else {
|
|
4615
|
+
value += line[i++];
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
fields.push(value);
|
|
4619
|
+
if (line[i] === ",") i++;
|
|
4620
|
+
} else {
|
|
4621
|
+
const next = line.indexOf(",", i);
|
|
4622
|
+
if (next === -1) {
|
|
4623
|
+
fields.push(line.slice(i));
|
|
4624
|
+
i = line.length;
|
|
4625
|
+
} else {
|
|
4626
|
+
fields.push(line.slice(i, next));
|
|
4627
|
+
i = next + 1;
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
return fields;
|
|
4632
|
+
}
|
|
4633
|
+
function csvEscape(value) {
|
|
4634
|
+
if (/[",\n\r]/.test(value)) return `"${value.replace(/"/g, '""')}"`;
|
|
4635
|
+
return value;
|
|
4636
|
+
}
|
|
4637
|
+
function writeLine(writer, line) {
|
|
4638
|
+
return new Promise((resolve, reject) => {
|
|
4639
|
+
writer.write(`${line}
|
|
4640
|
+
`, (err) => {
|
|
4641
|
+
if (err) reject(err);
|
|
4642
|
+
else resolve();
|
|
4643
|
+
});
|
|
4644
|
+
});
|
|
4645
|
+
}
|
|
4436
4646
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4437
4647
|
0 && (module.exports = {
|
|
4438
4648
|
Act,
|
|
@@ -4441,6 +4651,7 @@ function action_builder(state2) {
|
|
|
4441
4651
|
CommittedMetaSchema,
|
|
4442
4652
|
ConcurrencyError,
|
|
4443
4653
|
ConsoleLogger,
|
|
4654
|
+
CsvFile,
|
|
4444
4655
|
DEFAULT_LANE,
|
|
4445
4656
|
DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
4446
4657
|
DEFAULT_SETTLE_DEBOUNCE_MS,
|