@noy-db/as-xlsx 0.1.0-pre.3
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/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/index.cjs +624 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +274 -0
- package/dist/index.d.ts +274 -0
- package/dist/index.js +579 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { DictEntry, VaultDiff, Vault } from '@noy-db/hub';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal zero-dependency XLSX writer.
|
|
5
|
+
*
|
|
6
|
+
* An `.xlsx` file is a ZIP archive (Office Open XML / OOXML) with
|
|
7
|
+
* SpreadsheetML inside. This writer emits the six parts needed for
|
|
8
|
+
* a valid worksheet and hands them to `@noy-db/as-zip`'s
|
|
9
|
+
* `writeZip()` to assemble the final `.xlsx` bytes.
|
|
10
|
+
*
|
|
11
|
+
* ## Emitted parts
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* [Content_Types].xml # MIME descriptors
|
|
15
|
+
* _rels/.rels # root → workbook pointer
|
|
16
|
+
* xl/workbook.xml # sheet list
|
|
17
|
+
* xl/_rels/workbook.xml.rels # sheet-part pointers
|
|
18
|
+
* xl/worksheets/sheet<N>.xml # cell data
|
|
19
|
+
* xl/sharedStrings.xml # string pool (Unicode-safe)
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Strings route through the shared-string table (`sharedStrings.xml`)
|
|
23
|
+
* rather than being inlined on cells, which is:
|
|
24
|
+
*
|
|
25
|
+
* 1. Slightly more compact when strings repeat (client names,
|
|
26
|
+
* status labels, locale codes).
|
|
27
|
+
* 2. Consistent with how Excel writes its own files — some
|
|
28
|
+
* strict-OOXML readers refuse inline strings.
|
|
29
|
+
*
|
|
30
|
+
* Numbers, booleans, and dates are written as typed cells; strings
|
|
31
|
+
* and everything else fall back to the shared-string path.
|
|
32
|
+
*
|
|
33
|
+
* ## Not supported
|
|
34
|
+
*
|
|
35
|
+
* - Cell styles (fonts, colours, borders, number formats).
|
|
36
|
+
* - Formulas, merged cells, frozen panes, auto-filter.
|
|
37
|
+
* - Charts, images, drawings.
|
|
38
|
+
* - Zip64 / archives > 4 GiB.
|
|
39
|
+
*
|
|
40
|
+
* @module
|
|
41
|
+
*/
|
|
42
|
+
/** One row in a sheet. Values are coerced per type at emit time. */
|
|
43
|
+
type XlsxRow = ReadonlyArray<unknown>;
|
|
44
|
+
/** One sheet in a workbook. */
|
|
45
|
+
interface XlsxSheet {
|
|
46
|
+
/** Sheet tab name — Excel caps at 31 chars; we truncate with `…`. */
|
|
47
|
+
readonly name: string;
|
|
48
|
+
/** Header row, rendered as row 1. Omit to skip the header. */
|
|
49
|
+
readonly header?: readonly string[];
|
|
50
|
+
/** Data rows — each is an array aligned with `header` if present. */
|
|
51
|
+
readonly rows: readonly XlsxRow[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build a complete `.xlsx` byte stream from the supplied sheet data.
|
|
55
|
+
* Pure — no I/O beyond the internal zip concatenation.
|
|
56
|
+
*/
|
|
57
|
+
declare function writeXlsx(sheets: readonly XlsxSheet[]): Promise<Uint8Array>;
|
|
58
|
+
/**
|
|
59
|
+
* Convert a 1-based column index to Excel A1 letter notation.
|
|
60
|
+
* 1 → A, 26 → Z, 27 → AA, 702 → ZZ, 703 → AAA.
|
|
61
|
+
*/
|
|
62
|
+
declare function colLetter(n: number): string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Minimal OOXML reader. Inverse of `writeXlsx` in `xlsx.ts`.
|
|
66
|
+
*
|
|
67
|
+
* Walks the three parts the writer emits:
|
|
68
|
+
*
|
|
69
|
+
* - `xl/sharedStrings.xml` → string table (idx → string)
|
|
70
|
+
* - `xl/workbook.xml` → sheet name list (with sheetId)
|
|
71
|
+
* - `xl/_rels/workbook.xml.rels` → sheetId → sheet part path
|
|
72
|
+
* - `xl/worksheets/sheet<N>.xml` → cell data
|
|
73
|
+
*
|
|
74
|
+
* Cell types matched to the writer's emission rules:
|
|
75
|
+
* - no `t` attribute → number (`<v>` is parsed as Number)
|
|
76
|
+
* - `t="s"` → shared-string ref
|
|
77
|
+
* - `t="b"` → boolean (`1` ↔ true, `0` ↔ false)
|
|
78
|
+
* - empty `<c />` → undefined
|
|
79
|
+
*
|
|
80
|
+
* Excel date serials are NOT auto-converted — the writer outputs ISO-
|
|
81
|
+
* 8601 strings via the shared-string path, so dates round-trip as
|
|
82
|
+
* strings unless the consumer opts into `dateFields` coercion (handled
|
|
83
|
+
* one layer up in `index.ts:fromBytes`).
|
|
84
|
+
*
|
|
85
|
+
* @module
|
|
86
|
+
*/
|
|
87
|
+
/** A row of cell values keyed by column letter (`'A' → 'foo'`). */
|
|
88
|
+
type ReadXlsxRow = Record<string, unknown>;
|
|
89
|
+
interface ReadXlsxSheet {
|
|
90
|
+
/** Sheet tab name from `xl/workbook.xml`. */
|
|
91
|
+
readonly name: string;
|
|
92
|
+
/** All rows in declaration order. Columns indexed by Excel letter. */
|
|
93
|
+
readonly rows: readonly ReadXlsxRow[];
|
|
94
|
+
}
|
|
95
|
+
interface ReadXlsxResult {
|
|
96
|
+
readonly sheets: readonly ReadXlsxSheet[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Decode an `.xlsx` (OOXML) byte stream into per-sheet row data. The
|
|
100
|
+
* caller decides what to do with the rows (header inference, type
|
|
101
|
+
* coercion, record building) — the reader stays format-only.
|
|
102
|
+
*
|
|
103
|
+
* Throws on malformed XML, missing parts, or sheet-id mismatches.
|
|
104
|
+
*/
|
|
105
|
+
declare function readXlsx(bytes: Uint8Array): Promise<ReadXlsxResult>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* **@noy-db/as-xlsx** — Excel spreadsheet plaintext export for noy-db.
|
|
109
|
+
*
|
|
110
|
+
* Produces a real `.xlsx` file (Office Open XML / OOXML) from one
|
|
111
|
+
* or more noy-db collections. Opens natively in Excel, Numbers,
|
|
112
|
+
* LibreOffice Calc, Google Sheets, and every modern spreadsheet
|
|
113
|
+
* tool.
|
|
114
|
+
*
|
|
115
|
+
* Zero runtime dependencies — the XLSX encoder builds the required
|
|
116
|
+
* SpreadsheetML parts and assembles them with
|
|
117
|
+
* `@noy-db/as-zip`'s `writeZip()` (STORE method; most xlsx
|
|
118
|
+
* contents are XML text which Excel compresses at open time anyway).
|
|
119
|
+
*
|
|
120
|
+
* Part of the `@noy-db/as-*` portable-artefact family, plaintext
|
|
121
|
+
* tier. See [`docs/patterns/as-exports.md`](https://github.com/vLannaAi/noy-db/blob/main/docs/patterns/as-exports.md).
|
|
122
|
+
*
|
|
123
|
+
* ## Authorisation
|
|
124
|
+
*
|
|
125
|
+
* Every call is gated by `assertCanExport('plaintext', 'xlsx')`.
|
|
126
|
+
*
|
|
127
|
+
* ```ts
|
|
128
|
+
* await db.grant('firm', {
|
|
129
|
+
* userId: 'accountant', role: 'viewer', passphrase: '…',
|
|
130
|
+
* exportCapability: { plaintext: ['xlsx'] },
|
|
131
|
+
* })
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* @packageDocumentation
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
/** Per-sheet options for the noy-db consumer API. */
|
|
138
|
+
interface AsXlsxSheetOptions {
|
|
139
|
+
/**
|
|
140
|
+
* Sheet tab name. Excel caps at 31 chars; longer names are
|
|
141
|
+
* truncated with `…`. Duplicates are suffixed `(2)`, `(3)`.
|
|
142
|
+
*/
|
|
143
|
+
readonly name: string;
|
|
144
|
+
/** Source collection. Must be in the caller's read ACL. */
|
|
145
|
+
readonly collection: string;
|
|
146
|
+
/**
|
|
147
|
+
* Field list + order. When omitted, columns are inferred from
|
|
148
|
+
* the union of keys across all records (first-record-wins order).
|
|
149
|
+
*/
|
|
150
|
+
readonly columns?: readonly string[];
|
|
151
|
+
/**
|
|
152
|
+
* Optional predicate against each decrypted record. Runs after
|
|
153
|
+
* decryption; doesn't reduce I/O.
|
|
154
|
+
*/
|
|
155
|
+
readonly filter?: (record: unknown) => boolean;
|
|
156
|
+
}
|
|
157
|
+
/** Single-collection convenience — passed where a sheet-list is accepted. */
|
|
158
|
+
interface AsXlsxOptions {
|
|
159
|
+
/** One or more sheets. At least one required. */
|
|
160
|
+
readonly sheets: readonly AsXlsxSheetOptions[];
|
|
161
|
+
}
|
|
162
|
+
/** Options for `download()` — adds optional filename. */
|
|
163
|
+
interface AsXlsxDownloadOptions extends AsXlsxOptions {
|
|
164
|
+
/** Filename offered to the browser. Default `'export.xlsx'`. */
|
|
165
|
+
readonly filename?: string;
|
|
166
|
+
}
|
|
167
|
+
/** Options for `write()` — requires explicit risk acknowledgement. */
|
|
168
|
+
interface AsXlsxWriteOptions extends AsXlsxOptions {
|
|
169
|
+
/** Tier 3 egress — see `docs/patterns/as-exports.md`. */
|
|
170
|
+
readonly acknowledgeRisks: true;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Convenience — single-collection shorthand. Equivalent to
|
|
174
|
+
* `toBytes(vault, { sheets: [{ name: collectionName, collection: collectionName }] })`.
|
|
175
|
+
*/
|
|
176
|
+
declare function toBytesFromCollection(vault: Vault, collectionName: string): Promise<Uint8Array>;
|
|
177
|
+
/**
|
|
178
|
+
* Build the `.xlsx` byte stream from one or more sheets. Pure
|
|
179
|
+
* beyond the auth check + store reads.
|
|
180
|
+
*/
|
|
181
|
+
declare function toBytes(vault: Vault, options: AsXlsxOptions): Promise<Uint8Array>;
|
|
182
|
+
/**
|
|
183
|
+
* Browser download. Requires a browser-like environment with
|
|
184
|
+
* `URL.createObjectURL` + `document.createElement`.
|
|
185
|
+
*/
|
|
186
|
+
declare function download(vault: Vault, options: AsXlsxDownloadOptions): Promise<void>;
|
|
187
|
+
/**
|
|
188
|
+
* Node file-write. Requires `acknowledgeRisks: true` because the
|
|
189
|
+
* plaintext xlsx persists past the process (Tier 3 egress).
|
|
190
|
+
*/
|
|
191
|
+
declare function write(vault: Vault, path: string, options: AsXlsxWriteOptions): Promise<void>;
|
|
192
|
+
|
|
193
|
+
type ImportPolicy = 'merge' | 'replace' | 'insert-only';
|
|
194
|
+
/**
|
|
195
|
+
* Thrown when a dict field contains two different keys whose labels are
|
|
196
|
+
* identical in any locale — making label→key inversion ambiguous.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* dict has: { key: 'a', labels: { en: 'Open' } } and { key: 'b', labels: { th: 'Open' } }
|
|
200
|
+
* → XlsxDictAmbiguityError('status', 'Open')
|
|
201
|
+
*/
|
|
202
|
+
declare class XlsxDictAmbiguityError extends Error {
|
|
203
|
+
readonly column: string;
|
|
204
|
+
readonly label: string;
|
|
205
|
+
constructor(column: string, label: string);
|
|
206
|
+
}
|
|
207
|
+
interface AsXlsxImportOptions {
|
|
208
|
+
/** Target collection. xlsx has no native collection grouping. */
|
|
209
|
+
readonly collection: string;
|
|
210
|
+
/**
|
|
211
|
+
* Sheet name to read. Defaults to the first sheet in the workbook.
|
|
212
|
+
*/
|
|
213
|
+
readonly sheet?: string;
|
|
214
|
+
/**
|
|
215
|
+
* 1-based header row index. Default `1` (first row).
|
|
216
|
+
*/
|
|
217
|
+
readonly headerRow?: number;
|
|
218
|
+
/**
|
|
219
|
+
* Optional field type hints. xlsx cells already have a type
|
|
220
|
+
* (number, boolean, shared-string), so this is for the few cases
|
|
221
|
+
* where the writer's emission rules don't preserve intent —
|
|
222
|
+
* notably ISO-date strings the writer routed through the shared-
|
|
223
|
+
* string path. `'date'` parses the value with `new Date()` and
|
|
224
|
+
* keeps the result as an ISO-8601 string for stable round-tripping.
|
|
225
|
+
*/
|
|
226
|
+
readonly fieldTypes?: Record<string, 'string' | 'number' | 'boolean' | 'date'>;
|
|
227
|
+
/** Field carrying the record id. Default `'id'`. */
|
|
228
|
+
readonly idKey?: string;
|
|
229
|
+
/** Reconciliation policy. Default `'merge'`. */
|
|
230
|
+
readonly policy?: ImportPolicy;
|
|
231
|
+
/**
|
|
232
|
+
* Per-field dict definitions for label→key inversion. When a column
|
|
233
|
+
* header matches a key here, cell values are matched against every
|
|
234
|
+
* locale label in the dict entries; matching labels are replaced by
|
|
235
|
+
* their stable key before building the ImportPlan.
|
|
236
|
+
*
|
|
237
|
+
* Takes precedence over any vault dictionary with the same name.
|
|
238
|
+
* For fields not listed here, `fromBytes` automatically tries
|
|
239
|
+
* `vault.dictionary(fieldName).list()` as a fallback.
|
|
240
|
+
*
|
|
241
|
+
* Unknown labels (no match in any locale) pass through as-is.
|
|
242
|
+
*/
|
|
243
|
+
readonly dicts?: Readonly<Record<string, readonly DictEntry[]>>;
|
|
244
|
+
}
|
|
245
|
+
interface AsXlsxImportPlan {
|
|
246
|
+
readonly plan: VaultDiff;
|
|
247
|
+
readonly policy: ImportPolicy;
|
|
248
|
+
apply(): Promise<void>;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Build an import plan from an `.xlsx` byte stream. Inverts what
|
|
252
|
+
* `toBytes()` writes — the first row is the header, subsequent rows
|
|
253
|
+
* are records keyed by the column letters in the header row.
|
|
254
|
+
*
|
|
255
|
+
* Capability: `assertCanImport('plaintext', 'xlsx')`.
|
|
256
|
+
* Atomicity: `apply()` runs inside `vault.noydb.transaction()`.
|
|
257
|
+
*
|
|
258
|
+
* **Not supported (matches the writer scope):**
|
|
259
|
+
* - Cell styles / number formats / date format codes
|
|
260
|
+
* - Formulas, merged cells, frozen panes
|
|
261
|
+
* - Inline strings → handled defensively (since some upstream tools
|
|
262
|
+
* emit them) but the writer never produces them
|
|
263
|
+
* - Excel date serials → not auto-detected; pass `fieldTypes: { ts:
|
|
264
|
+
* 'date' }` to coerce a numeric serial to ISO. Date round-trip via
|
|
265
|
+
* the writer (which emits ISO strings) works without a hint.
|
|
266
|
+
*
|
|
267
|
+
* **Dict-label inversion** — supply `dicts` per field (or populate vault
|
|
268
|
+
* dictionaries with `withI18n()`) and the reader automatically inverts
|
|
269
|
+
* human labels back to their stable keys. Ambiguous labels throw
|
|
270
|
+
* `XlsxDictAmbiguityError`; unknown labels pass through unchanged.
|
|
271
|
+
*/
|
|
272
|
+
declare function fromBytes(vault: Vault, bytes: Uint8Array, options: AsXlsxImportOptions): Promise<AsXlsxImportPlan>;
|
|
273
|
+
|
|
274
|
+
export { type AsXlsxDownloadOptions, type AsXlsxImportOptions, type AsXlsxImportPlan, type AsXlsxOptions, type AsXlsxSheetOptions, type AsXlsxWriteOptions, type ImportPolicy, type ReadXlsxResult, type ReadXlsxRow, type ReadXlsxSheet, XlsxDictAmbiguityError, type XlsxRow, type XlsxSheet, colLetter, download, fromBytes, readXlsx, toBytes, toBytesFromCollection, write, writeXlsx };
|