@apifuse/provider-sdk 2.0.0-beta.1 → 2.1.0-beta.1
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/AUTHORING.md +93 -0
- package/CHANGELOG.md +21 -0
- package/README.md +133 -28
- package/bin/apifuse-check.ts +78 -71
- package/bin/apifuse-create.ts +12 -0
- package/bin/apifuse-dev.ts +24 -61
- package/bin/apifuse-pack-check.ts +87 -0
- package/bin/apifuse-pack-smoke.ts +122 -0
- package/bin/apifuse-perf.ts +33 -32
- package/bin/apifuse-record.ts +17 -7
- package/bin/apifuse-test.ts +6 -4
- package/bin/apifuse.ts +36 -35
- package/package.json +29 -9
- package/src/ceremonies/index.ts +768 -0
- package/src/cli/commands.ts +87 -0
- package/src/cli/create.ts +845 -0
- package/src/cli/templates/provider/Dockerfile.tpl +7 -0
- package/src/cli/templates/provider/README.md.tpl +41 -0
- package/src/cli/templates/provider/dev.ts.tpl +5 -0
- package/src/cli/templates/provider/index.test.ts.tpl +13 -0
- package/src/cli/templates/provider/index.ts.tpl +58 -0
- package/src/cli/templates/provider/start.ts.tpl +5 -0
- package/src/config/loader.ts +61 -1
- package/src/define.ts +565 -41
- package/src/dev.ts +2 -6
- package/src/errors.ts +42 -0
- package/src/index.ts +44 -38
- package/src/lint.ts +574 -0
- package/src/provider.ts +13 -0
- package/src/runtime/auth-flow.ts +67 -0
- package/src/runtime/credential.ts +95 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +13 -14
- package/src/runtime/http.ts +36 -12
- package/src/runtime/insights.ts +3 -3
- package/src/runtime/key-derivation.ts +122 -0
- package/src/runtime/keyring.ts +148 -0
- package/src/runtime/namespace.ts +33 -0
- package/src/runtime/prevalidate.ts +252 -0
- package/src/runtime/tls.ts +41 -17
- package/src/runtime/waterfall.ts +0 -1
- package/src/schema.ts +77 -0
- package/src/serve.ts +1 -664
- package/src/server/index.ts +22 -0
- package/src/server/serve.ts +624 -0
- package/src/server/types.ts +78 -0
- package/src/stealth/profiles.ts +10 -93
- package/src/testing/run.ts +391 -32
- package/src/types.ts +390 -41
- package/bin/apifuse-init.ts +0 -387
- package/src/__tests__/auth.test.ts +0 -396
- package/src/__tests__/browser-auth.test.ts +0 -180
- package/src/__tests__/browser.test.ts +0 -632
- package/src/__tests__/define.test.ts +0 -225
- package/src/__tests__/errors.test.ts +0 -69
- package/src/__tests__/executor.test.ts +0 -214
- package/src/__tests__/http.test.ts +0 -238
- package/src/__tests__/insights.test.ts +0 -210
- package/src/__tests__/instrumentation.test.ts +0 -290
- package/src/__tests__/otlp.test.ts +0 -141
- package/src/__tests__/perf.test.ts +0 -60
- package/src/__tests__/providers-yaml.test.ts +0 -135
- package/src/__tests__/proxy.test.ts +0 -359
- package/src/__tests__/recipes.test.ts +0 -36
- package/src/__tests__/serve.test.ts +0 -233
- package/src/__tests__/session.test.ts +0 -231
- package/src/__tests__/state.test.ts +0 -100
- package/src/__tests__/stealth.test.ts +0 -57
- package/src/__tests__/testing.test.ts +0 -97
- package/src/__tests__/tls.test.ts +0 -345
- package/src/__tests__/types.test.ts +0 -142
- package/src/__tests__/utils.test.ts +0 -62
- package/src/__tests__/waterfall.test.ts +0 -270
- package/src/config/providers-yaml.ts +0 -370
- package/src/index.test.ts +0 -1
- package/src/protocol.ts +0 -183
- package/src/runtime/auth.ts +0 -245
- package/src/runtime/session.ts +0 -573
- package/src/runtime/state.ts +0 -124
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { ProviderError, TransportError } from "../errors";
|
|
5
|
+
import { createScratchpad } from "../runtime/auth-flow";
|
|
6
|
+
import { createBrowserClient } from "../runtime/browser";
|
|
7
|
+
import { createCredentialContext } from "../runtime/credential";
|
|
8
|
+
import { createEnvContext } from "../runtime/env";
|
|
9
|
+
import { executeOperation } from "../runtime/executor";
|
|
10
|
+
import { createHttpClient } from "../runtime/http";
|
|
11
|
+
import { getProviderBaseUrl } from "../runtime/provider";
|
|
12
|
+
import { createTlsClient } from "../runtime/tls";
|
|
13
|
+
import { createTraceContext } from "../runtime/trace";
|
|
14
|
+
import type {
|
|
15
|
+
AuthContext,
|
|
16
|
+
BrowserClient,
|
|
17
|
+
FlowContext,
|
|
18
|
+
FlowContextStore,
|
|
19
|
+
ProviderContext,
|
|
20
|
+
ProviderDefinition,
|
|
21
|
+
TlsClient,
|
|
22
|
+
} from "../types";
|
|
23
|
+
import {
|
|
24
|
+
type AuthFlowRequest,
|
|
25
|
+
AuthFlowRequestSchema,
|
|
26
|
+
type AuthFlowResponse,
|
|
27
|
+
type AuthFlowSuccessResponse,
|
|
28
|
+
type OperationErrorResponse,
|
|
29
|
+
type OperationRequest,
|
|
30
|
+
OperationRequestSchema,
|
|
31
|
+
type OperationResponse,
|
|
32
|
+
type OperationSuccessResponse,
|
|
33
|
+
} from "./types";
|
|
34
|
+
|
|
35
|
+
const DEFAULT_HOST = "0.0.0.0";
|
|
36
|
+
const DEFAULT_PORT = 3000;
|
|
37
|
+
|
|
38
|
+
function createAuthStub(): AuthContext {
|
|
39
|
+
return {
|
|
40
|
+
async requestField(name) {
|
|
41
|
+
throw new ProviderError(`Auth prompt is unavailable for ${name}`, {
|
|
42
|
+
code: "AUTH_PROMPT_UNAVAILABLE",
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createBrowserStub(): BrowserClient {
|
|
49
|
+
return {
|
|
50
|
+
engine: "playwright-stealth",
|
|
51
|
+
async newPage() {
|
|
52
|
+
throw new ProviderError("Browser runtime is not available", {
|
|
53
|
+
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createTlsStub(): TlsClient {
|
|
60
|
+
return {
|
|
61
|
+
async fetch() {
|
|
62
|
+
throw new ProviderError("TLS runtime is not available", {
|
|
63
|
+
code: "TLS_RUNTIME_UNSUPPORTED",
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
createSession() {
|
|
67
|
+
throw new ProviderError("TLS runtime is not available", {
|
|
68
|
+
code: "TLS_RUNTIME_UNSUPPORTED",
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createProviderContext(
|
|
75
|
+
provider: ProviderDefinition,
|
|
76
|
+
request: OperationRequest,
|
|
77
|
+
): ProviderContext {
|
|
78
|
+
const baseUrl = getProviderBaseUrl(provider);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
|
|
82
|
+
credential: createCredentialContext({
|
|
83
|
+
allowedKeys: provider.credential?.keys,
|
|
84
|
+
mode: request.connection?.mode,
|
|
85
|
+
scopes: request.connection?.scopes,
|
|
86
|
+
values: request.connection?.secrets,
|
|
87
|
+
}),
|
|
88
|
+
request: {
|
|
89
|
+
connectionId: request.connection?.id,
|
|
90
|
+
headers: request.headers ?? {},
|
|
91
|
+
},
|
|
92
|
+
http: createHttpClient(baseUrl),
|
|
93
|
+
tls: baseUrl ? createTlsClient(baseUrl) : createTlsStub(),
|
|
94
|
+
browser:
|
|
95
|
+
provider.runtime === "browser"
|
|
96
|
+
? createBrowserClient({
|
|
97
|
+
cdpUrl:
|
|
98
|
+
process.env.CDP_POOL_URL ?? process.env.APIFUSE_CDP_POOL_URL,
|
|
99
|
+
headless: true,
|
|
100
|
+
stealth: true,
|
|
101
|
+
engine: provider.browser?.engine,
|
|
102
|
+
})
|
|
103
|
+
: createBrowserStub(),
|
|
104
|
+
trace: createTraceContext(),
|
|
105
|
+
auth: createAuthStub(),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function createFlowContextStore(
|
|
110
|
+
allowedKeys: string[],
|
|
111
|
+
initialContext: Record<string, unknown> = {},
|
|
112
|
+
): {
|
|
113
|
+
context: FlowContextStore;
|
|
114
|
+
getPatch: () => Record<string, unknown | null> | undefined;
|
|
115
|
+
} {
|
|
116
|
+
const context = createScratchpad(allowedKeys, initialContext);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
context,
|
|
120
|
+
getPatch() {
|
|
121
|
+
const next = context.toJSON();
|
|
122
|
+
const patch = new Map<string, unknown | null>();
|
|
123
|
+
|
|
124
|
+
for (const [key, value] of Object.entries(next)) {
|
|
125
|
+
if (initialContext[key] !== value) {
|
|
126
|
+
patch.set(key, value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const key of Object.keys(initialContext)) {
|
|
131
|
+
if (!(key in next)) {
|
|
132
|
+
patch.set(key, null);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (patch.size === 0) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return Object.fromEntries(patch.entries());
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function createAuthFlowContext(
|
|
146
|
+
provider: ProviderDefinition,
|
|
147
|
+
request: AuthFlowRequest,
|
|
148
|
+
): {
|
|
149
|
+
context: FlowContext;
|
|
150
|
+
getPatch: () => Record<string, unknown | null> | undefined;
|
|
151
|
+
} {
|
|
152
|
+
const baseUrl = getProviderBaseUrl(provider);
|
|
153
|
+
const contextData = request.context ?? {};
|
|
154
|
+
const flowContextStore = createFlowContextStore(
|
|
155
|
+
provider.context?.keys ?? Object.keys(contextData),
|
|
156
|
+
contextData,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
context: {
|
|
161
|
+
connectionId: request.connectionId,
|
|
162
|
+
externalRef: request.externalRef,
|
|
163
|
+
tenantId: request.tenantId ?? "",
|
|
164
|
+
providerId: request.providerId ?? provider.id,
|
|
165
|
+
http: createHttpClient(baseUrl),
|
|
166
|
+
env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
|
|
167
|
+
context: flowContextStore.context,
|
|
168
|
+
},
|
|
169
|
+
getPatch: flowContextStore.getPatch,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type ProviderServerLogEvent = {
|
|
174
|
+
level: "warn" | "error";
|
|
175
|
+
event: "provider_request_failed";
|
|
176
|
+
providerId: string;
|
|
177
|
+
kind: "operation" | "auth";
|
|
178
|
+
route: string;
|
|
179
|
+
requestId?: string;
|
|
180
|
+
status: number;
|
|
181
|
+
code: string;
|
|
182
|
+
errorClass: string;
|
|
183
|
+
message: string;
|
|
184
|
+
upstreamStatus?: number;
|
|
185
|
+
issues?: Array<{ path: string; code: string; message: string }>;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export type ProviderServerLogger = (event: ProviderServerLogEvent) => void;
|
|
189
|
+
|
|
190
|
+
export type ProviderServerOptions = {
|
|
191
|
+
logger?: ProviderServerLogger;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const defaultProviderServerLogger: ProviderServerLogger = (event) => {
|
|
195
|
+
console.error(JSON.stringify(event));
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
function zodDetails(error: z.ZodError): Array<{
|
|
199
|
+
path: string;
|
|
200
|
+
code: string;
|
|
201
|
+
message: string;
|
|
202
|
+
}> {
|
|
203
|
+
return error.issues.map((issue) => ({
|
|
204
|
+
path: issue.path.join("."),
|
|
205
|
+
code: issue.code,
|
|
206
|
+
message: issue.message,
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function toErrorResponse(
|
|
211
|
+
error: unknown,
|
|
212
|
+
requestId?: string,
|
|
213
|
+
): OperationErrorResponse {
|
|
214
|
+
if (error instanceof ProviderError) {
|
|
215
|
+
return {
|
|
216
|
+
error: {
|
|
217
|
+
code: error.code ?? "provider_error",
|
|
218
|
+
message: publicProviderErrorMessage(error),
|
|
219
|
+
...(requestId ? { requestId } : {}),
|
|
220
|
+
...(error instanceof TransportError && error.status
|
|
221
|
+
? { details: { upstreamStatus: error.status } }
|
|
222
|
+
: {}),
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (error instanceof z.ZodError) {
|
|
228
|
+
return {
|
|
229
|
+
error: {
|
|
230
|
+
code: "invalid_request",
|
|
231
|
+
message: "Invalid request body",
|
|
232
|
+
...(requestId ? { requestId } : {}),
|
|
233
|
+
details: zodDetails(error),
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
error: {
|
|
240
|
+
code: "internal_error",
|
|
241
|
+
message: "Internal error",
|
|
242
|
+
...(requestId ? { requestId } : {}),
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function publicProviderErrorMessage(error: ProviderError): string {
|
|
248
|
+
if (error instanceof TransportError) {
|
|
249
|
+
if (error.code === "transport_timeout") return "Request timed out";
|
|
250
|
+
if (error.code === "transport_network_error") return "Network error";
|
|
251
|
+
if (error.code === "upstream_http_error" && error.status) {
|
|
252
|
+
return `Upstream request failed with status ${error.status}`;
|
|
253
|
+
}
|
|
254
|
+
if (error.status) {
|
|
255
|
+
return `Upstream request failed with status ${error.status}`;
|
|
256
|
+
}
|
|
257
|
+
return "Upstream request failed";
|
|
258
|
+
}
|
|
259
|
+
return error.message;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function toStatusCode(error: unknown): 400 | 404 | 500 | 502 | 504 {
|
|
263
|
+
if (error instanceof z.ZodError) {
|
|
264
|
+
return 400;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (error instanceof TransportError) {
|
|
268
|
+
return error.code === "transport_timeout" ? 504 : 502;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (error instanceof ProviderError) {
|
|
272
|
+
if (error.code === "NOT_FOUND" || error.code === "NO_DATA") {
|
|
273
|
+
return 404;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return 400;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return 500;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function extractRequestId(raw: unknown): string | undefined {
|
|
283
|
+
if (!raw || typeof raw !== "object") {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const value = Object.getOwnPropertyDescriptor(raw, "requestId")?.value;
|
|
288
|
+
return typeof value === "string" ? value : undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function logProviderError(
|
|
292
|
+
logger: ProviderServerLogger | unknown,
|
|
293
|
+
provider: ProviderDefinition,
|
|
294
|
+
kind: "operation" | "auth",
|
|
295
|
+
route: string,
|
|
296
|
+
requestId: string | undefined,
|
|
297
|
+
error: unknown,
|
|
298
|
+
status: number,
|
|
299
|
+
): void {
|
|
300
|
+
const code =
|
|
301
|
+
error instanceof ProviderError
|
|
302
|
+
? (error.code ?? "provider_error")
|
|
303
|
+
: error instanceof z.ZodError
|
|
304
|
+
? "invalid_request"
|
|
305
|
+
: "internal_error";
|
|
306
|
+
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
307
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
308
|
+
const emit =
|
|
309
|
+
typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
310
|
+
emit({
|
|
311
|
+
level: status >= 500 ? "error" : "warn",
|
|
312
|
+
event: "provider_request_failed",
|
|
313
|
+
providerId: provider.id,
|
|
314
|
+
kind,
|
|
315
|
+
route,
|
|
316
|
+
...(requestId ? { requestId } : {}),
|
|
317
|
+
status,
|
|
318
|
+
code,
|
|
319
|
+
errorClass,
|
|
320
|
+
message,
|
|
321
|
+
...(error instanceof TransportError && error.status
|
|
322
|
+
? { upstreamStatus: error.status }
|
|
323
|
+
: {}),
|
|
324
|
+
...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function toJsonSuccessResponse(
|
|
329
|
+
result: unknown,
|
|
330
|
+
): Response | OperationSuccessResponse {
|
|
331
|
+
if (result instanceof Response) {
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (result instanceof ReadableStream) {
|
|
336
|
+
return new Response(result);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return { data: result };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function toAuthFlowResponse(
|
|
343
|
+
result: unknown,
|
|
344
|
+
contextPatch: Record<string, unknown | null> | undefined,
|
|
345
|
+
): Response | AuthFlowSuccessResponse {
|
|
346
|
+
if (result instanceof Response) {
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (result instanceof ReadableStream) {
|
|
351
|
+
return new Response(result);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
data: result,
|
|
356
|
+
...(contextPatch ? { contextPatch } : {}),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function handleOperation(
|
|
361
|
+
provider: ProviderDefinition,
|
|
362
|
+
request: OperationRequest,
|
|
363
|
+
operationId: string,
|
|
364
|
+
): Promise<Response | OperationResponse> {
|
|
365
|
+
const ctx = createProviderContext(provider, request);
|
|
366
|
+
const result = await executeOperation(
|
|
367
|
+
provider,
|
|
368
|
+
operationId,
|
|
369
|
+
ctx,
|
|
370
|
+
request.input,
|
|
371
|
+
);
|
|
372
|
+
return toJsonSuccessResponse(result);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
type AuthRoute = "start" | "continue" | "poll" | "abort";
|
|
376
|
+
|
|
377
|
+
async function handleAuthFlow(
|
|
378
|
+
provider: ProviderDefinition,
|
|
379
|
+
request: AuthFlowRequest,
|
|
380
|
+
route: AuthRoute,
|
|
381
|
+
): Promise<Response | AuthFlowResponse> {
|
|
382
|
+
const flow = provider.auth?.flow;
|
|
383
|
+
if (!flow) {
|
|
384
|
+
throw new ProviderError("Auth flow is not configured", {
|
|
385
|
+
code: "AUTH_FLOW_NOT_CONFIGURED",
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const { context, getPatch } = createAuthFlowContext(provider, request);
|
|
390
|
+
|
|
391
|
+
const result =
|
|
392
|
+
route === "start"
|
|
393
|
+
? await flow.start(context)
|
|
394
|
+
: route === "continue"
|
|
395
|
+
? await flow.continue(context, request.input ?? {})
|
|
396
|
+
: route === "poll"
|
|
397
|
+
? flow.poll
|
|
398
|
+
? await flow.poll(context)
|
|
399
|
+
: null
|
|
400
|
+
: flow.abort
|
|
401
|
+
? await flow.abort(context)
|
|
402
|
+
: null;
|
|
403
|
+
|
|
404
|
+
return toAuthFlowResponse(result, getPatch());
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function createServerApp(
|
|
408
|
+
provider: ProviderDefinition,
|
|
409
|
+
options: ProviderServerOptions = {},
|
|
410
|
+
): Hono {
|
|
411
|
+
const app = new Hono();
|
|
412
|
+
const logger = options.logger ?? defaultProviderServerLogger;
|
|
413
|
+
|
|
414
|
+
app.notFound((c) =>
|
|
415
|
+
c.json(
|
|
416
|
+
{
|
|
417
|
+
error: {
|
|
418
|
+
code: "not_found",
|
|
419
|
+
message: "Not found",
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
404,
|
|
423
|
+
),
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
app.get("/health", (c) =>
|
|
427
|
+
c.json({
|
|
428
|
+
status: "ok",
|
|
429
|
+
provider: provider.id,
|
|
430
|
+
version: provider.version,
|
|
431
|
+
}),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
app.post("/v1/:operation", async (c) => {
|
|
435
|
+
let rawBody: unknown;
|
|
436
|
+
const operation = c.req.param("operation");
|
|
437
|
+
try {
|
|
438
|
+
rawBody = await c.req.raw
|
|
439
|
+
.clone()
|
|
440
|
+
.json()
|
|
441
|
+
.catch(() => undefined);
|
|
442
|
+
const body = OperationRequestSchema.parse(rawBody);
|
|
443
|
+
const requestHeaders = Object.fromEntries(c.req.raw.headers.entries());
|
|
444
|
+
body.headers = { ...requestHeaders, ...body.headers };
|
|
445
|
+
const response = await handleOperation(provider, body, operation);
|
|
446
|
+
return response instanceof Response ? response : c.json(response);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
const status = toStatusCode(error);
|
|
449
|
+
const requestId = extractRequestId(rawBody);
|
|
450
|
+
logProviderError(
|
|
451
|
+
logger,
|
|
452
|
+
provider,
|
|
453
|
+
"operation",
|
|
454
|
+
operation,
|
|
455
|
+
requestId,
|
|
456
|
+
error,
|
|
457
|
+
status,
|
|
458
|
+
);
|
|
459
|
+
return c.json(toErrorResponse(error, requestId), status);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
app.post("/auth/start", async (c) => {
|
|
464
|
+
let rawBody: unknown;
|
|
465
|
+
try {
|
|
466
|
+
rawBody = await c.req.raw
|
|
467
|
+
.clone()
|
|
468
|
+
.json()
|
|
469
|
+
.catch(() => undefined);
|
|
470
|
+
const body = AuthFlowRequestSchema.parse(rawBody);
|
|
471
|
+
const response = await handleAuthFlow(provider, body, "start");
|
|
472
|
+
return response instanceof Response ? response : c.json(response);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
const status = toStatusCode(error);
|
|
475
|
+
const requestId = extractRequestId(rawBody);
|
|
476
|
+
logProviderError(
|
|
477
|
+
logger,
|
|
478
|
+
provider,
|
|
479
|
+
"auth",
|
|
480
|
+
"start",
|
|
481
|
+
requestId,
|
|
482
|
+
error,
|
|
483
|
+
status,
|
|
484
|
+
);
|
|
485
|
+
return c.json(toErrorResponse(error, requestId), status);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
app.post("/auth/continue", async (c) => {
|
|
490
|
+
let rawBody: unknown;
|
|
491
|
+
try {
|
|
492
|
+
rawBody = await c.req.raw
|
|
493
|
+
.clone()
|
|
494
|
+
.json()
|
|
495
|
+
.catch(() => undefined);
|
|
496
|
+
const body = AuthFlowRequestSchema.parse(rawBody);
|
|
497
|
+
const response = await handleAuthFlow(provider, body, "continue");
|
|
498
|
+
return response instanceof Response ? response : c.json(response);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
const status = toStatusCode(error);
|
|
501
|
+
const requestId = extractRequestId(rawBody);
|
|
502
|
+
logProviderError(
|
|
503
|
+
logger,
|
|
504
|
+
provider,
|
|
505
|
+
"auth",
|
|
506
|
+
"continue",
|
|
507
|
+
requestId,
|
|
508
|
+
error,
|
|
509
|
+
status,
|
|
510
|
+
);
|
|
511
|
+
return c.json(toErrorResponse(error, requestId), status);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
app.post("/auth/poll", async (c) => {
|
|
516
|
+
let rawBody: unknown;
|
|
517
|
+
try {
|
|
518
|
+
rawBody = await c.req.raw
|
|
519
|
+
.clone()
|
|
520
|
+
.json()
|
|
521
|
+
.catch(() => undefined);
|
|
522
|
+
const body = AuthFlowRequestSchema.parse(rawBody);
|
|
523
|
+
const response = await handleAuthFlow(provider, body, "poll");
|
|
524
|
+
return response instanceof Response ? response : c.json(response);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
const status = toStatusCode(error);
|
|
527
|
+
const requestId = extractRequestId(rawBody);
|
|
528
|
+
logProviderError(
|
|
529
|
+
logger,
|
|
530
|
+
provider,
|
|
531
|
+
"auth",
|
|
532
|
+
"poll",
|
|
533
|
+
requestId,
|
|
534
|
+
error,
|
|
535
|
+
status,
|
|
536
|
+
);
|
|
537
|
+
return c.json(toErrorResponse(error, requestId), status);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
app.post("/auth/disconnect", async (c) => {
|
|
542
|
+
let rawBody: unknown;
|
|
543
|
+
try {
|
|
544
|
+
rawBody = await c.req.raw
|
|
545
|
+
.clone()
|
|
546
|
+
.json()
|
|
547
|
+
.catch(() => undefined);
|
|
548
|
+
const body = AuthFlowRequestSchema.parse(rawBody);
|
|
549
|
+
const response = await handleAuthFlow(provider, body, "abort");
|
|
550
|
+
return response instanceof Response ? response : c.json(response);
|
|
551
|
+
} catch (error) {
|
|
552
|
+
const status = toStatusCode(error);
|
|
553
|
+
const requestId = extractRequestId(rawBody);
|
|
554
|
+
logProviderError(
|
|
555
|
+
logger,
|
|
556
|
+
provider,
|
|
557
|
+
"auth",
|
|
558
|
+
"disconnect",
|
|
559
|
+
requestId,
|
|
560
|
+
error,
|
|
561
|
+
status,
|
|
562
|
+
);
|
|
563
|
+
return c.json(toErrorResponse(error, requestId), status);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
return app;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
type BunServeRuntime = {
|
|
571
|
+
serve: (options: {
|
|
572
|
+
port: number;
|
|
573
|
+
hostname: string;
|
|
574
|
+
fetch: (request: Request) => Response | Promise<Response>;
|
|
575
|
+
}) => unknown;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
function getBunServeRuntime(): BunServeRuntime | undefined {
|
|
579
|
+
const bunValue = Object.getOwnPropertyDescriptor(globalThis, "Bun")?.value;
|
|
580
|
+
if (!bunValue || typeof bunValue !== "object") {
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const serve = Object.getOwnPropertyDescriptor(bunValue, "serve")?.value;
|
|
585
|
+
if (typeof serve !== "function") {
|
|
586
|
+
return undefined;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
serve(options) {
|
|
591
|
+
return serve(options);
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export interface ServeOptions extends ProviderServerOptions {
|
|
597
|
+
host?: string;
|
|
598
|
+
port?: number;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export async function serve(
|
|
602
|
+
provider: ProviderDefinition,
|
|
603
|
+
options: ServeOptions = {},
|
|
604
|
+
): Promise<void> {
|
|
605
|
+
const bunRuntime = getBunServeRuntime();
|
|
606
|
+
|
|
607
|
+
if (bunRuntime === undefined) {
|
|
608
|
+
throw new ProviderError(
|
|
609
|
+
"Bun runtime is required to start the provider server",
|
|
610
|
+
{
|
|
611
|
+
code: "RUNTIME_UNSUPPORTED",
|
|
612
|
+
},
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const app = createServerApp(provider, { logger: options.logger });
|
|
617
|
+
|
|
618
|
+
bunRuntime.serve({
|
|
619
|
+
port: options.port ?? DEFAULT_PORT,
|
|
620
|
+
hostname: options.host ?? DEFAULT_HOST,
|
|
621
|
+
fetch: app.fetch,
|
|
622
|
+
});
|
|
623
|
+
await Promise.resolve();
|
|
624
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const ConnectionModeSchema = z.enum([
|
|
4
|
+
"oauth2",
|
|
5
|
+
"credentials",
|
|
6
|
+
"platform-managed",
|
|
7
|
+
"none",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export const OperationConnectionSchema = z.object({
|
|
11
|
+
id: z.string(),
|
|
12
|
+
mode: ConnectionModeSchema,
|
|
13
|
+
secrets: z.record(z.string(), z.string()),
|
|
14
|
+
scopes: z.array(z.string()).optional(),
|
|
15
|
+
metadata: z.record(z.string(), z.unknown()),
|
|
16
|
+
externalRef: z.string(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const OperationRequestSchema = z.object({
|
|
20
|
+
requestId: z.string(),
|
|
21
|
+
input: z.record(z.string(), z.unknown()),
|
|
22
|
+
connection: OperationConnectionSchema.optional(),
|
|
23
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
24
|
+
trace: z.record(z.string(), z.string()).optional(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const ErrorEnvelopeSchema = z.object({
|
|
28
|
+
code: z.string(),
|
|
29
|
+
message: z.string(),
|
|
30
|
+
requestId: z.string().optional(),
|
|
31
|
+
fix: z.string().optional(),
|
|
32
|
+
details: z.unknown().optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const OperationSuccessResponseSchema = z.object({
|
|
36
|
+
data: z.unknown(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const OperationErrorResponseSchema = z.object({
|
|
40
|
+
error: ErrorEnvelopeSchema,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const AuthFlowRequestSchema = z.object({
|
|
44
|
+
requestId: z.string(),
|
|
45
|
+
flowId: z.string(),
|
|
46
|
+
connectionId: z.string().optional(),
|
|
47
|
+
externalRef: z.string().optional(),
|
|
48
|
+
tenantId: z.string().optional(),
|
|
49
|
+
providerId: z.string().optional(),
|
|
50
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
51
|
+
context: z.record(z.string(), z.unknown()).optional(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const AuthFlowSuccessResponseSchema = z.object({
|
|
55
|
+
data: z.unknown(),
|
|
56
|
+
contextPatch: z.record(z.string(), z.unknown().nullable()).optional(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const AuthFlowErrorResponseSchema = OperationErrorResponseSchema;
|
|
60
|
+
|
|
61
|
+
export type ConnectionMode = z.infer<typeof ConnectionModeSchema>;
|
|
62
|
+
export type OperationConnection = z.infer<typeof OperationConnectionSchema>;
|
|
63
|
+
export type OperationRequest = z.infer<typeof OperationRequestSchema>;
|
|
64
|
+
export type OperationSuccessResponse = z.infer<
|
|
65
|
+
typeof OperationSuccessResponseSchema
|
|
66
|
+
>;
|
|
67
|
+
export type OperationErrorResponse = z.infer<
|
|
68
|
+
typeof OperationErrorResponseSchema
|
|
69
|
+
>;
|
|
70
|
+
export type OperationResponse =
|
|
71
|
+
| OperationSuccessResponse
|
|
72
|
+
| OperationErrorResponse;
|
|
73
|
+
export type AuthFlowRequest = z.infer<typeof AuthFlowRequestSchema>;
|
|
74
|
+
export type AuthFlowSuccessResponse = z.infer<
|
|
75
|
+
typeof AuthFlowSuccessResponseSchema
|
|
76
|
+
>;
|
|
77
|
+
export type AuthFlowErrorResponse = z.infer<typeof AuthFlowErrorResponseSchema>;
|
|
78
|
+
export type AuthFlowResponse = AuthFlowSuccessResponse | AuthFlowErrorResponse;
|