@valon-technologies/gestalt 0.0.1-alpha.1 → 0.0.1-alpha.9

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/src/runtime.ts CHANGED
@@ -4,17 +4,22 @@ import { dirname, resolve } from "node:path";
4
4
 
5
5
  import { create } from "@bufbuild/protobuf";
6
6
  import { EmptySchema } from "@bufbuild/protobuf/wkt";
7
- import { Code, ConnectError, type ServiceImpl } from "@connectrpc/connect";
7
+ import {
8
+ Code,
9
+ ConnectError,
10
+ type ConnectRouter,
11
+ type ServiceImpl,
12
+ } from "@connectrpc/connect";
8
13
  import { connectNodeAdapter } from "@connectrpc/connect-node";
9
14
 
10
15
  import {
11
- AuthProvider as AuthProviderService,
16
+ AuthenticationProvider as AuthenticationProviderService,
12
17
  AuthSessionSettingsSchema,
13
18
  AuthenticatedUserSchema,
14
19
  BeginLoginResponseSchema,
15
20
  type CompleteLoginRequest as AuthCompleteLoginRequest,
16
21
  type ValidateExternalTokenRequest,
17
- } from "../gen/v1/auth_pb.ts";
22
+ } from "../gen/v1/authentication_pb.ts";
18
23
  import {
19
24
  Cache as CacheService,
20
25
  CacheDeleteManyResponseSchema,
@@ -53,23 +58,33 @@ import {
53
58
  type ConfigureProviderRequest,
54
59
  } from "../gen/v1/runtime_pb.ts";
55
60
  import { S3 as S3Service } from "../gen/v1/s3_pb.ts";
61
+ import { WorkflowProvider as WorkflowProviderService } from "../gen/v1/workflow_pb.ts";
56
62
  import { errorMessage, type Request } from "./api.ts";
57
63
  import {
58
- AuthProvider,
59
- isAuthProvider,
64
+ AuthenticationProvider,
65
+ isAuthenticationProvider,
60
66
  type AuthenticatedUser,
61
67
  } from "./auth.ts";
62
68
  import { CacheProvider, isCacheProvider } from "./cache.ts";
63
69
  import { SecretsProvider, isSecretsProvider } from "./secrets.ts";
64
70
  import { catalogToYaml, type Catalog } from "./catalog.ts";
65
71
  import {
66
- IntegrationProvider,
72
+ PluginProvider,
67
73
  connectionModeToProtoValue,
68
74
  connectionParamToProto,
69
- isIntegrationProvider,
75
+ isPluginProvider,
70
76
  } from "./plugin.ts";
77
+ import {
78
+ providerKindLabel,
79
+ resolveDefaultProviderExport,
80
+ } from "./provider-kind.ts";
71
81
  import { type ProviderKind, slugName } from "./provider.ts";
72
82
  import { S3Provider, createS3Service, isS3Provider } from "./s3.ts";
83
+ import {
84
+ WorkflowProvider,
85
+ createWorkflowProviderService,
86
+ isWorkflowProvider,
87
+ } from "./workflow.ts";
73
88
  import {
74
89
  defaultProviderName,
75
90
  formatProviderTarget,
@@ -79,24 +94,129 @@ import {
79
94
  resolveProviderImportUrl,
80
95
  } from "./target.ts";
81
96
 
97
+ /**
98
+ * Environment variable containing the Unix socket path for a running provider.
99
+ */
82
100
  export const ENV_PROVIDER_SOCKET = "GESTALT_PLUGIN_SOCKET";
101
+ /**
102
+ * Environment variable containing the parent process ID supplied by the host.
103
+ */
83
104
  export const ENV_PROVIDER_PARENT_PID = "GESTALT_PLUGIN_PARENT_PID";
105
+ /**
106
+ * Environment variable used to request static catalog generation.
107
+ */
84
108
  export const ENV_WRITE_CATALOG = "GESTALT_PLUGIN_WRITE_CATALOG";
85
- export const CURRENT_PROTOCOL_VERSION = 2;
109
+ /**
110
+ * Protocol version currently implemented by the TypeScript runtime.
111
+ */
112
+ export const CURRENT_PROTOCOL_VERSION = 3;
113
+ /**
114
+ * Command-line usage for the runtime entrypoint.
115
+ */
86
116
  export const USAGE = "usage: bun run runtime.ts ROOT PROVIDER_TARGET";
117
+ export { createWorkflowProviderService } from "./workflow.ts";
87
118
 
119
+ /**
120
+ * Parsed arguments for the runtime entrypoint.
121
+ */
88
122
  export type RuntimeArgs = {
89
123
  root: string;
90
124
  target: string;
91
125
  };
92
126
 
127
+ /**
128
+ * Provider implementations supported by the runtime host.
129
+ */
93
130
  export type LoadedProvider =
94
- | IntegrationProvider
95
- | AuthProvider
131
+ | PluginProvider
132
+ | AuthenticationProvider
96
133
  | CacheProvider
97
134
  | SecretsProvider
98
- | S3Provider;
135
+ | S3Provider
136
+ | WorkflowProvider;
137
+
138
+ type ProviderRuntimeEntry = {
139
+ isProvider: (value: unknown) => value is LoadedProvider;
140
+ protoKind: ProtoProviderKind;
141
+ registerService: (router: ConnectRouter, provider: LoadedProvider) => void;
142
+ };
143
+
144
+ const PROVIDER_RUNTIME_ENTRIES: Partial<
145
+ Record<ProviderKind, ProviderRuntimeEntry>
146
+ > = {
147
+ integration: {
148
+ isProvider: isPluginProvider as (value: unknown) => value is LoadedProvider,
149
+ protoKind: ProtoProviderKind.INTEGRATION,
150
+ registerService(router, provider) {
151
+ router.service(
152
+ IntegrationProviderService,
153
+ createProviderService(provider as PluginProvider),
154
+ );
155
+ },
156
+ },
157
+ authentication: {
158
+ isProvider:
159
+ isAuthenticationProvider as (value: unknown) => value is LoadedProvider,
160
+ protoKind: ProtoProviderKind.AUTHENTICATION,
161
+ registerService(router, provider) {
162
+ router.service(
163
+ AuthenticationProviderService,
164
+ createAuthenticationService(provider as AuthenticationProvider),
165
+ );
166
+ },
167
+ },
168
+ cache: {
169
+ isProvider: isCacheProvider as (value: unknown) => value is LoadedProvider,
170
+ protoKind: ProtoProviderKind.CACHE,
171
+ registerService(router, provider) {
172
+ router.service(
173
+ CacheService,
174
+ createCacheService(provider as CacheProvider),
175
+ );
176
+ },
177
+ },
178
+ secrets: {
179
+ isProvider: isSecretsProvider as (value: unknown) => value is LoadedProvider,
180
+ protoKind: ProtoProviderKind.SECRETS,
181
+ registerService(router, provider) {
182
+ router.service(
183
+ SecretsProviderService,
184
+ createSecretsService(provider as SecretsProvider),
185
+ );
186
+ },
187
+ },
188
+ s3: {
189
+ isProvider: isS3Provider as (value: unknown) => value is LoadedProvider,
190
+ protoKind: ProtoProviderKind.S3,
191
+ registerService(router, provider) {
192
+ router.service(S3Service, createS3Service(provider as S3Provider));
193
+ },
194
+ },
195
+ workflow: {
196
+ isProvider: isWorkflowProvider as (value: unknown) => value is LoadedProvider,
197
+ protoKind: ProtoProviderKind.WORKFLOW,
198
+ registerService(router, provider) {
199
+ router.service(
200
+ WorkflowProviderService,
201
+ createWorkflowProviderService(provider as WorkflowProvider),
202
+ );
203
+ },
204
+ },
205
+ };
206
+
207
+ function assertProtocolVersion(protocolVersion: number): void {
208
+ if (protocolVersion === CURRENT_PROTOCOL_VERSION) {
209
+ return;
210
+ }
211
+ throw new ConnectError(
212
+ `host requested protocol version ${protocolVersion}, provider requires ${CURRENT_PROTOCOL_VERSION}`,
213
+ Code.FailedPrecondition,
214
+ );
215
+ }
99
216
 
217
+ /**
218
+ * CLI entrypoint that loads a provider from source and starts serving it.
219
+ */
100
220
  export async function main(
101
221
  argv: string[] = process.argv.slice(2),
102
222
  ): Promise<number> {
@@ -112,6 +232,9 @@ export async function main(
112
232
  return 0;
113
233
  }
114
234
 
235
+ /**
236
+ * Parses `gestalt-ts-runtime` CLI arguments.
237
+ */
115
238
  export function parseRuntimeArgs(argv: string[]): RuntimeArgs | undefined {
116
239
  if (argv.length !== 2) {
117
240
  return undefined;
@@ -122,87 +245,35 @@ export function parseRuntimeArgs(argv: string[]): RuntimeArgs | undefined {
122
245
  };
123
246
  }
124
247
 
248
+ /**
249
+ * Loads any supported provider kind from a package root and optional target.
250
+ */
125
251
  export async function loadProviderFromTarget(
126
252
  root: string,
127
253
  rawTarget?: string,
128
254
  ): Promise<LoadedProvider> {
129
255
  const config = readPackageConfig(root);
130
- const targetValue =
131
- rawTarget?.trim() ||
132
- formatProviderTarget(
133
- config.providerTarget ?? readPackageProviderTarget(root),
134
- );
135
- const target = parseProviderTarget(targetValue);
256
+ const explicitTarget = rawTarget?.trim();
257
+ const target = explicitTarget
258
+ ? parseProviderTarget(explicitTarget)
259
+ : config.providerTarget ?? readPackageProviderTarget(root);
260
+ const targetValue = explicitTarget || formatProviderTarget(target);
136
261
  const module = await import(resolveProviderImportUrl(root, target));
137
262
  const candidate =
138
- (target.exportName ? module[target.exportName] : undefined) ??
139
- defaultProviderExport(module, target.kind);
263
+ (target.exportName ? Reflect.get(module, target.exportName) : undefined) ??
264
+ resolveDefaultProviderExport(module, target.kind);
140
265
 
141
266
  const defaultName =
142
267
  slugName(config.name ?? "") ||
143
268
  slugName(dirname(resolve(root, target.modulePath)));
144
- switch (target.kind) {
145
- case "integration": {
146
- if (!isIntegrationProvider(candidate)) {
147
- throw new Error(
148
- `${targetValue} did not resolve to a Gestalt integration provider`,
149
- );
150
- }
151
- candidate.resolveName(defaultName);
152
- return candidate;
153
- }
154
- case "auth": {
155
- if (!isAuthProvider(candidate)) {
156
- throw new Error(
157
- `${targetValue} did not resolve to a Gestalt auth provider`,
158
- );
159
- }
160
- candidate.resolveName(defaultName);
161
- return candidate;
162
- }
163
- case "cache": {
164
- if (!isCacheProvider(candidate)) {
165
- throw new Error(
166
- `${targetValue} did not resolve to a Gestalt cache provider`,
167
- );
168
- }
169
- candidate.resolveName(defaultName);
170
- return candidate;
171
- }
172
- case "secrets": {
173
- if (!isSecretsProvider(candidate)) {
174
- throw new Error(
175
- `${targetValue} did not resolve to a Gestalt secrets provider`,
176
- );
177
- }
178
- candidate.resolveName(defaultName);
179
- return candidate;
180
- }
181
- case "s3": {
182
- if (!isS3Provider(candidate)) {
183
- throw new Error(`${targetValue} did not resolve to a Gestalt s3 provider`);
184
- }
185
- candidate.resolveName(defaultName);
186
- return candidate;
187
- }
188
- default:
189
- throw new Error(
190
- `TypeScript SDK does not yet support provider kind ${JSON.stringify(target.kind)}`,
191
- );
192
- }
193
- }
194
-
195
- export async function loadPluginFromTarget(
196
- root: string,
197
- rawTarget?: string,
198
- ): Promise<IntegrationProvider> {
199
- const provider = await loadProviderFromTarget(root, rawTarget);
200
- if (!isIntegrationProvider(provider)) {
201
- throw new Error("target did not resolve to an integration provider");
202
- }
269
+ const provider = resolveLoadedProvider(candidate, target.kind, targetValue);
270
+ provider.resolveName(defaultName);
203
271
  return provider;
204
272
  }
205
273
 
274
+ /**
275
+ * Runs a provider that has already been loaded into memory.
276
+ */
206
277
  export async function runLoadedProvider(
207
278
  provider: LoadedProvider,
208
279
  options: {
@@ -218,101 +289,34 @@ export async function runLoadedProvider(
218
289
 
219
290
  const catalogPath = process.env[ENV_WRITE_CATALOG];
220
291
  if (catalogPath) {
221
- if (!isIntegrationProvider(provider)) {
292
+ if (!isPluginProvider(provider)) {
222
293
  throw new Error(
223
- "static catalog generation is only supported for integration providers",
294
+ "static catalog generation is only supported for plugin providers",
224
295
  );
225
296
  }
226
- writeFileSync(catalogPath, pluginCatalogYaml(provider), "utf8");
297
+ writeFileSync(catalogPath, catalogToYaml(provider.staticCatalog()), "utf8");
227
298
  return;
228
299
  }
229
300
 
230
301
  await serve(provider);
231
302
  }
232
303
 
233
- export async function runLoadedPlugin(
234
- plugin: IntegrationProvider,
235
- options: {
236
- root?: string;
237
- pluginName?: string;
238
- } = {},
239
- ): Promise<void> {
240
- const runtimeOptions: {
241
- root?: string;
242
- providerName?: string;
243
- } = {};
244
- if (options.root !== undefined) {
245
- runtimeOptions.root = options.root;
246
- }
247
- if (options.pluginName !== undefined) {
248
- runtimeOptions.providerName = options.pluginName;
249
- }
250
- await runLoadedProvider(plugin, runtimeOptions);
251
- }
252
-
304
+ /**
305
+ * Runs a bundled provider export after validating its provider kind.
306
+ */
253
307
  export async function runBundledProvider(
254
308
  provider: unknown,
255
309
  kind: ProviderKind,
256
310
  providerName: string,
257
311
  ): Promise<void> {
258
- let loaded: LoadedProvider;
259
- switch (kind) {
260
- case "integration":
261
- if (!isIntegrationProvider(provider)) {
262
- throw new Error(
263
- "bundled target did not resolve to a Gestalt integration provider",
264
- );
265
- }
266
- loaded = provider;
267
- break;
268
- case "auth":
269
- if (!isAuthProvider(provider)) {
270
- throw new Error(
271
- "bundled target did not resolve to a Gestalt auth provider",
272
- );
273
- }
274
- loaded = provider;
275
- break;
276
- case "cache":
277
- if (!isCacheProvider(provider)) {
278
- throw new Error(
279
- "bundled target did not resolve to a Gestalt cache provider",
280
- );
281
- }
282
- loaded = provider;
283
- break;
284
- case "secrets":
285
- if (!isSecretsProvider(provider)) {
286
- throw new Error(
287
- "bundled target did not resolve to a Gestalt secrets provider",
288
- );
289
- }
290
- loaded = provider;
291
- break;
292
- case "s3":
293
- if (!isS3Provider(provider)) {
294
- throw new Error("bundled target did not resolve to a Gestalt s3 provider");
295
- }
296
- loaded = provider;
297
- break;
298
- default:
299
- throw new Error(
300
- `TypeScript SDK does not yet support provider kind ${JSON.stringify(kind)}`,
301
- );
302
- }
303
- loaded.name = slugName(providerName);
304
- await runLoadedProvider(loaded, {
312
+ await runLoadedProvider(resolveLoadedProvider(provider, kind, "bundled target"), {
305
313
  providerName,
306
314
  });
307
315
  }
308
316
 
309
- export async function runBundledPlugin(
310
- plugin: unknown,
311
- pluginName: string,
312
- ): Promise<void> {
313
- await runBundledProvider(plugin, "integration", pluginName);
314
- }
315
-
317
+ /**
318
+ * Starts serving a provider over the Gestalt Unix socket transport.
319
+ */
316
320
  export async function serve(provider: LoadedProvider): Promise<void> {
317
321
  const socketPath = process.env[ENV_PROVIDER_SOCKET];
318
322
  if (!socketPath) {
@@ -328,20 +332,7 @@ export async function serve(provider: LoadedProvider): Promise<void> {
328
332
  connect: false,
329
333
  routes(router) {
330
334
  router.service(ProviderLifecycle, createRuntimeService(provider));
331
- if (isIntegrationProvider(provider)) {
332
- router.service(
333
- IntegrationProviderService,
334
- createProviderService(provider),
335
- );
336
- } else if (isAuthProvider(provider)) {
337
- router.service(AuthProviderService, createAuthService(provider));
338
- } else if (isCacheProvider(provider)) {
339
- router.service(CacheService, createCacheService(provider));
340
- } else if (isS3Provider(provider)) {
341
- router.service(S3Service, createS3Service(provider));
342
- } else if (isSecretsProvider(provider)) {
343
- router.service(SecretsProviderService, createSecretsService(provider));
344
- }
335
+ registerProviderService(router, provider);
345
336
  },
346
337
  });
347
338
 
@@ -392,13 +383,18 @@ export async function serve(provider: LoadedProvider): Promise<void> {
392
383
  }
393
384
  }
394
385
 
386
+ /**
387
+ * Adapts the provider lifecycle service used during startup and health checks.
388
+ *
389
+ * @internal
390
+ */
395
391
  export function createRuntimeService(
396
392
  provider: LoadedProvider,
397
393
  ): Partial<ServiceImpl<typeof ProviderLifecycle>> {
398
394
  return {
399
395
  async getProviderIdentity() {
400
396
  return create(ProviderIdentitySchema, {
401
- kind: providerKindToProto(provider.kind),
397
+ kind: providerRuntimeEntry(provider.kind).protoKind,
402
398
  name: provider.name,
403
399
  displayName: provider.displayName,
404
400
  description: provider.description,
@@ -409,12 +405,7 @@ export function createRuntimeService(
409
405
  });
410
406
  },
411
407
  async configureProvider(request: ConfigureProviderRequest) {
412
- if (request.protocolVersion !== CURRENT_PROTOCOL_VERSION) {
413
- throw new ConnectError(
414
- `host requested protocol version ${request.protocolVersion}, provider requires ${CURRENT_PROTOCOL_VERSION}`,
415
- Code.FailedPrecondition,
416
- );
417
- }
408
+ assertProtocolVersion(request.protocolVersion);
418
409
  try {
419
410
  await provider.configureProvider(
420
411
  request.name,
@@ -451,9 +442,17 @@ export function createRuntimeService(
451
442
  };
452
443
  }
453
444
 
445
+ /**
446
+ * Adapts a plugin provider to the shared protocol service implementation.
447
+ *
448
+ * @internal
449
+ */
454
450
  export function createProviderService(
455
- provider: IntegrationProvider,
451
+ provider: LoadedProvider,
456
452
  ): Partial<ServiceImpl<typeof IntegrationProviderService>> {
453
+ if (!isPluginProvider(provider)) {
454
+ throw new Error("provider is not a plugin provider");
455
+ }
457
456
  return {
458
457
  getMetadata() {
459
458
  return create(ProviderMetadataSchema, {
@@ -478,6 +477,7 @@ export function createProviderService(
478
477
  });
479
478
  },
480
479
  async startProvider(request: StartProviderRequest) {
480
+ assertProtocolVersion(request.protocolVersion);
481
481
  try {
482
482
  await provider.configureProvider(
483
483
  request.name,
@@ -503,6 +503,7 @@ export function createProviderService(
503
503
  request.token,
504
504
  request.connectionParams,
505
505
  request.context,
506
+ request.invocationToken,
506
507
  ),
507
508
  ),
508
509
  );
@@ -542,9 +543,15 @@ export function createProviderService(
542
543
  };
543
544
  }
544
545
 
545
- export function createAuthService(
546
- provider: AuthProvider,
547
- ): Partial<ServiceImpl<typeof AuthProviderService>> {
546
+ /**
547
+ * Adapts an authentication provider to the shared protocol service
548
+ * implementation.
549
+ *
550
+ * @internal
551
+ */
552
+ export function createAuthenticationService(
553
+ provider: AuthenticationProvider,
554
+ ): Partial<ServiceImpl<typeof AuthenticationProviderService>> {
548
555
  return {
549
556
  async beginLogin(request) {
550
557
  const response = await provider.beginLogin({
@@ -557,7 +564,7 @@ export function createAuthService(
557
564
  });
558
565
  if (!response) {
559
566
  throw new ConnectError(
560
- "auth provider returned nil response",
567
+ "authentication provider returned nil response",
561
568
  Code.Internal,
562
569
  );
563
570
  }
@@ -576,7 +583,7 @@ export function createAuthService(
576
583
  });
577
584
  if (!user) {
578
585
  throw new ConnectError(
579
- "auth provider returned nil user",
586
+ "authentication provider returned nil user",
580
587
  Code.Internal,
581
588
  );
582
589
  }
@@ -585,7 +592,7 @@ export function createAuthService(
585
592
  async validateExternalToken(request: ValidateExternalTokenRequest) {
586
593
  if (!provider.supportsExternalTokenValidation()) {
587
594
  throw new ConnectError(
588
- "auth provider does not support external token validation",
595
+ "authentication provider does not support external token validation",
589
596
  Code.Unimplemented,
590
597
  );
591
598
  }
@@ -598,7 +605,7 @@ export function createAuthService(
598
605
  async getSessionSettings() {
599
606
  if (!provider.supportsSessionSettings()) {
600
607
  throw new ConnectError(
601
- "auth provider does not expose session settings",
608
+ "authentication provider does not expose session settings",
602
609
  Code.Unimplemented,
603
610
  );
604
611
  }
@@ -610,6 +617,11 @@ export function createAuthService(
610
617
  };
611
618
  }
612
619
 
620
+ /**
621
+ * Adapts a cache provider to the shared protocol service implementation.
622
+ *
623
+ * @internal
624
+ */
613
625
  export function createCacheService(
614
626
  provider: CacheProvider,
615
627
  ): Partial<ServiceImpl<typeof CacheService>> {
@@ -674,6 +686,11 @@ export function createCacheService(
674
686
  };
675
687
  }
676
688
 
689
+ /**
690
+ * Adapts a secrets provider to the shared protocol service implementation.
691
+ *
692
+ * @internal
693
+ */
677
694
  export function createSecretsService(
678
695
  provider: SecretsProvider,
679
696
  ): Partial<ServiceImpl<typeof SecretsProviderService>> {
@@ -687,14 +704,11 @@ export function createSecretsService(
687
704
  };
688
705
  }
689
706
 
690
- export function pluginCatalogYaml(plugin: IntegrationProvider): string {
691
- return catalogToYaml(plugin.staticCatalog());
692
- }
693
-
694
707
  function providerRequest(
695
708
  token: string,
696
709
  connectionParams: Record<string, string>,
697
710
  requestContext?: ProtoRequestContext,
711
+ invocationToken = "",
698
712
  ): Request {
699
713
  const subject = requestContext?.subject;
700
714
  const credential = requestContext?.credential;
@@ -720,43 +734,44 @@ function providerRequest(
720
734
  policy: access?.policy ?? "",
721
735
  role: access?.role ?? "",
722
736
  },
737
+ workflow: {
738
+ ...(requestContext?.workflow ?? {}),
739
+ },
740
+ invocationToken,
723
741
  };
724
742
  }
725
743
 
726
- function providerKindToProto(kind: ProviderKind): ProtoProviderKind {
727
- switch (kind) {
728
- case "integration":
729
- return ProtoProviderKind.INTEGRATION;
730
- case "auth":
731
- return ProtoProviderKind.AUTH;
732
- case "cache":
733
- return ProtoProviderKind.CACHE;
734
- case "secrets":
735
- return ProtoProviderKind.SECRETS;
736
- case "s3":
737
- return ProtoProviderKind.S3;
738
- case "telemetry":
739
- return ProtoProviderKind.TELEMETRY;
740
- default:
741
- return ProtoProviderKind.UNSPECIFIED;
744
+ function providerRuntimeEntry(
745
+ kind: ProviderKind,
746
+ ): ProviderRuntimeEntry {
747
+ const entry = PROVIDER_RUNTIME_ENTRIES[kind];
748
+ if (!entry) {
749
+ throw new Error(
750
+ `TypeScript SDK does not yet support provider kind ${JSON.stringify(kind)}`,
751
+ );
742
752
  }
753
+ return entry;
743
754
  }
744
755
 
745
- function defaultProviderExport(module: Record<string, unknown>, kind: ProviderKind): unknown {
746
- switch (kind) {
747
- case "integration":
748
- return module.provider ?? module.plugin ?? module.default;
749
- case "auth":
750
- return module.auth ?? module.provider ?? module.default;
751
- case "cache":
752
- return module.cache ?? module.provider ?? module.default;
753
- case "secrets":
754
- return module.secrets ?? module.provider ?? module.default;
755
- case "s3":
756
- return module.s3 ?? module.provider ?? module.default;
757
- case "telemetry":
758
- return module.telemetry ?? module.provider ?? module.default;
756
+ function resolveLoadedProvider(
757
+ candidate: unknown,
758
+ kind: ProviderKind,
759
+ source: string,
760
+ ): LoadedProvider {
761
+ const entry = providerRuntimeEntry(kind);
762
+ if (!entry.isProvider(candidate)) {
763
+ throw new Error(
764
+ `${source} did not resolve to a Gestalt ${providerKindLabel(kind)}`,
765
+ );
759
766
  }
767
+ return candidate;
768
+ }
769
+
770
+ function registerProviderService(
771
+ router: ConnectRouter,
772
+ provider: LoadedProvider,
773
+ ): void {
774
+ providerRuntimeEntry(provider.kind).registerService(router, provider);
760
775
  }
761
776
 
762
777
  function objectFromUnknown(value: unknown): Record<string, unknown> {