@particle-academy/holy-sheet 2.3.1 → 2.4.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.d.ts CHANGED
@@ -102,9 +102,129 @@ interface BuilderOptions {
102
102
  enclosure?: string;
103
103
  }
104
104
 
105
+ /**
106
+ * One op from `Agent.diff`, applied by `Agent.reduce`. Mirrors the PHP
107
+ * `HolySheet\Ops\SheetOpSchema` variants: same `type`, same field names.
108
+ *
109
+ * `set_cell`, `set_range` and `set_workbook` are fancy-sheets' `SheetOp` shapes;
110
+ * `set_workbook.data` here is a Holy Sheet schema, not fancy-sheets'
111
+ * `WorkbookData`.
112
+ */
113
+ type SheetOp = SetCellOp | SetRangeOp | SetWorkbookOp | ClearCellOp | InsertRowsOp | DeleteRowsOp | InsertColumnsOp | DeleteColumnsOp | AddSheetOp | RemoveSheetOp | RenameSheetOp | MoveSheetOp | ReplaceSheetOp | SetMergedRegionsOp | SetColumnWidthsOp | SetFrozenOp | SetMetaOp;
114
+ type SheetOpType = SheetOp["type"];
115
+ /** Omitting `formula` or `computedValue` clears it; omitting `format` or `comment` keeps it; null clears it. */
116
+ interface SetCellOp {
117
+ type: "set_cell";
118
+ sheet: string;
119
+ address: string;
120
+ value?: CellPrimitive;
121
+ formula?: string;
122
+ computedValue?: CellPrimitive;
123
+ format?: CellFormatInput | null;
124
+ comment?: CommentInput | null;
125
+ }
126
+ /** Values row-major from `start`, each written as a `set_cell` with no formula. `end` is accepted and not read. */
127
+ interface SetRangeOp {
128
+ type: "set_range";
129
+ sheet: string;
130
+ start: string;
131
+ end?: string;
132
+ values: CellPrimitive[][];
133
+ }
134
+ interface SetWorkbookOp {
135
+ type: "set_workbook";
136
+ data: HolySheetSchema | Record<string, unknown>;
137
+ }
138
+ interface ClearCellOp {
139
+ type: "clear_cell";
140
+ sheet: string;
141
+ address: string;
142
+ }
143
+ /** Insert rows before 1-based row `at`. */
144
+ interface InsertRowsOp {
145
+ type: "insert_rows";
146
+ sheet: string;
147
+ at: number;
148
+ count: number;
149
+ }
150
+ /** Delete rows starting at 1-based row `at`. */
151
+ interface DeleteRowsOp {
152
+ type: "delete_rows";
153
+ sheet: string;
154
+ at: number;
155
+ count: number;
156
+ }
157
+ /** Insert columns before 1-based column `at` (A = 1). */
158
+ interface InsertColumnsOp {
159
+ type: "insert_columns";
160
+ sheet: string;
161
+ at: number;
162
+ count: number;
163
+ }
164
+ /** Delete columns starting at 1-based column `at` (A = 1). */
165
+ interface DeleteColumnsOp {
166
+ type: "delete_columns";
167
+ sheet: string;
168
+ at: number;
169
+ count: number;
170
+ }
171
+ /** Insert a sheet at a 0-based position. */
172
+ interface AddSheetOp {
173
+ type: "add_sheet";
174
+ index: number;
175
+ sheet: SheetSchema | Record<string, unknown>;
176
+ }
177
+ interface RemoveSheetOp {
178
+ type: "remove_sheet";
179
+ sheet: string;
180
+ }
181
+ interface RenameSheetOp {
182
+ type: "rename_sheet";
183
+ sheet: string;
184
+ name: string;
185
+ }
186
+ /** Move a sheet to a 0-based position. */
187
+ interface MoveSheetOp {
188
+ type: "move_sheet";
189
+ sheet: string;
190
+ toIndex: number;
191
+ }
192
+ interface ReplaceSheetOp {
193
+ type: "replace_sheet";
194
+ sheet: string;
195
+ data: SheetSchema | Record<string, unknown>;
196
+ }
197
+ /** An empty list removes the key. */
198
+ interface SetMergedRegionsOp {
199
+ type: "set_merged_regions";
200
+ sheet: string;
201
+ mergedRegions: {
202
+ start: string;
203
+ end: string;
204
+ }[];
205
+ }
206
+ /** 0-based column index to pixels. An empty map removes the key. */
207
+ interface SetColumnWidthsOp {
208
+ type: "set_column_widths";
209
+ sheet: string;
210
+ columnWidths: Record<number | string, number>;
211
+ }
212
+ /** 0 removes the key. */
213
+ interface SetFrozenOp {
214
+ type: "set_frozen";
215
+ sheet: string;
216
+ rows: number;
217
+ cols: number;
218
+ }
219
+ /** null removes the meta. */
220
+ interface SetMetaOp {
221
+ type: "set_meta";
222
+ meta: Record<string, unknown> | null;
223
+ }
224
+
105
225
  /** This package's own version, pinned to package.json by `version.test.ts`. */
