@apifuse/provider-sdk 2.1.0-beta.13 → 2.1.0-beta.14
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/CHANGELOG.md +4 -0
- package/dist/contract.js +1 -0
- package/dist/define.d.ts +4 -1
- package/dist/define.js +104 -46
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/provider.d.ts +2 -2
- package/dist/provider.js +1 -1
- package/dist/types.d.ts +11 -0
- package/package.json +1 -1
- package/src/contract.ts +1 -0
- package/src/define.ts +150 -48
- package/src/index.ts +3 -0
- package/src/provider.ts +3 -0
- package/src/types.ts +14 -0
package/CHANGELOG.md
CHANGED
package/dist/contract.js
CHANGED
|
@@ -122,6 +122,7 @@ function extractHealthCheck(value) {
|
|
|
122
122
|
return undefined;
|
|
123
123
|
return compactObject({
|
|
124
124
|
interval: value.interval,
|
|
125
|
+
schedule: toJsonValue(value.schedule),
|
|
125
126
|
timeoutMs: value.timeoutMs,
|
|
126
127
|
degradedThresholdMs: value.degradedThresholdMs,
|
|
127
128
|
requiresConnection: value.requiresConnection,
|
package/dist/define.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthConfig, BrowserEngine, ContextDeclaration, CredentialDeclaration, HealthJourneyDefinition, HealthJourneySchedule, 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, ProviderHealthMonitorConfig, ProviderProxyConfig, ProviderPublicProfile, ProviderReviewed, ProviderSecretDeclaration, ProviderStreamEvent, ProviderSttConfig, SchemaLike, SmsOtpMatcherDefinition, StealthPlatform } from "./types";
|
|
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 {
|
|
@@ -88,7 +88,10 @@ export declare function defineOperation<TInput extends SchemaLike, TOutput exten
|
|
|
88
88
|
export declare function defineStreamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(operation: StreamOperationConfig<TInput, TOutput>): OperationDefinition<TInput, TOutput>;
|
|
89
89
|
export declare function every(interval: string, options?: {
|
|
90
90
|
jitter?: string;
|
|
91
|
+
randomize?: HealthScheduleRandomization;
|
|
91
92
|
}): HealthJourneySchedule;
|
|
93
|
+
export declare function centered(maxOffset: string): HealthScheduleRandomization;
|
|
94
|
+
export declare function delayed(maxDelay: string): HealthScheduleRandomization;
|
|
92
95
|
export declare function defineSmsOtpMatcher(config: Omit<SmsOtpMatcherDefinition, "extractOtp">): SmsOtpMatcherDefinition;
|
|
93
96
|
export declare function defineHealthJourney(config: HealthJourneyDefinition): HealthJourneyDefinition;
|
|
94
97
|
export declare function defineProvider<TOperations extends Record<string, ProviderOperation>, TConfig extends ProviderConfig<TOperations>>(config: TConfig & AuthStartNoInputGuard<TConfig>): ProviderDefinition & {
|
package/dist/define.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ms from "ms";
|
|
1
2
|
import { ProviderError, ValidationError } from "./errors";
|
|
2
3
|
import { safeParseSchemaSync } from "./schema";
|
|
3
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";
|
|
@@ -50,51 +51,23 @@ const VALID_OPERATION_TRANSPORT_KINDS = [
|
|
|
50
51
|
];
|
|
51
52
|
const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
|
|
52
53
|
const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
53
|
-
const MS_DURATION_UNITS = new Set([
|
|
54
|
-
"years",
|
|
55
|
-
"year",
|
|
56
|
-
"yrs",
|
|
57
|
-
"yr",
|
|
58
|
-
"y",
|
|
59
|
-
"weeks",
|
|
60
|
-
"week",
|
|
61
|
-
"w",
|
|
62
|
-
"days",
|
|
63
|
-
"day",
|
|
64
|
-
"d",
|
|
65
|
-
"hours",
|
|
66
|
-
"hour",
|
|
67
|
-
"hrs",
|
|
68
|
-
"hr",
|
|
69
|
-
"h",
|
|
70
|
-
"minutes",
|
|
71
|
-
"minute",
|
|
72
|
-
"mins",
|
|
73
|
-
"min",
|
|
74
|
-
"m",
|
|
75
|
-
"seconds",
|
|
76
|
-
"second",
|
|
77
|
-
"secs",
|
|
78
|
-
"sec",
|
|
79
|
-
"s",
|
|
80
|
-
"milliseconds",
|
|
81
|
-
"millisecond",
|
|
82
|
-
"msecs",
|
|
83
|
-
"msec",
|
|
84
|
-
"ms",
|
|
85
|
-
]);
|
|
86
54
|
const MS_DURATION_PATTERN = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))\s*([a-zA-Z]+)?$/;
|
|
87
55
|
function isPositiveMsDurationString(value) {
|
|
88
56
|
if (typeof value !== "string")
|
|
89
57
|
return false;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
58
|
+
return parsePositiveMsDuration(value) !== undefined;
|
|
59
|
+
}
|
|
60
|
+
function msDurationMs(value) {
|
|
61
|
+
return parsePositiveMsDuration(value) ?? 0;
|
|
62
|
+
}
|
|
63
|
+
function parsePositiveMsDuration(value) {
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
if (!MS_DURATION_PATTERN.test(trimmed))
|
|
66
|
+
return undefined;
|
|
67
|
+
const parsed = ms((trimmed.startsWith("+") ? trimmed.slice(1) : trimmed));
|
|
68
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
69
|
+
return undefined;
|
|
70
|
+
return parsed;
|
|
98
71
|
}
|
|
99
72
|
/** Define one provider operation with schema-driven handler inference. */
|
|
100
73
|
export function defineOperation(operation) {
|
|
@@ -560,6 +533,7 @@ function validateOperationTransports(providerId, operations) {
|
|
|
560
533
|
}
|
|
561
534
|
const HEALTH_CHECK_SUITE_FIELDS = new Set([
|
|
562
535
|
"interval",
|
|
536
|
+
"schedule",
|
|
563
537
|
"timeoutMs",
|
|
564
538
|
"degradedThresholdMs",
|
|
565
539
|
"cases",
|
|
@@ -781,6 +755,21 @@ function validateHealthCheckSuite(providerId, operationName, suite) {
|
|
|
781
755
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.interval must be a positive ms-style duration string such as 30s, 5m, 8h, or 1 day.`, {
|
|
782
756
|
fix: `Set ${fieldPath}.interval to a positive ms-style duration string.`,
|
|
783
757
|
});
|
|
758
|
+
if (s.schedule !== undefined) {
|
|
759
|
+
if (!s.schedule ||
|
|
760
|
+
typeof s.schedule !== "object" ||
|
|
761
|
+
Array.isArray(s.schedule)) {
|
|
762
|
+
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.schedule must be an object.`);
|
|
763
|
+
}
|
|
764
|
+
if (Reflect.get(s.schedule, "jitter") !== undefined) {
|
|
765
|
+
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.schedule.jitter is not supported for operation healthCheck schedules. Use schedule.randomize instead.`);
|
|
766
|
+
}
|
|
767
|
+
rejectUnknownFields(s.schedule, new Set(["randomize"]), `${fieldPath}.schedule`);
|
|
768
|
+
const randomize = Reflect.get(s.schedule, "randomize");
|
|
769
|
+
if (randomize !== undefined) {
|
|
770
|
+
validateScheduleRandomization(randomize, `Provider "${providerId}" ${fieldPath}.schedule.randomize`, msDurationMs(s.interval));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
784
773
|
if (s.timeoutMs !== undefined) {
|
|
785
774
|
assertBoundedIntegerMs(s.timeoutMs, `Provider "${providerId}" ${fieldPath}.timeoutMs`, {
|
|
786
775
|
min: HEALTH_CHECK_TIMEOUT_MS_MIN,
|
|
@@ -842,7 +831,12 @@ const HEALTH_JOURNEY_FIELDS = new Set([
|
|
|
842
831
|
"steps",
|
|
843
832
|
"run",
|
|
844
833
|
]);
|
|
845
|
-
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
|
|
834
|
+
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
|
|
835
|
+
"kind",
|
|
836
|
+
"interval",
|
|
837
|
+
"jitter",
|
|
838
|
+
"randomize",
|
|
839
|
+
]);
|
|
846
840
|
const HEALTH_JOURNEY_STEP_FIELDS = new Set([
|
|
847
841
|
"id",
|
|
848
842
|
"description",
|
|
@@ -958,6 +952,40 @@ function isoDurationMs(value) {
|
|
|
958
952
|
const seconds = Number(/(\d+(?:\.\d+)?)S/.exec(value)?.[1] ?? 0);
|
|
959
953
|
return (days * 86_400_000 + hours * 3_600_000 + minutes * 60_000 + seconds * 1_000);
|
|
960
954
|
}
|
|
955
|
+
function scheduleRandomizationMs(randomize, fieldPath) {
|
|
956
|
+
const mode = Reflect.get(randomize, "mode");
|
|
957
|
+
switch (mode) {
|
|
958
|
+
case "centered": {
|
|
959
|
+
const maxOffset = Reflect.get(randomize, "maxOffset");
|
|
960
|
+
assertIsoDuration(maxOffset, `${fieldPath}.maxOffset`);
|
|
961
|
+
return isoDurationMs(maxOffset);
|
|
962
|
+
}
|
|
963
|
+
case "delayed": {
|
|
964
|
+
const maxDelay = Reflect.get(randomize, "maxDelay");
|
|
965
|
+
assertIsoDuration(maxDelay, `${fieldPath}.maxDelay`);
|
|
966
|
+
return isoDurationMs(maxDelay);
|
|
967
|
+
}
|
|
968
|
+
default:
|
|
969
|
+
throw new ValidationError(`${fieldPath}.mode must be "centered" or "delayed".`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function validateScheduleRandomization(randomize, fieldPath, intervalMs) {
|
|
973
|
+
if (!randomize || typeof randomize !== "object" || Array.isArray(randomize)) {
|
|
974
|
+
throw new ValidationError(`${fieldPath} must be an object.`);
|
|
975
|
+
}
|
|
976
|
+
const mode = Reflect.get(randomize, "mode");
|
|
977
|
+
const allowedFields = mode === "centered"
|
|
978
|
+
? new Set(["mode", "maxOffset"])
|
|
979
|
+
: new Set(["mode", "maxDelay"]);
|
|
980
|
+
rejectUnknownFields(randomize, allowedFields, fieldPath);
|
|
981
|
+
const offsetMs = scheduleRandomizationMs(randomize, fieldPath);
|
|
982
|
+
if (offsetMs <= 0) {
|
|
983
|
+
throw new ValidationError(`${fieldPath} duration must be positive.`);
|
|
984
|
+
}
|
|
985
|
+
if (offsetMs >= intervalMs) {
|
|
986
|
+
throw new ValidationError(`${fieldPath} duration must be shorter than schedule interval.`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
961
989
|
function assertIsoCountry(value, fieldPath) {
|
|
962
990
|
if (typeof value !== "string" || !ISO_COUNTRY_RE.test(value)) {
|
|
963
991
|
throw new ValidationError(`${fieldPath} must be an ISO 3166-1 alpha-2 country code for example KR.`);
|
|
@@ -967,11 +995,18 @@ function normalizeIntervalDuration(input) {
|
|
|
967
995
|
const trimmed = input.trim();
|
|
968
996
|
const shorthand = /^(\d+)(s|m|h|d)$/i.exec(trimmed);
|
|
969
997
|
if (shorthand) {
|
|
970
|
-
const
|
|
998
|
+
const durationMs = msDurationMs(trimmed);
|
|
999
|
+
const unit = shorthand[2]?.toLowerCase();
|
|
1000
|
+
const amount = unit === "s"
|
|
1001
|
+
? durationMs / 1_000
|
|
1002
|
+
: unit === "m"
|
|
1003
|
+
? durationMs / 60_000
|
|
1004
|
+
: unit === "h"
|
|
1005
|
+
? durationMs / 3_600_000
|
|
1006
|
+
: durationMs / 86_400_000;
|
|
971
1007
|
if (!Number.isInteger(amount) || amount <= 0) {
|
|
972
1008
|
throw new ValidationError(`Journey schedule interval must be a positive duration.`);
|
|
973
1009
|
}
|
|
974
|
-
const unit = shorthand[2]?.toLowerCase();
|
|
975
1010
|
if (unit === "s")
|
|
976
1011
|
return `PT${amount}S`;
|
|
977
1012
|
if (unit === "m")
|
|
@@ -985,15 +1020,27 @@ function normalizeIntervalDuration(input) {
|
|
|
985
1020
|
return trimmed;
|
|
986
1021
|
}
|
|
987
1022
|
export function every(interval, options = {}) {
|
|
1023
|
+
if (options.jitter !== undefined && options.randomize !== undefined) {
|
|
1024
|
+
throw new ValidationError(`Schedule cannot define both jitter and randomize. Use randomize instead.`);
|
|
1025
|
+
}
|
|
988
1026
|
const schedule = {
|
|
989
1027
|
kind: "interval",
|
|
990
1028
|
interval: normalizeIntervalDuration(interval),
|
|
991
1029
|
};
|
|
1030
|
+
if (options.randomize !== undefined) {
|
|
1031
|
+
schedule.randomize = options.randomize;
|
|
1032
|
+
}
|
|
992
1033
|
if (options.jitter !== undefined) {
|
|
993
1034
|
schedule.jitter = normalizeIntervalDuration(options.jitter);
|
|
994
1035
|
}
|
|
995
1036
|
return schedule;
|
|
996
1037
|
}
|
|
1038
|
+
export function centered(maxOffset) {
|
|
1039
|
+
return { mode: "centered", maxOffset: normalizeIntervalDuration(maxOffset) };
|
|
1040
|
+
}
|
|
1041
|
+
export function delayed(maxDelay) {
|
|
1042
|
+
return { mode: "delayed", maxDelay: normalizeIntervalDuration(maxDelay) };
|
|
1043
|
+
}
|
|
997
1044
|
function countCapturingGroups(pattern) {
|
|
998
1045
|
let count = 0;
|
|
999
1046
|
const source = pattern.source;
|
|
@@ -1145,9 +1192,18 @@ function validateHealthJourneySchedule(providerId, journeyId, schedule) {
|
|
|
1145
1192
|
rejectUnknownFields(schedule, HEALTH_JOURNEY_SCHEDULE_FIELDS, fieldPath);
|
|
1146
1193
|
if (Reflect.get(schedule, "kind") !== "interval")
|
|
1147
1194
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.kind must be "interval".`);
|
|
1148
|
-
|
|
1195
|
+
const interval = Reflect.get(schedule, "interval");
|
|
1196
|
+
assertIsoDuration(interval, `Provider "${providerId}" ${fieldPath}.interval`);
|
|
1197
|
+
const randomize = Reflect.get(schedule, "randomize");
|
|
1198
|
+
if (Reflect.get(schedule, "jitter") !== undefined &&
|
|
1199
|
+
randomize !== undefined) {
|
|
1200
|
+
throw new ValidationError(`Provider "${providerId}" ${fieldPath} cannot define both jitter and randomize.`);
|
|
1201
|
+
}
|
|
1149
1202
|
if (Reflect.get(schedule, "jitter") !== undefined)
|
|
1150
1203
|
assertIsoDuration(Reflect.get(schedule, "jitter"), `Provider "${providerId}" ${fieldPath}.jitter`);
|
|
1204
|
+
if (randomize !== undefined) {
|
|
1205
|
+
validateScheduleRandomization(randomize, `Provider "${providerId}" ${fieldPath}.randomize`, isoDurationMs(interval));
|
|
1206
|
+
}
|
|
1151
1207
|
}
|
|
1152
1208
|
function validateHealthJourneys(providerId, operations, healthJourneys) {
|
|
1153
1209
|
const covered = new Set();
|
|
@@ -1283,7 +1339,9 @@ export function defineProvider(config) {
|
|
|
1283
1339
|
fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
|
|
1284
1340
|
});
|
|
1285
1341
|
if (Object.keys(config.operations).length === 0)
|
|
1286
|
-
throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
|
|
1342
|
+
throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
|
|
1343
|
+
fix: "Add at least one operation to the operations object",
|
|
1344
|
+
});
|
|
1287
1345
|
validateOperationIds(config.id, config.operations);
|
|
1288
1346
|
validateOperationAnnotations(config.id, config.operations);
|
|
1289
1347
|
validateOperationObservability(config.id, config.operations);
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export * from "./choice-token";
|
|
|
4
4
|
export type { ApiFuseConfig, BrowserConfig, ProxyConfig, SessionConfig, } from "./config/loader";
|
|
5
5
|
export { defineConfig, loadApiFuseConfig } from "./config/loader";
|
|
6
6
|
export { canonicalJson, digestProviderContract, extractProviderContract, type JsonPrimitive, type JsonValue, PROVIDER_CONTRACT_SCHEMA_VERSION, type ProviderContractOperation, type ProviderContractSnapshot, } from "./contract";
|
|
7
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, type ProviderConfig, } from "./define";
|
|
7
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, type ProviderConfig, } from "./define";
|
|
8
8
|
export type { DevServerOptions } from "./dev";
|
|
9
9
|
export { createDevServer, startDevServer } from "./dev";
|
|
10
10
|
export * from "./errors";
|
|
@@ -34,7 +34,7 @@ export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SEN
|
|
|
34
34
|
export { createServerApp, type ServeOptions, serve } from "./server";
|
|
35
35
|
export { getStealthProfile, listStealthProfiles } from "./stealth/profiles";
|
|
36
36
|
export * from "./stream";
|
|
37
|
-
export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderHealthMonitorConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types";
|
|
37
|
+
export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthScheduleRandomization, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderHealthMonitorConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types";
|
|
38
38
|
export { DEFAULT_OPERATION_TRANSPORT, HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, PROBE_INTERVALS, 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";
|
|
39
39
|
export * from "./utils/date";
|
|
40
40
|
export * from "./utils/parse";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export * from "./ceremonies";
|
|
|
4
4
|
export * from "./choice-token";
|
|
5
5
|
export { defineConfig, loadApiFuseConfig } from "./config/loader";
|
|
6
6
|
export { canonicalJson, digestProviderContract, extractProviderContract, PROVIDER_CONTRACT_SCHEMA_VERSION, } from "./contract";
|
|
7
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, } from "./define";
|
|
7
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, } from "./define";
|
|
8
8
|
export { createDevServer, startDevServer } from "./dev";
|
|
9
9
|
export * from "./errors";
|
|
10
10
|
export * from "./i18n";
|
package/dist/provider.d.ts
CHANGED
|
@@ -2,10 +2,10 @@ export { AuthAbortError, credentialsAuthChallenge, createAuthFlowHelpers, define
|
|
|
2
2
|
export type { CredentialsAuthChallengeDefinition, CredentialsAuthChallengeRequest, CredentialsAuthCompleteResult, CredentialsAuthCredential, CredentialsAuthField, CredentialsAuthFields, CredentialsAuthFieldType, CredentialsAuthInput, CredentialsAuthLoginResult, DefineCredentialsAuthOptions, DefinedCredentialsAuth, } from "./auth";
|
|
3
3
|
export { createFormCeremony } from "./ceremonies";
|
|
4
4
|
export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderChoiceTokenError, type ProviderChoiceTokenErrorReason, type ProviderChoiceTokenPayload, parseProviderChoiceToken, } from "./choice-token";
|
|
5
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
5
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
6
6
|
export { AuthError, ProviderError, SessionExpiredError, TransportError, ValidationError, } from "./errors";
|
|
7
7
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n";
|
|
8
8
|
export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice";
|
|
9
9
|
export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, z, } from "./schema";
|
|
10
|
-
export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types";
|
|
10
|
+
export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types";
|
|
11
11
|
export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types";
|
package/dist/provider.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { AuthAbortError, credentialsAuthChallenge, createAuthFlowHelpers, defineCredentialsAuth, } from "./auth";
|
|
2
2
|
export { createFormCeremony } from "./ceremonies";
|
|
3
3
|
export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderChoiceTokenError, parseProviderChoiceToken, } from "./choice-token";
|
|
4
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
4
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
5
5
|
export { AuthError, ProviderError, SessionExpiredError, TransportError, ValidationError, } from "./errors";
|
|
6
6
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n";
|
|
7
7
|
export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice";
|
package/dist/types.d.ts
CHANGED
|
@@ -268,8 +268,16 @@ export interface HealthJourneySchedule {
|
|
|
268
268
|
kind: "interval";
|
|
269
269
|
/** ISO 8601 duration, for example PT8H. */
|
|
270
270
|
interval: Iso8601Duration;
|
|
271
|
+
randomize?: HealthScheduleRandomization;
|
|
271
272
|
jitter?: Iso8601Duration;
|
|
272
273
|
}
|
|
274
|
+
export type HealthScheduleRandomization = {
|
|
275
|
+
mode: "centered";
|
|
276
|
+
maxOffset: Iso8601Duration;
|
|
277
|
+
} | {
|
|
278
|
+
mode: "delayed";
|
|
279
|
+
maxDelay: Iso8601Duration;
|
|
280
|
+
};
|
|
273
281
|
export interface HealthJourneyStep {
|
|
274
282
|
id: string;
|
|
275
283
|
description?: string;
|
|
@@ -511,6 +519,9 @@ export interface HealthCheckCase<TInput = unknown, TOutput = unknown> {
|
|
|
511
519
|
export interface HealthCheckSuite<TInput = unknown, TOutput = unknown> {
|
|
512
520
|
/** Polling interval for the suite. All cases share this cadence. */
|
|
513
521
|
interval: ProbeInterval;
|
|
522
|
+
schedule?: {
|
|
523
|
+
randomize?: HealthScheduleRandomization;
|
|
524
|
+
};
|
|
514
525
|
/** Per-case timeout in milliseconds. Default: 30000. */
|
|
515
526
|
timeoutMs?: number;
|
|
516
527
|
/** Default degradation threshold for cases in this suite. Default: runtime threshold. */
|
package/package.json
CHANGED
package/src/contract.ts
CHANGED
|
@@ -170,6 +170,7 @@ function extractHealthCheck(
|
|
|
170
170
|
if (!value) return undefined;
|
|
171
171
|
return compactObject({
|
|
172
172
|
interval: value.interval,
|
|
173
|
+
schedule: toJsonValue(value.schedule),
|
|
173
174
|
timeoutMs: value.timeoutMs,
|
|
174
175
|
degradedThresholdMs: value.degradedThresholdMs,
|
|
175
176
|
requiresConnection: value.requiresConnection,
|
package/src/define.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import ms from "ms";
|
|
2
|
+
|
|
1
3
|
import { ProviderError, ValidationError } from "./errors";
|
|
2
4
|
import { safeParseSchemaSync } from "./schema";
|
|
3
5
|
import type {
|
|
@@ -10,6 +12,7 @@ import type {
|
|
|
10
12
|
HealthCheckUnsupported,
|
|
11
13
|
HealthJourneyDefinition,
|
|
12
14
|
HealthJourneySchedule,
|
|
15
|
+
HealthScheduleRandomization,
|
|
13
16
|
InferSchemaOutput,
|
|
14
17
|
OperationDefinition,
|
|
15
18
|
OperationHandlerResult,
|
|
@@ -118,49 +121,25 @@ const VALID_OPERATION_TRANSPORT_KINDS = [
|
|
|
118
121
|
const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
|
|
119
122
|
const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
120
123
|
|
|
121
|
-
const MS_DURATION_UNITS = new Set([
|
|
122
|
-
"years",
|
|
123
|
-
"year",
|
|
124
|
-
"yrs",
|
|
125
|
-
"yr",
|
|
126
|
-
"y",
|
|
127
|
-
"weeks",
|
|
128
|
-
"week",
|
|
129
|
-
"w",
|
|
130
|
-
"days",
|
|
131
|
-
"day",
|
|
132
|
-
"d",
|
|
133
|
-
"hours",
|
|
134
|
-
"hour",
|
|
135
|
-
"hrs",
|
|
136
|
-
"hr",
|
|
137
|
-
"h",
|
|
138
|
-
"minutes",
|
|
139
|
-
"minute",
|
|
140
|
-
"mins",
|
|
141
|
-
"min",
|
|
142
|
-
"m",
|
|
143
|
-
"seconds",
|
|
144
|
-
"second",
|
|
145
|
-
"secs",
|
|
146
|
-
"sec",
|
|
147
|
-
"s",
|
|
148
|
-
"milliseconds",
|
|
149
|
-
"millisecond",
|
|
150
|
-
"msecs",
|
|
151
|
-
"msec",
|
|
152
|
-
"ms",
|
|
153
|
-
]);
|
|
154
124
|
const MS_DURATION_PATTERN = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))\s*([a-zA-Z]+)?$/;
|
|
155
125
|
|
|
156
126
|
function isPositiveMsDurationString(value: unknown): value is string {
|
|
157
127
|
if (typeof value !== "string") return false;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
128
|
+
return parsePositiveMsDuration(value) !== undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function msDurationMs(value: string): number {
|
|
132
|
+
return parsePositiveMsDuration(value) ?? 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function parsePositiveMsDuration(value: string): number | undefined {
|
|
136
|
+
const trimmed = value.trim();
|
|
137
|
+
if (!MS_DURATION_PATTERN.test(trimmed)) return undefined;
|
|
138
|
+
const parsed = ms(
|
|
139
|
+
(trimmed.startsWith("+") ? trimmed.slice(1) : trimmed) as ms.StringValue,
|
|
140
|
+
);
|
|
141
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
|
|
142
|
+
return parsed;
|
|
164
143
|
}
|
|
165
144
|
|
|
166
145
|
type ProviderOperation = OperationDefinition<SchemaLike, SchemaLike>;
|
|
@@ -1124,6 +1103,7 @@ function validateOperationTransports(
|
|
|
1124
1103
|
|
|
1125
1104
|
const HEALTH_CHECK_SUITE_FIELDS = new Set([
|
|
1126
1105
|
"interval",
|
|
1106
|
+
"schedule",
|
|
1127
1107
|
"timeoutMs",
|
|
1128
1108
|
"degradedThresholdMs",
|
|
1129
1109
|
"cases",
|
|
@@ -1471,6 +1451,35 @@ function validateHealthCheckSuite(
|
|
|
1471
1451
|
fix: `Set ${fieldPath}.interval to a positive ms-style duration string.`,
|
|
1472
1452
|
},
|
|
1473
1453
|
);
|
|
1454
|
+
if (s.schedule !== undefined) {
|
|
1455
|
+
if (
|
|
1456
|
+
!s.schedule ||
|
|
1457
|
+
typeof s.schedule !== "object" ||
|
|
1458
|
+
Array.isArray(s.schedule)
|
|
1459
|
+
) {
|
|
1460
|
+
throw new ValidationError(
|
|
1461
|
+
`Provider "${providerId}" ${fieldPath}.schedule must be an object.`,
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
if (Reflect.get(s.schedule, "jitter") !== undefined) {
|
|
1465
|
+
throw new ValidationError(
|
|
1466
|
+
`Provider "${providerId}" ${fieldPath}.schedule.jitter is not supported for operation healthCheck schedules. Use schedule.randomize instead.`,
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
rejectUnknownFields(
|
|
1470
|
+
s.schedule,
|
|
1471
|
+
new Set(["randomize"]),
|
|
1472
|
+
`${fieldPath}.schedule`,
|
|
1473
|
+
);
|
|
1474
|
+
const randomize = Reflect.get(s.schedule, "randomize");
|
|
1475
|
+
if (randomize !== undefined) {
|
|
1476
|
+
validateScheduleRandomization(
|
|
1477
|
+
randomize,
|
|
1478
|
+
`Provider "${providerId}" ${fieldPath}.schedule.randomize`,
|
|
1479
|
+
msDurationMs(s.interval),
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1474
1483
|
if (s.timeoutMs !== undefined) {
|
|
1475
1484
|
assertBoundedIntegerMs(
|
|
1476
1485
|
s.timeoutMs,
|
|
@@ -1569,7 +1578,12 @@ const HEALTH_JOURNEY_FIELDS = new Set([
|
|
|
1569
1578
|
"steps",
|
|
1570
1579
|
"run",
|
|
1571
1580
|
]);
|
|
1572
|
-
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
|
|
1581
|
+
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
|
|
1582
|
+
"kind",
|
|
1583
|
+
"interval",
|
|
1584
|
+
"jitter",
|
|
1585
|
+
"randomize",
|
|
1586
|
+
]);
|
|
1573
1587
|
const HEALTH_JOURNEY_STEP_FIELDS = new Set([
|
|
1574
1588
|
"id",
|
|
1575
1589
|
"description",
|
|
@@ -1742,6 +1756,54 @@ function isoDurationMs(value: string): number {
|
|
|
1742
1756
|
);
|
|
1743
1757
|
}
|
|
1744
1758
|
|
|
1759
|
+
function scheduleRandomizationMs(
|
|
1760
|
+
randomize: unknown,
|
|
1761
|
+
fieldPath: string,
|
|
1762
|
+
): number {
|
|
1763
|
+
const mode = Reflect.get(randomize as object, "mode");
|
|
1764
|
+
switch (mode) {
|
|
1765
|
+
case "centered": {
|
|
1766
|
+
const maxOffset = Reflect.get(randomize as object, "maxOffset");
|
|
1767
|
+
assertIsoDuration(maxOffset, `${fieldPath}.maxOffset`);
|
|
1768
|
+
return isoDurationMs(maxOffset);
|
|
1769
|
+
}
|
|
1770
|
+
case "delayed": {
|
|
1771
|
+
const maxDelay = Reflect.get(randomize as object, "maxDelay");
|
|
1772
|
+
assertIsoDuration(maxDelay, `${fieldPath}.maxDelay`);
|
|
1773
|
+
return isoDurationMs(maxDelay);
|
|
1774
|
+
}
|
|
1775
|
+
default:
|
|
1776
|
+
throw new ValidationError(
|
|
1777
|
+
`${fieldPath}.mode must be "centered" or "delayed".`,
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
function validateScheduleRandomization(
|
|
1783
|
+
randomize: unknown,
|
|
1784
|
+
fieldPath: string,
|
|
1785
|
+
intervalMs: number,
|
|
1786
|
+
): void {
|
|
1787
|
+
if (!randomize || typeof randomize !== "object" || Array.isArray(randomize)) {
|
|
1788
|
+
throw new ValidationError(`${fieldPath} must be an object.`);
|
|
1789
|
+
}
|
|
1790
|
+
const mode = Reflect.get(randomize, "mode");
|
|
1791
|
+
const allowedFields =
|
|
1792
|
+
mode === "centered"
|
|
1793
|
+
? new Set(["mode", "maxOffset"])
|
|
1794
|
+
: new Set(["mode", "maxDelay"]);
|
|
1795
|
+
rejectUnknownFields(randomize, allowedFields, fieldPath);
|
|
1796
|
+
const offsetMs = scheduleRandomizationMs(randomize, fieldPath);
|
|
1797
|
+
if (offsetMs <= 0) {
|
|
1798
|
+
throw new ValidationError(`${fieldPath} duration must be positive.`);
|
|
1799
|
+
}
|
|
1800
|
+
if (offsetMs >= intervalMs) {
|
|
1801
|
+
throw new ValidationError(
|
|
1802
|
+
`${fieldPath} duration must be shorter than schedule interval.`,
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1745
1807
|
function assertIsoCountry(
|
|
1746
1808
|
value: unknown,
|
|
1747
1809
|
fieldPath: string,
|
|
@@ -1757,13 +1819,21 @@ function normalizeIntervalDuration(input: string): string {
|
|
|
1757
1819
|
const trimmed = input.trim();
|
|
1758
1820
|
const shorthand = /^(\d+)(s|m|h|d)$/i.exec(trimmed);
|
|
1759
1821
|
if (shorthand) {
|
|
1760
|
-
const
|
|
1822
|
+
const durationMs = msDurationMs(trimmed);
|
|
1823
|
+
const unit = shorthand[2]?.toLowerCase();
|
|
1824
|
+
const amount =
|
|
1825
|
+
unit === "s"
|
|
1826
|
+
? durationMs / 1_000
|
|
1827
|
+
: unit === "m"
|
|
1828
|
+
? durationMs / 60_000
|
|
1829
|
+
: unit === "h"
|
|
1830
|
+
? durationMs / 3_600_000
|
|
1831
|
+
: durationMs / 86_400_000;
|
|
1761
1832
|
if (!Number.isInteger(amount) || amount <= 0) {
|
|
1762
1833
|
throw new ValidationError(
|
|
1763
1834
|
`Journey schedule interval must be a positive duration.`,
|
|
1764
1835
|
);
|
|
1765
1836
|
}
|
|
1766
|
-
const unit = shorthand[2]?.toLowerCase();
|
|
1767
1837
|
if (unit === "s") return `PT${amount}S`;
|
|
1768
1838
|
if (unit === "m") return `PT${amount}M`;
|
|
1769
1839
|
if (unit === "h") return `PT${amount}H`;
|
|
@@ -1775,18 +1845,34 @@ function normalizeIntervalDuration(input: string): string {
|
|
|
1775
1845
|
|
|
1776
1846
|
export function every(
|
|
1777
1847
|
interval: string,
|
|
1778
|
-
options: { jitter?: string } = {},
|
|
1848
|
+
options: { jitter?: string; randomize?: HealthScheduleRandomization } = {},
|
|
1779
1849
|
): HealthJourneySchedule {
|
|
1850
|
+
if (options.jitter !== undefined && options.randomize !== undefined) {
|
|
1851
|
+
throw new ValidationError(
|
|
1852
|
+
`Schedule cannot define both jitter and randomize. Use randomize instead.`,
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1780
1855
|
const schedule: HealthJourneySchedule = {
|
|
1781
1856
|
kind: "interval",
|
|
1782
1857
|
interval: normalizeIntervalDuration(interval),
|
|
1783
1858
|
};
|
|
1859
|
+
if (options.randomize !== undefined) {
|
|
1860
|
+
schedule.randomize = options.randomize;
|
|
1861
|
+
}
|
|
1784
1862
|
if (options.jitter !== undefined) {
|
|
1785
1863
|
schedule.jitter = normalizeIntervalDuration(options.jitter);
|
|
1786
1864
|
}
|
|
1787
1865
|
return schedule;
|
|
1788
1866
|
}
|
|
1789
1867
|
|
|
1868
|
+
export function centered(maxOffset: string): HealthScheduleRandomization {
|
|
1869
|
+
return { mode: "centered", maxOffset: normalizeIntervalDuration(maxOffset) };
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
export function delayed(maxDelay: string): HealthScheduleRandomization {
|
|
1873
|
+
return { mode: "delayed", maxDelay: normalizeIntervalDuration(maxDelay) };
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1790
1876
|
function countCapturingGroups(pattern: RegExp): number {
|
|
1791
1877
|
let count = 0;
|
|
1792
1878
|
const source = pattern.source;
|
|
@@ -2001,15 +2087,29 @@ function validateHealthJourneySchedule(
|
|
|
2001
2087
|
throw new ValidationError(
|
|
2002
2088
|
`Provider "${providerId}" ${fieldPath}.kind must be "interval".`,
|
|
2003
2089
|
);
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2090
|
+
const interval = Reflect.get(schedule, "interval");
|
|
2091
|
+
assertIsoDuration(interval, `Provider "${providerId}" ${fieldPath}.interval`);
|
|
2092
|
+
const randomize = Reflect.get(schedule, "randomize");
|
|
2093
|
+
if (
|
|
2094
|
+
Reflect.get(schedule, "jitter") !== undefined &&
|
|
2095
|
+
randomize !== undefined
|
|
2096
|
+
) {
|
|
2097
|
+
throw new ValidationError(
|
|
2098
|
+
`Provider "${providerId}" ${fieldPath} cannot define both jitter and randomize.`,
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2008
2101
|
if (Reflect.get(schedule, "jitter") !== undefined)
|
|
2009
2102
|
assertIsoDuration(
|
|
2010
2103
|
Reflect.get(schedule, "jitter"),
|
|
2011
2104
|
`Provider "${providerId}" ${fieldPath}.jitter`,
|
|
2012
2105
|
);
|
|
2106
|
+
if (randomize !== undefined) {
|
|
2107
|
+
validateScheduleRandomization(
|
|
2108
|
+
randomize,
|
|
2109
|
+
`Provider "${providerId}" ${fieldPath}.randomize`,
|
|
2110
|
+
isoDurationMs(interval),
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2013
2113
|
}
|
|
2014
2114
|
|
|
2015
2115
|
function validateHealthJourneys(
|
|
@@ -2252,7 +2352,9 @@ export function defineProvider<
|
|
|
2252
2352
|
if (Object.keys(config.operations).length === 0)
|
|
2253
2353
|
throw new ProviderError(
|
|
2254
2354
|
`Provider "${config.id}" must define at least one operation`,
|
|
2255
|
-
{
|
|
2355
|
+
{
|
|
2356
|
+
fix: "Add at least one operation to the operations object",
|
|
2357
|
+
},
|
|
2256
2358
|
);
|
|
2257
2359
|
validateOperationIds(config.id, config.operations);
|
|
2258
2360
|
validateOperationAnnotations(config.id, config.operations);
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,8 @@ export {
|
|
|
21
21
|
type ProviderContractSnapshot,
|
|
22
22
|
} from "./contract";
|
|
23
23
|
export {
|
|
24
|
+
centered,
|
|
25
|
+
delayed,
|
|
24
26
|
defineHealthJourney,
|
|
25
27
|
defineOperation,
|
|
26
28
|
defineProvider,
|
|
@@ -152,6 +154,7 @@ export type {
|
|
|
152
154
|
HealthJourneyRunContext,
|
|
153
155
|
HealthJourneyRunResult,
|
|
154
156
|
HealthJourneySchedule,
|
|
157
|
+
HealthScheduleRandomization,
|
|
155
158
|
HealthJourneySmsContext,
|
|
156
159
|
HealthJourneyStep,
|
|
157
160
|
HttpClient,
|
package/src/provider.ts
CHANGED
|
@@ -27,6 +27,8 @@ export {
|
|
|
27
27
|
parseProviderChoiceToken,
|
|
28
28
|
} from "./choice-token";
|
|
29
29
|
export {
|
|
30
|
+
centered,
|
|
31
|
+
delayed,
|
|
30
32
|
defineHealthJourney,
|
|
31
33
|
defineOperation,
|
|
32
34
|
defineProvider,
|
|
@@ -85,6 +87,7 @@ export type {
|
|
|
85
87
|
HealthJourneyManualTriggerPolicy,
|
|
86
88
|
HealthJourneyRunContext,
|
|
87
89
|
HealthJourneyRunResult,
|
|
90
|
+
HealthScheduleRandomization,
|
|
88
91
|
HttpRetryOptions,
|
|
89
92
|
HttpRetrySummary,
|
|
90
93
|
InferSchemaOutput,
|
package/src/types.ts
CHANGED
|
@@ -331,9 +331,20 @@ export interface HealthJourneySchedule {
|
|
|
331
331
|
kind: "interval";
|
|
332
332
|
/** ISO 8601 duration, for example PT8H. */
|
|
333
333
|
interval: Iso8601Duration;
|
|
334
|
+
randomize?: HealthScheduleRandomization;
|
|
334
335
|
jitter?: Iso8601Duration;
|
|
335
336
|
}
|
|
336
337
|
|
|
338
|
+
export type HealthScheduleRandomization =
|
|
339
|
+
| {
|
|
340
|
+
mode: "centered";
|
|
341
|
+
maxOffset: Iso8601Duration;
|
|
342
|
+
}
|
|
343
|
+
| {
|
|
344
|
+
mode: "delayed";
|
|
345
|
+
maxDelay: Iso8601Duration;
|
|
346
|
+
};
|
|
347
|
+
|
|
337
348
|
export interface HealthJourneyStep {
|
|
338
349
|
id: string;
|
|
339
350
|
description?: string;
|
|
@@ -612,6 +623,9 @@ export interface HealthCheckCase<TInput = unknown, TOutput = unknown> {
|
|
|
612
623
|
export interface HealthCheckSuite<TInput = unknown, TOutput = unknown> {
|
|
613
624
|
/** Polling interval for the suite. All cases share this cadence. */
|
|
614
625
|
interval: ProbeInterval;
|
|
626
|
+
schedule?: {
|
|
627
|
+
randomize?: HealthScheduleRandomization;
|
|
628
|
+
};
|
|
615
629
|
/** Per-case timeout in milliseconds. Default: 30000. */
|
|
616
630
|
timeoutMs?: number;
|
|
617
631
|
/** Default degradation threshold for cases in this suite. Default: runtime threshold. */
|