@blokjs/runner 0.2.1 → 0.2.2

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.
Files changed (80) hide show
  1. package/dist/Blok.js +11 -11
  2. package/dist/Blok.js.map +1 -1
  3. package/dist/Configuration.d.ts +21 -2
  4. package/dist/Configuration.js +188 -26
  5. package/dist/Configuration.js.map +1 -1
  6. package/dist/ConfigurationResolver.d.ts +9 -0
  7. package/dist/ConfigurationResolver.js +17 -1
  8. package/dist/ConfigurationResolver.js.map +1 -1
  9. package/dist/RunnerSteps.js +52 -9
  10. package/dist/RunnerSteps.js.map +1 -1
  11. package/dist/RuntimeAdapterNode.d.ts +32 -2
  12. package/dist/RuntimeAdapterNode.js +122 -27
  13. package/dist/RuntimeAdapterNode.js.map +1 -1
  14. package/dist/TriggerBase.js +35 -2
  15. package/dist/TriggerBase.js.map +1 -1
  16. package/dist/adapters/BunRuntimeAdapter.d.ts +1 -0
  17. package/dist/adapters/BunRuntimeAdapter.js +1 -0
  18. package/dist/adapters/BunRuntimeAdapter.js.map +1 -1
  19. package/dist/adapters/DockerRuntimeAdapter.d.ts +2 -1
  20. package/dist/adapters/DockerRuntimeAdapter.js +10 -1
  21. package/dist/adapters/DockerRuntimeAdapter.js.map +1 -1
  22. package/dist/adapters/HttpRuntimeAdapter.d.ts +26 -5
  23. package/dist/adapters/HttpRuntimeAdapter.js +97 -16
  24. package/dist/adapters/HttpRuntimeAdapter.js.map +1 -1
  25. package/dist/adapters/NodeJsRuntimeAdapter.d.ts +1 -0
  26. package/dist/adapters/NodeJsRuntimeAdapter.js +1 -0
  27. package/dist/adapters/NodeJsRuntimeAdapter.js.map +1 -1
  28. package/dist/adapters/RuntimeAdapter.d.ts +17 -0
  29. package/dist/adapters/WasmRuntimeAdapter.d.ts +1 -0
  30. package/dist/adapters/WasmRuntimeAdapter.js +1 -0
  31. package/dist/adapters/WasmRuntimeAdapter.js.map +1 -1
  32. package/dist/adapters/grpc/GrpcChannelOptions.d.ts +31 -0
  33. package/dist/adapters/grpc/GrpcChannelOptions.js +68 -0
  34. package/dist/adapters/grpc/GrpcChannelOptions.js.map +1 -0
  35. package/dist/adapters/grpc/GrpcClientPool.d.ts +43 -0
  36. package/dist/adapters/grpc/GrpcClientPool.js +89 -0
  37. package/dist/adapters/grpc/GrpcClientPool.js.map +1 -0
  38. package/dist/adapters/grpc/GrpcCodec.d.ts +226 -0
  39. package/dist/adapters/grpc/GrpcCodec.js +275 -0
  40. package/dist/adapters/grpc/GrpcCodec.js.map +1 -0
  41. package/dist/adapters/grpc/GrpcErrors.d.ts +59 -0
  42. package/dist/adapters/grpc/GrpcErrors.js +190 -0
  43. package/dist/adapters/grpc/GrpcErrors.js.map +1 -0
  44. package/dist/adapters/grpc/GrpcHealthChecker.d.ts +69 -0
  45. package/dist/adapters/grpc/GrpcHealthChecker.js +96 -0
  46. package/dist/adapters/grpc/GrpcHealthChecker.js.map +1 -0
  47. package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +98 -0
  48. package/dist/adapters/grpc/GrpcRuntimeAdapter.js +478 -0
  49. package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -0
  50. package/dist/adapters/grpc/index.d.ts +13 -0
  51. package/dist/adapters/grpc/index.js +14 -0
  52. package/dist/adapters/grpc/index.js.map +1 -0
  53. package/dist/adapters/grpc/proto/blok/runtime/v1/runtime.proto +302 -0
  54. package/dist/adapters/grpc/types.d.ts +97 -0
  55. package/dist/adapters/grpc/types.js +41 -0
  56. package/dist/adapters/grpc/types.js.map +1 -0
  57. package/dist/adapters/transport.d.ts +108 -0
  58. package/dist/adapters/transport.js +196 -0
  59. package/dist/adapters/transport.js.map +1 -0
  60. package/dist/index.d.ts +13 -1
  61. package/dist/index.js +14 -0
  62. package/dist/index.js.map +1 -1
  63. package/dist/testing/WorkflowTestRunner.js +12 -0
  64. package/dist/testing/WorkflowTestRunner.js.map +1 -1
  65. package/dist/tracing/RunTracker.d.ts +23 -2
  66. package/dist/tracing/RunTracker.js +120 -10
  67. package/dist/tracing/RunTracker.js.map +1 -1
  68. package/dist/tracing/SqliteRunStore.js +19 -3
  69. package/dist/tracing/SqliteRunStore.js.map +1 -1
  70. package/dist/tracing/TraceRouter.js +245 -4
  71. package/dist/tracing/TraceRouter.js.map +1 -1
  72. package/dist/tracing/types.d.ts +82 -10
  73. package/dist/types/GlobalOptions.d.ts +9 -2
  74. package/dist/workflow/PersistenceHelper.d.ts +46 -0
  75. package/dist/workflow/PersistenceHelper.js +57 -0
  76. package/dist/workflow/PersistenceHelper.js.map +1 -0
  77. package/dist/workflow/WorkflowNormalizer.d.ts +91 -0
  78. package/dist/workflow/WorkflowNormalizer.js +304 -0
  79. package/dist/workflow/WorkflowNormalizer.js.map +1 -0
  80. package/package.json +8 -5
