A lightweight FaaS runtime (JavaScript only in v1): user code is Node ESM and exports a fixed-signature handler. The dispatcher process exposes an HTTP API and can optionally forward execution to a separate worker process. Source can live under local data/, or PostgreSQL + S3 (db_s3).
Languages: 简体中文
| Command | Role |
|---|---|
go run ./cmd/dispatcher |
Public POST /functions, POST /functions/{name}/invoke; if LOWCODE_FAAS_WORKER_URL is set, invoke is proxied to the worker over HTTP |
go run ./cmd/worker |
Internal POST /v1/run, reads storage and runs user handler in Docker |
For local dev you may run dispatcher only (omit WORKER_URL); execution then uses the same logic in-process as the worker.
- Files are ESM; on disk the default filename is
{name}.mjs. - Must export:
/** @param {import('../../js/runtime.d.ts').HandlerInput} input */
export async function handler(input) {
return { ok: true, data: { message: "..." } };
}input: same JSON object as theinputfield on theinvokerequest (any JSON object).- Return value (stored in
RunResult.output) must be an object with booleanok:- Success:
{ "ok": true, "data": <anything> } - Business failure:
{ "ok": false, "error": "message", "code": "optional" }
- Success:
For TypeScript, see js/runtime.d.ts in this repo.
Execution: the platform writes input.json in the container, runs an embedded bootstrap.mjs that calls handler, then reads output.json (user code does not open an HTTP port).
go build -o dispatcher ./cmd/dispatcher
go build -o worker ./cmd/workerCore settings live in internal/config/config.go and can be overridden via env (see internal/config/env.go).
| Variable | Meaning |
|---|---|
LOWCODE_FAAS_LISTEN |
Dispatcher listen address, default :8080 |
LOWCODE_FAAS_WORKER_URL |
If set, invoke is forwarded to this base URL (e.g. http://127.0.0.1:9090) |
LOWCODE_FAAS_WORKER_LISTEN |
Worker listen address, default :9090 |
LOWCODE_FAAS_STORAGE |
files (default) or db_s3 |
LOWCODE_FAAS_POSTGRES_DSN |
PostgreSQL DSN when using db_s3 |
LOWCODE_FAAS_S3_* |
S3 / MinIO settings for db_s3 |
LOWCODE_FAAS_FUNCTION_CACHE |
true / false |
LOWCODE_FAAS_INVOKE_DEFAULT_TIMEOUT_MS |
Default cap in ms when the request omits timeout_ms (default 120000); too low may cause signal: killed on first docker pull |
LOWCODE_FAAS_NODE_DOCKER_IMAGE |
Image for user handler, default node:22-alpine; use a private registry URL on air-gapped networks |
LOWCODE_FAAS_PREWARM_NODE_IMAGE |
Default true: background docker pull of the image above on startup; set false to disable |
LOWCODE_FAAS_FUNCTION_LOGS / LOWCODE_FAAS_FUNCTION_LOGS_SINK |
Capture user console.*. Recommended: docker_json or promtail: one JSON object per line to process stdout → Docker json-file → Promtail → Loki (see docker-compose.dev.yml). stdout keeps the prefixed legacy format. Direct Loki push uses loki + LOWCODE_FAAS_LOG_LOKI_* (usually avoid duplicating Promtail) |
LOWCODE_FAAS_LOGS_GRAFANA_EXPLORE_URL_TEMPLATE |
Optional; GET /function-logs/query may include grafana_explore_url in JSON, with placeholders {{logql}}, {{run_id}}, {{deployment_id}} (URL-encoded when substituted) |
User code runs via docker create + docker cp workspace into the container + docker start, then docker cp out output.json, so the worker does not bind-mount paths that only exist in a sibling container’s /tmp (which would cause missing bootstrap.mjs / MODULE_NOT_FOUND).
China / Docker registry mirrors (not HTTP proxy): docker pull during invoke runs on the host Docker daemon (with docker.sock mounted). Slow pulls from Docker Hub should be fixed with registry mirrors, e.g. Docker Desktop → Settings → Docker Engine, add registry-mirrors (example: "registry-mirrors": ["https://docker.m.daocloud.io"]—use whatever your mirror documents). On Linux, edit /etc/docker/daemon.json and restart Docker.
Without daemon mirror config: set LOWCODE_FAAS_NODE_DOCKER_IMAGE to the full name on your mirror (e.g. docker.m.daocloud.io/library/node:22-alpine). Keeping node:22-alpine uses the default registry; this is unrelated to HTTP_PROXY.
POST /collectionsbody:{ "path": "org/team", "env": { ... } }(envoptional)—register a path; non-root collections must exist before creating functions under them.GET /collections?prefix=org—list registered paths (optional prefix filter).PUT /collections/{path}/envorPUT /collections/env(root)—body{ "env": { ... } }for collection-level env.- URL form:
/collections/org/team/functions,/collections/org/team/functions/{name}/invoke, etc. (org/teamiscollection_path).
{
"name": "greet",
"language": "javascript",
"source_code": "...",
"source_url": "https://example.com/handler.mjs",
"collection_path": "org/team",
"env": {}
}collection_path may be omitted (root collection); or pass ?collection_path=org%2Fteam on the request.
language only supports javascript (may be omitted; default is javascript).
Query collection_path selects the collection; omit for root.
db_s3 returns full history; files returns a single row for the current version only. Same ?collection_path= or nested URL rules.
{ "input": { "name": "lowcode" }, "timeout_ms": 15000, "env": {}, "version": 3 }Optional version pins a deployment (db_s3: any historical version; files: only if it matches current). Also ?version=3. Collections: ?collection_path=org%2Fteam.
Response is still RunResult (includes execution_id, deployment_version, deployment_id, collection_path, etc.); business payload is output (JSON return value of handler).
Process env injected into the container is: ancestor collection env (root → … → current path, child overrides parent) ∪ function env ∪ request body env, with the last winning.
- Set
LOWCODE_FAAS_FUNCTION_LOGS=true,LOWCODE_FAAS_FUNCTION_LOGS_SINK=docker_json(aliaspromtail). - Logs are one JSON line per event (
run_id,execution_id,log_stream,function_name,collection_path,deployment_id, …) on worker/dispatcher stdout, scraped by Promtail (deploy/promtail-dev.yml) via the Docker API into Loki. Local:docker compose -f docker-compose.dev.yml up(includesloki:3100). - Query helper:
GET /function-logs/query?run_id=...&function=...&collection_path=...&deployment_id=...returns JSON with example queries per backend (loki_logql_json,loki_logql_substr,elasticsearch_query_string,aws_cloudwatch_logs_insights,google_cloud_logging). ES, Loki, CloudWatch, and GCP do not share one query language; align on stable JSON field names and express the same filters in each product. - If
LOWCODE_FAAS_LOGS_GRAFANA_EXPLORE_URL_TEMPLATEis set, the response may includegrafana_explore_url(template placeholders like{{logql}}are substituted server-side).
{ "source_url": "https://example.com/handler.mjs" }data/collections/__root__/functions/{name}.mjs— root collection source (current default)data/functions/{name}.mjs— legacy root source; still read if present when listing/loading root functionsdata/collections/__root__/functions/{name}.env.json— root function env (alongside the.mjsabove)data/functions/{name}.env.json— env for legacy root.mjsonlydata/collections/{segment}/.../{segment}/functions/{name}.mjs— non-root collection sourcedata/collections/__root__/collection.env.json— root collection-level env (optional)data/collections/{path}/collection.env.json— collection-level env for that path
lowcode-faas.service: run dispatcher (setExecStartto yourdispatcherbinary path).- If you use a worker: add another unit for
worker, and setEnvironment=LOWCODE_FAAS_WORKER_URL=http://127.0.0.1:9090on the dispatcher unit.
cmd/dispatcher/main.go # Dispatcher HTTP entry
cmd/worker/main.go # Worker HTTP entry
internal/config/ # Config and env overrides
internal/model/ # Types and contracts
internal/store/ # files + PostgreSQL/S3
internal/runner/ # Docker + bootstrap.mjs
internal/httpserver/ # Dispatcher routes
internal/workerserver/ # Worker routes
internal/workerclient/ # Dispatcher → worker HTTP
internal/envutil/ internal/limits/ internal/sourcefetch/
js/runtime.d.ts # TS types for handlers