@dipanshuhandoo/excelio 0.0.0-stage → 1.1.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/CHANGELOG.md +42 -0
- package/LICENSE +21 -0
- package/README.md +438 -3
- package/dist/cjs/index.cjs +861 -0
- package/dist/cjs/index.cjs.map +7 -0
- package/dist/cjs/index.d.cts +191 -0
- package/dist/cjs/workers/reader.worker.cjs +212 -0
- package/dist/cjs/workers/reader.worker.cjs.map +7 -0
- package/dist/cjs/workers/writer.worker.cjs +543 -0
- package/dist/cjs/workers/writer.worker.cjs.map +7 -0
- package/dist/cjs-min/index.cjs +2 -0
- package/dist/cjs-min/index.cjs.map +7 -0
- package/dist/cjs-min/index.d.cts +191 -0
- package/dist/cjs-min/workers/reader.worker.cjs +2 -0
- package/dist/cjs-min/workers/reader.worker.cjs.map +7 -0
- package/dist/cjs-min/workers/writer.worker.cjs +2 -0
- package/dist/cjs-min/workers/writer.worker.cjs.map +7 -0
- package/dist/esm/index.d.ts +191 -0
- package/dist/esm/index.js +824 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/workers/reader.worker.js +189 -0
- package/dist/esm/workers/reader.worker.js.map +7 -0
- package/dist/esm/workers/writer.worker.js +520 -0
- package/dist/esm/workers/writer.worker.js.map +7 -0
- package/dist/esm-min/index.d.ts +191 -0
- package/dist/esm-min/index.js +2 -0
- package/dist/esm-min/index.js.map +7 -0
- package/dist/esm-min/workers/reader.worker.js +2 -0
- package/dist/esm-min/workers/reader.worker.js.map +7 -0
- package/dist/esm-min/workers/writer.worker.js +2 -0
- package/dist/esm-min/workers/writer.worker.js.map +7 -0
- package/package.json +49 -5
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
// src/writer.js
|
|
2
|
+
import { PassThrough } from "node:stream";
|
|
3
|
+
import { createWriteStream } from "node:fs";
|
|
4
|
+
import { rm } from "node:fs/promises";
|
|
5
|
+
import { setImmediate as tick, setTimeout as sleep } from "node:timers/promises";
|
|
6
|
+
import ExcelJS from "exceljs";
|
|
7
|
+
|
|
8
|
+
// src/errors.js
|
|
9
|
+
var ExcelIoError = class extends Error {
|
|
10
|
+
constructor(message, { code = "IO_ERROR", cause, details } = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "ExcelIoError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
if (cause !== void 0) this.cause = cause;
|
|
15
|
+
if (details !== void 0) this.details = details;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/columns.js
|
|
20
|
+
function resolveColumns(declaredColumns, data) {
|
|
21
|
+
if (Array.isArray(declaredColumns) && declaredColumns.length) {
|
|
22
|
+
return declaredColumns.map(normaliseColumn);
|
|
23
|
+
}
|
|
24
|
+
if (!data?.length) return [];
|
|
25
|
+
const firstRow = data[0] || {};
|
|
26
|
+
return Object.keys(firstRow).map((k) => normaliseColumn({ key: k }));
|
|
27
|
+
}
|
|
28
|
+
function normaliseColumn(c) {
|
|
29
|
+
if (!c || typeof c !== "object" || !c.key) {
|
|
30
|
+
throw new Error(`excelio: column entry must have a "key" property; got ${JSON.stringify(c)}`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
key: String(c.key),
|
|
34
|
+
header: c.header ?? c.key,
|
|
35
|
+
type: c.type ?? null,
|
|
36
|
+
format: c.format ?? defaultFormatFor(c.type),
|
|
37
|
+
width: c.width ?? 15
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function defaultFormatFor(type) {
|
|
41
|
+
switch (type) {
|
|
42
|
+
case "date":
|
|
43
|
+
return "yyyy-mm-dd hh:mm:ss";
|
|
44
|
+
case "number":
|
|
45
|
+
return null;
|
|
46
|
+
default:
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/coerce.js
|
|
52
|
+
var ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
53
|
+
function valueForWrite(value, columnType) {
|
|
54
|
+
if (value === null || value === void 0) return null;
|
|
55
|
+
if (columnType === "date") {
|
|
56
|
+
if (value instanceof Date) return value;
|
|
57
|
+
if (typeof value === "number") return new Date(value);
|
|
58
|
+
if (typeof value === "string" && ISO_DATE_PATTERN.test(value)) {
|
|
59
|
+
const d = new Date(value);
|
|
60
|
+
return isNaN(d.getTime()) ? value : d;
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
if (columnType === "number" && typeof value === "string" && value !== "") {
|
|
65
|
+
const n = Number(value);
|
|
66
|
+
return Number.isFinite(n) ? n : value;
|
|
67
|
+
}
|
|
68
|
+
if (columnType === "boolean") {
|
|
69
|
+
if (typeof value === "boolean") return value;
|
|
70
|
+
if (value === "true" || value === 1) return true;
|
|
71
|
+
if (value === "false" || value === 0) return false;
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
function valueForRead(cell, { nullForBlank = true, trimStrings = true, dateFormat = "iso" } = {}) {
|
|
76
|
+
if (cell === void 0 || cell === null) return nullForBlank ? null : "";
|
|
77
|
+
const raw = cell && typeof cell === "object" && "value" in cell ? cell.value : cell;
|
|
78
|
+
if (raw === null || raw === void 0 || raw === "") return nullForBlank ? null : "";
|
|
79
|
+
if (raw instanceof Date) return formatDate(raw, dateFormat);
|
|
80
|
+
if (typeof raw === "object" && raw.text !== void 0 && raw.hyperlink !== void 0) {
|
|
81
|
+
return trimStrings ? String(raw.text).trim() : String(raw.text);
|
|
82
|
+
}
|
|
83
|
+
if (typeof raw === "object" && Array.isArray(raw.richText)) {
|
|
84
|
+
const joined = raw.richText.map((r) => r.text).join("");
|
|
85
|
+
return trimStrings ? joined.trim() : joined;
|
|
86
|
+
}
|
|
87
|
+
if (typeof raw === "object" && raw.formula !== void 0) {
|
|
88
|
+
return raw.result !== void 0 ? raw.result : null;
|
|
89
|
+
}
|
|
90
|
+
if (typeof raw === "object" && raw.error !== void 0) return null;
|
|
91
|
+
if (typeof raw === "string") return trimStrings ? raw.trim() : raw;
|
|
92
|
+
return raw;
|
|
93
|
+
}
|
|
94
|
+
function formatDate(d, fmt) {
|
|
95
|
+
switch (fmt) {
|
|
96
|
+
case "date":
|
|
97
|
+
return d;
|
|
98
|
+
case "epoch":
|
|
99
|
+
return d.getTime();
|
|
100
|
+
case "iso":
|
|
101
|
+
default:
|
|
102
|
+
return d.toISOString();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/rowSource.js
|
|
107
|
+
function rowSourceKind(data) {
|
|
108
|
+
if (data === void 0 || data === null) return "empty";
|
|
109
|
+
if (Array.isArray(data)) return "array";
|
|
110
|
+
if (typeof data !== "object" && typeof data !== "function") return "invalid";
|
|
111
|
+
if (typeof data[Symbol.asyncIterator] === "function") return "async";
|
|
112
|
+
if (typeof data[Symbol.iterator] === "function") return "sync";
|
|
113
|
+
return "invalid";
|
|
114
|
+
}
|
|
115
|
+
var isIterableSource = (data) => {
|
|
116
|
+
const kind = rowSourceKind(data);
|
|
117
|
+
return kind === "sync" || kind === "async";
|
|
118
|
+
};
|
|
119
|
+
var specHasIterable = (spec) => Array.isArray(spec) && spec.some((wb) => (Array.isArray(wb?.sheets) ? wb.sheets : []).some((s) => isIterableSource(s?.data)));
|
|
120
|
+
async function closeIterator(iterator) {
|
|
121
|
+
if (typeof iterator?.return !== "function") return;
|
|
122
|
+
try {
|
|
123
|
+
await iterator.return();
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function sourceFailure(err, details, signal) {
|
|
128
|
+
if (signal?.aborted) return new ExcelIoError("Aborted", { code: "ABORTED", cause: err, details });
|
|
129
|
+
return new ExcelIoError(`Row source failed for sheet "${details.sheetName}": ${err?.message ?? err}`, {
|
|
130
|
+
code: "SOURCE_ERROR",
|
|
131
|
+
cause: err,
|
|
132
|
+
details
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/writer.js
|
|
137
|
+
var DEFAULT_BATCH = 5e3;
|
|
138
|
+
var SHEET_NAME_MAX = 31;
|
|
139
|
+
var EXCEL_MAX_DATA_ROWS = 1048575;
|
|
140
|
+
var OUTPUT_HIGH_WATER_MARK = 8 * 1024 * 1024;
|
|
141
|
+
var BACKLOG_POLL_MS = 5;
|
|
142
|
+
async function writeSpec(spec, options = {}) {
|
|
143
|
+
preflightWrite(spec, options);
|
|
144
|
+
const { to } = options;
|
|
145
|
+
if (to !== void 0 && spec.length > 1) {
|
|
146
|
+
throw new ExcelIoError(
|
|
147
|
+
`Multi-workbook output requires omitting "to" \u2014 caller writes each buffer itself.`,
|
|
148
|
+
{ code: "INVALID_SPEC" }
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (to !== void 0) {
|
|
152
|
+
await writeOneWorkbook(spec[0], 0, to, options);
|
|
153
|
+
return to;
|
|
154
|
+
}
|
|
155
|
+
const buffers = [];
|
|
156
|
+
for (let wbIndex = 0; wbIndex < spec.length; wbIndex++) {
|
|
157
|
+
const buf = await writeOneWorkbookToBuffer(spec[wbIndex], wbIndex, options);
|
|
158
|
+
buffers.push(buf);
|
|
159
|
+
}
|
|
160
|
+
return buffers.length === 1 ? buffers[0] : buffers;
|
|
161
|
+
}
|
|
162
|
+
function writeSpecStream(spec, options = {}) {
|
|
163
|
+
preflightWrite(spec, options);
|
|
164
|
+
const passthrough = new PassThrough();
|
|
165
|
+
writeOneWorkbook(spec[0], 0, passthrough, options).catch((err) => passthrough.destroy(err));
|
|
166
|
+
return passthrough;
|
|
167
|
+
}
|
|
168
|
+
function preflightWrite(spec, options = {}) {
|
|
169
|
+
validateSpec(spec);
|
|
170
|
+
validateWriteOptions(options);
|
|
171
|
+
}
|
|
172
|
+
async function writeOneWorkbookToBuffer(workbookSpec, wbIndex, options) {
|
|
173
|
+
const chunks = [];
|
|
174
|
+
const collector = new PassThrough();
|
|
175
|
+
collector.on("data", (chunk) => chunks.push(chunk));
|
|
176
|
+
await writeOneWorkbook(workbookSpec, wbIndex, collector, options, { owned: true });
|
|
177
|
+
return Buffer.concat(chunks);
|
|
178
|
+
}
|
|
179
|
+
async function writeOneWorkbook(workbookSpec, wbIndex, target, options, sinkOptions = {}) {
|
|
180
|
+
const { signal, onProgress, beforeWrite, batchSize = DEFAULT_BATCH, logger } = options;
|
|
181
|
+
const maxRowsPerSheet = options.maxRowsPerSheet ?? EXCEL_MAX_DATA_ROWS;
|
|
182
|
+
const sheets = workbookSpec.sheets || [];
|
|
183
|
+
const sink = resolveSink(target, sinkOptions);
|
|
184
|
+
let wb = null;
|
|
185
|
+
try {
|
|
186
|
+
wb = new ExcelJS.stream.xlsx.WorkbookWriter({
|
|
187
|
+
stream: sink.writable,
|
|
188
|
+
useStyles: true,
|
|
189
|
+
useSharedStrings: resolveSharedStrings(options.sharedStrings, sheets)
|
|
190
|
+
});
|
|
191
|
+
applyWorkbookProperties(wb, workbookSpec.properties);
|
|
192
|
+
const used = /* @__PURE__ */ new Set();
|
|
193
|
+
const firstNames = sheets.map((s, i) => sanitiseSheetName(s.sheet || `Sheet ${i + 1}`, used, logger));
|
|
194
|
+
const totalRows = computeTotalRows(sheets);
|
|
195
|
+
const state = { rowsProcessed: 0, last: { sheetIndex: 0, sheetName: firstNames[0], sheetPart: 1 } };
|
|
196
|
+
const emit = () => onProgress?.({
|
|
197
|
+
phase: "write",
|
|
198
|
+
workbookIndex: wbIndex,
|
|
199
|
+
...state.last,
|
|
200
|
+
rowsProcessed: state.rowsProcessed,
|
|
201
|
+
totalRows
|
|
202
|
+
});
|
|
203
|
+
for (let sIndex = 0; sIndex < sheets.length; sIndex++) {
|
|
204
|
+
throwIfAborted(signal);
|
|
205
|
+
const sheetSpec = sheets[sIndex];
|
|
206
|
+
const writer = createSheetWriter({
|
|
207
|
+
wb,
|
|
208
|
+
workbookSpec,
|
|
209
|
+
wbIndex,
|
|
210
|
+
sIndex,
|
|
211
|
+
sheetSpec,
|
|
212
|
+
firstName: firstNames[sIndex],
|
|
213
|
+
used,
|
|
214
|
+
beforeWrite,
|
|
215
|
+
maxRowsPerSheet,
|
|
216
|
+
onOpen: (sheetName, sheetPart) => {
|
|
217
|
+
state.last = { sheetIndex: sIndex, sheetName, sheetPart };
|
|
218
|
+
},
|
|
219
|
+
onRow: () => {
|
|
220
|
+
state.rowsProcessed++;
|
|
221
|
+
if (onProgress && state.rowsProcessed % batchSize === 0) emit();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
const kind = rowSourceKind(sheetSpec.data);
|
|
225
|
+
if (kind === "sync" || kind === "async") {
|
|
226
|
+
await writeIterableSheet({ writer, wb, sink, sheetSpec, kind, signal, batchSize, wbIndex, sIndex });
|
|
227
|
+
} else {
|
|
228
|
+
const data = sheetSpec.data || [];
|
|
229
|
+
writer.start(resolveColumns(sheetSpec.columns, data));
|
|
230
|
+
for (let i = 0; i < data.length; i++) {
|
|
231
|
+
throwIfAborted(signal);
|
|
232
|
+
writer.write(data[i]);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
writer.finish();
|
|
236
|
+
}
|
|
237
|
+
const committing = wb.commit();
|
|
238
|
+
committing.catch(() => {
|
|
239
|
+
});
|
|
240
|
+
await Promise.race([committing, sink.failed]);
|
|
241
|
+
if (sink.cleanup) await Promise.race([sink.cleanup(), sink.failed]);
|
|
242
|
+
sink.release();
|
|
243
|
+
emit();
|
|
244
|
+
} catch (err) {
|
|
245
|
+
await abortWorkbook(wb, sink, err);
|
|
246
|
+
throw err;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function createSheetWriter({ wb, workbookSpec, wbIndex, sIndex, sheetSpec, firstName, used, beforeWrite, maxRowsPerSheet, onOpen, onRow }) {
|
|
250
|
+
const baseName = sheetSpec.sheet || `Sheet ${sIndex + 1}`;
|
|
251
|
+
let cols = [];
|
|
252
|
+
let ws = null;
|
|
253
|
+
let sheetName = null;
|
|
254
|
+
let part = 0;
|
|
255
|
+
let rowsInPart = 0;
|
|
256
|
+
const open = () => {
|
|
257
|
+
part += 1;
|
|
258
|
+
rowsInPart = 0;
|
|
259
|
+
sheetName = part === 1 ? firstName : sanitiseSheetName(baseName, used, null);
|
|
260
|
+
const wsOptions = {};
|
|
261
|
+
if (sheetSpec.styling?.freezeHeader) wsOptions.views = [{ state: "frozen", ySplit: 1 }];
|
|
262
|
+
ws = wb.addWorksheet(sheetName, wsOptions);
|
|
263
|
+
if (cols.length) {
|
|
264
|
+
ws.columns = cols.map((c) => ({
|
|
265
|
+
key: c.key,
|
|
266
|
+
header: c.header,
|
|
267
|
+
width: c.width,
|
|
268
|
+
style: c.format ? { numFmt: c.format } : void 0
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
applyStylingPostColumns(ws, sheetSpec.styling, cols.length);
|
|
272
|
+
onOpen(sheetName, part);
|
|
273
|
+
};
|
|
274
|
+
return {
|
|
275
|
+
get sheetName() {
|
|
276
|
+
return sheetName ?? firstName;
|
|
277
|
+
},
|
|
278
|
+
start(resolvedColumns) {
|
|
279
|
+
cols = resolvedColumns;
|
|
280
|
+
open();
|
|
281
|
+
},
|
|
282
|
+
write(row) {
|
|
283
|
+
if (rowsInPart >= maxRowsPerSheet) {
|
|
284
|
+
ws.commit();
|
|
285
|
+
open();
|
|
286
|
+
}
|
|
287
|
+
let out = row;
|
|
288
|
+
if (beforeWrite) {
|
|
289
|
+
out = beforeWrite(row, {
|
|
290
|
+
workbookIndex: wbIndex,
|
|
291
|
+
workbookName: workbookSpec.workbook,
|
|
292
|
+
sheetIndex: sIndex,
|
|
293
|
+
sheetName,
|
|
294
|
+
sheetPart: part,
|
|
295
|
+
rowNumber: rowsInPart + 2
|
|
296
|
+
// header is row 1
|
|
297
|
+
}) ?? row;
|
|
298
|
+
}
|
|
299
|
+
const values = cols.length ? cols.reduce((acc, c) => {
|
|
300
|
+
acc[c.key] = valueForWrite(out[c.key], c.type);
|
|
301
|
+
return acc;
|
|
302
|
+
}, {}) : out;
|
|
303
|
+
ws.addRow(values).commit();
|
|
304
|
+
rowsInPart++;
|
|
305
|
+
onRow();
|
|
306
|
+
},
|
|
307
|
+
finish() {
|
|
308
|
+
ws?.commit();
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
async function writeIterableSheet({ writer, wb, sink, sheetSpec, kind, signal, batchSize, wbIndex, sIndex }) {
|
|
313
|
+
let rowsRead = 0;
|
|
314
|
+
const details = () => ({ workbookIndex: wbIndex, sheetIndex: sIndex, sheetName: writer.sheetName, rowsRead });
|
|
315
|
+
let iterator;
|
|
316
|
+
try {
|
|
317
|
+
iterator = kind === "async" ? sheetSpec.data[Symbol.asyncIterator]() : sheetSpec.data[Symbol.iterator]();
|
|
318
|
+
} catch (err) {
|
|
319
|
+
throw sourceFailure(err, details(), signal);
|
|
320
|
+
}
|
|
321
|
+
const next = async () => {
|
|
322
|
+
let step;
|
|
323
|
+
try {
|
|
324
|
+
step = await iterator.next();
|
|
325
|
+
} catch (err) {
|
|
326
|
+
throw sourceFailure(err, details(), signal);
|
|
327
|
+
}
|
|
328
|
+
if (step === null || typeof step !== "object") {
|
|
329
|
+
throw sourceFailure(new TypeError("Iterator result is not an object"), details(), signal);
|
|
330
|
+
}
|
|
331
|
+
return step;
|
|
332
|
+
};
|
|
333
|
+
let finished = false;
|
|
334
|
+
try {
|
|
335
|
+
let step = await next();
|
|
336
|
+
writer.start(resolveColumns(sheetSpec.columns, step.done ? [] : [step.value]));
|
|
337
|
+
while (!step.done) {
|
|
338
|
+
throwIfAborted(signal);
|
|
339
|
+
writer.write(step.value);
|
|
340
|
+
rowsRead++;
|
|
341
|
+
if (rowsRead % batchSize === 0) await waitForCapacity({ wb, sink, signal });
|
|
342
|
+
step = await next();
|
|
343
|
+
}
|
|
344
|
+
finished = true;
|
|
345
|
+
} finally {
|
|
346
|
+
if (!finished) await closeIterator(iterator);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
var len = (n) => Number.isFinite(n) ? n : 0;
|
|
350
|
+
var streamLen = (s) => len(s?.writableLength ?? s?._writableState?.length) + len(s?.readableLength ?? s?._readableState?.length);
|
|
351
|
+
function backlogBytes(wb, sink) {
|
|
352
|
+
let total = streamLen(wb?.zip) + streamLen(wb?.zip?._module) + streamLen(sink?.writable);
|
|
353
|
+
for (const ws of wb?._worksheets ?? []) {
|
|
354
|
+
const sb = ws?._stream;
|
|
355
|
+
if (!sb) continue;
|
|
356
|
+
for (const buf of sb.buffers ?? []) total += len(buf?.length);
|
|
357
|
+
for (const p of sb.pipes ?? []) total += streamLen(p);
|
|
358
|
+
}
|
|
359
|
+
return total;
|
|
360
|
+
}
|
|
361
|
+
var backlogStats = { peak: 0 };
|
|
362
|
+
async function waitForCapacity({ wb, sink, signal }) {
|
|
363
|
+
const check = () => {
|
|
364
|
+
throwIfAborted(signal);
|
|
365
|
+
if (sink.failure) throw sink.failure;
|
|
366
|
+
};
|
|
367
|
+
for (; ; ) {
|
|
368
|
+
check();
|
|
369
|
+
const backlog = backlogBytes(wb, sink);
|
|
370
|
+
if (backlog > backlogStats.peak) backlogStats.peak = backlog;
|
|
371
|
+
if (backlog <= OUTPUT_HIGH_WATER_MARK) break;
|
|
372
|
+
await sleep(BACKLOG_POLL_MS);
|
|
373
|
+
}
|
|
374
|
+
await tick();
|
|
375
|
+
check();
|
|
376
|
+
}
|
|
377
|
+
function resolveSink(target, { owned = false } = {}) {
|
|
378
|
+
const track = (writable, isOwned) => {
|
|
379
|
+
const sink = { writable, owned: isOwned, failure: null };
|
|
380
|
+
let onError;
|
|
381
|
+
sink.failed = new Promise((_, reject) => {
|
|
382
|
+
onError = (err) => {
|
|
383
|
+
if (sink.failure) return;
|
|
384
|
+
sink.failure = err;
|
|
385
|
+
reject(err);
|
|
386
|
+
};
|
|
387
|
+
writable.on("error", onError);
|
|
388
|
+
});
|
|
389
|
+
sink.failed.catch(() => {
|
|
390
|
+
});
|
|
391
|
+
sink.release = () => {
|
|
392
|
+
if (!isOwned) writable.removeListener("error", onError);
|
|
393
|
+
};
|
|
394
|
+
return sink;
|
|
395
|
+
};
|
|
396
|
+
if (typeof target === "string") {
|
|
397
|
+
const fileStream = createWriteStream(target);
|
|
398
|
+
const sink = track(fileStream, true);
|
|
399
|
+
const finished = new Promise((resolve) => fileStream.once("finish", resolve));
|
|
400
|
+
const closed = new Promise((resolve) => fileStream.once("close", resolve));
|
|
401
|
+
sink.cleanup = () => finished;
|
|
402
|
+
sink.remove = async () => {
|
|
403
|
+
fileStream.destroy();
|
|
404
|
+
await closed;
|
|
405
|
+
await rm(target, { force: true });
|
|
406
|
+
};
|
|
407
|
+
return sink;
|
|
408
|
+
}
|
|
409
|
+
if (target && typeof target.write === "function") {
|
|
410
|
+
return track(target, owned);
|
|
411
|
+
}
|
|
412
|
+
throw new ExcelIoError(`Invalid write target \u2014 expected file path or writable stream`, { code: "INVALID_SPEC" });
|
|
413
|
+
}
|
|
414
|
+
async function abortWorkbook(wb, sink, err) {
|
|
415
|
+
const quiet = (s) => s?.on?.("error", () => {
|
|
416
|
+
});
|
|
417
|
+
try {
|
|
418
|
+
if (wb) {
|
|
419
|
+
wb.zip?.unpipe?.(sink.writable);
|
|
420
|
+
quiet(wb.zip);
|
|
421
|
+
quiet(wb.zip?._module);
|
|
422
|
+
for (const ws of wb._worksheets ?? []) (ws?._stream?.pipes ?? []).forEach(quiet);
|
|
423
|
+
wb.zip?.abort?.();
|
|
424
|
+
}
|
|
425
|
+
if (sink.remove) await sink.remove();
|
|
426
|
+
else if (sink.owned) sink.writable.destroy?.();
|
|
427
|
+
sink.release();
|
|
428
|
+
} catch (cleanupError) {
|
|
429
|
+
try {
|
|
430
|
+
if (typeof err === "object" && err !== null || typeof err === "function") err.cleanupError = cleanupError;
|
|
431
|
+
} catch {
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function resolveSharedStrings(option = "auto", sheets) {
|
|
436
|
+
if (typeof option === "boolean") return option;
|
|
437
|
+
return !sheets.some((s) => isIterableSource(s?.data));
|
|
438
|
+
}
|
|
439
|
+
function computeTotalRows(sheets) {
|
|
440
|
+
let total = 0;
|
|
441
|
+
for (const s of sheets) {
|
|
442
|
+
const kind = rowSourceKind(s.data);
|
|
443
|
+
if (kind === "array") total += s.data.length;
|
|
444
|
+
else if (kind === "sync" || kind === "async") {
|
|
445
|
+
if (s.totalRows === void 0) return void 0;
|
|
446
|
+
total += s.totalRows;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return total;
|
|
450
|
+
}
|
|
451
|
+
function throwIfAborted(signal) {
|
|
452
|
+
if (signal?.aborted) throw new ExcelIoError("Aborted", { code: "ABORTED" });
|
|
453
|
+
}
|
|
454
|
+
function applyWorkbookProperties(wb, props) {
|
|
455
|
+
if (!props) return;
|
|
456
|
+
if (props.author) wb.creator = props.author;
|
|
457
|
+
if (props.created) wb.created = props.created instanceof Date ? props.created : new Date(props.created);
|
|
458
|
+
if (props.modified) wb.modified = props.modified instanceof Date ? props.modified : new Date(props.modified);
|
|
459
|
+
if (props.company) wb.company = props.company;
|
|
460
|
+
if (props.title) wb.title = props.title;
|
|
461
|
+
}
|
|
462
|
+
function applyStylingPostColumns(ws, styling, colCount) {
|
|
463
|
+
if (!styling) return;
|
|
464
|
+
if (styling.headerBold) {
|
|
465
|
+
ws.getRow(1).font = { bold: true };
|
|
466
|
+
}
|
|
467
|
+
if (styling.autoFilter && colCount > 0) {
|
|
468
|
+
ws.autoFilter = { from: { row: 1, column: 1 }, to: { row: 1, column: colCount } };
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
var FORBIDDEN_SHEET_CHARS = /[\\/?*[\]:]/g;
|
|
472
|
+
function sanitiseSheetName(raw, used, logger) {
|
|
473
|
+
let name = String(raw).replace(FORBIDDEN_SHEET_CHARS, "_");
|
|
474
|
+
if (name.length > SHEET_NAME_MAX) {
|
|
475
|
+
if (logger?.warn) logger.warn(`excelio: sheet name "${raw}" truncated to ${SHEET_NAME_MAX} chars`);
|
|
476
|
+
name = name.slice(0, SHEET_NAME_MAX);
|
|
477
|
+
}
|
|
478
|
+
if (!used.has(name)) {
|
|
479
|
+
used.add(name);
|
|
480
|
+
return name;
|
|
481
|
+
}
|
|
482
|
+
let i = 2;
|
|
483
|
+
while (true) {
|
|
484
|
+
const suffix = ` (${i})`;
|
|
485
|
+
const candidate = (name.length + suffix.length > SHEET_NAME_MAX ? name.slice(0, SHEET_NAME_MAX - suffix.length) : name) + suffix;
|
|
486
|
+
if (!used.has(candidate)) {
|
|
487
|
+
used.add(candidate);
|
|
488
|
+
return candidate;
|
|
489
|
+
}
|
|
490
|
+
i++;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function validateSpec(spec) {
|
|
494
|
+
if (!Array.isArray(spec) || spec.length === 0) {
|
|
495
|
+
throw new ExcelIoError("Spec must be a non-empty array of workbooks", { code: "INVALID_SPEC" });
|
|
496
|
+
}
|
|
497
|
+
const owners = /* @__PURE__ */ new Map();
|
|
498
|
+
spec.forEach((wb, wbIndex) => {
|
|
499
|
+
if (!wb || typeof wb !== "object") {
|
|
500
|
+
throw new ExcelIoError("Workbook entries must be objects", { code: "INVALID_SPEC" });
|
|
501
|
+
}
|
|
502
|
+
if (!Array.isArray(wb.sheets) || wb.sheets.length === 0) {
|
|
503
|
+
throw new ExcelIoError("Workbook must have at least one sheet", { code: "INVALID_SPEC" });
|
|
504
|
+
}
|
|
505
|
+
wb.sheets.forEach((s, i) => {
|
|
506
|
+
if (!s || typeof s !== "object") {
|
|
507
|
+
throw new ExcelIoError(`Workbook ${wbIndex}: sheet entries must be objects`, { code: "INVALID_SPEC" });
|
|
508
|
+
}
|
|
509
|
+
const name = s.sheet || `Sheet ${i + 1}`;
|
|
510
|
+
const kind = rowSourceKind(s.data);
|
|
511
|
+
if (kind === "invalid") {
|
|
512
|
+
throw new ExcelIoError(`Sheet "${name}": data must be an array or an iterable of row objects`, { code: "INVALID_SPEC" });
|
|
513
|
+
}
|
|
514
|
+
if (kind === "sync" || kind === "async") {
|
|
515
|
+
if (owners.has(s.data)) {
|
|
516
|
+
throw new ExcelIoError(
|
|
517
|
+
`Sheet "${name}": its data source is already used by sheet "${owners.get(s.data)}". Pass a separate iterable (or an array) per sheet.`,
|
|
518
|
+
{ code: "INVALID_SPEC" }
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
owners.set(s.data, name);
|
|
522
|
+
}
|
|
523
|
+
if (s.totalRows !== void 0 && !(Number.isInteger(s.totalRows) && s.totalRows >= 0)) {
|
|
524
|
+
throw new ExcelIoError(`Sheet "${name}": totalRows must be a non-negative integer`, { code: "INVALID_SPEC" });
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
function validateWriteOptions({ maxRowsPerSheet, sharedStrings, batchSize } = {}) {
|
|
530
|
+
if (maxRowsPerSheet !== void 0 && !(Number.isInteger(maxRowsPerSheet) && maxRowsPerSheet >= 1 && maxRowsPerSheet <= EXCEL_MAX_DATA_ROWS)) {
|
|
531
|
+
throw new ExcelIoError(`maxRowsPerSheet must be an integer between 1 and ${EXCEL_MAX_DATA_ROWS}`, { code: "INVALID_SPEC" });
|
|
532
|
+
}
|
|
533
|
+
if (sharedStrings !== void 0 && sharedStrings !== "auto" && typeof sharedStrings !== "boolean") {
|
|
534
|
+
throw new ExcelIoError(`sharedStrings must be 'auto', true or false`, { code: "INVALID_SPEC" });
|
|
535
|
+
}
|
|
536
|
+
if (batchSize !== void 0 && !(Number.isInteger(batchSize) && batchSize >= 1)) {
|
|
537
|
+
throw new ExcelIoError("batchSize must be a positive integer", { code: "INVALID_SPEC" });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/reader.js
|
|
542
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
543
|
+
import ExcelJS2 from "exceljs";
|
|
544
|
+
var DEFAULT_BATCH2 = 5e3;
|
|
545
|
+
async function readSource(input, options = {}) {
|
|
546
|
+
const startedAt = Date.now();
|
|
547
|
+
const stats = { totalRows: 0, totalSheets: 0, durationMs: 0 };
|
|
548
|
+
const errors = [];
|
|
549
|
+
const {
|
|
550
|
+
headerRow = 1,
|
|
551
|
+
nullForBlank = true,
|
|
552
|
+
trimStrings = true,
|
|
553
|
+
dateFormat = "iso",
|
|
554
|
+
validateRow,
|
|
555
|
+
afterRead,
|
|
556
|
+
onRowError = "collect",
|
|
557
|
+
batchSize = DEFAULT_BATCH2,
|
|
558
|
+
onProgress,
|
|
559
|
+
signal
|
|
560
|
+
} = options;
|
|
561
|
+
const wb = new ExcelJS2.Workbook();
|
|
562
|
+
try {
|
|
563
|
+
if (Buffer2.isBuffer(input)) {
|
|
564
|
+
await wb.xlsx.load(input);
|
|
565
|
+
} else if (input instanceof Uint8Array) {
|
|
566
|
+
await wb.xlsx.load(Buffer2.from(input.buffer, input.byteOffset, input.byteLength));
|
|
567
|
+
} else if (typeof input === "string") {
|
|
568
|
+
await wb.xlsx.readFile(input);
|
|
569
|
+
} else if (input && typeof input.pipe === "function") {
|
|
570
|
+
await wb.xlsx.read(input);
|
|
571
|
+
} else {
|
|
572
|
+
throw new ExcelIoError("Invalid input \u2014 expected Buffer, file path, or readable stream", { code: "INVALID_SPEC" });
|
|
573
|
+
}
|
|
574
|
+
} catch (err) {
|
|
575
|
+
if (err instanceof ExcelIoError) throw err;
|
|
576
|
+
throw new ExcelIoError(`Failed to parse workbook: ${err.message}`, { code: "IO_ERROR", cause: err });
|
|
577
|
+
}
|
|
578
|
+
const sheets = [];
|
|
579
|
+
for (let s = 0; s < wb.worksheets.length; s++) {
|
|
580
|
+
if (signal?.aborted) throw new ExcelIoError("Aborted", { code: "ABORTED" });
|
|
581
|
+
const ws = wb.worksheets[s];
|
|
582
|
+
stats.totalSheets++;
|
|
583
|
+
const sheetMeta = { sheet: ws.name, columns: [], data: [] };
|
|
584
|
+
let columnKeys = null;
|
|
585
|
+
ws.eachRow({ includeEmpty: false }, (row, rowNumber) => {
|
|
586
|
+
if (rowNumber === headerRow) {
|
|
587
|
+
columnKeys = readHeaderRow(row);
|
|
588
|
+
sheetMeta.columns = columnKeys.filter(Boolean).map((k) => ({ key: k, header: k }));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (rowNumber < headerRow || !columnKeys) return;
|
|
592
|
+
if (signal?.aborted) throw new ExcelIoError("Aborted", { code: "ABORTED" });
|
|
593
|
+
const obj = {};
|
|
594
|
+
let isEmpty = true;
|
|
595
|
+
for (let i = 0; i < columnKeys.length; i++) {
|
|
596
|
+
const key = columnKeys[i];
|
|
597
|
+
if (!key) continue;
|
|
598
|
+
const cell = row.getCell(i + 1);
|
|
599
|
+
const v = valueForRead(cell, { nullForBlank, trimStrings, dateFormat });
|
|
600
|
+
if (v !== null && v !== "" && v !== void 0) isEmpty = false;
|
|
601
|
+
obj[key] = v;
|
|
602
|
+
}
|
|
603
|
+
if (isEmpty) return;
|
|
604
|
+
const ctx = {
|
|
605
|
+
workbookIndex: 0,
|
|
606
|
+
workbookName: void 0,
|
|
607
|
+
sheetIndex: s,
|
|
608
|
+
sheetName: ws.name,
|
|
609
|
+
rowNumber
|
|
610
|
+
};
|
|
611
|
+
let outRow = obj;
|
|
612
|
+
if (afterRead) {
|
|
613
|
+
try {
|
|
614
|
+
outRow = afterRead(obj, ctx) ?? obj;
|
|
615
|
+
} catch (err) {
|
|
616
|
+
const e = mkRowError(ctx, "TRANSFORM_ERROR", err.message);
|
|
617
|
+
if (onRowError === "fail") throw new ExcelIoError(e.message, { code: "ROW_VALIDATION", details: e });
|
|
618
|
+
errors.push(e);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (validateRow) {
|
|
623
|
+
const issue = validateRow(outRow, ctx);
|
|
624
|
+
if (issue) {
|
|
625
|
+
const e = mkRowError(ctx, issue.code || "VALIDATION_FAILED", issue.message, issue.column);
|
|
626
|
+
if (onRowError === "fail") throw new ExcelIoError(e.message, { code: "ROW_VALIDATION", details: e });
|
|
627
|
+
errors.push(e);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
sheetMeta.data.push(outRow);
|
|
632
|
+
stats.totalRows++;
|
|
633
|
+
if (onProgress && stats.totalRows % batchSize === 0) {
|
|
634
|
+
onProgress({ phase: "read", sheetName: ws.name, rowsProcessed: stats.totalRows });
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
sheets.push(sheetMeta);
|
|
638
|
+
}
|
|
639
|
+
stats.durationMs = Date.now() - startedAt;
|
|
640
|
+
return {
|
|
641
|
+
ok: errors.length === 0,
|
|
642
|
+
data: [{ workbook: void 0, sheets }],
|
|
643
|
+
errors,
|
|
644
|
+
stats
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function readHeaderRow(row) {
|
|
648
|
+
const headers = [];
|
|
649
|
+
const values = row.values;
|
|
650
|
+
if (!Array.isArray(values)) return headers;
|
|
651
|
+
for (let i = 1; i < values.length; i++) {
|
|
652
|
+
const v = values[i];
|
|
653
|
+
headers.push(v == null ? null : String(v).trim());
|
|
654
|
+
}
|
|
655
|
+
return headers;
|
|
656
|
+
}
|
|
657
|
+
function mkRowError(ctx, code, message, column) {
|
|
658
|
+
return {
|
|
659
|
+
workbook: ctx.workbookIndex,
|
|
660
|
+
sheet: ctx.sheetName,
|
|
661
|
+
row: ctx.rowNumber,
|
|
662
|
+
column: column ?? null,
|
|
663
|
+
code,
|
|
664
|
+
message
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/workerPool.js
|
|
669
|
+
import { Worker } from "node:worker_threads";
|
|
670
|
+
import { fileURLToPath } from "node:url";
|
|
671
|
+
import { Buffer as Buffer3 } from "node:buffer";
|
|
672
|
+
import path from "node:path";
|
|
673
|
+
import { statSync } from "node:fs";
|
|
674
|
+
var DEFAULT_THRESHOLD = { rows: 5e4, bytes: 10 * 1024 * 1024 };
|
|
675
|
+
var moduleDirectory = typeof __EXCELIO_MODULE_DIR__ !== "undefined" ? __EXCELIO_MODULE_DIR__ : path.dirname(fileURLToPath(import.meta.url));
|
|
676
|
+
var workerExtension = true ? ".js" : ".js";
|
|
677
|
+
var WORKER_PATHS = {
|
|
678
|
+
write: path.join(moduleDirectory, "workers", `writer.worker${workerExtension}`),
|
|
679
|
+
read: path.join(moduleDirectory, "workers", `reader.worker${workerExtension}`)
|
|
680
|
+
};
|
|
681
|
+
async function maybeUseWorker(op, payload, options, inlineFn) {
|
|
682
|
+
const decision = decideMode(op, payload, options);
|
|
683
|
+
if (decision.mode === "inline") return inlineFn();
|
|
684
|
+
return runInWorker(op, payload, sanitiseOptions(options));
|
|
685
|
+
}
|
|
686
|
+
function decideMode(op, payload, options) {
|
|
687
|
+
const useWorker = options.useWorker ?? "auto";
|
|
688
|
+
if (op === "read" && payload && typeof payload.pipe === "function") {
|
|
689
|
+
return { mode: "inline", reason: "stream input" };
|
|
690
|
+
}
|
|
691
|
+
if (op === "write" && specHasIterable(payload)) {
|
|
692
|
+
if (useWorker === true) options.logger?.warn?.("excelio: iterable sheet data is written inline; useWorker ignored");
|
|
693
|
+
return { mode: "inline", reason: "iterable sheet data" };
|
|
694
|
+
}
|
|
695
|
+
if (hasHook(options)) return { mode: "inline", reason: "hooks present" };
|
|
696
|
+
if (useWorker === false) return { mode: "inline", reason: "disabled" };
|
|
697
|
+
if (useWorker === true) return { mode: "worker", reason: "forced" };
|
|
698
|
+
const threshold = { ...DEFAULT_THRESHOLD, ...options.workerThreshold || {} };
|
|
699
|
+
if (op === "write") {
|
|
700
|
+
const totalRows = estimateRows(payload);
|
|
701
|
+
if (totalRows >= threshold.rows) return { mode: "worker", reason: `rows ${totalRows} \u2265 ${threshold.rows}` };
|
|
702
|
+
return { mode: "inline", reason: "below threshold" };
|
|
703
|
+
}
|
|
704
|
+
if (op === "read") {
|
|
705
|
+
const bytes = estimateBytes(payload);
|
|
706
|
+
if (bytes !== null && bytes >= threshold.bytes) return { mode: "worker", reason: `bytes ${bytes} \u2265 ${threshold.bytes}` };
|
|
707
|
+
return { mode: "inline", reason: "below threshold or unknown size" };
|
|
708
|
+
}
|
|
709
|
+
return { mode: "inline", reason: "unknown op" };
|
|
710
|
+
}
|
|
711
|
+
function hasHook(options) {
|
|
712
|
+
return Boolean(
|
|
713
|
+
options.validateRow || options.beforeWrite || options.afterRead || options.onProgress || options.logger || options.signal || // streams can't cross thread boundaries via postMessage either
|
|
714
|
+
options.to && typeof options.to !== "string"
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
function estimateRows(spec) {
|
|
718
|
+
if (!Array.isArray(spec)) return 0;
|
|
719
|
+
let n = 0;
|
|
720
|
+
for (const wb of spec) {
|
|
721
|
+
for (const sheet of wb?.sheets || []) {
|
|
722
|
+
n += Array.isArray(sheet?.data) ? sheet.data.length : 0;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return n;
|
|
726
|
+
}
|
|
727
|
+
function estimateBytes(input) {
|
|
728
|
+
if (typeof input === "string") {
|
|
729
|
+
try {
|
|
730
|
+
return statSync(input).size;
|
|
731
|
+
} catch {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (Buffer3.isBuffer(input)) return input.length;
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
function sanitiseOptions(options) {
|
|
739
|
+
const { validateRow, beforeWrite, afterRead, onProgress, signal, logger, to, ...rest } = options;
|
|
740
|
+
void validateRow;
|
|
741
|
+
void beforeWrite;
|
|
742
|
+
void afterRead;
|
|
743
|
+
void onProgress;
|
|
744
|
+
void signal;
|
|
745
|
+
void logger;
|
|
746
|
+
return { ...rest, ...typeof to === "string" ? { to } : {} };
|
|
747
|
+
}
|
|
748
|
+
function runInWorker(op, payload, options) {
|
|
749
|
+
return new Promise((resolve, reject) => {
|
|
750
|
+
const workerPath = WORKER_PATHS[op];
|
|
751
|
+
if (!workerPath) {
|
|
752
|
+
reject(new ExcelIoError(`No worker for op "${op}"`, { code: "WORKER_ERROR" }));
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
let worker;
|
|
756
|
+
try {
|
|
757
|
+
worker = new Worker(workerPath, { workerData: { op, payload, options } });
|
|
758
|
+
} catch (err) {
|
|
759
|
+
reject(new ExcelIoError(err.message, { code: "WORKER_ERROR", cause: err }));
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
let settled = false;
|
|
763
|
+
worker.once("message", (msg) => {
|
|
764
|
+
settled = true;
|
|
765
|
+
void worker.terminate().catch(() => {
|
|
766
|
+
});
|
|
767
|
+
if (msg.ok) {
|
|
768
|
+
const result = op === "write" && options.to === void 0 ? Array.isArray(msg.result) ? msg.result.map((value) => Buffer3.from(value)) : Buffer3.from(msg.result) : msg.result;
|
|
769
|
+
resolve(result);
|
|
770
|
+
} else reject(new ExcelIoError(msg.error?.message || "Worker error", {
|
|
771
|
+
code: msg.error?.code || "WORKER_ERROR",
|
|
772
|
+
cause: msg.error,
|
|
773
|
+
details: msg.error?.details
|
|
774
|
+
}));
|
|
775
|
+
});
|
|
776
|
+
worker.once("error", (err) => {
|
|
777
|
+
settled = true;
|
|
778
|
+
void worker.terminate().catch(() => {
|
|
779
|
+
});
|
|
780
|
+
reject(new ExcelIoError(err.message, { code: "WORKER_ERROR", cause: err }));
|
|
781
|
+
});
|
|
782
|
+
worker.once("exit", (code) => {
|
|
783
|
+
if (!settled) {
|
|
784
|
+
reject(new ExcelIoError(`Worker exited without a result (code ${code})`, { code: "WORKER_ERROR" }));
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/index.js
|
|
791
|
+
var excelio = {
|
|
792
|
+
/**
|
|
793
|
+
* Write one or more workbooks. Returns a Buffer (or array of Buffers for
|
|
794
|
+
* multi-workbook specs) when `to` is omitted; writes to file/stream when `to`
|
|
795
|
+
* is provided and returns the target.
|
|
796
|
+
*/
|
|
797
|
+
async write(spec, options = {}) {
|
|
798
|
+
preflightWrite(spec, options);
|
|
799
|
+
return maybeUseWorker("write", spec, options, () => writeSpec(spec, options));
|
|
800
|
+
},
|
|
801
|
+
/**
|
|
802
|
+
* Returns a Readable stream for the first workbook. Synchronous return;
|
|
803
|
+
* spec errors throw immediately, errors during the underlying write surface
|
|
804
|
+
* via the stream's 'error' event.
|
|
805
|
+
*/
|
|
806
|
+
writeStream(spec, options = {}) {
|
|
807
|
+
preflightWrite(spec, options);
|
|
808
|
+
return writeSpecStream(spec, options);
|
|
809
|
+
},
|
|
810
|
+
/**
|
|
811
|
+
* Read a workbook from a Buffer, file path, or Readable stream. Returns a
|
|
812
|
+
* structured result with parsed data, per-row errors, and stats.
|
|
813
|
+
*/
|
|
814
|
+
async read(input, options = {}) {
|
|
815
|
+
return maybeUseWorker("read", input, options, () => readSource(input, options));
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
var src_default = excelio;
|
|
819
|
+
export {
|
|
820
|
+
ExcelIoError,
|
|
821
|
+
src_default as default,
|
|
822
|
+
excelio
|
|
823
|
+
};
|
|
824
|
+
//# sourceMappingURL=index.js.map
|