WorkerDB is an offline-first storage and file management library running strictly within a Web Worker. It exposes three primary namespaces:
db: Asynchronous IndexedDB operations via RPC to the Worker.opfs: Asynchronous Origin Private File System operations via RPC to the Worker.ls: Synchronous LocalStorage wrapper.
All exports can be imported from jsr:@vanaware/workerdb.
Initializes the Web Worker connection. By default, it expects the worker to be served at ./worker.js.
import { db } from "jsr:@vanaware/workerdb";
db.init("./worker.js");Terminates the current Worker instance and spins up a fresh one, immediately processing queued RPC requests.
Safely shuts down and cleans up the active Web Worker instance.
The db object provides a complete set of asynchronous database operations for IndexedDB (via idb-keyval).
You can create isolated namespaces for different tables or stores by invoking db as a function with options or positional arguments. Supports TypeScript generics to type documents:
interface UserProfile {
username: string;
age: number;
}
// Option A: With configuration object
const myStore = db<UserProfile>({
dbName: "MyDatabase",
storeName: "users",
prefix: "usr_",
indexes: ["age"], // IndexedDB index
validator: (item) => typeof item === "object" && item !== null && (item as any).age >= 18,
});
// Option B: With positional parameters
const simpleStore = db<UserProfile>("MyDatabase", "users", "usr_");validator: (item: unknown) => booleanValidate items before writing to the database onset,setMany, andpatch.getByIndex<T>(indexName: string, query: IDBValidKey): Promise<WithId<T>[]>Queries records directly using IndexedDB secondary indexes (O(log N) indexed lookup).getManyByIndex<T>(indexName: string, queries: IDBValidKey[]): Promise<WithId<T>[]>Queries records matching any of the specified index keys in batch, deduplicating matching results.getSomeByIndex<T, C = unknown>(indexName: string, query: IDBValidKey, fn: (items: WithId<T>[], ctx?: C) => WithId<T>[], context?: C): Promise<WithId<T>[]>Uses the index to retrieve ONLY the subset matchingqueryand then executes a filtering function on that small subset, avoiding loading the full database into memory.queryByIndex<T, R, C = unknown>(indexName: string, query: IDBValidKey, fn: (items: WithId<T>[], ctx?: C) => R, context?: C): Promise<R>Executes an aggregation or transformation function over the index-matched records directly in the worker.deleteByIndex(indexName: string, query: IDBValidKey): Promise<void>Deletes all records matching an index key in O(log N) without retrieving or loading document contents into memory.deleteManyByIndex(indexName: string, queries: IDBValidKey[]): Promise<void>Deletes all records matching any of the specified index keys in batch.delSomeByIndex<T, C = unknown>(indexName: string, query: IDBValidKey, fn: (items: WithId<T>[], ctx?: C) => WithId<T>[], context?: C): Promise<void>Uses the index to retrieve only the matching subset, selects items to remove usingfn, and deletes them by key.setSomeByIndex<T, C = unknown>(indexName: string, query: IDBValidKey, selectFn: (items: WithId<T>[], ctx?: C) => WithId<T>[], updateFn: (item: WithId<T>, ctx?: C) => WithId<T>, context?: C): Promise<void>Uses the index to retrieve only the matching subset, selects targets withselectFn, computes updates withupdateFn, validates schema, and writes updates back.
get<T>(key: string): Promise<WithId<T> | undefined>Retrieves a record by its key. Automatically injects the_idinto the returned object.set<T>(key: string, val: T): Promise<string>Sets a specific key.set<T>(val: T): Promise<string>Automatically generates a highly collision-resistant ID and saves the document. Returns the generated key.update<T>(key: string, updater: (val: WithId<T> | undefined) => T): Promise<void>Atomically updates a record by passing its current value to an updater function.patch<T, C>(key: string, patchOrFn: Partial<T> | Function, context?: C): Promise<WithId<T>>Partially updates a record. Accepts a partial object, or a function that executes inside the worker to calculate the patch.delete(key: string): Promise<void>Removes a record by its key.
getMany<T>(keys: string[]): Promise<(WithId<T> | undefined)[]>Retrieves multiple records.setMany(entries: [string, unknown][]): Promise<void>Inserts or updates multiple records concurrently.deleteMany(keys: string[]): Promise<void>Deletes multiple records concurrently.
keys(): Promise<string[]>Returns all keys in the store (respecting prefix/scope).values<T>(): Promise<T[]>Returns all values in the store.entries<T>(): Promise<[string, T][]>Returns all entries[key, value]pairs.clear(): Promise<void>Removes all entries within the active scope.
Run heavy array methods directly inside the Web Worker (no data serialization back and forth just to filter items!).
-
query<T, R, C>(fn: (items: WithId<T>[], ctx: C) => R, context?: C): Promise<R>Executes an arbitrary function across all stored items in the Worker and returns the computed result. Useful for aggregations, counting, or deep searches.// Example: Calculate total invoice amount natively in the worker const result = await db("FINANCES", "invoices").query((items) => { return { count: items.length, total: items.reduce((acc, i) => acc + i.amount, 0), firstWorkItem: items.find(i => i.tag === "work"), sorted: items.toSorted((a, b) => a.amount - b.amount) }; });
-
getSome<T, C>(fn: (items: WithId<T>[], ctx: C) => WithId<T>[], context?: C): Promise<WithId<T>[]>Likequery, but specifically expects a filtered array of items returned. Great for complex native filters.// Example: Filter high value items by passing external context const targetThreshold = 500; const expensiveItems = await db("SHOP", "items").getSome( (items, ctx) => items.filter(i => i.price >= ctx.threshold), { threshold: targetThreshold } // Context is injected! );
-
delSome<T, C>(fn: (items: WithId<T>[], ctx: C) => WithId<T>[], context?: C): Promise<void>Filters items within the worker and automatically deletes the resulting matches.// Example: Delete all inactive employees await db("COMPANY", "employees").delSome((items) => items.filter(i => i.active === false) );
-
setSome<T, C>(selectFn: Function, updateFn: Function, context?: C): Promise<void>Selects records and updates them en-masse natively in the background thread. Applies type transformations or bulk updates without main-thread locking.// Example: Mass update department names to uppercase for active employees await db("COMPANY", "employees").setSome( // 1. Selector Function (items) => items.filter(item => item.active === true), // 2. Updater Function (item) => ({ ...item, name: item.name.toUpperCase(), department: item.department.toUpperCase(), level: String(item.level) // Mutating type from number to string! }) );
exportDB(): Promise<Record<string, unknown>>Dumps the database to a JSON object.importDB(data: Record<string, unknown>, clearFirst = false): Promise<void>Hydrates the database from a JSON dump.backupToOpfs(key: string, fileName?: string): Promise<string>Writes a full JSON database snapshot to OPFS natively within the worker.restoreFromOpfs(key: string, fileName: string, clearFirst = false): Promise<void>Restores the database from an OPFS JSON snapshot.
Provides native access to the Origin Private File System for robust offline blob/binary storage. Executed inside the Web Worker.
const userFiles = opfs("MyDb", "MyStore", "user_prefix_", "base/folder/path");listFiles(key: string): Promise<string[]>Lists all files associated with a specific logical key (folder representation).getFile(key: string, fileName: string): Promise<File>Retrieves a file as a binaryFileobject.getFileStream(key: string, fileName: string): Promise<ReadableStream<Uint8Array>>Retrieves a file as a chunkedReadableStream<Uint8Array>(memory-efficient for large files).addFile(key: string, file: File | Blob, fileName: string): Promise<void>Saves a file to OPFS.addFileStream(key: string, fileName: string, stream: ReadableStream<Uint8Array>): Promise<void>Pipes aReadableStream<Uint8Array>directly into an OPFS file.delFile(key: string, fileName: string): Promise<void>Deletes a specific file.renFile(key: string, oldName: string, newName: string): Promise<void>Renames a file in-place.mvFile(key: string, fileName: string, newKey: string): Promise<void>Moves a file to a new logical key folder.
All Zip actions execute natively in the Worker using fflate compression!
zip(key: string, zipName: string, filesToZip?: string[], deleteOriginals = false): Promise<void>Compresses specific files (or the entire folder) into a.ziparchive.unzip(key: string, zipName: string, deleteZip = false): Promise<void>Extracts an OPFS.ziparchive.addZip(key: string, zipName: string, file: File | Blob, fileName: string): Promise<void>Writes a file directly into an existing.ziparchive without extracting it.delZip(key: string, zipName: string, fileName: string): Promise<void>Deletes a specific file from inside a.ziparchive.
A synchronous fallback matching the structure of db, operating directly on window.localStorage.
const preferences = ls("prefs_");
preferences.set("theme", "dark");ls exposes synchronous variants of the fundamental CRUD and Map operations:
get(key),set(key, val),set(val),update(key, fn),patch(key, patch)delete(key)getMany(keys),setMany(entries),deleteMany(keys)keys(),values(),entries(),clear()getSome(fn),delSome(fn),setSome(fn)importDB(data),exportDB()
WorkerDB exports cryptographic identifier utilities for client-side key generation and validation:
import { gerarId, gerarIdComPrefixo, validarId, type WithId } from "jsr:@vanaware/workerdb";
// Generate a 12-character cryptographically random ID
const id = gerarId(); // e.g. "4f8a91b2c3d4"
// Generate an ID with a domain prefix
const userId = gerarIdComPrefixo("usr_"); // e.g. "usr_4f8a91b2c3d4"
// Validate ID string format and length (1-24 chars)
const isValid = validarId(id); // true
// Generic helper type for documents with an _id field
type UserDocument = WithId<{ name: string; email: string }>;The companion library jsr:@vanaware/opfs-explorer provides a zero-dependency, pluggable Service Worker handler and visual UI for browsing and downloading files in the Origin Private File System.
import {
createOpfsFetchHandler,
handleOpfsRequest,
listOpfsFiles,
getFileFromOpfs,
getMimeType,
resolveRoutePrefix,
type OpfsExplorerOptions,
type OpfsExplorerResponse,
} from "jsr:@vanaware/opfs-explorer";Creates a standard (event: FetchEvent) => void listener to plug directly into self.addEventListener("fetch", ...).
- Accepts either a custom subfolder name string (
"files","arquivos","opfs") or a configuration object. - Automatically handles canonical redirects (e.g.
/files->/files/), directory index generation, and binary file streaming.
// 1-liner with custom subfolder (auto-resolves scope on GitHub Pages or localhost):
self.addEventListener("fetch", createOpfsFetchHandler("files"));
// With options object:
self.addEventListener("fetch", createOpfsFetchHandler({
subfolder: "arquivos",
title: "Meus Arquivos Locais",
opfsDir: "backups", // Restrict explorer to a specific subfolder inside OPFS
}));2. handleOpfsRequest(request: Request, options?: string | OpfsExplorerOptions): Promise<OpfsExplorerResponse>
Low-level request handler for custom Service Worker routing pipelines:
self.addEventListener("fetch", async (event) => {
const { matched, response } = await handleOpfsRequest(event.request, "files");
if (matched && response) {
event.respondWith(response);
}
});interface OpfsExplorerOptions {
/** Subfolder name to mount the explorer under (e.g. "files", "arquivos", "opfs"). */
subfolder?: string;
/** Explicit route prefix override (e.g. "/files" or "/my-repo/files"). */
routePrefix?: string;
/** Base scope path override (defaults to self.registration.scope pathname). */
scopePath?: string;
/** Custom title for HTML header and page <title>. */
title?: string;
/** Custom CSS styles to inject into the explorer interface. */
customStyles?: string;
/** Custom root directory handle (defaults to OPFS root). */
rootDir?: FileSystemDirectoryHandle;
/** Name of a specific OPFS subdirectory to restrict exploration to. */
opfsDir?: string;
}These functions can be imported and executed standalone in any modern browser context (UI, Web Worker, or Service Worker):
listOpfsFiles(dirHandle?: FileSystemDirectoryHandle, path?: string): Promise<string[]>Recursively traverses the OPFS directory tree and returns an array of relative file paths (e.g.["backups/db.json", "photos/cover.png"]).getFileFromOpfs(filePath: string, rootDir?: FileSystemDirectoryHandle): Promise<File>Resolves a relative path and returns the nativeFileobject from OPFS.getMimeType(path: string): stringResolves the Content-Type MIME header for a file extension (e.g."image/png","application/json").resolveRoutePrefix(options?: string | OpfsExplorerOptions): stringComputes the canonical path prefix combining the active Service Worker scope and configured subfolder (e.g."/my-repo/files").