@nordsym/apiclaw 1.5.16 → 1.5.18

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 (172) hide show
  1. package/convex/http.js +196 -0
  2. package/convex/http.js.map +1 -1
  3. package/convex/http.ts +201 -0
  4. package/convex/http.ts.bak +934 -0
  5. package/dist/analytics.d.ts +0 -4
  6. package/dist/analytics.d.ts.map +1 -1
  7. package/dist/analytics.js +0 -1
  8. package/dist/analytics.js.map +1 -1
  9. package/dist/bin.js +1 -1
  10. package/dist/cli/commands/mcp-install.d.ts.map +1 -1
  11. package/dist/cli/commands/mcp-install.js +8 -87
  12. package/dist/cli/commands/mcp-install.js.map +1 -1
  13. package/dist/cli/index.js +0 -7
  14. package/dist/credentials.d.ts.map +1 -1
  15. package/dist/credentials.js +0 -128
  16. package/dist/credentials.js.map +1 -1
  17. package/dist/discovery.d.ts.map +1 -1
  18. package/dist/discovery.js +82 -191
  19. package/dist/discovery.js.map +1 -1
  20. package/dist/http-api.d.ts.map +1 -1
  21. package/dist/http-api.js +33 -17
  22. package/dist/http-api.js.map +1 -1
  23. package/dist/proxy.js +1 -1
  24. package/dist/proxy.js.map +1 -1
  25. package/landing/next-env.d.ts +0 -1
  26. package/landing/src/app/api/auth/magic-link/route.ts +1 -1
  27. package/landing/src/app/auth/verify/page.tsx +0 -6
  28. package/landing/src/app/dashboard/verify/page.tsx +0 -6
  29. package/landing/src/app/join/page.tsx +0 -6
  30. package/landing/src/app/layout.tsx +2 -2
  31. package/landing/src/app/login/page.tsx +1 -1
  32. package/landing/src/app/mou/[partnerId]/page.tsx +0 -6
  33. package/landing/src/app/page.tsx +18 -39
  34. package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +0 -6
  35. package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +0 -5
  36. package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +0 -5
  37. package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +1 -6
  38. package/landing/src/app/providers/dashboard/[apiId]/page.tsx +0 -5
  39. package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +0 -5
  40. package/landing/src/app/providers/dashboard/layout.tsx +6 -6
  41. package/landing/src/app/providers/dashboard/login/page.tsx +1 -1
  42. package/landing/src/app/providers/dashboard/page.tsx +1 -1
  43. package/landing/src/app/providers/dashboard/verify/page.tsx +0 -6
  44. package/landing/src/app/providers/layout.tsx +1 -1
  45. package/landing/src/app/upgrade/page.tsx +0 -6
  46. package/landing/src/app/workspace/page.tsx +0 -6
  47. package/landing/src/components/HeroTabs.tsx +2 -2
  48. package/landing/src/components/{Workspace.tsx → ProviderDashboard.tsx} +2 -2
  49. package/landing/src/components/VideoDemo.tsx +10 -21
  50. package/landing/src/lib/mock-data.ts +1 -1
  51. package/landing/src/lib/stats.json +1 -1
  52. package/package.json +4 -6
  53. package/src/analytics.ts +0 -5
  54. package/src/bin.ts +1 -1
  55. package/src/cli/commands/mcp-install.ts +8 -90
  56. package/src/cli/index.ts +0 -8
  57. package/src/credentials.ts +0 -136
  58. package/src/discovery.ts +82 -191
  59. package/src/http-api.ts +34 -18
  60. package/src/proxy.ts +1 -1
  61. package/APILAYER_STATUS_2026-03-24.md +0 -38
  62. package/CHANGELOG-WHITELIST-V2.md +0 -269
  63. package/HIVR-WHITELIST-STATUS.md +0 -205
  64. package/HIVR-WHITELIST.md +0 -148
  65. package/TERMINOLOGY-AUDIT.md +0 -99
  66. package/TERMINOLOGY-FIXED.md +0 -74
  67. package/VIDEO-DEMO-GUIDE.md +0 -82
  68. package/WHITELIST-ARCHITECTURE.md +0 -379
  69. package/api/discover.ts +0 -71
  70. package/api/health.ts +0 -20
  71. package/direct-test.mjs +0 -51
  72. package/dist/access-control.d.ts +0 -45
  73. package/dist/access-control.d.ts.map +0 -1
  74. package/dist/access-control.js +0 -142
  75. package/dist/access-control.js.map +0 -1
  76. package/dist/chain-types.d.ts +0 -187
  77. package/dist/chain-types.d.ts.map +0 -1
  78. package/dist/chain-types.js +0 -33
  79. package/dist/chain-types.js.map +0 -1
  80. package/dist/convex/adminActivate.js +0 -46
  81. package/dist/convex/adminStats.js +0 -41
  82. package/dist/convex/agents.js +0 -498
  83. package/dist/convex/analytics.js +0 -165
  84. package/dist/convex/billing.js +0 -654
  85. package/dist/convex/capabilities.js +0 -144
  86. package/dist/convex/chains.js +0 -1041
  87. package/dist/convex/credits.js +0 -185
  88. package/dist/convex/crons.js +0 -16
  89. package/dist/convex/directCall.js +0 -626
  90. package/dist/convex/earnProgress.js +0 -648
  91. package/dist/convex/email.js +0 -299
  92. package/dist/convex/feedback.js +0 -226
  93. package/dist/convex/http.js +0 -909
  94. package/dist/convex/logs.js +0 -486
  95. package/dist/convex/mou.js +0 -81
  96. package/dist/convex/providerKeys.js +0 -256
  97. package/dist/convex/providers.js +0 -755
  98. package/dist/convex/purchases.js +0 -156
  99. package/dist/convex/ratelimit.js +0 -90
  100. package/dist/convex/schema.js +0 -709
  101. package/dist/convex/searchLogs.js +0 -128
  102. package/dist/convex/spendAlerts.js +0 -379
  103. package/dist/convex/stripeActions.js +0 -410
  104. package/dist/convex/teams.js +0 -214
  105. package/dist/convex/telemetry.js +0 -73
  106. package/dist/convex/usage.js +0 -228
  107. package/dist/convex/waitlist.js +0 -48
  108. package/dist/convex/webhooks.js +0 -409
  109. package/dist/convex/workspaces.js +0 -879
  110. package/dist/hivr-whitelist.d.ts +0 -18
  111. package/dist/hivr-whitelist.d.ts.map +0 -1
  112. package/dist/hivr-whitelist.js +0 -95
  113. package/dist/hivr-whitelist.js.map +0 -1
  114. package/dist/http-server-minimal.d.ts +0 -7
  115. package/dist/http-server-minimal.d.ts.map +0 -1
  116. package/dist/http-server-minimal.js +0 -126
  117. package/dist/http-server-minimal.js.map +0 -1
  118. package/dist/product-whitelist.d.ts +0 -37
  119. package/dist/product-whitelist.d.ts.map +0 -1
  120. package/dist/product-whitelist.js +0 -203
  121. package/dist/product-whitelist.js.map +0 -1
  122. package/dist/src/analytics.js +0 -129
  123. package/dist/src/bin.js +0 -17
  124. package/dist/src/capability-router.js +0 -240
  125. package/dist/src/chainExecutor.js +0 -451
  126. package/dist/src/chainResolver.js +0 -518
  127. package/dist/src/cli/commands/doctor.js +0 -324
  128. package/dist/src/cli/commands/mcp-install.js +0 -255
  129. package/dist/src/cli/commands/restore.js +0 -259
  130. package/dist/src/cli/commands/setup.js +0 -205
  131. package/dist/src/cli/commands/uninstall.js +0 -188
  132. package/dist/src/cli/index.js +0 -111
  133. package/dist/src/cli.js +0 -302
  134. package/dist/src/confirmation.js +0 -240
  135. package/dist/src/credentials.js +0 -357
  136. package/dist/src/credits.js +0 -260
  137. package/dist/src/crypto.js +0 -66
  138. package/dist/src/discovery.js +0 -504
  139. package/dist/src/enterprise/env.js +0 -123
  140. package/dist/src/enterprise/script-generator.js +0 -460
  141. package/dist/src/execute-dynamic.js +0 -473
  142. package/dist/src/execute.js +0 -1727
  143. package/dist/src/index.js +0 -2062
  144. package/dist/src/metered.js +0 -80
  145. package/dist/src/open-apis.js +0 -276
  146. package/dist/src/proxy.js +0 -28
  147. package/dist/src/session.js +0 -86
  148. package/dist/src/stripe.js +0 -407
  149. package/dist/src/telemetry.js +0 -49
  150. package/dist/src/types.js +0 -2
  151. package/dist/src/utils/backup.js +0 -181
  152. package/dist/src/utils/config.js +0 -220
  153. package/dist/src/utils/os.js +0 -105
  154. package/dist/src/utils/paths.js +0 -159
  155. package/landing/pages/api/discover.ts +0 -43
  156. package/landing/pages/api/health.ts +0 -20
  157. package/scripts/test-whitelist-v2.sh +0 -128
  158. package/src/access-control.ts +0 -174
  159. package/src/hivr-whitelist.ts +0 -110
  160. package/src/http-server-minimal.ts +0 -154
  161. package/src/product-whitelist.ts +0 -246
  162. package/test-actual-handlers.ts +0 -92
  163. package/test-apilayer-all-14.ts +0 -249
  164. package/test-apilayer-fixed.ts +0 -248
  165. package/test-direct-endpoints.ts +0 -174
  166. package/test-exact-endpoints.ts +0 -144
  167. package/test-final.ts +0 -83
  168. package/test-full-routing.ts +0 -100
  169. package/test-handlers-correct.ts +0 -217
  170. package/test-numverify-key.ts +0 -41
  171. package/test-via-handlers.ts +0 -92
  172. package/test-worldnews.mjs +0 -26
