@apifuse/provider-sdk 2.1.0-beta.6 → 2.1.0-beta.8

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 (128) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/ceremonies/index.d.ts +41 -0
  3. package/dist/ceremonies/index.js +490 -0
  4. package/dist/choice-token.d.ts +24 -0
  5. package/dist/choice-token.js +74 -0
  6. package/dist/cli/commands.d.ts +10 -0
  7. package/dist/cli/commands.js +80 -0
  8. package/dist/cli/create.d.ts +47 -0
  9. package/dist/cli/create.js +762 -0
  10. package/dist/config/loader.d.ts +107 -0
  11. package/dist/config/loader.js +935 -0
  12. package/dist/contract-json.d.ts +9 -0
  13. package/dist/contract-json.js +51 -0
  14. package/dist/contract-serialization.d.ts +4 -0
  15. package/dist/contract-serialization.js +78 -0
  16. package/dist/contract-types.d.ts +49 -0
  17. package/dist/contract-types.js +1 -0
  18. package/dist/contract.d.ts +6 -0
  19. package/dist/contract.js +155 -0
  20. package/dist/define.d.ts +97 -0
  21. package/dist/define.js +1320 -0
  22. package/dist/dev.d.ts +9 -0
  23. package/dist/dev.js +15 -0
  24. package/dist/errors.d.ts +59 -0
  25. package/dist/errors.js +97 -0
  26. package/dist/i18n/catalog.d.ts +29 -0
  27. package/dist/i18n/catalog.js +159 -0
  28. package/dist/i18n/index.d.ts +2 -0
  29. package/dist/i18n/index.js +2 -0
  30. package/dist/i18n/keys.d.ts +10 -0
  31. package/dist/i18n/keys.js +34 -0
  32. package/dist/index.d.ts +41 -0
  33. package/dist/index.js +37 -0
  34. package/dist/lint.d.ts +73 -0
  35. package/dist/lint.js +702 -0
  36. package/dist/observability.d.ts +5 -0
  37. package/dist/observability.js +39 -0
  38. package/dist/provider.d.ts +9 -0
  39. package/dist/provider.js +8 -0
  40. package/dist/public-schema-field-lint.d.ts +2 -0
  41. package/dist/public-schema-field-lint.js +158 -0
  42. package/dist/recipes/gov-api.d.ts +19 -0
  43. package/dist/recipes/gov-api.js +72 -0
  44. package/dist/recipes/rest-api.d.ts +21 -0
  45. package/dist/recipes/rest-api.js +115 -0
  46. package/dist/runtime/auth-flow.d.ts +14 -0
  47. package/dist/runtime/auth-flow.js +44 -0
  48. package/dist/runtime/browser.d.ts +25 -0
  49. package/dist/runtime/browser.js +1034 -0
  50. package/dist/runtime/cache.d.ts +10 -0
  51. package/dist/runtime/cache.js +372 -0
  52. package/dist/runtime/choice.d.ts +15 -0
  53. package/dist/runtime/choice.js +435 -0
  54. package/dist/runtime/credential.d.ts +8 -0
  55. package/dist/runtime/credential.js +61 -0
  56. package/dist/runtime/env.d.ts +2 -0
  57. package/dist/runtime/env.js +10 -0
  58. package/dist/runtime/executor.d.ts +16 -0
  59. package/dist/runtime/executor.js +51 -0
  60. package/dist/runtime/http.d.ts +8 -0
  61. package/dist/runtime/http.js +706 -0
  62. package/dist/runtime/insights.d.ts +9 -0
  63. package/dist/runtime/insights.js +324 -0
  64. package/dist/runtime/instrumentation.d.ts +8 -0
  65. package/dist/runtime/instrumentation.js +269 -0
  66. package/dist/runtime/key-derivation.d.ts +24 -0
  67. package/dist/runtime/key-derivation.js +73 -0
  68. package/dist/runtime/keyring.d.ts +25 -0
  69. package/dist/runtime/keyring.js +93 -0
  70. package/dist/runtime/namespace.d.ts +9 -0
  71. package/dist/runtime/namespace.js +19 -0
  72. package/dist/runtime/otlp.d.ts +39 -0
  73. package/dist/runtime/otlp.js +103 -0
  74. package/dist/runtime/perf.d.ts +12 -0
  75. package/dist/runtime/perf.js +52 -0
  76. package/dist/runtime/prevalidate.d.ts +12 -0
  77. package/dist/runtime/prevalidate.js +173 -0
  78. package/dist/runtime/provider.d.ts +2 -0
  79. package/dist/runtime/provider.js +11 -0
  80. package/dist/runtime/proxy-errors.d.ts +21 -0
  81. package/dist/runtime/proxy-errors.js +83 -0
  82. package/dist/runtime/proxy-telemetry.d.ts +8 -0
  83. package/dist/runtime/proxy-telemetry.js +174 -0
  84. package/dist/runtime/redis.d.ts +17 -0
  85. package/dist/runtime/redis.js +82 -0
  86. package/dist/runtime/request-options.d.ts +3 -0
  87. package/dist/runtime/request-options.js +42 -0
  88. package/dist/runtime/state.d.ts +17 -0
  89. package/dist/runtime/state.js +344 -0
  90. package/dist/runtime/stealth.d.ts +18 -0
  91. package/dist/runtime/stealth.js +827 -0
  92. package/dist/runtime/stt.d.ts +22 -0
  93. package/dist/runtime/stt.js +480 -0
  94. package/dist/runtime/trace.d.ts +26 -0
  95. package/dist/runtime/trace.js +142 -0
  96. package/dist/runtime/waterfall.d.ts +12 -0
  97. package/dist/runtime/waterfall.js +147 -0
  98. package/dist/schema.d.ts +74 -0
  99. package/dist/schema.js +243 -0
  100. package/dist/serve.d.ts +1 -0
  101. package/dist/serve.js +1 -0
  102. package/dist/server/index.d.ts +3 -0
  103. package/dist/server/index.js +2 -0
  104. package/dist/server/serve.d.ts +64 -0
  105. package/dist/server/serve.js +1110 -0
  106. package/dist/server/types.d.ts +136 -0
  107. package/dist/server/types.js +86 -0
  108. package/dist/stealth/profiles.d.ts +4 -0
  109. package/dist/stealth/profiles.js +259 -0
  110. package/dist/stream.d.ts +44 -0
  111. package/dist/stream.js +151 -0
  112. package/dist/testing/helpers.d.ts +23 -0
  113. package/dist/testing/helpers.js +95 -0
  114. package/dist/testing/index.d.ts +2 -0
  115. package/dist/testing/index.js +2 -0
  116. package/dist/testing/run.d.ts +34 -0
  117. package/dist/testing/run.js +303 -0
  118. package/dist/types.d.ts +1324 -0
  119. package/dist/types.js +61 -0
  120. package/dist/utils/date.d.ts +6 -0
  121. package/dist/utils/date.js +101 -0
  122. package/dist/utils/parse.d.ts +16 -0
  123. package/dist/utils/parse.js +51 -0
  124. package/dist/utils/text.d.ts +4 -0
  125. package/dist/utils/text.js +14 -0
  126. package/dist/utils/transform.d.ts +8 -0
  127. package/dist/utils/transform.js +48 -0
  128. package/package.json +109 -107
