@cellrune/node 0.1.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/index.d.ts ADDED
@@ -0,0 +1,326 @@
1
+ import type { Buffer } from "node:buffer";
2
+
3
+ export const SCHEMA_VERSION: number;
4
+
5
+ export type CellRuneErrorKind =
6
+ | "input"
7
+ | "validation"
8
+ | "read"
9
+ | "write"
10
+ | "state";
11
+
12
+ export interface ErrorDetails {
13
+ readonly sourceCode: string | null;
14
+ readonly sourceId: string | null;
15
+ readonly detail: string | null;
16
+ }
17
+
18
+ export class CellRuneError extends Error {
19
+ private constructor();
20
+ readonly code: string;
21
+ readonly kind: CellRuneErrorKind;
22
+ readonly details: ErrorDetails;
23
+ }
24
+
25
+ export type CellValue =
26
+ | { readonly kind: "blank" }
27
+ | { readonly kind: "number"; readonly value: number }
28
+ | { readonly kind: "text"; readonly value: string }
29
+ | { readonly kind: "logical"; readonly value: boolean }
30
+ | { readonly kind: "error"; readonly value: string }
31
+ | { readonly kind: "unsupported" };
32
+
33
+ export type CalculationResult =
34
+ | { readonly kind: "value"; readonly value: CellValue }
35
+ | {
36
+ readonly kind: "unavailable";
37
+ readonly code: string;
38
+ readonly message: string;
39
+ readonly detail: string | null;
40
+ };
41
+
42
+ export interface Cell {
43
+ readonly address: string;
44
+ readonly formula: string | null;
45
+ readonly sourceValue: CellValue;
46
+ readonly sourceValueState: "literal" | "saved" | "missing" | "invalid";
47
+ readonly calculated: CalculationResult | null;
48
+ }
49
+
50
+ export interface RangePage {
51
+ readonly schemaVersion: number;
52
+ readonly sheet: string;
53
+ readonly start: string;
54
+ readonly end: string;
55
+ readonly totalCells: number;
56
+ readonly offset: number;
57
+ readonly nextOffset: number | null;
58
+ readonly cells: readonly Cell[];
59
+ }
60
+
61
+ export interface SheetSummary {
62
+ readonly id: number;
63
+ readonly name: string;
64
+ readonly visibility: "visible" | "hidden" | "very_hidden";
65
+ readonly cellCount: number;
66
+ readonly usedRange: string | null;
67
+ }
68
+
69
+ export interface WorkbookSummary {
70
+ readonly schemaVersion: number;
71
+ readonly semanticRevision: bigint;
72
+ readonly documentBacked: boolean;
73
+ readonly documentKind: "xlsx" | "xlsm" | "new_xlsx" | "open_xml";
74
+ readonly dateSystem: "excel_1900" | "excel_1904";
75
+ readonly diagnosticCount: number;
76
+ readonly sheets: readonly SheetSummary[];
77
+ }
78
+
79
+ export interface CalculationOptions {
80
+ readonly todaySerial?: number;
81
+ readonly nowSerial?: number;
82
+ }
83
+
84
+ export interface CalculationReport {
85
+ readonly schemaVersion: number;
86
+ readonly semanticRevision: bigint;
87
+ readonly formulaCount: number;
88
+ readonly valueCount: number;
89
+ readonly unavailableCount: number;
90
+ readonly materializedCellCount: number;
91
+ }
92
+
93
+ export type RecalculationMode = "auto" | "incremental" | "full";
94
+ export type CalculationExecutionMode = "incremental" | "full";
95
+ export type CalculationDecisionReason =
96
+ | "initial_calculation"
97
+ | "full_requested"
98
+ | "incremental_requested"
99
+ | "dirty_subset"
100
+ | "no_dirty_formulas"
101
+ | "topology_changed"
102
+ | "options_changed"
103
+ | "dynamic_topology"
104
+ | "dirty_set_covers_workbook"
105
+ | "unknown";
106
+
107
+ export interface RecalculationOptions extends CalculationOptions {
108
+ readonly mode?: RecalculationMode;
109
+ }
110
+
111
+ export interface CellReference {
112
+ readonly sheetId: number;
113
+ readonly sheetName: string;
114
+ readonly address: string;
115
+ }
116
+
117
+ export type WritableCellValue = Exclude<CellValue, { readonly kind: "unsupported" }>;
118
+
119
+ export type NumberFormatKind =
120
+ | "general"
121
+ | "number"
122
+ | "date"
123
+ | "time"
124
+ | "date_time"
125
+ | "duration";
126
+
127
+ export type SheetVisibility = "visible" | "hidden" | "very_hidden";
128
+
129
+ export type WorkbookChange =
130
+ | {
131
+ readonly kind: "setValue";
132
+ readonly sheet: string;
133
+ readonly address: string;
134
+ readonly value: WritableCellValue;
135
+ }
136
+ | {
137
+ readonly kind: "setFormula";
138
+ readonly sheet: string;
139
+ readonly address: string;
140
+ readonly formula: string;
141
+ readonly dynamicRange?: string | null;
142
+ }
143
+ | {
144
+ readonly kind: "clearCell";
145
+ readonly sheet: string;
146
+ readonly address: string;
147
+ }
148
+ | {
149
+ readonly kind: "setNumberFormat";
150
+ readonly sheet: string;
151
+ readonly address: string;
152
+ readonly id: number;
153
+ readonly code?: string | null;
154
+ readonly formatKind: NumberFormatKind;
155
+ }
156
+ | { readonly kind: "addSheet"; readonly name: string }
157
+ | {
158
+ readonly kind: "renameSheet";
159
+ readonly sheet: string;
160
+ readonly newName: string;
161
+ }
162
+ | {
163
+ readonly kind: "setSheetVisibility";
164
+ readonly sheet: string;
165
+ readonly visibility: SheetVisibility;
166
+ }
167
+ | {
168
+ readonly kind: "setDefinedName";
169
+ readonly name: string;
170
+ readonly scopeSheet?: string | null;
171
+ readonly formula: string;
172
+ readonly hidden: boolean;
173
+ }
174
+ | {
175
+ readonly kind: "removeDefinedName";
176
+ readonly name: string;
177
+ readonly scopeSheet?: string | null;
178
+ }
179
+ | {
180
+ readonly kind: "setDateSystem";
181
+ readonly dateSystem: "excel_1900" | "excel_1904";
182
+ }
183
+ | {
184
+ readonly kind: "setCalculationHints";
185
+ readonly mode?:
186
+ | "automatic"
187
+ | "automatic_except_data_tables"
188
+ | "manual"
189
+ | null;
190
+ readonly calculationId?: number | null;
191
+ readonly fullCalculationOnLoad?: boolean | null;
192
+ readonly forceFullCalculation?: boolean | null;
193
+ };
194
+
195
+ export interface EditReceipt {
196
+ readonly schemaVersion: number;
197
+ readonly baseRevision: bigint;
198
+ readonly resultRevision: bigint;
199
+ readonly appliedChangeCount: number;
200
+ readonly changedCells: readonly CellReference[];
201
+ readonly calculationChangedCells: readonly CellReference[];
202
+ readonly createdSheetIds: readonly number[];
203
+ readonly topologyChanged: boolean;
204
+ readonly calculationMetadataChanged: boolean;
205
+ }
206
+
207
+ export interface CalculationDeltaCell {
208
+ readonly cell: CellReference;
209
+ readonly origin:
210
+ | "direct_formula"
211
+ | "legacy_array"
212
+ | "dynamic_spill"
213
+ | "unknown";
214
+ readonly anchor: CellReference | null;
215
+ readonly range: string | null;
216
+ readonly result: CalculationResult;
217
+ }
218
+
219
+ export interface CalculationDelta {
220
+ readonly schemaVersion: number;
221
+ readonly cursor: bigint;
222
+ readonly baseRevision: bigint;
223
+ readonly resultRevision: bigint;
224
+ readonly mode: CalculationExecutionMode;
225
+ readonly reason: CalculationDecisionReason;
226
+ readonly dirtyCount: number;
227
+ readonly evaluatedCount: number;
228
+ readonly parsedFormulaCount: number;
229
+ readonly changedCells: readonly CalculationDeltaCell[];
230
+ readonly removedMaterializedCells: readonly CellReference[];
231
+ }
232
+
233
+ export interface CalculationDeltaPage {
234
+ readonly schemaVersion: number;
235
+ readonly requestedCursor: bigint;
236
+ readonly nextCursor: bigint | null;
237
+ readonly deltas: readonly CalculationDelta[];
238
+ }
239
+
240
+ export interface FunctionUsageEntry {
241
+ readonly name: string;
242
+ readonly supported: boolean;
243
+ readonly callCount: number;
244
+ readonly formulaCount: number;
245
+ readonly sampleCells: readonly CellReference[];
246
+ }
247
+
248
+ export interface FunctionUsageReport {
249
+ readonly schemaVersion: number;
250
+ readonly formulaCount: number;
251
+ readonly parsedFormulaCount: number;
252
+ readonly unparsedFormulaCount: number;
253
+ readonly entries: readonly FunctionUsageEntry[];
254
+ }
255
+
256
+ export interface WriteOptions {
257
+ readonly invalidateUnavailable?: boolean;
258
+ }
259
+
260
+ export interface SaveOptions extends WriteOptions {
261
+ readonly replaceExisting?: boolean;
262
+ }
263
+
264
+ export interface WriteReport {
265
+ readonly schemaVersion: number;
266
+ readonly complete: boolean;
267
+ readonly policy: "require_complete" | "invalidate_unavailable" | "unknown";
268
+ readonly materializedCount: number;
269
+ readonly invalidatedCells: readonly CellReference[];
270
+ readonly changedParts: readonly string[];
271
+ readonly removedParts: readonly string[];
272
+ readonly diagnosticCount: number;
273
+ }
274
+
275
+ export class Workbook {
276
+ private constructor();
277
+ static create(): Workbook;
278
+ static openPath(path: string): Promise<Workbook>;
279
+ static fromBytes(bytes: Buffer | Uint8Array): Promise<Workbook>;
280
+ get closed(): boolean;
281
+ close(): void;
282
+ summary(): WorkbookSummary;
283
+ readRange(
284
+ sheet: string,
285
+ start: string,
286
+ end: string,
287
+ options?: { readonly offset?: number; readonly limit?: number },
288
+ ): RangePage;
289
+ functionUsage(): FunctionUsageReport;
290
+ calculate(options?: CalculationOptions): Promise<CalculationReport>;
291
+ recalculate(options?: RecalculationOptions): Promise<CalculationDelta>;
292
+ applyChanges(
293
+ expectedRevision: bigint,
294
+ changes: readonly WorkbookChange[],
295
+ ): EditReceipt;
296
+ changesSince(
297
+ cursor?: bigint,
298
+ options?: { readonly limit?: number },
299
+ ): CalculationDeltaPage;
300
+ cancelCalculation(): boolean;
301
+ calculationActive(): boolean;
302
+ setBlank(sheet: string, address: string): void;
303
+ setNumber(sheet: string, address: string, value: number): void;
304
+ setText(sheet: string, address: string, value: string): void;
305
+ setLogical(sheet: string, address: string, value: boolean): void;
306
+ setError(sheet: string, address: string, value: string): void;
307
+ setFormula(
308
+ sheet: string,
309
+ address: string,
310
+ formula: string,
311
+ options?: { readonly dynamicRange?: string },
312
+ ): void;
313
+ clearCell(sheet: string, address: string): boolean;
314
+ addSheet(name: string): number;
315
+ renameSheet(currentName: string, newName: string): void;
316
+ toBytes(options?: WriteOptions): Promise<Buffer>;
317
+ save(path: string, options?: SaveOptions): Promise<WriteReport>;
318
+ }
319
+
320
+ declare const packageExports: {
321
+ readonly SCHEMA_VERSION: typeof SCHEMA_VERSION;
322
+ readonly CellRuneError: typeof CellRuneError;
323
+ readonly Workbook: typeof Workbook;
324
+ };
325
+
326
+ export default packageExports;
package/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import packageExports from "./wrapper.js";
2
+
3
+ export const SCHEMA_VERSION = packageExports.SCHEMA_VERSION;
4
+ export const CellRuneError = packageExports.CellRuneError;
5
+ export const Workbook = packageExports.Workbook;
6
+ export default packageExports;
package/lib/changes.js ADDED
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+
3
+ const { inputError } = require("./errors.js");
4
+ const {
5
+ requireFinite,
6
+ requireNonNegativeInteger,
7
+ requireOptionalBoolean,
8
+ requireOptions,
9
+ requireString,
10
+ } = require("./validation.js");
11
+
12
+ function serializeWorkbookChange(change, index) {
13
+ requireOptions(change);
14
+ requireString(change.kind, `changes[${index}].kind`);
15
+ switch (change.kind) {
16
+ case "setValue":
17
+ return {
18
+ kind: "set_value",
19
+ sheet: requiredChangeString(change.sheet, index, "sheet"),
20
+ address: requiredChangeString(change.address, index, "address"),
21
+ value: serializeCellValue(change.value, index),
22
+ };
23
+ case "setFormula":
24
+ if (
25
+ change.dynamicRange !== undefined &&
26
+ change.dynamicRange !== null
27
+ ) {
28
+ requireString(change.dynamicRange, `changes[${index}].dynamicRange`);
29
+ }
30
+ return {
31
+ kind: "set_formula",
32
+ sheet: requiredChangeString(change.sheet, index, "sheet"),
33
+ address: requiredChangeString(change.address, index, "address"),
34
+ formula: requiredChangeString(change.formula, index, "formula"),
35
+ dynamic_range: change.dynamicRange ?? null,
36
+ };
37
+ case "clearCell":
38
+ return {
39
+ kind: "clear_cell",
40
+ sheet: requiredChangeString(change.sheet, index, "sheet"),
41
+ address: requiredChangeString(change.address, index, "address"),
42
+ };
43
+ case "setNumberFormat":
44
+ requireNonNegativeInteger(change.id, `changes[${index}].id`);
45
+ if (change.code !== undefined && change.code !== null) {
46
+ requireString(change.code, `changes[${index}].code`);
47
+ }
48
+ return {
49
+ kind: "set_number_format",
50
+ sheet: requiredChangeString(change.sheet, index, "sheet"),
51
+ address: requiredChangeString(change.address, index, "address"),
52
+ id: change.id,
53
+ code: change.code ?? null,
54
+ format_kind: requiredChangeString(
55
+ change.formatKind,
56
+ index,
57
+ "formatKind",
58
+ ),
59
+ };
60
+ case "addSheet":
61
+ return {
62
+ kind: "add_sheet",
63
+ name: requiredChangeString(change.name, index, "name"),
64
+ };
65
+ case "renameSheet":
66
+ return {
67
+ kind: "rename_sheet",
68
+ sheet: requiredChangeString(change.sheet, index, "sheet"),
69
+ new_name: requiredChangeString(change.newName, index, "newName"),
70
+ };
71
+ case "setSheetVisibility":
72
+ return {
73
+ kind: "set_sheet_visibility",
74
+ sheet: requiredChangeString(change.sheet, index, "sheet"),
75
+ visibility: requiredChangeString(
76
+ change.visibility,
77
+ index,
78
+ "visibility",
79
+ ),
80
+ };
81
+ case "setDefinedName":
82
+ if (change.scopeSheet !== undefined && change.scopeSheet !== null) {
83
+ requireString(change.scopeSheet, `changes[${index}].scopeSheet`);
84
+ }
85
+ if (typeof change.hidden !== "boolean") {
86
+ throw inputError(`changes[${index}].hidden must be a boolean`);
87
+ }
88
+ return {
89
+ kind: "set_defined_name",
90
+ name: requiredChangeString(change.name, index, "name"),
91
+ scope_sheet: change.scopeSheet ?? null,
92
+ formula: requiredChangeString(change.formula, index, "formula"),
93
+ hidden: change.hidden,
94
+ };
95
+ case "removeDefinedName":
96
+ if (change.scopeSheet !== undefined && change.scopeSheet !== null) {
97
+ requireString(change.scopeSheet, `changes[${index}].scopeSheet`);
98
+ }
99
+ return {
100
+ kind: "remove_defined_name",
101
+ name: requiredChangeString(change.name, index, "name"),
102
+ scope_sheet: change.scopeSheet ?? null,
103
+ };
104
+ case "setDateSystem":
105
+ return {
106
+ kind: "set_date_system",
107
+ date_system: requiredChangeString(
108
+ change.dateSystem,
109
+ index,
110
+ "dateSystem",
111
+ ),
112
+ };
113
+ case "setCalculationHints":
114
+ if (change.mode !== undefined && change.mode !== null) {
115
+ requireString(change.mode, `changes[${index}].mode`);
116
+ }
117
+ if (
118
+ change.calculationId !== undefined &&
119
+ change.calculationId !== null
120
+ ) {
121
+ requireNonNegativeInteger(
122
+ change.calculationId,
123
+ `changes[${index}].calculationId`,
124
+ );
125
+ }
126
+ requireOptionalBoolean(
127
+ change.fullCalculationOnLoad,
128
+ `changes[${index}].fullCalculationOnLoad`,
129
+ );
130
+ requireOptionalBoolean(
131
+ change.forceFullCalculation,
132
+ `changes[${index}].forceFullCalculation`,
133
+ );
134
+ return {
135
+ kind: "set_calculation_hints",
136
+ mode: change.mode ?? null,
137
+ calculation_id: change.calculationId ?? null,
138
+ full_calculation_on_load: change.fullCalculationOnLoad ?? null,
139
+ force_full_calculation: change.forceFullCalculation ?? null,
140
+ };
141
+ default:
142
+ throw inputError(`changes[${index}].kind is not recognized`);
143
+ }
144
+ }
145
+
146
+ function serializeCellValue(value, index) {
147
+ requireOptions(value);
148
+ requireString(value.kind, `changes[${index}].value.kind`);
149
+ switch (value.kind) {
150
+ case "blank":
151
+ return { kind: "blank" };
152
+ case "number":
153
+ requireFinite(value.value, `changes[${index}].value.value`);
154
+ return { kind: "number", value: value.value };
155
+ case "text":
156
+ requireString(value.value, `changes[${index}].value.value`);
157
+ return { kind: "text", value: value.value };
158
+ case "logical":
159
+ if (typeof value.value !== "boolean") {
160
+ throw inputError(`changes[${index}].value.value must be a boolean`);
161
+ }
162
+ return { kind: "logical", value: value.value };
163
+ case "error":
164
+ requireString(value.value, `changes[${index}].value.value`);
165
+ return { kind: "error", value: value.value };
166
+ default:
167
+ throw inputError(`changes[${index}].value.kind is not writable`);
168
+ }
169
+ }
170
+
171
+ function requiredChangeString(value, index, name) {
172
+ requireString(value, `changes[${index}].${name}`);
173
+ return value;
174
+ }
175
+
176
+ module.exports = { serializeWorkbookChange };
package/lib/errors.js ADDED
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+
3
+ const ERROR_PREFIX = "CELLRUNE_ERROR:";
4
+ const ERROR = Object.freeze({
5
+ CLOSED: ["interop.session.closed", "workbook session is closed"],
6
+ ARGUMENT: ["interop.input.invalid", "argument failed runtime validation"],
7
+ PROTOCOL: [
8
+ "interop.binding.protocol",
9
+ "native binding returned an invalid typed value",
10
+ ],
11
+ });
12
+
13
+ class CellRuneError extends Error {
14
+ constructor(message, code, kind, details = {}) {
15
+ super(message);
16
+ this.name = "CellRuneError";
17
+ this.code = code;
18
+ this.kind = kind;
19
+ this.details = {
20
+ sourceCode: details.sourceCode ?? null,
21
+ sourceId: details.sourceId ?? null,
22
+ detail: details.detail ?? null,
23
+ };
24
+ }
25
+ }
26
+
27
+ function withSyncErrors(operation) {
28
+ try {
29
+ return operation();
30
+ } catch (error) {
31
+ throw convertError(error);
32
+ }
33
+ }
34
+
35
+ async function withErrors(promise) {
36
+ try {
37
+ return await promise;
38
+ } catch (error) {
39
+ throw convertError(error);
40
+ }
41
+ }
42
+
43
+ function convertError(error) {
44
+ if (error instanceof CellRuneError) {
45
+ return error;
46
+ }
47
+ const message = error instanceof Error ? error.message : String(error);
48
+ const marker = message.indexOf(ERROR_PREFIX);
49
+ if (marker >= 0) {
50
+ try {
51
+ const payload = JSON.parse(message.slice(marker + ERROR_PREFIX.length));
52
+ if (
53
+ typeof payload.code === "string" &&
54
+ typeof payload.kind === "string" &&
55
+ typeof payload.message === "string" &&
56
+ payload.details &&
57
+ typeof payload.details === "object"
58
+ ) {
59
+ return new CellRuneError(payload.message, payload.code, payload.kind, {
60
+ sourceCode: payload.details.source_code,
61
+ sourceId: payload.details.source_id,
62
+ detail: payload.details.detail,
63
+ });
64
+ }
65
+ } catch {
66
+ return protocolError("native error payload is malformed");
67
+ }
68
+ }
69
+ return error instanceof Error ? error : new Error(message);
70
+ }
71
+
72
+ function closedError() {
73
+ return new CellRuneError(ERROR.CLOSED[1], ERROR.CLOSED[0], "state");
74
+ }
75
+
76
+ function inputError(detail) {
77
+ return new CellRuneError(ERROR.ARGUMENT[1], ERROR.ARGUMENT[0], "input", {
78
+ detail,
79
+ });
80
+ }
81
+
82
+ function protocolError(detail) {
83
+ return new CellRuneError(ERROR.PROTOCOL[1], ERROR.PROTOCOL[0], "state", {
84
+ detail,
85
+ });
86
+ }
87
+
88
+ module.exports = {
89
+ CellRuneError,
90
+ closedError,
91
+ inputError,
92
+ protocolError,
93
+ withErrors,
94
+ withSyncErrors,
95
+ };