@@ -1,240 +0,0 @@
1
- /**
2
- * APIClaw Capability Router
3
- * Routes capability requests to the best available provider
4
- */
5
- import { executeAPICall } from './execute.js';
6
- import { logAPICall } from './analytics.js';
7
- // Convex HTTP API for capability queries
8
- const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://adventurous-avocet-799.convex.cloud';
9
- /**
10
- * Query Convex for providers that support a capability
11
- */
12
- async function getProvidersForCapability(capabilityId, region) {
13
- try {
14
- const res = await fetch(`${CONVEX_URL}/api/query`, {
15
- method: 'POST',
16
- headers: { 'Content-Type': 'application/json' },
17
- body: JSON.stringify({
18
- path: 'capabilities:getProviders',
19
- args: { capabilityId, region },
20
- }),
21
- });
22
- if (!res.ok)
23
- return [];
24
- const data = await res.json();
25
- if (Array.isArray(data))
26
- return data;
27
- return (data.value || []);
28
- }
29
- catch (e) {
30
- console.error('Failed to fetch capability providers:', e);
31
- return [];
32
- }
33
- }
34
- /**
35
- * Map capability params to provider-specific params
36
- */
37
- function mapParams(params, mapping) {
38
- const result = {};
39
- for (const [capParam, value] of Object.entries(params)) {
40
- const providerParam = mapping[capParam] || capParam;
41
- result[providerParam] = value;
42
- }
43
- return result;
44
- }
45
- /**
46
- * Log capability usage to Convex
47
- */
48
- async function logCapabilityUsage(params) {
49
- try {
50
- await fetch(`${CONVEX_URL}/api/mutation`, {
51
- method: 'POST',
52
- headers: { 'Content-Type': 'application/json' },
53
- body: JSON.stringify({
54
- path: 'capabilities:logUsage',
55
- args: params,
56
- }),
57
- });
58
- }
59
- catch (e) {
60
- console.error('Failed to log capability usage:', e);
61
- }
62
- }
63
- /**
64
- * Execute a capability request with automatic provider selection and fallback
65
- */
66
- export async function executeCapability(capabilityId, action, params, userId, preferences = {}) {
67
- const startTime = Date.now();
68
- const enableFallback = preferences.fallback !== false; // Default true
69
- // Get providers for this capability
70
- const providers = await getProvidersForCapability(capabilityId, preferences.region);
71
- if (providers.length === 0) {
72
- return {
73
- success: false,
74
- capability: capabilityId,
75
- action,
76
- fallbackAttempted: false,
77
- error: `No providers available for capability: ${capabilityId}`,
78
- };
79
- }
80
- // Filter by max price if specified
81
- let filteredProviders = providers;
82
- if (preferences.maxPrice !== undefined) {
83
- filteredProviders = providers.filter(p => p.pricePerUnit <= preferences.maxPrice);
84
- }
85
- // Prefer specific provider if requested
86
- if (preferences.preferredProvider) {
87
- const preferred = filteredProviders.find(p => p.providerId === preferences.preferredProvider);
88
- if (preferred) {
89
- filteredProviders = [preferred, ...filteredProviders.filter(p => p.providerId !== preferences.preferredProvider)];
90
- }
91
- }
92
- if (filteredProviders.length === 0) {
93
- return {
94
- success: false,
95
- capability: capabilityId,
96
- action,
97
- fallbackAttempted: false,
98
- error: 'No providers match your preferences (region/price)',
99
- };
100
- }
101
- // Try providers in order
102
- let fallbackAttempted = false;
103
- let lastError = '';
104
- for (let i = 0; i < filteredProviders.length; i++) {
105
- const provider = filteredProviders[i];
106
- const isFirstAttempt = i === 0;
107
- if (!isFirstAttempt) {
108
- fallbackAttempted = true;
109
- }
110
- try {
111
- // Map params to provider-specific format
112
- const mappedParams = mapParams(params, provider.paramMapping || {});
113
- // Execute via existing executeAPICall
114
- const result = await executeAPICall(provider.providerId, action, mappedParams, userId);
115
- const latencyMs = Date.now() - startTime;
116
- if (result.success) {
117
- // Log successful usage
118
- logCapabilityUsage({
119
- capabilityId,
120
- providerId: provider.providerId,
121
- userId,
122
- action,
123
- success: true,
124
- fallbackUsed: fallbackAttempted,
125
- fallbackReason: fallbackAttempted ? lastError : undefined,
126
- latencyMs,
127
- cost: provider.pricePerUnit,
128
- currency: provider.currency,
129
- });
130
- // Also log to file-based analytics
131
- logAPICall({
132
- timestamp: new Date().toISOString(),
133
- provider: provider.providerId,
134
- action,
135
- type: 'direct',
136
- userId,
137
- success: true,
138
- latencyMs,
139
- });
140
- return {
141
- success: true,
142
- capability: capabilityId,
143
- action,
144
- providerUsed: provider.providerId,
145
- fallbackAttempted,
146
- fallbackReason: fallbackAttempted ? lastError : undefined,
147
- data: result.data,
148
- cost: provider.pricePerUnit,
149
- currency: provider.currency,
150
- latencyMs,
151
- };
152
- }
153
- // Provider returned error, try next
154
- lastError = result.error || 'Unknown error';
155
- if (!enableFallback) {
156
- break;
157
- }
158
- }
159
- catch (e) {
160
- lastError = e.message || 'Provider execution failed';
161
- if (!enableFallback) {
162
- break;
163
- }
164
- }
165
- }
166
- // All providers failed
167
- const latencyMs = Date.now() - startTime;
168
- logCapabilityUsage({
169
- capabilityId,
170
- providerId: filteredProviders[0].providerId,
171
- userId,
172
- action,
173
- success: false,
174
- fallbackUsed: fallbackAttempted,
175
- fallbackReason: lastError,
176
- latencyMs,
177
- cost: 0,
178
- currency: 'SEK',
179
- });
180
- return {
181
- success: false,
182
- capability: capabilityId,
183
- action,
184
- fallbackAttempted,
185
- error: `All providers failed. Last error: ${lastError}`,
186
- latencyMs,
187
- };
188
- }
189
- /**
190
- * List available capabilities
191
- */
192
- export async function listCapabilities() {
193
- try {
194
- const res = await fetch(`${CONVEX_URL}/api/query`, {
195
- method: 'POST',
196
- headers: { 'Content-Type': 'application/json' },
197
- body: JSON.stringify({
198
- path: 'capabilities:list',
199
- args: {},
200
- }),
201
- });
202
- if (!res.ok)
203
- return [];
204
- const data = await res.json();
205
- const capabilities = Array.isArray(data) ? data : (data.value || []);
206
- return capabilities.map(c => ({
207
- id: c.id,
208
- name: c.name,
209
- category: c.category,
210
- }));
211
- }
212
- catch (e) {
213
- return [];
214
- }
215
- }
216
- /**
217
- * Check if a capability exists
218
- */
219
- export async function hasCapability(capabilityId) {
220
- try {
221
- const res = await fetch(`${CONVEX_URL}/api/query`, {
222
- method: 'POST',
223
- headers: { 'Content-Type': 'application/json' },
224
- body: JSON.stringify({
225
- path: 'capabilities:getById',
226
- args: { id: capabilityId },
227
- }),
228
- });
229
- if (!res.ok)
230
- return false;
231
- const data = await res.json();
232
- if (data && typeof data === 'object' && 'value' in data) {
233
- return !!data.value;
234
- }
235
- return !!data;
236
- }
237
- catch (e) {
238
- return false;
239
- }
240
- }
@@ -1,451 +0,0 @@
1
- /**
2
- * APIClaw Chain Executor
3
- *
4
- * Orchestrates multi-step API chains with:
5
- * - Sequential execution
6
- * - Parallel batches
7
- * - Conditional branching
8
- * - ForEach loops
9
- * - Error handling and retry
10
- * - Full execution trace
11
- */
12
- import { executeAPICall } from './execute.js';
13
- import { resolveReferences, validateReferences, evaluateCondition, } from './chainResolver.js';
14
- // ============================================================================
15
- // MAIN EXECUTOR
16
- // ============================================================================
17
- /**
18
- * Execute a chain of API calls
19
- */
20
- export async function executeChain(chain, credentials, inputs, options = {}) {
21
- const chainId = chain.id || generateChainId();
22
- const startedAt = new Date().toISOString();
23
- const startTime = Date.now();
24
- // Initialize context
25
- const context = {
26
- results: {},
27
- inputs: inputs || {},
28
- currentIndex: 0,
29
- startedAt,
30
- env: process.env,
31
- };
32
- // Initialize result
33
- const result = {
34
- success: false,
35
- chainId,
36
- startedAt,
37
- completedAt: '',
38
- totalLatencyMs: 0,
39
- totalCost: { cents: 0 },
40
- results: {},
41
- completedSteps: [],
42
- trace: [],
43
- canResume: false,
44
- };
45
- try {
46
- // Validate inputs
47
- if (chain.inputs) {
48
- validateInputs(inputs || {}, chain.inputs);
49
- }
50
- // Validate references before execution
51
- const validation = validateReferences(chain.steps);
52
- if (!validation.valid) {
53
- const errorMsg = validation.errors.map(e => `${e.stepId}: ${e.message}`).join('; ');
54
- throw new Error(`Reference validation failed: ${errorMsg}`);
55
- }
56
- // Log warnings
57
- if (options.verbose && validation.warnings.length > 0) {
58
- console.log('[Chain] Warnings:', validation.warnings);
59
- }
60
- // Check limits
61
- if (chain.limits?.maxSteps && chain.steps.length > chain.limits.maxSteps) {
62
- throw new Error(`Chain exceeds maxSteps limit (${chain.steps.length} > ${chain.limits.maxSteps})`);
63
- }
64
- // Execute steps
65
- let lastStepResult;
66
- for (let i = 0; i < chain.steps.length; i++) {
67
- const step = chain.steps[i];
68
- context.currentIndex = i;
69
- // Check timeout
70
- if (chain.timeout && Date.now() - startTime > chain.timeout) {
71
- throw new TimeoutError(`Chain exceeded timeout of ${chain.timeout}ms`);
72
- }
73
- // Execute based on step type
74
- if (isParallelStep(step)) {
75
- lastStepResult = await executeParallelStep(step, context, credentials, options, result);
76
- }
77
- else if (isConditionalStep(step)) {
78
- lastStepResult = await executeConditionalStep(step, context, credentials, options, result);
79
- }
80
- else if (isForEachStep(step)) {
81
- lastStepResult = await executeForEachStep(step, context, credentials, options, result, chain.limits);
82
- }
83
- else {
84
- lastStepResult = await executeSingleStep(step, context, credentials, options, result, chain.errorPolicy);
85
- }
86
- }
87
- // Success!
88
- result.success = true;
89
- result.finalResult = lastStepResult;
90
- result.results = { ...context.results };
91
- }
92
- catch (error) {
93
- // Handle failure
94
- result.success = false;
95
- result.error = {
96
- stepId: result.failedStep?.stepId || 'unknown',
97
- code: error.code || 'CHAIN_ERROR',
98
- message: error.message,
99
- retryAfter: error.retryAfter,
100
- };
101
- result.results = { ...context.results };
102
- // Handle rollback for transactional mode
103
- if (chain.errorPolicy?.mode === 'transactional' && chain.errorPolicy.rollback) {
104
- await executeRollback(chain.errorPolicy.rollback, context, credentials, options, result);
105
- }
106
- // Can resume if we have completed steps
107
- result.canResume = result.completedSteps.length > 0;
108
- if (result.canResume) {
109
- result.resumeToken = `${chainId}_step_${result.completedSteps.length}`;
110
- }
111
- }
112
- // Finalize
113
- result.completedAt = new Date().toISOString();
114
- result.totalLatencyMs = Date.now() - startTime;
115
- return result;
116
- }
117
- // ============================================================================
118
- // STEP TYPE GUARDS
119
- // ============================================================================
120
- function isParallelStep(step) {
121
- return 'parallel' in step;
122
- }
123
- function isConditionalStep(step) {
124
- return 'if' in step;
125
- }
126
- function isForEachStep(step) {
127
- return 'forEach' in step;
128
- }
129
- // ============================================================================
130
- // SINGLE STEP EXECUTION
131
- // ============================================================================
132
- async function executeSingleStep(step, context, credentials, options, result, errorPolicy) {
133
- const stepStart = Date.now();
134
- const stepTrace = {
135
- stepId: step.id,
136
- stepIndex: context.currentIndex,
137
- provider: step.provider,
138
- action: step.action,
139
- startedAt: new Date().toISOString(),
140
- completedAt: '',
141
- latencyMs: 0,
142
- success: false,
143
- input: {},
144
- };
145
- options.onStepStart?.(step.id, context.currentIndex);
146
- try {
147
- // Resolve references in params
148
- const resolvedParams = step.params ? resolveReferences(step.params, context) : {};
149
- stepTrace.input = resolvedParams;
150
- // Execute with retry if configured
151
- const maxRetries = step.onError?.retry?.attempts || 0;
152
- let lastError;
153
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
154
- try {
155
- // Get customer key for this provider
156
- const customerKey = credentials.customerKeys?.[step.provider];
157
- // Execute the API call
158
- const apiResult = await executeAPICall(step.provider, step.action, resolvedParams, credentials.userId, customerKey);
159
- if (!apiResult.success) {
160
- throw new StepError(apiResult.error || 'API call failed', apiResult.code || 'PROVIDER_ERROR');
161
- }
162
- // Success!
163
- stepTrace.success = true;
164
- stepTrace.output = apiResult.data;
165
- stepTrace.cost = apiResult.cost ? { cents: Math.round(apiResult.cost * 100) } : undefined;
166
- // Update context
167
- context.results[step.id] = apiResult.data;
168
- context.prevStepId = step.id;
169
- // Update result
170
- result.completedSteps.push(step.id);
171
- if (stepTrace.cost) {
172
- result.totalCost.cents += stepTrace.cost.cents;
173
- }
174
- return apiResult.data;
175
- }
176
- catch (error) {
177
- lastError = error;
178
- stepTrace.retries = attempt;
179
- if (attempt < maxRetries) {
180
- // Wait before retry
181
- const backoff = step.onError?.retry?.backoff?.[attempt] || 1000 * Math.pow(2, attempt);
182
- await sleep(backoff);
183
- }
184
- }
185
- }
186
- // All retries exhausted
187
- throw lastError;
188
- }
189
- catch (error) {
190
- stepTrace.success = false;
191
- stepTrace.error = error.message;
192
- stepTrace.errorCode = error.code;
193
- // Handle error based on policy
194
- const shouldAbort = step.onError?.abort !== false && errorPolicy?.mode !== 'best-effort';
195
- // Try fallback if configured
196
- if (step.onError?.fallback && !options.dryRun) {
197
- try {
198
- const fallbackResult = await executeSingleStep(step.onError.fallback, context, credentials, options, result, errorPolicy);
199
- // Use fallback result as this step's result
200
- context.results[step.id] = fallbackResult;
201
- context.prevStepId = step.id;
202
- stepTrace.success = true;
203
- stepTrace.output = { fallback: true, result: fallbackResult };
204
- result.completedSteps.push(step.id);
205
- return fallbackResult;
206
- }
207
- catch (fallbackError) {
208
- // Fallback also failed
209
- stepTrace.error = `Original: ${error.message}; Fallback: ${fallbackError.message}`;
210
- }
211
- }
212
- result.failedStep = stepTrace;
213
- if (shouldAbort) {
214
- throw error;
215
- }
216
- // Best-effort mode: continue despite failure
217
- context.results[step.id] = { error: error.message };
218
- context.prevStepId = step.id;
219
- return { error: error.message };
220
- }
221
- finally {
222
- stepTrace.completedAt = new Date().toISOString();
223
- stepTrace.latencyMs = Date.now() - stepStart;
224
- result.trace.push(stepTrace);
225
- options.onStepComplete?.(stepTrace);
226
- }
227
- }
228
- // ============================================================================
229
- // PARALLEL EXECUTION
230
- // ============================================================================
231
- async function executeParallelStep(step, context, credentials, options, result) {
232
- const parallelResults = await Promise.all(step.parallel.map(async (ps, idx) => {
233
- // Create a copy of context for each parallel step
234
- const parallelContext = {
235
- ...context,
236
- currentIndex: context.currentIndex,
237
- };
238
- try {
239
- return await executeSingleStep(ps, parallelContext, credentials, options, result);
240
- }
241
- catch (error) {
242
- // Return error as result for best-effort
243
- return { error: error.message };
244
- }
245
- }));
246
- // Store parallel results
247
- context.parallelResults = parallelResults;
248
- // Copy results from parallel steps to main context
249
- for (let i = 0; i < step.parallel.length; i++) {
250
- const ps = step.parallel[i];
251
- context.results[ps.id] = parallelResults[i];
252
- }
253
- // Last parallel step becomes prev
254
- context.prevStepId = step.parallel[step.parallel.length - 1].id;
255
- return parallelResults;
256
- }
257
- // ============================================================================
258
- // CONDITIONAL EXECUTION
259
- // ============================================================================
260
- async function executeConditionalStep(step, context, credentials, options, result) {
261
- // Evaluate condition
262
- const conditionResult = evaluateCondition(step.if, context);
263
- // Choose branch
264
- const branch = conditionResult ? step.then : step.else;
265
- if (!branch) {
266
- // No else branch and condition is false
267
- return null;
268
- }
269
- // Execute branch (can be single step or array)
270
- const steps = Array.isArray(branch) ? branch : [branch];
271
- let lastResult;
272
- for (const branchStep of steps) {
273
- lastResult = await executeSingleStep(branchStep, context, credentials, options, result);
274
- }
275
- return lastResult;
276
- }
277
- // ============================================================================
278
- // FOREACH EXECUTION
279
- // ============================================================================
280
- async function executeForEachStep(step, context, credentials, options, result, limits) {
281
- // Resolve the array to iterate over
282
- const arrayRef = step.forEach;
283
- let items;
284
- // Handle reference vs direct array
285
- if (arrayRef.startsWith('$')) {
286
- // It's a reference - resolve it
287
- const resolved = resolveReferences({ items: arrayRef }, context);
288
- items = resolved.items;
289
- }
290
- else {
291
- // Try to parse as JSON array
292
- try {
293
- items = JSON.parse(arrayRef);
294
- }
295
- catch {
296
- throw new Error(`forEach value must be a reference or JSON array: ${arrayRef}`);
297
- }
298
- }
299
- if (!Array.isArray(items)) {
300
- throw new Error(`forEach target must resolve to an array, got: ${typeof items}`);
301
- }
302
- // Check max iterations
303
- if (limits?.maxSteps && items.length > limits.maxSteps) {
304
- throw new Error(`forEach exceeds maxSteps limit (${items.length} iterations)`);
305
- }
306
- const forEachResults = [];
307
- for (let i = 0; i < items.length; i++) {
308
- // Set up forEach context
309
- context.forEachItem = items[i];
310
- context.forEachIndex = i;
311
- context.forEachResults = forEachResults;
312
- // Create step with dynamic ID
313
- const dynamicStep = {
314
- ...step.do,
315
- id: step.do.id.replace(/\$index/g, String(i)),
316
- };
317
- const itemResult = await executeSingleStep(dynamicStep, context, credentials, options, result);
318
- forEachResults.push(itemResult);
319
- }
320
- // Clean up forEach context
321
- delete context.forEachItem;
322
- delete context.forEachIndex;
323
- context.forEachResults = forEachResults;
324
- // Store aggregate results
325
- context.results['$forEach'] = { results: forEachResults };
326
- return forEachResults;
327
- }
328
- // ============================================================================
329
- // ROLLBACK EXECUTION
330
- // ============================================================================
331
- async function executeRollback(rollbackSteps, context, credentials, options, result) {
332
- for (const rb of rollbackSteps) {
333
- try {
334
- const shouldRollback = evaluateCondition(rb.if, context);
335
- if (shouldRollback) {
336
- await executeSingleStep(rb.do, context, credentials, { ...options, verbose: false }, result);
337
- }
338
- }
339
- catch (error) {
340
- // Log but don't fail on rollback errors
341
- console.error('[Chain] Rollback failed:', error);
342
- }
343
- }
344
- }
345
- // ============================================================================
346
- // INPUT VALIDATION
347
- // ============================================================================
348
- function validateInputs(inputs, definitions) {
349
- for (const [name, def] of Object.entries(definitions)) {
350
- const value = inputs[name];
351
- // Check required
352
- if (def.required && value === undefined) {
353
- if (def.default !== undefined) {
354
- inputs[name] = def.default;
355
- }
356
- else {
357
- throw new Error(`Missing required input: ${name}`);
358
- }
359
- }
360
- // Apply default
361
- if (value === undefined && def.default !== undefined) {
362
- inputs[name] = def.default;
363
- }
364
- // Type check (if value provided)
365
- if (inputs[name] !== undefined) {
366
- const actualType = Array.isArray(inputs[name]) ? 'array' : typeof inputs[name];
367
- if (actualType !== def.type) {
368
- throw new Error(`Input '${name}' must be ${def.type}, got ${actualType}`);
369
- }
370
- }
371
- }
372
- }
373
- // ============================================================================
374
- // UTILITIES
375
- // ============================================================================
376
- function generateChainId() {
377
- return `chain_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
378
- }
379
- function sleep(ms) {
380
- return new Promise(resolve => setTimeout(resolve, ms));
381
- }
382
- // ============================================================================
383
- // CUSTOM ERRORS
384
- // ============================================================================
385
- export class StepError extends Error {
386
- constructor(message, code, retryAfter) {
387
- super(message);
388
- this.name = 'StepError';
389
- this.code = code;
390
- this.retryAfter = retryAfter;
391
- }
392
- }
393
- export class TimeoutError extends Error {
394
- constructor(message) {
395
- super(message);
396
- this.code = 'TIMEOUT';
397
- this.name = 'TimeoutError';
398
- }
399
- }
400
- // ============================================================================
401
- // CHAIN STATUS & RESUME (Stubs for future implementation)
402
- // ============================================================================
403
- // In-memory store for running chains (would be Redis/DB in production)
404
- const runningChains = new Map();
405
- /**
406
- * Get status of a running or completed chain
407
- */
408
- export async function getChainStatus(chainId) {
409
- const entry = runningChains.get(chainId);
410
- if (!entry) {
411
- return { chainId, status: 'not_found' };
412
- }
413
- return {
414
- chainId,
415
- status: entry.status,
416
- result: entry.result,
417
- };
418
- }
419
- /**
420
- * Resume a failed chain from a resume token
421
- */
422
- export async function resumeChain(resumeToken, chain, credentials, inputs, overrides, options = {}) {
423
- // Parse resume token: chain_xyz_step_N
424
- const match = resumeToken.match(/^(.+)_step_(\d+)$/);
425
- if (!match) {
426
- throw new Error(`Invalid resume token: ${resumeToken}`);
427
- }
428
- const [, chainId, stepIndexStr] = match;
429
- const resumeFromIndex = parseInt(stepIndexStr, 10);
430
- // Create a modified chain starting from the resume point
431
- const resumedChain = {
432
- ...chain,
433
- id: chainId,
434
- steps: chain.steps.slice(resumeFromIndex),
435
- };
436
- // Apply any overrides to the resumed steps
437
- if (overrides) {
438
- for (const step of resumedChain.steps) {
439
- if ('id' in step && overrides[step.id]) {
440
- step.params = { ...step.params, ...overrides[step.id] };
441
- }
442
- }
443
- }
444
- // Execute the resumed chain
445
- // Note: In a real implementation, we'd restore the context from the previous run
446
- return executeChain(resumedChain, credentials, inputs, options);
447
- }
448
- // ============================================================================
449
- // RE-EXPORTS
450
- // ============================================================================
451
- export { resolveReferences, validateReferences, evaluateCondition, } from './chainResolver.js';