@apifuse/provider-sdk 2.2.0-beta.22 → 2.2.0-beta.24
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/CHANGELOG.md +8 -0
- package/bin/apifuse-dev.ts +4 -0
- package/bin/apifuse-pack-types.ts +234 -38
- package/bin/apifuse-perf.ts +15 -12
- package/bin/apifuse-record.ts +4 -0
- package/dist/config/loader.d.ts +8 -19
- package/dist/config/loader.js +28 -86
- package/dist/contract-types.d.ts +1 -0
- package/dist/contract.js +2 -0
- package/dist/define.d.ts +5 -1
- package/dist/define.js +79 -6
- package/dist/error-resolution.js +5 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/provider.d.ts +1 -1
- package/dist/runtime/auth-flow.d.ts +2 -1
- package/dist/runtime/auth-flow.js +4 -0
- package/dist/runtime/browser.js +78 -9
- package/dist/runtime/http.js +0 -1
- package/dist/runtime/instrumentation.js +26 -1
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +8 -0
- package/dist/runtime/resolver-vendors/bindings.js +15 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
- package/dist/runtime/resolver-vendors/browser.js +287 -0
- package/dist/runtime/resolver-vendors/types.d.ts +42 -0
- package/dist/runtime/resolver-vendors/types.js +57 -0
- package/dist/runtime/resolver.d.ts +39 -0
- package/dist/runtime/resolver.js +414 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +245 -141
- package/dist/runtime/stealth.js +3 -6
- package/dist/runtime/stt.js +1 -12
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/server/serve.d.ts +6 -1
- package/dist/server/serve.js +39 -8
- package/dist/testing/run.js +10 -0
- package/dist/types.d.ts +163 -4
- package/package.json +1 -1
- package/src/config/loader.ts +35 -111
- package/src/contract-types.ts +1 -0
- package/src/contract.ts +2 -0
- package/src/define.ts +121 -7
- package/src/error-resolution.ts +5 -0
- package/src/index.ts +45 -1
- package/src/provider.ts +1 -0
- package/src/runtime/auth-flow.ts +6 -0
- package/src/runtime/browser.ts +139 -19
- package/src/runtime/http.ts +0 -1
- package/src/runtime/instrumentation.ts +36 -2
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/resolver-vendors/bindings.ts +31 -0
- package/src/runtime/resolver-vendors/browser.ts +420 -0
- package/src/runtime/resolver-vendors/types.ts +113 -0
- package/src/runtime/resolver.ts +668 -0
- package/src/runtime/state.ts +323 -166
- package/src/runtime/stealth.ts +3 -6
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/server/serve.ts +80 -5
- package/src/testing/run.ts +15 -0
- package/src/types.ts +188 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.24
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit 0f21a2959dcbb9b96fe426259259d0870cfc537f.
|
|
6
|
+
|
|
7
|
+
## 2.2.0-beta.23
|
|
8
|
+
|
|
9
|
+
- Release candidate for main commit 36b3bedc9c14913c4152c4631cc87de71062f21f.
|
|
10
|
+
|
|
3
11
|
## 2.2.0-beta.22
|
|
4
12
|
|
|
5
13
|
- Release candidate for main commit b8bf920b5ca053d1bf43018167fd4eedff01700d.
|
package/bin/apifuse-dev.ts
CHANGED
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
createCredentialContext,
|
|
9
9
|
createEnvContext,
|
|
10
10
|
createHttpClient,
|
|
11
|
+
createOcrClientFromEnv,
|
|
11
12
|
createProviderCache,
|
|
12
13
|
createProviderChoiceContext,
|
|
14
|
+
createUnsupportedResolverClient,
|
|
13
15
|
createStealthClient,
|
|
14
16
|
createSttClientFromEnv,
|
|
15
17
|
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
@@ -96,7 +98,9 @@ export function createProviderContext(provider: ProviderDefinition): {
|
|
|
96
98
|
state,
|
|
97
99
|
trace: createTraceContext(),
|
|
98
100
|
stealth: createStealthClient("http://localhost"),
|
|
101
|
+
ocr: createOcrClientFromEnv(provider.ocr),
|
|
99
102
|
stt: createSttClientFromEnv(provider.stt),
|
|
103
|
+
resolver: createUnsupportedResolverClient("Resolver is not available in apifuse dev"),
|
|
100
104
|
choice: createProviderChoiceContext({
|
|
101
105
|
providerId: provider.id,
|
|
102
106
|
env,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// 3. A Node (not bun) ESM runtime import — node does not tolerate
|
|
19
19
|
// extensionless relative specifiers, bun does.
|
|
20
20
|
//
|
|
21
|
-
//
|
|
21
|
+
// Deliberate negative controls prove the fixture compiler actually fails on
|
|
22
22
|
// type errors, so this check cannot rot into a false-positive green.
|
|
23
23
|
|
|
24
24
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
@@ -36,6 +36,182 @@ const PACK_RESULT_SCHEMA = z.array(
|
|
|
36
36
|
const KEEP_TEMP = process.env.APIFUSE__PACK_TYPES__KEEP_TEMP === "1";
|
|
37
37
|
const sdkRoot = process.cwd();
|
|
38
38
|
|
|
39
|
+
const NEGATIVE_CONTROLS = [
|
|
40
|
+
{
|
|
41
|
+
filename: "negative-control-provider-error-name.ts",
|
|
42
|
+
expectedCode: "TS2322",
|
|
43
|
+
description: "ProviderError.name remains a string",
|
|
44
|
+
source: [
|
|
45
|
+
'import { ProviderError } from "@apifuse/provider-sdk";',
|
|
46
|
+
"",
|
|
47
|
+
"// Intentionally wrong: name is a string. If this file compiles, the",
|
|
48
|
+
"// fixture consumer is not actually type-checking and the guard is void.",
|
|
49
|
+
'export const mustNotCompile: number = new ProviderError("boom").name;',
|
|
50
|
+
"",
|
|
51
|
+
].join("\n"),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
filename: "negative-control-unknown-resolver-vendor.ts",
|
|
55
|
+
expectedCode: "TS2322",
|
|
56
|
+
description: "ProviderResolverConfig rejects an unknown vendor",
|
|
57
|
+
source: [
|
|
58
|
+
'import type { ProviderResolverConfig } from "@apifuse/provider-sdk";',
|
|
59
|
+
"",
|
|
60
|
+
"export const mustNotCompile: ProviderResolverConfig = {",
|
|
61
|
+
'\tvendors: ["unknown-vendor"],',
|
|
62
|
+
'\tkinds: ["turnstile"],',
|
|
63
|
+
"};",
|
|
64
|
+
"",
|
|
65
|
+
].join("\n"),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
filename: "negative-control-define-provider-resolver-vendor.ts",
|
|
69
|
+
expectedCode: "TS2322",
|
|
70
|
+
description: "defineProvider rejects an unknown resolver vendor",
|
|
71
|
+
source: [
|
|
72
|
+
'import { defineProvider, z } from "@apifuse/provider-sdk";',
|
|
73
|
+
"",
|
|
74
|
+
"export const mustNotCompile = defineProvider({",
|
|
75
|
+
'\tid: "invalid-resolver-vendor",',
|
|
76
|
+
'\tversion: "1.0.0",',
|
|
77
|
+
'\truntime: "standard",',
|
|
78
|
+
'\tresolver: { vendors: ["unknown-vendor"], kinds: ["turnstile"] },',
|
|
79
|
+
'\tmeta: { displayName: "Invalid Resolver Vendor", descriptionKey: "meta.description", category: "test" },',
|
|
80
|
+
"\toperations: {",
|
|
81
|
+
"\t\tprobe: {",
|
|
82
|
+
"\t\t\tinput: z.object({}),",
|
|
83
|
+
"\t\t\toutput: z.object({ ok: z.boolean() }),",
|
|
84
|
+
"\t\t\thandler: async () => ({ ok: true }),",
|
|
85
|
+
'\t\t\thealthCheckUnsupported: { reason: "type fixture" },',
|
|
86
|
+
"\t\t},",
|
|
87
|
+
"\t},",
|
|
88
|
+
"});",
|
|
89
|
+
"",
|
|
90
|
+
].join("\n"),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
filename: "negative-control-unknown-challenge-kind.ts",
|
|
94
|
+
expectedCode: "TS2820",
|
|
95
|
+
description: "ProviderChallenge rejects an unknown challenge kind",
|
|
96
|
+
source: [
|
|
97
|
+
'import type { ProviderChallenge } from "@apifuse/provider-sdk";',
|
|
98
|
+
"",
|
|
99
|
+
"export const mustNotCompile: ProviderChallenge = {",
|
|
100
|
+
'\tkind: "funcaptcha",',
|
|
101
|
+
'\tsiteKey: "key",',
|
|
102
|
+
'\tpageUrl: "https://example.com",',
|
|
103
|
+
"};",
|
|
104
|
+
"",
|
|
105
|
+
].join("\n"),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
filename: "negative-control-provider-challenge-kind.ts",
|
|
109
|
+
expectedCode: "TS2820",
|
|
110
|
+
description: "ProviderChallengeKind rejects an unknown challenge kind",
|
|
111
|
+
source: [
|
|
112
|
+
'import type { ProviderChallengeKind } from "@apifuse/provider-sdk";',
|
|
113
|
+
"",
|
|
114
|
+
'export const mustNotCompile: ProviderChallengeKind = "funcaptcha";',
|
|
115
|
+
"",
|
|
116
|
+
].join("\n"),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
filename: "negative-control-cookie-solution-token-read.ts",
|
|
120
|
+
expectedCode: "TS2339",
|
|
121
|
+
description: "ChallengeSolution requires narrowing before reading token",
|
|
122
|
+
source: [
|
|
123
|
+
'import type { ChallengeSolution } from "@apifuse/provider-sdk";',
|
|
124
|
+
"",
|
|
125
|
+
"export function mustNotCompile(solution: ChallengeSolution): string {",
|
|
126
|
+
"\treturn solution.token;",
|
|
127
|
+
"}",
|
|
128
|
+
"",
|
|
129
|
+
].join("\n"),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
filename: "negative-control-cookie-challenge-site-key.ts",
|
|
133
|
+
expectedCode: "TS2353",
|
|
134
|
+
description: "cookie-family challenges reject token-family fields",
|
|
135
|
+
source: [
|
|
136
|
+
'import type { ProviderChallenge } from "@apifuse/provider-sdk";',
|
|
137
|
+
"",
|
|
138
|
+
"export const mustNotCompile: ProviderChallenge = {",
|
|
139
|
+
'\tkind: "cloudflare_interstitial",',
|
|
140
|
+
'\tpageUrl: "https://example.com",',
|
|
141
|
+
'\tsiteKey: "not-allowed",',
|
|
142
|
+
"};",
|
|
143
|
+
"",
|
|
144
|
+
].join("\n"),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
filename: "negative-control-recaptcha-v3-action.ts",
|
|
148
|
+
expectedCode: "TS2322",
|
|
149
|
+
description: "recaptcha_v3 challenges require action",
|
|
150
|
+
source: [
|
|
151
|
+
'import type { ProviderChallenge } from "@apifuse/provider-sdk";',
|
|
152
|
+
"",
|
|
153
|
+
"export const mustNotCompile: ProviderChallenge = {",
|
|
154
|
+
'\tkind: "recaptcha_v3",',
|
|
155
|
+
'\tsiteKey: "key",',
|
|
156
|
+
'\tpageUrl: "https://example.com",',
|
|
157
|
+
"};",
|
|
158
|
+
"",
|
|
159
|
+
].join("\n"),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
filename: "negative-control-browser-cookie-expires.ts",
|
|
163
|
+
expectedCode: "TS2322",
|
|
164
|
+
description: "BrowserCookie.expires remains optional for session cookies",
|
|
165
|
+
source: [
|
|
166
|
+
'import type { BrowserCookie } from "@apifuse/provider-sdk";',
|
|
167
|
+
"",
|
|
168
|
+
"export function mustNotCompile(cookie: BrowserCookie): number {",
|
|
169
|
+
"\treturn cookie.expires;",
|
|
170
|
+
"}",
|
|
171
|
+
"",
|
|
172
|
+
].join("\n"),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
filename: "negative-control-resolver-runtime-identity.ts",
|
|
176
|
+
expectedCode: "TS2353",
|
|
177
|
+
description: "ResolverRuntimeOptions does not accept caller-synthesized identity",
|
|
178
|
+
source: [
|
|
179
|
+
'import type { ResolverRuntimeOptions } from "@apifuse/provider-sdk";',
|
|
180
|
+
"",
|
|
181
|
+
"export const mustNotCompile: ResolverRuntimeOptions = {",
|
|
182
|
+
'\tidentity: "caller-controlled",',
|
|
183
|
+
"};",
|
|
184
|
+
"",
|
|
185
|
+
].join("\n"),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
filename: "negative-control-resolver-runtime-allowed-hosts.ts",
|
|
189
|
+
expectedCode: "TS2322",
|
|
190
|
+
description: "ResolverRuntimeOptions.allowedHosts accepts only host strings",
|
|
191
|
+
source: [
|
|
192
|
+
'import type { ResolverRuntimeOptions } from "@apifuse/provider-sdk";',
|
|
193
|
+
"",
|
|
194
|
+
"export const mustNotCompile: ResolverRuntimeOptions = {",
|
|
195
|
+
'\tallowedHosts: ["example.com", 42],',
|
|
196
|
+
"};",
|
|
197
|
+
"",
|
|
198
|
+
].join("\n"),
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
filename: "negative-control-resolver-signal.ts",
|
|
202
|
+
expectedCode: "TS2345",
|
|
203
|
+
description: "ResolverContext.solve accepts only AbortSignal as its optional signal",
|
|
204
|
+
source: [
|
|
205
|
+
'import type { ProviderChallenge, ResolverContext } from "@apifuse/provider-sdk";',
|
|
206
|
+
"",
|
|
207
|
+
"declare const resolver: ResolverContext;",
|
|
208
|
+
"declare const challenge: ProviderChallenge;",
|
|
209
|
+
"resolver.solve(challenge, { aborted: false });",
|
|
210
|
+
"",
|
|
211
|
+
].join("\n"),
|
|
212
|
+
},
|
|
213
|
+
] as const;
|
|
214
|
+
|
|
39
215
|
const tempRoot = mkdtempSync(join(tmpdir(), "apifuse-provider-sdk-pack-types-"));
|
|
40
216
|
const packDir = join(tempRoot, "pack");
|
|
41
217
|
const consumerDir = join(tempRoot, "consumer");
|
|
@@ -123,8 +299,8 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
123
299
|
writeFileSync(
|
|
124
300
|
join(consumerDir, "consumer.ts"),
|
|
125
301
|
[
|
|
126
|
-
'import { ProviderError, resolveProxy, SessionExpiredError, z } from "@apifuse/provider-sdk";',
|
|
127
|
-
'import type { NativeNetworkClient, NativeNetworkConnection, NativeProviderConfig, NativeProviderContext, NativeTcpEgressGrant, ProviderContext, ProviderFileRef, ProviderFilesContext, ProviderResolvedFile } from "@apifuse/provider-sdk";',
|
|
302
|
+
'import { defineProvider, invalidateResolverSolution, ProviderError, resolveProxy, SessionExpiredError, z } from "@apifuse/provider-sdk";',
|
|
303
|
+
'import type { BrowserCookie, ChallengeSolution, NativeNetworkClient, NativeNetworkConnection, NativeProviderConfig, NativeProviderContext, NativeTcpEgressGrant, ProviderChallenge, ProviderContext, ProviderFileRef, ProviderFilesContext, ProviderResolvedFile, ProviderResolverConfig, ResolverContext, ResolverRuntimeOptions } from "@apifuse/provider-sdk";',
|
|
128
304
|
'import type { ProxyProtocol, ProxyResolutionOptions, ProxyResolutionSource, ProxyVendorName, RequestOptions, ResolvedProxyConfig } from "@apifuse/provider-sdk";',
|
|
129
305
|
'import { defineCredentialsAuth } from "@apifuse/provider-sdk/provider";',
|
|
130
306
|
'import type { NativeNetworkClient as ProviderEntryNativeNetworkClient, ProviderFilesContext as ProviderEntryFilesContext } from "@apifuse/provider-sdk/provider";',
|
|
@@ -164,6 +340,25 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
164
340
|
"const providerContext = undefined as unknown as ProviderContext;",
|
|
165
341
|
"const optionalFiles: ProviderFilesContext | undefined = providerContext.files;",
|
|
166
342
|
"const optionalNative: NativeProviderContext | undefined = providerContext.native;",
|
|
343
|
+
"export const providerResolver: ResolverContext = providerContext.resolver;",
|
|
344
|
+
'export const resolverRuntimeOptions: ResolverRuntimeOptions = { allowedHosts: ["example.com"], cache: providerContext.cache };',
|
|
345
|
+
"",
|
|
346
|
+
'export const turnstileChallenge: ProviderChallenge = { kind: "turnstile", siteKey: "key", pageUrl: "https://example.com" };',
|
|
347
|
+
"export const abortableResolverSolve = providerResolver.solve(turnstileChallenge, new AbortController().signal);",
|
|
348
|
+
'export const recaptchaV2Challenge: ProviderChallenge = { kind: "recaptcha_v2", siteKey: "key", pageUrl: "https://example.com" };',
|
|
349
|
+
'export const recaptchaV3Challenge: ProviderChallenge = { kind: "recaptcha_v3", siteKey: "key", pageUrl: "https://example.com", action: "login" };',
|
|
350
|
+
'export const hcaptchaChallenge: ProviderChallenge = { kind: "hcaptcha", siteKey: "key", pageUrl: "https://example.com" };',
|
|
351
|
+
'export const cloudflareInterstitialChallenge: ProviderChallenge = { kind: "cloudflare_interstitial", pageUrl: "https://example.com", blockedHtml: "<html></html>" };',
|
|
352
|
+
'export const awsWafChallenge: ProviderChallenge = { kind: "aws_waf", pageUrl: "https://example.com", captchaScript: "script" };',
|
|
353
|
+
'export const tokenSolution: ChallengeSolution = { form: "token", token: "solved-token" };',
|
|
354
|
+
'export const cookieSolution: ChallengeSolution = { form: "cookies", cookies: { cf_clearance: "clearance" }, userAgent: "fixture-agent" };',
|
|
355
|
+
"export const invalidation = invalidateResolverSolution(providerContext.resolver, awsWafChallenge, cookieSolution);",
|
|
356
|
+
'export const resolverConfig: ProviderResolverConfig = { vendors: ["browser", "capsolver"], kinds: ["cloudflare_interstitial", "turnstile"] };',
|
|
357
|
+
'export const resolverProvider = defineProvider({ id: "pack-types-resolver", version: "1.0.0", runtime: "standard", resolver: resolverConfig, meta: { displayName: "Pack Types Resolver", descriptionKey: "meta.description", category: "test" }, operations: { probe: { input: z.object({}), output: z.object({ ok: z.boolean() }), handler: async () => ({ ok: true }), healthCheckUnsupported: { reason: "type fixture" } } } });',
|
|
358
|
+
"export const resolverContext: ResolverContext = { solve: async () => tokenSolution };",
|
|
359
|
+
'export const browserCookie: BrowserCookie = { name: "persistent-id", value: "persistent-token", domain: "example.com", path: "/", expires: 1786698176, httpOnly: true, secure: true };',
|
|
360
|
+
'const browserPage = undefined as unknown as Awaited<ReturnType<ProviderContext["browser"]["newPage"]>>;',
|
|
361
|
+
"export const browserCookies: Promise<readonly BrowserCookie[]> = browserPage.cookies();",
|
|
167
362
|
'const queryCredentialOptions: RequestOptions = { sensitiveParams: { serviceKey: "type-test-key" } };',
|
|
168
363
|
"",
|
|
169
364
|
"export const witnesses = {",
|
|
@@ -190,6 +385,8 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
190
385
|
" nativeConfig,",
|
|
191
386
|
" optionalFiles,",
|
|
192
387
|
" optionalNative,",
|
|
388
|
+
" browserCookie,",
|
|
389
|
+
" browserCookies,",
|
|
193
390
|
" queryCredentialOptions,",
|
|
194
391
|
" defineCredentialsAuth,",
|
|
195
392
|
" extractProviderContract,",
|
|
@@ -201,17 +398,9 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
201
398
|
].join("\n"),
|
|
202
399
|
);
|
|
203
400
|
|
|
204
|
-
|
|
205
|
-
join(consumerDir,
|
|
206
|
-
|
|
207
|
-
'import { ProviderError } from "@apifuse/provider-sdk";',
|
|
208
|
-
"",
|
|
209
|
-
"// Intentionally wrong: name is a string. If this file compiles, the",
|
|
210
|
-
"// fixture consumer is not actually type-checking and the guard is void.",
|
|
211
|
-
'export const mustNotCompile: number = new ProviderError("boom").name;',
|
|
212
|
-
"",
|
|
213
|
-
].join("\n"),
|
|
214
|
-
);
|
|
401
|
+
for (const negativeControl of NEGATIVE_CONTROLS) {
|
|
402
|
+
writeFileSync(join(consumerDir, negativeControl.filename), negativeControl.source);
|
|
403
|
+
}
|
|
215
404
|
}
|
|
216
405
|
|
|
217
406
|
function compileFixtureConsumer(consumerDir: string): void {
|
|
@@ -223,31 +412,38 @@ function compileFixtureConsumer(consumerDir: string): void {
|
|
|
223
412
|
}
|
|
224
413
|
|
|
225
414
|
function assertNegativeControlFails(consumerDir: string): void {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (result.status === 0) {
|
|
243
|
-
throw new Error(
|
|
244
|
-
"Negative control compiled cleanly: the fixture consumer is not detecting type errors, so the nodenext guard proves nothing.",
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
if (!`${result.stdout}\n${result.stderr}`.includes("TS2322")) {
|
|
248
|
-
throw new Error(
|
|
249
|
-
`Negative control failed for an unexpected reason (wanted TS2322):\n${result.stdout}\n${result.stderr}`,
|
|
415
|
+
for (const negativeControl of NEGATIVE_CONTROLS) {
|
|
416
|
+
const result = spawnSync(
|
|
417
|
+
"bun",
|
|
418
|
+
[
|
|
419
|
+
join(consumerDir, "node_modules", ".bin", "tsc"),
|
|
420
|
+
"--target",
|
|
421
|
+
"ES2022",
|
|
422
|
+
"--module",
|
|
423
|
+
"nodenext",
|
|
424
|
+
"--moduleResolution",
|
|
425
|
+
"nodenext",
|
|
426
|
+
"--strict",
|
|
427
|
+
"--noEmit",
|
|
428
|
+
negativeControl.filename,
|
|
429
|
+
],
|
|
430
|
+
{ cwd: consumerDir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
|
|
250
431
|
);
|
|
432
|
+
if (result.status === 0) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
'Negative control "' +
|
|
435
|
+
negativeControl.description +
|
|
436
|
+
'" (' +
|
|
437
|
+
negativeControl.filename +
|
|
438
|
+
") compiled cleanly: the fixture consumer is not detecting its error, so the guard proves nothing.",
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
442
|
+
if (!output.includes(negativeControl.expectedCode)) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`Negative control "${negativeControl.description}" (${negativeControl.filename}) failed for an unexpected reason (wanted ${negativeControl.expectedCode}):\n${output}`,
|
|
445
|
+
);
|
|
446
|
+
}
|
|
251
447
|
}
|
|
252
448
|
}
|
|
253
449
|
|
package/bin/apifuse-perf.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type ProviderContext,
|
|
21
21
|
type ProviderDefinition,
|
|
22
22
|
ProviderError,
|
|
23
|
+
resolveProxy,
|
|
23
24
|
type Span,
|
|
24
25
|
type StealthClient,
|
|
25
26
|
type StealthResponse,
|
|
@@ -122,7 +123,7 @@ export async function main() {
|
|
|
122
123
|
|
|
123
124
|
let proxySuite: ProfileSuite | undefined;
|
|
124
125
|
if (args.compareProxy) {
|
|
125
|
-
assertProxyConfigured(
|
|
126
|
+
await assertProxyConfigured(provider);
|
|
126
127
|
proxySuite = await runProfileSuite({
|
|
127
128
|
args,
|
|
128
129
|
config,
|
|
@@ -454,14 +455,20 @@ async function loadFixtureReplay(providerDirectory: string): Promise<FixtureRepl
|
|
|
454
455
|
};
|
|
455
456
|
}
|
|
456
457
|
|
|
457
|
-
function assertProxyConfigured(
|
|
458
|
-
|
|
459
|
-
|
|
458
|
+
async function assertProxyConfigured(provider: ProviderDefinition): Promise<void> {
|
|
459
|
+
const policy = provider.proxy;
|
|
460
|
+
if (!policy || typeof policy !== "object" || policy.mode === "disabled") {
|
|
461
|
+
throw new Error(
|
|
462
|
+
"--compare-proxy requires an enabled ProviderProxyPolicy on the provider.",
|
|
463
|
+
);
|
|
460
464
|
}
|
|
461
465
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
466
|
+
const resolved = await resolveProxy({ proxyPolicy: policy });
|
|
467
|
+
if (!resolved.url) {
|
|
468
|
+
throw new Error(
|
|
469
|
+
"--compare-proxy could not resolve the provider proxy policy; configure its allocator credentials (for smartproxy, APIFUSE__PROXY__SMARTPROXY_APP_KEY).",
|
|
470
|
+
);
|
|
471
|
+
}
|
|
465
472
|
}
|
|
466
473
|
|
|
467
474
|
async function runProfileSuite(options: {
|
|
@@ -629,22 +636,18 @@ function createBaseContext(options: {
|
|
|
629
636
|
traceContext: ReturnType<typeof createTraceContext>;
|
|
630
637
|
}): ProviderContext {
|
|
631
638
|
const upstream = {
|
|
632
|
-
|
|
633
|
-
proxy: options.proxyEnabled,
|
|
639
|
+
proxy: options.proxyEnabled ? options.provider.proxy : false,
|
|
634
640
|
};
|
|
635
|
-
const apifuseConfig = options.proxyEnabled ? options.config : {};
|
|
636
641
|
const http =
|
|
637
642
|
options.forceFixtureReplay && options.fixtureReplay
|
|
638
643
|
? createFixtureHttpClient(options.fixtureReplay.raw)
|
|
639
644
|
: createHttpClient(getProviderBaseUrl(options.provider), {
|
|
640
|
-
apifuseConfig,
|
|
641
645
|
upstream,
|
|
642
646
|
});
|
|
643
647
|
const stealth =
|
|
644
648
|
options.forceFixtureReplay && options.fixtureReplay
|
|
645
649
|
? createFixtureStealthClient(options.fixtureReplay.rawText)
|
|
646
650
|
: createStealthClient(getProviderBaseUrl(options.provider), {
|
|
647
|
-
apifuseConfig,
|
|
648
651
|
upstream,
|
|
649
652
|
});
|
|
650
653
|
|
package/bin/apifuse-record.ts
CHANGED
|
@@ -8,9 +8,11 @@ import { pathToFileURL } from "node:url";
|
|
|
8
8
|
import {
|
|
9
9
|
createBypassProviderCache,
|
|
10
10
|
createHttpClient,
|
|
11
|
+
createOcrClientFromEnv,
|
|
11
12
|
createProviderChoiceContext,
|
|
12
13
|
createStealthClient,
|
|
13
14
|
createSttClientFromEnv,
|
|
15
|
+
createUnsupportedResolverClient,
|
|
14
16
|
executeOperation,
|
|
15
17
|
type HttpClient,
|
|
16
18
|
type HttpResponse,
|
|
@@ -536,7 +538,9 @@ function createCaptureContext(provider: ProviderRuntime, baseUrl: string, saniti
|
|
|
536
538
|
throw new Error("Auth prompts are not available in apifuse record.");
|
|
537
539
|
},
|
|
538
540
|
},
|
|
541
|
+
ocr: createOcrClientFromEnv(provider.ocr),
|
|
539
542
|
stt: createSttClientFromEnv(provider.stt),
|
|
543
|
+
resolver: createUnsupportedResolverClient("Resolver is not available in apifuse record"),
|
|
540
544
|
choice: createProviderChoiceContext({
|
|
541
545
|
providerId: provider.id,
|
|
542
546
|
env,
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Redis } from "ioredis";
|
|
|
2
2
|
import type { ProviderProxyPolicy, TraceConfig } from "../types.js";
|
|
3
3
|
import { type ProxyProtocol } from "../runtime/proxy-nodemaven.js";
|
|
4
4
|
export type { ProxyProtocol } from "../runtime/proxy-nodemaven.js";
|
|
5
|
-
/** Proxy vendors
|
|
5
|
+
/** Proxy vendors with SDK-managed resolution. */
|
|
6
6
|
export type ProxyVendorName = "smartproxy" | "nodemaven";
|
|
7
7
|
export declare const SMARTPROXY_APP_KEY_ENV = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
|
|
8
8
|
export declare const SMARTPROXY_MAX_LIFETIME_MINUTES = 2000;
|
|
@@ -14,13 +14,6 @@ export declare const DEFAULT_PROXY_LIFETIME_ENV = "APIFUSE__PROXY__DEFAULT_LIFET
|
|
|
14
14
|
export declare const PROVIDER_CACHE_REDIS_URL_ENV = "APIFUSE__PROVIDER__CACHE_REDIS_URL";
|
|
15
15
|
export declare const PROVIDER_STATE_REDIS_URL_ENV = "APIFUSE__PROVIDER__STATE_REDIS_URL";
|
|
16
16
|
export declare const REDIS_URL_ENV = "APIFUSE__REDIS__URL";
|
|
17
|
-
export type ProxyOptions = {
|
|
18
|
-
url: string;
|
|
19
|
-
};
|
|
20
|
-
export type ProxyConfig = Partial<ProxyOptions> & {
|
|
21
|
-
provider?: string;
|
|
22
|
-
apiKey?: string;
|
|
23
|
-
};
|
|
24
17
|
export type BrowserConfig = {
|
|
25
18
|
executablePath?: string;
|
|
26
19
|
headless?: boolean;
|
|
@@ -30,7 +23,6 @@ export type SessionConfig = {
|
|
|
30
23
|
path?: string;
|
|
31
24
|
};
|
|
32
25
|
export type ApiFuseConfig = {
|
|
33
|
-
proxy?: ProxyConfig;
|
|
34
26
|
browser?: BrowserConfig;
|
|
35
27
|
session?: SessionConfig;
|
|
36
28
|
trace?: TraceConfig;
|
|
@@ -41,7 +33,6 @@ export type ProxyResolutionOptions = {
|
|
|
41
33
|
upstream?: {
|
|
42
34
|
proxy?: boolean | ProviderProxyPolicy;
|
|
43
35
|
};
|
|
44
|
-
apifuseConfig?: Pick<ApiFuseConfig, "proxy">;
|
|
45
36
|
proxyPolicy?: ProviderProxyPolicy;
|
|
46
37
|
affinityKey?: string;
|
|
47
38
|
/** Zero-based proxy-pool attempt index used by SDK transports for failover. */
|
|
@@ -179,7 +170,7 @@ export declare function resolveWithVendor(vendor: ProxyVendorName, policy: Provi
|
|
|
179
170
|
* Ordered list of SDK-native proxy vendors declared by the policy. `providers`
|
|
180
171
|
* takes precedence over the legacy singular `provider`; the platform default
|
|
181
172
|
* env is the final fallback. Non-registry names (decodo/custom) are dropped so
|
|
182
|
-
* an all-
|
|
173
|
+
* an all-deprecated chain has no managed adapter.
|
|
183
174
|
*/
|
|
184
175
|
export declare function resolveVendorChain(policy: ProviderProxyPolicy): ProxyVendorName[];
|
|
185
176
|
/**
|
|
@@ -211,9 +202,8 @@ export declare function maxPolicyProxyPoolSpan(policy: ProviderProxyPolicy): num
|
|
|
211
202
|
* - the method is safe/idempotent — an unsafe request must never be duplicated
|
|
212
203
|
* across the pool even if some framework default would allow it;
|
|
213
204
|
* - the policy resolves a non-empty *registry* vendor chain (smartproxy /
|
|
214
|
-
* nodemaven).
|
|
215
|
-
*
|
|
216
|
-
* with no possible crossover — they keep the retry budget.
|
|
205
|
+
* nodemaven). Deprecated vendors (custom / decodo) resolve no managed pool,
|
|
206
|
+
* so there is no possible endpoint crossover — they keep the retry budget.
|
|
217
207
|
*
|
|
218
208
|
* The widened cap is bounded by the chain's true maximum span (sum of each
|
|
219
209
|
* vendor's max pool size), so a large NodeMaven pool (≤50) stays reachable and
|
|
@@ -232,8 +222,8 @@ export declare function maxPolicyProxyPoolSpan(policy: ProviderProxyPolicy): num
|
|
|
232
222
|
* endpoint each attempt resolves (even a repeated one), so no de-duplication;
|
|
233
223
|
* - the method is safe/idempotent — an unsafe request is never duplicated;
|
|
234
224
|
* - the policy resolves a non-empty registry vendor chain (smartproxy /
|
|
235
|
-
* nodemaven).
|
|
236
|
-
*
|
|
225
|
+
* nodemaven). Deprecated vendors (custom / decodo) resolve no managed
|
|
226
|
+
* endpoint, so there is nothing to rotate or de-duplicate.
|
|
237
227
|
*/
|
|
238
228
|
export declare function policyRotatesTransportVendorChain(input: {
|
|
239
229
|
policy: ProviderProxyPolicy | undefined;
|
|
@@ -252,9 +242,8 @@ export declare function resolvePolicyTransportAttemptCap(input: {
|
|
|
252
242
|
* A registry vendor chain (smartproxy/nodemaven) resolves a potentially
|
|
253
243
|
* *different* endpoint per flat attempt index, so a transport retry should
|
|
254
244
|
* advance across endpoints and de-duplicate once the chain stops yielding new
|
|
255
|
-
* ones.
|
|
256
|
-
*
|
|
257
|
-
* transport loop must not de-duplicate them.
|
|
245
|
+
* ones. Deprecated custom/decodo policies have an empty registry chain and no
|
|
246
|
+
* managed endpoint, so the transport loop has nothing to rotate or de-duplicate.
|
|
258
247
|
*/
|
|
259
248
|
export declare function policyResolvesRegistryVendorChain(policy: ProviderProxyPolicy | undefined): boolean;
|
|
260
249
|
/** Map a resolved proxy source label to the vendor that served it. */
|