@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.4

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 (165) hide show
  1. package/AUTHORING.md +14 -0
  2. package/CHANGELOG.md +4 -0
  3. package/bin/apifuse-check.ts +18 -58
  4. package/bin/apifuse-create.ts +1 -1
  5. package/bin/apifuse-dev.ts +27 -52
  6. package/bin/apifuse-pack-smoke.ts +22 -81
  7. package/bin/apifuse-pack-types.ts +266 -0
  8. package/bin/apifuse-perf.ts +45 -127
  9. package/bin/apifuse-record.ts +23 -61
  10. package/bin/apifuse-submit-check.ts +26 -21
  11. package/bin/apifuse.ts +1 -1
  12. package/bin/submit-check-xml.ts +1 -1
  13. package/dist/auth-turn/index.d.ts +1 -1
  14. package/dist/auth.d.ts +2 -2
  15. package/dist/auth.js +9 -18
  16. package/dist/ceremonies/index.d.ts +1 -1
  17. package/dist/ceremonies/index.js +8 -18
  18. package/dist/config/loader.d.ts +2 -2
  19. package/dist/config/loader.js +19 -42
  20. package/dist/contract-serialization.d.ts +2 -2
  21. package/dist/contract-serialization.js +3 -6
  22. package/dist/contract-types.d.ts +2 -2
  23. package/dist/contract.d.ts +3 -3
  24. package/dist/contract.js +4 -6
  25. package/dist/define.d.ts +8 -1
  26. package/dist/define.js +57 -115
  27. package/dist/dev.d.ts +1 -1
  28. package/dist/dev.js +1 -1
  29. package/dist/errors.d.ts +1 -1
  30. package/dist/errors.js +1 -3
  31. package/dist/i18n/catalog.d.ts +2 -2
  32. package/dist/i18n/catalog.js +4 -10
  33. package/dist/i18n/index.d.ts +2 -2
  34. package/dist/i18n/index.js +2 -2
  35. package/dist/i18n/keys.d.ts +2 -2
  36. package/dist/index.d.ts +43 -42
  37. package/dist/index.js +38 -37
  38. package/dist/lint.js +8 -15
  39. package/dist/provider.d.ts +11 -11
  40. package/dist/provider.js +9 -9
  41. package/dist/public-schema-field-lint.d.ts +1 -1
  42. package/dist/recipes/gov-api.js +1 -1
  43. package/dist/runtime/auth-flow.d.ts +1 -1
  44. package/dist/runtime/auth-flow.js +3 -3
  45. package/dist/runtime/browser.d.ts +1 -1
  46. package/dist/runtime/browser.js +15 -29
  47. package/dist/runtime/cache.d.ts +1 -1
  48. package/dist/runtime/cache.js +4 -8
  49. package/dist/runtime/choice.d.ts +1 -1
  50. package/dist/runtime/choice.js +12 -34
  51. package/dist/runtime/credential.d.ts +1 -1
  52. package/dist/runtime/credential.js +1 -1
  53. package/dist/runtime/env.d.ts +1 -1
  54. package/dist/runtime/executor.d.ts +1 -1
  55. package/dist/runtime/executor.js +2 -2
  56. package/dist/runtime/http.d.ts +2 -2
  57. package/dist/runtime/http.js +5 -5
  58. package/dist/runtime/insights.d.ts +1 -1
  59. package/dist/runtime/insights.js +6 -13
  60. package/dist/runtime/instrumentation.d.ts +2 -2
  61. package/dist/runtime/instrumentation.js +5 -15
  62. package/dist/runtime/keyring.js +1 -1
  63. package/dist/runtime/namespace.js +1 -1
  64. package/dist/runtime/otlp.d.ts +1 -1
  65. package/dist/runtime/perf.d.ts +1 -1
  66. package/dist/runtime/provider.d.ts +1 -1
  67. package/dist/runtime/provider.js +1 -2
  68. package/dist/runtime/proxy-errors.d.ts +1 -1
  69. package/dist/runtime/proxy-errors.js +3 -5
  70. package/dist/runtime/proxy-retry-policy.d.ts +2 -2
  71. package/dist/runtime/proxy-retry-policy.js +2 -2
  72. package/dist/runtime/proxy-telemetry.d.ts +1 -1
  73. package/dist/runtime/proxy-telemetry.js +16 -48
  74. package/dist/runtime/redis.d.ts +1 -1
  75. package/dist/runtime/redis.js +2 -4
  76. package/dist/runtime/request-options.d.ts +1 -1
  77. package/dist/runtime/state.d.ts +2 -2
  78. package/dist/runtime/state.js +3 -3
  79. package/dist/runtime/stealth.d.ts +2 -2
  80. package/dist/runtime/stealth.js +6 -6
  81. package/dist/runtime/stt.d.ts +1 -1
  82. package/dist/runtime/stt.js +11 -15
  83. package/dist/runtime/trace.d.ts +2 -2
  84. package/dist/runtime/trace.js +2 -4
  85. package/dist/runtime/waterfall.d.ts +1 -1
  86. package/dist/schema.d.ts +1 -1
  87. package/dist/schema.js +7 -15
  88. package/dist/serve.d.ts +1 -1
  89. package/dist/serve.js +1 -1
  90. package/dist/server/index.d.ts +7 -7
  91. package/dist/server/index.js +6 -6
  92. package/dist/server/self-test-redaction.d.ts +1 -1
  93. package/dist/server/self-test-redaction.js +1 -1
  94. package/dist/server/self-test.d.ts +3 -3
  95. package/dist/server/self-test.js +3 -3
  96. package/dist/server/serve.d.ts +3 -3
  97. package/dist/server/serve.js +36 -49
  98. package/dist/server/types.js +2 -7
  99. package/dist/stealth/profiles.d.ts +1 -1
  100. package/dist/stealth/profiles.js +5 -14
  101. package/dist/stream.d.ts +1 -1
  102. package/dist/testing/index.d.ts +2 -2
  103. package/dist/testing/index.js +2 -2
  104. package/dist/testing/run.d.ts +1 -1
  105. package/dist/testing/run.js +7 -14
  106. package/dist/types.d.ts +57 -0
  107. package/dist/user-input.d.ts +30 -0
  108. package/dist/user-input.js +66 -0
  109. package/package.json +6 -4
  110. package/src/auth-turn/index.ts +1 -1
  111. package/src/auth.ts +28 -86
  112. package/src/ceremonies/index.ts +24 -78
  113. package/src/config/loader.ts +58 -176
  114. package/src/contract-serialization.ts +5 -11
  115. package/src/contract-types.ts +2 -2
  116. package/src/contract.ts +12 -28
  117. package/src/define.ts +174 -495
  118. package/src/dev.ts +4 -9
  119. package/src/errors.ts +4 -11
  120. package/src/i18n/catalog.ts +10 -32
  121. package/src/i18n/index.ts +2 -2
  122. package/src/i18n/keys.ts +5 -11
  123. package/src/index.ts +44 -42
  124. package/src/lint.ts +89 -159
  125. package/src/provider.ts +12 -11
  126. package/src/public-schema-field-lint.ts +7 -33
  127. package/src/recipes/gov-api.ts +2 -5
  128. package/src/runtime/auth-flow.ts +5 -7
  129. package/src/runtime/browser.ts +64 -187
  130. package/src/runtime/cache.ts +20 -67
  131. package/src/runtime/choice.ts +55 -129
  132. package/src/runtime/credential.ts +2 -2
  133. package/src/runtime/env.ts +1 -1
  134. package/src/runtime/executor.ts +9 -19
  135. package/src/runtime/http.ts +7 -7
  136. package/src/runtime/insights.ts +15 -53
  137. package/src/runtime/instrumentation.ts +19 -58
  138. package/src/runtime/keyring.ts +7 -19
  139. package/src/runtime/namespace.ts +2 -7
  140. package/src/runtime/otlp.ts +12 -23
  141. package/src/runtime/perf.ts +1 -1
  142. package/src/runtime/provider.ts +4 -9
  143. package/src/runtime/proxy-errors.ts +17 -38
  144. package/src/runtime/proxy-retry-policy.ts +3 -3
  145. package/src/runtime/proxy-telemetry.ts +23 -72
  146. package/src/runtime/redis.ts +4 -12
  147. package/src/runtime/request-options.ts +4 -13
  148. package/src/runtime/state.ts +30 -109
  149. package/src/runtime/stealth.ts +8 -8
  150. package/src/runtime/stt.ts +38 -94
  151. package/src/runtime/trace.ts +14 -44
  152. package/src/runtime/waterfall.ts +5 -18
  153. package/src/schema.ts +23 -84
  154. package/src/serve.ts +1 -1
  155. package/src/server/index.ts +7 -7
  156. package/src/server/self-test-redaction.ts +2 -2
  157. package/src/server/self-test.ts +5 -5
  158. package/src/server/serve.ts +87 -242
  159. package/src/server/types.ts +6 -19
  160. package/src/stealth/profiles.ts +10 -26
  161. package/src/stream.ts +8 -19
  162. package/src/testing/index.ts +2 -2
  163. package/src/testing/run.ts +19 -67
  164. package/src/types.ts +58 -0
  165. package/src/user-input.ts +118 -0
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // Guards the published artifact against the nodenext type-resolution regression
4
+ // shipped in 2.2.0-beta.1/beta.2: emitted dist/*.d.ts (and dist/*.js) carried
5
+ // EXTENSIONLESS relative imports, so external provider repos compiling with
6
+ // moduleResolution nodenext could not resolve them. With skipLibCheck the
7
+ // errors were masked and types silently degraded — re-exported ProviderError
8
+ // lost its inherited Error members (name/message) and re-exported zod stopped
9
+ // inferring .refine() callback params (~17 false type errors in
10
+ // apifuse-provider-tabelog, see its PR #16).
11
+ //
12
+ // Three layers, all against the packed tarball (what consumers actually get):
13
+ // 1. @arethetypeswrong/cli — resolution/module-kind audit of every exports
14
+ // subpath, including internal resolution of relative specifiers in d.ts.
15
+ // 2. A fixture consumer compiled with real tsc under moduleResolution
16
+ // nodenext and skipLibCheck:false, asserting ProviderError keeps
17
+ // inherited Error members and zod re-exports keep .refine() inference.
18
+ // 3. A Node (not bun) ESM runtime import — node does not tolerate
19
+ // extensionless relative specifiers, bun does.
20
+ //
21
+ // A deliberate negative control proves the fixture compiler actually fails on
22
+ // type errors, so this check cannot rot into a false-positive green.
23
+
24
+ import { execFileSync, spawnSync } from "node:child_process";
25
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
26
+ import { tmpdir } from "node:os";
27
+ import { join, resolve } from "node:path";
28
+ import { z } from "zod";
29
+
30
+ const PACK_RESULT_SCHEMA = z.array(
31
+ z.object({
32
+ filename: z.string(),
33
+ }),
34
+ );
35
+
36
+ const KEEP_TEMP = process.env.APIFUSE__PACK_TYPES__KEEP_TEMP === "1";
37
+ const sdkRoot = process.cwd();
38
+
39
+ const tempRoot = mkdtempSync(join(tmpdir(), "apifuse-provider-sdk-pack-types-"));
40
+ const packDir = join(tempRoot, "pack");
41
+ const consumerDir = join(tempRoot, "consumer");
42
+
43
+ try {
44
+ mkdirSync(packDir, { recursive: true });
45
+ mkdirSync(consumerDir, { recursive: true });
46
+
47
+ const packed = packSdk(packDir);
48
+ const tarballPath = resolve(packDir, packed.filename);
49
+
50
+ runAreTheTypesWrong(tarballPath);
51
+ setUpFixtureConsumer(consumerDir, tarballPath);
52
+ run("bun", ["install"], consumerDir);
53
+ compileFixtureConsumer(consumerDir);
54
+ assertNegativeControlFails(consumerDir);
55
+ runNodeRuntimeImport(consumerDir);
56
+
57
+ console.log(`Packed artifact types OK under nodenext: ${tarballPath}`);
58
+ } finally {
59
+ if (KEEP_TEMP) {
60
+ console.log(`Keeping pack-types temp directory: ${tempRoot}`);
61
+ } else {
62
+ rmSync(tempRoot, { recursive: true, force: true });
63
+ }
64
+ }
65
+
66
+ function runAreTheTypesWrong(tarballPath: string): void {
67
+ // esm-only profile: this package publishes ESM only, so CJS-consumer
68
+ // resolution failures are expected and out of scope for this guard.
69
+ run(
70
+ "bun",
71
+ [join(sdkRoot, "node_modules", ".bin", "attw"), tarballPath, "--profile", "esm-only"],
72
+ sdkRoot,
73
+ );
74
+ }
75
+
76
+ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
77
+ writeFileSync(
78
+ join(consumerDir, "package.json"),
79
+ `${JSON.stringify(
80
+ {
81
+ private: true,
82
+ type: "module",
83
+ dependencies: {
84
+ "@apifuse/provider-sdk": `file:${tarballPath}`,
85
+ },
86
+ devDependencies: {
87
+ // Pinned to TS 5.x independently of the repo's own TypeScript 7
88
+ // toolchain: this must reflect what external provider polyrepos
89
+ // (e.g. apifuse-provider-tabelog) run in their CI.
90
+ typescript: "^5.9.3",
91
+ // skipLibCheck:false type-checks transitive deps too (ioredis
92
+ // references Buffer), so the fixture needs node types like any
93
+ // real provider repo.
94
+ "@types/node": "^25.9.3",
95
+ },
96
+ },
97
+ null,
98
+ 2,
99
+ )}\n`,
100
+ );
101
+
102
+ writeFileSync(
103
+ join(consumerDir, "tsconfig.json"),
104
+ `${JSON.stringify(
105
+ {
106
+ compilerOptions: {
107
+ target: "ES2022",
108
+ module: "nodenext",
109
+ moduleResolution: "nodenext",
110
+ strict: true,
111
+ // skipLibCheck must stay off: it is exactly what masked the
112
+ // extensionless-d.ts regression downstream.
113
+ skipLibCheck: false,
114
+ noEmit: true,
115
+ },
116
+ include: ["consumer.ts"],
117
+ },
118
+ null,
119
+ 2,
120
+ )}\n`,
121
+ );
122
+
123
+ writeFileSync(
124
+ join(consumerDir, "consumer.ts"),
125
+ [
126
+ 'import { ProviderError, SessionExpiredError, z } from "@apifuse/provider-sdk";',
127
+ 'import { defineCredentialsAuth } from "@apifuse/provider-sdk/provider";',
128
+ 'import { extractProviderContract } from "@apifuse/provider-sdk/contract";',
129
+ 'import { AUTH_TURN_SCHEMA } from "@apifuse/provider-sdk/auth-turn";',
130
+ 'import { serve } from "@apifuse/provider-sdk/server";',
131
+ 'import { runStandardTests } from "@apifuse/provider-sdk/testing";',
132
+ "",
133
+ "// ProviderError must keep its inherited Error members under nodenext.",
134
+ "// When dist d.ts imports fail to resolve, the class type degrades and",
135
+ "// name/message disappear (the tabelog false-error mode).",
136
+ 'const providerError = new ProviderError("boom");',
137
+ "const inheritedName: string = providerError.name;",
138
+ "const inheritedMessage: string = providerError.message;",
139
+ "const inheritedStack: string | undefined = providerError.stack;",
140
+ "const isError: Error = providerError;",
141
+ "const sessionExpired: ProviderError = new SessionExpiredError();",
142
+ "",
143
+ "// Re-exported zod must keep .refine() callback parameter inference.",
144
+ "const refined = z.object({ shopId: z.string() }).refine((value) => value.shopId.length > 0);",
145
+ 'const refinedString = z.string().refine((value) => value.startsWith("tabelog:"));',
146
+ "",
147
+ "export const witnesses = {",
148
+ " inheritedName,",
149
+ " inheritedMessage,",
150
+ " inheritedStack,",
151
+ " isError,",
152
+ " sessionExpired,",
153
+ " refined,",
154
+ " refinedString,",
155
+ " defineCredentialsAuth,",
156
+ " extractProviderContract,",
157
+ " AUTH_TURN_SCHEMA,",
158
+ " serve,",
159
+ " runStandardTests,",
160
+ "};",
161
+ "",
162
+ ].join("\n"),
163
+ );
164
+
165
+ writeFileSync(
166
+ join(consumerDir, "negative-control.ts"),
167
+ [
168
+ 'import { ProviderError } from "@apifuse/provider-sdk";',
169
+ "",
170
+ "// Intentionally wrong: name is a string. If this file compiles, the",
171
+ "// fixture consumer is not actually type-checking and the guard is void.",
172
+ 'export const mustNotCompile: number = new ProviderError("boom").name;',
173
+ "",
174
+ ].join("\n"),
175
+ );
176
+ }
177
+
178
+ function compileFixtureConsumer(consumerDir: string): void {
179
+ run(
180
+ "bun",
181
+ [join(consumerDir, "node_modules", ".bin", "tsc"), "-p", "tsconfig.json"],
182
+ consumerDir,
183
+ );
184
+ }
185
+
186
+ function assertNegativeControlFails(consumerDir: string): void {
187
+ const result = spawnSync(
188
+ "bun",
189
+ [
190
+ join(consumerDir, "node_modules", ".bin", "tsc"),
191
+ "--target",
192
+ "ES2022",
193
+ "--module",
194
+ "nodenext",
195
+ "--moduleResolution",
196
+ "nodenext",
197
+ "--strict",
198
+ "--noEmit",
199
+ "negative-control.ts",
200
+ ],
201
+ { cwd: consumerDir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
202
+ );
203
+ if (result.status === 0) {
204
+ throw new Error(
205
+ "Negative control compiled cleanly: the fixture consumer is not detecting type errors, so the nodenext guard proves nothing.",
206
+ );
207
+ }
208
+ if (!`${result.stdout}\n${result.stderr}`.includes("TS2322")) {
209
+ throw new Error(
210
+ `Negative control failed for an unexpected reason (wanted TS2322):\n${result.stdout}\n${result.stderr}`,
211
+ );
212
+ }
213
+ }
214
+
215
+ function runNodeRuntimeImport(consumerDir: string): void {
216
+ // Node, not bun: bun resolves extensionless relative specifiers in
217
+ // published dist/*.js, node correctly refuses them (ERR_MODULE_NOT_FOUND).
218
+ run(
219
+ "node",
220
+ [
221
+ "--input-type=module",
222
+ "-e",
223
+ [
224
+ 'const sdk = await import("@apifuse/provider-sdk");',
225
+ 'const error = new sdk.ProviderError("boom");',
226
+ 'if (error.name !== "ProviderError" || error.message !== "boom" || !(error instanceof Error)) {',
227
+ ' throw new Error("ProviderError runtime shape is wrong: " + JSON.stringify({ name: error.name, message: error.message }));',
228
+ "}",
229
+ 'console.log("node ESM runtime import OK");',
230
+ ].join("\n"),
231
+ ],
232
+ consumerDir,
233
+ );
234
+ }
235
+
236
+ function packSdk(destination: string): { filename: string } {
237
+ const raw = execFileSync("npm", ["pack", "--json", "--pack-destination", destination], {
238
+ cwd: sdkRoot,
239
+ encoding: "utf8",
240
+ stdio: ["ignore", "pipe", "inherit"],
241
+ });
242
+ const parsed = PACK_RESULT_SCHEMA.parse(JSON.parse(raw));
243
+ const first = parsed[0];
244
+ if (!first) {
245
+ throw new Error("npm pack --json returned no package metadata.");
246
+ }
247
+ return first;
248
+ }
249
+
250
+ function run(command: string, args: string[], cwd: string): void {
251
+ const result = spawnSync(command, args, {
252
+ cwd,
253
+ env: process.env,
254
+ stdio: "inherit",
255
+ });
256
+
257
+ if (result.error) {
258
+ throw result.error;
259
+ }
260
+
261
+ if (result.status !== 0) {
262
+ throw new Error(
263
+ `Command failed (${[command, ...args].join(" ")}) in ${cwd} with exit code ${result.status}`,
264
+ );
265
+ }
266
+ }
@@ -24,19 +24,12 @@ import {
24
24
  type StealthClient,
25
25
  type StealthResponse,
26
26
  wrapWithInstrumentation,
27
- } from "../src";
28
- import {
29
- computeStats,
30
- groupSpansByName,
31
- type PerfStats,
32
- } from "../src/runtime/perf";
33
- import { createMemoryProviderRuntimeState } from "../src/runtime/state";
34
- import {
35
- createTraceContext,
36
- resolveTraceContextOptions,
37
- } from "../src/runtime/trace";
38
- import { renderWaterfall } from "../src/runtime/waterfall";
39
- import type { BrowserClient } from "../src/types";
27
+ } from "../src/index.js";
28
+ import { computeStats, groupSpansByName, type PerfStats } from "../src/runtime/perf.js";
29
+ import { createMemoryProviderRuntimeState } from "../src/runtime/state.js";
30
+ import { createTraceContext, resolveTraceContextOptions } from "../src/runtime/trace.js";
31
+ import { renderWaterfall } from "../src/runtime/waterfall.js";
32
+ import type { BrowserClient } from "../src/types.js";
40
33
 
