@apifuse/provider-sdk 2.2.0-beta.1 → 2.2.0-beta.10

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 (203) hide show
  1. package/AUTHORING.md +106 -0
  2. package/CHANGELOG.md +37 -0
  3. package/README.md +5 -1
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +14 -0
  9. package/bin/apifuse-pack-smoke.ts +22 -81
  10. package/bin/apifuse-pack-types.ts +266 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +53 -70
  13. package/bin/apifuse-submit-check.ts +510 -45
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth.d.ts +2 -2
  21. package/dist/auth.js +9 -18
  22. package/dist/ceremonies/index.d.ts +1 -1
  23. package/dist/ceremonies/index.js +8 -18
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/config/loader.d.ts +149 -8
  32. package/dist/config/loader.js +378 -89
  33. package/dist/contract-serialization.d.ts +2 -2
  34. package/dist/contract-serialization.js +3 -6
  35. package/dist/contract-types.d.ts +2 -2
  36. package/dist/contract.d.ts +3 -3
  37. package/dist/contract.js +4 -6
  38. package/dist/define.d.ts +8 -1
  39. package/dist/define.js +119 -122
  40. package/dist/dev.d.ts +1 -1
  41. package/dist/dev.js +1 -1
  42. package/dist/errors.d.ts +4 -1
  43. package/dist/errors.js +48 -0
  44. package/dist/i18n/catalog.d.ts +2 -2
  45. package/dist/i18n/catalog.js +4 -10
  46. package/dist/i18n/index.d.ts +2 -2
  47. package/dist/i18n/index.js +2 -2
  48. package/dist/i18n/keys.d.ts +2 -2
  49. package/dist/index.d.ts +44 -42
  50. package/dist/index.js +39 -37
  51. package/dist/lint.js +8 -15
  52. package/dist/provider.d.ts +11 -11
  53. package/dist/provider.js +9 -9
  54. package/dist/public-schema-field-lint.d.ts +1 -1
  55. package/dist/recipes/gov-api.js +1 -1
  56. package/dist/runtime/auth-flow.d.ts +1 -1
  57. package/dist/runtime/auth-flow.js +3 -3
  58. package/dist/runtime/browser.d.ts +1 -1
  59. package/dist/runtime/browser.js +15 -29
  60. package/dist/runtime/cache.d.ts +1 -1
  61. package/dist/runtime/cache.js +4 -8
  62. package/dist/runtime/choice.d.ts +1 -1
  63. package/dist/runtime/choice.js +31 -35
  64. package/dist/runtime/credential.d.ts +1 -1
  65. package/dist/runtime/credential.js +1 -1
  66. package/dist/runtime/env.d.ts +1 -1
  67. package/dist/runtime/executor.d.ts +1 -1
  68. package/dist/runtime/executor.js +15 -3
  69. package/dist/runtime/http.d.ts +2 -2
  70. package/dist/runtime/http.js +144 -348
  71. package/dist/runtime/insights.d.ts +1 -1
  72. package/dist/runtime/insights.js +6 -13
  73. package/dist/runtime/instrumentation.d.ts +2 -2
  74. package/dist/runtime/instrumentation.js +56 -19
  75. package/dist/runtime/keyring.js +1 -1
  76. package/dist/runtime/namespace.js +1 -1
  77. package/dist/runtime/otlp.d.ts +1 -1
  78. package/dist/runtime/perf.d.ts +1 -1
  79. package/dist/runtime/provider.d.ts +1 -1
  80. package/dist/runtime/provider.js +1 -2
  81. package/dist/runtime/proxy-errors.d.ts +1 -1
  82. package/dist/runtime/proxy-errors.js +9 -7
  83. package/dist/runtime/proxy-nodemaven.d.ts +35 -0
  84. package/dist/runtime/proxy-nodemaven.js +128 -0
  85. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  86. package/dist/runtime/proxy-retry-policy.js +326 -0
  87. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  88. package/dist/runtime/proxy-telemetry.js +55 -52
  89. package/dist/runtime/redis.d.ts +1 -1
  90. package/dist/runtime/redis.js +2 -4
  91. package/dist/runtime/request-options.d.ts +1 -1
  92. package/dist/runtime/secrets.d.ts +27 -0
  93. package/dist/runtime/secrets.js +51 -0
  94. package/dist/runtime/state.d.ts +2 -2
  95. package/dist/runtime/state.js +15 -4
  96. package/dist/runtime/stealth.d.ts +2 -2
  97. package/dist/runtime/stealth.js +108 -212
  98. package/dist/runtime/stt.d.ts +1 -1
  99. package/dist/runtime/stt.js +11 -15
  100. package/dist/runtime/trace.d.ts +2 -2
  101. package/dist/runtime/trace.js +2 -4
  102. package/dist/runtime/waterfall.d.ts +1 -1
  103. package/dist/schema.d.ts +1 -1
  104. package/dist/schema.js +7 -15
  105. package/dist/serve.d.ts +1 -1
  106. package/dist/serve.js +1 -1
  107. package/dist/server/index.d.ts +7 -7
  108. package/dist/server/index.js +6 -6
  109. package/dist/server/self-test-redaction.d.ts +1 -1
  110. package/dist/server/self-test-redaction.js +1 -1
  111. package/dist/server/self-test.d.ts +104 -3
  112. package/dist/server/self-test.js +673 -115
  113. package/dist/server/serve.d.ts +8 -3
  114. package/dist/server/serve.js +105 -59
  115. package/dist/server/types.d.ts +10 -9
  116. package/dist/server/types.js +3 -7
  117. package/dist/stealth/profiles.d.ts +1 -1
  118. package/dist/stealth/profiles.js +5 -14
  119. package/dist/stream.d.ts +1 -1
  120. package/dist/testing/index.d.ts +2 -2
  121. package/dist/testing/index.js +2 -2
  122. package/dist/testing/run.d.ts +1 -1
  123. package/dist/testing/run.js +7 -14
  124. package/dist/types.d.ts +87 -1
  125. package/dist/user-input.d.ts +30 -0
  126. package/dist/user-input.js +66 -0
  127. package/package.json +9 -5
  128. package/src/auth-turn/index.ts +1 -1
  129. package/src/auth.ts +28 -86
  130. package/src/ceremonies/index.ts +24 -78
  131. package/src/cli/commands.ts +10 -0
  132. package/src/cli/create.ts +42 -35
  133. package/src/cli/prompt-assets.ts +865 -0
  134. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  135. package/src/config/loader.ts +543 -208
  136. package/src/contract-serialization.ts +5 -11
  137. package/src/contract-types.ts +2 -2
  138. package/src/contract.ts +12 -28
  139. package/src/define.ts +247 -505
  140. package/src/dev.ts +4 -9
  141. package/src/errors.ts +58 -5
  142. package/src/i18n/catalog.ts +10 -32
  143. package/src/i18n/index.ts +2 -2
  144. package/src/i18n/keys.ts +5 -11
  145. package/src/index.ts +49 -42
  146. package/src/lint.ts +89 -159
  147. package/src/provider.ts +15 -11
  148. package/src/public-schema-field-lint.ts +7 -33
  149. package/src/recipes/gov-api.ts +2 -5
  150. package/src/runtime/auth-flow.ts +5 -7
  151. package/src/runtime/browser.ts +64 -187
  152. package/src/runtime/cache.ts +20 -67
  153. package/src/runtime/choice.ts +79 -132
  154. package/src/runtime/credential.ts +2 -2
  155. package/src/runtime/env.ts +1 -1
  156. package/src/runtime/executor.ts +23 -20
  157. package/src/runtime/http.ts +195 -559
  158. package/src/runtime/insights.ts +15 -53
  159. package/src/runtime/instrumentation.ts +78 -65
  160. package/src/runtime/keyring.ts +7 -19
  161. package/src/runtime/namespace.ts +2 -7
  162. package/src/runtime/otlp.ts +12 -23
  163. package/src/runtime/perf.ts +1 -1
  164. package/src/runtime/provider.ts +4 -9
  165. package/src/runtime/proxy-errors.ts +29 -42
  166. package/src/runtime/proxy-nodemaven.ts +178 -0
  167. package/src/runtime/proxy-retry-policy.ts +469 -0
  168. package/src/runtime/proxy-telemetry.ts +79 -77
  169. package/src/runtime/redis.ts +4 -12
  170. package/src/runtime/request-options.ts +4 -13
  171. package/src/runtime/secrets.ts +64 -0
  172. package/src/runtime/state.ts +41 -110
  173. package/src/runtime/stealth.ts +162 -377
  174. package/src/runtime/stt.ts +38 -94
  175. package/src/runtime/trace.ts +14 -44
  176. package/src/runtime/waterfall.ts +5 -18
  177. package/src/schema.ts +23 -84
  178. package/src/serve.ts +1 -1
  179. package/src/server/index.ts +12 -7
  180. package/src/server/self-test-redaction.ts +2 -2
  181. package/src/server/self-test.ts +857 -132
  182. package/src/server/serve.ts +172 -245
  183. package/src/server/types.ts +7 -19
  184. package/src/stealth/profiles.ts +10 -26
  185. package/src/stream.ts +8 -19
  186. package/src/testing/index.ts +2 -2
  187. package/src/testing/run.ts +19 -67
  188. package/src/types.ts +88 -1
  189. package/src/user-input.ts +118 -0
  190. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  191. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  192. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  193. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  194. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  195. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  196. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  197. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  198. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  199. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  200. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  201. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  202. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  203. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -1,11 +1,10 @@
