@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
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { ProviderError, TransportError } from "../errors.js";
|
|
2
|
+
import { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "../types.js";
|
|
3
|
+
export const DEFAULT_PROXY_TRANSPORT_RETRY_METHODS = ["GET", "HEAD", "OPTIONS"];
|
|
4
|
+
export const DEFAULT_PROXY_TRANSPORT_RETRY_ERROR_CODES = [
|
|
5
|
+
"transport_network_error",
|
|
6
|
+
"transport_timeout",
|
|
7
|
+
];
|
|
8
|
+
const SAFE_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
9
|
+
const RATE_LIMIT_RETRY_STATUS_CODES = [429, 503];
|
|
10
|
+
const RATE_LIMIT_RETRY_ERROR_CODES = ["transport_timeout"];
|
|
11
|
+
const KNOWN_RETRY_METHODS = new Set([
|
|
12
|
+
"GET",
|
|
13
|
+
"HEAD",
|
|
14
|
+
"POST",
|
|
15
|
+
"PUT",
|
|
16
|
+
"DELETE",
|
|
17
|
+
"OPTIONS",
|
|
18
|
+
"TRACE",
|
|
19
|
+
"PATCH",
|
|
20
|
+
]);
|
|
21
|
+
const UNSAFE_RETRY_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE", "TRACE"]);
|
|
22
|
+
export const MAX_PROXY_TRANSPORT_RETRY_ATTEMPTS = 8;
|
|
23
|
+
const MAX_RETRY_DELAY_MS = 30_000;
|
|
24
|
+
function hasOwnValue(values, value) {
|
|
25
|
+
if (typeof value !== "string")
|
|
26
|
+
return false;
|
|
27
|
+
return Object.values(values).some((candidate) => candidate === value);
|
|
28
|
+
}
|
|
29
|
+
function createInvalidRetryPolicyError(message, label) {
|
|
30
|
+
return new ProviderError(message.replace("{{label}}", label), {
|
|
31
|
+
code: "retry_invalid_policy",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function retryErrorCode(error) {
|
|
35
|
+
if (error instanceof TransportError) {
|
|
36
|
+
return error.code;
|
|
37
|
+
}
|
|
38
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
39
|
+
const code = Reflect.get(error, "code");
|
|
40
|
+
return typeof code === "string" ? code : undefined;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function retryErrorStatus(error) {
|
|
45
|
+
if (error instanceof TransportError) {
|
|
46
|
+
return error.status ?? error.upstreamStatus;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
export function proxyTransportRetryErrorCode(error) {
|
|
51
|
+
return retryErrorCode(error);
|
|
52
|
+
}
|
|
53
|
+
export function proxyTransportRetryErrorStatus(error) {
|
|
54
|
+
return retryErrorStatus(error);
|
|
55
|
+
}
|
|
56
|
+
function createRetryOptions(preset, extraErrorCodes, label) {
|
|
57
|
+
const defaultErrorCodes = [...DEFAULT_PROXY_TRANSPORT_RETRY_ERROR_CODES, ...extraErrorCodes];
|
|
58
|
+
switch (preset) {
|
|
59
|
+
case HttpRetryPreset.Off:
|
|
60
|
+
return {
|
|
61
|
+
preset,
|
|
62
|
+
attempts: 1,
|
|
63
|
+
methods: DEFAULT_PROXY_TRANSPORT_RETRY_METHODS,
|
|
64
|
+
statusCodes: [],
|
|
65
|
+
errorCodes: defaultErrorCodes,
|
|
66
|
+
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
67
|
+
baseDelayMs: 100,
|
|
68
|
+
maxDelayMs: 1_000,
|
|
69
|
+
jitter: HttpRetryJitter.Full,
|
|
70
|
+
retryAfter: HttpRetryAfterPolicy.Ignore,
|
|
71
|
+
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
72
|
+
};
|
|
73
|
+
case HttpRetryPreset.SafeRead:
|
|
74
|
+
return {
|
|
75
|
+
preset,
|
|
76
|
+
attempts: 3,
|
|
77
|
+
methods: DEFAULT_PROXY_TRANSPORT_RETRY_METHODS,
|
|
78
|
+
statusCodes: SAFE_RETRY_STATUS_CODES,
|
|
79
|
+
errorCodes: defaultErrorCodes,
|
|
80
|
+
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
81
|
+
baseDelayMs: 100,
|
|
82
|
+
maxDelayMs: 2_000,
|
|
83
|
+
jitter: HttpRetryJitter.Full,
|
|
84
|
+
retryAfter: HttpRetryAfterPolicy.Cap,
|
|
85
|
+
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
86
|
+
};
|
|
87
|
+
case HttpRetryPreset.AggressiveRead:
|
|
88
|
+
return {
|
|
89
|
+
preset,
|
|
90
|
+
attempts: 4,
|
|
91
|
+
methods: DEFAULT_PROXY_TRANSPORT_RETRY_METHODS,
|
|
92
|
+
statusCodes: SAFE_RETRY_STATUS_CODES,
|
|
93
|
+
errorCodes: defaultErrorCodes,
|
|
94
|
+
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
95
|
+
baseDelayMs: 150,
|
|
96
|
+
maxDelayMs: 5_000,
|
|
97
|
+
jitter: HttpRetryJitter.Full,
|
|
98
|
+
retryAfter: HttpRetryAfterPolicy.Cap,
|
|
99
|
+
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
100
|
+
};
|
|
101
|
+
case HttpRetryPreset.RateLimitAware:
|
|
102
|
+
return {
|
|
103
|
+
preset,
|
|
104
|
+
attempts: 3,
|
|
105
|
+
methods: DEFAULT_PROXY_TRANSPORT_RETRY_METHODS,
|
|
106
|
+
statusCodes: RATE_LIMIT_RETRY_STATUS_CODES,
|
|
107
|
+
errorCodes: RATE_LIMIT_RETRY_ERROR_CODES,
|
|
108
|
+
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
109
|
+
baseDelayMs: 250,
|
|
110
|
+
maxDelayMs: 5_000,
|
|
111
|
+
jitter: HttpRetryJitter.Equal,
|
|
112
|
+
retryAfter: HttpRetryAfterPolicy.Respect,
|
|
113
|
+
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
114
|
+
};
|
|
115
|
+
case HttpRetryPreset.TransportTransient:
|
|
116
|
+
return {
|
|
117
|
+
preset,
|
|
118
|
+
attempts: 3,
|
|
119
|
+
methods: DEFAULT_PROXY_TRANSPORT_RETRY_METHODS,
|
|
120
|
+
statusCodes: [],
|
|
121
|
+
errorCodes: defaultErrorCodes,
|
|
122
|
+
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
123
|
+
baseDelayMs: 100,
|
|
124
|
+
maxDelayMs: 1_000,
|
|
125
|
+
jitter: HttpRetryJitter.Full,
|
|
126
|
+
retryAfter: HttpRetryAfterPolicy.Ignore,
|
|
127
|
+
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
throw createInvalidRetryPolicyError(`Unknown ${label} retry preset: ${preset}`, label);
|
|
131
|
+
}
|
|
132
|
+
function clampPositiveInteger(value, fallback, max) {
|
|
133
|
+
if (value === undefined)
|
|
134
|
+
return fallback;
|
|
135
|
+
if (!Number.isFinite(value) || value < 1)
|
|
136
|
+
return fallback;
|
|
137
|
+
return Math.min(Math.floor(value), max);
|
|
138
|
+
}
|
|
139
|
+
function clampDelay(value, fallback) {
|
|
140
|
+
if (value === undefined)
|
|
141
|
+
return fallback;
|
|
142
|
+
if (!Number.isFinite(value) || value < 0)
|
|
143
|
+
return fallback;
|
|
144
|
+
return Math.min(Math.floor(value), MAX_RETRY_DELAY_MS);
|
|
145
|
+
}
|
|
146
|
+
export function createDefaultProxyTransportRetryOptions(options = {}) {
|
|
147
|
+
return createRetryOptions(HttpRetryPreset.TransportTransient, options.extraErrorCodes ?? [], options.label ?? "Proxy transport");
|
|
148
|
+
}
|
|
149
|
+
export function normalizeProxyTransportRetryOptions(retry, options = {}) {
|
|
150
|
+
const label = options.label ?? "Proxy transport";
|
|
151
|
+
const extraErrorCodes = options.extraErrorCodes ?? [];
|
|
152
|
+
if (retry === undefined || retry === false) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
if (retry === true) {
|
|
156
|
+
return createRetryOptions(HttpRetryPreset.TransportTransient, extraErrorCodes, label);
|
|
157
|
+
}
|
|
158
|
+
if (typeof retry === "string") {
|
|
159
|
+
if (!hasOwnValue(HttpRetryPreset, retry)) {
|
|
160
|
+
throw createInvalidRetryPolicyError(`Unknown ${label} retry preset: ${retry}`, label);
|
|
161
|
+
}
|
|
162
|
+
return createRetryOptions(retry, extraErrorCodes, label);
|
|
163
|
+
}
|
|
164
|
+
if (typeof retry !== "object" || retry === null) {
|
|
165
|
+
throw createInvalidRetryPolicyError(`${label} retry policy must be an object`, label);
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(retry)) {
|
|
168
|
+
throw createInvalidRetryPolicyError(`${label} retry policy must be a plain object`, label);
|
|
169
|
+
}
|
|
170
|
+
validateRetryOptionsShape(retry, label);
|
|
171
|
+
const base = createRetryOptions(retry.preset ?? HttpRetryPreset.TransportTransient, extraErrorCodes, label);
|
|
172
|
+
const maxDelayMs = clampDelay(retry.maxDelayMs, base.maxDelayMs);
|
|
173
|
+
return {
|
|
174
|
+
preset: retry.preset ?? base.preset,
|
|
175
|
+
attempts: clampPositiveInteger(retry.attempts, base.attempts, MAX_PROXY_TRANSPORT_RETRY_ATTEMPTS),
|
|
176
|
+
methods: retry.methods?.map((method) => method.toUpperCase()) ?? base.methods,
|
|
177
|
+
statusCodes: retry.statusCodes?.filter((status) => Number.isInteger(status)) ?? base.statusCodes,
|
|
178
|
+
errorCodes: retry.errorCodes ?? base.errorCodes,
|
|
179
|
+
delayStrategy: retry.delayStrategy ?? base.delayStrategy,
|
|
180
|
+
baseDelayMs: clampDelay(retry.baseDelayMs, base.baseDelayMs),
|
|
181
|
+
maxDelayMs,
|
|
182
|
+
jitter: retry.jitter ?? base.jitter,
|
|
183
|
+
retryAfter: retry.retryAfter ?? base.retryAfter,
|
|
184
|
+
unsafeMethodPolicy: retry.unsafeMethodPolicy ?? base.unsafeMethodPolicy,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function validateRetryOptionsShape(retry, label) {
|
|
188
|
+
if (retry.preset !== undefined && !hasOwnValue(HttpRetryPreset, retry.preset)) {
|
|
189
|
+
throw createInvalidRetryPolicyError(`Unknown ${label} retry preset: ${String(retry.preset)}`, label);
|
|
190
|
+
}
|
|
191
|
+
if (retry.delayStrategy !== undefined &&
|
|
192
|
+
!hasOwnValue(HttpRetryDelayStrategy, retry.delayStrategy)) {
|
|
193
|
+
throw createInvalidRetryPolicyError(`Unknown ${label} retry delay strategy: ${String(retry.delayStrategy)}`, label);
|
|
194
|
+
}
|
|
195
|
+
if (retry.jitter !== undefined && !hasOwnValue(HttpRetryJitter, retry.jitter)) {
|
|
196
|
+
throw createInvalidRetryPolicyError(`Unknown ${label} retry jitter policy: ${String(retry.jitter)}`, label);
|
|
197
|
+
}
|
|
198
|
+
if (retry.retryAfter !== undefined && !hasOwnValue(HttpRetryAfterPolicy, retry.retryAfter)) {
|
|
199
|
+
throw createInvalidRetryPolicyError(`Unknown ${label} retry-after policy: ${String(retry.retryAfter)}`, label);
|
|
200
|
+
}
|
|
201
|
+
if (retry.unsafeMethodPolicy !== undefined &&
|
|
202
|
+
!hasOwnValue(HttpRetryUnsafeMethodPolicy, retry.unsafeMethodPolicy)) {
|
|
203
|
+
throw createInvalidRetryPolicyError(`Unknown ${label} retry unsafe method policy: ${String(retry.unsafeMethodPolicy)}`, label);
|
|
204
|
+
}
|
|
205
|
+
if (retry.methods !== undefined) {
|
|
206
|
+
if (!Array.isArray(retry.methods)) {
|
|
207
|
+
throw createInvalidRetryPolicyError(`${label} retry methods must be an array`, label);
|
|
208
|
+
}
|
|
209
|
+
const nonStringMethods = retry.methods.filter((method) => typeof method !== "string");
|
|
210
|
+
if (nonStringMethods.length > 0) {
|
|
211
|
+
throw createInvalidRetryPolicyError(`${label} retry methods must contain only strings`, label);
|
|
212
|
+
}
|
|
213
|
+
const unknownMethods = retry.methods
|
|
214
|
+
.map((method) => method.toUpperCase())
|
|
215
|
+
.filter((method) => !KNOWN_RETRY_METHODS.has(method));
|
|
216
|
+
if (unknownMethods.length > 0) {
|
|
217
|
+
throw createInvalidRetryPolicyError(`Unknown ${label} retry method(s): ${unknownMethods.join(", ")}`, label);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (retry.statusCodes !== undefined) {
|
|
221
|
+
if (!Array.isArray(retry.statusCodes)) {
|
|
222
|
+
throw createInvalidRetryPolicyError(`${label} retry statusCodes must be an array`, label);
|
|
223
|
+
}
|
|
224
|
+
const invalidStatusCodes = retry.statusCodes.filter((status) => !Number.isInteger(status) || Number(status) < 100 || Number(status) > 599);
|
|
225
|
+
if (invalidStatusCodes.length > 0) {
|
|
226
|
+
throw createInvalidRetryPolicyError(`${label} retry statusCodes must contain HTTP status integers in [100, 599]`, label);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (retry.errorCodes !== undefined) {
|
|
230
|
+
if (!Array.isArray(retry.errorCodes)) {
|
|
231
|
+
throw createInvalidRetryPolicyError(`${label} retry errorCodes must be an array`, label);
|
|
232
|
+
}
|
|
233
|
+
const nonStringErrorCodes = retry.errorCodes.filter((errorCode) => typeof errorCode !== "string");
|
|
234
|
+
if (nonStringErrorCodes.length > 0) {
|
|
235
|
+
throw createInvalidRetryPolicyError(`${label} retry errorCodes must contain only strings`, label);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (retry.preset === HttpRetryPreset.Off &&
|
|
239
|
+
((retry.attempts !== undefined && retry.attempts > 1) ||
|
|
240
|
+
(retry.statusCodes !== undefined && retry.statusCodes.length > 0))) {
|
|
241
|
+
throw createInvalidRetryPolicyError(`${label} retry preset off cannot be combined with retry-enabling overrides`, label);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
export function validateUnsafeProxyTransportRetryMethods(options, label = "Proxy transport") {
|
|
245
|
+
if (options.unsafeMethodPolicy === HttpRetryUnsafeMethodPolicy.AllowExplicitUnsafe) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const unsafeMethods = options.methods.filter((method) => UNSAFE_RETRY_METHODS.has(method.toUpperCase()));
|
|
249
|
+
if (unsafeMethods.length === 0)
|
|
250
|
+
return;
|
|
251
|
+
throw new ProviderError(`${label} retry methods include unsafe method(s): ${unsafeMethods.join(", ")}`, { code: "retry_unsafe_method" });
|
|
252
|
+
}
|
|
253
|
+
export function isProxyTransportRetryMethod(method, options) {
|
|
254
|
+
return options.methods
|
|
255
|
+
.map((allowedMethod) => allowedMethod.toUpperCase())
|
|
256
|
+
.includes(method.toUpperCase());
|
|
257
|
+
}
|
|
258
|
+
export function shouldRetryProxyTransportError(error, options) {
|
|
259
|
+
const code = retryErrorCode(error);
|
|
260
|
+
return Boolean(code && options.errorCodes.includes(code));
|
|
261
|
+
}
|
|
262
|
+
export function shouldRetryProxyTransportAttempt(input) {
|
|
263
|
+
const { error, explicitRetry, method, options, proxyUsed } = input;
|
|
264
|
+
if (!options || options.attempts <= 1)
|
|
265
|
+
return false;
|
|
266
|
+
if (!explicitRetry && !proxyUsed)
|
|
267
|
+
return false;
|
|
268
|
+
return (isProxyTransportRetryMethod(method, options) && shouldRetryProxyTransportError(error, options));
|
|
269
|
+
}
|
|
270
|
+
function retryAfterHeader(headers) {
|
|
271
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
272
|
+
if (name.toLowerCase() === "retry-after")
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
function parseRetryAfterMs(headers, now = Date.now()) {
|
|
278
|
+
const value = retryAfterHeader(headers);
|
|
279
|
+
if (!value)
|
|
280
|
+
return undefined;
|
|
281
|
+
const seconds = Number(value);
|
|
282
|
+
if (Number.isFinite(seconds)) {
|
|
283
|
+
return Math.max(0, Math.floor(seconds * 1_000));
|
|
284
|
+
}
|
|
285
|
+
const dateMs = Date.parse(value);
|
|
286
|
+
if (!Number.isNaN(dateMs)) {
|
|
287
|
+
return Math.max(0, dateMs - now);
|
|
288
|
+
}
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
export function computeProxyTransportRetryDelayMs(options, attemptIndex, headers) {
|
|
292
|
+
const multiplier = options.delayStrategy === HttpRetryDelayStrategy.Exponential
|
|
293
|
+
? 2 ** Math.max(0, attemptIndex - 1)
|
|
294
|
+
: 1;
|
|
295
|
+
const configuredDelay = Math.min(options.baseDelayMs * multiplier, options.maxDelayMs);
|
|
296
|
+
const retryAfterMs = options.retryAfter === HttpRetryAfterPolicy.Ignore
|
|
297
|
+
? undefined
|
|
298
|
+
: headers
|
|
299
|
+
? parseRetryAfterMs(headers)
|
|
300
|
+
: undefined;
|
|
301
|
+
if (retryAfterMs !== undefined) {
|
|
302
|
+
const boundedRetryAfterMs = Math.min(retryAfterMs, options.maxDelayMs);
|
|
303
|
+
if (options.retryAfter === HttpRetryAfterPolicy.Cap) {
|
|
304
|
+
return Math.min(boundedRetryAfterMs, configuredDelay);
|
|
305
|
+
}
|
|
306
|
+
return boundedRetryAfterMs;
|
|
307
|
+
}
|
|
308
|
+
switch (options.jitter) {
|
|
309
|
+
case HttpRetryJitter.None:
|
|
310
|
+
return configuredDelay;
|
|
311
|
+
case HttpRetryJitter.Equal:
|
|
312
|
+
return Math.floor(configuredDelay / 2 + Math.random() * (configuredDelay / 2));
|
|
313
|
+
case HttpRetryJitter.Full:
|
|
314
|
+
return Math.floor(Math.random() * configuredDelay);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
export function normalizeProxyAttemptIndex(value) {
|
|
318
|
+
if (value === undefined || !Number.isFinite(value))
|
|
319
|
+
return 0;
|
|
320
|
+
return Math.max(0, Math.floor(value));
|
|
321
|
+
}
|
|
322
|
+
export function computeProxyAttemptIndex(options) {
|
|
323
|
+
return (normalizeProxyAttemptIndex(options.baseProxyAttempt) +
|
|
324
|
+
normalizeProxyAttemptIndex(options.proxyAttemptOffset) +
|
|
325
|
+
normalizeProxyAttemptIndex(options.retryAttemptOffset));
|
|
326
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { ProxyAttemptTelemetryEvent, ProxyResolutionTelemetryEvent, ProxyTelemetrySink } from "../config/loader";
|
|
1
|
+
import type { ProxyAttemptTelemetryEvent, ProxyResolutionTelemetryEvent, ProxyTelemetrySink, ProxyVendorFailoverTelemetryEvent } from "../config/loader.js";
|
|
2
2
|
export declare const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
|
|
3
3
|
export declare class ProxyTelemetryCollector implements ProxyTelemetrySink {
|
|
4
4
|
#private;
|
|
5
5
|
recordProxyResolution(event: ProxyResolutionTelemetryEvent): void;
|
|
6
|
+
recordProxyVendorFailover(event: ProxyVendorFailoverTelemetryEvent): void;
|
|
6
7
|
recordProxyAttempt(event: ProxyAttemptTelemetryEvent): void;
|
|
7
8
|
toHeaderValue(): string | undefined;
|
|
8
9
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
|
|
2
2
|
const MAX_HEADER_BYTES = 4_096;
|
|
3
3
|
const MAX_PROXY_ATTEMPT_SAMPLES = 24;
|
|
4
|
+
const MAX_PROXY_FAILOVER_SAMPLES = 12;
|
|
4
5
|
const CACHE_STATUS_SEVERITY = {
|
|
5
6
|
disabled: 0,
|
|
6
7
|
memory_hit: 1,
|
|
@@ -20,9 +21,7 @@ function maxOptional(left, right) {
|
|
|
20
21
|
return values.length > 0 ? Math.max(...values) : undefined;
|
|
21
22
|
}
|
|
22
23
|
function worseStatus(left, right) {
|
|
23
|
-
return CACHE_STATUS_SEVERITY[right] > CACHE_STATUS_SEVERITY[left]
|
|
24
|
-
? right
|
|
25
|
-
: left;
|
|
24
|
+
return CACHE_STATUS_SEVERITY[right] > CACHE_STATUS_SEVERITY[left] ? right : left;
|
|
26
25
|
}
|
|
27
26
|
function encodeBase64Url(value) {
|
|
28
27
|
return Buffer.from(value, "utf8").toString("base64url");
|
|
@@ -30,15 +29,15 @@ function encodeBase64Url(value) {
|
|
|
30
29
|
export class ProxyTelemetryCollector {
|
|
31
30
|
#events = [];
|
|
32
31
|
#attempts = [];
|
|
32
|
+
#failovers = [];
|
|
33
33
|
recordProxyResolution(event) {
|
|
34
34
|
this.#events.push({
|
|
35
|
-
provider:
|
|
35
|
+
provider: event.provider,
|
|
36
|
+
...(event.protocol ? { protocol: event.protocol } : {}),
|
|
36
37
|
cacheStatus: event.cacheStatus,
|
|
37
38
|
cacheHit: event.cacheHit,
|
|
38
39
|
resolutionMs: Math.max(0, Math.floor(event.resolutionMs)),
|
|
39
|
-
allocatorMs: event.allocatorMs === undefined
|
|
40
|
-
? undefined
|
|
41
|
-
: Math.max(0, Math.floor(event.allocatorMs)),
|
|
40
|
+
allocatorMs: event.allocatorMs === undefined ? undefined : Math.max(0, Math.floor(event.allocatorMs)),
|
|
42
41
|
allocatorStatus: event.allocatorStatus === undefined
|
|
43
42
|
? undefined
|
|
44
43
|
: Math.max(0, Math.floor(event.allocatorStatus)),
|
|
@@ -46,32 +45,33 @@ export class ProxyTelemetryCollector {
|
|
|
46
45
|
allocatorAttempts: event.allocatorAttempts === undefined
|
|
47
46
|
? undefined
|
|
48
47
|
: Math.max(1, Math.floor(event.allocatorAttempts)),
|
|
49
|
-
lockWaitMs: event.lockWaitMs === undefined
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
? undefined
|
|
54
|
-
: Math.max(0, Math.floor(event.redisReadMs)),
|
|
55
|
-
redisWriteMs: event.redisWriteMs === undefined
|
|
56
|
-
? undefined
|
|
57
|
-
: Math.max(0, Math.floor(event.redisWriteMs)),
|
|
58
|
-
poolAgeMs: event.poolAgeMs === undefined
|
|
59
|
-
? undefined
|
|
60
|
-
: Math.max(0, Math.floor(event.poolAgeMs)),
|
|
48
|
+
lockWaitMs: event.lockWaitMs === undefined ? undefined : Math.max(0, Math.floor(event.lockWaitMs)),
|
|
49
|
+
redisReadMs: event.redisReadMs === undefined ? undefined : Math.max(0, Math.floor(event.redisReadMs)),
|
|
50
|
+
redisWriteMs: event.redisWriteMs === undefined ? undefined : Math.max(0, Math.floor(event.redisWriteMs)),
|
|
51
|
+
poolAgeMs: event.poolAgeMs === undefined ? undefined : Math.max(0, Math.floor(event.poolAgeMs)),
|
|
61
52
|
poolExpiresInMs: event.poolExpiresInMs === undefined
|
|
62
53
|
? undefined
|
|
63
54
|
: Math.max(0, Math.floor(event.poolExpiresInMs)),
|
|
64
55
|
attempts: Math.max(1, Math.floor(event.attempts || 1)),
|
|
65
|
-
refreshes: event.refreshes === undefined
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
refreshes: event.refreshes === undefined ? undefined : Math.max(0, Math.floor(event.refreshes)),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
recordProxyVendorFailover(event) {
|
|
60
|
+
if (this.#failovers.length >= MAX_PROXY_FAILOVER_SAMPLES)
|
|
61
|
+
return;
|
|
62
|
+
this.#failovers.push({
|
|
63
|
+
vendor: event.vendor,
|
|
64
|
+
...(event.nextVendor ? { nextVendor: event.nextVendor } : {}),
|
|
65
|
+
phase: event.phase,
|
|
66
|
+
reason: event.reason,
|
|
67
|
+
...(event.attempt === undefined ? {} : { attempt: Math.max(0, Math.floor(event.attempt)) }),
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
recordProxyAttempt(event) {
|
|
71
71
|
if (this.#attempts.length >= MAX_PROXY_ATTEMPT_SAMPLES)
|
|
72
72
|
return;
|
|
73
73
|
this.#attempts.push({
|
|
74
|
-
provider:
|
|
74
|
+
provider: event.provider,
|
|
75
75
|
attempt: Math.max(1, Math.floor(event.attempt || 1)),
|
|
76
76
|
...(event.poolIndex === undefined
|
|
77
77
|
? {}
|
|
@@ -79,9 +79,7 @@ export class ProxyTelemetryCollector {
|
|
|
79
79
|
...(event.proxyHash ? { proxyHash: event.proxyHash.slice(0, 16) } : {}),
|
|
80
80
|
outcome: event.outcome === "ok" ? "ok" : "error",
|
|
81
81
|
...(event.errorCode ? { errorCode: event.errorCode.slice(0, 80) } : {}),
|
|
82
|
-
...(event.status === undefined
|
|
83
|
-
? {}
|
|
84
|
-
: { status: Math.max(0, Math.floor(event.status)) }),
|
|
82
|
+
...(event.status === undefined ? {} : { status: Math.max(0, Math.floor(event.status)) }),
|
|
85
83
|
...(event.durationMs === undefined
|
|
86
84
|
? {}
|
|
87
85
|
: { durationMs: Math.max(0, Math.floor(event.durationMs)) }),
|
|
@@ -91,8 +89,16 @@ export class ProxyTelemetryCollector {
|
|
|
91
89
|
const [first, ...rest] = this.#events;
|
|
92
90
|
if (!first)
|
|
93
91
|
return undefined;
|
|
92
|
+
// The serving vendor/protocol is the last recorded resolution (a failed
|
|
93
|
+
// vendor records first, the vendor that served records last).
|
|
94
|
+
const serving = this.#events[this.#events.length - 1] ?? first;
|
|
95
|
+
const vendors = [];
|
|
96
|
+
for (const event of this.#events) {
|
|
97
|
+
if (!vendors.includes(event.provider))
|
|
98
|
+
vendors.push(event.provider);
|
|
99
|
+
}
|
|
94
100
|
const aggregate = rest.reduce((acc, event) => ({
|
|
95
|
-
provider:
|
|
101
|
+
provider: event.provider,
|
|
96
102
|
cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
|
|
97
103
|
cacheHit: acc.cacheHit && event.cacheHit,
|
|
98
104
|
resolutionMs: acc.resolutionMs + event.resolutionMs,
|
|
@@ -111,13 +117,12 @@ export class ProxyTelemetryCollector {
|
|
|
111
117
|
const payload = {
|
|
112
118
|
v: 1,
|
|
113
119
|
proxy: {
|
|
114
|
-
provider:
|
|
120
|
+
provider: serving.provider,
|
|
121
|
+
...(serving.protocol ? { protocol: serving.protocol } : {}),
|
|
115
122
|
cacheStatus: aggregate.cacheStatus,
|
|
116
123
|
cacheHit: aggregate.cacheHit,
|
|
117
124
|
resolutionMs: aggregate.resolutionMs,
|
|
118
|
-
...(aggregate.allocatorMs !== undefined
|
|
119
|
-
? { allocatorMs: aggregate.allocatorMs }
|
|
120
|
-
: {}),
|
|
125
|
+
...(aggregate.allocatorMs !== undefined ? { allocatorMs: aggregate.allocatorMs } : {}),
|
|
121
126
|
...(aggregate.allocatorStatus !== undefined
|
|
122
127
|
? { allocatorStatus: aggregate.allocatorStatus }
|
|
123
128
|
: {}),
|
|
@@ -127,40 +132,38 @@ export class ProxyTelemetryCollector {
|
|
|
127
132
|
...(aggregate.allocatorAttempts !== undefined
|
|
128
133
|
? { allocatorAttempts: aggregate.allocatorAttempts }
|
|
129
134
|
: {}),
|
|
130
|
-
...(aggregate.lockWaitMs !== undefined
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
...(aggregate.
|
|
134
|
-
? { redisReadMs: aggregate.redisReadMs }
|
|
135
|
-
: {}),
|
|
136
|
-
...(aggregate.redisWriteMs !== undefined
|
|
137
|
-
? { redisWriteMs: aggregate.redisWriteMs }
|
|
138
|
-
: {}),
|
|
139
|
-
...(aggregate.poolAgeMs !== undefined
|
|
140
|
-
? { poolAgeMs: aggregate.poolAgeMs }
|
|
141
|
-
: {}),
|
|
135
|
+
...(aggregate.lockWaitMs !== undefined ? { lockWaitMs: aggregate.lockWaitMs } : {}),
|
|
136
|
+
...(aggregate.redisReadMs !== undefined ? { redisReadMs: aggregate.redisReadMs } : {}),
|
|
137
|
+
...(aggregate.redisWriteMs !== undefined ? { redisWriteMs: aggregate.redisWriteMs } : {}),
|
|
138
|
+
...(aggregate.poolAgeMs !== undefined ? { poolAgeMs: aggregate.poolAgeMs } : {}),
|
|
142
139
|
...(aggregate.poolExpiresInMs !== undefined
|
|
143
140
|
? { poolExpiresInMs: aggregate.poolExpiresInMs }
|
|
144
141
|
: {}),
|
|
145
142
|
attempts: aggregate.attempts,
|
|
146
|
-
...(aggregate.refreshes !== undefined
|
|
147
|
-
? { refreshes: aggregate.refreshes }
|
|
148
|
-
: {}),
|
|
143
|
+
...(aggregate.refreshes !== undefined ? { refreshes: aggregate.refreshes } : {}),
|
|
149
144
|
...(this.#attempts.length > 0
|
|
150
145
|
? {
|
|
151
146
|
attemptSamples: this.#attempts.map((attempt, index) => ({
|
|
152
147
|
n: index + 1,
|
|
153
148
|
a: attempt.attempt,
|
|
154
|
-
...(attempt.poolIndex === undefined
|
|
155
|
-
? {}
|
|
156
|
-
: { i: attempt.poolIndex }),
|
|
149
|
+
...(attempt.poolIndex === undefined ? {} : { i: attempt.poolIndex }),
|
|
157
150
|
...(attempt.proxyHash ? { h: attempt.proxyHash } : {}),
|
|
158
151
|
o: attempt.outcome,
|
|
159
152
|
...(attempt.errorCode ? { c: attempt.errorCode } : {}),
|
|
160
153
|
...(attempt.status === undefined ? {} : { s: attempt.status }),
|
|
161
|
-
...(attempt.durationMs === undefined
|
|
162
|
-
|
|
163
|
-
|
|
154
|
+
...(attempt.durationMs === undefined ? {} : { d: attempt.durationMs }),
|
|
155
|
+
})),
|
|
156
|
+
}
|
|
157
|
+
: {}),
|
|
158
|
+
...(vendors.length > 1 ? { vendors } : {}),
|
|
159
|
+
...(this.#failovers.length > 0
|
|
160
|
+
? {
|
|
161
|
+
failovers: this.#failovers.map((failover) => ({
|
|
162
|
+
v: failover.vendor,
|
|
163
|
+
...(failover.nextVendor ? { nx: failover.nextVendor } : {}),
|
|
164
|
+
p: failover.phase,
|
|
165
|
+
r: failover.reason,
|
|
166
|
+
...(failover.attempt === undefined ? {} : { a: failover.attempt }),
|
|
164
167
|
})),
|
|
165
168
|
}
|
|
166
169
|
: {}),
|
package/dist/runtime/redis.d.ts
CHANGED
package/dist/runtime/redis.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Redis from "ioredis";
|
|
1
|
+
import { Redis } from "ioredis";
|
|
2
2
|
export function createProviderRedisClient(options) {
|
|
3
3
|
const redis = new Redis(options.redisUrl, {
|
|
4
4
|
connectTimeout: options.timeoutMs,
|
|
@@ -23,9 +23,7 @@ export async function withRedisTimeout(operation, options) {
|
|
|
23
23
|
}
|
|
24
24
|
}, options.timeoutMs);
|
|
25
25
|
});
|
|
26
|
-
const operationResult = options.onError
|
|
27
|
-
? operation().catch(options.onError)
|
|
28
|
-
: operation();
|
|
26
|
+
const operationResult = options.onError ? operation().catch(options.onError) : operation();
|
|
29
27
|
return await Promise.race([operationResult, timeout]);
|
|
30
28
|
}
|
|
31
29
|
finally {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { RequestParams } from "../types";
|
|
1
|
+
import type { RequestParams } from "../types.js";
|
|
2
2
|
export declare function appendQueryParams(url: string, params?: RequestParams): string;
|
|
3
3
|
export declare function normalizeHttpRequestBody(body: unknown): string | Buffer | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { EnvContext, ProviderDefinition } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical error code for a declared-but-unprovisioned provider secret.
|
|
4
|
+
*
|
|
5
|
+
* The SDK is the single source of truth for env/secret presence validation:
|
|
6
|
+
* providers declare secrets in `defineProvider({ secrets: [...] })` and the
|
|
7
|
+
* runtime enforces presence before any handler or auth-flow code runs.
|
|
8
|
+
* Provider-local presence guards (requireServiceKey/requireApiKey style) are a
|
|
9
|
+
* deprecated antipattern — see the `sdk-owned-secret-presence` submit-check
|
|
10
|
+
* rule.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MISSING_SECRET_CODE = "MISSING_SECRET";
|
|
13
|
+
/**
|
|
14
|
+
* Names of declared `required: true` secrets whose env values are unset or
|
|
15
|
+
* whitespace-only. Whitespace-only values count as missing for parity with the
|
|
16
|
+
* `.trim()` guards well-built providers used before the SDK owned this check —
|
|
17
|
+
* a blank value provisioned by a broken secret pipeline must not pass the gate.
|
|
18
|
+
*/
|
|
19
|
+
export declare function listMissingRequiredSecrets(provider: ProviderDefinition, env: EnvContext): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Throws the canonical structured missing-secret error when any declared
|
|
22
|
+
* `required: true` secret is absent. All missing names are reported in a
|
|
23
|
+
* single error so operators can provision the full set in one pass instead of
|
|
24
|
+
* discovering them one deploy at a time (the 2026-07-22 unprovisioned-secret
|
|
25
|
+
* incident failure mode).
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertRequiredSecretsPresent(provider: ProviderDefinition, env: EnvContext): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ProviderSecretError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical error code for a declared-but-unprovisioned provider secret.
|
|
4
|
+
*
|
|
5
|
+
* The SDK is the single source of truth for env/secret presence validation:
|
|
6
|
+
* providers declare secrets in `defineProvider({ secrets: [...] })` and the
|
|
7
|
+
* runtime enforces presence before any handler or auth-flow code runs.
|
|
8
|
+
* Provider-local presence guards (requireServiceKey/requireApiKey style) are a
|
|
9
|
+
* deprecated antipattern — see the `sdk-owned-secret-presence` submit-check
|
|
10
|
+
* rule.
|
|
11
|
+
*/
|
|
12
|
+
export const MISSING_SECRET_CODE = "MISSING_SECRET";
|
|
13
|
+
/**
|
|
14
|
+
* Names of declared `required: true` secrets whose env values are unset or
|
|
15
|
+
* whitespace-only. Whitespace-only values count as missing for parity with the
|
|
16
|
+
* `.trim()` guards well-built providers used before the SDK owned this check —
|
|
17
|
+
* a blank value provisioned by a broken secret pipeline must not pass the gate.
|
|
18
|
+
*/
|
|
19
|
+
export function listMissingRequiredSecrets(provider, env) {
|
|
20
|
+
const missing = [];
|
|
21
|
+
for (const secret of provider.secrets ?? []) {
|
|
22
|
+
if (secret.required !== true) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const value = env.get(secret.name);
|
|
26
|
+
if (value === undefined || value.trim() === "") {
|
|
27
|
+
missing.push(secret.name);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return missing;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Throws the canonical structured missing-secret error when any declared
|
|
34
|
+
* `required: true` secret is absent. All missing names are reported in a
|
|
35
|
+
* single error so operators can provision the full set in one pass instead of
|
|
36
|
+
* discovering them one deploy at a time (the 2026-07-22 unprovisioned-secret
|
|
37
|
+
* incident failure mode).
|
|
38
|
+
*/
|
|
39
|
+
export function assertRequiredSecretsPresent(provider, env) {
|
|
40
|
+
const missing = listMissingRequiredSecrets(provider, env);
|
|
41
|
+
if (missing.length === 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const names = missing.join(", ");
|
|
45
|
+
throw new ProviderSecretError(`Missing required provider secret${missing.length > 1 ? "s" : ""}: ${names}`, {
|
|
46
|
+
code: MISSING_SECRET_CODE,
|
|
47
|
+
category: "credential_unavailable",
|
|
48
|
+
retryable: false,
|
|
49
|
+
fix: `Provision ${names} in the provider environment (e.g. Doppler). Declared in defineProvider({ secrets: [...] }).`,
|
|
50
|
+
});
|
|
51
|
+
}
|
package/dist/runtime/state.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ProviderError } from "../errors";
|
|
2
|
-
import type { ProviderRuntimeState } from "../types";
|
|
1
|
+
import { ProviderError } from "../errors.js";
|
|
2
|
+
import type { ProviderRuntimeState } from "../types.js";
|
|
3
3
|
type RedisProviderRuntimeStateOptions = {
|
|
4
4
|
readonly redisUrl: string;
|
|
5
5
|
readonly providerId?: string;
|