41
34
  type CliArgs = {
42
35
  compareProxy: boolean;
@@ -112,11 +105,7 @@ export async function main() {
112
105
  const inputSchema = getOperationSchema(provider, operation, "input");
113
106
  const outputSchema = getOperationSchema(provider, operation, "output");
114
107
  const fixtureReplay = await loadFixtureReplay(providerDirectory);
115
- const inputTemplate = resolveInputTemplate(
116
- provider,
117
- inputSchema,
118
- args.params,
119
- );
108
+ const inputTemplate = resolveInputTemplate(provider, inputSchema, args.params);
120
109
 
121
110
  const directSuite = await runProfileSuite({
122
111
  args,
@@ -168,9 +157,7 @@ export async function main() {
168
157
  label: `${providerId}/${args.operation}`,
169
158
  proxyEnabled: false,
170
159
  stats: directSuite.stats,
171
- mode: directSuite.runs.some((run) => run.mode === "fixture")
172
- ? "fixture"
173
- : "live",
160
+ mode: directSuite.runs.some((run) => run.mode === "fixture") ? "fixture" : "live",
174
161
  });
175
162
  console.log(`Flamegraph: ${flamePath}`);
176
163
  }
@@ -294,27 +281,18 @@ function parseArgs(argv: string[]): CliArgs {
294
281
  }
295
282
 
296
283
  if (arg.startsWith("--warmup=")) {
297
- warmup = parseNonNegativeInteger(
298
- arg.slice("--warmup=".length),
299
- "--warmup",
300
- );
284
+ warmup = parseNonNegativeInteger(arg.slice("--warmup=".length), "--warmup");
301
285
  continue;
302
286
  }
303
287
 
304
288
  if (arg === "--concurrency" || arg === "-c") {
305
- concurrency = parsePositiveInteger(
306
- requireArgValue(argv, index, arg),
307
- arg,
308
- );
289
+ concurrency = parsePositiveInteger(requireArgValue(argv, index, arg), arg);
309
290
  index += 1;
310
291
  continue;
311
292
  }
312
293
 
313
294
  if (arg.startsWith("--concurrency=")) {
314
- concurrency = parsePositiveInteger(
315
- arg.slice("--concurrency=".length),
316
- "--concurrency",
317
- );
295
+ concurrency = parsePositiveInteger(arg.slice("--concurrency=".length), "--concurrency");
318
296
  continue;
319
297
  }
320
298
 
@@ -354,11 +332,7 @@ function parseArgs(argv: string[]): CliArgs {
354
332
  };
355
333
  }
356
334
 
357
- function requireArgValue(
358
- argv: string[],
359
- index: number,
360
- option: string,
361
- ): string {
335
+ function requireArgValue(argv: string[], index: number, option: string): string {
362
336
  const value = argv[index + 1];
363
337
  if (!value) {
364
338
  throw new Error(`Missing value for ${option}`);
@@ -396,9 +370,7 @@ function resolveProviderEntry(providerDirectory: string): string {
396
370
  throw new Error(`Provider entry not found in ${providerDirectory}`);
397
371
  }
398
372
 
399
- async function loadProvider(
400
- providerEntry: string,
401
- ): Promise<ProviderDefinition> {
373
+ async function loadProvider(providerEntry: string): Promise<ProviderDefinition> {
402
374
  const mod = (await import(pathToFileURL(providerEntry).href)) as {
403
375
  default?: ProviderDefinition;
404
376
  };
@@ -469,9 +441,7 @@ function resolveInputTemplate(
469
441
  }
470
442
  }
471
443
 
472
- async function loadFixtureReplay(
473
- providerDirectory: string,
474
- ): Promise<FixtureReplay | null> {
444
+ async function loadFixtureReplay(providerDirectory: string): Promise<FixtureReplay | null> {
475
445
  const fixturePath = resolve(providerDirectory, "__fixtures__", "raw.json");
476
446
  if (!existsSync(fixturePath)) {
477
447
  return null;
@@ -521,15 +491,8 @@ async function runProfileSuite(options: {
521
491
  }
522
492
 
523
493
  const runs: RunResult[] = [];
524
- for (
525
- let index = 0;
526
- index < options.args.runs;
527
- index += options.args.concurrency
528
- ) {
529
- const batchSize = Math.min(
530
- options.args.concurrency,
531
- options.args.runs - index,
532
- );
494
+ for (let index = 0; index < options.args.runs; index += options.args.concurrency) {
495
+ const batchSize = Math.min(options.args.concurrency, options.args.runs - index);
533
496
  const batch = Array.from({ length: batchSize }, () =>
534
497
  profileRun({
535
498
  config: options.config,
@@ -552,8 +515,7 @@ async function runProfileSuite(options: {
552
515
  const breakdown = [...groupedSpans.entries()]
553
516
  .filter(([name]) => name !== rootSpanName)
554
517
  .map(([name, durations]) => {
555
- const avgMs =
556
- durations.reduce((sum, value) => sum + value, 0) / durations.length;
518
+ const avgMs = durations.reduce((sum, value) => sum + value, 0) / durations.length;
557
519
  const percent = stats.avg > 0 ? (avgMs / stats.avg) * 100 : 0;
558
520
  return { avgMs, name, percent };
559
521
  })
@@ -611,9 +573,7 @@ async function executeProfileRun(options: {
611
573
  proxyEnabled: boolean;
612
574
  }): Promise<RunResult> {
613
575
  const _operation = getOperation(options.provider, options.operationName);
614
- const traceContext = createTraceContext(
615
- resolveTraceContextOptions(options.config.trace),
616
- );
576
+ const traceContext = createTraceContext(resolveTraceContextOptions(options.config.trace));
617
577
  const baseContext = createBaseContext({
618
578
  config: options.config,
619
579
  provider: options.provider,
@@ -638,9 +598,7 @@ async function executeProfileRun(options: {
638
598
  normalizedInput,
639
599
  );
640
600
 
641
- return ctx.trace.span("transformResponse", async () =>
642
- options.outputSchema.parse(result),
643
- );
601
+ return ctx.trace.span("transformResponse", async () => options.outputSchema.parse(result));
644
602
  });
645
603
  const durationMs = performance.now() - startedAt;
646
604
  const spans = ctx.trace.getSpans();
@@ -790,36 +748,24 @@ function createBrowserStub(): BrowserClient {
790
748
  engine: "playwright-stealth",
791
749
  async close() {},
792
750
  async newPage() {
793
- throw new ProviderError(
794
- "Browser runtime is not supported by apifuse perf yet.",
795
- {
796
- code: "BROWSER_RUNTIME_UNSUPPORTED",
797
- },
798
- );
751
+ throw new ProviderError("Browser runtime is not supported by apifuse perf yet.", {
752
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
753
+ });
799
754
  },
800
755
  async rawPage() {
801
- throw new ProviderError(
802
- "Browser runtime is not supported by apifuse perf yet.",
803
- {
804
- code: "BROWSER_RUNTIME_UNSUPPORTED",
805
- },
806
- );
756
+ throw new ProviderError("Browser runtime is not supported by apifuse perf yet.", {
757
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
758
+ });
807
759
  },
808
760
  async withIsolatedContext() {
809
- throw new ProviderError(
810
- "Browser runtime is not supported by apifuse perf yet.",
811
- {
812
- code: "BROWSER_RUNTIME_UNSUPPORTED",
813
- },
814
- );
761
+ throw new ProviderError("Browser runtime is not supported by apifuse perf yet.", {
762
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
763
+ });
815
764
  },
816
765
  async solveChallenge() {
817
- throw new ProviderError(
818
- "Browser runtime is not supported by apifuse perf yet.",
819
- {
820
- code: "BROWSER_RUNTIME_UNSUPPORTED",
821
- },
822
- );
766
+ throw new ProviderError("Browser runtime is not supported by apifuse perf yet.", {
767
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
768
+ });
823
769
  },
824
770
  };
825
771
  }
@@ -833,9 +779,7 @@ function buildInsights(
833
779
  const allSpans = runs.flatMap((run) => run.spans);
834
780
  const stealthSpans = allSpans.filter((span) => span.name === "stealth.fetch");
835
781
  const dnsSpans = allSpans.filter((span) => span.name === "dns");
836
- const transform = breakdown.find(
837
- (entry) => entry.name === "transformResponse",
838
- );
782
+ const transform = breakdown.find((entry) => entry.name === "transformResponse");
839
783
  const responseSizes = allSpans
840
784
  .map((span) => span.attributes.response_size)
841
785
  .filter((value): value is number => typeof value === "number");
@@ -844,9 +788,7 @@ function buildInsights(
844
788
  .filter((value): value is boolean => typeof value === "boolean");
845
789
 
846
790
  if (reuseFlags.length > 0) {
847
- const reusePercent = Math.round(
848
- (reuseFlags.filter(Boolean).length / reuseFlags.length) * 100,
849
- );
791
+ const reusePercent = Math.round((reuseFlags.filter(Boolean).length / reuseFlags.length) * 100);
850
792
  insights.push(
851
793
  reusePercent >= 80
852
794
  ? `✓ Stealth connection reuse: ${reusePercent}% (good)`
@@ -863,24 +805,16 @@ function buildInsights(
863
805
  }
864
806
 
865
807
  if (dnsSpans.length > 0) {
866
- const dnsAvg =
867
- dnsSpans.reduce((sum, span) => sum + span.duration_ms, 0) /
868
- dnsSpans.length;
808
+ const dnsAvg = dnsSpans.reduce((sum, span) => sum + span.duration_ms, 0) / dnsSpans.length;
869
809
  if (dnsAvg >= 5) {
870
- insights.push(
871
- `⚠ DNS resolution: ${formatDuration(dnsAvg)} avg — consider DNS caching`,
872
- );
810
+ insights.push(`⚠ DNS resolution: ${formatDuration(dnsAvg)} avg — consider DNS caching`);
873
811
  }
874
812
  }
875
813
 
876
814
  if (responseSizes.length > 0) {
877
- const avgSize =
878
- responseSizes.reduce((sum, value) => sum + value, 0) /
879
- responseSizes.length;
815
+ const avgSize = responseSizes.reduce((sum, value) => sum + value, 0) / responseSizes.length;
880
816
  if (avgSize >= 4096) {
881
- insights.push(
882
- `⚠ Response size: ${formatBytes(avgSize)} avg — check gzip (Accept-Encoding)`,
883
- );
817
+ insights.push(`⚠ Response size: ${formatBytes(avgSize)} avg — check gzip (Accept-Encoding)`);
884
818
  }
885
819
  }
886
820
 
@@ -925,13 +859,10 @@ function renderReport(options: {
925
859
 
926
860
  if (options.proxySuite) {
927
861
  const delta = options.proxySuite.stats.p50 - options.suite.stats.p50;
928
- const deltaPercent =
929
- options.suite.stats.p50 > 0 ? (delta / options.suite.stats.p50) * 100 : 0;
862
+ const deltaPercent = options.suite.stats.p50 > 0 ? (delta / options.suite.stats.p50) * 100 : 0;
930
863
  lines.push("│");
931
864
  lines.push("│ Proxy Comparison (--compare-proxy):");
932
- lines.push(
933
- `│ proxy: off → p50: ${formatDuration(options.suite.stats.p50)}`,
934
- );
865
+ lines.push(`│ proxy: off → p50: ${formatDuration(options.suite.stats.p50)}`);
935
866
  lines.push(
936
867
  `│ proxy: on → p50: ${formatDuration(options.proxySuite.stats.p50)} (${delta >= 0 ? "+" : ""}${Math.round(deltaPercent)}%)`,
937
868
  );
@@ -979,9 +910,7 @@ function formatBytes(value: number): string {
979
910
  }
980
911
 
981
912
  function selectRepresentativeRun(runs: RunResult[]): RunResult {
982
- const sorted = [...runs].sort(
983
- (left, right) => left.durationMs - right.durationMs,
984
- );
913
+ const sorted = [...runs].sort((left, right) => left.durationMs - right.durationMs);
985
914
  const middle = sorted[Math.floor(sorted.length / 2)];
986
915
  if (middle) {
987
916
  return middle;
@@ -1065,17 +994,10 @@ function buildFlamegraphSvg(
1065
994
 
1066
995
  const rects = flattened
1067
996
  .map((node) => {
1068
- const x =
1069
- 12 +
1070
- ((node.span.startedAt - root.startedAt) / totalDuration) * (width - 24);
1071
- const rectWidth = Math.max(
1072
- 1,
1073
- (node.span.duration_ms / totalDuration) * (width - 24),
1074
- );
997
+ const x = 12 + ((node.span.startedAt - root.startedAt) / totalDuration) * (width - 24);
998
+ const rectWidth = Math.max(1, (node.span.duration_ms / totalDuration) * (width - 24));
1075
999
  const y = chartTop + node.depth * rowHeight;
1076
- const label = escapeXml(
1077
- `${node.span.name} (${formatDuration(node.span.duration_ms)})`,
1078
- );
1000
+ const label = escapeXml(`${node.span.name} (${formatDuration(node.span.duration_ms)})`);
1079
1001
  const title = escapeXml(
1080
1002
  `${node.span.name}\n${formatDuration(node.span.duration_ms)}\nstatus: ${node.span.status}`,
1081
1003
  );
@@ -1094,9 +1016,7 @@ function buildFlamegraphSvg(
1094
1016
  }
1095
1017
 
1096
1018
  function buildFlameTree(spans: Span[]): FlameNode[] {
1097
- const sorted = [...spans].sort(
1098
- (left, right) => left.startedAt - right.startedAt,
1099
- );
1019
+ const sorted = [...spans].sort((left, right) => left.startedAt - right.startedAt);
1100
1020
  const nodeMap = new Map<string, FlameNode>();
1101
1021
 
1102
1022
  for (const span of sorted) {
@@ -1139,9 +1059,7 @@ function findRootSpan(spans: Span[]): Span {
1139
1059
  return root;
1140
1060
  }
1141
1061
 
1142
- const sorted = [...spans].sort(
1143
- (left, right) => left.startedAt - right.startedAt,
1144
- );
1062
+ const sorted = [...spans].sort((left, right) => left.startedAt - right.startedAt);
1145
1063
  const first = sorted[0];
1146
1064
  if (!first) {
1147
1065
  throw new Error("Expected at least one span");