@rowsncolumns/rnc-engine
v1.2.0
Published
Typed JS/TS interop for the Rust→WASM spreadsheet calc engine (rnc-wasm). Ergonomic JSON-in/out wrapper over the wasm-bindgen surface: applyCommand(Command)→CommitResult, readWindow→WindowSnapshot, and Map<"sheet!A1",CellFormat> style folding. Pluggable b
Downloads
38,921
Readme
@rowsncolumns/rnc-engine
Typed JS/TS interop for the Rust→WASM spreadsheet calc engine (rust/crates/rnc-wasm).
The low-level binding is generated by wasm-bindgen (rnc-wasm package: rnc_wasm.js + .d.ts +
_bg.wasm). This package is the thin, idiomatic layer on top: it parses/serializes the JSON the wasm
surface speaks and gives you typed methods using the exact rnc-model / commands.ts shapes.
Prerequisite — build the wasm package
cd rust
wasm-pack build crates/rnc-wasm --target web --out-dir pkg@rowsncolumns/rnc-engine depends on that output via "rnc-wasm": "file:../../rust/crates/rnc-wasm/pkg",
so build it before yarn install (or rebuild + reinstall after engine changes).
Usage
import { RncEngine } from "@rowsncolumns/rnc-engine";
import type { Command } from "@rowsncolumns/spreadsheet-state";
const engine = await RncEngine.create([{ sheetId: 1, title: "Sheet1" }]);
// Subscribe for results — the engine streams a complete change-set (computed formula results AND
// their dependents, structural shifts, cleared cells) for every command.
const unsubscribe = engine.onChanges((commit) => {
for (const c of commit.changes) {
// c.sheetId / c.rowIndex / c.columnIndex / c.formatted / c.number / c.removed
}
});
// Just send commands — results arrive via the subscriber above.
engine.applyCommand({
command: "set-value",
sheetId: 1,
coords: { rowIndex: 1, columnIndex: 1 },
value: "=SUM(A2:A10)",
} as Command);
// (applyCommand also returns the same CommitResult inline, for callers that prefer pull.)
// Read a windowed rectangle for lazy/virtualized rendering.
const win = engine.readWindow(1, 1, 1, 50, 26);
const styleMap = RncEngine.toStyleMap(win.styles); // Map<"1!A1", CellFormat>Wire it behind useSpreadsheetState: onCommand sends each command into the engine, and an
onChanges subscriber streams computed values back into the grid — see
libs/storybook/stories/spreadsheet-rust-wasm.stories.tsx. The UI does nothing but send + subscribe.
Why the subscriber lives in TS, not as a wasm callback
In-thread, Rust has no event loop — every entry is JS-initiated — and you can't safely invoke a JS
callback from inside a &mut self wasm method (it re-enters mid-borrow → "recursive use / unsafe
aliasing"). So applyCommand returns first (releasing the borrow), then RncEngine notifies
subscribers, who may safely call back into readWindow. The same onChanges API is the seam for
out-of-band changes later (volatile recalc ticks, or Worker postMessage when calc moves off the
main thread) — without the UI's subscribe code changing.
API
| Member | Returns | Notes |
|---|---|---|
| RncEngine.create(sheets?) | Promise<RncEngine> | Initializes wasm (idempotent) + registers sheets. |
| engine.addSheet(id, name) | void | Register a sheet. |
| engine.applyCommand(command) | CommitResult | Sends a command; notifies onChanges subscribers. Also returns { changes, styleChanges, fullResyncSheets } inline. |
| engine.onChanges(listener) | () => void | Subscribe to change-sets; returns an unsubscribe fn. |
| engine.readWindow(sheetId, sr, sc, er, ec) | WindowSnapshot | 1-indexed, inclusive. { cells, styles }. |
| engine.sheetIds() | number[] | Registered sheet ids, in order. |
| RncEngine.toStyleMap(styles) | Map<string, CellFormat> | Folds StyleEntry[] into "sheetId!A1" keys. |
| cellKey(sheetId, row, col) / columnLabel(col) | string | A1 helpers. |
All shapes (CommitResult, CellChange, WindowSnapshot, WindowCell, StyleEntry) mirror the
rnc-store Rust structs exactly and reuse the canonical contract types — coordinates are
CellInterface (rowIndex/columnIndex), windows are SheetRange, sheet-scoped cells are
SheetCoordinate. So the JSON is { rowIndex, columnIndex, sheetId, startRowIndex, … }, never
ad-hoc row/col/startRow keys. On the Rust side these are #[serde(flatten)]-embedded.
Publishing note:
main/modulepoint at./srcfor zero-build consumption inside the monorepo (Vite/esbuild compile the TS source). To publish standalone, runyarn build, vendor thernc-wasmoutput into the package, and pointmain/module/typesat./dist.
