@xeplr/actions 1.1.17 → 1.1.19
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/README.md +65 -7
- package/index.js +2 -0
- package/lib/builtins/db/move.js +8 -1
- package/lib/builtins/export/pages.js +92 -0
- package/lib/builtins/export/table.js +116 -0
- package/lib/builtins/file/upload.js +6 -1
- package/lib/builtins/index.js +8 -1
- package/lib/drivers/db/duckdb.js +96 -1
- package/lib/drivers/db/mssql.js +3 -2
- package/lib/drivers/db/mysql.js +2 -2
- package/lib/drivers/db/postgres.js +3 -2
- package/lib/formats/index.js +3 -1
- package/lib/formats/writers/csv.js +78 -0
- package/lib/formats/writers/index.js +23 -0
- package/lib/formats/writers/pdf.js +293 -0
- package/lib/formats/writers/xlsx.js +278 -0
- package/lib/uploader/index.js +13 -1
- package/package.json +23 -4
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// .xlsx writer — our own, on fflate's streaming zip.
|
|
2
|
+
//
|
|
3
|
+
// Why not exceljs: it pulls a uuid with a published advisory, has not had a
|
|
4
|
+
// release since December 2024, and the override that silences it has to be
|
|
5
|
+
// repeated in every app that installs this package. Writing SpreadsheetML is
|
|
6
|
+
// the other half of what lib/formats/_xlsx-stream.js already reads, and a
|
|
7
|
+
// workbook of one styled sheet is a small, well-specified thing.
|
|
8
|
+
//
|
|
9
|
+
// WHAT IT WRITES: one sheet; an optional title and subtitle above the header;
|
|
10
|
+
// the header bold on a tint, frozen, with an autofilter; numbers and dates as
|
|
11
|
+
// REAL cells carrying their number format, so the reader can still sum and
|
|
12
|
+
// filter; subtotal rows bold, total rows bold on a tint; column widths from
|
|
13
|
+
// the header and the first rows.
|
|
14
|
+
//
|
|
15
|
+
// Streaming: rows go straight into the zip, which goes straight to disk,
|
|
16
|
+
// waiting on the file stream when it is full — a million rows costs the
|
|
17
|
+
// same memory as a hundred. Only the first SAMPLE_ROWS are held, to size the
|
|
18
|
+
// columns, because <cols> must come before the rows in the sheet.
|
|
19
|
+
|
|
20
|
+
var fs = require('fs');
|
|
21
|
+
|
|
22
|
+
var MAX_ROWS = 1048576; // Excel's own limit, header and title rows included
|
|
23
|
+
var SAMPLE_ROWS = 200;
|
|
24
|
+
var CHUNK = 1 << 20; // push to the zip in ~1 MB pieces
|
|
25
|
+
|
|
26
|
+
// XML 1.0 cannot carry most control characters even escaped; Excel refuses
|
|
27
|
+
// the file ("We found a problem with some content"). Dropped.
|
|
28
|
+
var INVALID_XML = /[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g;
|
|
29
|
+
function esc(value) {
|
|
30
|
+
return String(value).replace(INVALID_XML, '')
|
|
31
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 0 → A, 25 → Z, 26 → AA. */
|
|
35
|
+
function colName(i) {
|
|
36
|
+
var s = '';
|
|
37
|
+
for (var n = i + 1; n > 0; n = Math.floor((n - 1) / 26)) s = String.fromCharCode(65 + ((n - 1) % 26)) + s;
|
|
38
|
+
return s;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A Date, or an ISO date string, as an Excel serial — or null. */
|
|
42
|
+
function dateSerial(value) {
|
|
43
|
+
var d = value instanceof Date ? value : (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}/.test(value) ? new Date(value) : null);
|
|
44
|
+
if (!d || isNaN(d.getTime())) return null;
|
|
45
|
+
return d.getTime() / 86400000 + 25569;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fonts: 0 normal, 1 bold, 2 title. Fills: 0 none, 1 gray125 (both required
|
|
49
|
+
// by Excel at those positions), 2 header tint, 3 total tint.
|
|
50
|
+
var FONT = { normal: 0, bold: 1, title: 2 };
|
|
51
|
+
var FILL = { none: 0, header: 2, total: 3 };
|
|
52
|
+
|
|
53
|
+
function stylesXml(numFmts, xfs) {
|
|
54
|
+
var fmts = numFmts.map(function(code, i) { return '<numFmt numFmtId="' + (164 + i) + '" formatCode="' + esc(code) + '"/>'; });
|
|
55
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
|
|
56
|
+
'<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">' +
|
|
57
|
+
(fmts.length ? '<numFmts count="' + fmts.length + '">' + fmts.join('') + '</numFmts>' : '') +
|
|
58
|
+
'<fonts count="3"><font><sz val="11"/><name val="Calibri"/></font><font><b/><sz val="11"/><name val="Calibri"/></font>' +
|
|
59
|
+
'<font><b/><sz val="14"/><name val="Calibri"/></font></fonts>' +
|
|
60
|
+
'<fills count="4"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill>' +
|
|
61
|
+
'<fill><patternFill patternType="solid"><fgColor rgb="FFF1F3F7"/><bgColor indexed="64"/></patternFill></fill>' +
|
|
62
|
+
'<fill><patternFill patternType="solid"><fgColor rgb="FFE8EBF2"/><bgColor indexed="64"/></patternFill></fill></fills>' +
|
|
63
|
+
'<borders count="2"><border><left/><right/><top/><bottom/><diagonal/></border>' +
|
|
64
|
+
'<border><left/><right/><top style="thin"><color rgb="FF9AA1AE"/></top><bottom/><diagonal/></border></borders>' +
|
|
65
|
+
'<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>' +
|
|
66
|
+
'<cellXfs count="' + xfs.length + '">' + xfs.map(function(x) {
|
|
67
|
+
return '<xf numFmtId="' + x.numFmtId + '" fontId="' + x.fontId + '" fillId="' + x.fillId + '" borderId="' + x.borderId + '" xfId="0"' +
|
|
68
|
+
(x.numFmtId ? ' applyNumberFormat="1"' : '') + (x.fontId ? ' applyFont="1"' : '') + (x.fillId ? ' applyFill="1"' : '') +
|
|
69
|
+
(x.borderId ? ' applyBorder="1"' : '') + (x.align ? ' applyAlignment="1"><alignment horizontal="' + x.align + '"/></xf>' : '/>');
|
|
70
|
+
}).join('') + '</cellXfs>' +
|
|
71
|
+
'<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var STATIC = {
|
|
75
|
+
'[Content_Types].xml': '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
|
|
76
|
+
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
|
|
77
|
+
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
|
|
78
|
+
'<Default Extension="xml" ContentType="application/xml"/>' +
|
|
79
|
+
'<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>' +
|
|
80
|
+
'<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>' +
|
|
81
|
+
'<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>' +
|
|
82
|
+
'</Types>',
|
|
83
|
+
'_rels/.rels': '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
|
|
84
|
+
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
|
|
85
|
+
'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' +
|
|
86
|
+
'</Relationships>',
|
|
87
|
+
'xl/_rels/workbook.xml.rels': '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
|
|
88
|
+
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
|
|
89
|
+
'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>' +
|
|
90
|
+
'<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>' +
|
|
91
|
+
'</Relationships>'
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function workbookXml(sheetName, filterRef) {
|
|
95
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
|
|
96
|
+
'<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' +
|
|
97
|
+
'<sheets><sheet name="' + esc(sheetName) + '" sheetId="1" r:id="rId1"/></sheets>' +
|
|
98
|
+
(filterRef ? '<definedNames><definedName name="_xlnm._FilterDatabase" localSheetId="0" hidden="1">' + esc("'" + sheetName.replace(/'/g, "''") + "'!" + filterRef) + '</definedName></definedNames>' : '') +
|
|
99
|
+
'</workbook>';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Excel refuses a sheet name over 31 characters or with any of : \ / ? * [ ] */
|
|
103
|
+
function sheetNameOf(title) {
|
|
104
|
+
var name = String(title || 'Sheet1').replace(/[:\\/?*[\]]/g, ' ').trim().slice(0, 31);
|
|
105
|
+
return name || 'Sheet1';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
requires: ['fflate'],
|
|
110
|
+
MAX_ROWS: MAX_ROWS,
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} filePath
|
|
114
|
+
* @param {object} doc - { title?, subtitle?, columns: [{ header, kind?: 'number'|'date'|'text'|'raw', numFmt?, width?, align? }] }
|
|
115
|
+
* kind 'number': the value is written as a number (numFmt applies);
|
|
116
|
+
* kind 'date': a Date or ISO string, written as a date serial (numFmt applies);
|
|
117
|
+
* anything else: a number stays a number, everything else is text.
|
|
118
|
+
* @returns {{ writeRow(row): Promise<boolean>, close(): Promise<{ rows, truncated }> }}
|
|
119
|
+
* row: { values: [], kind?: 'detail'|'subtotal'|'total' }. writeRow answers
|
|
120
|
+
* false once Excel's row limit is reached (the rest are counted, not written).
|
|
121
|
+
*/
|
|
122
|
+
open: function(filePath, doc) {
|
|
123
|
+
var fflate = require('fflate');
|
|
124
|
+
var columns = doc.columns || [];
|
|
125
|
+
var out = fs.createWriteStream(filePath);
|
|
126
|
+
var failed = null;
|
|
127
|
+
out.on('error', function(err) { failed = err; });
|
|
128
|
+
var zipDone;
|
|
129
|
+
var zipFinished = new Promise(function(resolve) { zipDone = resolve; });
|
|
130
|
+
var zip = new fflate.Zip(function(err, chunk, final) {
|
|
131
|
+
if (err) { failed = err; return; }
|
|
132
|
+
out.write(Buffer.from(chunk));
|
|
133
|
+
if (final) zipDone();
|
|
134
|
+
});
|
|
135
|
+
function addFile(name, text) {
|
|
136
|
+
var f = new fflate.ZipDeflate(name, { level: 6 });
|
|
137
|
+
zip.add(f);
|
|
138
|
+
f.push(fflate.strToU8(text), true);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Styles are collected as the rows use them and written at the end.
|
|
142
|
+
var numFmts = [];
|
|
143
|
+
var xfs = [{ numFmtId: 0, fontId: 0, fillId: 0, borderId: 0 }];
|
|
144
|
+
var xfIndex = { '0|0|0|0|': 0 };
|
|
145
|
+
function styleId(numFmt, fontId, fillId, borderId, align) {
|
|
146
|
+
var numFmtId = 0;
|
|
147
|
+
if (numFmt) {
|
|
148
|
+
var at = numFmts.indexOf(numFmt);
|
|
149
|
+
if (at < 0) { numFmts.push(numFmt); at = numFmts.length - 1; }
|
|
150
|
+
numFmtId = 164 + at;
|
|
151
|
+
}
|
|
152
|
+
var key = numFmtId + '|' + fontId + '|' + fillId + '|' + borderId + '|' + (align || '');
|
|
153
|
+
if (xfIndex[key] === undefined) {
|
|
154
|
+
xfs.push({ numFmtId: numFmtId, fontId: fontId, fillId: fillId, borderId: borderId, align: align || null });
|
|
155
|
+
xfIndex[key] = xfs.length - 1;
|
|
156
|
+
}
|
|
157
|
+
return xfIndex[key];
|
|
158
|
+
}
|
|
159
|
+
var LOOK = {
|
|
160
|
+
detail: { font: FONT.normal, fill: FILL.none, border: 0 },
|
|
161
|
+
subtotal: { font: FONT.bold, fill: FILL.none, border: 0 },
|
|
162
|
+
total: { font: FONT.bold, fill: FILL.total, border: 1 }
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
var sheet = new fflate.ZipDeflate('xl/worksheets/sheet1.xml', { level: 6 });
|
|
166
|
+
var buffered = '';
|
|
167
|
+
var rowNo = 0;
|
|
168
|
+
var written = 0; // data rows written
|
|
169
|
+
var offered = 0; // data rows asked for
|
|
170
|
+
var headerRow = 0;
|
|
171
|
+
var sample = [];
|
|
172
|
+
var started = false;
|
|
173
|
+
|
|
174
|
+
function cellXml(ref, value, column, look) {
|
|
175
|
+
if (value === null || value === undefined || value === '') return '';
|
|
176
|
+
var kind = column.kind;
|
|
177
|
+
var align = column.align === 'right' || column.align === 'center' ? column.align : null;
|
|
178
|
+
if (kind === 'date') {
|
|
179
|
+
var serial = dateSerial(value);
|
|
180
|
+
if (serial !== null) return '<c r="' + ref + '" s="' + styleId(column.numFmt || 'yyyy-mm-dd', look.font, look.fill, look.border, align) + '"><v>' + serial + '</v></c>';
|
|
181
|
+
}
|
|
182
|
+
var num = typeof value === 'number' ? value : (kind === 'number' && value !== '' && !isNaN(Number(value)) ? Number(value) : null);
|
|
183
|
+
if (num !== null && isFinite(num)) {
|
|
184
|
+
return '<c r="' + ref + '" s="' + styleId(kind === 'number' ? column.numFmt : null, look.font, look.fill, look.border, align) + '"><v>' + num + '</v></c>';
|
|
185
|
+
}
|
|
186
|
+
if (typeof value === 'boolean') return '<c r="' + ref + '" t="b" s="' + styleId(null, look.font, look.fill, look.border, align) + '"><v>' + (value ? 1 : 0) + '</v></c>';
|
|
187
|
+
return '<c r="' + ref + '" t="inlineStr" s="' + styleId(null, look.font, look.fill, look.border, align) + '"><is><t xml:space="preserve">' + esc(value) + '</t></is></c>';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function textRow(cells) { rowNo += 1; return '<row r="' + rowNo + '">' + cells + '</row>'; }
|
|
191
|
+
|
|
192
|
+
async function push(text, final) {
|
|
193
|
+
buffered += text;
|
|
194
|
+
if (buffered.length < CHUNK && !final) return;
|
|
195
|
+
sheet.push(fflate.strToU8(buffered), Boolean(final));
|
|
196
|
+
buffered = '';
|
|
197
|
+
if (failed) throw failed;
|
|
198
|
+
if (out.writableNeedDrain) await new Promise(function(r) { out.once('drain', r); });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// <cols> needs the widths, which need a look at the rows: written once
|
|
202
|
+
// the sample is full, or at close for a short table.
|
|
203
|
+
async function start() {
|
|
204
|
+
started = true;
|
|
205
|
+
var widths = columns.map(function(c, i) {
|
|
206
|
+
if (c.width) return c.width;
|
|
207
|
+
var longest = String(c.header || '').length;
|
|
208
|
+
// A date is as wide as its pattern, not as its JS string form.
|
|
209
|
+
if (c.kind === 'date') return Math.min(60, Math.max(10, longest, String(c.numFmt || 'yyyy-mm-dd').length) + 2);
|
|
210
|
+
sample.forEach(function(r) {
|
|
211
|
+
var shown = r.display ? r.display[i] : r.values[i];
|
|
212
|
+
if (shown !== null && shown !== undefined) longest = Math.max(longest, String(shown).length);
|
|
213
|
+
});
|
|
214
|
+
return Math.min(60, Math.max(8, longest + 2));
|
|
215
|
+
});
|
|
216
|
+
zip.add(sheet);
|
|
217
|
+
var head = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
|
|
218
|
+
'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">';
|
|
219
|
+
var titleRows = (doc.title ? 1 : 0) + (doc.subtitle ? 1 : 0);
|
|
220
|
+
headerRow = titleRows + (titleRows ? 2 : 1);
|
|
221
|
+
head += '<sheetViews><sheetView workbookViewId="0"><pane ySplit="' + headerRow + '" topLeftCell="A' + (headerRow + 1) +
|
|
222
|
+
'" activePane="bottomLeft" state="frozen"/></sheetView></sheetViews>';
|
|
223
|
+
head += '<cols>' + widths.map(function(w, i) { return '<col min="' + (i + 1) + '" max="' + (i + 1) + '" width="' + w + '" customWidth="1"/>'; }).join('') + '</cols>';
|
|
224
|
+
head += '<sheetData>';
|
|
225
|
+
if (doc.title) head += textRow('<c r="A' + (rowNo + 1) + '" t="inlineStr" s="' + styleId(null, FONT.title, 0, 0) + '"><is><t xml:space="preserve">' + esc(doc.title) + '</t></is></c>');
|
|
226
|
+
if (doc.subtitle) head += textRow('<c r="A' + (rowNo + 1) + '" t="inlineStr"><is><t xml:space="preserve">' + esc(doc.subtitle) + '</t></is></c>');
|
|
227
|
+
if (titleRows) rowNo += 1; // a blank row between the title block and the table
|
|
228
|
+
head += textRow(columns.map(function(c, i) {
|
|
229
|
+
var align = c.align === 'right' || c.align === 'center' ? c.align : null;
|
|
230
|
+
return '<c r="' + colName(i) + (rowNo + 1) + '" t="inlineStr" s="' + styleId(null, FONT.bold, FILL.header, 0, align) + '"><is><t xml:space="preserve">' + esc(c.header == null ? '' : c.header) + '</t></is></c>';
|
|
231
|
+
}).join(''));
|
|
232
|
+
await push(head);
|
|
233
|
+
var held = sample;
|
|
234
|
+
sample = null;
|
|
235
|
+
for (var k = 0; k < held.length; k++) await emit(held[k]);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function emit(row) {
|
|
239
|
+
if (rowNo >= MAX_ROWS) return false;
|
|
240
|
+
var look = LOOK[row.kind] || LOOK.detail;
|
|
241
|
+
var r = rowNo + 1;
|
|
242
|
+
var cells = '';
|
|
243
|
+
var values = row.values || [];
|
|
244
|
+
for (var i = 0; i < columns.length; i++) cells += cellXml(colName(i) + r, values[i], columns[i], look);
|
|
245
|
+
written += 1;
|
|
246
|
+
await push(textRow(cells));
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
writeRow: async function(row) {
|
|
252
|
+
offered += 1;
|
|
253
|
+
if (!started) {
|
|
254
|
+
sample.push(row);
|
|
255
|
+
if (sample.length >= SAMPLE_ROWS) await start();
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
return emit(row);
|
|
259
|
+
},
|
|
260
|
+
close: async function() {
|
|
261
|
+
if (!started) await start();
|
|
262
|
+
var lastRow = Math.max(rowNo, headerRow);
|
|
263
|
+
var filterRef = columns.length ? 'A' + headerRow + ':' + colName(columns.length - 1) + lastRow : null;
|
|
264
|
+
await push('</sheetData>' + (filterRef ? '<autoFilter ref="' + filterRef + '"/>' : '') +
|
|
265
|
+
'<pageSetup orientation="' + (columns.length > 6 ? 'landscape' : 'portrait') + '"/></worksheet>', true);
|
|
266
|
+
var sheetName = sheetNameOf(doc.title);
|
|
267
|
+
Object.keys(STATIC).forEach(function(name) { addFile(name, STATIC[name]); });
|
|
268
|
+
addFile('xl/workbook.xml', workbookXml(sheetName, filterRef ? '$A$' + headerRow + ':$' + colName(columns.length - 1) + '$' + lastRow : null));
|
|
269
|
+
addFile('xl/styles.xml', stylesXml(numFmts, xfs));
|
|
270
|
+
zip.end();
|
|
271
|
+
await zipFinished;
|
|
272
|
+
await new Promise(function(resolve, reject) { out.end(function(err) { return err ? reject(err) : resolve(); }); });
|
|
273
|
+
if (failed) throw failed;
|
|
274
|
+
return { rows: written, truncated: offered > written };
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
};
|
package/lib/uploader/index.js
CHANGED
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
// errorTable, // defaults to `<targetTable>_import_errors`
|
|
29
29
|
// batchDir, // where NDJSON files spool to. Defaults to os.tmpdir()/xeplr-actions
|
|
30
30
|
// keepBatchFiles, // default false — clean up NDJSON files after success
|
|
31
|
+
// replaceFrom, // optional, staged only: { column } — the move deletes target
|
|
32
|
+
// // rows from the earliest staged value on first (see mergeStaged)
|
|
31
33
|
// staged, // default false. true: batches land in <targetTable>__xeplr_staging
|
|
32
34
|
// // and move into the target in ONE transaction at the end, so a
|
|
33
35
|
// // load that stops or dies midway never touched the target.
|
|
@@ -247,7 +249,8 @@ async function upload(opts) {
|
|
|
247
249
|
targetTable: opts.targetTable,
|
|
248
250
|
columns: columns,
|
|
249
251
|
primaryKeys: opts.primaryKeys,
|
|
250
|
-
movementId: opts.movementId
|
|
252
|
+
movementId: opts.movementId,
|
|
253
|
+
replaceFrom: opts.replaceFrom
|
|
251
254
|
});
|
|
252
255
|
}
|
|
253
256
|
await opts.metaStore.recordEnd(opts.movementId, {
|
|
@@ -370,7 +373,16 @@ function normalizeOptions(opts) {
|
|
|
370
373
|
(process.env.XEPLR_ACTIONS_TMP_DIR || path.join(os.tmpdir(), 'xeplr-actions'));
|
|
371
374
|
opts.keepBatchFiles = !!opts.keepBatchFiles;
|
|
372
375
|
opts.staged = !!opts.staged;
|
|
376
|
+
// A driver that matches upsert keys only in a staged move (DuckDB: its
|
|
377
|
+
// written files keep no index) stages every upsert, asked or not.
|
|
378
|
+
if (opts.driver && opts.driver.stagesUpserts && (opts.primaryKeys || []).length) opts.staged = true;
|
|
373
379
|
opts.writeTable = null;
|
|
380
|
+
opts.replaceFrom = opts.replaceFrom || null;
|
|
381
|
+
if (opts.replaceFrom) {
|
|
382
|
+
if (!opts.replaceFrom.column) throw new Error('upload: replaceFrom needs a column');
|
|
383
|
+
if ((opts.primaryKeys || []).length) throw new Error('upload: replaceFrom replaces a range, so it takes no primaryKeys');
|
|
384
|
+
opts.staged = true;
|
|
385
|
+
}
|
|
374
386
|
if (opts.staged && typeof opts.driver.mergeStaged !== 'function') {
|
|
375
387
|
throw new Error('upload: staged loads are not supported for this database — its driver has no mergeStaged');
|
|
376
388
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xeplr/actions",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.19",
|
|
4
4
|
"description": "Named-action registry with typed inputs and safe invocation — plus built-in actions for HTTP, DB, files, email, spawn",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
"http",
|
|
17
17
|
"db",
|
|
18
18
|
"file",
|
|
19
|
-
"email"
|
|
19
|
+
"email",
|
|
20
|
+
"export",
|
|
21
|
+
"csv",
|
|
22
|
+
"xlsx",
|
|
23
|
+
"pdf"
|
|
20
24
|
],
|
|
21
25
|
"author": "xeplr",
|
|
22
26
|
"license": "MIT",
|
|
@@ -28,15 +32,17 @@
|
|
|
28
32
|
"access": "public"
|
|
29
33
|
},
|
|
30
34
|
"dependencies": {
|
|
31
|
-
"@xeplr/
|
|
32
|
-
"@xeplr/
|
|
35
|
+
"@xeplr/expression-handler": "^2.0.0",
|
|
36
|
+
"@xeplr/schema-handler": "^1.0.0"
|
|
33
37
|
},
|
|
34
38
|
"peerDependencies": {
|
|
35
39
|
"@duckdb/node-api": "1.5.5-r.3",
|
|
36
40
|
"@xeplr/email": "^1.0.0",
|
|
37
41
|
"@xeplr/utils": "^1.0.0",
|
|
38
42
|
"csv-parse": "^7.0.2",
|
|
43
|
+
"dejavu-fonts-ttf": "^2.37.3",
|
|
39
44
|
"exceljs": "^4.0.0",
|
|
45
|
+
"fflate": "^0.8.2",
|
|
40
46
|
"imapflow": "^1.0.0",
|
|
41
47
|
"knex": "^3.0.0",
|
|
42
48
|
"mailparser": "^3.0.0",
|
|
@@ -44,6 +50,7 @@
|
|
|
44
50
|
"mssql": "^11.0.0",
|
|
45
51
|
"mysql2": "^3.0.0",
|
|
46
52
|
"nodemailer": "^10.0.13",
|
|
53
|
+
"pdfkit": "^0.20.2",
|
|
47
54
|
"pg": "^8.0.0",
|
|
48
55
|
"saxes": "^6.0.0",
|
|
49
56
|
"ssh2-sftp-client": "^11.0.0",
|
|
@@ -97,15 +104,27 @@
|
|
|
97
104
|
},
|
|
98
105
|
"@xeplr/email": {
|
|
99
106
|
"optional": true
|
|
107
|
+
},
|
|
108
|
+
"fflate": {
|
|
109
|
+
"optional": true
|
|
110
|
+
},
|
|
111
|
+
"pdfkit": {
|
|
112
|
+
"optional": true
|
|
113
|
+
},
|
|
114
|
+
"dejavu-fonts-ttf": {
|
|
115
|
+
"optional": true
|
|
100
116
|
}
|
|
101
117
|
},
|
|
102
118
|
"devDependencies": {
|
|
103
119
|
"@duckdb/node-api": "^1.5.5-r.3",
|
|
104
120
|
"@xeplr/utils": "^1.0.0",
|
|
105
121
|
"csv-parse": "^7.0.2",
|
|
122
|
+
"dejavu-fonts-ttf": "^2.37.3",
|
|
106
123
|
"express": "^4.0.0",
|
|
124
|
+
"fflate": "^0.8.3",
|
|
107
125
|
"mssql": "^12.7.0",
|
|
108
126
|
"mysql2": "^3.23.1",
|
|
127
|
+
"pdfkit": "^0.20.2",
|
|
109
128
|
"pg": "^8.0.0",
|
|
110
129
|
"saxes": "^6.0.0",
|
|
111
130
|
"unzipper": "^0.12.0"
|