@blokjs/shared 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,20 +6,111 @@ import type FunctionContext from "./FunctionContext";
6
6
  import type LoggerContext from "./LoggerContext";
7
7
  import type RequestContext from "./RequestContext";
8
8
  import type ResponseContext from "./ResponseContext";
9
+ import type StateContext from "./StateContext";
9
10
  import type VarsContext from "./VarsContext";
11
+ /**
12
+ * The runtime context for a single workflow execution. One Context object
13
+ * is created per run and threaded through every step.
14
+ *
15
+ * **The two-tier read model:**
16
+ *
17
+ * - `ctx.prev` — the previous step's full result envelope ({ data, success,
18
+ * error }). Overwritten on every step. Use for adjacent-step access.
19
+ * Aliased by `ctx.response` for back-compat with v1.
20
+ *
21
+ * - `ctx.state[stepId]` — each step's `result.data` is automatically
22
+ * persisted here under the step's `id` (or its `as` override). Use
23
+ * for any-distance access. Aliased by `ctx.vars` for back-compat.
24
+ *
25
+ * **Side-channel publication:**
26
+ *
27
+ * - `ctx.publish(name, value)` — explicitly publish a value into state
28
+ * from inside a node. Logged in Studio's trace tab. Use sparingly —
29
+ * most nodes should just `return` their output and let the runner
30
+ * persist it.
31
+ *
32
+ * **Request envelope aliases:**
33
+ *
34
+ * - `ctx.req` — alias for `ctx.request`. Read query, body, params,
35
+ * headers, etc. via either form.
36
+ */
10
37
  type Context = {
11
38
  id: string;
12
39
  workflow_name?: string;
13
40
  workflow_path?: string;
41
+ /**
42
+ * Request envelope. Body, query, params, headers, etc.
43
+ *
44
+ * Also accessible as `ctx.req` (alias).
45
+ */
14
46
  request: RequestContext;
47
+ /**
48
+ * Alias for `ctx.request` — same object, read-only getter. v2
49
+ * authoring uses `req`; v1 authoring used `request`. Both work.
50
+ */
51
+ readonly req?: RequestContext;
52
+ /**
53
+ * Previous step's full result envelope. **Overwritten every step.**
54
+ * Use for adjacent-step access only; for cross-step access use
55
+ * `ctx.state[stepId]`.
56
+ *
57
+ * Aliased by `ctx.response` for v1 back-compat.
58
+ */
15
59
  response: ResponseContext;
60
+ /**
61
+ * Alias for `ctx.response` — same object. v2 authoring uses `prev`;
62
+ * v1 authoring used `response.data`. Both work.
63
+ */
64
+ readonly prev?: ResponseContext;
16
65
  error: ErrorContext;
17
66
  logger: LoggerContext;
18
67
  config: ConfigContext;
19
68
  func?: FunctionContext;
69
+ /**
70
+ * Accumulated step outputs by step `id`. Filled automatically when
71
+ * a step completes (unless `ephemeral: true`).
72
+ *
73
+ * Aliased by `ctx.vars` for v1 back-compat — both fields point at
74
+ * the same underlying object.
75
+ *
76
+ * Always initialized to `{}` by `TriggerBase.createContext`. Marked
77
+ * optional only so legacy code paths that hand-construct a Context
78
+ * (some tests, internal utilities) keep type-checking; production
79
+ * runs always have `state` defined.
80
+ */
81
+ state?: StateContext;
82
+ /**
83
+ * Alias for `ctx.state` — same underlying object. v2 authoring
84
+ * uses `state`; v1 authoring used `vars`. Both work.
85
+ *
86
+ * @deprecated Prefer `ctx.state` (or `$.state.<id>` from inputs).
87
+ */
20
88
  vars?: VarsContext;
21
89
  env?: EnvContext;
22
90
  eventLogger: GlobalLogger | unknown;
91
+ /**
92
+ * Explicit side-channel publication. Writes to state under `name`
93
+ * and emits a Studio trace event. Use only when a node needs to
94
+ * publish something other than its return value (most nodes don't).
95
+ */
96
+ publish?: (name: string, value: unknown) => void;
97
+ /**
98
+ * Tier 2 follow-up · cooperative cancellation. AbortSignal whose
99
+ * `aborted` flips to true when an operator cancels the run via
100
+ * `POST /__blok/runs/:runId/cancel` while it's in `running` status.
101
+ *
102
+ * Nodes that perform long-running work (loops, fetch, db queries)
103
+ * should consult `ctx.signal.aborted` periodically and abort early
104
+ * — or pass the signal to fetch/abort-aware APIs:
105
+ *
106
+ * await fetch(url, { signal: ctx.signal });
107
+ * if (ctx.signal?.aborted) throw new Error("aborted");
108
+ *
109
+ * Always present on contexts created by `TriggerBase.createContext`.
110
+ * Optional on the type for back-compat with hand-built contexts in
111
+ * tests.
112
+ */
113
+ signal?: AbortSignal;
23
114
  _PRIVATE_: unknown;
24
115
  };