@@ -0,0 +1,190 @@
1
+ import { BlokError, ErrorCategory } from "@blokjs/shared";
2
+ import { status as GrpcStatus } from "@grpc/grpc-js";
3
+ /**
4
+ * Mapping table from `@grpc/grpc-js` `status` codes to canonical
5
+ * `(ErrorCategory, defaultHttpStatus)` pairs.
6
+ *
7
+ * Single source of truth for gRPC-side error classification. Adapters never
8
+ * branch on status codes inline; they call {@link toBlokError} which uses
9
+ * this table.
10
+ *
11
+ * The table is exhaustive — every gRPC status defined in
12
+ * `@grpc/grpc-js#status` has an entry. CI verifies coverage in the unit test.
13
+ */
14
+ export const GRPC_STATUS_MAP = {
15
+ [GrpcStatus.OK]: { category: ErrorCategory.INTERNAL, httpStatus: 200, codePrefix: "GRPC_OK" },
16
+ [GrpcStatus.CANCELLED]: { category: ErrorCategory.CANCELLED, httpStatus: 499, codePrefix: "GRPC_CANCELLED" },
17
+ [GrpcStatus.UNKNOWN]: { category: ErrorCategory.INTERNAL, httpStatus: 500, codePrefix: "GRPC_UNKNOWN" },
18
+ [GrpcStatus.INVALID_ARGUMENT]: {
19
+ category: ErrorCategory.VALIDATION,
20
+ httpStatus: 400,
21
+ codePrefix: "GRPC_INVALID_ARGUMENT",
22
+ },
23
+ [GrpcStatus.DEADLINE_EXCEEDED]: {
24
+ category: ErrorCategory.TIMEOUT,
25
+ httpStatus: 504,
26
+ codePrefix: "GRPC_DEADLINE_EXCEEDED",
27
+ },
28
+ [GrpcStatus.NOT_FOUND]: { category: ErrorCategory.NOT_FOUND, httpStatus: 404, codePrefix: "GRPC_NOT_FOUND" },
29
+ [GrpcStatus.ALREADY_EXISTS]: { category: ErrorCategory.CONFLICT, httpStatus: 409, codePrefix: "GRPC_ALREADY_EXISTS" },
30
+ [GrpcStatus.PERMISSION_DENIED]: {
31
+ category: ErrorCategory.PERMISSION,
32
+ httpStatus: 403,
33
+ codePrefix: "GRPC_PERMISSION_DENIED",
34
+ },
35
+ [GrpcStatus.RESOURCE_EXHAUSTED]: {
36
+ category: ErrorCategory.RATE_LIMIT,
37
+ httpStatus: 429,
38
+ codePrefix: "GRPC_RESOURCE_EXHAUSTED",
39
+ },
40
+ [GrpcStatus.FAILED_PRECONDITION]: {
41
+ category: ErrorCategory.CONFIGURATION,
42
+ httpStatus: 412,
43
+ codePrefix: "GRPC_FAILED_PRECONDITION",
44
+ },
45
+ [GrpcStatus.ABORTED]: { category: ErrorCategory.CONFLICT, httpStatus: 409, codePrefix: "GRPC_ABORTED" },
46
+ [GrpcStatus.OUT_OF_RANGE]: { category: ErrorCategory.VALIDATION, httpStatus: 400, codePrefix: "GRPC_OUT_OF_RANGE" },
47
+ [GrpcStatus.UNIMPLEMENTED]: { category: ErrorCategory.PROTOCOL, httpStatus: 501, codePrefix: "GRPC_UNIMPLEMENTED" },
48
+ [GrpcStatus.INTERNAL]: { category: ErrorCategory.INTERNAL, httpStatus: 500, codePrefix: "GRPC_INTERNAL" },
49
+ [GrpcStatus.UNAVAILABLE]: { category: ErrorCategory.DEPENDENCY, httpStatus: 503, codePrefix: "GRPC_UNAVAILABLE" },
50
+ [GrpcStatus.DATA_LOSS]: { category: ErrorCategory.PROTOCOL, httpStatus: 500, codePrefix: "GRPC_DATA_LOSS" },
51
+ [GrpcStatus.UNAUTHENTICATED]: {
52
+ category: ErrorCategory.PERMISSION,
53
+ httpStatus: 401,
54
+ codePrefix: "GRPC_UNAUTHENTICATED",
55
+ },
56
+ };
57
+ /**
58
+ * Convert a gRPC `ServiceError` (or any thrown value from a gRPC call) into a
59
+ * canonical {@link BlokError}. Lossless when the SDK populates structured
60
+ * details: gRPC `status.details` carrying a serialized {@link NodeErrorPayload}
61
+ * is decoded and merged into the resulting error.
62
+ *
63
+ * Behavior:
64
+ * - `ServiceError` with a known `code`: looked up in {@link GRPC_STATUS_MAP}.
65
+ * If `metadata` carries `blok-error-bin` (a serialized NodeErrorPayload),
66
+ * the payload is reconstructed via {@link BlokError.fromJSON} and the
67
+ * gRPC status only refines the http_status / category fallback.
68
+ * - Plain `Error`: wrapped via {@link BlokError.fromUnknown}.
69
+ * - Anything else: stringified and wrapped as `INTERNAL`.
70
+ */
71
+ export function toBlokError(err, ctx) {
72
+ if (err instanceof BlokError)
73
+ return err;
74
+ if (isServiceError(err)) {
75
+ const payload = tryReadStructuredPayload(err);
76
+ if (payload) {
77
+ // SDK supplied a structured NodeError — round-trip it back into a
78
+ // BlokError so all 19 fields survive intact.
79
+ const rehydrated = BlokError.fromJSON(payload);
80
+ // The gRPC status is the more authoritative source for code/category
81
+ // fallback only when the payload didn't supply them. Since fromJSON
82
+ // already restored category, we leave it alone.
83
+ return rehydrated;
84
+ }
85
+ const mapping = GRPC_STATUS_MAP[err.code] ?? GRPC_STATUS_MAP[GrpcStatus.UNKNOWN];
86
+ return BlokError[categoryFactoryName(mapping.category)]({
87
+ code: mapping.codePrefix,
88
+ message: err.details || err.message || `gRPC ${GrpcStatus[err.code]} from ${ctx.runtimeKind}`,
89
+ httpStatus: mapping.httpStatus,
90
+ node: ctx.node,
91
+ sdk: ctx.sdk,
92
+ sdkVersion: ctx.sdkVersion,
93
+ runtimeKind: ctx.runtimeKind,
94
+ details: { grpcStatus: GrpcStatus[err.code], grpcMessage: err.message },
95
+ });
96
+ }
97
+ return BlokError.fromUnknown(err, ctx);
98
+ }
99
+ /**
100
+ * Type guard for {@link ServiceError}. Duck-typed because `instanceof` checks
101
+ * across `@grpc/grpc-js` module boundaries are unreliable when the dep is
102
+ * deduped at multiple versions.
103
+ */
104
+ export function isServiceError(err) {
105
+ return (!!err &&
106
+ typeof err === "object" &&
107
+ "code" in err &&
108
+ typeof err.code === "number" &&
109
+ "details" in err);
110
+ }
111
+ /**
112
+ * Read the `blok-error-bin` metadata key (if set) and parse it as a
113
+ * {@link NodeErrorPayload}. Returns `null` if the key is absent or invalid.
114
+ *
115
+ * This is how SDKs propagate the full structured error: they put the
116
+ * serialized payload into `Metadata.set("blok-error-bin", buffer)` and the
117
+ * runner reads it here.
118
+ */
119
+ function tryReadStructuredPayload(err) {
120
+ const metadata = err.metadata;
121
+ if (!metadata)
122
+ return null;
123
+ const values = metadata.get("blok-error-bin");
124
+ if (!values || values.length === 0)
125
+ return null;
126
+ const first = values[0];
127
+ if (!(first instanceof Buffer))
128
+ return null;
129
+ try {
130
+ const parsed = JSON.parse(first.toString("utf-8"));
131
+ if (parsed && typeof parsed === "object" && "code" in parsed && "category" in parsed) {
132
+ return parsed;
133
+ }
134
+ }
135
+ catch {
136
+ /* malformed metadata — fall through to status-based mapping */
137
+ }
138
+ return null;
139
+ }
140
+ /**
141
+ * Translate a `BlokError` produced by the runner side back into a gRPC status
142
+ * code, for cases where the runner needs to report failure to a downstream
143
+ * caller using gRPC. Inverse of {@link GRPC_STATUS_MAP}.
144
+ *
145
+ * Currently used only by tests; will be used by Phase 5 streaming when the
146
+ * runner re-emits node errors as `ExecuteStream` `final` frames.
147
+ */
148
+ export function categoryToGrpcStatus(category) {
149
+ for (const [statusKey, mapping] of Object.entries(GRPC_STATUS_MAP)) {
150
+ if (mapping.category === category) {
151
+ return Number(statusKey);
152
+ }
153
+ }
154
+ return GrpcStatus.INTERNAL;
155
+ }
156
+ /**
157
+ * Map an {@link ErrorCategory} to its corresponding {@link BlokError} factory
158
+ * method name. Single-source-of-truth for category → factory dispatch.
159
+ */
160
+ function categoryFactoryName(category) {
161
+ switch (category) {
162
+ case ErrorCategory.VALIDATION:
163
+ return "validation";
164
+ case ErrorCategory.CONFIGURATION:
165
+ return "configuration";
166
+ case ErrorCategory.DEPENDENCY:
167
+ return "dependency";
168
+ case ErrorCategory.TIMEOUT:
169
+ return "timeout";
170
+ case ErrorCategory.PERMISSION:
171
+ return "permission";
172
+ case ErrorCategory.RATE_LIMIT:
173
+ return "rateLimit";
174
+ case ErrorCategory.NOT_FOUND:
175
+ return "notFound";
176
+ case ErrorCategory.CONFLICT:
177
+ return "conflict";
178
+ case ErrorCategory.CANCELLED:
179
+ return "cancelled";
180
+ case ErrorCategory.INTERNAL:
181
+ return "internal";
182
+ case ErrorCategory.PROTOCOL:
183
+ return "protocol";
184
+ case ErrorCategory.DATA:
185
+ return "data";
186
+ default:
187
+ return "internal";
188
+ }
189
+ }
190
+ //# sourceMappingURL=GrpcErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrpcErrors.js","sourceRoot":"","sources":["../../../src/adapters/grpc/GrpcErrors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Background health probe + circuit breaker for a single gRPC adapter.
3
+ *
4
+ * Per the master plan §9: each {@link GrpcRuntimeAdapter} polls
5
+ * `Health/Check` on a fixed interval; after `failureThreshold` consecutive
6
+ * failures, the circuit opens and `execute()` / `executeStream()` fail fast
7
+ * with a typed `BlokError(category=DEPENDENCY)` instead of dialing the SDK.
8
+ * The first successful probe closes the circuit again.
9
+ *
10
+ * Design notes
11
+ * - Pure logic + a `setInterval` timer — no transport coupling. The
12
+ * `probe` callback abstracts over `adapter.checkHealth()` so this class
13
+ * can be unit-tested without spawning a real gRPC server.
14
+ * - State transitions go through `setAvailable()` so the (optional)
15
+ * `onStateChange` hook fires exactly once per transition. Useful for
16
+ * structured logging + future observability counters.
17
+ * - Stops cleanly via {@link stop} which is idempotent and unrefs the
18
+ * timer so it never blocks process shutdown in tests.
19
+ *
20
+ * Single Responsibility: track availability based on Health probes; expose
21
+ * `isAvailable()` for callers to gate work. Nothing else.
22
+ */
23
+ export interface HealthCheckerOptions {
24
+ /** Polling interval in ms. Must be > 0. */
25
+ readonly intervalMs: number;
26
+ /** Consecutive failures that open the circuit. Must be ≥ 1. */
27
+ readonly failureThreshold: number;
28
+ /** Optional hook fired exactly once per `available` state transition. */
29
+ readonly onStateChange?: (available: boolean) => void;
30
+ }
31
+ /**
32
+ * The probe function the checker calls on each tick. Returns `true` when
33
+ * the adapter is reachable + reports SERVING; `false` for any other
34
+ * outcome (network error, NOT_SERVING, deadline). The checker NEVER
35
+ * receives an exception — adapters MUST resolve `false` on failure so
36
+ * the polling loop is stable.
37
+ */
38
+ export type HealthProbe = () => Promise<boolean>;
39
+ export declare class GrpcHealthChecker {
40
+ private readonly probe;
41
+ private readonly options;
42
+ private timer;
43
+ private inflight;
44
+ private failures;
45
+ private available;
46
+ private started;
47
+ constructor(probe: HealthProbe, options: HealthCheckerOptions);
48
+ /**
49
+ * Begin polling. Idempotent — calling `start()` twice is a no-op. Does
50
+ * NOT run an immediate probe; the first tick fires after `intervalMs`.
51
+ */
52
+ start(): void;
53
+ /** Stop polling. Idempotent. Resets internal state for a future restart. */
54
+ stop(): void;
55
+ /**
56
+ * True when the circuit is closed (calls allowed). False after enough
57
+ * consecutive failures to trip the breaker.
58
+ */
59
+ isAvailable(): boolean;
60
+ /** Current consecutive-failure count. Useful for diagnostics + tests. */
61
+ getFailureCount(): number;
62
+ /**
63
+ * Run a single probe round. Exposed so tests can drive the state
64
+ * machine deterministically without waiting for the interval timer.
65
+ * Concurrent ticks are coalesced (a slow probe doesn't queue more).
66
+ */
67
+ tick(): Promise<void>;
68
+ private setAvailable;
69
+ }
@@ -0,0 +1,96 @@
1
+ export class GrpcHealthChecker {
2
+ probe;
3
+ options;
4
+ timer = null;
5
+ inflight = false;
6
+ failures = 0;
7
+ available = true;
8
+ started = false;
9
+ constructor(probe, options) {
10
+ this.probe = probe;
11
+ this.options = options;
12
+ if (options.intervalMs <= 0) {
13
+ throw new Error("GrpcHealthChecker: intervalMs must be > 0 (use start/stop to toggle polling)");
14
+ }
15
+ if (options.failureThreshold < 1) {
16
+ throw new Error("GrpcHealthChecker: failureThreshold must be ≥ 1");
17
+ }
18
+ }
19
+ /**
20
+ * Begin polling. Idempotent — calling `start()` twice is a no-op. Does
21
+ * NOT run an immediate probe; the first tick fires after `intervalMs`.
22
+ */
23
+ start() {
24
+ if (this.started)
25
+ return;
26
+ this.started = true;
27
+ this.timer = setInterval(() => {
28
+ void this.tick();
29
+ }, this.options.intervalMs);
30
+ // Don't keep the event loop alive just for this timer — important
31
+ // for tests that don't explicitly call `stop()`.
32
+ if (typeof this.timer.unref === "function")
33
+ this.timer.unref();
34
+ }
35
+ /** Stop polling. Idempotent. Resets internal state for a future restart. */
36
+ stop() {
37
+ if (this.timer !== null) {
38
+ clearInterval(this.timer);
39
+ this.timer = null;
40
+ }
41
+ this.started = false;
42
+ }
43
+ /**
44
+ * True when the circuit is closed (calls allowed). False after enough
45
+ * consecutive failures to trip the breaker.
46
+ */
47
+ isAvailable() {
48
+ return this.available;
49
+ }
50
+ /** Current consecutive-failure count. Useful for diagnostics + tests. */
51
+ getFailureCount() {
52
+ return this.failures;
53
+ }
54
+ /**
55
+ * Run a single probe round. Exposed so tests can drive the state
56
+ * machine deterministically without waiting for the interval timer.
57
+ * Concurrent ticks are coalesced (a slow probe doesn't queue more).
58
+ */
59
+ async tick() {
60
+ if (this.inflight)
61
+ return;
62
+ this.inflight = true;
63
+ try {
64
+ const healthy = await this.probe();
65
+ if (healthy) {
66
+ this.failures = 0;
67
+ this.setAvailable(true);
68
+ }
69
+ else {
70
+ this.failures += 1;
71
+ if (this.failures >= this.options.failureThreshold) {
72
+ this.setAvailable(false);
73
+ }
74
+ }
75
+ }
76
+ catch {
77
+ // Defensive: probe failures must already resolve `false`. If a
78
+ // probe throws anyway, treat it as a failure and never let the
79
+ // exception escape (would crash the timer callback).
80
+ this.failures += 1;
81
+ if (this.failures >= this.options.failureThreshold) {
82
+ this.setAvailable(false);
83
+ }
84
+ }
85
+ finally {
86
+ this.inflight = false;
87
+ }
88
+ }
89
+ setAvailable(next) {
90
+ if (next === this.available)
91
+ return;
92
+ this.available = next;
93
+ this.options.onStateChange?.(next);
94
+ }
95
+ }
96
+ //# sourceMappingURL=GrpcHealthChecker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrpcHealthChecker.js","sourceRoot":"","sources":["../../../src/adapters/grpc/GrpcHealthChecker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ import { type Context } from "@blokjs/shared";
2
+ import type RunnerNode from "../../RunnerNode";
3
+ import type { ExecutionResult, RuntimeAdapter, RuntimeKind } from "../RuntimeAdapter";
4
+ import { GrpcClientPool } from "./GrpcClientPool";
5
+ import { type DecodedExecuteEvent } from "./GrpcCodec";
6
+ import { type GrpcAdapterConfig } from "./types";
7
+ /**
8
+ * Runtime adapter that executes a node by calling
9
+ * `blok.runtime.v1.NodeRuntime/Execute` over gRPC.
10
+ *
11
+ * Sibling to {@link HttpRuntimeAdapter}. Implements the same
12
+ * {@link RuntimeAdapter} interface, so {@link Configuration} can swap between
13
+ * transports based on env without any caller change.
14
+ *
15
+ * Single Responsibility: orchestrate the unary `Execute` RPC.
16
+ *
17
+ * Encoding/decoding is delegated to {@link GrpcCodec}; error mapping to
18
+ * {@link GrpcErrors}; client lifetime to {@link GrpcClientPool}; channel
19
+ * options to {@link GrpcChannelOptions}.
20
+ *
21
+ * @example
22
+ * const adapter = new GrpcRuntimeAdapter({
23
+ * kind: "python3",
24
+ * host: "localhost",
25
+ * port: 10007,
26
+ * defaultDeadlineMs: 30_000,
27
+ * maxMessageBytes: 16 * 1024 * 1024,
28
+ * keepalive: { timeMs: 10000, timeoutMs: 5000, permitWithoutCalls: true },
29
+ * });
30
+ * const result = await adapter.execute(node, ctx);
31
+ */
32
+ export declare class GrpcRuntimeAdapter implements RuntimeAdapter {
33
+ readonly kind: RuntimeKind;
34
+ readonly transport: "grpc";
35
+ private readonly config;
36
+ private readonly pool;
37
+ private readonly ownsPool;
38
+ private readonly healthChecker;
39
+ private readonly tracer;
40
+ constructor(config: GrpcAdapterConfig, pool?: GrpcClientPool);
41
+ /**
42
+ * Lazily start the background health-check loop. Operators can disable
43
+ * the loop with `healthCheckIntervalMs: 0`. Tests typically construct
44
+ * adapters without starting the loop and use {@link checkHealth}
45
+ * directly.
46
+ */
47
+ startHealthCheck(): void;
48
+ private buildHealthChecker;
49
+ /** Build a typed `BlokError(category=DEPENDENCY)` for short-circuited calls. */
50
+ private circuitOpenError;
51
+ execute(node: RunnerNode, ctx: Context): Promise<ExecutionResult>;
52
+ /**
53
+ * Open a server-streaming `ExecuteStream` call and return both an
54
+ * AsyncIterable of decoded events AND a promise that resolves to the
55
+ * final {@link ExecutionResult} once the stream completes.
56
+ *
57
+ * Phase 5 capability: SDKs may emit `NodeStarted`, `LogLine`, `Progress`,
58
+ * and `PartialResult` events while a node executes; the stream always
59
+ * terminates with a single `final` event carrying the same
60
+ * {@link ExecuteResponseProto} that unary `Execute` would return.
61
+ *
62
+ * SDKs that don't implement streaming respond with gRPC `UNIMPLEMENTED`;
63
+ * the returned promise rejects with a {@link BlokError} of category
64
+ * `INTERNAL` (mapped from `UNIMPLEMENTED`) and the iterable yields nothing.
65
+ *
66
+ * Callers should consume the iterable in parallel with awaiting the
67
+ * promise — events are pushed live, while the promise gives the typed
68
+ * result for the rest of the runner pipeline.
69
+ *
70
+ * @example
71
+ * const { events, result } = adapter.executeStream(node, ctx);
72
+ * for await (const ev of events) {
73
+ * if (ev.type === "log") tracker.appendLog(ev.log);
74
+ * }
75
+ * const final = await result;
76
+ */
77
+ executeStream(node: RunnerNode, ctx: Context): {
78
+ events: AsyncIterable<DecodedExecuteEvent>;
79
+ result: Promise<ExecutionResult>;
80
+ };
81
+ private openExecuteStream;
82
+ /**
83
+ * Probe the SDK with `Health/Check`. Used by the health-check loop in
84
+ * `TriggerBase` and by the circuit breaker. Returns false on any failure
85
+ * (network, deadline, NOT_SERVING) — never throws.
86
+ */
87
+ checkHealth(): Promise<boolean>;
88
+ /**
89
+ * Close the underlying client pool and stop the health-check loop.
90
+ * Pool close is only effective when the pool is owned by this adapter.
91
+ */
92
+ close(): void;
93
+ private unaryExecute;
94
+ private unaryHealth;
95
+ private toExecutionResult;
96
+ private decodedErrorToBlokError;
97
+ private errorContext;
98
+ }