Foundations

Host functions

Learn how sandboxed code calls functions in the host application.

Host functions

Host functions are functions that guest code is allowed to call. They are the only supported way for source running in the sandbox to interact with your application or the systems connected to it.

A host function should expose one narrow capability. Rather than handing the guest a database client, expose a function that loads one kind of record, after the application's validation and authorization rules have run.

Defining host functions

Host functions are defined under a namespace. The namespace becomes a global object in the sandbox, and each host function becomes a method on that object.

import { run } from 'run';

const result = await run({
  source: `
    const user = await users.find("user_123");
    return user;
  `,
  hostFunctions: {
    users: {
      find: async (id: string) => {
        return { id, name: 'Ada' };
      },
    },
  },
});

The fully qualified name of the host function in this example is users.find. The users namespace is available only for this invocation.

A namespace must be a valid JavaScript identifier, must not conflict with a reserved global, and must not begin with __run.

Calls

Calling a host function creates a request from the guest to the host. The arguments are serialized, copied to the host, and passed to the host function as ordinary function arguments. The returned value is then serialized and copied back to the guest.

Host functions may return values directly or return promises:

const hostFunctions = {
  math: {
    double: (value: number) => value * 2,
  },
  users: {
    find: async (id: string) => {
      return { id, name: 'Ada' };
    },
  },
};

Guest code should await every host function call. Returning while host function work is still pending can fail the invocation because the runtime cannot safely detach host work from the guest that requested it.

await events.record({ name: 'opened' });
return { recorded: true };

Several host function calls can run concurrently when the guest awaits them together:

const [profile, orders] = await Promise.all([
  users.profile(userId),
  orders.list(userId),
]);

The invocation limits both the total number of host function calls and the number that may be in flight at once.

Context

A host function can call getHostFunctionContext() to read information about its active request:

import { getHostFunctionContext } from 'run';

const hostFunctions = {
  users: {
    find: async (id: string) => {
      const context = getHostFunctionContext();

      return {
        id,
        logicalRunId: context.logicalRunId,
        name: 'Ada',
      };
    },
  },
};

getHostFunctionContext() exposes the following information and controls:

  • abortSignal lets the host function stop its work when the run is cancelled, times out, or fails.
  • invocationId identifies the current execution attempt. It changes when an interrupted run is replayed.
  • logicalRunId identifies the complete workflow and remains stable across replay attempts.
  • requestId identifies the current host function call within the invocation.
  • requestIndex contains the one-based order of the host function call.
  • hostFunctionName contains the fully qualified host function name, such as users.find.
  • interrupt(payload) pauses the run and returns an application-defined payload to the host.
  • resume is available when an interrupted host function is invoked again. It contains the interruption ID, original payload, and supplied resolution.

Use these identifiers to trace, audit, and deduplicate external side effects. They are not authentication credentials.

Cancellation

The host function context includes an AbortSignal. It becomes aborted when the invocation is cancelled, times out, or otherwise fails.

Host functions should pass this signal to operations that support cancellation:

const hostFunctions = {
  search: {
    query: async (query: string) => {
      const { abortSignal } = getHostFunctionContext();

      return searchClient.query(query, {
        signal: abortSignal,
      });
    },
  },
};

Aborting the signal cannot automatically undo work that has already completed. Host functions that perform writes should use application-level idempotency and deduplication.

Failures

When a host function throws, its guest-side promise rejects. Guest code may catch the error and continue:

try {
  return await inventory.reserve('item_123');
} catch (error) {
  return {
    reserved: false,
    message: error.message,
  };
}

The guest receives a sanitized error without the host stack or private diagnostic details. The host should record the complete error before rethrowing it when those diagnostics are needed.

Host functions are trusted code. They should validate every argument, enforce authorization in the host, and return only the data required by the source.