@rotorsoft/act 1.1.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 +44 -1
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +9 -12
- package/dist/@types/adapters/in-memory-store.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 +36 -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 +115 -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 +45 -124
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/{chunk-TN4XS7WE.js → chunk-XKCTGUW2.js} +18 -54
- package/dist/chunk-XKCTGUW2.js.map +1 -0
- package/dist/index.cjs +343 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +326 -1
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +17 -53
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-TN4XS7WE.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
|
}
|
|
@@ -1154,23 +1155,19 @@ var InMemoryStore = class {
|
|
|
1154
1155
|
return result;
|
|
1155
1156
|
}
|
|
1156
1157
|
/**
|
|
1157
|
-
* Atomically rebuild the store
|
|
1158
|
+
* Atomically wipe-and-rebuild the store under an in-process snapshot.
|
|
1158
1159
|
*
|
|
1159
|
-
* Captures every index state up front, clears it, then
|
|
1160
|
-
*
|
|
1161
|
-
* the
|
|
1162
|
-
* perspective.
|
|
1160
|
+
* Captures every index state up front, clears it, then hands the
|
|
1161
|
+
* orchestrator a per-event insert `callback` via the driver. Any
|
|
1162
|
+
* throw inside the driver restores the snapshot, leaving the store
|
|
1163
|
+
* byte-for-byte unchanged from the operator's perspective.
|
|
1163
1164
|
*
|
|
1164
|
-
* `id`s are reassigned `0..N-1` as
|
|
1165
|
+
* `id`s are reassigned `0..N-1` as events arrive (matching the
|
|
1165
1166
|
* adapter's commit-id convention — InMemory uses `_events.length`).
|
|
1166
|
-
* `created` is preserved verbatim from the source.
|
|
1167
|
-
* references in `meta.causation.event.id` are remapped via the
|
|
1168
|
-
* `old → new` table that's built as rows land — references to ids
|
|
1169
|
-
* not in the source pass through unchanged.
|
|
1167
|
+
* `created` is preserved verbatim from the source.
|
|
1170
1168
|
*/
|
|
1171
|
-
async restore(
|
|
1169
|
+
async restore(driver) {
|
|
1172
1170
|
await sleep();
|
|
1173
|
-
const started = Date.now();
|
|
1174
1171
|
const prevEvents = this._events;
|
|
1175
1172
|
const prevStreams = this._streams;
|
|
1176
1173
|
const prevStreamVersions = this._streamVersions;
|
|
@@ -1181,50 +1178,18 @@ var InMemoryStore = class {
|
|
|
1181
1178
|
this._streamVersions = /* @__PURE__ */ new Map();
|
|
1182
1179
|
this._maxEventIdByStream = /* @__PURE__ */ new Map();
|
|
1183
1180
|
this._maxNonSnapEventId = -1;
|
|
1184
|
-
const idMap = /* @__PURE__ */ new Map();
|
|
1185
1181
|
try {
|
|
1186
|
-
|
|
1187
|
-
for await (const row of source) {
|
|
1182
|
+
await driver(async (event) => {
|
|
1188
1183
|
const id = this._events.length;
|
|
1189
|
-
const
|
|
1190
|
-
let meta = row.meta;
|
|
1191
|
-
const causedBy = meta.causation.event?.id;
|
|
1192
|
-
if (causedBy !== void 0) {
|
|
1193
|
-
const remapped = idMap.get(causedBy);
|
|
1194
|
-
if (remapped !== void 0 && remapped !== causedBy) {
|
|
1195
|
-
meta = {
|
|
1196
|
-
...meta,
|
|
1197
|
-
causation: {
|
|
1198
|
-
...meta.causation,
|
|
1199
|
-
event: { ...meta.causation.event, id: remapped }
|
|
1200
|
-
}
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
const committed = {
|
|
1205
|
-
id,
|
|
1206
|
-
stream: row.stream,
|
|
1207
|
-
version: row.version,
|
|
1208
|
-
created,
|
|
1209
|
-
name: row.name,
|
|
1210
|
-
data: row.data,
|
|
1211
|
-
meta
|
|
1212
|
-
};
|
|
1184
|
+
const committed = { ...event, id };
|
|
1213
1185
|
this._events.push(committed);
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
this._maxEventIdByStream.set(row.stream, id);
|
|
1186
|
+
this._streamVersions.set(event.stream, event.version);
|
|
1187
|
+
if (event.name !== SNAP_EVENT) {
|
|
1188
|
+
this._maxEventIdByStream.set(event.stream, id);
|
|
1218
1189
|
this._maxNonSnapEventId = id;
|
|
1219
1190
|
}
|
|
1220
|
-
|
|
1221
|
-
}
|
|
1222
|
-
return {
|
|
1223
|
-
kept,
|
|
1224
|
-
duration_ms: Date.now() - started,
|
|
1225
|
-
dropped: { closed_streams: 0, snapshots: 0, empty_streams: 0 },
|
|
1226
|
-
dry_run: false
|
|
1227
|
-
};
|
|
1191
|
+
return id;
|
|
1192
|
+
});
|
|
1228
1193
|
} catch (err) {
|
|
1229
1194
|
this._events = prevEvents;
|
|
1230
1195
|
this._streams = prevStreams;
|
|
@@ -2124,6 +2089,51 @@ var subscribe = (streams) => store2().subscribe(streams);
|
|
|
2124
2089
|
|
|
2125
2090
|
// src/internal/event-sourcing.ts
|
|
2126
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
|
+
}
|
|
2127
2137
|
async function snap(snapshot) {
|
|
2128
2138
|
try {
|
|
2129
2139
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -2155,6 +2165,60 @@ async function tombstone(stream, expectedVersion, correlation) {
|
|
|
2155
2165
|
throw error;
|
|
2156
2166
|
}
|
|
2157
2167
|
}
|
|
2168
|
+
function isValid(event) {
|
|
2169
|
+
if (event.version < 0) return false;
|
|
2170
|
+
if (!(event.created instanceof Date) || Number.isNaN(event.created.getTime()))
|
|
2171
|
+
return false;
|
|
2172
|
+
return true;
|
|
2173
|
+
}
|
|
2174
|
+
async function scan(source, opts = {}, callback) {
|
|
2175
|
+
const { drop_snapshots = false, on_progress } = opts;
|
|
2176
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
2177
|
+
let kept = 0;
|
|
2178
|
+
let droppedSnapshots = 0;
|
|
2179
|
+
let processed = 0;
|
|
2180
|
+
for await (const event of iterate(source)) {
|
|
2181
|
+
processed++;
|
|
2182
|
+
if (!isValid(event)) throw new Error(`Invalid event at index ${processed}`);
|
|
2183
|
+
if (on_progress) on_progress({ processed });
|
|
2184
|
+
if (drop_snapshots && event.name === SNAP_EVENT) {
|
|
2185
|
+
droppedSnapshots++;
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
if (!callback) {
|
|
2189
|
+
kept++;
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
2192
|
+
let remapped = event;
|
|
2193
|
+
const causedBy = event.meta.causation.event?.id;
|
|
2194
|
+
if (causedBy !== void 0) {
|
|
2195
|
+
const newCausedBy = idMap.get(causedBy);
|
|
2196
|
+
if (newCausedBy !== void 0 && newCausedBy !== causedBy) {
|
|
2197
|
+
remapped = {
|
|
2198
|
+
...event,
|
|
2199
|
+
meta: {
|
|
2200
|
+
...event.meta,
|
|
2201
|
+
causation: {
|
|
2202
|
+
...event.meta.causation,
|
|
2203
|
+
event: { ...event.meta.causation.event, id: newCausedBy }
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
const newId = await callback(remapped);
|
|
2210
|
+
idMap.set(event.id, newId);
|
|
2211
|
+
kept++;
|
|
2212
|
+
}
|
|
2213
|
+
return {
|
|
2214
|
+
kept,
|
|
2215
|
+
dropped: {
|
|
2216
|
+
closed_streams: 0,
|
|
2217
|
+
snapshots: droppedSnapshots,
|
|
2218
|
+
empty_streams: 0
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2158
2222
|
async function load(me, stream, callback, asOf) {
|
|
2159
2223
|
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
2160
2224
|
const cached = timeTravel ? void 0 : await cache2().get(stream);
|
|
@@ -3789,6 +3853,70 @@ var Act = class {
|
|
|
3789
3853
|
return count;
|
|
3790
3854
|
});
|
|
3791
3855
|
}
|
|
3856
|
+
/**
|
|
3857
|
+
* Atomically wipe the store and rebuild it from an async stream of
|
|
3858
|
+
* committed events. The framework owns iteration, validation,
|
|
3859
|
+
* `drop_snapshots` filtering, `on_progress`, and the per-call
|
|
3860
|
+
* `old → new` causation remap; the adapter's {@link Store.restore}
|
|
3861
|
+
* driver supplies the transaction lifecycle and per-event insert.
|
|
3862
|
+
*
|
|
3863
|
+
* Throws if the adapter has no restore capability. Throws on the
|
|
3864
|
+
* first invalid event (negative version, malformed `created`) with
|
|
3865
|
+
* the running index in the message; atomic transaction rollback in
|
|
3866
|
+
* the adapter means a failing restore leaves the store byte-for-byte
|
|
3867
|
+
* unchanged.
|
|
3868
|
+
*
|
|
3869
|
+
* @param source - Async stream of events in target order. Streamed
|
|
3870
|
+
* rather than buffered so multi-million-event backups don't OOM.
|
|
3871
|
+
* Each event's original `id` is used as a causation lookup key but
|
|
3872
|
+
* never written through — adapters renumber densely.
|
|
3873
|
+
* @param opts - {@link ScanOptions}. `drop_snapshots` skips
|
|
3874
|
+
* `__snapshot__` events (counted in the result); `on_progress`
|
|
3875
|
+
* fires once per event.
|
|
3876
|
+
* @returns {@link ScanResult} with `kept`, `duration_ms`, and
|
|
3877
|
+
* `dropped` per-category counters.
|
|
3878
|
+
*
|
|
3879
|
+
* @example Round-trip a CSV backup
|
|
3880
|
+
* ```typescript
|
|
3881
|
+
* async function* parseCsv(blob: string) {
|
|
3882
|
+
* for (const line of blob.split("\n").slice(1)) {
|
|
3883
|
+
* const [id, name, data, stream, version, created, meta] = parse(line);
|
|
3884
|
+
* yield {
|
|
3885
|
+
* id: +id, name, data: JSON.parse(data), stream,
|
|
3886
|
+
* version: +version, created: new Date(created),
|
|
3887
|
+
* meta: JSON.parse(meta),
|
|
3888
|
+
* };
|
|
3889
|
+
* }
|
|
3890
|
+
* }
|
|
3891
|
+
* const result = await app.restore(parseCsv(csvBlob), {});
|
|
3892
|
+
* console.log(`Restored ${result.kept} events in ${result.duration_ms}ms`);
|
|
3893
|
+
* await cache().clear(); // operator's responsibility
|
|
3894
|
+
* ```
|
|
3895
|
+
*
|
|
3896
|
+
* @see {@link Store.restore} for the underlying driver-pattern primitive.
|
|
3897
|
+
*/
|
|
3898
|
+
async restore(source, opts = {}, sink) {
|
|
3899
|
+
return this._scoped(async () => {
|
|
3900
|
+
const started = Date.now();
|
|
3901
|
+
if (opts.dry_run) {
|
|
3902
|
+
const partial = await scan(source, opts);
|
|
3903
|
+
return { ...partial, duration_ms: Date.now() - started };
|
|
3904
|
+
}
|
|
3905
|
+
const target = sink ?? (() => {
|
|
3906
|
+
const s = store2();
|
|
3907
|
+
if (!s.restore) throw new Error("adapter has no restore capability");
|
|
3908
|
+
return s;
|
|
3909
|
+
})();
|
|
3910
|
+
let kept = 0;
|
|
3911
|
+
let dropped = { closed_streams: 0, snapshots: 0, empty_streams: 0 };
|
|
3912
|
+
await target.restore(async (callback) => {
|
|
3913
|
+
const partial = await scan(source, opts, callback);
|
|
3914
|
+
kept = partial.kept;
|
|
3915
|
+
dropped = partial.dropped;
|
|
3916
|
+
});
|
|
3917
|
+
return { kept, dropped, duration_ms: Date.now() - started };
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3792
3920
|
/**
|
|
3793
3921
|
* Return every currently-blocked stream position. Convenience wrapper
|
|
3794
3922
|
* around `store().query_streams(cb, { blocked: true })` for the common
|
|
@@ -4354,6 +4482,167 @@ function action_builder(state2) {
|
|
|
4354
4482
|
};
|
|
4355
4483
|
return builder;
|
|
4356
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
|
+
}
|
|
4357
4646
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4358
4647
|
0 && (module.exports = {
|
|
4359
4648
|
Act,
|
|
@@ -4362,6 +4651,7 @@ function action_builder(state2) {
|
|
|
4362
4651
|
CommittedMetaSchema,
|
|
4363
4652
|
ConcurrencyError,
|
|
4364
4653
|
ConsoleLogger,
|
|
4654
|
+
CsvFile,
|
|
4365
4655
|
DEFAULT_LANE,
|
|
4366
4656
|
DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
4367
4657
|
DEFAULT_SETTLE_DEBOUNCE_MS,
|