@apifuse/provider-sdk 2.2.0-beta.54 → 2.2.0-beta.56

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 (47) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/apifuse-pack-types.ts +121 -0
  3. package/dist/cli/migrate-operation-declaration.d.ts +5 -1
  4. package/dist/cli/migrate-operation-declaration.js +645 -46
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.js +1 -0
  7. package/dist/runtime/proxy-telemetry.d.ts +53 -2
  8. package/dist/runtime/proxy-telemetry.js +106 -65
  9. package/dist/runtime/request-telemetry.d.ts +70 -0
  10. package/dist/runtime/request-telemetry.js +228 -0
  11. package/dist/server/index.d.ts +3 -1
  12. package/dist/server/index.js +1 -0
  13. package/dist/server/serve-implementation.js +58 -20
  14. package/package.json +1 -1
  15. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +3 -0
  16. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +3 -0
  17. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +2 -0
  18. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +3 -0
  19. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +6 -0
  20. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +1 -0
  21. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +1 -0
  22. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +3 -0
  23. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +7 -0
  24. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +19 -0
  25. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +6 -0
  26. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +3 -0
  27. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +8 -0
  28. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +5 -0
  29. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +4 -0
  30. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +3 -0
  31. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +3 -0
  32. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +10 -0
  33. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +1 -0
  34. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +1 -0
  35. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +2 -0
  36. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +3 -0
  37. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +3 -0
  38. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +11 -0
  39. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +7 -0
  40. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +18 -0
  41. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +13 -0
  42. package/src/cli/migrate-operation-declaration.ts +844 -49
  43. package/src/index.ts +13 -0
  44. package/src/runtime/proxy-telemetry.ts +160 -74
  45. package/src/runtime/request-telemetry.ts +376 -0
  46. package/src/server/index.ts +14 -0
  47. package/src/server/serve-implementation.ts +93 -21
