@apifuse/provider-sdk 2.2.0-beta.61 → 2.2.0-beta.63

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 (258) hide show
  1. package/AUTHORING.md +268 -9
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +1 -0
  4. package/bin/apifuse-check.ts +84 -2
  5. package/bin/apifuse-dev.ts +11 -31
  6. package/bin/apifuse-migrate-deployment.ts +200 -0
  7. package/bin/apifuse-pack-check.ts +11 -4
  8. package/bin/apifuse-pack-types.ts +191 -4
  9. package/bin/apifuse-record.ts +44 -32
  10. package/bin/apifuse-test.ts +3 -1
  11. package/dist/cli/commands.d.ts +1 -1
  12. package/dist/cli/commands.js +11 -0
  13. package/dist/cli/deployment-intent.d.ts +178 -0
  14. package/dist/cli/deployment-intent.js +277 -0
  15. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  16. package/dist/cli/migrate-deployment-intent.js +1552 -0
  17. package/dist/cli/migrate-provider-shape.d.ts +5 -1
  18. package/dist/cli/migrate-provider-shape.js +4 -2
  19. package/dist/cli/resolver-runtime.d.ts +21 -0
  20. package/dist/cli/resolver-runtime.js +31 -0
  21. package/dist/define.d.ts +3 -0
  22. package/dist/define.js +5 -0
  23. package/dist/dev.d.ts +3 -1
  24. package/dist/dev.js +6 -1
  25. package/dist/engine.d.ts +35 -1
  26. package/dist/engine.js +21 -2
  27. package/dist/error-resolution.d.ts +1 -0
  28. package/dist/error-resolution.js +54 -0
  29. package/dist/errors.d.ts +39 -1
  30. package/dist/errors.js +50 -0
  31. package/dist/handle-meta.d.ts +18 -2
  32. package/dist/handle-meta.js +30 -2
  33. package/dist/handle.d.ts +33 -12
  34. package/dist/handle.js +69 -24
  35. package/dist/i18n/error-messages.d.ts +86 -0
  36. package/dist/i18n/error-messages.js +192 -0
  37. package/dist/i18n/index.d.ts +1 -0
  38. package/dist/i18n/index.js +1 -0
  39. package/dist/i18n/keys.d.ts +12 -0
  40. package/dist/i18n/keys.js +76 -5
  41. package/dist/index.d.ts +10 -2
  42. package/dist/index.js +9 -1
  43. package/dist/lint.d.ts +10 -0
  44. package/dist/lint.js +440 -57
  45. package/dist/provider.d.ts +7 -5
  46. package/dist/provider.js +4 -2
  47. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  48. package/dist/runtime/browser-telemetry-binding.js +83 -0
  49. package/dist/runtime/browser-telemetry.d.ts +76 -0
  50. package/dist/runtime/browser-telemetry.js +165 -0
  51. package/dist/runtime/browser.d.ts +3 -0
  52. package/dist/runtime/browser.js +208 -47
  53. package/dist/runtime/cache-telemetry.d.ts +71 -0
  54. package/dist/runtime/cache-telemetry.js +177 -0
  55. package/dist/runtime/cache.js +67 -7
  56. package/dist/runtime/capability-response.d.ts +3 -0
  57. package/dist/runtime/capability-response.js +56 -0
  58. package/dist/runtime/capability-telemetry.d.ts +46 -0
  59. package/dist/runtime/capability-telemetry.js +68 -0
  60. package/dist/runtime/diagnostic-env.d.ts +3 -0
  61. package/dist/runtime/diagnostic-env.js +10 -1
  62. package/dist/runtime/engine-mode.d.ts +74 -0
  63. package/dist/runtime/engine-mode.js +152 -0
  64. package/dist/runtime/handle.js +15 -10
  65. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  66. package/dist/runtime/http-telemetry-binding.js +152 -0
  67. package/dist/runtime/http-telemetry-guard.d.ts +16 -0
  68. package/dist/runtime/http-telemetry-guard.js +202 -0
  69. package/dist/runtime/http-telemetry.d.ts +104 -0
  70. package/dist/runtime/http-telemetry.js +190 -0
  71. package/dist/runtime/http.d.ts +31 -0
  72. package/dist/runtime/http.js +356 -174
  73. package/dist/runtime/instrumentation.js +92 -13
  74. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  75. package/dist/runtime/ocr-telemetry.js +192 -0
  76. package/dist/runtime/ocr.js +30 -8
  77. package/dist/runtime/request-telemetry.d.ts +2 -0
  78. package/dist/runtime/request-telemetry.js +2 -1
  79. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  80. package/dist/runtime/resolver-runtime-options.js +33 -0
  81. package/dist/runtime/resolver-shared.d.ts +3 -2
  82. package/dist/runtime/resolver-shared.js +4 -4
  83. package/dist/runtime/resolver-telemetry.d.ts +1 -0
  84. package/dist/runtime/resolver-telemetry.js +1 -1
  85. package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
  86. package/dist/runtime/resolver-vendors/browser.js +46 -0
  87. package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
  88. package/dist/runtime/resolver-vendors/hosts.js +35 -0
  89. package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
  90. package/dist/runtime/resolver-vendors/types.d.ts +5 -4
  91. package/dist/runtime/resolver.d.ts +4 -1
  92. package/dist/runtime/resolver.js +46 -13
  93. package/dist/runtime/state-telemetry.d.ts +79 -0
  94. package/dist/runtime/state-telemetry.js +194 -0
  95. package/dist/runtime/state.d.ts +3 -0
  96. package/dist/runtime/state.js +26 -7
  97. package/dist/runtime/stealth-cookies.d.ts +5 -1
  98. package/dist/runtime/stealth-cookies.js +21 -1
  99. package/dist/runtime/stealth-owned-headers.d.ts +26 -0
  100. package/dist/runtime/stealth-owned-headers.js +37 -0
  101. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  102. package/dist/runtime/stealth-telemetry.js +179 -0
  103. package/dist/runtime/stealth.d.ts +4 -1
  104. package/dist/runtime/stealth.js +228 -85
  105. package/dist/runtime/stt-telemetry.d.ts +77 -0
  106. package/dist/runtime/stt-telemetry.js +176 -0
  107. package/dist/runtime/stt.js +15 -6
  108. package/dist/server/error-stack-frames.d.ts +35 -0
  109. package/dist/server/error-stack-frames.js +219 -0
  110. package/dist/server/index.d.ts +9 -0
  111. package/dist/server/index.js +5 -0
  112. package/dist/server/sdk-version.d.ts +3 -0
  113. package/dist/server/sdk-version.js +19 -0
  114. package/dist/server/self-test.js +1 -18
  115. package/dist/server/sensitive-values.d.ts +1 -1
  116. package/dist/server/sensitive-values.js +9 -0
  117. package/dist/server/serve-implementation.d.ts +53 -2
  118. package/dist/server/serve-implementation.js +369 -123
  119. package/dist/server/types.d.ts +1 -0
  120. package/dist/server/types.js +7 -0
  121. package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
  122. package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
  123. package/dist/stateful/stateful-provider-runtime-context.js +1 -0
  124. package/dist/types.d.ts +94 -1
  125. package/package.json +6 -1
  126. package/src/cli/commands.ts +14 -0
  127. package/src/cli/deployment-intent.ts +364 -0
  128. package/src/cli/migrate-deployment-intent.ts +1908 -0
  129. package/src/cli/migrate-provider-shape.ts +10 -4
  130. package/src/cli/resolver-runtime.ts +72 -0
  131. package/src/define.ts +13 -0
  132. package/src/dev.ts +9 -2
  133. package/src/engine.ts +38 -2
  134. package/src/error-resolution.ts +58 -0
  135. package/src/errors.ts +77 -1
  136. package/src/handle-meta.ts +39 -4
  137. package/src/handle.ts +121 -38
  138. package/src/i18n/error-messages.ts +255 -0
  139. package/src/i18n/index.ts +1 -0
  140. package/src/i18n/keys.ts +80 -6
  141. package/src/index.ts +91 -0
  142. package/src/lint.ts +552 -57
  143. package/src/provider.ts +20 -0
  144. package/src/runtime/browser-telemetry-binding.ts +92 -0
  145. package/src/runtime/browser-telemetry.ts +289 -0
  146. package/src/runtime/browser.ts +249 -65
  147. package/src/runtime/cache-telemetry.ts +247 -0
  148. package/src/runtime/cache.ts +68 -17
  149. package/src/runtime/capability-response.ts +53 -0
  150. package/src/runtime/capability-telemetry.ts +101 -0
  151. package/src/runtime/diagnostic-env.ts +11 -1
  152. package/src/runtime/engine-mode.ts +233 -0
  153. package/src/runtime/handle.ts +20 -10
  154. package/src/runtime/http-telemetry-binding.ts +162 -0
  155. package/src/runtime/http-telemetry-guard.ts +208 -0
  156. package/src/runtime/http-telemetry.ts +292 -0
  157. package/src/runtime/http.ts +457 -204
  158. package/src/runtime/instrumentation.ts +105 -16
  159. package/src/runtime/ocr-telemetry.ts +308 -0
  160. package/src/runtime/ocr.ts +109 -74
  161. package/src/runtime/request-telemetry.ts +2 -1
  162. package/src/runtime/resolver-runtime-options.ts +50 -0
  163. package/src/runtime/resolver-shared.ts +9 -4
  164. package/src/runtime/resolver-telemetry.ts +1 -1
  165. package/src/runtime/resolver-vendors/browser.ts +65 -7
  166. package/src/runtime/resolver-vendors/hosts.ts +43 -0
  167. package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
  168. package/src/runtime/resolver-vendors/types.ts +5 -4
  169. package/src/runtime/resolver.ts +64 -21
  170. package/src/runtime/state-telemetry.ts +298 -0
  171. package/src/runtime/state.ts +46 -45
  172. package/src/runtime/stealth-cookies.ts +24 -1
  173. package/src/runtime/stealth-owned-headers.ts +41 -0
  174. package/src/runtime/stealth-telemetry.ts +304 -0
  175. package/src/runtime/stealth.ts +355 -150
  176. package/src/runtime/stt-telemetry.ts +282 -0
  177. package/src/runtime/stt.ts +93 -71
  178. package/src/server/error-stack-frames.ts +214 -0
  179. package/src/server/index.ts +66 -0
  180. package/src/server/sdk-version.ts +22 -0
  181. package/src/server/self-test.ts +1 -21
  182. package/src/server/sensitive-values.ts +14 -0
  183. package/src/server/serve-implementation.ts +601 -157
  184. package/src/server/types.ts +7 -0
  185. package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
  186. package/src/stateful/stateful-provider-runtime-context.ts +4 -0
  187. package/src/types.ts +99 -1
  188. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
  189. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
  190. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
  191. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
  192. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
  193. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
  194. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
  195. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
  196. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
  197. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
  198. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
  199. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
  200. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
  201. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
  202. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
  203. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
  204. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
  205. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
  206. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
  207. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
  208. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
  209. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
  210. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
  211. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
  212. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
  213. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
  214. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
  215. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
  216. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
  217. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
  218. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
  219. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
  220. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
  221. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
  222. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
  223. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
  224. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
  225. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
  226. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
  227. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
  244. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
  245. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
  246. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
  247. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
  248. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
  249. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
  250. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
  251. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
  252. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
  253. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
  254. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
  255. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
  256. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
  257. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
  258. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
