Projects
StackShift Functions
LiveDeploy Node.js serverless-style functions from an `api/` directory with HTTP handlers, async invokes, queue triggers, schedules, retries, replayable runs, and instant HTTPS routing.
Goal
Build and operate a StackShift Functions project as the fastest path to one endpoint, one webhook, one cron, or one worker without learning the platform internals first.
Current status
This area is documented as current, user-reliable behavior.
Workflow
- 1Create function files under `api/` and export HTTP methods such as `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, or `HEAD`.
- 2Add `stackshift.functions.json` when you need async, queue, schedule, timeout, retry, or memory configuration.
- 3Deploy the project with workload type `functions`; StackShift builds `.stackshift/functions-runtime.mjs` and a functions manifest into the image.
- 4Caddy sends public traffic to the agent functions gateway, and the gateway cold-starts the project container only when a request arrives.
- 5Use `@stackshift/functions` from inside a function to enqueue async work by function path or explicit queue name.
- 6Use the Functions overview to inspect HTTP endpoints, queues, schedules, runs, retries, and replayable failures.
Project layout
A StackShift Functions project is detected from an `api/` directory when the repo does not have a stronger full-stack framework signal. During build, StackShift copies or compiles those handlers into `.stackshift/functions/api`, writes `.stackshift/functions-manifest.json`, and starts the generated runtime with `node .stackshift/functions-runtime.mjs`.
Use file paths to define routes. Static route segments win over dynamic route segments, so `api/users/me.js` is matched before `api/users/[id].js`.
Minimal layout
bashapi/
hello.js
users/
[id].ts
stackshift.functions.json
package.jsonHTTP handlers
Export named HTTP methods from a handler file. `HEAD` falls back to `GET` when a dedicated `HEAD` export is not present. CommonJS-style default objects are also supported after build, but named ESM exports are the clearest path.
api/hello.js
jsexport async function GET(request, context) {
return Response.json({
message: 'Hello from StackShift Functions',
requestId: context.requestId,
})
}
export async function POST(request) {
const body = await request.json()
return Response.json({ received: body }, { status: 201 })
}api/users/[id].ts
tsexport async function GET(_request: Request, context: { params: { id: string } }) {
return Response.json({ userId: context.params.id })
}Function configuration
`stackshift.functions.json` is optional. Add it when a function needs a non-default timeout, memory size, retry count, async trigger, explicit queue trigger, or schedule trigger. Config keys use source paths from the repo, not compiled `.stackshift` paths.
- HTTP timeout must be positive and within the supported HTTP limit.
- Async, queue, and scheduled functions may use longer timeouts than HTTP handlers.
- Supported memory values are `128m`, `256m`, `512m`, and `1g`.
- Retries apply to non-HTTP work and are capped by the platform.
- Use one explicit queue name per function in v1. `background: true` remains supported and maps to the built-in `_async` queue.
stackshift.functions.json
js{
"functions": {
"api/hello.js": {
"timeout": 10,
"memory": "128m"
},
"api/process-image.js": {
"background": true,
"timeout": 300,
"memory": "512m",
"retries": 3
},
"api/thumbnail.js": {
"queue": "images",
"timeout": 300,
"retries": 5
},
"api/cleanup.js": {
"schedule": "0 * * * *",
"timeout": 60
}
}
}Async invocation and queues
Use `@stackshift/functions` when one function needs to enqueue another function for async work. In deployed workloads, the helper submits a durable run through the private StackShift async endpoint so retries and replay stay available after the original HTTP request has finished.
Use `invoke(functionPath, payload)` for function-to-function async work or `enqueue(queueName, payload)` when you want a stable queue name for producers outside the target function file. The built-in `_async` queue backs legacy `background: true` functions.
Queue work from an HTTP handler
jsimport { enqueue, invoke } from '@stackshift/functions'
export async function POST(request, context) {
const payload = await request.json()
await invoke('api/process-image.js', payload, {
requestId: context.requestId,
})
await enqueue('images', payload, {
requestId: context.requestId,
})
return Response.json({ queued: true }, { status: 202 })
}Schedules
Scheduled functions export `run(context)` and must be configured with a schedule. StackShift syncs schedules from the generated manifest after a successful deployment and enqueues durable function runs on the declared cadence. New jobs are scheduled for their next real due time, not forced to run immediately after deploy.
- Supported aliases: `@hourly`, `@daily`, and `@weekly`.
- Supported intervals: `@every 15m`, `@every 2h`, and similar positive durations.
- Supported simple cron forms include `*/10 * * * *`, `0 * * * *`, and `0 */2 * * *`.
- When a scheduled function is removed from the manifest, StackShift disables the stale scheduled job.
- Failed scheduled runs appear in the project Runs view and can be replayed from there.
api/cleanup.js
jsexport async function run(context) {
console.log('cleanup started', {
scheduledAt: context.scheduledAt,
requestId: context.requestId,
})
// remove expired records, rotate caches, or sync external state
}Routing and cold starts
In Phase 4, functions are no longer just a file-based runtime inside an always-running project container. Public requests enter through Caddy, Caddy forwards to the local agent functions gateway, and the gateway starts or reuses the project function container.
The generated runtime exposes a health endpoint for readiness, normal HTTP route dispatch, and reserved internal invoke endpoints. The agent tracks idle time, active request count, startup timeout, and max concurrency for each functions deployment, while durable jobs drive non-HTTP retries and replayable execution.
- Caddy adds `X-StackShift-Functions-Runtime` so the gateway knows which project runtime to use.
- The gateway cold-starts `app_<runtimeID>` when the function is inactive and waits for `__stackshift/health`.
- Idle containers are stopped after the configured idle timeout and restarted on the next request.
- Reserved internal invoke paths require `X-StackShift-Internal-Token` before any cold start is attempted.
Environment variables
Most projects do not need to set any functions-specific environment variables. StackShift injects the async helper URL, per-project async token, and project ID during deployment. The runtime still exposes internal invoke defaults for local development and compatibility.
- `PORT`: the port used by the generated runtime; defaults to the project runtime port, commonly `3000`.
- `STACKSHIFT_FUNCTIONS_ASYNC_URL`: private StackShift endpoint used by `@stackshift/functions` to create durable async runs in deployed environments.
- `STACKSHIFT_FUNCTIONS_ASYNC_TOKEN`: private per-project token used to authorize async enqueue requests.
- `STACKSHIFT_PROJECT_ID`: injected project identifier used by the helper when submitting async or queue work.
- `STACKSHIFT_FUNCTIONS_INVOKE_TOKEN`: private token still used by the internal gateway/runtime invoke path.
- `STACKSHIFT_FUNCTIONS_INVOKE_URL`: optional local override for unusual topologies. Leave it unset in normal production; the compatibility default remains `http://127.0.0.1:$PORT/.stackshift/functions/invoke` inside the runtime container.
- `FUNCTIONS_ROOT` and `FUNCTIONS_MANIFEST`: generated runtime paths. Override only for custom runtime debugging.
Production URL guidance
`api.stackshift.cloud` is the StackShift control-plane API. It is not the public endpoint your users hit, and it should not be used as a browser-facing Functions origin. In deployed workloads, the async helper talks to a private StackShift endpoint using the injected async URL and token while public HTTP traffic continues to flow through your project domain and the functions gateway.
On the current production agent install, Caddy runs as a host/systemd service and can reach the functions gateway on `127.0.0.1:<FUNCTIONS_GATEWAY_PORT>`. If Caddy is moved into Docker, the gateway binding and Caddy upstream target must be reviewed together because Docker Caddy cannot use its own `127.0.0.1` to reach the host agent.
- Do not expose `STACKSHIFT_FUNCTIONS_ASYNC_TOKEN` or `STACKSHIFT_FUNCTIONS_INVOKE_TOKEN` to browsers, client bundles, or public logs.
- Do not point your frontend directly at the private async helper endpoint.
- Do keep normal platform API clients pointed at `https://api.stackshift.cloud/api/v1` when they are calling the public StackShift API.
Operational checklist
- Confirm the project workload type is `functions` after detection or manual override.
- Check build logs for manifest warnings about missing configured source files, duplicate queue names, or invalid schedules.
- Check deployment logs for gateway registration, Caddy route updates, and cold-start errors.
- Use the Functions overview for HTTP endpoints, queues, schedules, and runs before dropping down to raw durable-jobs internals.
- Use function logs for `[stackshift-functions]` entries including request ID, function path, status, duration, trigger type, run ID, and timeout state.
- Replay failed schedule and queue runs or retry failed async runs from the project Runs view.
Expected result
The project runs as a managed functions workload: public HTTP routes are routed through Caddy and the agent gateway, async and queue work is durable, schedules run on their declared cadence, and failed non-HTTP runs can be retried or replayed from the project surface.
Common failures
- A function file exports no HTTP method and has no configured `background`, `queue`, or `schedule` trigger.
- A scheduled function is configured but does not export `run`.
- Two functions are configured with the same explicit `queue` name.
- `STACKSHIFT_FUNCTIONS_ASYNC_URL` is pointed at the wrong host or the async token is missing, so helper-based enqueue requests are rejected.
- The app listens on the wrong port or overrides `PORT` without matching the project runtime port.
- Caddy is containerized while the functions gateway is bound to host `127.0.0.1`; the current production install expects host/systemd Caddy.