1
- import { createTraceContext, getTraceRecorder, } from "./trace";
2
- const BROWSER_PAGE_METHODS = new Set([
3
- "goto",
4
- "fill",
5
- "click",
6
- "type",
7
- "waitForSelector",
8
- ]);
1
+ import { createTraceContext, getTraceRecorder, } from "./trace.js";
2
+ const BROWSER_PAGE_METHODS = new Set(["goto", "fill", "click", "type", "waitForSelector"]);
3
+ function isThenable(value) {
4
+ return ((typeof value === "object" || typeof value === "function") &&
5
+ value !== null &&
6
+ typeof value.then === "function");
7
+ }
9
8
  function getErrorStatus(error) {
10
9
  if (typeof error === "object" &&
11
10
  error !== null &&
@@ -69,9 +68,7 @@ function buildSpanAttributes(namespace, methodName, args, result, error) {
69
68
  const attributes = {};
70
69
  const url = getUrl(namespace, args, result);
71
70
  const method = getMethod(namespace, methodName, args);
72
- const status = error
73
- ? getErrorStatus(error)
74
- : getResponseStatus(namespace, result);
71
+ const status = error ? getErrorStatus(error) : getResponseStatus(namespace, result);
75
72
  const duration = error ? undefined : getResponseDuration(result);
76
73
  if (url) {
77
74
  attributes.url = url;
@@ -79,8 +76,7 @@ function buildSpanAttributes(namespace, methodName, args, result, error) {
79
76
  if (method) {
80
77
  attributes.method = method;
81
78
  }
82
- if (status !== undefined &&
83
- (namespace === "http" || namespace === "stealth")) {
79
+ if (status !== undefined && (namespace === "http" || namespace === "stealth")) {
84
80
  attributes.status = status;
85
81
  }
86
82
  if (duration !== undefined) {
@@ -192,8 +188,7 @@ function wrapNamespace(namespace, target, trace) {
192
188
  if (namespaceTarget &&
193
189
  typeof namespaceTarget === "object" &&
194
190
  "engine" in namespaceTarget &&
195
- typeof namespaceTarget.engine ===
196
- "string") {
191
+ typeof namespaceTarget.engine === "string") {
197
192
  attributes.engine = namespaceTarget.engine;
198
193
  }
199
194
  return attributes;
@@ -224,10 +219,52 @@ function wrapNamespace(namespace, target, trace) {
224
219
  wrappedMethods.set(property, wrapped);
225
220
  return wrapped;
226
221
  }
227
- const wrapped = (...args) => recorder.runSpan(`${namespace}.${methodName}`, () => Reflect.apply(value, namespaceTarget, args), {
228
- onSuccess: (result) => buildSpanAttributes(namespace, methodName, args, result),
229
- onError: (error) => buildSpanAttributes(namespace, methodName, args, undefined, error),
230
- });
222
+ const wrapped = (...args) => {
223
+ // Invoke first and decide by the RETURN VALUE. `runSpan` always
224
+ // returns a Promise, so unconditionally span-wrapping every member
225
+ // silently rewrote synchronous contracts: `ctx.state.namespace()`
226
+ // (a sync factory) came back as a Promise, and every method call on
227
+ // it threw a raw TypeError — surfaced in production as catchtable's
228
+ // deterministic CONFIRM_STATE_UNAVAILABLE on 2026-07-27 (and the
229
+ // 2026-07-22 reserve internal_error loop before it). Sync members
230
+ // keep their sync contract; only genuinely async operations are
231
+ // recorded as spans.
232
+ let result;
233
+ try {
234
+ result = Reflect.apply(value, namespaceTarget, args);
235
+ }
236
+ catch (error) {
237
+ // A promise-returning implementation may still throw
238
+ // SYNCHRONOUSLY during pre-flight validation. Preserve the
239
+ // synchronous throw contract, but keep recording the failure
240
+ // span (the pre-fidelity wrapper captured these).
241
+ recorder
242
+ .runSpan(`${namespace}.${methodName}`, () => {
243
+ throw error;
244
+ }, {
245
+ onError: (spanError) => buildSpanAttributes(namespace, methodName, args, undefined, spanError),
246
+ })
247
+ .catch(() => undefined);
248
+ throw error;
249
+ }
250
+ if (!isThenable(result)) {
251
+ // The state namespace factory returns the object whose METHODS
252
+ // are the operations worth tracing — instrument that object so
253
+ // `state.get`/`state.compareAndSet`/… spans exist (they never
254
+ // could before: the factory's return value was destroyed).
255
+ if (namespace === "state" &&
256
+ methodName === "namespace" &&
257
+ typeof result === "object" &&
258
+ result !== null) {
259
+ return wrapNamespace(namespace, result, trace);
260
+ }
261
+ return result;
262
+ }
263
+ return recorder.runSpan(`${namespace}.${methodName}`, () => result, {
264
+ onSuccess: (spanResult) => buildSpanAttributes(namespace, methodName, args, spanResult),
265
+ onError: (error) => buildSpanAttributes(namespace, methodName, args, undefined, error),
266
+ });
267
+ };
231
268
  wrappedMethods.set(property, wrapped);
232
269
  return wrapped;
233
270
  },
@@ -1,4 +1,4 @@
1
- import { ConfigurationError, decodeMasterKey } from "./key-derivation";
1
+ import { ConfigurationError, decodeMasterKey } from "./key-derivation.js";
2
2
  const DEFAULT_KEY_PREFIX = "APIFUSE__KEYRING__MASTER_KEY_V";
3
3
  const DEFAULT_ACCEPT_LIST_VAR = "APIFUSE__KEYRING__MASTER_KEY_ACCEPT_LIST";
4
4
  const DEFAULT_WRITER_VERSION_VAR = "APIFUSE__KEYRING__MASTER_KEY_WRITER_VERSION";
@@ -1,5 +1,5 @@
1
1
  import { createHmac } from "node:crypto";
2
- import { deriveSubkey } from "./key-derivation";
2
+ import { deriveSubkey } from "./key-derivation.js";
3
3
  const HMAC_HEX_LENGTH = 16;
4
4
  /**
5
5
  * @internal Trusted loaders only; not re-exported to provider-importable paths.
@@ -1,4 +1,4 @@
1
- import type { TraceSpan } from "../types";
1
+ import type { TraceSpan } from "../types.js";
2
2
  export interface OTLPExportOptions {
3
3
  endpoint: string;
4
4
  headers?: Record<string, string>;
@@ -1,4 +1,4 @@
1
- import type { Span } from "./trace";
1
+ import type { Span } from "./trace.js";
2
2
  export type PerfStats = {
3
3
  p50: number;
4
4
  p95: number;
@@ -1,2 +1,2 @@
1
- import type { ProviderDefinition } from "../types";
1
+ import type { ProviderDefinition } from "../types.js";
2
2
  export declare function getProviderBaseUrl(provider: ProviderDefinition): string | undefined;
@@ -1,8 +1,7 @@
1
1
  export function getProviderBaseUrl(provider) {
2
2
  const operations = Object.values(provider.operations);
3
3
  for (const operation of operations) {
4
- const baseUrl = operation.upstream
5
- ?.baseUrl;
4
+ const baseUrl = operation.upstream?.baseUrl;
6
5
  if (typeof baseUrl === "string" && baseUrl.length > 0) {
7
6
  return baseUrl;
8
7
  }
@@ -1,4 +1,4 @@
1
- import { TransportError } from "../errors";
1
+ import { TransportError } from "../errors.js";
2
2
  export declare const PROXY_AUTH_IP_DENIED_CODE = "PROXY_AUTH_IP_DENIED";
3
3
  export declare const PROXY_AUTH_IP_DENIED_MESSAGE = "Proxy source IP is not authorized. Add the runtime egress IP to the proxy provider allowlist.";
4
4
  export declare const PROXY_EDGE_AUTH_REJECTED_CODE = "PROXY_EDGE_AUTH_REJECTED";
@@ -1,4 +1,4 @@
1
- import { TransportError } from "../errors";
1
+ import { TransportError } from "../errors.js";
2
2
  export const PROXY_AUTH_IP_DENIED_CODE = "PROXY_AUTH_IP_DENIED";
3
3
  export const PROXY_AUTH_IP_DENIED_MESSAGE = "Proxy source IP is not authorized. Add the runtime egress IP to the proxy provider allowlist.";
4
4
  export const PROXY_EDGE_AUTH_REJECTED_CODE = "PROXY_EDGE_AUTH_REJECTED";
@@ -11,8 +11,12 @@ const PROXY_POOL_STALE_STATUS_CODES = new Set([509, 512]);
11
11
  const PROXY_EDGE_TLS_REJECTED_STATUS_CODES = new Set([495]);
12
12
  const PROXY_AUTH_IP_DENIED_PATTERN = /\b(?:source|egress|client)\s+ip\b.{0,120}\b(?:deny|denied|unauthori[sz]ed|not\s+authori[sz]ed|white\s*list|allow\s*list)\b|\b(?:white\s*list|allow\s*list)\b.{0,120}\b(?:source|egress|client)\s+ip\b/i;
13
13
  const PROXY_EDGE_AUTH_REJECTED_PATTERN = /\bauth\s+ip\s+err\b|\bproxy\b.{0,120}\bauth(?:entication)?\b.{0,120}\b(?:reject(?:ed)?|fail(?:ed)?|invalid|den(?:y|ied)|unauthori[sz]ed)\b|\bauth(?:entication)?\b.{0,120}\b(?:reject(?:ed)?|fail(?:ed)?|invalid|den(?:y|ied)|unauthori[sz]ed)\b.{0,120}\bproxy\b/i;
14
- const PROXY_POOL_STALE_MESSAGE_PATTERN = /\bproxy\b.{0,120}\b(?:pool|lease|expired|unavailable|exhausted|non[\s-]?200\s+code:\s*(?:509|512))\b|\bnon[\s-]?200\s+code:\s*(?:509|512)\b.{0,120}\bproxy\b|\bsmartproxy\b.{0,120}\b(?:509|512)\b/i;
15
- const PROXY_EDGE_TLS_REJECTED_MESSAGE_PATTERN = /\b(?:smartproxy|proxy)\b.{0,160}\b(?:495|ssl|tls|cert(?:ificate)?|handshake|edge|connect|non[\s-]?200)\b|\b(?:495|ssl|tls|cert(?:ificate)?|handshake|edge|connect|non[\s-]?200)\b.{0,160}\b(?:smartproxy|proxy)\b/i;
14
+ // Vendor host tokens that can appear in upstream error strings. Adding a proxy
15
+ // vendor updates every classifier below in one place. `proxy` is the generic
16
+ // fallback so vendor-agnostic messages still classify.
17
+ const PROXY_VENDOR_ALTERNATION = "smartproxy|nodemaven|proxy";
18
+ const PROXY_POOL_STALE_MESSAGE_PATTERN = new RegExp(`\\bproxy\\b.{0,120}\\b(?:pool|lease|expired|unavailable|exhausted|non[\\s-]?200\\s+code:\\s*(?:509|512))\\b|\\bnon[\\s-]?200\\s+code:\\s*(?:509|512)\\b.{0,120}\\bproxy\\b|\\b(?:${PROXY_VENDOR_ALTERNATION})\\b.{0,120}\\b(?:509|512)\\b`, "i");
19
+ const PROXY_EDGE_TLS_REJECTED_MESSAGE_PATTERN = new RegExp(`\\b(?:${PROXY_VENDOR_ALTERNATION})\\b.{0,160}\\b(?:495|ssl|tls|cert(?:ificate)?|handshake|edge|connect|non[\\s-]?200)\\b|\\b(?:495|ssl|tls|cert(?:ificate)?|handshake|edge|connect|non[\\s-]?200)\\b.{0,160}\\b(?:${PROXY_VENDOR_ALTERNATION})\\b`, "i");
16
20
  export function isProxyAuthIpDeniedMessage(message) {
17
21
  return PROXY_AUTH_IP_DENIED_PATTERN.test(message);
18
22
  }
@@ -42,8 +46,7 @@ export function isProxyPoolStaleMessage(message) {
42
46
  return PROXY_POOL_STALE_MESSAGE_PATTERN.test(message);
43
47
  }
44
48
  export function isProxyPoolRefreshableError(error) {
45
- if (error instanceof TransportError &&
46
- error.code === PROXY_AUTH_IP_DENIED_CODE) {
49
+ if (error instanceof TransportError && error.code === PROXY_AUTH_IP_DENIED_CODE) {
47
50
  return false;
48
51
  }
49
52
  if (error instanceof TransportError &&
@@ -57,8 +60,7 @@ export function isProxyPoolRefreshableError(error) {
57
60
  error instanceof Error ? error.message : String(error),
58
61
  cause instanceof Error ? cause.message : "",
59
62
  ].join(" ");
60
- return (PROXY_POOL_STALE_MESSAGE_PATTERN.test(message) ||
61
- PROXY_EDGE_AUTH_REJECTED_PATTERN.test(message));
63
+ return (PROXY_POOL_STALE_MESSAGE_PATTERN.test(message) || PROXY_EDGE_AUTH_REJECTED_PATTERN.test(message));
62
64
  }
63
65
  export const isProxyPoolStaleError = isProxyPoolRefreshableError;
64
66
  export function createProxyPoolStaleError(status, cause) {
@@ -0,0 +1,35 @@
1
+ import type { ProviderProxyPolicy } from "../types.js";
2
+ export declare const NODEMAVEN_USERNAME_ENV = "APIFUSE__PROXY__NODEMAVEN_USERNAME";
3
+ export declare const NODEMAVEN_PASSWORD_ENV = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
4
+ export declare const NODEMAVEN_FILTER_ENV = "APIFUSE__PROXY__NODEMAVEN_FILTER";
5
+ export declare const NODEMAVEN_GATEWAY_HOST = "gate.nodemaven.com";
6
+ /** Both schemes tunnel bytes end-to-end, preserving the client TLS handshake. */
7
+ export type ProxyProtocol = "http" | "socks5";
8
+ /**
9
+ * NodeMaven's fastest protocol: HTTP CONNECT. Benchmarks (KR, cold + warm)
10
+ * showed socks5 through the gateway adds ~500ms per request over http, so
11
+ * NodeMaven never defaults to socks5.
12
+ */
13
+ export declare const NODEMAVEN_DEFAULT_PROTOCOL: ProxyProtocol;
14
+ export declare const NODEMAVEN_MAX_POOL_SIZE = 50;
15
+ export declare function hasNodemavenCredentials(): boolean;
16
+ export declare function nodemavenPoolSize(policy: ProviderProxyPolicy): number;
17
+ export type NodemavenSynthesisInput = {
18
+ policy: ProviderProxyPolicy;
19
+ affinityKey: string | undefined;
20
+ protocol: ProxyProtocol;
21
+ poolIndex: number;
22
+ refreshEpoch: number;
23
+ /** ISO 3166-1 alpha-2, already resolved by the caller (falls back to env). */
24
+ country?: string;
25
+ };
26
+ export type NodemavenSynthesis = {
27
+ url: string;
28
+ protocol: ProxyProtocol;
29
+ diagnostics: Record<string, string | number | boolean>;
30
+ };
31
+ /**
32
+ * Synthesize a NodeMaven gateway proxy URL locally from static credentials.
33
+ * There is no allocation API — geo/session are encoded in the username.
34
+ */
35
+ export declare function synthesizeNodemavenProxy(input: NodemavenSynthesisInput): NodemavenSynthesis;
@@ -0,0 +1,128 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ export const NODEMAVEN_USERNAME_ENV = "APIFUSE__PROXY__NODEMAVEN_USERNAME";
3
+ export const NODEMAVEN_PASSWORD_ENV = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
4
+ export const NODEMAVEN_FILTER_ENV = "APIFUSE__PROXY__NODEMAVEN_FILTER";
5
+ export const NODEMAVEN_GATEWAY_HOST = "gate.nodemaven.com";
6
+ /**
7
+ * NodeMaven's fastest protocol: HTTP CONNECT. Benchmarks (KR, cold + warm)
8
+ * showed socks5 through the gateway adds ~500ms per request over http, so
9
+ * NodeMaven never defaults to socks5.
10
+ */
11
+ export const NODEMAVEN_DEFAULT_PROTOCOL = "http";
12
+ /** NodeMaven gateway port ranges per protocol (docs: HTTP 8080-9080, SOCKS5 1080-2080). */
13
+ const NODEMAVEN_PORTS = {
14
+ http: { min: 8080, max: 9080 },
15
+ socks5: { min: 1080, max: 2080 },
16
+ };
17
+ const NODEMAVEN_FILTERS = new Set(["medium", "high"]);
18
+ const DEFAULT_NODEMAVEN_FILTER = "medium";
19
+ const DEFAULT_NODEMAVEN_POOL_SIZE = 20;
20
+ export const NODEMAVEN_MAX_POOL_SIZE = 50;
21
+ /** NodeMaven sticky sessions persist up to 24h server-side, keyed by the sid. */
22
+ const NODEMAVEN_MAX_LIFETIME_MINUTES = 1440;
23
+ const SID_LENGTH = 10;
24
+ export function hasNodemavenCredentials() {
25
+ return Boolean(readNodemavenUsername() && readNodemavenPassword());
26
+ }
27
+ function readNodemavenUsername() {
28
+ return process.env[NODEMAVEN_USERNAME_ENV]?.trim() || undefined;
29
+ }
30
+ function readNodemavenPassword() {
31
+ return process.env[NODEMAVEN_PASSWORD_ENV]?.trim() || undefined;
32
+ }
33
+ function resolveNodemavenFilter() {
34
+ const raw = process.env[NODEMAVEN_FILTER_ENV]?.trim().toLowerCase();
35
+ if (!raw)
36
+ return DEFAULT_NODEMAVEN_FILTER;
37
+ if (!NODEMAVEN_FILTERS.has(raw)) {
38
+ throw new Error(`${NODEMAVEN_FILTER_ENV} must be "medium" or "high"`);
39
+ }
40
+ return raw;
41
+ }
42
+ export function nodemavenPoolSize(policy) {
43
+ return Math.min(NODEMAVEN_MAX_POOL_SIZE, Math.max(1, Math.floor(policy.session?.poolSize ?? DEFAULT_NODEMAVEN_POOL_SIZE)));
44
+ }
45
+ function nodemavenLifetimeMinutes(policy) {
46
+ const configured = policy.session?.lifetimeMinutes;
47
+ if (typeof configured !== "number" || !Number.isFinite(configured) || configured <= 0) {
48
+ return NODEMAVEN_MAX_LIFETIME_MINUTES;
49
+ }
50
+ return Math.min(NODEMAVEN_MAX_LIFETIME_MINUTES, Math.max(1, Math.floor(configured)));
51
+ }
52
+ /** NodeMaven username tokens accept `[a-z0-9]`; slugify geo values to that set. */
53
+ function slugifyGeo(value) {
54
+ if (!value)
55
+ return undefined;
56
+ const slug = value
57
+ .trim()
58
+ .toLowerCase()
59
+ .replace(/[^a-z0-9]+/g, "");
60
+ return slug || undefined;
61
+ }
62
+ function isStickyAffinity(policy) {
63
+ return (policy.session?.affinity ?? "request") !== "request";
64
+ }
65
+ /**
66
+ * A sticky sid is deterministic from the affinity key so every process serving
67
+ * the same connection derives the same egress IP without shared storage. A
68
+ * rotating sid is random per call (a fresh egress IP per request).
69
+ */
70
+ function deriveSid(policy, affinityKey, poolIndex, refreshEpoch) {
71
+ if (!isStickyAffinity(policy) || !affinityKey) {
72
+ return randomBytes(SID_LENGTH).toString("hex").slice(0, SID_LENGTH);
73
+ }
74
+ const digest = createHash("sha256")
75
+ .update(`${affinityKey}:${refreshEpoch}:${poolIndex}`)
76
+ .digest("hex");
77
+ // hex digits are a subset of the allowed [a-z0-9] sid charset.
78
+ return digest.slice(0, SID_LENGTH);
79
+ }
80
+ function selectPort(protocol, sid, poolIndex) {
81
+ const { min, max } = NODEMAVEN_PORTS[protocol];
82
+ const span = max - min + 1;
83
+ const hashInt = Number.parseInt(createHash("sha256").update(`${sid}:${poolIndex}`).digest("hex").slice(0, 8), 16);
84
+ return min + (hashInt % span);
85
+ }
86
+ /**
87
+ * Synthesize a NodeMaven gateway proxy URL locally from static credentials.
88
+ * There is no allocation API — geo/session are encoded in the username.
89
+ */
90
+ export function synthesizeNodemavenProxy(input) {
91
+ const username = readNodemavenUsername();
92
+ const password = readNodemavenPassword();
93
+ if (!username || !password) {
94
+ throw new Error(`NodeMaven credentials missing: set ${NODEMAVEN_USERNAME_ENV} and ${NODEMAVEN_PASSWORD_ENV}.`);
95
+ }
96
+ const filter = resolveNodemavenFilter();
97
+ const sid = deriveSid(input.policy, input.affinityKey, input.poolIndex, input.refreshEpoch);
98
+ const port = selectPort(input.protocol, sid, input.poolIndex);
99
+ const lifetimeMinutes = nodemavenLifetimeMinutes(input.policy);
100
+ const country = slugifyGeo(input.country ?? input.policy.geo?.country);
101
+ const region = slugifyGeo(input.policy.geo?.subdivision);
102
+ const city = slugifyGeo(input.policy.geo?.city);
103
+ const tokens = [username];
104
+ if (country)
105
+ tokens.push("country", country);
106
+ if (region)
107
+ tokens.push("region", region);
108
+ if (city)
109
+ tokens.push("city", city);
110
+ tokens.push("sid", sid);
111
+ tokens.push("filter", filter);
112
+ tokens.push("ipv4", "true");
113
+ const proxyUsername = tokens.join("-");
114
+ // Username tokens are [a-z0-9-] only, which survive URL encoding unchanged.
115
+ const url = `${input.protocol}://${proxyUsername}:${encodeURIComponent(password)}@${NODEMAVEN_GATEWAY_HOST}:${port}`;
116
+ return {
117
+ url,
118
+ protocol: input.protocol,
119
+ diagnostics: {
120
+ vendor: "nodemaven",
121
+ protocol: input.protocol,
122
+ sticky: isStickyAffinity(input.policy),
123
+ filter,
124
+ lifetimeMinutes,
125
+ ...(country ? { country } : {}),
126
+ },
127
+ };
128
+ }
@@ -0,0 +1,40 @@
1
+ import type { HttpMethod, HttpRetryOptions, RequestOptions } from "../types.js";
2
+ import { HttpRetryPreset } from "../types.js";
3
+ export type NormalizedProxyTransportRetryOptions = Required<Pick<HttpRetryOptions, "attempts" | "delayStrategy" | "baseDelayMs" | "maxDelayMs" | "jitter" | "retryAfter" | "unsafeMethodPolicy">> & {
4
+ preset?: HttpRetryPreset;
5
+ methods: readonly string[];
6
+ statusCodes: readonly number[];
7
+ errorCodes: readonly string[];
8
+ };
9
+ export declare const DEFAULT_PROXY_TRANSPORT_RETRY_METHODS: readonly ["GET", "HEAD", "OPTIONS"];
10
+ export declare const DEFAULT_PROXY_TRANSPORT_RETRY_ERROR_CODES: readonly ["transport_network_error", "transport_timeout"];
11
+ export declare const MAX_PROXY_TRANSPORT_RETRY_ATTEMPTS = 8;
12
+ type RetryPolicyLabel = "HTTP" | "Stealth" | "Proxy transport";
13
+ export declare function proxyTransportRetryErrorCode(error: unknown): string | undefined;
14
+ export declare function proxyTransportRetryErrorStatus(error: unknown): number | undefined;
15
+ export declare function createDefaultProxyTransportRetryOptions(options?: {
16
+ extraErrorCodes?: readonly string[];
17
+ label?: RetryPolicyLabel;
18
+ }): NormalizedProxyTransportRetryOptions;
19
+ export declare function normalizeProxyTransportRetryOptions(retry: RequestOptions["retry"], options?: {
20
+ extraErrorCodes?: readonly string[];
21
+ label?: RetryPolicyLabel;
22
+ }): NormalizedProxyTransportRetryOptions | undefined;
23
+ export declare function validateUnsafeProxyTransportRetryMethods(options: NormalizedProxyTransportRetryOptions, label?: RetryPolicyLabel): void;
24
+ export declare function isProxyTransportRetryMethod(method: HttpMethod | string, options: NormalizedProxyTransportRetryOptions): boolean;
25
+ export declare function shouldRetryProxyTransportError(error: unknown, options: NormalizedProxyTransportRetryOptions): boolean;
26
+ export declare function shouldRetryProxyTransportAttempt(input: {
27
+ error: unknown;
28
+ explicitRetry: boolean;
29
+ method: HttpMethod | string;
30
+ options: NormalizedProxyTransportRetryOptions | undefined;
31
+ proxyUsed: boolean;
32
+ }): boolean;
33
+ export declare function computeProxyTransportRetryDelayMs(options: NormalizedProxyTransportRetryOptions, attemptIndex: number, headers?: Record<string, string>): number;
34
+ export declare function normalizeProxyAttemptIndex(value: number | undefined): number;
35
+ export declare function computeProxyAttemptIndex(options: {
36
+ baseProxyAttempt?: number;
37
+ proxyAttemptOffset?: number;
38
+ retryAttemptOffset?: number;
39
+ }): number;
40
+ export {};