@noy-db/as-xlsx 0.2.0-pre.21 → 0.2.0-pre.23

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/dist/index.cjs CHANGED
@@ -35,12 +35,14 @@ __export(index_exports, {
35
35
  download: () => download,
36
36
  formula: () => formula,
37
37
  fromBytes: () => fromBytes,
38
+ inferSchema: () => inferSchema,
38
39
  readXlsx: () => readXlsx,
39
40
  styled: () => styled,
40
41
  toBytes: () => toBytes,
41
42
  toBytesFromCollection: () => toBytesFromCollection,
42
43
  write: () => write,
43
- writeXlsx: () => writeXlsx
44
+ writeXlsx: () => writeXlsx,
45
+ zodSourceFor: () => zodSourceFor
44
46
  });
45
47
  module.exports = __toCommonJS(index_exports);
46
48
 
@@ -465,6 +467,92 @@ function stringAttr(raw) {
465
467
  return "";
466
468
  }
467
469
 
470
+ // src/infer.ts
471
+ var ISO_DATE = /^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2})?/;
472
+ function inferType(vals) {
473
+ if (vals.length === 0) return "string";
474
+ if (vals.every((v) => typeof v === "boolean")) return "boolean";
475
+ if (vals.every((v) => typeof v === "number")) return "number";
476
+ if (vals.every((v) => typeof v === "string" && ISO_DATE.test(v))) return "date";
477
+ return "string";
478
+ }
479
+ async function inferSchema(bytes, options = {}) {
480
+ const skip = new Set(options.skipSheets ?? []);
481
+ const decoded = await readXlsx(bytes);
482
+ const sheets = [];
483
+ for (const sh of decoded.sheets) {
484
+ if (sh.name.startsWith("_") || skip.has(sh.name)) continue;
485
+ const headerRow = sh.rows[0] ?? {};
486
+ const colToField = /* @__PURE__ */ new Map();
487
+ const fields = [];
488
+ for (const [letter, val] of Object.entries(headerRow)) {
489
+ const name = typeof val === "string" ? val.trim() : typeof val === "number" || typeof val === "boolean" ? String(val) : "";
490
+ if (!name) continue;
491
+ colToField.set(letter, name);
492
+ fields.push(name);
493
+ }
494
+ const records = sh.rows.slice(1).map((row) => {
495
+ const rec = {};
496
+ for (const [letter, val] of Object.entries(row)) {
497
+ const f = colToField.get(letter);
498
+ if (f !== void 0) rec[f] = val;
499
+ }
500
+ return rec;
501
+ });
502
+ sheets.push({ name: sh.name, fields, records });
503
+ }
504
+ const valuesOf = (s, f) => s.records.map((r) => r[f]).filter((v) => v != null && v !== "");
505
+ const idFieldOf = (s) => {
506
+ if (s.fields.includes("id")) return "id";
507
+ for (const f of s.fields) {
508
+ const vals = valuesOf(s, f);
509
+ if (vals.length > 0 && new Set(vals.map((v) => String(v))).size === vals.length) return f;
510
+ }
511
+ return s.fields[0] ?? "id";
512
+ };
513
+ const meta = sheets.map((s) => ({ s, idField: idFieldOf(s) }));
514
+ const idValuesByName = /* @__PURE__ */ new Map();
515
+ for (const { s, idField } of meta) {
516
+ idValuesByName.set(s.name, new Set(valuesOf(s, idField).map((v) => String(v))));
517
+ }
518
+ const collections = {};
519
+ for (const { s, idField } of meta) {
520
+ const fields = {};
521
+ for (const f of s.fields) {
522
+ const vals = valuesOf(s, f);
523
+ const type = inferType(vals);
524
+ let references;
525
+ if (f !== idField && type === "string" && vals.length > 0) {
526
+ const distinct = new Set(vals.map((v) => String(v)));
527
+ for (const { s: other } of meta) {
528
+ if (other.name === s.name) continue;
529
+ const ids = idValuesByName.get(other.name);
530
+ if (ids && ids.size > 0 && [...distinct].every((v) => ids.has(v))) {
531
+ references = other.name;
532
+ break;
533
+ }
534
+ }
535
+ }
536
+ fields[f] = references ? { type, references } : { type };
537
+ }
538
+ collections[s.name] = { idField, fields };
539
+ }
540
+ return { collections };
541
+ }
542
+ function zodSourceFor(schema) {
543
+ const zType = (t) => t === "number" ? "z.number()" : t === "boolean" ? "z.boolean()" : t === "date" ? "z.string().datetime()" : "z.string()";
544
+ const blocks = [`import { z } from 'zod'`];
545
+ for (const [name, c] of Object.entries(schema.collections)) {
546
+ const lines = Object.entries(c.fields).map(
547
+ ([f, d]) => ` ${f}: ${zType(d.type)},${d.references ? ` // \u2192 ${d.references}` : ""}`
548
+ );
549
+ blocks.push(`export const ${name}Schema = z.object({
550
+ ${lines.join("\n")}
551
+ })`);
552
+ }
553
+ return blocks.join("\n\n");
554
+ }
555
+
468
556
  // src/index.ts
