bilig-workpaper 0.161.0 → 0.164.1

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/AGENTS.md CHANGED
@@ -38,6 +38,7 @@ npm exec --yes --package bilig-workpaper@latest -- bilig-evaluate --door agent-m
38
38
  npm exec --package bilig-workpaper@latest -- bilig-mcp-challenge --json
39
39
  npm exec --package bilig-workpaper@latest -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable
40
40
  npm exec --package bilig-workpaper@latest -- bilig-workpaper-mcp --from-xlsx ./pricing.xlsx
41
+ pnpm --dir examples/headless-workpaper run agent:mcp-xlsx-risk-preflight
41
42
 
42
43
  For Node or TypeScript, import bilig-workpaper directly. Check the edit by
43
44
  reading the relevant range, writing one small input or formula, reading the
@@ -94,6 +95,14 @@ WorkPaper. Without `--workpaper --writable`, edits stay in memory; add a
94
95
  WorkPaper JSON path only when the task needs persisted file state. It does not
95
96
  certify Excel compatibility.
96
97
 
98
+ For a maintained transcript that starts from a real XLSX, call
99
+ `analyze_workbook_risk`, then prove `Inputs!B3` -> `Summary!B3` readback
100
+ and export with:
101
+
102
+ ```sh
103
+ pnpm --dir examples/headless-workpaper run agent:mcp-xlsx-risk-preflight
104
+ ```
105
+
97
106
  Claude Desktop users can skip manual JSON config by installing the released
98
107
  MCPB bundle:
99
108
 
@@ -106,9 +115,9 @@ Use `bilig-workpaper` when the workbook logic belongs in a service, queue
106
115
  worker, test, or route:
107
116
 
108
117
  ```ts
109
- import { WorkPaper, exportWorkPaperDocument, serializeWorkPaperDocument } from 'bilig-workpaper'
118
+ import { buildA1WorkPaper } from 'bilig-workpaper'
110
119
 
111
- const workbook = WorkPaper.buildFromSheets({
120
+ const book = buildA1WorkPaper({
112
121
  Inputs: [
113
122
  ['Metric', 'Value'],
114
123
  ['Customers', 20],
@@ -120,18 +129,19 @@ const workbook = WorkPaper.buildFromSheets({
120
129
  ],
121
130
  })
122
131
 
123
- const inputs = workbook.getSheetId('Inputs')
124
- const summary = workbook.getSheetId('Summary')
125
- if (inputs === undefined || summary === undefined) {
126
- throw new Error('Workbook is missing required sheets')
127
- }
128
-
129
- workbook.setCellContents({ sheet: inputs, row: 1, col: 1 }, 32)
132
+ const proof = book.editAndReadback('Inputs!B2', 32, {
133
+ readbackRange: 'Summary!B2',
134
+ })
130
135
 
131
- const revenue = workbook.getCellDisplayValue({ sheet: summary, row: 1, col: 1 })
132
- const saved = serializeWorkPaperDocument(exportWorkPaperDocument(workbook, { includeConfig: true }))
136
+ console.log({
137
+ editedCell: proof.editedCell,
138
+ after: proof.afterReadback.displayValues,
139
+ afterRestore: proof.restoredReadback.displayValues,
140
+ persistedDocumentBytes: proof.persistedDocumentBytes,
141
+ verified: proof.verified,
142
+ })
133
143
 
134
- console.log({ revenue, savedBytes: saved.length })
144
+ book.dispose()
135
145
  ```
136
146
 
137
147
  ## Verification shortcuts
@@ -146,6 +156,7 @@ npm exec --yes --package bilig-workpaper@latest -- bilig-evaluate --door agent-m
146
156
  npm exec --yes --package @bilig/xlsx-formula-recalc@latest -- bilig-evaluate --door xlsx-cache --json
147
157
  npm exec --package bilig-workpaper@latest -- bilig-agent-challenge --json
148
158
  npm exec --package bilig-workpaper@latest -- bilig-mcp-challenge --json
159
+ pnpm --dir examples/headless-workpaper run agent:mcp-xlsx-risk-preflight
149
160
  ```
150
161
 
151
162
  `bilig-agent-challenge` checks the direct WorkPaper API loop.
package/README.md CHANGED
@@ -13,9 +13,9 @@ npm install bilig-workpaper
13
13
  ## Use A WorkPaper In Node
14
14
 
15
15
  ```ts
