|
2 | 2 |
|
3 | 3 | This guide helps AI agents quickly navigate the constructive-functions workspace. |
4 | 4 |
|
5 | | -## Quick Start |
6 | | - |
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 |
13 | | - |
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/*` |
| 5 | +## Architecture |
18 | 6 |
|
19 | | -## Monorepo Layout |
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 |
| 7 | +See [docs/spec/function-templating.md](docs/spec/function-templating.md) for the full specification. |
24 | 8 |
|
25 | | -## Function Architecture |
| 9 | +Functions use a **templating system**: developers write `handler.ts` + `handler.json` in `functions/`, and `scripts/generate.ts` produces workspace packages in `generated/` with all boilerplate. |
26 | 10 |
|
27 | | -**Image naming:** |
| 11 | +## Quick Start |
28 | 12 |
|
29 | | -All Docker images use the registry prefix `ghcr.io/constructive-io/constructive-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 | +``` |
30 | 18 |
|
31 | | -- `ghcr.io/constructive-io/constructive-functions/simple-email:latest` |
32 | | -- `ghcr.io/constructive-io/constructive-functions/send-email-link:latest` |
| 19 | +## Monorepo Layout |
33 | 20 |
|
34 | | -The `REGISTRY` variable in the Makefile controls this prefix. |
| 21 | +``` |
| 22 | +functions/ # User-authored source (git tracked) |
| 23 | + <name>/ |
| 24 | + handler.ts # Business logic (default export) |
| 25 | + handler.json # Metadata + dependencies |
| 26 | + *.d.ts # Optional type declarations |
| 27 | +
|
| 28 | +generated/ # Generated workspace packages (gitignored) |
| 29 | + <name>/ |
| 30 | + package.json # Workspace package |
| 31 | + tsconfig.json # Compiler config |
| 32 | + index.ts # Entry point |
| 33 | + handler.ts # Symlink -> functions/<name>/handler.ts |
| 34 | + dist/ # Compiled output |
| 35 | +
|
| 36 | +packages/ |
| 37 | + fn-app/ # Express app factory with job callbacks |
| 38 | + fn-runtime/ # Runtime: createFunctionServer, GraphQL clients, context |
| 39 | +
|
| 40 | +job/ |
| 41 | + server/ # Callback receiver |
| 42 | + worker/ # Job dispatcher |
| 43 | + service/ # Orchestrator (loads functions + worker + scheduler) |
| 44 | +
|
| 45 | +scripts/ |
| 46 | + generate.ts # Generator script (runs via Node's native type stripping) |
| 47 | +
|
| 48 | +k8s/ # Kubernetes manifests and overlays |
| 49 | +``` |
35 | 50 |
|
36 | | -Both functions follow the same pattern: |
| 51 | +## Function Pattern |
37 | 52 |
|
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` |
| 53 | +Each function exports a `FunctionHandler` that receives params and a context: |
48 | 54 |
|
49 | | -## Common Workflows |
| 55 | +```typescript |
| 56 | +import type { FunctionHandler } from '@constructive-io/fn-runtime'; |
50 | 57 |
|
51 | | -**Build all functions locally:** |
| 58 | +const handler: FunctionHandler = async (params, context) => { |
| 59 | + const { client, meta, log, env, job } = context; |
| 60 | + // client/meta: GraphQL clients (tenant-scoped, created per-request) |
| 61 | + // log: structured logger |
| 62 | + // env: process.env |
| 63 | + // job: { jobId, workerId, databaseId } |
| 64 | + return { complete: true }; |
| 65 | +}; |
52 | 66 |
|
53 | | -```bash |
54 | | -pnpm install |
55 | | -pnpm build |
| 67 | +export default handler; |
56 | 68 | ``` |
57 | 69 |
|
58 | | -**Build Docker images:** |
| 70 | +## Entry Points |
59 | 71 |
|
60 | | -```bash |
61 | | -make docker-build # Build all functions |
62 | | -make docker-build-simple-email # Build one function |
63 | | -make docker-build-send-email-link |
64 | | -``` |
| 72 | +- Function handlers: `functions/*/handler.ts` |
| 73 | +- Generated entry points: `generated/*/index.ts` (compiled to `generated/*/dist/index.js`) |
| 74 | +- Job orchestrator: `job/service/src/index.ts` |
| 75 | +- Generator script: `scripts/generate.ts` |
| 76 | + |
| 77 | +## Common Workflows |
65 | 78 |
|
66 | | -**Deploy to Kubernetes (local):** |
| 79 | +**Add a new function:** |
| 80 | +1. Create `functions/<name>/handler.json` with name, version, dependencies |
| 81 | +2. Create `functions/<name>/handler.ts` with default export |
| 82 | +3. Run `pnpm generate && pnpm install && pnpm build` |
| 83 | +4. Add to function registry in `job/service/src/index.ts` if needed |
67 | 84 |
|
| 85 | +**Local development with Docker:** |
68 | 86 | ```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 |
| 87 | +make dev # docker compose up (postgres + job-service) |
| 88 | +make dev-down # docker compose down |
73 | 89 | ``` |
74 | 90 |
|
75 | | -**Run locally with Docker (manual):** |
76 | | - |
| 91 | +**Regenerate after changing handler.json:** |
77 | 92 | ```bash |
78 | | -docker run -p 8080:8080 -e SIMPLE_EMAIL_DRY_RUN=true ghcr.io/constructive-io/constructive-functions/simple-email:latest |
| 93 | +pnpm generate # Regenerates package.json, tsconfig, index.ts, symlinks |
| 94 | +pnpm install # Picks up any new dependencies |
| 95 | +pnpm build # Recompile |
79 | 96 | ``` |
80 | 97 |
|
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 |
| 98 | +## Key Details |
92 | 99 |
|
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 |
| 100 | +- Functions run on `PORT=8080` (Knative default) |
| 101 | +- Email functions support dry-run via `SIMPLE_EMAIL_DRY_RUN` / `SEND_EMAIL_LINK_DRY_RUN` |
| 102 | +- `loadFunctionApp()` in job/service resolves modules by name (e.g. `@constructive-io/simple-email-fn`) |
| 103 | +- GraphQL clients require `GRAPHQL_URL` env var and `X-Database-Id` header |
| 104 | +- The `generated/` directory is entirely gitignored |
0 commit comments