Sandbox
Use a Sandbox Definition to run a named package project in a Box. The package supplies dependencies, Workspace supplies durable files, and the Box adapter runs the process.
Quick start
Install and register the Vite integration:
pnpm add @vite-hub/sandbox
import { hubSandbox } from '@vite-hub/sandbox/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [hubSandbox()],
})
Every discovered Definition belongs to a real package project. ViteHub never writes a manifest into your repository, so create the smallest valid one when the package has no dependencies:
{
"private": true,
"type": "module",
"vitehub": {
"sandbox": {
"timeout": 30000
}
}
}
interface SandboxPayload {
notes?: string
}
export default async function releaseNotes(payload: SandboxPayload = {}) {
return { text: payload.notes?.toUpperCase() || 'No notes' }
}
import { runSandbox } from '@vite-hub/sandbox'
export default defineEventHandler(async () => {
const [error, result] = await runSandbox('release-notes', { notes: 'ship it' })
if (error) throw error
return result
})
How Sandbox, Workspace, and Box fit together
- Sandbox discovers definitions, resolves package projects, serializes values, applies timeouts, and coordinates each run.
- Workspace stores durable files and handles Sources, snapshots, diffs, commits, and rollbacks.
- Box provides process isolation, runtime files, caches, ports, and provider-specific deployment output.
Sandbox and Agent use the same Box Interface. Workspace never selects Cloudflare, Vercel, Crabbox, or trusted-host execution.
Package projects
Under server/sandboxes, use one folder per package project with an adjacent package.json and index.ts. The folder path supplies the Definition name. Other files in the package are ordinary helpers rather than independently discovered Sandboxes.
server/sandboxes/
├── image/
│ ├── package.json
│ └── index.ts
└── metadata/
├── package.json
└── index.ts
For free-form Definitions outside server/sandboxes, use the <path>.sandbox.ts suffix convention with defineSandbox(). Those Definitions use their nearest package.json, so several files can share one package project.
Package-manager selection uses the manifest's packageManager field, then a lockfile at that package root, then npm. A nested independent package never inherits an unrelated ancestor lockfile. Lockfiles enable frozen installation. ViteHub installs the project inside the Box before the entrypoint launches, and dependency trees never enter Workspace commits.
ViteHub also understands a standard pnpm Workspace without adding ViteHub-specific workspace configuration:
server/sandboxes/
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── image/
├── package.json
└── index.ts
Installation runs at the pnpm Workspace root and the Definition runs from server/sandboxes/image. ViteHub carries every local package in the transitive workspace:* dependency closure, then pnpm remains responsible for installation and linking semantics. Other Workspace packages stay outside the runtime project.
Package entry point
The package index.ts default-exports an ordinary async function. ViteHub calls it with the invocation payload and context, then returns its awaited result.
export default async function optimize(
payload: { image: Blob },
context: { requestId: string },
) {
return await optimizeImage(payload.image, context.requestId)
}
runSandbox() infers its payload and result from the default function. A zero-argument function accepts an unknown payload.
Nested Blob and Uint8Array values in payloads and results are staged through invocation-local Box files. Node.js Buffer values retain their Buffer type. Application code keeps the binary values and does not convert them to base64 JSON. Other values keep the existing JSON-serialization contract.
The entrypoint gets normal JavaScript, package imports, top-level await, process.cwd(), environment variables, and a filesystem, without a runtime framework import.
The first package metadata schema contains only vitehub.sandbox.timeout. It must be a positive integer no greater than 2_147_483_647, and ViteHub enforces it while preparing and executing the package.
Box adapters and images
Cloudflare, Vercel, Crabbox, and trusted host implement the Box Interface. The common contract covers binary files, directory operations, cwd/env/timeout command execution, abort, and lifecycle. Processes and ports are explicit optional capabilities.
Provider selection and full image overrides are application or host configuration. For Cloudflare, configure the application-owned container with a complete Dockerfile; for Vercel, configure the Box runtime image. Sandbox has no Dockerfile-fragment helper because partial image syntax cannot be portable across providers.
Public imports
| Import | Use |
|---|---|
defineSandbox from @vite-hub/sandbox | Declare a free-form <path>.sandbox.ts Definition. |
runSandbox from @vite-hub/sandbox | Invoke a discovered Definition. |
hubSandbox from @vite-hub/sandbox/vite | Register discovery, types, preparation, and provider output. |
Use Workspace for durable file state and Box configuration for execution environments.