Sandbox
Sandbox runs named server-side handlers inside an isolated provider runtime. Sandbox fits work that needs filesystem, process, or network isolation without introducing a longer-lived workflow or a delivery queue.
ViteHub discovers sandboxes from server/sandboxes/**.
Define a sandbox with createSandbox(options?)(handler) or defineSandbox(handler, options?), then execute it with runSandbox().
Getting started
Install the package
Install @vitehub/sandbox and the provider SDK you want to use.
pnpm add https://pkg.pr.new/nuxt-hub/agent/@vitehub/sandbox@main @cloudflare/sandbox
pnpm add https://pkg.pr.new/nuxt-hub/agent/@vitehub/sandbox@main @vercel/sandbox
pnpm add https://pkg.pr.new/nuxt-hub/agent/@vitehub/sandbox@main @deno/sandbox
pnpm add https://pkg.pr.new/nuxt-hub/agent/@vitehub/sandbox@main
Docker uses your local Docker installation. It does not require an extra npm package.
pnpm add https://pkg.pr.new/nuxt-hub/agent/@vitehub/sandbox@main
Local runs on your machine with OS-level isolation. It does not require an extra npm package.
Configure a provider
export default defineNuxtConfig({
modules: ['@vitehub/sandbox/nuxt'],
})
export default defineNuxtConfig({
modules: ['@vitehub/sandbox/nuxt'],
})
export default defineNuxtConfig({
modules: ['@vitehub/sandbox/nuxt'],
sandbox: {
provider: 'deno',
allowNet: ['api.notion.com'],
},
})
export default defineNuxtConfig({
modules: ['@vitehub/sandbox/nuxt'],
sandbox: {
provider: 'docker',
workspace: process.cwd(),
},
})
export default defineNuxtConfig({
modules: ['@vitehub/sandbox/nuxt'],
sandbox: {
provider: 'local',
},
})
Define a sandbox
Create a file in server/sandboxes/**. The file name becomes the sandbox name.
import { createSandbox } from '@vitehub/sandbox'
type ReleaseNotesPayload = {
notes?: unknown
}
export default createSandbox({
timeout: 30_000,
})(async (payload?: ReleaseNotesPayload) => {
const notes = typeof payload?.notes === 'string' ? payload.notes.trim() : ''
const items = notes
.split(/\n+/)
.map(line => line.replace(/^[-*]\s*/, '').trim())
.filter(Boolean)
return {
summary: items[0] || 'No notes provided.',
items: items.slice(0, 3),
}
})
Run the sandbox
Call the named sandbox from any server-side code. runSandbox() returns a result object, so check for errors before you return the value.
import { createError, readBody } from 'h3'
import { runSandbox } from '@vitehub/sandbox'
export default defineEventHandler(async (event) => {
const body = await readBody<{ notes?: unknown }>(event)
const result = await runSandbox('release-notes', body)
if (result.isErr()) {
throw createError({
statusCode: 500,
statusMessage: result.error.message,
data: {
code: result.error.code,
provider: result.error.provider,
},
})
}
return result.value
})
Public API
| Function | Use it for |
|---|---|
createSandbox(options?)(handler) | Register one named sandbox under the sandboxes directory. |
defineSandbox(handler, options?) | Compatibility wrapper for createSandbox(options?)(handler). |
runSandbox(name, payload, { context? }) | Execute that sandbox from a route, task, or webhook. |
Type reference
Handler signature
The handler receives an optional payload and an optional context object, and returns the result directly.
type SandboxHandler<TPayload, TResult> = (
payload?: TPayload,
context?: Record<string, unknown>,
) => TResult | Promise<TResult>
SandboxDefinitionOptions
The options object passed to createSandbox() or the second argument to defineSandbox() accepts portable options that work across all providers:
| Option | Type | Description |
|---|---|---|
timeout | number | Maximum execution time in milliseconds. |
env | Record<string, string> | Environment variables passed to the sandbox. |
runtime | { command, args? } | Custom runtime command for the sandbox process. |
cpu, ports, or sandboxId belong in the top-level sandbox config in nitro.config.ts, not in the definition.Result<T>
runSandbox() returns a Result<T> instead of throwing. Check the result before accessing the value.
| Method / Field | Type | Description |
|---|---|---|
isOk() | boolean | true when execution succeeded. |
isErr() | boolean | true when execution failed. |
value | T | The handler return value. Only safe after isOk(). |
error | SandboxError | Error details with message, code, provider, and details. |
How configuration works
Sandbox has two configuration layers:
- Top-level
sandboxconfig innitro.config.tsselects the provider and sets app-wide defaults. createSandbox(options?)(handler)configures one sandbox file with portable options such astimeoutandenv.defineSandbox(handler, options?)remains available as a compatibility wrapper.
runSandbox() is not configuration. It is the execution call, so that is where you pass the payload and optional per-request context.
export default defineNitroConfig({
sandbox: {
provider: 'vercel',
},
})
export default createSandbox({
timeout: 30_000,
})(async (payload) => {
return { ok: true }
})