@apifuse/provider-sdk 2.2.0-beta.61 → 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 +8 -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 +191 -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/handle-meta.d.ts +18 -2
- package/dist/handle-meta.js +30 -2
- package/dist/handle.d.ts +33 -12
- package/dist/handle.js +69 -24
- 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 +10 -2
- package/dist/index.js +9 -1
- package/dist/lint.d.ts +10 -0
- package/dist/lint.js +440 -57
- package/dist/provider.d.ts +7 -5
- package/dist/provider.js +4 -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/handle.js +15 -10
- package/dist/runtime/http-telemetry-binding.d.ts +10 -0
- package/dist/runtime/http-telemetry-binding.js +152 -0
- package/dist/runtime/http-telemetry-guard.d.ts +16 -0
- package/dist/runtime/http-telemetry-guard.js +202 -0
- package/dist/runtime/http-telemetry.d.ts +104 -0
- package/dist/runtime/http-telemetry.js +190 -0
- package/dist/runtime/http.d.ts +31 -0
- package/dist/runtime/http.js +356 -174
- 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 +9 -0
- package/dist/server/index.js +5 -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 +53 -2
- package/dist/server/serve-implementation.js +369 -123
- 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/handle-meta.ts +39 -4
- package/src/handle.ts +121 -38
- 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 +91 -0
- package/src/lint.ts +552 -57
- package/src/provider.ts +20 -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/handle.ts +20 -10
- package/src/runtime/http-telemetry-binding.ts +162 -0
- package/src/runtime/http-telemetry-guard.ts +208 -0
- package/src/runtime/http-telemetry.ts +292 -0
- package/src/runtime/http.ts +457 -204
- 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 +66 -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 +601 -157
- 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,152 @@
|
|
|
1
|
+
import { createInProcessProviderEngine, PROVIDER_ENGINE_MODE_ENV, } from "../engine.js";
|
|
2
|
+
import { ProviderError, ValidationError } from "../errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Engine attachment selection. Internal on purpose: ADR-0011 removes the
|
|
5
|
+
* in-process lane, so every symbol exported here would become a breaking removal
|
|
6
|
+
* later. The server surfaces the outcome on the `provider_engine_mode` boot
|
|
7
|
+
* event instead.
|
|
8
|
+
*/
|
|
9
|
+
const KNOWN_PROVIDER_ENGINE_MODES = ["in-process", "remote"];
|
|
10
|
+
/** Mode used when neither `engineMode` nor `APIFUSE__ENGINE__MODE` names one. */
|
|
11
|
+
export const DEFAULT_PROVIDER_ENGINE_MODE = "in-process";
|
|
12
|
+
/** Remote engine client endpoint; engine-owned, so never declarable as a provider secret. */
|
|
13
|
+
export const REMOTE_ENGINE_CLIENT_URL_ENV = "APIFUSE__ENGINE__URL";
|
|
14
|
+
/** Remote engine client credential; registered with the diagnostic redactor by sensitive-values. */
|
|
15
|
+
export const REMOTE_ENGINE_CLIENT_API_KEY_ENV = "APIFUSE__ENGINE__API_KEY";
|
|
16
|
+
/** Engine env names that only mean something in remote mode. */
|
|
17
|
+
const REMOTE_ONLY_ENGINE_ENV_NAMES = [
|
|
18
|
+
REMOTE_ENGINE_CLIENT_URL_ENV,
|
|
19
|
+
REMOTE_ENGINE_CLIENT_API_KEY_ENV,
|
|
20
|
+
];
|
|
21
|
+
function isKnownMode(value) {
|
|
22
|
+
return KNOWN_PROVIDER_ENGINE_MODES.includes(value);
|
|
23
|
+
}
|
|
24
|
+
function normalize(value) {
|
|
25
|
+
if (typeof value !== "string")
|
|
26
|
+
return undefined;
|
|
27
|
+
const trimmed = value.trim().toLowerCase();
|
|
28
|
+
return trimmed === "" ? undefined : trimmed;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the engine attachment mode once at boot.
|
|
32
|
+
*
|
|
33
|
+
* Precedence: an explicit `engine` object, then `APIFUSE__ENGINE__MODE`, then the
|
|
34
|
+
* `engineMode` option, then the default. The manifest outranks the option so
|
|
35
|
+
* provider code cannot silently downgrade what the deployment asked for; every
|
|
36
|
+
* disagreement is reported in `warnings` with a truthful `source` rather than
|
|
37
|
+
* being dropped.
|
|
38
|
+
*
|
|
39
|
+
* Deployment-authored input is never fatal (an unrecognized env value is warned
|
|
40
|
+
* about and ignored). A malformed `engineMode` option is a fault in the calling
|
|
41
|
+
* code, which no manifest can reach, so it throws.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveProviderEngineMode(input = {}) {
|
|
44
|
+
const environment = input.environment ?? process.env;
|
|
45
|
+
const warnings = [];
|
|
46
|
+
// Read directly: this is a mode name, not a credential, so it must not be
|
|
47
|
+
// registered with the diagnostic redactor the way readDiagnosticEnv is.
|
|
48
|
+
const rawEnv = normalize(environment[PROVIDER_ENGINE_MODE_ENV]);
|
|
49
|
+
let envMode;
|
|
50
|
+
if (rawEnv !== undefined) {
|
|
51
|
+
if (isKnownMode(rawEnv))
|
|
52
|
+
envMode = rawEnv;
|
|
53
|
+
else
|
|
54
|
+
warnings.push("invalid_env_value");
|
|
55
|
+
}
|
|
56
|
+
const optionMode = normalize(input.option);
|
|
57
|
+
if (input.option !== undefined && (optionMode === undefined || !isKnownMode(optionMode))) {
|
|
58
|
+
throw new ValidationError(`Invalid engineMode option "${String(input.option)}"; expected ${KNOWN_PROVIDER_ENGINE_MODES.join(" or ")}`, { fix: 'Pass engineMode: "in-process" | "remote", or omit it to use the default.' });
|
|
59
|
+
}
|
|
60
|
+
if (optionMode !== undefined && envMode !== undefined && optionMode !== envMode) {
|
|
61
|
+
warnings.push("env_overrode_option");
|
|
62
|
+
}
|
|
63
|
+
const requested = envMode ?? optionMode;
|
|
64
|
+
const requestedSource = envMode !== undefined ? "env" : optionMode !== undefined ? "option" : undefined;
|
|
65
|
+
// Normalized like every other mode input: an un-normalized host `kind` would
|
|
66
|
+
// reach the boot event and /readyz verbatim and mis-score the fleet audit.
|
|
67
|
+
const engineKind = input.engine === undefined ? undefined : (normalize(input.engine.kind) ?? "custom");
|
|
68
|
+
if (engineKind !== undefined) {
|
|
69
|
+
if (requested !== undefined && engineKind !== requested) {
|
|
70
|
+
warnings.push("engine_object_overrode_requested_mode");
|
|
71
|
+
}
|
|
72
|
+
return finalize(engineKind, "engine", environment, warnings);
|
|
73
|
+
}
|
|
74
|
+
return finalize(requested ?? DEFAULT_PROVIDER_ENGINE_MODE, requestedSource ?? "default", environment, warnings);
|
|
75
|
+
}
|
|
76
|
+
function finalize(mode, source, environment, warnings) {
|
|
77
|
+
// Presence of the remote engine env never selects a mode (the manifest
|
|
78
|
+
// generator projects it fleet-wide before the SDK can use it), but staying
|
|
79
|
+
// silent about it would hide a half-applied cutover. Skipped for a host-supplied
|
|
80
|
+
// engine: that object may be a remote client that consumes the env itself, so
|
|
81
|
+
// claiming the env is unused would be a false report.
|
|
82
|
+
if (mode !== "remote" &&
|
|
83
|
+
source !== "engine" &&
|
|
84
|
+
REMOTE_ONLY_ENGINE_ENV_NAMES.some((name) => normalize(environment[name]) !== undefined)) {
|
|
85
|
+
warnings.push("engine_env_ignored");
|
|
86
|
+
}
|
|
87
|
+
// Fail-closed for the ADR-0011 audit: everything that is not the remote lane
|
|
88
|
+
// still attaches capabilities inside the pod process and is scheduled for
|
|
89
|
+
// removal, including an opaque host engine that declares no kind.
|
|
90
|
+
return { mode, source, deprecated: mode !== "remote", warnings };
|
|
91
|
+
}
|
|
92
|
+
const UNAVAILABLE_ENGINES = new WeakSet();
|
|
93
|
+
/** True when the engine cannot attach, so readiness must report the process as unready. */
|
|
94
|
+
export function isUnavailableProviderEngine(engine) {
|
|
95
|
+
return UNAVAILABLE_ENGINES.has(engine);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Engine that refuses every attachment with `reason`.
|
|
99
|
+
*
|
|
100
|
+
* Requesting a mode this release cannot serve must not crash the process: the SDK
|
|
101
|
+
* answers a structured error per request and reports the process unready, the same
|
|
102
|
+
* trade-off `provider_secrets_missing` already makes at boot. Non-retryable, so a
|
|
103
|
+
* caller-side retry loop cannot turn a configuration fault into load.
|
|
104
|
+
*/
|
|
105
|
+
export function createUnavailableProviderEngine(kind, reason) {
|
|
106
|
+
const engine = {
|
|
107
|
+
kind,
|
|
108
|
+
attach: () => {
|
|
109
|
+
throw new ProviderError(reason.message, {
|
|
110
|
+
code: reason.code,
|
|
111
|
+
retryable: false,
|
|
112
|
+
fix: reason.fix,
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
UNAVAILABLE_ENGINES.add(engine);
|
|
117
|
+
return engine;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Engine for a resolved mode. `remote` has no client in this release, so it
|
|
121
|
+
* resolves to an engine that fails every request instead of falling back to
|
|
122
|
+
* in-process (ADR-0011 Pitfall 2: in-process is never a fallback).
|
|
123
|
+
*/
|
|
124
|
+
export function createEngineForMode(mode) {
|
|
125
|
+
// Default-deny on the mode name, not just on "remote": the mode union is open
|
|
126
|
+
// so a later lane can be named, and falling through to in-process for an
|
|
127
|
+
// unserved lane is the silent downgrade ADR-0011 Pitfall 2 forbids.
|
|
128
|
+
if (mode === DEFAULT_PROVIDER_ENGINE_MODE)
|
|
129
|
+
return createInProcessProviderEngine();
|
|
130
|
+
return createUnavailableProviderEngine(mode, {
|
|
131
|
+
message: `Provider engine mode "${mode}" was requested but this @apifuse/provider-sdk release only serves the ${DEFAULT_PROVIDER_ENGINE_MODE} engine`,
|
|
132
|
+
code: "PROVIDER_ENGINE_MODE_UNSUPPORTED",
|
|
133
|
+
fix: `Unset ${PROVIDER_ENGINE_MODE_ENV} (or the engineMode option), or upgrade to a release that serves this mode.`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Fail fast for the CLIs (`apifuse dev`, `apifuse record`). They are developer
|
|
138
|
+
* tools, not pods: an unusable mode should stop the command rather than start a
|
|
139
|
+
* server that refuses every request.
|
|
140
|
+
*/
|
|
141
|
+
export function assertProcessEngineModeSupported(environment = process.env) {
|
|
142
|
+
const resolution = resolveProviderEngineMode({ environment });
|
|
143
|
+
if (resolution.mode !== DEFAULT_PROVIDER_ENGINE_MODE) {
|
|
144
|
+
throw new ProviderError(`Provider engine mode "${resolution.mode}" was requested but this @apifuse/provider-sdk release only serves the ${DEFAULT_PROVIDER_ENGINE_MODE} engine`, {
|
|
145
|
+
code: "PROVIDER_ENGINE_MODE_UNSUPPORTED",
|
|
146
|
+
retryable: false,
|
|
147
|
+
details: { mode: resolution.mode, source: resolution.source },
|
|
148
|
+
fix: `Unset ${PROVIDER_ENGINE_MODE_ENV}, or upgrade to a release that serves this mode.`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return resolution;
|
|
152
|
+
}
|
package/dist/runtime/handle.js
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Storage layout: one state namespace per kind, `handle.<kind>`, scoped by the
|
|
5
5
|
* kind's access (bound → connection scope via `state.forConnection`, public →
|
|
6
|
-
* provider scope). The state key is the
|
|
6
|
+
* provider scope; both cursors and drafts may be either). The state key is the
|
|
7
|
+
* canonical handle string. Records hold
|
|
7
8
|
* the validated data plus status/timestamps; the handle string itself carries
|
|
8
9
|
* no payload and no signature.
|
|
9
10
|
*/
|
|
10
11
|
import { randomInt, randomUUID } from "node:crypto";
|
|
11
12
|
import { isProviderError } from "../errors.js";
|
|
12
13
|
import { HandleError, handleRecoverySentence, isDraftKind, } from "../handle.js";
|
|
14
|
+
import { formatHandleIssuers } from "../handle-meta.js";
|
|
13
15
|
import { BOUND_HANDLE_WORD_COUNT, findHandleWordWithinDistance, HIGH_PUBLIC_HANDLE_WORD_COUNT, ISSUABLE_HANDLE_WORDS, PUBLIC_HANDLE_WORD_COUNT, } from "./handle-wordlist.js";
|
|
14
16
|
import { createMemoryProviderRuntimeState } from "./state.js";
|
|
15
17
|
const RECORD_VERSION = 1;
|
|
@@ -151,14 +153,13 @@ export function formatNormalizationClasses(classes) {
|
|
|
151
153
|
// Errors (spec §5)
|
|
152
154
|
// ---------------------------------------------------------------------------
|
|
153
155
|
function publicCollapsedMessage(kind) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
: "Request a new one.";
|
|
156
|
+
const issuers = formatHandleIssuers(kind.issuedBy);
|
|
157
|
+
const again = issuers ? `Call ${issuers} again to get a new one.` : "Request a new one.";
|
|
157
158
|
return `\`${kind.fieldName}\` is not valid or has expired. ${again}`;
|
|
158
159
|
}
|
|
159
160
|
function normalizationError(kind, failure) {
|
|
160
161
|
if (failure.reason === "kind_mismatch") {
|
|
161
|
-
return new HandleError("HANDLE_KIND_MISMATCH", `\`${kind.fieldName}\` must be a \`${kind.name}_...\` handle as returned by ${kind.issuedBy
|
|
162
|
+
return new HandleError("HANDLE_KIND_MISMATCH", `\`${kind.fieldName}\` must be a \`${kind.name}_...\` handle as returned by ${formatHandleIssuers(kind.issuedBy) ?? "the issuing operation"}. ${handleRecoverySentence(kind)}`, { kind: kind.name, details: { field: kind.fieldName } });
|
|
162
163
|
}
|
|
163
164
|
if (kind.access === "public") {
|
|
164
165
|
return new HandleError("HANDLE_INVALID", publicCollapsedMessage(kind), {
|
|
@@ -217,7 +218,7 @@ function committedError(kind) {
|
|
|
217
218
|
return new HandleError("HANDLE_COMMITTED", `\`${kind.fieldName}\` has already been committed and can no longer be changed. ${handleRecoverySentence(kind)}`, { kind: kind.name, details: { field: kind.fieldName } });
|
|
218
219
|
}
|
|
219
220
|
function connectionRequiredError(kind, operation) {
|
|
220
|
-
return new HandleError("HANDLE_CONNECTION_REQUIRED", `Handle kind "${kind.name}" is bound to a connection, but request.connectionId is missing for ${operation}. Bound handles require a connection-scoped request; use access: "public" for connection-less
|
|
221
|
+
return new HandleError("HANDLE_CONNECTION_REQUIRED", `Handle kind "${kind.name}" is bound to a connection, but request.connectionId is missing for ${operation}. Bound handles require a connection-scoped request; use access: "public" for connection-less kinds.`, { kind: kind.name });
|
|
221
222
|
}
|
|
222
223
|
function storageUnavailableError(kind, reason, cause) {
|
|
223
224
|
return new HandleError("HANDLE_STORAGE_UNAVAILABLE", `Handle storage for kind "${kind.name}" is not available: ${reason}.`, { kind: kind.name, cause, details: { reason } });
|
|
@@ -526,7 +527,7 @@ export function createHandleContext(options) {
|
|
|
526
527
|
record: value,
|
|
527
528
|
};
|
|
528
529
|
}
|
|
529
|
-
async function
|
|
530
|
+
async function createRecord(kind, data) {
|
|
530
531
|
return await instrumented(kind, "create", async (report) => {
|
|
531
532
|
const validated = validateData(kind, "create", data);
|
|
532
533
|
const namespace = resolveNamespace(kind, "create");
|
|
@@ -551,12 +552,16 @@ export function createHandleContext(options) {
|
|
|
551
552
|
for (let attempt = 0; attempt < CREATE_ATTEMPTS; attempt += 1) {
|
|
552
553
|
const canonical = `${kind.name}_${generateWords(kind.wordCount).join("-")}`;
|
|
553
554
|
const result = await storage(kind, () => namespace.compareAndSet(canonical, 0, record, { ttl: msDuration(writeTtlMs) }));
|
|
554
|
-
if (result.ok)
|
|
555
|
-
return { value: canonical, outcome: "success" };
|
|
555
|
+
if (result.ok) {
|
|
556
|
+
return { value: toHandleRecord(kind, canonical, record), outcome: "success" };
|
|
557
|
+
}
|
|
556
558
|
}
|
|
557
559
|
throw storageUnavailableError(kind, `could not allocate a unique handle after ${CREATE_ATTEMPTS} attempts`);
|
|
558
560
|
});
|
|
559
561
|
}
|
|
562
|
+
async function create(kind, data) {
|
|
563
|
+
return (await createRecord(kind, data)).handle;
|
|
564
|
+
}
|
|
560
565
|
async function read(kind, handle) {
|
|
561
566
|
return await instrumented(kind, "read", async (report) => {
|
|
562
567
|
const loaded = await load(kind, "read", handle, report);
|
|
@@ -876,7 +881,7 @@ export function createHandleContext(options) {
|
|
|
876
881
|
throw busyError(kind);
|
|
877
882
|
});
|
|
878
883
|
}
|
|
879
|
-
return { create, read, update, commit, discard };
|
|
884
|
+
return { create, createRecord, read, update, commit, discard };
|
|
880
885
|
}
|
|
881
886
|
/**
|
|
882
887
|
* Handle context for unit tests: memory state, a fixed connection, and an
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HttpClient } from "../types.js";
|
|
2
|
+
import { type HttpTelemetrySink } from "./http-telemetry.js";
|
|
3
|
+
/** Return whether a host binding has at least one callable declared HTTP method. */
|
|
4
|
+
export declare function isSupportedHttpBinding(value: unknown): value is HttpClient;
|
|
5
|
+
/** Emit the unsupported-shape warning once for the process. */
|
|
6
|
+
export declare function warnUnsupportedHttpBinding(): void;
|
|
7
|
+
/** Route SDK transports inside a host binding through the same attempt collector. */
|
|
8
|
+
export declare function scopedHttpTelemetry(existing: HttpTelemetrySink | undefined): HttpTelemetrySink | undefined;
|
|
9
|
+
/** Observe opaque host calls without changing their arguments, receiver or returned promise. */
|
|
10
|
+
export declare function bindHttpTelemetry(client: HttpClient, sink: HttpTelemetrySink): HttpClient;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { httpTelemetryErrorCode } from "./http-telemetry.js";
|
|
3
|
+
import { startHttpTelemetry } from "./http-telemetry-guard.js";
|
|
4
|
+
import { proxyTransportRetryErrorCode, proxyTransportRetryErrorStatus, } from "./proxy-retry-policy.js";
|
|
5
|
+
const scopes = new AsyncLocalStorage();
|
|
6
|
+
const methods = new Set(["request", "get", "post", "put", "delete", "stream", "sse"]);
|
|
7
|
+
const adapters = new WeakMap();
|
|
8
|
+
let unsupportedShapeWarned = false;
|
|
9
|
+
/** Return whether a host binding has at least one callable declared HTTP method. */
|
|
10
|
+
export function isSupportedHttpBinding(value) {
|
|
11
|
+
if ((typeof value !== "object" && typeof value !== "function") || value === null)
|
|
12
|
+
return false;
|
|
13
|
+
for (const method of methods) {
|
|
14
|
+
try {
|
|
15
|
+
if (typeof Reflect.get(value, method) === "function")
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// A hostile property getter is not a usable binding.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
/** Emit the unsupported-shape warning once for the process. */
|
|
25
|
+
export function warnUnsupportedHttpBinding() {
|
|
26
|
+
if (unsupportedShapeWarned)
|
|
27
|
+
return;
|
|
28
|
+
unsupportedShapeWarned = true;
|
|
29
|
+
try {
|
|
30
|
+
console.warn("[apifuse] http telemetry not attached; reason=unsupported_binding_shape");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Warning output is diagnostic only and must not affect request handling.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Route SDK transports inside a host binding through the same attempt collector. */
|
|
37
|
+
export function scopedHttpTelemetry(existing) {
|
|
38
|
+
const scope = scopes.getStore();
|
|
39
|
+
if (!scope)
|
|
40
|
+
return existing;
|
|
41
|
+
scope.nativeObserved = true;
|
|
42
|
+
if (!existing || existing === scope.sink)
|
|
43
|
+
return scope.sink;
|
|
44
|
+
return {
|
|
45
|
+
markTelemetryFailed: () => scope.sink.markTelemetryFailed?.(),
|
|
46
|
+
startRequest(options) {
|
|
47
|
+
const request = startHttpTelemetry(scope.sink, options.retryPreset);
|
|
48
|
+
const observer = startHttpTelemetry({
|
|
49
|
+
startRequest: (options) => existing.startRequest(options),
|
|
50
|
+
markTelemetryFailed() {
|
|
51
|
+
scope.sink.markTelemetryFailed?.();
|
|
52
|
+
existing.markTelemetryFailed?.();
|
|
53
|
+
},
|
|
54
|
+
}, options.retryPreset);
|
|
55
|
+
return {
|
|
56
|
+
recordAttempt(event) {
|
|
57
|
+
request.recordAttempt(event);
|
|
58
|
+
observer.recordAttempt(event);
|
|
59
|
+
},
|
|
60
|
+
finish(ms) {
|
|
61
|
+
request.finish(ms);
|
|
62
|
+
observer.finish(ms);
|
|
63
|
+
},
|
|
64
|
+
toTenantRetryPayload() {
|
|
65
|
+
observer.toTenantRetryPayload();
|
|
66
|
+
return request.toTenantRetryPayload();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Observe opaque host calls without changing their arguments, receiver or returned promise. */
|
|
73
|
+
export function bindHttpTelemetry(client, sink) {
|
|
74
|
+
if ((typeof client === "object" && client !== null) || typeof client === "function") {
|
|
75
|
+
const existing = adapters.get(client);
|
|
76
|
+
if (existing) {
|
|
77
|
+
existing.sink = sink;
|
|
78
|
+
return existing.adapter;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const wrappers = new Map();
|
|
82
|
+
// An inheriting target also supports hosts whose own methods are frozen.
|
|
83
|
+
const adapter = new Proxy(Object.create(client), {
|
|
84
|
+
get(_target, property) {
|
|
85
|
+
const original = Reflect.get(client, property, client);
|
|
86
|
+
if (typeof property !== "string" || !methods.has(property) || typeof original !== "function")
|
|
87
|
+
return original;
|
|
88
|
+
const cached = wrappers.get(property);
|
|
89
|
+
if (cached?.original === original)
|
|
90
|
+
return cached.wrapped;
|
|
91
|
+
const wrapped = function (...args) {
|
|
92
|
+
// Direct calls keep the host receiver; explicit and unbound receivers pass through.
|
|
93
|
+
const receiver = this === adapter ? client : this;
|
|
94
|
+
const scope = { sink: state.sink, nativeObserved: false };
|
|
95
|
+
const started = performance.now();
|
|
96
|
+
const settled = (result, failed) => {
|
|
97
|
+
if (scope.nativeObserved)
|
|
98
|
+
return;
|
|
99
|
+
const request = startHttpTelemetry(state.sink, undefined);
|
|
100
|
+
request.observe(() => {
|
|
101
|
+
const status = failed
|
|
102
|
+
? proxyTransportRetryErrorStatus(result)
|
|
103
|
+
: result &&
|
|
104
|
+
typeof result === "object" &&
|
|
105
|
+
"status" in result &&
|
|
106
|
+
typeof result.status === "number"
|
|
107
|
+
? result.status
|
|
108
|
+
: undefined;
|
|
109
|
+
request.recordAttempt({
|
|
110
|
+
ms: performance.now() - started,
|
|
111
|
+
proxyUsed: false,
|
|
112
|
+
...(status === undefined ? {} : { status }),
|
|
113
|
+
...(failed
|
|
114
|
+
? { e: httpTelemetryErrorCode(proxyTransportRetryErrorCode(result)) }
|
|
115
|
+
: {}),
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
request.finish(performance.now() - started);
|
|
119
|
+
};
|
|
120
|
+
return scopes.run(scope, () => {
|
|
121
|
+
let result;
|
|
122
|
+
try {
|
|
123
|
+
result = Reflect.apply(original, receiver, args);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
settled(error, true);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
if (result instanceof Promise) {
|
|
131
|
+
void Promise.prototype.then.call(result, (value) => settled(value, false), (error) => settled(error, true));
|
|
132
|
+
}
|
|
133
|
+
else
|
|
134
|
+
settled(result, false);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
// Promise subclasses may throw while a telemetry continuation is attached.
|
|
138
|
+
startHttpTelemetry(state.sink, undefined).observe(() => {
|
|
139
|
+
throw error;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
wrappers.set(property, { original, wrapped });
|
|
146
|
+
return wrapped;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
const state = { sink, adapter };
|
|
150
|
+
adapters.set(adapter, state);
|
|
151
|
+
return adapter;
|
|
152
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HttpRetrySummary } from "../types.js";
|
|
2
|
+
import { type HttpTelemetryRequestSink, type HttpTelemetrySink } from "./http-telemetry.js";
|
|
3
|
+
/** Shared synchronous observer boundary for transport and capability contributors. */
|
|
4
|
+
export declare function observeTelemetry<T>(callback: () => T, fail: () => void): T | undefined;
|
|
5
|
+
/** Isolate synchronous void capability hooks using the same Promise defence. */
|
|
6
|
+
export declare function createTelemetryObserver(sink: {
|
|
7
|
+
markTelemetryFailed?(): void;
|
|
8
|
+
} | undefined): (callback: () => unknown) => void;
|
|
9
|
+
/** Keep all public observers, including property access, outside transport failures. */
|
|
10
|
+
export declare function startHttpTelemetry(sink: HttpTelemetrySink | undefined, retryPreset: HttpRetrySummary["preset"]): HttpTelemetryRequestSink & {
|
|
11
|
+
observe(callback: () => void): void;
|
|
12
|
+
};
|
|
13
|
+
/** Generic observer guard shared by capability hooks. Returned thenables are absorbed. */
|
|
14
|
+
export declare function observeTelemetryCallback(sink: {
|
|
15
|
+
markTelemetryFailed?: () => unknown;
|
|
16
|
+
} | undefined, callback: () => unknown): void;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { isPromise } from "node:util/types";
|
|
2
|
+
import { HttpTelemetryCollector, } from "./http-telemetry.js";
|
|
3
|
+
const intrinsicPromiseThen = Promise.prototype.then;
|
|
4
|
+
const ignoreObserverResult = () => { };
|
|
5
|
+
/** Attach handlers only through the Promise intrinsic; never invoke a host thenable. */
|
|
6
|
+
function absorbObserverPromise(value) {
|
|
7
|
+
let constructorDescriptor;
|
|
8
|
+
let shadowed = false;
|
|
9
|
+
try {
|
|
10
|
+
constructorDescriptor = Object.getOwnPropertyDescriptor(value, "constructor");
|
|
11
|
+
// Even the intrinsic `then` consults Symbol.species. An undefined constructor
|
|
12
|
+
// selects its intrinsic Promise without reading a subclass's species getter.
|
|
13
|
+
Object.defineProperty(value, "constructor", {
|
|
14
|
+
value: undefined,
|
|
15
|
+
configurable: constructorDescriptor?.configurable ?? true,
|
|
16
|
+
});
|
|
17
|
+
shadowed = true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Non-extensible ordinary promises can still accept intrinsic handlers.
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
void intrinsicPromiseThen.call(value, ignoreObserverResult, ignoreObserverResult);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// A non-Promise thenable has no intrinsic rejection to absorb. Do not run it.
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
if (shadowed) {
|
|
30
|
+
try {
|
|
31
|
+
if (constructorDescriptor) {
|
|
32
|
+
Object.defineProperty(value, "constructor", constructorDescriptor);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
Reflect.deleteProperty(value, "constructor");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Host object traps are also observer failures.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function isAsyncObserverReturn(value) {
|
|
45
|
+
if (!value || (typeof value !== "object" && typeof value !== "function"))
|
|
46
|
+
return false;
|
|
47
|
+
try {
|
|
48
|
+
if (!isPromise(value) && (!("then" in value) || typeof value.then !== "function"))
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// A rejected Promise can itself have a throwing `then` getter. Still absorb it.
|
|
53
|
+
}
|
|
54
|
+
absorbObserverPromise(value);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/** Shared synchronous observer boundary for transport and capability contributors. */
|
|
58
|
+
export function observeTelemetry(callback, fail) {
|
|
59
|
+
try {
|
|
60
|
+
const result = callback();
|
|
61
|
+
if (isAsyncObserverReturn(result)) {
|
|
62
|
+
fail();
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
fail();
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Isolate synchronous void capability hooks using the same Promise defence. */
|
|
73
|
+
export function createTelemetryObserver(sink) {
|
|
74
|
+
let failed = false;
|
|
75
|
+
const fail = () => {
|
|
76
|
+
if (failed)
|
|
77
|
+
return;
|
|
78
|
+
failed = true;
|
|
79
|
+
try {
|
|
80
|
+
isAsyncObserverReturn(sink?.markTelemetryFailed?.());
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// The failure marker is also an observer.
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return (callback) => {
|
|
87
|
+
try {
|
|
88
|
+
const result = callback();
|
|
89
|
+
if (result !== undefined) {
|
|
90
|
+
isAsyncObserverReturn(result);
|
|
91
|
+
fail();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
fail();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Keep all public observers, including property access, outside transport failures. */
|
|
100
|
+
export function startHttpTelemetry(sink, retryPreset) {
|
|
101
|
+
let failed = false;
|
|
102
|
+
const fail = () => {
|
|
103
|
+
if (failed)
|
|
104
|
+
return;
|
|
105
|
+
failed = true;
|
|
106
|
+
try {
|
|
107
|
+
isAsyncObserverReturn(sink?.markTelemetryFailed?.());
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// The failure reporter is itself an observer. Never recurse.
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const observe = (callback) => observeTelemetry(callback, fail);
|
|
114
|
+
const result = observe(() => (sink ?? new HttpTelemetryCollector()).startRequest({ retryPreset }));
|
|
115
|
+
const request = observe(() => {
|
|
116
|
+
if (!result ||
|
|
117
|
+
typeof result.recordAttempt !== "function" ||
|
|
118
|
+
typeof result.finish !== "function" ||
|
|
119
|
+
typeof result.toTenantRetryPayload !== "function") {
|
|
120
|
+
fail();
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
observe,
|
|
127
|
+
recordAttempt: (event) => {
|
|
128
|
+
observe(() => request?.recordAttempt(event));
|
|
129
|
+
},
|
|
130
|
+
finish: (ms) => {
|
|
131
|
+
observe(() => request?.finish(ms));
|
|
132
|
+
},
|
|
133
|
+
toTenantRetryPayload: () => {
|
|
134
|
+
if (failed)
|
|
135
|
+
return undefined;
|
|
136
|
+
return observe(() => {
|
|
137
|
+
const value = observe(() => request?.toTenantRetryPayload());
|
|
138
|
+
if (value === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
// Copy only validated primitive fields; never pass observer-owned objects on.
|
|
141
|
+
const { attempts, retries, preset, transport, lastErrorCode, lastStatus } = value;
|
|
142
|
+
if (!Number.isSafeInteger(attempts) ||
|
|
143
|
+
attempts < 2 ||
|
|
144
|
+
!Number.isSafeInteger(retries) ||
|
|
145
|
+
(retries !== attempts - 1 &&
|
|
146
|
+
!(attempts === Number.MAX_SAFE_INTEGER && retries === Number.MAX_SAFE_INTEGER)) ||
|
|
147
|
+
transport !== "native" ||
|
|
148
|
+
(preset !== undefined &&
|
|
149
|
+
![
|
|
150
|
+
"off",
|
|
151
|
+
"transport_transient",
|
|
152
|
+
"safe_read",
|
|
153
|
+
"aggressive_read",
|
|
154
|
+
"rate_limit_aware",
|
|
155
|
+
].includes(preset)) ||
|
|
156
|
+
(lastErrorCode !== undefined &&
|
|
157
|
+
![
|
|
158
|
+
"transport_network_error",
|
|
159
|
+
"transport_timeout",
|
|
160
|
+
"transport_cancelled",
|
|
161
|
+
"upstream_http_error",
|
|
162
|
+
"other",
|
|
163
|
+
].includes(lastErrorCode)) ||
|
|
164
|
+
(lastStatus !== undefined && (!Number.isSafeInteger(lastStatus) || lastStatus < 0))) {
|
|
165
|
+
fail();
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
attempts,
|
|
170
|
+
retries,
|
|
171
|
+
...(preset === undefined ? {} : { preset }),
|
|
172
|
+
transport,
|
|
173
|
+
...(lastErrorCode === undefined ? {} : { lastErrorCode }),
|
|
174
|
+
...(lastStatus === undefined ? {} : { lastStatus }),
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** Generic observer guard shared by capability hooks. Returned thenables are absorbed. */
|
|
181
|
+
export function observeTelemetryCallback(sink, callback) {
|
|
182
|
+
let failed = false;
|
|
183
|
+
const fail = () => {
|
|
184
|
+
if (failed)
|
|
185
|
+
return;
|
|
186
|
+
failed = true;
|
|
187
|
+
try {
|
|
188
|
+
isAsyncObserverReturn(sink?.markTelemetryFailed?.());
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Observer failure is never transport failure.
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
try {
|
|
195
|
+
const result = callback();
|
|
196
|
+
if (isAsyncObserverReturn(result) || result !== undefined)
|
|
197
|
+
fail();
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
fail();
|
|
201
|
+
}
|
|
202
|
+
}
|