106
- declare const VERSION = "2.3.1";
107
- type Any$5 = any;
226
+ declare const VERSION = "2.4.0";
227
+ type Any$6 = any;
108
228
  /**
109
229
  * Agent — the structured-tool surface for Holy Sheet. Mirrors PHP `Agent`.
110
230
  * Universal methods are synchronous; file-touching methods (`write`,
@@ -112,11 +232,11 @@ type Any$5 = any;
112
232
  */
113
233
  declare const Agent: {
114
234
  /** Validate a schema without writing. Empty array = valid. */
115
- validate(schema: Any$5): ValidationError[];
235
+ validate(schema: Any$6): ValidationError[];
116
236
  /** xlsx bytes without touching disk. Universal. Throws SchemaException if invalid. */
117
- toBytes(schema: Any$5): Uint8Array;
237
+ toBytes(schema: Any$6): Uint8Array;
118
238
  /** Write a workbook to disk (Node only). Throws SchemaException if invalid. */
119
- write(schema: Any$5, path: string): Promise<WriteResult>;
239
+ write(schema: Any$6, path: string): Promise<WriteResult>;
120
240
  /** JSON Schema for LLM tool-use. */
121
241
  toolDefinition(): Record<string, unknown>;
122
242
  /**
@@ -128,33 +248,64 @@ declare const Agent: {
128
248
  /** Round-trip an xlsx or ods file on disk back to a schema (Node only). */
129
249
  describe(path: string): Promise<Record<string, unknown>>;
130
250
  /** Validate + conservative repairs in one call. */
131
- validateAndRepair(schema: Any$5): RepairResult;
251
+ validateAndRepair(schema: Any$6): RepairResult;
132
252
  /** Build a schema from a flat array of rows (+ optional headers). */
133
- fromArray(rows: Any$5[][], headers?: string[] | null, sheetName?: string, options?: BuilderOptions): HolySheetSchema;
253
+ fromArray(rows: Any$6[][], headers?: string[] | null, sheetName?: string, options?: BuilderOptions): HolySheetSchema;
134
254
  /** Build a schema from CSV content (read files yourself in Node). */
135
255
  fromCsv(csv: string, options?: BuilderOptions): HolySheetSchema;
136
256
  /** Evaluate every formula and report Excel-style errors. Empty array = clean. */
137
- lint(schema: Any$5): FormulaProblem[];
257
+ lint(schema: Any$6): FormulaProblem[];
258
+ /**
259
+ * The ops that turn schema `a` into schema `b`. Mirrors PHP `Agent::diff`, and
260
+ * gives the same ops in the same order for the same inputs.
261
+ *
262
+ * - `reduce(a, diff(a, b))` equals `b` (key order aside).
263
+ * - Schemas that write the same workbook diff to `[]`, so
264
+ * `diff(s, read(toBytes(s)))` is `[]`: a save without a change records
265
+ * nothing.
266
+ * - One changed cell is one `set_cell`; an inserted row is one
267
+ * `insert_rows` plus its cells.
268
+ *
269
+ * Store `diff(newer, older)` to keep a version as the ops that restore it.
270
+ * Both schemas must be valid: the "same workbook" check writes them.
271
+ */
272
+ diff(a: Any$6, b: Any$6): SheetOp[];
273
+ /**
274
+ * Apply one op, or a list of them, to a schema; returns a new schema and never
275
+ * modifies the input. An op naming a sheet that is not there is skipped.
276
+ */
277
+ reduce(schema: Any$6, opOrOps: SheetOp | readonly SheetOp[]): Record<string, any>;
278
+ /**
279
+ * JSON Schema for one op. `set_cell`, `set_range` and `set_workbook` are
280
+ * fancy-sheets' `SheetOp` shapes.
281
+ */
282
+ opSchema(): Record<string, unknown>;
283
+ /**
284
+ * Whether two schemas write the same workbook: a columns/rows sheet and the
285
+ * cell map it becomes are equivalent, and so are a schema without
286
+ * `meta.created` and its written copy.
287
+ */
288
+ equivalent(a: Any$6, b: Any$6): boolean;
138
289
  version(): string;
139
290
  };
