@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.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
sleep,
|
|
20
20
|
store,
|
|
21
21
|
validate
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-XKCTGUW2.js";
|
|
23
23
|
import {
|
|
24
24
|
ActorSchema,
|
|
25
25
|
CausationEventSchema,
|
|
@@ -868,6 +868,51 @@ var subscribe = (streams) => store().subscribe(streams);
|
|
|
868
868
|
|
|
869
869
|
// src/internal/event-sourcing.ts
|
|
870
870
|
import { patch } from "@rotorsoft/act-patch";
|
|
871
|
+
async function* iterate(source, filter) {
|
|
872
|
+
const state2 = {
|
|
873
|
+
slot: null,
|
|
874
|
+
onProduce: null,
|
|
875
|
+
onConsume: null,
|
|
876
|
+
done: false,
|
|
877
|
+
error: void 0
|
|
878
|
+
};
|
|
879
|
+
const wakeProduce = () => {
|
|
880
|
+
const fn = state2.onProduce;
|
|
881
|
+
state2.onProduce = null;
|
|
882
|
+
if (fn) fn();
|
|
883
|
+
};
|
|
884
|
+
void source.query((event) => {
|
|
885
|
+
state2.slot = event;
|
|
886
|
+
wakeProduce();
|
|
887
|
+
return new Promise((resolve) => {
|
|
888
|
+
state2.onConsume = () => resolve();
|
|
889
|
+
});
|
|
890
|
+
}, filter).then(
|
|
891
|
+
() => {
|
|
892
|
+
state2.done = true;
|
|
893
|
+
wakeProduce();
|
|
894
|
+
},
|
|
895
|
+
(err) => {
|
|
896
|
+
state2.error = err;
|
|
897
|
+
state2.done = true;
|
|
898
|
+
wakeProduce();
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
while (true) {
|
|
902
|
+
if (state2.slot === null && !state2.done)
|
|
903
|
+
await new Promise((resolve) => {
|
|
904
|
+
state2.onProduce = resolve;
|
|
905
|
+
});
|
|
906
|
+
if (state2.error) throw state2.error;
|
|
907
|
+
if (state2.slot === null) return;
|
|
908
|
+
const event = state2.slot;
|
|
909
|
+
state2.slot = null;
|
|
910
|
+
const fn = state2.onConsume;
|
|
911
|
+
state2.onConsume = null;
|
|
912
|
+
fn();
|
|
913
|
+
yield event;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
871
916
|
async function snap(snapshot) {
|
|
872
917
|
try {
|
|
873
918
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -911,7 +956,7 @@ async function scan(source, opts = {}, callback) {
|
|
|
911
956
|
let kept = 0;
|
|
912
957
|
let droppedSnapshots = 0;
|
|
913
958
|
let processed = 0;
|
|
914
|
-
for await (const event of source) {
|
|
959
|
+
for await (const event of iterate(source)) {
|
|
915
960
|
processed++;
|
|
916
961
|
if (!isValid(event)) throw new Error(`Invalid event at index ${processed}`);
|
|
917
962
|
if (on_progress) on_progress({ processed });
|
|
@@ -2629,18 +2674,21 @@ var Act = class {
|
|
|
2629
2674
|
*
|
|
2630
2675
|
* @see {@link Store.restore} for the underlying driver-pattern primitive.
|
|
2631
2676
|
*/
|
|
2632
|
-
async restore(source, opts = {}) {
|
|
2677
|
+
async restore(source, opts = {}, sink) {
|
|
2633
2678
|
return this._scoped(async () => {
|
|
2634
2679
|
const started = Date.now();
|
|
2635
2680
|
if (opts.dry_run) {
|
|
2636
2681
|
const partial = await scan(source, opts);
|
|
2637
2682
|
return { ...partial, duration_ms: Date.now() - started };
|
|
2638
2683
|
}
|
|
2639
|
-
const
|
|
2640
|
-
|
|
2684
|
+
const target = sink ?? (() => {
|
|
2685
|
+
const s = store();
|
|
2686
|
+
if (!s.restore) throw new Error("adapter has no restore capability");
|
|
2687
|
+
return s;
|
|
2688
|
+
})();
|
|
2641
2689
|
let kept = 0;
|
|
2642
2690
|
let dropped = { closed_streams: 0, snapshots: 0, empty_streams: 0 };
|
|
2643
|
-
await
|
|
2691
|
+
await target.restore(async (callback) => {
|
|
2644
2692
|
const partial = await scan(source, opts, callback);
|
|
2645
2693
|
kept = partial.kept;
|
|
2646
2694
|
dropped = partial.dropped;
|
|
@@ -3213,6 +3261,167 @@ function action_builder(state2) {
|
|
|
3213
3261
|
};
|
|
3214
3262
|
return builder;
|
|
3215
3263
|
}
|
|
3264
|
+
|
|
3265
|
+
// src/csv.ts
|
|
3266
|
+
import { createReadStream, createWriteStream } from "fs";
|
|
3267
|
+
import { createInterface } from "readline";
|
|
3268
|
+
var CSV_COLUMNS = [
|
|
3269
|
+
"id",
|
|
3270
|
+
"name",
|
|
3271
|
+
"data",
|
|
3272
|
+
"stream",
|
|
3273
|
+
"version",
|
|
3274
|
+
"created",
|
|
3275
|
+
"meta"
|
|
3276
|
+
];
|
|
3277
|
+
var CsvFile = class {
|
|
3278
|
+
path;
|
|
3279
|
+
blob;
|
|
3280
|
+
constructor(options) {
|
|
3281
|
+
if ("path" in options) {
|
|
3282
|
+
this.path = options.path;
|
|
3283
|
+
this.blob = null;
|
|
3284
|
+
} else {
|
|
3285
|
+
this.path = null;
|
|
3286
|
+
this.blob = options.blob;
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
async query(callback, _filter) {
|
|
3290
|
+
const lines = this.blob !== null ? linesFromBlob(this.blob) : linesFromFile(this.path);
|
|
3291
|
+
let count = 0;
|
|
3292
|
+
let header = null;
|
|
3293
|
+
for await (const line of lines) {
|
|
3294
|
+
if (!line.trim()) continue;
|
|
3295
|
+
const fields = parseCsvLine(line);
|
|
3296
|
+
if (!header) {
|
|
3297
|
+
header = fields;
|
|
3298
|
+
const expected = CSV_COLUMNS.join(",");
|
|
3299
|
+
if (header.join(",") !== expected)
|
|
3300
|
+
throw new Error(`Invalid CSV header. Expected: ${expected}`);
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
if (fields.length !== CSV_COLUMNS.length)
|
|
3304
|
+
throw new Error(
|
|
3305
|
+
`Row ${count + 1}: expected ${CSV_COLUMNS.length} fields, got ${fields.length}`
|
|
3306
|
+
);
|
|
3307
|
+
const event = {
|
|
3308
|
+
id: Number.parseInt(fields[0], 10),
|
|
3309
|
+
name: fields[1],
|
|
3310
|
+
data: JSON.parse(fields[2]),
|
|
3311
|
+
stream: fields[3],
|
|
3312
|
+
version: Number.parseInt(fields[4], 10),
|
|
3313
|
+
created: new Date(fields[5]),
|
|
3314
|
+
meta: JSON.parse(fields[6])
|
|
3315
|
+
};
|
|
3316
|
+
await Promise.resolve(callback(event));
|
|
3317
|
+
count++;
|
|
3318
|
+
}
|
|
3319
|
+
if (header === null)
|
|
3320
|
+
throw new Error("CSV must have a header and at least one row");
|
|
3321
|
+
return count;
|
|
3322
|
+
}
|
|
3323
|
+
async restore(driver) {
|
|
3324
|
+
if (this.path === null)
|
|
3325
|
+
throw new Error(
|
|
3326
|
+
"CsvFile in blob mode is read-only \u2014 provide `path` to write"
|
|
3327
|
+
);
|
|
3328
|
+
const writer = createWriteStream(this.path, {
|
|
3329
|
+
flags: "w",
|
|
3330
|
+
encoding: "utf8"
|
|
3331
|
+
});
|
|
3332
|
+
let nextId = 1;
|
|
3333
|
+
try {
|
|
3334
|
+
await writeLine(writer, CSV_COLUMNS.join(","));
|
|
3335
|
+
await driver(async (event) => {
|
|
3336
|
+
const id = nextId++;
|
|
3337
|
+
const row = [
|
|
3338
|
+
String(id),
|
|
3339
|
+
csvEscape(event.name),
|
|
3340
|
+
csvEscape(JSON.stringify(event.data)),
|
|
3341
|
+
csvEscape(event.stream),
|
|
3342
|
+
String(event.version),
|
|
3343
|
+
event.created.toISOString(),
|
|
3344
|
+
csvEscape(JSON.stringify(event.meta))
|
|
3345
|
+
].join(",");
|
|
3346
|
+
await writeLine(writer, row);
|
|
3347
|
+
return id;
|
|
3348
|
+
});
|
|
3349
|
+
} finally {
|
|
3350
|
+
await new Promise((resolve) => writer.end(resolve));
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
async dispose() {
|
|
3354
|
+
}
|
|
3355
|
+
};
|
|
3356
|
+
async function* linesFromFile(path) {
|
|
3357
|
+
const stream = createReadStream(path, { encoding: "utf8" });
|
|
3358
|
+
const rl = createInterface({
|
|
3359
|
+
input: stream,
|
|
3360
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
3361
|
+
});
|
|
3362
|
+
try {
|
|
3363
|
+
for await (const line of rl) yield line;
|
|
3364
|
+
} finally {
|
|
3365
|
+
rl.close();
|
|
3366
|
+
stream.close();
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
async function* linesFromBlob(blob) {
|
|
3370
|
+
let start = 0;
|
|
3371
|
+
while (start < blob.length) {
|
|
3372
|
+
const nl = blob.indexOf("\n", start);
|
|
3373
|
+
const end = nl === -1 ? blob.length : nl;
|
|
3374
|
+
yield blob.slice(start, end);
|
|
3375
|
+
start = nl === -1 ? blob.length : nl + 1;
|
|
3376
|
+
await Promise.resolve();
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
function parseCsvLine(line) {
|
|
3380
|
+
const fields = [];
|
|
3381
|
+
let i = 0;
|
|
3382
|
+
while (i < line.length) {
|
|
3383
|
+
if (line[i] === '"') {
|
|
3384
|
+
let value = "";
|
|
3385
|
+
i++;
|
|
3386
|
+
while (i < line.length) {
|
|
3387
|
+
if (line[i] === '"' && line[i + 1] === '"') {
|
|
3388
|
+
value += '"';
|
|
3389
|
+
i += 2;
|
|
3390
|
+
} else if (line[i] === '"') {
|
|
3391
|
+
i++;
|
|
3392
|
+
break;
|
|
3393
|
+
} else {
|
|
3394
|
+
value += line[i++];
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
fields.push(value);
|
|
3398
|
+
if (line[i] === ",") i++;
|
|
3399
|
+
} else {
|
|
3400
|
+
const next = line.indexOf(",", i);
|
|
3401
|
+
if (next === -1) {
|
|
3402
|
+
fields.push(line.slice(i));
|
|
3403
|
+
i = line.length;
|
|
3404
|
+
} else {
|
|
3405
|
+
fields.push(line.slice(i, next));
|
|
3406
|
+
i = next + 1;
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
return fields;
|
|
3411
|
+
}
|
|
3412
|
+
function csvEscape(value) {
|
|
3413
|
+
if (/[",\n\r]/.test(value)) return `"${value.replace(/"/g, '""')}"`;
|
|
3414
|
+
return value;
|
|
3415
|
+
}
|
|
3416
|
+
function writeLine(writer, line) {
|
|
3417
|
+
return new Promise((resolve, reject) => {
|
|
3418
|
+
writer.write(`${line}
|
|
3419
|
+
`, (err) => {
|
|
3420
|
+
if (err) reject(err);
|
|
3421
|
+
else resolve();
|
|
3422
|
+
});
|
|
3423
|
+
});
|
|
3424
|
+
}
|
|
3216
3425
|
export {
|
|
3217
3426
|
Act,
|
|
3218
3427
|
ActorSchema,
|
|
@@ -3220,6 +3429,7 @@ export {
|
|
|
3220
3429
|
CommittedMetaSchema,
|
|
3221
3430
|
ConcurrencyError,
|
|
3222
3431
|
ConsoleLogger,
|
|
3432
|
+
CsvFile,
|
|
3223
3433
|
DEFAULT_LANE,
|
|
3224
3434
|
DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
3225
3435
|
DEFAULT_SETTLE_DEBOUNCE_MS,
|