469
557
  var import_hub = require("@noy-db/hub");
470
558
  async function toBytesFromCollection(vault, collectionName) {
@@ -682,6 +770,7 @@ async function buildSmartSheets(vault, options) {
682
770
  return [m.opt.name, m.records.length, Object.entries(refs).map(([f, r]) => `${f}\u2192${r.target}`).join(", ")];
683
771
  })
684
772
  };
773
+ const dialect = options.dialect ?? "excel";
685
774
  const summarySheets = [];
686
775
  for (const spec of options.summaries ?? []) {
687
776
  const src = sheetMeta.get(spec.from);
@@ -689,6 +778,26 @@ async function buildSmartSheets(vault, options) {
689
778
  const gCol = src?.colIndex.get(spec.groupBy);
690
779
  if (!src || !srcMat || gCol === void 0) continue;
691
780
  const gLetter = colLetter(gCol);
781
+ if (dialect === "sheets") {
782
+ const selects = [];
783
+ const labels = [];
784
+ for (const a of spec.aggregates) {
785
+ if (a.op === "count") {
786
+ selects.push(`COUNT(${gLetter})`);
787
+ labels.push(`COUNT(${gLetter}) '${a.label}'`);
788
+ continue;
789
+ }
790
+ const vIdx = a.field ? src.colIndex.get(a.field) : void 0;
791
+ if (vIdx === void 0) continue;
792
+ const vL = colLetter(vIdx);
793
+ const fn = a.op === "sum" ? "SUM" : "AVG";
794
+ selects.push(`${fn}(${vL})`);
795
+ labels.push(`${fn}(${vL}) '${a.label}'`);
796
+ }
797
+ const q = `QUERY('${src.name}'!A:ZZ, "SELECT ${gLetter}, ${selects.join(", ")} GROUP BY ${gLetter} LABEL ${labels.join(", ")}", 1)`;
798
+ summarySheets.push({ name: spec.name, rows: [[formula(q)]] });
799
+ continue;
800
+ }
692
801
  const seen = /* @__PURE__ */ new Set();
693
802
  const groups = [];
694
803
  for (const r of srcMat.records) {
@@ -818,6 +927,13 @@ async function fromBytes(vault, bytes, options) {
818
927
  } catch {
819
928
  }
820
929
  }
930
+ const i18nBases = /* @__PURE__ */ new Set();
931
+ if (options.smart) {
932
+ for (const field of colToField.values()) {
933
+ const m = /^(.+)__(.+)$/.exec(field);
934
+ if (m && m[2] !== "label") i18nBases.add(m[1]);
935
+ }
936
+ }
821
937
  const records = [];
822
938
  for (let i = headerRowIdx + 1; i < allRows.length; i++) {
823
939
  const row = allRows[i];
@@ -826,6 +942,22 @@ async function fromBytes(vault, bytes, options) {
826
942
  for (const [col, value] of Object.entries(row)) {
827
943
  const field = colToField.get(col);
828
944
  if (field === void 0) continue;
945
+ if (options.smart) {
946
+ if (field.endsWith("__label")) continue;
947
+ const lm = /^(.+)__(.+)$/.exec(field);
948
+ if (lm && lm[2] !== "label" && i18nBases.has(lm[1])) {
949
+ const base = lm[1];
950
+ const coerced2 = coerceXlsxCell(value, types[base]);
951
+ if (coerced2 !== void 0 && coerced2 !== "") {
952
+ const map = record[base] ?? {};
953
+ map[lm[2]] = coerced2;
954
+ record[base] = map;
955
+ hasAny = true;
956
+ }
957
+ continue;
958
+ }
959
+ if (i18nBases.has(field)) continue;
960
+ }
829
961
  const coerced = coerceXlsxCell(value, types[field]);
830
962
  if (coerced !== void 0) {
831
963
  const invMap = invertMaps.get(field);
@@ -935,11 +1067,13 @@ function excelSerialToMs(serial) {
935
1067
  download,
936
1068
  formula,
937
1069
  fromBytes,
1070
+ inferSchema,
938
1071
  readXlsx,
939
1072
  styled,
940
1073
  toBytes,
941
1074
  toBytesFromCollection,
942
1075
  write,
943
- writeXlsx
1076
+ writeXlsx,
1077
+ zodSourceFor
944
1078
  });
945
1079
  //# sourceMappingURL=index.cjs.map