@@ -119,10 +119,7 @@ type RedisBackend = {
119
119
 
120
120
  const redisBackends = new Map<string, RedisBackend>();
121
121
 
122
- function getRedisBackend(
123
- redisUrl: string,
124
- injectedRedis?: ProviderRedisClient,
125
- ): RedisBackend {
122
+ function getRedisBackend(redisUrl: string, injectedRedis?: ProviderRedisClient): RedisBackend {
126
123
  const existing = redisBackends.get(redisUrl);
127
124
  if (existing) return existing;
128
125
  const redis =
@@ -287,20 +284,11 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
287
284
  ) {}
288
285
 
289
286
  private redisKey(key: string): string {
290
- return providerStateKey(
291
- this.providerId,
292
- this.namespaceName,
293
- this.scopeDiscriminator,
294
- key,
295
- );
287
+ return providerStateKey(this.providerId, this.namespaceName, this.scopeDiscriminator, key);
296
288
  }
297
289
 
298
290
  private statePrefix(): string {
299
- return providerStatePrefix(
300
- this.providerId,
301
- this.namespaceName,
302
- this.scopeDiscriminator,
303
- );
291
+ return providerStatePrefix(this.providerId, this.namespaceName, this.scopeDiscriminator);
304
292
  }
305
293
 
306
294
  private indexKey(): string {
@@ -308,11 +296,7 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
308
296
  // any state key (including "__index"), so a suffix inside the namespace
309
297
  // could turn the ZSET into a string and break every subsequent write.
310
298
  const namespaceIdentity = Buffer.from(
311
- providerStatePrefix(
312
- this.providerId,
313
- this.namespaceName,
314
- this.scopeDiscriminator,
315
- ),
299
+ providerStatePrefix(this.providerId, this.namespaceName, this.scopeDiscriminator),
316
300
  "utf8",
317
301
  ).toString("base64url");
318
302
  return `${REDIS_STATE_PREFIX}:index:${namespaceIdentity}`;
@@ -342,15 +326,17 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
342
326
  private enforceValueSize(value: unknown): void {
343
327
  const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
344
328
  if (bytes > this.options.maxValueBytes) {
345
- throw new UnsupportedProviderStateError(
329
+ throw stateViolationError(
346
330
  `Provider runtime state value exceeds maxValueBytes (${bytes} > ${this.options.maxValueBytes})`,
331
+ "size",
347
332
  );
348
333
  }
349
334
  }
350
335
 
351
336
  private quotaExceeded(): UnsupportedProviderStateError {
352
- return new UnsupportedProviderStateError(
337
+ return stateViolationError(
353
338
  `Provider runtime state namespace quota exceeded (${this.options.maxEntries + 1} > ${this.options.maxEntries})`,
339
+ "quota",
354
340
  );
355
341
  }
356
342
 
@@ -362,8 +348,9 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
362
348
  const ttlMs = parseStateDurationMs(ttl ?? this.options.defaultTtl);
363
349
  const maxTtlMs = parseStateDurationMs(this.options.maxTtl);
364
350
  if (ttlMs > maxTtlMs) {
365
- throw new UnsupportedProviderStateError(
351
+ throw stateViolationError(
366
352
  `Provider runtime state ttl exceeds maxTtl (${ttlMs} > ${maxTtlMs})`,
353
+ "ttl",
367
354
  );
368
355
  }
369
356
  const expiresAtMs = Date.now() + ttlMs;
@@ -376,7 +363,7 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
376
363
 
377
364
  async list<T>(options?: { limit?: number; prefix?: string }): Promise<StateValue<T>[]> {
378
365
  const requestedLimit = Math.max(0, options?.limit ?? this.options.maxEntries);
379
- if (requestedLimit === 0) return [];
366
+ if (requestedLimit === 0) return recordStateRedisNoop([]);
380
367
  const keys = await this.indexedKeys(this.options.maxEntries);
381
368
  if (keys.length === 0) return [];
382
369
  const values = await withRequiredRedis(() => this.backend.redis.mget(keys));
@@ -453,12 +440,7 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
453
440
  this.enforceValueSize(value);
454
441
  const createdAt = new Date().toISOString();
455
442
  const timing = this.writeTiming(options?.ttl);
456
- const envelope = redisEnvelope(
457
- value,
458
- expectedVersion + 1,
459
- createdAt,
460
- timing.expiresAt,
461
- );
443
+ const envelope = redisEnvelope(value, expectedVersion + 1, createdAt, timing.expiresAt);
462
444
  await requireRedisReady(this.backend.redis);
463
445
  const result = await withRequiredRedis(() =>
464
446
  this.backend.redis.eval(
@@ -556,9 +538,34 @@ export class UnsupportedProviderStateError extends ProviderError {
556
538
  constructor(message = "Provider runtime state is not available in this runtime") {
557
539
  super(message, { code: "PROVIDER_STATE_UNSUPPORTED" });
558
540
  this.name = "UnsupportedProviderStateError";
541
+ stateViolationKinds.set(this, "unsupported");
559
542
  }
560
543
  }
561
544
 
545
+ // Keep the exact no-command result out of band, without inspecting caller options twice.
546
+ const stateRedisNoopResults = new WeakSet<object>();
547
+ function recordStateRedisNoop<T extends object>(result: T): T {
548
+ stateRedisNoopResults.add(result);
549
+ return result;
550
+ }
551
+ export function isStateRedisNoop(result: unknown): boolean {
552
+ return result !== null && typeof result === "object" && stateRedisNoopResults.has(result);
553
+ }
554
+
555
+ type StateViolationKind = "quota" | "ttl" | "size" | "unsupported";
556
+ const stateViolationKinds = new WeakMap<object, StateViolationKind>();
557
+ function stateViolationError(
558
+ message: string,
559
+ kind: Exclude<StateViolationKind, "unsupported">,
560
+ ): UnsupportedProviderStateError {
561
+ const error = new UnsupportedProviderStateError(message);
562
+ stateViolationKinds.set(error, kind);
563
+ return error;
564
+ }
565
+ export function getStateViolation(error: unknown): StateViolationKind | undefined {
566
+ return error !== null && typeof error === "object" ? stateViolationKinds.get(error) : undefined;
567
+ }
568
+
562
569
  class UnsupportedProviderStateNamespace implements ProviderStateNamespace {
563
570
  async list<T>(_options?: { limit?: number; prefix?: string }): Promise<StateValue<T>[]> {
564
571
  throw new UnsupportedProviderStateError();
@@ -619,8 +626,9 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
619
626
  private enforceValueSize(value: unknown): void {
620
627
  const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
621
628
  if (bytes > this.options.maxValueBytes) {
622
- throw new UnsupportedProviderStateError(
629
+ throw stateViolationError(
623
630
  `Provider runtime state value exceeds maxValueBytes (${bytes} > ${this.options.maxValueBytes})`,
631
+ "size",
624
632
  );
625
633
  }
626
634
  }
@@ -630,13 +638,15 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
630
638
  const ttlMs = parseStateDurationMs(ttl ?? this.options.defaultTtl);
631
639
  const maxTtlMs = parseStateDurationMs(this.options.maxTtl);
632
640
  if (ttlMs > maxTtlMs) {
633
- throw new UnsupportedProviderStateError(
641
+ throw stateViolationError(
634
642
  `Provider runtime state ttl exceeds maxTtl (${ttlMs} > ${maxTtlMs})`,
643
+ "ttl",
635
644
  );
636
645
  }
637
646
  if (!this.values.has(key) && this.values.size >= this.options.maxEntries) {
638
- throw new UnsupportedProviderStateError(
647
+ throw stateViolationError(
639
648
  `Provider runtime state namespace quota exceeded (${this.options.maxEntries + 1} > ${this.options.maxEntries})`,
649
+ "quota",
640
650
  );
641
651
  }
642
652
  }
@@ -710,10 +720,7 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
710
720
  key,
711
721
  value,
712
722
  version: expectedVersion + 1,
713
- expiresAt: resolveMemoryStateExpiresAt(
714
- options?.ttl ?? this.options.defaultTtl,
715
- this.now(),
716
- ),
723
+ expiresAt: resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl, this.now()),
717
724
  createdAt: current?.createdAt ?? now,
718
725
  updatedAt: now,
719
726
  } satisfies StateValue<T>;
@@ -753,10 +760,7 @@ class MemoryProviderRuntimeState implements ProviderRuntimeState {
753
760
  ) {}
754
761
 
755
762
  forConnection(connectionId: string | undefined): ProviderRuntimeState {
756
- return new MemoryProviderRuntimeState(
757
- this.backend,
758
- connectionScopeDiscriminator(connectionId),
759
- );
763
+ return new MemoryProviderRuntimeState(this.backend, connectionScopeDiscriminator(connectionId));
760
764
  }
761
765
 
762
766
  namespace(name: string, options: StateNamespaceOptions): ProviderStateNamespace {
@@ -771,10 +775,7 @@ class MemoryProviderRuntimeState implements ProviderRuntimeState {
771
775
  }
772
776
  }
773
777
 
774
- function resolveMemoryStateExpiresAt(
775
- ttl: StateWriteOptions["ttl"],
776
- nowMs = Date.now(),
777
- ): string {
778
+ function resolveMemoryStateExpiresAt(ttl: StateWriteOptions["ttl"], nowMs = Date.now()): string {
778
779
  const match = /^(\d+)(ms|s|m|h|d)$/.exec(ttl ?? "1h");
779
780
  if (!match) return new Date(nowMs + 3_600_000).toISOString();
780
781
  const amount = Number(match[1]);
@@ -14,16 +14,32 @@ function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
14
14
 
15
15
  const LEGACY_COOKIE_ORIGIN = "https://legacy-cookie.invalid/";
16
16
 
17
+ /** Extract cookie values from Set-Cookie/header strings for request-scoped registration. */
18
+ export function cookieValuesFromHeaders(headers: string | readonly string[]): string[] {
19
+ const values: string[] = [];
20
+ for (const header of typeof headers === "string" ? [headers] : headers) {
21
+ const cookie = Cookie.parse(header);
22
+ if (cookie?.value) values.push(cookie.value);
23
+ }
24
+ return values;
25
+ }
26
+
17
27
  export class StealthCookieJar implements CookieJar, StealthSessionCookies {
18
28
  private cookies: ToughCookieJar;
19
29
  private readonly defaultUrl: string;
30
+ private readonly register: ((value: string) => void) | undefined;
20
31
 
21
- constructor(cookieStrings: readonly string[], defaultUrl = LEGACY_COOKIE_ORIGIN) {
32
+ constructor(
33
+ cookieStrings: readonly string[],
34
+ defaultUrl = LEGACY_COOKIE_ORIGIN,
35
+ register?: (value: string) => void,
36
+ ) {
22
37
  this.cookies = new ToughCookieJar(undefined, {
23
38
  allowSecureOnLocal: false,
24
39
  rejectPublicSuffixes: true,
25
40
  });
26
41
  this.defaultUrl = this.normalizeUrl(defaultUrl) ?? LEGACY_COOKIE_ORIGIN;
42
+ this.register = register;
27
43
  this.setFromCookieStrings(cookieStrings);
28
44
  }
29
45
 
@@ -31,6 +47,7 @@ export class StealthCookieJar implements CookieJar, StealthSessionCookies {
31
47
  const cookieUrl = this.normalizeUrl(url);
32
48
  if (!cookieUrl) return;
33
49
  for (const cookieString of cookieStrings) {
50
+ for (const value of cookieValuesFromHeaders(cookieString)) this.register?.(value);
34
51
  this.cookies.setCookieSync(cookieString, cookieUrl, { ignoreError: true });
35
52
  }
36
53
  }
@@ -73,6 +90,7 @@ export class StealthCookieJar implements CookieJar, StealthSessionCookies {
73
90
  this.clear();
74
91
  for (const [name, value] of Object.entries(cookies)) {
75
92
  if (!name) continue;
93
+ this.register?.(value);
76
94
  this.cookies.setCookieSync(new Cookie({ key: name, path: "/", value }), this.defaultUrl, {
77
95
  ignoreError: true,
78
96
  });
@@ -111,6 +129,10 @@ export class StealthCookieJar implements CookieJar, StealthSessionCookies {
111
129
  return undefined;
112
130
  }
113
131
 
132
+ names(url?: string): string[] {
133
+ return this.getUniqueCookies(url ?? this.defaultUrl).map((cookie) => cookie.key);
134
+ }
135
+
114
136
  private normalizeUrl(url: string): string | undefined {
115
137
  try {
116
138
  return new URL(url).toString();
@@ -124,6 +146,7 @@ export class StealthCookieJar implements CookieJar, StealthSessionCookies {
124
146
  if (!cookieUrl) return [];
125
147
  const names = new Set<string>();
126
148
  return this.cookies.getCookiesSync(cookieUrl).filter((cookie) => {
149
+ if (cookie.value) this.register?.(cookie.value);
127
150
  if (names.has(cookie.key)) return false;
128
151
  names.add(cookie.key);
129
152
  return true;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Header names the stealth transport owns.
3
+ *
4
+ * `ctx.stealth` composes these from the Chrome emulation and the request class,
5
+ * so a caller value is rejected at request time with `SDKError` code
6
+ * `STEALTH_HEADER_OVERRIDE_UNSUPPORTED` (`assertCallerHeadersSupported` in
7
+ * stealth.ts). The `browser-version-literal` lint reports a subset of these
8
+ * names — the `sec-fetch-*` and `sec-ch-ua*` families — in files that use
9
+ * `ctx.stealth` (`isReportedOwnedHeaderName` in lint.ts). Both read this
10
+ * module, so the lint cannot claim a rejection the runtime does not perform;
11
+ * it reports deliberately less than the runtime rejects (host, connection,
12
+ * accept-encoding and a non-literal user-agent stay silent — see that
13
+ * predicate for why), so a green `apifuse check` is not proof that no owned
14
+ * header is set.
15
+ *
16
+ * Leaf module by design: no imports, so the lazily loaded CLI lint path does
17
+ * not pull the stealth runtime in.
18
+ */
19
+ export const SDK_OWNED_EXACT_CHROME_HEADERS: ReadonlySet<string> = new Set([
20
+ "host",
21
+ "connection",
22
+ "user-agent",
23
+ "sec-ch-ua",
24
+ "sec-ch-ua-mobile",
25
+ "sec-ch-ua-platform",
26
+ "accept-encoding",
27
+ ]);
28
+
29
+ export const SDK_OWNED_CHROME_HEADER_PREFIX = "sec-fetch-";
30
+
31
+ /**
32
+ * True when `ctx.stealth` rejects a caller value for `name` (case-insensitive):
33
+ * an exact member of {@link SDK_OWNED_EXACT_CHROME_HEADERS} or any
34
+ * `sec-fetch-*` name.
35
+ */
36
+ export function isStealthOwnedHeaderName(name: string): boolean {
37
+ const lower = name.toLowerCase();
38
+ return (
39
+ SDK_OWNED_EXACT_CHROME_HEADERS.has(lower) || lower.startsWith(SDK_OWNED_CHROME_HEADER_PREFIX)
40
+ );
41
+ }
@@ -0,0 +1,304 @@
1
+ import { Buffer } from "node:buffer";
2
+
3
+ import type {
4
+ StealthBrowser,
5
+ StealthChallengeClassification,
6
+ StealthOS,
7
+ StealthProfileDescriptor,
8
+ } from "../types.js";
9
+ import { type ClosedEnum, closedEnum, type TelemetryContributor } from "./request-telemetry.js";
10
+
11
+ export type StealthTelemetryErrorCode =
12
+ | "transport_network_error"
13
+ | "transport_timeout"
14
+ | "transport_cancelled"
15
+ | "upstream_http_error"
16
+ | "response_too_large"
17
+ | "proxy_connect_failed"
18
+ | "PROXY_POOL_STALE"
19
+ | "PROXY_EDGE_AUTH_REJECTED"
20
+ | "PROXY_AUTH_IP_DENIED"
21
+ | "PROXY_EDGE_TLS_REJECTED"
22
+ | "PROXY_REQUIRED"
23
+ | "other";
24
+
25
+ export type StealthTelemetryRequestClass = "navigation" | "script_navigation" | "xhr" | "post";
26
+
27
+ export type StealthTelemetryAttemptKind = "request" | "resolver" | "proxy_diagnostic";
28
+
29
+ export type StealthTelemetrySbsdOutcome =
30
+ | StealthChallengeClassification["outcome"]
31
+ | "detected"
32
+ | "refetch_clear";
33
+
34
+ export type StealthTelemetryDiagnostics = {
35
+ readonly name?: string;
36
+ readonly message?: string;
37
+ readonly code?: string;
38
+ };
39
+
40
+ export type StealthTelemetryAttemptEvent = {
41
+ readonly ms: number;
42
+ readonly status?: number;
43
+ readonly errorCode?: StealthTelemetryErrorCode;
44
+ readonly diagnostics?: StealthTelemetryDiagnostics;
45
+ readonly profileId: StealthProfileDescriptor;
46
+ readonly proxyUsed: boolean;
47
+ readonly requestClass: StealthTelemetryRequestClass;
48
+ readonly kind?: StealthTelemetryAttemptKind;
49
+ };
50
+
51
+ export type StealthTelemetrySbsdEvent = {
52
+ readonly detected: boolean;
53
+ readonly outcome?: StealthTelemetrySbsdOutcome;
54
+ };
55
+
56
+ export interface StealthTelemetrySink {
57
+ recordAttempt(event: StealthTelemetryAttemptEvent): void;
58
+ recordPoolRefresh(): void;
59
+ recordRedirectHop(): void;
60
+ recordSbsd(event?: StealthTelemetrySbsdEvent | StealthTelemetrySbsdOutcome): void;
61
+ recordSafeRefetch(): void;
62
+ }
63
+
64
+ export type StealthTelemetryAttemptSample = {
65
+ n: number;
66
+ ms: number;
67
+ status?: number;
68
+ e?: StealthTelemetryErrorCode;
69
+ kind?: StealthTelemetryAttemptKind;
70
+ diagnostics?: StealthTelemetryDiagnostics;
71
+ };
72
+
73
+ export type StealthTelemetryLogPayload = {
74
+ attempts: number;
75
+ poolRefreshes: number;
76
+ redirectHops: number;
77
+ profileId?: StealthProfileDescriptor;
78
+ proxyUsed: boolean;
79
+ requestClass?: StealthTelemetryRequestClass;
80
+ sbsdDetected?: boolean;
81
+ sbsdOutcome?: StealthTelemetrySbsdOutcome;
82
+ safeRefetch: number;
83
+ lastStatus?: number;
84
+ ms: number;
85
+ attemptSamples?: StealthTelemetryAttemptSample[];
86
+ attemptSamplesDropped?: number;
87
+ };
88
+
89
+ export type StealthTelemetryHeaderPayload = {
90
+ attempts: number;
91
+ poolRefreshes: number;
92
+ redirectHops: number;
93
+ profileId?: {
94
+ browser: ClosedEnum<StealthBrowser>;
95
+ os: ClosedEnum<StealthOS>;
96
+ };
97
+ proxyUsed: boolean;
98
+ requestClass?: ClosedEnum<StealthTelemetryRequestClass>;
99
+ sbsdDetected?: boolean;
100
+ sbsdOutcome?: ClosedEnum<StealthTelemetrySbsdOutcome>;
101
+ safeRefetch: number;
102
+ lastStatus?: number;
103
+ ms: number;
104
+ attemptSamples?: {
105
+ n: number;
106
+ ms: number;
107
+ status?: number;
108
+ e?: ClosedEnum<StealthTelemetryErrorCode>;
109
+ kind?: ClosedEnum<StealthTelemetryAttemptKind>;
110
+ }[];
111
+ };
112
+
113
+ const MAX_ATTEMPT_SAMPLES = 24;
114
+ const MAX_DIAGNOSTIC_TEXT = 300;
115
+
116
+ function boundedInteger(value: number): number {
117
+ if (!Number.isFinite(value)) return Number.MAX_SAFE_INTEGER;
118
+ return Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, Math.floor(value)));
119
+ }
120
+
121
+ function saturatingAdd(a: number, b: number): number {
122
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return Number.MAX_SAFE_INTEGER;
123
+ return Math.min(Number.MAX_SAFE_INTEGER, boundedInteger(a) + boundedInteger(b));
124
+ }
125
+
126
+ function boundedText(value: string, redact?: (text: string) => string): string {
127
+ let result = value;
128
+ try {
129
+ result = redact?.(value) ?? value;
130
+ } catch {
131
+ result = "[REDACTION_FAILED]";
132
+ }
133
+ const length = Math.min(result.length, MAX_DIAGNOSTIC_TEXT);
134
+ const units = Buffer.alloc(length * 2);
135
+ for (let index = 0; index < length; index += 1) {
136
+ units.writeUInt16LE(result.charCodeAt(index), index * 2);
137
+ }
138
+ return units.toString("utf16le");
139
+ }
140
+
141
+ function cloneDiagnostics(
142
+ diagnostics: StealthTelemetryDiagnostics | undefined,
143
+ redact: ((text: string) => string) | undefined,
144
+ ): StealthTelemetryDiagnostics | undefined {
145
+ if (!diagnostics) return undefined;
146
+ const result: StealthTelemetryDiagnostics = {
147
+ ...(diagnostics.name === undefined ? {} : { name: boundedText(diagnostics.name, redact) }),
148
+ ...(diagnostics.message === undefined
149
+ ? {}
150
+ : { message: boundedText(diagnostics.message, redact) }),
151
+ ...(diagnostics.code === undefined ? {} : { code: boundedText(diagnostics.code, redact) }),
152
+ };
153
+ return Object.keys(result).length > 0 ? result : undefined;
154
+ }
155
+
156
+ function cloneProfile(profile: StealthProfileDescriptor): StealthProfileDescriptor {
157
+ return { browser: profile.browser, os: profile.os } as StealthProfileDescriptor;
158
+ }
159
+
160
+ export class StealthTelemetryCollector
161
+ implements
162
+ StealthTelemetrySink,
163
+ TelemetryContributor<StealthTelemetryLogPayload, StealthTelemetryHeaderPayload>
164
+ {
165
+ readonly key = "stealth" as const;
166
+ readonly #redact: ((text: string) => string) | undefined;
167
+ #attempts = 0;
168
+ #poolRefreshes = 0;
169
+ #redirectHops = 0;
170
+ #profileId: StealthProfileDescriptor | undefined;
171
+ #proxyUsed = false;
172
+ #requestClass: StealthTelemetryRequestClass | undefined;
173
+ #sbsdDetected: boolean | undefined;
174
+ #sbsdOutcome: StealthTelemetrySbsdOutcome | undefined;
175
+ #safeRefetch = 0;
176
+ #lastStatus: number | undefined;
177
+ #ms = 0;
178
+ #attemptSamples: StealthTelemetryAttemptSample[] = [];
179
+ #attemptSamplesDropped = 0;
180
+
181
+ constructor(options: { redact?: (text: string) => string } = {}) {
182
+ this.#redact = options.redact;
183
+ }
184
+
185
+ recordAttempt(event: StealthTelemetryAttemptEvent): void {
186
+ const ms = boundedInteger(event.ms);
187
+ this.#attempts = saturatingAdd(this.#attempts, 1);
188
+ this.#ms = saturatingAdd(this.#ms, ms);
189
+ this.#profileId = cloneProfile(event.profileId);
190
+ this.#proxyUsed ||= event.proxyUsed;
191
+ this.#requestClass = event.requestClass;
192
+ if (event.status !== undefined) this.#lastStatus = boundedInteger(event.status);
193
+ if (this.#attemptSamples.length >= MAX_ATTEMPT_SAMPLES) {
194
+ this.#attemptSamplesDropped = saturatingAdd(this.#attemptSamplesDropped, 1);
195
+ return;
196
+ }
197
+ const sample: StealthTelemetryAttemptSample = {
198
+ n: this.#attempts,
199
+ ms,
200
+ ...(event.status === undefined ? {} : { status: boundedInteger(event.status) }),
201
+ ...(event.errorCode === undefined ? {} : { e: event.errorCode }),
202
+ ...(event.kind === undefined ? {} : { kind: event.kind }),
203
+ ...(event.diagnostics
204
+ ? { diagnostics: cloneDiagnostics(event.diagnostics, this.#redact) }
205
+ : {}),
206
+ };
207
+ if (sample.diagnostics === undefined) delete sample.diagnostics;
208
+ this.#attemptSamples.push(sample);
209
+ }
210
+
211
+ recordPoolRefresh(): void {
212
+ this.#poolRefreshes = saturatingAdd(this.#poolRefreshes, 1);
213
+ }
214
+
215
+ recordRedirectHop(): void {
216
+ this.#redirectHops = saturatingAdd(this.#redirectHops, 1);
217
+ }
218
+
219
+ recordSbsd(event?: StealthTelemetrySbsdEvent | StealthTelemetrySbsdOutcome): void {
220
+ if (typeof event === "string") {
221
+ this.#sbsdDetected = true;
222
+ this.#sbsdOutcome = event;
223
+ return;
224
+ }
225
+ if (!event) return;
226
+ this.#sbsdDetected = (this.#sbsdDetected ?? false) || event.detected;
227
+ if (event.outcome !== undefined) this.#sbsdOutcome = event.outcome;
228
+ }
229
+
230
+ recordSafeRefetch(): void {
231
+ this.#safeRefetch = saturatingAdd(this.#safeRefetch, 1);
232
+ }
233
+
234
+ toLogPayload(): StealthTelemetryLogPayload | undefined {
235
+ if (
236
+ this.#attempts === 0 &&
237
+ this.#poolRefreshes === 0 &&
238
+ this.#redirectHops === 0 &&
239
+ this.#safeRefetch === 0 &&
240
+ this.#sbsdDetected === undefined
241
+ ) {
242
+ return undefined;
243
+ }
244
+ return {
245
+ attempts: this.#attempts,
246
+ poolRefreshes: this.#poolRefreshes,
247
+ redirectHops: this.#redirectHops,
248
+ ...(this.#profileId ? { profileId: cloneProfile(this.#profileId) } : {}),
249
+ proxyUsed: this.#proxyUsed,
250
+ ...(this.#requestClass ? { requestClass: this.#requestClass } : {}),
251
+ ...(this.#sbsdDetected === undefined ? {} : { sbsdDetected: this.#sbsdDetected }),
252
+ ...(this.#sbsdOutcome ? { sbsdOutcome: this.#sbsdOutcome } : {}),
253
+ safeRefetch: this.#safeRefetch,
254
+ ...(this.#lastStatus === undefined ? {} : { lastStatus: this.#lastStatus }),
255
+ ms: this.#ms,
256
+ ...(this.#attemptSamples.length > 0
257
+ ? {
258
+ attemptSamples: this.#attemptSamples.map((sample) => ({
259
+ ...sample,
260
+ ...(sample.diagnostics
261
+ ? {
262
+ diagnostics: { ...sample.diagnostics },
263
+ }
264
+ : {}),
265
+ })),
266
+ }
267
+ : {}),
268
+ ...(this.#attemptSamplesDropped > 0
269
+ ? { attemptSamplesDropped: this.#attemptSamplesDropped }
270
+ : {}),
271
+ };
272
+ }
273
+
274
+ toHeaderPayload(log: StealthTelemetryLogPayload): StealthTelemetryHeaderPayload {
275
+ const attemptSamples = log.attemptSamples?.map((sample) => ({
276
+ n: sample.n,
277
+ ms: sample.ms,
278
+ ...(sample.status === undefined ? {} : { status: sample.status }),
279
+ ...(sample.e ? { e: closedEnum(sample.e) } : {}),
280
+ ...(sample.kind ? { kind: closedEnum(sample.kind) } : {}),
281
+ }));
282
+ return {
283
+ attempts: log.attempts,
284
+ poolRefreshes: log.poolRefreshes,
285
+ redirectHops: log.redirectHops,
286
+ ...(log.profileId
287
+ ? {
288
+ profileId: {
289
+ browser: closedEnum(log.profileId.browser),
290
+ os: closedEnum(log.profileId.os),
291
+ },
292
+ }
293
+ : {}),
294
+ proxyUsed: log.proxyUsed,
295
+ ...(log.requestClass ? { requestClass: closedEnum(log.requestClass) } : {}),
296
+ ...(log.sbsdDetected === undefined ? {} : { sbsdDetected: log.sbsdDetected }),
297
+ ...(log.sbsdOutcome ? { sbsdOutcome: closedEnum(log.sbsdOutcome) } : {}),
298
+ safeRefetch: log.safeRefetch,
299
+ ...(log.lastStatus === undefined ? {} : { lastStatus: log.lastStatus }),
300
+ ms: log.ms,
301
+ ...(attemptSamples && attemptSamples.length > 0 ? { attemptSamples } : {}),
302
+ };
303
+ }
304
+ }