Skip to main content

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

What this is

A target is a public callable name. Your app sends a payload to that name. Runtime Kit routes the payload to the package code registered for that target. 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 action 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 action reply shape stays the same.That is the value: app code calls the public target name, not the private implementation.

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 action reply shape
read

useBitfieldData(...) reads values already prepared for the surface.

act

sendRequestToBitfieldTarget(...) sends bytes to a named target.

reply

The app decodes only what the action reply shape promises.

The request shape is:
type BitfieldTargetRequest = {
  target: string;
  payload?: unknown;
};

type BitfieldTargetReply = {
  payload: Uint8Array;
};
For the exact request type, payload conversion table, reply type, cancellation behavior, and invalid examples, read Runtime Kit API.

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 action reply shape 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, or booleanSerialized as JSON bytes.
null or 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.If a target needs JSON with a null value, send an object that contains the null field:
await sendRequestToBitfieldTarget({
  target: 'settings.update',
  payload: { value: null },
});

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 the private target implementation.

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 imports implementation details.App code uses only the payload and reply contract.
A caller has to understand private execution code.A caller sends a request to a public target name.

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 action reply shape. Do not assume every target returns JSON unless that target says it does.Calling private implementation code directlyIf a component imports the target implementation, the request 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));

Now build the bigger version

Write the action reply shape before the button.
type SearchRequest = {
  query: string;
};

type SearchReply = {
  items: Array<{ id: string; name: string }>;
};
Then make the button use only that contract:
async function search(query: string): Promise<SearchReply> {
  const reply = await sendRequestToBitfieldTarget({
    target: 'product.search',
    payload: { query } satisfies SearchRequest,
  });

  return JSON.parse(new TextDecoder().decode(reply.payload)) as SearchReply;
}
For binary work, keep the same shape. The target name stays public, the payload contract stays explicit, and the reply stays bytes:
const reply = await sendRequestToBitfieldTarget({
  target: 'image.thumbnail',
  payload: imageBytes,
});

const thumbnailBytes = reply.payload;
Last modified on May 10, 2026