rowbound 1.0.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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +258 -0
  3. package/dist/adapters/adapter.d.ts +1 -0
  4. package/dist/adapters/adapter.js +1 -0
  5. package/dist/adapters/sheets/sheets-adapter.d.ts +66 -0
  6. package/dist/adapters/sheets/sheets-adapter.js +531 -0
  7. package/dist/cli/config.d.ts +2 -0
  8. package/dist/cli/config.js +397 -0
  9. package/dist/cli/env.d.ts +3 -0
  10. package/dist/cli/env.js +103 -0
  11. package/dist/cli/format.d.ts +5 -0
  12. package/dist/cli/format.js +6 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +39 -0
  15. package/dist/cli/init.d.ts +10 -0
  16. package/dist/cli/init.js +72 -0
  17. package/dist/cli/run.d.ts +2 -0
  18. package/dist/cli/run.js +212 -0
  19. package/dist/cli/runs.d.ts +2 -0
  20. package/dist/cli/runs.js +108 -0
  21. package/dist/cli/status.d.ts +2 -0
  22. package/dist/cli/status.js +108 -0
  23. package/dist/cli/sync.d.ts +2 -0
  24. package/dist/cli/sync.js +84 -0
  25. package/dist/cli/watch.d.ts +2 -0
  26. package/dist/cli/watch.js +348 -0
  27. package/dist/core/condition.d.ts +25 -0
  28. package/dist/core/condition.js +66 -0
  29. package/dist/core/defaults.d.ts +3 -0
  30. package/dist/core/defaults.js +7 -0
  31. package/dist/core/engine.d.ts +50 -0
  32. package/dist/core/engine.js +234 -0
  33. package/dist/core/env.d.ts +13 -0
  34. package/dist/core/env.js +72 -0
  35. package/dist/core/exec.d.ts +24 -0
  36. package/dist/core/exec.js +134 -0
  37. package/dist/core/extractor.d.ts +10 -0
  38. package/dist/core/extractor.js +33 -0
  39. package/dist/core/http-client.d.ts +32 -0
  40. package/dist/core/http-client.js +161 -0
  41. package/dist/core/rate-limiter.d.ts +25 -0
  42. package/dist/core/rate-limiter.js +64 -0
  43. package/dist/core/reconcile.d.ts +24 -0
  44. package/dist/core/reconcile.js +192 -0
  45. package/dist/core/run-format.d.ts +39 -0
  46. package/dist/core/run-format.js +201 -0
  47. package/dist/core/run-state.d.ts +64 -0
  48. package/dist/core/run-state.js +141 -0
  49. package/dist/core/run-tracker.d.ts +15 -0
  50. package/dist/core/run-tracker.js +57 -0
  51. package/dist/core/safe-compare.d.ts +8 -0
  52. package/dist/core/safe-compare.js +19 -0
  53. package/dist/core/shell-escape.d.ts +7 -0
  54. package/dist/core/shell-escape.js +9 -0
  55. package/dist/core/tab-resolver.d.ts +17 -0
  56. package/dist/core/tab-resolver.js +44 -0
  57. package/dist/core/template.d.ts +32 -0
  58. package/dist/core/template.js +82 -0
  59. package/dist/core/types.d.ts +105 -0
  60. package/dist/core/types.js +2 -0
  61. package/dist/core/url-guard.d.ts +21 -0
  62. package/dist/core/url-guard.js +184 -0
  63. package/dist/core/validator.d.ts +11 -0
  64. package/dist/core/validator.js +261 -0
  65. package/dist/core/waterfall.d.ts +26 -0
  66. package/dist/core/waterfall.js +55 -0
  67. package/dist/index.d.ts +15 -0
  68. package/dist/index.js +16 -0
  69. package/dist/mcp/server.d.ts +1 -0
  70. package/dist/mcp/server.js +943 -0
  71. package/package.json +67 -0
