Skip to content

Latest commit

 

History

517 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mercury Composable — polyglot functions for Node.js

Write decoupled functions in JavaScript/TypeScript and let Mercury Composable engines (Java, and the official Rust port) orchestrate them from Event Script flows and MiniGraph knowledge graphs — with no orchestration code in Node.js at all.

This package is a deliberately lightweight wrapper of the Event-over-HTTP protocol:

  • an Event API host (POST /api/event) that dispatches incoming event envelopes to your registered functions,
  • a thin client (PostOffice) to call functions on peer applications the same way,
  • the standard event envelope wire format codec (language-neutral MsgPack), and
  • a primitive in-process event bus — the single dispatch pipeline: one FIFO mailbox per route consumed by instances worker loops, and
  • the minimalist utilities shared with the engines for consistency: configuration management, logging in the engines' presentation format, and distributed-trace context.

Orchestration deliberately stays in the engines. Functions written here are addressed by route name through the engines' declarative yaml.event.over.http map, so a flow or a graph task calls a Node.js function exactly as if it were local.

Documentation: https://accenture.github.io/mercury-nodejs/ — including the AI Agent Guide for deterministic function generation.

Status: pre-release. This repository was repurposed in August 2026 for the polyglot initiative: instead of re-porting the full composable foundation to Node.js, the fresh start rides the engines' Event-over-HTTP protocol — light by design. The previous Node.js port (up to v4.3.28) remains available in the git history and on npm.

Quick start

// app.mjs
import { AppException, platform, preload } from 'mercury-composable';

preload('hello.node', { instances: 10 }, async (headers, body) => {
  if (typeof body !== 'object' || body === null || !('text' in body)) {
    throw new AppException(400, "missing 'text'");
  }
  return { text: String(body.text).toUpperCase(), language: 'node.js' };
});

await platform.run(); // port from rest.server.port (default 8085)

Run it:

npm install && npm run build
node dist/src/cli.js app.mjs -Drest.server.port=8087     # or: mercury-serve app.mjs -Drest.server.port=8087

Call it from a Mercury engine application with two configuration entries and no code — application.properties:

yaml.event.over.http=classpath:/event-over-http.yaml

event-over-http.yaml:

event.http:
  - route: 'hello.node'
    target: 'http://127.0.0.1:8087/api/event'

Any Event Script task or MiniGraph graph.task node that names the route hello.node now executes the Node.js function, with trace context carried end to end.

The function contract

A handler receives the same two-part input as an engine TypedLambdaFunction(headers, body) — and returns the reply body (or an EventEnvelope for full control of status and reply headers). Node.js is non-blocking by nature; handlers may be async.

  • Throw AppException(status, message) for intentional errors — it becomes the portable error contract on the wire (envelope status + message), handled by the calling flow's exception handler or the graph's error.* contract.
  • getTrace() exposes traceId / tracePath / cid; annotateTrace(k, v) sends an annotation back on the reply envelope.
  • Functions must be stateless; anything you must keep belongs to the caller's flow model or state machine.

Local function calls

PostOffice without an endpoint delivers through this application's own event bus — the engines' semantics for an in-app po call:

  • isPrivate: true means exactly what it means in the engines: callable in-app only. Local calls reach private and public routes alike; the HTTP host keeps answering 403 for private targets from the wire.
  • instances is faithful: each route has one FIFO mailbox consumed by that many worker loops. RPC waits are bounded by timeoutMs (the standard 408 envelope on breach), and a queued call whose caller already timed out is skipped, never wastefully executed.
  • There is no spill tier and no queue cap by design: back-pressure belongs to the tier that owns recovery — the engines' flows and graphs. A leaf host fails fast by deadline instead of hoarding work.

Local eventing is for simple leaf-side composition. Workflow processing belongs in Event Script and Knowledge Graph on the engines — that boundary is the architecture.

Configuration, logging, telemetry

The same conventions as the engines, so a polyglot installation stays uniform:

Key Meaning Default
application.name application identity in logs application
rest.server.port Event API port 8085
log.format text, json (pretty-printed) or compact (single-line JSONL) text
log.level log level (LOG_LEVEL env var wins) INFO

Configuration lives in the resources folder, mirroring the engines: resources/application.yml (or .yaml / .properties) in the working directory or next to the application file, or an explicit --config path — see examples/resources/application.yml for a worked sample. Values support ${ENV_VAR:default} substitution. Runtime parameter overrides use the same -D syntax as the Java engine and the Rust port — checked first on every read (appConfig().set(key, value) does the same programmatically, the f:setConfig analog):

mercury-serve app.mjs -Drest.server.port=8087 -Dlog.format=json

Log lines follow the Java reference engine's pattern for one-aggregation consistency:

2026-08-22 10:15:30.123 INFO  app:12 - Loaded PUBLIC hello.node, instances=10

Actuator endpoints

The host serves the engines' operational endpoints on the same port as /api/event, so Kubernetes probes and dashboards treat a Node.js app exactly like a Java or Rust engine app:

Endpoint Purpose
GET / minimal index page linking the endpoints below
GET /info app identity, runtime, origin id, start time, uptime
GET /info/routes registered routes split by visibility, with instance counts
GET /env selected environment variables and configuration parameters
GET /health dependency health checks — UP (HTTP 200) or DOWN (HTTP 400)
GET /livenessprobe OK while the last health outcome was good, else HTTP 400

Configuration keys carry the engines' names: info.app.version, info.app.description, show.env.variables and show.application.properties (opt-in lists — secrets are never dumped wholesale), and mandatory.health.dependencies / optional.health.dependencies (routes of health check functions; optional ones never change the overall status). A health check function is a normal registered function — usually private — speaking the engines' interface contract, called through the event bus:

preload('demo.health', { isPrivate: true }, async (headers, _body) => {
  if (headers.type === 'info') {
    return { service: 'demo.service', href: 'http://127.0.0.1' };
  }
  return 'demo.service is running fine';   // a non-200 reply marks it down
});

JSON responses are pretty-printed — the engines' default-serializer presentation — and unknown paths answer the engines' error shape ({"status": 404, "message": "Resource not found", "type": "error"}).

Kubernetes wiring: point livenessProbe at /livenessprobe and readinessProbe at /health.

Wire compatibility

The codec implements the Event Envelope Wire Format (standard format) and is verified against the golden conformance vectors shared by the Java and Rust engines (test/vectors/vectors.json). The classic compact format is detected and rejected with a teaching error — engines default to the standard format for Event over HTTP.

Serialization notes: 64-bit integers beyond Number.MAX_SAFE_INTEGER decode as BigInt (exact), smaller ones as number; timestamps travel as ISO-8601 UTC strings with millisecond precision; binary payloads are Uint8Array.

Scope

This package intentionally contains no orchestration: no flows, no graphs, no persistence, no pub/sub broadcast — those live in the engines. What it does carry is deliberately minimal: functions, a primitive in-process event bus (route mailboxes + workers, RPC and drop-n-forget — nothing more), and the minimalist foundation utilities, keeping Node.js fast to prototype with while the composable core guarantees the architecture.

License

Apache 2.0 — see LICENSE.

Releases

Packages

Used by

Contributors

Languages