@blokjs/shared 0.2.2 → 0.6.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.
package/dist/NodeBase.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import _ from "lodash";
2
2
  import GlobalError from "./GlobalError";
3
3
  import mapper from "./utils/Mapper";
4
+ import { MapperResolutionError } from "./utils/MapperResolutionError";
4
5
  export default class NodeBase {
5
6
  flow = false;
6
7
  name = "";
@@ -8,17 +9,6 @@ export default class NodeBase {
8
9
  active = true;
9
10
  stop = false;
10
11
  originalConfig = {};
11
- /**
12
- * @deprecated v2 default-stores every step's output. `set_var: true` is
13
- * a no-op (default behaviour); `set_var: false` is normalized to
14
- * `ephemeral: true` at workflow load time. Reading this field is still
15
- * supported for legacy code paths but new code should rely on `ephemeral`.
16
- *
17
- * Default is `undefined` (NOT `false`) — `false` here would short-circuit
18
- * `PersistenceHelper.applyStepOutput` and disable the v2 default-store
19
- * rule for every step that didn't explicitly set the field.
20
- */
21
- set_var;
22
12
  // =========================================================================
23
13
  // V2 persistence knobs — populated by Configuration.getSteps from the
24
14
  // step definition. Read by PersistenceHelper.applyStepOutput.
@@ -39,6 +29,75 @@ export default class NodeBase {
39
29
  * Only `ctx.prev` carries the result to the immediately next step.
40
30
  */
41
31
  ephemeral = false;
32
+ // =========================================================================
33
+ // V2 idempotency cache + retry knobs — populated by Configuration.getSteps
34
+ // from the step definition. Read by RunnerSteps before delegating to
35
+ // `step.process()`. Caching layers ABOVE PersistenceHelper.applyStepOutput;
36
+ // retry wraps the same call site.
37
+ //
38
+ // Mirrors the Zod schema in `@blokjs/helper/src/types/StepOpts.ts`. Kept
39
+ // as a structural interface here to avoid a runtime dep from shared on
40
+ // helper.
41
+ // =========================================================================
42
+ /**
43
+ * Optional cache key for this step's result. When set, the runner consults
44
+ * the idempotency cache before executing — a hit returns the cached result
45
+ * (and emits a NODE_CACHED event); a miss runs the step and caches its
46
+ * result on success. Cache namespace is (workflowName, name, idempotencyKey).
47
+ *
48
+ * Author-facing values may be a literal string ("user-123") or a $ proxy
49
+ * expression compiled to `js/ctx....`. The runner resolves the expression
50
+ * against the live ctx at run time before consulting the cache.
51
+ */
52
+ idempotencyKey;
53
+ /**
54
+ * Optional cache lifetime in milliseconds. Defaults to 24 hours
55
+ * (86_400_000) when undefined. Pass 0 to mark a stored result as
56
+ * immediately expired (effectively disables caching for this step).
57
+ */
58
+ idempotencyKeyTTL;
59
+ /**
60
+ * Optional retry configuration with capped exponential backoff. When
61
+ * undefined, the step runs at most once (matches pre-v0.3.x behaviour).
62
+ * Per-attempt failures emit `NODE_ATTEMPT_FAILED` trace events.
63
+ */
64
+ retry;
65
+ /**
66
+ * Tier 2 quick-wins — per-attempt execution timeout in milliseconds.
67
+ * When set, `RunnerSteps` wraps each `step.process()` in a setTimeout-
68
+ * based Promise.race. On timeout, throws `StepTimeoutError` (which the
69
+ * retry loop treats as any other error). On final-attempt timeout,
70
+ * the run auto-flips to `"timedOut"` status. When undefined, the
71
+ * step runs without a per-attempt cap (matches pre-quick-wins
72
+ * behaviour).
73
+ *
74
+ * Originally set as a duration string or number on the step schema;
75
+ * `Configuration.getSteps` converts to milliseconds via
76
+ * `parseDuration` before assigning here.
77
+ */
78
+ maxDurationMs;
79
+ // =========================================================================
80
+ // V2 sub-workflow knobs — populated by Configuration.getSteps for steps
81
+ // that invoke another workflow (`subworkflow: "<name>"` shape). Read by
82
+ // `SubworkflowNode.run()` to look up the child workflow in the
83
+ // WorkflowRegistry. Mirrors the Zod schema in `@blokjs/helper`.
84
+ // =========================================================================
85
+ /**
86
+ * Name of the workflow to invoke when this step runs. When set, the
87
+ * step's `node` ref is `"@blokjs/subworkflow"` (a sentinel) and the
88
+ * runner resolves it to a `SubworkflowNode` that looks up the child
89
+ * by this name in the `WorkflowRegistry` singleton.
90
+ */
91
+ subworkflow;
92
+ /**
93
+ * If true (default), the parent step blocks until the child workflow
94
+ * completes. The child's `ctx.response` becomes the parent step's
95
+ * output (lands on `state[<id>]` like any other step).
96
+ *
97
+ * `wait: false` (fire-and-forget) is rejected at workflow load time
98
+ * in v0.3.x — the schema includes a deferred-feature error message.
99
+ */
100
+ wait;
42
101
  async process(ctx, step) {
43
102
  let response = {
44
103
  success: true,
@@ -106,7 +165,15 @@ export default class NodeBase {
106
165
  mapper.replaceObjectStrings(newObj, ctx, data);
107
166
  }
108
167
  catch (e) {
109
- console.log("MAPPER ERROR", e);
168
+ // `MapperResolutionError` (strict mode) carries full diagnostic
169
+ // context — let it escape so the step's error envelope surfaces
170
+ // the workflow / step / expression that failed.
171
+ if (e instanceof MapperResolutionError)
172
+ throw e;
173
+ // Anything else here is an UNEXPECTED bug in the mapper itself
174
+ // (recursion fault, OOM, logger crash). Surface loudly via
175
+ // stderr WITH the stack trace — never silently swallow.
176
+ console.error("[blok][mapper] unexpected error during input resolution:", e);
110
177
  }
111
178
  return newObj;
112
179
  };
@@ -1 +1 @@
1
- {"version":3,"file":"NodeBase.js","sourceRoot":"","sources":["../src/NodeBase.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NodeBase.js","sourceRoot":"","sources":["../src/NodeBase.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,9 @@ import { Metrics, type MetricsType } from "./Metrics";
5
5
  import NodeBase from "./NodeBase";
6
6
  import Trigger from "./Trigger";
7
7
  import ConfigContext from "./types/ConfigContext";
8
+ import type ConnectionContext from "./types/ConnectionContext";
8
9
  import Context from "./types/Context";
10
+ import EnvContext from "./types/EnvContext";
9
11
  import ErrorContext from "./types/ErrorContext";
10
12
  import FunctionContext from "./types/FunctionContext";
11
13
  import LoggerContext from "./types/LoggerContext";
@@ -14,6 +16,8 @@ import RequestContext from "./types/RequestContext";
14
16
  import ResponseContext from "./types/ResponseContext";
15
17
  import StateContext from "./types/StateContext";
16
18
  import Step from "./types/Step";
19
+ import type StreamContext from "./types/StreamContext";
17
20
  import VarsContext from "./types/VarsContext";
21
+ import mapper from "./utils/Mapper";
18
22
  import MemoryUsage from "./utils/MemoryUsage";
19
- export { NodeBase, Context, RequestContext, ResponseContext, ErrorContext, LoggerContext, ConfigContext, Trigger, NodeConfigContext, FunctionContext, StateContext, VarsContext, Step, GlobalLogger, GlobalError, BlokError, type BlokErrorOpts, type NodeErrorPayload, ErrorCategory, ErrorSeverity, DEFAULT_HTTP_STATUS, DEFAULT_RETRYABLE, Metrics, MemoryUsage, type MetricsType, };
23
+ export { NodeBase, Context, RequestContext, ResponseContext, EnvContext, ErrorContext, LoggerContext, ConfigContext, type ConnectionContext, type StreamContext, Trigger, NodeConfigContext, FunctionContext, StateContext, VarsContext, Step, GlobalLogger, GlobalError, BlokError, type BlokErrorOpts, type NodeErrorPayload, ErrorCategory, ErrorSeverity, DEFAULT_HTTP_STATUS, DEFAULT_RETRYABLE, Metrics, MemoryUsage, type MetricsType, mapper, };
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import GlobalLogger from "./GlobalLogger";
4
4
  import { Metrics } from "./Metrics";
5
5
  import NodeBase from "./NodeBase";
6
6
  import Trigger from "./Trigger";
7
+ import mapper from "./utils/Mapper";
7
8
  import MemoryUsage from "./utils/MemoryUsage";
8
- export { NodeBase, Trigger, GlobalLogger, GlobalError, BlokError, ErrorCategory, ErrorSeverity, DEFAULT_HTTP_STATUS, DEFAULT_RETRYABLE, Metrics, MemoryUsage, };
9
+ export { NodeBase, Trigger, GlobalLogger, GlobalError, BlokError, ErrorCategory, ErrorSeverity, DEFAULT_HTTP_STATUS, DEFAULT_RETRYABLE, Metrics, MemoryUsage, mapper, };
9
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,EAAE,EAEjB,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,aAAa,GAEb,MAAM,aAAa,CAAC;AACrB,OAAO,WAAW,MAAM,eAAe,CAAC;AACxC,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAoB,MAAM,WAAW,CAAC;AACtD,OAAO,QAAQ,MAAM,YAAY,CAAC;AAClC,OAAO,OAAO,MAAM,WAAW,CAAC;AAYhC,OAAO,WAAW,MAAM,qBAAqB,CAAC;AAE9C,OAAO,EACN,QAAQ,EAOR,OAAO,EAMP,YAAY,EACZ,WAAW,EACX,SAAS,EAGT,aAAa,EACb,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,OAAO,EACP,WAAW,GAEX,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,EAAE,EAEjB,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,aAAa,GAEb,MAAM,aAAa,CAAC;AACrB,OAAO,WAAW,MAAM,eAAe,CAAC;AACxC,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAoB,MAAM,WAAW,CAAC;AACtD,OAAO,QAAQ,MAAM,YAAY,CAAC;AAClC,OAAO,OAAO,MAAM,WAAW,CAAC;AAehC,OAAO,MAAM,MAAM,gBAAgB,CAAC;AACpC,OAAO,WAAW,MAAM,qBAAqB,CAAC;AAE9C,OAAO,EACN,QAAQ,EAUR,OAAO,EAMP,YAAY,EACZ,WAAW,EACX,SAAS,EAGT,aAAa,EACb,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,OAAO,EACP,WAAW,EAEX,MAAM,GACN,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * v0.7 — per-connection API exposed on `ctx.connection` for triggers
3
+ * that hold long-lived bidirectional channels (WebSocket today; SSE
4
+ * gets a streaming variant in PR 3).
5
+ *
6
+ * Authors interact with the connection through this object — the
7
+ * trigger wraps the underlying transport's send/close primitives and
8
+ * tracks per-connection state. Lifecycle:
9
+ *
10
+ * - On `connect`: trigger creates the connection, runs the workflow
11
+ * once with `ctx.connection` bound. Author can call
12
+ * `setAttachment()` to store per-connection state.
13
+ * - On `message`: trigger runs the workflow again with the same
14
+ * `ctx.connection` instance — author reads `attachment` to recover
15
+ * the per-connection state.
16
+ * - On `disconnect`: same connection, last run. Cleanup happens here.
17
+ *
18
+ * Absent on contexts built for HTTP / Worker / Cron triggers.
19
+ */
20
+ export interface ConnectionContext {
21
+ /** Stable connection identifier (uuid). Set once at connect. */
22
+ readonly id: string;
23
+ /**
24
+ * Send data to THIS connection.
25
+ * - Text payloads: send a string (typically `JSON.stringify(...)`).
26
+ * - Binary payloads: send a `Uint8Array` or `ArrayBuffer`.
27
+ *
28
+ * Buffered if the underlying socket is busy; backpressure visible
29
+ * via the trigger's `messageRateLimit` and Studio trace metrics.
30
+ */
31
+ send(data: string | ArrayBuffer | Uint8Array): void;
32
+ /**
33
+ * Close THIS connection cleanly. Subsequent `send` calls are no-ops.
34
+ * Triggers the `disconnect` workflow run as a final step.
35
+ *
36
+ * @param code Close code per RFC 6455 (default 1000 — normal).
37
+ * @param reason Human-readable reason (max 123 bytes per RFC).
38
+ */
39
+ close(code?: number, reason?: string): void;
40
+ /**
41
+ * Store per-connection state. Survives across message-event workflow
42
+ * runs on the same connection — the "userId + joinedAt + cursor"
43
+ * pattern. Reset by every call (no merge).
44
+ *
45
+ * Inspired by Cloudflare Durable Objects'
46
+ * `state.serializeAttachment()` API. Capped at 2 KB serialized JSON;
47
+ * larger values are rejected with a warning log (the connection
48
+ * stays open).
49
+ */
50
+ setAttachment(value: unknown): void;
51
+ /**
52
+ * Retrieve per-connection state set by `setAttachment()`. Returns
53
+ * `undefined` if nothing was set on this connection.
54
+ */
55
+ readonly attachment: unknown;
56
+ /**
57
+ * Channel/room membership for fan-out broadcasts. Multiple
58
+ * connections can join the same room; `@blokjs/ws-broadcast`
59
+ * targets every member.
60
+ */
61
+ joinRoom(name: string): void;
62
+ leaveRoom(name: string): void;
63
+ readonly rooms: ReadonlySet<string>;
64
+ /**
65
+ * v0.7 — broadcast a message to every connection in the named
66
+ * room (workflow-scoped). Returns the number of recipients
67
+ * the trigger successfully sent to.
68
+ *
69
+ * Set `exceptSelf: true` to skip the connection that triggered
70
+ * the current workflow run (the "send to everyone except me"
71
+ * pattern). Bound by the trigger via this `ctx.connection`
72
+ * accessor so helper nodes (`@blokjs/ws-broadcast`) don't need
73
+ * to import the trigger package directly.
74
+ */
75
+ broadcast(room: string, data: string | ArrayBuffer | Uint8Array, opts?: {
76
+ exceptSelf?: boolean;
77
+ }): number;
78
+ }
79
+ export default ConnectionContext;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ConnectionContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConnectionContext.js","sourceRoot":"","sources":["../../src/types/ConnectionContext.ts"],"names":[],"mappings":""}
@@ -1,5 +1,6 @@
1
1
  import type GlobalLogger from "../GlobalLogger";
2
2
  import type ConfigContext from "./ConfigContext";
3
+ import type ConnectionContext from "./ConnectionContext";
3
4
  import type EnvContext from "./EnvContext";
4
5
  import type ErrorContext from "./ErrorContext";
5
6
  import type FunctionContext from "./FunctionContext";
@@ -7,6 +8,7 @@ import type LoggerContext from "./LoggerContext";
7
8
  import type RequestContext from "./RequestContext";
8
9
  import type ResponseContext from "./ResponseContext";
9
10
  import type StateContext from "./StateContext";
11
+ import type StreamContext from "./StreamContext";
10
12
  import type VarsContext from "./VarsContext";
11
13
  /**
12
14
  * The runtime context for a single workflow execution. One Context object
@@ -94,6 +96,55 @@ type Context = {
94
96
  * publish something other than its return value (most nodes don't).
95
97
  */
96
98
  publish?: (name: string, value: unknown) => void;
99
+ /**
100
+ * Tier 2 follow-up · cooperative cancellation. AbortSignal whose
101
+ * `aborted` flips to true when an operator cancels the run via
102
+ * `POST /__blok/runs/:runId/cancel` while it's in `running` status.
103
+ *
104
+ * Nodes that perform long-running work (loops, fetch, db queries)
105
+ * should consult `ctx.signal.aborted` periodically and abort early
106
+ * — or pass the signal to fetch/abort-aware APIs:
107
+ *
108
+ * await fetch(url, { signal: ctx.signal });
109
+ * if (ctx.signal?.aborted) throw new Error("aborted");
110
+ *
111
+ * Always present on contexts created by `TriggerBase.createContext`.
112
+ * Optional on the type for back-compat with hand-built contexts in
113
+ * tests.
114
+ */
115
+ signal?: AbortSignal;
116
+ /**
117
+ * v0.7 — per-connection API for long-lived bidirectional triggers
118
+ * (WebSocket). Present on contexts dispatched by `WebSocketTrigger`;
119
+ * absent on HTTP / Worker / Cron contexts.
120
+ *
121
+ * Authors call `ctx.connection.send(...)` to push to the specific
122
+ * connection that fired the event, `ctx.connection.close(...)` to
123
+ * terminate it, or `ctx.connection.setAttachment(...)` to store
124
+ * per-connection state that survives across the connect → N×message
125
+ * → disconnect lifecycle.
126
+ *
127
+ * Helper nodes (`@blokjs/ws-reply`, `@blokjs/ws-broadcast`,
128
+ * `@blokjs/ws-close`) read this field to interact with the
129
+ * connection without authors having to thread the ws handle through
130
+ * step inputs.
131
+ */
132
+ connection?: ConnectionContext;
133
+ /**
134
+ * v0.7 — per-stream API for server-push HTTP triggers (SSE).
135
+ * Present on contexts dispatched by `SSETrigger` once per stream
136
+ * open; absent on HTTP / WebSocket / Worker / Cron contexts.
137
+ *
138
+ * Authors call `ctx.stream.writeSSE({ event, data, id, retry })`
139
+ * to emit frames, `ctx.stream.close()` to end the stream, and
140
+ * read `ctx.stream.signal.aborted` to detect client disconnects
141
+ * inside long-running loops.
142
+ *
143
+ * Helper nodes (`@blokjs/sse-stream`) read this field so workflow
144
+ * authors don't have to thread the stream handle through step
145
+ * inputs.
146
+ */
147
+ stream?: StreamContext;
97
148
  _PRIVATE_: unknown;
98
149
  };
99
150
  export default Context;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * v0.7 — per-stream API exposed on `ctx.stream` for triggers that hold
3
+ * a long-lived, server-push HTTP channel (SSE today; the `streaming`
4
+ * Hono helper underneath). Authors emit events through this object;
5
+ * the trigger owns the underlying `c.writeSSE` / drain handling.
6
+ *
7
+ * Lifecycle (Pattern A — one workflow run per stream open):
8
+ *
9
+ * - The SSE trigger opens the HTTP stream via Hono's `streamSSE`,
10
+ * creates this object, attaches it to `ctx.stream`, and dispatches
11
+ * the workflow once.
12
+ * - The workflow body writes zero or more events via
13
+ * `writeSSE(...)`, typically via `@blokjs/sse-stream` consuming an
14
+ * async iterator from `@blokjs/sse-subscribe`.
15
+ * - The workflow returns when its iterator ends OR when
16
+ * `ctx.stream.signal.aborted` flips (client closed). The trigger
17
+ * ends the run with status `completed`.
18
+ *
19
+ * Absent on contexts built for HTTP (request/response), Worker, Cron,
20
+ * or WebSocket triggers.
21
+ */
22
+ export interface StreamContext {
23
+ /** Stable stream/connection identifier (uuid). Set once at open. */
24
+ readonly id: string;
25
+ /**
26
+ * Write one SSE-framed event to the client. Each call produces a
27
+ * `data:` (and optional `event:`, `id:`, `retry:`) frame followed
28
+ * by a blank line.
29
+ *
30
+ * `data` is JSON-stringified when it's not a string. Pass a string
31
+ * to write the payload verbatim. Returns once the chunk has been
32
+ * accepted by Node's stream (honors backpressure via `drain`).
33
+ */
34
+ writeSSE(opts: {
35
+ event?: string;
36
+ data: unknown;
37
+ id?: string;
38
+ retry?: number;
39
+ }): Promise<void>;
40
+ /**
41
+ * Write an SSE comment line (`: <text>\n\n`). Used internally for
42
+ * heartbeats; authors rarely need this directly. Comments are
43
+ * ignored by `EventSource` clients but keep proxies from idling
44
+ * the connection.
45
+ */
46
+ writeComment(text: string): Promise<void>;
47
+ /**
48
+ * Close the stream cleanly. Subsequent `writeSSE` calls are no-ops.
49
+ * The workflow run ends at the next yield point.
50
+ */
51
+ close(): void;
52
+ /** True after `close()` is called or the client disconnects. */
53
+ readonly closed: boolean;
54
+ /**
55
+ * `AbortSignal` that fires when the client disconnects (browser tab
56
+ * closed, network drop, manual `EventSource.close()`). Long-running
57
+ * iterators / fetches should be bound to this signal so they unwind
58
+ * promptly:
59
+ *
60
+ * for await (const evt of source) {
61
+ * if (ctx.stream.signal.aborted) break;
62
+ * await ctx.stream.writeSSE({ data: evt });
63
+ * }
64
+ */
65
+ readonly signal: AbortSignal;
66
+ /**
67
+ * `Last-Event-Id` HTTP header value from the inbound request, if
68
+ * the client is reconnecting. Helper nodes (`@blokjs/sse-subscribe`)
69
+ * read this to resume from the indicated cursor.
70
+ */
71
+ readonly lastEventId: string | null;
72
+ /**
73
+ * v0.7 — subscribe to the in-process SSE event bus. Returns an
74
+ * async iterator that yields events published to any of the named
75
+ * channels. When `lastEventId` is provided (or omitted — defaults
76
+ * to `ctx.stream.lastEventId`), buffered events with `seq > lastEventId`
77
+ * are replayed before live events begin. Caller stops the
78
+ * subscription by exiting the for-await loop or calling
79
+ * `iterator.return()`.
80
+ *
81
+ * Bound by the trigger so helper nodes (`@blokjs/sse-subscribe`,
82
+ * `@blokjs/sse-stream`) don't have to import the trigger directly.
83
+ */
84
+ subscribe(channels: string[], lastEventId?: string | null): AsyncIterableIterator<{
85
+ channel: string;
86
+ id: string;
87
+ event?: string;
88
+ data: unknown;
89
+ timestamp: number;
90
+ }>;
91
+ }
92
+ export default StreamContext;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=StreamContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamContext.js","sourceRoot":"","sources":["../../src/types/StreamContext.ts"],"names":[],"mappings":""}
@@ -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;