{"id":50434650,"url":"https://github.com/constructive-io/constructive-functions","last_synced_at":"2026-05-31T16:30:48.808Z","repository":{"id":358204644,"uuid":"1128981184","full_name":"constructive-io/constructive-functions","owner":"constructive-io","description":"Playground for constructive functions","archived":false,"fork":false,"pushed_at":"2026-05-24T01:19:29.000Z","size":2981,"stargazers_count":0,"open_issues_count":14,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T03:19:35.444Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/constructive-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-01-06T12:39:40.000Z","updated_at":"2026-05-24T01:19:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/constructive-io/constructive-functions","commit_stats":null,"previous_names":["constructive-io/constructive-functions"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/constructive-io/constructive-functions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constructive-io%2Fconstructive-functions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constructive-io%2Fconstructive-functions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constructive-io%2Fconstructive-functions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constructive-io%2Fconstructive-functions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/constructive-io","download_url":"https://codeload.github.com/constructive-io/constructive-functions/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constructive-io%2Fconstructive-functions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33739860,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T16:30:47.316Z","updated_at":"2026-05-31T16:30:48.803Z","avatar_url":"https://github.com/constructive-io.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# constructive-functions\n\n\u003cp align=\"center\" width=\"100%\"\u003e\n  \u003cimg height=\"250\" src=\"https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\" width=\"100%\"\u003e\n  \u003ca href=\"https://github.com/constructive-io/constructive-functions/actions/workflows/ci.yaml\"\u003e\n    \u003cimg height=\"20\" src=\"https://github.com/constructive-io/constructive-functions/actions/workflows/ci.yaml/badge.svg\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/constructive-io/constructive-functions/blob/main/LICENSE\"\u003e\u003cimg height=\"20\" src=\"https://img.shields.io/badge/license-Constructive-blue.svg\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nFunctions playground for Constructive — a workspace for building, testing, and deploying serverless HTTP functions backed by a Postgres-backed job queue.\n\nFunctions are authored in `functions/\u003cname\u003e/` (a `handler.ts` plus a `handler.json` manifest), generated into runnable workspace packages by `pnpm generate`, and dispatched by the job service in `job/service/`. Templates live in `templates/` (`node-graphql` and `python` are supported today).\n\nThis repo is also the source of the **Portable Functions Toolkit**: a set of `@constructive-io/fn-*` npm packages that any external repo can `pnpm add` to get the same code-gen + Docker + k8s pipeline against its own `functions/` directory. See [docs/portable-functions-toolkit.md](docs/portable-functions-toolkit.md) for the full toolkit guide.\n\n## Quick start (in another repo)\n\n```bash\npnpm add -D @constructive-io/fn-cli\npnpm add @constructive-io/fn-runtime\n\npnpm fn init send-welcome --no-tty           # scaffold functions/send-welcome/\npnpm fn generate                             # stamp out generated/\u003cname\u003e/ packages\npnpm install                                 # link the new workspaces\npnpm fn build                                # compile\npnpm fn dev                                  # run functions as local Node processes\n```\n\n## Quick start (this repo, dogfood)\n\n```bash\npnpm generate     # generate workspace packages from handler.json manifests\npnpm install      # install dependencies (including generated packages)\npnpm build        # build all packages and functions\n\nmake docker-build                        # build all function Docker images\nmake docker-build-send-email           # build a single function image\nmake docker-build-send-verification-link\n```\n\n## Functions\n\n| Function | Port | Type | Image |\n|----------|------|------|-------|\n| `send-email` | 8081 | node-graphql | `ghcr.io/constructive-io/send-email-fn:latest` |\n| `send-verification-link` | 8082 | node-graphql | `ghcr.io/constructive-io/send-verification-link-fn:latest` |\n| `knative-job-example` | 8083 | node-graphql | `ghcr.io/constructive-io/knative-job-example-fn:latest` |\n| `python-example` | 8084 | python | `ghcr.io/constructive-io/python-example-fn:latest` |\n\nPort `8080` is reserved for the job service.\n\n### `send-email`\n\nSends emails directly from a job payload.\n\n- `SEND_EMAIL_DRY_RUN` — if `true`, logs the payload instead of sending\n- `MAILGUN_API_KEY`, `MAILGUN_KEY`, `MAILGUN_DOMAIN`, `MAILGUN_FROM`, `MAILGUN_REPLY` — Mailgun config\n\n### `send-verification-link`\n\nSends invite, password reset, and verification emails (rendered via MJML).\n\n- `SEND_VERIFICATION_LINK_DRY_RUN` — if `true`, logs the payload instead of sending\n- `DEFAULT_DATABASE_ID` — default database UUID\n- `GRAPHQL_URL`, `META_GRAPHQL_URL` — GraphQL API endpoints\n- `GRAPHQL_AUTH_TOKEN` — optional Bearer token for GraphQL requests\n- `LOCAL_APP_PORT` — local port for dashboard links (e.g. `3000`)\n- `MAILGUN_*` — same Mailgun config as `send-email`\n\n### `knative-job-example` / `python-example`\n\nReference implementations for the `node-graphql` and `python` templates.\n\n## Development\n\nThe full local-dev guide lives in [DEVELOPMENT.md](./DEVELOPMENT.md). Two paths:\n\n- **Docker Compose + local Node** (fastest iteration) — `make dev` for infrastructure, `make dev-fn` to run functions as local Node processes.\n- **Skaffold on local k8s** (production-like, with hot reload) — `make skaffold-dev` deploys the full stack to the `constructive-functions` namespace and watches handler files.\n\nFor the Knative variant of the k8s setup, see `k8s/DEVELOPMENT_LOCAL.md`.\n\n### CI/CD\n\nThe `CI Test K8s` workflow (`.github/workflows/test-k8s-deployment.yaml`) runs on PRs and pushes to `main` that touch `k8s/`, `tests/e2e/`, or `functions/`. It spins up a `kind` cluster, applies the `k8s/overlays/ci` overlay, and runs the per-function e2e tests.\n\n## Project Structure\n\n```\n.\n├── functions/             # User-authored handler.ts + handler.json (git tracked)\n├── templates/             # Template definitions (node-graphql, python, shared, k8s)\n├── generated/             # Generated workspace packages (gitignored)\n├── packages/\n│   ├── fn-runtime/        # createFunctionServer, GraphQL clients, FunctionContext\n│   └── fn-app/            # Express app factory with job callbacks\n├── job/\n│   ├── service/           # Orchestrator (loads functions + worker + scheduler)\n│   ├── server/            # Callback receiver\n│   └── worker/            # Job dispatcher\n├── k8s/\n│   ├── base/              # Shared manifests\n│   └── overlays/          # local-simple, local, ci, dev, staging\n├── tests/                 # integration + e2e suites\n├── scripts/               # generate.ts, dev.ts, docker-build.ts\n├── skaffold.yaml\n├── package.json\n└── Makefile\n```\n\n## Notes\n\n- Functions use `@constructive-io/fn-runtime` (runtime + GraphQL clients) and `@constructive-io/fn-app` (Express wrapper).\n- Email providers are wired through `@constructive-io/postmaster` (Mailgun) and `@launchql/mjml` / `@launchql/styled-email` for templating.\n- Both Node (`node-graphql`) and Python (`python`) templates are supported — pick via the `type` field in `handler.json`.\n- The `generated/` directory is entirely gitignored; rerun `pnpm generate` after changing any `handler.json`.\n- Root workspace manages shared linting/formatting; each generated function ships its own build config and Dockerfile.\n\n## Pushing Images\n\nImages are tagged with the GHCR prefix automatically:\n\n```bash\ndocker push ghcr.io/constructive-io/send-email-fn:latest\ndocker push ghcr.io/constructive-io/send-verification-link-fn:latest\ndocker push ghcr.io/constructive-io/knative-job-example-fn:latest\ndocker push ghcr.io/constructive-io/python-example-fn:latest\n```\n\n- `make docker-build` — builds all function images\n- `make docker-build-\u003cname\u003e` — builds a single function image\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconstructive-io%2Fconstructive-functions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconstructive-io%2Fconstructive-functions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconstructive-io%2Fconstructive-functions/lists"}