25
116
  export default Context;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * StateContext — accumulated step outputs for a single workflow run.
3
+ *
4
+ * Every step's `result.data` lands here under the step's `id` (or its
5
+ * `as` override) automatically. Steps marked `ephemeral: true` skip
6
+ * persistence; steps marked `spread: true` shallow-merge their result
7
+ * keys into state.
8
+ *
9
+ * Read via `ctx.state[stepId]` or `$.state[stepId]` from a workflow's
10
+ * `inputs`. Always initialized to `{}` at run start — never undefined.
11
+ *
12
+ * Aliased by `ctx.vars` for backward compatibility with v1 workflows.
13
+ *
14
+ * @example
15
+ * ctx.state["fetch-users"] // the data returned by the fetch-users step
16
+ * ctx.state.user // when a step has `as: "user"`
17
+ */
18
+ type StateContext = {
19
+ [key: string]: unknown;
20
+ };
21
+ export default StateContext;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=StateContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StateContext.js","sourceRoot":"","sources":["../../src/types/StateContext.ts"],"names":[],"mappings":""}
@@ -1,5 +1,18 @@
1
- import type ParamsDictionary from "./ParamsDictionary";
1
+ /**
2
+ * VarsContext — accumulated step outputs for a single workflow run.
3
+ *
4
+ * **Legacy alias for {@link StateContext}.** The two types describe the
5
+ * same underlying object (`ctx.state` and `ctx.vars` are aliases). v2
6
+ * code should prefer `ctx.state` / `StateContext`.
7
+ *
8
+ * Values can be any JSON-serializable shape — a step's output is whatever
9
+ * its node returned. The strict `ParamsDictionary` shape from earlier
10
+ * versions of Blok was inaccurate; real workflows have always stored
11
+ * arbitrary objects, arrays, primitives, etc. here.
12
+ *
13
+ * @deprecated Prefer {@link StateContext}.
14
+ */
2
15
  type VarsContext = {
3
- [key: string]: ParamsDictionary;
16
+ [key: string]: unknown;
4
17
  };
5
18
  export default VarsContext;
@@ -1,10 +1,119 @@
1
1
  import type Context from "../types/Context";
2
2
  import type ParamsDictionary from "../types/ParamsDictionary";
