Skip to content

JavaScript language nodes

A JavaScript language node uses kind: javascript. Scripts export a defineNode(...) bundle from @rimekit/runtime; Rime reads the manifest and calls run(...) with named slot values.

- id: enriched
kind: javascript
source: scripts/enrich.mjs
in:
cohort: features
threshold: params.threshold
scripts/enrich.mjs
import { defineNode } from '@rimekit/runtime'
export default defineNode({
in: { cohort: 'table', threshold: 'any' },
out: { default: 'table' },
run: async ({ cohort, threshold }) => {
return cohort.rows.map((row) => ({
...row,
flag: row.score > Number(threshold),
}))
},
})

The manifest’s in keys should match the YAML in: slot keys. Bare default functions are rejected in Rime 2.1.

The runtime passes a single object to run.

YAML in: slotJavaScript value
Upstream node ref, for example cohort: features{ rows: Array<Record<string, unknown>> }
Param ref, for example threshold: params.thresholdnative JS scalar/array/object
run: ({ cohort, threshold }) => {
for (const row of cohort.rows) {
...
}
}

Return an array of plain row objects:

export default defineNode({
in: { orders: 'table' },
out: { default: 'table' },
run: ({ orders }) => orders.rows.filter((row) => row.total > 0),
})

Declare named outputs in the manifest and YAML, then return an object with matching keys:

- id: split
kind: javascript
source: scripts/split.mjs
in: { cohort: features }
out: { train: table, test: table }
export default defineNode({
in: { cohort: 'table' },
out: { train: 'table', test: 'table' },
run: ({ cohort }) => {
const pivot = Math.floor(cohort.rows.length * 0.8)
return {
train: cohort.rows.slice(0, pivot),
test: cohort.rows.slice(pivot),
}
},
})

Downstream refs are split.train and split.test.

Use any for JSON-like values such as API responses or metadata:

export default defineNode({
in: { config: 'any' },
out: { result: 'any' },
run: async ({ config }) => {
const response = await fetch(config.endpoint)
return response.json()
},
})

JavaScript nodes run in a Node child process per node. The subprocess imports your script, verifies the defineNode bundle, calls run(...), and sends the result back to the runtime.

This keeps script execution separate from the main CLI/editor process while still using the same Node version that launched Rime. JavaScript is a good fit for API fetches, row-level reshaping, and logic that already belongs close to a web/product codebase.

run can be async; the runtime awaits it.

run: async ({ config }) => {
const response = await fetch(config.endpoint)
const data = await response.json()
return data.rows.map((row) => ({ ts: row.timestamp, value: row.measurement }))
}

Avoid nondeterministic values such as Date.now() or Math.random() unless they are passed in through params, because params become part of the cache contract.

Required: Node 22+, supplied by the same environment that runs the Rime CLI or desktop app.