feat: Functions — JS/TS execution with run log

core.functions + core.function_runs: project-scoped code executed by a
runtime binary on the backend host (bun or deno per function). Payload
arrives on stdin + PRIMORA_PAYLOAD env; stdout/stderr captured per run
(capped), with status/exit code/duration. Invoke returns 502 + the
recorded run on failure so errors stay observable.

Dashboard gains a Functions page: code editor, JSON payload invoke,
live result, run history. Runner config via FUNCTIONS_RUNTIME /
FUNCTIONS_TIMEOUT_SECONDS / FUNCTIONS_MAX_OUTPUT_BYTES.

Verified live: bun ran code, stdin payload delivered, error path returns
502 with captured stderr; timeout kills at deadline.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-24 19:46:32 +02:00
1 parent 3aca5bd1e0
commit 32587c8063
37 files changed
+2157 -6

No files matched your search

+15
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
)
type Config struct {
@@ -45,6 +46,12 @@ type Config struct {
S3SecretAccessKey string
S3Prefix string
S3PathStyle bool
// Functions runtime: binary name/path ("auto" resolves per-function
// runtime from PATH — "bun" or "deno"), timeout and output cap.
FunctionsRuntime string
FunctionsTimeout time.Duration
FunctionsMaxBytes int64
}
// devEncryptionKey is the built-in credential key for local development only.
@@ -80,6 +87,8 @@ func Load() (Config, error) {
S3AccessKeyID: os.Getenv("S3_ACCESS_KEY_ID"),
S3SecretAccessKey: os.Getenv("S3_SECRET_ACCESS_KEY"),
S3Prefix: os.Getenv("S3_PREFIX"),
FunctionsRuntime: getenv("FUNCTIONS_RUNTIME", "auto"),
FunctionsMaxBytes: 65536,
}
smtpPort, err := strconv.Atoi(getenv("SMTP_PORT", "1025"))
@@ -118,6 +127,12 @@ func Load() (Config, error) {
}
cfg.S3PathStyle = s3PathStyle
fnTimeout, err := strconv.Atoi(getenv("FUNCTIONS_TIMEOUT_SECONDS", "30"))
if err != nil {
return Config{}, fmt.Errorf("parse FUNCTIONS_TIMEOUT_SECONDS: %w", err)
}
cfg.FunctionsTimeout = time.Duration(fnTimeout) * time.Second
var missing []string
if cfg.DatabaseURL == "" {
missing = append(missing, "DATABASE_URL")