Skip to content

Commit 272763a

Browse files
committed
initial setup for templatized functions
1 parent b375db9 commit 272763a

42 files changed

Lines changed: 1086 additions & 940 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: 77 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,95 +2,103 @@
22

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

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
186

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

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

27-
**Image naming:**
11+
## Quick Start
2812

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

31-
- `ghcr.io/constructive-io/constructive-functions/simple-email:latest`
32-
- `ghcr.io/constructive-io/constructive-functions/send-email-link:latest`
19+
## Monorepo Layout
3320

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

36-
Both functions follow the same pattern:
51+
## Function Pattern
3752

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:
4854

49-
## Common Workflows
55+
```typescript
56+
import type { FunctionHandler } from '@constructive-io/fn-runtime';
5057

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

53-
```bash
54-
pnpm install
55-
pnpm build
67+
export default handler;
5668
```
5769

58-
**Build Docker images:**
70+
## Entry Points
5971

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
6578

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
6784

85+
**Local development with Docker:**
6886
```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
7389
```
7490

75-
**Run locally with Docker (manual):**
76-
91+
**Regenerate after changing handler.json:**
7792
```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
7996
```
8097

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
9299

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

Dockerfile.dev

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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 and generate script
8+
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml tsconfig.json ./
9+
COPY scripts/ scripts/
10+
11+
# Copy all package/job manifests for caching
12+
COPY packages/fn-app/package.json packages/fn-app/
13+
COPY packages/fn-runtime/package.json packages/fn-runtime/
14+
COPY job/server/package.json job/server/
15+
COPY job/worker/package.json job/worker/
16+
COPY job/service/package.json job/service/
17+
18+
# Copy function handler.json manifests
19+
COPY functions/ functions/
20+
21+
# Generate workspace packages + install deps
22+
RUN node --experimental-strip-types scripts/generate.ts && pnpm install --frozen-lockfile
23+
24+
# Copy source and build
25+
COPY . .
26+
RUN pnpm generate && pnpm build
27+
28+
ENV NODE_ENV=production

Makefile

Lines changed: 9 additions & 29 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
42

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

14-
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
12+
generate:
13+
pnpm run generate
2814

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
15+
dev-build:
16+
docker compose build
3717

38-
docker-push-simple-email:
39-
docker push $(REGISTRY)/simple-email:latest
18+
dev:
19+
docker compose up
4020

41-
docker-push-send-email-link:
42-
docker push $(REGISTRY)/send-email-link:latest
21+
dev-down:
22+
docker compose down

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)