@apifuse/provider-sdk 2.2.0-beta.35 → 2.2.0-beta.37
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/ceremonies/index.d.ts +8 -0
- package/dist/ceremonies/index.js +32 -25
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -1
- package/dist/define.d.ts +46 -25
- package/dist/define.js +381 -9
- package/dist/index.d.ts +2 -2
- package/dist/provider.d.ts +2 -1
- package/dist/runtime/browser.js +19 -11
- package/dist/runtime/choice.js +24 -7
- 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.js +25 -0
- package/dist/types.d.ts +25 -17
- package/package.json +6 -1
- package/src/ceremonies/index.ts +45 -31
- package/src/cli/templates/provider/Dockerfile.tpl +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/define.ts +462 -59
- package/src/index.ts +5 -2
- package/src/provider.ts +6 -1
- package/src/runtime/browser.ts +34 -11
- package/src/runtime/choice.ts +25 -8
- 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 +41 -17
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.37
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit aa14b268dffe1196f25c2b6b314598fe0896edec.
|
|
6
|
+
|
|
7
|
+
## 2.2.0-beta.36
|
|
8
|
+
|
|
9
|
+
- Release candidate for main commit c6858f8b87d78b3b755adeb28982e875434be406.
|
|
10
|
+
|
|
3
11
|
## 2.2.0-beta.35
|
|
4
12
|
|
|
5
13
|
- Release candidate for main commit 027fa0087b883a6281bc6d8bdaaf6d0be382000f.
|
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,8 +1,16 @@
|
|
|
1
|
+
import type { AuthStartNoInputGuard } from "../define.js";
|
|
1
2
|
import type { AuthFlowDefinition, AuthTurn } from "../types.js";
|
|
2
3
|
type JsonObject = Record<string, unknown>;
|
|
3
4
|
export declare const OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY = "APIFUSE__AUTH_PROXY__URL";
|
|
4
5
|
export declare const OAUTH2_PROXIED_PKCE_VERIFIER_KEY = "__oauth2_proxied_pkce_verifier";
|
|
5
6
|
export declare function validateCeremonyOutput(turn: unknown): AuthTurn;
|
|
7
|
+
/**
|
|
8
|
+
* Defines an auth flow while preserving its concrete type for downstream checks.
|
|
9
|
+
* The compile-time guard validates the inferred literal; annotating or widening
|
|
10
|
+
* a flow to `AuthFlowDefinition` before passing it defeats the check. Full
|
|
11
|
+
* enforcement requires branding, which is deferred to a future major.
|
|
12
|
+
*/
|
|
13
|
+
export declare function defineAuthFlow<const TFlow extends AuthFlowDefinition>(flow: TFlow & AuthStartNoInputGuard<TFlow>): TFlow;
|
|
6
14
|
export declare function createOAuth2Ceremony(options: {
|
|
7
15
|
authorizeUrl: string;
|
|
8
16
|
tokenUrl: string;
|
package/dist/ceremonies/index.js
CHANGED
|
@@ -163,8 +163,17 @@ export function validateCeremonyOutput(turn) {
|
|
|
163
163
|
}
|
|
164
164
|
return turn;
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Defines an auth flow while preserving its concrete type for downstream checks.
|
|
168
|
+
* The compile-time guard validates the inferred literal; annotating or widening
|
|
169
|
+
* a flow to `AuthFlowDefinition` before passing it defeats the check. Full
|
|
170
|
+
* enforcement requires branding, which is deferred to a future major.
|
|
171
|
+
*/
|
|
172
|
+
export function defineAuthFlow(flow) {
|
|
173
|
+
return flow;
|
|
174
|
+
}
|
|
166
175
|
export function createOAuth2Ceremony(options) {
|
|
167
|
-
return {
|
|
176
|
+
return defineAuthFlow({
|
|
168
177
|
start: (ctx) => runCeremonyHandler(async () => {
|
|
169
178
|
const clientId = getRequiredEnv(ctx, options.clientIdEnvKey);
|
|
170
179
|
getRequiredEnv(ctx, options.clientSecretEnvKey);
|
|
@@ -221,7 +230,7 @@ export function createOAuth2Ceremony(options) {
|
|
|
221
230
|
});
|
|
222
231
|
}, "OAuth token exchange failed", ctx, input),
|
|
223
232
|
abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "OAuth flow aborted." })),
|
|
224
|
-
};
|
|
233
|
+
});
|
|
225
234
|
}
|
|
226
235
|
/**
|
|
227
236
|
* Builds the start handler for a custom-scheme OAuth provider. The shared
|
|
@@ -272,7 +281,7 @@ export function createOAuth2ProxiedStart(options) {
|
|
|
272
281
|
}, "OAuth2 proxied start failed", ctx);
|
|
273
282
|
}
|
|
274
283
|
export function createDeviceFlowCeremony(options) {
|
|
275
|
-
return {
|
|
284
|
+
return defineAuthFlow({
|
|
276
285
|
start: (ctx) => runCeremonyHandler(async () => {
|
|
277
286
|
const response = await ctx.http.post(options.deviceCodeUrl, {
|
|
278
287
|
client_id: getRequiredEnv(ctx, options.clientIdEnvKey),
|
|
@@ -330,10 +339,10 @@ export function createDeviceFlowCeremony(options) {
|
|
|
330
339
|
});
|
|
331
340
|
}, "Device flow polling failed", ctx),
|
|
332
341
|
abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "Device flow aborted." })),
|
|
333
|
-
};
|
|
342
|
+
});
|
|
334
343
|
}
|
|
335
344
|
export function createWebAuthnCeremony(options) {
|
|
336
|
-
return {
|
|
345
|
+
return defineAuthFlow({
|
|
337
346
|
start: (ctx) => runCeremonyHandler(async () => {
|
|
338
347
|
const challenge = toBase64Url(randomBytes(32));
|
|
339
348
|
ctx.context.set("__webauthn_challenge", challenge);
|
|
@@ -375,21 +384,23 @@ export function createWebAuthnCeremony(options) {
|
|
|
375
384
|
});
|
|
376
385
|
}, "WebAuthn verification failed", ctx, input),
|
|
377
386
|
abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "WebAuthn ceremony aborted." })),
|
|
378
|
-
};
|
|
387
|
+
});
|
|
379
388
|
}
|
|
380
389
|
export function createMagicLinkCeremony(options) {
|
|
381
390
|
const emailField = options.emailField ?? "email";
|
|
382
|
-
|
|
383
|
-
|
|
391
|
+
const buildEmailForm = () => buildJsonSchemaForm({
|
|
392
|
+
type: "object",
|
|
393
|
+
required: [emailField],
|
|
394
|
+
properties: {
|
|
395
|
+
[emailField]: { type: "string", format: "email" },
|
|
396
|
+
},
|
|
397
|
+
}, "Provide the email address to receive a magic link.");
|
|
398
|
+
return defineAuthFlow({
|
|
399
|
+
start: (ctx) => runCeremonyHandler(async () => buildEmailForm(), "Magic link start failed", ctx),
|
|
400
|
+
continue: (ctx, input = {}) => runCeremonyHandler(async () => {
|
|
384
401
|
const email = getString(input, emailField);
|
|
385
402
|
if (!email) {
|
|
386
|
-
return
|
|
387
|
-
type: "object",
|
|
388
|
-
required: [emailField],
|
|
389
|
-
properties: {
|
|
390
|
-
[emailField]: { type: "string", format: "email" },
|
|
391
|
-
},
|
|
392
|
-
}, "Provide the email address to receive a magic link.");
|
|
403
|
+
return buildEmailForm();
|
|
393
404
|
}
|
|
394
405
|
await ctx.http.post(options.sendUrl, { email });
|
|
395
406
|
ctx.context.set(MAGIC_LINK_KEY, {
|
|
@@ -401,11 +412,7 @@ export function createMagicLinkCeremony(options) {
|
|
|
401
412
|
hint: "Check your email for the magic link, then poll for completion.",
|
|
402
413
|
timing: { suggestedPollIntervalMs: 5_000, maxWaitMs: 300_000 },
|
|
403
414
|
});
|
|
404
|
-
}, "Magic link
|
|
405
|
-
continue: async () => validateCeremonyOutput(createTurn("poll", {
|
|
406
|
-
hint: "Continue polling for magic link completion.",
|
|
407
|
-
timing: { suggestedPollIntervalMs: 5_000, maxWaitMs: 300_000 },
|
|
408
|
-
})),
|
|
415
|
+
}, "Magic link continuation failed", ctx, input),
|
|
409
416
|
poll: (ctx) => runCeremonyHandler(async () => {
|
|
410
417
|
const state = getNestedRecord(ctx, MAGIC_LINK_KEY);
|
|
411
418
|
const email = getString(state, "email");
|
|
@@ -431,10 +438,10 @@ export function createMagicLinkCeremony(options) {
|
|
|
431
438
|
});
|
|
432
439
|
}, "Magic link polling failed", ctx),
|
|
433
440
|
abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "Magic link flow aborted." })),
|
|
434
|
-
};
|
|
441
|
+
});
|
|
435
442
|
}
|
|
436
443
|
export function createFormCeremony(options) {
|
|
437
|
-
return {
|
|
444
|
+
return defineAuthFlow({
|
|
438
445
|
start: async () => validateCeremonyOutput(buildJsonSchemaForm(options.schema, options.hint ?? "Provide the required input to continue.")),
|
|
439
446
|
continue: (ctx, input = {}) => runCeremonyHandler(async () => {
|
|
440
447
|
const { prevalidate } = await import("../runtime/prevalidate.js");
|
|
@@ -452,7 +459,7 @@ export function createFormCeremony(options) {
|
|
|
452
459
|
});
|
|
453
460
|
}, "Form submission failed", ctx, input),
|
|
454
461
|
abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "Form ceremony aborted." })),
|
|
455
|
-
};
|
|
462
|
+
});
|
|
456
463
|
}
|
|
457
464
|
export function combineCeremonies(...ceremonies) {
|
|
458
465
|
function getStage(ctx) {
|
|
@@ -504,7 +511,7 @@ export function combineCeremonies(...ceremonies) {
|
|
|
504
511
|
}
|
|
505
512
|
export function createSwitchCeremony(options) {
|
|
506
513
|
const choiceKeys = Object.keys(options.choices);
|
|
507
|
-
return {
|
|
514
|
+
return defineAuthFlow({
|
|
508
515
|
start: async () => validateCeremonyOutput(createTurn("multi_choice", {
|
|
509
516
|
data: { choices: choiceKeys },
|
|
510
517
|
hint: options.prompt ?? "Choose an authentication method.",
|
|
@@ -550,5 +557,5 @@ export function createSwitchCeremony(options) {
|
|
|
550
557
|
}
|
|
551
558
|
return createTurn("abort", { hint: "Switch ceremony aborted." });
|
|
552
559
|
}, "Switch ceremony abort failed", ctx),
|
|
553
|
-
};
|
|
560
|
+
});
|
|
554
561
|
}
|
|
@@ -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,
|
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,36 +10,39 @@ 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
|
-
type
|
|
33
|
+
type AuthStartHandlerNoInputGuard<TStart> = TStart extends (...args: infer TArgs) => unknown ? TArgs["length"] extends 0 | 1 ? unknown : {
|
|
34
|
+
"auth start handlers must not declare input parameters; return a form turn from start and receive user input in continue": never;
|
|
35
|
+
} : unknown;
|
|
36
|
+
export type AuthStartNoInputGuard<TConfig> = TConfig extends {
|
|
34
37
|
auth?: {
|
|
35
38
|
flow?: {
|
|
36
39
|
start: infer TStart;
|
|
37
40
|
};
|
|
38
41
|
};
|
|
39
|
-
} ? TStart
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
export interface
|
|
42
|
+
} ? AuthStartHandlerNoInputGuard<TStart> : TConfig extends {
|
|
43
|
+
start: infer TStart;
|
|
44
|
+
} ? AuthStartHandlerNoInputGuard<TStart> : unknown;
|
|
45
|
+
export interface ProviderDeclaration {
|
|
43
46
|
id: string;
|
|
44
47
|
version: string;
|
|
45
48
|
runtime: "standard" | "shared" | "browser";
|
|
@@ -50,6 +53,8 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
50
53
|
* resolves omitted fields against the runtime deployment profiles.
|
|
51
54
|
*/
|
|
52
55
|
deployment?: ProviderDeploymentOverrides;
|
|
56
|
+
/** Declares that provider operations use the SDK HTTP client. */
|
|
57
|
+
http?: true;
|
|
53
58
|
allowedHosts?: string[];
|
|
54
59
|
native?: NativeProviderConfig;
|
|
55
60
|
stealth?: {
|
|
@@ -64,11 +69,21 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
64
69
|
engine: BrowserEngine;
|
|
65
70
|
};
|
|
66
71
|
auth?: AuthConfig;
|
|
72
|
+
/** Declares that provider operations issue and consume SDK choice tokens. */
|
|
73
|
+
choice?: true;
|
|
67
74
|
reviewed?: ProviderReviewed;
|
|
68
75
|
access?: ProviderAccessConfig;
|
|
69
76
|
secrets?: ProviderSecretDeclaration[];
|
|
77
|
+
/** Declares that provider operations read SDK-managed environment values. */
|
|
78
|
+
env?: true;
|
|
70
79
|
credential?: CredentialDeclaration;
|
|
71
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;
|
|
72
87
|
meta: {
|
|
73
88
|
displayName: string;
|
|
74
89
|
displayNameKey?: string;
|
|
@@ -90,16 +105,15 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
90
105
|
publicSchemaFieldNames?: "normalized";
|
|
91
106
|
};
|
|
92
107
|
};
|
|
93
|
-
operations: OperationMapConfig<TOperations>;
|
|
94
108
|
healthMonitor?: ProviderHealthMonitorConfig;
|
|
95
109
|
/** New name for `healthMonitor` (transitional alias); declaring both is a ValidationError. */
|
|
96
110
|
healthProbe?: ProviderHealthMonitorConfig;
|
|
97
111
|
healthJourneys?: readonly HealthJourneyDefinition[];
|
|
98
112
|
}
|
|
99
|
-
/** Define one provider operation with schema-driven handler inference. */
|
|
100
|
-
export declare function defineOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
101
|
-
/** Define a non-JSON
|
|
102
|
-
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>;
|
|
103
117
|
export declare function every(interval: string, options?: {
|
|
104
118
|
jitter?: string;
|
|
105
119
|
randomize?: HealthScheduleRandomization;
|
|
@@ -108,7 +122,14 @@ export declare function centered(maxOffset: string): HealthScheduleRandomization
|
|
|
108
122
|
export declare function delayed(maxDelay: string): HealthScheduleRandomization;
|
|
109
123
|
export declare function defineSmsOtpMatcher(config: Omit<SmsOtpMatcherDefinition, "extractOtp">): SmsOtpMatcherDefinition;
|
|
110
124
|
export declare function defineHealthJourney(config: HealthJourneyDefinition): HealthJourneyDefinition;
|
|
111
|
-
|
|
112
|
-
|
|
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>>;
|
|
113
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>;
|
|
114
135
|
export {};
|