@particle-academy/holy-sheet 2.4.0 → 2.4.2

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
@@ -177,7 +177,7 @@ var CsvBuilder = {
177
177
  return ArrayBuilder.build(dataRows, headers, sheetName2, options);
178
178
  }
179
179
  };
180
- function parseRows(csv, delimiter, quote) {
180
+ function parseRows(csv, delimiter, quote2) {
181
181
  const rows = [];
182
182
  let field = "";
183
183
  let row = [];
@@ -197,9 +197,9 @@ function parseRows(csv, delimiter, quote) {
197
197
  for (let i = 0; i < csv.length; i++) {
198
198
  const ch = csv[i];
199
199
  if (inQuotes) {
200
- if (ch === quote) {
201
- if (csv[i + 1] === quote) {
202
- field += quote;
200
+ if (ch === quote2) {
201
+ if (csv[i + 1] === quote2) {
202
+ field += quote2;
203
203
  i++;
204
204
  } else {
205
205
  inQuotes = false;
@@ -209,7 +209,7 @@ function parseRows(csv, delimiter, quote) {
209
209
  }
210
210
  continue;
211
211
  }
212
- if (ch === quote) {
212
+ if (ch === quote2) {
213
213
  inQuotes = true;
214
214
  started = true;
215
215
  continue;
@@ -2682,13 +2682,13 @@ function unescapeXml(s) {
2682
2682
  });
2683
2683
  }
2684
2684
  function findTagEnd(src, from) {
2685
- let quote = "";
2685
+ let quote2 = "";
2686
2686
  for (let i = from; i < src.length; i++) {
2687
2687
  const ch = src[i];
2688
- if (quote) {
2689
- if (ch === quote) quote = "";
2688
+ if (quote2) {
2689
+ if (ch === quote2) quote2 = "";
2690
2690
  } else if (ch === '"' || ch === "'") {
2691
- quote = ch;
2691
+ quote2 = ch;
2692
2692
  } else if (ch === ">") {
2693
2693
  return i;
2694
2694
  }
@@ -3836,16 +3836,132 @@ function phpString(value) {
3836
3836
  if (isArr(value)) return "Array";
3837
3837
  return String(value);
3838
3838
  }
3839
+ function phpTrim2(value) {
3840
+ return value.replace(/^[ \t\n\r\x00\x0B]+|[ \t\n\r\x00\x0B]+$/g, "");
3841
+ }
3842
+ function phpInteger(value) {
3843
+ if (typeof value === "number") {
3844
+ return Number.isInteger(value) && Math.abs(value) <= 2 ** 63 ? value || 0 : null;
3845
+ }
3846
+ return typeof value === "string" && /^[0-9]+$/.test(value) ? phpInt2(value) : null;
3847
+ }
3848
+ function isIndexKey(key) {
3849
+ return /^[0-9]+$/.test(key) || /^-[1-9][0-9]*$/.test(key) && BigInt(key) >= -(2n ** 63n);
3850
+ }
3839
3851
  function asciiUpper(value) {
3840
3852
  return value.replace(/[a-z]+/g, (s) => s.toUpperCase());
3841
3853
  }
3842
3854
  function parseAddress(address) {
3843
- const m = /^([A-Z]+)(\d+)$/.exec(asciiUpper(address.replace(/^[ \t\n\r\v\0]+|[ \t\n\r\v\0]+$/g, "")));
3855
+ const m = /^([A-Z]+)(\d+)$/.exec(asciiUpper(phpTrim2(address)));
3844
3856
  if (!m) return null;
3845
3857
  return [CellAddress.index(m[1]), parseInt(m[2], 10)];
3846
3858
  }
3847
3859
  var letter = CellAddress.letter;
3848
3860
 
3861
+ // src/ops/sheet-op-schema.ts
3862
+ var SheetOpSchema = {
3863
+ /** Every op type, in the order the variants are listed. */
3864
+ TYPES: [
3865
+ "set_cell",
3866
+ "set_range",
3867
+ "set_workbook",
3868
+ "clear_cell",
3869
+ "insert_rows",
3870
+ "delete_rows",
3871
+ "insert_columns",
3872
+ "delete_columns",
3873
+ "add_sheet",
3874
+ "remove_sheet",
3875
+ "rename_sheet",
3876
+ "move_sheet",
3877
+ "replace_sheet",
3878
+ "set_merged_regions",
3879
+ "set_column_widths",
3880
+ "set_frozen",
3881
+ "set_meta"
3882
+ ],
3883
+ /** A fresh object on every call, so a caller editing it changes nothing here. */
3884
+ jsonSchema() {
3885
+ const address = () => ({ type: "string", pattern: "^[A-Za-z]+[0-9]+$" });
3886
+ const sheet = () => ({ type: "string", minLength: 1 });
3887
+ const count = () => ({ type: "integer", minimum: 1 });
3888
+ const position2 = () => ({ type: "integer", minimum: 1 });
3889
+ const object = () => ({ type: "object" });
3890
+ const nullableObject = () => ({ type: ["object", "null"] });
3891
+ const value = () => ({ type: ["string", "number", "boolean", "null"] });
3892
+ const variants = [
3893
+ variant(
3894
+ "set_cell",
3895
+ { sheet: sheet(), address: address(), value: value(), formula: { type: "string" }, computedValue: value(), format: nullableObject(), comment: nullableObject() },
3896
+ ["sheet", "address"],
3897
+ "Write one cell. Omitting formula or computedValue clears it; omitting format or comment keeps it; null clears it."
3898
+ ),
3899
+ variant(
3900
+ "set_range",
3901
+ { sheet: sheet(), start: address(), end: address(), values: { type: "array", items: { type: "array", items: value() } } },
3902
+ ["sheet", "start", "values"],
3903
+ "Write a block of values row-major from start, each as a set_cell with no formula."
3904
+ ),
3905
+ variant("set_workbook", { data: object() }, ["data"], "Replace the whole workbook schema."),
3906
+ variant("clear_cell", { sheet: sheet(), address: address() }, ["sheet", "address"], "Remove one cell."),
3907
+ variant("insert_rows", { sheet: sheet(), at: position2(), count: count() }, ["sheet", "at", "count"], "Insert rows before 1-based row `at`; cells, merges below move down."),
3908
+ variant("delete_rows", { sheet: sheet(), at: position2(), count: count() }, ["sheet", "at", "count"], "Delete rows starting at 1-based row `at`; cells, merges below move up."),
3909
+ variant("insert_columns", { sheet: sheet(), at: position2(), count: count() }, ["sheet", "at", "count"], "Insert columns before 1-based column `at` (A = 1); cells, merges and widths move right."),
3910
+ variant("delete_columns", { sheet: sheet(), at: position2(), count: count() }, ["sheet", "at", "count"], "Delete columns starting at 1-based column `at`; cells, merges and widths move left."),
3911
+ variant("add_sheet", { index: { type: "integer", minimum: 0 }, sheet: object() }, ["index", "sheet"], "Insert a sheet at a 0-based position."),
3912
+ variant("remove_sheet", { sheet: sheet() }, ["sheet"], "Remove a sheet by name."),
3913
+ variant("rename_sheet", { sheet: sheet(), name: sheet() }, ["sheet", "name"], "Rename a sheet."),
3914
+ variant("move_sheet", { sheet: sheet(), toIndex: { type: "integer", minimum: 0 } }, ["sheet", "toIndex"], "Move a sheet to a 0-based position."),
3915
+ variant("replace_sheet", { sheet: sheet(), data: object() }, ["sheet", "data"], "Replace one sheet whole."),
3916
+ variant(
3917
+ "set_merged_regions",
3918
+ { sheet: sheet(), mergedRegions: { type: "array", items: { type: "object", required: ["start", "end"], properties: { start: address(), end: address() } } } },
3919
+ ["sheet", "mergedRegions"],
3920
+ "Set every merged region of a sheet."
3921
+ ),
3922
+ // A LIST too, as PHP 2.3.2 declares. PHP encodes a map whose keys run
3923
+ // 0..n-1 as a JSON list, so widths for columns A, B and C arrive as
3924
+ // `[120, 80, 140]` and no widths as `[]`; this port passes such a list
3925
+ // through a diff unchanged. A list's position is the column index.
3926
+ variant(
3927
+ "set_column_widths",
3928
+ {
3929
+ sheet: sheet(),
3930
+ columnWidths: {
3931
+ type: ["object", "array"],
3932
+ items: { type: "number", minimum: 0 },
3933
+ additionalProperties: { type: "number", minimum: 0 }
3934
+ }
3935
+ },
3936
+ ["sheet", "columnWidths"],
3937
+ "Set every column width of a sheet (0-based column index to pixels; a list is indexed by position); empty removes them."
3938
+ ),
3939
+ variant(
3940
+ "set_frozen",
3941
+ { sheet: sheet(), rows: { type: "integer", minimum: 0 }, cols: { type: "integer", minimum: 0 } },
3942
+ ["sheet", "rows", "cols"],
3943
+ "Set frozen rows and columns."
3944
+ ),
3945
+ variant("set_meta", { meta: nullableObject() }, ["meta"], "Replace the workbook meta, or remove it with null.")
3946
+ ];
3947
+ return {
3948
+ $schema: "http://json-schema.org/draft-07/schema#",
3949
+ title: "Holy Sheet op",
3950
+ description: "One op from Agent::diff, applied by Agent::reduce.",
3951
+ oneOf: variants
3952
+ };
3953
+ }
3954
+ };
3955
+ function variant(type, properties, required, description) {
3956
+ return {
3957
+ type: "object",
3958
+ description,
3959
+ required: ["type", ...required],
3960
+ additionalProperties: false,
3961
+ properties: { type: { const: type }, ...properties }
3962
+ };
3963
+ }
3964
+
3849
3965
  // src/ops/sheet-reducer.ts
3850
3966
  var SheetReducer = {
3851
3967
  /** Sheet keys the granular ops address. A sheet with any other key is authored form. */
@@ -3858,6 +3974,9 @@ var SheetReducer = {
3858
3974
  },
3859
3975
  apply(schema, op) {
3860
3976
  const type = get(op, "type");
3977
+ if (typeof type !== "string" || !SheetOpSchema.TYPES.includes(type)) {
3978
+ return schema;
3979
+ }
3861
3980
  if (type === "set_workbook") {
3862
3981
  return isArr(get(op, "data")) ? op.data : schema;
3863
3982
  }
@@ -3875,14 +3994,22 @@ var SheetReducer = {
3875
3994
  if (!isArr(get(op, "sheet"))) {
3876
3995
  return schema;
3877
3996
  }
3878
- const index = Math.max(0, Math.min(sheets.length, phpInt2(get(op, "index") ?? sheets.length)));
3879
- sheets.splice(index, 0, op.sheet);
3997
+ const index = has(op, "index") ? phpInteger(op.index) : sheets.length;
3998
+ if (index === null) {
3999
+ return schema;
4000
+ }
4001
+ sheets.splice(Math.max(0, Math.min(sheets.length, index)), 0, op.sheet);
3880
4002
  return { ...schema, sheets };
3881
4003
  }
3882
4004
  const at2 = find(sheets, phpString(get(op, "sheet") ?? ""));
3883
4005
  if (at2 === null) {
3884
4006
  return schema;
3885
4007
  }
4008
+ for (const field of POSITION_FIELDS) {
4009
+ if (has(op, field) && phpInteger(op[field]) === null) {
4010
+ return schema;
4011
+ }
4012
+ }
3886
4013
  switch (type) {
3887
4014
  case "remove_sheet":
3888
4015
  sheets.splice(at2, 1);
@@ -3892,7 +4019,7 @@ var SheetReducer = {
3892
4019
  break;
3893
4020
  case "move_sheet": {
3894
4021
  const [moved] = sheets.splice(at2, 1);
3895
- const to = Math.max(0, Math.min(sheets.length, phpInt2(get(op, "toIndex") ?? at2)));
4022
+ const to = Math.max(0, Math.min(sheets.length, phpInteger(get(op, "toIndex") ?? at2) ?? at2));
3896
4023
  sheets.splice(to, 0, moved);
3897
4024
  break;
3898
4025
  }
@@ -3908,8 +4035,8 @@ var SheetReducer = {
3908
4035
  sheets[at2] = setOrUnset(sheets[at2], "columnWidths", get(op, "columnWidths") ?? [], EMPTY);
3909
4036
  break;
3910
4037
  case "set_frozen":
3911
- sheets[at2] = setOrUnset(sheets[at2], "frozenRows", phpInt2(get(op, "rows") ?? 0), 0);
3912
- sheets[at2] = setOrUnset(sheets[at2], "frozenCols", phpInt2(get(op, "cols") ?? 0), 0);
4038
+ sheets[at2] = setOrUnset(sheets[at2], "frozenRows", phpInteger(get(op, "rows") ?? 0) ?? 0, 0);
4039
+ sheets[at2] = setOrUnset(sheets[at2], "frozenCols", phpInteger(get(op, "cols") ?? 0) ?? 0, 0);
3913
4040
  break;
3914
4041
  case "set_cell":
3915
4042
  sheets[at2] = setCell(sheets[at2], op);
@@ -3918,7 +4045,7 @@ var SheetReducer = {
3918
4045
  sheets[at2] = setRange(sheets[at2], op);
3919
4046
  break;
3920
4047
  case "clear_cell": {
3921
- const address = asciiUpper(phpString(get(op, "address") ?? ""));
4048
+ const address = asciiUpper(phpTrim2(phpString(get(op, "address") ?? "")));
3922
4049
  const cells = sheets[at2].cells;
3923
4050
  if (isArr(cells) && !Array.isArray(cells) && has(cells, address)) {
3924
4051
  const copy = { ...cells };
@@ -3928,16 +4055,16 @@ var SheetReducer = {
3928
4055
  break;
3929
4056
  }
3930
4057
  case "insert_rows":
3931
- sheets[at2] = shiftRows(sheets[at2], phpInt2(get(op, "at") ?? 0), Math.max(0, phpInt2(get(op, "count") ?? 0)));
4058
+ sheets[at2] = shiftRows(sheets[at2], position(op, "at"), Math.max(0, position(op, "count")));
3932
4059
  break;
3933
4060
  case "delete_rows":
3934
- sheets[at2] = shiftRows(sheets[at2], phpInt2(get(op, "at") ?? 0), -Math.max(0, phpInt2(get(op, "count") ?? 0)));
4061
+ sheets[at2] = shiftRows(sheets[at2], position(op, "at"), -Math.max(0, position(op, "count")));
3935
4062
  break;
3936
4063
  case "insert_columns":
3937
- sheets[at2] = shiftColumns(sheets[at2], phpInt2(get(op, "at") ?? 0), Math.max(0, phpInt2(get(op, "count") ?? 0)));
4064
+ sheets[at2] = shiftColumns(sheets[at2], position(op, "at"), Math.max(0, position(op, "count")));
3938
4065
  break;
3939
4066
  case "delete_columns":
3940
- sheets[at2] = shiftColumns(sheets[at2], phpInt2(get(op, "at") ?? 0), -Math.max(0, phpInt2(get(op, "count") ?? 0)));
4067
+ sheets[at2] = shiftColumns(sheets[at2], position(op, "at"), -Math.max(0, position(op, "count")));
3941
4068
  break;
3942
4069
  default:
3943
4070
  return schema;
@@ -3945,6 +4072,10 @@ var SheetReducer = {
3945
4072
  return { ...schema, sheets };
3946
4073
  }
3947
4074
  };
4075
+ var POSITION_FIELDS = ["index", "toIndex", "rows", "cols", "at", "count"];
4076
+ function position(op, key) {
4077
+ return phpInteger(get(op, key) ?? 0) ?? 0;
4078
+ }
3948
4079
  var EMPTY = /* @__PURE__ */ Symbol("empty");
3949
4080
  function find(sheets, name) {
3950
4081
  for (let i = 0; i < sheets.length; i++) {
@@ -3964,7 +4095,7 @@ function setOrUnset(sheet, key, value, empty) {
3964
4095
  return out;
3965
4096
  }
3966
4097
  function setCell(sheet, op) {
3967
- const address = asciiUpper(phpString(get(op, "address") ?? ""));
4098
+ const address = asciiUpper(phpTrim2(phpString(get(op, "address") ?? "")));
3968
4099
  if (parseAddress(address) === null) {
3969
4100
  return sheet;
3970
4101
  }
@@ -4044,6 +4175,9 @@ function shiftColumns(sheet, at2, delta) {
4044
4175
  if (isArr(get(sheet, "columnWidths"))) {
4045
4176
  const widths = /* @__PURE__ */ new Map();
4046
4177
  for (const [index, width] of entriesOf(sheet.columnWidths)) {
4178
+ if (!isIndexKey(index)) {
4179
+ continue;
4180
+ }
4047
4181
  const i = phpInt2(index);
4048
4182
  const number = i + 1;
4049
4183
  if (number < at2) {
@@ -4170,6 +4304,21 @@ var SheetDiff = {
4170
4304
  * example `columnWidths` decoded from `{"1":140,"0":120}`) as a map, not a
4171
4305
  * list, and so as different from the same widths in order. A JS object has
4172
4306
  * no such order to compare.
4307
+ *
4308
+ * Throws a `TypeError` on a value JSON cannot hold, as PHP 2.3.2 throws
4309
+ * `JsonException`, rather than calling two such values the same:
4310
+ *
4311
+ * - a non-finite number (PHP: NAN, INF);
4312
+ * - a lone UTF-16 surrogate in a string or a key, which is this runtime's
4313
+ * invalid UTF-8 (a JS string cannot hold invalid UTF-8, and PHP's own
4314
+ * `json_decode` refuses a lone surrogate);
4315
+ * - a function or a symbol, which `JSON.stringify` would silently drop
4316
+ * (a bigint already throws there);
4317
+ * - nesting deeper than PHP's `json_encode` depth of 4096, where every object
4318
+ * and array is a level, an empty one included. The walk is iterative, so
4319
+ * this is PHP's limit and not the call stack's.
4320
+ *
4321
+ * `undefined` stays what JSON makes it: an absent key, and `null` in a list.
4173
4322
  */
4174
4323
  same(a, b) {
4175
4324
  return canon(a) === canon(b);
@@ -4421,19 +4570,19 @@ function lines(sheet, axis) {
4421
4570
  }
4422
4571
  const [col, row] = parsed;
4423
4572
  const line = axis === "row" ? row : col + 1;
4424
- const position = axis === "row" ? col : row;
4573
+ const position2 = axis === "row" ? col : row;
4425
4574
  let group = grouped.get(line);
4426
4575
  if (group === void 0) {
4427
4576
  group = /* @__PURE__ */ new Map();
4428
4577
  grouped.set(line, group);
4429
4578
  }
4430
- group.set(position, cell);
4579
+ group.set(position2, cell);
4431
4580
  max = Math.max(max, line);
4432
4581
  }
4433
4582
  const out = [];
4434
4583
  for (let i = 1; i <= max; i++) {
4435
4584
  const positions = [...(grouped.get(i) ?? /* @__PURE__ */ new Map()).entries()].sort((x, y) => x[0] - y[0]);
4436
- out.push(canonEntries(positions.map(([position, cell]) => [String(position), cell])));
4585
+ out.push(canonEntries(positions.map(([position2, cell]) => [String(position2), cell])));
4437
4586
  }
4438
4587
  return out;
4439
4588
  }
@@ -4460,108 +4609,72 @@ function rowMajor(addresses) {
4460
4609
  return parsed.map(([, , address]) => address);
4461
4610
  }
4462
4611
  function canon(value) {
4463
- if (Array.isArray(value)) {
4464
- return "[" + value.map(canon).join(",") + "]";
4612
+ return isArr(value) ? canonEntries(entriesFor(value)) : scalar(value);
4613
+ }
4614
+ var JSON_DEPTH = 4096;
4615
+ function canonEntries(root) {
4616
+ const stack = [];
4617
+ const open = (key, entries) => {
4618
+ if (stack.length === JSON_DEPTH) {
4619
+ throw unencodable("Maximum stack depth exceeded");
4620
+ }
4621
+ const list = entries.every(([k], i) => k === String(i));
4622
+ if (!list) {
4623
+ entries = entries.slice().sort((x, y) => x[0] < y[0] ? -1 : x[0] > y[0] ? 1 : 0);
4624
+ }
4625
+ stack.push({ key, entries, list, next: 0, parts: [] });
4626
+ };
4627
+ open("", root);
4628
+ for (; ; ) {
4629
+ const top = stack[stack.length - 1];
4630
+ if (top.next < top.entries.length) {
4631
+ const [key, item] = top.entries[top.next++];
4632
+ if (isArr(item)) {
4633
+ open(key, entriesFor(item));
4634
+ } else {
4635
+ top.parts.push(member(top, key, scalar(item)));
4636
+ }
4637
+ continue;
4638
+ }
4639
+ stack.pop();
4640
+ const text = top.list ? "[" + top.parts.join(",") + "]" : "{" + top.parts.join(",") + "}";
4641
+ const parent = stack[stack.length - 1];
4642
+ if (parent === void 0) {
4643
+ return text;
4644
+ }
4645
+ parent.parts.push(member(parent, top.key, text));
4465
4646
  }
4466
- if (isArr(value)) {
4467
- return canonEntries(entriesOf(value));
4647
+ }
4648
+ function entriesFor(value) {
4649
+ return Array.isArray(value) ? Array.from(value, (item, i) => [String(i), item]) : entriesOf(value);
4650
+ }
4651
+ function member(frame, key, text) {
4652
+ return frame.list ? text : quote(key) + ":" + text;
4653
+ }
4654
+ function scalar(value) {
4655
+ switch (typeof value) {
4656
+ case "number":
4657
+ if (!Number.isFinite(value)) {
4658
+ throw unencodable("Inf and NaN cannot be JSON encoded");
4659
+ }
4660
+ break;
4661
+ case "string":
4662
+ return quote(value);
4663
+ case "function":
4664
+ case "symbol":
4665
+ throw unencodable(`a ${typeof value} cannot be JSON encoded`);
4468
4666
  }
4469
4667
  return JSON.stringify(value) ?? "null";
4470
4668
  }
4471
- function canonEntries(entries) {
4472
- if (entries.every(([key], i) => key === String(i))) {
4473
- return "[" + entries.map(([, item]) => canon(item)).join(",") + "]";
4669
+ var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
4670
+ function quote(text) {
4671
+ if (LONE_SURROGATE.test(text)) {
4672
+ throw unencodable("Single unpaired UTF-16 surrogate");
4474
4673
  }
4475
- const sorted = entries.slice().sort((x, y) => x[0] < y[0] ? -1 : x[0] > y[0] ? 1 : 0);
4476
- return "{" + sorted.map(([key, item]) => JSON.stringify(key) + ":" + canon(item)).join(",") + "}";
4674
+ return JSON.stringify(text);
4477
4675
  }
4478
-
4479
- // src/ops/sheet-op-schema.ts
4480
- var SheetOpSchema = {
4481
- /** Every op type, in the order the variants are listed. */
4482
- TYPES: [
4483
- "set_cell",
4484
- "set_range",
4485
- "set_workbook",
4486
- "clear_cell",
4487
- "insert_rows",
4488
- "delete_rows",
4489
- "insert_columns",
4490
- "delete_columns",
4491
- "add_sheet",
4492
- "remove_sheet",
4493
- "rename_sheet",
4494
- "move_sheet",
4495
- "replace_sheet",
4496
- "set_merged_regions",
4497
- "set_column_widths",
4498
- "set_frozen",
4499
- "set_meta"
4500
- ],
4501
- /** A fresh object on every call, so a caller editing it changes nothing here. */
4502
- jsonSchema() {
4503
- const address = () => ({ type: "string", pattern: "^[A-Za-z]+[0-9]+$" });
4504
- const sheet = () => ({ type: "string", minLength: 1 });
4505
- const count = () => ({ type: "integer", minimum: 1 });
4506
- const position = () => ({ type: "integer", minimum: 1 });
4507
- const object = () => ({ type: "object" });
4508
- const nullableObject = () => ({ type: ["object", "null"] });
4509
- const value = () => ({ type: ["string", "number", "boolean", "null"] });
4510
- const variants = [
4511
- variant(
4512
- "set_cell",
4513
- { sheet: sheet(), address: address(), value: value(), formula: { type: "string" }, computedValue: value(), format: nullableObject(), comment: nullableObject() },
4514
- ["sheet", "address"],
4515
- "Write one cell. Omitting formula or computedValue clears it; omitting format or comment keeps it; null clears it."
4516
- ),
4517
- variant(
4518
- "set_range",
4519
- { sheet: sheet(), start: address(), end: address(), values: { type: "array", items: { type: "array", items: value() } } },
4520
- ["sheet", "start", "values"],
4521
- "Write a block of values row-major from start, each as a set_cell with no formula."
4522
- ),
4523
- variant("set_workbook", { data: object() }, ["data"], "Replace the whole workbook schema."),
4524
- variant("clear_cell", { sheet: sheet(), address: address() }, ["sheet", "address"], "Remove one cell."),
4525
- variant("insert_rows", { sheet: sheet(), at: position(), count: count() }, ["sheet", "at", "count"], "Insert rows before 1-based row `at`; cells, merges below move down."),
4526
- variant("delete_rows", { sheet: sheet(), at: position(), count: count() }, ["sheet", "at", "count"], "Delete rows starting at 1-based row `at`; cells, merges below move up."),
4527
- variant("insert_columns", { sheet: sheet(), at: position(), count: count() }, ["sheet", "at", "count"], "Insert columns before 1-based column `at` (A = 1); cells, merges and widths move right."),
4528
- variant("delete_columns", { sheet: sheet(), at: position(), count: count() }, ["sheet", "at", "count"], "Delete columns starting at 1-based column `at`; cells, merges and widths move left."),
4529
- variant("add_sheet", { index: { type: "integer", minimum: 0 }, sheet: object() }, ["index", "sheet"], "Insert a sheet at a 0-based position."),
4530
- variant("remove_sheet", { sheet: sheet() }, ["sheet"], "Remove a sheet by name."),
4531
- variant("rename_sheet", { sheet: sheet(), name: sheet() }, ["sheet", "name"], "Rename a sheet."),
4532
- variant("move_sheet", { sheet: sheet(), toIndex: { type: "integer", minimum: 0 } }, ["sheet", "toIndex"], "Move a sheet to a 0-based position."),
4533
- variant("replace_sheet", { sheet: sheet(), data: object() }, ["sheet", "data"], "Replace one sheet whole."),
4534
- variant(
4535
- "set_merged_regions",
4536
- { sheet: sheet(), mergedRegions: { type: "array", items: { type: "object", required: ["start", "end"], properties: { start: address(), end: address() } } } },
4537
- ["sheet", "mergedRegions"],
4538
- "Set every merged region of a sheet."
4539
- ),
4540
- variant("set_column_widths", { sheet: sheet(), columnWidths: object() }, ["sheet", "columnWidths"], "Set every column width of a sheet (0-based column index to pixels)."),
4541
- variant(
4542
- "set_frozen",
4543
- { sheet: sheet(), rows: { type: "integer", minimum: 0 }, cols: { type: "integer", minimum: 0 } },
4544
- ["sheet", "rows", "cols"],
4545
- "Set frozen rows and columns."
4546
- ),
4547
- variant("set_meta", { meta: nullableObject() }, ["meta"], "Replace the workbook meta, or remove it with null.")
4548
- ];
4549
- return {
4550
- $schema: "http://json-schema.org/draft-07/schema#",
4551
- title: "Holy Sheet op",
4552
- description: "One op from Agent::diff, applied by Agent::reduce.",
4553
- oneOf: variants
4554
- };
4555
- }
4556
- };
4557
- function variant(type, properties, required, description) {
4558
- return {
4559
- type: "object",
4560
- description,
4561
- required: ["type", ...required],
4562
- additionalProperties: false,
4563
- properties: { type: { const: type }, ...properties }
4564
- };
4676
+ function unencodable(reason) {
4677
+ return new TypeError(`[holy-sheet] cannot compare a value JSON cannot hold: ${reason}`);
4565
4678
  }
4566
4679
 
4567
4680
  // src/holy-sheet.schema.json
@@ -4928,7 +5041,7 @@ var holy_sheet_schema_default = {
4928
5041
  };
4929
5042
 
4930
5043
  // src/agent.ts
4931
- var VERSION = "2.4.0";
5044
+ var VERSION = "2.4.2";
4932
5045
  function toU8(input) {
4933
5046
  return input instanceof Uint8Array ? input : new Uint8Array(input);
4934
5047
  }