16
- import { WorkPaper } from 'bilig-workpaper'
16
+ import { buildA1WorkPaper } from 'bilig-workpaper'
17
17
 
18
- const workbook = WorkPaper.buildFromSheets({
18
+ const book = buildA1WorkPaper({
19
19
  Inputs: [
20
20
  ['Metric', 'Value'],
21
21
  ['Units', 40],
@@ -27,20 +27,19 @@ const workbook = WorkPaper.buildFromSheets({
27
27
  ],
28
28
  })
29
29
 
30
- const inputs = workbook.getSheetId('Inputs')
31
- const summary = workbook.getSheetId('Summary')
32
-
33
- if (inputs === undefined || summary === undefined) {
34
- throw new Error('Expected sheets to exist')
35
- }
36
-
37
- workbook.setCellContents({ sheet: inputs, row: 1, col: 1 }, 48)
38
- workbook.setCellContents({ sheet: inputs, row: 2, col: 1 }, 1500)
30
+ const proof = book.editAndReadback('Inputs!B2', 48, {
31
+ readbackRange: 'Summary!B2',
32
+ })
39
33
 
40
- console.log(workbook.getCellValue({ sheet: summary, row: 1, col: 1 }))
41
- console.log(workbook.exportSnapshot())
34
+ console.log({
35
+ editedCell: proof.editedCell,
36
+ after: proof.afterReadback.displayValues,
37
+ afterRestore: proof.restoredReadback.displayValues,
38
+ persistedDocumentBytes: proof.persistedDocumentBytes,
39
+ verified: proof.verified,
40
+ })
42
41
 
43
- workbook.dispose()
42
+ book.dispose()
44
43
  ```
45
44
 
46
45
  ## XLSX Import And Export
package/SKILL.md CHANGED
@@ -119,6 +119,12 @@ WorkPaper. Without `--workpaper --writable`, edits stay in memory; add a
119
119
  WorkPaper JSON path only when the task needs persisted file state. It does not
120
120
  certify Excel compatibility.
121
121
 
122
+ For a maintained XLSX preflight transcript, run
123
+ `pnpm --dir examples/headless-workpaper run agent:mcp-xlsx-risk-preflight`.
124
+ It requires `analyze_workbook_risk`, `set_cell_contents_and_readback`,
125
+ `export_workpaper_document`, `Inputs!B3`, `Summary!B3`, `60000 -> 96000`,
126
+ and `verified: true`.
127
+
122
128
  After a write, always read the dependent output cell and export the WorkPaper
123
129
  document. If the listed tool set includes `set_cell_contents_and_readback`,
124
130
  prefer it for stateless clients because the edit and dependent readback happen
@@ -142,9 +148,9 @@ stdio command when the workflow must persist a project WorkPaper JSON file.
142
148
  Use `bilig-workpaper` directly when workbook logic belongs in a service, queue worker, test, or route:
143
149
 
144
150
  ```ts
145
- import { WorkPaper, exportWorkPaperDocument, serializeWorkPaperDocument } from 'bilig-workpaper'
151
+ import { buildA1WorkPaper } from 'bilig-workpaper'
146
152
 
147
- const workbook = WorkPaper.buildFromSheets({
153
+ const book = buildA1WorkPaper({
148
154
  Inputs: [
149
155
  ['Metric', 'Value'],
150
156
  ['Customers', 20],
@@ -156,17 +162,19 @@ const workbook = WorkPaper.buildFromSheets({
156
162
  ],
157
163
  })
158
164
 
159
- const inputs = workbook.getSheetId('Inputs')
160
- const summary = workbook.getSheetId('Summary')
161
- if (inputs === undefined || summary === undefined) {
162
- throw new Error('Workbook is missing required sheets')
163
- }
165
+ const proof = book.editAndReadback('Inputs!B2', 32, {
166
+ readbackRange: 'Summary!B2',
167
+ })
164
168
 
165
- workbook.setCellContents({ sheet: inputs, row: 1, col: 1 }, 32)
166
- const revenue = workbook.getCellDisplayValue({ sheet: summary, row: 1, col: 1 })
167
- const saved = serializeWorkPaperDocument(exportWorkPaperDocument(workbook, { includeConfig: true }))
169
+ console.log({
170
+ editedCell: proof.editedCell,
171
+ after: proof.afterReadback.displayValues,
172
+ afterRestore: proof.restoredReadback.displayValues,
173
+ persistedDocumentBytes: proof.persistedDocumentBytes,
174
+ verified: proof.verified,
175
+ })
168
176
 
169
- console.log({ revenue, savedBytes: saved.length })
177
+ book.dispose()
170
178
  ```
171
179
 
172
180
  ## XLSX Formula Clinic
@@ -204,6 +212,7 @@ If any readback step fails, report the blocker instead of claiming the workbook
204
212
  - MCP server guide: https://proompteng.github.io/bilig/mcp-workpaper-tool-server.html
205
213
  - OpenHands MCP setup: https://proompteng.github.io/bilig/openhands-workpaper-mcp.html
206
214
  - OpenCode MCP setup: https://proompteng.github.io/bilig/opencode-workpaper-mcp.html
215
+ - Goose MCP recipe: https://proompteng.github.io/bilig/goose-workpaper-mcp.html
207
216
  - Open WebUI tool setup: https://proompteng.github.io/bilig/open-webui-workpaper-mcp.html
208
217
  - LobeHub MCP setup: https://proompteng.github.io/bilig/lobehub-workpaper-mcp.html
209
218
  - AnythingLLM MCP setup: https://proompteng.github.io/bilig/anythingllm-workpaper-mcp.html
@@ -1,4 +1,2 @@
1
1
  #!/usr/bin/env node
2
- const { runBiligEvaluatorCli } = await import('@bilig/xlsx-formula-recalc/evaluator')
3
-
4
- process.exitCode = runBiligEvaluatorCli(process.argv.slice(2))
2
+ await import('../dist/evaluator-bin.js')
package/dist/a1.d.ts ADDED
@@ -0,0 +1,118 @@
1
+ import { WorkPaper } from '@bilig/headless';
2
+ import type { PersistedWorkPaperDocument, RawCellContent, WorkPaperChange, WorkPaperConfig, WorkPaperFormulaDiagnostic, WorkPaperSheets } from '@bilig/headless';
3
+ type WorkPaperInstance = ReturnType<typeof WorkPaper.buildFromSheets>;
4
+ export type A1CellInput = RawCellContent;
5
+ export type A1CellValue = ReturnType<WorkPaperInstance['getCellValue']>;
6
+ export interface A1WorkPaperOptions {
7
+ readonly defaultSheetName?: string;
8
+ readonly writableSheets?: readonly string[];
9
+ readonly limitations?: readonly string[];
10
+ }
11
+ export interface A1CellRead {
12
+ readonly address: string;
13
+ readonly value: A1CellValue;
14
+ readonly serialized: RawCellContent;
15
+ readonly formula: string | null;
16
+ readonly displayValue: string;
17
+ readonly formulaDiagnostics: readonly WorkPaperFormulaDiagnostic[];
18
+ }
19
+ export interface A1RangeRead {
20
+ readonly range: string;
21
+ readonly values: A1CellValue[][];
22
+ readonly serialized: RawCellContent[][];
23
+ readonly displayValues: string[][];
24
+ readonly formulaDiagnostics: readonly WorkPaperFormulaDiagnostic[];
25
+ readonly cells: readonly (readonly A1CellRead[])[];
26
+ }
27
+ export interface A1SetCellAndReadbackOptions {
28
+ readonly readbackRange: string;
29
+ readonly requireReadbackChange?: boolean;
30
+ readonly includeConfig?: boolean;
31
+ readonly includeSerializedDocument?: boolean;
32
+ readonly limitations?: readonly string[];
33
+ }
34
+ export interface A1SetCellAndReadbackProof {
35
+ readonly editedCell: string;
36
+ readonly readbackRange: string;
37
+ readonly before: A1CellRead;
38
+ readonly after: A1CellRead;
39
+ readonly beforeReadback: A1RangeRead;
40
+ readonly afterReadback: A1RangeRead;
41
+ readonly restoredReadback: A1RangeRead;
42
+ readonly persistedDocumentBytes: number;
43
+ readonly checks: {
44
+ readonly readbackChanged: boolean;
45
+ readonly computedReadbackChanged: boolean;
46
+ readonly editedFormulaReadbackChanged: boolean;
47
+ readonly readbackIncludesEditedCell: boolean;
48
+ readonly readbackContainsOnlyEditedCell: boolean;
49
+ readonly restoredReadbackMatchesAfter: boolean;
50
+ readonly blockingFormulaDiagnosticCount: number;
51
+ readonly formulaDiagnostics: readonly WorkPaperFormulaDiagnostic[];
52
+ readonly previousSerialized: RawCellContent;
53
+ readonly newSerialized: RawCellContent;
54
+ };
55
+ readonly verified: boolean;
56
+ readonly limitations: readonly string[];
57
+ readonly changes: readonly WorkPaperChange[];
58
+ readonly serializedDocument?: string;
59
+ }
60
+ export interface A1EditManyAndReadbackProof {
61
+ readonly editedCells: readonly string[];
62
+ readonly readbackRange: string;
63
+ readonly before: Readonly<Record<string, A1CellRead>>;
64
+ readonly after: Readonly<Record<string, A1CellRead>>;
65
+ readonly beforeReadback: A1RangeRead;
66
+ readonly afterReadback: A1RangeRead;
67
+ readonly restoredReadback: A1RangeRead;
68
+ readonly persistedDocumentBytes: number;
69
+ readonly checks: {
70
+ readonly readbackChanged: boolean;
71
+ readonly computedReadbackChanged: boolean;
72
+ readonly editedFormulaReadbackChanged: boolean;
73
+ readonly readbackIncludesEditedCells: boolean;
74
+ readonly readbackContainsOnlyEditedCells: boolean;
75
+ readonly restoredReadbackMatchesAfter: boolean;
76
+ readonly blockingFormulaDiagnosticCount: number;
77
+ readonly formulaDiagnostics: readonly WorkPaperFormulaDiagnostic[];
78
+ readonly previousSerialized: Readonly<Record<string, RawCellContent>>;
79
+ readonly newSerialized: Readonly<Record<string, RawCellContent>>;
80
+ };
81
+ readonly verified: boolean;
82
+ readonly limitations: readonly string[];
83
+ readonly changes: readonly WorkPaperChange[];
84
+ readonly serializedDocument?: string;
85
+ }
86
+ export interface A1WorkPaper {
87
+ readonly workpaper: WorkPaperInstance;
88
+ readCell(address: string): A1CellRead;
89
+ get(address: string): A1CellValue;
90
+ display(address: string): string;
91
+ formula(address: string): string | null;
92
+ readRange(range: string): A1RangeRead;
93
+ range(range: string): A1CellValue[][];
94
+ setCell(address: string, value: A1CellInput): WorkPaperChange[];
95
+ set(address: string, value: A1CellInput): WorkPaperChange[];
96
+ readMany(addresses: readonly string[]): Readonly<Record<string, A1CellRead>>;
97
+ setCells(edits: Readonly<Record<string, A1CellInput>>): WorkPaperChange[];
98
+ setMany(edits: Readonly<Record<string, A1CellInput>>): WorkPaperChange[];
99
+ validateFormula(formula: string): boolean;
100
+ setCellAndReadback(address: string, value: A1CellInput, options: A1SetCellAndReadbackOptions): A1SetCellAndReadbackProof;
101
+ editAndReadback(address: string, value: A1CellInput, options: A1SetCellAndReadbackOptions): A1SetCellAndReadbackProof;
102
+ editManyAndReadback(edits: Readonly<Record<string, A1CellInput>>, options: A1SetCellAndReadbackOptions): A1EditManyAndReadbackProof;
103
+ exportDocument(options?: {
104
+ readonly includeConfig?: boolean;
105
+ }): PersistedWorkPaperDocument;
106
+ serialize(options?: {
107
+ readonly includeConfig?: boolean;
108
+ }): string;
109
+ saveJson(options?: {
110
+ readonly includeConfig?: boolean;
111
+ }): string;
112
+ restoreJson(input: string | PersistedWorkPaperDocument): A1WorkPaper;
113
+ dispose(): void;
114
+ }
115
+ export declare function createA1WorkPaper(workpaper: WorkPaperInstance, options?: A1WorkPaperOptions): A1WorkPaper;
116
+ export declare function buildA1WorkPaper(sheets: WorkPaperSheets, config?: WorkPaperConfig, options?: A1WorkPaperOptions): A1WorkPaper;
117
+ export declare function restoreA1WorkPaper(input: string | PersistedWorkPaperDocument, options?: A1WorkPaperOptions): A1WorkPaper;
118
+ export {};
package/dist/a1.js ADDED
@@ -0,0 +1,361 @@
1
+ import { WorkPaper, createWorkPaperFromDocument, exportWorkPaperDocument, parseWorkPaperDocument, serializeWorkPaperDocument, } from '@bilig/headless';
2
+ export function createA1WorkPaper(workpaper, options = {}) {
3
+ return new A1WorkPaperFacade(workpaper, options);
4
+ }
5
+ export function buildA1WorkPaper(sheets, config, options = {}) {
6
+ return createA1WorkPaper(WorkPaper.buildFromSheets(sheets, config), options);
7
+ }
8
+ export function restoreA1WorkPaper(input, options = {}) {
9
+ const document = typeof input === 'string' ? parseWorkPaperDocument(input) : input;
10
+ return createA1WorkPaper(createWorkPaperFromDocument(document), options);
11
+ }
12
+ class A1WorkPaperFacade {
13
+ workpaper;
14
+ defaultSheetName;
15
+ writableSheets;
16
+ limitations;
17
+ constructor(workpaper, options) {
18
+ this.workpaper = workpaper;
19
+ this.defaultSheetName = options.defaultSheetName;
20
+ this.writableSheets = options.writableSheets;
21
+ this.limitations = options.limitations ?? [];
22
+ }
23
+ readCell(address) {
24
+ const parsed = this.requireCellAddress(address);
25
+ return this.readParsedCell(parsed);
26
+ }
27
+ get(address) {
28
+ return this.readCell(address).value;
29
+ }
30
+ display(address) {
31
+ return this.readCell(address).displayValue;
32
+ }
33
+ formula(address) {
34
+ return this.readCell(address).formula;
35
+ }
36
+ readRange(range) {
37
+ const parsed = this.requireRange(range);
38
+ const cells = this.readRangeCells(parsed);
39
+ return {
40
+ range: this.formatRange(parsed),
41
+ values: cells.map((row) => row.map((cell) => cell.value)),
42
+ serialized: cells.map((row) => row.map((cell) => cell.serialized)),
43
+ displayValues: cells.map((row) => row.map((cell) => cell.displayValue)),
44
+ formulaDiagnostics: cells.flatMap((row) => row.flatMap((cell) => cell.formulaDiagnostics)),
45
+ cells,
46
+ };
47
+ }
48
+ range(range) {
49
+ return this.readRange(range).values;
50
+ }
51
+ setCell(address, value) {
52
+ const parsed = this.requireCellAddress(address);
53
+ this.assertWritable(parsed);
54
+ this.assertFormulaCanParse(value, parsed);
55
+ return this.workpaper.setCellContents(parsed, value);
56
+ }
57
+ set(address, value) {
58
+ return this.setCell(address, value);
59
+ }
60
+ readMany(addresses) {
61
+ const reads = {};
62
+ for (const address of addresses) {
63
+ const read = this.readCell(address);
64
+ reads[read.address] = read;
65
+ }
66
+ return reads;
67
+ }
68
+ setCells(edits) {
69
+ const prepared = this.prepareEdits(edits);
70
+ return this.applyPreparedEdits(prepared);
71
+ }
72
+ setMany(edits) {
73
+ return this.setCells(edits);
74
+ }
75
+ validateFormula(formula) {
76
+ return this.workpaper.validateFormula(formula);
77
+ }
78
+ setCellAndReadback(address, value, options) {
79
+ const proof = this.editManyAndReadback({ [address]: value }, options);
80
+ const editedCell = proof.editedCells[0];
81
+ if (editedCell === undefined) {
82
+ throw new Error('Expected one edited WorkPaper cell');
83
+ }
84
+ const before = proof.before[editedCell];
85
+ const after = proof.after[editedCell];
86
+ if (before === undefined || after === undefined) {
87
+ throw new Error(`Expected proof reads for ${editedCell}`);
88
+ }
89
+ return {
90
+ editedCell,
91
+ readbackRange: proof.readbackRange,
92
+ before,
93
+ after,
94
+ beforeReadback: proof.beforeReadback,
95
+ afterReadback: proof.afterReadback,
96
+ restoredReadback: proof.restoredReadback,
97
+ persistedDocumentBytes: proof.persistedDocumentBytes,
98
+ checks: {
99
+ readbackChanged: proof.checks.readbackChanged,
100
+ computedReadbackChanged: proof.checks.computedReadbackChanged,
101
+ editedFormulaReadbackChanged: proof.checks.editedFormulaReadbackChanged,
102
+ readbackIncludesEditedCell: proof.checks.readbackIncludesEditedCells,
103
+ readbackContainsOnlyEditedCell: proof.checks.readbackContainsOnlyEditedCells,
104
+ restoredReadbackMatchesAfter: proof.checks.restoredReadbackMatchesAfter,
105
+ blockingFormulaDiagnosticCount: proof.checks.blockingFormulaDiagnosticCount,
106
+ formulaDiagnostics: proof.checks.formulaDiagnostics,
107
+ previousSerialized: before.serialized,
108
+ newSerialized: after.serialized,
109
+ },
110
+ verified: proof.verified,
111
+ limitations: proof.limitations,
112
+ changes: proof.changes,
113
+ ...(options.includeSerializedDocument && proof.serializedDocument !== undefined
114
+ ? { serializedDocument: proof.serializedDocument }
115
+ : {}),
116
+ };
117
+ }
118
+ editAndReadback(address, value, options) {
119
+ return this.setCellAndReadback(address, value, options);
120
+ }
121
+ editManyAndReadback(edits, options) {
122
+ const requireReadbackChange = options.requireReadbackChange ?? true;
123
+ const includeConfig = options.includeConfig ?? true;
124
+ const prepared = this.prepareEdits(edits);
125
+ const editedCells = prepared.map((edit) => edit.address);
126
+ const editedCellSet = new Set(editedCells);
127
+ const before = this.readPreparedCells(prepared);
128
+ const beforeReadback = this.readRange(options.readbackRange);
129
+ const changes = this.applyPreparedEdits(prepared);
130
+ const after = this.readPreparedCells(prepared);
131
+ const afterReadback = this.readRange(options.readbackRange);
132
+ const serializedDocument = this.serialize({ includeConfig });
133
+ const restored = restoreA1WorkPaper(serializedDocument, this.facadeOptions());
134
+ try {
135
+ const restoredReadback = restored.readRange(options.readbackRange);
136
+ const persistedDocumentBytes = new TextEncoder().encode(serializedDocument).byteLength;
137
+ const readbackChanged = !sameJson(rangeComparison(beforeReadback), rangeComparison(afterReadback));
138
+ const computedReadbackChanged = !sameJson(rangeComparisonExcludingEditedCells(beforeReadback, editedCellSet), rangeComparisonExcludingEditedCells(afterReadback, editedCellSet));
139
+ const editedFormulaReadbackChanged = readbackChanged && readbackEditedFormulaCells(afterReadback, editedCellSet).length > 0;
140
+ const readbackAddresses = rangeCellAddresses(afterReadback);
141
+ const readbackIncludesEditedCells = readbackAddresses.some((readbackAddress) => editedCellSet.has(readbackAddress));
142
+ const readbackContainsOnlyEditedCells = readbackAddresses.length > 0 && readbackAddresses.every((readbackAddress) => editedCellSet.has(readbackAddress));
143
+ const restoredReadbackMatchesAfter = sameJson(rangeComparison(afterReadback), rangeComparison(restoredReadback));
144
+ const formulaDiagnostics = afterReadback.formulaDiagnostics;
145
+ const blockingFormulaDiagnosticCount = formulaDiagnostics.filter((diagnostic) => diagnostic.severity === 'error').length;
146
+ const meaningfulReadbackChanged = computedReadbackChanged || editedFormulaReadbackChanged;
147
+ return {
148
+ editedCells,
149
+ readbackRange: afterReadback.range,
150
+ before,
151
+ after,
152
+ beforeReadback,
153
+ afterReadback,
154
+ restoredReadback,
155
+ persistedDocumentBytes,
156
+ checks: {
157
+ readbackChanged,
158
+ computedReadbackChanged,
159
+ editedFormulaReadbackChanged,
160
+ readbackIncludesEditedCells,
161
+ readbackContainsOnlyEditedCells,
162
+ restoredReadbackMatchesAfter,
163
+ blockingFormulaDiagnosticCount,
164
+ formulaDiagnostics,
165
+ previousSerialized: serializedByAddress(before),
166
+ newSerialized: serializedByAddress(after),
167
+ },
168
+ verified: persistedDocumentBytes > 0 &&
169
+ restoredReadbackMatchesAfter &&
170
+ blockingFormulaDiagnosticCount === 0 &&
171
+ (!requireReadbackChange || meaningfulReadbackChanged),
172
+ limitations: [...this.limitations, ...(options.limitations ?? [])],
173
+ changes,
174
+ ...(options.includeSerializedDocument ? { serializedDocument } : {}),
175
+ };
176
+ }
177
+ finally {
178
+ restored.dispose();
179
+ }
180
+ }
181
+ exportDocument(options = {}) {
182
+ return exportWorkPaperDocument(this.workpaper, options);
183
+ }
184
+ serialize(options = {}) {
185
+ return serializeWorkPaperDocument(this.exportDocument(options));
186
+ }
187
+ saveJson(options = {}) {
188
+ return this.serialize(options);
189
+ }
190
+ restoreJson(input) {
191
+ return restoreA1WorkPaper(input, this.facadeOptions());
192
+ }
193
+ dispose() {
194
+ this.workpaper.dispose();
195
+ }
196
+ facadeOptions() {
197
+ return {
198
+ ...(this.defaultSheetName !== undefined ? { defaultSheetName: this.defaultSheetName } : {}),
199
+ ...(this.writableSheets !== undefined ? { writableSheets: this.writableSheets } : {}),
200
+ limitations: this.limitations,
201
+ };
202
+ }
203
+ readParsedCell(address) {
204
+ const formula = this.workpaper.getCellFormula(address);
205
+ return {
206
+ address: this.formatCell(address),
207
+ value: this.workpaper.getCellValue(address),
208
+ serialized: this.workpaper.getCellSerialized(address),
209
+ formula: formula ?? null,
210
+ displayValue: this.workpaper.getCellDisplayValue(address),
211
+ formulaDiagnostics: this.workpaper.getCellFormulaDiagnostics(address),
212
+ };
213
+ }
214
+ readRangeCells(range) {
215
+ const rows = [];
216
+ for (let row = range.start.row; row <= range.end.row; row += 1) {
217
+ const cells = [];
218
+ for (let col = range.start.col; col <= range.end.col; col += 1) {
219
+ cells.push(this.readParsedCell({ sheet: range.start.sheet, row, col }));
220
+ }
221
+ rows.push(cells);
222
+ }
223
+ return rows;
224
+ }
225
+ prepareEdits(edits) {
226
+ const prepared = [];
227
+ const seen = new Set();
228
+ for (const [address, value] of Object.entries(edits)) {
229
+ const parsed = this.requireCellAddress(address);
230
+ const formatted = this.formatCell(parsed);
231
+ if (seen.has(formatted)) {
232
+ throw new Error(`Duplicate WorkPaper edit target: ${formatted}`);
233
+ }
234
+ this.assertWritable(parsed);
235
+ this.assertFormulaCanParse(value, parsed);
236
+ seen.add(formatted);
237
+ prepared.push({ address: formatted, parsed, value });
238
+ }
239
+ if (prepared.length === 0) {
240
+ throw new Error('At least one WorkPaper edit is required');
241
+ }
242
+ return prepared;
243
+ }
244
+ applyPreparedEdits(edits) {
245
+ return this.workpaper.transaction(() => {
246
+ for (const edit of edits) {
247
+ this.workpaper.setCellContents(edit.parsed, edit.value);
248
+ }
249
+ });
250
+ }
251
+ readPreparedCells(edits) {
252
+ const reads = {};
253
+ for (const edit of edits) {
254
+ reads[edit.address] = this.readParsedCell(edit.parsed);
255
+ }
256
+ return reads;
257
+ }
258
+ requireCellAddress(address) {
259
+ this.assertDefaultSheetAllowsUnqualifiedAddress(address, 'cell address');
260
+ const parsed = this.workpaper.simpleCellAddressFromString(address, this.defaultSheetId());
261
+ if (parsed === undefined) {
262
+ throw new Error(`Invalid WorkPaper cell address: ${address}`);
263
+ }
264
+ return parsed;
265
+ }
266
+ requireRange(range) {
267
+ this.assertDefaultSheetAllowsUnqualifiedAddress(range, 'range');
268
+ const parsedRange = this.workpaper.simpleCellRangeFromString(range, this.defaultSheetId());
269
+ if (parsedRange !== undefined) {
270
+ return parsedRange;
271
+ }
272
+ const parsedCell = this.workpaper.simpleCellAddressFromString(range, this.defaultSheetId());
273
+ if (parsedCell !== undefined) {
274
+ return {
275
+ start: parsedCell,
276
+ end: parsedCell,
277
+ };
278
+ }
279
+ throw new Error(`Invalid WorkPaper range: ${range}`);
280
+ }
281
+ assertDefaultSheetAllowsUnqualifiedAddress(value, kind) {
282
+ if (this.defaultSheetName === undefined && !value.includes('!')) {
283
+ throw new Error(`Sheet-qualified WorkPaper ${kind} required: ${value}`);
284
+ }
285
+ }
286
+ defaultSheetId() {
287
+ if (this.defaultSheetName === undefined) {
288
+ return undefined;
289
+ }
290
+ const sheetId = this.workpaper.getSheetId(this.defaultSheetName);
291
+ if (sheetId === undefined) {
292
+ throw new Error(`Expected default sheet "${this.defaultSheetName}" to exist`);
293
+ }
294
+ return sheetId;
295
+ }
296
+ assertWritable(address) {
297
+ if (this.writableSheets === undefined) {
298
+ return;
299
+ }
300
+ const sheetName = this.workpaper.getSheetName(address.sheet);
301
+ if (sheetName === undefined || !this.writableSheets.includes(sheetName)) {
302
+ throw new Error(`Sheet "${sheetName ?? address.sheet}" is not writable`);
303
+ }
304
+ }
305
+ assertFormulaCanParse(value, address) {
306
+ if (typeof value !== 'string' || !value.startsWith('=')) {
307
+ return;
308
+ }
309
+ if (!this.workpaper.validateFormula(value)) {
310
+ throw new Error(`Invalid WorkPaper formula for ${this.formatCell(address)}: ${value}`);
311
+ }
312
+ }
313
+ formatCell(address) {
314
+ return this.workpaper.simpleCellAddressToString(address, {
315
+ includeSheetName: true,
316
+ });
317
+ }
318
+ formatRange(range) {
319
+ if (sameAddress(range.start, range.end)) {
320
+ return this.formatCell(range.start);
321
+ }
322
+ return this.workpaper.simpleCellRangeToString(range, {
323
+ includeSheetName: true,
324
+ });
325
+ }
326
+ }
327
+ function rangeComparison(read) {
328
+ return read.cells.map((row) => row.map(cellComparison));
329
+ }
330
+ function rangeComparisonExcludingEditedCells(read, editedCells) {
331
+ return read.cells.map((row) => row.filter((cell) => !editedCells.has(cell.address)).map(cellComparison)).filter((row) => row.length > 0);
332
+ }
333
+ function readbackEditedFormulaCells(read, editedCells) {
334
+ return read.cells.flatMap((row) => row.filter((cell) => editedCells.has(cell.address) && cell.formula !== null));
335
+ }
336
+ function rangeCellAddresses(read) {
337
+ return read.cells.flatMap((row) => row.map((cell) => cell.address));
338
+ }
339
+ function cellComparison(read) {
340
+ return {
341
+ address: read.address,
342
+ displayValue: read.displayValue,
343
+ formula: read.formula,
344
+ serialized: read.serialized,
345
+ value: read.value,
346
+ };
347
+ }
348
+ function serializedByAddress(reads) {
349
+ const serialized = {};
350
+ for (const [address, read] of Object.entries(reads)) {
351
+ serialized[address] = read.serialized;
352
+ }
353
+ return serialized;
354
+ }
355
+ function sameJson(left, right) {
356
+ return JSON.stringify(left) === JSON.stringify(right);
357
+ }
358
+ function sameAddress(left, right) {
359
+ return left.sheet === right.sheet && left.row === right.row && left.col === right.col;
360
+ }
361
+ //# sourceMappingURL=a1.js.map