@apifuse/provider-sdk 2.2.0-beta.1 → 2.2.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORING.md +106 -0
- package/CHANGELOG.md +37 -0
- package/README.md +5 -1
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +44 -59
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-check.ts +14 -0
- package/bin/apifuse-pack-smoke.ts +22 -81
- package/bin/apifuse-pack-types.ts +266 -0
- package/bin/apifuse-perf.ts +45 -127
- package/bin/apifuse-record.ts +53 -70
- package/bin/apifuse-submit-check.ts +510 -45
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml-semantics.ts +204 -0
- package/bin/submit-check-xml.ts +134 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +9 -18
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +8 -18
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +34 -35
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/config/loader.d.ts +149 -8
- package/dist/config/loader.js +378 -89
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +3 -6
- package/dist/contract-types.d.ts +2 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +4 -6
- package/dist/define.d.ts +8 -1
- package/dist/define.js +119 -122
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.js +48 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +44 -42
- package/dist/index.js +39 -37
- package/dist/lint.js +8 -15
- package/dist/provider.d.ts +11 -11
- package/dist/provider.js +9 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +1 -1
- package/dist/runtime/auth-flow.js +3 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +15 -29
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.js +4 -8
- package/dist/runtime/choice.d.ts +1 -1
- package/dist/runtime/choice.js +31 -35
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +15 -3
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +144 -348
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +56 -19
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +35 -0
- package/dist/runtime/proxy-nodemaven.js +128 -0
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +55 -52
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +2 -4
- package/dist/runtime/request-options.d.ts +1 -1
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +15 -4
- package/dist/runtime/stealth.d.ts +2 -2
- package/dist/runtime/stealth.js +108 -212
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +11 -15
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +104 -3
- package/dist/server/self-test.js +673 -115
- package/dist/server/serve.d.ts +8 -3
- package/dist/server/serve.js +105 -59
- package/dist/server/types.d.ts +10 -9
- package/dist/server/types.js +3 -7
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +5 -14
- package/dist/stream.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/run.d.ts +1 -1
- package/dist/testing/run.js +7 -14
- package/dist/types.d.ts +87 -1
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +9 -5
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +28 -86
- package/src/ceremonies/index.ts +24 -78
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +42 -35
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/config/loader.ts +543 -208
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +12 -28
- package/src/define.ts +247 -505
- package/src/dev.ts +4 -9
- package/src/errors.ts +58 -5
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +49 -42
- package/src/lint.ts +89 -159
- package/src/provider.ts +15 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +5 -7
- package/src/runtime/browser.ts +64 -187
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +79 -132
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +23 -20
- package/src/runtime/http.ts +195 -559
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +78 -65
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +178 -0
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/proxy-telemetry.ts +79 -77
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +41 -110
- package/src/runtime/stealth.ts +162 -377
- package/src/runtime/stt.ts +38 -94
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +1 -1
- package/src/server/index.ts +12 -7
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +857 -132
- package/src/server/serve.ts +172 -245
- package/src/server/types.ts +7 -19
- package/src/stealth/profiles.ts +10 -26
- package/src/stream.ts +8 -19
- package/src/testing/index.ts +2 -2
- package/src/testing/run.ts +19 -67
- package/src/types.ts +88 -1
- package/src/user-input.ts +118 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JsonValue } from "./contract-json";
|
|
2
|
-
import type { SchemaLike } from "./types";
|
|
1
|
+
import { type JsonValue } from "./contract-json.js";
|
|
2
|
+
import type { SchemaLike } from "./types.js";
|
|
3
3
|
export declare function describeSchema(schema: SchemaLike): JsonValue;
|
|
4
4
|
export declare function serializeSmsMatcher(value: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { canonicalJson, compactObject, isRecord, toJsonValue, } from "./contract-json";
|
|
3
|
+
import { canonicalJson, compactObject, isRecord, toJsonValue, } from "./contract-json.js";
|
|
4
4
|
export function describeSchema(schema) {
|
|
5
5
|
if (isZodSchema(schema)) {
|
|
6
6
|
const jsonSchema = zodJsonSchema(schema);
|
|
@@ -9,9 +9,7 @@ export function describeSchema(schema) {
|
|
|
9
9
|
vendor: "zod",
|
|
10
10
|
typeName: getSchemaTypeName(schema),
|
|
11
11
|
jsonSchema,
|
|
12
|
-
jsonSchemaHash: jsonSchema === undefined
|
|
13
|
-
? undefined
|
|
14
|
-
: digest(canonicalJson(jsonSchema)),
|
|
12
|
+
jsonSchemaHash: jsonSchema === undefined ? undefined : digest(canonicalJson(jsonSchema)),
|
|
15
13
|
});
|
|
16
14
|
}
|
|
17
15
|
const standard = isRecord(schema) ? schema["~standard"] : undefined;
|
|
@@ -20,8 +18,7 @@ export function describeSchema(schema) {
|
|
|
20
18
|
kind: "schema",
|
|
21
19
|
standard: "standard-schema-v1",
|
|
22
20
|
vendor: typeof standard.vendor === "string" ? standard.vendor : "unknown",
|
|
23
|
-
version: typeof standard.version === "number" ||
|
|
24
|
-
typeof standard.version === "string"
|
|
21
|
+
version: typeof standard.version === "number" || typeof standard.version === "string"
|
|
25
22
|
? standard.version
|
|
26
23
|
: undefined,
|
|
27
24
|
});
|
package/dist/contract-types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { JsonValue } from "./contract-json";
|
|
2
|
-
import type { ProviderDefinition } from "./types";
|
|
1
|
+
import type { JsonValue } from "./contract-json.js";
|
|
2
|
+
import type { ProviderDefinition } from "./types.js";
|
|
3
3
|
export declare const PROVIDER_CONTRACT_SCHEMA_VERSION = "2026-06-23";
|
|
4
4
|
export interface ProviderContractSnapshot {
|
|
5
5
|
readonly schemaVersion: typeof PROVIDER_CONTRACT_SCHEMA_VERSION;
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { canonicalJson, type JsonPrimitive, type JsonValue } from "./contract-json";
|
|
2
|
-
import { PROVIDER_CONTRACT_SCHEMA_VERSION, type ProviderContractOperation, type ProviderContractSnapshot } from "./contract-types";
|
|
3
|
-
import type { ProviderDefinition } from "./types";
|
|
1
|
+
import { canonicalJson, type JsonPrimitive, type JsonValue } from "./contract-json.js";
|
|
2
|
+
import { PROVIDER_CONTRACT_SCHEMA_VERSION, type ProviderContractOperation, type ProviderContractSnapshot } from "./contract-types.js";
|
|
3
|
+
import type { ProviderDefinition } from "./types.js";
|
|
4
4
|
export { canonicalJson, type JsonPrimitive, type JsonValue, PROVIDER_CONTRACT_SCHEMA_VERSION, type ProviderContractOperation, type ProviderContractSnapshot, };
|
|
5
5
|
export declare function extractProviderContract(provider: ProviderDefinition): ProviderContractSnapshot;
|
|
6
6
|
export declare function digestProviderContract(snapshot: ProviderContractSnapshot): string;
|
package/dist/contract.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { canonicalJson, compactObject, copyRecordWithout, toJsonValue, } from "./contract-json";
|
|
3
|
-
import { describeSchema, serializeSmsMatcher } from "./contract-serialization";
|
|
4
|
-
import { PROVIDER_CONTRACT_SCHEMA_VERSION, } from "./contract-types";
|
|
2
|
+
import { canonicalJson, compactObject, copyRecordWithout, toJsonValue, } from "./contract-json.js";
|
|
3
|
+
import { describeSchema, serializeSmsMatcher } from "./contract-serialization.js";
|
|
4
|
+
import { PROVIDER_CONTRACT_SCHEMA_VERSION, } from "./contract-types.js";
|
|
5
5
|
export { canonicalJson, PROVIDER_CONTRACT_SCHEMA_VERSION, };
|
|
6
6
|
export function extractProviderContract(provider) {
|
|
7
7
|
const auth = extractAuth(provider.auth);
|
|
@@ -27,9 +27,7 @@ export function extractProviderContract(provider) {
|
|
|
27
27
|
operations: Object.entries(provider.operations)
|
|
28
28
|
.sort(([leftId], [rightId]) => leftId.localeCompare(rightId))
|
|
29
29
|
.map(([operationId, operation]) => extractOperation(operationId, operation)),
|
|
30
|
-
...(provider.allowedHosts
|
|
31
|
-
? { allowedHosts: [...provider.allowedHosts].sort() }
|
|
32
|
-
: {}),
|
|
30
|
+
...(provider.allowedHosts ? { allowedHosts: [...provider.allowedHosts].sort() } : {}),
|
|
33
31
|
...(stealth === undefined ? {} : { stealth }),
|
|
34
32
|
...(proxy === undefined ? {} : { proxy }),
|
|
35
33
|
...(stt === undefined ? {} : { stt }),
|
package/dist/define.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthConfig, BrowserEngine, ContextDeclaration, CredentialDeclaration, HealthJourneyDefinition, HealthJourneySchedule, HealthScheduleRandomization, InferSchemaOutput, OperationDefinition, OperationHandlerResult, OperationHttpStreamTransport, OperationSseTransport, OperationWebSocketTransport, ProviderAccessConfig, ProviderDefinition, ProviderHealthMonitorConfig, ProviderProxyConfig, ProviderPublicProfile, ProviderReviewed, ProviderSecretDeclaration, ProviderStreamEvent, ProviderSttConfig, SchemaLike, SmsOtpMatcherDefinition, StealthPlatform } from "./types";
|
|
1
|
+
import type { AuthConfig, BrowserEngine, ContextDeclaration, CredentialDeclaration, HealthJourneyDefinition, HealthJourneySchedule, HealthScheduleRandomization, InferSchemaOutput, OperationDefinition, OperationHandlerResult, OperationHttpStreamTransport, OperationSseTransport, OperationWebSocketTransport, ProviderAccessConfig, ProviderDefinition, ProviderDeploymentOverrides, ProviderHealthMonitorConfig, ProviderProxyConfig, ProviderPublicProfile, ProviderReviewed, ProviderSecretDeclaration, ProviderStreamEvent, ProviderSttConfig, SchemaLike, SmsOtpMatcherDefinition, StealthPlatform } from "./types.js";
|
|
2
2
|
type ProviderImplementationSourceAccess = "official_api" | "private_api" | "browser_flow" | "hybrid";
|
|
3
3
|
type ProviderImplementationCredentialStrategy = "apifuse_managed" | "workspace_secret" | "user_oauth" | "user_session" | "none";
|
|
4
4
|
interface ProviderImplementationProfile {
|
|
@@ -41,6 +41,13 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
41
41
|
id: string;
|
|
42
42
|
version: string;
|
|
43
43
|
runtime: "standard" | "shared" | "browser";
|
|
44
|
+
/**
|
|
45
|
+
* Optional deployment overrides, passed through verbatim onto the returned
|
|
46
|
+
* provider definition. The SDK types this field but does not deep-validate
|
|
47
|
+
* it — the APIFuse registry builder owns deployment validation and
|
|
48
|
+
* resolves omitted fields against the runtime deployment profiles.
|
|
49
|
+
*/
|
|
50
|
+
deployment?: ProviderDeploymentOverrides;
|
|
44
51
|
allowedHosts?: string[];
|
|
45
52
|
stealth?: {
|
|
46
53
|
profile: string;
|
package/dist/define.js
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
import ms from "ms";
|
|
2
|
-
import { ProviderError, ValidationError } from "./errors";
|
|
3
|
-
import { safeParseSchemaSync } from "./schema";
|
|
4
|
-
import { HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX, HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN, HEALTH_CHECK_TIMEOUT_MS_MAX, HEALTH_CHECK_TIMEOUT_MS_MIN, OPERATION_TIMEOUT_MS_MAX, OPERATION_TIMEOUT_MS_MIN, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types";
|
|
2
|
+
import { ProviderError, ValidationError } from "./errors.js";
|
|
3
|
+
import { safeParseSchemaSync } from "./schema.js";
|
|
4
|
+
import { HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX, HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN, HEALTH_CHECK_TIMEOUT_MS_MAX, HEALTH_CHECK_TIMEOUT_MS_MIN, OPERATION_TIMEOUT_MS_MAX, OPERATION_TIMEOUT_MS_MIN, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types.js";
|
|
5
5
|
const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
|
|
6
6
|
const OPERATION_ID_REGEX = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
7
7
|
const VALID_RUNTIMES = ["standard", "shared", "browser"];
|
|
8
|
-
const VALID_AUTH_MODES = [
|
|
9
|
-
"none",
|
|
10
|
-
"platform-managed",
|
|
11
|
-
"credentials",
|
|
12
|
-
"oauth2",
|
|
13
|
-
];
|
|
8
|
+
const VALID_AUTH_MODES = ["none", "platform-managed", "credentials", "oauth2"];
|
|
14
9
|
const VALID_PROVIDER_ACCESS_VISIBILITIES = ["public", "early_access"];
|
|
15
|
-
const VALID_PROVIDER_PROXY_MODES = [
|
|
16
|
-
|
|
17
|
-
"optional",
|
|
18
|
-
"required",
|
|
19
|
-
];
|
|
20
|
-
const VALID_PROVIDER_PROXY_PROVIDERS = [
|
|
21
|
-
"smartproxy",
|
|
22
|
-
"decodo",
|
|
23
|
-
"custom",
|
|
24
|
-
];
|
|
10
|
+
const VALID_PROVIDER_PROXY_MODES = ["disabled", "optional", "required"];
|
|
11
|
+
const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "nodemaven", "decodo", "custom"];
|
|
25
12
|
const VALID_PROVIDER_PROXY_AFFINITIES = [
|
|
26
13
|
"request",
|
|
27
14
|
"operation",
|
|
@@ -30,25 +17,25 @@ const VALID_PROVIDER_PROXY_AFFINITIES = [
|
|
|
30
17
|
];
|
|
31
18
|
const VALID_PROVIDER_STT_MODES = ["optional", "required"];
|
|
32
19
|
const SMARTPROXY_APP_KEY_SECRET = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
|
|
20
|
+
const NODEMAVEN_USERNAME_SECRET = "APIFUSE__PROXY__NODEMAVEN_USERNAME";
|
|
21
|
+
const NODEMAVEN_PASSWORD_SECRET = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
|
|
22
|
+
// Per-vendor provider-declared credential secrets. A required-mode chain must
|
|
23
|
+
// declare every secret of every credentialed vendor it names, so a missing
|
|
24
|
+
// credential fails at build/validation time rather than during a live outage: a
|
|
25
|
+
// declared-but-uncredentialed fallback leg is a silently dead SPOF, which is
|
|
26
|
+
// exactly the failure class the multi-vendor chain exists to remove. Vendors
|
|
27
|
+
// absent from this map (e.g. `custom`/`decodo`, whose credentials come from the
|
|
28
|
+
// `APIFUSE__PROXY__URL` bring-your-own escape hatch, not provider secrets) impose
|
|
29
|
+
// no declaration requirement.
|
|
30
|
+
const VENDOR_REQUIRED_SECRETS = {
|
|
31
|
+
smartproxy: [SMARTPROXY_APP_KEY_SECRET],
|
|
32
|
+
nodemaven: [NODEMAVEN_USERNAME_SECRET, NODEMAVEN_PASSWORD_SECRET],
|
|
33
|
+
};
|
|
33
34
|
const RESERVED_OPERATION_IDS = new Set(["auth", "health"]);
|
|
34
35
|
const MCP_TOOL_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_]{0,127}$/;
|
|
35
|
-
const VALID_OPERATION_RISK_CLASSES = [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"destructive",
|
|
39
|
-
"external-send",
|
|
40
|
-
];
|
|
41
|
-
const VALID_OPERATION_APPROVAL_POLICIES = [
|
|
42
|
-
"never",
|
|
43
|
-
"risk-based",
|
|
44
|
-
"always",
|
|
45
|
-
];
|
|
46
|
-
const VALID_OPERATION_TRANSPORT_KINDS = [
|
|
47
|
-
"json",
|
|
48
|
-
"sse",
|
|
49
|
-
"http-stream",
|
|
50
|
-
"websocket",
|
|
51
|
-
];
|
|
36
|
+
const VALID_OPERATION_RISK_CLASSES = ["read", "write", "destructive", "external-send"];
|
|
37
|
+
const VALID_OPERATION_APPROVAL_POLICIES = ["never", "risk-based", "always"];
|
|
38
|
+
const VALID_OPERATION_TRANSPORT_KINDS = ["json", "sse", "http-stream", "websocket"];
|
|
52
39
|
const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
|
|
53
40
|
const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
54
41
|
const MS_DURATION_PATTERN = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))\s*([a-zA-Z]+)?$/;
|
|
@@ -109,10 +96,7 @@ function validateProviderShape(config) {
|
|
|
109
96
|
if (typeof config.runtime === "string")
|
|
110
97
|
assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
|
|
111
98
|
const auth = config.auth;
|
|
112
|
-
if (auth &&
|
|
113
|
-
typeof auth === "object" &&
|
|
114
|
-
"mode" in auth &&
|
|
115
|
-
typeof auth.mode === "string")
|
|
99
|
+
if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
|
|
116
100
|
assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
|
|
117
101
|
if (auth && typeof auth === "object" && "exchange" in auth) {
|
|
118
102
|
throw new ProviderError(`Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`, {
|
|
@@ -167,15 +151,23 @@ function validateProviderProxy(config) {
|
|
|
167
151
|
fix: `Use proxy: { mode: "required", provider: "smartproxy", geo: { country: "KR" }, session: { affinity: "connection", lifetimeMinutes: 30 } }`,
|
|
168
152
|
});
|
|
169
153
|
}
|
|
170
|
-
rejectUnknownFields(proxy, new Set(["mode", "provider", "geo", "session"]), "proxy");
|
|
154
|
+
rejectUnknownFields(proxy, new Set(["mode", "provider", "providers", "geo", "session"]), "proxy");
|
|
171
155
|
assertLiteralField(proxy.mode, "proxy.mode", VALID_PROVIDER_PROXY_MODES, config.id);
|
|
172
156
|
if (proxy.provider !== undefined) {
|
|
173
157
|
assertLiteralField(proxy.provider, "proxy.provider", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
|
|
174
158
|
}
|
|
159
|
+
if (proxy.providers !== undefined) {
|
|
160
|
+
if (!Array.isArray(proxy.providers) || proxy.providers.length === 0) {
|
|
161
|
+
throw new ValidationError(`Provider "${config.id}" has invalid proxy.providers: must be a non-empty array of proxy vendors.`, {
|
|
162
|
+
fix: `Use proxy.providers: ["smartproxy", "nodemaven"] to declare an ordered fallback chain.`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
for (const vendor of proxy.providers) {
|
|
166
|
+
assertLiteralField(vendor, "proxy.providers[]", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
175
169
|
if (proxy.geo !== undefined) {
|
|
176
|
-
if (!proxy.geo ||
|
|
177
|
-
typeof proxy.geo !== "object" ||
|
|
178
|
-
Array.isArray(proxy.geo)) {
|
|
170
|
+
if (!proxy.geo || typeof proxy.geo !== "object" || Array.isArray(proxy.geo)) {
|
|
179
171
|
throw new ValidationError(`Provider "${config.id}" has invalid proxy.geo: must be an object.`, {
|
|
180
172
|
fix: `Use proxy.geo: { country: "KR" } with ISO alpha-2 country codes.`,
|
|
181
173
|
});
|
|
@@ -192,9 +184,7 @@ function validateProviderProxy(config) {
|
|
|
192
184
|
}
|
|
193
185
|
}
|
|
194
186
|
if (proxy.session !== undefined) {
|
|
195
|
-
if (!proxy.session ||
|
|
196
|
-
typeof proxy.session !== "object" ||
|
|
197
|
-
Array.isArray(proxy.session)) {
|
|
187
|
+
if (!proxy.session || typeof proxy.session !== "object" || Array.isArray(proxy.session)) {
|
|
198
188
|
throw new ValidationError(`Provider "${config.id}" has invalid proxy.session: must be an object.`, {
|
|
199
189
|
fix: `Use proxy.session: { affinity: "connection", lifetimeMinutes: 30 }.`,
|
|
200
190
|
});
|
|
@@ -204,31 +194,62 @@ function validateProviderProxy(config) {
|
|
|
204
194
|
assertLiteralField(proxy.session.affinity, "proxy.session.affinity", VALID_PROVIDER_PROXY_AFFINITIES, config.id);
|
|
205
195
|
}
|
|
206
196
|
const lifetime = proxy.session.lifetimeMinutes;
|
|
207
|
-
if (lifetime !== undefined &&
|
|
208
|
-
(!Number.isFinite(lifetime) || lifetime <= 0)) {
|
|
197
|
+
if (lifetime !== undefined && (!Number.isFinite(lifetime) || lifetime <= 0)) {
|
|
209
198
|
throw new ValidationError(`Provider "${config.id}" has invalid proxy.session.lifetimeMinutes: must be a positive number of minutes.`);
|
|
210
199
|
}
|
|
211
200
|
const poolSize = proxy.session.poolSize;
|
|
212
|
-
if (poolSize !== undefined &&
|
|
213
|
-
(!Number.isInteger(poolSize) || poolSize <= 0)) {
|
|
201
|
+
if (poolSize !== undefined && (!Number.isInteger(poolSize) || poolSize <= 0)) {
|
|
214
202
|
throw new ValidationError(`Provider "${config.id}" has invalid proxy.session.poolSize: must be a positive integer.`);
|
|
215
203
|
}
|
|
216
204
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
205
|
+
// Every credentialed vendor in a required-mode chain must declare its
|
|
206
|
+
// provider secret(s) so a missing credential fails at build/validation time,
|
|
207
|
+
// not during a live outage. This covers the fallback legs too (not just the
|
|
208
|
+
// first vendor): a declared-but-uncredentialed nodemaven fallback would leave
|
|
209
|
+
// the chain silently down to a single vendor, reintroducing the SPOF the chain
|
|
210
|
+
// removes.
|
|
211
|
+
const vendorChain = proxy.providers && proxy.providers.length > 0
|
|
212
|
+
? proxy.providers
|
|
213
|
+
: proxy.provider
|
|
214
|
+
? [proxy.provider]
|
|
215
|
+
: [];
|
|
216
|
+
if (proxy.mode === "required") {
|
|
217
|
+
for (const vendor of vendorChain) {
|
|
218
|
+
const requiredSecrets = VENDOR_REQUIRED_SECRETS[vendor];
|
|
219
|
+
if (!requiredSecrets)
|
|
220
|
+
continue;
|
|
221
|
+
for (const secretName of requiredSecrets) {
|
|
222
|
+
// Match the canonical runtime gate (assertRequiredSecretsPresent /
|
|
223
|
+
// listMissingRequiredSecrets), which enforces only `required === true`
|
|
224
|
+
// declarations. A declaration that omits `required` (defaulting to
|
|
225
|
+
// optional) is skipped at runtime, so accepting it here would pass
|
|
226
|
+
// validation while leaving the credential unenforced until proxy
|
|
227
|
+
// resolution during a live request — the fail-open gap this check exists
|
|
228
|
+
// to close.
|
|
229
|
+
const declared = config.secrets?.some((secret) => secret.name === secretName && secret.required === true);
|
|
230
|
+
if (!declared) {
|
|
231
|
+
throw new ValidationError(`Provider "${config.id}" requires ${vendor} egress but does not declare ${secretName}.`, {
|
|
232
|
+
fix: `Add secrets: [{ name: "${secretName}", required: true }] to the provider (every vendor in a required proxy chain must declare its credential secrets).`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
223
236
|
}
|
|
224
237
|
}
|
|
238
|
+
// `decodo`/`custom` are deprecated vendor values (string-union members, so the
|
|
239
|
+
// @deprecated symbol gate can't catch them — warn at validation time instead).
|
|
240
|
+
const deprecatedVendors = vendorChain.filter((vendor) => vendor === "decodo" || vendor === "custom");
|
|
241
|
+
if (deprecatedVendors.length > 0) {
|
|
242
|
+
console.warn(`[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven", or the APIFUSE__PROXY__URL bring-your-own escape hatch.`);
|
|
243
|
+
}
|
|
225
244
|
}
|
|
226
245
|
function validateProviderStt(config) {
|
|
227
246
|
const stt = config.stt;
|
|
228
247
|
if (stt === undefined)
|
|
229
248
|
return;
|
|
230
249
|
if (!stt || typeof stt !== "object" || Array.isArray(stt)) {
|
|
231
|
-
throw new ValidationError(`Provider "${config.id}" has invalid stt: must be an object.`, {
|
|
250
|
+
throw new ValidationError(`Provider "${config.id}" has invalid stt: must be an object.`, {
|
|
251
|
+
fix: `Use stt: { mode: "required" } or stt: { mode: "optional" }.`,
|
|
252
|
+
});
|
|
232
253
|
}
|
|
233
254
|
rejectUnknownFields(stt, new Set(["mode"]), "stt");
|
|
234
255
|
assertLiteralField(stt.mode, "stt.mode", VALID_PROVIDER_STT_MODES, config.id);
|
|
@@ -247,12 +268,7 @@ function validateOperationIds(providerId, operations) {
|
|
|
247
268
|
}
|
|
248
269
|
const OPERATION_CONTRACT_VERSION_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
249
270
|
const OPERATION_SENSITIVE_PATH_REGEX = /^(?:[A-Za-z0-9_$-]+|\*)(?:\.(?:[A-Za-z0-9_$-]+|\*))*$/;
|
|
250
|
-
const VALID_OPERATION_LIFECYCLES = [
|
|
251
|
-
"stable",
|
|
252
|
-
"beta",
|
|
253
|
-
"deprecated",
|
|
254
|
-
"removed",
|
|
255
|
-
];
|
|
271
|
+
const VALID_OPERATION_LIFECYCLES = ["stable", "beta", "deprecated", "removed"];
|
|
256
272
|
function assertNonEmptyString(value, field, providerId, operationName) {
|
|
257
273
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
258
274
|
throw new ValidationError(`Provider "${providerId}" operation "${operationName}" has invalid ${field}: must be a non-empty string.`, { fix: `Set ${field} to a non-empty customer-facing value.` });
|
|
@@ -268,8 +284,7 @@ function validateToolRouterMetadata(providerId, operations) {
|
|
|
268
284
|
fix: `Remove operations.${operationName}.toolRouter or provide MCP-safe metadata.`,
|
|
269
285
|
});
|
|
270
286
|
}
|
|
271
|
-
if (toolRouter.name !== undefined &&
|
|
272
|
-
!MCP_TOOL_NAME_REGEX.test(toolRouter.name)) {
|
|
287
|
+
if (toolRouter.name !== undefined && !MCP_TOOL_NAME_REGEX.test(toolRouter.name)) {
|
|
273
288
|
throw new ValidationError(`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter.name: expected an MCP-safe name.`, {
|
|
274
289
|
fix: `Use letters, numbers, and underscores only, starting with a letter, for example "${providerId.replace(/[^A-Za-z0-9]+/g, "_")}__${operationName.replace(/[^A-Za-z0-9]+/g, "_")}".`,
|
|
275
290
|
});
|
|
@@ -307,8 +322,7 @@ function validateOperationContracts(providerId, operations) {
|
|
|
307
322
|
if (contract.lifecycle !== undefined) {
|
|
308
323
|
assertLiteralField(contract.lifecycle, `operations.${operationName}.contract.lifecycle`, VALID_OPERATION_LIFECYCLES, providerId);
|
|
309
324
|
}
|
|
310
|
-
if (contract.lifecycle === "deprecated" ||
|
|
311
|
-
contract.lifecycle === "removed") {
|
|
325
|
+
if (contract.lifecycle === "deprecated" || contract.lifecycle === "removed") {
|
|
312
326
|
if (!contract.deprecation || typeof contract.deprecation !== "object") {
|
|
313
327
|
throw new ValidationError(`Provider "${providerId}" operation "${operationName}" is ${contract.lifecycle} but lacks operations.${operationName}.contract.deprecation metadata.`, {
|
|
314
328
|
fix: `Add announcedAt, removalAfter, and migrationGuide to operations.${operationName}.contract.deprecation.`,
|
|
@@ -333,8 +347,7 @@ function validateOperationAnnotations(providerId, operations) {
|
|
|
333
347
|
throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an integer number of milliseconds.`, {
|
|
334
348
|
fix: `Set ${field} to an integer in [${OPERATION_TIMEOUT_MS_MIN}, ${OPERATION_TIMEOUT_MS_MAX}] (milliseconds).`,
|
|
335
349
|
});
|
|
336
|
-
if (timeoutMs < OPERATION_TIMEOUT_MS_MIN ||
|
|
337
|
-
timeoutMs > OPERATION_TIMEOUT_MS_MAX)
|
|
350
|
+
if (timeoutMs < OPERATION_TIMEOUT_MS_MIN || timeoutMs > OPERATION_TIMEOUT_MS_MAX)
|
|
338
351
|
throw new ValidationError(`Provider "${providerId}" has invalid ${field}: ${timeoutMs} is outside [${OPERATION_TIMEOUT_MS_MIN}, ${OPERATION_TIMEOUT_MS_MAX}] ms.`, {
|
|
339
352
|
fix: `Set ${field} to an integer in [${OPERATION_TIMEOUT_MS_MIN}, ${OPERATION_TIMEOUT_MS_MAX}] ms (the upper bound stays below the gateway/ALB ceiling).`,
|
|
340
353
|
});
|
|
@@ -478,9 +491,7 @@ function validateOperationTransports(providerId, operations) {
|
|
|
478
491
|
assertStreamMs(maxDurationMs, `${fieldPath}.maxDurationMs`, STREAM_MAX_DURATION_MS_MIN, STREAM_MAX_DURATION_MS_MAX, "max duration");
|
|
479
492
|
assertPositiveBytes(Reflect.get(transport, "maxEventBytes"), `${fieldPath}.maxEventBytes`);
|
|
480
493
|
const resumable = Reflect.get(transport, "resumable");
|
|
481
|
-
if (resumable !== undefined &&
|
|
482
|
-
resumable !== false &&
|
|
483
|
-
resumable !== "last-event-id") {
|
|
494
|
+
if (resumable !== undefined && resumable !== false && resumable !== "last-event-id") {
|
|
484
495
|
throw new ValidationError(`Provider "${providerId}" operation "${operationName}" has invalid ${fieldPath}.resumable: expected false or "last-event-id".`, {
|
|
485
496
|
fix: `Use ${fieldPath}.resumable: "last-event-id" for SSE Last-Event-ID resume support, or false to disable resume.`,
|
|
486
497
|
});
|
|
@@ -515,8 +526,7 @@ function validateOperationTransports(providerId, operations) {
|
|
|
515
526
|
});
|
|
516
527
|
}
|
|
517
528
|
for (const subprotocol of subprotocols) {
|
|
518
|
-
if (typeof subprotocol !== "string" ||
|
|
519
|
-
!WEBSOCKET_SUBPROTOCOL_REGEX.test(subprotocol)) {
|
|
529
|
+
if (typeof subprotocol !== "string" || !WEBSOCKET_SUBPROTOCOL_REGEX.test(subprotocol)) {
|
|
520
530
|
throw new ValidationError(`Provider "${providerId}" operation "${operationName}" has invalid ${fieldPath}.subprotocols: each subprotocol must be an RFC token string.`, {
|
|
521
531
|
fix: `Use values such as "apifuse.v1" without spaces or separators that are invalid for Sec-WebSocket-Protocol.`,
|
|
522
532
|
});
|
|
@@ -624,9 +634,7 @@ function assertBoundedIntegerMs(value, fieldPath, options) {
|
|
|
624
634
|
function validateProviderHealthMonitor(providerId, healthMonitor, field = "healthMonitor") {
|
|
625
635
|
if (healthMonitor === undefined)
|
|
626
636
|
return;
|
|
627
|
-
if (!healthMonitor ||
|
|
628
|
-
typeof healthMonitor !== "object" ||
|
|
629
|
-
Array.isArray(healthMonitor))
|
|
637
|
+
if (!healthMonitor || typeof healthMonitor !== "object" || Array.isArray(healthMonitor))
|
|
630
638
|
throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an object.`, {
|
|
631
639
|
fix: `Set ${field} to { requiredSecrets?: string[]; serviceAccount?: string }`,
|
|
632
640
|
});
|
|
@@ -676,9 +684,7 @@ function validateProviderHealthMonitor(providerId, healthMonitor, field = "healt
|
|
|
676
684
|
}
|
|
677
685
|
const probeOverrides = healthMonitorRecord.probeOverrides;
|
|
678
686
|
if (probeOverrides !== undefined) {
|
|
679
|
-
if (!probeOverrides ||
|
|
680
|
-
typeof probeOverrides !== "object" ||
|
|
681
|
-
Array.isArray(probeOverrides))
|
|
687
|
+
if (!probeOverrides || typeof probeOverrides !== "object" || Array.isArray(probeOverrides))
|
|
682
688
|
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.probeOverrides: must be an object keyed by probe id.`);
|
|
683
689
|
for (const [probeId, override] of Object.entries(probeOverrides)) {
|
|
684
690
|
if (probeId.length === 0)
|
|
@@ -756,9 +762,7 @@ function validateHealthCheckSuite(providerId, operationName, suite) {
|
|
|
756
762
|
fix: `Set ${fieldPath}.interval to a positive ms-style duration string.`,
|
|
757
763
|
});
|
|
758
764
|
if (s.schedule !== undefined) {
|
|
759
|
-
if (!s.schedule ||
|
|
760
|
-
typeof s.schedule !== "object" ||
|
|
761
|
-
Array.isArray(s.schedule)) {
|
|
765
|
+
if (!s.schedule || typeof s.schedule !== "object" || Array.isArray(s.schedule)) {
|
|
762
766
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.schedule must be an object.`);
|
|
763
767
|
}
|
|
764
768
|
if (Reflect.get(s.schedule, "jitter") !== undefined) {
|
|
@@ -784,8 +788,7 @@ function validateHealthCheckSuite(providerId, operationName, suite) {
|
|
|
784
788
|
label: "degraded threshold",
|
|
785
789
|
});
|
|
786
790
|
}
|
|
787
|
-
if (s.requiresConnection !== undefined &&
|
|
788
|
-
typeof s.requiresConnection !== "boolean")
|
|
791
|
+
if (s.requiresConnection !== undefined && typeof s.requiresConnection !== "boolean")
|
|
789
792
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.requiresConnection must be a boolean.`);
|
|
790
793
|
if (!Array.isArray(s.cases) || s.cases.length === 0)
|
|
791
794
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.cases must be a non-empty array.`, {
|
|
@@ -804,9 +807,7 @@ function validateHealthCheckSuite(providerId, operationName, suite) {
|
|
|
804
807
|
}
|
|
805
808
|
function validateHealthCheckUnsupported(providerId, operationName, unsupported) {
|
|
806
809
|
const fieldPath = `operations.${operationName}.healthCheckUnsupported`;
|
|
807
|
-
if (!unsupported ||
|
|
808
|
-
typeof unsupported !== "object" ||
|
|
809
|
-
Array.isArray(unsupported))
|
|
810
|
+
if (!unsupported || typeof unsupported !== "object" || Array.isArray(unsupported))
|
|
810
811
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath} must be an object.`);
|
|
811
812
|
rejectUnknownFields(unsupported, HEALTH_CHECK_UNSUPPORTED_FIELDS, fieldPath);
|
|
812
813
|
const u = unsupported;
|
|
@@ -831,12 +832,7 @@ const HEALTH_JOURNEY_FIELDS = new Set([
|
|
|
831
832
|
"steps",
|
|
832
833
|
"run",
|
|
833
834
|
]);
|
|
834
|
-
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
|
|
835
|
-
"kind",
|
|
836
|
-
"interval",
|
|
837
|
-
"jitter",
|
|
838
|
-
"randomize",
|
|
839
|
-
]);
|
|
835
|
+
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set(["kind", "interval", "jitter", "randomize"]);
|
|
840
836
|
const HEALTH_JOURNEY_STEP_FIELDS = new Set([
|
|
841
837
|
"id",
|
|
842
838
|
"description",
|
|
@@ -854,10 +850,7 @@ const HEALTH_JOURNEY_MANUAL_TRIGGER_FIELDS = new Set([
|
|
|
854
850
|
"minManualInterval",
|
|
855
851
|
"publicRationale",
|
|
856
852
|
]);
|
|
857
|
-
const HEALTH_JOURNEY_MANUAL_TRIGGER_DISABLED_FIELDS = new Set([
|
|
858
|
-
"enabled",
|
|
859
|
-
"reason",
|
|
860
|
-
]);
|
|
853
|
+
const HEALTH_JOURNEY_MANUAL_TRIGGER_DISABLED_FIELDS = new Set(["enabled", "reason"]);
|
|
861
854
|
const HEALTH_JOURNEY_MANUAL_TRIGGER_ENABLED_FIELDS = new Set([
|
|
862
855
|
"enabled",
|
|
863
856
|
"requiresAcknowledgement",
|
|
@@ -872,9 +865,7 @@ const HEALTH_JOURNEY_MANUAL_TRIGGER_RISKS = new Set([
|
|
|
872
865
|
]);
|
|
873
866
|
function validateHealthJourneyManualTrigger(providerId, journeyId, manualTrigger) {
|
|
874
867
|
const fieldPath = `healthJourneys.${journeyId}.manualTrigger`;
|
|
875
|
-
if (!manualTrigger ||
|
|
876
|
-
typeof manualTrigger !== "object" ||
|
|
877
|
-
Array.isArray(manualTrigger)) {
|
|
868
|
+
if (!manualTrigger || typeof manualTrigger !== "object" || Array.isArray(manualTrigger)) {
|
|
878
869
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath} must be an object when present.`);
|
|
879
870
|
}
|
|
880
871
|
rejectUnknownFields(manualTrigger, HEALTH_JOURNEY_MANUAL_TRIGGER_FIELDS, fieldPath);
|
|
@@ -897,8 +888,7 @@ function validateHealthJourneyManualTrigger(providerId, journeyId, manualTrigger
|
|
|
897
888
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.requiresAcknowledgement must be a boolean.`);
|
|
898
889
|
}
|
|
899
890
|
const risk = Reflect.get(manualTrigger, "risk");
|
|
900
|
-
if (typeof risk !== "string" ||
|
|
901
|
-
!HEALTH_JOURNEY_MANUAL_TRIGGER_RISKS.has(risk)) {
|
|
891
|
+
if (typeof risk !== "string" || !HEALTH_JOURNEY_MANUAL_TRIGGER_RISKS.has(risk)) {
|
|
902
892
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.risk must be one of read_only, writes_external_state, or sms_or_payment.`);
|
|
903
893
|
}
|
|
904
894
|
if (risk !== "read_only" && requiresAcknowledgement !== true) {
|
|
@@ -950,7 +940,7 @@ function isoDurationMs(value) {
|
|
|
950
940
|
const hours = Number(/(\d+)H/.exec(value)?.[1] ?? 0);
|
|
951
941
|
const minutes = Number(/(\d+)M/.exec(value)?.[1] ?? 0);
|
|
952
942
|
const seconds = Number(/(\d+(?:\.\d+)?)S/.exec(value)?.[1] ?? 0);
|
|
953
|
-
return
|
|
943
|
+
return days * 86_400_000 + hours * 3_600_000 + minutes * 60_000 + seconds * 1_000;
|
|
954
944
|
}
|
|
955
945
|
function scheduleRandomizationMs(randomize, fieldPath) {
|
|
956
946
|
const mode = Reflect.get(randomize, "mode");
|
|
@@ -974,9 +964,7 @@ function validateScheduleRandomization(randomize, fieldPath, intervalMs) {
|
|
|
974
964
|
throw new ValidationError(`${fieldPath} must be an object.`);
|
|
975
965
|
}
|
|
976
966
|
const mode = Reflect.get(randomize, "mode");
|
|
977
|
-
const allowedFields = mode === "centered"
|
|
978
|
-
? new Set(["mode", "maxOffset"])
|
|
979
|
-
: new Set(["mode", "maxDelay"]);
|
|
967
|
+
const allowedFields = mode === "centered" ? new Set(["mode", "maxOffset"]) : new Set(["mode", "maxDelay"]);
|
|
980
968
|
rejectUnknownFields(randomize, allowedFields, fieldPath);
|
|
981
969
|
const offsetMs = scheduleRandomizationMs(randomize, fieldPath);
|
|
982
970
|
if (offsetMs <= 0) {
|
|
@@ -1062,9 +1050,7 @@ function countCapturingGroups(pattern) {
|
|
|
1062
1050
|
const next = source[i + 1];
|
|
1063
1051
|
if (next === "?" && source[i + 2] !== "<")
|
|
1064
1052
|
continue;
|
|
1065
|
-
if (next === "?" &&
|
|
1066
|
-
source[i + 2] === "<" &&
|
|
1067
|
-
(source[i + 3] === "=" || source[i + 3] === "!"))
|
|
1053
|
+
if (next === "?" && source[i + 2] === "<" && (source[i + 3] === "=" || source[i + 3] === "!"))
|
|
1068
1054
|
continue;
|
|
1069
1055
|
count += 1;
|
|
1070
1056
|
}
|
|
@@ -1142,8 +1128,7 @@ function validateSmsOtpMatcher(matcher, fieldPath) {
|
|
|
1142
1128
|
throw new ValidationError(`${fieldPath}.code.pattern must be a RegExp or pattern source string.`);
|
|
1143
1129
|
}
|
|
1144
1130
|
const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern);
|
|
1145
|
-
if (countCapturingGroups(regex) !== 1 &&
|
|
1146
|
-
Reflect.get(code, "capture") === undefined) {
|
|
1131
|
+
if (countCapturingGroups(regex) !== 1 && Reflect.get(code, "capture") === undefined) {
|
|
1147
1132
|
throw new ValidationError(`${fieldPath}.code.pattern must contain exactly one OTP capture or declare code.capture.`);
|
|
1148
1133
|
}
|
|
1149
1134
|
if (Reflect.get(code, "capture") !== undefined &&
|
|
@@ -1195,8 +1180,7 @@ function validateHealthJourneySchedule(providerId, journeyId, schedule) {
|
|
|
1195
1180
|
const interval = Reflect.get(schedule, "interval");
|
|
1196
1181
|
assertIsoDuration(interval, `Provider "${providerId}" ${fieldPath}.interval`);
|
|
1197
1182
|
const randomize = Reflect.get(schedule, "randomize");
|
|
1198
|
-
if (Reflect.get(schedule, "jitter") !== undefined &&
|
|
1199
|
-
randomize !== undefined) {
|
|
1183
|
+
if (Reflect.get(schedule, "jitter") !== undefined && randomize !== undefined) {
|
|
1200
1184
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath} cannot define both jitter and randomize.`);
|
|
1201
1185
|
}
|
|
1202
1186
|
if (Reflect.get(schedule, "jitter") !== undefined)
|
|
@@ -1226,8 +1210,7 @@ function validateHealthJourneys(providerId, operations, healthJourneys) {
|
|
|
1226
1210
|
throw new ValidationError(`Provider "${providerId}" has duplicate health journey id "${journey.id}".`);
|
|
1227
1211
|
journeyIds.add(journey.id);
|
|
1228
1212
|
validateHealthJourneySchedule(providerId, journey.id, journey.schedule);
|
|
1229
|
-
if (!Array.isArray(journey.coversOperations) ||
|
|
1230
|
-
journey.coversOperations.length === 0) {
|
|
1213
|
+
if (!Array.isArray(journey.coversOperations) || journey.coversOperations.length === 0) {
|
|
1231
1214
|
throw new ValidationError(`Provider "${providerId}" healthJourneys.${journey.id}.coversOperations must be a non-empty array.`);
|
|
1232
1215
|
}
|
|
1233
1216
|
for (const operationId of journey.coversOperations) {
|
|
@@ -1265,8 +1248,7 @@ function validateHealthJourneys(providerId, operations, healthJourneys) {
|
|
|
1265
1248
|
throw new ValidationError(`Provider "${providerId}" ${stepPath}.id must be a kebab-case identifier.`);
|
|
1266
1249
|
if (step.operationId !== undefined && !operations[step.operationId])
|
|
1267
1250
|
throw new ValidationError(`Provider "${providerId}" ${stepPath}.operationId references unknown operation "${step.operationId}".`);
|
|
1268
|
-
if (step.usesSmsMatcher !== undefined &&
|
|
1269
|
-
!matcherIds.has(step.usesSmsMatcher))
|
|
1251
|
+
if (step.usesSmsMatcher !== undefined && !matcherIds.has(step.usesSmsMatcher))
|
|
1270
1252
|
throw new ValidationError(`Provider "${providerId}" ${stepPath}.usesSmsMatcher references unknown matcher "${step.usesSmsMatcher}".`);
|
|
1271
1253
|
}
|
|
1272
1254
|
if (journey.manualTrigger !== undefined)
|
|
@@ -1300,9 +1282,7 @@ function validateOperationHealthChecks(providerId, operations, journeyCoveredOpe
|
|
|
1300
1282
|
validateHealthCheckSuite(providerId, operationName, operation.healthCheck);
|
|
1301
1283
|
if (hasUnsupported)
|
|
1302
1284
|
validateHealthCheckUnsupported(providerId, operationName, operation.healthCheckUnsupported);
|
|
1303
|
-
if (!hasCheck &&
|
|
1304
|
-
!hasUnsupported &&
|
|
1305
|
-
!journeyCoveredOperations.has(operationName))
|
|
1285
|
+
if (!hasCheck && !hasUnsupported && !journeyCoveredOperations.has(operationName))
|
|
1306
1286
|
throw new ValidationError(`Provider "${providerId}" operation "${operationName}" declares neither healthCheck nor healthCheckUnsupported.`, {
|
|
1307
1287
|
fix: `Add \`healthCheck: { interval, cases: [...] }\` or \`healthCheckUnsupported: { reason: "..." }\` to operations.${operationName}.`,
|
|
1308
1288
|
});
|
|
@@ -1332,6 +1312,19 @@ function validateOperationFixtures(providerId, operations) {
|
|
|
1332
1312
|
}
|
|
1333
1313
|
}
|
|
1334
1314
|
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Shallow shape guard only: the `deployment` object is passed through
|
|
1317
|
+
* verbatim and deliberately not deep-validated by the SDK — the APIFuse
|
|
1318
|
+
* registry builder owns deployment validation and profile resolution.
|
|
1319
|
+
*/
|
|
1320
|
+
function validateProviderDeployment(providerId, deployment) {
|
|
1321
|
+
if (deployment === undefined)
|
|
1322
|
+
return;
|
|
1323
|
+
if (!deployment || typeof deployment !== "object" || Array.isArray(deployment))
|
|
1324
|
+
throw new ProviderError(`Provider "${providerId}" deployment must be an object when present`, {
|
|
1325
|
+
fix: 'Pass deployment: { runtime: "shared" | "dedicated" | "browser", ... } or remove the field',
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1335
1328
|
export function defineProvider(config) {
|
|
1336
1329
|
validateProviderShape(config);
|
|
1337
1330
|
if (!CONNECTOR_ID_REGEX.test(config.id))
|
|
@@ -1356,6 +1349,7 @@ export function defineProvider(config) {
|
|
|
1356
1349
|
});
|
|
1357
1350
|
validateProviderHealthMonitor(config.id, config.healthProbe ?? config.healthMonitor, config.healthProbe !== undefined ? "healthProbe" : "healthMonitor");
|
|
1358
1351
|
validateOperationFixtures(config.id, config.operations);
|
|
1352
|
+
validateProviderDeployment(config.id, config.deployment);
|
|
1359
1353
|
validateProviderProxy(config);
|
|
1360
1354
|
validateProviderStt(config);
|
|
1361
1355
|
if (config.runtime === "browser" && !config.browser)
|
|
@@ -1368,6 +1362,9 @@ export function defineProvider(config) {
|
|
|
1368
1362
|
id: config.id,
|
|
1369
1363
|
version: config.version,
|
|
1370
1364
|
runtime: config.runtime,
|
|
1365
|
+
// Verbatim passthrough: deployment validation and profile resolution
|
|
1366
|
+
// are owned by the APIFuse registry builder, not the SDK.
|
|
1367
|
+
deployment: config.deployment,
|
|
1371
1368
|
allowedHosts: config.allowedHosts,
|
|
1372
1369
|
stealth: config.stealth,
|
|
1373
1370
|
proxy: config.proxy,
|
package/dist/dev.d.ts
CHANGED
package/dist/dev.js
CHANGED
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProviderErrorCategory } from "./observability";
|
|
1
|
+
import type { ProviderErrorCategory } from "./observability.js";
|
|
2
2
|
export type ProviderErrorOptions = {
|
|
3
3
|
fix?: string;
|
|
4
4
|
code?: string;
|
|
@@ -39,6 +39,9 @@ export declare class TransportError extends ProviderError {
|
|
|
39
39
|
readonly upstreamStatus?: number;
|
|
40
40
|
constructor(message: string, options?: TransportErrorOptions);
|
|
41
41
|
}
|
|
42
|
+
export declare function isProviderError(value: unknown): value is ProviderError;
|
|
43
|
+
export declare function isSessionExpiredError(value: unknown): value is SessionExpiredError;
|
|
44
|
+
export declare function isTransportError(value: unknown): value is TransportError;
|
|
42
45
|
export declare class ProviderSecretError extends ProviderError {
|
|
43
46
|
constructor(message: string, options?: ProviderErrorOptions);
|
|
44
47
|
}
|