@apifuse/provider-sdk 2.2.0-beta.36 → 2.2.0-beta.38
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 +22 -8
- package/CHANGELOG.md +8 -0
- package/README.md +20 -8
- package/bin/apifuse-dev.ts +1 -1
- package/bin/apifuse-pack-types.ts +6 -5
- package/bin/apifuse-record.ts +1 -1
- package/bin/apifuse-submit-check.ts +23 -10
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -1
- package/dist/declaration-validation.d.ts +2 -0
- package/dist/declaration-validation.js +29 -3
- package/dist/define.d.ts +39 -21
- package/dist/define.js +33 -9
- package/dist/health-scenario.d.ts +1842 -0
- package/dist/health-scenario.js +624 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/dist/provider.d.ts +5 -1
- package/dist/provider.js +1 -0
- package/dist/runtime/browser.js +19 -11
- package/dist/runtime/resolver-public.d.ts +1 -1
- package/dist/runtime/resolver-public.js +1 -1
- package/dist/runtime/resolver-vendors/browser.js +57 -14
- package/dist/runtime/resolver-vendors/types.d.ts +9 -1
- package/dist/runtime/resolver-vendors/types.js +15 -0
- package/dist/runtime/resolver.d.ts +1 -0
- package/dist/runtime/resolver.js +13 -7
- package/dist/server/serve-implementation.d.ts +1 -1
- package/dist/server/serve-implementation.js +25 -0
- package/dist/server/types.d.ts +4 -4
- package/dist/types.d.ts +35 -25
- package/package.json +1 -1
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +2 -1
- package/src/declaration-validation.ts +30 -3
- package/src/define.ts +144 -51
- package/src/health-scenario.ts +875 -0
- package/src/index.ts +78 -2
- package/src/provider.ts +81 -1
- package/src/runtime/browser.ts +34 -11
- package/src/runtime/resolver-public.ts +2 -0
- package/src/runtime/resolver-vendors/browser.ts +69 -11
- package/src/runtime/resolver-vendors/types.ts +21 -0
- package/src/runtime/resolver.ts +17 -5
- package/src/server/serve-implementation.ts +39 -1
- package/src/testing/run.ts +3 -3
- package/src/types.ts +52 -25
package/AUTHORING.md
CHANGED
|
@@ -104,7 +104,14 @@ description:
|
|
|
104
104
|
|
|
105
105
|
### Factored operations
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
`defineProvider(declaration)` returns the builder that accepts `operations`, so
|
|
108
|
+
inline handlers are typed only after capability declarations are fixed. Export
|
|
109
|
+
`ProviderContextOf<typeof buildProvider>` once from the provider entry point.
|
|
110
|
+
Separate operation files import that provider context and call
|
|
111
|
+
`defineOperation<ProviderContext>()({...})`; helpers should accept the one SDK
|
|
112
|
+
client they use rather than the provider context. Zod and Standard Schema v1
|
|
113
|
+
schemas retain input/output inference. Invalid configs name the offending field,
|
|
114
|
+
such as `auth.mode` or `operations.<id>.fixtures.response`.
|
|
108
115
|
|
|
109
116
|
### Replay-safe fixtures
|
|
110
117
|
|
|
@@ -236,8 +243,9 @@ level, then call `ctx.stt` from operation handlers or auth-flow handlers.
|
|
|
236
243
|
```ts
|
|
237
244
|
export default defineProvider({
|
|
238
245
|
id: "example-provider",
|
|
239
|
-
// ...metadata, auth,
|
|
246
|
+
// ...metadata, auth, allowedHosts
|
|
240
247
|
stt: { mode: "required" },
|
|
248
|
+
})({
|
|
241
249
|
operations: {
|
|
242
250
|
verifyAudioOtp: {
|
|
243
251
|
input: z.object({
|
|
@@ -357,11 +365,13 @@ const paymentWebviewJourney = defineHealthJourney({
|
|
|
357
365
|
],
|
|
358
366
|
});
|
|
359
367
|
|
|
360
|
-
|
|
368
|
+
const buildProvider = defineProvider({
|
|
361
369
|
id: "example-provider",
|
|
362
|
-
// ...metadata, auth,
|
|
370
|
+
// ...metadata, auth, allowedHosts
|
|
363
371
|
healthJourneys: [paymentWebviewJourney],
|
|
364
372
|
});
|
|
373
|
+
|
|
374
|
+
export default buildProvider({ operations });
|
|
365
375
|
```
|
|
366
376
|
<!-- @magic-end:sample -->
|
|
367
377
|
|
|
@@ -604,7 +614,7 @@ failures are sanitized before propagation.
|
|
|
604
614
|
HTML, or upstream `Error` objects.
|
|
605
615
|
|
|
606
616
|
```ts
|
|
607
|
-
|
|
617
|
+
const buildProvider = defineProvider({
|
|
608
618
|
id: "example-provider",
|
|
609
619
|
version: "1.0.0",
|
|
610
620
|
runtime: "standard",
|
|
@@ -638,8 +648,10 @@ export default defineProvider({
|
|
|
638
648
|
},
|
|
639
649
|
},
|
|
640
650
|
credential: { keys: ["cookie"] },
|
|
641
|
-
// ...metadata
|
|
651
|
+
// ...metadata
|
|
642
652
|
});
|
|
653
|
+
|
|
654
|
+
export default buildProvider({ operations });
|
|
643
655
|
```
|
|
644
656
|
|
|
645
657
|
- Credentials auth providers should use `defineCredentialsAuth()` instead of
|
|
@@ -666,15 +678,17 @@ const credentialsAuth = defineCredentialsAuth({
|
|
|
666
678
|
},
|
|
667
679
|
});
|
|
668
680
|
|
|
669
|
-
|
|
681
|
+
const buildProvider = defineProvider({
|
|
670
682
|
id: "example-provider",
|
|
671
683
|
version: "1.0.0",
|
|
672
684
|
runtime: "standard",
|
|
673
685
|
auth: credentialsAuth.auth,
|
|
674
686
|
credential: credentialsAuth.credential,
|
|
675
687
|
context: credentialsAuth.context,
|
|
676
|
-
|
|
688
|
+
// ...metadata
|
|
677
689
|
});
|
|
690
|
+
|
|
691
|
+
export default buildProvider({ operations });
|
|
678
692
|
```
|
|
679
693
|
|
|
680
694
|
For OTP, MFA, CAPTCHA handoff, or user-approved login, return a challenge from
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.38
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit 448775077dbc9ab907209f1feee4e6e4e5ef4283.
|
|
6
|
+
|
|
7
|
+
## 2.2.0-beta.37
|
|
8
|
+
|
|
9
|
+
- Release candidate for main commit aa14b268dffe1196f25c2b6b314598fe0896edec.
|
|
10
|
+
|
|
3
11
|
## 2.2.0-beta.36
|
|
4
12
|
|
|
5
13
|
- Release candidate for main commit c6858f8b87d78b3b755adeb28982e875434be406.
|
package/README.md
CHANGED
|
@@ -196,12 +196,28 @@ the bad request path; provider/runtime failures include `code`, `message`, and
|
|
|
196
196
|
|
|
197
197
|
## Authoring ergonomics
|
|
198
198
|
|
|
199
|
-
`defineProvider()`
|
|
199
|
+
`defineProvider()` establishes the capability declaration before its returned
|
|
200
|
+
builder contextually types operations. For larger providers, export the derived
|
|
201
|
+
context once and use it with `defineOperation()` in separate files:
|
|
200
202
|
|
|
201
203
|
```ts
|
|
202
|
-
import {
|
|
204
|
+
import {
|
|
205
|
+
defineOperation,
|
|
206
|
+
defineProvider,
|
|
207
|
+
type ProviderContextOf,
|
|
208
|
+
z,
|
|
209
|
+
} from "@apifuse/provider-sdk/provider"
|
|
210
|
+
|
|
211
|
+
const buildProvider = defineProvider({
|
|
212
|
+
id: "factored-provider",
|
|
213
|
+
version: "1.0.0",
|
|
214
|
+
runtime: "standard",
|
|
215
|
+
meta: { displayName: "Factored", category: "demo" },
|
|
216
|
+
})
|
|
203
217
|
|
|
204
|
-
|
|
218
|
+
export type ProviderContext = ProviderContextOf<typeof buildProvider>
|
|
219
|
+
|
|
220
|
+
const search = defineOperation<ProviderContext>()({
|
|
205
221
|
input: z.object({ q: z.string().describe("Search query") }),
|
|
206
222
|
output: z.object({ count: z.number().describe("Result count") }),
|
|
207
223
|
async handler(ctx, input) {
|
|
@@ -212,11 +228,7 @@ const search = defineOperation({
|
|
|
212
228
|
},
|
|
213
229
|
})
|
|
214
230
|
|
|
215
|
-
export default
|
|
216
|
-
id: "factored-provider",
|
|
217
|
-
version: "1.0.0",
|
|
218
|
-
runtime: "standard",
|
|
219
|
-
meta: { displayName: "Factored", category: "demo" },
|
|
231
|
+
export default buildProvider({
|
|
220
232
|
operations: { search },
|
|
221
233
|
})
|
|
222
234
|
```
|
package/bin/apifuse-dev.ts
CHANGED
|
@@ -77,6 +77,7 @@ const NEGATIVE_CONTROLS = [
|
|
|
77
77
|
'\truntime: "standard",',
|
|
78
78
|
'\tresolver: { vendors: ["unknown-vendor"], kinds: ["turnstile"] },',
|
|
79
79
|
'\tmeta: { displayName: "Invalid Resolver Vendor", descriptionKey: "meta.description", category: "test" },',
|
|
80
|
+
"})({",
|
|
80
81
|
"\toperations: {",
|
|
81
82
|
"\t\tprobe: {",
|
|
82
83
|
"\t\t\tinput: z.object({}),",
|
|
@@ -381,7 +382,7 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
381
382
|
[
|
|
382
383
|
'import { defineProvider, ProviderError, resolveProxy, SessionExpiredError, z } from "@apifuse/provider-sdk";',
|
|
383
384
|
'import { invalidateResolverSolution } from "@apifuse/provider-sdk/runtime/resolver";',
|
|
384
|
-
'import type { BrowserCookie, ChallengeSolution, NativeNetworkClient, NativeNetworkConnection, NativeProviderConfig,
|
|
385
|
+
'import type { BrowserCookie, ChallengeSolution, NativeContext, NativeNetworkClient, NativeNetworkConnection, NativeProviderConfig, NativeTcpEgressGrant, ProviderChallenge, ProviderContext, ProviderFileRef, ProviderFilesContext, ProviderResolvedFile, ProviderResolverConfig, ResolverContext, ResolverRuntimeOptions } from "@apifuse/provider-sdk";',
|
|
385
386
|
'import type { ProxyProtocol, ProxyResolutionOptions, ProxyResolutionSource, ProxyVendorName, RequestOptions, ResolvedProxyConfig } from "@apifuse/provider-sdk";',
|
|
386
387
|
'import { defineCredentialsAuth } from "@apifuse/provider-sdk/provider";',
|
|
387
388
|
'import type { NativeNetworkClient as ProviderEntryNativeNetworkClient, ProviderFilesContext as ProviderEntryFilesContext } from "@apifuse/provider-sdk/provider";',
|
|
@@ -415,12 +416,12 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
415
416
|
"const connection: NativeNetworkConnection = { read: async () => null, write: async () => {}, close: async () => {} };",
|
|
416
417
|
"const network: NativeNetworkClient = { connectTcp: async () => connection, connectTls: async () => connection, grantTcpEgress: () => ({ revoke() {} }) };",
|
|
417
418
|
"const providerEntryNetwork: ProviderEntryNativeNetworkClient = network;",
|
|
418
|
-
"const nativeContext:
|
|
419
|
+
"const nativeContext: NativeContext = { network };",
|
|
419
420
|
'const grant: NativeTcpEgressGrant = network.grantTcpEgress({ sourceHost: "booking-loco.kakao.com", sourcePort: 443, host: "loco.kakao.com", port: 5228, tls: "disabled" });',
|
|
420
421
|
'const nativeConfig: NativeProviderConfig = { network: { tcp: [{ host: "booking-loco.kakao.com", ports: [443], tls: "required" }] } };',
|
|
421
422
|
"const providerContext = undefined as unknown as ProviderContext;",
|
|
422
423
|
"const optionalFiles: ProviderFilesContext | undefined = providerContext.files;",
|
|
423
|
-
"const
|
|
424
|
+
"const native: NativeContext = providerContext.native;",
|
|
424
425
|
"export const providerResolver: ResolverContext = providerContext.resolver;",
|
|
425
426
|
'export const resolverRuntimeOptions: ResolverRuntimeOptions = { allowedHosts: ["example.com"], cache: providerContext.cache };',
|
|
426
427
|
"",
|
|
@@ -435,7 +436,7 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
435
436
|
'export const cookieSolution: ChallengeSolution = { form: "cookies", cookies: { cf_clearance: "clearance" }, userAgent: "fixture-agent" };',
|
|
436
437
|
"export const invalidation = invalidateResolverSolution(providerContext.resolver, awsWafChallenge, cookieSolution);",
|
|
437
438
|
'export const resolverConfig: ProviderResolverConfig = { vendors: ["browser", "capsolver"], kinds: ["cloudflare_interstitial", "turnstile"] };',
|
|
438
|
-
'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" }
|
|
439
|
+
'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" } } } });',
|
|
439
440
|
"export const resolverContext: ResolverContext = { solve: async () => tokenSolution };",
|
|
440
441
|
'export const browserCookie: BrowserCookie = { name: "persistent-id", value: "persistent-token", domain: "example.com", path: "/", expires: 1786698176, httpOnly: true, secure: true };',
|
|
441
442
|
'const browserPage = undefined as unknown as Awaited<ReturnType<ProviderContext["browser"]["newPage"]>>;',
|
|
@@ -465,7 +466,7 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
465
466
|
" grant,",
|
|
466
467
|
" nativeConfig,",
|
|
467
468
|
" optionalFiles,",
|
|
468
|
-
"
|
|
469
|
+
" native,",
|
|
469
470
|
" browserCookie,",
|
|
470
471
|
" browserCookies,",
|
|
471
472
|
" queryCredentialOptions,",
|
package/bin/apifuse-record.ts
CHANGED
|
@@ -1196,18 +1196,31 @@ function scoreFlatOperationComposition(providerRoot: string): SubmitCheck {
|
|
|
1196
1196
|
);
|
|
1197
1197
|
}
|
|
1198
1198
|
|
|
1199
|
-
// Scope the scan to the
|
|
1199
|
+
// Scope the scan to the exported provider builder's implementation argument,
|
|
1200
|
+
// or to the legacy-looking declaration call used by structural test fixtures.
|
|
1200
1201
|
// A provider can contain helper/non-exported defineProvider calls before the
|
|
1201
1202
|
// real default export (e.g. test scaffolds), so resolve the default export
|
|
1202
1203
|
// rather than blindly taking the first regex match. Resolution order:
|
|
1203
|
-
// 1. `export default
|
|
1204
|
-
// 2. `export default
|
|
1205
|
-
// 3.
|
|
1204
|
+
// 1. `export default <builder>(` — phase-separated provider
|
|
1205
|
+
// 2. `export default defineProvider(` — structural fixture
|
|
1206
|
+
// 3. `export default <ident>` then `const <ident> = defineProvider(`
|
|
1207
|
+
// 4. fallback: first `defineProvider(` in the file
|
|
1206
1208
|
let defineParenIndex = -1;
|
|
1209
|
+
const builderDefault = /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*\(/.exec(source);
|
|
1210
|
+
if (builderDefault?.[1] !== undefined && builderDefault[1] !== "defineProvider") {
|
|
1211
|
+
defineParenIndex = builderDefault.index + builderDefault[0].length - 1;
|
|
1212
|
+
}
|
|
1207
1213
|
const inlineDefault = /\bexport\s+default\s+defineProvider\s*\(/.exec(source);
|
|
1208
|
-
if (inlineDefault) {
|
|
1209
|
-
|
|
1210
|
-
|
|
1214
|
+
if (defineParenIndex === -1 && inlineDefault) {
|
|
1215
|
+
const declarationParen = inlineDefault.index + inlineDefault[0].length - 1;
|
|
1216
|
+
const declarationStart = declarationParen + 1;
|
|
1217
|
+
const declaration = balancedValueExpression(source, declarationStart);
|
|
1218
|
+
let cursor = declarationStart + declaration.length;
|
|
1219
|
+
while (/\s/.test(source[cursor] ?? "")) cursor++;
|
|
1220
|
+
if (source[cursor] === ")") cursor++;
|
|
1221
|
+
while (/\s/.test(source[cursor] ?? "")) cursor++;
|
|
1222
|
+
defineParenIndex = source[cursor] === "(" ? cursor : declarationParen;
|
|
1223
|
+
} else if (defineParenIndex === -1) {
|
|
1211
1224
|
const namedDefault = /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;?/.exec(source);
|
|
1212
1225
|
const exportedName = namedDefault?.[1];
|
|
1213
1226
|
if (exportedName !== undefined) {
|
|
@@ -1236,7 +1249,7 @@ function scoreFlatOperationComposition(providerRoot: string): SubmitCheck {
|
|
|
1236
1249
|
const argStart = defineParenIndex + 1;
|
|
1237
1250
|
const argText = balancedValueExpression(source, argStart);
|
|
1238
1251
|
|
|
1239
|
-
// Resolve the value passed as `operations:` inside the
|
|
1252
|
+
// Resolve the value passed as `operations:` inside the implementation call,
|
|
1240
1253
|
// following one alias hop. The value is classified as a static object
|
|
1241
1254
|
// literal (pass) or a factory/call expression (block). The regex index is
|
|
1242
1255
|
// offset back into the full source so line numbers stay accurate.
|
|
@@ -1249,7 +1262,7 @@ function scoreFlatOperationComposition(providerRoot: string): SubmitCheck {
|
|
|
1249
1262
|
opsLine = offsetToLine(source, valueStart);
|
|
1250
1263
|
}
|
|
1251
1264
|
|
|
1252
|
-
// Property shorthand: `
|
|
1265
|
+
// Property shorthand: `buildProvider({ operations })` — resolve the
|
|
1253
1266
|
// local `operations` const initializer.
|
|
1254
1267
|
let aliasName: string | undefined;
|
|
1255
1268
|
if (opsValue === undefined) {
|
|
@@ -1395,7 +1408,7 @@ function scoreFlatOperationComposition(providerRoot: string): SubmitCheck {
|
|
|
1395
1408
|
blockerMessage:
|
|
1396
1409
|
"defineProvider operations are composed by a factory call instead of a static object literal.",
|
|
1397
1410
|
remediation:
|
|
1398
|
-
"Declare operations as a static literal
|
|
1411
|
+
"Declare operations as a static literal in the provider builder call. The provider-registry AST gate requires static runtime/operations; factory composition fails the registry build. If composition is unavoidable, add `// @apifuse-allow flat-operation-composition: <reason>`.",
|
|
1399
1412
|
passMessage: "defineProvider declares operations as a static object literal.",
|
|
1400
1413
|
});
|
|
1401
1414
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { defineProvider } from "@apifuse/provider-sdk/provider";
|
|
1
|
+
import { defineProvider, type ProviderContextOf } from "@apifuse/provider-sdk/provider";
|
|
2
2
|
|
|
3
3
|
import { providerMeta } from "./meta";
|
|
4
4
|
import { operations } from "./operations";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const buildProvider = defineProvider({
|
|
7
7
|
id: "{{PROVIDER_ID}}",
|
|
8
8
|
version: "1.0.0",
|
|
9
9
|
runtime: "{{RUNTIME}}"{{BROWSER_BLOCK}},
|
|
@@ -11,5 +11,8 @@ export default defineProvider({
|
|
|
11
11
|
reviewed: "community",
|
|
12
12
|
{{SECRETS_BLOCK}}{{CREDENTIAL_BLOCK}}auth: {{AUTH_BLOCK}},
|
|
13
13
|
meta: providerMeta,
|
|
14
|
-
operations: operations,
|
|
15
14
|
});
|
|
15
|
+
|
|
16
|
+
export type ProviderContext = ProviderContextOf<typeof buildProvider>;
|
|
17
|
+
|
|
18
|
+
export default buildProvider({ operations });
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { defineOperation } from "@apifuse/provider-sdk/provider";
|
|
2
|
+
import type { ProviderContext } from "../index";
|
|
2
3
|
|
|
3
4
|
import { pingInputSchema, pingOutputSchema } from "../schemas/ping";
|
|
4
5
|
|
|
5
|
-
export const pingOperation = defineOperation({
|
|
6
|
+
export const pingOperation = defineOperation<ProviderContext>()({
|
|
6
7
|
descriptionKey: "operations.ping.description",
|
|
7
8
|
input: pingInputSchema,
|
|
8
9
|
output: pingOutputSchema,
|
|
@@ -4,6 +4,8 @@ export declare const DECLARATION_INVALID_CODE = "DECLARATION_INVALID";
|
|
|
4
4
|
export declare const DECLARATION_RULE_IDS: {
|
|
5
5
|
readonly challengeShape: "credentials-challenge-shape";
|
|
6
6
|
readonly journeyExecutable: "health-journey-executable";
|
|
7
|
+
readonly journeyRunScenarioExclusive: "health-journey-run-scenario-exclusive";
|
|
8
|
+
readonly journeyScenarioValid: "health-journey-scenario-valid";
|
|
7
9
|
readonly schemaSerializable: "operation-schema-serializable";
|
|
8
10
|
readonly proxyExplicitPolicy: "proxy-explicit-policy";
|
|
9
11
|
readonly proxyVendorExclusive: "proxy-vendor-fields-exclusive";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { describeSchema } from "./contract-serialization.js";
|
|
2
2
|
import { ProviderError } from "./errors.js";
|
|
3
|
+
import { HealthScenarioSchema } from "./health-scenario.js";
|
|
3
4
|
export const DECLARATION_INVALID_CODE = "DECLARATION_INVALID";
|
|
4
5
|
export const DECLARATION_RULE_IDS = {
|
|
5
6
|
challengeShape: "credentials-challenge-shape",
|
|
6
7
|
journeyExecutable: "health-journey-executable",
|
|
8
|
+
journeyRunScenarioExclusive: "health-journey-run-scenario-exclusive",
|
|
9
|
+
journeyScenarioValid: "health-journey-scenario-valid",
|
|
7
10
|
schemaSerializable: "operation-schema-serializable",
|
|
8
11
|
proxyExplicitPolicy: "proxy-explicit-policy",
|
|
9
12
|
proxyVendorExclusive: "proxy-vendor-fields-exclusive",
|
|
@@ -35,15 +38,38 @@ function validateHealthDeclaration(provider, violations) {
|
|
|
35
38
|
for (const [index, journey] of (provider.healthJourneys ?? []).entries()) {
|
|
36
39
|
if (!journey || typeof journey !== "object")
|
|
37
40
|
continue;
|
|
38
|
-
|
|
41
|
+
const hasRun = journey.run !== undefined;
|
|
42
|
+
const hasScenario = journey.scenario !== undefined;
|
|
43
|
+
if (hasRun && hasScenario) {
|
|
44
|
+
const journeyPath = healthJourneyPath(journey, index);
|
|
45
|
+
violations.push({
|
|
46
|
+
ruleId: DECLARATION_RULE_IDS.journeyRunScenarioExclusive,
|
|
47
|
+
path: journeyPath,
|
|
48
|
+
message: "A health journey must declare exactly one of run or scenario, not both.",
|
|
49
|
+
fix: `Remove either ${journeyPath}.run or ${journeyPath}.scenario.`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (!hasRun && !hasScenario) {
|
|
39
53
|
const journeyPath = healthJourneyPath(journey, index);
|
|
40
54
|
violations.push({
|
|
41
55
|
ruleId: DECLARATION_RULE_IDS.journeyExecutable,
|
|
42
56
|
path: `${journeyPath}.run`,
|
|
43
|
-
message: "coversOperations cannot provide health coverage without
|
|
44
|
-
fix: `Add an async run(ctx) implementation to ${journeyPath}.`,
|
|
57
|
+
message: "coversOperations cannot provide health coverage without run or a declarative scenario.",
|
|
58
|
+
fix: `Add an async run(ctx) implementation or a valid scenario to ${journeyPath}.`,
|
|
45
59
|
});
|
|
46
60
|
}
|
|
61
|
+
if (hasScenario) {
|
|
62
|
+
const parsed = HealthScenarioSchema.safeParse(journey.scenario);
|
|
63
|
+
if (!parsed.success) {
|
|
64
|
+
const journeyPath = healthJourneyPath(journey, index);
|
|
65
|
+
violations.push({
|
|
66
|
+
ruleId: DECLARATION_RULE_IDS.journeyScenarioValid,
|
|
67
|
+
path: `${journeyPath}.scenario`,
|
|
68
|
+
message: "scenario must conform to HealthScenario.",
|
|
69
|
+
fix: "Build the scenario with defineHealthScenario().",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
47
73
|
}
|
|
48
74
|
// NOTE: healthCheck.cases[].enabled is intentionally NOT validated here.
|
|
49
75
|
// self-test.ts reports a gated case as status "skipped" with skipReason
|
package/dist/define.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthConfig, BrowserEngine, ContextDeclaration, CredentialDeclaration, HealthJourneyDefinition, HealthJourneySchedule, HealthScheduleRandomization, InferSchemaOutput, NativeProviderConfig, OperationDefinition, OperationHandlerResult, OperationHttpStreamTransport, OperationSseTransport, OperationWebSocketTransport, ProviderAccessConfig, ProviderDefinition, ProviderOcrConfig, ProviderDeploymentOverrides, ProviderHealthMonitorConfig, ProviderProxyConfig, ProviderPublicProfile, ProviderResolverConfig, ProviderReviewed, ProviderSecretDeclaration, ProviderStreamEvent, ProviderSttConfig, SchemaLike, SmsOtpMatcherDefinition, StealthPlatform } from "./types.js";
|
|
1
|
+
import type { AuthConfig, BrowserEngine, ContextDeclaration, CredentialDeclaration, HealthJourneyDefinition, HealthJourneySchedule, HealthScheduleRandomization, InferSchemaOutput, NativeProviderConfig, OperationDefinition, OperationHandlerResult, OperationHttpStreamTransport, OperationSseTransport, OperationWebSocketTransport, ProviderAccessConfig, ProviderDefinition, ProviderContext, ProviderContextFor, ProviderOcrConfig, ProviderDeploymentOverrides, ProviderHealthMonitorConfig, ProviderProxyConfig, ProviderPublicProfile, ProviderResolverConfig, ProviderReviewed, ProviderSecretDeclaration, ProviderStreamEvent, ProviderSttConfig, SchemaLike, SmsOtpMatcherDefinition, StealthPlatform } from "./types.js";
|
|
2
2
|
type ProviderImplementationSourceAccess = "official_api" | "private_api" | "browser_flow" | "hybrid";
|
|
3
3
|
type ProviderImplementationCredentialStrategy = "apifuse_managed" | "workspace_secret" | "user_oauth" | "user_session" | "none";
|
|
4
4
|
interface ProviderImplementationProfile {
|
|
@@ -10,25 +10,25 @@ interface ProviderImplementationProfile {
|
|
|
10
10
|
}
|
|
11
11
|
export declare const VALID_PROVIDER_RESOLVER_VENDORS: readonly ["browser", "capsolver", "capmonster", "2captcha", "custom"];
|
|
12
12
|
export declare const VALID_PROVIDER_CHALLENGE_KINDS: readonly ["turnstile", "recaptcha_v2", "recaptcha_v3", "hcaptcha", "cloudflare_interstitial", "aws_waf", "akamai_sec_cpt", "akamai_sensor"];
|
|
13
|
-
type ProviderOperation = OperationDefinition<
|
|
14
|
-
type OperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<OperationDefinition<TInput, TOutput>, "handler"> & {
|
|
15
|
-
handler(ctx:
|
|
13
|
+
type ProviderOperation = OperationDefinition<any, any, any>;
|
|
14
|
+
type OperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike, TContext = ProviderContext> = Omit<OperationDefinition<TInput, TOutput, TContext>, "handler"> & {
|
|
15
|
+
handler(ctx: TContext, input: InferSchemaOutput<TInput>): OperationHandlerResult<InferSchemaOutput<TOutput>> | Promise<OperationHandlerResult<InferSchemaOutput<TOutput>>>;
|
|
16
16
|
};
|
|
17
|
-
type OperationMapConfig<TOperations extends Record<string, ProviderOperation
|
|
18
|
-
[K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput> ? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput> : never;
|
|
17
|
+
type OperationMapConfig<TOperations extends Record<string, ProviderOperation>, TContext = ProviderContext> = {
|
|
18
|
+
[K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput> ? OperationConfig<TInput, TOutput, TContext> | OperationDefinition<TInput, TOutput, TContext> : never;
|
|
19
19
|
};
|
|
20
|
-
type StreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = SseOperationConfig<TInput, TOutput> | HttpStreamOperationConfig<TInput, TOutput> | WebSocketOperationConfig<TInput, TOutput>;
|
|
21
|
-
type SseOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<OperationConfig<TInput, TOutput>, "handler" | "transport"> & {
|
|
20
|
+
type StreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike, TContext = ProviderContext> = SseOperationConfig<TInput, TOutput, TContext> | HttpStreamOperationConfig<TInput, TOutput, TContext> | WebSocketOperationConfig<TInput, TOutput, TContext>;
|
|
21
|
+
type SseOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike, TContext = ProviderContext> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
22
22
|
transport: OperationSseTransport;
|
|
23
|
-
handler(ctx:
|
|
23
|
+
handler(ctx: TContext, input: InferSchemaOutput<TInput>): AsyncIterable<ProviderStreamEvent> | Promise<AsyncIterable<ProviderStreamEvent>>;
|
|
24
24
|
};
|
|
25
|
-
type HttpStreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<OperationConfig<TInput, TOutput>, "handler" | "transport"> & {
|
|
25
|
+
type HttpStreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike, TContext = ProviderContext> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
26
26
|
transport: OperationHttpStreamTransport;
|
|
27
|
-
handler(ctx:
|
|
27
|
+
handler(ctx: TContext, input: InferSchemaOutput<TInput>): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
28
28
|
};
|
|
29
|
-
type WebSocketOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<OperationConfig<TInput, TOutput>, "handler" | "transport"> & {
|
|
29
|
+
type WebSocketOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike, TContext = ProviderContext> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
30
30
|
transport: OperationWebSocketTransport;
|
|
31
|
-
handler(ctx:
|
|
31
|
+
handler(ctx: TContext, input: InferSchemaOutput<TInput>): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
32
32
|
};
|
|
33
33
|
type AuthStartHandlerNoInputGuard<TStart> = TStart extends (...args: infer TArgs) => unknown ? TArgs["length"] extends 0 | 1 ? unknown : {
|
|
34
34
|
"auth start handlers must not declare input parameters; return a form turn from start and receive user input in continue": never;
|
|
@@ -42,7 +42,7 @@ export type AuthStartNoInputGuard<TConfig> = TConfig extends {
|
|
|
42
42
|
} ? AuthStartHandlerNoInputGuard<TStart> : TConfig extends {
|
|
43
43
|
start: infer TStart;
|
|
44
44
|
} ? AuthStartHandlerNoInputGuard<TStart> : unknown;
|
|
45
|
-
export interface
|
|
45
|
+
export interface ProviderDeclaration {
|
|
46
46
|
id: string;
|
|
47
47
|
version: string;
|
|
48
48
|
runtime: "standard" | "shared" | "browser";
|
|
@@ -53,6 +53,8 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
53
53
|
* resolves omitted fields against the runtime deployment profiles.
|
|
54
54
|
*/
|
|
55
55
|
deployment?: ProviderDeploymentOverrides;
|
|
56
|
+
/** Declares that provider operations use the SDK HTTP client. */
|
|
57
|
+
http?: true;
|
|
56
58
|
allowedHosts?: string[];
|
|
57
59
|
native?: NativeProviderConfig;
|
|
58
60
|
stealth?: {
|
|
@@ -67,11 +69,21 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
67
69
|
engine: BrowserEngine;
|
|
68
70
|
};
|
|
69
71
|
auth?: AuthConfig;
|
|
72
|
+
/** Declares that provider operations issue and consume SDK choice tokens. */
|
|
73
|
+
choice?: true;
|
|
70
74
|
reviewed?: ProviderReviewed;
|
|
71
75
|
access?: ProviderAccessConfig;
|
|
72
76
|
secrets?: ProviderSecretDeclaration[];
|
|
77
|
+
/** Declares that provider operations read SDK-managed environment values. */
|
|
78
|
+
env?: true;
|
|
73
79
|
credential?: CredentialDeclaration;
|
|
74
80
|
context?: ContextDeclaration;
|
|
81
|
+
/** Declares that provider operations use SDK-managed persistent state. */
|
|
82
|
+
state?: true;
|
|
83
|
+
/** Declares that provider operations use the SDK provider cache. */
|
|
84
|
+
cache?: true;
|
|
85
|
+
/** Declares that provider operations access runtime-resolvable files. */
|
|
86
|
+
files?: true;
|
|
75
87
|
meta: {
|
|
76
88
|
displayName: string;
|
|
77
89
|
displayNameKey?: string;
|
|
@@ -93,16 +105,15 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
93
105
|
publicSchemaFieldNames?: "normalized";
|
|
94
106
|
};
|
|
95
107
|
};
|
|
96
|
-
operations: OperationMapConfig<TOperations>;
|
|
97
108
|
healthMonitor?: ProviderHealthMonitorConfig;
|
|
98
109
|
/** New name for `healthMonitor` (transitional alias); declaring both is a ValidationError. */
|
|
99
110
|
healthProbe?: ProviderHealthMonitorConfig;
|
|
100
111
|
healthJourneys?: readonly HealthJourneyDefinition[];
|
|
101
112
|
}
|
|
102
|
-
/** Define one provider operation with schema-driven handler inference. */
|
|
103
|
-
export declare function defineOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
104
|
-
/** Define a non-JSON
|
|
105
|
-
export declare function defineStreamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
113
|
+
/** Define one factored provider operation with schema-driven handler inference. */
|
|
114
|
+
export declare function defineOperation<TContext>(): <TInput extends SchemaLike, TOutput extends SchemaLike>(config: OperationConfig<TInput, TOutput, TContext>) => OperationDefinition<TInput, TOutput, TContext>;
|
|
115
|
+
/** Define a factored non-JSON operation with explicit transport metadata. */
|
|
116
|
+
export declare function defineStreamOperation<TContext>(): <TInput extends SchemaLike, TOutput extends SchemaLike>(config: StreamOperationConfig<TInput, TOutput, TContext>) => OperationDefinition<TInput, TOutput, TContext>;
|
|
106
117
|
export declare function every(interval: string, options?: {
|
|
107
118
|
jitter?: string;
|
|
108
119
|
randomize?: HealthScheduleRandomization;
|
|
@@ -111,7 +122,14 @@ export declare function centered(maxOffset: string): HealthScheduleRandomization
|
|
|
111
122
|
export declare function delayed(maxDelay: string): HealthScheduleRandomization;
|
|
112
123
|
export declare function defineSmsOtpMatcher(config: Omit<SmsOtpMatcherDefinition, "extractOtp">): SmsOtpMatcherDefinition;
|
|
113
124
|
export declare function defineHealthJourney(config: HealthJourneyDefinition): HealthJourneyDefinition;
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
/** The second authoring phase for a declaration established by defineProvider. */
|
|
126
|
+
export type ProviderBuilder<TDeclaration extends ProviderDeclaration> = <TOperations extends Record<string, ProviderOperation>>(implementation: {
|
|
127
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
128
|
+
}) => ProviderDefinition & {
|
|
129
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
116
130
|
};
|
|
131
|
+
/** Extract the declaration-derived operation context from a provider builder. */
|
|
132
|
+
export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer TDeclaration> ? ProviderContextFor<TDeclaration> : never;
|
|
133
|
+
/** Establish a provider declaration before its operations are contextually typed. */
|
|
134
|
+
export declare function defineProvider<const TDeclaration extends ProviderDeclaration>(declaration: TDeclaration & Record<Exclude<keyof TDeclaration, keyof ProviderDeclaration>, never> & AuthStartNoInputGuard<TDeclaration>): ProviderBuilder<TDeclaration>;
|
|
117
135
|
export {};
|
package/dist/define.js
CHANGED
|
@@ -449,13 +449,17 @@ function authStartHasHiddenInput(start) {
|
|
|
449
449
|
return false;
|
|
450
450
|
return /\s=\s/.test(second);
|
|
451
451
|
}
|
|
452
|
-
/** Define one provider operation with schema-driven handler inference. */
|
|
453
|
-
export function defineOperation(
|
|
454
|
-
return operation
|
|
452
|
+
/** Define one factored provider operation with schema-driven handler inference. */
|
|
453
|
+
export function defineOperation() {
|
|
454
|
+
return function operation(config) {
|
|
455
|
+
return config;
|
|
456
|
+
};
|
|
455
457
|
}
|
|
456
|
-
/** Define a non-JSON
|
|
457
|
-
export function defineStreamOperation(
|
|
458
|
-
return
|
|
458
|
+
/** Define a factored non-JSON operation with explicit transport metadata. */
|
|
459
|
+
export function defineStreamOperation() {
|
|
460
|
+
return function streamOperation(config) {
|
|
461
|
+
return config;
|
|
462
|
+
};
|
|
459
463
|
}
|
|
460
464
|
function assertObjectConfig(value) {
|
|
461
465
|
if (!value || typeof value !== "object") {
|
|
@@ -558,6 +562,11 @@ function validateProviderShape(config) {
|
|
|
558
562
|
assertRequiredField(config, "operations", String(config.id));
|
|
559
563
|
if (typeof config.runtime === "string")
|
|
560
564
|
assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
|
|
565
|
+
if (config.native !== undefined && config.runtime === "browser") {
|
|
566
|
+
throw new ValidationError(`Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`, {
|
|
567
|
+
fix: 'Use runtime: "standard" or runtime: "shared", or remove the native declaration.',
|
|
568
|
+
});
|
|
569
|
+
}
|
|
561
570
|
const auth = config.auth;
|
|
562
571
|
if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
|
|
563
572
|
assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
|
|
@@ -780,7 +789,9 @@ function validateProviderResolver(config) {
|
|
|
780
789
|
});
|
|
781
790
|
}
|
|
782
791
|
rejectUnknownFields(resolver, new Set(["vendors", "kinds", "clientProfile"]), "resolver", config.id);
|
|
783
|
-
|
|
792
|
+
if (resolver.vendors !== undefined) {
|
|
793
|
+
validateResolverLiteralArray(resolver.vendors, "resolver.vendors", VALID_PROVIDER_RESOLVER_VENDORS, config.id);
|
|
794
|
+
}
|
|
784
795
|
validateResolverLiteralArray(resolver.kinds, "resolver.kinds", VALID_PROVIDER_CHALLENGE_KINDS, config.id);
|
|
785
796
|
if (resolver.clientProfile !== undefined &&
|
|
786
797
|
(typeof resolver.clientProfile !== "string" || !resolver.clientProfile.trim())) {
|
|
@@ -1402,6 +1413,7 @@ const HEALTH_JOURNEY_FIELDS = new Set([
|
|
|
1402
1413
|
"manualTrigger",
|
|
1403
1414
|
"steps",
|
|
1404
1415
|
"run",
|
|
1416
|
+
"scenario",
|
|
1405
1417
|
]);
|
|
1406
1418
|
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set(["kind", "interval", "jitter", "randomize"]);
|
|
1407
1419
|
const HEALTH_JOURNEY_STEP_FIELDS = new Set([
|
|
@@ -1824,6 +1836,10 @@ function validateHealthJourneys(providerId, operations, healthJourneys) {
|
|
|
1824
1836
|
}
|
|
1825
1837
|
if (journey.manualTrigger !== undefined)
|
|
1826
1838
|
validateHealthJourneyManualTrigger(providerId, journey.id, journey.manualTrigger);
|
|
1839
|
+
if (journey.scenario !== undefined && journey.smsMatchers !== undefined)
|
|
1840
|
+
throw new ValidationError(`Provider "${providerId}" healthJourneys.${journey.id}.smsMatchers is not allowed on declarative scenarios.`);
|
|
1841
|
+
if (journey.scenario !== undefined && journey.requiredSecrets !== undefined)
|
|
1842
|
+
throw new ValidationError(`Provider "${providerId}" healthJourneys.${journey.id}.requiredSecrets is not allowed on declarative scenarios.`);
|
|
1827
1843
|
if (journey.timeout !== undefined)
|
|
1828
1844
|
assertIsoDuration(journey.timeout, `Provider "${providerId}" healthJourneys.${journey.id}.timeout`);
|
|
1829
1845
|
if (journey.cooldown !== undefined)
|
|
@@ -1933,7 +1949,15 @@ function validateProviderDeployment(providerId, deployment) {
|
|
|
1933
1949
|
fix: 'Pass deployment: { runtime: "shared" | "dedicated" | "browser", ... } or remove the field',
|
|
1934
1950
|
});
|
|
1935
1951
|
}
|
|
1936
|
-
|
|
1952
|
+
/** Establish a provider declaration before its operations are contextually typed. */
|
|
1953
|
+
export function defineProvider(declaration) {
|
|
1954
|
+
const buildProvider = (implementation) => finalizeProvider({
|
|
1955
|
+
...declaration,
|
|
1956
|
+
...implementation,
|
|
1957
|
+
});
|
|
1958
|
+
return buildProvider;
|
|
1959
|
+
}
|
|
1960
|
+
function finalizeProvider(config) {
|
|
1937
1961
|
validateProviderShape(config);
|
|
1938
1962
|
const operations = resolveOperationFixtureRequests(config.operations);
|
|
1939
1963
|
if (!CONNECTOR_ID_REGEX.test(config.id))
|
|
@@ -2000,7 +2024,7 @@ export function defineProvider(config) {
|
|
|
2000
2024
|
credential: config.credential,
|
|
2001
2025
|
context: config.context,
|
|
2002
2026
|
meta: config.meta,
|
|
2003
|
-
operations,
|
|
2027
|
+
operations: operations,
|
|
2004
2028
|
// Transitional healthMonitor → healthProbe alias: mirror whichever field
|
|
2005
2029
|
// was declared onto both so old and new consumers keep working.
|
|
2006
2030
|
healthMonitor: config.healthMonitor ?? config.healthProbe,
|