@@ -0,0 +1,9 @@
1
+ import type { Span } from "./trace";
2
+ export type InsightSeverity = "info" | "warning" | "error";
3
+ export type Insight = {
4
+ id: string;
5
+ severity: InsightSeverity;
6
+ message: string;
7
+ fix?: string;
8
+ };
9
+ export declare function generateInsights(spans: Span[]): Insight[];
@@ -0,0 +1,324 @@
1
+ import { computePercentile } from "./perf";
2
+ const LARGE_RESPONSE_BYTES = 100_000;
3
+ const SLOW_TRANSFORM_MS = 10;
4
+ const DNS_WARN_MS = 5;
5
+ const BROWSER_IDLE_MS = 5_000;
6
+ const REFRESH_WARN_RATE = 0.1;
7
+ const TLS_REUSE_FIX = `const session = ctx.stealth.createSession({ profile: 'chrome-146' });
8
+ const resp = await session.fetch(url, opts);`;
9
+ const TRANSFORM_FIX = `transformResponse: (raw) => {
10
+ return raw.items.map(({ id, name, price }) => ({ id, name, price }));
11
+ }`;
12
+ const LARGE_RESPONSE_FIX = `const resp = await ctx.http.get('/items', {
13
+ params: { limit: 50, page: 1 },
14
+ });`;
15
+ const DNS_FIX = `// Enable DNS caching or reuse a long-lived session per host.
16
+ const session = ctx.stealth.createSession({ profile: 'chrome-146' });
17
+ await session.fetch(url, opts);`;
18
+ const PROXY_FIX = `// Re-check whether this operation really needs a proxy.
19
+ await ctx.stealth.fetch(url, { ...opts, proxy: undefined });`;
20
+ const BROWSER_FIX = `await page.waitForSelector('[data-ready="true"]', {
21
+ timeout: 5_000,
22
+ });`;
23
+ const SESSION_FIX = `export default defineProvider({
24
+ session: {
25
+ ttl: 60 * 60,
26
+ },
27
+ });`;
28
+ function isNumber(value) {
29
+ return typeof value === "number" && Number.isFinite(value);
30
+ }
31
+ function isBoolean(value) {
32
+ return typeof value === "boolean";
33
+ }
34
+ function isString(value) {
35
+ return typeof value === "string" && value.length > 0;
36
+ }
37
+ function getNumberAttribute(span, key) {
38
+ const value = span.attributes[key];
39
+ return isNumber(value) ? value : undefined;
40
+ }
41
+ function getBooleanAttribute(span, key) {
42
+ const value = span.attributes[key];
43
+ return isBoolean(value) ? value : undefined;
44
+ }
45
+ function getStringAttribute(span, key) {
46
+ const value = span.attributes[key];
47
+ return isString(value) ? value : undefined;
48
+ }
49
+ function formatPercent(value) {
50
+ return `${Math.round(value)}%`;
51
+ }
52
+ function formatMs(value) {
53
+ const rounded = Number(value.toFixed(value >= 10 ? 0 : 1));
54
+ return `${rounded}ms`;
55
+ }
56
+ function formatBytes(value) {
57
+ if (value >= 1_000_000) {
58
+ return `${(value / 1_000_000).toFixed(1)}MB`;
59
+ }
60
+ if (value >= 1_000) {
61
+ return `${(value / 1_000).toFixed(1)}KB`;
62
+ }
63
+ return `${value}B`;
64
+ }
65
+ function parseHostname(url) {
66
+ if (!url) {
67
+ return undefined;
68
+ }
69
+ try {
70
+ return new URL(url).hostname;
71
+ }
72
+ catch {
73
+ return undefined;
74
+ }
75
+ }
76
+ function makeInsight(id, severity, result) {
77
+ return {
78
+ id,
79
+ severity,
80
+ message: result.message,
81
+ ...(result.fix ? { fix: result.fix } : {}),
82
+ };
83
+ }
84
+ function isStealthSpan(span) {
85
+ return span.name.startsWith("stealth.");
86
+ }
87
+ function isRequestSpan(span) {
88
+ return span.name === "stealth.fetch" || span.name.startsWith("http.");
89
+ }
90
+ function isBrowserSpan(span) {
91
+ return span.name.startsWith("browser.") || span.name.startsWith("page.");
92
+ }
93
+ function hasProxy(span) {
94
+ const proxy = span.attributes.proxy;
95
+ return proxy === true || (typeof proxy === "string" && proxy.length > 0);
96
+ }
97
+ function getTlsReuseInsight(spans) {
98
+ const tlsSpans = spans.filter(isStealthSpan);
99
+ if (tlsSpans.length === 0) {
100
+ return {
101
+ triggered: false,
102
+ message: "✓ Stealth connection reuse: no stealth spans sampled yet",
103
+ };
104
+ }
105
+ const reusedCount = tlsSpans.filter((span) => getBooleanAttribute(span, "connection_reused") === true).length;
106
+ const reuseRate = reusedCount / tlsSpans.length;
107
+ if (1 - reuseRate >= 0.8) {
108
+ return {
109
+ triggered: true,
110
+ message: `⚠ Stealth connection reuse: ${formatPercent(reuseRate * 100)} reused — stealth handshakes are happening on most requests`,
111
+ fix: TLS_REUSE_FIX,
112
+ };
113
+ }
114
+ return {
115
+ triggered: false,
116
+ message: `✓ Stealth connection reuse: ${formatPercent(reuseRate * 100)} (good)`,
117
+ };
118
+ }
119
+ function getSlowTransformInsight(spans) {
120
+ const durations = spans
121
+ .filter((span) => span.name === "transformResponse")
122
+ .map((span) => span.duration_ms)
123
+ .filter((value) => Number.isFinite(value));
124
+ if (durations.length === 0) {
125
+ return {
126
+ triggered: false,
127
+ message: "✓ Transform overhead: no transformResponse spans sampled yet",
128
+ };
129
+ }
130
+ const p95 = computePercentile([...durations].sort((a, b) => a - b), 95);
131
+ if (p95 > SLOW_TRANSFORM_MS) {
132
+ return {
133
+ triggered: true,
134
+ message: `⚠ Transform overhead: p95 ${formatMs(p95)} — trim array size or transformation complexity`,
135
+ fix: TRANSFORM_FIX,
136
+ };
137
+ }
138
+ return {
139
+ triggered: false,
140
+ message: `✓ Transform overhead: p95 ${formatMs(p95)} (good)`,
141
+ };
142
+ }
143
+ function getLargeResponseInsight(spans) {
144
+ const sizes = spans
145
+ .map((span) => getNumberAttribute(span, "response_size"))
146
+ .filter((value) => value !== undefined);
147
+ if (sizes.length === 0) {
148
+ return {
149
+ triggered: false,
150
+ message: "✓ Response size: no response payloads sampled yet",
151
+ };
152
+ }
153
+ const maxSize = Math.max(...sizes);
154
+ if (maxSize > LARGE_RESPONSE_BYTES) {
155
+ return {
156
+ triggered: true,
157
+ message: `⚠ Response size: ${formatBytes(maxSize)} — consider pagination or a lower limit`,
158
+ fix: LARGE_RESPONSE_FIX,
159
+ };
160
+ }
161
+ return {
162
+ triggered: false,
163
+ message: `✓ Response size: ${formatBytes(maxSize)} max (good)`,
164
+ };
165
+ }
166
+ function getDnsRepeatedCandidate(spans) {
167
+ const grouped = new Map();
168
+ for (const span of spans.filter(isStealthSpan)) {
169
+ const hostname = parseHostname(getStringAttribute(span, "url"));
170
+ const dnsMs = getNumberAttribute(span, "dns_ms");
171
+ if (!hostname || dnsMs === undefined) {
172
+ continue;
173
+ }
174
+ const entry = grouped.get(hostname) ?? {
175
+ dnsDurations: [],
176
+ reuseCount: 0,
177
+ totalCount: 0,
178
+ };
179
+ entry.dnsDurations.push(dnsMs);
180
+ entry.totalCount += 1;
181
+ if (getBooleanAttribute(span, "connection_reused") === true) {
182
+ entry.reuseCount += 1;
183
+ }
184
+ grouped.set(hostname, entry);
185
+ }
186
+ let candidate = null;
187
+ for (const [hostname, entry] of grouped) {
188
+ if (entry.totalCount < 2) {
189
+ continue;
190
+ }
191
+ const avgDnsMs = entry.dnsDurations.reduce((sum, value) => sum + value, 0) /
192
+ entry.dnsDurations.length;
193
+ const reuseRate = entry.reuseCount / entry.totalCount;
194
+ if (avgDnsMs > DNS_WARN_MS && reuseRate < 0.2) {
195
+ if (!candidate || avgDnsMs > candidate.avgDnsMs) {
196
+ candidate = {
197
+ hostname,
198
+ avgDnsMs,
199
+ };
200
+ }
201
+ }
202
+ }
203
+ return candidate;
204
+ }
205
+ function getDnsRepeatedInsight(spans) {
206
+ const candidate = getDnsRepeatedCandidate(spans);
207
+ if (!candidate) {
208
+ return {
209
+ triggered: false,
210
+ message: "✓ DNS resolution: no repeated DNS bottleneck detected",
211
+ };
212
+ }
213
+ return {
214
+ triggered: true,
215
+ message: `⚠ DNS resolution: ${formatMs(candidate.avgDnsMs)} avg for ${candidate.hostname} — consider DNS caching`,
216
+ fix: DNS_FIX,
217
+ };
218
+ }
219
+ function getProxyOverheadInsight(spans) {
220
+ const requestSpans = spans.filter(isRequestSpan);
221
+ const proxiedDurations = requestSpans
222
+ .filter(hasProxy)
223
+ .map((span) => span.duration_ms);
224
+ const directDurations = requestSpans
225
+ .filter((span) => !hasProxy(span))
226
+ .map((span) => span.duration_ms);
227
+ if (proxiedDurations.length === 0 || directDurations.length === 0) {
228
+ return {
229
+ triggered: false,
230
+ message: "✓ Proxy overhead: insufficient proxy/direct samples",
231
+ };
232
+ }
233
+ const proxyAvg = proxiedDurations.reduce((sum, value) => sum + value, 0) /
234
+ proxiedDurations.length;
235
+ const directAvg = directDurations.reduce((sum, value) => sum + value, 0) /
236
+ directDurations.length;
237
+ if (directAvg > 0 && proxyAvg >= directAvg * 2) {
238
+ return {
239
+ triggered: true,
240
+ message: `⚠ Proxy overhead: ${formatMs(proxyAvg)} avg with proxy vs ${formatMs(directAvg)} direct`,
241
+ fix: PROXY_FIX,
242
+ };
243
+ }
244
+ return {
245
+ triggered: false,
246
+ message: `✓ Proxy overhead: ${formatMs(proxyAvg)} avg with proxy vs ${formatMs(directAvg)} direct (good)`,
247
+ };
248
+ }
249
+ function getBrowserIdleInsight(spans) {
250
+ const waits = spans
251
+ .filter(isBrowserSpan)
252
+ .map((span) => {
253
+ const waitMs = getNumberAttribute(span, "wait_ms");
254
+ if (waitMs !== undefined) {
255
+ return waitMs;
256
+ }
257
+ return span.name.toLowerCase().includes("wait")
258
+ ? span.duration_ms
259
+ : undefined;
260
+ })
261
+ .filter((value) => value !== undefined);
262
+ if (waits.length === 0) {
263
+ return {
264
+ triggered: false,
265
+ message: "✓ Browser waits: no idle wait spans sampled yet",
266
+ };
267
+ }
268
+ const maxWait = Math.max(...waits);
269
+ if (maxWait > BROWSER_IDLE_MS) {
270
+ return {
271
+ triggered: true,
272
+ message: `⚠ Browser idle wait: ${formatMs(maxWait)} — optimize waitFor conditions`,
273
+ fix: BROWSER_FIX,
274
+ };
275
+ }
276
+ return {
277
+ triggered: false,
278
+ message: `✓ Browser waits: ${formatMs(maxWait)} max (good)`,
279
+ };
280
+ }
281
+ function getSessionExpiryInsight(spans) {
282
+ const refreshCount = spans.filter((span) => span.name === "credential.refresh").length;
283
+ const requestCount = spans.filter(isRequestSpan).length;
284
+ if (requestCount === 0) {
285
+ return {
286
+ triggered: false,
287
+ message: "✓ Session refresh frequency: no request spans sampled yet",
288
+ };
289
+ }
290
+ const refreshRate = refreshCount / requestCount;
291
+ if (refreshRate > REFRESH_WARN_RATE) {
292
+ return {
293
+ triggered: true,
294
+ message: `⚠ Session refresh frequency: ${formatPercent(refreshRate * 100)} of requests — adjust session TTL`,
295
+ fix: SESSION_FIX,
296
+ };
297
+ }
298
+ return {
299
+ triggered: false,
300
+ message: `✓ Session refresh frequency: ${formatPercent(refreshRate * 100)} of requests (good)`,
301
+ };
302
+ }
303
+ export function generateInsights(spans) {
304
+ if (spans.length === 0) {
305
+ return [];
306
+ }
307
+ const tlsReuse = getTlsReuseInsight(spans);
308
+ const slowTransform = getSlowTransformInsight(spans);
309
+ const largeResponse = getLargeResponseInsight(spans);
310
+ const dnsRepeated = getDnsRepeatedInsight(spans);
311
+ const proxyOverhead = getProxyOverheadInsight(spans);
312
+ const browserIdle = getBrowserIdleInsight(spans);
313
+ const sessionExpiry = getSessionExpiryInsight(spans);
314
+ const rules = [
315
+ makeInsight("tls_reuse_failure", tlsReuse.triggered ? "warning" : "info", tlsReuse),
316
+ makeInsight("slow_transform", slowTransform.triggered ? "warning" : "info", slowTransform),
317
+ makeInsight("large_response", largeResponse.triggered ? "warning" : "info", largeResponse),
318
+ makeInsight("dns_repeated", dnsRepeated.triggered ? "warning" : "info", dnsRepeated),
319
+ makeInsight("proxy_overhead", proxyOverhead.triggered ? "warning" : "info", proxyOverhead),
320
+ makeInsight("browser_idle", browserIdle.triggered ? "warning" : "info", browserIdle),
321
+ makeInsight("session_expiry_frequent", sessionExpiry.triggered ? "warning" : "info", sessionExpiry),
322
+ ];
323
+ return rules;
324
+ }
@@ -0,0 +1,8 @@
1
+ import type { ProviderContext } from "../types";
2
+ import { type CreateTraceContextOptions, type TraceContext } from "./trace";
3
+ export interface InstrumentationOptions extends CreateTraceContextOptions {
4
+ }
5
+ export type InstrumentedProviderContext<T extends ProviderContext> = Omit<T, "trace"> & {
6
+ trace: TraceContext;
7
+ };
8
+ export declare function wrapWithInstrumentation<T extends ProviderContext>(ctx: T, options?: InstrumentationOptions): InstrumentedProviderContext<T>;
@@ -0,0 +1,269 @@
1
+ import { createTraceContext, getTraceRecorder, } from "./trace";
2
+ const BROWSER_PAGE_METHODS = new Set([
3
+ "goto",
4
+ "fill",
5
+ "click",
6
+ "type",
7
+ "waitForSelector",
8
+ ]);
9
+ function getErrorStatus(error) {
10
+ if (typeof error === "object" &&
11
+ error !== null &&
12
+ "status" in error &&
13
+ typeof error.status === "number") {
14
+ return error.status;
15
+ }
16
+ return undefined;
17
+ }
18
+ function getResponseDuration(result) {
19
+ if (typeof result === "object" &&
20
+ result !== null &&
21
+ "meta" in result &&
22
+ typeof result.meta === "object" &&
23
+ result.meta !== null &&
24
+ "duration" in result.meta &&
25
+ typeof result.meta.duration === "number") {
26
+ return result.meta.duration;
27
+ }
28
+ return undefined;
29
+ }
30
+ function getResponseStatus(namespace, result) {
31
+ if (typeof result === "object" &&
32
+ result !== null &&
33
+ "status" in result &&
34
+ typeof result.status === "number") {
35
+ return result.status;
36
+ }
37
+ if (namespace === "http") {
38
+ return 200;
39
+ }
40
+ return undefined;
41
+ }
42
+ function getUrl(namespace, args, result) {
43
+ if (typeof args[0] === "string") {
44
+ return args[0];
45
+ }
46
+ if (namespace === "browser" &&
47
+ typeof result === "object" &&
48
+ result !== null &&
49
+ "url" in result &&
50
+ typeof result.url === "string") {
51
+ return result.url;
52
+ }
53
+ return undefined;
54
+ }
55
+ function getMethod(namespace, methodName, args) {
56
+ if (namespace === "http") {
57
+ return methodName.toUpperCase();
58
+ }
59
+ if (namespace === "stealth") {
60
+ const options = typeof args[1] === "object" && args[1] !== null ? args[1] : undefined;
61
+ if (options && "method" in options && typeof options.method === "string") {
62
+ return options.method.toUpperCase();
63
+ }
64
+ return "GET";
65
+ }
66
+ return undefined;
67
+ }
68
+ function buildSpanAttributes(namespace, methodName, args, result, error) {
69
+ const attributes = {};
70
+ const url = getUrl(namespace, args, result);
71
+ const method = getMethod(namespace, methodName, args);
72
+ const status = error
73
+ ? getErrorStatus(error)
74
+ : getResponseStatus(namespace, result);
75
+ const duration = error ? undefined : getResponseDuration(result);
76
+ if (url) {
77
+ attributes.url = url;
78
+ }
79
+ if (method) {
80
+ attributes.method = method;
81
+ }
82
+ if (status !== undefined &&
83
+ (namespace === "http" || namespace === "stealth")) {
84
+ attributes.status = status;
85
+ }
86
+ if (duration !== undefined) {
87
+ attributes.duration_ms = duration;
88
+ }
89
+ if (namespace === "session" || namespace === "state") {
90
+ attributes.operation = methodName;
91
+ const key = typeof args[0] === "string" ? args[0] : undefined;
92
+ if (key) {
93
+ attributes.key = key;
94
+ }
95
+ }
96
+ return attributes;
97
+ }
98
+ function getBrowserPageAttributes(methodName, args, elapsedMs, error) {
99
+ const attributes = {};
100
+ if (methodName === "goto") {
101
+ const url = typeof args[0] === "string" ? args[0] : undefined;
102
+ if (url) {
103
+ attributes.url = url;
104
+ }
105
+ if (error === undefined) {
106
+ attributes.navigation_ms = elapsedMs ?? 0;
107
+ }
108
+ return attributes;
109
+ }
110
+ const selector = typeof args[0] === "string" ? args[0] : undefined;
111
+ if (selector) {
112
+ attributes.selector = selector;
113
+ }
114
+ if (error === undefined) {
115
+ const key = methodName === "waitForSelector" ? "wait_ms" : "action_ms";
116
+ attributes[key] = elapsedMs ?? 0;
117
+ }
118
+ return attributes;
119
+ }
120
+ function wrapPage(page, trace) {
121
+ if (page === null || page === undefined) {
122
+ return page;
123
+ }
124
+ const recorder = getTraceRecorder(trace);
125
+ if (!recorder) {
126
+ return page;
127
+ }
128
+ const wrappedMethods = new Map();
129
+ return new Proxy(page, {
130
+ get(pageTarget, property, receiver) {
131
+ const value = Reflect.get(pageTarget, property, receiver);
132
+ if (typeof value !== "function" ||
133
+ property === "constructor" ||
134
+ !BROWSER_PAGE_METHODS.has(String(property))) {
135
+ return value;
136
+ }
137
+ if (wrappedMethods.has(property)) {
138
+ return wrappedMethods.get(property);
139
+ }
140
+ const methodName = String(property);
141
+ const wrapped = (...args) => {
142
+ let elapsedMs = 0;
143
+ return recorder.runSpan(`browser.page.${methodName}`, async () => {
144
+ const startedAt = Date.now();
145
+ const result = await Reflect.apply(value, pageTarget, args);
146
+ elapsedMs = Date.now() - startedAt;
147
+ return result;
148
+ }, {
149
+ onSuccess: () => getBrowserPageAttributes(methodName, args, elapsedMs),
150
+ onError: (error) => getBrowserPageAttributes(methodName, args, undefined, error),
151
+ });
152
+ };
153
+ wrappedMethods.set(property, wrapped);
154
+ return wrapped;
155
+ },
156
+ });
157
+ }
158
+ function wrapNamespace(namespace, target, trace) {
159
+ const recorder = getTraceRecorder(trace);
160
+ if (!recorder) {
161
+ return target;
162
+ }
163
+ const wrappedMethods = new Map();
164
+ return new Proxy(target, {
165
+ get(namespaceTarget, property, receiver) {
166
+ const value = Reflect.get(namespaceTarget, property, receiver);
167
+ if (typeof value !== "function" || property === "constructor") {
168
+ return value;
169
+ }
170
+ if (namespace === "browser" && property === "newPage") {
171
+ if (wrappedMethods.has(property)) {
172
+ return wrappedMethods.get(property);
173
+ }
174
+ const wrapped = (...args) => {
175
+ let allocateMs = 0;
176
+ return recorder.runSpan("browser.newPage", async () => {
177
+ const startedAt = Date.now();
178
+ const page = await Reflect.apply(value, namespaceTarget, args);
179
+ allocateMs = Date.now() - startedAt;
180
+ return wrapPage(page, trace);
181
+ }, {
182
+ onSuccess: (result) => {
183
+ const attributes = {
184
+ allocate_ms: allocateMs,
185
+ };
186
+ if (result &&
187
+ typeof result === "object" &&
188
+ "pageId" in result &&
189
+ typeof result.pageId === "string") {
190
+ attributes.page_id = result.pageId;
191
+ }
192
+ if (namespaceTarget &&
193
+ typeof namespaceTarget === "object" &&
194
+ "engine" in namespaceTarget &&
195
+ typeof namespaceTarget.engine ===
196
+ "string") {
197
+ attributes.engine = namespaceTarget.engine;
198
+ }
199
+ return attributes;
200
+ },
201
+ onError: (error) => getBrowserPageAttributes("newPage", args, undefined, error),
202
+ });
203
+ };
204
+ wrappedMethods.set(property, wrapped);
205
+ return wrapped;
206
+ }
207
+ if (wrappedMethods.has(property)) {
208
+ return wrappedMethods.get(property);
209
+ }
210
+ const methodName = String(property);
211
+ if (namespace === "browser") {
212
+ const wrapped = (...args) => {
213
+ let elapsedMs = 0;
214
+ return recorder.runSpan(`browser.${methodName}`, async () => {
215
+ const startedAt = Date.now();
216
+ const result = await Reflect.apply(value, namespaceTarget, args);
217
+ elapsedMs = Date.now() - startedAt;
218
+ return result;
219
+ }, {
220
+ onSuccess: () => getBrowserPageAttributes(methodName, args, elapsedMs),
221
+ onError: (error) => getBrowserPageAttributes(methodName, args, undefined, error),
222
+ });
223
+ };
224
+ wrappedMethods.set(property, wrapped);
225
+ return wrapped;
226
+ }
227
+ const wrapped = (...args) => recorder.runSpan(`${namespace}.${methodName}`, () => Reflect.apply(value, namespaceTarget, args), {
228
+ onSuccess: (result) => buildSpanAttributes(namespace, methodName, args, result),
229
+ onError: (error) => buildSpanAttributes(namespace, methodName, args, undefined, error),
230
+ });
231
+ wrappedMethods.set(property, wrapped);
232
+ return wrapped;
233
+ },
234
+ });
235
+ }
236
+ function hasTraceOverrides(options) {
237
+ return options.maxSpans !== undefined || options.onSpan !== undefined;
238
+ }
239
+ export function wrapWithInstrumentation(ctx, options = {}) {
240
+ const trace = getTraceRecorder(ctx.trace) && !hasTraceOverrides(options)
241
+ ? ctx.trace
242
+ : createTraceContext(options);
243
+ const wrappedTargets = new Map();
244
+ return new Proxy(ctx, {
245
+ get(target, property, receiver) {
246
+ if (property === "trace") {
247
+ return trace;
248
+ }
249
+ if (property === "http" ||
250
+ property === "stealth" ||
251
+ property === "browser" ||
252
+ property === "session" ||
253
+ property === "state") {
254
+ const namespace = property;
255
+ if (wrappedTargets.has(namespace)) {
256
+ return wrappedTargets.get(namespace);
257
+ }
258
+ const value = Reflect.get(target, property, receiver);
259
+ if (!value || typeof value !== "object") {
260
+ return value;
261
+ }
262
+ const wrapped = wrapNamespace(namespace, value, trace);
263
+ wrappedTargets.set(namespace, wrapped);
264
+ return wrapped;
265
+ }
266
+ return Reflect.get(target, property, receiver);
267
+ },
268
+ });
269
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * HKDF purpose enum. Closed set; adding a purpose requires a spec amendment in
3
+ * `provider-isolation-hardening`. Each purpose uses a distinct salt so a leak
4
+ * of one subkey cannot reveal another for the same provider.
5
+ *
6
+ * Tenant-scoped columns (`connections.external_ref`, `connections.metadata`)
7
+ * are plaintext with RLS + log redaction + audit log; no HKDF purpose exists
8
+ * for them.
9
+ */
10
+ export type KeyPurpose = "credential-encryption" | "context-namespace" | "token-signing";
11
+ /** @internal Trusted loaders only; not re-exported to provider-importable paths. */
12
+ export declare function decodeMasterKey(encoded: string): Buffer;
13
+ export declare class ConfigurationError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ /** @internal Trusted loaders only; not re-exported to provider-importable paths. */
17
+ export declare function deriveSubkey(masterSecret: Buffer, providerId: string, purpose: KeyPurpose, keyVersion: number): Buffer;
18
+ /**
19
+ * Invalidate the subkey cache. Used by the master-key rotation worker after a
20
+ * writer version change, and by tests to assert determinism.
21
+ *
22
+ * @internal
23
+ */
24
+ export declare function invalidateSubkeyCache(): void;