@apifuse/provider-sdk 2.2.0-beta.36 → 2.2.0-beta.38

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.
Files changed (47) hide show
  1. package/AUTHORING.md +22 -8
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +20 -8
  4. package/bin/apifuse-dev.ts +1 -1
  5. package/bin/apifuse-pack-types.ts +6 -5
  6. package/bin/apifuse-record.ts +1 -1
  7. package/bin/apifuse-submit-check.ts +23 -10
  8. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  9. package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -1
  10. package/dist/declaration-validation.d.ts +2 -0
  11. package/dist/declaration-validation.js +29 -3
  12. package/dist/define.d.ts +39 -21
  13. package/dist/define.js +33 -9
  14. package/dist/health-scenario.d.ts +1842 -0
  15. package/dist/health-scenario.js +624 -0
  16. package/dist/index.d.ts +4 -2
  17. package/dist/index.js +1 -0
  18. package/dist/provider.d.ts +5 -1
  19. package/dist/provider.js +1 -0
  20. package/dist/runtime/browser.js +19 -11
  21. package/dist/runtime/resolver-public.d.ts +1 -1
  22. package/dist/runtime/resolver-public.js +1 -1
  23. package/dist/runtime/resolver-vendors/browser.js +57 -14
  24. package/dist/runtime/resolver-vendors/types.d.ts +9 -1
  25. package/dist/runtime/resolver-vendors/types.js +15 -0
  26. package/dist/runtime/resolver.d.ts +1 -0
  27. package/dist/runtime/resolver.js +13 -7
  28. package/dist/server/serve-implementation.d.ts +1 -1
  29. package/dist/server/serve-implementation.js +25 -0
  30. package/dist/server/types.d.ts +4 -4
  31. package/dist/types.d.ts +35 -25
  32. package/package.json +1 -1
  33. package/src/cli/templates/provider/index.ts.tpl +6 -3
  34. package/src/cli/templates/provider/operations/ping.ts.tpl +2 -1
  35. package/src/declaration-validation.ts +30 -3
  36. package/src/define.ts +144 -51
  37. package/src/health-scenario.ts +875 -0
  38. package/src/index.ts +78 -2
  39. package/src/provider.ts +81 -1
  40. package/src/runtime/browser.ts +34 -11
  41. package/src/runtime/resolver-public.ts +2 -0
  42. package/src/runtime/resolver-vendors/browser.ts +69 -11
  43. package/src/runtime/resolver-vendors/types.ts +21 -0
  44. package/src/runtime/resolver.ts +17 -5
  45. package/src/server/serve-implementation.ts +39 -1
  46. package/src/testing/run.ts +3 -3
  47. package/src/types.ts +52 -25
@@ -1,9 +1,9 @@
1
- import { defineProvider } from "@apifuse/provider-sdk/provider";
1
+ import { defineProvider, type ProviderContextOf } from "@apifuse/provider-sdk/provider";
2
2
 
3
3
  import { providerMeta } from "./meta";
4
4
  import { operations } from "./operations";
5
5
 
