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