@apifuse/connector-sdk 2.0.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/README.md +44 -0
- package/bin/apifuse-check.ts +408 -0
- package/bin/apifuse-dev.ts +222 -0
- package/bin/apifuse-init.ts +390 -0
- package/bin/apifuse-perf.ts +1101 -0
- package/bin/apifuse-record.ts +446 -0
- package/bin/apifuse-test.ts +688 -0
- package/bin/apifuse.ts +51 -0
- package/package.json +64 -0
- package/src/__tests__/auth.test.ts +396 -0
- package/src/__tests__/browser-auth.test.ts +180 -0
- package/src/__tests__/browser.test.ts +632 -0
- package/src/__tests__/connectors-yaml.test.ts +135 -0
- package/src/__tests__/define.test.ts +225 -0
- package/src/__tests__/errors.test.ts +69 -0
- package/src/__tests__/executor.test.ts +214 -0
- package/src/__tests__/http.test.ts +238 -0
- package/src/__tests__/insights.test.ts +210 -0
- package/src/__tests__/instrumentation.test.ts +290 -0
- package/src/__tests__/otlp.test.ts +141 -0
- package/src/__tests__/perf.test.ts +60 -0
- package/src/__tests__/proxy.test.ts +359 -0
- package/src/__tests__/recipes.test.ts +36 -0
- package/src/__tests__/serve.test.ts +233 -0
- package/src/__tests__/session.test.ts +231 -0
- package/src/__tests__/state.test.ts +100 -0
- package/src/__tests__/stealth.test.ts +57 -0
- package/src/__tests__/testing.test.ts +97 -0
- package/src/__tests__/tls.test.ts +345 -0
- package/src/__tests__/types.test.ts +142 -0
- package/src/__tests__/utils.test.ts +62 -0
- package/src/__tests__/waterfall.test.ts +270 -0
- package/src/config/connectors-yaml.ts +373 -0
- package/src/config/loader.ts +122 -0
- package/src/define.ts +137 -0
- package/src/dev.ts +38 -0
- package/src/errors.ts +68 -0
- package/src/index.test.ts +1 -0
- package/src/index.ts +100 -0
- package/src/protocol.ts +183 -0
- package/src/recipes/gov-api.ts +97 -0
- package/src/recipes/rest-api.ts +152 -0
- package/src/runtime/auth.ts +245 -0
- package/src/runtime/browser.ts +724 -0
- package/src/runtime/connector.ts +20 -0
- package/src/runtime/executor.ts +51 -0
- package/src/runtime/http.ts +248 -0
- package/src/runtime/insights.ts +456 -0
- package/src/runtime/instrumentation.ts +424 -0
- package/src/runtime/otlp.ts +171 -0
- package/src/runtime/perf.ts +73 -0
- package/src/runtime/session.ts +573 -0
- package/src/runtime/state.ts +124 -0
- package/src/runtime/tls.ts +410 -0
- package/src/runtime/trace.ts +261 -0
- package/src/runtime/waterfall.ts +245 -0
- package/src/serve.ts +665 -0
- package/src/stealth/profiles.ts +391 -0
- package/src/testing/helpers.ts +144 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/run.ts +88 -0
- package/src/types/playwright-stealth.d.ts +9 -0
- package/src/types.ts +243 -0
- package/src/utils/date.ts +163 -0
- package/src/utils/parse.ts +66 -0
- package/src/utils/text.ts +20 -0
- package/src/utils/transform.ts +62 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { TraceConfig } from "../types";
|
|
5
|
+
|
|
6
|
+
export type ProxyOptions = {
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ProxyConfig = Partial<ProxyOptions> & {
|
|
11
|
+
provider?: string;
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type BrowserConfig = {
|
|
16
|
+
executablePath?: string;
|
|
17
|
+
headless?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type SessionConfig = {
|
|
21
|
+
storage?: "sqlite" | "supabase";
|
|
22
|
+
path?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type ApiFuseConfig = {
|
|
26
|
+
proxy?: ProxyConfig;
|
|
27
|
+
browser?: BrowserConfig;
|
|
28
|
+
session?: SessionConfig;
|
|
29
|
+
trace?: TraceConfig;
|
|
30
|
+
credentials?: Record<string, Record<string, string>>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ProxyResolutionOptions = {
|
|
34
|
+
proxy?: string;
|
|
35
|
+
upstream?: { proxy?: boolean };
|
|
36
|
+
apifuseConfig?: Pick<ApiFuseConfig, "proxy">;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ResolvedProxyConfig = {
|
|
40
|
+
shouldWarn: boolean;
|
|
41
|
+
url?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function normalizeProxyUrl(url?: string): string | undefined {
|
|
45
|
+
const normalized = url?.trim();
|
|
46
|
+
return normalized ? normalized : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function syncProxyEnv(config: ApiFuseConfig): void {
|
|
50
|
+
const configProxyUrl = normalizeProxyUrl(config.proxy?.url);
|
|
51
|
+
if (!process.env.APIFUSE_PROXY_URL && configProxyUrl) {
|
|
52
|
+
process.env.APIFUSE_PROXY_URL = configProxyUrl;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function resolveProxyConfig(
|
|
57
|
+
options: ProxyResolutionOptions = {},
|
|
58
|
+
): ResolvedProxyConfig {
|
|
59
|
+
const explicitProxyUrl = normalizeProxyUrl(options.proxy);
|
|
60
|
+
if (explicitProxyUrl) {
|
|
61
|
+
return { shouldWarn: false, url: explicitProxyUrl };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!options.upstream?.proxy) {
|
|
65
|
+
return { shouldWarn: false };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const envProxyUrl = normalizeProxyUrl(process.env.APIFUSE_PROXY_URL);
|
|
69
|
+
if (envProxyUrl) {
|
|
70
|
+
return { shouldWarn: false, url: envProxyUrl };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const configuredProxyUrl = normalizeProxyUrl(
|
|
74
|
+
options.apifuseConfig?.proxy?.url,
|
|
75
|
+
);
|
|
76
|
+
if (configuredProxyUrl) {
|
|
77
|
+
return { shouldWarn: false, url: configuredProxyUrl };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { shouldWarn: true };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function defineConfig(config: ApiFuseConfig): ApiFuseConfig {
|
|
84
|
+
return config;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function importConfig(filePath: string): Promise<ApiFuseConfig | null> {
|
|
88
|
+
try {
|
|
89
|
+
const moduleUrl = new URL(`file://${encodeURI(filePath)}`);
|
|
90
|
+
const mod = (await import(moduleUrl.href)) as { default?: ApiFuseConfig };
|
|
91
|
+
if (mod.default && typeof mod.default === "object") {
|
|
92
|
+
return mod.default;
|
|
93
|
+
}
|
|
94
|
+
console.warn(`[connector-sdk] Ignoring invalid config export: ${filePath}`);
|
|
95
|
+
return {};
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.warn(`[connector-sdk] Failed to load config ${filePath}:`, error);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function loadApiFuseConfig(
|
|
103
|
+
dir: string = process.cwd(),
|
|
104
|
+
): Promise<ApiFuseConfig> {
|
|
105
|
+
const tsPath = path.resolve(dir, "apifuse.config.ts");
|
|
106
|
+
if (existsSync(tsPath)) {
|
|
107
|
+
const config = await importConfig(tsPath);
|
|
108
|
+
const resolvedConfig = config ?? {};
|
|
109
|
+
syncProxyEnv(resolvedConfig);
|
|
110
|
+
return resolvedConfig;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const jsPath = path.resolve(dir, "apifuse.config.js");
|
|
114
|
+
if (existsSync(jsPath)) {
|
|
115
|
+
const config = await importConfig(jsPath);
|
|
116
|
+
const resolvedConfig = config ?? {};
|
|
117
|
+
syncProxyEnv(resolvedConfig);
|
|
118
|
+
return resolvedConfig;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {};
|
|
122
|
+
}
|
package/src/define.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { ZodType } from "zod";
|
|
2
|
+
import { ConnectorError, ValidationError } from "./errors";
|
|
3
|
+
import type {
|
|
4
|
+
AuthConfig,
|
|
5
|
+
BrowserEngine,
|
|
6
|
+
ConnectorDefinition,
|
|
7
|
+
OperationDefinition,
|
|
8
|
+
StealthPlatform,
|
|
9
|
+
} from "./types";
|
|
10
|
+
|
|
11
|
+
const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
|
|
12
|
+
|
|
13
|
+
type ConnectorOperation = OperationDefinition<ZodType, ZodType>;
|
|
14
|
+
|
|
15
|
+
export interface ConnectorConfig<
|
|
16
|
+
TOperations extends Record<string, ConnectorOperation>,
|
|
17
|
+
> {
|
|
18
|
+
id: string;
|
|
19
|
+
version: string;
|
|
20
|
+
runtime: "standard" | "browser";
|
|
21
|
+
stealth?: {
|
|
22
|
+
profile: string;
|
|
23
|
+
platform: StealthPlatform;
|
|
24
|
+
};
|
|
25
|
+
proxy?: boolean;
|
|
26
|
+
browser?: {
|
|
27
|
+
engine: BrowserEngine;
|
|
28
|
+
};
|
|
29
|
+
auth?: AuthConfig;
|
|
30
|
+
meta: {
|
|
31
|
+
displayName: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
category: string;
|
|
34
|
+
tags?: string[];
|
|
35
|
+
icon?: string;
|
|
36
|
+
};
|
|
37
|
+
operations: TOperations;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function validateOperationFixtures(
|
|
41
|
+
connectorId: string,
|
|
42
|
+
operations: Record<string, ConnectorOperation>,
|
|
43
|
+
): void {
|
|
44
|
+
for (const [operationName, operation] of Object.entries(operations)) {
|
|
45
|
+
if (typeof operation.handler !== "function") {
|
|
46
|
+
throw new ValidationError(
|
|
47
|
+
`Operation handler must be defined for connector "${connectorId}" operation "${operationName}"`,
|
|
48
|
+
{
|
|
49
|
+
fix: `Add operations.${operationName}.handler as an async function with signature (ctx, input) => Promise<output>`,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (operation.fixtures?.request !== undefined) {
|
|
55
|
+
const result = operation.input.safeParse(operation.fixtures.request);
|
|
56
|
+
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
throw new ValidationError(
|
|
59
|
+
`Fixture request does not match input schema for connector "${connectorId}" operation "${operationName}"`,
|
|
60
|
+
{
|
|
61
|
+
fix: `Update operations.${operationName}.fixtures.request to match operations.${operationName}.input`,
|
|
62
|
+
zodError: result.error,
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (operation.fixtures?.response !== undefined) {
|
|
69
|
+
const result = operation.output.safeParse(operation.fixtures.response);
|
|
70
|
+
|
|
71
|
+
if (!result.success) {
|
|
72
|
+
throw new ValidationError(
|
|
73
|
+
`Fixture response does not match output schema for connector "${connectorId}" operation "${operationName}"`,
|
|
74
|
+
{
|
|
75
|
+
fix: `Update operations.${operationName}.fixtures.response to match operations.${operationName}.output`,
|
|
76
|
+
zodError: result.error,
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function defineConnector<
|
|
85
|
+
TOperations extends Record<string, ConnectorOperation>,
|
|
86
|
+
>(
|
|
87
|
+
config: ConnectorConfig<TOperations>,
|
|
88
|
+
): ConnectorDefinition & { operations: TOperations } {
|
|
89
|
+
if (!CONNECTOR_ID_REGEX.test(config.id)) {
|
|
90
|
+
throw new ConnectorError(`Invalid connector id: "${config.id}"`, {
|
|
91
|
+
fix: 'Use lowercase alphanumeric with dashes, e.g., "coingecko-prices"',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (Object.keys(config.operations).length === 0) {
|
|
96
|
+
throw new ConnectorError(
|
|
97
|
+
`Connector "${config.id}" must define at least one operation`,
|
|
98
|
+
{ fix: "Add at least one operation to the operations object" },
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
validateOperationFixtures(config.id, config.operations);
|
|
103
|
+
|
|
104
|
+
if (config.runtime === "browser" && !config.browser) {
|
|
105
|
+
throw new ConnectorError(
|
|
106
|
+
`Connector "${config.id}" must define browser.engine when runtime is "browser"`,
|
|
107
|
+
{
|
|
108
|
+
fix: 'Add browser: { engine: "nodriver" } or another supported engine',
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (config.browser && config.runtime !== "browser") {
|
|
114
|
+
throw new ConnectorError(
|
|
115
|
+
`Connector "${config.id}" cannot define browser config unless runtime is "browser"`,
|
|
116
|
+
{ fix: 'Set runtime: "browser" or remove the browser config' },
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (config.proxy && !config.stealth) {
|
|
121
|
+
console.warn(
|
|
122
|
+
`[connector-sdk] Connector "${config.id}" enables proxy without a stealth profile.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
id: config.id,
|
|
128
|
+
version: config.version,
|
|
129
|
+
runtime: config.runtime,
|
|
130
|
+
stealth: config.stealth,
|
|
131
|
+
proxy: config.proxy,
|
|
132
|
+
browser: config.browser,
|
|
133
|
+
auth: config.auth,
|
|
134
|
+
meta: config.meta,
|
|
135
|
+
operations: config.operations,
|
|
136
|
+
};
|
|
137
|
+
}
|
package/src/dev.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createConnectorServer } from "./serve";
|
|
2
|
+
import type { ConnectorDefinition } from "./types";
|
|
3
|
+
|
|
4
|
+
export interface DevServerOptions {
|
|
5
|
+
port?: number;
|
|
6
|
+
sessionDbPath?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createDevServer(
|
|
10
|
+
connector: ConnectorDefinition,
|
|
11
|
+
options?: DevServerOptions,
|
|
12
|
+
): { start: () => void } {
|
|
13
|
+
const port = options?.port ?? 3900;
|
|
14
|
+
const server = createConnectorServer(connector, {
|
|
15
|
+
port,
|
|
16
|
+
sessionDbPath: options?.sessionDbPath,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
start: () => {
|
|
21
|
+
server.start();
|
|
22
|
+
console.log(
|
|
23
|
+
`[apifuse dev] ${connector.id}@${connector.version} running at http://localhost:${port}`,
|
|
24
|
+
);
|
|
25
|
+
console.log(
|
|
26
|
+
`[apifuse dev] Operations: ${Object.keys(connector.operations).join(", ")}`,
|
|
27
|
+
);
|
|
28
|
+
console.log(`[apifuse dev] Health: http://localhost:${port}/health`);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function startDevServer(
|
|
34
|
+
connector: ConnectorDefinition,
|
|
35
|
+
options?: DevServerOptions,
|
|
36
|
+
): void {
|
|
37
|
+
createDevServer(connector, options).start();
|
|
38
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type ConnectorErrorOptions = {
|
|
2
|
+
fix?: string;
|
|
3
|
+
code?: string;
|
|
4
|
+
cause?: Error;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export class ConnectorError extends Error {
|
|
8
|
+
constructor(
|
|
9
|
+
message: string,
|
|
10
|
+
public readonly options?: ConnectorErrorOptions,
|
|
11
|
+
) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "ConnectorError";
|
|
14
|
+
if (options?.cause) {
|
|
15
|
+
this.cause = options.cause;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get fix(): string | undefined {
|
|
20
|
+
return this.options?.fix;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get code(): string | undefined {
|
|
24
|
+
return this.options?.code;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class SDKError extends ConnectorError {
|
|
29
|
+
constructor(message: string, options?: ConnectorErrorOptions) {
|
|
30
|
+
super(message, options);
|
|
31
|
+
this.name = "SDKError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class AuthError extends ConnectorError {
|
|
36
|
+
constructor(message: string, options?: ConnectorErrorOptions) {
|
|
37
|
+
super(message, options);
|
|
38
|
+
this.name = "AuthError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ValidationErrorOptions = ConnectorErrorOptions & {
|
|
43
|
+
zodError?: unknown;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export class ValidationError extends ConnectorError {
|
|
47
|
+
readonly zodError?: unknown;
|
|
48
|
+
|
|
49
|
+
constructor(message: string, options?: ValidationErrorOptions) {
|
|
50
|
+
super(message, options);
|
|
51
|
+
this.name = "ValidationError";
|
|
52
|
+
this.zodError = options?.zodError;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type TransportErrorOptions = ConnectorErrorOptions & {
|
|
57
|
+
status?: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export class TransportError extends ConnectorError {
|
|
61
|
+
readonly status?: number;
|
|
62
|
+
|
|
63
|
+
constructor(message: string, options?: TransportErrorOptions) {
|
|
64
|
+
super(message, options);
|
|
65
|
+
this.name = "TransportError";
|
|
66
|
+
this.status = options?.status;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// placeholder to keep bun test from erroring on zero test files
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @apifuse/connector-sdk
|
|
2
|
+
|
|
3
|
+
export { z } from "zod";
|
|
4
|
+
export type {
|
|
5
|
+
ConnectorConfig,
|
|
6
|
+
ConnectorsYaml,
|
|
7
|
+
PoolSizeRange,
|
|
8
|
+
} from "./config/connectors-yaml";
|
|
9
|
+
export {
|
|
10
|
+
CdpSchema,
|
|
11
|
+
ConnectorConfigSchema,
|
|
12
|
+
ConnectorsYamlSchema,
|
|
13
|
+
ContainerSchema,
|
|
14
|
+
loadConnectorsYaml,
|
|
15
|
+
parseConnectorsYaml,
|
|
16
|
+
resolveConnectorConfig,
|
|
17
|
+
SecuritySchema,
|
|
18
|
+
} from "./config/connectors-yaml";
|
|
19
|
+
export type {
|
|
20
|
+
ApiFuseConfig,
|
|
21
|
+
BrowserConfig,
|
|
22
|
+
ProxyConfig,
|
|
23
|
+
SessionConfig,
|
|
24
|
+
} from "./config/loader";
|
|
25
|
+
export { defineConfig, loadApiFuseConfig } from "./config/loader";
|
|
26
|
+
export { defineConnector } from "./define";
|
|
27
|
+
export type { DevServerOptions } from "./dev";
|
|
28
|
+
export { createDevServer, startDevServer } from "./dev";
|
|
29
|
+
export * from "./errors";
|
|
30
|
+
export * from "./protocol";
|
|
31
|
+
export * from "./recipes/gov-api";
|
|
32
|
+
export * from "./recipes/rest-api";
|
|
33
|
+
export type { AuthManager } from "./runtime/auth";
|
|
34
|
+
export { createAuthManager } from "./runtime/auth";
|
|
35
|
+
export type { BrowserClientOptions } from "./runtime/browser";
|
|
36
|
+
export { BrowserClient, createBrowserClient } from "./runtime/browser";
|
|
37
|
+
export { getConnectorBaseUrl } from "./runtime/connector";
|
|
38
|
+
export { executeOperation } from "./runtime/executor";
|
|
39
|
+
export { createHttpClient } from "./runtime/http";
|
|
40
|
+
export type { Insight, InsightSeverity } from "./runtime/insights";
|
|
41
|
+
export { generateInsights } from "./runtime/insights";
|
|
42
|
+
export {
|
|
43
|
+
type InstrumentationOptions,
|
|
44
|
+
type InstrumentedConnectorContext,
|
|
45
|
+
wrapWithInstrumentation,
|
|
46
|
+
} from "./runtime/instrumentation";
|
|
47
|
+
export type {
|
|
48
|
+
SessionFetch,
|
|
49
|
+
SupabaseSessionStoreConfig,
|
|
50
|
+
} from "./runtime/session";
|
|
51
|
+
export {
|
|
52
|
+
createSessionStore,
|
|
53
|
+
createSqliteSessionStore,
|
|
54
|
+
SupabaseSessionStore,
|
|
55
|
+
} from "./runtime/session";
|
|
56
|
+
export { createStateContext } from "./runtime/state";
|
|
57
|
+
export { createTlsClient } from "./runtime/tls";
|
|
58
|
+
export {
|
|
59
|
+
type CreateTraceContextOptions,
|
|
60
|
+
createTraceContext,
|
|
61
|
+
type Span,
|
|
62
|
+
type TraceContext,
|
|
63
|
+
} from "./runtime/trace";
|
|
64
|
+
export { createConnectorServer } from "./serve";
|
|
65
|
+
export { getStealthProfile, listStealthProfiles } from "./stealth/profiles";
|
|
66
|
+
export {
|
|
67
|
+
runStandardTests,
|
|
68
|
+
snapshotTransform,
|
|
69
|
+
toMatchShape,
|
|
70
|
+
} from "./testing";
|
|
71
|
+
export type {
|
|
72
|
+
ApiFuseResponse,
|
|
73
|
+
AuthConfig,
|
|
74
|
+
AuthContext,
|
|
75
|
+
AuthField,
|
|
76
|
+
AuthMode,
|
|
77
|
+
BrowserEngine,
|
|
78
|
+
BrowserOptions,
|
|
79
|
+
ConnectorContext,
|
|
80
|
+
ConnectorDefinition,
|
|
81
|
+
ConnectorMeta,
|
|
82
|
+
HttpClient,
|
|
83
|
+
HttpResponse,
|
|
84
|
+
OperationDefinition,
|
|
85
|
+
RequestOptions,
|
|
86
|
+
SessionStore,
|
|
87
|
+
StateContext,
|
|
88
|
+
StealthPlatform,
|
|
89
|
+
StealthProfile,
|
|
90
|
+
TlsClient,
|
|
91
|
+
TlsFetchOptions,
|
|
92
|
+
TlsResponse,
|
|
93
|
+
TlsSession,
|
|
94
|
+
TraceConfig,
|
|
95
|
+
TraceSpan,
|
|
96
|
+
} from "./types";
|
|
97
|
+
export * from "./utils/date";
|
|
98
|
+
export * from "./utils/parse";
|
|
99
|
+
export * from "./utils/text";
|
|
100
|
+
export * from "./utils/transform";
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container Protocol — Communication contract between Route Server and Connector Containers.
|
|
3
|
+
*
|
|
4
|
+
* All connector containers expose HTTP endpoints. Route Server sends requests
|
|
5
|
+
* and receives responses in these formats.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════
|
|
9
|
+
// Execute Operation
|
|
10
|
+
// POST /execute/:operationId
|
|
11
|
+
// ═══════════════════════════════════════════════════════════════
|
|
12
|
+
|
|
13
|
+
export interface ExecuteRequest {
|
|
14
|
+
/** Validated input (already passed Zod validation by Route Server) */
|
|
15
|
+
input: unknown;
|
|
16
|
+
/** Decrypted session key-value pairs (loaded by Route Server from DB) */
|
|
17
|
+
session?: Record<string, string>;
|
|
18
|
+
/** Trace context for distributed tracing */
|
|
19
|
+
trace?: { traceId: string; spanId: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ExecuteResponse {
|
|
23
|
+
/** Operation result (will be Zod-validated by Route Server) */
|
|
24
|
+
data: unknown;
|
|
25
|
+
/** Session mutations to persist back to DB (key→value for set, key→null for delete) */
|
|
26
|
+
sessionPatch?: Record<string, string | null>;
|
|
27
|
+
/** Trace spans collected during execution */
|
|
28
|
+
trace?: { spans: unknown[] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════
|
|
32
|
+
// Connect (Auth Exchange)
|
|
33
|
+
// POST /connect
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════
|
|
35
|
+
|
|
36
|
+
export interface ConnectRequest {
|
|
37
|
+
/** User-provided credentials */
|
|
38
|
+
credentials: Record<string, string>;
|
|
39
|
+
/** Existing session (for re-auth scenarios) */
|
|
40
|
+
session?: Record<string, string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Successful connection */
|
|
44
|
+
export interface ConnectSuccessResponse {
|
|
45
|
+
status: "success";
|
|
46
|
+
/** Display name for the connected upstream account */
|
|
47
|
+
accountRef?: string;
|
|
48
|
+
/** Session data to persist (full session state after exchange) */
|
|
49
|
+
sessionPatch: Record<string, string>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Connection requires additional field (e.g., OTP) */
|
|
53
|
+
export interface ConnectPendingFieldResponse {
|
|
54
|
+
status: "pending_field";
|
|
55
|
+
/** Name of the field to request from user */
|
|
56
|
+
field: string;
|
|
57
|
+
/** Label to display to user */
|
|
58
|
+
fieldLabel?: string;
|
|
59
|
+
/** Opaque token to resume the connection flow */
|
|
60
|
+
resumeToken: string;
|
|
61
|
+
/** Partial session state to persist (intermediate state) */
|
|
62
|
+
sessionPatch?: Record<string, string>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Connection failed */
|
|
66
|
+
export interface ConnectFailedResponse {
|
|
67
|
+
status: "failed";
|
|
68
|
+
/** Error code for programmatic handling */
|
|
69
|
+
error: string;
|
|
70
|
+
/** Human-readable error message */
|
|
71
|
+
message?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type ConnectResponse =
|
|
75
|
+
| ConnectSuccessResponse
|
|
76
|
+
| ConnectPendingFieldResponse
|
|
77
|
+
| ConnectFailedResponse;
|
|
78
|
+
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════
|
|
80
|
+
// Resume (Continue deferred auth)
|
|
81
|
+
// POST /connect/resume
|
|
82
|
+
// ═══════════════════════════════════════════════════════════════
|
|
83
|
+
|
|
84
|
+
export interface ResumeRequest {
|
|
85
|
+
/** Resume token from ConnectPendingFieldResponse */
|
|
86
|
+
resumeToken: string;
|
|
87
|
+
/** User-provided value for the requested field */
|
|
88
|
+
fieldValue: string;
|
|
89
|
+
/** Current session state */
|
|
90
|
+
session?: Record<string, string>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type ResumeResponse = ConnectResponse; // same as ConnectResponse
|
|
94
|
+
|
|
95
|
+
// ═══════════════════════════════════════════════════════════════
|
|
96
|
+
// Refresh (Auth token refresh)
|
|
97
|
+
// POST /refresh
|
|
98
|
+
// ═══════════════════════════════════════════════════════════════
|
|
99
|
+
|
|
100
|
+
export interface RefreshRequest {
|
|
101
|
+
/** Current session to refresh */
|
|
102
|
+
session: Record<string, string>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface RefreshSuccessResponse {
|
|
106
|
+
status: "success";
|
|
107
|
+
/** Updated session data */
|
|
108
|
+
sessionPatch: Record<string, string>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface RefreshFailedResponse {
|
|
112
|
+
status: "failed";
|
|
113
|
+
error: string;
|
|
114
|
+
message?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type RefreshResponse = RefreshSuccessResponse | RefreshFailedResponse;
|
|
118
|
+
|
|
119
|
+
// ═══════════════════════════════════════════════════════════════
|
|
120
|
+
// Disconnect
|
|
121
|
+
// POST /disconnect
|
|
122
|
+
// ═══════════════════════════════════════════════════════════════
|
|
123
|
+
|
|
124
|
+
export interface DisconnectRequest {
|
|
125
|
+
/** Session to clean up */
|
|
126
|
+
session: Record<string, string>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface DisconnectResponse {
|
|
130
|
+
status: "success" | "failed";
|
|
131
|
+
error?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ═══════════════════════════════════════════════════════════════
|
|
135
|
+
// Health
|
|
136
|
+
// GET /health
|
|
137
|
+
// ═══════════════════════════════════════════════════════════════
|
|
138
|
+
|
|
139
|
+
export interface HealthResponse {
|
|
140
|
+
status: "ok" | "degraded" | "error";
|
|
141
|
+
connector: string;
|
|
142
|
+
version: string;
|
|
143
|
+
uptime?: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ═══════════════════════════════════════════════════════════════
|
|
147
|
+
// Schema
|
|
148
|
+
// GET /schema/:operationId
|
|
149
|
+
// ═══════════════════════════════════════════════════════════════
|
|
150
|
+
|
|
151
|
+
export interface SchemaResponse {
|
|
152
|
+
operationId: string;
|
|
153
|
+
description?: string;
|
|
154
|
+
input: Record<string, unknown>; // JSON Schema
|
|
155
|
+
output: Record<string, unknown>; // JSON Schema
|
|
156
|
+
hints?: Record<string, string>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ═══════════════════════════════════════════════════════════════
|
|
160
|
+
// Error envelope (used for any error response)
|
|
161
|
+
// ═══════════════════════════════════════════════════════════════
|
|
162
|
+
|
|
163
|
+
export interface ContainerErrorResponse {
|
|
164
|
+
error: {
|
|
165
|
+
code: string;
|
|
166
|
+
message: string;
|
|
167
|
+
details?: unknown;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ═══════════════════════════════════════════════════════════════
|
|
172
|
+
// Standard endpoint paths
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════
|
|
174
|
+
|
|
175
|
+
export const CONTAINER_ENDPOINTS = {
|
|
176
|
+
execute: "/execute/:operationId",
|
|
177
|
+
connect: "/connect",
|
|
178
|
+
resume: "/connect/resume",
|
|
179
|
+
refresh: "/refresh",
|
|
180
|
+
disconnect: "/disconnect",
|
|
181
|
+
health: "/health",
|
|
182
|
+
schema: "/schema/:operationId",
|
|
183
|
+
} as const;
|