3
+ /**
4
+ * Mapper — the workflow input resolver.
5
+ *
6
+ * Every step's `inputs` object is walked by `replaceObjectStrings`
7
+ * before the step runs (see `NodeBase.process` → `blueprintMapper`).
8
+ * Two template syntaxes are recognised:
9
+ *
10
+ * 1. **`${path.to.value}`** — interpolated string placeholder.
11
+ * Resolved via lodash `_.get(data, key)` first, with a JS-eval
12
+ * fallback when the key is not in `data`. Multiple placeholders
13
+ * in one string are concatenated.
14
+ *
15
+ * 2. **`"js/..."`** — full-string JS expression. The string is
16
+ * replaced ENTIRELY by the result of evaluating the expression
17
+ * (after stripping the `js/` prefix) against the live `ctx`.
18
+ * Returns whatever value the expression produces (string, number,
19
+ * object, array, etc.) — preserves type fidelity end-to-end.
20
+ *
21
+ * Both syntaxes evaluate inside a sandboxed `Function` with these
22
+ * names in scope: `ctx`, `data`, `func`, `vars`. (Symmetric scope
23
+ * across both syntaxes since v0.3.x — pre-v0.3.x `${...}` evaluator
24
+ * lacked `func`+`vars`, leading to surprising scope mismatches.)
25
+ *
26
+ * ## Failure modes — see {@link MapperResolutionError}
27
+ *
28
+ * Resolution failures (typo, undefined access, syntax error) used to
29
+ * be swallowed silently with a noisy `console.log("Mapper Error N", e)`
30
+ * — and worse, the unresolved expression string passed through to the
31
+ * node, producing silent miscompiles downstream. Since v0.3.x the
32
+ * Mapper packages every failure in a `MapperResolutionError` with full
33
+ * context (workflow, step, expression, underlying cause + heuristic
34
+ * hint) and routes it according to `BLOK_MAPPER_MODE`:
35
+ *
36
+ * - `"warn"` (default) — log via `ctx.logger.logLevel("warn", ...)`,
37
+ * pass through the original string. Backward-compatible diagnostics.
38
+ * - `"strict"` — throw the error, fail the step fast. **Recommended
39
+ * for production.**
40
+ * - `"silent"` — full suppression. Tests / opt-out only.
41
+ *
42
+ * ## Bug fixes shipped alongside the diagnostic upgrade (v0.3.x)
43
+ *
44
+ * - **Falsy values now preserved** — `_.get(data, key) || runJs(...)`
45
+ * used to fall through to `runJs` when the lookup returned `0`,
46
+ * `false`, `null`, or `""` (all valid values incorrectly treated
47
+ * as missing). Now uses an explicit `=== undefined` check.
48
+ * - **Object interpolation now JSON-encodes** — `value as string`
49
+ * used to produce `"[object Object]"` for object values. Now
50
+ * round-trips via `JSON.stringify`.
51
+ * - **`js/` prefix stripping** uses `slice(3)` instead of
52
+ * `replace("js/", "")` (the latter only strips the FIRST
53
+ * occurrence — fragile if the expression itself contained `js/`).
54
+ */
55
+ /**
56
+ * How the Mapper reacts to expression resolution failures. Read from
57
+ * the `BLOK_MAPPER_MODE` env var at every call (no caching) so unit
58
+ * tests can flip the mode between cases.
59
+ */
60
+ export type MapperMode = "warn" | "strict" | "silent";
3
61
  declare class Mapper {
62
+ /**
63
+ * Walk an object recursively, resolving every string value via
64
+ * `replaceString`. Mutates in place. Used by `NodeBase.process` to
65
+ * resolve a step's `inputs` against the live `ctx` before the
66
+ * step runs.
67
+ *
68
+ * Object/array values are recursed into; primitive non-string
69
+ * values are left untouched. Null values are NOT recursed (avoids
70
+ * a TypeError on `for (const k in null)`).
71
+ */
4
72
  replaceObjectStrings(obj: ParamsDictionary, ctx: Context, data: ParamsDictionary): void;
5
- replaceString: (strData: string, ctx: Context, data: ParamsDictionary) => string;
6
- private runJs;
73
+ /**
74
+ * Resolve a single string. Returns `unknown` because a `js/...`
75
+ * expression may yield any value (number, object, array, etc.) —
76
+ * type fidelity is preserved across the resolver boundary.
77
+ *
78
+ * Pre-v0.3.x return was typed as `string` via `as string` cast
79
+ * which was a type lie; downstream consumers received the actual
80
+ * runtime value but couldn't see it through the type system.
81
+ */
82
+ replaceString: (strData: string, ctx: Context, data: ParamsDictionary) => unknown;
83
+ /**
84
+ * Resolve a `${path}` expression. Lodash lookup first; JS-eval
85
+ * fallback when the path is not in `data`. Returns the resolved
86
+ * value OR the failure sentinel.
87
+ */
88
+ private resolveTemplateExpression;
89
+ /**
90
+ * Evaluate a `js/...` full-string expression. Returns whatever the
91
+ * expression produces (any type), or — in warn/silent mode on
92
+ * failure — the original literal `js/...` string.
93
+ *
94
+ * Strict-mode failures throw `MapperResolutionError`.
95
+ */
7
96
  private jsMapper;
97
+ /**
98
+ * Apply the configured mode (`BLOK_MAPPER_MODE`) to a resolution
99
+ * failure. In strict mode, the error escapes here and propagates
100
+ * up through `replaceString` → `NodeBase.blueprintMapper` →
101
+ * `NodeBase.process` → step error envelope.
102
+ */
103
+ private handleResolutionError;
104
+ /**
105
+ * Sandboxed JS evaluation. Compiles the expression as a function
106
+ * body returning the expression value; binds `ctx`, `data`, `func`,
107
+ * `vars` as positional arguments; runs in `"use strict"` mode.
108
+ *
109
+ * Throws on any evaluation error (typo, undefined access, syntax,
110
+ * unknown identifier). Callers wrap in try/catch and translate to
111
+ * `MapperResolutionError`.
112
+ *
113
+ * Public via the prototype but documented as internal — the only
114
+ * supported call sites are inside this class.
115
+ */
116
+ private runJs;
8
117
  }
