Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.bitfield.so/llms.txt

Use this file to discover all available pages before exploring further.

Runtime Kit

Send a request.

Use sendRequestToBitfieldTarget(...) when app code needs to ask a named Bitfield target to do work.

What this is

A target is a public callable door. Your app sends a payload to the door. Runtime Kit gets the payload to the thing behind the door. Your app receives reply bytes.
import { sendRequestToBitfieldTarget } from '@bitfield/runtime-kit';

The mental model

Your app should not care how the work is performed. It should care about the public target name and the payload it sends.If a user clicks a search button, your app can call product.search. The package behind that target can change later. Your button can stay the same as long as the target contract stays the same.That is the value: app code calls the door, not the implementation behind the door.

How it works technically

app action
  -> sendRequestToBitfieldTarget({ target, payload })
  -> Runtime Kit encodes payload as bytes
  -> named target receives the request
  -> target replies with bytes
  -> app decodes bytes according to the public target contract
The request shape is:
type BitfieldTargetRequest = {
  target: string;
  payload?: unknown;
};

type BitfieldTargetReply = {
  payload: Uint8Array;
};

Send JSON-like data

const reply = await sendRequestToBitfieldTarget({
  target: 'product.search',
  payload: { query: 'blue jacket' },
});

const text = new TextDecoder().decode(reply.payload);
const result = JSON.parse(text);
Use JSON-like payloads when your target contract is ordinary app data.

Send text

await sendRequestToBitfieldTarget({
  target: 'note.analyze',
  payload: 'Look for missing details.',
});
Runtime Kit encodes strings as bytes before sending.

Send bytes

const reply = await sendRequestToBitfieldTarget({
  target: 'image.thumbnail',
  payload: imageBytes,
});
If payload is already a Uint8Array, Runtime Kit sends those bytes.

Understand payload encoding

Payload valuePublic behavior
Uint8ArraySent as bytes.
stringEncoded as text bytes.
object, array, number, boolean, or nullSerialized as JSON bytes.
omitted payloadSent as empty bytes.
The target decides what those bytes mean. Your app and target should agree on a public contract, such as JSON in and JSON out.

Decode the reply

The reply is always bytes because targets are not forced into one format.
const text = new TextDecoder().decode(reply.payload);
For JSON replies:
const data = JSON.parse(new TextDecoder().decode(reply.payload));
For binary replies, keep the Uint8Array:
const thumbnailBytes = reply.payload;

Cancel a request

Use an AbortController when the user leaves a screen, changes a search query, or cancels work.
let currentSearch: AbortController | null = null;

async function runSearch(query: string) {
  currentSearch?.abort();
  currentSearch = new AbortController();

  return sendRequestToBitfieldTarget(
    { target: 'product.search', payload: { query } },
    currentSearch.signal,
  );
}

Handle errors

Wrap user-triggered requests so the UI can recover.
try {
  const reply = await sendRequestToBitfieldTarget({
    target: 'product.search',
    payload: { query },
  });
  return JSON.parse(new TextDecoder().decode(reply.payload));
} catch (error) {
  return { error: error instanceof Error ? error.message : 'Request failed' };
}
An error means the public request did not complete. It does not mean the component should learn private target internals.

Before / after

BeforeAfter
Button imports the feature implementation directly.Button calls a named target.
Replacing the feature requires rewiring app imports.Replacing the package keeps the target name stable.
App code knows implementation details.App code knows payload and reply shape.
AI has to understand private execution code.AI sends a request to a public door.

Common mistakes

Using target names as storage addressesproduct.search is a callable target name. It is not a storage path.Parsing every reply the same wayThe reply is bytes. Decode according to the target contract. Do not assume every target returns JSON unless that target says it does.Calling private implementation code directlyIf a component imports the thing behind the target, the target boundary has been bypassed.Forgetting cancellationSearch boxes, typeahead, and long tasks should use an abort signal so stale work can stop.

Quick reference

const reply = await sendRequestToBitfieldTarget({
  target: 'target.name',
  payload: { ok: true },
});
With cancellation:
const controller = new AbortController();
await sendRequestToBitfieldTarget(request, controller.signal);
Decode JSON:
const json = JSON.parse(new TextDecoder().decode(reply.payload));

Ceiling you have not hit yet

  • Replace work behind a target: Keep target stable while swapping package implementation.
  • Use binary replies: Return bytes for image, audio, packed table, or model output workflows.
  • Run local-first actions: Let a device ask local targets for work even when the network is not part of the path.
  • Give AI a stable command surface: Let an AI agent call named targets instead of editing implementation wiring.
Last modified on May 9, 2026