@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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.1.0-beta.14
4
+
5
+ - Release candidate for main commit 6eb132be6abf34ad9a70bfe28c6d26b36348ff4a.
6
+
3
7
  ## 2.1.0-beta.13
4
8
 
5
9
  - Release candidate for main commit d7b12716f54781df3e40206144c167844a485f8f.
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
- const match = value.trim().match(MS_DURATION_PATTERN);
91
- if (!match)
92
- return false;
93
- const amount = Number(match[1]);
94
- if (!Number.isFinite(amount) || amount <= 0)
95
- return false;
96
- const unit = match[2]?.toLowerCase();
97
- return unit === undefined || MS_DURATION_UNITS.has(unit);
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(["kind", "interval", "jitter"]);
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 amount = Number(shorthand[1]);
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
- assertIsoDuration(Reflect.get(schedule, "interval"), `Provider "${providerId}" ${fieldPath}.interval`);
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`, { fix: "Add at least one operation to the operations object" });
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";
@@ -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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.1.0-beta.13",
2
+ "version": "2.1.0-beta.14",
3
3
  "name": "@apifuse/provider-sdk",
4
4
  "private": false,
5
5
  "type": "module",
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
- const match = value.trim().match(MS_DURATION_PATTERN);
159
- if (!match) return false;
160
- const amount = Number(match[1]);
161
- if (!Number.isFinite(amount) || amount <= 0) return false;
162
- const unit = match[2]?.toLowerCase();
163
- return unit === undefined || MS_DURATION_UNITS.has(unit);
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(["kind", "interval", "jitter"]);
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 amount = Number(shorthand[1]);
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
- assertIsoDuration(
2005
- Reflect.get(schedule, "interval"),
2006
- `Provider "${providerId}" ${fieldPath}.interval`,
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
- { fix: "Add at least one operation to the operations object" },
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. */