@vrai-ie/heat-runtime
v0.1.0
Published
Official HEAT runtime for Node.js: build runners and applications that use HEAT as a backend. Token auth, task lifecycle, sessions, templates, nodes, and config.
Readme
heat-runtime
The official HEAT runtime for Node.js: build HEAT runners (containers that process node tasks) and build applications that use HEAT as a backend (sessions, nodes, data sources, config). Use a single library in both cases; pass a session token when calling from outside the cluster so all requests are authenticated.
Requirements: Node.js ≥ 16.
Installation
From the public npm registry:
npm install heat-runtimeFrom the HEAT repo (development or local link):
cd runtimes/js && npm install && npm run build
# Or from another project:
# "heat-runtime": "file:../path/to/HEAT/runtimes/js"{
"dependencies": {
"heat-runtime": "file:../../runtimes/js"
}
}Quick start (published package)
import { HEATRuntimeOptions, HEATRuntime } from 'heat-runtime';
const coreApiUrl = process.env.CORE_API_URL || 'http://localhost:5220';
const sessionToken = process.env.HEAT_SESSION_TOKEN;
const runtime = new HEATRuntime(new HEATRuntimeOptions(coreApiUrl, sessionToken));
// List sessions
const sessions = await runtime.listSessions();
console.log(sessions);Configuration
Create a runtime with the Core API URL and, when needed, a session token:
import { HEATRuntimeOptions, HEATRuntime } from 'heat-runtime';
const coreApiUrl = process.env.CORE_API_URL || 'http://localhost:5220';
const sessionToken = process.env.HEAT_SESSION_TOKEN || process.env.RuntimeSessionToken;
const opts = new HEATRuntimeOptions(coreApiUrl, sessionToken);
const runtime = new HEATRuntime(opts);coreApiUrl(required): Base URL of the HEAT Core API (e.g.http://localhost:5220orhttps://core.heat.example.com).sessionToken(optional): Bearer token for authentication. Use it when:- Running outside the cluster (scripts, web apps, CLI tools).
- Core API is in Strict auth mode, or in ClusterOnly mode and the request is treated as external.
If you omit sessionToken, the runtime does not send an Authorization header. That is fine when Core is in Local mode or when something else injects the header (e.g. a facade that forwards the incoming request’s token).
isDebug(optional): Whentrue,init()does not poll/api/tasks/claim. Instead it builds a synthetic task for a single node instance and runs the matching processor once, then exits. Use for local iteration without the scheduler.debugNodeId(required whenisDebugis true): The node instance id to run locally (same as Python--node).
Example: run once against node instance 12:
const opts = new HEATRuntimeOptions(coreApiUrl, sessionToken, true, 12);
const runtime = new HEATRuntime(opts);
runtime.register(processor);
await runtime.init('my-runner-debug'); // runs once then returnsUse case 1: Building runners
Runners poll the Core API for tasks, run your logic, then report results. You can use either a manual claim loop or the register + init pattern.
Manual claim loop
Create a runtime, then in a loop: claim a task, run your processor, then complete or fail the task.
const runtime = new HEATRuntime(new HEATRuntimeOptions(coreApiUrl, process.env.HEAT_SESSION_TOKEN));
const runnerTag = 'my-runner-' + Math.random().toString(36).slice(2, 10);
const supportedTemplates = ['my-node-template'];
while (true) {
try {
const task = await runtime.claimTask(runnerTag, supportedTemplates);
if (!task?.id) {
await sleep(5000);
continue;
}
try {
await runtime.signalTaskExecution(task.id);
// Your logic: read task.parentNodeOutputs, compute, then:
await runtime.createNodeOutput(task.nodeInstanceId, outputDto);
await runtime.completeNode(task.id, task.nodeInstanceId);
} catch (err) {
await runtime.failNode(task.id, task.nodeInstanceId, err.message);
}
} catch (err) {
if (err?.response?.status === 404) {
await sleep(5000);
continue;
}
throw err;
}
}Register + init (recommended)
Implement the processor contract and start the built-in loop:
- Implement
IProcessor:templateName, optionalvalidate(task), andprocess(task, runtime). - Call
runtime.register(processor)for each processor. - Call
runtime.init(runnerTag)to run the claim → validate → signal → process → complete/fail loop.
import { HEATRuntime, HEATRuntimeOptions, IProcessor, RunnerTask, IProcessorRuntime } from 'heat-runtime';
const processor: IProcessor = {
templateName: 'my-node-template',
async validate(task) {
if (!task.parentNodeOutputs?.length) return { success: false, message: 'No parent outputs' };
return { success: true };
},
async process(task, runtime: IProcessorRuntime) {
// Build output from task.parentNodeOutputs, then:
await runtime.createNodeOutput(task.nodeInstanceId, {
nodeInstanceId: task.nodeInstanceId,
configuration: JSON.stringify(result),
dataSourceId: 1,
dataPath: 'out',
outputIdentifier: 'out',
});
},
};
const runtime = new HEATRuntime(new HEATRuntimeOptions(coreApiUrl, process.env.HEAT_SESSION_TOKEN));
runtime.register(processor);
await runtime.init(runnerTag, { pollingIntervalMs: 2000 });Optional: load platform config into process.env before starting:
await runtime.loadEnv({ runnerName: 'my-runner', failOnMissing: false });
runtime.register(processor);
await runtime.init(runnerTag);Task lifecycle
- claimTask(runnerTag, supportedNodeTemplateNames) → returns a
RunnerTaskor 404 when none available. - signalTaskExecution(taskId) → marks task as started.
- createNodeOutput(nodeId, dto) → attach output to the node (do this inside your process logic when needed).
- completeNode(taskId, nodeId [, statusDetails]) → set node success and complete the task.
- failNode(taskId, nodeId [, statusDetails]) → set node failed and fail the task.
Low-level: completeTask(taskId), failTask(taskId) are also available if you manage node status yourself.
Use case 2: Building a service that depends on HEAT
Use the same runtime as a full client to create, edit, manipulate, and query HEAT resources. Always pass a session token when the app runs outside the cluster and Core requires auth. The runtime exposes the Core API surface needed for services: sessions, templates, node instances, node outputs, projects, and platform config.
Authentication
import { HEATRuntimeOptions, HEATRuntime } from 'heat-runtime';
const runtime = new HEATRuntime(
new HEATRuntimeOptions(process.env.CORE_API_URL!, process.env.HEAT_SESSION_TOKEN!)
);API summary (grouped by resource)
Sessions: listSessions, getSessionById, createSession, updateSession, deleteSession, getSessionUsers, addSessionUser, removeSessionUser, getSessionNodeOutputs
Session templates: listSessionTemplates, getSessionTemplateById, createSessionTemplate, updateSessionTemplate, deleteSessionTemplate, exportSessionTemplate, importSessionTemplate
Session template nodes: listSessionTemplateNodes, getSessionTemplateNodeById, createSessionTemplateNode, updateSessionTemplateNode, deleteSessionTemplateNode
Projects: listProjects, getProjectById, createProject, updateProject, deleteProject
Node instances: getNodeById, listNodes, createNodeOutput, updateNodeStatus, getNodeLatestOutput, listNodeOutputs, reprocessNode
Node outputs: getNodeOutputById (output data by id)
Scenario instances: getSessionScenarioInstances, addScenarioInstance, updateScenarioInstance, removeScenarioInstance
Dimensions: createSessionDimension, getDimensionById, updateSessionDimension, deleteSessionDimension, listDimensionsForUser
Node templates: listNodeTemplates, getNodeTemplateById, createNodeTemplate, updateNodeTemplate, deleteNodeTemplate, getNodeTemplateRunnerMappings
Runner types: listRunnerTypes, getRunnerTypeById, createRunnerType, updateRunnerType, deleteRunnerType, getRunnerTypeSupportedTemplates
Platform config: getConfig, getConfigValue, setConfigValue (object-style, like Python); getPlatformConfigurationByPrefix, getAllPlatformConfig, getPlatformConfigDetail, createPlatformConfig, updatePlatformConfig, deletePlatformConfig
Data sources: listDataSources, getDataSourceById, createDataSource, updateDataSource, deleteDataSource
Example: create project, session, then list node outputs
const runtime = new HEATRuntime(
new HEATRuntimeOptions(process.env.CORE_API_URL!, process.env.HEAT_SESSION_TOKEN!)
);
// Create a project
const project = await runtime.createProject({
title: 'My Project',
description: 'Description',
icon: 'folder',
ownerId: 1,
defaultSessionTemplateId: 1,
});
// Create a session from a template
const session = await runtime.createSession(project.id!, {
name: 'New Session',
sessionTemplateId: 1,
});
// Later: list node outputs for the session (all node instances' outputs in one call)
const nodeOutputs = await runtime.getSessionNodeOutputs(session.id!);Example: query and update
const sessions = await runtime.listSessions();
const session = await runtime.getSessionById(sessions[0].id!);
await runtime.updateSession(session.id!, { name: 'Updated name' });
const nodes = await runtime.listNodes(session.id!);
const latestOutput = await runtime.getNodeLatestOutput(nodes[0].id);
const outputData = await runtime.getNodeOutputById(latestOutput.id!);Configuration as object (like Python)
You can read and write platform config as an object or by dotted path, matching the Python runtime:
// Get config for a prefix as an object
const runnerConfig = await runtime.getConfig('runner_config');
const defaultStore = runnerConfig.default_store;
// Get a single value by dotted path
const store = await runtime.getConfigValue('runner_config.default_store');
// Set a single value by dotted name
await runtime.setConfigValue('runner_config.default_store', 'HEAT Managed Object Store');Behaviour
- 404 from claim: No task available. The manual loop and
init()both treat 404 as “sleep and retry” (no throw). - Errors: Other HTTP errors (4xx/5xx) are thrown by axios; handle them in your loop or process.
- Idempotency: Process each task once using only the provided task data and config; avoid external side effects that are not reported back to Core.
Documentation
- Full API reference: docs/API.md — all
HEATRuntimemethods, options, and exported types. - Guides: HEAT docs site → Building Node Templates → Developing Runners (JavaScript) (Dockerfile, manifest, local test).
- Publishing: See PUBLISHING.md for the npm publish checklist.
The JavaScript runtime mirrors the behaviour of the Python runtime; use the same Core concepts (node templates, sessions, tasks, runner types).
Troubleshooting
- 401 Unauthorized: Set
HEAT_SESSION_TOKEN(orRuntimeSessionToken) when calling Core from outside the cluster or when Core is in Strict/ClusterOnly auth. - 404 on claim: No task available; the built-in loop and manual examples treat this as “sleep and retry”. Not an error.
- CORS / network: Ensure
coreApiUrlis reachable from your environment (browser apps may need a proxy if Core does not allow CORS).
License
See LICENSE in the repository root, or the license field in package.json.
