Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion BACKEND_DOCUMENTATION.MD
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,12 @@ Node-graph automations (React Flow) run against the connection. A workflow's
`{{input.path}}` (dotted paths supported, e.g. `{{input.row.id}}`); values are
rendered as SQL literals (strings quoted with `''` doubling, numbers bare,
booleans `TRUE`/`FALSE`, missing → `NULL`; a non-scalar value fails the node).
· `http`: `{ method, url, headers, body }` · `js`: `{ code }`
Add an `:ident` modifier — `{{input.field:ident}}` — to render the value as a
quoted SQL **identifier** instead (double-quoted with `""` doubling), for the
few statements that take a bare name and can't be parameterized, e.g.
`VACUUM (ANALYZE) {{input.relname:ident}}`. The value must be a non-empty
string; NULL/number/boolean fail the node so an identifier slot never silently
vanishes or injects. · `http`: `{ method, url, headers, body }` · `js`: `{ code }`
- `js`: `{ code }` — the body receives `input`, returns the node output. Besides
`input`/`console`, a curated **`crypto`** helper is in scope (no `require`):
`crypto.hmac(algo, key, data, enc="hex")`, `crypto.hash(algo, data, enc="hex")`,
Expand Down
15 changes: 14 additions & 1 deletion server.js
Original file line number Diff line number Diff line change
Expand Up @@ -1361,10 +1361,23 @@ function runUserJs(code, input) {
// Dialect-agnostic on purpose: single-quoted strings with '' doubling, bare
// numeric literals, and NULL are valid in every roadmap dialect. Non-scalar
// values are an error, not silently stringified.
//
// An optional `:ident` modifier — `{{input.field:ident}}` — renders the value
// as a quoted SQL *identifier* (double-quoted with "" doubling) instead of a
// literal, for the few statements that take a bare name and can't be
// parameterized (e.g. VACUUM/ANALYZE/REINDEX <table>). Double-quoted identifiers
// are standard SQL, so this stays dialect-agnostic. The value must be a
// non-empty string; NULL/number/boolean are rejected so an identifier slot can
// never silently vanish or inject.
function substituteWorkflowInput(sql, input) {
return sql.replace(/\{\{\s*input((?:\.[A-Za-z_][A-Za-z0-9_]*)+)\s*\}\}/g, (_, path) => {
return sql.replace(/\{\{\s*input((?:\.[A-Za-z_][A-Za-z0-9_]*)+)\s*(?::\s*(ident))?\s*\}\}/g, (_, path, mod) => {
let v = input
for (const key of path.slice(1).split('.')) v = v?.[key]
if (mod === 'ident') {
if (typeof v !== 'string' || v === '')
throw new Error(`{{input${path}:ident}} must be a non-empty string identifier (got ${v === null || v === undefined ? 'null' : typeof v})`)
return `"${v.replace(/"/g, '""')}"`
}
if (v === null || v === undefined) return 'NULL'
if (typeof v === 'number' && Number.isFinite(v)) return String(v)
if (typeof v === 'boolean') return v ? 'TRUE' : 'FALSE'
Expand Down