@@ -0,0 +1,234 @@
1
+ import vm from "node:vm";
2
+ import { evaluateCondition, preCheckExpression } from "./condition.js";
3
+ import { executeExecAction } from "./exec.js";
4
+ import { extractValue } from "./extractor.js";
5
+ import { httpRequest } from "./http-client.js";
6
+ import { RateLimiter } from "./rate-limiter.js";
7
+ import { resolveObject, resolveTemplate, } from "./template.js";
8
+ import { executeWaterfall } from "./waterfall.js";
9
+ /**
10
+ * Evaluate a JavaScript expression in a sandboxed context, returning the result as a string.
11
+ *
12
+ * WARNING: Node.js vm module is NOT a security boundary. The pre-check
13
+ * and Object.create(null) sandbox are defense-in-depth measures only.
14
+ * Do not rely on this for untrusted code execution.
15
+ *
16
+ * Unlike evaluateCondition (which coerces to boolean), this returns the raw value
17
+ * stringified — used for transform action expressions.
18
+ */
19
+ export function evaluateExpression(expression, context) {
20
+ preCheckExpression(expression);
21
+ const rawSandbox = Object.create(null);
22
+ rawSandbox.row = { ...context.row };
23
+ rawSandbox.env = context.env;
24
+ rawSandbox.results = context.results ?? {};
25
+ const sandbox = vm.createContext(rawSandbox);
26
+ const result = vm.runInContext(expression, sandbox, { timeout: 100 });
27
+ if (result === undefined || result === null) {
28
+ return "";
29
+ }
30
+ if (typeof result === "object") {
31
+ return JSON.stringify(result);
32
+ }
33
+ return String(result);
34
+ }
35
+ /**
36
+ * Parse a range string like "2:50" into start/end indices (0-based data row indices).
37
+ * Range uses sheet row numbers (1-indexed, row 1 = headers, row 2 = first data row).
38
+ * So range "2:50" means data rows 0..48.
39
+ */
40
+ function parseRange(range, totalRows) {
41
+ if (!range) {
42
+ return { start: 0, end: totalRows };
43
+ }
44
+ const parts = range.split(":");
45
+ if (parts.length !== 2) {
46
+ throw new Error(`Invalid range "${range}": expected format "start:end" (e.g. "2:50")`);
47
+ }
48
+ const sheetStart = parseInt(parts[0], 10);
49
+ const sheetEnd = parseInt(parts[1], 10);
50
+ if (Number.isNaN(sheetStart) || Number.isNaN(sheetEnd)) {
51
+ throw new Error(`Invalid range "${range}": start and end must be numbers`);
52
+ }
53
+ if (sheetStart < 1) {
54
+ throw new Error(`Invalid range "${range}": start must be >= 1 (got ${sheetStart})`);
55
+ }
56
+ if (sheetStart > sheetEnd) {
57
+ throw new Error(`Invalid range "${range}": start (${sheetStart}) must be <= end (${sheetEnd})`);
58
+ }
59
+ // Sheet row 2 = data row 0, sheet row 3 = data row 1, etc.
60
+ const start = Math.max(0, sheetStart - 2);
61
+ const end = Math.min(totalRows, sheetEnd - 1);
62
+ return { start, end };
63
+ }
64
+ /**
65
+ * Execute an HTTP action: resolve templates, make request, extract value.
66
+ */
67
+ async function executeHttpAction(action, context, rateLimiter, retryAttempts, signal, retryBackoff, onMissing) {
68
+ const resolvedUrl = resolveTemplate(action.url, context, onMissing);
69
+ const resolvedHeaders = action.headers
70
+ ? resolveObject(action.headers, context, onMissing)
71
+ : undefined;
72
+ const resolvedBody = action.body !== undefined
73
+ ? resolveObject(action.body, context, onMissing)
74
+ : undefined;
75
+ const response = await httpRequest({
76
+ method: action.method,
77
+ url: resolvedUrl,
78
+ headers: resolvedHeaders,
79
+ body: resolvedBody,
80
+ retryAttempts,
81
+ retryBackoff,
82
+ onError: action.onError,
83
+ rateLimiter,
84
+ signal,
85
+ });
86
+ if (response === null) {
87
+ return null;
88
+ }
89
+ const value = extractValue(response.data, action.extract);
90
+ return value !== "" ? value : null;
91
+ }
92
+ /**
93
+ * Run the full pipeline: read rows, process each through actions, write results back.
94
+ *
95
+ * Execution flow per row:
96
+ * 1. Read row data (header -> value map)
97
+ * 2. For each action: evaluate condition, execute, update in-memory row state
98
+ * 3. Batch-write all cell updates for the row
99
+ * 4. Fire progress callbacks
100
+ */
101
+ export async function runPipeline(options) {
102
+ const { adapter, ref, config, env, range, actionFilter, dryRun = false, signal, } = options;
103
+ // Read all rows from the sheet
104
+ const rows = await adapter.readRows(ref);
105
+ // Create rate limiter if configured
106
+ const rateLimiter = config.settings.rateLimit > 0
107
+ ? new RateLimiter(config.settings.rateLimit)
108
+ : undefined;
109
+ const retryAttempts = config.settings.retryAttempts ?? 0;
110
+ const retryBackoff = config.settings.retryBackoff;
111
+ // Deduplicate missing-variable warnings (warn once per unique source.key)
112
+ const warnedMissing = new Set();
113
+ const onMissing = (source, key) => {
114
+ const tag = `${source}.${key}`;
115
+ if (!warnedMissing.has(tag)) {
116
+ warnedMissing.add(tag);
117
+ console.warn(`Warning: template variable {{${tag}}} resolved to empty string (not found in context)`);
118
+ }
119
+ };
120
+ // Determine which actions to run
121
+ const actions = actionFilter
122
+ ? config.actions.filter((s) => s.id === actionFilter)
123
+ : config.actions;
124
+ // Parse range
125
+ const { start, end } = parseRange(range, rows.length);
126
+ // Notify caller of total rows to process (for progress display)
127
+ options.onTotalRows?.(end - start);
128
+ const result = {
129
+ totalRows: rows.length,
130
+ processedRows: 0,
131
+ skippedRows: 0,
132
+ errors: [],
133
+ updates: 0,
134
+ };
135
+ // Warn if concurrency > 1 since it's not yet implemented
136
+ if (config.settings.concurrency > 1) {
137
+ console.warn(`Warning: concurrency is set to ${config.settings.concurrency} but parallel row processing is not yet implemented. All rows will be processed sequentially (concurrency=1).`);
138
+ }
139
+ for (let i = start; i < end; i++) {
140
+ // Check abort signal between rows
141
+ if (signal?.aborted) {
142
+ break;
143
+ }
144
+ // Build ID-keyed row from name-keyed sheet data
145
+ const nameKeyedRow = rows[i];
146
+ const row = {};
147
+ if (options.columnMap) {
148
+ for (const [id, name] of Object.entries(options.columnMap)) {
149
+ if (nameKeyedRow[name] !== undefined) {
150
+ row[id] = nameKeyedRow[name];
151
+ }
152
+ }
153
+ }
154
+ else {
155
+ // No column map — use name-keyed row directly (legacy/testing)
156
+ Object.assign(row, nameKeyedRow);
157
+ }
158
+ const rowUpdates = [];
159
+ options.onRowStart?.(i, row);
160
+ const context = { row, env };
161
+ for (const action of actions) {
162
+ // Check abort between actions (not just between rows)
163
+ if (signal?.aborted)
164
+ break;
165
+ try {
166
+ // Skip if target cell already has a value
167
+ if (row[action.target] !== undefined && row[action.target] !== "") {
168
+ options.onActionComplete?.(i, action.id, null);
169
+ continue;
170
+ }
171
+ // Evaluate `when` condition
172
+ if (!evaluateCondition(action.when, context)) {
173
+ options.onActionComplete?.(i, action.id, null);
174
+ continue;
175
+ }
176
+ let value = null;
177
+ if (action.type === "transform") {
178
+ value = evaluateExpression(action.expression, context);
179
+ }
180
+ else if (action.type === "http") {
181
+ value = await executeHttpAction(action, context, rateLimiter, retryAttempts, signal, retryBackoff, onMissing);
182
+ }
183
+ else if (action.type === "waterfall") {
184
+ const waterfallResult = await executeWaterfall(action, context, {
185
+ rateLimiter,
186
+ retryAttempts,
187
+ retryBackoff,
188
+ signal,
189
+ onMissing,
190
+ });
191
+ value = waterfallResult?.value ?? null;
192
+ }
193
+ else if (action.type === "exec") {
194
+ value = await executeExecAction(action, context, {
195
+ signal,
196
+ });
197
+ }
198
+ if (value !== null) {
199
+ // Update in-memory row so subsequent actions see new values (ID-keyed)
200
+ row[action.target] = value;
201
+ // Resolve target ID to column name for sheet write
202
+ const columnName = options.columnMap?.[action.target] ?? action.target;
203
+ // Sheet row = data index + 2 (row 1 is headers)
204
+ rowUpdates.push({
205
+ row: i + 2,
206
+ column: columnName,
207
+ value,
208
+ });
209
+ }
210
+ options.onActionComplete?.(i, action.id, value);
211
+ }
212
+ catch (error) {
213
+ const err = error instanceof Error ? error : new Error(String(error));
214
+ result.errors.push({
215
+ rowIndex: i,
216
+ actionId: action.id,
217
+ error: err.message,
218
+ });
219
+ options.onError?.(i, action.id, err);
220
+ options.onActionComplete?.(i, action.id, null);
221
+ }
222
+ }
223
+ // Write batch for this row
224
+ if (rowUpdates.length > 0 && !dryRun) {
225
+ await adapter.writeBatch(ref, rowUpdates);
226
+ }
227
+ result.updates += rowUpdates.length;
228
+ result.processedRows++;
229
+ options.onRowComplete?.(i, rowUpdates);
230
+ }
231
+ // Count skipped rows (rows outside the range)
232
+ result.skippedRows = rows.length - result.processedRows;
233
+ return result;
234
+ }
@@ -0,0 +1,13 @@
1
+ import type { PipelineConfig } from "./types.js";
2
+ /**
3
+ * Build a filtered environment object that only includes safe variables.
4
+ *
5
+ * Instead of leaking all of process.env into the pipeline context, this
6
+ * function constructs a minimal env by:
7
+ * 1. Including all ROWBOUND_* prefixed vars
8
+ * 2. Scanning config template strings for {{env.X}} references and
9
+ * including those specific keys from process.env
10
+ * 3. Including NODE_ENV if set
11
+ * 4. Including PATH so child processes can find executables
12
+ */
13
+ export declare function buildSafeEnv(config?: PipelineConfig): Record<string, string>;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Build a filtered environment object that only includes safe variables.
3
+ *
4
+ * Instead of leaking all of process.env into the pipeline context, this
5
+ * function constructs a minimal env by:
6
+ * 1. Including all ROWBOUND_* prefixed vars
7
+ * 2. Scanning config template strings for {{env.X}} references and
8
+ * including those specific keys from process.env
9
+ * 3. Including NODE_ENV if set
10
+ * 4. Including PATH so child processes can find executables
11
+ */
12
+ export function buildSafeEnv(config) {
13
+ const env = {};
14
+ // 1. ROWBOUND_* prefixed vars
15
+ for (const [key, value] of Object.entries(process.env)) {
16
+ if (value !== undefined && key.startsWith("ROWBOUND_")) {
17
+ env[key] = value;
18
+ }
19
+ }
20
+ // 2. NODE_ENV
21
+ if (process.env.NODE_ENV !== undefined) {
22
+ env.NODE_ENV = process.env.NODE_ENV;
23
+ }
24
+ // 3. PATH so child processes can find executables
25
+ if (process.env.PATH !== undefined) {
26
+ env.PATH = process.env.PATH;
27
+ }
28
+ // 4. Scan config templates for {{env.X}} references
29
+ if (config) {
30
+ const referencedKeys = extractEnvReferences(config);
31
+ for (const key of referencedKeys) {
32
+ if (process.env[key] !== undefined) {
33
+ env[key] = process.env[key];
34
+ }
35
+ }
36
+ }
37
+ return env;
38
+ }
39
+ /**
40
+ * Scan all template strings in a PipelineConfig for {{env.X}} patterns
41
+ * and return the set of referenced env var names.
42
+ */
43
+ function extractEnvReferences(config) {
44
+ const keys = new Set();
45
+ const ENV_REGEX = /\{\{env\.([^}]+)\}\}/g;
46
+ function scanValue(value) {
47
+ if (typeof value === "string") {
48
+ for (const match of value.matchAll(ENV_REGEX)) {
49
+ keys.add(match[1]);
50
+ }
51
+ }
52
+ else if (Array.isArray(value)) {
53
+ for (const item of value) {
54
+ scanValue(item);
55
+ }
56
+ }
57
+ else if (value !== null && typeof value === "object") {
58
+ for (const v of Object.values(value)) {
59
+ scanValue(v);
60
+ }
61
+ }
62
+ }
63
+ // Scan top-level actions
64
+ scanValue(config.actions);
65
+ // Scan per-tab actions
66
+ if (config.tabs) {
67
+ for (const tab of Object.values(config.tabs)) {
68
+ scanValue(tab.actions);
69
+ }
70
+ }
71
+ return keys;
72
+ }
@@ -0,0 +1,24 @@
1
+ import type { ExecAction, ExecutionContext } from "./types.js";
2
+ export interface ExecResult {
3
+ stdout: string;
4
+ stderr: string;
5
+ exitCode: number;
6
+ }
7
+ /**
8
+ * Execute a shell command and capture its output.
9
+ *
10
+ * Uses execFile('/bin/sh', ['-c', command]) for shell features (pipes, env vars)
11
+ * while staying consistent with the codebase's execFile pattern.
12
+ */
13
+ export declare function executeCommand(command: string, options?: {
14
+ timeout?: number;
15
+ signal?: AbortSignal;
16
+ env?: Record<string, string>;
17
+ }): Promise<ExecResult>;
18
+ /**
19
+ * Execute an exec action: resolve templates in the command, run it,
20
+ * optionally extract a value from JSON output, and handle errors.
21
+ */
22
+ export declare function executeExecAction(action: ExecAction, context: ExecutionContext, options?: {
23
+ signal?: AbortSignal;
24
+ }): Promise<string | null>;
@@ -0,0 +1,134 @@
1
+ import { execFile } from "node:child_process";
2
+ import { extractValue } from "./extractor.js";
3
+ import { shellEscape } from "./shell-escape.js";
4
+ import { resolveTemplateEscaped } from "./template.js";
5
+ /**
6
+ * Execute a shell command and capture its output.
7
+ *
8
+ * Uses execFile('/bin/sh', ['-c', command]) for shell features (pipes, env vars)
9
+ * while staying consistent with the codebase's execFile pattern.
10
+ */
11
+ export async function executeCommand(command, options = {}) {
12
+ const { timeout = 30_000, signal, env } = options;
13
+ return new Promise((resolve, reject) => {
14
+ const childEnv = env ?? {};
15
+ const child = execFile("/bin/sh", ["-c", command], {
16
+ timeout,
17
+ signal,
18
+ env: childEnv,
19
+ maxBuffer: 10 * 1024 * 1024, // 10MB
20
+ }, (error, stdout, stderr) => {
21
+ if (error) {
22
+ // Cast to access killed and code properties from ExecException
23
+ const execError = error;
24
+ // Check if it was killed by timeout or signal
25
+ if (execError.killed || error.message?.includes("TIMEOUT")) {
26
+ reject(new Error(`Command timed out after ${timeout}ms`));
27
+ return;
28
+ }
29
+ // If signal was aborted
30
+ if (signal?.aborted) {
31
+ reject(new Error("Command aborted"));
32
+ return;
33
+ }
34
+ // Non-zero exit code — resolve with the exit code and captured output
35
+ resolve({
36
+ stdout: String(stdout ?? ""),
37
+ stderr: String(stderr ?? ""),
38
+ exitCode: typeof execError.code === "number" ? execError.code : 1,
39
+ });
40
+ return;
41
+ }
42
+ resolve({
43
+ stdout: String(stdout),
44
+ stderr: String(stderr),
45
+ exitCode: 0,
46
+ });
47
+ });
48
+ // Handle abort signal — kill the entire process group so grandchildren
49
+ // (e.g. headless Claude) are also terminated, not just the /bin/sh wrapper.
50
+ if (signal) {
51
+ signal.addEventListener("abort", () => {
52
+ try {
53
+ if (child.pid) {
54
+ process.kill(-child.pid, "SIGTERM");
55
+ }
56
+ }
57
+ catch {
58
+ child.kill();
59
+ }
60
+ }, { once: true });
61
+ }
62
+ });
63
+ }
64
+ /**
65
+ * Resolve the onError action for a given exit code.
66
+ * Checks the specific exit code first, then falls back to "default".
67
+ */
68
+ function resolveErrorAction(onError, exitCode) {
69
+ if (!onError)
70
+ return undefined;
71
+ const codeKey = String(exitCode);
72
+ if (codeKey in onError) {
73
+ return onError[codeKey];
74
+ }
75
+ if ("default" in onError) {
76
+ return onError.default;
77
+ }
78
+ return undefined;
79
+ }
80
+ /**
81
+ * Apply the resolved error action, returning a fallback value or throwing.
82
+ */
83
+ function applyErrorAction(action, exitCode, stderr) {
84
+ if (action === undefined) {
85
+ throw new Error(`Command failed with exit code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
86
+ }
87
+ if (action === "skip") {
88
+ return null;
89
+ }
90
+ if (typeof action === "object" && "write" in action) {
91
+ return action.write;
92
+ }
93
+ // Unknown action — treat as skip
94
+ return null;
95
+ }
96
+ /**
97
+ * Execute an exec action: resolve templates in the command, run it,
98
+ * optionally extract a value from JSON output, and handle errors.
99
+ */
100
+ export async function executeExecAction(action, context, options = {}) {
101
+ const resolvedCommand = resolveTemplateEscaped(action.command, context, shellEscape);
102
+ let result;
103
+ try {
104
+ result = await executeCommand(resolvedCommand, {
105
+ timeout: action.timeout,
106
+ signal: options.signal,
107
+ env: context.env,
108
+ });
109
+ }
110
+ catch (error) {
111
+ // Timeout or abort errors
112
+ const errorAction = resolveErrorAction(action.onError, 1);
113
+ return applyErrorAction(errorAction, 1, error instanceof Error ? error.message : String(error));
114
+ }
115
+ // Non-zero exit code
116
+ if (result.exitCode !== 0) {
117
+ const errorAction = resolveErrorAction(action.onError, result.exitCode);
118
+ return applyErrorAction(errorAction, result.exitCode, result.stderr);
119
+ }
120
+ // Success — extract or return raw stdout
121
+ if (action.extract) {
122
+ let parsed;
123
+ try {
124
+ parsed = JSON.parse(result.stdout);
125
+ }
126
+ catch {
127
+ throw new Error(`Exec action "${action.id}": output is not valid JSON for extraction`);
128
+ }
129
+ const value = extractValue(parsed, action.extract);
130
+ return value !== "" ? value : null;
131
+ }
132
+ const trimmed = result.stdout.trim();
133
+ return trimmed !== "" ? trimmed : null;
134
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Extract a value from data using a JSONPath expression.
3
+ *
4
+ * - Applies the JSONPath expression to the input data
5
+ * - Arrays: takes the first element
6
+ * - Objects: JSON.stringify
7
+ * - Coerces the final result to string
8
+ * - Returns empty string if no match
9
+ */
10
+ export declare function extractValue(data: unknown, expression: string): string;
@@ -0,0 +1,33 @@
1
+ import { JSONPath } from "jsonpath-plus";
2
+ /**
3
+ * Extract a value from data using a JSONPath expression.
4
+ *
5
+ * - Applies the JSONPath expression to the input data
6
+ * - Arrays: takes the first element
7
+ * - Objects: JSON.stringify
8
+ * - Coerces the final result to string
9
+ * - Returns empty string if no match
10
+ */
11
+ export function extractValue(data, expression) {
12
+ let result;
13
+ try {
14
+ result = JSONPath({ path: expression, json: data, eval: false });
15
+ }
16
+ catch {
17
+ return "";
18
+ }
19
+ // JSONPath always returns an array of matches
20
+ if (Array.isArray(result)) {
21
+ if (result.length === 0) {
22
+ return "";
23
+ }
24
+ result = result[0];
25
+ }
26
+ if (result === undefined || result === null) {
27
+ return "";
28
+ }
29
+ if (typeof result === "object") {
30
+ return JSON.stringify(result);
31
+ }
32
+ return String(result);
33
+ }
@@ -0,0 +1,32 @@
1
+ import type { RateLimiter } from "./rate-limiter.js";
2
+ import type { OnErrorConfig } from "./types.js";
3
+ /** Options for httpRequest */
4
+ export interface HttpRequestOptions {
5
+ method: string;
6
+ url: string;
7
+ headers?: Record<string, string>;
8
+ body?: unknown;
9
+ retryAttempts?: number;
10
+ retryBackoff?: string;
11
+ onError?: OnErrorConfig;
12
+ rateLimiter?: RateLimiter;
13
+ signal?: AbortSignal;
14
+ }
15
+ /** Successful HTTP response */
16
+ export interface HttpResponse {
17
+ status: number;
18
+ data: unknown;
19
+ }
20
+ /** Thrown when onError config specifies "stop_provider" */
21
+ export declare class StopProviderError extends Error {
22
+ constructor(message?: string);
23
+ }
24
+ /**
25
+ * Make an HTTP request with retry, rate limiting, and structured error handling.
26
+ *
27
+ * - Acquires a rate limiter token before each request attempt
28
+ * - Retries on 429/5xx with exponential backoff
29
+ * - Applies onError config for non-retryable errors or exhausted retries
30
+ * - Respects AbortSignal for cancellation
31
+ */
32
+ export declare function httpRequest(options: HttpRequestOptions): Promise<HttpResponse | null>;