@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
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ProxyAttemptTelemetryEvent,
|
|
3
3
|
ProxyCacheStatus,
|
|
4
|
+
ProxyProtocol,
|
|
4
5
|
ProxyResolutionTelemetryEvent,
|
|
5
6
|
ProxyTelemetrySink,
|
|
7
|
+
ProxyVendorFailoverTelemetryEvent,
|
|
8
|
+
ProxyVendorName,
|
|
6
9
|
SmartproxyAllocatorBodyClass,
|
|
7
|
-
} from "../config/loader";
|
|
10
|
+
} from "../config/loader.js";
|
|
8
11
|
|
|
9
12
|
export const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
|
|
10
13
|
|
|
11
14
|
type ProviderTelemetryHeader = {
|
|
12
15
|
v: 1;
|
|
13
16
|
proxy?: {
|
|
14
|
-
provider:
|
|
17
|
+
provider: ProxyVendorName;
|
|
18
|
+
protocol?: ProxyProtocol;
|
|
15
19
|
cacheStatus: ProxyCacheStatus;
|
|
16
20
|
cacheHit: boolean;
|
|
17
21
|
resolutionMs: number;
|
|
@@ -27,6 +31,10 @@ type ProviderTelemetryHeader = {
|
|
|
27
31
|
attempts: number;
|
|
28
32
|
refreshes?: number;
|
|
29
33
|
attemptSamples?: CompactProxyAttemptSample[];
|
|
34
|
+
/** Distinct vendors attempted across the resolution chain, in order seen. */
|
|
35
|
+
vendors?: ProxyVendorName[];
|
|
36
|
+
/** Cross-vendor failover events (bounded). */
|
|
37
|
+
failovers?: CompactVendorFailoverSample[];
|
|
30
38
|
};
|
|
31
39
|
};
|
|
32
40
|
|
|
@@ -41,8 +49,17 @@ type CompactProxyAttemptSample = {
|
|
|
41
49
|
d?: number;
|
|
42
50
|
};
|
|
43
51
|
|
|
52
|
+
type CompactVendorFailoverSample = {
|
|
53
|
+
v: ProxyVendorName;
|
|
54
|
+
nx?: ProxyVendorName;
|
|
55
|
+
p: "resolution" | "transport";
|
|
56
|
+
r: ProxyVendorFailoverTelemetryEvent["reason"];
|
|
57
|
+
a?: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
44
60
|
const MAX_HEADER_BYTES = 4_096;
|
|
45
61
|
const MAX_PROXY_ATTEMPT_SAMPLES = 24;
|
|
62
|
+
const MAX_PROXY_FAILOVER_SAMPLES = 12;
|
|
46
63
|
|
|
47
64
|
const CACHE_STATUS_SEVERITY: Record<ProxyCacheStatus, number> = {
|
|
48
65
|
disabled: 0,
|
|
@@ -55,31 +72,18 @@ const CACHE_STATUS_SEVERITY: Record<ProxyCacheStatus, number> = {
|
|
|
55
72
|
allocator: 7,
|
|
56
73
|
};
|
|
57
74
|
|
|
58
|
-
function sumOptional(
|
|
59
|
-
left: number | undefined,
|
|
60
|
-
right: number | undefined,
|
|
61
|
-
): number | undefined {
|
|
75
|
+
function sumOptional(left: number | undefined, right: number | undefined): number | undefined {
|
|
62
76
|
const total = (left ?? 0) + (right ?? 0);
|
|
63
77
|
return total > 0 ? total : undefined;
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
function maxOptional(
|
|
67
|
-
left: number
|
|
68
|
-
right: number | undefined,
|
|
69
|
-
): number | undefined {
|
|
70
|
-
const values = [left, right].filter(
|
|
71
|
-
(value): value is number => typeof value === "number",
|
|
72
|
-
);
|
|
80
|
+
function maxOptional(left: number | undefined, right: number | undefined): number | undefined {
|
|
81
|
+
const values = [left, right].filter((value): value is number => typeof value === "number");
|
|
73
82
|
return values.length > 0 ? Math.max(...values) : undefined;
|
|
74
83
|
}
|
|
75
84
|
|
|
76
|
-
function worseStatus(
|
|
77
|
-
left:
|
|
78
|
-
right: ProxyCacheStatus,
|
|
79
|
-
): ProxyCacheStatus {
|
|
80
|
-
return CACHE_STATUS_SEVERITY[right] > CACHE_STATUS_SEVERITY[left]
|
|
81
|
-
? right
|
|
82
|
-
: left;
|
|
85
|
+
function worseStatus(left: ProxyCacheStatus, right: ProxyCacheStatus): ProxyCacheStatus {
|
|
86
|
+
return CACHE_STATUS_SEVERITY[right] > CACHE_STATUS_SEVERITY[left] ? right : left;
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
function encodeBase64Url(value: string): string {
|
|
@@ -89,17 +93,17 @@ function encodeBase64Url(value: string): string {
|
|
|
89
93
|
export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
90
94
|
#events: ProxyResolutionTelemetryEvent[] = [];
|
|
91
95
|
#attempts: ProxyAttemptTelemetryEvent[] = [];
|
|
96
|
+
#failovers: ProxyVendorFailoverTelemetryEvent[] = [];
|
|
92
97
|
|
|
93
98
|
recordProxyResolution(event: ProxyResolutionTelemetryEvent): void {
|
|
94
99
|
this.#events.push({
|
|
95
|
-
provider:
|
|
100
|
+
provider: event.provider,
|
|
101
|
+
...(event.protocol ? { protocol: event.protocol } : {}),
|
|
96
102
|
cacheStatus: event.cacheStatus,
|
|
97
103
|
cacheHit: event.cacheHit,
|
|
98
104
|
resolutionMs: Math.max(0, Math.floor(event.resolutionMs)),
|
|
99
105
|
allocatorMs:
|
|
100
|
-
event.allocatorMs === undefined
|
|
101
|
-
? undefined
|
|
102
|
-
: Math.max(0, Math.floor(event.allocatorMs)),
|
|
106
|
+
event.allocatorMs === undefined ? undefined : Math.max(0, Math.floor(event.allocatorMs)),
|
|
103
107
|
allocatorStatus:
|
|
104
108
|
event.allocatorStatus === undefined
|
|
105
109
|
? undefined
|
|
@@ -110,37 +114,38 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
|
110
114
|
? undefined
|
|
111
115
|
: Math.max(1, Math.floor(event.allocatorAttempts)),
|
|
112
116
|
lockWaitMs:
|
|
113
|
-
event.lockWaitMs === undefined
|
|
114
|
-
? undefined
|
|
115
|
-
: Math.max(0, Math.floor(event.lockWaitMs)),
|
|
117
|
+
event.lockWaitMs === undefined ? undefined : Math.max(0, Math.floor(event.lockWaitMs)),
|
|
116
118
|
redisReadMs:
|
|
117
|
-
event.redisReadMs === undefined
|
|
118
|
-
? undefined
|
|
119
|
-
: Math.max(0, Math.floor(event.redisReadMs)),
|
|
119
|
+
event.redisReadMs === undefined ? undefined : Math.max(0, Math.floor(event.redisReadMs)),
|
|
120
120
|
redisWriteMs:
|
|
121
|
-
event.redisWriteMs === undefined
|
|
122
|
-
? undefined
|
|
123
|
-
: Math.max(0, Math.floor(event.redisWriteMs)),
|
|
121
|
+
event.redisWriteMs === undefined ? undefined : Math.max(0, Math.floor(event.redisWriteMs)),
|
|
124
122
|
poolAgeMs:
|
|
125
|
-
event.poolAgeMs === undefined
|
|
126
|
-
? undefined
|
|
127
|
-
: Math.max(0, Math.floor(event.poolAgeMs)),
|
|
123
|
+
event.poolAgeMs === undefined ? undefined : Math.max(0, Math.floor(event.poolAgeMs)),
|
|
128
124
|
poolExpiresInMs:
|
|
129
125
|
event.poolExpiresInMs === undefined
|
|
130
126
|
? undefined
|
|
131
127
|
: Math.max(0, Math.floor(event.poolExpiresInMs)),
|
|
132
128
|
attempts: Math.max(1, Math.floor(event.attempts || 1)),
|
|
133
129
|
refreshes:
|
|
134
|
-
event.refreshes === undefined
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
event.refreshes === undefined ? undefined : Math.max(0, Math.floor(event.refreshes)),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
recordProxyVendorFailover(event: ProxyVendorFailoverTelemetryEvent): void {
|
|
135
|
+
if (this.#failovers.length >= MAX_PROXY_FAILOVER_SAMPLES) return;
|
|
136
|
+
this.#failovers.push({
|
|
137
|
+
vendor: event.vendor,
|
|
138
|
+
...(event.nextVendor ? { nextVendor: event.nextVendor } : {}),
|
|
139
|
+
phase: event.phase,
|
|
140
|
+
reason: event.reason,
|
|
141
|
+
...(event.attempt === undefined ? {} : { attempt: Math.max(0, Math.floor(event.attempt)) }),
|
|
137
142
|
});
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
recordProxyAttempt(event: ProxyAttemptTelemetryEvent): void {
|
|
141
146
|
if (this.#attempts.length >= MAX_PROXY_ATTEMPT_SAMPLES) return;
|
|
142
147
|
this.#attempts.push({
|
|
143
|
-
provider:
|
|
148
|
+
provider: event.provider,
|
|
144
149
|
attempt: Math.max(1, Math.floor(event.attempt || 1)),
|
|
145
150
|
...(event.poolIndex === undefined
|
|
146
151
|
? {}
|
|
@@ -148,9 +153,7 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
|
148
153
|
...(event.proxyHash ? { proxyHash: event.proxyHash.slice(0, 16) } : {}),
|
|
149
154
|
outcome: event.outcome === "ok" ? "ok" : "error",
|
|
150
155
|
...(event.errorCode ? { errorCode: event.errorCode.slice(0, 80) } : {}),
|
|
151
|
-
...(event.status === undefined
|
|
152
|
-
? {}
|
|
153
|
-
: { status: Math.max(0, Math.floor(event.status)) }),
|
|
156
|
+
...(event.status === undefined ? {} : { status: Math.max(0, Math.floor(event.status)) }),
|
|
154
157
|
...(event.durationMs === undefined
|
|
155
158
|
? {}
|
|
156
159
|
: { durationMs: Math.max(0, Math.floor(event.durationMs)) }),
|
|
@@ -161,27 +164,29 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
|
161
164
|
const [first, ...rest] = this.#events;
|
|
162
165
|
if (!first) return undefined;
|
|
163
166
|
|
|
167
|
+
// The serving vendor/protocol is the last recorded resolution (a failed
|
|
168
|
+
// vendor records first, the vendor that served records last).
|
|
169
|
+
const serving = this.#events[this.#events.length - 1] ?? first;
|
|
170
|
+
const vendors: ProxyVendorName[] = [];
|
|
171
|
+
for (const event of this.#events) {
|
|
172
|
+
if (!vendors.includes(event.provider)) vendors.push(event.provider);
|
|
173
|
+
}
|
|
174
|
+
|
|
164
175
|
const aggregate = rest.reduce<ProxyResolutionTelemetryEvent>(
|
|
165
176
|
(acc, event) => ({
|
|
166
|
-
provider:
|
|
177
|
+
provider: event.provider,
|
|
167
178
|
cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
|
|
168
179
|
cacheHit: acc.cacheHit && event.cacheHit,
|
|
169
180
|
resolutionMs: acc.resolutionMs + event.resolutionMs,
|
|
170
181
|
allocatorMs: sumOptional(acc.allocatorMs, event.allocatorMs),
|
|
171
182
|
allocatorStatus: event.allocatorStatus ?? acc.allocatorStatus,
|
|
172
183
|
allocatorBodyClass: event.allocatorBodyClass ?? acc.allocatorBodyClass,
|
|
173
|
-
allocatorAttempts: sumOptional(
|
|
174
|
-
acc.allocatorAttempts,
|
|
175
|
-
event.allocatorAttempts,
|
|
176
|
-
),
|
|
184
|
+
allocatorAttempts: sumOptional(acc.allocatorAttempts, event.allocatorAttempts),
|
|
177
185
|
lockWaitMs: sumOptional(acc.lockWaitMs, event.lockWaitMs),
|
|
178
186
|
redisReadMs: sumOptional(acc.redisReadMs, event.redisReadMs),
|
|
179
187
|
redisWriteMs: sumOptional(acc.redisWriteMs, event.redisWriteMs),
|
|
180
188
|
poolAgeMs: maxOptional(acc.poolAgeMs, event.poolAgeMs),
|
|
181
|
-
poolExpiresInMs: maxOptional(
|
|
182
|
-
acc.poolExpiresInMs,
|
|
183
|
-
event.poolExpiresInMs,
|
|
184
|
-
),
|
|
189
|
+
poolExpiresInMs: maxOptional(acc.poolExpiresInMs, event.poolExpiresInMs),
|
|
185
190
|
attempts: acc.attempts + event.attempts,
|
|
186
191
|
refreshes: sumOptional(acc.refreshes, event.refreshes),
|
|
187
192
|
}),
|
|
@@ -190,13 +195,12 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
|
190
195
|
const payload: ProviderTelemetryHeader = {
|
|
191
196
|
v: 1,
|
|
192
197
|
proxy: {
|
|
193
|
-
provider:
|
|
198
|
+
provider: serving.provider,
|
|
199
|
+
...(serving.protocol ? { protocol: serving.protocol } : {}),
|
|
194
200
|
cacheStatus: aggregate.cacheStatus,
|
|
195
201
|
cacheHit: aggregate.cacheHit,
|
|
196
202
|
resolutionMs: aggregate.resolutionMs,
|
|
197
|
-
...(aggregate.allocatorMs !== undefined
|
|
198
|
-
? { allocatorMs: aggregate.allocatorMs }
|
|
199
|
-
: {}),
|
|
203
|
+
...(aggregate.allocatorMs !== undefined ? { allocatorMs: aggregate.allocatorMs } : {}),
|
|
200
204
|
...(aggregate.allocatorStatus !== undefined
|
|
201
205
|
? { allocatorStatus: aggregate.allocatorStatus }
|
|
202
206
|
: {}),
|
|
@@ -206,40 +210,38 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
|
206
210
|
...(aggregate.allocatorAttempts !== undefined
|
|
207
211
|
? { allocatorAttempts: aggregate.allocatorAttempts }
|
|
208
212
|
: {}),
|
|
209
|
-
...(aggregate.lockWaitMs !== undefined
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
...(aggregate.
|
|
213
|
-
? { redisReadMs: aggregate.redisReadMs }
|
|
214
|
-
: {}),
|
|
215
|
-
...(aggregate.redisWriteMs !== undefined
|
|
216
|
-
? { redisWriteMs: aggregate.redisWriteMs }
|
|
217
|
-
: {}),
|
|
218
|
-
...(aggregate.poolAgeMs !== undefined
|
|
219
|
-
? { poolAgeMs: aggregate.poolAgeMs }
|
|
220
|
-
: {}),
|
|
213
|
+
...(aggregate.lockWaitMs !== undefined ? { lockWaitMs: aggregate.lockWaitMs } : {}),
|
|
214
|
+
...(aggregate.redisReadMs !== undefined ? { redisReadMs: aggregate.redisReadMs } : {}),
|
|
215
|
+
...(aggregate.redisWriteMs !== undefined ? { redisWriteMs: aggregate.redisWriteMs } : {}),
|
|
216
|
+
...(aggregate.poolAgeMs !== undefined ? { poolAgeMs: aggregate.poolAgeMs } : {}),
|
|
221
217
|
...(aggregate.poolExpiresInMs !== undefined
|
|
222
218
|
? { poolExpiresInMs: aggregate.poolExpiresInMs }
|
|
223
219
|
: {}),
|
|
224
220
|
attempts: aggregate.attempts,
|
|
225
|
-
...(aggregate.refreshes !== undefined
|
|
226
|
-
? { refreshes: aggregate.refreshes }
|
|
227
|
-
: {}),
|
|
221
|
+
...(aggregate.refreshes !== undefined ? { refreshes: aggregate.refreshes } : {}),
|
|
228
222
|
...(this.#attempts.length > 0
|
|
229
223
|
? {
|
|
230
224
|
attemptSamples: this.#attempts.map((attempt, index) => ({
|
|
231
225
|
n: index + 1,
|
|
232
226
|
a: attempt.attempt,
|
|
233
|
-
...(attempt.poolIndex === undefined
|
|
234
|
-
? {}
|
|
235
|
-
: { i: attempt.poolIndex }),
|
|
227
|
+
...(attempt.poolIndex === undefined ? {} : { i: attempt.poolIndex }),
|
|
236
228
|
...(attempt.proxyHash ? { h: attempt.proxyHash } : {}),
|
|
237
229
|
o: attempt.outcome,
|
|
238
230
|
...(attempt.errorCode ? { c: attempt.errorCode } : {}),
|
|
239
231
|
...(attempt.status === undefined ? {} : { s: attempt.status }),
|
|
240
|
-
...(attempt.durationMs === undefined
|
|
241
|
-
|
|
242
|
-
|
|
232
|
+
...(attempt.durationMs === undefined ? {} : { d: attempt.durationMs }),
|
|
233
|
+
})),
|
|
234
|
+
}
|
|
235
|
+
: {}),
|
|
236
|
+
...(vendors.length > 1 ? { vendors } : {}),
|
|
237
|
+
...(this.#failovers.length > 0
|
|
238
|
+
? {
|
|
239
|
+
failovers: this.#failovers.map((failover) => ({
|
|
240
|
+
v: failover.vendor,
|
|
241
|
+
...(failover.nextVendor ? { nx: failover.nextVendor } : {}),
|
|
242
|
+
p: failover.phase,
|
|
243
|
+
r: failover.reason,
|
|
244
|
+
...(failover.attempt === undefined ? {} : { a: failover.attempt }),
|
|
243
245
|
})),
|
|
244
246
|
}
|
|
245
247
|
: {}),
|
package/src/runtime/redis.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Redis from "ioredis";
|
|
1
|
+
import { Redis } from "ioredis";
|
|
2
2
|
|
|
3
3
|
export type ProviderRedisClient = Redis;
|
|
4
4
|
|
|
@@ -43,9 +43,7 @@ export async function withRedisTimeout<T>(
|
|
|
43
43
|
}
|
|
44
44
|
}, options.timeoutMs);
|
|
45
45
|
});
|
|
46
|
-
const operationResult = options.onError
|
|
47
|
-
? operation().catch(options.onError)
|
|
48
|
-
: operation();
|
|
46
|
+
const operationResult = options.onError ? operation().catch(options.onError) : operation();
|
|
49
47
|
return await Promise.race([operationResult, timeout]);
|
|
50
48
|
} finally {
|
|
51
49
|
if (timeoutId) clearTimeout(timeoutId);
|
|
@@ -56,10 +54,7 @@ export function redisStatus(redis: ProviderRedisClient): string {
|
|
|
56
54
|
return redis.status;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
async function waitForRedisReady(
|
|
60
|
-
redis: ProviderRedisClient,
|
|
61
|
-
timeoutMs: number,
|
|
62
|
-
): Promise<boolean> {
|
|
57
|
+
async function waitForRedisReady(redis: ProviderRedisClient, timeoutMs: number): Promise<boolean> {
|
|
63
58
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
64
59
|
let settled = false;
|
|
65
60
|
|
|
@@ -80,10 +75,7 @@ async function waitForRedisReady(
|
|
|
80
75
|
const onReady = () => finish(true);
|
|
81
76
|
const onUnavailable = () => finish(false);
|
|
82
77
|
|
|
83
|
-
timeoutId = setTimeout(
|
|
84
|
-
() => finish(redisStatus(redis) === "ready"),
|
|
85
|
-
timeoutMs,
|
|
86
|
-
);
|
|
78
|
+
timeoutId = setTimeout(() => finish(redisStatus(redis) === "ready"), timeoutMs);
|
|
87
79
|
redis.once("ready", onReady);
|
|
88
80
|
redis.once("close", onUnavailable);
|
|
89
81
|
redis.once("end", onUnavailable);
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
RequestParamPrimitive,
|
|
3
|
-
RequestParams,
|
|
4
|
-
RequestParamValue,
|
|
5
|
-
} from "../types";
|
|
1
|
+
import type { RequestParamPrimitive, RequestParams, RequestParamValue } from "../types.js";
|
|
6
2
|
|
|
7
|
-
function isParamArray(
|
|
8
|
-
value: RequestParamValue,
|
|
9
|
-
): value is readonly RequestParamPrimitive[] {
|
|
3
|
+
function isParamArray(value: RequestParamValue): value is readonly RequestParamPrimitive[] {
|
|
10
4
|
return Array.isArray(value);
|
|
11
5
|
}
|
|
12
6
|
|
|
@@ -29,8 +23,7 @@ export function appendQueryParams(url: string, params?: RequestParams): string {
|
|
|
29
23
|
const parsed = new URL(url);
|
|
30
24
|
for (const [key, value] of Object.entries(params)) {
|
|
31
25
|
if (isParamArray(value)) {
|
|
32
|
-
for (const item of value)
|
|
33
|
-
appendQueryValue(parsed.searchParams, key, item);
|
|
26
|
+
for (const item of value) appendQueryValue(parsed.searchParams, key, item);
|
|
34
27
|
continue;
|
|
35
28
|
}
|
|
36
29
|
appendQueryValue(parsed.searchParams, key, value);
|
|
@@ -39,9 +32,7 @@ export function appendQueryParams(url: string, params?: RequestParams): string {
|
|
|
39
32
|
return parsed.toString();
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
export function normalizeHttpRequestBody(
|
|
43
|
-
body: unknown,
|
|
44
|
-
): string | Buffer | undefined {
|
|
35
|
+
export function normalizeHttpRequestBody(body: unknown): string | Buffer | undefined {
|
|
45
36
|
if (body === undefined) {
|
|
46
37
|
return undefined;
|
|
47
38
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ProviderSecretError } from "../errors.js";
|
|
2
|
+
import type { EnvContext, ProviderDefinition } from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonical error code for a declared-but-unprovisioned provider secret.
|
|
6
|
+
*
|
|
7
|
+
* The SDK is the single source of truth for env/secret presence validation:
|
|
8
|
+
* providers declare secrets in `defineProvider({ secrets: [...] })` and the
|
|
9
|
+
* runtime enforces presence before any handler or auth-flow code runs.
|
|
10
|
+
* Provider-local presence guards (requireServiceKey/requireApiKey style) are a
|
|
11
|
+
* deprecated antipattern — see the `sdk-owned-secret-presence` submit-check
|
|
12
|
+
* rule.
|
|
13
|
+
*/
|
|
14
|
+
export const MISSING_SECRET_CODE = "MISSING_SECRET";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Names of declared `required: true` secrets whose env values are unset or
|
|
18
|
+
* whitespace-only. Whitespace-only values count as missing for parity with the
|
|
19
|
+
* `.trim()` guards well-built providers used before the SDK owned this check —
|
|
20
|
+
* a blank value provisioned by a broken secret pipeline must not pass the gate.
|
|
21
|
+
*/
|
|
22
|
+
export function listMissingRequiredSecrets(
|
|
23
|
+
provider: ProviderDefinition,
|
|
24
|
+
env: EnvContext,
|
|
25
|
+
): string[] {
|
|
26
|
+
const missing: string[] = [];
|
|
27
|
+
for (const secret of provider.secrets ?? []) {
|
|
28
|
+
if (secret.required !== true) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const value = env.get(secret.name);
|
|
32
|
+
if (value === undefined || value.trim() === "") {
|
|
33
|
+
missing.push(secret.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return missing;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Throws the canonical structured missing-secret error when any declared
|
|
41
|
+
* `required: true` secret is absent. All missing names are reported in a
|
|
42
|
+
* single error so operators can provision the full set in one pass instead of
|
|
43
|
+
* discovering them one deploy at a time (the 2026-07-22 unprovisioned-secret
|
|
44
|
+
* incident failure mode).
|
|
45
|
+
*/
|
|
46
|
+
export function assertRequiredSecretsPresent(
|
|
47
|
+
provider: ProviderDefinition,
|
|
48
|
+
env: EnvContext,
|
|
49
|
+
): void {
|
|
50
|
+
const missing = listMissingRequiredSecrets(provider, env);
|
|
51
|
+
if (missing.length === 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const names = missing.join(", ");
|
|
55
|
+
throw new ProviderSecretError(
|
|
56
|
+
`Missing required provider secret${missing.length > 1 ? "s" : ""}: ${names}`,
|
|
57
|
+
{
|
|
58
|
+
code: MISSING_SECRET_CODE,
|
|
59
|
+
category: "credential_unavailable",
|
|
60
|
+
retryable: false,
|
|
61
|
+
fix: `Provision ${names} in the provider environment (e.g. Doppler). Declared in defineProvider({ secrets: [...] }).`,
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
}
|