@apifuse/provider-sdk 2.1.0-beta.2 → 2.1.0-beta.21

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.
Files changed (231) hide show
  1. package/AUTHORING.md +330 -8
  2. package/CHANGELOG.md +89 -1
  3. package/README.md +64 -17
  4. package/SUBMISSION.md +86 -0
  5. package/bin/apifuse-check.ts +60 -6
  6. package/bin/apifuse-dev.ts +58 -8
  7. package/bin/apifuse-pack-check.ts +32 -2
  8. package/bin/apifuse-pack-smoke.ts +133 -6
  9. package/bin/apifuse-perf.ts +142 -49
  10. package/bin/apifuse-record.ts +182 -104
  11. package/bin/apifuse-submit-check.ts +3243 -0
  12. package/bin/apifuse.ts +1 -1
  13. package/dist/auth.d.ts +76 -0
  14. package/dist/auth.js +436 -0
  15. package/dist/ceremonies/index.d.ts +41 -0
  16. package/dist/ceremonies/index.js +490 -0
  17. package/dist/choice-token.d.ts +24 -0
  18. package/dist/choice-token.js +74 -0
  19. package/dist/cli/commands.d.ts +10 -0
  20. package/dist/cli/commands.js +80 -0
  21. package/dist/cli/create.d.ts +47 -0
  22. package/dist/cli/create.js +777 -0
  23. package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
  24. package/dist/cli/templates/provider/.gitignore.tpl +22 -0
  25. package/dist/cli/templates/provider/AGENTS.md.tpl +87 -0
  26. package/dist/cli/templates/provider/CLAUDE.md.tpl +1 -0
  27. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  28. package/dist/cli/templates/provider/README.md.tpl +163 -0
  29. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  30. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  31. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  32. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  33. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  34. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  35. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  36. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  37. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  38. package/dist/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  39. package/dist/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  40. package/dist/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
  41. package/dist/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  42. package/dist/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  43. package/dist/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
  44. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  45. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  46. package/dist/config/loader.d.ts +107 -0
  47. package/dist/config/loader.js +935 -0
  48. package/dist/contract-json.d.ts +9 -0
  49. package/dist/contract-json.js +51 -0
  50. package/dist/contract-serialization.d.ts +4 -0
  51. package/dist/contract-serialization.js +78 -0
  52. package/dist/contract-types.d.ts +49 -0
  53. package/dist/contract-types.js +1 -0
  54. package/dist/contract.d.ts +6 -0
  55. package/dist/contract.js +156 -0
  56. package/dist/define.d.ts +100 -0
  57. package/dist/define.js +1383 -0
  58. package/dist/dev.d.ts +9 -0
  59. package/dist/dev.js +15 -0
  60. package/dist/errors.d.ts +59 -0
  61. package/dist/errors.js +97 -0
  62. package/dist/i18n/catalog.d.ts +29 -0
  63. package/dist/i18n/catalog.js +159 -0
  64. package/dist/i18n/index.d.ts +2 -0
  65. package/dist/i18n/index.js +2 -0
  66. package/dist/i18n/keys.d.ts +10 -0
  67. package/dist/i18n/keys.js +34 -0
  68. package/dist/index.d.ts +42 -0
  69. package/dist/index.js +38 -0
  70. package/dist/lint.d.ts +74 -0
  71. package/dist/lint.js +729 -0
  72. package/dist/observability.d.ts +5 -0
  73. package/dist/observability.js +39 -0
  74. package/dist/provider.d.ts +11 -0
  75. package/dist/provider.js +9 -0
  76. package/dist/public-schema-field-lint.d.ts +2 -0
  77. package/dist/public-schema-field-lint.js +158 -0
  78. package/dist/recipes/gov-api.d.ts +19 -0
  79. package/dist/recipes/gov-api.js +72 -0
  80. package/dist/recipes/rest-api.d.ts +21 -0
  81. package/dist/recipes/rest-api.js +115 -0
  82. package/dist/runtime/auth-flow.d.ts +14 -0
  83. package/dist/runtime/auth-flow.js +46 -0
  84. package/dist/runtime/browser.d.ts +25 -0
  85. package/dist/runtime/browser.js +1237 -0
  86. package/dist/runtime/cache.d.ts +10 -0
  87. package/dist/runtime/cache.js +372 -0
  88. package/dist/runtime/choice.d.ts +15 -0
  89. package/dist/runtime/choice.js +435 -0
  90. package/dist/runtime/credential.d.ts +8 -0
  91. package/dist/runtime/credential.js +61 -0
  92. package/dist/runtime/env.d.ts +2 -0
  93. package/dist/runtime/env.js +10 -0
  94. package/dist/runtime/executor.d.ts +16 -0
  95. package/dist/runtime/executor.js +51 -0
  96. package/dist/runtime/http.d.ts +8 -0
  97. package/dist/runtime/http.js +726 -0
  98. package/dist/runtime/insights.d.ts +9 -0
  99. package/dist/runtime/insights.js +324 -0
  100. package/dist/runtime/instrumentation.d.ts +8 -0
  101. package/dist/runtime/instrumentation.js +269 -0
  102. package/dist/runtime/key-derivation.d.ts +24 -0
  103. package/dist/runtime/key-derivation.js +73 -0
  104. package/dist/runtime/keyring.d.ts +25 -0
  105. package/dist/runtime/keyring.js +93 -0
  106. package/dist/runtime/namespace.d.ts +9 -0
  107. package/dist/runtime/namespace.js +19 -0
  108. package/dist/runtime/otlp.d.ts +39 -0
  109. package/dist/runtime/otlp.js +103 -0
  110. package/dist/runtime/perf.d.ts +12 -0
  111. package/dist/runtime/perf.js +52 -0
  112. package/dist/runtime/prevalidate.d.ts +12 -0
  113. package/dist/runtime/prevalidate.js +173 -0
  114. package/dist/runtime/provider.d.ts +2 -0
  115. package/dist/runtime/provider.js +11 -0
  116. package/dist/runtime/proxy-errors.d.ts +21 -0
  117. package/dist/runtime/proxy-errors.js +83 -0
  118. package/dist/runtime/proxy-telemetry.d.ts +8 -0
  119. package/dist/runtime/proxy-telemetry.js +174 -0
  120. package/dist/runtime/redis.d.ts +17 -0
  121. package/dist/runtime/redis.js +82 -0
  122. package/dist/runtime/request-options.d.ts +3 -0
  123. package/dist/runtime/request-options.js +42 -0
  124. package/dist/runtime/state.d.ts +17 -0
  125. package/dist/runtime/state.js +344 -0
  126. package/dist/runtime/stealth.d.ts +21 -0
  127. package/dist/runtime/stealth.js +980 -0
  128. package/dist/runtime/stt.d.ts +22 -0
  129. package/dist/runtime/stt.js +480 -0
  130. package/dist/runtime/trace.d.ts +26 -0
  131. package/dist/runtime/trace.js +142 -0
  132. package/dist/runtime/waterfall.d.ts +12 -0
  133. package/dist/runtime/waterfall.js +147 -0
  134. package/dist/schema.d.ts +74 -0
  135. package/dist/schema.js +243 -0
  136. package/dist/serve.d.ts +1 -0
  137. package/dist/serve.js +1 -0
  138. package/dist/server/index.d.ts +3 -0
  139. package/dist/server/index.js +2 -0
  140. package/dist/server/serve.d.ts +64 -0
  141. package/dist/server/serve.js +1118 -0
  142. package/dist/server/types.d.ts +136 -0
  143. package/dist/server/types.js +86 -0
  144. package/dist/stealth/profiles.d.ts +4 -0
  145. package/dist/stealth/profiles.js +259 -0
  146. package/dist/stream.d.ts +44 -0
  147. package/dist/stream.js +151 -0
  148. package/dist/testing/helpers.d.ts +23 -0
  149. package/dist/testing/helpers.js +95 -0
  150. package/dist/testing/index.d.ts +2 -0
  151. package/dist/testing/index.js +2 -0
  152. package/dist/testing/run.d.ts +34 -0
  153. package/dist/testing/run.js +307 -0
  154. package/dist/types.d.ts +1467 -0
  155. package/dist/types.js +61 -0
  156. package/dist/utils/date.d.ts +6 -0
  157. package/dist/utils/date.js +101 -0
  158. package/dist/utils/parse.d.ts +16 -0
  159. package/dist/utils/parse.js +51 -0
  160. package/dist/utils/text.d.ts +4 -0
  161. package/dist/utils/text.js +14 -0
  162. package/dist/utils/transform.d.ts +8 -0
  163. package/dist/utils/transform.js +48 -0
  164. package/package.json +57 -29
  165. package/src/auth.ts +786 -0
  166. package/src/ceremonies/index.ts +8 -2
  167. package/src/choice-token.ts +165 -0
  168. package/src/cli/commands.ts +34 -11
  169. package/src/cli/create.ts +254 -128
  170. package/src/cli/templates/provider/.dockerignore.tpl +22 -0
  171. package/src/cli/templates/provider/.gitignore.tpl +22 -0
  172. package/src/cli/templates/provider/AGENTS.md.tpl +87 -0
  173. package/src/cli/templates/provider/CLAUDE.md.tpl +1 -0
  174. package/src/cli/templates/provider/README.md.tpl +87 -7
  175. package/src/cli/templates/provider/dev.ts.tpl +1 -1
  176. package/src/cli/templates/provider/domain/README.md.tpl +3 -0
  177. package/src/cli/templates/provider/index.ts.tpl +5 -47
  178. package/src/cli/templates/provider/mappers/README.md.tpl +3 -0
  179. package/src/cli/templates/provider/meta.ts.tpl +7 -0
  180. package/src/cli/templates/provider/operations/index.ts.tpl +5 -0
  181. package/src/cli/templates/provider/operations/ping.ts.tpl +24 -0
  182. package/src/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  183. package/src/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  184. package/src/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  185. package/src/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
  186. package/src/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  187. package/src/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  188. package/src/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
  189. package/src/cli/templates/provider/start.ts.tpl +1 -1
  190. package/src/cli/templates/provider/upstream/README.md.tpl +3 -0
  191. package/src/config/loader.ts +1224 -9
  192. package/src/contract-json.ts +75 -0
  193. package/src/contract-serialization.ts +89 -0
  194. package/src/contract-types.ts +52 -0
  195. package/src/contract.ts +216 -0
  196. package/src/define.ts +1820 -70
  197. package/src/errors.ts +27 -0
  198. package/src/i18n/catalog.ts +277 -0
  199. package/src/i18n/index.ts +2 -0
  200. package/src/i18n/keys.ts +64 -0
  201. package/src/index.ts +189 -9
  202. package/src/lint.ts +580 -73
  203. package/src/observability.ts +41 -0
  204. package/src/provider.ts +131 -4
  205. package/src/public-schema-field-lint.ts +237 -0
  206. package/src/runtime/auth-flow.ts +9 -0
  207. package/src/runtime/browser.ts +1054 -51
  208. package/src/runtime/cache.ts +528 -0
  209. package/src/runtime/choice.ts +760 -0
  210. package/src/runtime/executor.ts +32 -3
  211. package/src/runtime/http.ts +980 -195
  212. package/src/runtime/insights.ts +11 -11
  213. package/src/runtime/instrumentation.ts +12 -4
  214. package/src/runtime/key-derivation.ts +1 -1
  215. package/src/runtime/keyring.ts +4 -3
  216. package/src/runtime/proxy-errors.ts +132 -0
  217. package/src/runtime/proxy-telemetry.ts +253 -0
  218. package/src/runtime/redis.ts +116 -0
  219. package/src/runtime/request-options.ts +66 -0
  220. package/src/runtime/state.ts +563 -0
  221. package/src/runtime/stealth.ts +1336 -0
  222. package/src/runtime/stt.ts +629 -0
  223. package/src/runtime/trace.ts +1 -1
  224. package/src/schema.ts +363 -1
  225. package/src/server/serve.ts +1192 -75
  226. package/src/server/types.ts +37 -0
  227. package/src/stream.ts +210 -0
  228. package/src/testing/run.ts +40 -6
  229. package/src/types.ts +1283 -59
  230. package/src/runtime/tls.ts +0 -434
  231. package/src/types/playwright-stealth.d.ts +0 -9
