This guide helps AI agents quickly navigate the constructive-functions workspace.
See docs/spec/function-templating.md for the full specification.
Functions use a template-based system: developers write handler.ts + handler.json in functions/, and scripts/generate.ts copies template files from templates/<type>/ into generated/ with placeholder replacement and dependency merging.
pnpm generate # Generate generated/<name>/ from functions/*/handler.json
pnpm install # Install deps (preinstall runs generate.ts automatically)
pnpm build # Build all packages + functionsfunctions/ # User-authored source (git tracked)
<name>/
handler.ts # Business logic (default export)
handler.json # Metadata + dependencies + template type
*.d.ts # Optional type declarations
templates/ # Template definitions (git tracked)
node-graphql/ # Default template type
package.json # Base package.json with {{placeholders}}
tsconfig.json # Static compiler config
index.ts # Entry point template with {{name}}
Dockerfile # Per-function production Docker build
k8s/
knative-service.yaml # Base Knative Service manifest
generated/ # Generated workspace packages (gitignored)
<name>/
package.json # Merged from template + handler.json deps
tsconfig.json # Copied from template (+ .d.ts includes)
index.ts # Copied from template with {{name}} replaced
handler.ts # Symlink -> functions/<name>/handler.ts
dist/ # Compiled output
packages/
fn-app/ # Express app factory with job callbacks
fn-runtime/ # Runtime: createFunctionServer, GraphQL clients, context
job/
server/ # Callback receiver
worker/ # Job dispatcher
service/ # Orchestrator (loads functions + worker + scheduler)
scripts/
generate.ts # Template-based generator (copies + merges + replaces)
docker-build.ts # Per-function Docker image builder
Each function exports a FunctionHandler that receives params and a context:
import type { FunctionHandler } from '@constructive-io/fn-runtime';
const handler: FunctionHandler = async (params, context) => {
const { client, meta, log, env, job } = context;
// client/meta: GraphQL clients (tenant-scoped, created per-request)
// log: structured logger
// env: process.env
// job: { jobId, workerId, databaseId, actorId }
return { complete: true };
};
export default handler;{
"name": "send-verification-link",
"version": "1.1.0",
"description": "Sends invite, password reset, and verification emails",
"type": "node-graphql",
"dependencies": {
"graphql-tag": "^2.12.6"
}
}typeselects the template fromtemplates/<type>/(default:"node-graphql")dependenciesare merged into the template's base package.json
- Function handlers:
functions/*/handler.ts - Generated entry points:
generated/*/index.ts(compiled togenerated/*/dist/index.js) - Job orchestrator:
job/service/src/index.ts - Generator script:
scripts/generate.ts - Docker builder:
scripts/docker-build.ts
Add a new function:
- Create
functions/<name>/handler.jsonwith name, version, type, dependencies - Create
functions/<name>/handler.tswith default export - Run
pnpm generate && pnpm install && pnpm build - Add to function registry in
job/service/src/index.tsif needed
Build Docker images:
make docker-build # build all function images
make docker-build-send-verification-link # build single function imageLocal development with Docker:
make dev # docker compose up (postgres + job-service)
make dev-down # docker compose downRegenerate after changing handler.json:
pnpm generate # Copies templates, merges deps, replaces placeholders
pnpm install # Picks up any new dependencies
pnpm build # Recompile- Each function declares its port in
handler.json(send-email8081,send-verification-link8082,knative-job-example8083,python-example8084); the job service uses 8080 - Email functions support dry-run via
SEND_EMAIL_DRY_RUN/SEND_VERIFICATION_LINK_DRY_RUN(legacySIMPLE_EMAIL_DRY_RUN/SEND_EMAIL_LINK_DRY_RUNstill honored as fallback) loadFunctionApp()in job/service resolves modules by name (e.g.@constructive-io/send-email-fn)- GraphQL clients require
GRAPHQL_URLenv var andX-Database-Idheader - The
generated/directory is entirely gitignored - Templates use
{{name}},{{version}},{{description}}placeholders - Generator supports
--only=<name>for single-function generation