@particle-academy/holy-sheet 2.4.3 → 2.6.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/dist/index.cjs CHANGED
@@ -495,6 +495,54 @@ var DateConverter = {
495
495
  }
496
496
  };
497
497
 
498
+ // src/exceptions.ts
499
+ var SchemaException = class _SchemaException extends Error {
500
+ constructor(errors, message) {
501
+ super(message ?? summarize(errors));
502
+ this.name = "SchemaException";
503
+ this.errors = errors;
504
+ Object.setPrototypeOf(this, _SchemaException.prototype);
505
+ }
506
+ static fromErrors(errors) {
507
+ return new _SchemaException(errors);
508
+ }
509
+ getErrors() {
510
+ return this.errors;
511
+ }
512
+ };
513
+ function summarize(errors) {
514
+ if (errors.length === 1) {
515
+ const e = errors[0];
516
+ return `[holy-sheet] schema invalid at ${e.path}: expected ${e.expected}, got ${e.got}`;
517
+ }
518
+ const first2 = errors[0];
519
+ const rest = errors.length - 1;
520
+ return `[holy-sheet] schema invalid at ${first2.path}: expected ${first2.expected}, got ${first2.got} (+${rest} more)`;
521
+ }
522
+ var UnsupportedFormatException = class _UnsupportedFormatException extends Error {
523
+ constructor(message, path = null, mimetype = null) {
524
+ super(message);
525
+ this.name = "UnsupportedFormatException";
526
+ this.path = path;
527
+ this.mimetype = mimetype;
528
+ Object.setPrototypeOf(this, _UnsupportedFormatException.prototype);
529
+ }
530
+ static notAZip(path = null) {
531
+ return new _UnsupportedFormatException(
532
+ `[holy-sheet] cannot read ${path ?? "this file"}: it is not a zip archive, and xlsx and ods both are`,
533
+ path
534
+ );
535
+ }
536
+ static unknownPackage(path, mimetype) {
537
+ const what = mimetype !== null && mimetype !== "" ? `its mimetype is ${mimetype}` : "it has neither xl/workbook.xml nor an OpenDocument mimetype";
538
+ return new _UnsupportedFormatException(
539
+ `[holy-sheet] cannot read ${path ?? "this file"}: ${what}. Supported: xlsx, ods`,
540
+ path,
541
+ mimetype !== "" ? mimetype : null
542
+ );
543
+ }
544
+ };
545
+
498
546
  // src/schema/theme.ts
499
547
  var Theme = class {
500
548
  constructor(key) {
@@ -572,7 +620,7 @@ var Normalizer = class {
572
620
  const columnFormat = columnFormats.get(c) ?? null;
573
621
  const rowBand = theme.dataFormat(r);
574
622
  const merged = rowBand !== null ? columnFormat ? columnFormat.mergeWith(rowBand) : rowBand : columnFormat;
575
- cells.set(address, this.buildCell(address, value, merged));
623
+ cells.set(address, this.buildCell(address, value, merged, name));
576
624
  });
577
625
  });
578
626
  if (sheet.totals && isPlainObject(sheet.totals) && Object.keys(sheet.totals).length > 0 && rows.length > 0 && columns.length > 0) {
@@ -596,7 +644,7 @@ var Normalizer = class {
596
644
  }
597
645
  }
