@valon-technologies/gestalt 0.0.1-alpha.1

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 ADDED
@@ -0,0 +1,871 @@
1
+ import { existsSync, rmSync, writeFileSync } from "node:fs";
2
+ import { createServer } from "node:http2";
3
+ import { dirname, resolve } from "node:path";
4
+
5
+ import { create } from "@bufbuild/protobuf";
6
+ import { EmptySchema } from "@bufbuild/protobuf/wkt";
7
+ import { Code, ConnectError, type ServiceImpl } from "@connectrpc/connect";
8
+ import { connectNodeAdapter } from "@connectrpc/connect-node";
9
+
10
+ import {
11
+ AuthProvider as AuthProviderService,
12
+ AuthSessionSettingsSchema,
13
+ AuthenticatedUserSchema,
14
+ BeginLoginResponseSchema,
15
+ type CompleteLoginRequest as AuthCompleteLoginRequest,
16
+ type ValidateExternalTokenRequest,
17
+ } from "../gen/v1/auth_pb.ts";
18
+ import {
19
+ Cache as CacheService,
20
+ CacheDeleteManyResponseSchema,
21
+ CacheDeleteResponseSchema,
22
+ CacheGetManyResponseSchema,
23
+ CacheGetResponseSchema,
24
+ CacheResultSchema,
25
+ CacheTouchResponseSchema,
26
+ } from "../gen/v1/cache_pb.ts";
27
+ import {
28
+ SecretsProvider as SecretsProviderService,
29
+ GetSecretResponseSchema,
30
+ type GetSecretRequest,
31
+ } from "../gen/v1/secrets_pb.ts";
32
+ import {
33
+ CatalogOperationSchema as ProtoCatalogOperationSchema,
34
+ CatalogParameterSchema as ProtoCatalogParameterSchema,
35
+ CatalogSchema as ProtoCatalogSchema,
36
+ ConnectionMode as ProviderConnectionMode,
37
+ GetSessionCatalogResponseSchema,
38
+ OperationResultSchema,
39
+ ProviderMetadataSchema,
40
+ type RequestContext as ProtoRequestContext,
41
+ IntegrationProvider as IntegrationProviderService,
42
+ StartProviderResponseSchema,
43
+ type ExecuteRequest,
44
+ type GetSessionCatalogRequest,
45
+ type StartProviderRequest,
46
+ } from "../gen/v1/plugin_pb.ts";
47
+ import {
48
+ ConfigureProviderResponseSchema,
49
+ HealthCheckResponseSchema,
50
+ ProviderIdentitySchema,
51
+ ProviderKind as ProtoProviderKind,
52
+ ProviderLifecycle,
53
+ type ConfigureProviderRequest,
54
+ } from "../gen/v1/runtime_pb.ts";
55
+ import { S3 as S3Service } from "../gen/v1/s3_pb.ts";
56
+ import { errorMessage, type Request } from "./api.ts";
57
+ import {
58
+ AuthProvider,
59
+ isAuthProvider,
60
+ type AuthenticatedUser,
61
+ } from "./auth.ts";
62
+ import { CacheProvider, isCacheProvider } from "./cache.ts";
63
+ import { SecretsProvider, isSecretsProvider } from "./secrets.ts";
64
+ import { catalogToYaml, type Catalog } from "./catalog.ts";
65
+ import {
66
+ IntegrationProvider,
67
+ connectionModeToProtoValue,
68
+ connectionParamToProto,
69
+ isIntegrationProvider,
70
+ } from "./plugin.ts";
71
+ import { type ProviderKind, slugName } from "./provider.ts";
72
+ import { S3Provider, createS3Service, isS3Provider } from "./s3.ts";
73
+ import {
74
+ defaultProviderName,
75
+ formatProviderTarget,
76
+ parseProviderTarget,
77
+ readPackageConfig,
78
+ readPackageProviderTarget,
79
+ resolveProviderImportUrl,
80
+ } from "./target.ts";
81
+
82
+ export const ENV_PROVIDER_SOCKET = "GESTALT_PLUGIN_SOCKET";
83
+ export const ENV_PROVIDER_PARENT_PID = "GESTALT_PLUGIN_PARENT_PID";
84
+ export const ENV_WRITE_CATALOG = "GESTALT_PLUGIN_WRITE_CATALOG";
85
+ export const CURRENT_PROTOCOL_VERSION = 2;
86
+ export const USAGE = "usage: bun run runtime.ts ROOT PROVIDER_TARGET";
87
+
88
+ export type RuntimeArgs = {
89
+ root: string;
90
+ target: string;
91
+ };
92
+
93
+ export type LoadedProvider =
94
+ | IntegrationProvider
95
+ | AuthProvider
96
+ | CacheProvider
97
+ | SecretsProvider
98
+ | S3Provider;
99
+
100
+ export async function main(
101
+ argv: string[] = process.argv.slice(2),
102
+ ): Promise<number> {
103
+ const args = parseRuntimeArgs(argv);
104
+ if (!args) {
105
+ console.error(USAGE);
106
+ return 2;
107
+ }
108
+ const provider = await loadProviderFromTarget(args.root, args.target);
109
+ await runLoadedProvider(provider, {
110
+ root: args.root,
111
+ });
112
+ return 0;
113
+ }
114
+
115
+ export function parseRuntimeArgs(argv: string[]): RuntimeArgs | undefined {
116
+ if (argv.length !== 2) {
117
+ return undefined;
118
+ }
119
+ return {
120
+ root: argv[0]!,
121
+ target: argv[1]!,
122
+ };
123
+ }
124
+
125
+ export async function loadProviderFromTarget(
126
+ root: string,
127
+ rawTarget?: string,
128
+ ): Promise<LoadedProvider> {
129
+ const config = readPackageConfig(root);
130
+ const targetValue =
131
+ rawTarget?.trim() ||
132
+ formatProviderTarget(
133
+ config.providerTarget ?? readPackageProviderTarget(root),
134
+ );
135
+ const target = parseProviderTarget(targetValue);
136
+ const module = await import(resolveProviderImportUrl(root, target));
137
+ const candidate =
138
+ (target.exportName ? module[target.exportName] : undefined) ??
139
+ defaultProviderExport(module, target.kind);
140
+
141
+ const defaultName =
142
+ slugName(config.name ?? "") ||
143
+ 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
+ }
203
+ return provider;
204
+ }
205
+
206
+ export async function runLoadedProvider(
207
+ provider: LoadedProvider,
208
+ options: {
209
+ root?: string;
210
+ providerName?: string;
211
+ } = {},
212
+ ): Promise<void> {
213
+ if (options.providerName) {
214
+ provider.name = slugName(options.providerName);
215
+ } else if (!provider.name && options.root) {
216
+ provider.resolveName(defaultProviderName(options.root));
217
+ }
218
+
219
+ const catalogPath = process.env[ENV_WRITE_CATALOG];
220
+ if (catalogPath) {
221
+ if (!isIntegrationProvider(provider)) {
222
+ throw new Error(
223
+ "static catalog generation is only supported for integration providers",
224
+ );
225
+ }
226
+ writeFileSync(catalogPath, pluginCatalogYaml(provider), "utf8");
227
+ return;
228
+ }
229
+
230
+ await serve(provider);
231
+ }
232
+
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
+
253
+ export async function runBundledProvider(
254
+ provider: unknown,
255
+ kind: ProviderKind,
256
+ providerName: string,
257
+ ): 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, {
305
+ providerName,
306
+ });
307
+ }
308
+
309
+ export async function runBundledPlugin(
310
+ plugin: unknown,
311
+ pluginName: string,
312
+ ): Promise<void> {
313
+ await runBundledProvider(plugin, "integration", pluginName);
314
+ }
315
+
316
+ export async function serve(provider: LoadedProvider): Promise<void> {
317
+ const socketPath = process.env[ENV_PROVIDER_SOCKET];
318
+ if (!socketPath) {
319
+ throw new Error(`${ENV_PROVIDER_SOCKET} is required`);
320
+ }
321
+ if (existsSync(socketPath)) {
322
+ rmSync(socketPath);
323
+ }
324
+
325
+ const handler = connectNodeAdapter({
326
+ grpc: true,
327
+ grpcWeb: false,
328
+ connect: false,
329
+ routes(router) {
330
+ 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
+ }
345
+ },
346
+ });
347
+
348
+ const server = createServer(handler);
349
+ let shutdownError: unknown;
350
+ let closePromise: Promise<void> | undefined;
351
+ const close = () => {
352
+ closePromise ??= (async () => {
353
+ try {
354
+ await provider.closeProvider();
355
+ } catch (error) {
356
+ shutdownError = error;
357
+ } finally {
358
+ try {
359
+ await new Promise<void>((resolveClose) => {
360
+ server.close(() => resolveClose());
361
+ });
362
+ } finally {
363
+ if (existsSync(socketPath)) {
364
+ rmSync(socketPath);
365
+ }
366
+ }
367
+ }
368
+ })();
369
+ return closePromise;
370
+ };
371
+
372
+ await new Promise<void>((resolveListen, rejectListen) => {
373
+ server.once("error", rejectListen);
374
+ server.listen(socketPath, () => {
375
+ server.off("error", rejectListen);
376
+ resolveListen();
377
+ });
378
+ });
379
+
380
+ const shutdown = () => {
381
+ void close();
382
+ };
383
+ process.once("SIGINT", shutdown);
384
+ process.once("SIGTERM", shutdown);
385
+
386
+ await new Promise<void>((resolveClose, rejectClose) => {
387
+ server.once("close", resolveClose);
388
+ server.once("error", rejectClose);
389
+ });
390
+ if (shutdownError) {
391
+ throw shutdownError;
392
+ }
393
+ }
394
+
395
+ export function createRuntimeService(
396
+ provider: LoadedProvider,
397
+ ): Partial<ServiceImpl<typeof ProviderLifecycle>> {
398
+ return {
399
+ async getProviderIdentity() {
400
+ return create(ProviderIdentitySchema, {
401
+ kind: providerKindToProto(provider.kind),
402
+ name: provider.name,
403
+ displayName: provider.displayName,
404
+ description: provider.description,
405
+ version: provider.version,
406
+ warnings: await provider.warnings(),
407
+ minProtocolVersion: CURRENT_PROTOCOL_VERSION,
408
+ maxProtocolVersion: CURRENT_PROTOCOL_VERSION,
409
+ });
410
+ },
411
+ 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
+ }
418
+ try {
419
+ await provider.configureProvider(
420
+ request.name,
421
+ objectFromUnknown(request.config),
422
+ );
423
+ } catch (error) {
424
+ throw new ConnectError(
425
+ `configure provider: ${errorMessage(error)}`,
426
+ Code.Unknown,
427
+ );
428
+ }
429
+ return create(ConfigureProviderResponseSchema, {
430
+ protocolVersion: CURRENT_PROTOCOL_VERSION,
431
+ });
432
+ },
433
+ async healthCheck() {
434
+ if (!provider.supportsHealthCheck()) {
435
+ return create(HealthCheckResponseSchema, {
436
+ ready: true,
437
+ });
438
+ }
439
+ try {
440
+ await provider.healthCheck();
441
+ return create(HealthCheckResponseSchema, {
442
+ ready: true,
443
+ });
444
+ } catch (error) {
445
+ return create(HealthCheckResponseSchema, {
446
+ ready: false,
447
+ message: errorMessage(error),
448
+ });
449
+ }
450
+ },
451
+ };
452
+ }
453
+
454
+ export function createProviderService(
455
+ provider: IntegrationProvider,
456
+ ): Partial<ServiceImpl<typeof IntegrationProviderService>> {
457
+ return {
458
+ getMetadata() {
459
+ return create(ProviderMetadataSchema, {
460
+ name: provider.name,
461
+ displayName: provider.displayName,
462
+ description: provider.description,
463
+ connectionMode: connectionModeToProtoValue(
464
+ provider.connectionMode,
465
+ ) as ProviderConnectionMode,
466
+ authTypes: [...provider.authTypes],
467
+ connectionParams: Object.fromEntries(
468
+ Object.entries(provider.connectionParams).map(([key, value]) => [
469
+ key,
470
+ connectionParamToProto(value),
471
+ ]),
472
+ ),
473
+ staticCatalog: catalogToProto(provider.staticCatalog()),
474
+ supportsSessionCatalog: provider.supportsSessionCatalog(),
475
+ supportsPostConnect: false,
476
+ minProtocolVersion: CURRENT_PROTOCOL_VERSION,
477
+ maxProtocolVersion: CURRENT_PROTOCOL_VERSION,
478
+ });
479
+ },
480
+ async startProvider(request: StartProviderRequest) {
481
+ try {
482
+ await provider.configureProvider(
483
+ request.name,
484
+ objectFromUnknown(request.config),
485
+ );
486
+ } catch (error) {
487
+ throw new ConnectError(
488
+ `configure provider: ${errorMessage(error)}`,
489
+ Code.Unknown,
490
+ );
491
+ }
492
+ return create(StartProviderResponseSchema, {
493
+ protocolVersion: CURRENT_PROTOCOL_VERSION,
494
+ });
495
+ },
496
+ async execute(request: ExecuteRequest) {
497
+ return create(
498
+ OperationResultSchema,
499
+ await provider.execute(
500
+ request.operation,
501
+ objectFromUnknown(request.params),
502
+ providerRequest(
503
+ request.token,
504
+ request.connectionParams,
505
+ request.context,
506
+ ),
507
+ ),
508
+ );
509
+ },
510
+ async getSessionCatalog(request: GetSessionCatalogRequest) {
511
+ let catalog: Catalog | Record<string, unknown> | null | undefined;
512
+ try {
513
+ catalog = await provider.catalogForRequest(
514
+ providerRequest(
515
+ request.token,
516
+ request.connectionParams,
517
+ request.context,
518
+ ),
519
+ );
520
+ } catch (error) {
521
+ throw new ConnectError(
522
+ `session catalog: ${errorMessage(error)}`,
523
+ Code.Unknown,
524
+ );
525
+ }
526
+ if (!catalog) {
527
+ throw new ConnectError(
528
+ "provider does not support session catalogs",
529
+ Code.Unimplemented,
530
+ );
531
+ }
532
+ return create(GetSessionCatalogResponseSchema, {
533
+ catalog: catalogToProto(catalog),
534
+ });
535
+ },
536
+ async postConnect() {
537
+ throw new ConnectError(
538
+ "provider does not support post connect",
539
+ Code.Unimplemented,
540
+ );
541
+ },
542
+ };
543
+ }
544
+
545
+ export function createAuthService(
546
+ provider: AuthProvider,
547
+ ): Partial<ServiceImpl<typeof AuthProviderService>> {
548
+ return {
549
+ async beginLogin(request) {
550
+ const response = await provider.beginLogin({
551
+ callbackUrl: request.callbackUrl,
552
+ hostState: request.hostState,
553
+ scopes: [...request.scopes],
554
+ options: {
555
+ ...request.options,
556
+ },
557
+ });
558
+ if (!response) {
559
+ throw new ConnectError(
560
+ "auth provider returned nil response",
561
+ Code.Internal,
562
+ );
563
+ }
564
+ return create(BeginLoginResponseSchema, {
565
+ authorizationUrl: response.authorizationUrl,
566
+ providerState: response.providerState ?? new Uint8Array(),
567
+ });
568
+ },
569
+ async completeLogin(request: AuthCompleteLoginRequest) {
570
+ const user = await provider.completeLogin({
571
+ query: {
572
+ ...request.query,
573
+ },
574
+ providerState: cloneUint8Array(request.providerState),
575
+ callbackUrl: request.callbackUrl,
576
+ });
577
+ if (!user) {
578
+ throw new ConnectError(
579
+ "auth provider returned nil user",
580
+ Code.Internal,
581
+ );
582
+ }
583
+ return authenticatedUserToProto(user);
584
+ },
585
+ async validateExternalToken(request: ValidateExternalTokenRequest) {
586
+ if (!provider.supportsExternalTokenValidation()) {
587
+ throw new ConnectError(
588
+ "auth provider does not support external token validation",
589
+ Code.Unimplemented,
590
+ );
591
+ }
592
+ const user = await provider.validateExternalToken(request.token);
593
+ if (!user) {
594
+ throw new ConnectError("token not recognized", Code.NotFound);
595
+ }
596
+ return authenticatedUserToProto(user);
597
+ },
598
+ async getSessionSettings() {
599
+ if (!provider.supportsSessionSettings()) {
600
+ throw new ConnectError(
601
+ "auth provider does not expose session settings",
602
+ Code.Unimplemented,
603
+ );
604
+ }
605
+ const settings = await provider.sessionSettings();
606
+ return create(AuthSessionSettingsSchema, {
607
+ sessionTtlSeconds: normalizeBigInt(settings?.sessionTtlSeconds ?? 0),
608
+ });
609
+ },
610
+ };
611
+ }
612
+
613
+ export function createCacheService(
614
+ provider: CacheProvider,
615
+ ): Partial<ServiceImpl<typeof CacheService>> {
616
+ return {
617
+ async get(request) {
618
+ const value = await provider.get(request.key);
619
+ return create(CacheGetResponseSchema, {
620
+ found: value !== undefined,
621
+ value: value ? cloneUint8Array(value) : new Uint8Array(),
622
+ });
623
+ },
624
+ async getMany(request) {
625
+ const entries = await provider.getMany([...request.keys]);
626
+ return create(CacheGetManyResponseSchema, {
627
+ entries: request.keys.map((key) => {
628
+ const found = Object.hasOwn(entries, key);
629
+ const value = found ? entries[key] : undefined;
630
+ return create(CacheResultSchema, {
631
+ key,
632
+ found,
633
+ value: value ? cloneUint8Array(value) : new Uint8Array(),
634
+ });
635
+ }),
636
+ });
637
+ },
638
+ async set(request) {
639
+ await provider.set(
640
+ request.key,
641
+ cloneUint8Array(request.value),
642
+ durationToSetOptions(request.ttl),
643
+ );
644
+ return create(EmptySchema, {});
645
+ },
646
+ async setMany(request) {
647
+ await provider.setMany(
648
+ request.entries.map((entry) => ({
649
+ key: entry.key,
650
+ value: cloneUint8Array(entry.value),
651
+ })),
652
+ durationToSetOptions(request.ttl),
653
+ );
654
+ return create(EmptySchema, {});
655
+ },
656
+ async delete(request) {
657
+ return create(CacheDeleteResponseSchema, {
658
+ deleted: await provider.delete(request.key),
659
+ });
660
+ },
661
+ async deleteMany(request) {
662
+ return create(CacheDeleteManyResponseSchema, {
663
+ deleted: normalizeBigInt(await provider.deleteMany([...request.keys])),
664
+ });
665
+ },
666
+ async touch(request) {
667
+ return create(CacheTouchResponseSchema, {
668
+ touched: await provider.touch(
669
+ request.key,
670
+ durationToMs(request.ttl),
671
+ ),
672
+ });
673
+ },
674
+ };
675
+ }
676
+
677
+ export function createSecretsService(
678
+ provider: SecretsProvider,
679
+ ): Partial<ServiceImpl<typeof SecretsProviderService>> {
680
+ return {
681
+ async getSecret(request: GetSecretRequest) {
682
+ const value = await provider.getSecret(request.name);
683
+ return create(GetSecretResponseSchema, {
684
+ value,
685
+ });
686
+ },
687
+ };
688
+ }
689
+
690
+ export function pluginCatalogYaml(plugin: IntegrationProvider): string {
691
+ return catalogToYaml(plugin.staticCatalog());
692
+ }
693
+
694
+ function providerRequest(
695
+ token: string,
696
+ connectionParams: Record<string, string>,
697
+ requestContext?: ProtoRequestContext,
698
+ ): Request {
699
+ const subject = requestContext?.subject;
700
+ const credential = requestContext?.credential;
701
+ const access = requestContext?.access;
702
+ return {
703
+ token,
704
+ connectionParams: {
705
+ ...connectionParams,
706
+ },
707
+ subject: {
708
+ id: subject?.id ?? "",
709
+ kind: subject?.kind ?? "",
710
+ displayName: subject?.displayName ?? "",
711
+ authSource: subject?.authSource ?? "",
712
+ },
713
+ credential: {
714
+ mode: credential?.mode ?? "",
715
+ subjectId: credential?.subjectId ?? "",
716
+ connection: credential?.connection ?? "",
717
+ instance: credential?.instance ?? "",
718
+ },
719
+ access: {
720
+ policy: access?.policy ?? "",
721
+ role: access?.role ?? "",
722
+ },
723
+ };
724
+ }
725
+
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;
742
+ }
743
+ }
744
+
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;
759
+ }
760
+ }
761
+
762
+ function objectFromUnknown(value: unknown): Record<string, unknown> {
763
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
764
+ return {
765
+ ...(value as Record<string, unknown>),
766
+ };
767
+ }
768
+ return {};
769
+ }
770
+
771
+ function catalogToProto(catalog: Catalog | Record<string, unknown>) {
772
+ const typed = catalog as Catalog;
773
+ return create(ProtoCatalogSchema, {
774
+ name: typed.name ?? "",
775
+ displayName: typed.displayName ?? "",
776
+ description: typed.description ?? "",
777
+ iconSvg: typed.iconSvg ?? "",
778
+ operations: (typed.operations ?? []).map((op) => {
779
+ const protoOp = create(ProtoCatalogOperationSchema, {
780
+ id: op.id,
781
+ method: op.method,
782
+ title: op.title ?? "",
783
+ description: op.description ?? "",
784
+ tags: op.tags ?? [],
785
+ readOnly: op.readOnly ?? false,
786
+ allowedRoles: op.allowedRoles ?? [],
787
+ parameters: (op.parameters ?? []).map((p) =>
788
+ create(ProtoCatalogParameterSchema, {
789
+ name: p.name,
790
+ type: p.type,
791
+ description: p.description ?? "",
792
+ required: p.required ?? false,
793
+ }),
794
+ ),
795
+ });
796
+ if (op.visible !== undefined) {
797
+ protoOp.visible = op.visible;
798
+ }
799
+ return protoOp;
800
+ }),
801
+ });
802
+ }
803
+
804
+ function authenticatedUserToProto(user: AuthenticatedUser) {
805
+ return create(AuthenticatedUserSchema, {
806
+ subject: user.subject,
807
+ email: user.email ?? "",
808
+ emailVerified: user.emailVerified ?? false,
809
+ displayName: user.displayName ?? "",
810
+ avatarUrl: user.avatarUrl ?? "",
811
+ claims: {
812
+ ...(user.claims ?? {}),
813
+ },
814
+ });
815
+ }
816
+
817
+ function normalizeBigInt(value: number | bigint): bigint {
818
+ if (typeof value === "bigint") {
819
+ return value < 0n ? 0n : value;
820
+ }
821
+ if (!Number.isFinite(value)) {
822
+ return 0n;
823
+ }
824
+ return BigInt(Math.max(0, Math.trunc(value)));
825
+ }
826
+
827
+ function cloneUint8Array(value: Uint8Array | undefined): Uint8Array {
828
+ if (!value) {
829
+ return new Uint8Array();
830
+ }
831
+ return new Uint8Array(value);
832
+ }
833
+
834
+ function durationToMs(
835
+ value: { seconds: bigint; nanos: number } | undefined,
836
+ ): number {
837
+ if (!value) {
838
+ return 0;
839
+ }
840
+ const seconds = Number(value.seconds ?? 0n);
841
+ const nanos = Number(value.nanos ?? 0);
842
+ if (!Number.isFinite(seconds) || !Number.isFinite(nanos)) {
843
+ return 0;
844
+ }
845
+ return Math.max(0, (seconds * 1000) + Math.trunc(nanos / 1_000_000));
846
+ }
847
+
848
+ function durationToSetOptions(
849
+ value: { seconds: bigint; nanos: number } | undefined,
850
+ ): { ttlMs: number } | undefined {
851
+ if (!value) {
852
+ return undefined;
853
+ }
854
+ return {
855
+ ttlMs: durationToMs(value),
856
+ };
857
+ }
858
+
859
+ if (import.meta.main) {
860
+ void main().then(
861
+ (code) => {
862
+ process.exitCode = code;
863
+ },
864
+ (error: unknown) => {
865
+ console.error(
866
+ error instanceof Error ? (error.stack ?? error.message) : String(error),
867
+ );
868
+ process.exitCode = 1;
869
+ },
870
+ );
871
+ }