bilig-workpaper 0.160.2 → 0.164.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/AGENTS.md +23 -12
- package/README.md +13 -14
- package/SKILL.md +20 -11
- package/bin/bilig-evaluate.js +1 -3
- package/dist/a1.d.ts +118 -0
- package/dist/a1.js +361 -0
- package/dist/a1.js.map +1 -0
- package/dist/evaluator-bin.d.ts +2 -0
- package/dist/evaluator-bin.js +4 -0
- package/dist/evaluator-bin.js.map +1 -0
- package/dist/evaluator.d.ts +57 -0
- package/dist/evaluator.js +352 -0
- package/dist/evaluator.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/work-paper-mcp-stdio-bin.js +12 -7
- package/dist/work-paper-mcp-stdio-bin.js.map +1 -1
- package/dist/work-paper-mcp-xlsx-risk-tool.js +6 -6
- package/dist/work-paper-mcp-xlsx-risk-tool.js.map +1 -1
- package/dist/xlsx-recalc-targets.d.ts +15 -0
- package/dist/xlsx-recalc-targets.js +89 -0
- package/dist/xlsx-recalc-targets.js.map +1 -0
- package/dist/xlsx-recalc.d.ts +126 -0
- package/dist/xlsx-recalc.js +605 -0
- package/dist/xlsx-recalc.js.map +1 -0
- package/dist/xlsx.d.ts +4 -0
- package/dist/xlsx.js +8 -0
- package/dist/xlsx.js.map +1 -1
- package/package.json +10 -4
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 {
|
|
118
|
+
import { buildA1WorkPaper } from 'bilig-workpaper'
|
|
110
119
|
|
|
111
|
-
const
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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 {
|
|
16
|
+
import { buildA1WorkPaper } from 'bilig-workpaper'
|
|
17
17
|
|
|
18
|
-
const
|
|
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
|
|
31
|
-
|
|
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(
|
|
41
|
-
|
|
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
|
-
|
|
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 {
|
|
151
|
+
import { buildA1WorkPaper } from 'bilig-workpaper'
|
|
146
152
|
|
|
147
|
-
const
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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
|
package/bin/bilig-evaluate.js
CHANGED
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
|