API reference · Core
Engine
The engine owns the WebAssembly memory and the worker threads; every table, dataset and query belongs to one.
NodeBrowserEdge
An Engine holds one block of WebAssembly memory (at most 4 GB) and, where the runtime allows it, one worker thread per core. Tables are made through it and keep their columns in its memory. Each entry point exports its own Engine: the Node one reads files and spawns worker threads, the browser one uses Web Workers on cross-origin-isolated pages, and the portable one runs on one thread with no Node or browser APIs. The core methods below are the same in all three.
import { Engine } from 'vectrojs'; // Node: worker threads on every core
import { Engine } from 'vectrojs/browser'; // browsers: Web Workers when isolated, else one thread
import { Engine } from 'vectrojs/portable'; // Deno, Bun, Cloudflare Workers: one thread
Making an engine
new Engine(options?)
Starts the threads and compiles the static kernels. In Node the worker threads are unref’d, so a script can exit without close(), but closing is still the clean way to stop them.
| Name | Type | Default | Meaning |
|---|---|---|---|
threads |
number | all cores | Threads to use, the calling thread included. Needs a platform with workers and SharedArrayBuffer; otherwise 1. |
jit |
true, false or 'auto' |
'auto' |
true compiles one SIMD kernel per expression; false runs expressions on the vectorized interpreter; 'auto' compiles wherever the runtime allows it. |
wasmModule |
WebAssembly.Module |
none | The precompiled dist/vectro.wasm, for runtimes that can’t compile WebAssembly (Cloudflare Workers). Single-threaded only. |
initialMemoryMB |
number | 16 on one thread, 0 with threads | Memory to reserve at the start. Plain (non-shared) memory moves when it grows, so starting bigger avoids copies. |
spin |
number | 20000 (Node), 0 (browser) | How long idle workers spin before they sleep. |
minParallel |
object | tuned per operation | Row counts below which an operation runs on the calling thread alone. |
wasm |
boolean | true |
false uses JavaScript kernels for the low-level array operations. |
platform |
object | set by the entry point | Thread support. Node passes nodePlatform; leave it alone. |
const engine = new Engine(); // Node: every core
const small = new Engine({ threads: 2 });
On Cloudflare Workers, bundle the precompiled module and pass it in:
import { Engine } from 'vectrojs/portable';
import wasmModule from 'vectrojs/dist/vectro.wasm';
const engine = new Engine({ wasmModule });
See On the edge.
canCompileWasm()
Returns true when the runtime can compile WebAssembly from bytes. Cloudflare Workers and Vercel Edge return false; there the engine needs wasmModule. Exported from every entry point.
nodePlatform
The object the Node entry passes as platform: cpus(), spawn(workerData) and receive(port). Exported for completeness; you don’t need it to make an engine.
Making tables
Each of these returns a Table whose columns live in this engine’s memory.
engine.table(columns, options?)
Builds a table from columns, copying them into engine memory. A column is a Float64Array, Float32Array or number[] (numbers), a string[] (a category column: each distinct string gets a code), an array of Date objects (a date column), or { codes, labels } (codes into a labels array, a category column with no string lookups). All columns must have the same length.
| Name | Type | Default | Meaning |
|---|---|---|---|
type |
Float64Array or Float32Array |
Float64Array |
Storage for every column. Float32Array halves memory but loses precision; tables with date columns are refused (times don’t fit in float32). |
dates |
string[] |
[] |
Columns that hold dates as milliseconds or ISO text. Arrays of Date objects are dates anyway. |
compress |
boolean | false |
Store each column in 1, 2 or 4 bytes per row where every value comes back exactly. See Compressed columns. |
Column names must be letters, digits and _, not starting with a digit.
import { Engine } from 'vectrojs';
const engine = new Engine();
const orders = engine.table({
region: ['EU', 'US', 'EU', 'APAC'],
price: [120, 80, 45, 300],
qty: [2, 1, 4, 1],
ts: [new Date('2024-03-01'), new Date('2024-03-02'), new Date('2024-03-02'), new Date('2024-03-05')],
});
console.log(orders.length, orders.columnNames);
console.log(orders.filter("region == 'EU'").agg({ n: 'count()', revenue: 'sum(price * qty)' }));
await engine.close();
engine.tableFromRows(rows, options?)
Builds a table from an array of plain objects. Column names and kinds come from the first row: a string there makes a category column, anything else a number column. Takes type as in table(). Each row is visited once, so prefer table() when you already have columns.
engine.parseCSV(text, options?)
Parses CSV given as a string or bytes (Uint8Array, Buffer) into a table, on every thread: the bytes are cut at row ends and each thread parses its share straight into the columns. Column types are guessed from a sample of rows: numbers, dates (ISO text), categories (text that repeats) and free text (text where most values differ, which can be returned but not filtered or grouped on).
| Name | Type | Default | Meaning |
|---|---|---|---|
delimiter |
string | ',' |
One character, not a quote or newline. |
header |
boolean | true |
false: no header line; columns are named col0, col1… |
types |
object | {} |
Per column: 'number', 'category', 'string' (free text), 'text' (category or string, from the sample) or 'date'. |
columns |
string[] |
all | Load only these columns. |
type |
Float64Array or Float32Array |
Float64Array |
Number storage. |
sampleRows |
number | 1000 | Rows read to guess the types. |
compress |
boolean | false |
Compress the columns after parsing. |
Header names are cleaned to work in expressions: Order Date becomes Order_Date, a leading digit gets _ in front, and repeated names get _2, _3. A UTF-8 byte order mark is skipped. Blank fields load as missing (NaN for numbers, '' for text).
Also parseCSVAsync(text, options?), which parses on the worker threads and returns a promise.
import { Engine } from 'vectrojs';
const engine = new Engine();
const csv = 'day,region,amount\n2024-03-01,EU,10.5\n2024-03-01,US,7\n2024-03-02,EU,3\n';
const t = engine.parseCSV(csv);
console.log(t.groupBy('region').agg({ total: 'sum(amount)' }).toRows());
const typed = await engine.parseCSVAsync(csv, { types: { region: 'string' }, columns: ['region', 'amount'] });
console.log(typed.toRows(2));
await engine.close();
engine.readCSV(path, options?)
Node only. Reads a CSV file straight into engine memory and parses it as parseCSV does (same options, compress included). Also readCSVAsync(path, options?), which reads with async I/O and parses on the worker threads.
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Engine } from 'vectrojs';
const dir = mkdtempSync(join(tmpdir(), 'vectro-'));
const file = join(dir, 'sales.tsv');
writeFileSync(file, 'region\tamount\nEU\t10\nUS\t20\nEU\t5\n');
const engine = new Engine();
const sales = engine.readCSV(file, { delimiter: '\t' });
console.log(sales.sum('amount'), await (await engine.readCSVAsync(file, { delimiter: '\t' })).countAsync());
await engine.close();
rmSync(dir, { recursive: true, force: true });
engine.fromArrow(bytes, options?)
Loads Arrow IPC bytes (the stream format or the file format, .arrow / .feather, LZ4-compressed bodies included) as a table. Options: columns (names to read; a column of a type Vectro can’t read is an error unless left out) and type. See Arrow.
Datasets
Node methods for datasets, tables kept on disk and read a chunk at a time. Each returns or takes the objects documented there.
engine.openDataset(path)
Opens a .vectro file for queries and returns a Dataset. Only the footer is read. Also openDatasetAsync(source), which also takes a URL (read with HTTP range requests), a Blob or File, or a file object from blobFile or urlFile.
engine.createDataset(path, options?)
Returns a DatasetWriter for path: append() tables or columns as often as needed, then close(). Options: rowsPerChunk (default 4,194,304), append (add to an existing dataset with the same columns), bitPack (default true). Also createDatasetAsync(target, options?), which also takes a writable file object such as memoryFile() and returns a promise of the writer.
engine.writeDataset(path, source, options?)
Writes a table (the rows matching its filter), a lazy query, a dataset, or { column: values } as a dataset, and returns { path, rows, chunks, bytes }. A query over a dataset is written chunk by chunk, so its result needn’t fit in memory. Options as for createDataset. Also writeDatasetAsync(target, source, options?).
engine.csvToDataset(csvPath, path, options?)
Converts a CSV file of any size into a dataset, reading it in pieces of pieceBytes (default 64 MB). The column types come from the first piece. Takes the CSV options of parseCSV, the writer options of createDataset, and onProgress, called after each piece with { bytes, size, rows }. Returns the writer’s summary. Also csvToDatasetAsync(csv, out, options?), where csv may also be a URL or a Blob and out a writable file object.
In the browser
The vectrojs/browser engine has the core methods above and these instead of the Node file methods. All of them are async.
| Method | Does |
|---|---|
loadCSV(url, options?) |
Fetches a CSV and parses it (on the workers when there are any). |
openDataset(source) |
Opens a dataset from a URL, a File or Blob, or a file object. |
writeDataset(target, source, options?) |
Writes into browser storage (OPFS) by name, to a FileSystemFileHandle, or to a file object such as memoryFile(). |
csvToDataset(csv, target, options?) |
Converts a CSV (File, Blob or URL) of any size; chunks go to disk as they are made. |
createDataset(target, options?) |
A writer with appendAsync and closeAsync. |
storedDatasets() |
The .vectro files in browser storage, as [{ name, size }]. |
openStoredDataset(name) |
Opens one of them. |
storedDatasetFile(name) |
One of them as a File, to download or open. |
removeStoredDataset(name) |
Deletes one of them. |
On a page served with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp the engine runs Web Workers on every core; the async methods use them while the page stays responsive, and sync calls run on the page’s thread alone. Other pages get the one-thread engine. threadsAvailable(), also exported from vectrojs/browser, tells which you’ll get. For multi-core work on pages without those headers, use SplitEngine. See In the browser.
import { Engine } from 'vectrojs/browser';
const engine = new Engine();
const orders = await engine.loadCSV('/orders.csv');
await orders.filter("region == 'EU'").sumAsync('price * qty');
State and shutdown
engine.busy
A getter: true while an async query is running on the worker threads. A sync call made then throws, because it would overwrite the running task; await the async call first, or use async calls throughout. Async calls queue up and run one at a time.
engine.close()
Stops the worker threads and returns a promise.
Arrays in engine memory
These are the building blocks the tables use. Only arrays that live in engine memory get the WebAssembly SIMD kernels.
engine.alloc(Type, n)
Allocates a typed array of n elements of Type (for example Float64Array) in engine memory and returns it. Throws a RangeError past 4 GB.
engine.shared(Type, n)
The same as alloc.
engine.toShared(array)
Copies a normal typed array into engine memory and returns the copy.
engine.live(array)
Returns a working view of an array from alloc. Plain (non-shared) memory gets a new buffer when it grows, which empties older views; live() rebuilds the view. Shared memory never goes stale, and every engine method calls live() on its inputs, so you need it only for your own reads and writes after other allocations.
engine.free(array)
Returns an array from alloc to the engine. Throws for arrays it didn’t allocate.
engine.interpreterScratch(bytes)
Reserves (once) the scratch area the vectorized interpreter keeps its registers in, and returns its byte offset. The engine calls it itself; you don’t need to.
Kernels on arrays
Whole-array operations on all threads. Inputs are arrays from alloc or toShared (or views of a SharedArrayBuffer); small arrays run on the calling thread.
| Method | Returns |
|---|---|
engine.sum(a) |
The sum of a. |
engine.min(a), engine.max(a) |
The smallest, largest value. |
engine.dot(a, b) |
The sum of a[i] * b[i]. |
engine.countGreater(a, x) |
How many values are greater than x. |
engine.add(a, b, out) |
out, set to a[i] + b[i]. |
engine.scale(a, k, c, out) |
out, set to a[i] * k + c. |
engine.sort(a) |
a, sorted in place (ascending; threads sort chunks, then merge). |
engine.countBytes(bytes, pattern) |
Occurrences of pattern (a string or bytes) in bytes. |
engine.heavy(a) |
The sum of sqrt(x) * sin(x) + cos(x / 2): a compute-bound benchmark kernel. |
engine.compile(spec)
Compiles an expression into one SIMD kernel and returns it; compiled kernels are cached by their text. spec is a reduction ('sum(a * 2 + 1)'), an array of up to 8 reductions (['sum(a)', 'max(a)']), an object of them ({ total: 'sum(a)' }), or an elementwise expression ('a * k + b'). Reductions are sum, mean, min, max and count. The kernel has run(vars, out?) and runAsync(vars, out?): vars maps each name to a Float64Array or Float32Array from engine memory or to a number. All arrays must have the same type and length. A reduction returns a number, an array or an object, in the shape of spec; an elementwise expression writes into out and returns it. The expression language is in Expressions.
engine.eval(spec, vars, out?)
compile(spec).run(vars, out). Also evalAsync(spec, vars, out?).
engine.evalG(spec, vars, out?)
The generator form of eval, used inside the engine’s own operations (it yields “run this on the threads” requests to a driver). Use eval or evalAsync instead.
import { Engine } from 'vectrojs';
const engine = new Engine();
const a = engine.alloc(Float64Array, 1_000_000);
for (let i = 0; i < a.length; i++) a[i] = i % 100;
console.log(engine.sum(a), engine.max(a), engine.countGreater(a, 90));
console.log(engine.eval(['sum(a)', 'mean(a)', 'count(a > 50)'], { a }));
const out = engine.alloc(Float64Array, a.length);
engine.eval('a * k + 1', { a, k: 2 }, out);
console.log(out[3], await engine.evalAsync({ total: 'sum(a * a)' }, { a }));
engine.free(out);
engine.free(a);
await engine.close();