AlbexEngine API
Albex exposes one primary class — AlbexEngine — that wraps the WebAssembly module and orchestrates indexing, search, persistence, and the adaptive runtime. Additional opt-in helpers (AlbexEngineWorker, AlbexPool, TieredStore, BloomGpu) are exported from subpath entry points so consumers only pay for what they use.
Install, import, use. The WASM binary travels with the npm package and your bundler (Vite, Webpack 5+, Next, esbuild, Rollup, Parcel 2, Bun, Deno) resolves it automatically through import.meta.url. No assets to copy. No paths to remember.
npm install albex
import { AlbexEngine } from "albex";
const engine = new AlbexEngine();
await engine.init();
await engine.indexFile(myFile);
const results = engine.search('contrato', { windowed: true }); The library ships six WASM variants of the main engine (3 capacity tiers × baseline/SIMD) plus a lazy 1 MB PDF module. The zero-config path loads the std-baseline binary, which works on every device. If you want runtime tier auto-selection (mini/std/pro picked from navigator.deviceMemory) serve the variants yourself and pass wasmBaseUrl. See the Architecture page for the rationale.
new AlbexEngine(opts)
new AlbexEngine(opts: AlbexOptions)AlbexEngine Constructs the engine. The WASM module is NOT loaded yet — call init() before any other method. The constructor only validates options and stores them.
import { AlbexEngine } from "albex";
// Zero config — the WASM binary ships with the package and your bundler
// (Vite, Webpack 5+, Next, esbuild, Rollup, Parcel 2, Bun) resolves it
// automatically through `import.meta.url`. No assets to copy, no URL to
// configure, no path to remember.
const engine = new AlbexEngine();
await engine.init();
// Optional overrides — only if you want tier auto-selection or a CDN.
// new AlbexEngine({ wasmBaseUrl: "/assets" }) // serve the 6 variants yourself
// new AlbexEngine({ wasmUrl: "https://cdn.example.com/albex_wasm.wasm" })| Field | Type | Description |
|---|---|---|
wasmUrl? | string | Explicit URL to the .wasm binary. Overrides every other option. Useful when serving from a custom CDN. |
wasmBaseUrl? | string | Base directory containing tier variants (`albex_wasm_<tier>[_simd].wasm`). Required only if you want runtime tier auto-selection. Leave undefined to fall back to the bundled std-baseline binary. |
pdfWasmUrl? | string | Override for `albex_pdf.wasm`. By default the bundled module is resolved via `import.meta.url` and loaded lazily on first PDF. |
tier? | 'auto'|'mini'|'std'|'pro' | Capacity tier. `auto` requires `wasmBaseUrl` because the bundler cannot know which of the 6 binaries to copy. Without `wasmBaseUrl` the engine loads std by default. |
simd? | 'auto'|'on'|'off' | SIMD variant policy. Only effective when `wasmBaseUrl` is set. |
gpu? | 'auto'|'on'|'off' | WebGPU pre-filter policy. Default: `auto` (enabled when corpus > `gpuThreshold`). |
gpuThreshold? | number | Minimum chunk count to engage WebGPU. Default: 20 000. |
engine.init()
engine.init(): Promise<void>Promise<void> Resolves the WASM URL (using wasmUrl or wasmBaseUrl + tier auto-detection), fetches the binary, instantiates it, runs initial setup, and subscribes the engine to the global ResourceManager. Throws AlbexInitError on fetch or instantiation failure.
await engine.init(); // engine.tier → 'mini' | 'std' | 'pro' // engine.simdEnabled → boolean // engine.gpuEngaged → true after the first search that uses WebGPU
reset() & [Symbol.dispose]()
engine.reset(): voidClears every indexed document and search result. The engine is immediately ready to index a fresh corpus. The WASM module instance is preserved (no re-fetch).
engine[Symbol.dispose](): void TC39 explicit-resource-management hook. Resets state, unsubscribes from the resource manager, destroys the GPU device (if any), and nulls out internal references so the WASM instance becomes unreachable for GC. Use with using engine = new AlbexEngine(...) when available.
engine.indexFile()
engine.indexFile(file: File): Promise<IndexedDocument>Promise<IndexedDocument> Detects the format from the extension, parses the file, and streams text into the WASM index. Content is hashed (FNV-1a 64-bit) before indexing — if a document with the same hash already exists, the previous entry is returned and no work is done. Throws AlbexUnsupportedFormatError or AlbexParseError on failures.
Supported: .docx, .xlsx, .pdf, .md, .html/.htm, .json, .csv, .eml, .rtf, .txt, .xml.
const input = document.querySelector('input[type=file]');
input.addEventListener('change', async () => {
for (const file of input.files) {
const doc = await engine.indexFile(file);
console.log(`${file.name}: ${doc.chunks} chunks, hash=${doc.contentHash}`);
}
});
// Idempotent: re-indexing the same file is a no-op and returns the existing
// IndexedDocument (matched by FNV-1a content hash).| Field | Type | Description |
|---|---|---|
name | string | Original file name from the File object. |
ext | string | Lowercase extension without leading dot. |
chunks | number | Number of chunks produced from this document. |
indexTimeMs | number | Wall-clock time spent indexing. |
textBytes | number | Bytes of indexed text contributed by this document. |
docId | number | Stable identifier within the engine. Persists across compact(). |
contentHash | string | 64-bit FNV-1a hex hash of the source bytes. Used for dedup. |
engine.search()
engine.search(query: string, opts?: SearchOptions): SearchResult[]SearchResult[] Synchronous full-corpus search. Drives the pipeline: parse query → Bloom filter → Bitap fuzzy match → rich scoring → min-heap top-K. Results are returned already sorted by score, descending. May invoke the WebGPU pre-filter automatically when opts.gpu permits and chunk count exceeds gpuThreshold.
// Synchronous fast path — best for small corpora and tests.
const results = engine.search('"contrato marco" | rescisión', { windowed: true });
for (const r of results) {
console.log(`[${r.score}] ${r.documentName} · ${r.snippet}`);
}engine.searchCooperative()
engine.searchCooperative(query: string, opts?: SearchOptions): AsyncIterable<SearchResult>AsyncIterable<SearchResult> Cooperative variant of search(). The corpus is processed in slices; between slices the engine yields to the browser scheduler via scheduler.yield() (or requestAnimationFrame fallback). UI stays responsive during 50 ms+ scans. Use this in any interactive search box.
// Cooperative streaming search — main thread stays at 60 fps.
for await (const r of engine.searchCooperative('contrato', { frameBudgetMs: 8 })) {
renderResult(r); // render incrementally as results arrive
}| Field | Type | Description |
|---|---|---|
windowed? | boolean | Return cropped snippets with ASCII ellipsis markers instead of full chunks. |
before? | number | Bytes of context before the match (default 60). |
after? | number | Bytes of context after the match (default 120). |
frameBudgetMs? | number | Slice duration for searchCooperative() before yielding to the scheduler. Default 8 ms. |
| Field | Type | Description |
|---|---|---|
documentName | string | File name as registered by indexFile(). |
location | number | Paragraph index (DOCX/TXT/MD/…) or page number (PDF, 1-based). |
score | number | Composite relevance score 0–1000 (higher is better). |
snippet | string | Chunk text, optionally windowed with `"... "` / `" ..."` sentinels. |
matchStart | number | Byte offset of the primary token start in `snippet`. |
matchEnd | number | Byte offset of the primary token end (exclusive). |
matches | MatchSpan[] | All matched token spans in query order. Length 1–4. |
Tuning knobs
engine.setMaxErrors(n: 0 | 1 | 2 | 3): voidvoidMaximum edit distance for fuzzy match. 0 = exact only. Engine auto-shrinks for short queries.
engine.setMaxErrors(1);
engine.setThreshold(n: number): voidvoidMinimum score (0-1000) below which results are dropped. Default 250.
engine.setThreshold(400);
engine.setMaxResults(n: number): voidvoidCap on number of returned results. 1-200. Default 50.
engine.setMaxResults(100);
engine.setLanguage(lang: 'off' | 'es'): voidvoidEnable lightweight Spanish stemming on query tokens. Indexed text is never stemmed, so snippets stay faithful to the source.
engine.setLanguage('es'); // "contratos" now matches "contrato"Remove · replace · compact
Indexing is idempotent (content-hash dedup is automatic). Documents can be removed individually without rebuilding the entire index. Reclaim storage on demand with compact().
engine.removeDocument(idOrName: string): booleanbooleanTombstone a document. Subsequent searches skip its chunks. Storage is reclaimed by compact(). `id` accepts the file name or contentHash returned by indexFile.
engine.removeDocument('contract-2024-03.pdf');
engine.removeDocument(doc.contentHash); // by hash also worksengine.replaceDocument(name: string, newFile: File): Promise<IndexedDocument>Promise<IndexedDocument>Atomic remove + re-index. Bypasses dedup so re-indexing the same bytes after a remove works.
await engine.replaceDocument('contract.pdf', newVersionFile);engine.compact(): voidvoidReclaim storage from tombstoned documents. Rewrites internal arrays in place. Doc IDs of survivors are preserved.
engine.removeDocument('old.pdf');
engine.compact(); // bytes freed; subsequent indexFile sees real headroomSnapshots: OPFS & IndexedDB
The full engine state can be serialised to a binary blob and restored later. OPFS is preferred (zero-copy writes); IndexedDB is the universal fallback. A 16 MB snapshot typically restores in tens of milliseconds — far faster than re-parsing the documents.
engine.save(name: string): Promise<void>Promise<void>Serialise the index to a binary snapshot in OPFS (preferred) or IndexedDB.
await engine.save('my-corpus');engine.load(name: string): Promise<boolean>Promise<boolean>Restore a previously saved snapshot. Returns true on success, false if missing or header mismatched.
if (!await engine.load('my-corpus')) {
console.log('No snapshot yet; starting fresh');
}engine.loadOrInit(name: string): Promise<boolean>Promise<boolean>Convenience wrapper: load if it exists, otherwise reset() and start clean.
await engine.loadOrInit('my-corpus');engine.deleteSnapshot(name: string): Promise<void>Promise<void>Remove a snapshot from storage.
await engine.deleteSnapshot('old-corpus');engine.listSnapshots(): Promise<string[]>Promise<string[]>List the names of all snapshots saved in the current origin.
const names = await engine.listSnapshots();
getStats() & getLastSearchStats()
engine.getStats(): EngineStatsSnapshot of current engine state: document count, chunk count, memory usage, loaded tier, capacity caps.
engine.getLastSearchStats(): SearchStats | nullBloom/Bitap pipeline counters from the most recent search call. Useful for debugging relevance and detecting performance regressions.
| Field | Type | Description |
|---|---|---|
documents | number | Active (non-tombstoned) document count. |
chunks | number | Total indexed chunks. |
textUsed | number | Bytes of indexed text currently stored. |
textCapacity | number | Maximum text bytes the loaded tier can hold. |
wasmMemoryBytes | number | Total WASM linear memory size (BSS + grown pages). |
tier | 'mini'|'std'|'pro'|null | Tier of the loaded binary. null before init(). |
maxChunks | number | Compile-time chunk capacity for the loaded tier. |
maxDocs | number | Compile-time document capacity for the loaded tier. |
| Field | Type | Description |
|---|---|---|
query | string | Verbatim query string. |
timeMs | number | End-to-end search time. |
results | number | Number of results above the threshold. |
bloomTested | number | Chunks tested against the Bloom filter. |
bloomPassed | number | Chunks that passed Bloom (subset of tested). |
bitapMatched | number | Chunks confirmed by Bitap (subset of passed). |
Profile detection & helpers
The library exposes pure helpers from the same package so consumers can read the device profile, override tier selection, or build their own UI around resource state. None of them require an initialised engine.
detectProfile(opts?: { fresh?: boolean }): Promise<DeviceProfile>Promise<DeviceProfile>Probe the host capabilities (cores, memory, WASM features, WebGPU, storage budget, network, battery). Result cached in sessionStorage. Pass `fresh: true` to bypass the cache.
import { detectProfile } from 'albex';
const profile = await detectProfile();
console.log(profile.memoryGB, profile.wasm.simd);pickTier(profile: DeviceProfile): 'mini' | 'std' | 'pro''mini' | 'std' | 'pro'Pure heuristic: <=1 GB → mini, 2-4 GB → std, >=8 GB → pro, null (Safari) → std.
const tier = pickTier(profile);
pickWorkerCount(profile: DeviceProfile): numbernumbercores/2 clamped [1, 8]. Falls to 1 when battery is reported below 20 % and discharging.
const workers = pickWorkerCount(profile);
shouldUseGpu(profile: DeviceProfile, chunkCount: number, threshold?: number): booleanbooleantrue when WebGPU is available AND chunk count crosses the threshold (default 20 000).
if (shouldUseGpu(profile, engine.getStats().chunks)) { /* … */ }| Field | Type | Description |
|---|---|---|
cores | number | navigator.hardwareConcurrency. |
memoryGB | number | null | navigator.deviceMemory (capped at 8 GB by spec; null on Safari). |
wasm.simd | boolean | WebAssembly v128 supported (validated via probe module). |
wasm.bulkMemory | boolean | Bulk memory ops supported. |
wasm.threads | boolean | Threads supported AND page cross-origin isolated. |
webgpu | boolean | navigator.gpu present. |
coopCoep | boolean | crossOriginIsolated === true. |
storage | { quotaBytes, usageBytes } | navigator.storage.estimate() result. |
net | { effectiveType, saveData } | Connection info if reported (Chrome only). |
battery | { level, charging } | null | Battery state if available. |
visible | boolean | document.visibilityState at probe time. |
| Tier | Max docs | Max chunks | Max text | Working set |
|---|---|---|---|---|
mini | 32 | 25 000 | 4 MB | ~5 MB |
std | 128 | 100 000 | 16 MB | ~20 MB |
pro | 1 024 | 800 000 | 128 MB | ~160 MB |
AlbexEngineWorker
new AlbexEngineWorker(opts: AlbexWorkerOptions)AlbexEngineWorker Drop-in replacement for AlbexEngine that runs the entire engine inside a Web Worker. Surface is identical except every method returns a Promise. Files are transferred via postMessage with transferable ArrayBuffers — no copy.
Import from albex/worker. The runtime script is exported separately as albex/worker-runtime and must be referenced via new URL(..., import.meta.url).
import { AlbexEngineWorker } from 'albex/worker';
// Same zero-config pattern as the main engine — the worker runtime URL is
// the only thing you must point at (so the bundler can spawn it).
const engine = new AlbexEngineWorker({
workerUrl: new URL('albex/worker-runtime', import.meta.url),
});
await engine.init();
const results = await engine.search('contrato', { windowed: true });AlbexPool
new AlbexPool(opts: AlbexPoolOptions)AlbexPool Orchestrates N worker shards. Documents are sharded round-robin across workers. Searches broadcast to every shard and the coordinator merges top-K results preserving the global descending score order. Default worker count is half of hardwareConcurrency clamped to [1, 8]; battery-aware (drops to 1 on low battery).
Import from albex/pool.
import { AlbexPool } from 'albex/pool';
const pool = new AlbexPool({
workerUrl: new URL('albex/worker-runtime', import.meta.url),
workers: 'auto', // cores/2 clamped [1, 8]
});
await pool.init();
await pool.indexFile(fileA); // sharded round-robin
await pool.indexFile(fileB);
const results = await pool.search('contrato'); // map-reduce across shardsTieredStore
new TieredStore(engine: AlbexEngine, opts?: TieredStoreOptions)TieredStore Adds hot/warm tiers behind the engine. When the engine reaches evictThreshold of capacity, the LRU document is removed and its original blob remains in OPFS. promote(name) brings it back by re-indexing from the persisted blob.
Import from albex/tiered.
import { AlbexEngine, TieredStore } from 'albex';
const engine = new AlbexEngine();
await engine.init();
const store = new TieredStore(engine, { evictThreshold: 0.85, hotFloor: 4 });
await store.init();
await store.indexFile(file); // persists original blob in OPFS
await store.promote('older-doc.pdf'); // brings warm doc back to engineBloomGpu (advanced)
new BloomGpu()BloomGpu Standalone WebGPU Bloom-scan accelerator. AlbexEngine instantiates one automatically when opts.gpu permits and the corpus exceeds gpuThreshold (default 20 000 chunks). You only need to touch this class directly to integrate the WGSL shader into a non-Albex pipeline.
Import from albex/gpu.
Typed error hierarchy
Every error thrown by Albex extends AlbexError. Switch on the kind discriminator (which survives structuredClone across worker boundaries) or use instanceof against the subclasses.
| Class | kind | Thrown when |
|---|---|---|
AlbexError | (base) | Base class for every Albex error. Carries `kind` discriminator. |
AlbexInitError | init | WASM fetch failed, init() not called, or PDF module not initialised. |
AlbexUnsupportedFormatError | unsupported_format | File extension is not in the supported list. Carries `ext` field. |
AlbexParseError | parse | A parser (DOCX/XLSX/PDF/JSON/…) failed. Carries `format` field. |
AlbexCapacityError | capacity | Scratchpad write would exceed buffer size, or hard cap reached. |
import {
AlbexError, AlbexInitError, AlbexParseError,
AlbexUnsupportedFormatError, AlbexCapacityError,
} from 'albex';
try {
await engine.indexFile(file);
} catch (e) {
if (e instanceof AlbexUnsupportedFormatError) {
console.warn(`Unsupported: .${e.ext}`);
} else if (e instanceof AlbexParseError) {
console.warn(`Failed to parse ${e.format}:`, e.message);
} else if (e instanceof AlbexCapacityError) {
console.warn('Engine full — upgrade tier or use TieredStore');
} else throw e;
}npm entry points
Albex ships multiple subpath exports so each feature can be tree-shaken independently. Import only what you use.
| Import path | Surface | Pulls in |
|---|---|---|
albex | AlbexEngine, types, errors, profile helpers | main engine code, profile detector, persistence layer, resource manager |
albex/worker | AlbexEngineWorker | main-thread wrapper that proxies to a Worker |
albex/worker-runtime | (runs inside a Worker) | Worker-side handler; reference via `new URL(..., import.meta.url)` |
albex/pool | AlbexPool, AlbexPoolOptions | pool coordinator that orchestrates N worker shards |
albex/tiered | TieredStore, TieredStoreOptions | hot/warm tier manager with OPFS persistence of original blobs |
albex/gpu | BloomGpu, packBloomsFromChunks | standalone WebGPU runtime + WGSL shader for Bloom scan |