package/bin/apifuse.ts CHANGED
@@ -28,7 +28,7 @@ await module.main();
28
28
 
29
29
  function printHelp() {
30
30
  console.log(`
31
- apifuse - ApiFuse Provider SDK CLI
31
+ apifuse - APIFuse Provider SDK CLI
32
32
 
33
33
  Commands:`);
34
34
  for (const name of COMMAND_ORDER) {
package/dist/auth.d.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { AuthError } from "./errors";
2
+ import type { AuthConfig, AuthFlowTerminalContext, AuthTurn, ContextDeclaration, CredentialDeclaration, FlowContext, ProviderLocaleKeyInput } from "./types";
3
+ export type CredentialsAuthFieldType = "string" | "email" | "password" | "otp";
4
+ export interface CredentialsAuthField {
5
+ type?: CredentialsAuthFieldType;
6
+ labelKey?: ProviderLocaleKeyInput;
7
+ descriptionKey?: ProviderLocaleKeyInput;
8
+ placeholderKey?: ProviderLocaleKeyInput;
9
+ required?: boolean;
10
+ /** Marks field values as secret UI/input material, for example passwords or OTPs. */
11
+ sensitive?: boolean;
12
+ }
13
+ export type CredentialsAuthFields = Record<string, CredentialsAuthField>;
14
+ export type CredentialsAuthInput<TFields extends CredentialsAuthFields> = {
15
+ [K in keyof TFields]: string;
16
+ };
17
+ export type CredentialsAuthCredential<TCredentialKeys extends readonly string[]> = {
18
+ [K in TCredentialKeys[number]]: string;
19
+ };
20
+ export declare function createAuthFlowHelpers(options?: {
21
+ readonly signal?: AbortSignal;
22
+ readonly deadline?: string;
23
+ }): AuthFlowTerminalContext;
24
+ export declare class AuthAbortError extends AuthError {
25
+ readonly turn: AuthTurn;
26
+ constructor(options: Parameters<AuthFlowTerminalContext["abort"]>[0]);
27
+ }
28
+ export interface CredentialsAuthCompleteResult<TCredentialKeys extends readonly string[]> {
29
+ credential: CredentialsAuthCredential<TCredentialKeys>;
30
+ /** Additional non-credential auth-flow data to return alongside credential. */
31
+ data?: Record<string, unknown>;
32
+ turnId?: string;
33
+ expiresAt?: string;
34
+ }
35
+ export interface CredentialsAuthChallengeRequest<TChallengeId extends string = string> {
36
+ kind: "challenge";
37
+ challengeId: TChallengeId;
38
+ state?: Record<string, unknown>;
39
+ turnId?: string;
40
+ hintKey?: ProviderLocaleKeyInput;
41
+ expiresAt?: string;
42
+ data?: Record<string, unknown>;
43
+ timing?: AuthTurn["timing"];
44
+ }
45
+ export type CredentialsAuthLoginResult<TCredentialKeys extends readonly string[], TChallengeId extends string = string> = CredentialsAuthCompleteResult<TCredentialKeys> | CredentialsAuthChallengeRequest<TChallengeId>;
46
+ export interface CredentialsAuthChallengeDefinition<TFields extends CredentialsAuthFields, TCredentialKeys extends readonly string[], TChallengeId extends string> {
47
+ fields?: TFields;
48
+ hintKey?: ProviderLocaleKeyInput;
49
+ turnId?: string;
50
+ retryTurnId?: string;
51
+ pendingTurnId?: string;
52
+ timing?: AuthTurn["timing"];
53
+ verify?: (ctx: FlowContext, input: CredentialsAuthInput<TFields>, state: Record<string, unknown>) => CredentialsAuthLoginResult<TCredentialKeys, TChallengeId> | Promise<CredentialsAuthLoginResult<TCredentialKeys, TChallengeId>>;
54
+ poll?: (ctx: FlowContext, state: Record<string, unknown>) => CredentialsAuthLoginResult<TCredentialKeys, TChallengeId> | null | Promise<CredentialsAuthLoginResult<TCredentialKeys, TChallengeId> | null>;
55
+ }
56
+ export interface DefineCredentialsAuthOptions<TFields extends CredentialsAuthFields, TCredentialKeys extends readonly string[], TChallenges extends Record<string, CredentialsAuthChallengeDefinition<CredentialsAuthFields, TCredentialKeys, keyof TChallenges & string>> = Record<string, CredentialsAuthChallengeDefinition<CredentialsAuthFields, TCredentialKeys, string>>> {
57
+ fields: TFields;
58
+ credentialKeys: TCredentialKeys;
59
+ storesReusableSecret?: boolean;
60
+ justification?: string;
61
+ hintKey?: ProviderLocaleKeyInput;
62
+ startTurnId?: string;
63
+ retryTurnId?: string;
64
+ completeTurnId?: string;
65
+ challenges?: TChallenges;
66
+ /** Extra auth-flow context keys used by custom login/challenge code. */
67
+ contextKeys?: readonly string[];
68
+ login(ctx: FlowContext, input: CredentialsAuthInput<TFields>): CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string> | Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
69
+ }
70
+ export interface DefinedCredentialsAuth {
71
+ auth: AuthConfig;
72
+ credential: CredentialDeclaration;
73
+ context: ContextDeclaration;
74
+ }
75
+ export declare function credentialsAuthChallenge<TChallengeId extends string>(challengeId: TChallengeId, options?: Omit<CredentialsAuthChallengeRequest<TChallengeId>, "kind" | "challengeId">): CredentialsAuthChallengeRequest<TChallengeId>;
76
+ export declare function defineCredentialsAuth<TFields extends CredentialsAuthFields, TCredentialKeys extends readonly [string, ...string[]], TChallenges extends Record<string, CredentialsAuthChallengeDefinition<CredentialsAuthFields, TCredentialKeys, keyof TChallenges & string>> = Record<string, CredentialsAuthChallengeDefinition<CredentialsAuthFields, TCredentialKeys, string>>>(options: DefineCredentialsAuthOptions<TFields, TCredentialKeys, TChallenges>): DefinedCredentialsAuth;
package/dist/auth.js ADDED
@@ -0,0 +1,436 @@
1
+ import { AuthError, ProviderError } from "./errors";
2
+ const CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY = "__credentialsAuthChallenge";
3
+ const DEFAULT_COMPLETE_TURN_ID = "auth.complete";
4
+ const DEFAULT_ABORT_TURN_ID = "auth.abort";
5
+ const DEFAULT_FORM_TURN_ID = "auth.form";
6
+ const DEFAULT_POLL_TURN_ID = "auth.poll";
7
+ const SENSITIVE_ABORT_DATA_KEY_PATTERN = /(authorization|cookie|credential|header|html|password|secret|session|token|apikey)/i;
8
+ function normalizedAuthDataKey(key) {
9
+ return key.replace(/[^a-z0-9]/gi, "").toLowerCase();
10
+ }
11
+ function isPlainAuthJsonObject(value) {
12
+ const prototype = Object.getPrototypeOf(value);
13
+ return prototype === Object.prototype || prototype === null;
14
+ }
15
+ function isSensitiveAuthDataKey(key) {
16
+ return SENSITIVE_ABORT_DATA_KEY_PATTERN.test(normalizedAuthDataKey(key));
17
+ }
18
+ function assertSafeAuthJson(value, path) {
19
+ if (value === null)
20
+ return;
21
+ if (typeof value === "string" || typeof value === "boolean")
22
+ return;
23
+ if (typeof value === "number") {
24
+ if (Number.isFinite(value))
25
+ return;
26
+ throw new AuthError(`Auth abort ${path} must be a finite number`, {
27
+ code: "auth_abort_unsafe_data",
28
+ });
29
+ }
30
+ if (Array.isArray(value)) {
31
+ for (const [index, item] of value.entries()) {
32
+ assertSafeAuthJson(item, `${path}[${index}]`);
33
+ }
34
+ return;
35
+ }
36
+ if (value instanceof Error) {
37
+ throw new AuthError(`Auth abort ${path} must not include Error objects`, {
38
+ code: "auth_abort_unsafe_data",
39
+ });
40
+ }
41
+ if (typeof value !== "object") {
42
+ throw new AuthError(`Auth abort ${path} must be JSON-safe`, {
43
+ code: "auth_abort_unsafe_data",
44
+ });
45
+ }
46
+ if (!isPlainAuthJsonObject(value)) {
47
+ throw new AuthError(`Auth abort ${path} must be a plain JSON object`, {
48
+ code: "auth_abort_unsafe_data",
49
+ });
50
+ }
51
+ for (const [key, item] of Object.entries(value)) {
52
+ if (isSensitiveAuthDataKey(key)) {
53
+ throw new AuthError(`Auth abort ${path}.${key} must not include secrets`, {
54
+ code: "auth_abort_unsafe_data",
55
+ });
56
+ }
57
+ assertSafeAuthJson(item, `${path}.${key}`);
58
+ }
59
+ }
60
+ function assertSafeAuthData(data, path) {
61
+ assertSafeAuthJson(data, path);
62
+ }
63
+ function authTurnBase(options) {
64
+ return {
65
+ turnId: options.turnId ?? options.defaultTurnId,
66
+ ...(options.expiresAt ? { expiresAt: options.expiresAt } : {}),
67
+ };
68
+ }
69
+ function abortData(options) {
70
+ if (options.actionHint !== undefined) {
71
+ assertSafeAuthJson(options.actionHint, "actionHint");
72
+ }
73
+ if (options.data !== undefined) {
74
+ assertSafeAuthData(options.data, "data");
75
+ }
76
+ return {
77
+ code: options.code,
78
+ ...(options.message ? { message: options.message } : {}),
79
+ ...(options.retry ? { retry: options.retry } : {}),
80
+ ...(options.actionHint !== undefined ? { actionHint: options.actionHint } : {}),
81
+ ...(options.fieldErrors ? { fieldErrors: options.fieldErrors } : {}),
82
+ ...(options.data ? { details: options.data } : {}),
83
+ };
84
+ }
85
+ export function createAuthFlowHelpers(options = {}) {
86
+ return {
87
+ ...(options.signal ? { signal: options.signal } : {}),
88
+ ...(options.deadline ? { deadline: options.deadline } : {}),
89
+ complete({ credential, metadata, data, turnId, expiresAt }) {
90
+ return {
91
+ kind: "complete",
92
+ ...authTurnBase({ turnId, defaultTurnId: DEFAULT_COMPLETE_TURN_ID, expiresAt }),
93
+ data: {
94
+ ...(data ?? {}),
95
+ credential,
96
+ ...(metadata ? { metadata } : {}),
97
+ },
98
+ };
99
+ },
100
+ abort({ code, message, retry, actionHint, fieldErrors, data, turnId, expiresAt, }) {
101
+ return {
102
+ kind: "abort",
103
+ ...authTurnBase({ turnId, defaultTurnId: DEFAULT_ABORT_TURN_ID, expiresAt }),
104
+ data: abortData({
105
+ code,
106
+ message,
107
+ retry,
108
+ actionHint,
109
+ fieldErrors,
110
+ data,
111
+ }),
112
+ };
113
+ },
114
+ nextForm(options) {
115
+ return {
116
+ kind: "form",
117
+ ...authTurnBase({
118
+ turnId: options.turnId,
119
+ defaultTurnId: DEFAULT_FORM_TURN_ID,
120
+ expiresAt: options.expiresAt,
121
+ }),
122
+ ...(options.hintKey ? { hintKey: options.hintKey } : {}),
123
+ ...(options.timing ? { timing: options.timing } : {}),
124
+ ...(options.data ? { data: options.data } : {}),
125
+ expectedInput: options.expectedInput ?? expectedInputFromFields(options.fields ?? {}),
126
+ };
127
+ },
128
+ nextPoll(options = {}) {
129
+ return {
130
+ kind: "poll",
131
+ ...authTurnBase({
132
+ turnId: options.turnId,
133
+ defaultTurnId: DEFAULT_POLL_TURN_ID,
134
+ expiresAt: options.expiresAt,
135
+ }),
136
+ ...(options.hintKey ? { hintKey: options.hintKey } : {}),
137
+ ...(options.timing ? { timing: options.timing } : {}),
138
+ ...(options.data ? { data: options.data } : {}),
139
+ };
140
+ },
141
+ };
142
+ }
143
+ export class AuthAbortError extends AuthError {
144
+ turn;
145
+ constructor(options) {
146
+ super(options.message ?? options.code, {
147
+ code: options.code,
148
+ retryable: options.retry === "retry",
149
+ });
150
+ this.name = "AuthAbortError";
151
+ this.turn = createAuthFlowHelpers().abort(options);
152
+ }
153
+ }
154
+ export function credentialsAuthChallenge(challengeId, options = {}) {
155
+ return {
156
+ kind: "challenge",
157
+ challengeId,
158
+ ...options,
159
+ };
160
+ }
161
+ function expectedInputFromFields(fields) {
162
+ return {
163
+ type: "object",
164
+ properties: Object.fromEntries(Object.entries(fields).map(([name, field]) => [
165
+ name,
166
+ {
167
+ type: "string",
168
+ ...(field.type === "email" ? { format: "email" } : {}),
169
+ ...(field.type === "password" ? { format: "password" } : {}),
170
+ ...(field.type === "otp" ? { format: "otp" } : {}),
171
+ ...(field.labelKey ? { nameKey: field.labelKey } : {}),
172
+ ...(field.descriptionKey
173
+ ? { descriptionKey: field.descriptionKey }
174
+ : {}),
175
+ ...(field.placeholderKey
176
+ ? { placeholderKey: field.placeholderKey }
177
+ : {}),
178
+ ...(field.sensitive || field.type === "password" || field.type === "otp"
179
+ ? { sensitive: true }
180
+ : {}),
181
+ },
182
+ ])),
183
+ required: Object.entries(fields)
184
+ .filter(([, field]) => field.required !== false)
185
+ .map(([name]) => name),
186
+ };
187
+ }
188
+ function collectMissingFields(fields, input) {
189
+ return Object.entries(fields)
190
+ .filter(([, field]) => field.required !== false)
191
+ .map(([name]) => name)
192
+ .filter((name) => {
193
+ const value = input?.[name];
194
+ return typeof value !== "string" || value.trim().length === 0;
195
+ });
196
+ }
197
+ function normalizeInput(fields, input) {
198
+ const result = {};
199
+ for (const name of Object.keys(fields)) {
200
+ const value = input?.[name];
201
+ result[name] = typeof value === "string" ? value : "";
202
+ }
203
+ return result;
204
+ }
205
+ function assertCredentialKeys(credentialKeys, credential) {
206
+ const missing = credentialKeys.filter((key) => {
207
+ const value = credential[key];
208
+ return typeof value !== "string" || value.length === 0;
209
+ });
210
+ if (missing.length > 0) {
211
+ throw new ProviderError(`Credentials auth login completed without required credential key(s): ${missing.join(", ")}`, {
212
+ code: "credentials_auth_missing_credential_keys",
213
+ fix: "Return every credentialKeys entry from defineCredentialsAuth({ login }) as result.credential. Gateway persists only auth.flow complete data.credential into the connection.",
214
+ });
215
+ }
216
+ }
217
+ function getPendingChallenge(ctx) {
218
+ const value = ctx.context.get(CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY);
219
+ if (!value || typeof value !== "object" || Array.isArray(value))
220
+ return null;
221
+ const record = value;
222
+ if (typeof record.challengeId !== "string")
223
+ return null;
224
+ const state = record.state && typeof record.state === "object" && !Array.isArray(record.state)
225
+ ? record.state
226
+ : {};
227
+ return {
228
+ challengeId: record.challengeId,
229
+ state,
230
+ ...(typeof record.turnId === "string" ? { turnId: record.turnId } : {}),
231
+ ...(typeof record.hintKey === "string" ? { hintKey: record.hintKey } : {}),
232
+ ...(typeof record.expiresAt === "string" ? { expiresAt: record.expiresAt } : {}),
233
+ ...(record.data && typeof record.data === "object" && !Array.isArray(record.data)
234
+ ? { data: record.data }
235
+ : {}),
236
+ ...(record.timing && typeof record.timing === "object" && !Array.isArray(record.timing)
237
+ ? { timing: record.timing }
238
+ : {}),
239
+ };
240
+ }
241
+ function setPendingChallenge(ctx, challenge) {
242
+ ctx.context.set(CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY, challenge);
243
+ }
244
+ function clearPendingChallenge(ctx) {
245
+ ctx.context.set(CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY, null);
246
+ }
247
+ function pendingToChallengeRequest(pending) {
248
+ return {
249
+ kind: "challenge",
250
+ challengeId: pending.challengeId,
251
+ state: pending.state,
252
+ ...(pending.turnId ? { turnId: pending.turnId } : {}),
253
+ ...(pending.hintKey ? { hintKey: pending.hintKey } : {}),
254
+ ...(pending.expiresAt ? { expiresAt: pending.expiresAt } : {}),
255
+ ...(pending.data ? { data: pending.data } : {}),
256
+ ...(pending.timing ? { timing: pending.timing } : {}),
257
+ };
258
+ }
259
+ function retryTurn(expectedInput, missing, retryTurnId) {
260
+ return {
261
+ kind: "retry",
262
+ turnId: retryTurnId,
263
+ expectedInput,
264
+ data: {
265
+ fieldErrors: Object.fromEntries(missing.map((name) => [name, "Required"])),
266
+ fieldErrorKeys: Object.fromEntries(missing.map((name) => [name, "auth.credentials.fieldRequired"])),
267
+ },
268
+ };
269
+ }
270
+ function isChallengeRequest(result) {
271
+ return "kind" in result && result.kind === "challenge";
272
+ }
273
+ function completeTurn(credentialKeys, result, defaultTurnId) {
274
+ if (!result.credential ||
275
+ typeof result.credential !== "object" ||
276
+ Array.isArray(result.credential)) {
277
+ throw new ProviderError("Credentials auth login completed without a credential object", {
278
+ code: "credentials_auth_missing_credential",
279
+ fix: "Return { credential: { ... } } from defineCredentialsAuth handlers. Gateway persists only auth.flow complete data.credential into the connection.",
280
+ });
281
+ }
282
+ assertCredentialKeys(credentialKeys, result.credential);
283
+ return {
284
+ kind: "complete",
285
+ turnId: result.turnId ?? defaultTurnId,
286
+ ...(result.expiresAt ? { expiresAt: result.expiresAt } : {}),
287
+ data: {
288
+ ...(result.data ?? {}),
289
+ credential: result.credential,
290
+ },
291
+ };
292
+ }
293
+ function challengeTurn(definition, request) {
294
+ const expectedInput = definition.fields
295
+ ? expectedInputFromFields(definition.fields)
296
+ : undefined;
297
+ return {
298
+ kind: expectedInput ? "form" : "pending",
299
+ turnId: request.turnId ?? definition.turnId ?? `credentials.${request.challengeId}`,
300
+ ...(request.expiresAt ? { expiresAt: request.expiresAt } : {}),
301
+ ...(request.hintKey ?? definition.hintKey
302
+ ? { hintKey: request.hintKey ?? definition.hintKey }
303
+ : {}),
304
+ ...(request.timing ?? definition.timing
305
+ ? { timing: request.timing ?? definition.timing }
306
+ : {}),
307
+ ...(expectedInput ? { expectedInput } : {}),
308
+ data: {
309
+ ...(request.data ?? {}),
310
+ challengeId: request.challengeId,
311
+ },
312
+ };
313
+ }
314
+ async function resolveAuthResult(ctx, credentialKeys, challenges, result, completeTurnId) {
315
+ if (!result || typeof result !== "object") {
316
+ throw new AuthError("Credentials auth login did not return a result", {
317
+ code: "credentials_auth_invalid_login_result",
318
+ fix: "Return { credential: { ... } } or credentialsAuthChallenge(...) from defineCredentialsAuth handlers.",
319
+ });
320
+ }
321
+ if (!isChallengeRequest(result)) {
322
+ clearPendingChallenge(ctx);
323
+ return completeTurn(credentialKeys, result, completeTurnId);
324
+ }
325
+ const definition = challenges[result.challengeId];
326
+ if (!definition) {
327
+ throw new ProviderError(`Credentials auth requested unknown challenge "${result.challengeId}"`, {
328
+ code: "credentials_auth_unknown_challenge",
329
+ fix: `Add challenges.${result.challengeId} to defineCredentialsAuth({ challenges }).`,
330
+ });
331
+ }
332
+ setPendingChallenge(ctx, {
333
+ challengeId: result.challengeId,
334
+ state: result.state ?? {},
335
+ ...(result.turnId ? { turnId: result.turnId } : {}),
336
+ ...(result.hintKey ? { hintKey: result.hintKey } : {}),
337
+ ...(result.expiresAt ? { expiresAt: result.expiresAt } : {}),
338
+ ...(result.data ? { data: result.data } : {}),
339
+ ...(result.timing ? { timing: result.timing } : {}),
340
+ });
341
+ return challengeTurn(definition, result);
342
+ }
343
+ async function continuePendingChallenge(ctx, credentialKeys, challenges, pending, rawInput, completeTurnId) {
344
+ const definition = challenges[pending.challengeId];
345
+ if (!definition) {
346
+ throw new ProviderError(`Credentials auth has pending unknown challenge "${pending.challengeId}"`, { code: "credentials_auth_unknown_pending_challenge" });
347
+ }
348
+ if (!definition.fields || !definition.verify) {
349
+ return challengeTurn(definition, pendingToChallengeRequest(pending));
350
+ }
351
+ const missing = collectMissingFields(definition.fields, rawInput);
352
+ const expectedInput = expectedInputFromFields(definition.fields);
353
+ if (missing.length > 0) {
354
+ return retryTurn(expectedInput, missing, definition.retryTurnId ?? `credentials.${pending.challengeId}.retry`);
355
+ }
356
+ const result = await definition.verify(ctx, normalizeInput(definition.fields, rawInput), pending.state);
357
+ return await resolveAuthResult(ctx, credentialKeys, challenges, result, completeTurnId);
358
+ }
359
+ async function pollPendingChallenge(ctx, credentialKeys, challenges, pending, completeTurnId) {
360
+ const definition = challenges[pending.challengeId];
361
+ if (!definition) {
362
+ throw new ProviderError(`Credentials auth has pending unknown challenge "${pending.challengeId}"`, { code: "credentials_auth_unknown_pending_challenge" });
363
+ }
364
+ if (!definition.poll) {
365
+ return challengeTurn(definition, pendingToChallengeRequest(pending));
366
+ }
367
+ const result = await definition.poll(ctx, pending.state);
368
+ if (!result) {
369
+ return {
370
+ ...challengeTurn(definition, pendingToChallengeRequest(pending)),
371
+ turnId: definition.pendingTurnId ??
372
+ definition.turnId ??
373
+ `credentials.${pending.challengeId}.pending`,
374
+ };
375
+ }
376
+ return await resolveAuthResult(ctx, credentialKeys, challenges, result, completeTurnId);
377
+ }
378
+ export function defineCredentialsAuth(options) {
379
+ if (Object.keys(options.fields).length === 0) {
380
+ throw new ProviderError("defineCredentialsAuth requires at least one field", {
381
+ fix: "Pass fields such as { email: { type: \"email\" }, password: { type: \"password\" } }.",
382
+ });
383
+ }
384
+ const expectedInput = expectedInputFromFields(options.fields);
385
+ const retryTurnId = options.retryTurnId ?? "credentials.retry";
386
+ const completeTurnId = options.completeTurnId ?? "credentials.complete";
387
+ const challenges = (options.challenges ?? {});
388
+ return {
389
+ auth: {
390
+ mode: "credentials",
391
+ flow: {
392
+ start: async () => ({
393
+ kind: "form",
394
+ turnId: options.startTurnId ?? "credentials.start",
395
+ ...(options.hintKey ? { hintKey: options.hintKey } : {}),
396
+ expectedInput,
397
+ }),
398
+ continue: async (ctx, rawInput) => {
399
+ const pending = getPendingChallenge(ctx);
400
+ if (pending) {
401
+ return await continuePendingChallenge(ctx, options.credentialKeys, challenges, pending, rawInput, completeTurnId);
402
+ }
403
+ const missing = collectMissingFields(options.fields, rawInput);
404
+ if (missing.length > 0) {
405
+ return retryTurn(expectedInput, missing, retryTurnId);
406
+ }
407
+ const result = await options.login(ctx, normalizeInput(options.fields, rawInput));
408
+ return await resolveAuthResult(ctx, options.credentialKeys, challenges, result, completeTurnId);
409
+ },
410
+ poll: async (ctx) => {
411
+ const pending = getPendingChallenge(ctx);
412
+ if (!pending) {
413
+ return {
414
+ kind: "pending",
415
+ turnId: "credentials.noPendingChallenge",
416
+ };
417
+ }
418
+ return await pollPendingChallenge(ctx, options.credentialKeys, challenges, pending, completeTurnId);
419
+ },
420
+ },
421
+ },
422
+ credential: {
423
+ keys: Array.from(options.credentialKeys),
424
+ ...(options.storesReusableSecret === undefined
425
+ ? {}
426
+ : { storesReusableSecret: options.storesReusableSecret }),
427
+ ...(options.justification ? { justification: options.justification } : {}),
428
+ },
429
+ context: {
430
+ keys: Array.from(new Set([
431
+ CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY,
432
+ ...(options.contextKeys ?? []),
433
+ ])),
434
+ },
435
+ };
436
+ }
@@ -0,0 +1,41 @@
1
+ import type { AuthFlowDefinition, AuthTurn } from "../types";
2
+ type JsonObject = Record<string, unknown>;
3
+ export declare function validateCeremonyOutput(turn: unknown): AuthTurn;
4
+ export declare function createOAuth2Ceremony(options: {
5
+ authorizeUrl: string;
6
+ tokenUrl: string;
7
+ clientIdEnvKey: string;
8
+ clientSecretEnvKey: string;
9
+ scopes: string[];
10
+ usePKCE?: boolean;
11
+ }): AuthFlowDefinition;
12
+ export declare function createDeviceFlowCeremony(options: {
13
+ deviceCodeUrl: string;
14
+ tokenUrl: string;
15
+ clientIdEnvKey: string;
16
+ clientSecretEnvKey?: string;
17
+ scopes: string[];
18
+ }): AuthFlowDefinition;
19
+ export declare function createWebAuthnCeremony(options: {
20
+ rpId: string;
21
+ challengeUrl?: string;
22
+ verifyUrl?: string;
23
+ timeoutMs?: number;
24
+ }): AuthFlowDefinition;
25
+ export declare function createMagicLinkCeremony(options: {
26
+ sendUrl: string;
27
+ verifyUrl: string;
28
+ emailField?: string;
29
+ expiresInMs?: number;
30
+ }): AuthFlowDefinition;
31
+ export declare function createFormCeremony(options: {
32
+ schema: JsonObject;
33
+ hint?: string;
34
+ mapCredential?: (input: Record<string, unknown>) => JsonObject;
35
+ }): AuthFlowDefinition;
36
+ export declare function combineCeremonies(...ceremonies: AuthFlowDefinition[]): AuthFlowDefinition;
37
+ export declare function createSwitchCeremony(options: {
38
+ choices: Record<string, AuthFlowDefinition>;
39
+ prompt?: string;
40
+ }): AuthFlowDefinition;
41
+ export {};