@noy-db/as-xlsx 0.7.0 → 0.8.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/LICENSE +202 -21
- package/NOTICE +1 -0
- package/README.md +16 -2
- package/dist/index.d.ts +43 -17
- package/dist/index.js +11 -8
- package/dist/index.js.map +1 -1
- package/package.json +15 -14
package/LICENSE
CHANGED
|
@@ -1,21 +1,202 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright 2026 vLannaAi
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/NOTICE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
noy-db — Copyright 2026 vLannaAi
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ ExcelJS, no xlsx-populate — ~230 LOC across the two-file encoder.
|
|
|
10
10
|
One runtime dependency: `fast-xml-parser`.
|
|
11
11
|
|
|
12
12
|
Part of the `@noy-db/as-*` portable-artefact family, plaintext
|
|
13
|
-
tier. See [`docs/packages-exports.md#authorization-model`](https://github.com/
|
|
13
|
+
tier. See [`docs/packages-exports.md#authorization-model`](https://github.com/noy-db/core/blob/main/docs/packages-exports.md#authorization-model).
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
@@ -25,6 +25,7 @@ pnpm add @noy-db/as-xlsx @noy-db/as-zip @noy-db/hub
|
|
|
25
25
|
```ts
|
|
26
26
|
await db.grant('firm', {
|
|
27
27
|
userId: 'accountant',
|
|
28
|
+
displayName: 'Somchai',
|
|
28
29
|
role: 'viewer',
|
|
29
30
|
secret: '…',
|
|
30
31
|
exportCapability: { plaintext: ['xlsx'] }, // or ['*']
|
|
@@ -41,12 +42,25 @@ Absent the grant → `ExportCapabilityError`.
|
|
|
41
42
|
```ts
|
|
42
43
|
import { toBytes } from '@noy-db/as-xlsx'
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
interface Invoice {
|
|
46
|
+
id: string
|
|
47
|
+
client: string
|
|
48
|
+
amount: string
|
|
49
|
+
status: string
|
|
50
|
+
issueDate: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const bytes = await toBytes<Invoice>(vault, {
|
|
45
54
|
sheets: [
|
|
46
55
|
{
|
|
47
56
|
name: 'Invoices',
|
|
48
57
|
collection: 'invoices',
|
|
58
|
+
// With a type argument, `columns` and `numberFormats` keys are checked
|
|
59
|
+
// against `Invoice` — a typo is a compile error, not an empty column.
|
|
49
60
|
columns: ['id', 'client', 'amount', 'status', 'issueDate'],
|
|
61
|
+
// Money is stored as a decimal string; without a format it lands in a
|
|
62
|
+
// TEXT cell and Excel's SUM() reads it as zero.
|
|
63
|
+
numberFormats: { amount: '#,##0.00' },
|
|
50
64
|
filter: (r) => r.status !== 'draft',
|
|
51
65
|
},
|
|
52
66
|
{ name: 'Payments', collection: 'payments' },
|
package/dist/index.d.ts
CHANGED
|
@@ -194,7 +194,7 @@ declare function zodSourceFor(schema: InferredSchema): string;
|
|
|
194
194
|
* contents are XML text which Excel compresses at open time anyway).
|
|
195
195
|
*
|
|
196
196
|
* Part of the `@noy-db/as-*` portable-artefact family, plaintext
|
|
197
|
-
* tier. See [`docs/patterns/as-exports.md`](https://github.com/
|
|
197
|
+
* tier. See [`docs/patterns/as-exports.md`](https://github.com/noy-db/core/blob/main/docs/patterns/as-exports.md).
|
|
198
198
|
*
|
|
199
199
|
* ## Authorisation
|
|
200
200
|
*
|
|
@@ -211,7 +211,27 @@ declare function zodSourceFor(schema: InferredSchema): string;
|
|
|
211
211
|
*/
|
|
212
212
|
|
|
213
213
|
/** Per-sheet options for the noy-db consumer API. */
|
|
214
|
-
|
|
214
|
+
/**
|
|
215
|
+
* The column names addressable on a sheet.
|
|
216
|
+
*
|
|
217
|
+
* ⚠️ `keyof unknown` is `never`, so writing `readonly (keyof T & string)[]`
|
|
218
|
+
* directly would type `columns` as `never[]` for every call that does not pass
|
|
219
|
+
* a type argument — i.e. it would break every existing caller while looking
|
|
220
|
+
* like a tightening. The conditional keeps `string` in that case and narrows
|
|
221
|
+
* only when the caller actually said what the record is.
|
|
222
|
+
*/
|
|
223
|
+
type AsXlsxColumn<T> = [keyof T] extends [never] ? string : keyof T & string;
|
|
224
|
+
/**
|
|
225
|
+
* One sheet's selection options.
|
|
226
|
+
*
|
|
227
|
+
* `T` is the shape of a decrypted record in this sheet's collection, defaulting
|
|
228
|
+
* to `unknown` so existing calls compile unchanged. Note that `AsXlsxOptions`
|
|
229
|
+
* threads a SINGLE `T` across every sheet: a workbook mixing collections with
|
|
230
|
+
* different record shapes still gets `unknown` unless the shapes are unioned.
|
|
231
|
+
* Per-sheet inference would need the sheet list to be a tuple, which costs more
|
|
232
|
+
* in call-site noise than it buys.
|
|
233
|
+
*/
|
|
234
|
+
interface AsXlsxSheetOptions<T = unknown> {
|
|
215
235
|
/**
|
|
216
236
|
* Sheet tab name. Excel caps at 31 chars; longer names are
|
|
217
237
|
* truncated with `…`. Duplicates are suffixed `(2)`, `(3)`.
|
|
@@ -223,12 +243,12 @@ interface AsXlsxSheetOptions {
|
|
|
223
243
|
* Field list + order. When omitted, columns are inferred from
|
|
224
244
|
* the union of keys across all records (first-record-wins order).
|
|
225
245
|
*/
|
|
226
|
-
readonly columns?: readonly
|
|
246
|
+
readonly columns?: readonly AsXlsxColumn<T>[];
|
|
227
247
|
/**
|
|
228
248
|
* Optional predicate against each decrypted record. Runs after
|
|
229
249
|
* decryption; doesn't reduce I/O.
|
|
230
250
|
*/
|
|
231
|
-
readonly filter?: (record:
|
|
251
|
+
readonly filter?: (record: T) => boolean;
|
|
232
252
|
/**
|
|
233
253
|
* Optional per-column character widths (Excel `wch` units). When set,
|
|
234
254
|
* the emitted sheet opens with the requested column widths instead of
|
|
@@ -245,12 +265,18 @@ interface AsXlsxSheetOptions {
|
|
|
245
265
|
*/
|
|
246
266
|
readonly widths?: ReadonlyArray<number | undefined>;
|
|
247
267
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
268
|
+
* Per-field Excel number-format codes (e.g. `{ amount: '#,##0.00' }` for
|
|
269
|
+
* currency). The value is coerced to a number so the format renders, which is
|
|
270
|
+
* what makes `SUM()` work: hub stores money as a decimal STRING to avoid
|
|
271
|
+
* float error, and a string lands in a text cell that Excel sums as zero.
|
|
272
|
+
* Money carries no introspection signal, so this is opt-in.
|
|
273
|
+
*
|
|
274
|
+
* ⚠️ Was smart-mode only until 0.7.1-pre.1, and silently ignored elsewhere —
|
|
275
|
+
* a declared option that did nothing on the default path (#8). It now applies
|
|
276
|
+
* in every mode, through one shared helper, so the two paths cannot drift
|
|
277
|
+
* again.
|
|
252
278
|
*/
|
|
253
|
-
readonly numberFormats?: Record<
|
|
279
|
+
readonly numberFormats?: Partial<Record<AsXlsxColumn<T>, string>>;
|
|
254
280
|
/**
|
|
255
281
|
* Smart mode only: per-field explicit dropdown value lists. Overrides the
|
|
256
282
|
* auto-detected enum/ref dropdown for that field.
|
|
@@ -334,9 +360,9 @@ interface AsXlsxSummarySpec {
|
|
|
334
360
|
readonly aggregates: readonly AsXlsxSummaryAggregate[];
|
|
335
361
|
}
|
|
336
362
|
/** Single-collection convenience — passed where a sheet-list is accepted. */
|
|
337
|
-
interface AsXlsxOptions {
|
|
363
|
+
interface AsXlsxOptions<T = unknown> {
|
|
338
364
|
/** One or more sheets. At least one required. */
|
|
339
|
-
readonly sheets: readonly AsXlsxSheetOptions[];
|
|
365
|
+
readonly sheets: readonly AsXlsxSheetOptions<T>[];
|
|
340
366
|
/** Smart mode only: groupBy summary sheets (live SUMIFS/COUNTIFS/AVERAGEIFS). */
|
|
341
367
|
readonly summaries?: readonly AsXlsxSummarySpec[];
|
|
342
368
|
/**
|
|
@@ -384,12 +410,12 @@ interface AsXlsxOptions {
|
|
|
384
410
|
};
|
|
385
411
|
}
|
|
386
412
|
/** Options for `download()` — adds optional filename. */
|
|
387
|
-
interface AsXlsxDownloadOptions extends AsXlsxOptions {
|
|
413
|
+
interface AsXlsxDownloadOptions<T = unknown> extends AsXlsxOptions<T> {
|
|
388
414
|
/** Filename offered to the browser. Default `'export.xlsx'`. */
|
|
389
415
|
readonly filename?: string;
|
|
390
416
|
}
|
|
391
417
|
/** Options for `write()` — requires explicit risk acknowledgement. */
|
|
392
|
-
interface AsXlsxWriteOptions extends AsXlsxOptions {
|
|
418
|
+
interface AsXlsxWriteOptions<T = unknown> extends AsXlsxOptions<T> {
|
|
393
419
|
/** Tier 3 egress — see `docs/patterns/as-exports.md`. */
|
|
394
420
|
readonly acknowledgeRisks: true;
|
|
395
421
|
}
|
|
@@ -402,17 +428,17 @@ declare function toBytesFromCollection(vault: Vault, collectionName: string): Pr
|
|
|
402
428
|
* Build the `.xlsx` byte stream from one or more sheets. Pure
|
|
403
429
|
* beyond the auth check + store reads.
|
|
404
430
|
*/
|
|
405
|
-
declare function toBytes(vault: Vault, options: AsXlsxOptions): Promise<Uint8Array>;
|
|
431
|
+
declare function toBytes<T = unknown>(vault: Vault, options: AsXlsxOptions<T>): Promise<Uint8Array>;
|
|
406
432
|
/**
|
|
407
433
|
* Browser download. Requires a browser-like environment with
|
|
408
434
|
* `URL.createObjectURL` + `document.createElement`.
|
|
409
435
|
*/
|
|
410
|
-
declare function download(vault: Vault, options: AsXlsxDownloadOptions): Promise<void>;
|
|
436
|
+
declare function download<T = unknown>(vault: Vault, options: AsXlsxDownloadOptions<T>): Promise<void>;
|
|
411
437
|
/**
|
|
412
438
|
* Node file-write. Requires `acknowledgeRisks: true` because the
|
|
413
439
|
* plaintext xlsx persists past the process (Tier 3 egress).
|
|
414
440
|
*/
|
|
415
|
-
declare function write(vault: Vault, path: string, options: AsXlsxWriteOptions): Promise<void>;
|
|
441
|
+
declare function write<T = unknown>(vault: Vault, path: string, options: AsXlsxWriteOptions<T>): Promise<void>;
|
|
416
442
|
/**
|
|
417
443
|
* One vault entry in a multi-vault export. Supplies the pre-opened
|
|
418
444
|
* vault, the sheet specs to render for it, and an optional closure
|
|
@@ -561,4 +587,4 @@ type AsXlsxImportPlan = ImportPlan;
|
|
|
561
587
|
*/
|
|
562
588
|
declare function fromBytes(vault: Vault, bytes: Uint8Array, options: AsXlsxImportOptions): Promise<AsXlsxImportPlan>;
|
|
563
589
|
|
|
564
|
-
export { type AsXlsxDownloadOptions, type AsXlsxImportOptions, type AsXlsxImportPlan, type AsXlsxOptions, type AsXlsxSheetOptions, type AsXlsxSummaryAggregate, type AsXlsxSummarySpec, type AsXlsxWriteOptions, type InferredCollection, type InferredField, type InferredSchema, type InferredType, type MultiVaultDenormColumn, type MultiVaultXlsxEntry, type MultiVaultXlsxOptions, type ReadXlsxResult, type ReadXlsxRow, type ReadXlsxSheet, XlsxDictAmbiguityError, type XlsxFormulaCell, type XlsxRow, type XlsxSheet, type XlsxStyledCell, type XlsxValidation, colLetter, download, formula, fromBytes, inferSchema, readXlsx, styled, toBytes, toBytesFromCollection, toBytesMultiVault, write, writeXlsx, zodSourceFor };
|
|
590
|
+
export { type AsXlsxColumn, type AsXlsxDownloadOptions, type AsXlsxImportOptions, type AsXlsxImportPlan, type AsXlsxOptions, type AsXlsxSheetOptions, type AsXlsxSummaryAggregate, type AsXlsxSummarySpec, type AsXlsxWriteOptions, type InferredCollection, type InferredField, type InferredSchema, type InferredType, type MultiVaultDenormColumn, type MultiVaultXlsxEntry, type MultiVaultXlsxOptions, type ReadXlsxResult, type ReadXlsxRow, type ReadXlsxSheet, XlsxDictAmbiguityError, type XlsxFormulaCell, type XlsxRow, type XlsxSheet, type XlsxStyledCell, type XlsxValidation, colLetter, download, formula, fromBytes, inferSchema, readXlsx, styled, toBytes, toBytesFromCollection, toBytesMultiVault, write, writeXlsx, zodSourceFor };
|
package/dist/index.js
CHANGED
|
@@ -536,10 +536,11 @@ async function toBytes(vault, options) {
|
|
|
536
536
|
}
|
|
537
537
|
const projected = projectRecords(vault, sheetOpt.collection, records, options.redact);
|
|
538
538
|
const columns = sheetOpt.columns ?? inferColumns(projected);
|
|
539
|
+
const fmts = sheetOpt.numberFormats;
|
|
539
540
|
materialisedSheets.push({
|
|
540
541
|
name: sheetOpt.name,
|
|
541
|
-
header: columns,
|
|
542
|
-
rows: projected.map((r) => columns.map((c) => r[c] ?? null)),
|
|
542
|
+
header: [...columns],
|
|
543
|
+
rows: projected.map((r) => columns.map((c) => numberFormatted(r[c] ?? null, fmts?.[c]))),
|
|
543
544
|
...sheetOpt.widths !== void 0 ? { widths: sheetOpt.widths } : {}
|
|
544
545
|
});
|
|
545
546
|
}
|
|
@@ -657,11 +658,16 @@ function projectRecords(vault, collectionName, records, redact) {
|
|
|
657
658
|
const projectionOpts = redact === true ? void 0 : { sensitivity: redact.sensitivity };
|
|
658
659
|
return records.map((r) => applyListProjection(desc, r, projectionOpts));
|
|
659
660
|
}
|
|
660
|
-
function
|
|
661
|
+
function numberFormatted(raw, fmt) {
|
|
662
|
+
if (fmt === void 0) return raw;
|
|
663
|
+
const num = typeof raw === "string" && raw.trim() !== "" && Number.isFinite(Number(raw)) ? Number(raw) : raw;
|
|
664
|
+
return styled(num, fmt);
|
|
665
|
+
}
|
|
666
|
+
function buildFlatSheet(name, columns, records, numberFormats) {
|
|
661
667
|
return {
|
|
662
668
|
name,
|
|
663
669
|
header: [...columns],
|
|
664
|
-
rows: records.map((r) => columns.map((c) => r[c] ?? null))
|
|
670
|
+
rows: records.map((r) => columns.map((c) => numberFormatted(r[c] ?? null, numberFormats?.[c])))
|
|
665
671
|
};
|
|
666
672
|
}
|
|
667
673
|
function safeStringify(v) {
|
|
@@ -774,10 +780,7 @@ async function buildSmartSheets(vault, options) {
|
|
|
774
780
|
const rowNum = i + 2;
|
|
775
781
|
const baseCells = baseCols.map((c) => {
|
|
776
782
|
const raw = c === "id" ? r.id ?? null : r[c] ?? null;
|
|
777
|
-
|
|
778
|
-
if (fmt === void 0) return raw;
|
|
779
|
-
const num = typeof raw === "string" && raw.trim() !== "" && Number.isFinite(Number(raw)) ? Number(raw) : raw;
|
|
780
|
-
return styled(num, fmt);
|
|
783
|
+
return numberFormatted(raw, m.opt.numberFormats?.[c]);
|
|
781
784
|
});
|
|
782
785
|
const i18nCells = m.i18nFields.flatMap((f) => {
|
|
783
786
|
const rawMap = r[f] && typeof r[f] === "object" ? r[f] : {};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/xlsx.ts","../src/read.ts","../src/infer.ts"],"sourcesContent":["/**\n * **@noy-db/as-xlsx** — Excel spreadsheet plaintext export for noy-db.\n *\n * Produces a real `.xlsx` file (Office Open XML / OOXML) from one\n * or more noy-db collections. Opens natively in Excel, Numbers,\n * LibreOffice Calc, Google Sheets, and every modern spreadsheet\n * tool.\n *\n * Zero runtime dependencies — the XLSX encoder builds the required\n * SpreadsheetML parts and assembles them with\n * `@noy-db/as-zip`'s `writeZip()` (STORE method; most xlsx\n * contents are XML text which Excel compresses at open time anyway).\n *\n * Part of the `@noy-db/as-*` portable-artefact family, plaintext\n * tier. See [`docs/patterns/as-exports.md`](https://github.com/vLannaAi/noy-db/blob/main/docs/patterns/as-exports.md).\n *\n * ## Authorisation\n *\n * Every call is gated by `assertCanExport('plaintext', 'xlsx')`.\n *\n * ```ts\n * await db.grant('firm', {\n * userId: 'accountant', role: 'viewer', secret: '…',\n * exportCapability: { plaintext: ['xlsx'] },\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport { type Vault, type DictEntry } from '@noy-db/hub'\nimport { applyListProjection } from '@noy-db/hub/introspection'\nimport { writeXlsx, colLetter, formula, styled, type XlsxSheet, type XlsxValidation } from './xlsx.js'\nimport { readXlsx } from './read.js'\n\nexport { writeXlsx, colLetter, formula, styled, type XlsxSheet, type XlsxRow, type XlsxFormulaCell, type XlsxStyledCell, type XlsxValidation } from './xlsx.js'\nexport { readXlsx, type ReadXlsxResult, type ReadXlsxSheet, type ReadXlsxRow } from './read.js'\nexport { inferSchema, zodSourceFor, type InferredSchema, type InferredCollection, type InferredField, type InferredType } from './infer.js'\n\n/** Per-sheet options for the noy-db consumer API. */\nexport interface AsXlsxSheetOptions {\n /**\n * Sheet tab name. Excel caps at 31 chars; longer names are\n * truncated with `…`. Duplicates are suffixed `(2)`, `(3)`.\n */\n readonly name: string\n /** Source collection. Must be in the caller's read ACL. */\n readonly collection: string\n /**\n * Field list + order. When omitted, columns are inferred from\n * the union of keys across all records (first-record-wins order).\n */\n readonly columns?: readonly string[]\n /**\n * Optional predicate against each decrypted record. Runs after\n * decryption; doesn't reduce I/O.\n */\n readonly filter?: (record: unknown) => boolean\n /**\n * Optional per-column character widths (Excel `wch` units). When set,\n * the emitted sheet opens with the requested column widths instead of\n * Excel's default 10-character fallback. Index aligned with\n * `columns` / inferred-column order.\n *\n * Non-finite or non-positive entries are skipped so consumers can\n * mix explicit + auto (pass `undefined` for \"auto\").\n *\n * Length is NOT validated against `columns.length` — extra entries\n * are harmless (no `<col>` is emitted past the column count) and a\n * short array leaves trailing columns at Excel's default. This\n * mirrors `XlsxSheet.widths`, which it threads through.\n */\n readonly widths?: ReadonlyArray<number | undefined>\n /**\n * Smart mode only: per-field Excel number-format codes (e.g.\n * `{ amount: '#,##0.00' }` for currency). The value is coerced to a number so\n * the format renders. Money has no introspection signal, so currency\n * formatting is opt-in here.\n */\n readonly numberFormats?: Record<string, string>\n /**\n * Smart mode only: per-field explicit dropdown value lists. Overrides the\n * auto-detected enum/ref dropdown for that field.\n */\n readonly dropdowns?: Record<string, readonly string[]>\n /**\n * Smart mode only: fields whose stored value is a multi-locale i18n map\n * (`{ en: '…', th: '…' }`). Each becomes per-locale columns plus a display\n * column resolved **live by the global LANG cell** (change LANG → every label\n * re-renders). Read raw, so open the export vault without an active locale.\n */\n readonly i18nFields?: readonly string[]\n /**\n * Smart mode only: map a dict-backed (code) field to its dictionary name, e.g.\n * `{ status: 'status' }`. Emits a hidden `_Lookups_<dict>` sheet (code +\n * per-locale labels) and a `<field>__label` display column resolved by the\n * global LANG cell via `VLOOKUP(code, …, MATCH(LANG, …))`.\n */\n readonly dictFields?: Record<string, string>\n /**\n * Multi-vault export only: pull FK-referenced fields from a supporting vault\n * into this sheet as extra columns. Each entry resolves `localField` on the\n * primary record against the `keyField` of another vault's collection (matched\n * by `from.label`/`from.collection`), and emits the `pick` field value as\n * `column`. Appended AFTER the declared `columns` in declaration order.\n * Unresolved FK (row not in the supporting-vault index) → empty cell.\n * Ignored by single-vault `toBytes`.\n */\n readonly denormalize?: readonly MultiVaultDenormColumn[]\n}\n\n/**\n * One denormalized column in a multi-vault export: pulls a field from a\n * supporting vault into the primary sheet via an in-memory join.\n *\n * @example\n * ```ts\n * {\n * column: 'entityName', // new column header in the primary sheet\n * localField: 'entityId', // FK field on the primary record\n * from: { label: 'directory', collection: 'entities', keyField: 'id', pick: 'name' },\n * }\n * ```\n */\nexport interface MultiVaultDenormColumn {\n /** Header of the new column to append to the primary sheet. */\n readonly column: string\n /** Field on the primary record whose value is the FK. */\n readonly localField: string\n readonly from: {\n /** Label of the supporting vault entry (matches `MultiVaultXlsxEntry.label`). */\n readonly label: string\n /** Collection in the supporting vault. */\n readonly collection: string\n /** Field in the supporting record that is the join key (usually `'id'`). */\n readonly keyField: string\n /** Field in the supporting record to copy as the denorm value. */\n readonly pick: string\n }\n}\n\n/** One aggregate column in a smart-mode summary sheet (#414 P3). */\nexport interface AsXlsxSummaryAggregate {\n /** Column header for this aggregate. */\n readonly label: string\n readonly op: 'sum' | 'count' | 'avg'\n /** Field to aggregate — required for `sum`/`avg`, ignored for `count`. */\n readonly field?: string\n}\n\n/**\n * A groupBy summary sheet (#414 P3): groups the source collection's sheet by\n * `groupBy` and emits live SUMIFS/COUNTIFS/AVERAGEIFS columns (values cached at\n * export). The source value columns must be numeric for the live formulas to\n * compute (apply `numberFormats` to money fields).\n */\nexport interface AsXlsxSummarySpec {\n /** Summary sheet name. */\n readonly name: string\n /** Source collection (must be exported as a sheet). */\n readonly from: string\n /** Field to group by. */\n readonly groupBy: string\n readonly aggregates: readonly AsXlsxSummaryAggregate[]\n}\n\n/** Single-collection convenience — passed where a sheet-list is accepted. */\nexport interface AsXlsxOptions {\n /** One or more sheets. At least one required. */\n readonly sheets: readonly AsXlsxSheetOptions[]\n /** Smart mode only: groupBy summary sheets (live SUMIFS/COUNTIFS/AVERAGEIFS). */\n readonly summaries?: readonly AsXlsxSummarySpec[]\n /**\n * Smart mode only: summary formula dialect. `'excel'` (default) emits\n * cross-compatible per-row SUMIFS/COUNTIFS/AVERAGEIFS. `'sheets'` emits a\n * single Google-Sheets `QUERY` formula per summary — **Sheets-only** (QUERY\n * errors in Excel); use when the target is Google Sheets.\n */\n readonly dialect?: 'excel' | 'sheets'\n /**\n * Smart-workbook mode (#414). Emits a relational workbook instead of a flat\n * dump:\n * - every sheet is **id-first** (record `id` in column A);\n * - each foreign-key field (auto-detected via `vault.dumpSchema()`) gets a\n * `<field>__label` column — a cross-sheet `VLOOKUP` that resolves the\n * reference to the target's first field, carrying a **cached** label so it\n * shows immediately and recomputes live on edit;\n * - a `_manifest` index sheet lists every collection, its row count, and\n * its refs.\n *\n * Requires unique sheet names ≤ 31 chars (so cross-sheet refs resolve) and an\n * `id` field on records. Defaults to the existing flat export when omitted.\n */\n readonly smart?: boolean\n /**\n * Apply the hub's `applyListProjection` read-projection to every sheet's\n * records before rendering. `true` redacts only `classifiedFields` (mask /\n * omit / rider, per the field's preset). The object form additionally\n * redacts fields carrying a plain `fieldMeta` `sensitivity: 'pii' |\n * 'secret'` tag, per `sensitivity: 'omit' | 'mask'`.\n *\n * Caveat: `describe()` reflects the declarations of *this session's*\n * collection instance — redaction only takes effect when the collection\n * was opened (this call or earlier in the session) with its\n * `classifiedFields` / `fieldMeta` options. This is presentation-layer\n * redaction; it never affects what's on disk. Sealed handles are\n * unaffected either way — they always serialize as `'[sealed]'`, so\n * ciphertext never leaks regardless of this option.\n *\n * Rider companion fields (e.g. `pan_last4`) remain visible as their own\n * columns — they are safe write-time projections.\n */\n readonly redact?: boolean | { readonly sensitivity: 'omit' | 'mask' }\n}\n\n/** Options for `download()` — adds optional filename. */\nexport interface AsXlsxDownloadOptions extends AsXlsxOptions {\n /** Filename offered to the browser. Default `'export.xlsx'`. */\n readonly filename?: string\n}\n\n/** Options for `write()` — requires explicit risk acknowledgement. */\nexport interface AsXlsxWriteOptions extends AsXlsxOptions {\n /** Tier 3 egress — see `docs/patterns/as-exports.md`. */\n readonly acknowledgeRisks: true\n}\n\n/**\n * Convenience — single-collection shorthand. Equivalent to\n * `toBytes(vault, { sheets: [{ name: collectionName, collection: collectionName }] })`.\n */\nexport async function toBytesFromCollection(\n vault: Vault,\n collectionName: string,\n): Promise<Uint8Array> {\n return toBytes(vault, {\n sheets: [{ name: collectionName, collection: collectionName }],\n })\n}\n\n/**\n * Build the `.xlsx` byte stream from one or more sheets. Pure\n * beyond the auth check + store reads.\n */\nexport async function toBytes(vault: Vault, options: AsXlsxOptions): Promise<Uint8Array> {\n vault.assertCanExport('plaintext', 'xlsx')\n\n if (options.sheets.length === 0) {\n throw new Error('as-xlsx: at least one sheet is required')\n }\n\n if (options.smart) {\n const { sheets, definedNames } = await buildSmartSheets(vault, options)\n return writeXlsx(sheets, { definedNames })\n }\n\n const materialisedSheets: XlsxSheet[] = []\n for (const sheetOpt of options.sheets) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const collection = vault.collection<any>(sheetOpt.collection)\n const list = await collection.list()\n const records: Record<string, unknown>[] = []\n for (const item of list) {\n const r = item as Record<string, unknown>\n if (sheetOpt.filter && !sheetOpt.filter(r)) continue\n records.push(r)\n }\n const projected = projectRecords(vault, sheetOpt.collection, records, options.redact)\n const columns = sheetOpt.columns ?? inferColumns(projected)\n materialisedSheets.push({\n name: sheetOpt.name,\n header: columns,\n rows: projected.map((r) => columns.map((c) => r[c] ?? null)),\n ...(sheetOpt.widths !== undefined ? { widths: sheetOpt.widths } : {}),\n })\n }\n\n return writeXlsx(materialisedSheets)\n}\n\n/**\n * Browser download. Requires a browser-like environment with\n * `URL.createObjectURL` + `document.createElement`.\n */\nexport async function download(vault: Vault, options: AsXlsxDownloadOptions): Promise<void> {\n const bytes = await toBytes(vault, options)\n const filename = options.filename ?? 'export.xlsx'\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const blob = new Blob([bytes as any], {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n a.click()\n URL.revokeObjectURL(url)\n}\n\n/**\n * Node file-write. Requires `acknowledgeRisks: true` because the\n * plaintext xlsx persists past the process (Tier 3 egress).\n */\nexport async function write(\n vault: Vault,\n path: string,\n options: AsXlsxWriteOptions,\n): Promise<void> {\n if (options.acknowledgeRisks !== true) {\n throw new Error(\n 'as-xlsx.write: acknowledgeRisks: true is required for on-disk plaintext output. ' +\n 'This call creates a persistent plaintext xlsx outside noy-db\\'s encrypted storage — ' +\n 'see docs/patterns/as-exports.md §\"The three tiers of \\\\\"plaintext out\\\\\"\"',\n )\n }\n const bytes = await toBytes(vault, options)\n const { writeFile } = await import('node:fs/promises')\n await writeFile(path, bytes)\n}\n\n// ── multi-vault export ─────────────────────────────────────────────\n\n/**\n * One vault entry in a multi-vault export. Supplies the pre-opened\n * vault, the sheet specs to render for it, and an optional closure\n * (per-collection id allowlist computed externally by the orchestrator).\n */\nexport interface MultiVaultXlsxEntry {\n readonly vault: Vault\n readonly sheets: readonly AsXlsxSheetOptions[]\n /**\n * Optional per-collection id allowlist (e.g. from walkCrossVaultClosure).\n * When set, only rows whose `id` appears in the set are exported for that\n * collection. Omit to export all rows.\n */\n readonly closure?: ReadonlyMap<string, ReadonlySet<string>>\n /**\n * Optional display label for sheet-name prefixing.\n * Defaults to `vault.name`.\n */\n readonly label?: string\n /**\n * Same semantics as {@link AsXlsxOptions.redact}, applied to this entry's\n * sheets via `entry.vault.collection(sheetCollection).describe()` — each\n * entry's own vault, since a multi-vault export spans several sessions.\n */\n readonly redact?: boolean | { readonly sensitivity: 'omit' | 'mask' }\n}\n\n/** Options for {@link toBytesMultiVault}. */\nexport interface MultiVaultXlsxOptions {\n /**\n * Separator inserted between the vault label and the sheet name when\n * building tab names. Default `'_'`. Names are then truncated to 31 chars\n * (Excel limit) by `writeXlsx`.\n */\n readonly sheetSeparator?: string\n}\n\n/**\n * Build an `.xlsx` byte stream spanning **multiple vaults**. Each entry\n * supplies a pre-opened vault with its sheet specs and an optional\n * per-collection id-closure (rows filtered to exactly those ids). A\n * `_manifest` sheet is prepended that lists every vault-collection pair\n * and its exported record count.\n *\n * ## Auth\n * Every vault in `entries` must independently hold `assertCanExport('plaintext','xlsx')`.\n * The check fires fail-fast before any rows are materialised.\n *\n * ## Architecture\n * This function is **edge-pure** — it takes pre-opened vaults and a\n * pre-computed closure; it performs no cross-vault FK walk itself.\n * Cross-vault orchestration lives in the outward orchestration layer (allowed direction).\n *\n * ## Two-pass execution (when `denormalize` is declared)\n * **Pass 1** — load and closure-filter ALL entries' rows, building a\n * `Map<\"${label}/${collection}\", Map<keyValue, row>>` index keyed by each\n * sheet's `keyField` (default `'id'`). Only closure-filtered rows are indexed,\n * so an FK pointing outside the closure yields an empty cell (correct: that\n * row was not referenced / not exported).\n * **Pass 2** — emit each sheet; for sheets with `denormalize`, append each\n * denorm column AFTER the declared columns by resolving the index lookup.\n */\nexport async function toBytesMultiVault(\n entries: readonly MultiVaultXlsxEntry[],\n options: MultiVaultXlsxOptions = {},\n): Promise<Uint8Array> {\n const sep = options.sheetSeparator ?? '_'\n\n // Fail-fast auth check on every vault before materialising any rows.\n for (const entry of entries) {\n entry.vault.assertCanExport('plaintext', 'xlsx')\n }\n\n // ── Pass 1: load + closure-filter all rows, build denorm index ────\n\n /** Loaded rows per (label, collection) pair. */\n interface LoadedSheet {\n entry: MultiVaultXlsxEntry\n prefix: string\n sheetOpt: AsXlsxSheetOptions\n records: Record<string, unknown>[]\n }\n const loaded: LoadedSheet[] = []\n\n /** Denorm index: `\"${label}/${collection}\" → Map<keyValue, row>`. */\n const denormIndex = new Map<string, Map<string, Record<string, unknown>>>()\n\n for (const entry of entries) {\n const prefix = entry.label ?? entry.vault.name\n for (const s of entry.sheets) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const all = await entry.vault.collection<any>(s.collection).list()\n const records: Record<string, unknown>[] = []\n for (const item of all) {\n const r = item as Record<string, unknown>\n // Closure filter: if a closure is supplied for this collection, keep\n // only rows whose id is in the allowlist.\n const allow = entry.closure?.get(s.collection)\n if (allow && !allow.has(String((r as { id?: unknown }).id))) continue\n // User-supplied row predicate (same semantics as single-vault toBytes).\n if (s.filter && !s.filter(r)) continue\n records.push(r)\n }\n const projected = projectRecords(entry.vault, s.collection, records, entry.redact)\n loaded.push({ entry, prefix, sheetOpt: s, records: projected })\n\n // Build index for this collection, keyed by every field that any denorm\n // might reference as `keyField`. We index by `id` by default, and also\n // by any explicitly declared `keyField` that differs from `'id'`.\n const indexKey = `${prefix}/${s.collection}`\n const collectionIndex = denormIndex.get(indexKey) ?? new Map<string, Record<string, unknown>>()\n denormIndex.set(indexKey, collectionIndex)\n for (const r of projected) {\n // Always index by `id` (most common key) and by any declared keyFields.\n const idVal = (r as { id?: unknown }).id\n if (idVal != null) collectionIndex.set(safeStringify(idVal), r)\n }\n }\n }\n\n // If any sheet declares denormalize with a non-'id' keyField, also index by that.\n for (const ls of loaded) {\n for (const d of ls.sheetOpt.denormalize ?? []) {\n if (d.from.keyField === 'id') continue // already indexed\n const indexKey = `${d.from.label}/${d.from.collection}`\n const idx = denormIndex.get(indexKey)\n if (!idx) continue\n // Find the loaded sheet for that label+collection and re-index by keyField.\n const srcLoaded = loaded.find(\n (l) => l.prefix === d.from.label && l.sheetOpt.collection === d.from.collection,\n )\n if (!srcLoaded) continue\n for (const r of srcLoaded.records) {\n const kv = r[d.from.keyField]\n if (kv != null) idx.set(safeStringify(kv), r)\n }\n }\n }\n\n // ── Pass 2: emit sheets with optional denorm columns ─────────────\n\n const allSheets: XlsxSheet[] = []\n const manifestRows: (string | number)[][] = []\n\n for (const { prefix, sheetOpt: s, records } of loaded) {\n const baseColumns = s.columns ?? inferColumns(records)\n const denormDefs = s.denormalize ?? []\n\n if (denormDefs.length === 0) {\n // Fast path: no denorm — identical to Task 1 behaviour.\n const sheetName = `${prefix}${sep}${s.name}`\n allSheets.push(buildFlatSheet(sheetName, baseColumns, records))\n manifestRows.push([prefix, s.collection, records.length])\n continue\n }\n\n // Denorm path: append extra columns after the declared ones.\n const allColumns = [...baseColumns, ...denormDefs.map((d) => d.column)]\n const sheetName = `${prefix}${sep}${s.name}`\n const rows = records.map((r) => {\n const baseCells = baseColumns.map((c) => r[c] ?? null)\n const denormCells = denormDefs.map((d) => {\n const idxKey = `${d.from.label}/${d.from.collection}`\n const idx = denormIndex.get(idxKey)\n if (!idx) return ''\n const fkVal = r[d.localField]\n if (fkVal == null) return ''\n const supporting = idx.get(safeStringify(fkVal))\n if (!supporting) return '' // unresolved FK → empty cell\n return supporting[d.from.pick] ?? ''\n })\n return [...baseCells, ...denormCells]\n })\n allSheets.push({ name: sheetName, header: allColumns, rows })\n manifestRows.push([prefix, s.collection, records.length])\n }\n\n // Prepend the _manifest sheet.\n allSheets.unshift({\n name: '_manifest',\n header: ['Vault', 'Collection', 'Records'],\n rows: manifestRows,\n })\n\n return writeXlsx(allSheets)\n}\n\n// ── internals ─────────────────────────────────────────────────────\n\n/**\n * Apply `AsXlsxOptions.redact` to a sheet's records via the hub's\n * `applyListProjection`, describing the collection on its own vault (each\n * sheet/path may have a different vault instance — multi-vault entries in\n * particular). No-op (returns a shallow copy) when `redact` is unset.\n */\nfunction projectRecords(\n vault: Vault,\n collectionName: string,\n records: readonly Record<string, unknown>[],\n redact: AsXlsxOptions['redact'],\n): Record<string, unknown>[] {\n if (redact === undefined || redact === false) return [...records]\n const desc = vault.collection(collectionName).describe()\n const projectionOpts = redact === true ? undefined : { sensitivity: redact.sensitivity }\n return records.map((r) => applyListProjection(desc, r, projectionOpts))\n}\n\n/**\n * Build a flat {@link XlsxSheet} from pre-filtered records.\n * Used by both {@link toBytes} (via its own inline path) and\n * {@link toBytesMultiVault}.\n */\nfunction buildFlatSheet(\n name: string,\n columns: readonly string[],\n records: readonly Record<string, unknown>[],\n): XlsxSheet {\n return {\n name,\n header: [...columns],\n rows: records.map((r) => columns.map((c) => r[c] ?? null)),\n }\n}\n\n/** Safely stringify an unknown id/code/label (objects → JSON, never '[object Object]'). */\nfunction safeStringify(v: unknown): string {\n if (typeof v === 'string') return v\n if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'bigint') return String(v)\n if (v == null) return ''\n try {\n return JSON.stringify(v) ?? ''\n } catch {\n return ''\n }\n}\n\n/** Coerce an arbitrary value to a formula cached-value (string/number/boolean). */\nfunction asCached(v: unknown): string | number | boolean {\n if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'string') return v\n return safeStringify(v)\n}\n\n/**\n * Smart-workbook builder (#414 P1+P2): id-first sheets, FK→VLOOKUP label columns,\n * a `_manifest` index, and — when i18n fields are declared — per-locale columns\n * with a display column driven live by a global `LANG` named range on a\n * `_settings` sheet. Refs auto-detected from `vault.dumpSchema()`.\n */\nasync function buildSmartSheets(\n vault: Vault,\n options: AsXlsxOptions,\n): Promise<{ sheets: XlsxSheet[]; definedNames: { name: string; ref: string }[] }> {\n const snapshot = await vault.dumpSchema()\n const sheetNameByCollection = new Map<string, string>()\n for (const s of options.sheets) sheetNameByCollection.set(s.collection, s.name)\n\n interface Mat {\n opt: AsXlsxSheetOptions\n records: Record<string, unknown>[]\n cols: string[]\n labelMap: Map<string, unknown>\n i18nFields: string[]\n }\n const mats: Mat[] = []\n const localeSet = new Set<string>()\n for (const sheetOpt of options.sheets) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const collection = vault.collection<any>(sheetOpt.collection)\n const list = await collection.list()\n const records: Record<string, unknown>[] = []\n for (const item of list) {\n const r = item as Record<string, unknown>\n if (sheetOpt.filter && !sheetOpt.filter(r)) continue\n records.push(r)\n }\n const projected = projectRecords(vault, sheetOpt.collection, records, options.redact)\n const base = sheetOpt.columns ?? inferColumns(projected)\n const cols = ['id', ...base.filter((c) => c !== 'id')]\n const labelCol = cols.find((c) => c !== 'id')\n const labelMap = new Map<string, unknown>()\n if (labelCol) for (const r of projected) if (r.id != null) labelMap.set(safeStringify(r.id), r[labelCol])\n const i18nFields = (sheetOpt.i18nFields ?? []).filter((f) => cols.includes(f))\n for (const f of i18nFields) {\n for (const r of projected) {\n const v = r[f]\n if (v && typeof v === 'object') for (const loc of Object.keys(v as Record<string, unknown>)) localeSet.add(loc)\n }\n }\n mats.push({ opt: sheetOpt, records: projected, cols, labelMap, i18nFields })\n }\n const matByCollection = new Map(mats.map((m) => [m.opt.collection, m]))\n\n // Load declared dictionaries once; merge their locales into the global set.\n const dictEntries = new Map<string, DictEntry[]>()\n for (const m of mats) {\n for (const [field, dictName] of Object.entries(m.opt.dictFields ?? {})) {\n if (!m.cols.includes(field) || dictEntries.has(dictName)) continue\n dictEntries.set(dictName, await vault.dictionary(dictName).list())\n }\n }\n for (const entries of dictEntries.values()) {\n for (const e of entries) for (const loc of Object.keys(e.labels)) localeSet.add(loc)\n }\n\n const locales = [...localeSet].sort()\n const hasLang = locales.length > 0\n const defaultLocale = locales.includes('en') ? 'en' : (locales[0] ?? 'en')\n // Build an IF-chain over the per-locale cells, switched by the LANG name.\n const ifChain = (refOf: (loc: string) => string): string => {\n if (locales.length === 0) return '\"\"'\n let expr = refOf(locales[0]!)\n for (let k = locales.length - 1; k >= 0; k--) {\n expr = `IF(LANG=\"${locales[k]}\",${refOf(locales[k]!)},${expr})`\n }\n return expr\n }\n\n // Per-collection sheet metadata so summaries can reference data-sheet columns.\n const sheetMeta = new Map<string, { name: string; colIndex: Map<string, number> }>()\n\n const dataSheets: XlsxSheet[] = mats.map((m) => {\n const refs = snapshot.collections[m.opt.collection]?.refs ?? {}\n const fields = snapshot.collections[m.opt.collection]?.fields ?? {}\n const i18nSet = new Set(m.i18nFields)\n const refFields = Object.keys(refs).filter((f) => m.cols.includes(f) && matByCollection.has(refs[f]!.target))\n const baseCols = m.cols.filter((c) => !i18nSet.has(c))\n const i18nFlat = m.i18nFields.flatMap((f) => [f, ...locales.map((l) => `${f}__${l}`)])\n const dictPairs = Object.entries(m.opt.dictFields ?? {}).filter(([f]) => m.cols.includes(f))\n const header = [\n ...baseCols,\n ...i18nFlat,\n ...dictPairs.map(([f]) => `${f}__label`),\n ...refFields.map((f) => `${f}__label`),\n ]\n const colIndex = new Map<string, number>()\n header.forEach((h, i) => colIndex.set(h, i + 1))\n sheetMeta.set(m.opt.collection, { name: m.opt.name, colIndex })\n\n // Data-validation dropdowns on base columns: explicit > ref-range > enum.\n const lastRow = Math.max(m.records.length + 1, 2)\n const validations: XlsxValidation[] = []\n for (const field of baseCols) {\n const colL = colLetter(colIndex.get(field)!)\n const sqref = `${colL}2:${colL}${lastRow}`\n const explicit = m.opt.dropdowns?.[field]\n if (explicit && explicit.length > 0) {\n validations.push({ sqref, values: explicit })\n continue\n }\n const rf = refs[field]\n if (rf && matByCollection.has(rf.target)) {\n const targetSheet = sheetNameByCollection.get(rf.target)!\n const targetRows = Math.max(matByCollection.get(rf.target)!.records.length + 1, 2)\n validations.push({ sqref, formula1: `'${targetSheet}'!$A$2:$A$${targetRows}` })\n continue\n }\n const enumVals = fields[field]?.constraints?.['values']\n if (Array.isArray(enumVals) && enumVals.length > 0) validations.push({ sqref, values: enumVals.map((v) => safeStringify(v)) })\n }\n\n const rows = m.records.map((r, i) => {\n const rowNum = i + 2 // header is row 1\n const baseCells = baseCols.map((c) => {\n const raw = c === 'id' ? (r.id ?? null) : (r[c] ?? null)\n const fmt = m.opt.numberFormats?.[c]\n if (fmt === undefined) return raw\n const num = typeof raw === 'string' && raw.trim() !== '' && Number.isFinite(Number(raw)) ? Number(raw) : raw\n return styled(num as string | number | boolean | null, fmt)\n })\n const i18nCells = m.i18nFields.flatMap((f) => {\n const rawMap = (r[f] && typeof r[f] === 'object' ? r[f] : {}) as Record<string, unknown>\n const display = formula(\n ifChain((l) => `${colLetter(colIndex.get(`${f}__${l}`)!)}${rowNum}`),\n asCached(rawMap[defaultLocale] ?? ''),\n )\n return [display, ...locales.map((l) => (rawMap[l] ?? null) as unknown)]\n })\n const dictCells = dictPairs.map(([f, dn]) => {\n const codeRef = `${colLetter(colIndex.get(f)!)}${rowNum}`\n const lk = `_Lookups_${dn}`\n // MATCH(LANG, lookup header row) → the locale's column in the lookup sheet.\n const f1 = `IFERROR(VLOOKUP(${codeRef},'${lk}'!$A:$ZZ,MATCH(LANG,'${lk}'!$1:$1,0),FALSE),${codeRef})`\n const code = r[f]\n const entry = code == null ? undefined : dictEntries.get(dn)?.find((e) => e.key === safeStringify(code))\n return formula(f1, asCached(entry?.labels[defaultLocale] ?? (code ?? '')))\n })\n const refCells = refFields.map((f) => {\n const target = refs[f]!.target\n const targetSheet = sheetNameByCollection.get(target)!\n const targetMat = matByCollection.get(target)!\n const codeRef = `${colLetter(colIndex.get(f)!)}${rowNum}`\n const f1 = `IFERROR(VLOOKUP(${codeRef},'${targetSheet}'!$A:$ZZ,2,FALSE),\"\")`\n const code = r[f]\n const cached = code == null ? '' : asCached(targetMat.labelMap.get(safeStringify(code)))\n return formula(f1, cached)\n })\n return [...baseCells, ...i18nCells, ...dictCells, ...refCells]\n })\n return {\n name: m.opt.name,\n header,\n rows,\n ...(validations.length > 0 ? { validations } : {}),\n ...(m.opt.widths !== undefined ? { widths: m.opt.widths } : {}),\n }\n })\n\n const manifest: XlsxSheet = {\n name: '_manifest',\n header: ['Collection', 'Records', 'Refs'],\n rows: mats.map((m) => {\n const refs = snapshot.collections[m.opt.collection]?.refs ?? {}\n return [m.opt.name, m.records.length, Object.entries(refs).map(([f, r]) => `${f}→${r.target}`).join(', ')]\n }),\n }\n\n // Global LANG control — a Settings sheet + named range every i18n/dict label\n // references via IF(LANG=…). Only emitted when there are i18n fields.\n // GroupBy summary sheets (#414 P3/P5). Excel dialect (default): per-row live\n // SUMIFS/COUNTIFS/AVERAGEIFS, values cached. Sheets dialect: a single\n // Google-Sheets QUERY formula (Sheets-only — QUERY errors in Excel).\n const dialect = options.dialect ?? 'excel'\n const summarySheets: XlsxSheet[] = []\n for (const spec of options.summaries ?? []) {\n const src = sheetMeta.get(spec.from)\n const srcMat = matByCollection.get(spec.from)\n const gCol = src?.colIndex.get(spec.groupBy)\n if (!src || !srcMat || gCol === undefined) continue\n const gLetter = colLetter(gCol)\n\n if (dialect === 'sheets') {\n // One live QUERY formula that spills the grouped table (#414 P5).\n const selects: string[] = []\n const labels: string[] = []\n for (const a of spec.aggregates) {\n if (a.op === 'count') {\n selects.push(`COUNT(${gLetter})`)\n labels.push(`COUNT(${gLetter}) '${a.label}'`)\n continue\n }\n const vIdx = a.field ? src.colIndex.get(a.field) : undefined\n if (vIdx === undefined) continue\n const vL = colLetter(vIdx)\n const fn = a.op === 'sum' ? 'SUM' : 'AVG'\n selects.push(`${fn}(${vL})`)\n labels.push(`${fn}(${vL}) '${a.label}'`)\n }\n const q = `QUERY('${src.name}'!A:ZZ, \"SELECT ${gLetter}, ${selects.join(', ')} GROUP BY ${gLetter} LABEL ${labels.join(', ')}\", 1)`\n summarySheets.push({ name: spec.name, rows: [[formula(q)]] })\n continue\n }\n\n const seen = new Set<string>()\n const groups: unknown[] = []\n for (const r of srcMat.records) {\n const k = safeStringify(r[spec.groupBy])\n if (!seen.has(k)) {\n seen.add(k)\n groups.push(r[spec.groupBy])\n }\n }\n const header = [spec.groupBy, ...spec.aggregates.map((a) => a.label)]\n const rows = groups.map((g, i) => {\n const rowNum = i + 2\n const groupRecs = srcMat.records.filter((r) => safeStringify(r[spec.groupBy]) === safeStringify(g))\n const cells: unknown[] = [g]\n for (const a of spec.aggregates) {\n if (a.op === 'count') {\n cells.push(formula(`COUNTIFS('${src.name}'!$${gLetter}:$${gLetter},$A${rowNum})`, groupRecs.length))\n continue\n }\n const vIdx = a.field ? src.colIndex.get(a.field) : undefined\n if (vIdx === undefined) {\n cells.push(null)\n continue\n }\n const vLetter = colLetter(vIdx)\n const nums = groupRecs.map((r) => Number(r[a.field!])).filter((n) => Number.isFinite(n))\n const sum = nums.reduce((s, n) => s + n, 0)\n const cached = a.op === 'sum' ? sum : nums.length ? sum / nums.length : 0\n const fn = a.op === 'sum' ? 'SUMIFS' : 'AVERAGEIFS'\n cells.push(\n formula(`${fn}('${src.name}'!$${vLetter}:$${vLetter},'${src.name}'!$${gLetter}:$${gLetter},$A${rowNum})`, cached),\n )\n }\n return cells\n })\n summarySheets.push({ name: spec.name, header, rows })\n }\n\n // Hidden lookup sheets: one per declared dictionary (code + per-locale labels).\n const lookupSheets: XlsxSheet[] = [...dictEntries.entries()].map(([dn, entries]) => ({\n name: `_Lookups_${dn}`,\n header: ['Code', ...locales],\n rows: entries.map((e) => [e.key, ...locales.map((l) => e.labels[l] ?? '')]),\n }))\n\n const settingsSheets: XlsxSheet[] = []\n const definedNames: { name: string; ref: string }[] = []\n if (hasLang) {\n settingsSheets.push({\n name: '_settings',\n header: ['Setting', 'Value'],\n rows: [['Language', defaultLocale]],\n validations: [{ sqref: 'B2:B2', values: locales }],\n })\n definedNames.push({ name: 'LANG', ref: `'_settings'!$B$2` })\n }\n\n return {\n sheets: [...settingsSheets, ...lookupSheets, manifest, ...summarySheets, ...dataSheets],\n definedNames,\n }\n}\n\nfunction inferColumns(records: readonly Record<string, unknown>[]): string[] {\n const seen = new Set<string>()\n const out: string[] = []\n for (const r of records) {\n for (const key of Object.keys(r)) {\n if (!seen.has(key)) {\n seen.add(key)\n out.push(key)\n }\n }\n }\n return out\n}\n\n// ── Reader ──────────────────────────────────────\n\nimport { diffVault } from '@noy-db/hub'\n\n// Hub-owned as of 0.7 (ADR 0004). This line replaced a local declaration that\n// existed identically in six as-* packages, with nothing comparing them.\nimport type { ImportPolicy, ImportPlan } from '@noy-db/hub/as'\nexport type { ImportPolicy }\n\n/**\n * Thrown when a dict field contains two different keys whose labels are\n * identical in any locale — making label→key inversion ambiguous.\n *\n * @example\n * dict has: { key: 'a', labels: { en: 'Open' } } and { key: 'b', labels: { th: 'Open' } }\n * → XlsxDictAmbiguityError('status', 'Open')\n */\nexport class XlsxDictAmbiguityError extends Error {\n constructor(\n public readonly column: string,\n public readonly label: string,\n ) {\n super(\n `as-xlsx.fromBytes: dict for column \"${column}\" has ambiguous label \"${label}\" — ` +\n 'it maps to more than one key across locales. ' +\n 'Supply a stricter dict or resolve the label conflict before importing.',\n )\n this.name = 'XlsxDictAmbiguityError'\n }\n}\n\nexport interface AsXlsxImportOptions {\n /** Target collection. xlsx has no native collection grouping. */\n readonly collection: string\n /**\n * Sheet name to read. Defaults to the first sheet in the workbook.\n */\n readonly sheet?: string\n /**\n * 1-based header row index. Default `1` (first row).\n */\n readonly headerRow?: number\n /**\n * Optional field type hints. xlsx cells already have a type\n * (number, boolean, shared-string), so this is for the few cases\n * where the writer's emission rules don't preserve intent —\n * notably ISO-date strings the writer routed through the shared-\n * string path. `'date'` parses the value with `new Date()` and\n * keeps the result as an ISO-8601 string for stable round-tripping.\n */\n readonly fieldTypes?: Record<string, 'string' | 'number' | 'boolean' | 'date'>\n /** Field carrying the record id. Default `'id'`. */\n readonly idKey?: string\n /** Reconciliation policy. Default `'merge'`. */\n readonly policy?: ImportPolicy\n /**\n * Per-field dict definitions for label→key inversion. When a column\n * header matches a key here, cell values are matched against every\n * locale label in the dict entries; matching labels are replaced by\n * their stable key before building the ImportPlan.\n *\n * Takes precedence over any vault dictionary with the same name.\n * For fields not listed here, `fromBytes` automatically tries\n * `vault.dictionary(fieldName).list()` as a fallback.\n *\n * Unknown labels (no match in any locale) pass through as-is.\n */\n readonly dicts?: Readonly<Record<string, readonly DictEntry[]>>\n /**\n * Read a sheet produced by smart export (#414 P4). Reverses the smart layout:\n * reconstructs i18n fields from their per-locale columns (`<f>__<loc>` →\n * `{ loc: value }`), and drops derived columns — the i18n display column and\n * every `<f>__label` (FK/dict) formula column. Code columns (the real values)\n * pass through. Maps onto the existing collection schema (Mode A).\n */\n readonly smart?: boolean\n}\n\nexport type AsXlsxImportPlan = ImportPlan\n\n/**\n * Build an import plan from an `.xlsx` byte stream. Inverts what\n * `toBytes()` writes — the first row is the header, subsequent rows\n * are records keyed by the column letters in the header row.\n *\n * Capability: `assertCanImport('plaintext', 'xlsx')`.\n * Atomicity: `apply()` runs inside `vault.noydb.transaction()`.\n *\n * **Not supported (matches the writer scope):**\n * - Cell styles / number formats / date format codes\n * - Formulas, merged cells, frozen panes\n * - Inline strings → handled defensively (since some upstream tools\n * emit them) but the writer never produces them\n * - Excel date serials → not auto-detected; pass `fieldTypes: { ts:\n * 'date' }` to coerce a numeric serial to ISO. Date round-trip via\n * the writer (which emits ISO strings) works without a hint.\n *\n * **Dict-label inversion** — supply `dicts` per field (or populate vault\n * dictionaries with `withI18n()`) and the reader automatically inverts\n * human labels back to their stable keys. Ambiguous labels throw\n * `XlsxDictAmbiguityError`; unknown labels pass through unchanged.\n */\nexport async function fromBytes(\n vault: Vault,\n bytes: Uint8Array,\n options: AsXlsxImportOptions,\n): Promise<AsXlsxImportPlan> {\n vault.assertCanImport('plaintext', 'xlsx')\n\n const policy: ImportPolicy = options.policy ?? 'merge'\n const idKey = options.idKey ?? 'id'\n const types = options.fieldTypes ?? {}\n const headerRowIdx = (options.headerRow ?? 1) - 1\n if (headerRowIdx < 0) {\n throw new Error('as-xlsx.fromBytes: headerRow must be 1-based and >= 1')\n }\n\n const decoded = await readXlsx(bytes)\n if (decoded.sheets.length === 0) {\n return emptyXlsxPlan(vault, options.collection, policy, idKey)\n }\n const sheet = options.sheet === undefined\n ? decoded.sheets[0]!\n : decoded.sheets.find((s) => s.name === options.sheet)\n if (sheet === undefined) {\n throw new Error(\n `as-xlsx.fromBytes: workbook has no sheet named \"${options.sheet}\". ` +\n `Available: ${decoded.sheets.map((s) => `\"${s.name}\"`).join(', ')}`,\n )\n }\n\n const allRows = sheet.rows\n if (allRows.length <= headerRowIdx) {\n return emptyXlsxPlan(vault, options.collection, policy, idKey)\n }\n const headerRow = allRows[headerRowIdx]!\n // Map column letter → field name. Only columns that have a\n // non-empty header cell contribute fields; blank columns are\n // ignored on read so a half-populated header doesn't synthesise\n // numeric `__EMPTY` keys the way some xlsx libs do.\n const colToField = new Map<string, string>()\n for (const [col, value] of Object.entries(headerRow)) {\n const fieldName = headerCellToField(value)\n if (fieldName === '') continue\n colToField.set(col, fieldName)\n }\n\n // Build per-field inversion maps.\n // Priority: explicit dicts option > vault dictionary lookup.\n // Vault lookup fires for every column not covered by explicit dicts;\n // vault.dictionary(name).list() returns [] when no dict exists — safe to call on any name.\n const invertMaps = new Map<string, Map<string, string>>()\n const allFields = new Set(colToField.values())\n for (const field of allFields) {\n const explicitEntries = options.dicts?.[field]\n if (explicitEntries !== undefined && explicitEntries.length > 0) {\n invertMaps.set(field, buildInversionMap(field, explicitEntries))\n continue\n }\n try {\n const vaultEntries = await vault.dictionary(field).list()\n if (vaultEntries.length > 0) {\n invertMaps.set(field, buildInversionMap(field, vaultEntries))\n }\n } catch {\n // No dictionary for this field — skip silently\n }\n }\n\n // Smart layout: any `<base>__<suffix>` where suffix isn't 'label' marks an\n // i18n base whose display column (`<base>`) is a formula to be dropped and\n // whose per-locale columns rebuild the `{ loc: value }` map.\n const i18nBases = new Set<string>()\n if (options.smart) {\n for (const field of colToField.values()) {\n const m = /^(.+)__(.+)$/.exec(field)\n if (m && m[2] !== 'label') i18nBases.add(m[1]!)\n }\n }\n\n const records: Record<string, unknown>[] = []\n for (let i = headerRowIdx + 1; i < allRows.length; i++) {\n const row = allRows[i]!\n const record: Record<string, unknown> = {}\n let hasAny = false\n for (const [col, value] of Object.entries(row)) {\n const field = colToField.get(col)\n if (field === undefined) continue\n if (options.smart) {\n if (field.endsWith('__label')) continue // derived FK/dict label\n const lm = /^(.+)__(.+)$/.exec(field)\n if (lm && lm[2] !== 'label' && i18nBases.has(lm[1]!)) {\n const base = lm[1]!\n const coerced = coerceXlsxCell(value, types[base])\n if (coerced !== undefined && coerced !== '') {\n const map = (record[base] as Record<string, unknown> | undefined) ?? {}\n map[lm[2]!] = coerced\n record[base] = map\n hasAny = true\n }\n continue\n }\n if (i18nBases.has(field)) continue // i18n display column (formula)\n }\n const coerced = coerceXlsxCell(value, types[field])\n if (coerced !== undefined) {\n const invMap = invertMaps.get(field)\n if (invMap !== undefined && typeof coerced === 'string') {\n record[field] = invMap.get(coerced) ?? coerced\n } else {\n record[field] = coerced\n }\n hasAny = true\n }\n }\n if (hasAny) records.push(record)\n }\n\n const plan = await diffVault(vault, { [options.collection]: records }, {\n collections: [options.collection],\n idKey,\n })\n\n return {\n plan,\n policy,\n async apply(): Promise<void> {\n // Routes through the transactionsStrategy seam — clear error when\n // withTransactions() isn't opted in.\n await vault.noydb.transaction((tx) => {\n const txVault = tx.vault(vault.name)\n for (const entry of plan.added) {\n txVault.collection(entry.collection).put(entry.id, entry.record, { reason: 'import:xlsx' })\n }\n if (policy !== 'insert-only') {\n for (const entry of plan.modified) {\n txVault.collection(entry.collection).put(entry.id, entry.record, { reason: 'import:xlsx' })\n }\n }\n if (policy === 'replace') {\n for (const entry of plan.deleted) {\n txVault.collection(entry.collection).delete(entry.id)\n }\n }\n })\n },\n }\n}\n\nasync function emptyXlsxPlan(\n vault: Vault,\n collection: string,\n policy: ImportPolicy,\n idKey: string,\n): Promise<AsXlsxImportPlan> {\n const plan = await diffVault(vault, { [collection]: [] }, { collections: [collection], idKey })\n return { plan, policy, async apply() { /* nothing to do */ } }\n}\n\n/**\n * Build a label→key inversion map from a list of DictEntry objects.\n * Scans every locale in every entry's labels map. Throws\n * XlsxDictAmbiguityError if the same label string appears for two\n * different keys across any locale.\n */\nfunction buildInversionMap(column: string, entries: readonly DictEntry[]): Map<string, string> {\n const map = new Map<string, string>()\n for (const entry of entries) {\n for (const label of Object.values(entry.labels)) {\n if (label === '') continue\n const existing = map.get(label)\n if (existing !== undefined && existing !== entry.key) {\n throw new XlsxDictAmbiguityError(column, label)\n }\n map.set(label, entry.key)\n }\n }\n return map\n}\n\nfunction headerCellToField(value: unknown): string {\n // Header cells should be strings; defensively coerce numbers/booleans\n // and reject everything else (objects, undefined). Empty string =\n // \"this column has no header → ignore\".\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return ''\n}\n\nfunction coerceXlsxCell(\n value: unknown,\n type?: 'string' | 'number' | 'boolean' | 'date',\n): unknown {\n if (value === undefined || value === null) return undefined\n if (type === undefined) return value\n if (type === 'string') {\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return undefined\n }\n if (type === 'number') {\n if (typeof value === 'number') return value\n const n = Number(value)\n return Number.isFinite(n) ? n : undefined\n }\n if (type === 'boolean') {\n if (typeof value === 'boolean') return value\n if (value === 'true' || value === 1) return true\n if (value === 'false' || value === 0) return false\n return undefined\n }\n if (type === 'date') {\n // Excel date serial: days since 1900-01-01 with the historical\n // 1900-leap-year quirk. Numbers are converted; strings parsed\n // with `new Date()` and re-emitted as ISO so round-trips are\n // stable. Returning a string keeps the JSON envelope canonical.\n if (typeof value === 'number' && Number.isFinite(value)) {\n const ms = excelSerialToMs(value)\n const d = new Date(ms)\n return d.toISOString()\n }\n if (typeof value === 'string') {\n const d = new Date(value)\n if (!Number.isNaN(d.getTime())) return d.toISOString()\n }\n return undefined\n }\n return value\n}\n\n/**\n * Convert an Excel-style date serial to a JS millisecond timestamp.\n * Excel's \"1900 system\" treats day 1 as 1900-01-01 and includes the\n * non-existent 1900-02-29, so the offset between Excel serial and\n * Unix epoch days is 25569 for any date past 1900-03-01.\n *\n * Pre-1900-03 dates (serial ≤ 60) are uncommon in noy-db's domains\n * and we don't try to compensate for the leap-year bug there — they\n * round-trip with one-day skew, same as Excel itself.\n */\nfunction excelSerialToMs(serial: number): number {\n const EPOCH_OFFSET_DAYS = 25569\n const MS_PER_DAY = 86400_000\n return Math.round((serial - EPOCH_OFFSET_DAYS) * MS_PER_DAY)\n}\n","/**\n * Minimal zero-dependency XLSX writer.\n *\n * An `.xlsx` file is a ZIP archive (Office Open XML / OOXML) with\n * SpreadsheetML inside. This writer emits the six parts needed for\n * a valid worksheet and hands them to `@noy-db/as-zip`'s\n * `writeZip()` to assemble the final `.xlsx` bytes.\n *\n * ## Emitted parts\n *\n * ```\n * [Content_Types].xml # MIME descriptors\n * _rels/.rels # root → workbook pointer\n * xl/workbook.xml # sheet list\n * xl/_rels/workbook.xml.rels # sheet-part pointers\n * xl/worksheets/sheet<N>.xml # cell data\n * xl/sharedStrings.xml # string pool (Unicode-safe)\n * ```\n *\n * Strings route through the shared-string table (`sharedStrings.xml`)\n * rather than being inlined on cells, which is:\n *\n * 1. Slightly more compact when strings repeat (client names,\n * status labels, locale codes).\n * 2. Consistent with how Excel writes its own files — some\n * strict-OOXML readers refuse inline strings.\n *\n * Numbers, booleans, and dates are written as typed cells; strings\n * and everything else fall back to the shared-string path.\n *\n * ## Not supported\n *\n * - Cell styles (fonts, colours, borders, number formats).\n * - Formulas, merged cells, frozen panes, auto-filter.\n * - Charts, images, drawings.\n * - Zip64 / archives > 4 GiB.\n *\n * @module\n */\n\nimport { writeZip, type ZipEntry } from '@noy-db/as-zip'\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>'\nconst ENCODER = new TextEncoder()\n\n/**\n * A formula cell. Emits `<f>` so the spreadsheet recomputes live; `v` is an\n * optional cached result (what the formula currently evaluates to) so the value\n * shows immediately on open and survives a round-trip read. Build via\n * {@link formula}. The formula string must NOT include a leading `=`.\n */\nexport interface XlsxFormulaCell {\n readonly __xlsxFormula: string\n readonly v?: string | number | boolean\n}\n\n/** Build a {@link XlsxFormulaCell}. `f` is the formula body without a leading `=`. */\nexport function formula(f: string, cachedValue?: string | number | boolean): XlsxFormulaCell {\n return cachedValue === undefined ? { __xlsxFormula: f } : { __xlsxFormula: f, v: cachedValue }\n}\n\nfunction isFormulaCell(v: unknown): v is XlsxFormulaCell {\n return typeof v === 'object' && v !== null && typeof (v as { __xlsxFormula?: unknown }).__xlsxFormula === 'string'\n}\n\n/**\n * A value cell carrying a number-format code (e.g. `'#,##0.00'` for currency,\n * `'yyyy-mm-dd'` for dates). Build via {@link styled}. The format only renders\n * meaningfully on numeric values.\n */\nexport interface XlsxStyledCell {\n readonly __xlsxStyle: string\n readonly v: string | number | boolean | null\n}\n\n/** Build a {@link XlsxStyledCell} — a value plus an Excel number-format code. */\nexport function styled(value: string | number | boolean | null, numberFormat: string): XlsxStyledCell {\n return { __xlsxStyle: numberFormat, v: value }\n}\n\nfunction isStyledCell(v: unknown): v is XlsxStyledCell {\n return typeof v === 'object' && v !== null && typeof (v as { __xlsxStyle?: unknown }).__xlsxStyle === 'string'\n}\n\n/** A data-validation rule (dropdown) over a cell range. */\nexport interface XlsxValidation {\n /** Target range in A1 notation, e.g. `'B2:B100'`. */\n readonly sqref: string\n /** Inline allowed values → a `\"a,b,c\"` list. Mutually exclusive with `formula1`. */\n readonly values?: readonly string[]\n /** Explicit `formula1` (e.g. a range ref `Clients!$A$2:$A$999`). */\n readonly formula1?: string\n}\n\n/** One row in a sheet. A cell is a primitive value or an {@link XlsxFormulaCell}. */\nexport type XlsxRow = ReadonlyArray<unknown>\n\n/** One sheet in a workbook. */\nexport interface XlsxSheet {\n /** Sheet tab name — Excel caps at 31 chars; we truncate with `…`. */\n readonly name: string\n /** Header row, rendered as row 1. Omit to skip the header. */\n readonly header?: readonly string[]\n /** Data rows — each is an array aligned with `header` if present. */\n readonly rows: readonly XlsxRow[]\n /**\n * Optional per-column widths in Excel character units (same scale as\n * SheetJS's `wch`). When set, emits a `<cols>` block so Excel opens\n * the file with the columns sized as specified instead of the default\n * 10-character width. Index aligned with `header` / row cells.\n *\n * Non-finite or non-positive entries are skipped (column falls back\n * to Excel's default width). A consumer typically passes `undefined`\n * for \"auto\" columns and a number for explicit widths.\n */\n readonly widths?: ReadonlyArray<number | undefined>\n /** Data-validation dropdowns applied to ranges on this sheet. */\n readonly validations?: readonly XlsxValidation[]\n}\n\n/**\n * Build a complete `.xlsx` byte stream from the supplied sheet data.\n * Pure — no I/O beyond the internal zip concatenation.\n */\nexport async function writeXlsx(\n sheets: readonly XlsxSheet[],\n options: { definedNames?: ReadonlyArray<{ readonly name: string; readonly ref: string }> } = {},\n): Promise<Uint8Array> {\n if (sheets.length === 0) {\n throw new Error('writeXlsx: at least one sheet is required')\n }\n\n // Dedup sheet names (Excel rejects duplicates) + truncate to 31 chars.\n const seen = new Set<string>()\n const safeSheets: XlsxSheet[] = sheets.map((s, i) => {\n let name = truncateSheetName(s.name || `Sheet${i + 1}`)\n let n = 1\n while (seen.has(name)) {\n const suffix = `(${n++})`\n name = truncateSheetName(name.slice(0, 31 - suffix.length) + suffix)\n }\n seen.add(name)\n return { ...s, name }\n })\n\n // Build shared-string table across every sheet. Emit order = insertion order.\n const sharedStrings: string[] = []\n const stringIndex = new Map<string, number>()\n const internString = (s: string): number => {\n const existing = stringIndex.get(s)\n if (existing !== undefined) return existing\n const idx = sharedStrings.length\n sharedStrings.push(s)\n stringIndex.set(s, idx)\n return idx\n }\n\n // Number-format styles. cellXf 0 is the default (no format); each distinct\n // format code gets a numFmt (id 164+) and a cellXf that applies it.\n const numFmtCodes: string[] = []\n const styleIndexByCode = new Map<string, number>()\n const internStyle = (code: string): number => {\n const existing = styleIndexByCode.get(code)\n if (existing !== undefined) return existing\n const idx = numFmtCodes.length + 1 // cellXf index; 0 is the default xf\n numFmtCodes.push(code)\n styleIndexByCode.set(code, idx)\n return idx\n }\n\n // Build the worksheet XML for each sheet — coercing values per type.\n const sheetXmls: string[] = safeSheets.map((sheet) => {\n const lines: string[] = [\n XML_HEADER,\n '<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">',\n ]\n // `<cols>` must precede `<sheetData>` per OOXML schema. Emit one\n // `<col>` element per defined width; skip entries that are not\n // positive finite numbers so consumers can mix explicit + auto.\n if (sheet.widths && sheet.widths.length > 0) {\n const colLines: string[] = []\n for (let i = 0; i < sheet.widths.length; i++) {\n const w = sheet.widths[i]\n if (typeof w !== 'number' || !Number.isFinite(w) || w <= 0) continue\n const n = i + 1\n colLines.push(`<col min=\"${n}\" max=\"${n}\" width=\"${w}\" customWidth=\"1\"/>`)\n }\n if (colLines.length > 0) {\n lines.push('<cols>', ...colLines, '</cols>')\n }\n }\n lines.push('<sheetData>')\n let rowNum = 0\n if (sheet.header && sheet.header.length > 0) {\n rowNum++\n const cells = sheet.header\n .map((h, i) => {\n const idx = internString(String(h))\n return `<c r=\"${colLetter(i + 1)}${rowNum}\" t=\"s\"><v>${idx}</v></c>`\n })\n .join('')\n lines.push(`<row r=\"${rowNum}\">${cells}</row>`)\n }\n for (const row of sheet.rows) {\n rowNum++\n const cells = row\n .map((value, i) => cellXml(value, i + 1, rowNum, internString, internStyle))\n .join('')\n lines.push(`<row r=\"${rowNum}\">${cells}</row>`)\n }\n lines.push('</sheetData>')\n if (sheet.validations && sheet.validations.length > 0) {\n lines.push(`<dataValidations count=\"${sheet.validations.length}\">`)\n for (const dv of sheet.validations) {\n const f1 = dv.formula1 ?? `\"${(dv.values ?? []).join(',')}\"`\n lines.push(\n `<dataValidation type=\"list\" allowBlank=\"1\" showInputMessage=\"1\" showErrorMessage=\"1\" sqref=\"${escapeXmlAttr(dv.sqref)}\"><formula1>${escapeXmlText(f1)}</formula1></dataValidation>`,\n )\n }\n lines.push('</dataValidations>')\n }\n lines.push('</worksheet>')\n return lines.join('')\n })\n\n // ── Styles (number formats) ─────────────────────────────────────\n\n const hasStyles = numFmtCodes.length > 0\n const stylesXml = hasStyles\n ? [\n XML_HEADER,\n '<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">',\n `<numFmts count=\"${numFmtCodes.length}\">`,\n ...numFmtCodes.map((code, i) => `<numFmt numFmtId=\"${164 + i}\" formatCode=\"${escapeXmlAttr(code)}\"/>`),\n '</numFmts>',\n '<fonts count=\"1\"><font><sz val=\"11\"/><name val=\"Calibri\"/></font></fonts>',\n '<fills count=\"1\"><fill><patternFill patternType=\"none\"/></fill></fills>',\n '<borders count=\"1\"><border/></borders>',\n '<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>',\n `<cellXfs count=\"${numFmtCodes.length + 1}\">`,\n '<xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/>',\n ...numFmtCodes.map(\n (_, i) => `<xf numFmtId=\"${164 + i}\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyNumberFormat=\"1\"/>`,\n ),\n '</cellXfs>',\n '</styleSheet>',\n ].join('')\n : ''\n\n // ── Fixed parts ─────────────────────────────────────────────────\n\n const sheetEntries = safeSheets.map((s, i) => ({\n index: i + 1,\n id: `rId${i + 1}`,\n name: s.name,\n path: `xl/worksheets/sheet${i + 1}.xml`,\n }))\n\n const contentTypes = [\n XML_HEADER,\n '<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">',\n '<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>',\n '<Default Extension=\"xml\" ContentType=\"application/xml\"/>',\n '<Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>',\n ...sheetEntries.map(\n (s) =>\n `<Override PartName=\"/${s.path}\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>`,\n ),\n '<Override PartName=\"/xl/sharedStrings.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml\"/>',\n ...(hasStyles\n ? ['<Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"/>']\n : []),\n '</Types>',\n ].join('')\n\n const rootRels =\n XML_HEADER +\n '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">' +\n '<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/>' +\n '</Relationships>'\n\n const workbookXml = [\n XML_HEADER,\n '<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">',\n '<sheets>',\n ...sheetEntries.map(\n (s) => `<sheet name=\"${escapeXmlAttr(s.name)}\" sheetId=\"${s.index}\" r:id=\"${s.id}\"/>`,\n ),\n '</sheets>',\n ...(options.definedNames && options.definedNames.length > 0\n ? [\n '<definedNames>',\n ...options.definedNames.map(\n (d) => `<definedName name=\"${escapeXmlAttr(d.name)}\">${escapeXmlText(d.ref)}</definedName>`,\n ),\n '</definedNames>',\n ]\n : []),\n '</workbook>',\n ].join('')\n\n const workbookRels = [\n XML_HEADER,\n '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">',\n ...sheetEntries.map(\n (s) =>\n `<Relationship Id=\"${s.id}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet${s.index}.xml\"/>`,\n ),\n `<Relationship Id=\"rIdSharedStrings\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings\" Target=\"sharedStrings.xml\"/>`,\n ...(hasStyles\n ? [`<Relationship Id=\"rIdStyles\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>`]\n : []),\n '</Relationships>',\n ].join('')\n\n const sharedStringsXml = [\n XML_HEADER,\n `<sst xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" count=\"${sharedStrings.length}\" uniqueCount=\"${sharedStrings.length}\">`,\n ...sharedStrings.map((s) => `<si><t xml:space=\"preserve\">${escapeXmlText(s)}</t></si>`),\n '</sst>',\n ].join('')\n\n const entries: ZipEntry[] = [\n { path: '[Content_Types].xml', bytes: ENCODER.encode(contentTypes) },\n { path: '_rels/.rels', bytes: ENCODER.encode(rootRels) },\n { path: 'xl/workbook.xml', bytes: ENCODER.encode(workbookXml) },\n { path: 'xl/_rels/workbook.xml.rels', bytes: ENCODER.encode(workbookRels) },\n { path: 'xl/sharedStrings.xml', bytes: ENCODER.encode(sharedStringsXml) },\n ...(hasStyles ? [{ path: 'xl/styles.xml', bytes: ENCODER.encode(stylesXml) }] : []),\n ...sheetEntries.map((s, i) => ({ path: s.path, bytes: ENCODER.encode(sheetXmls[i] ?? '') })),\n ]\n\n return await writeZip(entries)\n}\n\n// ── Cell emission ─────────────────────────────────────────────────\n\nfunction cellXml(\n value: unknown,\n colIdx: number,\n rowNum: number,\n intern: (s: string) => number,\n internStyle: (code: string) => number,\n): string {\n const ref = `${colLetter(colIdx)}${rowNum}`\n if (isStyledCell(value)) {\n const s = internStyle(value.__xlsxStyle)\n const v = value.v\n if (v === null || v === undefined || v === '') return `<c r=\"${ref}\" s=\"${s}\"/>`\n if (typeof v === 'number' && Number.isFinite(v)) return `<c r=\"${ref}\" s=\"${s}\"><v>${v}</v></c>`\n if (typeof v === 'boolean') return `<c r=\"${ref}\" s=\"${s}\" t=\"b\"><v>${v ? 1 : 0}</v></c>`\n return `<c r=\"${ref}\" s=\"${s}\" t=\"s\"><v>${intern(typeof v === 'string' ? v : String(v))}</v></c>`\n }\n if (isFormulaCell(value)) {\n const f = escapeXmlText(value.__xlsxFormula)\n if (value.v === undefined) return `<c r=\"${ref}\"><f>${f}</f></c>`\n if (typeof value.v === 'number' && Number.isFinite(value.v)) return `<c r=\"${ref}\"><f>${f}</f><v>${value.v}</v></c>`\n if (typeof value.v === 'boolean') return `<c r=\"${ref}\" t=\"b\"><f>${f}</f><v>${value.v ? 1 : 0}</v></c>`\n return `<c r=\"${ref}\" t=\"str\"><f>${f}</f><v>${escapeXmlText(String(value.v))}</v></c>`\n }\n if (value === null || value === undefined || value === '') return `<c r=\"${ref}\"/>`\n if (typeof value === 'number' && Number.isFinite(value)) {\n return `<c r=\"${ref}\"><v>${value}</v></c>`\n }\n if (typeof value === 'boolean') {\n return `<c r=\"${ref}\" t=\"b\"><v>${value ? 1 : 0}</v></c>`\n }\n // Date → ISO-8601 string (Excel renders as text unless the cell\n // has a date-format style; styles are out of scope for this\n // minimal writer).\n const s =\n value instanceof Date\n ? value.toISOString()\n : typeof value === 'string'\n ? value\n : JSON.stringify(value)\n const idx = intern(s)\n return `<c r=\"${ref}\" t=\"s\"><v>${idx}</v></c>`\n}\n\n// ── Helpers ───────────────────────────────────────────────────────\n\n/**\n * Convert a 1-based column index to Excel A1 letter notation.\n * 1 → A, 26 → Z, 27 → AA, 702 → ZZ, 703 → AAA.\n */\nexport function colLetter(n: number): string {\n let s = ''\n let x = n\n while (x > 0) {\n const r = (x - 1) % 26\n s = String.fromCharCode(65 + r) + s\n x = Math.floor((x - 1) / 26)\n }\n return s\n}\n\nfunction truncateSheetName(name: string): string {\n // Excel sheet-name rules: max 31 chars, forbid :/\\?*[]\n const cleaned = name.replace(/[:/\\\\?*[\\]]/g, '_')\n if (cleaned.length <= 31) return cleaned\n return cleaned.slice(0, 30) + '…'\n}\n\n/** XML text escaping — `& < > \\r` (quotes only matter in attributes). */\nfunction escapeXmlText(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\\r/g, ' ')\n}\n\n/** XML attribute escaping. */\nfunction escapeXmlAttr(s: string): string {\n return escapeXmlText(s).replace(/\"/g, '"')\n}\n","/**\n * Minimal OOXML reader. Inverse of `writeXlsx` in `xlsx.ts`.\n *\n * Walks the three parts the writer emits:\n *\n * - `xl/sharedStrings.xml` → string table (idx → string)\n * - `xl/workbook.xml` → sheet name list (with sheetId)\n * - `xl/_rels/workbook.xml.rels` → sheetId → sheet part path\n * - `xl/worksheets/sheet<N>.xml` → cell data\n *\n * Cell types matched to the writer's emission rules:\n * - no `t` attribute → number (`<v>` is parsed as Number)\n * - `t=\"s\"` → shared-string ref\n * - `t=\"b\"` → boolean (`1` ↔ true, `0` ↔ false)\n * - empty `<c />` → undefined\n *\n * Excel date serials are NOT auto-converted — the writer outputs ISO-\n * 8601 strings via the shared-string path, so dates round-trip as\n * strings unless the consumer opts into `dateFields` coercion (handled\n * one layer up in `index.ts:fromBytes`).\n *\n * @module\n */\n\nimport { readZip } from '@noy-db/as-zip'\nimport { XMLParser, XMLValidator } from 'fast-xml-parser'\n\nconst PARSER = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: '@_',\n parseTagValue: false,\n parseAttributeValue: false,\n trimValues: false,\n isArray: (name) => name === 'sheet' || name === 'row' || name === 'c' || name === 'si' || name === 'Relationship',\n})\n\n/** A row of cell values keyed by column letter (`'A' → 'foo'`). */\nexport type ReadXlsxRow = Record<string, unknown>\n\nexport interface ReadXlsxSheet {\n /** Sheet tab name from `xl/workbook.xml`. */\n readonly name: string\n /** All rows in declaration order. Columns indexed by Excel letter. */\n readonly rows: readonly ReadXlsxRow[]\n}\n\nexport interface ReadXlsxResult {\n readonly sheets: readonly ReadXlsxSheet[]\n}\n\n/**\n * Decode an `.xlsx` (OOXML) byte stream into per-sheet row data. The\n * caller decides what to do with the rows (header inference, type\n * coercion, record building) — the reader stays format-only.\n *\n * Throws on malformed XML, missing parts, or sheet-id mismatches.\n */\nexport async function readXlsx(bytes: Uint8Array): Promise<ReadXlsxResult> {\n const entries = await readZip(bytes)\n const partByPath = new Map<string, Uint8Array>()\n for (const e of entries) partByPath.set(e.path, e.bytes)\n\n const sharedStrings = readSharedStrings(partByPath.get('xl/sharedStrings.xml'))\n const sheetMeta = readWorkbook(partByPath.get('xl/workbook.xml'))\n const rels = readWorkbookRels(partByPath.get('xl/_rels/workbook.xml.rels'))\n\n const sheets: ReadXlsxSheet[] = []\n for (const meta of sheetMeta) {\n const target = rels.get(meta.rId)\n if (target === undefined) {\n throw new Error(\n `as-xlsx.readXlsx: workbook references rId=\"${meta.rId}\" but the rels file has no matching target`,\n )\n }\n const sheetPath = `xl/${target}`\n const sheetBytes = partByPath.get(sheetPath)\n if (sheetBytes === undefined) {\n throw new Error(`as-xlsx.readXlsx: missing sheet part ${sheetPath}`)\n }\n sheets.push({\n name: meta.name,\n rows: readSheet(sheetBytes, sharedStrings),\n })\n }\n\n return { sheets }\n}\n\n// ── XML helpers ─────────────────────────────────────────────────────\n\nfunction decodeXml(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes)\n}\n\nfunction parseStrict(xml: string, where: string): unknown {\n const validation = XMLValidator.validate(xml)\n if (validation !== true) {\n const err = validation.err\n throw new Error(\n `as-xlsx.readXlsx: ${where} is not valid XML (${err.code} at line ${err.line}: ${err.msg})`,\n )\n }\n try {\n return PARSER.parse(xml)\n } catch (err) {\n throw new Error(`as-xlsx.readXlsx: failed to parse ${where} (${(err as Error).message})`)\n }\n}\n\n// ── sharedStrings.xml ───────────────────────────────────────────────\n\nfunction readSharedStrings(bytes: Uint8Array | undefined): readonly string[] {\n // The writer always emits sharedStrings.xml, but a hand-crafted\n // .xlsx might omit it if no string cells are present. Treat missing\n // as an empty table so cells with t=\"s\" surface as a clear error\n // (\"ref out of bounds\") rather than a misleading null.\n if (bytes === undefined) return []\n const parsed = parseStrict(decodeXml(bytes), 'xl/sharedStrings.xml')\n const sst = (parsed as Record<string, unknown>).sst\n if (sst === null || sst === undefined || typeof sst !== 'object') return []\n const items = (sst as { si?: unknown[] }).si ?? []\n if (!Array.isArray(items)) return []\n\n return items.map((si): string => {\n if (si === null || typeof si !== 'object') return ''\n const t = (si as { t?: unknown }).t\n return textValue(t)\n })\n}\n\n// ── workbook.xml ────────────────────────────────────────────────────\n\ninterface WorkbookSheetMeta {\n readonly name: string\n readonly sheetId: string\n readonly rId: string\n}\n\nfunction readWorkbook(bytes: Uint8Array | undefined): readonly WorkbookSheetMeta[] {\n if (bytes === undefined) {\n throw new Error('as-xlsx.readXlsx: missing xl/workbook.xml')\n }\n const parsed = parseStrict(decodeXml(bytes), 'xl/workbook.xml')\n const workbook = (parsed as Record<string, unknown>).workbook\n if (workbook === null || workbook === undefined || typeof workbook !== 'object') {\n throw new Error('as-xlsx.readXlsx: xl/workbook.xml has no <workbook> root')\n }\n const sheetsObj = (workbook as { sheets?: unknown }).sheets\n if (sheetsObj === null || sheetsObj === undefined || typeof sheetsObj !== 'object') {\n return []\n }\n const sheetEntries = (sheetsObj as { sheet?: unknown[] }).sheet ?? []\n if (!Array.isArray(sheetEntries)) return []\n\n return sheetEntries.map((s): WorkbookSheetMeta => {\n const obj = s as Record<string, unknown>\n return {\n name: stringAttr(obj['@_name']),\n sheetId: stringAttr(obj['@_sheetId']),\n rId: stringAttr(obj['@_r:id']) || stringAttr(obj['@_id']),\n }\n })\n}\n\n// ── workbook.xml.rels ───────────────────────────────────────────────\n\nfunction readWorkbookRels(bytes: Uint8Array | undefined): Map<string, string> {\n // Map of relationship id → target path (relative to xl/).\n if (bytes === undefined) {\n throw new Error('as-xlsx.readXlsx: missing xl/_rels/workbook.xml.rels')\n }\n const parsed = parseStrict(decodeXml(bytes), 'xl/_rels/workbook.xml.rels')\n const root = (parsed as Record<string, unknown>).Relationships\n if (root === null || root === undefined || typeof root !== 'object') {\n return new Map()\n }\n const rels = (root as { Relationship?: unknown[] }).Relationship ?? []\n if (!Array.isArray(rels)) return new Map()\n\n const map = new Map<string, string>()\n for (const r of rels) {\n const obj = r as Record<string, unknown>\n const id = stringAttr(obj['@_Id'])\n const target = stringAttr(obj['@_Target'])\n if (id) map.set(id, target)\n }\n return map\n}\n\n// ── sheet<N>.xml ────────────────────────────────────────────────────\n\nfunction readSheet(bytes: Uint8Array, sharedStrings: readonly string[]): ReadXlsxRow[] {\n const parsed = parseStrict(decodeXml(bytes), 'sheet')\n const ws = (parsed as Record<string, unknown>).worksheet\n if (ws === null || ws === undefined || typeof ws !== 'object') return []\n const sheetData = (ws as { sheetData?: unknown }).sheetData\n if (sheetData === null || sheetData === undefined || typeof sheetData !== 'object') return []\n const rowEntries = (sheetData as { row?: unknown[] }).row ?? []\n if (!Array.isArray(rowEntries)) return []\n\n const rows: ReadXlsxRow[] = []\n for (const row of rowEntries) {\n if (row === null || typeof row !== 'object') {\n rows.push({})\n continue\n }\n const cells = (row as { c?: unknown[] }).c ?? []\n if (!Array.isArray(cells)) {\n rows.push({})\n continue\n }\n const out: ReadXlsxRow = {}\n for (const c of cells) {\n if (c === null || typeof c !== 'object') continue\n const cellObj = c as Record<string, unknown>\n const ref = stringAttr(cellObj['@_r'])\n if (!ref) continue\n const col = letterFromRef(ref)\n const t = cellObj['@_t']\n const v = cellObj['v']\n const text = textValue(v)\n if (text === '') {\n if (t === 's') {\n out[col] = ''\n continue\n }\n // Empty <c r=\"A1\"/> — leave undefined (caller can drop).\n continue\n }\n if (t === 's') {\n const idx = Number(text)\n if (!Number.isInteger(idx) || idx < 0 || idx >= sharedStrings.length) {\n throw new Error(\n `as-xlsx.readXlsx: shared-string reference ${idx} out of range ` +\n `(table has ${sharedStrings.length} entries)`,\n )\n }\n out[col] = sharedStrings[idx]\n } else if (t === 'b') {\n out[col] = text === '1'\n } else if (t === 'str' || t === 'inlineStr') {\n // Inline strings — writer never emits these but readers should\n // accept them since some upstream tools default to inline.\n out[col] = text\n } else {\n // Numeric cell (no `t` attribute, or t=\"n\").\n const n = Number(text)\n out[col] = Number.isFinite(n) ? n : text\n }\n }\n rows.push(out)\n }\n return rows\n}\n\n/**\n * Extract column letters from an A1-style reference (`\"BC42\"` → `\"BC\"`).\n * Used to build the sparse `Record<column, value>` row shape.\n */\nfunction letterFromRef(ref: string): string {\n let i = 0\n while (i < ref.length && /[A-Z]/.test(ref[i]!)) i++\n return ref.slice(0, i)\n}\n\n/**\n * fast-xml-parser surfaces text-only elements as primitives or as\n * objects with a `#text` field when other attributes are present.\n * Normalize to a string.\n */\nfunction textValue(raw: unknown): string {\n if (raw === null || raw === undefined) return ''\n if (typeof raw === 'string') return raw\n if (typeof raw === 'number' || typeof raw === 'boolean') return String(raw)\n if (typeof raw === 'object') {\n const txt = (raw as { '#text'?: unknown })['#text']\n if (txt !== undefined) return textValue(txt)\n }\n return ''\n}\n\n/**\n * Stringify a fast-xml-parser attribute value. Attributes come through\n * as primitives; defensively narrow before `String()` so the lint rule\n * (`@typescript-eslint/no-base-to-string`) is satisfied. Anything not\n * a primitive is coerced to the empty string — silent failures here\n * surface as missing parts (e.g. unknown rId) downstream.\n */\nfunction stringAttr(raw: unknown): string {\n if (raw === undefined || raw === null) return ''\n if (typeof raw === 'string') return raw\n if (typeof raw === 'number' || typeof raw === 'boolean') return String(raw)\n return ''\n}\n","/**\n * #414 P4 Mode B — infer a noy-db schema from an arbitrary workbook.\n *\n * Heuristic by nature (the spec's genuinely-hard part): types come from sampling\n * cell values, foreign keys from value-subset matching against another sheet's\n * id column. Use it to BOOTSTRAP a schema you then review — not as ground truth.\n * Standard Schema validators aren't JSON-serializable, so {@link zodSourceFor}\n * emits a Zod snippet you adopt in code rather than a live validator.\n */\nimport { readXlsx } from './read.js'\n\nexport type InferredType = 'string' | 'number' | 'boolean' | 'date'\n\nexport interface InferredField {\n readonly type: InferredType\n /** Target collection (sheet) name when this field looks like a foreign key. */\n readonly references?: string\n}\n\nexport interface InferredCollection {\n readonly idField: string\n readonly fields: Record<string, InferredField>\n}\n\nexport interface InferredSchema {\n readonly collections: Record<string, InferredCollection>\n}\n\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}([T ]\\d{2}:\\d{2})?/\n\nfunction inferType(vals: readonly unknown[]): InferredType {\n if (vals.length === 0) return 'string'\n if (vals.every((v) => typeof v === 'boolean')) return 'boolean'\n if (vals.every((v) => typeof v === 'number')) return 'number'\n if (vals.every((v) => typeof v === 'string' && ISO_DATE.test(v))) return 'date'\n return 'string'\n}\n\ninterface SheetData {\n name: string\n fields: string[]\n records: Record<string, unknown>[]\n}\n\n/**\n * Infer a {@link InferredSchema} from an `.xlsx` byte stream. Sheets whose name\n * starts with `_` (our smart-export meta sheets) are skipped; pass `skipSheets`\n * to exclude more (e.g. summary sheets).\n */\nexport async function inferSchema(\n bytes: Uint8Array,\n options: { skipSheets?: readonly string[] } = {},\n): Promise<InferredSchema> {\n const skip = new Set(options.skipSheets ?? [])\n const decoded = await readXlsx(bytes)\n\n const sheets: SheetData[] = []\n for (const sh of decoded.sheets) {\n if (sh.name.startsWith('_') || skip.has(sh.name)) continue\n const headerRow = sh.rows[0] ?? {}\n const colToField = new Map<string, string>()\n const fields: string[] = []\n for (const [letter, val] of Object.entries(headerRow)) {\n const name =\n typeof val === 'string' ? val.trim() : typeof val === 'number' || typeof val === 'boolean' ? String(val) : ''\n if (!name) continue\n colToField.set(letter, name)\n fields.push(name)\n }\n const records = sh.rows.slice(1).map((row) => {\n const rec: Record<string, unknown> = {}\n for (const [letter, val] of Object.entries(row)) {\n const f = colToField.get(letter)\n if (f !== undefined) rec[f] = val\n }\n return rec\n })\n sheets.push({ name: sh.name, fields, records })\n }\n\n const valuesOf = (s: SheetData, f: string): unknown[] =>\n s.records.map((r) => r[f]).filter((v) => v != null && v !== '')\n\n const idFieldOf = (s: SheetData): string => {\n if (s.fields.includes('id')) return 'id'\n // First field whose non-null values are all unique → a natural key.\n for (const f of s.fields) {\n const vals = valuesOf(s, f)\n if (vals.length > 0 && new Set(vals.map((v) => String(v))).size === vals.length) return f\n }\n return s.fields[0] ?? 'id'\n }\n\n const meta = sheets.map((s) => ({ s, idField: idFieldOf(s) }))\n const idValuesByName = new Map<string, Set<string>>()\n for (const { s, idField } of meta) {\n idValuesByName.set(s.name, new Set(valuesOf(s, idField).map((v) => String(v))))\n }\n\n const collections: Record<string, InferredCollection> = {}\n for (const { s, idField } of meta) {\n const fields: Record<string, InferredField> = {}\n for (const f of s.fields) {\n const vals = valuesOf(s, f)\n const type = inferType(vals)\n let references: string | undefined\n if (f !== idField && type === 'string' && vals.length > 0) {\n const distinct = new Set(vals.map((v) => String(v)))\n for (const { s: other } of meta) {\n if (other.name === s.name) continue\n const ids = idValuesByName.get(other.name)\n if (ids && ids.size > 0 && [...distinct].every((v) => ids.has(v))) {\n references = other.name\n break\n }\n }\n }\n fields[f] = references ? { type, references } : { type }\n }\n collections[s.name] = { idField, fields }\n }\n return { collections }\n}\n\n/** Emit a Zod schema snippet (guidance) from an {@link InferredSchema}. */\nexport function zodSourceFor(schema: InferredSchema): string {\n const zType = (t: InferredType): string =>\n t === 'number' ? 'z.number()' : t === 'boolean' ? 'z.boolean()' : t === 'date' ? 'z.string().datetime()' : 'z.string()'\n const blocks = [`import { z } from 'zod'`]\n for (const [name, c] of Object.entries(schema.collections)) {\n const lines = Object.entries(c.fields).map(\n ([f, d]) => ` ${f}: ${zType(d.type)},${d.references ? ` // → ${d.references}` : ''}`,\n )\n blocks.push(`export const ${name}Schema = z.object({\\n${lines.join('\\n')}\\n})`)\n }\n return blocks.join('\\n\\n')\n}\n"],"mappings":";AA+BA,SAAS,2BAA2B;;;ACSpC,SAAS,gBAA+B;AAExC,IAAM,aAAa;AACnB,IAAM,UAAU,IAAI,YAAY;AAczB,SAAS,QAAQ,GAAW,aAA0D;AAC3F,SAAO,gBAAgB,SAAY,EAAE,eAAe,EAAE,IAAI,EAAE,eAAe,GAAG,GAAG,YAAY;AAC/F;AAEA,SAAS,cAAc,GAAkC;AACvD,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,OAAQ,EAAkC,kBAAkB;AAC5G;AAaO,SAAS,OAAO,OAAyC,cAAsC;AACpG,SAAO,EAAE,aAAa,cAAc,GAAG,MAAM;AAC/C;AAEA,SAAS,aAAa,GAAiC;AACrD,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,OAAQ,EAAgC,gBAAgB;AACxG;AA0CA,eAAsB,UACpB,QACA,UAA6F,CAAC,GACzE;AACrB,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAGA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,aAA0B,OAAO,IAAI,CAAC,GAAG,MAAM;AACnD,QAAI,OAAO,kBAAkB,EAAE,QAAQ,QAAQ,IAAI,CAAC,EAAE;AACtD,QAAI,IAAI;AACR,WAAO,KAAK,IAAI,IAAI,GAAG;AACrB,YAAM,SAAS,IAAI,GAAG;AACtB,aAAO,kBAAkB,KAAK,MAAM,GAAG,KAAK,OAAO,MAAM,IAAI,MAAM;AAAA,IACrE;AACA,SAAK,IAAI,IAAI;AACb,WAAO,EAAE,GAAG,GAAG,KAAK;AAAA,EACtB,CAAC;AAGD,QAAM,gBAA0B,CAAC;AACjC,QAAM,cAAc,oBAAI,IAAoB;AAC5C,QAAM,eAAe,CAAC,MAAsB;AAC1C,UAAM,WAAW,YAAY,IAAI,CAAC;AAClC,QAAI,aAAa,OAAW,QAAO;AACnC,UAAM,MAAM,cAAc;AAC1B,kBAAc,KAAK,CAAC;AACpB,gBAAY,IAAI,GAAG,GAAG;AACtB,WAAO;AAAA,EACT;AAIA,QAAM,cAAwB,CAAC;AAC/B,QAAM,mBAAmB,oBAAI,IAAoB;AACjD,QAAM,cAAc,CAAC,SAAyB;AAC5C,UAAM,WAAW,iBAAiB,IAAI,IAAI;AAC1C,QAAI,aAAa,OAAW,QAAO;AACnC,UAAM,MAAM,YAAY,SAAS;AACjC,gBAAY,KAAK,IAAI;AACrB,qBAAiB,IAAI,MAAM,GAAG;AAC9B,WAAO;AAAA,EACT;AAGA,QAAM,YAAsB,WAAW,IAAI,CAAC,UAAU;AACpD,UAAM,QAAkB;AAAA,MACtB;AAAA,MACA;AAAA,IACF;AAIA,QAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AAC3C,YAAM,WAAqB,CAAC;AAC5B,eAAS,IAAI,GAAG,IAAI,MAAM,OAAO,QAAQ,KAAK;AAC5C,cAAM,IAAI,MAAM,OAAO,CAAC;AACxB,YAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG;AAC5D,cAAM,IAAI,IAAI;AACd,iBAAS,KAAK,aAAa,CAAC,UAAU,CAAC,YAAY,CAAC,qBAAqB;AAAA,MAC3E;AACA,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,KAAK,UAAU,GAAG,UAAU,SAAS;AAAA,MAC7C;AAAA,IACF;AACA,UAAM,KAAK,aAAa;AACxB,QAAI,SAAS;AACb,QAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AAC3C;AACA,YAAM,QAAQ,MAAM,OACjB,IAAI,CAAC,GAAG,MAAM;AACb,cAAM,MAAM,aAAa,OAAO,CAAC,CAAC;AAClC,eAAO,SAAS,UAAU,IAAI,CAAC,CAAC,GAAG,MAAM,cAAc,GAAG;AAAA,MAC5D,CAAC,EACA,KAAK,EAAE;AACV,YAAM,KAAK,WAAW,MAAM,KAAK,KAAK,QAAQ;AAAA,IAChD;AACA,eAAW,OAAO,MAAM,MAAM;AAC5B;AACA,YAAM,QAAQ,IACX,IAAI,CAAC,OAAO,MAAM,QAAQ,OAAO,IAAI,GAAG,QAAQ,cAAc,WAAW,CAAC,EAC1E,KAAK,EAAE;AACV,YAAM,KAAK,WAAW,MAAM,KAAK,KAAK,QAAQ;AAAA,IAChD;AACA,UAAM,KAAK,cAAc;AACzB,QAAI,MAAM,eAAe,MAAM,YAAY,SAAS,GAAG;AACrD,YAAM,KAAK,2BAA2B,MAAM,YAAY,MAAM,IAAI;AAClE,iBAAW,MAAM,MAAM,aAAa;AAClC,cAAM,KAAK,GAAG,YAAY,KAAK,GAAG,UAAU,CAAC,GAAG,KAAK,GAAG,CAAC;AACzD,cAAM;AAAA,UACJ,+FAA+F,cAAc,GAAG,KAAK,CAAC,eAAe,cAAc,EAAE,CAAC;AAAA,QACxJ;AAAA,MACF;AACA,YAAM,KAAK,oBAAoB;AAAA,IACjC;AACA,UAAM,KAAK,cAAc;AACzB,WAAO,MAAM,KAAK,EAAE;AAAA,EACtB,CAAC;AAID,QAAM,YAAY,YAAY,SAAS;AACvC,QAAM,YAAY,YACd;AAAA,IACE;AAAA,IACA;AAAA,IACA,mBAAmB,YAAY,MAAM;AAAA,IACrC,GAAG,YAAY,IAAI,CAAC,MAAM,MAAM,qBAAqB,MAAM,CAAC,iBAAiB,cAAc,IAAI,CAAC,KAAK;AAAA,IACrG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB,YAAY,SAAS,CAAC;AAAA,IACzC;AAAA,IACA,GAAG,YAAY;AAAA,MACb,CAAC,GAAG,MAAM,iBAAiB,MAAM,CAAC;AAAA,IACpC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,EAAE,IACT;AAIJ,QAAM,eAAe,WAAW,IAAI,CAAC,GAAG,OAAO;AAAA,IAC7C,OAAO,IAAI;AAAA,IACX,IAAI,MAAM,IAAI,CAAC;AAAA,IACf,MAAM,EAAE;AAAA,IACR,MAAM,sBAAsB,IAAI,CAAC;AAAA,EACnC,EAAE;AAEF,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,aAAa;AAAA,MACd,CAAC,MACC,wBAAwB,EAAE,IAAI;AAAA,IAClC;AAAA,IACA;AAAA,IACA,GAAI,YACA,CAAC,4HAA4H,IAC7H,CAAC;AAAA,IACL;AAAA,EACF,EAAE,KAAK,EAAE;AAET,QAAM,WACJ,aACA;AAIF,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,aAAa;AAAA,MACd,CAAC,MAAM,gBAAgB,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,WAAW,EAAE,EAAE;AAAA,IAClF;AAAA,IACA;AAAA,IACA,GAAI,QAAQ,gBAAgB,QAAQ,aAAa,SAAS,IACtD;AAAA,MACE;AAAA,MACA,GAAG,QAAQ,aAAa;AAAA,QACtB,CAAC,MAAM,sBAAsB,cAAc,EAAE,IAAI,CAAC,KAAK,cAAc,EAAE,GAAG,CAAC;AAAA,MAC7E;AAAA,MACA;AAAA,IACF,IACA,CAAC;AAAA,IACL;AAAA,EACF,EAAE,KAAK,EAAE;AAET,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA,GAAG,aAAa;AAAA,MACd,CAAC,MACC,qBAAqB,EAAE,EAAE,kHAAkH,EAAE,KAAK;AAAA,IACtJ;AAAA,IACA;AAAA,IACA,GAAI,YACA,CAAC,sIAAsI,IACvI,CAAC;AAAA,IACL;AAAA,EACF,EAAE,KAAK,EAAE;AAET,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA,iFAAiF,cAAc,MAAM,kBAAkB,cAAc,MAAM;AAAA,IAC3I,GAAG,cAAc,IAAI,CAAC,MAAM,+BAA+B,cAAc,CAAC,CAAC,WAAW;AAAA,IACtF;AAAA,EACF,EAAE,KAAK,EAAE;AAET,QAAM,UAAsB;AAAA,IAC1B,EAAE,MAAM,uBAAuB,OAAO,QAAQ,OAAO,YAAY,EAAE;AAAA,IACnE,EAAE,MAAM,eAAe,OAAO,QAAQ,OAAO,QAAQ,EAAE;AAAA,IACvD,EAAE,MAAM,mBAAmB,OAAO,QAAQ,OAAO,WAAW,EAAE;AAAA,IAC9D,EAAE,MAAM,8BAA8B,OAAO,QAAQ,OAAO,YAAY,EAAE;AAAA,IAC1E,EAAE,MAAM,wBAAwB,OAAO,QAAQ,OAAO,gBAAgB,EAAE;AAAA,IACxE,GAAI,YAAY,CAAC,EAAE,MAAM,iBAAiB,OAAO,QAAQ,OAAO,SAAS,EAAE,CAAC,IAAI,CAAC;AAAA,IACjF,GAAG,aAAa,IAAI,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,QAAQ,OAAO,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE;AAAA,EAC7F;AAEA,SAAO,MAAM,SAAS,OAAO;AAC/B;AAIA,SAAS,QACP,OACA,QACA,QACA,QACA,aACQ;AACR,QAAM,MAAM,GAAG,UAAU,MAAM,CAAC,GAAG,MAAM;AACzC,MAAI,aAAa,KAAK,GAAG;AACvB,UAAMA,KAAI,YAAY,MAAM,WAAW;AACvC,UAAM,IAAI,MAAM;AAChB,QAAI,MAAM,QAAQ,MAAM,UAAa,MAAM,GAAI,QAAO,SAAS,GAAG,QAAQA,EAAC;AAC3E,QAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,EAAG,QAAO,SAAS,GAAG,QAAQA,EAAC,QAAQ,CAAC;AACtF,QAAI,OAAO,MAAM,UAAW,QAAO,SAAS,GAAG,QAAQA,EAAC,cAAc,IAAI,IAAI,CAAC;AAC/E,WAAO,SAAS,GAAG,QAAQA,EAAC,cAAc,OAAO,OAAO,MAAM,WAAW,IAAI,OAAO,CAAC,CAAC,CAAC;AAAA,EACzF;AACA,MAAI,cAAc,KAAK,GAAG;AACxB,UAAM,IAAI,cAAc,MAAM,aAAa;AAC3C,QAAI,MAAM,MAAM,OAAW,QAAO,SAAS,GAAG,QAAQ,CAAC;AACvD,QAAI,OAAO,MAAM,MAAM,YAAY,OAAO,SAAS,MAAM,CAAC,EAAG,QAAO,SAAS,GAAG,QAAQ,CAAC,UAAU,MAAM,CAAC;AAC1G,QAAI,OAAO,MAAM,MAAM,UAAW,QAAO,SAAS,GAAG,cAAc,CAAC,UAAU,MAAM,IAAI,IAAI,CAAC;AAC7F,WAAO,SAAS,GAAG,gBAAgB,CAAC,UAAU,cAAc,OAAO,MAAM,CAAC,CAAC,CAAC;AAAA,EAC9E;AACA,MAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,GAAI,QAAO,SAAS,GAAG;AAC9E,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,WAAO,SAAS,GAAG,QAAQ,KAAK;AAAA,EAClC;AACA,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO,SAAS,GAAG,cAAc,QAAQ,IAAI,CAAC;AAAA,EAChD;AAIA,QAAM,IACJ,iBAAiB,OACb,MAAM,YAAY,IAClB,OAAO,UAAU,WACf,QACA,KAAK,UAAU,KAAK;AAC5B,QAAM,MAAM,OAAO,CAAC;AACpB,SAAO,SAAS,GAAG,cAAc,GAAG;AACtC;AAQO,SAAS,UAAU,GAAmB;AAC3C,MAAI,IAAI;AACR,MAAI,IAAI;AACR,SAAO,IAAI,GAAG;AACZ,UAAM,KAAK,IAAI,KAAK;AACpB,QAAI,OAAO,aAAa,KAAK,CAAC,IAAI;AAClC,QAAI,KAAK,OAAO,IAAI,KAAK,EAAE;AAAA,EAC7B;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,MAAsB;AAE/C,QAAM,UAAU,KAAK,QAAQ,gBAAgB,GAAG;AAChD,MAAI,QAAQ,UAAU,GAAI,QAAO;AACjC,SAAO,QAAQ,MAAM,GAAG,EAAE,IAAI;AAChC;AAGA,SAAS,cAAc,GAAmB;AACxC,SAAO,EACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,OAAO,OAAO;AAC3B;AAGA,SAAS,cAAc,GAAmB;AACxC,SAAO,cAAc,CAAC,EAAE,QAAQ,MAAM,QAAQ;AAChD;;;ACxYA,SAAS,eAAe;AACxB,SAAS,WAAW,oBAAoB;AAExC,IAAM,SAAS,IAAI,UAAU;AAAA,EAC3B,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,YAAY;AAAA,EACZ,SAAS,CAAC,SAAS,SAAS,WAAW,SAAS,SAAS,SAAS,OAAO,SAAS,QAAQ,SAAS;AACrG,CAAC;AAuBD,eAAsB,SAAS,OAA4C;AACzE,QAAM,UAAU,MAAM,QAAQ,KAAK;AACnC,QAAM,aAAa,oBAAI,IAAwB;AAC/C,aAAW,KAAK,QAAS,YAAW,IAAI,EAAE,MAAM,EAAE,KAAK;AAEvD,QAAM,gBAAgB,kBAAkB,WAAW,IAAI,sBAAsB,CAAC;AAC9E,QAAM,YAAY,aAAa,WAAW,IAAI,iBAAiB,CAAC;AAChE,QAAM,OAAO,iBAAiB,WAAW,IAAI,4BAA4B,CAAC;AAE1E,QAAM,SAA0B,CAAC;AACjC,aAAW,QAAQ,WAAW;AAC5B,UAAM,SAAS,KAAK,IAAI,KAAK,GAAG;AAChC,QAAI,WAAW,QAAW;AACxB,YAAM,IAAI;AAAA,QACR,8CAA8C,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AACA,UAAM,YAAY,MAAM,MAAM;AAC9B,UAAM,aAAa,WAAW,IAAI,SAAS;AAC3C,QAAI,eAAe,QAAW;AAC5B,YAAM,IAAI,MAAM,wCAAwC,SAAS,EAAE;AAAA,IACrE;AACA,WAAO,KAAK;AAAA,MACV,MAAM,KAAK;AAAA,MACX,MAAM,UAAU,YAAY,aAAa;AAAA,IAC3C,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,OAAO;AAClB;AAIA,SAAS,UAAU,OAA2B;AAC5C,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACvC;AAEA,SAAS,YAAY,KAAa,OAAwB;AACxD,QAAM,aAAa,aAAa,SAAS,GAAG;AAC5C,MAAI,eAAe,MAAM;AACvB,UAAM,MAAM,WAAW;AACvB,UAAM,IAAI;AAAA,MACR,qBAAqB,KAAK,sBAAsB,IAAI,IAAI,YAAY,IAAI,IAAI,KAAK,IAAI,GAAG;AAAA,IAC1F;AAAA,EACF;AACA,MAAI;AACF,WAAO,OAAO,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,qCAAqC,KAAK,KAAM,IAAc,OAAO,GAAG;AAAA,EAC1F;AACF;AAIA,SAAS,kBAAkB,OAAkD;AAK3E,MAAI,UAAU,OAAW,QAAO,CAAC;AACjC,QAAM,SAAS,YAAY,UAAU,KAAK,GAAG,sBAAsB;AACnE,QAAM,MAAO,OAAmC;AAChD,MAAI,QAAQ,QAAQ,QAAQ,UAAa,OAAO,QAAQ,SAAU,QAAO,CAAC;AAC1E,QAAM,QAAS,IAA2B,MAAM,CAAC;AACjD,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AAEnC,SAAO,MAAM,IAAI,CAAC,OAAe;AAC/B,QAAI,OAAO,QAAQ,OAAO,OAAO,SAAU,QAAO;AAClD,UAAM,IAAK,GAAuB;AAClC,WAAO,UAAU,CAAC;AAAA,EACpB,CAAC;AACH;AAUA,SAAS,aAAa,OAA6D;AACjF,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,QAAM,SAAS,YAAY,UAAU,KAAK,GAAG,iBAAiB;AAC9D,QAAM,WAAY,OAAmC;AACrD,MAAI,aAAa,QAAQ,aAAa,UAAa,OAAO,aAAa,UAAU;AAC/E,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AACA,QAAM,YAAa,SAAkC;AACrD,MAAI,cAAc,QAAQ,cAAc,UAAa,OAAO,cAAc,UAAU;AAClF,WAAO,CAAC;AAAA,EACV;AACA,QAAM,eAAgB,UAAoC,SAAS,CAAC;AACpE,MAAI,CAAC,MAAM,QAAQ,YAAY,EAAG,QAAO,CAAC;AAE1C,SAAO,aAAa,IAAI,CAAC,MAAyB;AAChD,UAAM,MAAM;AACZ,WAAO;AAAA,MACL,MAAM,WAAW,IAAI,QAAQ,CAAC;AAAA,MAC9B,SAAS,WAAW,IAAI,WAAW,CAAC;AAAA,MACpC,KAAK,WAAW,IAAI,QAAQ,CAAC,KAAK,WAAW,IAAI,MAAM,CAAC;AAAA,IAC1D;AAAA,EACF,CAAC;AACH;AAIA,SAAS,iBAAiB,OAAoD;AAE5E,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AACA,QAAM,SAAS,YAAY,UAAU,KAAK,GAAG,4BAA4B;AACzE,QAAM,OAAQ,OAAmC;AACjD,MAAI,SAAS,QAAQ,SAAS,UAAa,OAAO,SAAS,UAAU;AACnE,WAAO,oBAAI,IAAI;AAAA,EACjB;AACA,QAAM,OAAQ,KAAsC,gBAAgB,CAAC;AACrE,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,oBAAI,IAAI;AAEzC,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,KAAK,MAAM;AACpB,UAAM,MAAM;AACZ,UAAM,KAAK,WAAW,IAAI,MAAM,CAAC;AACjC,UAAM,SAAS,WAAW,IAAI,UAAU,CAAC;AACzC,QAAI,GAAI,KAAI,IAAI,IAAI,MAAM;AAAA,EAC5B;AACA,SAAO;AACT;AAIA,SAAS,UAAU,OAAmB,eAAiD;AACrF,QAAM,SAAS,YAAY,UAAU,KAAK,GAAG,OAAO;AACpD,QAAM,KAAM,OAAmC;AAC/C,MAAI,OAAO,QAAQ,OAAO,UAAa,OAAO,OAAO,SAAU,QAAO,CAAC;AACvE,QAAM,YAAa,GAA+B;AAClD,MAAI,cAAc,QAAQ,cAAc,UAAa,OAAO,cAAc,SAAU,QAAO,CAAC;AAC5F,QAAM,aAAc,UAAkC,OAAO,CAAC;AAC9D,MAAI,CAAC,MAAM,QAAQ,UAAU,EAAG,QAAO,CAAC;AAExC,QAAM,OAAsB,CAAC;AAC7B,aAAW,OAAO,YAAY;AAC5B,QAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC3C,WAAK,KAAK,CAAC,CAAC;AACZ;AAAA,IACF;AACA,UAAM,QAAS,IAA0B,KAAK,CAAC;AAC/C,QAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AACzB,WAAK,KAAK,CAAC,CAAC;AACZ;AAAA,IACF;AACA,UAAM,MAAmB,CAAC;AAC1B,eAAW,KAAK,OAAO;AACrB,UAAI,MAAM,QAAQ,OAAO,MAAM,SAAU;AACzC,YAAM,UAAU;AAChB,YAAM,MAAM,WAAW,QAAQ,KAAK,CAAC;AACrC,UAAI,CAAC,IAAK;AACV,YAAM,MAAM,cAAc,GAAG;AAC7B,YAAM,IAAI,QAAQ,KAAK;AACvB,YAAM,IAAI,QAAQ,GAAG;AACrB,YAAM,OAAO,UAAU,CAAC;AACxB,UAAI,SAAS,IAAI;AACf,YAAI,MAAM,KAAK;AACb,cAAI,GAAG,IAAI;AACX;AAAA,QACF;AAEA;AAAA,MACF;AACA,UAAI,MAAM,KAAK;AACb,cAAM,MAAM,OAAO,IAAI;AACvB,YAAI,CAAC,OAAO,UAAU,GAAG,KAAK,MAAM,KAAK,OAAO,cAAc,QAAQ;AACpE,gBAAM,IAAI;AAAA,YACR,6CAA6C,GAAG,4BAChC,cAAc,MAAM;AAAA,UACtC;AAAA,QACF;AACA,YAAI,GAAG,IAAI,cAAc,GAAG;AAAA,MAC9B,WAAW,MAAM,KAAK;AACpB,YAAI,GAAG,IAAI,SAAS;AAAA,MACtB,WAAW,MAAM,SAAS,MAAM,aAAa;AAG3C,YAAI,GAAG,IAAI;AAAA,MACb,OAAO;AAEL,cAAM,IAAI,OAAO,IAAI;AACrB,YAAI,GAAG,IAAI,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,MACtC;AAAA,IACF;AACA,SAAK,KAAK,GAAG;AAAA,EACf;AACA,SAAO;AACT;AAMA,SAAS,cAAc,KAAqB;AAC1C,MAAI,IAAI;AACR,SAAO,IAAI,IAAI,UAAU,QAAQ,KAAK,IAAI,CAAC,CAAE,EAAG;AAChD,SAAO,IAAI,MAAM,GAAG,CAAC;AACvB;AAOA,SAAS,UAAU,KAAsB;AACvC,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAAW,QAAO,OAAO,GAAG;AAC1E,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,MAAO,IAA8B,OAAO;AAClD,QAAI,QAAQ,OAAW,QAAO,UAAU,GAAG;AAAA,EAC7C;AACA,SAAO;AACT;AASA,SAAS,WAAW,KAAsB;AACxC,MAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO;AAC9C,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAAW,QAAO,OAAO,GAAG;AAC1E,SAAO;AACT;;;ACzQA,IAAM,WAAW;AAEjB,SAAS,UAAU,MAAwC;AACzD,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,MAAI,KAAK,MAAM,CAAC,MAAM,OAAO,MAAM,SAAS,EAAG,QAAO;AACtD,MAAI,KAAK,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ,EAAG,QAAO;AACrD,MAAI,KAAK,MAAM,CAAC,MAAM,OAAO,MAAM,YAAY,SAAS,KAAK,CAAC,CAAC,EAAG,QAAO;AACzE,SAAO;AACT;AAaA,eAAsB,YACpB,OACA,UAA8C,CAAC,GACtB;AACzB,QAAM,OAAO,IAAI,IAAI,QAAQ,cAAc,CAAC,CAAC;AAC7C,QAAM,UAAU,MAAM,SAAS,KAAK;AAEpC,QAAM,SAAsB,CAAC;AAC7B,aAAW,MAAM,QAAQ,QAAQ;AAC/B,QAAI,GAAG,KAAK,WAAW,GAAG,KAAK,KAAK,IAAI,GAAG,IAAI,EAAG;AAClD,UAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC;AACjC,UAAM,aAAa,oBAAI,IAAoB;AAC3C,UAAM,SAAmB,CAAC;AAC1B,eAAW,CAAC,QAAQ,GAAG,KAAK,OAAO,QAAQ,SAAS,GAAG;AACrD,YAAM,OACJ,OAAO,QAAQ,WAAW,IAAI,KAAK,IAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,YAAY,OAAO,GAAG,IAAI;AAC7G,UAAI,CAAC,KAAM;AACX,iBAAW,IAAI,QAAQ,IAAI;AAC3B,aAAO,KAAK,IAAI;AAAA,IAClB;AACA,UAAM,UAAU,GAAG,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ;AAC5C,YAAM,MAA+B,CAAC;AACtC,iBAAW,CAAC,QAAQ,GAAG,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC/C,cAAM,IAAI,WAAW,IAAI,MAAM;AAC/B,YAAI,MAAM,OAAW,KAAI,CAAC,IAAI;AAAA,MAChC;AACA,aAAO;AAAA,IACT,CAAC;AACD,WAAO,KAAK,EAAE,MAAM,GAAG,MAAM,QAAQ,QAAQ,CAAC;AAAA,EAChD;AAEA,QAAM,WAAW,CAAC,GAAc,MAC9B,EAAE,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,KAAK,QAAQ,MAAM,EAAE;AAEhE,QAAM,YAAY,CAAC,MAAyB;AAC1C,QAAI,EAAE,OAAO,SAAS,IAAI,EAAG,QAAO;AAEpC,eAAW,KAAK,EAAE,QAAQ;AACxB,YAAM,OAAO,SAAS,GAAG,CAAC;AAC1B,UAAI,KAAK,SAAS,KAAK,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,SAAS,KAAK,OAAQ,QAAO;AAAA,IAC1F;AACA,WAAO,EAAE,OAAO,CAAC,KAAK;AAAA,EACxB;AAEA,QAAM,OAAO,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,SAAS,UAAU,CAAC,EAAE,EAAE;AAC7D,QAAM,iBAAiB,oBAAI,IAAyB;AACpD,aAAW,EAAE,GAAG,QAAQ,KAAK,MAAM;AACjC,mBAAe,IAAI,EAAE,MAAM,IAAI,IAAI,SAAS,GAAG,OAAO,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;AAAA,EAChF;AAEA,QAAM,cAAkD,CAAC;AACzD,aAAW,EAAE,GAAG,QAAQ,KAAK,MAAM;AACjC,UAAM,SAAwC,CAAC;AAC/C,eAAW,KAAK,EAAE,QAAQ;AACxB,YAAM,OAAO,SAAS,GAAG,CAAC;AAC1B,YAAM,OAAO,UAAU,IAAI;AAC3B,UAAI;AACJ,UAAI,MAAM,WAAW,SAAS,YAAY,KAAK,SAAS,GAAG;AACzD,cAAM,WAAW,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC;AACnD,mBAAW,EAAE,GAAG,MAAM,KAAK,MAAM;AAC/B,cAAI,MAAM,SAAS,EAAE,KAAM;AAC3B,gBAAM,MAAM,eAAe,IAAI,MAAM,IAAI;AACzC,cAAI,OAAO,IAAI,OAAO,KAAK,CAAC,GAAG,QAAQ,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG;AACjE,yBAAa,MAAM;AACnB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,aAAO,CAAC,IAAI,aAAa,EAAE,MAAM,WAAW,IAAI,EAAE,KAAK;AAAA,IACzD;AACA,gBAAY,EAAE,IAAI,IAAI,EAAE,SAAS,OAAO;AAAA,EAC1C;AACA,SAAO,EAAE,YAAY;AACvB;AAGO,SAAS,aAAa,QAAgC;AAC3D,QAAM,QAAQ,CAAC,MACb,MAAM,WAAW,eAAe,MAAM,YAAY,gBAAgB,MAAM,SAAS,0BAA0B;AAC7G,QAAM,SAAS,CAAC,yBAAyB;AACzC,aAAW,CAAC,MAAM,CAAC,KAAK,OAAO,QAAQ,OAAO,WAAW,GAAG;AAC1D,UAAM,QAAQ,OAAO,QAAQ,EAAE,MAAM,EAAE;AAAA,MACrC,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,KAAK,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,aAAa,cAAS,EAAE,UAAU,KAAK,EAAE;AAAA,IACrF;AACA,WAAO,KAAK,gBAAgB,IAAI;AAAA,EAAwB,MAAM,KAAK,IAAI,CAAC;AAAA,GAAM;AAAA,EAChF;AACA,SAAO,OAAO,KAAK,MAAM;AAC3B;;;AH6sBA,SAAS,iBAAiB;AA9mB1B,eAAsB,sBACpB,OACA,gBACqB;AACrB,SAAO,QAAQ,OAAO;AAAA,IACpB,QAAQ,CAAC,EAAE,MAAM,gBAAgB,YAAY,eAAe,CAAC;AAAA,EAC/D,CAAC;AACH;AAMA,eAAsB,QAAQ,OAAc,SAA6C;AACvF,QAAM,gBAAgB,aAAa,MAAM;AAEzC,MAAI,QAAQ,OAAO,WAAW,GAAG;AAC/B,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AAEA,MAAI,QAAQ,OAAO;AACjB,UAAM,EAAE,QAAQ,aAAa,IAAI,MAAM,iBAAiB,OAAO,OAAO;AACtE,WAAO,UAAU,QAAQ,EAAE,aAAa,CAAC;AAAA,EAC3C;AAEA,QAAM,qBAAkC,CAAC;AACzC,aAAW,YAAY,QAAQ,QAAQ;AAErC,UAAM,aAAa,MAAM,WAAgB,SAAS,UAAU;AAC5D,UAAM,OAAO,MAAM,WAAW,KAAK;AACnC,UAAM,UAAqC,CAAC;AAC5C,eAAW,QAAQ,MAAM;AACvB,YAAM,IAAI;AACV,UAAI,SAAS,UAAU,CAAC,SAAS,OAAO,CAAC,EAAG;AAC5C,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM,YAAY,eAAe,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM;AACpF,UAAM,UAAU,SAAS,WAAW,aAAa,SAAS;AAC1D,uBAAmB,KAAK;AAAA,MACtB,MAAM,SAAS;AAAA,MACf,QAAQ;AAAA,MACR,MAAM,UAAU,IAAI,CAAC,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;AAAA,MAC3D,GAAI,SAAS,WAAW,SAAY,EAAE,QAAQ,SAAS,OAAO,IAAI,CAAC;AAAA,IACrE,CAAC;AAAA,EACH;AAEA,SAAO,UAAU,kBAAkB;AACrC;AAMA,eAAsB,SAAS,OAAc,SAA+C;AAC1F,QAAM,QAAQ,MAAM,QAAQ,OAAO,OAAO;AAC1C,QAAM,WAAW,QAAQ,YAAY;AAErC,QAAM,OAAO,IAAI,KAAK,CAAC,KAAY,GAAG;AAAA,IACpC,MAAM;AAAA,EACR,CAAC;AACD,QAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,QAAM,IAAI,SAAS,cAAc,GAAG;AACpC,IAAE,OAAO;AACT,IAAE,WAAW;AACb,IAAE,MAAM;AACR,MAAI,gBAAgB,GAAG;AACzB;AAMA,eAAsB,MACpB,OACA,MACA,SACe;AACf,MAAI,QAAQ,qBAAqB,MAAM;AACrC,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACA,QAAM,QAAQ,MAAM,QAAQ,OAAO,OAAO;AAC1C,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,aAAkB;AACrD,QAAM,UAAU,MAAM,KAAK;AAC7B;AAkEA,eAAsB,kBACpB,SACA,UAAiC,CAAC,GACb;AACrB,QAAM,MAAM,QAAQ,kBAAkB;AAGtC,aAAW,SAAS,SAAS;AAC3B,UAAM,MAAM,gBAAgB,aAAa,MAAM;AAAA,EACjD;AAWA,QAAM,SAAwB,CAAC;AAG/B,QAAM,cAAc,oBAAI,IAAkD;AAE1E,aAAW,SAAS,SAAS;AAC3B,UAAM,SAAS,MAAM,SAAS,MAAM,MAAM;AAC1C,eAAW,KAAK,MAAM,QAAQ;AAE5B,YAAM,MAAM,MAAM,MAAM,MAAM,WAAgB,EAAE,UAAU,EAAE,KAAK;AACjE,YAAM,UAAqC,CAAC;AAC5C,iBAAW,QAAQ,KAAK;AACtB,cAAM,IAAI;AAGV,cAAM,QAAQ,MAAM,SAAS,IAAI,EAAE,UAAU;AAC7C,YAAI,SAAS,CAAC,MAAM,IAAI,OAAQ,EAAuB,EAAE,CAAC,EAAG;AAE7D,YAAI,EAAE,UAAU,CAAC,EAAE,OAAO,CAAC,EAAG;AAC9B,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,YAAM,YAAY,eAAe,MAAM,OAAO,EAAE,YAAY,SAAS,MAAM,MAAM;AACjF,aAAO,KAAK,EAAE,OAAO,QAAQ,UAAU,GAAG,SAAS,UAAU,CAAC;AAK9D,YAAM,WAAW,GAAG,MAAM,IAAI,EAAE,UAAU;AAC1C,YAAM,kBAAkB,YAAY,IAAI,QAAQ,KAAK,oBAAI,IAAqC;AAC9F,kBAAY,IAAI,UAAU,eAAe;AACzC,iBAAW,KAAK,WAAW;AAEzB,cAAM,QAAS,EAAuB;AACtC,YAAI,SAAS,KAAM,iBAAgB,IAAI,cAAc,KAAK,GAAG,CAAC;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AAGA,aAAW,MAAM,QAAQ;AACvB,eAAW,KAAK,GAAG,SAAS,eAAe,CAAC,GAAG;AAC7C,UAAI,EAAE,KAAK,aAAa,KAAM;AAC9B,YAAM,WAAW,GAAG,EAAE,KAAK,KAAK,IAAI,EAAE,KAAK,UAAU;AACrD,YAAM,MAAM,YAAY,IAAI,QAAQ;AACpC,UAAI,CAAC,IAAK;AAEV,YAAM,YAAY,OAAO;AAAA,QACvB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,SAAS,eAAe,EAAE,KAAK;AAAA,MACvE;AACA,UAAI,CAAC,UAAW;AAChB,iBAAW,KAAK,UAAU,SAAS;AACjC,cAAM,KAAK,EAAE,EAAE,KAAK,QAAQ;AAC5B,YAAI,MAAM,KAAM,KAAI,IAAI,cAAc,EAAE,GAAG,CAAC;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AAIA,QAAM,YAAyB,CAAC;AAChC,QAAM,eAAsC,CAAC;AAE7C,aAAW,EAAE,QAAQ,UAAU,GAAG,QAAQ,KAAK,QAAQ;AACrD,UAAM,cAAc,EAAE,WAAW,aAAa,OAAO;AACrD,UAAM,aAAa,EAAE,eAAe,CAAC;AAErC,QAAI,WAAW,WAAW,GAAG;AAE3B,YAAMC,aAAY,GAAG,MAAM,GAAG,GAAG,GAAG,EAAE,IAAI;AAC1C,gBAAU,KAAK,eAAeA,YAAW,aAAa,OAAO,CAAC;AAC9D,mBAAa,KAAK,CAAC,QAAQ,EAAE,YAAY,QAAQ,MAAM,CAAC;AACxD;AAAA,IACF;AAGA,UAAM,aAAa,CAAC,GAAG,aAAa,GAAG,WAAW,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AACtE,UAAM,YAAY,GAAG,MAAM,GAAG,GAAG,GAAG,EAAE,IAAI;AAC1C,UAAM,OAAO,QAAQ,IAAI,CAAC,MAAM;AAC9B,YAAM,YAAY,YAAY,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI;AACrD,YAAM,cAAc,WAAW,IAAI,CAAC,MAAM;AACxC,cAAM,SAAS,GAAG,EAAE,KAAK,KAAK,IAAI,EAAE,KAAK,UAAU;AACnD,cAAM,MAAM,YAAY,IAAI,MAAM;AAClC,YAAI,CAAC,IAAK,QAAO;AACjB,cAAM,QAAQ,EAAE,EAAE,UAAU;AAC5B,YAAI,SAAS,KAAM,QAAO;AAC1B,cAAM,aAAa,IAAI,IAAI,cAAc,KAAK,CAAC;AAC/C,YAAI,CAAC,WAAY,QAAO;AACxB,eAAO,WAAW,EAAE,KAAK,IAAI,KAAK;AAAA,MACpC,CAAC;AACD,aAAO,CAAC,GAAG,WAAW,GAAG,WAAW;AAAA,IACtC,CAAC;AACD,cAAU,KAAK,EAAE,MAAM,WAAW,QAAQ,YAAY,KAAK,CAAC;AAC5D,iBAAa,KAAK,CAAC,QAAQ,EAAE,YAAY,QAAQ,MAAM,CAAC;AAAA,EAC1D;AAGA,YAAU,QAAQ;AAAA,IAChB,MAAM;AAAA,IACN,QAAQ,CAAC,SAAS,cAAc,SAAS;AAAA,IACzC,MAAM;AAAA,EACR,CAAC;AAED,SAAO,UAAU,SAAS;AAC5B;AAUA,SAAS,eACP,OACA,gBACA,SACA,QAC2B;AAC3B,MAAI,WAAW,UAAa,WAAW,MAAO,QAAO,CAAC,GAAG,OAAO;AAChE,QAAM,OAAO,MAAM,WAAW,cAAc,EAAE,SAAS;AACvD,QAAM,iBAAiB,WAAW,OAAO,SAAY,EAAE,aAAa,OAAO,YAAY;AACvF,SAAO,QAAQ,IAAI,CAAC,MAAM,oBAAoB,MAAM,GAAG,cAAc,CAAC;AACxE;AAOA,SAAS,eACP,MACA,SACA,SACW;AACX,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,CAAC,GAAG,OAAO;AAAA,IACnB,MAAM,QAAQ,IAAI,CAAC,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;AAAA,EAC3D;AACF;AAGA,SAAS,cAAc,GAAoB;AACzC,MAAI,OAAO,MAAM,SAAU,QAAO;AAClC,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,aAAa,OAAO,MAAM,SAAU,QAAO,OAAO,CAAC;AAC7F,MAAI,KAAK,KAAM,QAAO;AACtB,MAAI;AACF,WAAO,KAAK,UAAU,CAAC,KAAK;AAAA,EAC9B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,SAAS,GAAuC;AACvD,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,aAAa,OAAO,MAAM,SAAU,QAAO;AACrF,SAAO,cAAc,CAAC;AACxB;AAQA,eAAe,iBACb,OACA,SACiF;AACjF,QAAM,WAAW,MAAM,MAAM,WAAW;AACxC,QAAM,wBAAwB,oBAAI,IAAoB;AACtD,aAAW,KAAK,QAAQ,OAAQ,uBAAsB,IAAI,EAAE,YAAY,EAAE,IAAI;AAS9E,QAAM,OAAc,CAAC;AACrB,QAAM,YAAY,oBAAI,IAAY;AAClC,aAAW,YAAY,QAAQ,QAAQ;AAErC,UAAM,aAAa,MAAM,WAAgB,SAAS,UAAU;AAC5D,UAAM,OAAO,MAAM,WAAW,KAAK;AACnC,UAAM,UAAqC,CAAC;AAC5C,eAAW,QAAQ,MAAM;AACvB,YAAM,IAAI;AACV,UAAI,SAAS,UAAU,CAAC,SAAS,OAAO,CAAC,EAAG;AAC5C,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM,YAAY,eAAe,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM;AACpF,UAAM,OAAO,SAAS,WAAW,aAAa,SAAS;AACvD,UAAM,OAAO,CAAC,MAAM,GAAG,KAAK,OAAO,CAAC,MAAM,MAAM,IAAI,CAAC;AACrD,UAAM,WAAW,KAAK,KAAK,CAAC,MAAM,MAAM,IAAI;AAC5C,UAAM,WAAW,oBAAI,IAAqB;AAC1C,QAAI;AAAU,iBAAW,KAAK,UAAW,KAAI,EAAE,MAAM,KAAM,UAAS,IAAI,cAAc,EAAE,EAAE,GAAG,EAAE,QAAQ,CAAC;AAAA;AACxG,UAAM,cAAc,SAAS,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;AAC7E,eAAW,KAAK,YAAY;AAC1B,iBAAW,KAAK,WAAW;AACzB,cAAM,IAAI,EAAE,CAAC;AACb,YAAI,KAAK,OAAO,MAAM,SAAU,YAAW,OAAO,OAAO,KAAK,CAA4B,EAAG,WAAU,IAAI,GAAG;AAAA,MAChH;AAAA,IACF;AACA,SAAK,KAAK,EAAE,KAAK,UAAU,SAAS,WAAW,MAAM,UAAU,WAAW,CAAC;AAAA,EAC7E;AACA,QAAM,kBAAkB,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,YAAY,CAAC,CAAC,CAAC;AAGtE,QAAM,cAAc,oBAAI,IAAyB;AACjD,aAAW,KAAK,MAAM;AACpB,eAAW,CAAC,OAAO,QAAQ,KAAK,OAAO,QAAQ,EAAE,IAAI,cAAc,CAAC,CAAC,GAAG;AACtE,UAAI,CAAC,EAAE,KAAK,SAAS,KAAK,KAAK,YAAY,IAAI,QAAQ,EAAG;AAC1D,kBAAY,IAAI,UAAU,MAAM,MAAM,WAAW,QAAQ,EAAE,KAAK,CAAC;AAAA,IACnE;AAAA,EACF;AACA,aAAW,WAAW,YAAY,OAAO,GAAG;AAC1C,eAAW,KAAK,QAAS,YAAW,OAAO,OAAO,KAAK,EAAE,MAAM,EAAG,WAAU,IAAI,GAAG;AAAA,EACrF;AAEA,QAAM,UAAU,CAAC,GAAG,SAAS,EAAE,KAAK;AACpC,QAAM,UAAU,QAAQ,SAAS;AACjC,QAAM,gBAAgB,QAAQ,SAAS,IAAI,IAAI,OAAQ,QAAQ,CAAC,KAAK;AAErE,QAAM,UAAU,CAAC,UAA2C;AAC1D,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAI,OAAO,MAAM,QAAQ,CAAC,CAAE;AAC5B,aAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AAC5C,aAAO,YAAY,QAAQ,CAAC,CAAC,KAAK,MAAM,QAAQ,CAAC,CAAE,CAAC,IAAI,IAAI;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,oBAAI,IAA6D;AAEnF,QAAM,aAA0B,KAAK,IAAI,CAAC,MAAM;AAC9C,UAAM,OAAO,SAAS,YAAY,EAAE,IAAI,UAAU,GAAG,QAAQ,CAAC;AAC9D,UAAM,SAAS,SAAS,YAAY,EAAE,IAAI,UAAU,GAAG,UAAU,CAAC;AAClE,UAAM,UAAU,IAAI,IAAI,EAAE,UAAU;AACpC,UAAM,YAAY,OAAO,KAAK,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,SAAS,CAAC,KAAK,gBAAgB,IAAI,KAAK,CAAC,EAAG,MAAM,CAAC;AAC5G,UAAM,WAAW,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AACrD,UAAM,WAAW,EAAE,WAAW,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,QAAQ,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACrF,UAAM,YAAY,OAAO,QAAQ,EAAE,IAAI,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,SAAS,CAAC,CAAC;AAC3F,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG,UAAU,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,SAAS;AAAA,MACvC,GAAG,UAAU,IAAI,CAAC,MAAM,GAAG,CAAC,SAAS;AAAA,IACvC;AACA,UAAM,WAAW,oBAAI,IAAoB;AACzC,WAAO,QAAQ,CAAC,GAAG,MAAM,SAAS,IAAI,GAAG,IAAI,CAAC,CAAC;AAC/C,cAAU,IAAI,EAAE,IAAI,YAAY,EAAE,MAAM,EAAE,IAAI,MAAM,SAAS,CAAC;AAG9D,UAAM,UAAU,KAAK,IAAI,EAAE,QAAQ,SAAS,GAAG,CAAC;AAChD,UAAM,cAAgC,CAAC;AACvC,eAAW,SAAS,UAAU;AAC5B,YAAM,OAAO,UAAU,SAAS,IAAI,KAAK,CAAE;AAC3C,YAAM,QAAQ,GAAG,IAAI,KAAK,IAAI,GAAG,OAAO;AACxC,YAAM,WAAW,EAAE,IAAI,YAAY,KAAK;AACxC,UAAI,YAAY,SAAS,SAAS,GAAG;AACnC,oBAAY,KAAK,EAAE,OAAO,QAAQ,SAAS,CAAC;AAC5C;AAAA,MACF;AACA,YAAM,KAAK,KAAK,KAAK;AACrB,UAAI,MAAM,gBAAgB,IAAI,GAAG,MAAM,GAAG;AACxC,cAAM,cAAc,sBAAsB,IAAI,GAAG,MAAM;AACvD,cAAM,aAAa,KAAK,IAAI,gBAAgB,IAAI,GAAG,MAAM,EAAG,QAAQ,SAAS,GAAG,CAAC;AACjF,oBAAY,KAAK,EAAE,OAAO,UAAU,IAAI,WAAW,aAAa,UAAU,GAAG,CAAC;AAC9E;AAAA,MACF;AACA,YAAM,WAAW,OAAO,KAAK,GAAG,cAAc,QAAQ;AACtD,UAAI,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS,EAAG,aAAY,KAAK,EAAE,OAAO,QAAQ,SAAS,IAAI,CAAC,MAAM,cAAc,CAAC,CAAC,EAAE,CAAC;AAAA,IAC/H;AAEA,UAAM,OAAO,EAAE,QAAQ,IAAI,CAAC,GAAG,MAAM;AACnC,YAAM,SAAS,IAAI;AACnB,YAAM,YAAY,SAAS,IAAI,CAAC,MAAM;AACpC,cAAM,MAAM,MAAM,OAAQ,EAAE,MAAM,OAAS,EAAE,CAAC,KAAK;AACnD,cAAM,MAAM,EAAE,IAAI,gBAAgB,CAAC;AACnC,YAAI,QAAQ,OAAW,QAAO;AAC9B,cAAM,MAAM,OAAO,QAAQ,YAAY,IAAI,KAAK,MAAM,MAAM,OAAO,SAAS,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,IAAI;AACzG,eAAO,OAAO,KAAyC,GAAG;AAAA,MAC5D,CAAC;AACD,YAAM,YAAY,EAAE,WAAW,QAAQ,CAAC,MAAM;AAC5C,cAAM,SAAU,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC,MAAM,WAAW,EAAE,CAAC,IAAI,CAAC;AAC3D,cAAM,UAAU;AAAA,UACd,QAAQ,CAAC,MAAM,GAAG,UAAU,SAAS,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,CAAE,CAAC,GAAG,MAAM,EAAE;AAAA,UACnE,SAAS,OAAO,aAAa,KAAK,EAAE;AAAA,QACtC;AACA,eAAO,CAAC,SAAS,GAAG,QAAQ,IAAI,CAAC,MAAO,OAAO,CAAC,KAAK,IAAgB,CAAC;AAAA,MACxE,CAAC;AACD,YAAM,YAAY,UAAU,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM;AAC3C,cAAM,UAAU,GAAG,UAAU,SAAS,IAAI,CAAC,CAAE,CAAC,GAAG,MAAM;AACvD,cAAM,KAAK,YAAY,EAAE;AAEzB,cAAM,KAAK,mBAAmB,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,OAAO;AAClG,cAAM,OAAO,EAAE,CAAC;AAChB,cAAM,QAAQ,QAAQ,OAAO,SAAY,YAAY,IAAI,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,QAAQ,cAAc,IAAI,CAAC;AACvG,eAAO,QAAQ,IAAI,SAAS,OAAO,OAAO,aAAa,MAAM,QAAQ,GAAG,CAAC;AAAA,MAC3E,CAAC;AACD,YAAM,WAAW,UAAU,IAAI,CAAC,MAAM;AACpC,cAAM,SAAS,KAAK,CAAC,EAAG;AACxB,cAAM,cAAc,sBAAsB,IAAI,MAAM;AACpD,cAAM,YAAY,gBAAgB,IAAI,MAAM;AAC5C,cAAM,UAAU,GAAG,UAAU,SAAS,IAAI,CAAC,CAAE,CAAC,GAAG,MAAM;AACvD,cAAM,KAAK,mBAAmB,OAAO,KAAK,WAAW;AACrD,cAAM,OAAO,EAAE,CAAC;AAChB,cAAM,SAAS,QAAQ,OAAO,KAAK,SAAS,UAAU,SAAS,IAAI,cAAc,IAAI,CAAC,CAAC;AACvF,eAAO,QAAQ,IAAI,MAAM;AAAA,MAC3B,CAAC;AACD,aAAO,CAAC,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ;AAAA,IAC/D,CAAC;AACD,WAAO;AAAA,MACL,MAAM,EAAE,IAAI;AAAA,MACZ;AAAA,MACA;AAAA,MACA,GAAI,YAAY,SAAS,IAAI,EAAE,YAAY,IAAI,CAAC;AAAA,MAChD,GAAI,EAAE,IAAI,WAAW,SAAY,EAAE,QAAQ,EAAE,IAAI,OAAO,IAAI,CAAC;AAAA,IAC/D;AAAA,EACF,CAAC;AAED,QAAM,WAAsB;AAAA,IAC1B,MAAM;AAAA,IACN,QAAQ,CAAC,cAAc,WAAW,MAAM;AAAA,IACxC,MAAM,KAAK,IAAI,CAAC,MAAM;AACpB,YAAM,OAAO,SAAS,YAAY,EAAE,IAAI,UAAU,GAAG,QAAQ,CAAC;AAC9D,aAAO,CAAC,EAAE,IAAI,MAAM,EAAE,QAAQ,QAAQ,OAAO,QAAQ,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,SAAI,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,IAC3G,CAAC;AAAA,EACH;AAOA,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,gBAA6B,CAAC;AACpC,aAAW,QAAQ,QAAQ,aAAa,CAAC,GAAG;AAC1C,UAAM,MAAM,UAAU,IAAI,KAAK,IAAI;AACnC,UAAM,SAAS,gBAAgB,IAAI,KAAK,IAAI;AAC5C,UAAM,OAAO,KAAK,SAAS,IAAI,KAAK,OAAO;AAC3C,QAAI,CAAC,OAAO,CAAC,UAAU,SAAS,OAAW;AAC3C,UAAM,UAAU,UAAU,IAAI;AAE9B,QAAI,YAAY,UAAU;AAExB,YAAM,UAAoB,CAAC;AAC3B,YAAM,SAAmB,CAAC;AAC1B,iBAAW,KAAK,KAAK,YAAY;AAC/B,YAAI,EAAE,OAAO,SAAS;AACpB,kBAAQ,KAAK,SAAS,OAAO,GAAG;AAChC,iBAAO,KAAK,SAAS,OAAO,MAAM,EAAE,KAAK,GAAG;AAC5C;AAAA,QACF;AACA,cAAM,OAAO,EAAE,QAAQ,IAAI,SAAS,IAAI,EAAE,KAAK,IAAI;AACnD,YAAI,SAAS,OAAW;AACxB,cAAM,KAAK,UAAU,IAAI;AACzB,cAAM,KAAK,EAAE,OAAO,QAAQ,QAAQ;AACpC,gBAAQ,KAAK,GAAG,EAAE,IAAI,EAAE,GAAG;AAC3B,eAAO,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,GAAG;AAAA,MACzC;AACA,YAAM,IAAI,UAAU,IAAI,IAAI,mBAAmB,OAAO,KAAK,QAAQ,KAAK,IAAI,CAAC,aAAa,OAAO,UAAU,OAAO,KAAK,IAAI,CAAC;AAC5H,oBAAc,KAAK,EAAE,MAAM,KAAK,MAAM,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5D;AAAA,IACF;AAEA,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,SAAoB,CAAC;AAC3B,eAAW,KAAK,OAAO,SAAS;AAC9B,YAAM,IAAI,cAAc,EAAE,KAAK,OAAO,CAAC;AACvC,UAAI,CAAC,KAAK,IAAI,CAAC,GAAG;AAChB,aAAK,IAAI,CAAC;AACV,eAAO,KAAK,EAAE,KAAK,OAAO,CAAC;AAAA,MAC7B;AAAA,IACF;AACA,UAAM,SAAS,CAAC,KAAK,SAAS,GAAG,KAAK,WAAW,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACpE,UAAM,OAAO,OAAO,IAAI,CAAC,GAAG,MAAM;AAChC,YAAM,SAAS,IAAI;AACnB,YAAM,YAAY,OAAO,QAAQ,OAAO,CAAC,MAAM,cAAc,EAAE,KAAK,OAAO,CAAC,MAAM,cAAc,CAAC,CAAC;AAClG,YAAM,QAAmB,CAAC,CAAC;AAC3B,iBAAW,KAAK,KAAK,YAAY;AAC/B,YAAI,EAAE,OAAO,SAAS;AACpB,gBAAM,KAAK,QAAQ,aAAa,IAAI,IAAI,MAAM,OAAO,KAAK,OAAO,MAAM,MAAM,KAAK,UAAU,MAAM,CAAC;AACnG;AAAA,QACF;AACA,cAAM,OAAO,EAAE,QAAQ,IAAI,SAAS,IAAI,EAAE,KAAK,IAAI;AACnD,YAAI,SAAS,QAAW;AACtB,gBAAM,KAAK,IAAI;AACf;AAAA,QACF;AACA,cAAM,UAAU,UAAU,IAAI;AAC9B,cAAM,OAAO,UAAU,IAAI,CAAC,MAAM,OAAO,EAAE,EAAE,KAAM,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AACvF,cAAM,MAAM,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAC1C,cAAM,SAAS,EAAE,OAAO,QAAQ,MAAM,KAAK,SAAS,MAAM,KAAK,SAAS;AACxE,cAAM,KAAK,EAAE,OAAO,QAAQ,WAAW;AACvC,cAAM;AAAA,UACJ,QAAQ,GAAG,EAAE,KAAK,IAAI,IAAI,MAAM,OAAO,KAAK,OAAO,KAAK,IAAI,IAAI,MAAM,OAAO,KAAK,OAAO,MAAM,MAAM,KAAK,MAAM;AAAA,QAClH;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AACD,kBAAc,KAAK,EAAE,MAAM,KAAK,MAAM,QAAQ,KAAK,CAAC;AAAA,EACtD;AAGA,QAAM,eAA4B,CAAC,GAAG,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,OAAO,OAAO;AAAA,IACnF,MAAM,YAAY,EAAE;AAAA,IACpB,QAAQ,CAAC,QAAQ,GAAG,OAAO;AAAA,IAC3B,MAAM,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;AAAA,EAC5E,EAAE;AAEF,QAAM,iBAA8B,CAAC;AACrC,QAAM,eAAgD,CAAC;AACvD,MAAI,SAAS;AACX,mBAAe,KAAK;AAAA,MAClB,MAAM;AAAA,MACN,QAAQ,CAAC,WAAW,OAAO;AAAA,MAC3B,MAAM,CAAC,CAAC,YAAY,aAAa,CAAC;AAAA,MAClC,aAAa,CAAC,EAAE,OAAO,SAAS,QAAQ,QAAQ,CAAC;AAAA,IACnD,CAAC;AACD,iBAAa,KAAK,EAAE,MAAM,QAAQ,KAAK,mBAAmB,CAAC;AAAA,EAC7D;AAEA,SAAO;AAAA,IACL,QAAQ,CAAC,GAAG,gBAAgB,GAAG,cAAc,UAAU,GAAG,eAAe,GAAG,UAAU;AAAA,IACtF;AAAA,EACF;AACF;AAEA,SAAS,aAAa,SAAuD;AAC3E,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAgB,CAAC;AACvB,aAAW,KAAK,SAAS;AACvB,eAAW,OAAO,OAAO,KAAK,CAAC,GAAG;AAChC,UAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAClB,aAAK,IAAI,GAAG;AACZ,YAAI,KAAK,GAAG;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAmBO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YACkB,QACA,OAChB;AACA;AAAA,MACE,uCAAuC,MAAM,0BAA0B,KAAK;AAAA,IAG9E;AAPgB;AACA;AAOhB,SAAK,OAAO;AAAA,EACd;AAAA,EATkB;AAAA,EACA;AASpB;AAyEA,eAAsB,UACpB,OACA,OACA,SAC2B;AAC3B,QAAM,gBAAgB,aAAa,MAAM;AAEzC,QAAM,SAAuB,QAAQ,UAAU;AAC/C,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,QAAQ,QAAQ,cAAc,CAAC;AACrC,QAAM,gBAAgB,QAAQ,aAAa,KAAK;AAChD,MAAI,eAAe,GAAG;AACpB,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AAEA,QAAM,UAAU,MAAM,SAAS,KAAK;AACpC,MAAI,QAAQ,OAAO,WAAW,GAAG;AAC/B,WAAO,cAAc,OAAO,QAAQ,YAAY,QAAQ,KAAK;AAAA,EAC/D;AACA,QAAM,QAAQ,QAAQ,UAAU,SAC5B,QAAQ,OAAO,CAAC,IAChB,QAAQ,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,KAAK;AACvD,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI;AAAA,MACR,mDAAmD,QAAQ,KAAK,iBAChD,QAAQ,OAAO,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,IACrE;AAAA,EACF;AAEA,QAAM,UAAU,MAAM;AACtB,MAAI,QAAQ,UAAU,cAAc;AAClC,WAAO,cAAc,OAAO,QAAQ,YAAY,QAAQ,KAAK;AAAA,EAC/D;AACA,QAAM,YAAY,QAAQ,YAAY;AAKtC,QAAM,aAAa,oBAAI,IAAoB;AAC3C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,UAAM,YAAY,kBAAkB,KAAK;AACzC,QAAI,cAAc,GAAI;AACtB,eAAW,IAAI,KAAK,SAAS;AAAA,EAC/B;AAMA,QAAM,aAAa,oBAAI,IAAiC;AACxD,QAAM,YAAY,IAAI,IAAI,WAAW,OAAO,CAAC;AAC7C,aAAW,SAAS,WAAW;AAC7B,UAAM,kBAAkB,QAAQ,QAAQ,KAAK;AAC7C,QAAI,oBAAoB,UAAa,gBAAgB,SAAS,GAAG;AAC/D,iBAAW,IAAI,OAAO,kBAAkB,OAAO,eAAe,CAAC;AAC/D;AAAA,IACF;AACA,QAAI;AACF,YAAM,eAAe,MAAM,MAAM,WAAW,KAAK,EAAE,KAAK;AACxD,UAAI,aAAa,SAAS,GAAG;AAC3B,mBAAW,IAAI,OAAO,kBAAkB,OAAO,YAAY,CAAC;AAAA,MAC9D;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAKA,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,QAAQ,OAAO;AACjB,eAAW,SAAS,WAAW,OAAO,GAAG;AACvC,YAAM,IAAI,eAAe,KAAK,KAAK;AACnC,UAAI,KAAK,EAAE,CAAC,MAAM,QAAS,WAAU,IAAI,EAAE,CAAC,CAAE;AAAA,IAChD;AAAA,EACF;AAEA,QAAM,UAAqC,CAAC;AAC5C,WAAS,IAAI,eAAe,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACtD,UAAM,MAAM,QAAQ,CAAC;AACrB,UAAM,SAAkC,CAAC;AACzC,QAAI,SAAS;AACb,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,YAAM,QAAQ,WAAW,IAAI,GAAG;AAChC,UAAI,UAAU,OAAW;AACzB,UAAI,QAAQ,OAAO;AACjB,YAAI,MAAM,SAAS,SAAS,EAAG;AAC/B,cAAM,KAAK,eAAe,KAAK,KAAK;AACpC,YAAI,MAAM,GAAG,CAAC,MAAM,WAAW,UAAU,IAAI,GAAG,CAAC,CAAE,GAAG;AACpD,gBAAM,OAAO,GAAG,CAAC;AACjB,gBAAMC,WAAU,eAAe,OAAO,MAAM,IAAI,CAAC;AACjD,cAAIA,aAAY,UAAaA,aAAY,IAAI;AAC3C,kBAAM,MAAO,OAAO,IAAI,KAA6C,CAAC;AACtE,gBAAI,GAAG,CAAC,CAAE,IAAIA;AACd,mBAAO,IAAI,IAAI;AACf,qBAAS;AAAA,UACX;AACA;AAAA,QACF;AACA,YAAI,UAAU,IAAI,KAAK,EAAG;AAAA,MAC5B;AACA,YAAM,UAAU,eAAe,OAAO,MAAM,KAAK,CAAC;AAClD,UAAI,YAAY,QAAW;AACzB,cAAM,SAAS,WAAW,IAAI,KAAK;AACnC,YAAI,WAAW,UAAa,OAAO,YAAY,UAAU;AACvD,iBAAO,KAAK,IAAI,OAAO,IAAI,OAAO,KAAK;AAAA,QACzC,OAAO;AACL,iBAAO,KAAK,IAAI;AAAA,QAClB;AACA,iBAAS;AAAA,MACX;AAAA,IACF;AACA,QAAI,OAAQ,SAAQ,KAAK,MAAM;AAAA,EACjC;AAEA,QAAM,OAAO,MAAM,UAAU,OAAO,EAAE,CAAC,QAAQ,UAAU,GAAG,QAAQ,GAAG;AAAA,IACrE,aAAa,CAAC,QAAQ,UAAU;AAAA,IAChC;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,QAAuB;AAG3B,YAAM,MAAM,MAAM,YAAY,CAAC,OAAO;AACpC,cAAM,UAAU,GAAG,MAAM,MAAM,IAAI;AACnC,mBAAW,SAAS,KAAK,OAAO;AAC9B,kBAAQ,WAAW,MAAM,UAAU,EAAE,IAAI,MAAM,IAAI,MAAM,QAAQ,EAAE,QAAQ,cAAc,CAAC;AAAA,QAC5F;AACA,YAAI,WAAW,eAAe;AAC5B,qBAAW,SAAS,KAAK,UAAU;AACjC,oBAAQ,WAAW,MAAM,UAAU,EAAE,IAAI,MAAM,IAAI,MAAM,QAAQ,EAAE,QAAQ,cAAc,CAAC;AAAA,UAC5F;AAAA,QACF;AACA,YAAI,WAAW,WAAW;AACxB,qBAAW,SAAS,KAAK,SAAS;AAChC,oBAAQ,WAAW,MAAM,UAAU,EAAE,OAAO,MAAM,EAAE;AAAA,UACtD;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,eAAe,cACb,OACA,YACA,QACA,OAC2B;AAC3B,QAAM,OAAO,MAAM,UAAU,OAAO,EAAE,CAAC,UAAU,GAAG,CAAC,EAAE,GAAG,EAAE,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC;AAC9F,SAAO,EAAE,MAAM,QAAQ,MAAM,QAAQ;AAAA,EAAsB,EAAE;AAC/D;AAQA,SAAS,kBAAkB,QAAgB,SAAoD;AAC7F,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,SAAS,SAAS;AAC3B,eAAW,SAAS,OAAO,OAAO,MAAM,MAAM,GAAG;AAC/C,UAAI,UAAU,GAAI;AAClB,YAAM,WAAW,IAAI,IAAI,KAAK;AAC9B,UAAI,aAAa,UAAa,aAAa,MAAM,KAAK;AACpD,cAAM,IAAI,uBAAuB,QAAQ,KAAK;AAAA,MAChD;AACA,UAAI,IAAI,OAAO,MAAM,GAAG;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,OAAwB;AAIjD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAW,QAAO,OAAO,KAAK;AAChF,SAAO;AACT;AAEA,SAAS,eACP,OACA,MACS;AACT,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,SAAS,OAAW,QAAO;AAC/B,MAAI,SAAS,UAAU;AACrB,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAW,QAAO,OAAO,KAAK;AAChF,WAAO;AAAA,EACT;AACA,MAAI,SAAS,UAAU;AACrB,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,MAAI,SAAS,WAAW;AACtB,QAAI,OAAO,UAAU,UAAW,QAAO;AACvC,QAAI,UAAU,UAAU,UAAU,EAAG,QAAO;AAC5C,QAAI,UAAU,WAAW,UAAU,EAAG,QAAO;AAC7C,WAAO;AAAA,EACT;AACA,MAAI,SAAS,QAAQ;AAKnB,QAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,YAAM,KAAK,gBAAgB,KAAK;AAChC,YAAM,IAAI,IAAI,KAAK,EAAE;AACrB,aAAO,EAAE,YAAY;AAAA,IACvB;AACA,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,IAAI,IAAI,KAAK,KAAK;AACxB,UAAI,CAAC,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO,EAAE,YAAY;AAAA,IACvD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAYA,SAAS,gBAAgB,QAAwB;AAC/C,QAAM,oBAAoB;AAC1B,QAAM,aAAa;AACnB,SAAO,KAAK,OAAO,SAAS,qBAAqB,UAAU;AAC7D;","names":["s","sheetName","coerced"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/xlsx.ts","../src/read.ts","../src/infer.ts"],"sourcesContent":["/**\n * **@noy-db/as-xlsx** — Excel spreadsheet plaintext export for noy-db.\n *\n * Produces a real `.xlsx` file (Office Open XML / OOXML) from one\n * or more noy-db collections. Opens natively in Excel, Numbers,\n * LibreOffice Calc, Google Sheets, and every modern spreadsheet\n * tool.\n *\n * Zero runtime dependencies — the XLSX encoder builds the required\n * SpreadsheetML parts and assembles them with\n * `@noy-db/as-zip`'s `writeZip()` (STORE method; most xlsx\n * contents are XML text which Excel compresses at open time anyway).\n *\n * Part of the `@noy-db/as-*` portable-artefact family, plaintext\n * tier. See [`docs/patterns/as-exports.md`](https://github.com/noy-db/core/blob/main/docs/patterns/as-exports.md).\n *\n * ## Authorisation\n *\n * Every call is gated by `assertCanExport('plaintext', 'xlsx')`.\n *\n * ```ts\n * await db.grant('firm', {\n * userId: 'accountant', role: 'viewer', secret: '…',\n * exportCapability: { plaintext: ['xlsx'] },\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport { type Vault, type DictEntry } from '@noy-db/hub'\nimport { applyListProjection } from '@noy-db/hub/introspection'\nimport { writeXlsx, colLetter, formula, styled, type XlsxSheet, type XlsxValidation } from './xlsx.js'\nimport { readXlsx } from './read.js'\n\nexport { writeXlsx, colLetter, formula, styled, type XlsxSheet, type XlsxRow, type XlsxFormulaCell, type XlsxStyledCell, type XlsxValidation } from './xlsx.js'\nexport { readXlsx, type ReadXlsxResult, type ReadXlsxSheet, type ReadXlsxRow } from './read.js'\nexport { inferSchema, zodSourceFor, type InferredSchema, type InferredCollection, type InferredField, type InferredType } from './infer.js'\n\n/** Per-sheet options for the noy-db consumer API. */\n/**\n * The column names addressable on a sheet.\n *\n * ⚠️ `keyof unknown` is `never`, so writing `readonly (keyof T & string)[]`\n * directly would type `columns` as `never[]` for every call that does not pass\n * a type argument — i.e. it would break every existing caller while looking\n * like a tightening. The conditional keeps `string` in that case and narrows\n * only when the caller actually said what the record is.\n */\nexport type AsXlsxColumn<T> = [keyof T] extends [never] ? string : keyof T & string\n\n/**\n * One sheet's selection options.\n *\n * `T` is the shape of a decrypted record in this sheet's collection, defaulting\n * to `unknown` so existing calls compile unchanged. Note that `AsXlsxOptions`\n * threads a SINGLE `T` across every sheet: a workbook mixing collections with\n * different record shapes still gets `unknown` unless the shapes are unioned.\n * Per-sheet inference would need the sheet list to be a tuple, which costs more\n * in call-site noise than it buys.\n */\nexport interface AsXlsxSheetOptions<T = unknown> {\n /**\n * Sheet tab name. Excel caps at 31 chars; longer names are\n * truncated with `…`. Duplicates are suffixed `(2)`, `(3)`.\n */\n readonly name: string\n /** Source collection. Must be in the caller's read ACL. */\n readonly collection: string\n /**\n * Field list + order. When omitted, columns are inferred from\n * the union of keys across all records (first-record-wins order).\n */\n readonly columns?: readonly AsXlsxColumn<T>[]\n /**\n * Optional predicate against each decrypted record. Runs after\n * decryption; doesn't reduce I/O.\n */\n readonly filter?: (record: T) => boolean\n /**\n * Optional per-column character widths (Excel `wch` units). When set,\n * the emitted sheet opens with the requested column widths instead of\n * Excel's default 10-character fallback. Index aligned with\n * `columns` / inferred-column order.\n *\n * Non-finite or non-positive entries are skipped so consumers can\n * mix explicit + auto (pass `undefined` for \"auto\").\n *\n * Length is NOT validated against `columns.length` — extra entries\n * are harmless (no `<col>` is emitted past the column count) and a\n * short array leaves trailing columns at Excel's default. This\n * mirrors `XlsxSheet.widths`, which it threads through.\n */\n readonly widths?: ReadonlyArray<number | undefined>\n /**\n * Per-field Excel number-format codes (e.g. `{ amount: '#,##0.00' }` for\n * currency). The value is coerced to a number so the format renders, which is\n * what makes `SUM()` work: hub stores money as a decimal STRING to avoid\n * float error, and a string lands in a text cell that Excel sums as zero.\n * Money carries no introspection signal, so this is opt-in.\n *\n * ⚠️ Was smart-mode only until 0.7.1-pre.1, and silently ignored elsewhere —\n * a declared option that did nothing on the default path (#8). It now applies\n * in every mode, through one shared helper, so the two paths cannot drift\n * again.\n */\n readonly numberFormats?: Partial<Record<AsXlsxColumn<T>, string>>\n /**\n * Smart mode only: per-field explicit dropdown value lists. Overrides the\n * auto-detected enum/ref dropdown for that field.\n */\n readonly dropdowns?: Record<string, readonly string[]>\n /**\n * Smart mode only: fields whose stored value is a multi-locale i18n map\n * (`{ en: '…', th: '…' }`). Each becomes per-locale columns plus a display\n * column resolved **live by the global LANG cell** (change LANG → every label\n * re-renders). Read raw, so open the export vault without an active locale.\n */\n readonly i18nFields?: readonly string[]\n /**\n * Smart mode only: map a dict-backed (code) field to its dictionary name, e.g.\n * `{ status: 'status' }`. Emits a hidden `_Lookups_<dict>` sheet (code +\n * per-locale labels) and a `<field>__label` display column resolved by the\n * global LANG cell via `VLOOKUP(code, …, MATCH(LANG, …))`.\n */\n readonly dictFields?: Record<string, string>\n /**\n * Multi-vault export only: pull FK-referenced fields from a supporting vault\n * into this sheet as extra columns. Each entry resolves `localField` on the\n * primary record against the `keyField` of another vault's collection (matched\n * by `from.label`/`from.collection`), and emits the `pick` field value as\n * `column`. Appended AFTER the declared `columns` in declaration order.\n * Unresolved FK (row not in the supporting-vault index) → empty cell.\n * Ignored by single-vault `toBytes`.\n */\n readonly denormalize?: readonly MultiVaultDenormColumn[]\n}\n\n/**\n * One denormalized column in a multi-vault export: pulls a field from a\n * supporting vault into the primary sheet via an in-memory join.\n *\n * @example\n * ```ts\n * {\n * column: 'entityName', // new column header in the primary sheet\n * localField: 'entityId', // FK field on the primary record\n * from: { label: 'directory', collection: 'entities', keyField: 'id', pick: 'name' },\n * }\n * ```\n */\nexport interface MultiVaultDenormColumn {\n /** Header of the new column to append to the primary sheet. */\n readonly column: string\n /** Field on the primary record whose value is the FK. */\n readonly localField: string\n readonly from: {\n /** Label of the supporting vault entry (matches `MultiVaultXlsxEntry.label`). */\n readonly label: string\n /** Collection in the supporting vault. */\n readonly collection: string\n /** Field in the supporting record that is the join key (usually `'id'`). */\n readonly keyField: string\n /** Field in the supporting record to copy as the denorm value. */\n readonly pick: string\n }\n}\n\n/** One aggregate column in a smart-mode summary sheet (#414 P3). */\nexport interface AsXlsxSummaryAggregate {\n /** Column header for this aggregate. */\n readonly label: string\n readonly op: 'sum' | 'count' | 'avg'\n /** Field to aggregate — required for `sum`/`avg`, ignored for `count`. */\n readonly field?: string\n}\n\n/**\n * A groupBy summary sheet (#414 P3): groups the source collection's sheet by\n * `groupBy` and emits live SUMIFS/COUNTIFS/AVERAGEIFS columns (values cached at\n * export). The source value columns must be numeric for the live formulas to\n * compute (apply `numberFormats` to money fields).\n */\nexport interface AsXlsxSummarySpec {\n /** Summary sheet name. */\n readonly name: string\n /** Source collection (must be exported as a sheet). */\n readonly from: string\n /** Field to group by. */\n readonly groupBy: string\n readonly aggregates: readonly AsXlsxSummaryAggregate[]\n}\n\n/** Single-collection convenience — passed where a sheet-list is accepted. */\nexport interface AsXlsxOptions<T = unknown> {\n /** One or more sheets. At least one required. */\n readonly sheets: readonly AsXlsxSheetOptions<T>[]\n /** Smart mode only: groupBy summary sheets (live SUMIFS/COUNTIFS/AVERAGEIFS). */\n readonly summaries?: readonly AsXlsxSummarySpec[]\n /**\n * Smart mode only: summary formula dialect. `'excel'` (default) emits\n * cross-compatible per-row SUMIFS/COUNTIFS/AVERAGEIFS. `'sheets'` emits a\n * single Google-Sheets `QUERY` formula per summary — **Sheets-only** (QUERY\n * errors in Excel); use when the target is Google Sheets.\n */\n readonly dialect?: 'excel' | 'sheets'\n /**\n * Smart-workbook mode (#414). Emits a relational workbook instead of a flat\n * dump:\n * - every sheet is **id-first** (record `id` in column A);\n * - each foreign-key field (auto-detected via `vault.dumpSchema()`) gets a\n * `<field>__label` column — a cross-sheet `VLOOKUP` that resolves the\n * reference to the target's first field, carrying a **cached** label so it\n * shows immediately and recomputes live on edit;\n * - a `_manifest` index sheet lists every collection, its row count, and\n * its refs.\n *\n * Requires unique sheet names ≤ 31 chars (so cross-sheet refs resolve) and an\n * `id` field on records. Defaults to the existing flat export when omitted.\n */\n readonly smart?: boolean\n /**\n * Apply the hub's `applyListProjection` read-projection to every sheet's\n * records before rendering. `true` redacts only `classifiedFields` (mask /\n * omit / rider, per the field's preset). The object form additionally\n * redacts fields carrying a plain `fieldMeta` `sensitivity: 'pii' |\n * 'secret'` tag, per `sensitivity: 'omit' | 'mask'`.\n *\n * Caveat: `describe()` reflects the declarations of *this session's*\n * collection instance — redaction only takes effect when the collection\n * was opened (this call or earlier in the session) with its\n * `classifiedFields` / `fieldMeta` options. This is presentation-layer\n * redaction; it never affects what's on disk. Sealed handles are\n * unaffected either way — they always serialize as `'[sealed]'`, so\n * ciphertext never leaks regardless of this option.\n *\n * Rider companion fields (e.g. `pan_last4`) remain visible as their own\n * columns — they are safe write-time projections.\n */\n readonly redact?: boolean | { readonly sensitivity: 'omit' | 'mask' }\n}\n\n/** Options for `download()` — adds optional filename. */\nexport interface AsXlsxDownloadOptions<T = unknown> extends AsXlsxOptions<T> {\n /** Filename offered to the browser. Default `'export.xlsx'`. */\n readonly filename?: string\n}\n\n/** Options for `write()` — requires explicit risk acknowledgement. */\nexport interface AsXlsxWriteOptions<T = unknown> extends AsXlsxOptions<T> {\n /** Tier 3 egress — see `docs/patterns/as-exports.md`. */\n readonly acknowledgeRisks: true\n}\n\n/**\n * Convenience — single-collection shorthand. Equivalent to\n * `toBytes(vault, { sheets: [{ name: collectionName, collection: collectionName }] })`.\n */\nexport async function toBytesFromCollection(\n vault: Vault,\n collectionName: string,\n): Promise<Uint8Array> {\n return toBytes(vault, {\n sheets: [{ name: collectionName, collection: collectionName }],\n })\n}\n\n/**\n * Build the `.xlsx` byte stream from one or more sheets. Pure\n * beyond the auth check + store reads.\n */\nexport async function toBytes<T = unknown>(vault: Vault, options: AsXlsxOptions<T>): Promise<Uint8Array> {\n vault.assertCanExport('plaintext', 'xlsx')\n\n if (options.sheets.length === 0) {\n throw new Error('as-xlsx: at least one sheet is required')\n }\n\n if (options.smart) {\n const { sheets, definedNames } = await buildSmartSheets(vault, options)\n return writeXlsx(sheets, { definedNames })\n }\n\n const materialisedSheets: XlsxSheet[] = []\n for (const sheetOpt of options.sheets) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const collection = vault.collection<any>(sheetOpt.collection)\n const list = await collection.list()\n const records: Record<string, unknown>[] = []\n for (const item of list) {\n const r = item as Record<string, unknown>\n // See the note on AsXlsxSheetOptions: `T` is the caller's assertion about\n // the record shape, unverifiable at runtime — the same contract\n // `vault.collection<T>()` already makes. This is the only such cast.\n if (sheetOpt.filter && !sheetOpt.filter(r as T)) continue\n records.push(r)\n }\n const projected = projectRecords(vault, sheetOpt.collection, records, options.redact)\n // Widened to `string[]` here on purpose. `AsXlsxColumn<T>` narrows the\n // CALLER's surface; inside the function T is opaque, and `inferColumns`\n // legitimately returns keys nobody declared. At runtime every key is a\n // string, so the widening is where that fact is stated once.\n const columns: readonly string[] = sheetOpt.columns ?? inferColumns(projected)\n const fmts = sheetOpt.numberFormats as Partial<Record<string, string>> | undefined\n materialisedSheets.push({\n name: sheetOpt.name,\n header: [...columns],\n rows: projected.map((r) => columns.map((c) => numberFormatted(r[c] ?? null, fmts?.[c]))),\n ...(sheetOpt.widths !== undefined ? { widths: sheetOpt.widths } : {}),\n })\n }\n\n return writeXlsx(materialisedSheets)\n}\n\n/**\n * Browser download. Requires a browser-like environment with\n * `URL.createObjectURL` + `document.createElement`.\n */\nexport async function download<T = unknown>(vault: Vault, options: AsXlsxDownloadOptions<T>): Promise<void> {\n const bytes = await toBytes(vault, options)\n const filename = options.filename ?? 'export.xlsx'\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const blob = new Blob([bytes as any], {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n a.click()\n URL.revokeObjectURL(url)\n}\n\n/**\n * Node file-write. Requires `acknowledgeRisks: true` because the\n * plaintext xlsx persists past the process (Tier 3 egress).\n */\nexport async function write<T = unknown>(\n vault: Vault,\n path: string,\n options: AsXlsxWriteOptions<T>,\n): Promise<void> {\n if (options.acknowledgeRisks !== true) {\n throw new Error(\n 'as-xlsx.write: acknowledgeRisks: true is required for on-disk plaintext output. ' +\n 'This call creates a persistent plaintext xlsx outside noy-db\\'s encrypted storage — ' +\n 'see docs/patterns/as-exports.md §\"The three tiers of \\\\\"plaintext out\\\\\"\"',\n )\n }\n const bytes = await toBytes(vault, options)\n const { writeFile } = await import('node:fs/promises')\n await writeFile(path, bytes)\n}\n\n// ── multi-vault export ─────────────────────────────────────────────\n\n/**\n * One vault entry in a multi-vault export. Supplies the pre-opened\n * vault, the sheet specs to render for it, and an optional closure\n * (per-collection id allowlist computed externally by the orchestrator).\n */\nexport interface MultiVaultXlsxEntry {\n readonly vault: Vault\n readonly sheets: readonly AsXlsxSheetOptions[]\n /**\n * Optional per-collection id allowlist (e.g. from walkCrossVaultClosure).\n * When set, only rows whose `id` appears in the set are exported for that\n * collection. Omit to export all rows.\n */\n readonly closure?: ReadonlyMap<string, ReadonlySet<string>>\n /**\n * Optional display label for sheet-name prefixing.\n * Defaults to `vault.name`.\n */\n readonly label?: string\n /**\n * Same semantics as {@link AsXlsxOptions.redact}, applied to this entry's\n * sheets via `entry.vault.collection(sheetCollection).describe()` — each\n * entry's own vault, since a multi-vault export spans several sessions.\n */\n readonly redact?: boolean | { readonly sensitivity: 'omit' | 'mask' }\n}\n\n/** Options for {@link toBytesMultiVault}. */\nexport interface MultiVaultXlsxOptions {\n /**\n * Separator inserted between the vault label and the sheet name when\n * building tab names. Default `'_'`. Names are then truncated to 31 chars\n * (Excel limit) by `writeXlsx`.\n */\n readonly sheetSeparator?: string\n}\n\n/**\n * Build an `.xlsx` byte stream spanning **multiple vaults**. Each entry\n * supplies a pre-opened vault with its sheet specs and an optional\n * per-collection id-closure (rows filtered to exactly those ids). A\n * `_manifest` sheet is prepended that lists every vault-collection pair\n * and its exported record count.\n *\n * ## Auth\n * Every vault in `entries` must independently hold `assertCanExport('plaintext','xlsx')`.\n * The check fires fail-fast before any rows are materialised.\n *\n * ## Architecture\n * This function is **edge-pure** — it takes pre-opened vaults and a\n * pre-computed closure; it performs no cross-vault FK walk itself.\n * Cross-vault orchestration lives in the outward orchestration layer (allowed direction).\n *\n * ## Two-pass execution (when `denormalize` is declared)\n * **Pass 1** — load and closure-filter ALL entries' rows, building a\n * `Map<\"${label}/${collection}\", Map<keyValue, row>>` index keyed by each\n * sheet's `keyField` (default `'id'`). Only closure-filtered rows are indexed,\n * so an FK pointing outside the closure yields an empty cell (correct: that\n * row was not referenced / not exported).\n * **Pass 2** — emit each sheet; for sheets with `denormalize`, append each\n * denorm column AFTER the declared columns by resolving the index lookup.\n */\nexport async function toBytesMultiVault(\n entries: readonly MultiVaultXlsxEntry[],\n options: MultiVaultXlsxOptions = {},\n): Promise<Uint8Array> {\n const sep = options.sheetSeparator ?? '_'\n\n // Fail-fast auth check on every vault before materialising any rows.\n for (const entry of entries) {\n entry.vault.assertCanExport('plaintext', 'xlsx')\n }\n\n // ── Pass 1: load + closure-filter all rows, build denorm index ────\n\n /** Loaded rows per (label, collection) pair. */\n interface LoadedSheet {\n entry: MultiVaultXlsxEntry\n prefix: string\n sheetOpt: AsXlsxSheetOptions\n records: Record<string, unknown>[]\n }\n const loaded: LoadedSheet[] = []\n\n /** Denorm index: `\"${label}/${collection}\" → Map<keyValue, row>`. */\n const denormIndex = new Map<string, Map<string, Record<string, unknown>>>()\n\n for (const entry of entries) {\n const prefix = entry.label ?? entry.vault.name\n for (const s of entry.sheets) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const all = await entry.vault.collection<any>(s.collection).list()\n const records: Record<string, unknown>[] = []\n for (const item of all) {\n const r = item as Record<string, unknown>\n // Closure filter: if a closure is supplied for this collection, keep\n // only rows whose id is in the allowlist.\n const allow = entry.closure?.get(s.collection)\n if (allow && !allow.has(String((r as { id?: unknown }).id))) continue\n // User-supplied row predicate (same semantics as single-vault toBytes).\n if (s.filter && !s.filter(r)) continue\n records.push(r)\n }\n const projected = projectRecords(entry.vault, s.collection, records, entry.redact)\n loaded.push({ entry, prefix, sheetOpt: s, records: projected })\n\n // Build index for this collection, keyed by every field that any denorm\n // might reference as `keyField`. We index by `id` by default, and also\n // by any explicitly declared `keyField` that differs from `'id'`.\n const indexKey = `${prefix}/${s.collection}`\n const collectionIndex = denormIndex.get(indexKey) ?? new Map<string, Record<string, unknown>>()\n denormIndex.set(indexKey, collectionIndex)\n for (const r of projected) {\n // Always index by `id` (most common key) and by any declared keyFields.\n const idVal = (r as { id?: unknown }).id\n if (idVal != null) collectionIndex.set(safeStringify(idVal), r)\n }\n }\n }\n\n // If any sheet declares denormalize with a non-'id' keyField, also index by that.\n for (const ls of loaded) {\n for (const d of ls.sheetOpt.denormalize ?? []) {\n if (d.from.keyField === 'id') continue // already indexed\n const indexKey = `${d.from.label}/${d.from.collection}`\n const idx = denormIndex.get(indexKey)\n if (!idx) continue\n // Find the loaded sheet for that label+collection and re-index by keyField.\n const srcLoaded = loaded.find(\n (l) => l.prefix === d.from.label && l.sheetOpt.collection === d.from.collection,\n )\n if (!srcLoaded) continue\n for (const r of srcLoaded.records) {\n const kv = r[d.from.keyField]\n if (kv != null) idx.set(safeStringify(kv), r)\n }\n }\n }\n\n // ── Pass 2: emit sheets with optional denorm columns ─────────────\n\n const allSheets: XlsxSheet[] = []\n const manifestRows: (string | number)[][] = []\n\n for (const { prefix, sheetOpt: s, records } of loaded) {\n const baseColumns = s.columns ?? inferColumns(records)\n const denormDefs = s.denormalize ?? []\n\n if (denormDefs.length === 0) {\n // Fast path: no denorm — identical to Task 1 behaviour.\n const sheetName = `${prefix}${sep}${s.name}`\n allSheets.push(buildFlatSheet(sheetName, baseColumns, records))\n manifestRows.push([prefix, s.collection, records.length])\n continue\n }\n\n // Denorm path: append extra columns after the declared ones.\n const allColumns = [...baseColumns, ...denormDefs.map((d) => d.column)]\n const sheetName = `${prefix}${sep}${s.name}`\n const rows = records.map((r) => {\n const baseCells = baseColumns.map((c) => r[c] ?? null)\n const denormCells = denormDefs.map((d) => {\n const idxKey = `${d.from.label}/${d.from.collection}`\n const idx = denormIndex.get(idxKey)\n if (!idx) return ''\n const fkVal = r[d.localField]\n if (fkVal == null) return ''\n const supporting = idx.get(safeStringify(fkVal))\n if (!supporting) return '' // unresolved FK → empty cell\n return supporting[d.from.pick] ?? ''\n })\n return [...baseCells, ...denormCells]\n })\n allSheets.push({ name: sheetName, header: allColumns, rows })\n manifestRows.push([prefix, s.collection, records.length])\n }\n\n // Prepend the _manifest sheet.\n allSheets.unshift({\n name: '_manifest',\n header: ['Vault', 'Collection', 'Records'],\n rows: manifestRows,\n })\n\n return writeXlsx(allSheets)\n}\n\n// ── internals ─────────────────────────────────────────────────────\n\n/**\n * Apply `AsXlsxOptions.redact` to a sheet's records via the hub's\n * `applyListProjection`, describing the collection on its own vault (each\n * sheet/path may have a different vault instance — multi-vault entries in\n * particular). No-op (returns a shallow copy) when `redact` is unset.\n */\nfunction projectRecords(\n vault: Vault,\n collectionName: string,\n records: readonly Record<string, unknown>[],\n redact: AsXlsxOptions['redact'],\n): Record<string, unknown>[] {\n if (redact === undefined || redact === false) return [...records]\n const desc = vault.collection(collectionName).describe()\n const projectionOpts = redact === true ? undefined : { sensitivity: redact.sensitivity }\n return records.map((r) => applyListProjection(desc, r, projectionOpts))\n}\n\n/**\n * Apply a number format to one cell value.\n *\n * ⚠️ THE COERCION IS THE POINT, not the style. Hub stores money as a decimal\n * STRING (to avoid binary-float error), so without this the value reaches\n * `cellXml` as a string, is interned into the shared-string table, and Excel\n * renders a TEXT cell — which `SUM()` counts as zero. The format code alone\n * would not fix that; a text cell with a currency format is still text.\n *\n * A value that does not parse as finite is passed through untouched rather than\n * becoming `NaN`: a column with one malformed row should lose that row's\n * formatting, not its contents.\n */\nfunction numberFormatted(raw: unknown, fmt: string | undefined): unknown {\n if (fmt === undefined) return raw\n const num =\n typeof raw === 'string' && raw.trim() !== '' && Number.isFinite(Number(raw)) ? Number(raw) : raw\n return styled(num as string | number | boolean | null, fmt)\n}\n\n/**\n * Build a flat {@link XlsxSheet} from pre-filtered records.\n * Used by both {@link toBytes} (via its own inline path) and\n * {@link toBytesMultiVault}.\n */\nfunction buildFlatSheet(\n name: string,\n columns: readonly string[],\n records: readonly Record<string, unknown>[],\n numberFormats?: Partial<Record<string, string>>,\n): XlsxSheet {\n return {\n name,\n header: [...columns],\n rows: records.map((r) => columns.map((c) => numberFormatted(r[c] ?? null, numberFormats?.[c]))),\n }\n}\n\n/** Safely stringify an unknown id/code/label (objects → JSON, never '[object Object]'). */\nfunction safeStringify(v: unknown): string {\n if (typeof v === 'string') return v\n if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'bigint') return String(v)\n if (v == null) return ''\n try {\n return JSON.stringify(v) ?? ''\n } catch {\n return ''\n }\n}\n\n/** Coerce an arbitrary value to a formula cached-value (string/number/boolean). */\nfunction asCached(v: unknown): string | number | boolean {\n if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'string') return v\n return safeStringify(v)\n}\n\n/**\n * Smart-workbook builder (#414 P1+P2): id-first sheets, FK→VLOOKUP label columns,\n * a `_manifest` index, and — when i18n fields are declared — per-locale columns\n * with a display column driven live by a global `LANG` named range on a\n * `_settings` sheet. Refs auto-detected from `vault.dumpSchema()`.\n */\nasync function buildSmartSheets<T = unknown>(\n vault: Vault,\n options: AsXlsxOptions<T>,\n): Promise<{ sheets: XlsxSheet[]; definedNames: { name: string; ref: string }[] }> {\n const snapshot = await vault.dumpSchema()\n const sheetNameByCollection = new Map<string, string>()\n for (const s of options.sheets) sheetNameByCollection.set(s.collection, s.name)\n\n interface Mat {\n // `AsXlsxSheetOptions<T>`, not `<unknown>`: `filter` is contravariant in T,\n // so the narrower form is NOT assignable to the wider one. Nothing here\n // reads `filter` — but typing it `<unknown>` would still reject the assign.\n opt: AsXlsxSheetOptions<T>\n records: Record<string, unknown>[]\n cols: string[]\n labelMap: Map<string, unknown>\n i18nFields: string[]\n }\n const mats: Mat[] = []\n const localeSet = new Set<string>()\n for (const sheetOpt of options.sheets) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const collection = vault.collection<any>(sheetOpt.collection)\n const list = await collection.list()\n const records: Record<string, unknown>[] = []\n for (const item of list) {\n const r = item as Record<string, unknown>\n // Same caller-asserted shape as in `toBytes` — see AsXlsxSheetOptions.\n if (sheetOpt.filter && !sheetOpt.filter(r as T)) continue\n records.push(r)\n }\n const projected = projectRecords(vault, sheetOpt.collection, records, options.redact)\n const base = sheetOpt.columns ?? inferColumns(projected)\n const cols = ['id', ...base.filter((c) => c !== 'id')]\n const labelCol = cols.find((c) => c !== 'id')\n const labelMap = new Map<string, unknown>()\n if (labelCol) for (const r of projected) if (r.id != null) labelMap.set(safeStringify(r.id), r[labelCol])\n const i18nFields = (sheetOpt.i18nFields ?? []).filter((f) => cols.includes(f))\n for (const f of i18nFields) {\n for (const r of projected) {\n const v = r[f]\n if (v && typeof v === 'object') for (const loc of Object.keys(v)) localeSet.add(loc)\n }\n }\n mats.push({ opt: sheetOpt, records: projected, cols, labelMap, i18nFields })\n }\n const matByCollection = new Map(mats.map((m) => [m.opt.collection, m]))\n\n // Load declared dictionaries once; merge their locales into the global set.\n const dictEntries = new Map<string, DictEntry[]>()\n for (const m of mats) {\n for (const [field, dictName] of Object.entries(m.opt.dictFields ?? {})) {\n if (!m.cols.includes(field) || dictEntries.has(dictName)) continue\n dictEntries.set(dictName, await vault.dictionary(dictName).list())\n }\n }\n for (const entries of dictEntries.values()) {\n for (const e of entries) for (const loc of Object.keys(e.labels)) localeSet.add(loc)\n }\n\n const locales = [...localeSet].sort()\n const hasLang = locales.length > 0\n const defaultLocale = locales.includes('en') ? 'en' : (locales[0] ?? 'en')\n // Build an IF-chain over the per-locale cells, switched by the LANG name.\n const ifChain = (refOf: (loc: string) => string): string => {\n if (locales.length === 0) return '\"\"'\n let expr = refOf(locales[0]!)\n for (let k = locales.length - 1; k >= 0; k--) {\n expr = `IF(LANG=\"${locales[k]}\",${refOf(locales[k]!)},${expr})`\n }\n return expr\n }\n\n // Per-collection sheet metadata so summaries can reference data-sheet columns.\n const sheetMeta = new Map<string, { name: string; colIndex: Map<string, number> }>()\n\n const dataSheets: XlsxSheet[] = mats.map((m) => {\n const refs = snapshot.collections[m.opt.collection]?.refs ?? {}\n const fields = snapshot.collections[m.opt.collection]?.fields ?? {}\n const i18nSet = new Set(m.i18nFields)\n const refFields = Object.keys(refs).filter((f) => m.cols.includes(f) && matByCollection.has(refs[f]!.target))\n const baseCols = m.cols.filter((c) => !i18nSet.has(c))\n const i18nFlat = m.i18nFields.flatMap((f) => [f, ...locales.map((l) => `${f}__${l}`)])\n const dictPairs = Object.entries(m.opt.dictFields ?? {}).filter(([f]) => m.cols.includes(f))\n const header = [\n ...baseCols,\n ...i18nFlat,\n ...dictPairs.map(([f]) => `${f}__label`),\n ...refFields.map((f) => `${f}__label`),\n ]\n const colIndex = new Map<string, number>()\n header.forEach((h, i) => colIndex.set(h, i + 1))\n sheetMeta.set(m.opt.collection, { name: m.opt.name, colIndex })\n\n // Data-validation dropdowns on base columns: explicit > ref-range > enum.\n const lastRow = Math.max(m.records.length + 1, 2)\n const validations: XlsxValidation[] = []\n for (const field of baseCols) {\n const colL = colLetter(colIndex.get(field)!)\n const sqref = `${colL}2:${colL}${lastRow}`\n const explicit = m.opt.dropdowns?.[field]\n if (explicit && explicit.length > 0) {\n validations.push({ sqref, values: explicit })\n continue\n }\n const rf = refs[field]\n if (rf && matByCollection.has(rf.target)) {\n const targetSheet = sheetNameByCollection.get(rf.target)!\n const targetRows = Math.max(matByCollection.get(rf.target)!.records.length + 1, 2)\n validations.push({ sqref, formula1: `'${targetSheet}'!$A$2:$A$${targetRows}` })\n continue\n }\n const enumVals = fields[field]?.constraints?.['values']\n if (Array.isArray(enumVals) && enumVals.length > 0) validations.push({ sqref, values: enumVals.map((v) => safeStringify(v)) })\n }\n\n const rows = m.records.map((r, i) => {\n const rowNum = i + 2 // header is row 1\n const baseCells = baseCols.map((c) => {\n const raw = c === 'id' ? (r.id ?? null) : (r[c] ?? null)\n return numberFormatted(raw, (m.opt.numberFormats as Partial<Record<string, string>> | undefined)?.[c])\n })\n const i18nCells = m.i18nFields.flatMap((f) => {\n const rawMap = (r[f] && typeof r[f] === 'object' ? r[f] : {}) as Record<string, unknown>\n const display = formula(\n ifChain((l) => `${colLetter(colIndex.get(`${f}__${l}`)!)}${rowNum}`),\n asCached(rawMap[defaultLocale] ?? ''),\n )\n return [display, ...locales.map((l) => (rawMap[l] ?? null) as unknown)]\n })\n const dictCells = dictPairs.map(([f, dn]) => {\n const codeRef = `${colLetter(colIndex.get(f)!)}${rowNum}`\n const lk = `_Lookups_${dn}`\n // MATCH(LANG, lookup header row) → the locale's column in the lookup sheet.\n const f1 = `IFERROR(VLOOKUP(${codeRef},'${lk}'!$A:$ZZ,MATCH(LANG,'${lk}'!$1:$1,0),FALSE),${codeRef})`\n const code = r[f]\n const entry = code == null ? undefined : dictEntries.get(dn)?.find((e) => e.key === safeStringify(code))\n return formula(f1, asCached(entry?.labels[defaultLocale] ?? (code ?? '')))\n })\n const refCells = refFields.map((f) => {\n const target = refs[f]!.target\n const targetSheet = sheetNameByCollection.get(target)!\n const targetMat = matByCollection.get(target)!\n const codeRef = `${colLetter(colIndex.get(f)!)}${rowNum}`\n const f1 = `IFERROR(VLOOKUP(${codeRef},'${targetSheet}'!$A:$ZZ,2,FALSE),\"\")`\n const code = r[f]\n const cached = code == null ? '' : asCached(targetMat.labelMap.get(safeStringify(code)))\n return formula(f1, cached)\n })\n return [...baseCells, ...i18nCells, ...dictCells, ...refCells]\n })\n return {\n name: m.opt.name,\n header,\n rows,\n ...(validations.length > 0 ? { validations } : {}),\n ...(m.opt.widths !== undefined ? { widths: m.opt.widths } : {}),\n }\n })\n\n const manifest: XlsxSheet = {\n name: '_manifest',\n header: ['Collection', 'Records', 'Refs'],\n rows: mats.map((m) => {\n const refs = snapshot.collections[m.opt.collection]?.refs ?? {}\n return [m.opt.name, m.records.length, Object.entries(refs).map(([f, r]) => `${f}→${r.target}`).join(', ')]\n }),\n }\n\n // Global LANG control — a Settings sheet + named range every i18n/dict label\n // references via IF(LANG=…). Only emitted when there are i18n fields.\n // GroupBy summary sheets (#414 P3/P5). Excel dialect (default): per-row live\n // SUMIFS/COUNTIFS/AVERAGEIFS, values cached. Sheets dialect: a single\n // Google-Sheets QUERY formula (Sheets-only — QUERY errors in Excel).\n const dialect = options.dialect ?? 'excel'\n const summarySheets: XlsxSheet[] = []\n for (const spec of options.summaries ?? []) {\n const src = sheetMeta.get(spec.from)\n const srcMat = matByCollection.get(spec.from)\n const gCol = src?.colIndex.get(spec.groupBy)\n if (!src || !srcMat || gCol === undefined) continue\n const gLetter = colLetter(gCol)\n\n if (dialect === 'sheets') {\n // One live QUERY formula that spills the grouped table (#414 P5).\n const selects: string[] = []\n const labels: string[] = []\n for (const a of spec.aggregates) {\n if (a.op === 'count') {\n selects.push(`COUNT(${gLetter})`)\n labels.push(`COUNT(${gLetter}) '${a.label}'`)\n continue\n }\n const vIdx = a.field ? src.colIndex.get(a.field) : undefined\n if (vIdx === undefined) continue\n const vL = colLetter(vIdx)\n const fn = a.op === 'sum' ? 'SUM' : 'AVG'\n selects.push(`${fn}(${vL})`)\n labels.push(`${fn}(${vL}) '${a.label}'`)\n }\n const q = `QUERY('${src.name}'!A:ZZ, \"SELECT ${gLetter}, ${selects.join(', ')} GROUP BY ${gLetter} LABEL ${labels.join(', ')}\", 1)`\n summarySheets.push({ name: spec.name, rows: [[formula(q)]] })\n continue\n }\n\n const seen = new Set<string>()\n const groups: unknown[] = []\n for (const r of srcMat.records) {\n const k = safeStringify(r[spec.groupBy])\n if (!seen.has(k)) {\n seen.add(k)\n groups.push(r[spec.groupBy])\n }\n }\n const header = [spec.groupBy, ...spec.aggregates.map((a) => a.label)]\n const rows = groups.map((g, i) => {\n const rowNum = i + 2\n const groupRecs = srcMat.records.filter((r) => safeStringify(r[spec.groupBy]) === safeStringify(g))\n const cells: unknown[] = [g]\n for (const a of spec.aggregates) {\n if (a.op === 'count') {\n cells.push(formula(`COUNTIFS('${src.name}'!$${gLetter}:$${gLetter},$A${rowNum})`, groupRecs.length))\n continue\n }\n const vIdx = a.field ? src.colIndex.get(a.field) : undefined\n if (vIdx === undefined) {\n cells.push(null)\n continue\n }\n const vLetter = colLetter(vIdx)\n const nums = groupRecs.map((r) => Number(r[a.field!])).filter((n) => Number.isFinite(n))\n const sum = nums.reduce((s, n) => s + n, 0)\n const cached = a.op === 'sum' ? sum : nums.length ? sum / nums.length : 0\n const fn = a.op === 'sum' ? 'SUMIFS' : 'AVERAGEIFS'\n cells.push(\n formula(`${fn}('${src.name}'!$${vLetter}:$${vLetter},'${src.name}'!$${gLetter}:$${gLetter},$A${rowNum})`, cached),\n )\n }\n return cells\n })\n summarySheets.push({ name: spec.name, header, rows })\n }\n\n // Hidden lookup sheets: one per declared dictionary (code + per-locale labels).\n const lookupSheets: XlsxSheet[] = [...dictEntries.entries()].map(([dn, entries]) => ({\n name: `_Lookups_${dn}`,\n header: ['Code', ...locales],\n rows: entries.map((e) => [e.key, ...locales.map((l) => e.labels[l] ?? '')]),\n }))\n\n const settingsSheets: XlsxSheet[] = []\n const definedNames: { name: string; ref: string }[] = []\n if (hasLang) {\n settingsSheets.push({\n name: '_settings',\n header: ['Setting', 'Value'],\n rows: [['Language', defaultLocale]],\n validations: [{ sqref: 'B2:B2', values: locales }],\n })\n definedNames.push({ name: 'LANG', ref: `'_settings'!$B$2` })\n }\n\n return {\n sheets: [...settingsSheets, ...lookupSheets, manifest, ...summarySheets, ...dataSheets],\n definedNames,\n }\n}\n\nfunction inferColumns(records: readonly Record<string, unknown>[]): string[] {\n const seen = new Set<string>()\n const out: string[] = []\n for (const r of records) {\n for (const key of Object.keys(r)) {\n if (!seen.has(key)) {\n seen.add(key)\n out.push(key)\n }\n }\n }\n return out\n}\n\n// ── Reader ──────────────────────────────────────\n\nimport { diffVault } from '@noy-db/hub'\n\n// Hub-owned as of 0.7 (ADR 0004). This line replaced a local declaration that\n// existed identically in six as-* packages, with nothing comparing them.\nimport type { ImportPolicy, ImportPlan } from '@noy-db/hub/as'\nexport type { ImportPolicy }\n\n/**\n * Thrown when a dict field contains two different keys whose labels are\n * identical in any locale — making label→key inversion ambiguous.\n *\n * @example\n * dict has: { key: 'a', labels: { en: 'Open' } } and { key: 'b', labels: { th: 'Open' } }\n * → XlsxDictAmbiguityError('status', 'Open')\n */\nexport class XlsxDictAmbiguityError extends Error {\n constructor(\n public readonly column: string,\n public readonly label: string,\n ) {\n super(\n `as-xlsx.fromBytes: dict for column \"${column}\" has ambiguous label \"${label}\" — ` +\n 'it maps to more than one key across locales. ' +\n 'Supply a stricter dict or resolve the label conflict before importing.',\n )\n this.name = 'XlsxDictAmbiguityError'\n }\n}\n\nexport interface AsXlsxImportOptions {\n /** Target collection. xlsx has no native collection grouping. */\n readonly collection: string\n /**\n * Sheet name to read. Defaults to the first sheet in the workbook.\n */\n readonly sheet?: string\n /**\n * 1-based header row index. Default `1` (first row).\n */\n readonly headerRow?: number\n /**\n * Optional field type hints. xlsx cells already have a type\n * (number, boolean, shared-string), so this is for the few cases\n * where the writer's emission rules don't preserve intent —\n * notably ISO-date strings the writer routed through the shared-\n * string path. `'date'` parses the value with `new Date()` and\n * keeps the result as an ISO-8601 string for stable round-tripping.\n */\n readonly fieldTypes?: Record<string, 'string' | 'number' | 'boolean' | 'date'>\n /** Field carrying the record id. Default `'id'`. */\n readonly idKey?: string\n /** Reconciliation policy. Default `'merge'`. */\n readonly policy?: ImportPolicy\n /**\n * Per-field dict definitions for label→key inversion. When a column\n * header matches a key here, cell values are matched against every\n * locale label in the dict entries; matching labels are replaced by\n * their stable key before building the ImportPlan.\n *\n * Takes precedence over any vault dictionary with the same name.\n * For fields not listed here, `fromBytes` automatically tries\n * `vault.dictionary(fieldName).list()` as a fallback.\n *\n * Unknown labels (no match in any locale) pass through as-is.\n */\n readonly dicts?: Readonly<Record<string, readonly DictEntry[]>>\n /**\n * Read a sheet produced by smart export (#414 P4). Reverses the smart layout:\n * reconstructs i18n fields from their per-locale columns (`<f>__<loc>` →\n * `{ loc: value }`), and drops derived columns — the i18n display column and\n * every `<f>__label` (FK/dict) formula column. Code columns (the real values)\n * pass through. Maps onto the existing collection schema (Mode A).\n */\n readonly smart?: boolean\n}\n\nexport type AsXlsxImportPlan = ImportPlan\n\n/**\n * Build an import plan from an `.xlsx` byte stream. Inverts what\n * `toBytes()` writes — the first row is the header, subsequent rows\n * are records keyed by the column letters in the header row.\n *\n * Capability: `assertCanImport('plaintext', 'xlsx')`.\n * Atomicity: `apply()` runs inside `vault.noydb.transaction()`.\n *\n * **Not supported (matches the writer scope):**\n * - Cell styles / number formats / date format codes\n * - Formulas, merged cells, frozen panes\n * - Inline strings → handled defensively (since some upstream tools\n * emit them) but the writer never produces them\n * - Excel date serials → not auto-detected; pass `fieldTypes: { ts:\n * 'date' }` to coerce a numeric serial to ISO. Date round-trip via\n * the writer (which emits ISO strings) works without a hint.\n *\n * **Dict-label inversion** — supply `dicts` per field (or populate vault\n * dictionaries with `withI18n()`) and the reader automatically inverts\n * human labels back to their stable keys. Ambiguous labels throw\n * `XlsxDictAmbiguityError`; unknown labels pass through unchanged.\n */\nexport async function fromBytes(\n vault: Vault,\n bytes: Uint8Array,\n options: AsXlsxImportOptions,\n): Promise<AsXlsxImportPlan> {\n vault.assertCanImport('plaintext', 'xlsx')\n\n const policy: ImportPolicy = options.policy ?? 'merge'\n const idKey = options.idKey ?? 'id'\n const types = options.fieldTypes ?? {}\n const headerRowIdx = (options.headerRow ?? 1) - 1\n if (headerRowIdx < 0) {\n throw new Error('as-xlsx.fromBytes: headerRow must be 1-based and >= 1')\n }\n\n const decoded = await readXlsx(bytes)\n if (decoded.sheets.length === 0) {\n return emptyXlsxPlan(vault, options.collection, policy, idKey)\n }\n const sheet = options.sheet === undefined\n ? decoded.sheets[0]!\n : decoded.sheets.find((s) => s.name === options.sheet)\n if (sheet === undefined) {\n throw new Error(\n `as-xlsx.fromBytes: workbook has no sheet named \"${options.sheet}\". ` +\n `Available: ${decoded.sheets.map((s) => `\"${s.name}\"`).join(', ')}`,\n )\n }\n\n const allRows = sheet.rows\n if (allRows.length <= headerRowIdx) {\n return emptyXlsxPlan(vault, options.collection, policy, idKey)\n }\n const headerRow = allRows[headerRowIdx]!\n // Map column letter → field name. Only columns that have a\n // non-empty header cell contribute fields; blank columns are\n // ignored on read so a half-populated header doesn't synthesise\n // numeric `__EMPTY` keys the way some xlsx libs do.\n const colToField = new Map<string, string>()\n for (const [col, value] of Object.entries(headerRow)) {\n const fieldName = headerCellToField(value)\n if (fieldName === '') continue\n colToField.set(col, fieldName)\n }\n\n // Build per-field inversion maps.\n // Priority: explicit dicts option > vault dictionary lookup.\n // Vault lookup fires for every column not covered by explicit dicts;\n // vault.dictionary(name).list() returns [] when no dict exists — safe to call on any name.\n const invertMaps = new Map<string, Map<string, string>>()\n const allFields = new Set(colToField.values())\n for (const field of allFields) {\n const explicitEntries = options.dicts?.[field]\n if (explicitEntries !== undefined && explicitEntries.length > 0) {\n invertMaps.set(field, buildInversionMap(field, explicitEntries))\n continue\n }\n try {\n const vaultEntries = await vault.dictionary(field).list()\n if (vaultEntries.length > 0) {\n invertMaps.set(field, buildInversionMap(field, vaultEntries))\n }\n } catch {\n // No dictionary for this field — skip silently\n }\n }\n\n // Smart layout: any `<base>__<suffix>` where suffix isn't 'label' marks an\n // i18n base whose display column (`<base>`) is a formula to be dropped and\n // whose per-locale columns rebuild the `{ loc: value }` map.\n const i18nBases = new Set<string>()\n if (options.smart) {\n for (const field of colToField.values()) {\n const m = /^(.+)__(.+)$/.exec(field)\n if (m && m[2] !== 'label') i18nBases.add(m[1]!)\n }\n }\n\n const records: Record<string, unknown>[] = []\n for (let i = headerRowIdx + 1; i < allRows.length; i++) {\n const row = allRows[i]!\n const record: Record<string, unknown> = {}\n let hasAny = false\n for (const [col, value] of Object.entries(row)) {\n const field = colToField.get(col)\n if (field === undefined) continue\n if (options.smart) {\n if (field.endsWith('__label')) continue // derived FK/dict label\n const lm = /^(.+)__(.+)$/.exec(field)\n if (lm && lm[2] !== 'label' && i18nBases.has(lm[1]!)) {\n const base = lm[1]!\n const coerced = coerceXlsxCell(value, types[base])\n if (coerced !== undefined && coerced !== '') {\n const map = (record[base] as Record<string, unknown> | undefined) ?? {}\n map[lm[2]!] = coerced\n record[base] = map\n hasAny = true\n }\n continue\n }\n if (i18nBases.has(field)) continue // i18n display column (formula)\n }\n const coerced = coerceXlsxCell(value, types[field])\n if (coerced !== undefined) {\n const invMap = invertMaps.get(field)\n if (invMap !== undefined && typeof coerced === 'string') {\n record[field] = invMap.get(coerced) ?? coerced\n } else {\n record[field] = coerced\n }\n hasAny = true\n }\n }\n if (hasAny) records.push(record)\n }\n\n const plan = await diffVault(vault, { [options.collection]: records }, {\n collections: [options.collection],\n idKey,\n })\n\n return {\n plan,\n policy,\n async apply(): Promise<void> {\n // Routes through the transactionsStrategy seam — clear error when\n // withTransactions() isn't opted in.\n await vault.noydb.transaction((tx) => {\n const txVault = tx.vault(vault.name)\n for (const entry of plan.added) {\n txVault.collection(entry.collection).put(entry.id, entry.record, { reason: 'import:xlsx' })\n }\n if (policy !== 'insert-only') {\n for (const entry of plan.modified) {\n txVault.collection(entry.collection).put(entry.id, entry.record, { reason: 'import:xlsx' })\n }\n }\n if (policy === 'replace') {\n for (const entry of plan.deleted) {\n txVault.collection(entry.collection).delete(entry.id)\n }\n }\n })\n },\n }\n}\n\nasync function emptyXlsxPlan(\n vault: Vault,\n collection: string,\n policy: ImportPolicy,\n idKey: string,\n): Promise<AsXlsxImportPlan> {\n const plan = await diffVault(vault, { [collection]: [] }, { collections: [collection], idKey })\n return { plan, policy, async apply() { /* nothing to do */ } }\n}\n\n/**\n * Build a label→key inversion map from a list of DictEntry objects.\n * Scans every locale in every entry's labels map. Throws\n * XlsxDictAmbiguityError if the same label string appears for two\n * different keys across any locale.\n */\nfunction buildInversionMap(column: string, entries: readonly DictEntry[]): Map<string, string> {\n const map = new Map<string, string>()\n for (const entry of entries) {\n for (const label of Object.values(entry.labels)) {\n if (label === '') continue\n const existing = map.get(label)\n if (existing !== undefined && existing !== entry.key) {\n throw new XlsxDictAmbiguityError(column, label)\n }\n map.set(label, entry.key)\n }\n }\n return map\n}\n\nfunction headerCellToField(value: unknown): string {\n // Header cells should be strings; defensively coerce numbers/booleans\n // and reject everything else (objects, undefined). Empty string =\n // \"this column has no header → ignore\".\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return ''\n}\n\nfunction coerceXlsxCell(\n value: unknown,\n type?: 'string' | 'number' | 'boolean' | 'date',\n): unknown {\n if (value === undefined || value === null) return undefined\n if (type === undefined) return value\n if (type === 'string') {\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return undefined\n }\n if (type === 'number') {\n if (typeof value === 'number') return value\n const n = Number(value)\n return Number.isFinite(n) ? n : undefined\n }\n if (type === 'boolean') {\n if (typeof value === 'boolean') return value\n if (value === 'true' || value === 1) return true\n if (value === 'false' || value === 0) return false\n return undefined\n }\n if (type === 'date') {\n // Excel date serial: days since 1900-01-01 with the historical\n // 1900-leap-year quirk. Numbers are converted; strings parsed\n // with `new Date()` and re-emitted as ISO so round-trips are\n // stable. Returning a string keeps the JSON envelope canonical.\n if (typeof value === 'number' && Number.isFinite(value)) {\n const ms = excelSerialToMs(value)\n const d = new Date(ms)\n return d.toISOString()\n }\n if (typeof value === 'string') {\n const d = new Date(value)\n if (!Number.isNaN(d.getTime())) return d.toISOString()\n }\n return undefined\n }\n return value\n}\n\n/**\n * Convert an Excel-style date serial to a JS millisecond timestamp.\n * Excel's \"1900 system\" treats day 1 as 1900-01-01 and includes the\n * non-existent 1900-02-29, so the offset between Excel serial and\n * Unix epoch days is 25569 for any date past 1900-03-01.\n *\n * Pre-1900-03 dates (serial ≤ 60) are uncommon in noy-db's domains\n * and we don't try to compensate for the leap-year bug there — they\n * round-trip with one-day skew, same as Excel itself.\n */\nfunction excelSerialToMs(serial: number): number {\n const EPOCH_OFFSET_DAYS = 25569\n const MS_PER_DAY = 86400_000\n return Math.round((serial - EPOCH_OFFSET_DAYS) * MS_PER_DAY)\n}\n","/**\n * Minimal zero-dependency XLSX writer.\n *\n * An `.xlsx` file is a ZIP archive (Office Open XML / OOXML) with\n * SpreadsheetML inside. This writer emits the six parts needed for\n * a valid worksheet and hands them to `@noy-db/as-zip`'s\n * `writeZip()` to assemble the final `.xlsx` bytes.\n *\n * ## Emitted parts\n *\n * ```\n * [Content_Types].xml # MIME descriptors\n * _rels/.rels # root → workbook pointer\n * xl/workbook.xml # sheet list\n * xl/_rels/workbook.xml.rels # sheet-part pointers\n * xl/worksheets/sheet<N>.xml # cell data\n * xl/sharedStrings.xml # string pool (Unicode-safe)\n * ```\n *\n * Strings route through the shared-string table (`sharedStrings.xml`)\n * rather than being inlined on cells, which is:\n *\n * 1. Slightly more compact when strings repeat (client names,\n * status labels, locale codes).\n * 2. Consistent with how Excel writes its own files — some\n * strict-OOXML readers refuse inline strings.\n *\n * Numbers, booleans, and dates are written as typed cells; strings\n * and everything else fall back to the shared-string path.\n *\n * ## Not supported\n *\n * - Cell styles (fonts, colours, borders, number formats).\n * - Formulas, merged cells, frozen panes, auto-filter.\n * - Charts, images, drawings.\n * - Zip64 / archives > 4 GiB.\n *\n * @module\n */\n\nimport { writeZip, type ZipEntry } from '@noy-db/as-zip'\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>'\nconst ENCODER = new TextEncoder()\n\n/**\n * A formula cell. Emits `<f>` so the spreadsheet recomputes live; `v` is an\n * optional cached result (what the formula currently evaluates to) so the value\n * shows immediately on open and survives a round-trip read. Build via\n * {@link formula}. The formula string must NOT include a leading `=`.\n */\nexport interface XlsxFormulaCell {\n readonly __xlsxFormula: string\n readonly v?: string | number | boolean\n}\n\n/** Build a {@link XlsxFormulaCell}. `f` is the formula body without a leading `=`. */\nexport function formula(f: string, cachedValue?: string | number | boolean): XlsxFormulaCell {\n return cachedValue === undefined ? { __xlsxFormula: f } : { __xlsxFormula: f, v: cachedValue }\n}\n\nfunction isFormulaCell(v: unknown): v is XlsxFormulaCell {\n return typeof v === 'object' && v !== null && typeof (v as { __xlsxFormula?: unknown }).__xlsxFormula === 'string'\n}\n\n/**\n * A value cell carrying a number-format code (e.g. `'#,##0.00'` for currency,\n * `'yyyy-mm-dd'` for dates). Build via {@link styled}. The format only renders\n * meaningfully on numeric values.\n */\nexport interface XlsxStyledCell {\n readonly __xlsxStyle: string\n readonly v: string | number | boolean | null\n}\n\n/** Build a {@link XlsxStyledCell} — a value plus an Excel number-format code. */\nexport function styled(value: string | number | boolean | null, numberFormat: string): XlsxStyledCell {\n return { __xlsxStyle: numberFormat, v: value }\n}\n\nfunction isStyledCell(v: unknown): v is XlsxStyledCell {\n return typeof v === 'object' && v !== null && typeof (v as { __xlsxStyle?: unknown }).__xlsxStyle === 'string'\n}\n\n/** A data-validation rule (dropdown) over a cell range. */\nexport interface XlsxValidation {\n /** Target range in A1 notation, e.g. `'B2:B100'`. */\n readonly sqref: string\n /** Inline allowed values → a `\"a,b,c\"` list. Mutually exclusive with `formula1`. */\n readonly values?: readonly string[]\n /** Explicit `formula1` (e.g. a range ref `Clients!$A$2:$A$999`). */\n readonly formula1?: string\n}\n\n/** One row in a sheet. A cell is a primitive value or an {@link XlsxFormulaCell}. */\nexport type XlsxRow = ReadonlyArray<unknown>\n\n/** One sheet in a workbook. */\nexport interface XlsxSheet {\n /** Sheet tab name — Excel caps at 31 chars; we truncate with `…`. */\n readonly name: string\n /** Header row, rendered as row 1. Omit to skip the header. */\n readonly header?: readonly string[]\n /** Data rows — each is an array aligned with `header` if present. */\n readonly rows: readonly XlsxRow[]\n /**\n * Optional per-column widths in Excel character units (same scale as\n * SheetJS's `wch`). When set, emits a `<cols>` block so Excel opens\n * the file with the columns sized as specified instead of the default\n * 10-character width. Index aligned with `header` / row cells.\n *\n * Non-finite or non-positive entries are skipped (column falls back\n * to Excel's default width). A consumer typically passes `undefined`\n * for \"auto\" columns and a number for explicit widths.\n */\n readonly widths?: ReadonlyArray<number | undefined>\n /** Data-validation dropdowns applied to ranges on this sheet. */\n readonly validations?: readonly XlsxValidation[]\n}\n\n/**\n * Build a complete `.xlsx` byte stream from the supplied sheet data.\n * Pure — no I/O beyond the internal zip concatenation.\n */\nexport async function writeXlsx(\n sheets: readonly XlsxSheet[],\n options: { definedNames?: ReadonlyArray<{ readonly name: string; readonly ref: string }> } = {},\n): Promise<Uint8Array> {\n if (sheets.length === 0) {\n throw new Error('writeXlsx: at least one sheet is required')\n }\n\n // Dedup sheet names (Excel rejects duplicates) + truncate to 31 chars.\n const seen = new Set<string>()\n const safeSheets: XlsxSheet[] = sheets.map((s, i) => {\n let name = truncateSheetName(s.name || `Sheet${i + 1}`)\n let n = 1\n while (seen.has(name)) {\n const suffix = `(${n++})`\n name = truncateSheetName(name.slice(0, 31 - suffix.length) + suffix)\n }\n seen.add(name)\n return { ...s, name }\n })\n\n // Build shared-string table across every sheet. Emit order = insertion order.\n const sharedStrings: string[] = []\n const stringIndex = new Map<string, number>()\n const internString = (s: string): number => {\n const existing = stringIndex.get(s)\n if (existing !== undefined) return existing\n const idx = sharedStrings.length\n sharedStrings.push(s)\n stringIndex.set(s, idx)\n return idx\n }\n\n // Number-format styles. cellXf 0 is the default (no format); each distinct\n // format code gets a numFmt (id 164+) and a cellXf that applies it.\n const numFmtCodes: string[] = []\n const styleIndexByCode = new Map<string, number>()\n const internStyle = (code: string): number => {\n const existing = styleIndexByCode.get(code)\n if (existing !== undefined) return existing\n const idx = numFmtCodes.length + 1 // cellXf index; 0 is the default xf\n numFmtCodes.push(code)\n styleIndexByCode.set(code, idx)\n return idx\n }\n\n // Build the worksheet XML for each sheet — coercing values per type.\n const sheetXmls: string[] = safeSheets.map((sheet) => {\n const lines: string[] = [\n XML_HEADER,\n '<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">',\n ]\n // `<cols>` must precede `<sheetData>` per OOXML schema. Emit one\n // `<col>` element per defined width; skip entries that are not\n // positive finite numbers so consumers can mix explicit + auto.\n if (sheet.widths && sheet.widths.length > 0) {\n const colLines: string[] = []\n for (let i = 0; i < sheet.widths.length; i++) {\n const w = sheet.widths[i]\n if (typeof w !== 'number' || !Number.isFinite(w) || w <= 0) continue\n const n = i + 1\n colLines.push(`<col min=\"${n}\" max=\"${n}\" width=\"${w}\" customWidth=\"1\"/>`)\n }\n if (colLines.length > 0) {\n lines.push('<cols>', ...colLines, '</cols>')\n }\n }\n lines.push('<sheetData>')\n let rowNum = 0\n if (sheet.header && sheet.header.length > 0) {\n rowNum++\n const cells = sheet.header\n .map((h, i) => {\n const idx = internString(String(h))\n return `<c r=\"${colLetter(i + 1)}${rowNum}\" t=\"s\"><v>${idx}</v></c>`\n })\n .join('')\n lines.push(`<row r=\"${rowNum}\">${cells}</row>`)\n }\n for (const row of sheet.rows) {\n rowNum++\n const cells = row\n .map((value, i) => cellXml(value, i + 1, rowNum, internString, internStyle))\n .join('')\n lines.push(`<row r=\"${rowNum}\">${cells}</row>`)\n }\n lines.push('</sheetData>')\n if (sheet.validations && sheet.validations.length > 0) {\n lines.push(`<dataValidations count=\"${sheet.validations.length}\">`)\n for (const dv of sheet.validations) {\n const f1 = dv.formula1 ?? `\"${(dv.values ?? []).join(',')}\"`\n lines.push(\n `<dataValidation type=\"list\" allowBlank=\"1\" showInputMessage=\"1\" showErrorMessage=\"1\" sqref=\"${escapeXmlAttr(dv.sqref)}\"><formula1>${escapeXmlText(f1)}</formula1></dataValidation>`,\n )\n }\n lines.push('</dataValidations>')\n }\n lines.push('</worksheet>')\n return lines.join('')\n })\n\n // ── Styles (number formats) ─────────────────────────────────────\n\n const hasStyles = numFmtCodes.length > 0\n const stylesXml = hasStyles\n ? [\n XML_HEADER,\n '<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">',\n `<numFmts count=\"${numFmtCodes.length}\">`,\n ...numFmtCodes.map((code, i) => `<numFmt numFmtId=\"${164 + i}\" formatCode=\"${escapeXmlAttr(code)}\"/>`),\n '</numFmts>',\n '<fonts count=\"1\"><font><sz val=\"11\"/><name val=\"Calibri\"/></font></fonts>',\n '<fills count=\"1\"><fill><patternFill patternType=\"none\"/></fill></fills>',\n '<borders count=\"1\"><border/></borders>',\n '<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>',\n `<cellXfs count=\"${numFmtCodes.length + 1}\">`,\n '<xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/>',\n ...numFmtCodes.map(\n (_, i) => `<xf numFmtId=\"${164 + i}\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyNumberFormat=\"1\"/>`,\n ),\n '</cellXfs>',\n '</styleSheet>',\n ].join('')\n : ''\n\n // ── Fixed parts ─────────────────────────────────────────────────\n\n const sheetEntries = safeSheets.map((s, i) => ({\n index: i + 1,\n id: `rId${i + 1}`,\n name: s.name,\n path: `xl/worksheets/sheet${i + 1}.xml`,\n }))\n\n const contentTypes = [\n XML_HEADER,\n '<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">',\n '<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>',\n '<Default Extension=\"xml\" ContentType=\"application/xml\"/>',\n '<Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>',\n ...sheetEntries.map(\n (s) =>\n `<Override PartName=\"/${s.path}\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>`,\n ),\n '<Override PartName=\"/xl/sharedStrings.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml\"/>',\n ...(hasStyles\n ? ['<Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"/>']\n : []),\n '</Types>',\n ].join('')\n\n const rootRels =\n XML_HEADER +\n '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">' +\n '<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/>' +\n '</Relationships>'\n\n const workbookXml = [\n XML_HEADER,\n '<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">',\n '<sheets>',\n ...sheetEntries.map(\n (s) => `<sheet name=\"${escapeXmlAttr(s.name)}\" sheetId=\"${s.index}\" r:id=\"${s.id}\"/>`,\n ),\n '</sheets>',\n ...(options.definedNames && options.definedNames.length > 0\n ? [\n '<definedNames>',\n ...options.definedNames.map(\n (d) => `<definedName name=\"${escapeXmlAttr(d.name)}\">${escapeXmlText(d.ref)}</definedName>`,\n ),\n '</definedNames>',\n ]\n : []),\n '</workbook>',\n ].join('')\n\n const workbookRels = [\n XML_HEADER,\n '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">',\n ...sheetEntries.map(\n (s) =>\n `<Relationship Id=\"${s.id}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet${s.index}.xml\"/>`,\n ),\n `<Relationship Id=\"rIdSharedStrings\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings\" Target=\"sharedStrings.xml\"/>`,\n ...(hasStyles\n ? [`<Relationship Id=\"rIdStyles\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>`]\n : []),\n '</Relationships>',\n ].join('')\n\n const sharedStringsXml = [\n XML_HEADER,\n `<sst xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" count=\"${sharedStrings.length}\" uniqueCount=\"${sharedStrings.length}\">`,\n ...sharedStrings.map((s) => `<si><t xml:space=\"preserve\">${escapeXmlText(s)}</t></si>`),\n '</sst>',\n ].join('')\n\n const entries: ZipEntry[] = [\n { path: '[Content_Types].xml', bytes: ENCODER.encode(contentTypes) },\n { path: '_rels/.rels', bytes: ENCODER.encode(rootRels) },\n { path: 'xl/workbook.xml', bytes: ENCODER.encode(workbookXml) },\n { path: 'xl/_rels/workbook.xml.rels', bytes: ENCODER.encode(workbookRels) },\n { path: 'xl/sharedStrings.xml', bytes: ENCODER.encode(sharedStringsXml) },\n ...(hasStyles ? [{ path: 'xl/styles.xml', bytes: ENCODER.encode(stylesXml) }] : []),\n ...sheetEntries.map((s, i) => ({ path: s.path, bytes: ENCODER.encode(sheetXmls[i] ?? '') })),\n ]\n\n return await writeZip(entries)\n}\n\n// ── Cell emission ─────────────────────────────────────────────────\n\nfunction cellXml(\n value: unknown,\n colIdx: number,\n rowNum: number,\n intern: (s: string) => number,\n internStyle: (code: string) => number,\n): string {\n const ref = `${colLetter(colIdx)}${rowNum}`\n if (isStyledCell(value)) {\n const s = internStyle(value.__xlsxStyle)\n const v = value.v\n if (v === null || v === undefined || v === '') return `<c r=\"${ref}\" s=\"${s}\"/>`\n if (typeof v === 'number' && Number.isFinite(v)) return `<c r=\"${ref}\" s=\"${s}\"><v>${v}</v></c>`\n if (typeof v === 'boolean') return `<c r=\"${ref}\" s=\"${s}\" t=\"b\"><v>${v ? 1 : 0}</v></c>`\n return `<c r=\"${ref}\" s=\"${s}\" t=\"s\"><v>${intern(typeof v === 'string' ? v : String(v))}</v></c>`\n }\n if (isFormulaCell(value)) {\n const f = escapeXmlText(value.__xlsxFormula)\n if (value.v === undefined) return `<c r=\"${ref}\"><f>${f}</f></c>`\n if (typeof value.v === 'number' && Number.isFinite(value.v)) return `<c r=\"${ref}\"><f>${f}</f><v>${value.v}</v></c>`\n if (typeof value.v === 'boolean') return `<c r=\"${ref}\" t=\"b\"><f>${f}</f><v>${value.v ? 1 : 0}</v></c>`\n return `<c r=\"${ref}\" t=\"str\"><f>${f}</f><v>${escapeXmlText(String(value.v))}</v></c>`\n }\n if (value === null || value === undefined || value === '') return `<c r=\"${ref}\"/>`\n if (typeof value === 'number' && Number.isFinite(value)) {\n return `<c r=\"${ref}\"><v>${value}</v></c>`\n }\n if (typeof value === 'boolean') {\n return `<c r=\"${ref}\" t=\"b\"><v>${value ? 1 : 0}</v></c>`\n }\n // Date → ISO-8601 string (Excel renders as text unless the cell\n // has a date-format style; styles are out of scope for this\n // minimal writer).\n const s =\n value instanceof Date\n ? value.toISOString()\n : typeof value === 'string'\n ? value\n : JSON.stringify(value)\n const idx = intern(s)\n return `<c r=\"${ref}\" t=\"s\"><v>${idx}</v></c>`\n}\n\n// ── Helpers ───────────────────────────────────────────────────────\n\n/**\n * Convert a 1-based column index to Excel A1 letter notation.\n * 1 → A, 26 → Z, 27 → AA, 702 → ZZ, 703 → AAA.\n */\nexport function colLetter(n: number): string {\n let s = ''\n let x = n\n while (x > 0) {\n const r = (x - 1) % 26\n s = String.fromCharCode(65 + r) + s\n x = Math.floor((x - 1) / 26)\n }\n return s\n}\n\nfunction truncateSheetName(name: string): string {\n // Excel sheet-name rules: max 31 chars, forbid :/\\?*[]\n const cleaned = name.replace(/[:/\\\\?*[\\]]/g, '_')\n if (cleaned.length <= 31) return cleaned\n return cleaned.slice(0, 30) + '…'\n}\n\n/** XML text escaping — `& < > \\r` (quotes only matter in attributes). */\nfunction escapeXmlText(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\\r/g, ' ')\n}\n\n/** XML attribute escaping. */\nfunction escapeXmlAttr(s: string): string {\n return escapeXmlText(s).replace(/\"/g, '"')\n}\n","/**\n * Minimal OOXML reader. Inverse of `writeXlsx` in `xlsx.ts`.\n *\n * Walks the three parts the writer emits:\n *\n * - `xl/sharedStrings.xml` → string table (idx → string)\n * - `xl/workbook.xml` → sheet name list (with sheetId)\n * - `xl/_rels/workbook.xml.rels` → sheetId → sheet part path\n * - `xl/worksheets/sheet<N>.xml` → cell data\n *\n * Cell types matched to the writer's emission rules:\n * - no `t` attribute → number (`<v>` is parsed as Number)\n * - `t=\"s\"` → shared-string ref\n * - `t=\"b\"` → boolean (`1` ↔ true, `0` ↔ false)\n * - empty `<c />` → undefined\n *\n * Excel date serials are NOT auto-converted — the writer outputs ISO-\n * 8601 strings via the shared-string path, so dates round-trip as\n * strings unless the consumer opts into `dateFields` coercion (handled\n * one layer up in `index.ts:fromBytes`).\n *\n * @module\n */\n\nimport { readZip } from '@noy-db/as-zip'\nimport { XMLParser, XMLValidator } from 'fast-xml-parser'\n\nconst PARSER = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: '@_',\n parseTagValue: false,\n parseAttributeValue: false,\n trimValues: false,\n isArray: (name) => name === 'sheet' || name === 'row' || name === 'c' || name === 'si' || name === 'Relationship',\n})\n\n/** A row of cell values keyed by column letter (`'A' → 'foo'`). */\nexport type ReadXlsxRow = Record<string, unknown>\n\nexport interface ReadXlsxSheet {\n /** Sheet tab name from `xl/workbook.xml`. */\n readonly name: string\n /** All rows in declaration order. Columns indexed by Excel letter. */\n readonly rows: readonly ReadXlsxRow[]\n}\n\nexport interface ReadXlsxResult {\n readonly sheets: readonly ReadXlsxSheet[]\n}\n\n/**\n * Decode an `.xlsx` (OOXML) byte stream into per-sheet row data. The\n * caller decides what to do with the rows (header inference, type\n * coercion, record building) — the reader stays format-only.\n *\n * Throws on malformed XML, missing parts, or sheet-id mismatches.\n */\nexport async function readXlsx(bytes: Uint8Array): Promise<ReadXlsxResult> {\n const entries = await readZip(bytes)\n const partByPath = new Map<string, Uint8Array>()\n for (const e of entries) partByPath.set(e.path, e.bytes)\n\n const sharedStrings = readSharedStrings(partByPath.get('xl/sharedStrings.xml'))\n const sheetMeta = readWorkbook(partByPath.get('xl/workbook.xml'))\n const rels = readWorkbookRels(partByPath.get('xl/_rels/workbook.xml.rels'))\n\n const sheets: ReadXlsxSheet[] = []\n for (const meta of sheetMeta) {\n const target = rels.get(meta.rId)\n if (target === undefined) {\n throw new Error(\n `as-xlsx.readXlsx: workbook references rId=\"${meta.rId}\" but the rels file has no matching target`,\n )\n }\n const sheetPath = `xl/${target}`\n const sheetBytes = partByPath.get(sheetPath)\n if (sheetBytes === undefined) {\n throw new Error(`as-xlsx.readXlsx: missing sheet part ${sheetPath}`)\n }\n sheets.push({\n name: meta.name,\n rows: readSheet(sheetBytes, sharedStrings),\n })\n }\n\n return { sheets }\n}\n\n// ── XML helpers ─────────────────────────────────────────────────────\n\nfunction decodeXml(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes)\n}\n\nfunction parseStrict(xml: string, where: string): unknown {\n const validation = XMLValidator.validate(xml)\n if (validation !== true) {\n const err = validation.err\n throw new Error(\n `as-xlsx.readXlsx: ${where} is not valid XML (${err.code} at line ${err.line}: ${err.msg})`,\n )\n }\n try {\n return PARSER.parse(xml)\n } catch (err) {\n throw new Error(`as-xlsx.readXlsx: failed to parse ${where} (${(err as Error).message})`)\n }\n}\n\n// ── sharedStrings.xml ───────────────────────────────────────────────\n\nfunction readSharedStrings(bytes: Uint8Array | undefined): readonly string[] {\n // The writer always emits sharedStrings.xml, but a hand-crafted\n // .xlsx might omit it if no string cells are present. Treat missing\n // as an empty table so cells with t=\"s\" surface as a clear error\n // (\"ref out of bounds\") rather than a misleading null.\n if (bytes === undefined) return []\n const parsed = parseStrict(decodeXml(bytes), 'xl/sharedStrings.xml')\n const sst = (parsed as Record<string, unknown>).sst\n if (sst === null || sst === undefined || typeof sst !== 'object') return []\n const items = (sst as { si?: unknown[] }).si ?? []\n if (!Array.isArray(items)) return []\n\n return items.map((si): string => {\n if (si === null || typeof si !== 'object') return ''\n const t = (si as { t?: unknown }).t\n return textValue(t)\n })\n}\n\n// ── workbook.xml ────────────────────────────────────────────────────\n\ninterface WorkbookSheetMeta {\n readonly name: string\n readonly sheetId: string\n readonly rId: string\n}\n\nfunction readWorkbook(bytes: Uint8Array | undefined): readonly WorkbookSheetMeta[] {\n if (bytes === undefined) {\n throw new Error('as-xlsx.readXlsx: missing xl/workbook.xml')\n }\n const parsed = parseStrict(decodeXml(bytes), 'xl/workbook.xml')\n const workbook = (parsed as Record<string, unknown>).workbook\n if (workbook === null || workbook === undefined || typeof workbook !== 'object') {\n throw new Error('as-xlsx.readXlsx: xl/workbook.xml has no <workbook> root')\n }\n const sheetsObj = (workbook as { sheets?: unknown }).sheets\n if (sheetsObj === null || sheetsObj === undefined || typeof sheetsObj !== 'object') {\n return []\n }\n const sheetEntries = (sheetsObj as { sheet?: unknown[] }).sheet ?? []\n if (!Array.isArray(sheetEntries)) return []\n\n return sheetEntries.map((s): WorkbookSheetMeta => {\n const obj = s as Record<string, unknown>\n return {\n name: stringAttr(obj['@_name']),\n sheetId: stringAttr(obj['@_sheetId']),\n rId: stringAttr(obj['@_r:id']) || stringAttr(obj['@_id']),\n }\n })\n}\n\n// ── workbook.xml.rels ───────────────────────────────────────────────\n\nfunction readWorkbookRels(bytes: Uint8Array | undefined): Map<string, string> {\n // Map of relationship id → target path (relative to xl/).\n if (bytes === undefined) {\n throw new Error('as-xlsx.readXlsx: missing xl/_rels/workbook.xml.rels')\n }\n const parsed = parseStrict(decodeXml(bytes), 'xl/_rels/workbook.xml.rels')\n const root = (parsed as Record<string, unknown>).Relationships\n if (root === null || root === undefined || typeof root !== 'object') {\n return new Map()\n }\n const rels = (root as { Relationship?: unknown[] }).Relationship ?? []\n if (!Array.isArray(rels)) return new Map()\n\n const map = new Map<string, string>()\n for (const r of rels) {\n const obj = r as Record<string, unknown>\n const id = stringAttr(obj['@_Id'])\n const target = stringAttr(obj['@_Target'])\n if (id) map.set(id, target)\n }\n return map\n}\n\n// ── sheet<N>.xml ────────────────────────────────────────────────────\n\nfunction readSheet(bytes: Uint8Array, sharedStrings: readonly string[]): ReadXlsxRow[] {\n const parsed = parseStrict(decodeXml(bytes), 'sheet')\n const ws = (parsed as Record<string, unknown>).worksheet\n if (ws === null || ws === undefined || typeof ws !== 'object') return []\n const sheetData = (ws as { sheetData?: unknown }).sheetData\n if (sheetData === null || sheetData === undefined || typeof sheetData !== 'object') return []\n const rowEntries = (sheetData as { row?: unknown[] }).row ?? []\n if (!Array.isArray(rowEntries)) return []\n\n const rows: ReadXlsxRow[] = []\n for (const row of rowEntries) {\n if (row === null || typeof row !== 'object') {\n rows.push({})\n continue\n }\n const cells = (row as { c?: unknown[] }).c ?? []\n if (!Array.isArray(cells)) {\n rows.push({})\n continue\n }\n const out: ReadXlsxRow = {}\n for (const c of cells) {\n if (c === null || typeof c !== 'object') continue\n const cellObj = c as Record<string, unknown>\n const ref = stringAttr(cellObj['@_r'])\n if (!ref) continue\n const col = letterFromRef(ref)\n const t = cellObj['@_t']\n const v = cellObj['v']\n const text = textValue(v)\n if (text === '') {\n if (t === 's') {\n out[col] = ''\n continue\n }\n // Empty <c r=\"A1\"/> — leave undefined (caller can drop).\n continue\n }\n if (t === 's') {\n const idx = Number(text)\n if (!Number.isInteger(idx) || idx < 0 || idx >= sharedStrings.length) {\n throw new Error(\n `as-xlsx.readXlsx: shared-string reference ${idx} out of range ` +\n `(table has ${sharedStrings.length} entries)`,\n )\n }\n out[col] = sharedStrings[idx]\n } else if (t === 'b') {\n out[col] = text === '1'\n } else if (t === 'str' || t === 'inlineStr') {\n // Inline strings — writer never emits these but readers should\n // accept them since some upstream tools default to inline.\n out[col] = text\n } else {\n // Numeric cell (no `t` attribute, or t=\"n\").\n const n = Number(text)\n out[col] = Number.isFinite(n) ? n : text\n }\n }\n rows.push(out)\n }\n return rows\n}\n\n/**\n * Extract column letters from an A1-style reference (`\"BC42\"` → `\"BC\"`).\n * Used to build the sparse `Record<column, value>` row shape.\n */\nfunction letterFromRef(ref: string): string {\n let i = 0\n while (i < ref.length && /[A-Z]/.test(ref[i]!)) i++\n return ref.slice(0, i)\n}\n\n/**\n * fast-xml-parser surfaces text-only elements as primitives or as\n * objects with a `#text` field when other attributes are present.\n * Normalize to a string.\n */\nfunction textValue(raw: unknown): string {\n if (raw === null || raw === undefined) return ''\n if (typeof raw === 'string') return raw\n if (typeof raw === 'number' || typeof raw === 'boolean') return String(raw)\n if (typeof raw === 'object') {\n const txt = (raw as { '#text'?: unknown })['#text']\n if (txt !== undefined) return textValue(txt)\n }\n return ''\n}\n\n/**\n * Stringify a fast-xml-parser attribute value. Attributes come through\n * as primitives; defensively narrow before `String()` so the lint rule\n * (`@typescript-eslint/no-base-to-string`) is satisfied. Anything not\n * a primitive is coerced to the empty string — silent failures here\n * surface as missing parts (e.g. unknown rId) downstream.\n */\nfunction stringAttr(raw: unknown): string {\n if (raw === undefined || raw === null) return ''\n if (typeof raw === 'string') return raw\n if (typeof raw === 'number' || typeof raw === 'boolean') return String(raw)\n return ''\n}\n","/**\n * #414 P4 Mode B — infer a noy-db schema from an arbitrary workbook.\n *\n * Heuristic by nature (the spec's genuinely-hard part): types come from sampling\n * cell values, foreign keys from value-subset matching against another sheet's\n * id column. Use it to BOOTSTRAP a schema you then review — not as ground truth.\n * Standard Schema validators aren't JSON-serializable, so {@link zodSourceFor}\n * emits a Zod snippet you adopt in code rather than a live validator.\n */\nimport { readXlsx } from './read.js'\n\nexport type InferredType = 'string' | 'number' | 'boolean' | 'date'\n\nexport interface InferredField {\n readonly type: InferredType\n /** Target collection (sheet) name when this field looks like a foreign key. */\n readonly references?: string\n}\n\nexport interface InferredCollection {\n readonly idField: string\n readonly fields: Record<string, InferredField>\n}\n\nexport interface InferredSchema {\n readonly collections: Record<string, InferredCollection>\n}\n\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}([T ]\\d{2}:\\d{2})?/\n\nfunction inferType(vals: readonly unknown[]): InferredType {\n if (vals.length === 0) return 'string'\n if (vals.every((v) => typeof v === 'boolean')) return 'boolean'\n if (vals.every((v) => typeof v === 'number')) return 'number'\n if (vals.every((v) => typeof v === 'string' && ISO_DATE.test(v))) return 'date'\n return 'string'\n}\n\ninterface SheetData {\n name: string\n fields: string[]\n records: Record<string, unknown>[]\n}\n\n/**\n * Infer a {@link InferredSchema} from an `.xlsx` byte stream. Sheets whose name\n * starts with `_` (our smart-export meta sheets) are skipped; pass `skipSheets`\n * to exclude more (e.g. summary sheets).\n */\nexport async function inferSchema(\n bytes: Uint8Array,\n options: { skipSheets?: readonly string[] } = {},\n): Promise<InferredSchema> {\n const skip = new Set(options.skipSheets ?? [])\n const decoded = await readXlsx(bytes)\n\n const sheets: SheetData[] = []\n for (const sh of decoded.sheets) {\n if (sh.name.startsWith('_') || skip.has(sh.name)) continue\n const headerRow = sh.rows[0] ?? {}\n const colToField = new Map<string, string>()\n const fields: string[] = []\n for (const [letter, val] of Object.entries(headerRow)) {\n const name =\n typeof val === 'string' ? val.trim() : typeof val === 'number' || typeof val === 'boolean' ? String(val) : ''\n if (!name) continue\n colToField.set(letter, name)\n fields.push(name)\n }\n const records = sh.rows.slice(1).map((row) => {\n const rec: Record<string, unknown> = {}\n for (const [letter, val] of Object.entries(row)) {\n const f = colToField.get(letter)\n if (f !== undefined) rec[f] = val\n }\n return rec\n })\n sheets.push({ name: sh.name, fields, records })\n }\n\n const valuesOf = (s: SheetData, f: string): unknown[] =>\n s.records.map((r) => r[f]).filter((v) => v != null && v !== '')\n\n const idFieldOf = (s: SheetData): string => {\n if (s.fields.includes('id')) return 'id'\n // First field whose non-null values are all unique → a natural key.\n for (const f of s.fields) {\n const vals = valuesOf(s, f)\n if (vals.length > 0 && new Set(vals.map((v) => String(v))).size === vals.length) return f\n }\n return s.fields[0] ?? 'id'\n }\n\n const meta = sheets.map((s) => ({ s, idField: idFieldOf(s) }))\n const idValuesByName = new Map<string, Set<string>>()\n for (const { s, idField } of meta) {\n idValuesByName.set(s.name, new Set(valuesOf(s, idField).map((v) => String(v))))\n }\n\n const collections: Record<string, InferredCollection> = {}\n for (const { s, idField } of meta) {\n const fields: Record<string, InferredField> = {}\n for (const f of s.fields) {\n const vals = valuesOf(s, f)\n const type = inferType(vals)\n let references: string | undefined\n if (f !== idField && type === 'string' && vals.length > 0) {\n const distinct = new Set(vals.map((v) => String(v)))\n for (const { s: other } of meta) {\n if (other.name === s.name) continue\n const ids = idValuesByName.get(other.name)\n if (ids && ids.size > 0 && [...distinct].every((v) => ids.has(v))) {\n references = other.name\n break\n }\n }\n }\n fields[f] = references ? { type, references } : { type }\n }\n collections[s.name] = { idField, fields }\n }\n return { collections }\n}\n\n/** Emit a Zod schema snippet (guidance) from an {@link InferredSchema}. */\nexport function zodSourceFor(schema: InferredSchema): string {\n const zType = (t: InferredType): string =>\n t === 'number' ? 'z.number()' : t === 'boolean' ? 'z.boolean()' : t === 'date' ? 'z.string().datetime()' : 'z.string()'\n const blocks = [`import { z } from 'zod'`]\n for (const [name, c] of Object.entries(schema.collections)) {\n const lines = Object.entries(c.fields).map(\n ([f, d]) => ` ${f}: ${zType(d.type)},${d.references ? ` // → ${d.references}` : ''}`,\n )\n blocks.push(`export const ${name}Schema = z.object({\\n${lines.join('\\n')}\\n})`)\n }\n return blocks.join('\\n\\n')\n}\n"],"mappings":"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","names":["s","sheetName","coerced"]}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noy-db/as-xlsx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Excel spreadsheet plaintext export for noy-db — produces a real .xlsx (Office Open XML) from one or more collections. Multi-sheet, Unicode-safe via shared-strings table. Zero runtime deps beyond the workspace zip encoder. Gated by `vault.assertCanExport('plaintext', …)` with format `'xlsx'`.",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
6
|
"author": "vLannaAi <vicio@lanna.ai>",
|
|
7
|
-
"homepage": "https://github.com/
|
|
7
|
+
"homepage": "https://github.com/noy-db/as/tree/main/as-xlsx#readme",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
11
|
-
"directory": "
|
|
10
|
+
"url": "git+https://github.com/noy-db/as.git",
|
|
11
|
+
"directory": "as-xlsx"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/
|
|
14
|
+
"url": "https://github.com/noy-db/as/issues"
|
|
15
15
|
},
|
|
16
16
|
"type": "module",
|
|
17
17
|
"sideEffects": false,
|
|
@@ -26,25 +26,26 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
28
|
"README.md",
|
|
29
|
-
"LICENSE"
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"NOTICE"
|
|
30
31
|
],
|
|
31
32
|
"engines": {
|
|
32
33
|
"node": ">=22.0.0"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
|
-
"@noy-db/
|
|
36
|
-
"@noy-db/
|
|
36
|
+
"@noy-db/hub": "^0.7.0 || ^0.7.1-pre.0 || ^0.8.0-pre.0",
|
|
37
|
+
"@noy-db/as-zip": "^0.8.0-pre.0"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
40
|
"fast-xml-parser": "^5.0.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
43
|
+
"@noy-db/as-zip": "^0.8.0-pre.0",
|
|
44
|
+
"@noy-db/hub": "0.8.0",
|
|
45
|
+
"@noy-db/test-format-conformance": "0.8.0",
|
|
46
|
+
"@noy-db/to-memory": "0.8.0",
|
|
42
47
|
"@types/node": "^22.0.0",
|
|
43
|
-
"happy-dom": "^18.0.0"
|
|
44
|
-
"@noy-db/as-zip": "0.7.0",
|
|
45
|
-
"@noy-db/test-format-conformance": "0.7.0",
|
|
46
|
-
"@noy-db/hub": "0.7.0",
|
|
47
|
-
"@noy-db/to-memory": "0.7.0"
|
|
48
|
+
"happy-dom": "^18.0.0"
|
|
48
49
|
},
|
|
49
50
|
"keywords": [
|
|
50
51
|
"noy-db",
|