|
2 | 2 |
|
3 | 3 | This guide helps AI agents quickly navigate the constructive-functions workspace. |
4 | 4 |
|
5 | | -## Quick Start |
| 5 | +## Architecture |
| 6 | + |
| 7 | +See [docs/spec/function-templating.md](docs/spec/function-templating.md) for the full specification. |
| 8 | + |
| 9 | +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. |
6 | 10 |
|
7 | | -**Most important commands:** |
8 | | -- `pnpm build` — Build all function packages (TypeScript → `dist/`) |
9 | | -- `make docker-build` — Build Docker images for all functions |
10 | | -- `make docker-build-simple-email` — Build just the simple-email image |
11 | | -- `make docker-build-send-email-link` — Build just the send-email-link image |
12 | | -- `cd k8s && make kustomize-local` — Deploy functions to a local Kubernetes cluster |
| 11 | +## Quick Start |
13 | 12 |
|
14 | | -**Entry points:** |
15 | | -- Function HTTP handlers: `functions/*/src/index.ts` |
16 | | -- Docker entrypoints: `functions/*/Dockerfile` (`CMD ["node", "dist/index.js"]`) |
17 | | -- K8s manifests: `k8s/base/functions/*` and `k8s/overlays/local/functions/*` |
| 13 | +```bash |
| 14 | +pnpm generate # Generate generated/<name>/ from functions/*/handler.json |
| 15 | +pnpm install # Install deps (preinstall runs generate.ts automatically) |
| 16 | +pnpm build # Build all packages + functions |
| 17 | +``` |
18 | 18 |
|
19 | 19 | ## Monorepo Layout |
20 | 20 |
|
21 | | -- `functions/*` — Function packages (`send-email-link`, `simple-email`) |
22 | | -- `k8s/` — Kubernetes manifests, overlays, and setup scripts |
23 | | -- `types/` — Custom type definitions for `@launchql/*` packages |
| 21 | +``` |
| 22 | +functions/ # User-authored source (git tracked) |
| 23 | + <name>/ |
| 24 | + handler.ts # Business logic (default export) |
| 25 | + handler.json # Metadata + dependencies + template type |
| 26 | + *.d.ts # Optional type declarations |
| 27 | +
|
| 28 | +templates/ # Template definitions (git tracked) |
| 29 | + node-graphql/ # Default template type |
| 30 | + package.json # Base package.json with {{placeholders}} |
| 31 | + tsconfig.json # Static compiler config |
| 32 | + index.ts # Entry point template with {{name}} |
| 33 | + Dockerfile # Per-function production Docker build |
| 34 | + k8s/ |
| 35 | + knative-service.yaml # Base Knative Service manifest |
| 36 | +
|
| 37 | +generated/ # Generated workspace packages (gitignored) |
| 38 | + <name>/ |
| 39 | + package.json # Merged from template + handler.json deps |
| 40 | + tsconfig.json # Copied from template (+ .d.ts includes) |
| 41 | + index.ts # Copied from template with {{name}} replaced |
| 42 | + handler.ts # Symlink -> functions/<name>/handler.ts |
| 43 | + dist/ # Compiled output |
| 44 | +
|
| 45 | +packages/ |
| 46 | + fn-app/ # Express app factory with job callbacks |
| 47 | + fn-runtime/ # Runtime: createFunctionServer, GraphQL clients, context |
| 48 | +
|
| 49 | +job/ |
| 50 | + server/ # Callback receiver |
| 51 | + worker/ # Job dispatcher |
| 52 | + service/ # Orchestrator (loads functions + worker + scheduler) |
| 53 | +
|
| 54 | +scripts/ |
| 55 | + generate.ts # Template-based generator (copies + merges + replaces) |
| 56 | + docker-build.ts # Per-function Docker image builder |
| 57 | +``` |
24 | 58 |
|
25 | | -## Function Architecture |
| 59 | +## Function Pattern |
26 | 60 |
|
27 | | -**Image naming:** |
| 61 | +Each function exports a `FunctionHandler` that receives params and a context: |
28 | 62 |
|
29 | | -All Docker images use the registry prefix `ghcr.io/constructive-io/constructive-functions/`: |
| 63 | +```typescript |
| 64 | +import type { FunctionHandler } from '@constructive-io/fn-runtime'; |
30 | 65 |
|
31 | | -- `ghcr.io/constructive-io/constructive-functions/simple-email:latest` |
32 | | -- `ghcr.io/constructive-io/constructive-functions/send-email-link:latest` |
| 66 | +const handler: FunctionHandler = async (params, context) => { |
| 67 | + const { client, meta, log, env, job } = context; |
| 68 | + // client/meta: GraphQL clients (tenant-scoped, created per-request) |
| 69 | + // log: structured logger |
| 70 | + // env: process.env |
| 71 | + // job: { jobId, workerId, databaseId } |
| 72 | + return { complete: true }; |
| 73 | +}; |
33 | 74 |
|
34 | | -The `REGISTRY` variable in the Makefile controls this prefix. |
| 75 | +export default handler; |
| 76 | +``` |
35 | 77 |
|
36 | | -Both functions follow the same pattern: |
| 78 | +## handler.json Schema |
| 79 | + |
| 80 | +```json |
| 81 | +{ |
| 82 | + "name": "send-email-link", |
| 83 | + "version": "1.1.0", |
| 84 | + "description": "Sends invite, password reset, and verification emails", |
| 85 | + "type": "node-graphql", |
| 86 | + "dependencies": { |
| 87 | + "graphql-tag": "^2.12.6" |
| 88 | + } |
| 89 | +} |
| 90 | +``` |
37 | 91 |
|
38 | | -1. **Source** (`src/index.ts`): |
39 | | - - Import `app` from `@constructive-io/knative-job-fn` (Express app) |
40 | | - - Define HTTP handler with `app.post('/', ...)` |
41 | | - - Export `app` and start server if `require.main === module` |
42 | | -2. **Build** (`package.json` → `tsc`): |
43 | | - - TypeScript compiles to `dist/index.js` |
44 | | -3. **Docker** (`Dockerfile`): |
45 | | - - Uses `node:22-alpine` |
46 | | - - Installs production deps via `pnpm` |
47 | | - - Copies `dist/` and runs `node dist/index.js` |
| 92 | +- `type` selects the template from `templates/<type>/` (default: `"node-graphql"`) |
| 93 | +- `dependencies` are merged into the template's base package.json |
48 | 94 |
|
49 | | -## Common Workflows |
| 95 | +## Entry Points |
50 | 96 |
|
51 | | -**Build all functions locally:** |
| 97 | +- Function handlers: `functions/*/handler.ts` |
| 98 | +- Generated entry points: `generated/*/index.ts` (compiled to `generated/*/dist/index.js`) |
| 99 | +- Job orchestrator: `job/service/src/index.ts` |
| 100 | +- Generator script: `scripts/generate.ts` |
| 101 | +- Docker builder: `scripts/docker-build.ts` |
52 | 102 |
|
53 | | -```bash |
54 | | -pnpm install |
55 | | -pnpm build |
56 | | -``` |
| 103 | +## Common Workflows |
57 | 104 |
|
58 | | -**Build Docker images:** |
| 105 | +**Add a new function:** |
| 106 | +1. Create `functions/<name>/handler.json` with name, version, type, dependencies |
| 107 | +2. Create `functions/<name>/handler.ts` with default export |
| 108 | +3. Run `pnpm generate && pnpm install && pnpm build` |
| 109 | +4. Add to function registry in `job/service/src/index.ts` if needed |
59 | 110 |
|
| 111 | +**Build Docker images:** |
60 | 112 | ```bash |
61 | | -make docker-build # Build all functions |
62 | | -make docker-build-simple-email # Build one function |
63 | | -make docker-build-send-email-link |
| 113 | +make docker-build # build all function images |
| 114 | +make docker-build-send-email-link # build single function image |
64 | 115 | ``` |
65 | 116 |
|
66 | | -**Deploy to Kubernetes (local):** |
67 | | - |
| 117 | +**Local development with Docker:** |
68 | 118 | ```bash |
69 | | -cd k8s |
70 | | -make operators-knative-only # Install Knative |
71 | | -make kustomize-local # Apply manifests |
72 | | -make proxy-server # Forward API to localhost:8080 |
| 119 | +make dev # docker compose up (postgres + job-service) |
| 120 | +make dev-down # docker compose down |
73 | 121 | ``` |
74 | 122 |
|
75 | | -**Run locally with Docker (manual):** |
76 | | - |
| 123 | +**Regenerate after changing handler.json:** |
77 | 124 | ```bash |
78 | | -docker run -p 8080:8080 -e SIMPLE_EMAIL_DRY_RUN=true ghcr.io/constructive-io/constructive-functions/simple-email:latest |
| 125 | +pnpm generate # Copies templates, merges deps, replaces placeholders |
| 126 | +pnpm install # Picks up any new dependencies |
| 127 | +pnpm build # Recompile |
79 | 128 | ``` |
80 | 129 |
|
81 | | -## Type Definitions |
82 | | - |
83 | | -The `types/` directory contains manual type definitions for packages without built-in types: |
84 | | - |
85 | | -- `@launchql/mjml` — Styled-email generator (MJML-based email templates) |
86 | | -- `@launchql/postmaster` — Mailgun email sender |
87 | | -- `@launchql/styled-email` — Email template components |
88 | | - |
89 | | -These are referenced in `tsconfig.json` via `"typeRoots": ["./types", "./node_modules/@types"]`. |
90 | | - |
91 | | -## Tips |
| 130 | +## Key Details |
92 | 131 |
|
93 | | -1. Build functions before building Docker images — `dist/` must exist |
94 | | -2. Functions run on `PORT=8080` (Knative default) |
95 | | -3. Email functions support dry-run mode via env vars (`SIMPLE_EMAIL_DRY_RUN`, `SEND_EMAIL_LINK_DRY_RUN`) |
96 | | -4. K8s manifests reference Docker images; update image tags after pushing to registry |
| 132 | +- Functions run on `PORT=8080` (Knative default) |
| 133 | +- Email functions support dry-run via `SIMPLE_EMAIL_DRY_RUN` / `SEND_EMAIL_LINK_DRY_RUN` |
| 134 | +- `loadFunctionApp()` in job/service resolves modules by name (e.g. `@constructive-io/simple-email-fn`) |
| 135 | +- GraphQL clients require `GRAPHQL_URL` env var and `X-Database-Id` header |
| 136 | +- The `generated/` directory is entirely gitignored |
| 137 | +- Templates use `{{name}}`, `{{version}}`, `{{description}}` placeholders |
| 138 | +- Generator supports `--only=<name>` for single-function generation |
0 commit comments