@apifuse/provider-sdk 2.2.0-beta.62 → 2.2.0-beta.63
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 +268 -9
- package/CHANGELOG.md +4 -0
- package/README.md +1 -0
- package/bin/apifuse-check.ts +84 -2
- package/bin/apifuse-dev.ts +11 -31
- package/bin/apifuse-migrate-deployment.ts +200 -0
- package/bin/apifuse-pack-check.ts +11 -4
- package/bin/apifuse-pack-types.ts +187 -4
- package/bin/apifuse-record.ts +44 -32
- package/bin/apifuse-test.ts +3 -1
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +11 -0
- package/dist/cli/deployment-intent.d.ts +178 -0
- package/dist/cli/deployment-intent.js +277 -0
- package/dist/cli/migrate-deployment-intent.d.ts +132 -0
- package/dist/cli/migrate-deployment-intent.js +1552 -0
- package/dist/cli/migrate-provider-shape.d.ts +5 -1
- package/dist/cli/migrate-provider-shape.js +4 -2
- package/dist/cli/resolver-runtime.d.ts +21 -0
- package/dist/cli/resolver-runtime.js +31 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +5 -0
- package/dist/dev.d.ts +3 -1
- package/dist/dev.js +6 -1
- package/dist/engine.d.ts +35 -1
- package/dist/engine.js +21 -2
- package/dist/error-resolution.d.ts +1 -0
- package/dist/error-resolution.js +54 -0
- package/dist/errors.d.ts +39 -1
- package/dist/errors.js +50 -0
- package/dist/i18n/error-messages.d.ts +86 -0
- package/dist/i18n/error-messages.js +192 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/keys.d.ts +12 -0
- package/dist/i18n/keys.js +76 -5
- package/dist/index.d.ts +7 -1
- package/dist/index.js +6 -0
- package/dist/lint.d.ts +10 -0
- package/dist/lint.js +412 -49
- package/dist/provider.d.ts +5 -5
- package/dist/provider.js +2 -2
- package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
- package/dist/runtime/browser-telemetry-binding.js +83 -0
- package/dist/runtime/browser-telemetry.d.ts +76 -0
- package/dist/runtime/browser-telemetry.js +165 -0
- package/dist/runtime/browser.d.ts +3 -0
- package/dist/runtime/browser.js +208 -47
- package/dist/runtime/cache-telemetry.d.ts +71 -0
- package/dist/runtime/cache-telemetry.js +177 -0
- package/dist/runtime/cache.js +67 -7
- package/dist/runtime/capability-response.d.ts +3 -0
- package/dist/runtime/capability-response.js +56 -0
- package/dist/runtime/capability-telemetry.d.ts +46 -0
- package/dist/runtime/capability-telemetry.js +68 -0
- package/dist/runtime/diagnostic-env.d.ts +3 -0
- package/dist/runtime/diagnostic-env.js +10 -1
- package/dist/runtime/engine-mode.d.ts +74 -0
- package/dist/runtime/engine-mode.js +152 -0
- package/dist/runtime/http-telemetry-guard.d.ts +10 -0
- package/dist/runtime/http-telemetry-guard.js +62 -10
- package/dist/runtime/http.js +141 -35
- package/dist/runtime/instrumentation.js +92 -13
- package/dist/runtime/ocr-telemetry.d.ts +82 -0
- package/dist/runtime/ocr-telemetry.js +192 -0
- package/dist/runtime/ocr.js +30 -8
- package/dist/runtime/request-telemetry.d.ts +2 -0
- package/dist/runtime/request-telemetry.js +2 -1
- package/dist/runtime/resolver-runtime-options.d.ts +14 -0
- package/dist/runtime/resolver-runtime-options.js +33 -0
- package/dist/runtime/resolver-shared.d.ts +3 -2
- package/dist/runtime/resolver-shared.js +4 -4
- package/dist/runtime/resolver-telemetry.d.ts +1 -0
- package/dist/runtime/resolver-telemetry.js +1 -1
- package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
- package/dist/runtime/resolver-vendors/browser.js +46 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
- package/dist/runtime/resolver-vendors/hosts.js +35 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
- package/dist/runtime/resolver-vendors/types.d.ts +5 -4
- package/dist/runtime/resolver.d.ts +4 -1
- package/dist/runtime/resolver.js +46 -13
- package/dist/runtime/state-telemetry.d.ts +79 -0
- package/dist/runtime/state-telemetry.js +194 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +26 -7
- package/dist/runtime/stealth-cookies.d.ts +5 -1
- package/dist/runtime/stealth-cookies.js +21 -1
- package/dist/runtime/stealth-owned-headers.d.ts +26 -0
- package/dist/runtime/stealth-owned-headers.js +37 -0
- package/dist/runtime/stealth-telemetry.d.ts +92 -0
- package/dist/runtime/stealth-telemetry.js +179 -0
- package/dist/runtime/stealth.d.ts +4 -1
- package/dist/runtime/stealth.js +228 -85
- package/dist/runtime/stt-telemetry.d.ts +77 -0
- package/dist/runtime/stt-telemetry.js +176 -0
- package/dist/runtime/stt.js +15 -6
- package/dist/server/error-stack-frames.d.ts +35 -0
- package/dist/server/error-stack-frames.js +219 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +4 -0
- package/dist/server/sdk-version.d.ts +3 -0
- package/dist/server/sdk-version.js +19 -0
- package/dist/server/self-test.js +1 -18
- package/dist/server/sensitive-values.d.ts +1 -1
- package/dist/server/sensitive-values.js +9 -0
- package/dist/server/serve-implementation.d.ts +51 -2
- package/dist/server/serve-implementation.js +337 -90
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +7 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
- package/dist/stateful/stateful-provider-runtime-context.js +1 -0
- package/dist/types.d.ts +94 -1
- package/package.json +6 -1
- package/src/cli/commands.ts +14 -0
- package/src/cli/deployment-intent.ts +364 -0
- package/src/cli/migrate-deployment-intent.ts +1908 -0
- package/src/cli/migrate-provider-shape.ts +10 -4
- package/src/cli/resolver-runtime.ts +72 -0
- package/src/define.ts +13 -0
- package/src/dev.ts +9 -2
- package/src/engine.ts +38 -2
- package/src/error-resolution.ts +58 -0
- package/src/errors.ts +77 -1
- package/src/i18n/error-messages.ts +255 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/keys.ts +80 -6
- package/src/index.ts +72 -0
- package/src/lint.ts +522 -50
- package/src/provider.ts +10 -0
- package/src/runtime/browser-telemetry-binding.ts +92 -0
- package/src/runtime/browser-telemetry.ts +289 -0
- package/src/runtime/browser.ts +249 -65
- package/src/runtime/cache-telemetry.ts +247 -0
- package/src/runtime/cache.ts +68 -17
- package/src/runtime/capability-response.ts +53 -0
- package/src/runtime/capability-telemetry.ts +101 -0
- package/src/runtime/diagnostic-env.ts +11 -1
- package/src/runtime/engine-mode.ts +233 -0
- package/src/runtime/http-telemetry-guard.ts +66 -13
- package/src/runtime/http.ts +168 -37
- package/src/runtime/instrumentation.ts +105 -16
- package/src/runtime/ocr-telemetry.ts +308 -0
- package/src/runtime/ocr.ts +109 -74
- package/src/runtime/request-telemetry.ts +2 -1
- package/src/runtime/resolver-runtime-options.ts +50 -0
- package/src/runtime/resolver-shared.ts +9 -4
- package/src/runtime/resolver-telemetry.ts +1 -1
- package/src/runtime/resolver-vendors/browser.ts +65 -7
- package/src/runtime/resolver-vendors/hosts.ts +43 -0
- package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
- package/src/runtime/resolver-vendors/types.ts +5 -4
- package/src/runtime/resolver.ts +64 -21
- package/src/runtime/state-telemetry.ts +298 -0
- package/src/runtime/state.ts +46 -45
- package/src/runtime/stealth-cookies.ts +24 -1
- package/src/runtime/stealth-owned-headers.ts +41 -0
- package/src/runtime/stealth-telemetry.ts +304 -0
- package/src/runtime/stealth.ts +355 -150
- package/src/runtime/stt-telemetry.ts +282 -0
- package/src/runtime/stt.ts +93 -71
- package/src/server/error-stack-frames.ts +214 -0
- package/src/server/index.ts +53 -0
- package/src/server/sdk-version.ts +22 -0
- package/src/server/self-test.ts +1 -21
- package/src/server/sensitive-values.ts +14 -0
- package/src/server/serve-implementation.ts +569 -108
- package/src/server/types.ts +7 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
- package/src/stateful/stateful-provider-runtime-context.ts +4 -0
- package/src/types.ts +99 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { closedEnum, } from "./request-telemetry.js";
|
|
3
|
+
import { createTelemetryObserver } from "./http-telemetry-guard.js";
|
|
4
|
+
const cacheTelemetryBindings = new WeakMap();
|
|
5
|
+
/** Internal construction binding; keeps sink plumbing off ProviderCacheOptions. */
|
|
6
|
+
export function bindCacheTelemetry(options, sink) {
|
|
7
|
+
if (sink)
|
|
8
|
+
cacheTelemetryBindings.set(options, sink);
|
|
9
|
+
return options;
|
|
10
|
+
}
|
|
11
|
+
/** Internal lookup used only by the cache factory. */
|
|
12
|
+
export function cacheTelemetryFor(options) {
|
|
13
|
+
return cacheTelemetryBindings.get(options);
|
|
14
|
+
}
|
|
15
|
+
export function guardCacheTelemetry(sink) {
|
|
16
|
+
const observe = createTelemetryObserver(sink);
|
|
17
|
+
return {
|
|
18
|
+
setRedisConfigured: (configured) => observe(() => sink?.setRedisConfigured?.(configured)),
|
|
19
|
+
recordLookup: (meta) => observe(() => sink?.recordLookup(meta)),
|
|
20
|
+
recordWrite: () => observe(() => sink?.recordWrite()),
|
|
21
|
+
recordLoaderError: () => observe(() => sink?.recordLoaderError?.()),
|
|
22
|
+
recordWriteError: () => observe(() => sink?.recordWriteError?.()),
|
|
23
|
+
recordRedisFallback: () => observe(() => sink?.recordRedisFallback()),
|
|
24
|
+
recordRedisRoundTrip: () => observe(() => sink?.recordRedisRoundTrip?.()),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const MAX_SAMPLES = 24;
|
|
28
|
+
const MAX_TEXT = 300;
|
|
29
|
+
function integer(value) {
|
|
30
|
+
return Number.isFinite(value)
|
|
31
|
+
? Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, Math.floor(value)))
|
|
32
|
+
: Number.MAX_SAFE_INTEGER;
|
|
33
|
+
}
|
|
34
|
+
function add(a, b) {
|
|
35
|
+
return Math.min(Number.MAX_SAFE_INTEGER, integer(a) + integer(b));
|
|
36
|
+
}
|
|
37
|
+
function boundedText(value, redact) {
|
|
38
|
+
let result;
|
|
39
|
+
try {
|
|
40
|
+
result = redact?.(value) ?? value;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
result = "[REDACTION_FAILED]";
|
|
44
|
+
}
|
|
45
|
+
const length = Math.min(result.length, MAX_TEXT);
|
|
46
|
+
const units = Buffer.alloc(length * 2);
|
|
47
|
+
for (let index = 0; index < length; index += 1)
|
|
48
|
+
units.writeUInt16LE(result.charCodeAt(index), index * 2);
|
|
49
|
+
return units.toString("utf16le");
|
|
50
|
+
}
|
|
51
|
+
export class CacheTelemetryCollector {
|
|
52
|
+
key = "cache";
|
|
53
|
+
#redact;
|
|
54
|
+
#telemetryFailed = false;
|
|
55
|
+
#lookups = 0;
|
|
56
|
+
#hits = 0;
|
|
57
|
+
#stale = 0;
|
|
58
|
+
#misses = 0;
|
|
59
|
+
#writes = 0;
|
|
60
|
+
#loaderErrors = 0;
|
|
61
|
+
#writeErrors = 0;
|
|
62
|
+
#source = { memory: 0, redis: 0, loader: 0 };
|
|
63
|
+
#redisFallbacks = 0;
|
|
64
|
+
#configured;
|
|
65
|
+
#redisRoundTrips = 0;
|
|
66
|
+
#ages = [];
|
|
67
|
+
#maxAge = 0;
|
|
68
|
+
#samples = [];
|
|
69
|
+
#dropped = 0;
|
|
70
|
+
constructor(options = {}) {
|
|
71
|
+
this.#redact = options.redact;
|
|
72
|
+
this.#configured = "not_configured";
|
|
73
|
+
}
|
|
74
|
+
setRedisConfigured(configured) {
|
|
75
|
+
if (configured && this.#configured === "not_configured")
|
|
76
|
+
this.#configured = "configured";
|
|
77
|
+
}
|
|
78
|
+
markTelemetryFailed() {
|
|
79
|
+
this.#telemetryFailed = true;
|
|
80
|
+
}
|
|
81
|
+
recordLookup(meta) {
|
|
82
|
+
if (!["memory", "redis", "loader"].includes(meta.source)) {
|
|
83
|
+
this.#telemetryFailed = true;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.#lookups = add(this.#lookups, 1);
|
|
87
|
+
if (meta.hit)
|
|
88
|
+
this.#hits = add(this.#hits, 1);
|
|
89
|
+
else
|
|
90
|
+
this.#misses = add(this.#misses, 1);
|
|
91
|
+
if (meta.stale)
|
|
92
|
+
this.#stale = add(this.#stale, 1);
|
|
93
|
+
this.#source[meta.source] = add(this.#source[meta.source], 1);
|
|
94
|
+
if (meta.source === "redis" && this.#configured === "not_configured")
|
|
95
|
+
this.#configured = "configured";
|
|
96
|
+
if (meta.ageMs !== undefined) {
|
|
97
|
+
const age = integer(meta.ageMs);
|
|
98
|
+
if (this.#ages.length < MAX_SAMPLES)
|
|
99
|
+
this.#ages.push(age);
|
|
100
|
+
this.#maxAge = Math.max(this.#maxAge, age);
|
|
101
|
+
}
|
|
102
|
+
if (this.#samples.length < MAX_SAMPLES) {
|
|
103
|
+
this.#samples.push({
|
|
104
|
+
key: boundedText(meta.key, this.#redact),
|
|
105
|
+
hit: meta.hit,
|
|
106
|
+
stale: meta.stale,
|
|
107
|
+
...(meta.ageMs === undefined ? {} : { ageMs: integer(meta.ageMs) }),
|
|
108
|
+
source: meta.source,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
this.#dropped = add(this.#dropped, 1);
|
|
113
|
+
}
|
|
114
|
+
recordWrite() {
|
|
115
|
+
this.#writes = add(this.#writes, 1);
|
|
116
|
+
}
|
|
117
|
+
recordLoaderError() {
|
|
118
|
+
this.#loaderErrors = add(this.#loaderErrors, 1);
|
|
119
|
+
}
|
|
120
|
+
recordWriteError() {
|
|
121
|
+
this.#writeErrors = add(this.#writeErrors, 1);
|
|
122
|
+
}
|
|
123
|
+
recordRedisFallback() {
|
|
124
|
+
this.#redisFallbacks = add(this.#redisFallbacks, 1);
|
|
125
|
+
this.#configured = "degraded";
|
|
126
|
+
}
|
|
127
|
+
recordRedisRoundTrip() {
|
|
128
|
+
this.#redisRoundTrips = add(this.#redisRoundTrips, 1);
|
|
129
|
+
}
|
|
130
|
+
toLogPayload(_spans) {
|
|
131
|
+
if (this.#lookups === 0 &&
|
|
132
|
+
this.#writes === 0 &&
|
|
133
|
+
this.#redisFallbacks === 0 &&
|
|
134
|
+
!this.#telemetryFailed)
|
|
135
|
+
return undefined;
|
|
136
|
+
const sorted = [...this.#ages].sort((a, b) => a - b);
|
|
137
|
+
const p50 = sorted.length ? sorted[Math.floor((sorted.length - 1) / 2)] : undefined;
|
|
138
|
+
return {
|
|
139
|
+
...(this.#telemetryFailed ? { telemetryFailed: true } : {}),
|
|
140
|
+
lookups: this.#lookups,
|
|
141
|
+
hits: this.#hits,
|
|
142
|
+
stale: this.#stale,
|
|
143
|
+
misses: this.#misses,
|
|
144
|
+
writes: this.#writes,
|
|
145
|
+
loaderErrors: this.#loaderErrors,
|
|
146
|
+
writeErrors: this.#writeErrors,
|
|
147
|
+
source: { ...this.#source },
|
|
148
|
+
redisFallbacks: this.#redisFallbacks,
|
|
149
|
+
redisMode: closedEnum(this.#configured),
|
|
150
|
+
redisRoundTrips: this.#redisRoundTrips,
|
|
151
|
+
...(p50 === undefined ? {} : { ageMs: { p50, max: this.#maxAge } }),
|
|
152
|
+
samples: this.#samples.map((sample) => ({ ...sample })),
|
|
153
|
+
dropped: this.#dropped,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
toHeaderPayload(log) {
|
|
157
|
+
return {
|
|
158
|
+
lookups: log.lookups,
|
|
159
|
+
hits: log.hits,
|
|
160
|
+
stale: log.stale,
|
|
161
|
+
misses: log.misses,
|
|
162
|
+
writes: log.writes,
|
|
163
|
+
loaderErrors: log.loaderErrors,
|
|
164
|
+
writeErrors: log.writeErrors,
|
|
165
|
+
source: {
|
|
166
|
+
memory: log.source.memory,
|
|
167
|
+
redis: log.source.redis,
|
|
168
|
+
loader: log.source.loader,
|
|
169
|
+
},
|
|
170
|
+
redisFallbacks: log.redisFallbacks,
|
|
171
|
+
redisMode: log.redisMode,
|
|
172
|
+
redisRoundTrips: log.redisRoundTrips,
|
|
173
|
+
...(log.ageMs ? { ageP50: log.ageMs.p50, ageMax: log.ageMs.max } : {}),
|
|
174
|
+
dropped: log.dropped,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dist/runtime/cache.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readDiagnosticEnv } from "./diagnostic-env.js";
|
|
|
2
2
|
import { createHash, createHmac } from "node:crypto";
|
|
3
3
|
import { providerCacheRedisUrlFromEnv } from "../config/loader.js";
|
|
4
4
|
import { ProviderError } from "../errors.js";
|
|
5
|
+
import { cacheTelemetryFor, guardCacheTelemetry } from "./cache-telemetry.js";
|
|
5
6
|
import { createProviderRedisClient, ensureRedisReady, withRedisTimeout, } from "./redis.js";
|
|
6
7
|
export const APIFUSE__CACHE__KEY_PEPPER_ENV = "APIFUSE__CACHE__KEY_PEPPER";
|
|
7
8
|
const DEFAULT_PREFIX = "apifuse:provider-cache:v1";
|
|
@@ -286,9 +287,13 @@ export function createProviderCache(options) {
|
|
|
286
287
|
const memoryMaxEntries = Math.max(1, options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES);
|
|
287
288
|
const now = options.now ?? Date.now;
|
|
288
289
|
const events = [];
|
|
290
|
+
const telemetry = guardCacheTelemetry(cacheTelemetryFor(options));
|
|
291
|
+
telemetry.setRedisConfigured?.(!!redisUrl);
|
|
292
|
+
let redisUnavailable = false;
|
|
289
293
|
const secretScopedKeys = new Set();
|
|
290
294
|
function record(meta) {
|
|
291
295
|
events.push(meta);
|
|
296
|
+
telemetry?.recordLookup(meta);
|
|
292
297
|
}
|
|
293
298
|
function sweepMemory(currentTime) {
|
|
294
299
|
for (const [entryKey, entry] of backend.memory) {
|
|
@@ -322,13 +327,21 @@ export function createProviderCache(options) {
|
|
|
322
327
|
async function readRedis(key, currentTime) {
|
|
323
328
|
const redis = backend.redis;
|
|
324
329
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
330
|
+
if (redis) {
|
|
331
|
+
redisUnavailable = true;
|
|
332
|
+
telemetry?.recordRedisFallback();
|
|
333
|
+
}
|
|
325
334
|
return null;
|
|
326
335
|
}
|
|
327
336
|
const raw = await withRedisFallback(async () => {
|
|
328
337
|
return await redis.get(key);
|
|
329
338
|
});
|
|
330
|
-
if (typeof raw !== "string" && raw !== null)
|
|
339
|
+
if (typeof raw !== "string" && raw !== null) {
|
|
340
|
+
redisUnavailable = true;
|
|
341
|
+
telemetry.recordRedisFallback();
|
|
331
342
|
return null;
|
|
343
|
+
}
|
|
344
|
+
telemetry?.recordRedisRoundTrip?.();
|
|
332
345
|
const envelope = safeParseEnvelope(raw);
|
|
333
346
|
if (!envelope)
|
|
334
347
|
return null;
|
|
@@ -370,6 +383,7 @@ export function createProviderCache(options) {
|
|
|
370
383
|
return staleMemoryResult;
|
|
371
384
|
}
|
|
372
385
|
async function write(key, value, cacheOptions) {
|
|
386
|
+
telemetry?.recordWrite();
|
|
373
387
|
const currentTime = now();
|
|
374
388
|
const freshTtlMs = jitteredTtlMs(cacheOptions.ttlMs, cacheOptions.jitterPct);
|
|
375
389
|
const staleIfErrorMs = cacheOptions.staleIfErrorMs ?? 0;
|
|
@@ -383,9 +397,21 @@ export function createProviderCache(options) {
|
|
|
383
397
|
rememberEnvelope(key, envelope, currentTime);
|
|
384
398
|
const redis = backend.redis;
|
|
385
399
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
400
|
+
if (redis) {
|
|
401
|
+
redisUnavailable = true;
|
|
402
|
+
telemetry?.recordRedisFallback();
|
|
403
|
+
telemetry?.recordWriteError?.();
|
|
404
|
+
}
|
|
386
405
|
return;
|
|
387
406
|
}
|
|
388
|
-
await withRedisFallback(() => redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs));
|
|
407
|
+
const written = await withRedisFallback(() => redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs));
|
|
408
|
+
if (written === undefined) {
|
|
409
|
+
redisUnavailable = true;
|
|
410
|
+
telemetry?.recordRedisFallback();
|
|
411
|
+
telemetry?.recordWriteError?.();
|
|
412
|
+
}
|
|
413
|
+
else
|
|
414
|
+
telemetry?.recordRedisRoundTrip?.();
|
|
389
415
|
}
|
|
390
416
|
async function loadAndStore(key, loader, cacheOptions, staleCandidate) {
|
|
391
417
|
try {
|
|
@@ -405,6 +431,13 @@ export function createProviderCache(options) {
|
|
|
405
431
|
if (staleCandidate?.meta.stale) {
|
|
406
432
|
return staleCandidate;
|
|
407
433
|
}
|
|
434
|
+
telemetry?.recordLookup({
|
|
435
|
+
key,
|
|
436
|
+
hit: false,
|
|
437
|
+
stale: false,
|
|
438
|
+
source: "loader",
|
|
439
|
+
});
|
|
440
|
+
telemetry?.recordLoaderError?.();
|
|
408
441
|
throw error;
|
|
409
442
|
}
|
|
410
443
|
}
|
|
@@ -421,16 +454,30 @@ export function createProviderCache(options) {
|
|
|
421
454
|
const result = await read(key);
|
|
422
455
|
if (result)
|
|
423
456
|
record(result.meta);
|
|
457
|
+
else
|
|
458
|
+
telemetry.recordLookup({
|
|
459
|
+
key,
|
|
460
|
+
hit: false,
|
|
461
|
+
stale: false,
|
|
462
|
+
source: backend.redis && !redisUnavailable ? "redis" : "memory",
|
|
463
|
+
});
|
|
424
464
|
return result;
|
|
425
465
|
},
|
|
426
466
|
set: write,
|
|
427
467
|
async delete(key) {
|
|
468
|
+
telemetry?.recordWrite();
|
|
428
469
|
backend.memory.delete(key);
|
|
429
470
|
const redis = backend.redis;
|
|
430
471
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
472
|
+
if (redis)
|
|
473
|
+
telemetry?.recordRedisFallback();
|
|
431
474
|
return;
|
|
432
475
|
}
|
|
433
|
-
await withRedisFallback(() => redis.del(key));
|
|
476
|
+
const deleted = await withRedisFallback(() => redis.del(key));
|
|
477
|
+
if (deleted === undefined)
|
|
478
|
+
telemetry?.recordRedisFallback();
|
|
479
|
+
else
|
|
480
|
+
telemetry?.recordRedisRoundTrip?.();
|
|
434
481
|
},
|
|
435
482
|
async getOrSet(key, loader, cacheOptions) {
|
|
436
483
|
const existing = await read(key);
|
|
@@ -440,10 +487,23 @@ export function createProviderCache(options) {
|
|
|
440
487
|
}
|
|
441
488
|
const existingInflight = backend.inflight.get(key);
|
|
442
489
|
if (existingInflight) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
490
|
+
try {
|
|
491
|
+
const inflightResult = await existingInflight;
|
|
492
|
+
const result = resultWithValue(inflightResult.value, inflightResult.meta);
|
|
493
|
+
record(result.meta);
|
|
494
|
+
return result;
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
const meta = {
|
|
498
|
+
key,
|
|
499
|
+
hit: false,
|
|
500
|
+
stale: false,
|
|
501
|
+
source: "loader",
|
|
502
|
+
};
|
|
503
|
+
telemetry.recordLookup(meta);
|
|
504
|
+
telemetry?.recordLoaderError?.();
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
447
507
|
}
|
|
448
508
|
const promise = loadAndStore(key, loader, cacheOptions, existing).finally(() => {
|
|
449
509
|
backend.inflight.delete(key);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const CAPABILITY_BODY_UNAVAILABLE = "<body unavailable>";
|
|
2
|
+
/** Read optional diagnostic response text without letting a body stall the request failure. */
|
|
3
|
+
export declare function readCapabilityErrorBody(response: Response, signal: AbortSignal): Promise<string>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const CAPABILITY_BODY_UNAVAILABLE = "<body unavailable>";
|
|
2
|
+
/** Read optional diagnostic response text without letting a body stall the request failure. */
|
|
3
|
+
export async function readCapabilityErrorBody(response, signal) {
|
|
4
|
+
if (signal.aborted)
|
|
5
|
+
return CAPABILITY_BODY_UNAVAILABLE;
|
|
6
|
+
let timer;
|
|
7
|
+
let onAbort;
|
|
8
|
+
let reader;
|
|
9
|
+
let unavailableWon = false;
|
|
10
|
+
const cancelBody = (reason) => {
|
|
11
|
+
const cancellation = reader ? reader.cancel(reason) : response.body?.cancel(reason);
|
|
12
|
+
if (cancellation)
|
|
13
|
+
void cancellation.catch(() => { });
|
|
14
|
+
};
|
|
15
|
+
const unavailable = new Promise((resolve) => {
|
|
16
|
+
onAbort = () => {
|
|
17
|
+
unavailableWon = true;
|
|
18
|
+
resolve(CAPABILITY_BODY_UNAVAILABLE);
|
|
19
|
+
cancelBody(signal.reason);
|
|
20
|
+
};
|
|
21
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
22
|
+
timer = setTimeout(() => {
|
|
23
|
+
unavailableWon = true;
|
|
24
|
+
resolve(CAPABILITY_BODY_UNAVAILABLE);
|
|
25
|
+
cancelBody(new Error("capability error body read timeout"));
|
|
26
|
+
}, 250);
|
|
27
|
+
});
|
|
28
|
+
const body = Promise.resolve()
|
|
29
|
+
.then(async () => {
|
|
30
|
+
if (unavailableWon)
|
|
31
|
+
return CAPABILITY_BODY_UNAVAILABLE;
|
|
32
|
+
if (!response.body)
|
|
33
|
+
return response.text();
|
|
34
|
+
reader = response.body.getReader();
|
|
35
|
+
const decoder = new TextDecoder();
|
|
36
|
+
let text = "";
|
|
37
|
+
while (true) {
|
|
38
|
+
if (unavailableWon)
|
|
39
|
+
return CAPABILITY_BODY_UNAVAILABLE;
|
|
40
|
+
const chunk = await reader.read();
|
|
41
|
+
if (chunk.done)
|
|
42
|
+
return text + decoder.decode();
|
|
43
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
.catch(() => CAPABILITY_BODY_UNAVAILABLE);
|
|
47
|
+
try {
|
|
48
|
+
return await Promise.race([body, unavailable]);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
if (timer !== undefined)
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
if (onAbort)
|
|
54
|
+
signal.removeEventListener("abort", onAbort);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
/** Internal capture facts, isolated across concurrent calls and never attached to caller results. */
|
|
3
|
+
export type CapabilityFacts = {
|
|
4
|
+
status?: number;
|
|
5
|
+
finishReason?: string;
|
|
6
|
+
diagnostics?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const capabilityFacts: AsyncLocalStorage<CapabilityFacts>;
|
|
9
|
+
export declare function tagCapability<T extends object>(context: T, facts: {
|
|
10
|
+
backend: string;
|
|
11
|
+
engine: string;
|
|
12
|
+
model?: string;
|
|
13
|
+
}): T;
|
|
14
|
+
export declare function capabilityMetadata(context: object): {
|
|
15
|
+
backend: string;
|
|
16
|
+
engine: string;
|
|
17
|
+
model?: string;
|
|
18
|
+
} | undefined;
|
|
19
|
+
export declare function captureCapability(facts: CapabilityFacts): void;
|
|
20
|
+
export declare function registerCapabilityInput(input: {
|
|
21
|
+
kind: string;
|
|
22
|
+
data?: string;
|
|
23
|
+
url?: string;
|
|
24
|
+
}): void;
|
|
25
|
+
/**
|
|
26
|
+
* Formats capability diagnostics after credential redaction.
|
|
27
|
+
* When `stripPayload` is enabled, long base64-shaped runs are replaced as a
|
|
28
|
+
* defence in depth; this may replace an unregistered long identifier, while
|
|
29
|
+
* protecting against the more likely case of payload data in an upstream body.
|
|
30
|
+
*/
|
|
31
|
+
export declare function capabilityText(value: string, redact?: (text: string) => string, stripPayload?: boolean): string;
|
|
32
|
+
export declare function capabilityErrorText(error: unknown): string;
|
|
33
|
+
export declare const integer: (n: number | undefined) => number;
|
|
34
|
+
export declare const add: (a: number, b: number) => number;
|
|
35
|
+
export declare function inputBytes(input: {
|
|
36
|
+
kind: string;
|
|
37
|
+
data?: string;
|
|
38
|
+
}): number | undefined;
|
|
39
|
+
/** All capability hooks use the HTTP observer guard, including failure reporting. */
|
|
40
|
+
export declare function observeCapability(sink: {
|
|
41
|
+
markTelemetryFailed?(): void;
|
|
42
|
+
}, callback: () => unknown): void;
|
|
43
|
+
type RootRunner = <T>(callback: () => Promise<T>) => Promise<T>;
|
|
44
|
+
export declare function bindCapabilityRoot<T extends object>(context: T, run: RootRunner): T;
|
|
45
|
+
export declare function runCapabilitySpan<T>(context: object, callback: () => Promise<T>): Promise<T>;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { registerDiagnosticValue } from "./diagnostic-env.js";
|
|
4
|
+
import { redactDiagnosticText } from "./diagnostic-redactor.js";
|
|
5
|
+
import { observeTelemetry } from "./http-telemetry-guard.js";
|
|
6
|
+
import { boundedText } from "./resolver-telemetry.js";
|
|
7
|
+
export const capabilityFacts = new AsyncLocalStorage();
|
|
8
|
+
const metadata = new WeakMap();
|
|
9
|
+
export function tagCapability(context, facts) {
|
|
10
|
+
metadata.set(context, facts);
|
|
11
|
+
return context;
|
|
12
|
+
}
|
|
13
|
+
export function capabilityMetadata(context) {
|
|
14
|
+
return metadata.get(context);
|
|
15
|
+
}
|
|
16
|
+
export function captureCapability(facts) {
|
|
17
|
+
const current = capabilityFacts.getStore();
|
|
18
|
+
if (current)
|
|
19
|
+
Object.assign(current, facts);
|
|
20
|
+
}
|
|
21
|
+
export function registerCapabilityInput(input) {
|
|
22
|
+
// Payloads are measurements, never redaction needles. Only bounded URLs belong
|
|
23
|
+
// in the request inventory; structural stripping covers oversized URL echoes.
|
|
24
|
+
if (input.kind === "url" && input.url && Buffer.byteLength(input.url) <= 2048)
|
|
25
|
+
registerDiagnosticValue(input.url.trim());
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Formats capability diagnostics after credential redaction.
|
|
29
|
+
* When `stripPayload` is enabled, long base64-shaped runs are replaced as a
|
|
30
|
+
* defence in depth; this may replace an unregistered long identifier, while
|
|
31
|
+
* protecting against the more likely case of payload data in an upstream body.
|
|
32
|
+
*/
|
|
33
|
+
export function capabilityText(value, redact, stripPayload = false) {
|
|
34
|
+
const stripped = value
|
|
35
|
+
.replace(/(?:authorization|proxy-authorization|cookie|set-cookie)\s*["']?\s*[:=]\s*[^\r\n]+/gi, "[REDACTED]")
|
|
36
|
+
.replace(/Bearer\s+[^\r\n]+/gi, "[REDACTED]")
|
|
37
|
+
.replace(/https?:\/\/[^\s"'<>]+/gi, "[REDACTED]")
|
|
38
|
+
.replace(/data:[^\s"']+/gi, "[BASE64_STRIPPED]");
|
|
39
|
+
return boundedText(stripPayload ? stripped.replace(/[A-Za-z0-9+/=]{64,}/g, "[BASE64_STRIPPED]") : stripped, (text) => redactDiagnosticText(text, redact));
|
|
40
|
+
}
|
|
41
|
+
export function capabilityErrorText(error) {
|
|
42
|
+
if (!(error instanceof Error))
|
|
43
|
+
return String(error);
|
|
44
|
+
const details = "details" in error ? error.details : undefined;
|
|
45
|
+
const body = details && typeof details === "object" && "body" in details ? details.body : undefined;
|
|
46
|
+
return `${error.message}${typeof body === "string" ? ` ${body}` : ""}`;
|
|
47
|
+
}
|
|
48
|
+
export const integer = (n) => Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, Math.floor(Number.isFinite(n ?? 0) ? (n ?? 0) : 0)));
|
|
49
|
+
export const add = (a, b) => Math.min(Number.MAX_SAFE_INTEGER, integer(a) + integer(b));
|
|
50
|
+
export function inputBytes(input) {
|
|
51
|
+
if (input.kind !== "base64" || input.data === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
return Buffer.byteLength(input.data.trim(), "base64");
|
|
54
|
+
}
|
|
55
|
+
/** All capability hooks use the HTTP observer guard, including failure reporting. */
|
|
56
|
+
export function observeCapability(sink, callback) {
|
|
57
|
+
observeTelemetry(callback, () => {
|
|
58
|
+
observeTelemetry(() => sink.markTelemetryFailed?.(), () => { });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const roots = new WeakMap();
|
|
62
|
+
export function bindCapabilityRoot(context, run) {
|
|
63
|
+
roots.set(context, run);
|
|
64
|
+
return context;
|
|
65
|
+
}
|
|
66
|
+
export function runCapabilitySpan(context, callback) {
|
|
67
|
+
return roots.get(context)?.(callback) ?? callback();
|
|
68
|
+
}
|
|
@@ -4,8 +4,11 @@ export interface DiagnosticEnvScope {
|
|
|
4
4
|
observe: EnvObserver;
|
|
5
5
|
finished: boolean;
|
|
6
6
|
staticValues?: CompiledDiagnosticSensitiveValues;
|
|
7
|
+
register?: (values: readonly string[]) => void;
|
|
7
8
|
}
|
|
8
9
|
/** Resolve and register atomically, before any consumer can use a live env credential. */
|
|
9
10
|
export declare function readDiagnosticEnv(name: string, env?: Readonly<Record<string, string | undefined>>): string | undefined;
|
|
11
|
+
/** Register a runtime-discovered sensitive value in the active request inventory. */
|
|
12
|
+
export declare function registerDiagnosticValue(value: string): void;
|
|
10
13
|
export declare function withDiagnosticEnv<T>(observer: EnvObserver | DiagnosticEnvScope, fn: () => T): T;
|
|
11
14
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import { resolvedStringVariants, urlCredentialComponents,
|
|
2
|
+
import { otlpHeaderCredentialValues, resolvedStringVariants, urlCredentialComponents, } from "./diagnostic-env-values.js";
|
|
3
3
|
import { registerProcessDiagnosticValues, suppressProcessDiagnosticValues, withAppDiagnosticFallback, } from "./diagnostic-redactor.js";
|
|
4
4
|
const observers = new AsyncLocalStorage();
|
|
5
5
|
/** Resolve and register atomically, before any consumer can use a live env credential. */
|
|
@@ -28,6 +28,15 @@ export function readDiagnosticEnv(name, env = process.env) {
|
|
|
28
28
|
}
|
|
29
29
|
return value;
|
|
30
30
|
}
|
|
31
|
+
/** Register a runtime-discovered sensitive value in the active request inventory. */
|
|
32
|
+
export function registerDiagnosticValue(value) {
|
|
33
|
+
if (value.length < 4)
|
|
34
|
+
return;
|
|
35
|
+
const scope = observers.getStore();
|
|
36
|
+
if (scope && !scope.finished && scope.register) {
|
|
37
|
+
scope.register([value]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
31
40
|
export function withDiagnosticEnv(observer, fn) {
|
|
32
41
|
const scope = typeof observer === "function" ? { observe: observer, finished: false } : observer;
|
|
33
42
|
const run = () => observers.run(scope, fn);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type ProviderEngine, type ProviderEngineMode } from "../engine.js";
|
|
2
|
+
/** Mode used when neither `engineMode` nor `APIFUSE__ENGINE__MODE` names one. */
|
|
3
|
+
export declare const DEFAULT_PROVIDER_ENGINE_MODE = "in-process";
|
|
4
|
+
/** Remote engine client endpoint; engine-owned, so never declarable as a provider secret. */
|
|
5
|
+
export declare const REMOTE_ENGINE_CLIENT_URL_ENV = "APIFUSE__ENGINE__URL";
|
|
6
|
+
/** Remote engine client credential; registered with the diagnostic redactor by sensitive-values. */
|
|
7
|
+
export declare const REMOTE_ENGINE_CLIENT_API_KEY_ENV = "APIFUSE__ENGINE__API_KEY";
|
|
8
|
+
/** Where the resolved mode came from, in precedence order. */
|
|
9
|
+
export type ProviderEngineModeSource = "engine" | "env" | "option" | "default";
|
|
10
|
+
/**
|
|
11
|
+
* Non-fatal conditions worth reporting at boot:
|
|
12
|
+
* - `invalid_env_value`: `APIFUSE__ENGINE__MODE` held an unrecognized value.
|
|
13
|
+
* - `env_overrode_option`: the manifest and the host code disagreed; the manifest won.
|
|
14
|
+
* - `engine_object_overrode_requested_mode`: an explicit engine object cannot honour the requested mode.
|
|
15
|
+
* - `engine_env_ignored`: remote-only engine env is projected but the mode is not
|
|
16
|
+
* remote (not reported for a host-supplied engine, which may consume it itself).
|
|
17
|
+
*/
|
|
18
|
+
export type ProviderEngineModeWarning = "invalid_env_value" | "env_overrode_option" | "engine_object_overrode_requested_mode" | "engine_env_ignored";
|
|
19
|
+
export interface ProviderEngineModeResolution {
|
|
20
|
+
readonly mode: ProviderEngineMode | "custom";
|
|
21
|
+
readonly source: ProviderEngineModeSource;
|
|
22
|
+
/** True for every non-remote attachment, whose removal is scheduled by ADR-0011. */
|
|
23
|
+
readonly deprecated: boolean;
|
|
24
|
+
readonly warnings: readonly ProviderEngineModeWarning[];
|
|
25
|
+
}
|
|
26
|
+
export interface ProviderEngineModeResolutionInput {
|
|
27
|
+
/** Explicit `engineMode` option passed by the host process. */
|
|
28
|
+
readonly option?: ProviderEngineMode;
|
|
29
|
+
/** Explicit engine object passed by the host; used as given when present. */
|
|
30
|
+
readonly engine?: Pick<ProviderEngine, "kind">;
|
|
31
|
+
/** Environment to read the engine env from; defaults to `process.env`. */
|
|
32
|
+
readonly environment?: Readonly<Record<string, string | undefined>>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the engine attachment mode once at boot.
|
|
36
|
+
*
|
|
37
|
+
* Precedence: an explicit `engine` object, then `APIFUSE__ENGINE__MODE`, then the
|
|
38
|
+
* `engineMode` option, then the default. The manifest outranks the option so
|
|
39
|
+
* provider code cannot silently downgrade what the deployment asked for; every
|
|
40
|
+
* disagreement is reported in `warnings` with a truthful `source` rather than
|
|
41
|
+
* being dropped.
|
|
42
|
+
*
|
|
43
|
+
* Deployment-authored input is never fatal (an unrecognized env value is warned
|
|
44
|
+
* about and ignored). A malformed `engineMode` option is a fault in the calling
|
|
45
|
+
* code, which no manifest can reach, so it throws.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveProviderEngineMode(input?: ProviderEngineModeResolutionInput): ProviderEngineModeResolution;
|
|
48
|
+
/** True when the engine cannot attach, so readiness must report the process as unready. */
|
|
49
|
+
export declare function isUnavailableProviderEngine(engine: ProviderEngine): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Engine that refuses every attachment with `reason`.
|
|
52
|
+
*
|
|
53
|
+
* Requesting a mode this release cannot serve must not crash the process: the SDK
|
|
54
|
+
* answers a structured error per request and reports the process unready, the same
|
|
55
|
+
* trade-off `provider_secrets_missing` already makes at boot. Non-retryable, so a
|
|
56
|
+
* caller-side retry loop cannot turn a configuration fault into load.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createUnavailableProviderEngine(kind: ProviderEngineMode, reason: {
|
|
59
|
+
message: string;
|
|
60
|
+
code: string;
|
|
61
|
+
fix: string;
|
|
62
|
+
}): ProviderEngine;
|
|
63
|
+
/**
|
|
64
|
+
* Engine for a resolved mode. `remote` has no client in this release, so it
|
|
65
|
+
* resolves to an engine that fails every request instead of falling back to
|
|
66
|
+
* in-process (ADR-0011 Pitfall 2: in-process is never a fallback).
|
|
67
|
+
*/
|
|
68
|
+
export declare function createEngineForMode(mode: ProviderEngineMode | "custom"): ProviderEngine;
|
|
69
|
+
/**
|
|
70
|
+
* Fail fast for the CLIs (`apifuse dev`, `apifuse record`). They are developer
|
|
71
|
+
* tools, not pods: an unusable mode should stop the command rather than start a
|
|
72
|
+
* server that refuses every request.
|
|
73
|
+
*/
|
|
74
|
+
export declare function assertProcessEngineModeSupported(environment?: Readonly<Record<string, string | undefined>>): ProviderEngineModeResolution;
|