140
291
 
141
- type Any$4 = any;
292
+ type Any$5 = any;
142
293
  /**
143
294
  * Holy Sheet — instance entry point mirroring PHP `HolySheet`. Thin wrappers
144
295
  * over `Agent`. Provided for parity / DI ergonomics; most callers use `Agent`.
145
296
  */
146
297
  declare class HolySheet {
147
- static readonly VERSION = "2.3.1";
148
- validate(schema: Any$4): ValidationError[];
149
- toBytes(schema: Any$4): Uint8Array;
150
- write(schema: Any$4, path: string): Promise<WriteResult>;
298
+ static readonly VERSION = "2.4.0";
299
+ validate(schema: Any$5): ValidationError[];
300
+ toBytes(schema: Any$5): Uint8Array;
301
+ write(schema: Any$5, path: string): Promise<WriteResult>;
151
302
  toolDefinition(): Record<string, unknown>;
152
303
  read(input: Uint8Array | ArrayBuffer): Record<string, unknown>;
153
304
  describe(path: string): Promise<Record<string, unknown>>;
154
- validateAndRepair(schema: Any$4): RepairResult;
155
- fromArray(rows: Any$4[][], headers?: string[] | null, sheetName?: string, options?: BuilderOptions): HolySheetSchema;
305
+ validateAndRepair(schema: Any$5): RepairResult;
306
+ fromArray(rows: Any$5[][], headers?: string[] | null, sheetName?: string, options?: BuilderOptions): HolySheetSchema;
156
307
  fromCsv(csv: string, options?: BuilderOptions): HolySheetSchema;
157
- lint(schema: Any$4): FormulaProblem[];
308
+ lint(schema: Any$5): FormulaProblem[];
158
309
  getVersion(): string;
159
310
  static version(): string;
160
311
  }
@@ -184,24 +335,24 @@ declare class UnsupportedFormatException extends Error {
184
335
  static unknownPackage(path: string | null, mimetype: string | null): UnsupportedFormatException;
185
336
  }
186
337
 
187
- type Any$3 = any;
338
+ type Any$4 = any;
188
339
  /** Hand-rolled schema validator. Mirrors PHP `Schema\Validator`. */
189
340
  declare class Validator {
190
- validate(schema: Any$3): ValidationError[];
191
- assert(schema: Any$3): void;
192
- validateAndRepair(schema: Any$3): RepairResult;
341
+ validate(schema: Any$4): ValidationError[];
342
+ assert(schema: Any$4): void;
343
+ validateAndRepair(schema: Any$4): RepairResult;
193
344
  private validateSheet;
194
345
  private validateColumn;
195
346
  }
196
347
 
197
- type Any$2 = any;
348
+ type Any$3 = any;
198
349
  /**
199
350
  * Conservative, high-confidence schema repairs. Mirrors PHP `Schema\Repairer`.
200
351
  * Returns [repairedSchema, repairs].
201
352
  */
