@dynamic-entity/server 1.14.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/index.d.ts ADDED
@@ -0,0 +1,180 @@
1
+ import { I as ImportLimits } from './write-template-C2dCEp48.js';
2
+ export { B as BatchInfo, a as ByteSource, D as DEFAULT_LIMITS, b as ImportRunResult, O as OnBatch, P as PreviewSheetOptions, R as ReadSheetOptions, c as RunImportOptions, S as SheetFormat, d as SheetPreview, e as SheetSource, f as TEMPLATE_EXTENSION, g as TEMPLATE_MEDIA_TYPE, T as TemplateFormat, W as WriteTemplateOptions, h as destroySource, i as detectFormat, j as guardRows, l as limitBytes, p as peek, k as previewSheet, r as readSheet, m as resolveLimits, n as runImport, t as toByteStream, w as writeTemplate } from './write-template-C2dCEp48.js';
3
+ import '@dynamic-entity/core';
4
+ import 'node:stream';
5
+
6
+ /**
7
+ * errors.ts — one error type, a closed set of codes, and an envelope that says nothing else.
8
+ *
9
+ * **Information disclosure is a threat, so the envelope is the guard.** A stack trace tells an
10
+ * attacker the library versions in use and the filesystem layout of the host; a parser's own
11
+ * message tells them which parser. So a response carries a machine-readable `code` and a
12
+ * message this package wrote, and nothing that came from below it. Whatever the real cause
13
+ * was stays on the server, reachable through `cause` for the consumer's logger.
14
+ */
15
+ /**
16
+ * Why a request failed, in a form a client can branch on.
17
+ *
18
+ * Closed on purpose: a client that has to match on message text is a client that breaks when
19
+ * the text improves, and the wire shape is shared with `ngx-dynamic-entity`.
20
+ */
21
+ type ImportErrorCode =
22
+ /** No file part in the multipart body. */
23
+ 'NO_FILE'
24
+ /** More file parts than `limits.maxFiles`. */
25
+ | 'TOO_MANY_FILES'
26
+ /** The body exceeded `limits.maxBytes`, or a field exceeded its own bound. */
27
+ | 'TOO_LARGE'
28
+ /** The bytes are not a format this package reads. Decided by content, not by filename. */
29
+ | 'UNSUPPORTED_FORMAT'
30
+ /** The bytes claim a format and are not valid in it. */
31
+ | 'MALFORMED_FILE'
32
+ /** A zip that inflates out of proportion to its size, or has too many entries. */
33
+ | 'ARCHIVE_REFUSED'
34
+ /** Rows, columns or one cell past the configured bound. */
35
+ | 'SHEET_TOO_LARGE'
36
+ /** No config is registered under the requested entity. */
37
+ | 'UNKNOWN_ENTITY'
38
+ /** The `plan` field is missing, is not JSON, or does not describe this config. */
39
+ | 'INVALID_PLAN'
40
+ /** The request took longer than `limits.totalTimeoutMs`, or stalled past the idle bound. */
41
+ | 'TIMEOUT'
42
+ /** The consumer's `onImport` threw. Nothing about *why* crosses the wire. */
43
+ | 'IMPORT_FAILED'
44
+ /** Anything else. Deliberately the least informative code there is. */
45
+ | 'INTERNAL';
46
+ /**
47
+ * What a failing route sends.
48
+ *
49
+ * `details` is present only for `INVALID_PLAN`, and carries `validateMappingPlan`'s own
50
+ * problems — those describe the client's own plan against a config the client already has,
51
+ * so they disclose nothing the caller did not send.
52
+ */
53
+ interface ImportErrorBody {
54
+ error: {
55
+ code: ImportErrorCode;
56
+ message: string;
57
+ details?: unknown;
58
+ };
59
+ }
60
+ declare class ImportError extends Error {
61
+ readonly code: ImportErrorCode;
62
+ readonly status: number;
63
+ readonly details?: unknown;
64
+ constructor(code: ImportErrorCode, message: string, options?: {
65
+ cause?: unknown;
66
+ details?: unknown;
67
+ });
68
+ }
69
+ /**
70
+ * Turn anything thrown into a response body.
71
+ *
72
+ * An `ImportError` says what it says. **Everything else becomes `INTERNAL` with a fixed
73
+ * message** — that is the whole point, and the reason this is a function rather than a
74
+ * `catch` block per route. A parser that throws `ENOENT: open '/srv/app/tmp/…'` must not be
75
+ * the thing that answers the request.
76
+ */
77
+ declare function toErrorBody(error: unknown): {
78
+ status: number;
79
+ body: ImportErrorBody;
80
+ };
81
+
82
+ /**
83
+ * cell-text.ts — how a typed cell is rendered when text is what has to cross the wire.
84
+ *
85
+ * Its own module because both the reader and the preview need it, and a second copy would be
86
+ * a second place for the date rule to be wrong.
87
+ */
88
+ /**
89
+ * Render a cell as the text a mapping screen shows.
90
+ *
91
+ * It mirrors `coerceTypedCell`'s date rule on purpose. A preview sample crosses the wire as
92
+ * text and the browser then runs `coerceCell` over that text, so if this rendered a `Date`
93
+ * with `String()` the preview would show — and the browser would coerce — a different day
94
+ * from the one the import will store. That is a preview lying about the only thing it exists
95
+ * to show, and it would be wrong by exactly one day, silently, only for users at a negative
96
+ * UTC offset. `run-import.spec.ts` asserts the two agree rather than trusting this comment.
97
+ */
98
+ declare function sampleText(raw: unknown): string;
99
+
100
+ /**
101
+ * guard-zip.ts — open the archive ourselves, and hand the parser only what it should see.
102
+ *
103
+ * An `.xlsx` is a zip, and a zip is the one upload format where the bytes on the wire say
104
+ * nothing about the work they cause. Ten kilobytes can inflate to ten gigabytes. `maxBytes`
105
+ * bounds what arrives and is therefore the wrong guard entirely for this.
106
+ *
107
+ * **Why the archive is rebuilt rather than passed through.** Two reasons, and the second was a
108
+ * surprise:
109
+ *
110
+ * 1. exceljs inflates internally and reports nothing while it does, so a limit applied to its
111
+ * output is applied after the memory it was protecting has been spent. The inflated size is
112
+ * knowable early only here, so this inflates every entry to *measure* it and refuses at the
113
+ * byte that crosses a bound.
114
+ * 2. **exceljs's streaming reader loses entries when a worksheet precedes the shared strings.**
115
+ * It defers such a worksheet to a temp file, and the archive's entry stream then ends at
116
+ * that entry — `xl/workbook.xml`, which exceljs's own writer puts *last*, never arrives,
117
+ * and the reader throws `Cannot read properties of undefined (reading 'sheets')`. Verified
118
+ * by replicating its parse loop: sixteen entries became eight, on two runs out of three.
119
+ * Reordering the archive so the strings, the relationships and the workbook all precede the
120
+ * worksheet avoids the deferral entirely — thirty-two runs, no failures, chunked and whole.
121
+ *
122
+ * Rebuilding also means the parser sees **only the parts an import needs**. The theme, the
123
+ * document properties, the drawings, the printer settings and every worksheet after the first
124
+ * are dropped rather than parsed, and a part that is never parsed cannot be hostile.
125
+ *
126
+ * What this costs is the retained archive, held **compressed** and therefore bounded by
127
+ * `maxBytes` — ten megabytes by default, against a fifty-thousand-row workbook's few. Entry
128
+ * data is re-packed exactly as it arrived; nothing is inflated into memory to be kept.
129
+ */
130
+
131
+ /**
132
+ * Walk an archive under every archive-shaped bound, and return the parts an import needs.
133
+ *
134
+ * Parsing stops at the first thing that is not a local file header — the central directory —
135
+ * because nothing after the last entry inflates and there is nothing there to guard.
136
+ */
137
+ declare function guardZip(source: AsyncIterable<Uint8Array>, limits: ImportLimits): Promise<Buffer>;
138
+
139
+ /**
140
+ * xlsx-source.ts — a workbook, one row at a time.
141
+ *
142
+ * exceljs's streaming reader does the XML; this file does the three things around it that
143
+ * decide whether a server-side xlsx import matches a browser one.
144
+ *
145
+ * 1. **Every byte goes through `guardZip` first.** exceljs inflates internally and reports
146
+ * nothing while it does, so the only place an archive's real size is knowable early is
147
+ * before it gets there.
148
+ * 2. **Blank rows are put back.** exceljs skips them — a sheet with a gap emits row 3 then
149
+ * row 5 — and a reader that passed that through would shift every later row number in
150
+ * every error message, and disagree with the CSV path about `skipped`. The row numbers a
151
+ * user is shown are the ones in their spreadsheet's gutter, so the gaps are synthesised.
152
+ * 3. **Cells keep their types.** A date cell arrives as a `Date` at UTC midnight and is
153
+ * handed on as one, because `coerceCell` reads a typed cell as its type. Stringifying here
154
+ * to tidy the signature is the exact defect this feature already fixed once.
155
+ *
156
+ * **What this holds, stated rather than discovered:** the archive's wanted parts, kept
157
+ * **compressed** and therefore bounded by `maxBytes` — ten megabytes by default, against a
158
+ * fifty-thousand-row workbook's few. That is the cost of `guardZip` rebuilding the archive
159
+ * rather than forwarding it, and the reasons it does are in that file. Row data is never
160
+ * accumulated, which is the part that scales with the file.
161
+ */
162
+
163
+ /**
164
+ * Turn one exceljs cell value into the value a record should be built from.
165
+ *
166
+ * A formula cell yields its **result**, never its formula: a sheet someone uploaded may
167
+ * contain anything, and importing the text `=SUM(A1:A9)` into a field is importing a string
168
+ * the user never saw. An error cell yields its error text, which fails coercion loudly rather
169
+ * than importing as blank.
170
+ */
171
+ declare function cellValue(raw: unknown): unknown;
172
+ /**
173
+ * Rows of the first worksheet, starting at spreadsheet row 1.
174
+ *
175
+ * The first worksheet and no other: a mapping plan addresses one sheet's columns, and picking
176
+ * a second one silently would import a different file from the one the user previewed.
177
+ */
178
+ declare function xlsxRows(source: AsyncIterable<Uint8Array>, limits: ImportLimits): AsyncGenerator<unknown[]>;
179
+
180
+ export { ImportError, type ImportErrorBody, type ImportErrorCode, ImportLimits, cellValue, guardZip, sampleText, toErrorBody, xlsxRows };