@apifuse/provider-sdk 2.1.0-beta.8 → 2.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORING.md +134 -0
- package/CHANGELOG.md +61 -0
- package/README.md +21 -9
- package/SUBMISSION.md +10 -11
- package/bin/apifuse-pack-check.ts +22 -0
- package/bin/apifuse-perf.ts +18 -9
- package/bin/apifuse-submit-check.ts +1747 -388
- package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
- package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/dist/auth-turn/fixtures/valid/abort.json +8 -0
- package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
- package/dist/auth-turn/fixtures/valid/complete.json +13 -0
- package/dist/auth-turn/fixtures/valid/form.json +14 -0
- package/dist/auth-turn/fixtures/valid/message.json +13 -0
- package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/dist/auth-turn/fixtures/valid/pending.json +5 -0
- package/dist/auth-turn/fixtures/valid/poll.json +9 -0
- package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
- package/dist/auth-turn/fixtures/valid/retry.json +8 -0
- package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/dist/auth-turn/index.d.ts +195 -0
- package/dist/auth-turn/index.js +133 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +436 -0
- package/dist/ceremonies/index.js +7 -31
- package/dist/cli/create.js +45 -30
- package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
- package/dist/cli/templates/provider/.gitignore.tpl +22 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +87 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +1 -0
- package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
- package/dist/cli/templates/provider/README.md.tpl +163 -0
- package/dist/cli/templates/provider/dev.ts.tpl +5 -0
- package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
- package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
- package/dist/cli/templates/provider/index.ts.tpl +15 -0
- package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/dist/cli/templates/provider/meta.ts.tpl +7 -0
- package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/dist/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/dist/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/dist/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/dist/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/dist/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/contract.js +1 -0
- package/dist/define.d.ts +6 -1
- package/dist/define.js +140 -70
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +27 -0
- package/dist/provider.d.ts +4 -2
- package/dist/provider.js +2 -1
- package/dist/runtime/auth-flow.js +2 -0
- package/dist/runtime/browser.js +203 -0
- package/dist/runtime/http.js +28 -8
- package/dist/runtime/stealth.d.ts +5 -2
- package/dist/runtime/stealth.js +157 -4
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +4 -0
- package/dist/server/self-test-input-tokens.d.ts +1 -0
- package/dist/server/self-test-input-tokens.js +37 -0
- package/dist/server/self-test-redaction.d.ts +20 -0
- package/dist/server/self-test-redaction.js +70 -0
- package/dist/server/self-test-token.d.ts +30 -0
- package/dist/server/self-test-token.js +50 -0
- package/dist/server/self-test.d.ts +98 -0
- package/dist/server/self-test.js +555 -0
- package/dist/server/serve.d.ts +6 -0
- package/dist/server/serve.js +33 -8
- package/dist/testing/run.js +5 -1
- package/dist/types.d.ts +152 -0
- package/package.json +10 -3
- package/src/auth-turn/auth-turn.v1.schema.json +89 -0
- package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/src/auth-turn/fixtures/valid/abort.json +8 -0
- package/src/auth-turn/fixtures/valid/challenge.json +17 -0
- package/src/auth-turn/fixtures/valid/complete.json +13 -0
- package/src/auth-turn/fixtures/valid/form.json +14 -0
- package/src/auth-turn/fixtures/valid/message.json +13 -0
- package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/src/auth-turn/fixtures/valid/pending.json +5 -0
- package/src/auth-turn/fixtures/valid/poll.json +9 -0
- package/src/auth-turn/fixtures/valid/redirect.json +16 -0
- package/src/auth-turn/fixtures/valid/retry.json +8 -0
- package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/src/auth-turn/index.ts +177 -0
- package/src/auth.ts +786 -0
- package/src/ceremonies/index.ts +9 -43
- package/src/cli/create.ts +60 -97
- package/src/cli/templates/provider/AGENTS.md.tpl +87 -0
- package/src/cli/templates/provider/CLAUDE.md.tpl +1 -0
- package/src/cli/templates/provider/README.md.tpl +7 -4
- package/src/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/src/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/src/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/src/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/src/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/src/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
- package/src/contract.ts +1 -0
- package/src/define.ts +198 -71
- package/src/index.ts +16 -0
- package/src/lint.ts +33 -0
- package/src/provider.ts +27 -0
- package/src/runtime/auth-flow.ts +2 -0
- package/src/runtime/browser.ts +293 -1
- package/src/runtime/http.ts +48 -7
- package/src/runtime/stealth.ts +190 -6
- package/src/server/index.ts +36 -0
- package/src/server/self-test-input-tokens.ts +46 -0
- package/src/server/self-test-redaction.ts +97 -0
- package/src/server/self-test-token.ts +70 -0
- package/src/server/self-test.ts +725 -0
- package/src/server/serve.ts +67 -4
- package/src/testing/run.ts +9 -1
- package/src/types.ts +188 -0
package/dist/server/serve.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { Hono } from "hono";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { AuthAbortError, createAuthFlowHelpers } from "../auth";
|
|
5
6
|
import { AuthError, ProviderError, SessionExpiredError, TransportError, } from "../errors";
|
|
6
7
|
import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog";
|
|
7
8
|
import { categoryForStatus, isRetryableCategory, PROVIDER_OBSERVABILITY_TAXONOMY_VERSION, } from "../observability";
|
|
@@ -24,6 +25,8 @@ import { createTraceContext } from "../runtime/trace";
|
|
|
24
25
|
import { parseSchema } from "../schema";
|
|
25
26
|
import { getStealthProfile } from "../stealth/profiles";
|
|
26
27
|
import { APIFUSE_STREAM_DONE_EVENT, APIFUSE_STREAM_ERROR_EVENT, encodeSseEvent, error as streamError, } from "../stream";
|
|
28
|
+
import { createSelfTestApp, createSelfTestInvoke, resolveSelfTestPort, } from "./self-test";
|
|
29
|
+
import { resolveSelfTestMasterSecrets } from "./self-test-token";
|
|
27
30
|
import { AuthFlowRequestSchema, OperationRequestSchema, } from "./types";
|
|
28
31
|
const DEFAULT_HOST = "0.0.0.0";
|
|
29
32
|
const DEFAULT_PORT = 3000;
|
|
@@ -209,7 +212,7 @@ function createFlowContextStore(allowedKeys, initialContext = {}) {
|
|
|
209
212
|
},
|
|
210
213
|
};
|
|
211
214
|
}
|
|
212
|
-
function createAuthFlowContext(provider, request, options = {}) {
|
|
215
|
+
function createAuthFlowContext(provider, request, options = {}, signal) {
|
|
213
216
|
const baseUrl = getProviderBaseUrl(provider);
|
|
214
217
|
const stealthBaseUrl = getProviderStealthBaseUrl(provider);
|
|
215
218
|
const stealthProfile = getProviderStealthProfile(provider);
|
|
@@ -251,6 +254,7 @@ function createAuthFlowContext(provider, request, options = {}) {
|
|
|
251
254
|
credential,
|
|
252
255
|
context: flowContextStore.context,
|
|
253
256
|
stt: options.stt ?? createSttClientFromEnv(provider.stt),
|
|
257
|
+
auth: createAuthFlowHelpers({ signal }),
|
|
254
258
|
},
|
|
255
259
|
getPatch: flowContextStore.getPatch,
|
|
256
260
|
};
|
|
@@ -879,14 +883,14 @@ function responseWithProviderTelemetry(response, proxyTelemetry) {
|
|
|
879
883
|
statusText: response.statusText,
|
|
880
884
|
});
|
|
881
885
|
}
|
|
882
|
-
async function handleAuthFlow(provider, request, route, options = {}) {
|
|
886
|
+
async function handleAuthFlow(provider, request, route, options = {}, signal) {
|
|
883
887
|
const flow = provider.auth?.flow;
|
|
884
888
|
if (!flow) {
|
|
885
889
|
throw new ProviderError("Auth flow is not configured", {
|
|
886
890
|
code: "AUTH_FLOW_NOT_CONFIGURED",
|
|
887
891
|
});
|
|
888
892
|
}
|
|
889
|
-
const { context, getPatch } = createAuthFlowContext(provider, request, options);
|
|
893
|
+
const { context, getPatch } = createAuthFlowContext(provider, request, options, signal);
|
|
890
894
|
try {
|
|
891
895
|
const result = route === "start"
|
|
892
896
|
? await flow.start(context)
|
|
@@ -916,6 +920,12 @@ async function handleAuthFlow(provider, request, route, options = {}) {
|
|
|
916
920
|
: result;
|
|
917
921
|
return toAuthFlowResponse(materializedResult, getPatch());
|
|
918
922
|
}
|
|
923
|
+
catch (error) {
|
|
924
|
+
if (error instanceof AuthAbortError) {
|
|
925
|
+
return toAuthFlowResponse(error.turn, getPatch());
|
|
926
|
+
}
|
|
927
|
+
throw error;
|
|
928
|
+
}
|
|
919
929
|
finally {
|
|
920
930
|
context.stealth.close?.();
|
|
921
931
|
}
|
|
@@ -982,7 +992,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
982
992
|
.json()
|
|
983
993
|
.catch(() => undefined);
|
|
984
994
|
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
985
|
-
const response = await handleAuthFlow(provider, body, "start", options);
|
|
995
|
+
const response = await handleAuthFlow(provider, body, "start", options, c.req.raw.signal);
|
|
986
996
|
logProviderSuccess(logger, provider, "auth", "start", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
|
|
987
997
|
return response instanceof Response ? response : c.json(response);
|
|
988
998
|
}
|
|
@@ -1002,7 +1012,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1002
1012
|
.json()
|
|
1003
1013
|
.catch(() => undefined);
|
|
1004
1014
|
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
1005
|
-
const response = await handleAuthFlow(provider, body, "continue", options);
|
|
1015
|
+
const response = await handleAuthFlow(provider, body, "continue", options, c.req.raw.signal);
|
|
1006
1016
|
logProviderSuccess(logger, provider, "auth", "continue", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
|
|
1007
1017
|
return response instanceof Response ? response : c.json(response);
|
|
1008
1018
|
}
|
|
@@ -1022,7 +1032,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1022
1032
|
.json()
|
|
1023
1033
|
.catch(() => undefined);
|
|
1024
1034
|
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
1025
|
-
const response = await handleAuthFlow(provider, body, "poll", options);
|
|
1035
|
+
const response = await handleAuthFlow(provider, body, "poll", options, c.req.raw.signal);
|
|
1026
1036
|
logProviderSuccess(logger, provider, "auth", "poll", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
|
|
1027
1037
|
return response instanceof Response ? response : c.json(response);
|
|
1028
1038
|
}
|
|
@@ -1042,7 +1052,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1042
1052
|
.json()
|
|
1043
1053
|
.catch(() => undefined);
|
|
1044
1054
|
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
1045
|
-
const response = await handleAuthFlow(provider, body, "refresh", options);
|
|
1055
|
+
const response = await handleAuthFlow(provider, body, "refresh", options, c.req.raw.signal);
|
|
1046
1056
|
logProviderSuccess(logger, provider, "auth", "refresh", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
|
|
1047
1057
|
return response instanceof Response ? response : c.json(response);
|
|
1048
1058
|
}
|
|
@@ -1062,7 +1072,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1062
1072
|
.json()
|
|
1063
1073
|
.catch(() => undefined);
|
|
1064
1074
|
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
1065
|
-
const response = await handleAuthFlow(provider, body, "abort", options);
|
|
1075
|
+
const response = await handleAuthFlow(provider, body, "abort", options, c.req.raw.signal);
|
|
1066
1076
|
logProviderSuccess(logger, provider, "auth", "disconnect", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
|
|
1067
1077
|
return response instanceof Response ? response : c.json(response);
|
|
1068
1078
|
}
|
|
@@ -1106,5 +1116,20 @@ export async function serve(provider, options = {}) {
|
|
|
1106
1116
|
hostname: options.host ?? DEFAULT_HOST,
|
|
1107
1117
|
fetch: app.fetch,
|
|
1108
1118
|
});
|
|
1119
|
+
// Internal self-test listener (health dependency inversion): a SEPARATE
|
|
1120
|
+
// socket the tenant-facing gateway never dials. Off by default — it only
|
|
1121
|
+
// starts when the shared self-test master secret env is present.
|
|
1122
|
+
const selfTestSecrets = resolveSelfTestMasterSecrets();
|
|
1123
|
+
if (selfTestSecrets) {
|
|
1124
|
+
const selfTestApp = createSelfTestApp(provider, {
|
|
1125
|
+
secrets: selfTestSecrets,
|
|
1126
|
+
invoke: createSelfTestInvoke(app),
|
|
1127
|
+
});
|
|
1128
|
+
bunRuntime.serve({
|
|
1129
|
+
port: options.selfTestPort ?? resolveSelfTestPort(),
|
|
1130
|
+
hostname: options.host ?? DEFAULT_HOST,
|
|
1131
|
+
fetch: selfTestApp.fetch,
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1109
1134
|
await Promise.resolve();
|
|
1110
1135
|
}
|
package/dist/testing/run.js
CHANGED
|
@@ -68,13 +68,17 @@ function inferFixtureDir(providerId) {
|
|
|
68
68
|
return `providers/${providerId}/__fixtures__`;
|
|
69
69
|
}
|
|
70
70
|
function jsonResponse(data) {
|
|
71
|
+
const body = JSON.stringify(data);
|
|
72
|
+
const bodyBytes = new TextEncoder().encode(body);
|
|
71
73
|
return {
|
|
72
74
|
status: 200,
|
|
73
75
|
ok: true,
|
|
74
76
|
headers: {},
|
|
75
77
|
data,
|
|
76
78
|
json: async () => JSON.parse(JSON.stringify(data)),
|
|
77
|
-
text: async () =>
|
|
79
|
+
text: async () => body,
|
|
80
|
+
arrayBuffer: async () => bodyBytes.buffer.slice(bodyBytes.byteOffset, bodyBytes.byteOffset + bodyBytes.byteLength),
|
|
81
|
+
bytes: async () => bodyBytes.slice(0),
|
|
78
82
|
};
|
|
79
83
|
}
|
|
80
84
|
function unsupported(name) {
|
package/dist/types.d.ts
CHANGED
|
@@ -268,8 +268,16 @@ export interface HealthJourneySchedule {
|
|
|
268
268
|
kind: "interval";
|
|
269
269
|
/** ISO 8601 duration, for example PT8H. */
|
|
270
270
|
interval: Iso8601Duration;
|
|
271
|
+
randomize?: HealthScheduleRandomization;
|
|
271
272
|
jitter?: Iso8601Duration;
|
|
272
273
|
}
|
|
274
|
+
export type HealthScheduleRandomization = {
|
|
275
|
+
mode: "centered";
|
|
276
|
+
maxOffset: Iso8601Duration;
|
|
277
|
+
} | {
|
|
278
|
+
mode: "delayed";
|
|
279
|
+
maxDelay: Iso8601Duration;
|
|
280
|
+
};
|
|
273
281
|
export interface HealthJourneyStep {
|
|
274
282
|
id: string;
|
|
275
283
|
description?: string;
|
|
@@ -511,6 +519,9 @@ export interface HealthCheckCase<TInput = unknown, TOutput = unknown> {
|
|
|
511
519
|
export interface HealthCheckSuite<TInput = unknown, TOutput = unknown> {
|
|
512
520
|
/** Polling interval for the suite. All cases share this cadence. */
|
|
513
521
|
interval: ProbeInterval;
|
|
522
|
+
schedule?: {
|
|
523
|
+
randomize?: HealthScheduleRandomization;
|
|
524
|
+
};
|
|
514
525
|
/** Per-case timeout in milliseconds. Default: 30000. */
|
|
515
526
|
timeoutMs?: number;
|
|
516
527
|
/** Default degradation threshold for cases in this suite. Default: runtime threshold. */
|
|
@@ -583,6 +594,13 @@ export interface ProviderHealthMonitorConfig {
|
|
|
583
594
|
*/
|
|
584
595
|
serviceAccount?: string;
|
|
585
596
|
}
|
|
597
|
+
/**
|
|
598
|
+
* New name for `ProviderHealthMonitorConfig` as part of the health dependency
|
|
599
|
+
* inversion (`healthMonitor` → `healthProbe`). Both provider-level fields are
|
|
600
|
+
* accepted transitionally with identical validation; declaring both is a
|
|
601
|
+
* `ValidationError`.
|
|
602
|
+
*/
|
|
603
|
+
export type ProviderHealthProbeConfig = ProviderHealthMonitorConfig;
|
|
586
604
|
export interface HealthMonitorProbeOverride {
|
|
587
605
|
/** Optional runtime interval override as a positive `ms`-style duration string. */
|
|
588
606
|
interval?: ProbeInterval;
|
|
@@ -805,6 +823,7 @@ export type HttpMethod = "HEAD" | "head" | "GET" | "get" | "POST" | "post" | "PU
|
|
|
805
823
|
export interface StealthFetchOptions extends RequestOptions {
|
|
806
824
|
method?: HttpMethod;
|
|
807
825
|
body?: string | Buffer;
|
|
826
|
+
redirect?: "follow" | "manual" | "error";
|
|
808
827
|
/**
|
|
809
828
|
* Offsets policy-managed proxy pool selection for caller-managed retries.
|
|
810
829
|
* Use when a request receives an upstream challenge page rather than a
|
|
@@ -829,9 +848,19 @@ export interface CookieJar {
|
|
|
829
848
|
toString(): string;
|
|
830
849
|
find?(predicate: (cookie: string) => boolean): string | undefined;
|
|
831
850
|
}
|
|
851
|
+
export interface StealthSessionCookies extends CookieJar {
|
|
852
|
+
has(name: string): boolean;
|
|
853
|
+
setFromCookieStrings(cookieStrings: readonly string[]): void;
|
|
854
|
+
toHeader(): string;
|
|
855
|
+
snapshot(): Record<string, string>;
|
|
856
|
+
restore(cookies: Record<string, string>): void;
|
|
857
|
+
clear(): void;
|
|
858
|
+
}
|
|
832
859
|
export interface DeclarativeStealthResponse {
|
|
833
860
|
status: number;
|
|
834
861
|
ok: boolean;
|
|
862
|
+
url?: string;
|
|
863
|
+
redirected?: boolean;
|
|
835
864
|
headers: Record<string, string>;
|
|
836
865
|
rawHeaders: [string, string][];
|
|
837
866
|
body: string;
|
|
@@ -843,14 +872,38 @@ export interface DeclarativeStealthResponse {
|
|
|
843
872
|
};
|
|
844
873
|
cookies: CookieJar;
|
|
845
874
|
json<T>(): Promise<T>;
|
|
875
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
876
|
+
bytes(): Promise<Uint8Array>;
|
|
846
877
|
}
|
|
847
878
|
export type StealthResponse = DeclarativeStealthResponse;
|
|
848
879
|
export type RequestWithMethodOptions = RequestOptions & {
|
|
849
880
|
method?: string;
|
|
850
881
|
body?: unknown;
|
|
851
882
|
};
|
|
883
|
+
export interface StealthRedirectHop {
|
|
884
|
+
url: string;
|
|
885
|
+
status: number;
|
|
886
|
+
method: string;
|
|
887
|
+
location?: string;
|
|
888
|
+
nextUrl?: string;
|
|
889
|
+
}
|
|
890
|
+
export interface StealthRedirectRunOptions extends Omit<StealthFetchOptions, "redirect"> {
|
|
891
|
+
url: string;
|
|
892
|
+
maxHops?: number;
|
|
893
|
+
stopWhen?: (hop: StealthRedirectHop) => boolean | Promise<boolean>;
|
|
894
|
+
}
|
|
895
|
+
export interface StealthRedirectRunResult {
|
|
896
|
+
final: StealthResponse;
|
|
897
|
+
hops: StealthRedirectHop[];
|
|
898
|
+
reason: "completed" | "stopped" | "max_hops" | "missing_location" | "loop";
|
|
899
|
+
cookies: Record<string, string>;
|
|
900
|
+
}
|
|
852
901
|
export interface StealthSession {
|
|
853
902
|
fetch(url: string, options?: StealthFetchOptions): Promise<StealthResponse>;
|
|
903
|
+
cookies: StealthSessionCookies;
|
|
904
|
+
redirects: {
|
|
905
|
+
run(options: StealthRedirectRunOptions): Promise<StealthRedirectRunResult>;
|
|
906
|
+
};
|
|
854
907
|
close(): void;
|
|
855
908
|
}
|
|
856
909
|
export interface ApiFuseResponse<T> {
|
|
@@ -871,6 +924,8 @@ export interface HttpResponse<T = unknown> {
|
|
|
871
924
|
data: T;
|
|
872
925
|
json<U = T>(): Promise<U>;
|
|
873
926
|
text(): Promise<string>;
|
|
927
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
928
|
+
bytes(): Promise<Uint8Array>;
|
|
874
929
|
}
|
|
875
930
|
export interface HttpStreamResponse {
|
|
876
931
|
status: number;
|
|
@@ -981,6 +1036,32 @@ export interface BrowserFrame {
|
|
|
981
1036
|
evaluate<T>(fn: string | (() => T)): Promise<T>;
|
|
982
1037
|
locator(selector: string): BrowserLocator;
|
|
983
1038
|
}
|
|
1039
|
+
export type BrowserResourceMethod = "GET" | "HEAD";
|
|
1040
|
+
export type BrowserResourceRequest = {
|
|
1041
|
+
readonly url: string;
|
|
1042
|
+
readonly method: BrowserResourceMethod;
|
|
1043
|
+
readonly resourceType?: string;
|
|
1044
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
1045
|
+
};
|
|
1046
|
+
export type BrowserResourceBody = Buffer | Uint8Array | ArrayBuffer | string;
|
|
1047
|
+
export type BrowserResourceDecision = {
|
|
1048
|
+
readonly action: "fulfill";
|
|
1049
|
+
readonly status?: number;
|
|
1050
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
1051
|
+
readonly body?: BrowserResourceBody;
|
|
1052
|
+
} | {
|
|
1053
|
+
readonly action: "block";
|
|
1054
|
+
readonly reason?: string;
|
|
1055
|
+
};
|
|
1056
|
+
export type BrowserResourceRoute = {
|
|
1057
|
+
readonly match: string | RegExp | ((request: BrowserResourceRequest) => boolean);
|
|
1058
|
+
readonly handle: (request: BrowserResourceRequest) => Promise<BrowserResourceDecision> | BrowserResourceDecision;
|
|
1059
|
+
};
|
|
1060
|
+
export type BrowserResourcePolicy = {
|
|
1061
|
+
readonly defaultAction?: "block";
|
|
1062
|
+
readonly allowedMethods?: readonly BrowserResourceMethod[];
|
|
1063
|
+
readonly routes: readonly BrowserResourceRoute[];
|
|
1064
|
+
};
|
|
984
1065
|
export interface BrowserPage extends BrowserFrame {
|
|
985
1066
|
close(): Promise<void>;
|
|
986
1067
|
fill(selector: string, text: string): Promise<void>;
|
|
@@ -995,6 +1076,7 @@ export interface BrowserPage extends BrowserFrame {
|
|
|
995
1076
|
timeout?: number;
|
|
996
1077
|
}): Promise<void>;
|
|
997
1078
|
frames(): Promise<BrowserFrame[]>;
|
|
1079
|
+
withResourcePolicy<T>(policy: BrowserResourcePolicy, run: () => Promise<T>): Promise<T>;
|
|
998
1080
|
}
|
|
999
1081
|
export type BrowserChallengeRequest = {
|
|
1000
1082
|
type: "recaptcha";
|
|
@@ -1135,6 +1217,73 @@ export interface ContextScratchpad {
|
|
|
1135
1217
|
toJSON(): Record<string, unknown>;
|
|
1136
1218
|
}
|
|
1137
1219
|
export type FlowContextStore = ContextScratchpad;
|
|
1220
|
+
export type AuthSafeJson = string | number | boolean | null | readonly AuthSafeJson[] | {
|
|
1221
|
+
readonly [key: string]: AuthSafeJson;
|
|
1222
|
+
};
|
|
1223
|
+
export type AuthSafeData = {
|
|
1224
|
+
readonly [key: string]: AuthSafeJson;
|
|
1225
|
+
};
|
|
1226
|
+
export type AuthAbortRetry = "never" | "retry" | "after_user_action";
|
|
1227
|
+
export type AuthAbortData = Record<string, unknown> & {
|
|
1228
|
+
readonly code: string;
|
|
1229
|
+
readonly message?: string;
|
|
1230
|
+
readonly retry?: AuthAbortRetry;
|
|
1231
|
+
readonly actionHint?: AuthSafeJson;
|
|
1232
|
+
readonly fieldErrors?: {
|
|
1233
|
+
readonly [field: string]: string;
|
|
1234
|
+
};
|
|
1235
|
+
readonly details?: AuthSafeData;
|
|
1236
|
+
};
|
|
1237
|
+
export interface AuthFlowTerminalContext {
|
|
1238
|
+
readonly signal?: AbortSignal;
|
|
1239
|
+
readonly deadline?: string;
|
|
1240
|
+
complete<TCredential extends Record<string, string>>(options: {
|
|
1241
|
+
readonly credential: TCredential;
|
|
1242
|
+
readonly metadata?: AuthSafeData;
|
|
1243
|
+
readonly data?: AuthSafeData;
|
|
1244
|
+
readonly turnId?: string;
|
|
1245
|
+
readonly expiresAt?: string;
|
|
1246
|
+
}): AuthTurn;
|
|
1247
|
+
abort(options: {
|
|
1248
|
+
readonly code: string;
|
|
1249
|
+
readonly message?: string;
|
|
1250
|
+
readonly retry?: AuthAbortRetry;
|
|
1251
|
+
readonly actionHint?: AuthSafeJson;
|
|
1252
|
+
readonly fieldErrors?: {
|
|
1253
|
+
readonly [field: string]: string;
|
|
1254
|
+
};
|
|
1255
|
+
readonly data?: AuthSafeData;
|
|
1256
|
+
readonly turnId?: string;
|
|
1257
|
+
readonly expiresAt?: string;
|
|
1258
|
+
}): AuthTurn;
|
|
1259
|
+
nextForm(options: {
|
|
1260
|
+
readonly hintKey?: ProviderLocaleKeyInput;
|
|
1261
|
+
readonly data?: AuthSafeData;
|
|
1262
|
+
readonly turnId?: string;
|
|
1263
|
+
readonly expiresAt?: string;
|
|
1264
|
+
readonly timing?: AuthTurn["timing"];
|
|
1265
|
+
} & ({
|
|
1266
|
+
readonly fields: Record<string, {
|
|
1267
|
+
readonly type?: "string" | "email" | "password" | "otp";
|
|
1268
|
+
readonly labelKey?: ProviderLocaleKeyInput;
|
|
1269
|
+
readonly descriptionKey?: ProviderLocaleKeyInput;
|
|
1270
|
+
readonly placeholderKey?: ProviderLocaleKeyInput;
|
|
1271
|
+
readonly required?: boolean;
|
|
1272
|
+
readonly sensitive?: boolean;
|
|
1273
|
+
}>;
|
|
1274
|
+
readonly expectedInput?: never;
|
|
1275
|
+
} | {
|
|
1276
|
+
readonly expectedInput: Record<string, unknown>;
|
|
1277
|
+
readonly fields?: never;
|
|
1278
|
+
})): AuthTurn;
|
|
1279
|
+
nextPoll(options?: {
|
|
1280
|
+
readonly hintKey?: ProviderLocaleKeyInput;
|
|
1281
|
+
readonly data?: AuthSafeData;
|
|
1282
|
+
readonly turnId?: string;
|
|
1283
|
+
readonly expiresAt?: string;
|
|
1284
|
+
readonly timing?: AuthTurn["timing"];
|
|
1285
|
+
}): AuthTurn;
|
|
1286
|
+
}
|
|
1138
1287
|
export interface FlowContext {
|
|
1139
1288
|
connectionId?: string;
|
|
1140
1289
|
externalRef?: string;
|
|
@@ -1146,6 +1295,7 @@ export interface FlowContext {
|
|
|
1146
1295
|
credential?: CredentialContext;
|
|
1147
1296
|
context: ContextScratchpad;
|
|
1148
1297
|
stt: SttContext;
|
|
1298
|
+
auth: AuthFlowTerminalContext;
|
|
1149
1299
|
}
|
|
1150
1300
|
export interface AuthTurn {
|
|
1151
1301
|
kind: string;
|
|
@@ -1320,5 +1470,7 @@ export interface ProviderDefinition {
|
|
|
1320
1470
|
meta: ProviderMeta;
|
|
1321
1471
|
operations: Record<string, OperationDefinition<SchemaLike, SchemaLike>>;
|
|
1322
1472
|
healthMonitor?: ProviderHealthMonitorConfig;
|
|
1473
|
+
/** Transitional alias for `healthMonitor`; `defineProvider` mirrors both. */
|
|
1474
|
+
healthProbe?: ProviderHealthProbeConfig;
|
|
1323
1475
|
healthJourneys?: readonly HealthJourneyDefinition[];
|
|
1324
1476
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
+
"version": "2.2.0-beta.1",
|
|
2
3
|
"name": "@apifuse/provider-sdk",
|
|
3
|
-
"version": "2.1.0-beta.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "APIFuse Provider SDK
|
|
6
|
+
"description": "APIFuse Provider SDK — Build providers with zero architectural constraints",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
@@ -50,6 +50,12 @@
|
|
|
50
50
|
"import": "./dist/contract.js",
|
|
51
51
|
"default": "./dist/contract.js"
|
|
52
52
|
},
|
|
53
|
+
"./auth-turn": {
|
|
54
|
+
"types": "./dist/auth-turn/index.d.ts",
|
|
55
|
+
"import": "./dist/auth-turn/index.js",
|
|
56
|
+
"default": "./dist/auth-turn/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./auth-turn/auth-turn.v1.schema.json": "./dist/auth-turn/auth-turn.v1.schema.json",
|
|
53
59
|
"./server": {
|
|
54
60
|
"types": "./dist/server/index.d.ts",
|
|
55
61
|
"import": "./dist/server/index.js",
|
|
@@ -77,7 +83,7 @@
|
|
|
77
83
|
"pack:smoke": "bun run build && bun bin/apifuse-pack-smoke.ts",
|
|
78
84
|
"release:guard": "bun scripts/guard-release-pr.ts",
|
|
79
85
|
"format:check": "biome format .",
|
|
80
|
-
"build": "tsgo -p tsconfig.build.json"
|
|
86
|
+
"build": "tsgo -p tsconfig.build.json && rm -rf dist/cli/templates && cp -R src/cli/templates dist/cli/templates && rm -rf dist/auth-turn/fixtures && cp -R src/auth-turn/fixtures dist/auth-turn/fixtures && cp src/auth-turn/auth-turn.v1.schema.json dist/auth-turn/auth-turn.v1.schema.json"
|
|
81
87
|
},
|
|
82
88
|
"devDependencies": {
|
|
83
89
|
"@biomejs/biome": "^2.5.0",
|
|
@@ -88,6 +94,7 @@
|
|
|
88
94
|
"dependencies": {
|
|
89
95
|
"@clack/prompts": "^1.5.1",
|
|
90
96
|
"@types/ms": "^2.1.0",
|
|
97
|
+
"acorn": "^8.17.0",
|
|
91
98
|
"ajv": "^8.17",
|
|
92
99
|
"hono": "^4.12.25",
|
|
93
100
|
"impit": "0.14.1",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://apifuse.com/contracts/auth-turn/v1",
|
|
4
|
+
"title": "APIFuse AuthTurn envelope v1",
|
|
5
|
+
"description": "Protocol message exchanged during auth.flow ceremonies. kind is an open string; known kinds are tooling metadata, not a wire constraint.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["kind", "turnId"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"kind": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"description": "Open turn kind. Known kinds are listed in the TURN_KINDS registry; unknown kinds remain valid on the wire."
|
|
14
|
+
},
|
|
15
|
+
"turnId": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1,
|
|
18
|
+
"description": "Provider-scoped identifier of this turn."
|
|
19
|
+
},
|
|
20
|
+
"expiresAt": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1,
|
|
23
|
+
"description": "Turn expiry timestamp as an ISO 8601 / RFC 3339 string."
|
|
24
|
+
},
|
|
25
|
+
"data": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"additionalProperties": true,
|
|
28
|
+
"description": "Kind-specific payload. Terminal kinds carry the payloads described in $defs."
|
|
29
|
+
},
|
|
30
|
+
"expectedInput": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": true,
|
|
33
|
+
"description": "JSON Schema describing the input expected next."
|
|
34
|
+
},
|
|
35
|
+
"hint": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Deprecated but load-bearing human-readable hint materialized from provider locale catalogs."
|
|
38
|
+
},
|
|
39
|
+
"hintKey": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Provider locale catalog key for the turn hint."
|
|
42
|
+
},
|
|
43
|
+
"timing": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"description": "Client pacing guidance for poll-style turns.",
|
|
47
|
+
"properties": {
|
|
48
|
+
"suggestedPollIntervalMs": {
|
|
49
|
+
"type": "number",
|
|
50
|
+
"minimum": 1
|
|
51
|
+
},
|
|
52
|
+
"maxWaitMs": {
|
|
53
|
+
"type": "number",
|
|
54
|
+
"minimum": 1
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"$defs": {
|
|
60
|
+
"completeTurnData": {
|
|
61
|
+
"title": "Terminal payload for kind \"complete\"",
|
|
62
|
+
"description": "data payload of a complete turn. The gateway extracts data.credential for persistence; complete turns are never echoed to browsers.",
|
|
63
|
+
"type": "object",
|
|
64
|
+
"additionalProperties": true,
|
|
65
|
+
"required": ["credential"],
|
|
66
|
+
"properties": {
|
|
67
|
+
"credential": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": true
|
|
70
|
+
},
|
|
71
|
+
"metadata": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"abortTurnData": {
|
|
78
|
+
"title": "Terminal payload for kind \"abort\"",
|
|
79
|
+
"description": "data payload of an abort turn. code, when present, is the machine-readable abort reason.",
|
|
80
|
+
"type": "object",
|
|
81
|
+
"additionalProperties": true,
|
|
82
|
+
"properties": {
|
|
83
|
+
"code": {
|
|
84
|
+
"type": "string"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "challenge",
|
|
3
|
+
"turnId": "fixture.challenge",
|
|
4
|
+
"hint": "Complete the WebAuthn prompt in your browser.",
|
|
5
|
+
"expiresAt": "2026-01-01T00:00:00.000Z",
|
|
6
|
+
"data": {
|
|
7
|
+
"challenge": "fixture-challenge-not-real",
|
|
8
|
+
"rpId": "example.com"
|
|
9
|
+
},
|
|
10
|
+
"expectedInput": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["attestation"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"attestation": { "type": "object" }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "form",
|
|
3
|
+
"turnId": "fixture.form",
|
|
4
|
+
"hint": "Provide the required input to continue.",
|
|
5
|
+
"data": {},
|
|
6
|
+
"expectedInput": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"required": ["apiKey"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"apiKey": { "type": "string" }
|
|
11
|
+
},
|
|
12
|
+
"x-apifuse-field-order": ["apiKey"]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "message",
|
|
3
|
+
"turnId": "fixture.message",
|
|
4
|
+
"hint": "Enter the code on the verification page, then poll for completion.",
|
|
5
|
+
"data": {
|
|
6
|
+
"user_code": "FIXT-CODE",
|
|
7
|
+
"verification_uri": "https://example.com/device"
|
|
8
|
+
},
|
|
9
|
+
"timing": {
|
|
10
|
+
"suggestedPollIntervalMs": 5000,
|
|
11
|
+
"maxWaitMs": 120000
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "multi_choice",
|
|
3
|
+
"turnId": "fixture.multi_choice",
|
|
4
|
+
"hint": "Choose an authentication method.",
|
|
5
|
+
"data": {
|
|
6
|
+
"choices": ["oauth", "form"]
|
|
7
|
+
},
|
|
8
|
+
"expectedInput": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["choice"],
|
|
11
|
+
"properties": {
|
|
12
|
+
"choice": { "type": "string", "enum": ["oauth", "form"] }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|