202
353
  declare class Repairer {
203
354
  private repairs;
204
- repair(schema: Any$2): [Record<string, unknown>, string[]];
355
+ repair(schema: Any$3): [Record<string, unknown>, string[]];
205
356
  private repairTopLevel;
206
357
  private repairSheet;
207
358
  private repairColumnTypeInference;
@@ -302,10 +453,10 @@ declare class Workbook {
302
453
  constructor(sheets: Sheet[], meta?: Record<string, unknown>);
303
454
  }
304
455
 
305
- type Any$1 = any;
456
+ type Any$2 = any;
306
457
  /** Schema → canonical Workbook. Mirrors PHP `Schema\Normalizer`. */
307
458
  declare class Normalizer {
308
- normalize(schema: Any$1): Workbook;
459
+ normalize(schema: Any$2): Workbook;
309
460
  private normalizeSheet;
310
461
  private normalizeCellMap;
311
462
  private normalizeMerges;
@@ -440,13 +591,13 @@ declare const FormatSniffer: {
440
591
  };
441
592
  };
442
593
 
443
- type Any = any;
594
+ type Any$1 = any;
444
595
  /**
445
596
  * Flat rows (+ optional headers) → Holy Sheet schema with inferred column types.
446
597
  * Mirrors PHP `Helpers\ArrayBuilder`.
447
598
  */
448
599
  declare const ArrayBuilder: {
449
- build(rows: Any[][], headers?: string[] | null, sheetName?: string, options?: BuilderOptions): HolySheetSchema;
600
+ build(rows: Any$1[][], headers?: string[] | null, sheetName?: string, options?: BuilderOptions): HolySheetSchema;
450
601
  };
451
602
 
452
603
  /**
@@ -471,6 +622,145 @@ declare const CellAddress: {
471
622
  parse(address: string): [number, number] | null;
472
623
  };
473
624
 
625
+ /**
626
+ * The few PHP array and cast semantics the op reducer and diff depend on, in one
627
+ * place so `sheet-reducer.ts` and `sheet-diff.ts` read like the PHP they mirror.
628
+ *
629
+ * PHP has one array type for lists and maps; a JSON object and a JSON array both
630
+ * decode to it. `isArr` is PHP's `is_array` over parsed JSON: any object or array.
631
+ * A key whose value is `undefined` is treated as absent, as JSON would.
632
+ */
633
+ type Any = any;
634
+
635
+ /**
636
+ * The op list that turns one Holy Sheet schema into another. Mirrors PHP
637
+ * `HolySheet\Ops\SheetDiff`: the same algorithm, so the same inputs give the
638
+ * same ops in the same order in both runtimes (pinned against the PHP package
639
+ * by `tests/sheet-ops-parity.test.ts`).
640
+ *
641
+ * ## Two guarantees, and where each applies
642
+ *
643
+ * 1. **Same workbook, no ops.** When `a` and `b` write the same workbook —
644
+ * compared as `read(toBytes(...))`, so a columns/rows sheet and the cell
645
+ * map it becomes are the same, and the timestamp the writer stamps on a
646
+ * schema that names none is not a change — the diff is `[]`. This is what
647
+ * makes `diff(s, read(toBytes(s)))` `[]`: saving without a change records
648
+ * nothing.
649
+ * 2. **Otherwise, exact.** `reduce(a, diff(a, b))` equals `b`, key order
650
+ * aside. The ops are computed on the schemas as given and VERIFIED by
651
+ * replaying them through `SheetReducer`; a sheet whose granular ops do not
652
+ * reproduce it is replaced whole, and a workbook that still does not match
653
+ * is replaced whole. Correct first, small second.
654
+ *
655
+ * ## Small edits stay small
656
+ *
657
+ * Rows and columns are ALIGNED before cells are compared (a longest common
658
+ * subsequence over each row's, then each column's, contents), so inserting a row
659
+ * above a hundred others is one `insert_rows` plus the new row's cells, not a
660
+ * hundred rewritten cells. One changed cell is one `set_cell`.
661
+ *
662
+ * Granular ops apply to sheets in CELL form — the form `read()` returns. A sheet
663
+ * authored as columns/rows/theme/totals, on either side, is replaced whole when
664
+ * it changes: those keys expand at write time into cells an op could not
665
+ * address without re-deriving the writer.
666
+ *
667
+ * ## Determinism
668
+ *
669
+ * Alignment ties break toward deleting first, and the alignment is skipped (the
670
+ * changed middle treated as rows changed in place) past `ALIGN_LIMIT` cells of
671
+ * work.
672
+ */
673
+ declare const SheetDiff: {
674
+ /** Above this many LCS cells (after trimming the common prefix and suffix), rows or columns are not aligned. */
675
+ ALIGN_LIMIT: number;
676
+ diff(a: Any, b: Any): SheetOp[];
677
+ /**
678
+ * Whether two schemas write the same workbook.
679
+ *
680
+ * Both are written and read back. `meta.created` is compared only when both
681
+ * schemas name one: the writer stamps the current time on a schema that
682
+ * does not, and that timestamp is not an edit.
683
+ */
684
+ equivalent(a: Any, b: Any): boolean;
685
+ /**
686
+ * Structural equality with map key order ignored and list order kept.
687
+ *
688
+ * Equality is on values, never on insertion order — a JS object cannot keep
689
+ * the order of integer-like keys anyway (`columnWidths`). It follows PHP's
690
+ * canonical form, where lists and maps are one array type: `{}` equals `[]`,
691
+ * and an object keyed exactly "0".."n-1" equals the list of its values.
692
+ *
693
+ * Two divergences from the PHP `same()` cannot be removed, because the
694
+ * distinctions do not exist in JS values:
695
+ *
696
+ * - PHP encodes the float `1.0` as `1.0` and the int `1` as `1`, so it calls
697
+ * them different; JS has one number, so here they are the same. A PHP diff
698
+ * can therefore emit a `set_cell` that rewrites `1` as `1.0` where this one
699
+ * emits nothing. Parsed JSON gives PHP a float only for a literal written
700
+ * with a fraction or exponent.
701
+ * - PHP treats an int-keyed array whose keys are 0..n-1 but out of order (for
702
+ * example `columnWidths` decoded from `{"1":140,"0":120}`) as a map, not a
703
+ * list, and so as different from the same widths in order. A JS object has
704
+ * no such order to compare.
705
+ */
706
+ same(a: unknown, b: unknown): boolean;
707
+ /**
708
+ * Hunks of a longest-common-subsequence alignment: [start in a, deleted, inserted].
709
+ *
710
+ * Ties break toward deleting first. Past `ALIGN_LIMIT` the changed middle is
711
+ * one hunk.
712
+ */
713
+ hunks(a: readonly string[], b: readonly string[]): [number, number, number][];
714
+ };
715
+
716
+ /**
717
+ * Apply `SheetOpSchema` ops to a Holy Sheet schema, returning a new schema.
718
+ * Mirrors PHP `HolySheet\Ops\SheetReducer`, op for op.
719
+ *
720
+ * Pure: the input is never modified. An op naming a sheet or cell that is not
721
+ * there is skipped, as fancy-sheets' `reduceWorkbook` skips an unknown sheet, so
722
+ * a replayed history degrades rather than throws. Unchanged parts of the input
723
+ * are shared with the result rather than copied, as in any immutable reducer:
724
+ * nothing here mutates either, but a caller that mutates a result in place
725
+ * reaches into the input too.
726
+ *
727
+ * `set_cell`, `set_range` and `set_workbook` keep fancy-sheets' shapes and
728
+ * semantics — a `set_cell` without `formula` clears the formula and keeps the
729
+ * format and comment; a null write to an absent cell does nothing — so the same
730
+ * ops can drive a live `useSheetSync` session. Everything else is holy-sheet's:
731
+ * the structure fancy-sheets has no op for (sheets, rows, columns, merges, widths,
732
+ * panes, meta) and the cell parts it does not carry (`computedValue`, `format`,
733
+ * `comment`).
734
+ *
735
+ * The row and column ops move cells, merged regions and column widths. They do
736
+ * not rewrite formula text: a formula that should follow an inserted row is a
737
+ * change to that cell, and a diff records it as one.
738
+ */
739
+ declare const SheetReducer: {
740
+ /** Sheet keys the granular ops address. A sheet with any other key is authored form. */
741
+ CELL_FORM_KEYS: readonly string[];
742
+ applyAll(schema: Any, ops: readonly Any[]): Any;
743
+ apply(schema: Any, op: Any): Any;
744
+ };
745
+
746
+ /**
747
+ * JSON Schema for one sheet op — validate ops on the wire, or register the op
748
+ * vocabulary as an LLM tool. Mirrors PHP `HolySheet\Ops\SheetOpSchema`, key for
749
+ * key (pinned byte-for-byte against the PHP package by
750
+ * `tests/sheet-ops-parity.test.ts`).
751
+ *
752
+ * `set_cell`, `set_range` and `set_workbook` are fancy-sheets' `SheetOp`
753
+ * variants, same `type` and same fields, so a stored op stream can drive a live
754
+ * `useSheetSync` session. One difference: `set_workbook.data` here is a Holy
755
+ * Sheet schema, not fancy-sheets' `WorkbookData`.
756
+ */
757
+ declare const SheetOpSchema: {
758
+ /** Every op type, in the order the variants are listed. */
759
+ TYPES: readonly ["set_cell", "set_range", "set_workbook", "clear_cell", "insert_rows", "delete_rows", "insert_columns", "delete_columns", "add_sheet", "remove_sheet", "rename_sheet", "move_sheet", "replace_sheet", "set_merged_regions", "set_column_widths", "set_frozen", "set_meta"];
760
+ /** A fresh object on every call, so a caller editing it changes nothing here. */
761
+ jsonSchema(): Record<string, unknown>;
762
+ };
763
+
474
764
  interface ZipFile {
475
765
  name: string;
476
766
  data: Uint8Array;
@@ -479,4 +769,4 @@ declare function zipSync(files: ZipFile[]): Uint8Array;
479
769
 
480
770
  declare function unzipSync(data: Uint8Array): Record<string, Uint8Array>;
481
771
 
482
- export { Agent, type AggOp, ArrayBuilder, type BuilderOptions, CellAddress, type CellData, type CellFormatInput, type CellPrimitive, type ColumnSchema, type ColumnType, type CommentInput, CsvBuilder, type DisplayFormat, FormatSniffer, FormulaLinter, type FormulaProblem, HolySheet, type HolySheetSchema, Inference, Normalizer, OdsReader, type RepairResult, Repairer, type RowCell, SchemaException, type SheetSchema, type SpreadsheetFormat, Theme, type ThemeKey, UnsupportedFormatException, VERSION, type ValidationError, Validator, type WriteResult, XlsxReader, XlsxWriter, type ZipFile, unzipSync, zipSync };
772
+ export { type AddSheetOp, Agent, type AggOp, ArrayBuilder, type BuilderOptions, CellAddress, type CellData, type CellFormatInput, type CellPrimitive, type ClearCellOp, type ColumnSchema, type ColumnType, type CommentInput, CsvBuilder, type DeleteColumnsOp, type DeleteRowsOp, type DisplayFormat, FormatSniffer, FormulaLinter, type FormulaProblem, HolySheet, type HolySheetSchema, Inference, type InsertColumnsOp, type InsertRowsOp, type MoveSheetOp, Normalizer, OdsReader, type RemoveSheetOp, type RenameSheetOp, type RepairResult, Repairer, type ReplaceSheetOp, type RowCell, SchemaException, type SetCellOp, type SetColumnWidthsOp, type SetFrozenOp, type SetMergedRegionsOp, type SetMetaOp, type SetRangeOp, type SetWorkbookOp, SheetDiff, type SheetOp, SheetOpSchema, type SheetOpType, SheetReducer, type SheetSchema, type SpreadsheetFormat, Theme, type ThemeKey, UnsupportedFormatException, VERSION, type ValidationError, Validator, type WriteResult, XlsxReader, XlsxWriter, type ZipFile, unzipSync, zipSync };