For AI agents: fetch /llms.txt first for the curated documentation index, then use same-host Markdown pages when available.

Concepts

Workers

Move expensive work — search, filtering, parsing — off the main thread with a tiny RPC layer over Web Workers and no message-plumbing boilerplate.

Three helpers cover the whole loop: exposeWorker registers handlers inside the worker, createWorkerClient calls them from the page, and transferResult moves large buffers instead of copying them.

Exposing handlers

In the worker module, pass exposeWorker an object of named handlers. Each handler receives the call payload and returns a value (or a promise).

// client/search-worker.js
import { exposeWorker } from "@nativefragments/core/client/worker.js";

exposeWorker({
  // "search" is the handler name the page will call.
  search: ({ rows, query }) =>
    rows.filter((row) =>
      row.title.toLowerCase().includes(query.toLowerCase())
    ),
});

Calling from the page

createWorkerClient spins up a module worker and returns a client. call(name, payload) resolves with the handler's result and rejects on error or timeout.

// client/index.js
import { createWorkerClient } from "@nativefragments/core/client/worker.js";

const search = createWorkerClient("/build/search-worker.js");

// Runs off the main thread — typing stays responsive.
const hits = await search.call("search", { rows, query: "native" });

search.dispose(); // detach listeners + reject pending calls when done

Transferring large buffers

By default the result is structured-cloned (copied). Wrap it in transferResult to transfer ownership of an ArrayBuffer or other Transferable instead — no copy.

// client/decode-worker.js
import { exposeWorker, transferResult } from "@nativefragments/core/client/worker.js";

exposeWorker({
  decode: (buffer) => {
    const result = process(buffer); // returns an ArrayBuffer
    // Move the buffer back to the page instead of copying it.
    return transferResult(result, [result]);
  },
});

See also