6
- export default defineProvider({
6
+ const buildProvider = defineProvider({
7
7
  id: "{{PROVIDER_ID}}",
8
8
  version: "1.0.0",
9
9
  runtime: "{{RUNTIME}}"{{BROWSER_BLOCK}},
@@ -11,5 +11,8 @@ export default defineProvider({
11
11
  reviewed: "community",
12
12
  {{SECRETS_BLOCK}}{{CREDENTIAL_BLOCK}}auth: {{AUTH_BLOCK}},
13
13
  meta: providerMeta,
14
- operations: operations,
15
14
  });
15
+
16
+ export type ProviderContext = ProviderContextOf<typeof buildProvider>;
17
+
18
+ export default buildProvider({ operations });
@@ -1,8 +1,9 @@
1
1
  import { defineOperation } from "@apifuse/provider-sdk/provider";
2
+ import type { ProviderContext } from "../index";
2
3
 
3
4
  import { pingInputSchema, pingOutputSchema } from "../schemas/ping";
4
5
 
5
- export const pingOperation = defineOperation({
6
+ export const pingOperation = defineOperation<ProviderContext>()({
6
7
  descriptionKey: "operations.ping.description",
7
8
  input: pingInputSchema,
8
9
  output: pingOutputSchema,
@@ -1,5 +1,6 @@
1
1
  import { describeSchema } from "./contract-serialization.js";
2
2
  import { ProviderError } from "./errors.js";
3
+ import { HealthScenarioSchema } from "./health-scenario.js";
3
4
  import type {
4
5
  HealthJourneyDefinition,
5
6
  ProviderDefinition,
@@ -12,6 +13,8 @@ export const DECLARATION_INVALID_CODE = "DECLARATION_INVALID";
12
13
  export const DECLARATION_RULE_IDS = {
13
14
  challengeShape: "credentials-challenge-shape",
14
15
  journeyExecutable: "health-journey-executable",
16
+ journeyRunScenarioExclusive: "health-journey-run-scenario-exclusive",
17
+ journeyScenarioValid: "health-journey-scenario-valid",
15
18
  schemaSerializable: "operation-schema-serializable",
16
19
  proxyExplicitPolicy: "proxy-explicit-policy",
17
20
  proxyVendorExclusive: "proxy-vendor-fields-exclusive",
@@ -62,15 +65,39 @@ function validateHealthDeclaration(
62
65
  ): void {
63
66
  for (const [index, journey] of (provider.healthJourneys ?? []).entries()) {
64
67
  if (!journey || typeof journey !== "object") continue;
65
- if (typeof journey.run !== "function") {
68
+ const hasRun = journey.run !== undefined;
69
+ const hasScenario = journey.scenario !== undefined;
70
+ if (hasRun && hasScenario) {
71
+ const journeyPath = healthJourneyPath(journey, index);
72
+ violations.push({
73
+ ruleId: DECLARATION_RULE_IDS.journeyRunScenarioExclusive,
74
+ path: journeyPath,
75
+ message: "A health journey must declare exactly one of run or scenario, not both.",
76
+ fix: `Remove either ${journeyPath}.run or ${journeyPath}.scenario.`,
77
+ });
78
+ }
79
+ if (!hasRun && !hasScenario) {
66
80
  const journeyPath = healthJourneyPath(journey, index);
67
81
  violations.push({
68
82
  ruleId: DECLARATION_RULE_IDS.journeyExecutable,
69
83
  path: `${journeyPath}.run`,
70
- message: "coversOperations cannot provide health coverage without executable run logic.",
71
- fix: `Add an async run(ctx) implementation to ${journeyPath}.`,
84
+ message:
85
+ "coversOperations cannot provide health coverage without run or a declarative scenario.",
86
+ fix: `Add an async run(ctx) implementation or a valid scenario to ${journeyPath}.`,
72
87
  });
73
88
  }
89
+ if (hasScenario) {
90
+ const parsed = HealthScenarioSchema.safeParse(journey.scenario);
91
+ if (!parsed.success) {
92
+ const journeyPath = healthJourneyPath(journey, index);
93
+ violations.push({
94
+ ruleId: DECLARATION_RULE_IDS.journeyScenarioValid,
95
+ path: `${journeyPath}.scenario`,
96
+ message: "scenario must conform to HealthScenario.",
97
+ fix: "Build the scenario with defineHealthScenario().",
98
+ });
99
+ }
100
+ }
74
101
  }
75
102
 
76
103
  // NOTE: healthCheck.cases[].enabled is intentionally NOT validated here.
package/src/define.ts CHANGED
@@ -31,6 +31,8 @@ import type {
31
31
  ProviderAccessConfig,
32
32
  ProviderChallengeKind,
33
33
  ProviderDefinition,
34
+ ProviderContext,
35
+ ProviderContextFor,
34
36
  ProviderOcrConfig,
35
37
  ProviderDeploymentOverrides,
36
38
  ProviderHealthMonitorConfig,
@@ -197,54 +199,65 @@ function parsePositiveMsDuration(value: string): number | undefined {
197
199
  return parsed;
198
200
  }
199
201
 
200
- type ProviderOperation = OperationDefinition<SchemaLike, SchemaLike>;
201
- type OperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
202
- OperationDefinition<TInput, TOutput>,
203
- "handler"
204
- > & {
202
+ type ProviderOperation = OperationDefinition<any, any, any>;
203
+ type OperationConfig<
204
+ TInput extends SchemaLike,
205
+ TOutput extends SchemaLike,
206
+ TContext = ProviderContext,
207
+ > = Omit<OperationDefinition<TInput, TOutput, TContext>, "handler"> & {
205
208
  handler(
206
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
209
+ ctx: TContext,
207
210
  input: InferSchemaOutput<TInput>,
208
211
  ):
209
212
  | OperationHandlerResult<InferSchemaOutput<TOutput>>
210
213
  | Promise<OperationHandlerResult<InferSchemaOutput<TOutput>>>;
211
214
  };
212
- type OperationMapConfig<TOperations extends Record<string, ProviderOperation>> = {
215
+ type OperationMapConfig<
216
+ TOperations extends Record<string, ProviderOperation>,
217
+ TContext = ProviderContext,
218
+ > = {
213
219
  [K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput>
214
- ? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput>
220
+ ? OperationConfig<TInput, TOutput, TContext> | OperationDefinition<TInput, TOutput, TContext>
215
221
  : never;
216
222
  };
217
- type StreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> =
218
- | SseOperationConfig<TInput, TOutput>
219
- | HttpStreamOperationConfig<TInput, TOutput>
220
- | WebSocketOperationConfig<TInput, TOutput>;
221
- type SseOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
222
- OperationConfig<TInput, TOutput>,
223
- "handler" | "transport"
224
- > & {
223
+ type StreamOperationConfig<
224
+ TInput extends SchemaLike,
225
+ TOutput extends SchemaLike,
226
+ TContext = ProviderContext,
227
+ > =
228
+ | SseOperationConfig<TInput, TOutput, TContext>
229
+ | HttpStreamOperationConfig<TInput, TOutput, TContext>
230
+ | WebSocketOperationConfig<TInput, TOutput, TContext>;
231
+ type SseOperationConfig<
232
+ TInput extends SchemaLike,
233
+ TOutput extends SchemaLike,
234
+ TContext = ProviderContext,
235
+ > = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
225
236
  transport: OperationSseTransport;
226
237
  handler(
227
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
238
+ ctx: TContext,
228
239
  input: InferSchemaOutput<TInput>,
229
240
  ): AsyncIterable<ProviderStreamEvent> | Promise<AsyncIterable<ProviderStreamEvent>>;
230
241
  };
231
- type HttpStreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
232
- OperationConfig<TInput, TOutput>,
233
- "handler" | "transport"
234
- > & {
242
+ type HttpStreamOperationConfig<
243
+ TInput extends SchemaLike,
244
+ TOutput extends SchemaLike,
245
+ TContext = ProviderContext,
246
+ > = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
235
247
  transport: OperationHttpStreamTransport;
236
248
  handler(
237
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
249
+ ctx: TContext,
238
250
  input: InferSchemaOutput<TInput>,
239
251
  ): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
240
252
  };
241
- type WebSocketOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
242
- OperationConfig<TInput, TOutput>,
243
- "handler" | "transport"
244
- > & {
253
+ type WebSocketOperationConfig<
254
+ TInput extends SchemaLike,
255
+ TOutput extends SchemaLike,
256
+ TContext = ProviderContext,
257
+ > = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
245
258
  transport: OperationWebSocketTransport;
246
259
  handler(
247
- ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
260
+ ctx: TContext,
248
261
  input: InferSchemaOutput<TInput>,
249
262
  ): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
250
263
  };
@@ -562,7 +575,7 @@ function authStartHasHiddenInput(start: unknown): boolean {
562
575
  return /\s=\s/.test(second);
563
576
  }
564
577
 
565
- export interface ProviderConfig<TOperations extends Record<string, ProviderOperation>> {
578
+ export interface ProviderDeclaration {
566
579
  id: string;
567
580
  version: string;
568
581
  runtime: "standard" | "shared" | "browser";
@@ -573,6 +586,8 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
573
586
  * resolves omitted fields against the runtime deployment profiles.
574
587
  */
575
588
  deployment?: ProviderDeploymentOverrides;
589
+ /** Declares that provider operations use the SDK HTTP client. */
590
+ http?: true;
576
591
  allowedHosts?: string[];
577
592
  native?: NativeProviderConfig;
578
593
  stealth?: {
@@ -585,11 +600,21 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
585
600
  resolver?: ProviderResolverConfig;
586
601
  browser?: { engine: BrowserEngine };
587
602
  auth?: AuthConfig;
603
+ /** Declares that provider operations issue and consume SDK choice tokens. */
604
+ choice?: true;
588
605
  reviewed?: ProviderReviewed;
589
606
  access?: ProviderAccessConfig;
590
607
  secrets?: ProviderSecretDeclaration[];
608
+ /** Declares that provider operations read SDK-managed environment values. */
609
+ env?: true;
591
610
  credential?: CredentialDeclaration;
592
611
  context?: ContextDeclaration;
612
+ /** Declares that provider operations use SDK-managed persistent state. */
613
+ state?: true;
614
+ /** Declares that provider operations use the SDK provider cache. */
615
+ cache?: true;
616
+ /** Declares that provider operations access runtime-resolvable files. */
617
+ files?: true;
593
618
  meta: {
594
619
  displayName: string;
595
620
  displayNameKey?: string;
@@ -611,25 +636,35 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
611
636
  publicSchemaFieldNames?: "normalized";
612
637
  };
613
638
  };
614
- operations: OperationMapConfig<TOperations>;
615
639
  healthMonitor?: ProviderHealthMonitorConfig;
616
640
  /** New name for `healthMonitor` (transitional alias); declaring both is a ValidationError. */
617
641
  healthProbe?: ProviderHealthMonitorConfig;
618
642
  healthJourneys?: readonly HealthJourneyDefinition[];
619
643
  }
620
644
 
621
- /** Define one provider operation with schema-driven handler inference. */
622
- export function defineOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
623
- operation: OperationConfig<TInput, TOutput>,
624
- ): OperationDefinition<TInput, TOutput> {
625
- return operation;
645
+ interface ProviderConfig<
646
+ TOperations extends Record<string, ProviderOperation>,
647
+ TContext = ProviderContext,
648
+ > extends ProviderDeclaration {
649
+ operations: OperationMapConfig<TOperations, TContext>;
626
650
  }
627
651
 
628
- /** Define a non-JSON provider operation with explicit transport metadata. */
629
- export function defineStreamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
630
- operation: StreamOperationConfig<TInput, TOutput>,
631
- ): OperationDefinition<TInput, TOutput> {
632
- return operation;
652
+ /** Define one factored provider operation with schema-driven handler inference. */
653
+ export function defineOperation<TContext>() {
654
+ return function operation<TInput extends SchemaLike, TOutput extends SchemaLike>(
655
+ config: OperationConfig<TInput, TOutput, TContext>,
656
+ ): OperationDefinition<TInput, TOutput, TContext> {
657
+ return config;
658
+ };
659
+ }
660
+
661
+ /** Define a factored non-JSON operation with explicit transport metadata. */
662
+ export function defineStreamOperation<TContext>() {
663
+ return function streamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
664
+ config: StreamOperationConfig<TInput, TOutput, TContext>,
665
+ ): OperationDefinition<TInput, TOutput, TContext> {
666
+ return config;
667
+ };
633
668
  }
634
669
 
635
670
  function assertObjectConfig(value: unknown): asserts value is Record<string, unknown> {
@@ -780,6 +815,14 @@ function validateProviderShape(config: unknown): void {
780
815
  assertRequiredField(config, "operations", String(config.id));
781
816
  if (typeof config.runtime === "string")
782
817
  assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
818
+ if (config.native !== undefined && config.runtime === "browser") {
819
+ throw new ValidationError(
820
+ `Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`,
821
+ {
822
+ fix: 'Use runtime: "standard" or runtime: "shared", or remove the native declaration.',
823
+ },
824
+ );
825
+ }
783
826
  const auth = config.auth;
784
827
  if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
785
828
  assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
@@ -1087,12 +1130,14 @@ function validateProviderResolver(config: { id: string; resolver?: ProviderResol
1087
1130
  "resolver",
1088
1131
  config.id,
1089
1132
  );
1090
- validateResolverLiteralArray(
1091
- resolver.vendors,
1092
- "resolver.vendors",
1093
- VALID_PROVIDER_RESOLVER_VENDORS,
1094
- config.id,
1095
- );
1133
+ if (resolver.vendors !== undefined) {
1134
+ validateResolverLiteralArray(
1135
+ resolver.vendors,
1136
+ "resolver.vendors",
1137
+ VALID_PROVIDER_RESOLVER_VENDORS,
1138
+ config.id,
1139
+ );
1140
+ }
1096
1141
  validateResolverLiteralArray(
1097
1142
  resolver.kinds,
1098
1143
  "resolver.kinds",
@@ -2087,6 +2132,7 @@ const HEALTH_JOURNEY_FIELDS = new Set([
2087
2132
  "manualTrigger",
2088
2133
  "steps",
2089
2134
  "run",
2135
+ "scenario",
2090
2136
  ]);
2091
2137
  const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set(["kind", "interval", "jitter", "randomize"]);
2092
2138
  const HEALTH_JOURNEY_STEP_FIELDS = new Set([
@@ -2622,6 +2668,14 @@ function validateHealthJourneys(
2622
2668
  }
2623
2669
  if (journey.manualTrigger !== undefined)
2624
2670
  validateHealthJourneyManualTrigger(providerId, journey.id, journey.manualTrigger);
2671
+ if (journey.scenario !== undefined && journey.smsMatchers !== undefined)
2672
+ throw new ValidationError(
2673
+ `Provider "${providerId}" healthJourneys.${journey.id}.smsMatchers is not allowed on declarative scenarios.`,
2674
+ );
2675
+ if (journey.scenario !== undefined && journey.requiredSecrets !== undefined)
2676
+ throw new ValidationError(
2677
+ `Provider "${providerId}" healthJourneys.${journey.id}.requiredSecrets is not allowed on declarative scenarios.`,
2678
+ );
2625
2679
  if (journey.timeout !== undefined)
2626
2680
  assertIsoDuration(
2627
2681
  journey.timeout,
@@ -2783,12 +2837,48 @@ function validateProviderDeployment(providerId: string, deployment: unknown): vo
2783
2837
  });
2784
2838
  }
2785
2839
 
2786
- export function defineProvider<
2840
+ /** The second authoring phase for a declaration established by defineProvider. */
2841
+ export type ProviderBuilder<TDeclaration extends ProviderDeclaration> = <
2842
+ TOperations extends Record<string, ProviderOperation>,
2843
+ >(
2844
+ implementation: {
2845
+ operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
2846
+ },
2847
+ ) => ProviderDefinition & {
2848
+ operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
2849
+ };
2850
+
2851
+ /** Extract the declaration-derived operation context from a provider builder. */
2852
+ export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer TDeclaration>
2853
+ ? ProviderContextFor<TDeclaration>
2854
+ : never;
2855
+
2856
+ /** Establish a provider declaration before its operations are contextually typed. */
2857
+ export function defineProvider<const TDeclaration extends ProviderDeclaration>(
2858
+ declaration: TDeclaration &
2859
+ Record<Exclude<keyof TDeclaration, keyof ProviderDeclaration>, never> &
2860
+ AuthStartNoInputGuard<TDeclaration>,
2861
+ ): ProviderBuilder<TDeclaration> {
2862
+ const buildProvider = <TOperations extends Record<string, ProviderOperation>>(
2863
+ implementation: {
2864
+ operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
2865
+ },
2866
+ ) =>
2867
+ finalizeProvider({
2868
+ ...declaration,
2869
+ ...implementation,
2870
+ } as ProviderConfig<TOperations, ProviderContextFor<TDeclaration>>);
2871
+ return buildProvider as ProviderBuilder<TDeclaration>;
2872
+ }
2873
+
2874
+ function finalizeProvider<
2787
2875
  TOperations extends Record<string, ProviderOperation>,
2788
- TConfig extends ProviderConfig<TOperations>,
2876
+ TContext,
2789
2877
  >(
2790
- config: TConfig & AuthStartNoInputGuard<TConfig>,
2791
- ): ProviderDefinition & { operations: OperationMapConfig<TOperations> } {
2878
+ config: ProviderConfig<TOperations, TContext>,
2879
+ ): ProviderDefinition & {
2880
+ operations: OperationMapConfig<TOperations, TContext>;
2881
+ } {
2792
2882
  validateProviderShape(config);
2793
2883
  const operations = resolveOperationFixtureRequests(config.operations);
2794
2884
  if (!CONNECTOR_ID_REGEX.test(config.id))
@@ -2849,7 +2939,9 @@ export function defineProvider<
2849
2939
  `Provider "${config.id}" cannot define browser config unless runtime is "browser"`,
2850
2940
  { fix: 'Set runtime: "browser" or remove the browser config' },
2851
2941
  );
2852
- const provider: ProviderDefinition & { operations: OperationMapConfig<TOperations> } = {
2942
+ const provider: ProviderDefinition & {
2943
+ operations: OperationMapConfig<TOperations, TContext>;
2944
+ } = {
2853
2945
  id: config.id,
2854
2946
  version: config.version,
2855
2947
  runtime: config.runtime,
@@ -2871,7 +2963,8 @@ export function defineProvider<
2871
2963
  credential: config.credential,
2872
2964
  context: config.context,
2873
2965
  meta: config.meta,
2874
- operations,
2966
+ operations: operations as ProviderDefinition["operations"] &
2967
+ OperationMapConfig<TOperations, TContext>,
2875
2968
  // Transitional healthMonitor → healthProbe alias: mirror whichever field
2876
2969
  // was declared onto both so old and new consumers keep working.
2877
2970
  healthMonitor: config.healthMonitor ?? config.healthProbe,