@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/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  sleep,
20
20
  store,
21
21
  validate
22
- } from "./chunk-J6NDEEXC.js";
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 s = store();
2640
- if (!s.restore) throw new Error("adapter has no restore capability");
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 s.restore(async (callback) => {
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,