@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,5 +1,5 @@
1
- import { computePercentile } from "./perf";
2
- import type { Span, SpanAttributeValue } from "./trace";
1
+ import { computePercentile } from "./perf.js";
2
+ import type { Span, SpanAttributeValue } from "./trace.js";
3
3
 
4
4
  export type InsightSeverity = "info" | "warning" | "error";
5
5
 
@@ -115,11 +115,7 @@ function parseHostname(url: string | undefined): string | undefined {
115
115
  }
116
116
  }
117
117
 
118
- function makeInsight(
119
- id: string,
120
- severity: InsightSeverity,
121
- result: InsightResult,
122
- ): Insight {
118
+ function makeInsight(id: string, severity: InsightSeverity, result: InsightResult): Insight {
123
119
  return {
124
120
  id,
125
121
  severity,
@@ -266,8 +262,7 @@ function getDnsRepeatedCandidate(spans: Span[]): DnsHostInsight | null {
266
262
  }
267
263
 
268
264
  const avgDnsMs =
269
- entry.dnsDurations.reduce((sum, value) => sum + value, 0) /
270
- entry.dnsDurations.length;
265
+ entry.dnsDurations.reduce((sum, value) => sum + value, 0) / entry.dnsDurations.length;
271
266
  const reuseRate = entry.reuseCount / entry.totalCount;
272
267
 
273
268
  if (avgDnsMs > DNS_WARN_MS && reuseRate < 0.2) {
@@ -301,9 +296,7 @@ function getDnsRepeatedInsight(spans: Span[]): InsightResult {
301
296
 
302
297
  function getProxyOverheadInsight(spans: Span[]): InsightResult {
303
298
  const requestSpans = spans.filter(isRequestSpan);
304
- const proxiedDurations = requestSpans
305
- .filter(hasProxy)
306
- .map((span) => span.duration_ms);
299
+ const proxiedDurations = requestSpans.filter(hasProxy).map((span) => span.duration_ms);
307
300
  const directDurations = requestSpans
308
301
  .filter((span) => !hasProxy(span))
309
302
  .map((span) => span.duration_ms);
@@ -316,11 +309,8 @@ function getProxyOverheadInsight(spans: Span[]): InsightResult {
316
309
  }
317
310
 
318
311
  const proxyAvg =
319
- proxiedDurations.reduce((sum, value) => sum + value, 0) /
320
- proxiedDurations.length;
321
- const directAvg =
322
- directDurations.reduce((sum, value) => sum + value, 0) /
323
- directDurations.length;
312
+ proxiedDurations.reduce((sum, value) => sum + value, 0) / proxiedDurations.length;
313
+ const directAvg = directDurations.reduce((sum, value) => sum + value, 0) / directDurations.length;
324
314
 
325
315
  if (directAvg > 0 && proxyAvg >= directAvg * 2) {
326
316
  return {
@@ -345,9 +335,7 @@ function getBrowserIdleInsight(spans: Span[]): InsightResult {
345
335
  return waitMs;
346
336
  }
347
337
 
348
- return span.name.toLowerCase().includes("wait")
349
- ? span.duration_ms
350
- : undefined;
338
+ return span.name.toLowerCase().includes("wait") ? span.duration_ms : undefined;
351
339
  })
352
340
  .filter((value): value is number => value !== undefined);
353
341
 
@@ -374,9 +362,7 @@ function getBrowserIdleInsight(spans: Span[]): InsightResult {
374
362
  }
375
363
 
376
364
  function getSessionExpiryInsight(spans: Span[]): InsightResult {
377
- const refreshCount = spans.filter(
378
- (span) => span.name === "credential.refresh",
379
- ).length;
365
+ const refreshCount = spans.filter((span) => span.name === "credential.refresh").length;
380
366
  const requestCount = spans.filter(isRequestSpan).length;
381
367
 
382
368
  if (requestCount === 0) {
@@ -415,36 +401,12 @@ export function generateInsights(spans: Span[]): Insight[] {
415
401
  const sessionExpiry = getSessionExpiryInsight(spans);
416
402
 
417
403
  const rules = [
418
- makeInsight(
419
- "tls_reuse_failure",
420
- tlsReuse.triggered ? "warning" : "info",
421
- tlsReuse,
422
- ),
423
- makeInsight(
424
- "slow_transform",
425
- slowTransform.triggered ? "warning" : "info",
426
- slowTransform,
427
- ),
428
- makeInsight(
429
- "large_response",
430
- largeResponse.triggered ? "warning" : "info",
431
- largeResponse,
432
- ),
433
- makeInsight(
434
- "dns_repeated",
435
- dnsRepeated.triggered ? "warning" : "info",
436
- dnsRepeated,
437
- ),
438
- makeInsight(
439
- "proxy_overhead",
440
- proxyOverhead.triggered ? "warning" : "info",
441
- proxyOverhead,
442
- ),
443
- makeInsight(
444
- "browser_idle",
445
- browserIdle.triggered ? "warning" : "info",
446
- browserIdle,
447
- ),
404
+ makeInsight("tls_reuse_failure", tlsReuse.triggered ? "warning" : "info", tlsReuse),
405
+ makeInsight("slow_transform", slowTransform.triggered ? "warning" : "info", slowTransform),
406
+ makeInsight("large_response", largeResponse.triggered ? "warning" : "info", largeResponse),
407
+ makeInsight("dns_repeated", dnsRepeated.triggered ? "warning" : "info", dnsRepeated),
408
+ makeInsight("proxy_overhead", proxyOverhead.triggered ? "warning" : "info", proxyOverhead),
409
+ makeInsight("browser_idle", browserIdle.triggered ? "warning" : "info", browserIdle),
448
410
  makeInsight(
449
411
  "session_expiry_frequent",
450
412
  sessionExpiry.triggered ? "warning" : "info",
@@ -1,34 +1,28 @@
1
- import type { ProviderContext } from "../types";
1
+ import type { ProviderContext } from "../types.js";
2
2
  import {
3
3
  type CreateTraceContextOptions,
4
4
  createTraceContext,
5
5
  getTraceRecorder,
6
6
  type TraceContext,
7
- } from "./trace";
7
+ } from "./trace.js";
8
8
 
9
9
  export interface InstrumentationOptions extends CreateTraceContextOptions {}
10
10
 
11
- export type InstrumentedProviderContext<T extends ProviderContext> = Omit<
12
- T,
13
- "trace"
14
- > & {
11
+ export type InstrumentedProviderContext<T extends ProviderContext> = Omit<T, "trace"> & {
15
12
  trace: TraceContext;
16
13
  };
17
14
 
18
- type InstrumentedNamespace =
19
- | "http"
20
- | "stealth"
21
- | "browser"
22
- | "session"
23
- | "state";
24
-
25
- const BROWSER_PAGE_METHODS = new Set([
26
- "goto",
27
- "fill",
28
- "click",
29
- "type",
30
- "waitForSelector",
31
- ]);
15
+ type InstrumentedNamespace = "http" | "stealth" | "browser" | "session" | "state";
16
+
17
+ const BROWSER_PAGE_METHODS = new Set(["goto", "fill", "click", "type", "waitForSelector"]);
18
+
19
+ function isThenable(value: unknown): value is PromiseLike<unknown> {
20
+ return (
21
+ (typeof value === "object" || typeof value === "function") &&
22
+ value !== null &&
23
+ typeof (value as { then?: unknown }).then === "function"
24
+ );
25
+ }
32
26
 
33
27
  function getErrorStatus(error: unknown): number | undefined {
34
28
  if (
@@ -59,10 +53,7 @@ function getResponseDuration(result: unknown): number | undefined {
59
53
  return undefined;
60
54
  }
61
55
 
62
- function getResponseStatus(
63
- namespace: InstrumentedNamespace,
64
- result: unknown,
65
- ): number | undefined {
56
+ function getResponseStatus(namespace: InstrumentedNamespace, result: unknown): number | undefined {
66
57
  if (
67
58
  typeof result === "object" &&
68
59
  result !== null &&
@@ -111,8 +102,7 @@ function getMethod(
111
102
  }
112
103
 
113
104
  if (namespace === "stealth") {
114
- const options =
115
- typeof args[1] === "object" && args[1] !== null ? args[1] : undefined;
105
+ const options = typeof args[1] === "object" && args[1] !== null ? args[1] : undefined;
116
106
  if (options && "method" in options && typeof options.method === "string") {
117
107
  return options.method.toUpperCase();
118
108
  }
@@ -133,9 +123,7 @@ function buildSpanAttributes(
133
123
  const attributes: Record<string, string | number | boolean> = {};
134
124
  const url = getUrl(namespace, args, result);
135
125
  const method = getMethod(namespace, methodName, args);
136
- const status = error
137
- ? getErrorStatus(error)
138
- : getResponseStatus(namespace, result);
126
+ const status = error ? getErrorStatus(error) : getResponseStatus(namespace, result);
139
127
  const duration = error ? undefined : getResponseDuration(result);
140
128
 
141
129
  if (url) {
@@ -146,10 +134,7 @@ function buildSpanAttributes(
146
134
  attributes.method = method;
147
135
  }
148
136
 
149
- if (
150
- status !== undefined &&
151
- (namespace === "http" || namespace === "stealth")
152
- ) {
137
+ if (status !== undefined && (namespace === "http" || namespace === "stealth")) {
153
138
  attributes.status = status;
154
139
  }
155
140
 
@@ -241,10 +226,8 @@ function wrapPage<T extends object>(page: T, trace: TraceContext): T {
241
226
  return result;
242
227
  },
243
228
  {
244
- onSuccess: () =>
245
- getBrowserPageAttributes(methodName, args, elapsedMs),
246
- onError: (error) =>
247
- getBrowserPageAttributes(methodName, args, undefined, error),
229
+ onSuccess: () => getBrowserPageAttributes(methodName, args, elapsedMs),
230
+ onError: (error) => getBrowserPageAttributes(methodName, args, undefined, error),
248
231
  },
249
232
  );
250
233
  };
@@ -310,18 +293,14 @@ function wrapNamespace<T extends object>(
310
293
  namespaceTarget &&
311
294
  typeof namespaceTarget === "object" &&
312
295
  "engine" in namespaceTarget &&
313
- typeof (namespaceTarget as { engine?: unknown }).engine ===
314
- "string"
296
+ typeof (namespaceTarget as { engine?: unknown }).engine === "string"
315
297
  ) {
316
- attributes.engine = (
317
- namespaceTarget as { engine: string }
318
- ).engine;
298
+ attributes.engine = (namespaceTarget as { engine: string }).engine;
319
299
  }
320
300
 
321
301
  return attributes;
322
302
  },
323
- onError: (error) =>
324
- getBrowserPageAttributes("newPage", args, undefined, error),
303
+ onError: (error) => getBrowserPageAttributes("newPage", args, undefined, error),
325
304
  },
326
305
  );
327
306
  };
@@ -348,10 +327,8 @@ function wrapNamespace<T extends object>(
348
327
  return result;
349
328
  },
350
329
  {
351
- onSuccess: () =>
352
- getBrowserPageAttributes(methodName, args, elapsedMs),
353
- onError: (error) =>
354
- getBrowserPageAttributes(methodName, args, undefined, error),
330
+ onSuccess: () => getBrowserPageAttributes(methodName, args, elapsedMs),
331
+ onError: (error) => getBrowserPageAttributes(methodName, args, undefined, error),
355
332
  },
356
333
  );
357
334
  };
@@ -360,23 +337,59 @@ function wrapNamespace<T extends object>(
360
337
  return wrapped;
361
338
  }
362
339
 
363
- const wrapped = (...args: unknown[]) =>
364
- recorder.runSpan(
365
- `${namespace}.${methodName}`,
366
- () => Reflect.apply(value, namespaceTarget, args),
367
- {
368
- onSuccess: (result) =>
369
- buildSpanAttributes(namespace, methodName, args, result),
370
- onError: (error) =>
371
- buildSpanAttributes(
372
- namespace,
373
- methodName,
374
- args,
375
- undefined,
376
- error,
377
- ),
378
- },
379
- );
340
+ const wrapped = (...args: unknown[]) => {
341
+ // Invoke first and decide by the RETURN VALUE. `runSpan` always
342
+ // returns a Promise, so unconditionally span-wrapping every member
343
+ // silently rewrote synchronous contracts: `ctx.state.namespace()`
344
+ // (a sync factory) came back as a Promise, and every method call on
345
+ // it threw a raw TypeError — surfaced in production as catchtable's
346
+ // deterministic CONFIRM_STATE_UNAVAILABLE on 2026-07-27 (and the
347
+ // 2026-07-22 reserve internal_error loop before it). Sync members
348
+ // keep their sync contract; only genuinely async operations are
349
+ // recorded as spans.
350
+ let result: unknown;
351
+ try {
352
+ result = Reflect.apply(value, namespaceTarget, args);
353
+ } catch (error) {
354
+ // A promise-returning implementation may still throw
355
+ // SYNCHRONOUSLY during pre-flight validation. Preserve the
356
+ // synchronous throw contract, but keep recording the failure
357
+ // span (the pre-fidelity wrapper captured these).
358
+ recorder
359
+ .runSpan(
360
+ `${namespace}.${methodName}`,
361
+ () => {
362
+ throw error;
363
+ },
364
+ {
365
+ onError: (spanError) =>
366
+ buildSpanAttributes(namespace, methodName, args, undefined, spanError),
367
+ },
368
+ )
369
+ .catch(() => undefined);
370
+ throw error;
371
+ }
372
+ if (!isThenable(result)) {
373
+ // The state namespace factory returns the object whose METHODS
374
+ // are the operations worth tracing — instrument that object so
375
+ // `state.get`/`state.compareAndSet`/… spans exist (they never
376
+ // could before: the factory's return value was destroyed).
377
+ if (
378
+ namespace === "state" &&
379
+ methodName === "namespace" &&
380
+ typeof result === "object" &&
381
+ result !== null
382
+ ) {
383
+ return wrapNamespace(namespace, result, trace);
384
+ }
385
+ return result;
386
+ }
387
+ return recorder.runSpan(`${namespace}.${methodName}`, () => result, {
388
+ onSuccess: (spanResult) =>
389
+ buildSpanAttributes(namespace, methodName, args, spanResult),
390
+ onError: (error) => buildSpanAttributes(namespace, methodName, args, undefined, error),
391
+ });
392
+ };
380
393
 
381
394
  wrappedMethods.set(property, wrapped);
382
395
  return wrapped;
@@ -1,4 +1,4 @@
1
- import { ConfigurationError, decodeMasterKey } from "./key-derivation";
1
+ import { ConfigurationError, decodeMasterKey } from "./key-derivation.js";
2
2
 
3
3
  export interface KeyRingOptions {
4
4
  env: NodeJS.ProcessEnv;
@@ -16,16 +16,12 @@ export interface KeyRing {
16
16
  accept(version: number): KeyRingEntry;
17
17
  activeWriter(): KeyRingEntry;
18
18
  versions(): number[];
19
- purgeVersion(
20
- version: number,
21
- isActiveInStore: (v: number) => Promise<boolean>,
22
- ): Promise<void>;
19
+ purgeVersion(version: number, isActiveInStore: (v: number) => Promise<boolean>): Promise<void>;
23
20
  }
24
21
 
25
22
  const DEFAULT_KEY_PREFIX = "APIFUSE__KEYRING__MASTER_KEY_V";
26
23
  const DEFAULT_ACCEPT_LIST_VAR = "APIFUSE__KEYRING__MASTER_KEY_ACCEPT_LIST";
27
- const DEFAULT_WRITER_VERSION_VAR =
28
- "APIFUSE__KEYRING__MASTER_KEY_WRITER_VERSION";
24
+ const DEFAULT_WRITER_VERSION_VAR = "APIFUSE__KEYRING__MASTER_KEY_WRITER_VERSION";
29
25
 
30
26
  function parseAcceptList(raw: string | undefined): number[] {
31
27
  if (!raw || raw.trim().length === 0) {
@@ -56,19 +52,14 @@ function parseAcceptList(raw: string | undefined): number[] {
56
52
  return versions;
57
53
  }
58
54
 
59
- function parseWriterVersion(
60
- raw: string | undefined,
61
- acceptList: number[],
62
- ): number {
55
+ function parseWriterVersion(raw: string | undefined, acceptList: number[]): number {
63
56
  if (!raw || raw.trim().length === 0) {
64
57
  throw new ConfigurationError("writer-version env var is empty");
65
58
  }
66
59
  const trimmed = raw.trim();
67
60
  const v = Number.parseInt(trimmed, 10);
68
61
  if (!Number.isInteger(v) || v <= 0 || String(v) !== trimmed) {
69
- throw new ConfigurationError(
70
- `writer-version "${trimmed}" is not a positive integer`,
71
- );
62
+ throw new ConfigurationError(`writer-version "${trimmed}" is not a positive integer`);
72
63
  }
73
64
  if (!acceptList.includes(v)) {
74
65
  throw new ConfigurationError(
@@ -90,8 +81,7 @@ export function loadKeyRing(options: KeyRingOptions): KeyRing {
90
81
  const env = options.env;
91
82
  const prefix = options.envPrefix ?? DEFAULT_KEY_PREFIX;
92
83
  const acceptListVar = options.acceptListVar ?? DEFAULT_ACCEPT_LIST_VAR;
93
- const writerVersionVar =
94
- options.writerVersionVar ?? DEFAULT_WRITER_VERSION_VAR;
84
+ const writerVersionVar = options.writerVersionVar ?? DEFAULT_WRITER_VERSION_VAR;
95
85
 
96
86
  const acceptList = parseAcceptList(env[acceptListVar]);
97
87
  const writerVersion = parseWriterVersion(env[writerVersionVar], acceptList);
@@ -100,9 +90,7 @@ export function loadKeyRing(options: KeyRingOptions): KeyRing {
100
90
  for (const version of acceptList) {
101
91
  const raw = env[`${prefix}${version}`];
102
92
  if (raw === undefined) {
103
- throw new ConfigurationError(
104
- `${prefix}${version} is missing (listed in accept-list)`,
105
- );
93
+ throw new ConfigurationError(`${prefix}${version} is missing (listed in accept-list)`);
106
94
  }
107
95
  const key = decodeMasterKey(raw);
108
96
  entries.set(version, { version, key });
@@ -1,6 +1,6 @@
1
1
  import { createHmac } from "node:crypto";
2
2
 
3
- import { deriveSubkey } from "./key-derivation";
3
+ import { deriveSubkey } from "./key-derivation.js";
4
4
 
5
5
  const HMAC_HEX_LENGTH = 16;
6
6
 
@@ -22,12 +22,7 @@ export function deriveContextNamespace(
22
22
  throw new Error("sessionId is empty");
23
23
  }
24
24
 
25
- const subkey = deriveSubkey(
26
- masterSecret,
27
- providerId,
28
- "context-namespace",
29
- keyVersion,
30
- );
25
+ const subkey = deriveSubkey(masterSecret, providerId, "context-namespace", keyVersion);
31
26
  const hmac = createHmac("sha256", subkey).update(providerId).digest("hex");
32
27
  return `provider:${hmac.slice(0, HMAC_HEX_LENGTH)}:${sessionId}`;
33
28
  }
@@ -1,4 +1,4 @@
1
- import type { TraceSpan } from "../types";
1
+ import type { TraceSpan } from "../types.js";
2
2
 
3
3
  export interface OTLPExportOptions {
4
4
  endpoint: string;
@@ -32,10 +32,7 @@ function createTraceId(signature: string): string {
32
32
  return traceId;
33
33
  }
34
34
 
35
- function normalizeHexId(
36
- value: string | undefined,
37
- length: number,
38
- ): string | undefined {
35
+ function normalizeHexId(value: string | undefined, length: number): string | undefined {
39
36
  if (!value) {
40
37
  return undefined;
41
38
  }
@@ -44,9 +41,7 @@ function normalizeHexId(
44
41
  return normalized.padStart(length, "0").slice(-length);
45
42
  }
46
43
 
47
- function toAttributeValue(
48
- value: unknown,
49
- ): Record<string, string | number | boolean> {
44
+ function toAttributeValue(value: unknown): Record<string, string | number | boolean> {
50
45
  if (typeof value === "string") {
51
46
  return { stringValue: value };
52
47
  }
@@ -92,20 +87,16 @@ export function spansToOTLP(
92
87
  }>;
93
88
  }>;
94
89
  } {
95
- const traceId = createTraceId(
96
- createBatchSignature(spans, resourceAttributes),
97
- );
90
+ const traceId = createTraceId(createBatchSignature(spans, resourceAttributes));
98
91
 
99
92
  return {
100
93
  resourceSpans: [
101
94
  {
102
95
  resource: {
103
- attributes: Object.entries(resourceAttributes ?? {}).map(
104
- ([key, value]) => ({
105
- key,
106
- value: { stringValue: value },
107
- }),
108
- ),
96
+ attributes: Object.entries(resourceAttributes ?? {}).map(([key, value]) => ({
97
+ key,
98
+ value: { stringValue: value },
99
+ })),
109
100
  },
110
101
  scopeSpans: [
111
102
  {
@@ -122,12 +113,10 @@ export function spansToOTLP(
122
113
  startTimeUnixNano: String(span.startedAt * 1_000_000),
123
114
  endTimeUnixNano: String(span.endedAt * 1_000_000),
124
115
  status: { code: span.status === "ok" ? 1 : 2 },
125
- attributes: Object.entries(span.attributes ?? {}).map(
126
- ([key, value]) => ({
127
- key,
128
- value: toAttributeValue(value),
129
- }),
130
- ),
116
+ attributes: Object.entries(span.attributes ?? {}).map(([key, value]) => ({
117
+ key,
118
+ value: toAttributeValue(value),
119
+ })),
131
120
  })),
132
121
  },
133
122
  ],
@@ -1,4 +1,4 @@
1
- import type { Span } from "./trace";
1
+ import type { Span } from "./trace.js";
2
2
 
3
3
  export type PerfStats = {
4
4
  p50: number;
@@ -1,15 +1,10 @@
1
- import type { ProviderDefinition } from "../types";
1
+ import type { ProviderDefinition } from "../types.js";
2
2
 
3
- export function getProviderBaseUrl(
4
- provider: ProviderDefinition,
5
- ): string | undefined {
6
- const operations = Object.values(
7
- provider.operations as Record<string, unknown>,
8
- );
3
+ export function getProviderBaseUrl(provider: ProviderDefinition): string | undefined {
4
+ const operations = Object.values(provider.operations as Record<string, unknown>);
9
5
 
10
6
  for (const operation of operations) {
11
- const baseUrl = (operation as { upstream?: { baseUrl?: unknown } }).upstream
12
- ?.baseUrl;
7
+ const baseUrl = (operation as { upstream?: { baseUrl?: unknown } }).upstream?.baseUrl;
13
8
 
14
9
  if (typeof baseUrl === "string" && baseUrl.length > 0) {
15
10
  return baseUrl;
@@ -1,4 +1,4 @@
1
- import { TransportError } from "../errors";
1
+ import { TransportError } from "../errors.js";
2
2
 
3
3
  export const PROXY_AUTH_IP_DENIED_CODE = "PROXY_AUTH_IP_DENIED";
4
4
  export const PROXY_AUTH_IP_DENIED_MESSAGE =
@@ -19,10 +19,18 @@ const PROXY_AUTH_IP_DENIED_PATTERN =
19
19
  /\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;
20
20
  const PROXY_EDGE_AUTH_REJECTED_PATTERN =
21
21
  /\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;
22
- const PROXY_POOL_STALE_MESSAGE_PATTERN =
23
- /\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;
24
- const PROXY_EDGE_TLS_REJECTED_MESSAGE_PATTERN =
25
- /\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;
22
+ // Vendor host tokens that can appear in upstream error strings. Adding a proxy
23
+ // vendor updates every classifier below in one place. `proxy` is the generic
24
+ // fallback so vendor-agnostic messages still classify.
25
+ const PROXY_VENDOR_ALTERNATION = "smartproxy|nodemaven|proxy";
26
+ const PROXY_POOL_STALE_MESSAGE_PATTERN = new RegExp(
27
+ `\\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`,
28
+ "i",
29
+ );
30
+ const PROXY_EDGE_TLS_REJECTED_MESSAGE_PATTERN = new RegExp(
31
+ `\\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`,
32
+ "i",
33
+ );
26
34
 
27
35
  export function isProxyAuthIpDeniedMessage(message: string): boolean {
28
36
  return PROXY_AUTH_IP_DENIED_PATTERN.test(message);
@@ -39,9 +47,7 @@ export function isProxyEdgeAuthRejectedMessage(message: string): boolean {
39
47
  return PROXY_EDGE_AUTH_REJECTED_PATTERN.test(message);
40
48
  }
41
49
 
42
- export function createProxyEdgeAuthRejectedError(
43
- cause?: Error,
44
- ): TransportError {
50
+ export function createProxyEdgeAuthRejectedError(cause?: Error): TransportError {
45
51
  return new TransportError(PROXY_EDGE_AUTH_REJECTED_MESSAGE, {
46
52
  code: PROXY_EDGE_AUTH_REJECTED_CODE,
47
53
  cause,
@@ -52,10 +58,7 @@ export function isProxyPoolStaleStatus(status: number): boolean {
52
58
  return PROXY_POOL_STALE_STATUS_CODES.has(status);
53
59
  }
54
60
 
55
- export function isProxyEdgeTlsRejectedResponse(
56
- status: number,
57
- evidence: string,
58
- ): boolean {
61
+ export function isProxyEdgeTlsRejectedResponse(status: number, evidence: string): boolean {
59
62
  return (
60
63
  PROXY_EDGE_TLS_REJECTED_STATUS_CODES.has(status) &&
61
64
  PROXY_EDGE_TLS_REJECTED_MESSAGE_PATTERN.test(evidence)
@@ -67,10 +70,7 @@ export function isProxyPoolStaleMessage(message: string): boolean {
67
70
  }
68
71
 
69
72
  export function isProxyPoolRefreshableError(error: unknown): boolean {
70
- if (
71
- error instanceof TransportError &&
72
- error.code === PROXY_AUTH_IP_DENIED_CODE
73
- ) {
73
+ if (error instanceof TransportError && error.code === PROXY_AUTH_IP_DENIED_CODE) {
74
74
  return false;
75
75
  }
76
76
 
@@ -89,39 +89,26 @@ export function isProxyPoolRefreshableError(error: unknown): boolean {
89
89
  cause instanceof Error ? cause.message : "",
90
90
  ].join(" ");
91
91
  return (
92
- PROXY_POOL_STALE_MESSAGE_PATTERN.test(message) ||
93
- PROXY_EDGE_AUTH_REJECTED_PATTERN.test(message)
92
+ PROXY_POOL_STALE_MESSAGE_PATTERN.test(message) || PROXY_EDGE_AUTH_REJECTED_PATTERN.test(message)
94
93
  );
95
94
  }
96
95
 
97
96
  export const isProxyPoolStaleError = isProxyPoolRefreshableError;
98
97
 
99
- export function createProxyPoolStaleError(
100
- status: number,
101
- cause?: Error,
102
- ): TransportError {
103
- return new TransportError(
104
- `Proxy provider pool failed with status ${status}`,
105
- {
106
- code: PROXY_POOL_STALE_CODE,
107
- status,
108
- cause,
109
- },
110
- );
98
+ export function createProxyPoolStaleError(status: number, cause?: Error): TransportError {
99
+ return new TransportError(`Proxy provider pool failed with status ${status}`, {
100
+ code: PROXY_POOL_STALE_CODE,
101
+ status,
102
+ cause,
103
+ });
111
104
  }
112
105
 
113
- export function createProxyEdgeTlsRejectedError(
114
- status: number,
115
- cause?: Error,
116
- ): TransportError {
117
- return new TransportError(
118
- `Proxy edge TLS request was rejected with status ${status}`,
119
- {
120
- code: PROXY_EDGE_TLS_REJECTED_CODE,
121
- status,
122
- cause,
123
- },
124
- );
106
+ export function createProxyEdgeTlsRejectedError(status: number, cause?: Error): TransportError {
107
+ return new TransportError(`Proxy edge TLS request was rejected with status ${status}`, {
108
+ code: PROXY_EDGE_TLS_REJECTED_CODE,
109
+ status,
110
+ cause,
111
+ });
125
112
  }
126
113
 
127
114
  export function createProxyPoolExhaustedError(cause?: Error): TransportError {