@ingentic/serverless-function-wrapper
v0.3.0
Published
Public wrapper for ingentic serverless functions: HTTP shell, S3/R2 storage, file pipeline, registry export, health and metrics.
Downloads
616
Maintainers
Readme
@ingentic/serverless-function-wrapper
Public wrapper for ingentic serverless functions. It provides the platform boilerplate once — HTTP shell, S3/R2 storage, the file pipeline, registry export, health and metrics — so that each business function ships only its business logic plus a small functionDefinition.
The wrapper has zero knowledge of any business domain (drugs, RxNorm, OWL, templates, ...). It deals only in generic concepts: HTTP, object storage, file references and constants, envelopes, and the function registry.
How a business function uses the wrapper
Each function is a separate, independently deployed container that depends on this package. Its entry point is a few lines:
import { startServer } from "@ingentic/serverless-function-wrapper";
import definition from "./function.js";
startServer(definition);The automation runtime continues to call each function's POST /function directly; it never sees the wrapper.
The functionDefinition contract
{
functionId: "fn-...", // registry identity (env override: FUNCTION_ID)
functionName: "SomeFunction", // env override: FUNCTION_NAME
functionPurpose: "One sentence.", // env override: FUNCTION_PURPOSE
serviceName: "some-function", // logging (default: SERVICE_NAME env, else functionId)
defaultPort: 8080, // optional
bodyLimit: "1mb", // optional
inputParameters: [{ name, type: "string"|"array", required: true }], // optional; defaults derived from inputs
inputs: {
"<bodyParam>": {
kind: "file" | "value" | "file-insert", // file: S3 key -> fetched; value: constant/literal; file-insert: array of key+literal objects -> fetched content
parse: "text" | "json", // how to parse the file content / literal (text for file-insert)
multiple: true, // optional: accept a string or an array of strings (file/value only)
fileKeyField: "<keyProp>", // file-insert only: property holding the S3 object key
literalFields: ["<litProp>"], // file-insert only: other required string properties, passed through
contentField: "<contentProp>", // file-insert only: property the wrapper fills with each file's content
validate: (value) => boolean, // optional: extra validation; failure -> 400
required: true, // optional, default true
},
},
outputParamName: "outputFileKey", // response key + registry outputParameter
outputKey: "outputBase", // string, or (inputKeys) => base; wrapper appends -<timestamp>.<ext>
outputExtension: "json", // optional, default "json"
outputContentType: "application/json", // optional
outputRaw: false, // optional: write the body as-is (a string) instead of JSON-serializing it
// Instead of the legacy single-output fields above, a function may declare an
// `outputs` array for multiple named outputs. When present, `outputs` takes
// precedence and the legacy fields are ignored. Each entry is:
// { name: "ontologyId", kind: "value" } // inline scalar in the response
// { name: "resultFileKey", kind: "file", key: "resultBase", // S3-backed; response carries the key
// extension: "json", contentType: "application/json", raw: false }
// `key` mirrors `outputKey` (string or (inputKeys) => base); the registry export
// marks file outputs with `fileKey: true` so consumers know the value is a key.
readiness: ["s3Storage"], // or (ctx) => ({ [name]: "up"|"down" })
handler: async (inputs, context) => {
// inputs[param] = fetched/parsed value for each declared input
// context = { requestId, logger, storage, bucket, buildOutputKey }
// Single-output: returns { body: <object to serialize to S3>, info?: <stats> }
// Multi-output: returns { body: { <outputName>: <data|scalar>, ... }, info? }
// - `value` output -> body[name] is a non-empty string (returned inline)
// - `file` output -> body[name] is the data to persist (JSON-serialized, or a string when `raw`)
// throw BadRequestError / HttpError for 4xx; anything else -> masked 500
},
}Input kinds
file— the request parameter is an object key (a reference to a file, not the file). The wrapper validates the key, fetches the object from the configured bucket, and parses it (textorjson).value— the request parameter is a string constant or literal (including JSON strings) passed through from a calling function. The wrapper validates it is a string and optionally JSON-parses it.file-insert— the request parameter is a non-empty array of objects, each pairing a file key (a reference to a file in the configured bucket) with one or more literal values. The wrapper validates each key and each declared literal field, fetches each referenced file, and delivers each object enriched with the fetched content under the declaredcontentField.fileKeyField,literalFields, andcontentFieldare all required and named by the function:insertions: { kind: "file-insert", fileKeyField: "sourceFileKey", // property holding the S3 object key literalFields: ["insertionPoint"], // user-supplied literal properties contentField: "sourceContent", // wrapper fills this with each file's content },For a request body
{ insertions: [{ sourceFileKey: "a.owl", insertionPoint: "<!-- Classes -->" }] }, the handler receives[{ sourceFileKey: "a.owl", insertionPoint: "<!-- Classes -->", sourceContent: "<fetched content>" }]in array order. The registry export marks the file-key property withfileKey: trueand the wrapper-populated content field withinternal: true(a field the runtime passes through without marshalling). Content is fetched as text by default; setparse: "json"to have each file JSON-parsed before it is attached.
Either kind can be a single value or, with multiple: true, an array of values (an array of keys, or an array of constants). The registry inputParameters type reflects this as string or array.
Multiple outputs (outputs)
A function may declare an optional outputs array instead of the legacy single-output fields (outputParamName/outputKey/outputExtension/outputContentType/outputRaw). When present, the legacy fields are ignored. This lets a function return small scalar values inline (e.g. an ontologyId) alongside an S3-backed output, keeping response bodies small regardless of artifact size:
outputs: [
{ name: "ontologyId", kind: "value" },
{
name: "ontologyUploadFileKey",
kind: "file",
key: "ontologyUpload",
extension: "json",
contentType: "application/json",
},
],name(required) — the response key and registryoutputParametername; must be unique and must not collide with an input name.kind(required) —"value"(inline scalar) or"file"(S3-backed).key(file only) — base S3 key: a string, or(inputKeys) => base; the wrapper appends-<timestamp>.<ext>.extension(file only) — default"json".contentType(file only) — optional; emitted in the registry export when set.raw(file only) — defaultfalse. Whentrue, the value must be a string and is written verbatim; the registry export emitsraw: true.
The handler returns { body, info } where body carries one key per declared output:
kind: "file"→body[name]is the data to persist (JSON-serialized, or a raw string whenraw: true).kind: "value"→body[name]is a non-empty string returned inline in the response.
A declared output missing from body, a non-string/empty value output, or a non-string file value when raw is enabled are handler contract violations and fail the request with a masked 500. info remains logging-only.
The registry export emits one outputParameter per entry: value → { name, type: "string", required: true }; file → { name, type: "string", required: true, fileKey: true, ... } so consuming services know the value is a key to fetch.
The pipeline
POST /function (POST-only; query parameters rejected) →
validate each declared input → fetch file inputs / pass through value inputs →
call the business handler →
for each declared output (multi-output) or the single output (legacy): write file outputs to S3 under {key}-<timestamp>.<ext> and place value outputs inline →
respond with the assembled object ({ [outputName]: key | value, ... }).
When outputs is absent, the legacy single-output pipeline runs unchanged: write the returned body (JSON-serialized by default, or raw when outputRaw is set) under {outputKey}-<timestamp>.<ext> and respond { [outputParamName]: key }.
Public API
| Export | Purpose |
| ------ | ------- |
| startServer(definition) | Boot the HTTP server (PORT, graceful shutdown, definition validation). |
| createApp(definition) | Return an Express app (for tests / embedding). |
| runFunction(definition, requestData) | Run the pipeline for a single request. |
| buildRegistryConfig(definition) | Build the GET /function/json payload. |
| buildOutputKey(base, ext, date?) | Timestamped output key builder. |
| buildEnvelope({ info, params }) | Platform { status, info, params } success envelope. |
| BadRequestError, HttpError | Error types business handlers throw; the wrapper maps them to status codes. |
| S3Service | Storage client (getObject, putObject, getFile, validateKey, isConfigured, getConfiguredBucket). |
| validateDefinition(definition) | Startup validation of a definition. |
Endpoints served by every function
GET /healthz— liveness.GET /readyz— readiness from the definition'sreadinesschecks.GET /metrics— Prometheus metrics.POST /function— the function.GET /function/json— registry configuration export. Eachresponse.outputParametersentry carriescontentType(andraw: true) when the definition declaresoutputContentType/outputRaw, so consuming services know the output's format; entries from anoutputsarray carryfileKey: truewhen the output is S3-backed.file-insertinputs are advertised withkind: "file-insert"and anitemsschema whose content field is markedinternal: true.
Environment variables
| Variable | Required | Default | Description |
| -------- | -------- | ------- | ----------- |
| PORT | No | definition.defaultPort | 8080 | HTTP port |
| ALLOWED_ORIGINS | No | * | Comma-separated CORS origins |
| SERVICE_NAME | No | definition.serviceName | functionId | Structured-log service name |
| S3_ENDPOINT | Yes | - | S3/R2 API endpoint (e.g. Cloudflare R2 account endpoint) |
| S3_ACCESS_KEY | Yes | - | Access key |
| S3_SECRET_ACCESS_KEY | Yes | - | Secret access key |
| S3_BUCKET_NAME | Yes | - | Bucket for input and output objects |
| FUNCTION_ID / FUNCTION_SLUG / FUNCTION_URI / FUNCTION_NAME / FUNCTION_PURPOSE | No | definition defaults | GET /function/json overrides |
| FUNCTION_REGISTRY_ID | No | - | Emits registryId in GET /function/json when set |
Configuration is supplied externally (Kubernetes Secrets/ConfigMaps or equivalent). .env files exist only for local development and must never be committed or embedded in images.
Onboarding a new function
Copy templates/function/ to a new function repository, fill in the placeholders (see templates/function/README.md), and add your business logic under src/domain/. The template is a repo-level aid; it is not part of the published package.
Governance
Versioning, release, and pinning policies live in GOVERNANCE.md. Notable rules:
- The wrapper is consumed by functions as an npm dependency only — no
file:or other local-path inter-project dependencies. - Functions depend with a caret range (e.g.
^0.1.2); keep functions within one minor of the latest release and use Renovate/Dependabot per function.
Development
npm install
npm test
npm run lintLicense
ISC