598
646
  if (sheet.cells != null) {
599
- for (const [address, cell] of this.normalizeCellMap(sheet.cells)) {
647
+ for (const [address, cell] of this.normalizeCellMap(sheet.cells, name)) {
600
648
  const beneath = cells.get(address);
601
649
  cells.set(
602
650
  address,
@@ -615,19 +663,23 @@ var Normalizer = class {
615
663
  name,
616
664
  cells,
617
665
  this.normalizeMerges(sheet.mergedRegions ?? []),
618
- this.normalizeColumnWidths(sheet.columnWidths ?? {}),
666
+ this.normalizeWidths(sheet),
619
667
  toInt(sheet.frozenRows ?? 0),
620
668
  toInt(sheet.frozenCols ?? 0)
621
669
  );
622
670
  }
623
- normalizeCellMap(map) {
671
+ normalizeCellMap(map, sheetName2) {
624
672
  const cells = /* @__PURE__ */ new Map();
625
673
  for (const [address, cellData] of Object.entries(map)) {
626
- if (!isPlainObject(cellData)) {
674
+ if (!isCellSpec(cellData)) {
627
675
  const [bare, formula] = promoteFormula(cellData);
628
676
  cells.set(
629
677
  address,
630
- new Cell(address, formula !== null ? null : this.coerceValue(bare, null), formula)
678
+ new Cell(
679
+ address,
680
+ formula !== null ? null : this.coerceValue(bare, null, address, sheetName2),
681
+ formula
682
+ )
631
683
  );
632
684
  continue;
633
685
  }
@@ -642,7 +694,7 @@ var Normalizer = class {
642
694
  address,
643
695
  new Cell(
644
696
  address,
645
- this.coerceValue(rawValue, format),
697
+ this.coerceValue(rawValue, format, address, sheetName2),
646
698
  cellData?.formula ?? null,
647
699
  format,
648
700
  comment,
@@ -661,6 +713,36 @@ var Normalizer = class {
661
713
  }
662
714
  return out;
663
715
  }
716
+ /**
717
+ * The two ways a width can be stated, folded into one map.
718
+ *
719
+ * `columns[].width` widens the column at that POSITION; the sheet-level
720
+ * `columnWidths` map keys the same columns by 0-based index. Both have been
721
+ * described in the shared `skills/holy-sheet.schema.json` ("Same as
722
+ * columnWidths but per-column") for as long as the field has existed, and only
723
+ * the map was ever read — so a width written the way the schema documents it
724
+ * emitted no `<cols>` element at all, with no error and no repair note
725
+ * (holy-sheet#8, fixed in PHP 2.4.0; this is the twin).
726
+ *
727
+ * The MAP IS APPLIED LAST and wins, matching PHP. It is the mechanism that
728
+ * already worked, so anyone who reached for it to work around this bug must not
729
+ * now find a leftover `width` quietly overriding it.
730
+ */
731
+ normalizeWidths(sheet) {
732
+ const out = /* @__PURE__ */ new Map();
733
+ for (const [colIdx, columnDef] of (sheet.columns ?? []).entries()) {
734
+ if (columnDef === null || typeof columnDef !== "object") continue;
735
+ if (columnDef.width === void 0) continue;
736
+ const index = ColumnWidths.index(colIdx);
737
+ const width = ColumnWidths.width(columnDef.width);
738
+ if (index === null || width === null) continue;
739
+ out.set(index, width);
740
+ }
741
+ for (const [index, width] of this.normalizeColumnWidths(sheet.columnWidths ?? {})) {
742
+ out.set(index, width);
743
+ }
744
+ return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
745
+ }
664
746
  normalizeColumnWidths(widths) {
665
747
  const out = /* @__PURE__ */ new Map();
666
748
  for (const [key, px] of Object.entries(widths)) {
@@ -693,8 +775,8 @@ var Normalizer = class {
693
775
  return null;
694
776
  }
695
777
  }
696
- buildCell(address, value, columnFormat) {
697
- if (isPlainObject(value)) {
778
+ buildCell(address, value, columnFormat, sheetName2) {
779
+ if (isCellSpec(value)) {
698
780
  const cellFormat = isPlainObject(value.format) ? CellFormat.fromInput(value.format) : null;
699
781
  const merged = columnFormat ? columnFormat.mergeWith(cellFormat) : cellFormat;
700
782
  const comment = isPlainObject(value.comment) ? new CellComment(
@@ -705,7 +787,7 @@ var Normalizer = class {
705
787
  const rawValue = value.value ?? null;
706
788
  return new Cell(
707
789
  address,
708
- this.coerceValue(rawValue, merged),
790
+ this.coerceValue(rawValue, merged, address, sheetName2),
709
791
  value.formula ?? null,
710
792
  merged,
711
793
  comment,
@@ -715,12 +797,12 @@ var Normalizer = class {
715
797
  const [bare, formula] = promoteFormula(value);
716
798
  return new Cell(
717
799
  address,
718
- formula !== null ? null : this.coerceValue(bare, columnFormat),
800
+ formula !== null ? null : this.coerceValue(bare, columnFormat, address, sheetName2),
719
801
  formula,
720
802
  columnFormat
721
803
  );
722
804
  }
723
- coerceValue(value, format) {
805
+ coerceValue(value, format, address = "?", sheetName2) {
724
806
  if (value === null || value === void 0) return null;
725
807
  const df = format?.displayFormat;
726
808
  if (df === "date" || df === "datetime") {
@@ -734,12 +816,36 @@ var Normalizer = class {
734
816
  if (value instanceof Date) {
735
817
  return DateConverter.toSerial(value, true);
736
818
  }
737
- if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
819
+ if (typeof value === "bigint") {
738
820
  return String(value);
739
821
  }
822
+ if (typeof value === "object" && !Array.isArray(value) && hasOwnToString(value)) {
823
+ return String(value);
824
+ }
825
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
826
+ const where = sheetName2 === void 0 ? address : `${sheetName2}!${address}`;
827
+ const got = typeOf(value);
828
+ throw SchemaException.fromErrors([
829
+ {
830
+ path: where,
831
+ expected: "a string, number, bool, null, Date, or an object with its own toString",
832
+ got,
833
+ value,
834
+ hint: Array.isArray(value) ? 'This cell holds an array. The usual cause is a `rows` entry nested one level too deep: `rows: [[["x"]]]` makes cell A1 an array, where `rows: [["x"]]` makes it the string "x". For a cell with formatting use the object form: {value: "x", format: {...}}.' : "Give the cell a scalar or a Date, or define toString() on the object so the conversion is yours rather than implicit."
835
+ }
836
+ ]);
837
+ }
740
838
  return value;
741
839
  }
742
840
  };
841
+ function isCellSpec(value) {
842
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
843
+ return Object.getPrototypeOf(value) === Object.prototype;
844
+ }
845
+ function hasOwnToString(value) {
846
+ const fn = value.toString;
847
+ return typeof fn === "function" && fn !== Object.prototype.toString;
848
+ }
743
849
  function promoteFormula(value) {
744
850
  if (typeof value === "string" && value.length > 1 && value[0] === "=") {
745
851
  return [null, value.slice(1)];
@@ -1271,54 +1377,6 @@ function phpRound(x, n) {
1271
1377
  return Math.sign(x) * Math.round(Math.abs(x) * factor) / factor;
1272
1378
  }
1273
1379
 
1274
- // src/exceptions.ts
1275
- var SchemaException = class _SchemaException extends Error {
1276
- constructor(errors, message) {
1277
- super(message ?? summarize(errors));
1278
- this.name = "SchemaException";
1279
- this.errors = errors;
1280
- Object.setPrototypeOf(this, _SchemaException.prototype);
1281
- }
1282
- static fromErrors(errors) {
1283
- return new _SchemaException(errors);
1284
- }
1285
- getErrors() {
1286
- return this.errors;
1287
- }
1288
- };
1289
- function summarize(errors) {
1290
- if (errors.length === 1) {
1291
- const e = errors[0];
1292
- return `[holy-sheet] schema invalid at ${e.path}: expected ${e.expected}, got ${e.got}`;
1293
- }
1294
- const first2 = errors[0];
1295
- const rest = errors.length - 1;
1296
- return `[holy-sheet] schema invalid at ${first2.path}: expected ${first2.expected}, got ${first2.got} (+${rest} more)`;
1297
- }
1298
- var UnsupportedFormatException = class _UnsupportedFormatException extends Error {
1299
- constructor(message, path = null, mimetype = null) {
1300
- super(message);
1301
- this.name = "UnsupportedFormatException";
1302
- this.path = path;
1303
- this.mimetype = mimetype;
1304
- Object.setPrototypeOf(this, _UnsupportedFormatException.prototype);
1305
- }
1306
- static notAZip(path = null) {
1307
- return new _UnsupportedFormatException(
1308
- `[holy-sheet] cannot read ${path ?? "this file"}: it is not a zip archive, and xlsx and ods both are`,
1309
- path
1310
- );
1311
- }
1312
- static unknownPackage(path, mimetype) {
1313
- const what = mimetype !== null && mimetype !== "" ? `its mimetype is ${mimetype}` : "it has neither xl/workbook.xml nor an OpenDocument mimetype";
1314
- return new _UnsupportedFormatException(
1315
- `[holy-sheet] cannot read ${path ?? "this file"}: ${what}. Supported: xlsx, ods`,
1316
- path,
1317
- mimetype !== "" ? mimetype : null
1318
- );
1319
- }
1320
- };
1321
-
1322
1380
  // src/schema/repairer.ts
1323
1381
  var VALID_THEMES = ["default", "minimal", "plain", "business"];
1324
1382
  var ISO_DATE2 = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/;
@@ -1565,7 +1623,21 @@ var Validator = class {
1565
1623
  "Each row is an array of cell values, in column order."
1566
1624
  )
1567
1625
  );
1626
+ return;
1568
1627
  }
1628
+ row.forEach((cell, k) => {
1629
+ if (Array.isArray(cell)) {
1630
+ errors.push(
1631
+ error(
1632
+ `${path}.rows[${j}][${k}]`,
1633
+ "a cell value or a cell object",
1634
+ "array",
1635
+ cell,
1636
+ 'This cell is an array, which usually means the row is nested one level too deep: `rows: [[["x"]]]` makes cell A1 an array, where `rows: [["x"]]` makes it "x". For a cell with formatting use the object form: {value: "x", format: {...}}.'
1637
+ )
1638
+ );
1639
+ }
1640
+ });
1569
1641
  });
1570
1642
  }
1571
1643
  }
@@ -1662,6 +1734,17 @@ var Validator = class {
1662
1734
  )
1663
1735
  );
1664
1736
  }
1737
+ if (col.width !== void 0 && ColumnWidths.width(col.width) === null) {
1738
+ errors.push(
1739
+ error(
1740
+ `${path}.width`,
1741
+ "a non-negative number of pixels",
1742
+ typeOf(col.width),
1743
+ col.width,
1744
+ "A column width is a number of pixels, like 120. The sheet-level columnWidths map takes precedence over it."
1745
+ )
1746
+ );
1747
+ }
1665
1748
  return errors;
1666
1749
  }
1667
1750
  };
@@ -5138,7 +5221,7 @@ var holy_sheet_schema_default = {
5138
5221
  };
5139
5222
 
5140
5223
  // src/agent.ts
5141
- var VERSION = "2.4.3";
5224
+ var VERSION = "2.6.0";
5142
5225
  function toU8(input) {
5143
5226
  return input instanceof Uint8Array ? input : new Uint8Array(input);
5144
5227
  }