9
118
  declare const _default: Mapper;
10
119
  export default _default;
@@ -1,55 +1,288 @@
1
1
  import _ from "lodash";
2
+ import { MapperResolutionError } from "./MapperResolutionError";
3
+ // =============================================================================
4
+ // Internal sentinels + helpers
5
+ // =============================================================================
6
+ /**
7
+ * Returned by the template-resolution path to signal "this placeholder
8
+ * could not be resolved; leave the literal `${...}` in place". Using a
9
+ * Symbol avoids ambiguity with the legitimate `undefined` value an
10
+ * expression might produce.
11
+ */
12
+ const TEMPLATE_RESOLUTION_FAILED = Symbol("TEMPLATE_RESOLUTION_FAILED");
13
+ function readMode() {
14
+ const raw = process.env.BLOK_MAPPER_MODE;
15
+ if (raw === "strict")
16
+ return "strict";
17
+ if (raw === "silent")
18
+ return "silent";
19
+ return "warn";
20
+ }
21
+ function readStepContext(ctx) {
22
+ const ctxAny = ctx;
23
+ const stepInfo = ctxAny._stepInfo;
24
+ const stepName = typeof stepInfo?.name === "string" ? stepInfo.name : undefined;
25
+ const workflowName = typeof ctx.workflow_name === "string" ? ctx.workflow_name : undefined;
26
+ return { workflowName, stepName };
27
+ }
28
+ /**
29
+ * Build the actionable error message — every line carries information
30
+ * a developer can act on: WHERE it failed, WHAT failed, WHY it likely
31
+ * failed, and HOW to fix it.
32
+ */
33
+ function buildErrorMessage(opts) {
34
+ const wf = opts.workflowName ?? "<unknown workflow>";
35
+ const step = opts.stepName ?? "<unknown step>";
36
+ const literal = opts.syntax === "js" ? `js/${opts.expression}` : `\${${opts.expression}}`;
37
+ const causeMsg = opts.cause instanceof Error ? opts.cause.message : String(opts.cause);
38
+ const hint = guessHint(opts.expression, causeMsg);
39
+ const lines = [
40
+ `[blok][mapper] Failed to resolve \`${literal}\` in step "${step}" of workflow "${wf}"`,
41
+ ` underlying: ${causeMsg}`,
42
+ ];
43
+ if (hint)
44
+ lines.push(` hint: ${hint}`);
45
+ lines.push(" fix: verify the referenced ctx path exists at run time. Set BLOK_MAPPER_MODE=strict in production to fail fast on these errors.");
46
+ return lines.join("\n");
47
+ }
48
+ /**
49
+ * Heuristic — translate common JS evaluation errors into actionable
50
+ * developer hints. Returns `null` when the error doesn't match any
51
+ * known pattern (the underlying message is still surfaced).
52
+ */
53
+ function guessHint(expression, errorMessage) {
54
+ // Most common case — `Cannot read properties of undefined (reading 'X')`
55
+ // or the older `Cannot read property 'X' of undefined`. Both forms
56
+ // appear depending on Node/Bun version.
57
+ const undefMatch = errorMessage.match(/Cannot read propert(?:y '(\w+)' of undefined|ies of undefined \(reading '(\w+)'\))/);
58
+ if (undefMatch) {
59
+ const prop = undefMatch[1] ?? undefMatch[2];
60
+ const segments = expression.split(".");
61
+ const parent = segments.slice(0, -1).join(".") || expression;
62
+ return `the path \`${parent}\` is undefined or doesn't have a "${prop}" field at run time. Check the trigger payload (ctx.req.body) or the upstream step's output (ctx.state.<id>).`;
63
+ }
64
+ // Identifier not in scope — only `ctx`, `data`, `func`, `vars` are
65
+ // available inside expressions.
66
+ const refMatch = errorMessage.match(/(\w+) is not defined/);
67
+ if (refMatch) {
68
+ return `\`${refMatch[1]}\` is not in scope. Available identifiers inside expressions: ctx, data, func, vars.`;
69
+ }
70
+ // Syntax error.
71
+ if (/SyntaxError/.test(errorMessage) || /Unexpected token/.test(errorMessage)) {
72
+ return "the expression is not valid JavaScript. Check for typos, unmatched parentheses, or stray characters.";
73
+ }
74
+ return null;
75
+ }
76
+ /**
77
+ * Route a warn-mode log line to the best available sink. Prefers
78
+ * `ctx.logger.logLevel("warn", ...)` so the warning lands in BOTH the
79
+ * console (via DefaultLogger) AND Studio's log viewer (via
80
+ * TracingLogger.normalizeLevel → addLog). Falls back to console.warn
81
+ * when no logger is attached (early-boot or hand-rolled test ctx).
82
+ */
83
+ function logViaCtxOrConsole(ctx, message) {
84
+ const logger = ctx.logger;
85
+ if (logger?.logLevel) {
86
+ try {
87
+ logger.logLevel("warn", message);
88
+ return;
89
+ }
90
+ catch {
91
+ // fall through to console.warn — never let logging itself crash a step.
92
+ }
93
+ }
94
+ if (logger?.log) {
95
+ try {
96
+ logger.log(message);
97
+ return;
98
+ }
99
+ catch {
100
+ // fall through
101
+ }
102
+ }
103
+ console.warn(message);
104
+ }
105
+ /**
106
+ * Coerce a resolved value to its string form for `${...}` interpolation.
107
+ *
108
+ * Pre-v0.3.x used `value as string` which fell back to `String(value)`
109
+ * via implicit coercion — producing `"[object Object]"` for any object/
110
+ * array. Now JSON-encodes complex values so interpolated strings
111
+ * preserve information instead of silently miscompiling.
112
+ *
113
+ * - `null` / `undefined` → empty string (matches user intent for
114
+ * missing optional fields)
115
+ * - `string` → identity
116
+ * - `number` / `boolean` / `bigint` → `String(value)`
117
+ * - everything else → `JSON.stringify(value)` with a defensive fallback
118
+ * to `String(value)` for circular structures.
119
+ */
120
+ function toInterpolatedString(value) {
121
+ if (value === null || value === undefined)
122
+ return "";
123
+ if (typeof value === "string")
124
+ return value;
125
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
126
+ return String(value);
127
+ }
128
+ try {
129
+ return JSON.stringify(value) ?? String(value);
130
+ }
131
+ catch {
132
+ // JSON.stringify throws on circular references — degrade gracefully.
133
+ return String(value);
134
+ }
135
+ }
136
+ // =============================================================================
137
+ // Mapper
138
+ // =============================================================================
2
139
  class Mapper {
140
+ /**
141
+ * Walk an object recursively, resolving every string value via
142
+ * `replaceString`. Mutates in place. Used by `NodeBase.process` to
143
+ * resolve a step's `inputs` against the live `ctx` before the
144
+ * step runs.
145
+ *
146
+ * Object/array values are recursed into; primitive non-string
147
+ * values are left untouched. Null values are NOT recursed (avoids
148
+ * a TypeError on `for (const k in null)`).
149
+ */
3
150
  replaceObjectStrings(obj, ctx, data) {
4
151
  for (const key in obj) {
5
152
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
6
153
  const value = obj[key];
7
154
  if (typeof value === "string") {
155
+ // `ParamsDictionary[key]` is typed `string`, but the
156
+ // runtime contract has always been "the resolved value
157
+ // keeps its actual type" (so `js/ctx.req.body.count`
158
+ // produces a number, not the string "42"). The
159
+ // `as unknown as string` boundary cast acknowledges
160
+ // that the type system can't express this widening
161
+ // without changing the global ParamsDictionary shape
162
+ // (a much larger refactor).
8
163
  obj[key] = this.replaceString(value, ctx, data);
9
164
  }
10
- else if (typeof value === "object") {
165
+ else if (value !== null && typeof value === "object") {
11
166
  this.replaceObjectStrings(value, ctx, data);
12
167
  }
13
168
  }
14
169
  }
15
170
  }
171
+ /**
172
+ * Resolve a single string. Returns `unknown` because a `js/...`
173
+ * expression may yield any value (number, object, array, etc.) —
174
+ * type fidelity is preserved across the resolver boundary.
175
+ *
176
+ * Pre-v0.3.x return was typed as `string` via `as string` cast
177
+ * which was a type lie; downstream consumers received the actual
178
+ * runtime value but couldn't see it through the type system.
179
+ */
16
180
  replaceString = (strData, ctx, data) => {
17
181
  let str = strData;
18
- // if (str.length > 15000) {
19
- // throw new Error("Input too long");
20
- // }
182
+ // === 1. `${path}` template interpolation ===
21
183
  const regex = /\${(.*?)}/g;
22
184
  const matches = str.match(regex);
23
185
  if (matches) {
24
186
  for (const match of matches) {
25
- try {
26
- const key = match.replace(/\${/g, "").replace(/}/g, "");
27
- const value = _.get(data, key) || this.runJs(key, ctx, data);
28
- // if (value) str = this.parseBasedOnType(str.replace(match, value), typeof value);
29
- str = str.replace(match, value);
30
- }
31
- catch (e) {
32
- console.log("Mapper Error 1", e);
187
+ const key = match.slice(2, -1); // strip `${` and `}`
188
+ const value = this.resolveTemplateExpression(key, ctx, data);
189
+ if (value !== TEMPLATE_RESOLUTION_FAILED) {
190
+ str = str.replace(match, toInterpolatedString(value));
33
191
  }
192
+ // On failure: leave the literal `${...}` placeholder in
193
+ // place. The failure was already reported per the active
194
+ // mode (warn/strict/silent).
34
195
  }
35
196
  }
36
- const result = this.jsMapper(str, ctx, data);
37
- return result;
197
+ // === 2. `js/...` full-string evaluation ===
198
+ return this.jsMapper(str, ctx, data);
38
199
  };
39
- runJs(str, ctx, data = {}, func = {}, vars = {}) {
40
- return Function("ctx", "data", "func", "vars", `"use strict";return (${str});`)(ctx, data, func, vars);
200
+ /**
201
+ * Resolve a `${path}` expression. Lodash lookup first; JS-eval
202
+ * fallback when the path is not in `data`. Returns the resolved
203
+ * value OR the failure sentinel.
204
+ */
205
+ resolveTemplateExpression(key, ctx, data) {
206
+ // Lodash lookup. Use explicit `=== undefined` instead of `||`
207
+ // so falsy-but-valid values (0, false, "", null) are preserved.
208
+ const lookupValue = _.get(data, key);
209
+ if (lookupValue !== undefined)
210
+ return lookupValue;
211
+ // Fallback to JS evaluation against ctx. Symmetric with jsMapper:
212
+ // pass `ctx.func` and `ctx.vars` so `${func.X}` and `${vars.X}`
213
+ // have the same scope as `js/func.X` / `js/vars.X`.
214
+ try {
215
+ return this.runJs(key, ctx, data, (ctx.func ?? {}), (ctx.vars ?? {}));
216
+ }
217
+ catch (cause) {
218
+ const stepCtx = readStepContext(ctx);
219
+ const error = new MapperResolutionError(buildErrorMessage({ expression: key, syntax: "template", ...stepCtx, cause }), { expression: key, syntax: "template", ...stepCtx, cause });
220
+ this.handleResolutionError(ctx, error);
221
+ return TEMPLATE_RESOLUTION_FAILED;
222
+ }
41
223
  }
224
+ /**
225
+ * Evaluate a `js/...` full-string expression. Returns whatever the
226
+ * expression produces (any type), or — in warn/silent mode on
227
+ * failure — the original literal `js/...` string.
228
+ *
229
+ * Strict-mode failures throw `MapperResolutionError`.
230
+ */
42
231
  jsMapper(str, ctx, data) {
232
+ if (typeof str !== "string" || !str.startsWith("js/"))
233
+ return str;
234
+ // `slice(3)` strips exactly the leading `js/` prefix.
235
+ // Pre-v0.3.x used `replace("js/", "")` which only strips the
236
+ // FIRST occurrence — fragile if the expression itself contained
237
+ // the substring `js/` (e.g., a URL like `https://js/foo`).
238
+ const expression = str.slice(3);
43
239
  try {
44
- if (typeof str === "string" && str.startsWith("js/")) {
45
- const fn = str.replace("js/", "");
46
- return this.runJs(fn, ctx, data, ctx.func, ctx.vars);
47
- }
240
+ return this.runJs(expression, ctx, data, (ctx.func ?? {}), (ctx.vars ?? {}));
48
241
  }
49
- catch (error) {
50
- console.log("Mapper Error 2", error);
242
+ catch (cause) {
243
+ const stepCtx = readStepContext(ctx);
244
+ const error = new MapperResolutionError(buildErrorMessage({ expression, syntax: "js", ...stepCtx, cause }), {
245
+ expression,
246
+ syntax: "js",
247
+ ...stepCtx,
248
+ cause,
249
+ });
250
+ this.handleResolutionError(ctx, error);
251
+ return str; // pre-v0.3.x behavior — pass through the literal string
51
252
  }
52
- return str;
253
+ }
254
+ /**
255
+ * Apply the configured mode (`BLOK_MAPPER_MODE`) to a resolution
256
+ * failure. In strict mode, the error escapes here and propagates
257
+ * up through `replaceString` → `NodeBase.blueprintMapper` →
258
+ * `NodeBase.process` → step error envelope.
259
+ */
260
+ handleResolutionError(ctx, error) {
261
+ const mode = readMode();
262
+ if (mode === "strict")
263
+ throw error;
264
+ if (mode === "silent")
265
+ return;
266
+ // mode === "warn"
267
+ logViaCtxOrConsole(ctx, error.message);
268
+ }
269
+ /**
270
+ * Sandboxed JS evaluation. Compiles the expression as a function
271
+ * body returning the expression value; binds `ctx`, `data`, `func`,
272
+ * `vars` as positional arguments; runs in `"use strict"` mode.
273
+ *
274
+ * Throws on any evaluation error (typo, undefined access, syntax,
275
+ * unknown identifier). Callers wrap in try/catch and translate to
276
+ * `MapperResolutionError`.
277
+ *
278
+ * Public via the prototype but documented as internal — the only
279
+ * supported call sites are inside this class.
280
+ */
281
+ runJs(str, ctx, data = {}, func = {}, vars = {}) {
282
+ // Function constructor (NOT eval) — creates a fresh function
283
+ // scope without lexical access to the surrounding module. Same
284
+ // security profile as the v1 implementation.
285
+ return Function("ctx", "data", "func", "vars", `"use strict";return (${str});`)(ctx, data, func, vars);
53
286
  }
54
287
  }
55
288
  export default new Mapper();
@@ -1 +1 @@
1
- {"version":3,"file":"Mapper.js","sourceRoot":"","sources":["../../src/utils/Mapper.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Mapper.js","sourceRoot":"","sources":["../../src/utils/Mapper.ts"],"names":[],"mappings":"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"}