Skip to content

Commit 328c3c3

Browse files
authored
Merge pull request #13 from constructive-io/anmol/templates
feature: templates
2 parents 4d5e9c7 + 0ed117a commit 328c3c3

47 files changed

Lines changed: 1443 additions & 933 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,6 @@ dist
137137
# Vite logs files
138138
vite.config.js.timestamp-*
139139
vite.config.ts.timestamp-*
140+
141+
# Generated function workspace packages (created by scripts/generate.ts)
142+
generated/

AGENTS.md

Lines changed: 107 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,95 +2,137 @@
22

33
This guide helps AI agents quickly navigate the constructive-functions workspace.
44

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.
610

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
1312

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+
```
1818

1919
## Monorepo Layout
2020

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+
```
2458

25-
## Function Architecture
59+
## Function Pattern
2660

27-
**Image naming:**
61+
Each function exports a `FunctionHandler` that receives params and a context:
2862

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';
3065

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+
};
3374

34-
The `REGISTRY` variable in the Makefile controls this prefix.
75+
export default handler;
76+
```
3577

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+
```
3791

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
4894

49-
## Common Workflows
95+
## Entry Points
5096

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`
52102

53-
```bash
54-
pnpm install
55-
pnpm build
56-
```
103+
## Common Workflows
57104

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
59110

111+
**Build Docker images:**
60112
```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
64115
```
65116

66-
**Deploy to Kubernetes (local):**
67-
117+
**Local development with Docker:**
68118
```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
73121
```
74122

75-
**Run locally with Docker (manual):**
76-
123+
**Regenerate after changing handler.json:**
77124
```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
79128
```
80129

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
92131

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

Dockerfile.dev

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
FROM node:22-alpine
2+
3+
RUN npm install -g pnpm@10.12.2
4+
5+
WORKDIR /usr/src/app
6+
7+
# Copy config files, generate script, and templates
8+
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml tsconfig.json ./
9+
COPY scripts/ scripts/
10+
COPY templates/ templates/
11+
12+
# Copy all package/job manifests for caching
13+
COPY packages/fn-app/package.json packages/fn-app/
14+
COPY packages/fn-runtime/package.json packages/fn-runtime/
15+
COPY job/server/package.json job/server/
16+
COPY job/worker/package.json job/worker/
17+
COPY job/service/package.json job/service/
18+
19+
# Copy function handler.json manifests
20+
COPY functions/ functions/
21+
22+
# Generate workspace packages + install deps
23+
RUN node --experimental-strip-types scripts/generate.ts && pnpm install --frozen-lockfile
24+
25+
# Copy source and build
26+
COPY . .
27+
RUN pnpm generate && pnpm build
28+
29+
ENV NODE_ENV=production

Makefile

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
.PHONY: build clean lint docker-build docker-build-simple-email docker-build-send-email-link docker-push docker-push-simple-email docker-push-send-email-link
2-
3-
REGISTRY := ghcr.io/constructive-io/constructive-functions
1+
.PHONY: build clean lint generate dev dev-build dev-down docker-build
42

53
build:
64
pnpm run build
@@ -11,32 +9,21 @@ clean:
119
lint:
1210
pnpm run lint
1311

12+
generate:
13+
pnpm run generate
14+
15+
dev-build:
16+
docker compose build
17+
18+
dev:
19+
docker compose up
20+
21+
dev-down:
22+
docker compose down
23+
1424
docker-build:
15-
@echo "Building Docker images for functions..."
16-
@for fn in functions/*; do \
17-
if [ -f "$$fn/Dockerfile" ]; then \
18-
echo "Building $$fn..."; \
19-
docker build -t "$(REGISTRY)/$$(basename $$fn):latest" "$$fn"; \
20-
fi \
21-
done
22-
23-
docker-build-simple-email:
24-
docker build -t $(REGISTRY)/simple-email:latest functions/simple-email
25-
26-
docker-build-send-email-link:
27-
docker build -t $(REGISTRY)/send-email-link:latest functions/send-email-link
28-
29-
docker-push:
30-
@echo "Pushing Docker images to $(REGISTRY)..."
31-
@for fn in functions/*; do \
32-
if [ -f "$$fn/Dockerfile" ]; then \
33-
echo "Pushing $$fn..."; \
34-
docker push "$(REGISTRY)/$$(basename $$fn):latest"; \
35-
fi \
36-
done
37-
38-
docker-push-simple-email:
39-
docker push $(REGISTRY)/simple-email:latest
40-
41-
docker-push-send-email-link:
42-
docker push $(REGISTRY)/send-email-link:latest
25+
pnpm run docker:build
26+
27+
# Build a single function image: make docker-build-send-email-link
28+
docker-build-%:
29+
node --experimental-strip-types scripts/docker-build.ts --only=$*

docker-compose.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
services:
2+
postgres:
3+
image: ghcr.io/launchql/pgvector-postgis:16.10
4+
environment:
5+
POSTGRES_DB: launchql
6+
POSTGRES_USER: postgres
7+
POSTGRES_PASSWORD: password
8+
ports:
9+
- "5432:5432"
10+
volumes:
11+
- pgdata:/var/lib/postgresql/data
12+
13+
job-service:
14+
build:
15+
context: .
16+
dockerfile: Dockerfile.dev
17+
command: node job/service/dist/run.js
18+
environment:
19+
PGHOST: postgres
20+
PGUSER: postgres
21+
PGPASSWORD: password
22+
PGDATABASE: launchql
23+
CONSTRUCTIVE_FUNCTIONS: all
24+
CONSTRUCTIVE_JOBS_ENABLED: "true"
25+
depends_on:
26+
- postgres
27+
ports:
28+
- "8080:8080"
29+
- "8081:8081"
30+
- "8082:8082"
31+
32+
volumes:
33+
pgdata:

0 commit comments

Comments
 (0)