package/src/index.ts CHANGED
@@ -181,10 +181,23 @@ export {
181
181
  export type { PrevalidateResult } from "./runtime/prevalidate.js";
182
182
  export { getProviderBaseUrl } from "./runtime/provider.js";
183
183
  export type {
184
+ ProxyTelemetryHeaderPayload,
185
+ ProxyHash,
184
186
  ProxyTelemetryLogPayload,
185
187
  ProxyTelemetryResolvedPayload,
186
188
  ProxyTelemetryUnresolvedPayload,
187
189
  } from "./runtime/proxy-telemetry.js";
190
+ export {
191
+ RequestTelemetry,
192
+ closedEnum,
193
+ type ClosedEnum,
194
+ type GatewayIngestible,
195
+ type RequestTelemetryLogPayload,
196
+ type SpanIndex,
197
+ type TelemetryContributor,
198
+ type TelemetryKey,
199
+ type TenantNeutral,
200
+ } from "./runtime/request-telemetry.js";
188
201
  export {
189
202
  APIFUSE__CDP_POOL__URL,
190
203
  APIFUSE__RESOLVER__2CAPTCHA__API_KEY,
@@ -9,8 +9,19 @@ import type {
9
9
  ProxyVendorName,
10
10
  SmartproxyAllocatorBodyClass,
11
11
  } from "../config/loader.js";
12
+ import {
13
+ closedEnum,
14
+ RequestTelemetry,
15
+ type ClosedEnum,
16
+ type TelemetryContributor,
17
+ } from "./request-telemetry.js";
18
+ import { createTraceContext } from "./trace.js";
12
19
 
13
- export const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
20
+ export { PROVIDER_TELEMETRY_HEADER } from "./request-telemetry.js";
21
+
22
+ declare const PROXY_HASH: unique symbol;
23
+ /** Bounded hexadecimal host hash used in proxy attempt samples. */
24
+ export type ProxyHash = ClosedEnum<string> & { readonly [PROXY_HASH]: true };
14
25
 
15
26
  export type ProxyTelemetryResolvedPayload = {
16
27
  kind: "resolved";
@@ -94,14 +105,51 @@ export type ProxyTelemetryLogPayload =
94
105
  | ProxyTelemetryResolvedPayload
95
106
  | ProxyTelemetryUnresolvedPayload;
96
107
 
97
- type ProviderTelemetryHeader = {
98
- v: 1;
99
- proxy?: ProxyTelemetryLogPayload;
108
+ /** Concrete gateway-safe projection of the unchanged proxy sibling. */
109
+ export type ProxyTelemetryHeaderPayload = {
110
+ kind: ClosedEnum<"resolved" | "unresolved">;
111
+ provider?: ClosedEnum<ProxyVendorName>;
112
+ userAgentSource?: ClosedEnum<ProxyUserAgentSource>;
113
+ protocol?: ClosedEnum<ProxyProtocol>;
114
+ cacheStatus?: ClosedEnum<ProxyCacheStatus>;
115
+ cacheHit?: boolean;
116
+ resolutionMs?: number;
117
+ allocatorMs?: number;
118
+ allocatorStatus?: number;
119
+ allocatorBodyClass?: ClosedEnum<SmartproxyAllocatorBodyClass>;
120
+ allocatorAttempts?: number;
121
+ lockWaitMs?: number;
122
+ redisReadMs?: number;
123
+ redisWriteMs?: number;
124
+ poolAgeMs?: number;
125
+ poolExpiresInMs?: number;
126
+ attempts?: number;
127
+ refreshes?: number;
128
+ attemptSamples?: {
129
+ n: number;
130
+ a: number;
131
+ i?: number;
132
+ h?: ProxyHash;
133
+ o: ClosedEnum<ProxyAttemptTelemetryEvent["outcome"]>;
134
+ c?: ClosedEnum<string>;
135
+ s?: number;
136
+ d?: number;
137
+ }[];
138
+ vendors?: ClosedEnum<ProxyVendorName>[];
139
+ failovers?: {
140
+ v: ClosedEnum<ProxyVendorName>;
141
+ nx?: ClosedEnum<ProxyVendorName>;
142
+ p: ClosedEnum<ProxyVendorFailoverTelemetryEvent["phase"]>;
143
+ r: ClosedEnum<ProxyVendorFailoverTelemetryEvent["reason"]>;
144
+ a?: number;
145
+ }[];
100
146
  };
101
147
 
102
- const MAX_HEADER_BYTES = 4_096;
103
148
  const MAX_PROXY_ATTEMPT_SAMPLES = 24;
104
149
  const MAX_PROXY_FAILOVER_SAMPLES = 12;
150
+ const MAX_RETAINED_PROXY_RESOLUTIONS = 64;
151
+ const PROXY_HEADER_TOKEN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,63}$/;
152
+ const PROXY_HEADER_HASH = /^[0-9a-f]{1,16}$/;
105
153
 
106
154
  const CACHE_STATUS_SEVERITY: Record<ProxyCacheStatus, number> = {
107
155
  disabled: 0,
@@ -128,19 +176,48 @@ function worseStatus(left: ProxyCacheStatus, right: ProxyCacheStatus): ProxyCach
128
176
  return CACHE_STATUS_SEVERITY[right] > CACHE_STATUS_SEVERITY[left] ? right : left;
129
177
  }
130
178
 
131
- function encodeBase64Url(value: string): string {
132
- return Buffer.from(value, "utf8").toString("base64url");
179
+ function aggregateResolution(
180
+ acc: ProxyResolutionTelemetryEvent,
181
+ event: ProxyResolutionTelemetryEvent,
182
+ ): ProxyResolutionTelemetryEvent {
183
+ return {
184
+ provider: event.provider,
185
+ userAgentSource: event.userAgentSource ?? acc.userAgentSource,
186
+ cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
187
+ cacheHit: acc.cacheHit && event.cacheHit,
188
+ resolutionMs: acc.resolutionMs + event.resolutionMs,
189
+ allocatorMs: sumOptional(acc.allocatorMs, event.allocatorMs),
190
+ allocatorStatus: event.allocatorStatus ?? acc.allocatorStatus,
191
+ allocatorBodyClass: event.allocatorBodyClass ?? acc.allocatorBodyClass,
192
+ allocatorAttempts: sumOptional(acc.allocatorAttempts, event.allocatorAttempts),
193
+ lockWaitMs: sumOptional(acc.lockWaitMs, event.lockWaitMs),
194
+ redisReadMs: sumOptional(acc.redisReadMs, event.redisReadMs),
195
+ redisWriteMs: sumOptional(acc.redisWriteMs, event.redisWriteMs),
196
+ poolAgeMs: maxOptional(acc.poolAgeMs, event.poolAgeMs),
197
+ poolExpiresInMs: maxOptional(acc.poolExpiresInMs, event.poolExpiresInMs),
198
+ attempts: acc.attempts + event.attempts,
199
+ refreshes: sumOptional(acc.refreshes, event.refreshes),
200
+ };
133
201
  }
134
202
 
135
- export class ProxyTelemetryCollector implements ProxyTelemetrySink {
203
+ export class ProxyTelemetryCollector
204
+ implements
205
+ ProxyTelemetrySink,
206
+ TelemetryContributor<ProxyTelemetryLogPayload, ProxyTelemetryHeaderPayload>
207
+ {
208
+ readonly key = "proxy" as const;
136
209
  #events: ProxyResolutionTelemetryEvent[] = [];
210
+ #resolutionCount = 0;
211
+ #aggregateResolution: ProxyResolutionTelemetryEvent | undefined;
212
+ #lastSuccessfulResolution: ProxyResolutionTelemetryEvent | undefined;
213
+ #vendors: ProxyVendorName[] = [];
137
214
  #attempts: ProxyAttemptTelemetryEvent[] = [];
138
215
  #failovers: ProxyVendorFailoverTelemetryEvent[] = [];
139
216
  #unresolvedVendors: ProxyVendorName[] = [];
140
217
 
141
218
  recordProxyResolution(event: ProxyResolutionTelemetryEvent): void {
142
219
  const outcome = event.outcome === "error" ? "error" : "ok";
143
- this.#events.push({
220
+ const normalized: ProxyResolutionTelemetryEvent = {
144
221
  provider: event.provider,
145
222
  outcome,
146
223
  ...(event.userAgentSource ? { userAgentSource: event.userAgentSource } : {}),
@@ -174,12 +251,29 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
174
251
  attempts: Math.max(1, Math.floor(event.attempts || 1)),
175
252
  refreshes:
176
253
  event.refreshes === undefined ? undefined : Math.max(0, Math.floor(event.refreshes)),
177
- });
254
+ };
255
+ this.#resolutionCount += 1;
256
+ if (this.#events.length < MAX_RETAINED_PROXY_RESOLUTIONS) this.#events.push(normalized);
257
+ this.#aggregateResolution = this.#aggregateResolution
258
+ ? aggregateResolution(this.#aggregateResolution, normalized)
259
+ : normalized;
260
+ if (outcome === "ok") this.#lastSuccessfulResolution = normalized;
261
+ if (!this.#vendors.includes(event.provider)) this.#vendors.push(event.provider);
178
262
  if (outcome === "error" && !this.#unresolvedVendors.includes(event.provider)) {
179
263
  this.#unresolvedVendors.push(event.provider);
180
264
  }
181
265
  }
182
266
 
267
+ /** Number of raw resolution events retained for bounded-history verification. */
268
+ get retainedResolutionEventCount(): number {
269
+ return this.#events.length;
270
+ }
271
+
272
+ /** Total resolutions included in the incremental aggregate. */
273
+ get resolutionEventCount(): number {
274
+ return this.#resolutionCount;
275
+ }
276
+
183
277
  recordProxyVendorFailover(event: ProxyVendorFailoverTelemetryEvent): void {
184
278
  if (this.#failovers.length >= MAX_PROXY_FAILOVER_SAMPLES) return;
185
279
  this.#failovers.push({
@@ -213,8 +307,7 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
213
307
  }
214
308
 
215
309
  toLogPayload(): ProxyTelemetryLogPayload | undefined {
216
- const [first, ...rest] = this.#events;
217
- const okEvents = this.#events.filter((event) => event.outcome !== "error");
310
+ const aggregate = this.#aggregateResolution;
218
311
  const attemptSamples = this.#attempts.map((attempt, index) => ({
219
312
  n: index + 1,
220
313
  a: attempt.attempt,
@@ -233,33 +326,8 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
233
326
  ...(failover.attempt === undefined ? {} : { a: failover.attempt }),
234
327
  }));
235
328
 
236
- if (okEvents.length === 0) {
237
- if (!first && failovers.length === 0) return undefined;
238
- const failureEvents = this.#events.filter((event) => event.outcome === "error");
239
- const [firstFailure, ...remainingFailures] = failureEvents;
240
- const aggregate = firstFailure
241
- ? remainingFailures.reduce<ProxyResolutionTelemetryEvent>(
242
- (acc, event) => ({
243
- provider: event.provider,
244
- outcome: "error",
245
- cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
246
- cacheHit: acc.cacheHit && event.cacheHit,
247
- resolutionMs: acc.resolutionMs + event.resolutionMs,
248
- allocatorMs: sumOptional(acc.allocatorMs, event.allocatorMs),
249
- allocatorStatus: event.allocatorStatus ?? acc.allocatorStatus,
250
- allocatorBodyClass: event.allocatorBodyClass ?? acc.allocatorBodyClass,
251
- allocatorAttempts: sumOptional(acc.allocatorAttempts, event.allocatorAttempts),
252
- lockWaitMs: sumOptional(acc.lockWaitMs, event.lockWaitMs),
253
- redisReadMs: sumOptional(acc.redisReadMs, event.redisReadMs),
254
- redisWriteMs: sumOptional(acc.redisWriteMs, event.redisWriteMs),
255
- poolAgeMs: maxOptional(acc.poolAgeMs, event.poolAgeMs),
256
- poolExpiresInMs: maxOptional(acc.poolExpiresInMs, event.poolExpiresInMs),
257
- attempts: acc.attempts + event.attempts,
258
- refreshes: sumOptional(acc.refreshes, event.refreshes),
259
- }),
260
- firstFailure,
261
- )
262
- : undefined;
329
+ if (!this.#lastSuccessfulResolution) {
330
+ if (!aggregate && failovers.length === 0) return undefined;
263
331
  return {
264
332
  kind: "unresolved",
265
333
  vendors: [...this.#unresolvedVendors],
@@ -301,33 +369,8 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
301
369
  }
302
370
 
303
371
  // The serving vendor/protocol is the last successful resolution.
304
- const serving = okEvents[okEvents.length - 1] ?? first;
305
- const vendors: ProxyVendorName[] = [];
306
- for (const event of this.#events) {
307
- if (!vendors.includes(event.provider)) vendors.push(event.provider);
308
- }
309
-
310
- const aggregate = rest.reduce<ProxyResolutionTelemetryEvent>(
311
- (acc, event) => ({
312
- provider: event.provider,
313
- userAgentSource: event.userAgentSource ?? acc.userAgentSource,
314
- cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
315
- cacheHit: acc.cacheHit && event.cacheHit,
316
- resolutionMs: acc.resolutionMs + event.resolutionMs,
317
- allocatorMs: sumOptional(acc.allocatorMs, event.allocatorMs),
318
- allocatorStatus: event.allocatorStatus ?? acc.allocatorStatus,
319
- allocatorBodyClass: event.allocatorBodyClass ?? acc.allocatorBodyClass,
320
- allocatorAttempts: sumOptional(acc.allocatorAttempts, event.allocatorAttempts),
321
- lockWaitMs: sumOptional(acc.lockWaitMs, event.lockWaitMs),
322
- redisReadMs: sumOptional(acc.redisReadMs, event.redisReadMs),
323
- redisWriteMs: sumOptional(acc.redisWriteMs, event.redisWriteMs),
324
- poolAgeMs: maxOptional(acc.poolAgeMs, event.poolAgeMs),
325
- poolExpiresInMs: maxOptional(acc.poolExpiresInMs, event.poolExpiresInMs),
326
- attempts: acc.attempts + event.attempts,
327
- refreshes: sumOptional(acc.refreshes, event.refreshes),
328
- }),
329
- first,
330
- );
372
+ if (!aggregate) return undefined;
373
+ const serving = this.#lastSuccessfulResolution;
331
374
  return {
332
375
  kind: "resolved",
333
376
  provider: serving.provider,
@@ -356,18 +399,61 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
356
399
  attempts: aggregate.attempts,
357
400
  ...(aggregate.refreshes !== undefined ? { refreshes: aggregate.refreshes } : {}),
358
401
  ...(attemptSamples.length > 0 ? { attemptSamples } : {}),
359
- ...(vendors.length > 1 ? { vendors } : {}),
402
+ ...(this.#vendors.length > 1 ? { vendors: [...this.#vendors] } : {}),
360
403
  ...(failovers.length > 0 ? { failovers } : {}),
361
404
  };
362
405
  }
363
406
 
364
- toHeaderValue(): string | undefined {
365
- const proxy = this.toLogPayload();
366
- if (!proxy) return undefined;
367
- const payload: ProviderTelemetryHeader = { v: 1, proxy };
407
+ toHeaderPayload(log: ProxyTelemetryLogPayload): ProxyTelemetryHeaderPayload {
408
+ const attemptSamples = log.attemptSamples?.map((sample) => ({
409
+ n: sample.n,
410
+ a: sample.a,
411
+ ...(sample.i === undefined ? {} : { i: sample.i }),
412
+ ...(sample.h && PROXY_HEADER_HASH.test(sample.h)
413
+ ? { h: closedEnum(sample.h) as ProxyHash }
414
+ : {}),
415
+ o: closedEnum(sample.o),
416
+ ...(sample.c && PROXY_HEADER_TOKEN.test(sample.c) ? { c: closedEnum(sample.c) } : {}),
417
+ ...(sample.s === undefined ? {} : { s: sample.s }),
418
+ ...(sample.d === undefined ? {} : { d: sample.d }),
419
+ }));
420
+ const failovers = log.failovers?.map((failover) => ({
421
+ ...failover,
422
+ v: closedEnum(failover.v),
423
+ ...(failover.nx ? { nx: closedEnum(failover.nx) } : {}),
424
+ p: closedEnum(failover.p),
425
+ r: closedEnum(failover.r),
426
+ }));
427
+ if (log.kind === "resolved") {
428
+ return {
429
+ ...log,
430
+ kind: closedEnum(log.kind),
431
+ provider: closedEnum(log.provider),
432
+ ...(log.userAgentSource ? { userAgentSource: closedEnum(log.userAgentSource) } : {}),
433
+ ...(log.protocol ? { protocol: closedEnum(log.protocol) } : {}),
434
+ cacheStatus: closedEnum(log.cacheStatus),
435
+ ...(log.allocatorBodyClass
436
+ ? { allocatorBodyClass: closedEnum(log.allocatorBodyClass) }
437
+ : {}),
438
+ ...(attemptSamples ? { attemptSamples } : {}),
439
+ ...(log.vendors ? { vendors: log.vendors.map((vendor) => closedEnum(vendor)) } : {}),
440
+ ...(failovers ? { failovers } : {}),
441
+ } as ProxyTelemetryHeaderPayload;
442
+ }
443
+ return {
444
+ ...log,
445
+ kind: closedEnum(log.kind),
446
+ vendors: log.vendors.map((vendor) => closedEnum(vendor)),
447
+ ...(log.cacheStatus ? { cacheStatus: closedEnum(log.cacheStatus) } : {}),
448
+ ...(log.allocatorBodyClass ? { allocatorBodyClass: closedEnum(log.allocatorBodyClass) } : {}),
449
+ ...(failovers ? { failovers } : {}),
450
+ ...(attemptSamples ? { attemptSamples } : {}),
451
+ } as ProxyTelemetryHeaderPayload;
452
+ }
368
453
 
369
- const encoded = encodeBase64Url(JSON.stringify(payload));
370
- if (encoded.length > MAX_HEADER_BYTES) return undefined;
371
- return encoded;
454
+ toHeaderValue(): string | undefined {
455
+ const telemetry = new RequestTelemetry(createTraceContext());
456
+ telemetry.register(this);
457
+ return telemetry.toHeaderValue();
372
458
  }
373
459
  }