@valon-technologies/gestalt 0.0.1-alpha.11 → 0.0.1-alpha.12

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.
@@ -0,0 +1,113 @@
1
+ import type { Access, Credential, MaybePromise, Subject } from "./api.ts";
2
+
3
+ /**
4
+ * Verified hosted HTTP request metadata passed into optional plugin-local
5
+ * subject resolution hooks before normal operation dispatch.
6
+ */
7
+ export interface HTTPSubjectRequest {
8
+ binding: string;
9
+ method: string;
10
+ path: string;
11
+ contentType: string;
12
+ headers: Record<string, string[]>;
13
+ query: Record<string, string[]>;
14
+ params: Record<string, unknown>;
15
+ rawBody: Uint8Array;
16
+ securityScheme: string;
17
+ verifiedSubject: string;
18
+ verifiedClaims: Record<string, string>;
19
+ }
20
+
21
+ /**
22
+ * Request-scoped caller context available while resolving the concrete subject
23
+ * for a hosted HTTP request.
24
+ */
25
+ export interface HTTPSubjectResolutionContext {
26
+ subject: Subject;
27
+ credential: Credential;
28
+ access: Access;
29
+ workflow: Record<string, unknown>;
30
+ }
31
+
32
+ /**
33
+ * Explicit HTTP rejection surfaced from a hosted HTTP subject resolver.
34
+ */
35
+ export class HTTPSubjectResolutionError extends Error {
36
+ readonly status: number;
37
+
38
+ constructor(status: number, message: string) {
39
+ super(message);
40
+ this.name = "HTTPSubjectResolutionError";
41
+ this.status = status;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Creates an explicit hosted HTTP subject-resolution rejection.
47
+ */
48
+ export function httpSubjectError(
49
+ status: number,
50
+ message: string,
51
+ ): HTTPSubjectResolutionError {
52
+ return new HTTPSubjectResolutionError(status, message);
53
+ }
54
+
55
+ /**
56
+ * Optional hook that maps a verified hosted HTTP request to a concrete Gestalt
57
+ * subject before the target operation is authorized and executed.
58
+ */
59
+ export type HTTPSubjectResolver = (
60
+ request: HTTPSubjectRequest,
61
+ context: HTTPSubjectResolutionContext,
62
+ ) => MaybePromise<Subject | null | undefined>;
63
+
64
+ export function cloneHTTPSubjectRequest(
65
+ input: HTTPSubjectRequest,
66
+ ): HTTPSubjectRequest {
67
+ return {
68
+ binding: input.binding,
69
+ method: input.method,
70
+ path: input.path,
71
+ contentType: input.contentType,
72
+ headers: cloneStringLists(input.headers),
73
+ query: cloneStringLists(input.query),
74
+ params: {
75
+ ...input.params,
76
+ },
77
+ rawBody: new Uint8Array(input.rawBody),
78
+ securityScheme: input.securityScheme,
79
+ verifiedSubject: input.verifiedSubject,
80
+ verifiedClaims: {
81
+ ...input.verifiedClaims,
82
+ },
83
+ };
84
+ }
85
+
86
+ export function cloneHTTPSubjectResolutionContext(
87
+ input: HTTPSubjectResolutionContext,
88
+ ): HTTPSubjectResolutionContext {
89
+ return {
90
+ subject: {
91
+ ...input.subject,
92
+ },
93
+ credential: {
94
+ ...input.credential,
95
+ },
96
+ access: {
97
+ ...input.access,
98
+ },
99
+ workflow: {
100
+ ...input.workflow,
101
+ },
102
+ };
103
+ }
104
+
105
+ function cloneStringLists(
106
+ input: Record<string, string[]>,
107
+ ): Record<string, string[]> {
108
+ const output: Record<string, string[]> = {};
109
+ for (const [key, value] of Object.entries(input)) {
110
+ output[key] = [...value];
111
+ }
112
+ return output;
113
+ }
package/src/index.ts CHANGED
@@ -28,6 +28,22 @@
28
28
  * import { parseRuntimeArgs, serve } from "@valon-technologies/gestalt/runtime";
29
29
  * ```
30
30
  */
31
+ export {
32
+ Authorization,
33
+ AuthorizationClient,
34
+ ENV_AUTHORIZATION_SOCKET,
35
+ type AuthorizationActionSearchMessage,
36
+ type AuthorizationDecisionMessage,
37
+ type AuthorizationEvaluateInput,
38
+ type AuthorizationMetadataMessage,
39
+ type AuthorizationReadRelationshipsInput,
40
+ type AuthorizationReadRelationshipsMessage,
41
+ type AuthorizationResourceSearchMessage,
42
+ type AuthorizationSearchActionsInput,
43
+ type AuthorizationSearchResourcesInput,
44
+ type AuthorizationSearchSubjectsInput,
45
+ type AuthorizationSubjectSearchMessage,
46
+ } from "./authorization.ts";
31
47
  export {
32
48
  connectionParam,
33
49
  ok,
@@ -42,6 +58,13 @@ export {
42
58
  type Response,
43
59
  type Subject,
44
60
  } from "./api.ts";
61
+ export {
62
+ type HTTPSubjectRequest,
63
+ type HTTPSubjectResolutionContext,
64
+ HTTPSubjectResolutionError,
65
+ type HTTPSubjectResolver,
66
+ httpSubjectError,
67
+ } from "./http-subject.ts";
45
68
  export {
46
69
  catalogToJson,
47
70
  catalogToYaml,
@@ -76,6 +99,7 @@ export {
76
99
  } from "./build.ts";
77
100
  export {
78
101
  ENV_PLUGIN_INVOKER_SOCKET,
102
+ ENV_PLUGIN_INVOKER_SOCKET_TOKEN,
79
103
  PluginInvoker,
80
104
  type PluginGraphQLInvokeOptions,
81
105
  type PluginInvocationGrant,
@@ -84,12 +108,21 @@ export {
84
108
  export {
85
109
  ENV_WORKFLOW_MANAGER_SOCKET,
86
110
  WorkflowManager,
111
+ type ManagedWorkflowEventTriggerMessage,
87
112
  type ManagedWorkflowScheduleMessage,
113
+ type WorkflowEventMessage,
114
+ type WorkflowManagerCreateTriggerInput,
88
115
  type WorkflowManagerCreateScheduleInput,
116
+ type WorkflowManagerDeleteTriggerInput,
89
117
  type WorkflowManagerDeleteScheduleInput,
118
+ type WorkflowManagerGetTriggerInput,
90
119
  type WorkflowManagerGetScheduleInput,
120
+ type WorkflowManagerPauseTriggerInput,
91
121
  type WorkflowManagerPauseScheduleInput,
122
+ type WorkflowManagerPublishEventInput,
123
+ type WorkflowManagerResumeTriggerInput,
92
124
  type WorkflowManagerResumeScheduleInput,
125
+ type WorkflowManagerUpdateTriggerInput,
93
126
  type WorkflowManagerUpdateScheduleInput,
94
127
  } from "./workflow-manager.ts";
95
128
  export {
@@ -107,8 +140,11 @@ export {
107
140
  Cache,
108
141
  CacheProvider,
109
142
  cacheSocketEnv,
143
+ cacheSocketTokenEnv,
110
144
  defineCacheProvider,
111
145
  isCacheProvider,
146
+ ENV_CACHE_SOCKET,
147
+ ENV_CACHE_SOCKET_TOKEN,
112
148
  type CacheEntry,
113
149
  type CacheProviderOptions,
114
150
  type CacheSetOptions,
@@ -200,6 +236,7 @@ export {
200
236
  AlreadyExistsError,
201
237
  ColumnType,
202
238
  indexedDBSocketEnv,
239
+ indexedDBSocketTokenEnv,
203
240
  type Record,
204
241
  type KeyRange,
205
242
  type ColumnSchema,
package/src/indexeddb.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createClient, type Client } from "@connectrpc/connect";
1
+ import { createClient, type Client, type Interceptor } from "@connectrpc/connect";
2
2
  import { createGrpcTransport } from "@connectrpc/connect-node";
3
3
  import {
4
4
  IndexedDB as IndexedDBService,
@@ -6,6 +6,8 @@ import {
6
6
  } from "../gen/v1/datastore_pb";
7
7
 
8
8
  const ENV_INDEXEDDB_SOCKET = "GESTALT_INDEXEDDB_SOCKET";
9
+ const INDEXEDDB_SOCKET_TOKEN_SUFFIX = "_TOKEN";
10
+ const INDEXEDDB_RELAY_TOKEN_HEADER = "x-gestalt-host-service-relay-token";
9
11
 
10
12
  /**
11
13
  * Returns the environment variable name used to discover an IndexedDB socket.
@@ -16,6 +18,49 @@ export function indexedDBSocketEnv(name?: string): string {
16
18
  return `${ENV_INDEXEDDB_SOCKET}_${trimmed.replace(/[^A-Za-z0-9]/g, "_").toUpperCase()}`;
17
19
  }
18
20
 
21
+ /**
22
+ * Returns the environment variable name used to discover an IndexedDB relay token.
23
+ */
24
+ export function indexedDBSocketTokenEnv(name?: string): string {
25
+ return `${indexedDBSocketEnv(name)}${INDEXEDDB_SOCKET_TOKEN_SUFFIX}`;
26
+ }
27
+
28
+ function indexedDBTransportOptions(rawTarget: string): {
29
+ baseUrl: string;
30
+ nodeOptions?: { path: string };
31
+ } {
32
+ const target = rawTarget.trim();
33
+ if (!target) {
34
+ throw new Error("IndexedDB transport target is required");
35
+ }
36
+ if (target.startsWith("tcp://")) {
37
+ const address = target.slice("tcp://".length).trim();
38
+ if (!address) {
39
+ throw new Error(`IndexedDB tcp target ${JSON.stringify(rawTarget)} is missing host:port`);
40
+ }
41
+ return { baseUrl: `http://${address}` };
42
+ }
43
+ if (target.startsWith("tls://")) {
44
+ const address = target.slice("tls://".length).trim();
45
+ if (!address) {
46
+ throw new Error(`IndexedDB tls target ${JSON.stringify(rawTarget)} is missing host:port`);
47
+ }
48
+ return { baseUrl: `https://${address}` };
49
+ }
50
+ if (target.startsWith("unix://")) {
51
+ const socketPath = target.slice("unix://".length).trim();
52
+ if (!socketPath) {
53
+ throw new Error(`IndexedDB unix target ${JSON.stringify(rawTarget)} is missing a socket path`);
54
+ }
55
+ return { baseUrl: "http://localhost", nodeOptions: { path: socketPath } };
56
+ }
57
+ if (target.includes("://")) {
58
+ const parsed = new URL(target);
59
+ throw new Error(`Unsupported IndexedDB target scheme ${JSON.stringify(parsed.protocol.replace(/:$/, ""))}`);
60
+ }
61
+ return { baseUrl: "http://localhost", nodeOptions: { path: target } };
62
+ }
63
+
19
64
  class AsyncQueue<T> implements AsyncIterable<T> {
20
65
  private queue: T[] = [];
21
66
  private waiting: ((result: IteratorResult<T>) => void) | null = null;
@@ -447,15 +492,16 @@ export interface ObjectStoreSchema {
447
492
  export class IndexedDB {
448
493
  private client: Client<typeof IndexedDBService>;
449
494
 
450
- constructor(name?: string) {
495
+ constructor(name?: string) {
451
496
  const envName = indexedDBSocketEnv(name);
452
- const socketPath = process.env[envName];
453
- if (!socketPath) {
497
+ const target = process.env[envName];
498
+ if (!target) {
454
499
  throw new Error(`${envName} is not set`);
455
500
  }
501
+ const token = process.env[indexedDBSocketTokenEnv(name)]?.trim() ?? "";
456
502
  const transport = createGrpcTransport({
457
- baseUrl: `http://localhost`,
458
- nodeOptions: { path: socketPath },
503
+ ...indexedDBTransportOptions(target),
504
+ interceptors: token ? [indexedDBRelayTokenInterceptor(token)] : [],
459
505
  });
460
506
  this.client = createClient(IndexedDBService, transport);
461
507
  }
@@ -498,6 +544,13 @@ export class IndexedDB {
498
544
  }
499
545
  }
500
546
 
547
+ function indexedDBRelayTokenInterceptor(token: string): Interceptor {
548
+ return (next) => async (req) => {
549
+ req.header.set(INDEXEDDB_RELAY_TOKEN_HEADER, token);
550
+ return next(req);
551
+ };
552
+ }
553
+
501
554
  /**
502
555
  * Object store client used for primary-key operations.
503
556
  */
package/src/invoker.ts CHANGED
@@ -1,13 +1,13 @@
1
- import { connect } from "node:net";
2
-
3
1
  import type { JsonObject, JsonValue } from "@bufbuild/protobuf";
4
- import { createClient, type Client } from "@connectrpc/connect";
2
+ import { createClient, type Client, type Interceptor } from "@connectrpc/connect";
5
3
  import { createGrpcTransport } from "@connectrpc/connect-node";
6
4
 
7
5
  import { PluginInvoker as PluginInvokerService } from "../gen/v1/plugin_pb.ts";
8
6
  import type { OperationResult, Request } from "./api.ts";
9
7
 
10
8
  export const ENV_PLUGIN_INVOKER_SOCKET = "GESTALT_PLUGIN_INVOKER_SOCKET";
9
+ export const ENV_PLUGIN_INVOKER_SOCKET_TOKEN = `${ENV_PLUGIN_INVOKER_SOCKET}_TOKEN`;
10
+ const PLUGIN_INVOKER_RELAY_TOKEN_HEADER = "x-gestalt-host-service-relay-token";
11
11
 
12
12
  export interface PluginInvokeOptions {
13
13
  connection?: string;
@@ -38,12 +38,11 @@ export class PluginInvoker {
38
38
  if (!socketPath) {
39
39
  throw new Error(`plugin invoker: ${ENV_PLUGIN_INVOKER_SOCKET} is not set`);
40
40
  }
41
+ const relayToken = process.env[ENV_PLUGIN_INVOKER_SOCKET_TOKEN]?.trim() ?? "";
41
42
 
42
43
  const transport = createGrpcTransport({
43
- baseUrl: "http://localhost",
44
- nodeOptions: {
45
- createConnection: () => connect(socketPath),
46
- },
44
+ ...pluginInvokerTransportOptions(socketPath),
45
+ interceptors: relayToken ? [pluginInvokerRelayTokenInterceptor(relayToken)] : [],
47
46
  });
48
47
  this.client = createClient(PluginInvokerService, transport);
49
48
  }
@@ -118,6 +117,49 @@ export class PluginInvoker {
118
117
  }
119
118
  }
120
119
 
120
+ function pluginInvokerTransportOptions(rawTarget: string): {
121
+ baseUrl: string;
122
+ nodeOptions?: { path: string };
123
+ } {
124
+ const target = rawTarget.trim();
125
+ if (!target) {
126
+ throw new Error("plugin invoker: transport target is required");
127
+ }
128
+ if (target.startsWith("tcp://")) {
129
+ const address = target.slice("tcp://".length).trim();
130
+ if (!address) {
131
+ throw new Error(`plugin invoker: tcp target ${JSON.stringify(rawTarget)} is missing host:port`);
132
+ }
133
+ return { baseUrl: `http://${address}` };
134
+ }
135
+ if (target.startsWith("tls://")) {
136
+ const address = target.slice("tls://".length).trim();
137
+ if (!address) {
138
+ throw new Error(`plugin invoker: tls target ${JSON.stringify(rawTarget)} is missing host:port`);
139
+ }
140
+ return { baseUrl: `https://${address}` };
141
+ }
142
+ if (target.startsWith("unix://")) {
143
+ const socketPath = target.slice("unix://".length).trim();
144
+ if (!socketPath) {
145
+ throw new Error(`plugin invoker: unix target ${JSON.stringify(rawTarget)} is missing a socket path`);
146
+ }
147
+ return { baseUrl: "http://localhost", nodeOptions: { path: socketPath } };
148
+ }
149
+ if (target.includes("://")) {
150
+ const parsed = new URL(target);
151
+ throw new Error(`plugin invoker: unsupported target scheme ${JSON.stringify(parsed.protocol.replace(/:$/, ""))}`);
152
+ }
153
+ return { baseUrl: "http://localhost", nodeOptions: { path: target } };
154
+ }
155
+
156
+ function pluginInvokerRelayTokenInterceptor(token: string): Interceptor {
157
+ return (next) => async (req) => {
158
+ req.header.set(PLUGIN_INVOKER_RELAY_TOKEN_HEADER, token);
159
+ return next(req);
160
+ };
161
+ }
162
+
121
163
  function normalizeInvocationToken(requestOrToken: Request | string): string {
122
164
  const invocationToken =
123
165
  typeof requestOrToken === "string"
@@ -3,7 +3,7 @@ import { writeFileSync } from "node:fs";
3
3
  import YAML from "yaml";
4
4
 
5
5
  export type HTTPSecuritySchemeType =
6
- | "slack_signature"
6
+ | "hmac"
7
7
  | "apiKey"
8
8
  | "http"
9
9
  | "none";
@@ -20,6 +20,11 @@ export interface HTTPSecretRef {
20
20
  export interface HTTPSecurityScheme {
21
21
  type?: HTTPSecuritySchemeType;
22
22
  description?: string;
23
+ signatureHeader?: string;
24
+ signaturePrefix?: string;
25
+ payloadTemplate?: string;
26
+ timestampHeader?: string;
27
+ maxAgeSeconds?: number;
23
28
  name?: string;
24
29
  in?: HTTPIn;
25
30
  scheme?: HTTPAuthScheme;
package/src/plugin.ts CHANGED
@@ -23,7 +23,15 @@ import {
23
23
  type Request,
24
24
  responseBrand,
25
25
  type Response,
26
+ type Subject,
26
27
  } from "./api.ts";
28
+ import {
29
+ cloneHTTPSubjectRequest,
30
+ cloneHTTPSubjectResolutionContext,
31
+ type HTTPSubjectRequest,
32
+ type HTTPSubjectResolutionContext,
33
+ type HTTPSubjectResolver,
34
+ } from "./http-subject.ts";
27
35
  import { RuntimeProvider, type RuntimeProviderOptions } from "./provider.ts";
28
36
  import type { Schema } from "./schema.ts";
29
37
 
@@ -93,6 +101,7 @@ export interface PluginDefinitionOptions extends RuntimeProviderOptions {
93
101
  connectionParams?: Record<string, ConnectionParamDefinition>;
94
102
  securitySchemes?: Record<string, HTTPSecurityScheme>;
95
103
  http?: Record<string, HTTPBinding>;
104
+ resolveHTTPSubject?: HTTPSubjectResolver;
96
105
  iconSvg?: string;
97
106
  operations: Array<OperationDefinition<any, any>>;
98
107
  sessionCatalog?: SessionCatalogHandler;
@@ -149,6 +158,7 @@ export class PluginProvider extends RuntimeProvider {
149
158
  readonly http: Record<string, HTTPBinding>;
150
159
 
151
160
  private readonly sessionCatalogHandler: SessionCatalogHandler | undefined;
161
+ private readonly httpSubjectResolver: HTTPSubjectResolver | undefined;
152
162
  private readonly operations = new Map<string, OperationDefinition<any, any>>();
153
163
 
154
164
  constructor(options: PluginDefinitionOptions) {
@@ -159,6 +169,7 @@ export class PluginProvider extends RuntimeProvider {
159
169
  this.connectionParams = normalizeConnectionParams(options.connectionParams);
160
170
  this.securitySchemes = normalizeHTTPSecuritySchemes(options.securitySchemes);
161
171
  this.http = normalizeHTTPBindings(options.http);
172
+ this.httpSubjectResolver = options.resolveHTTPSubject;
162
173
  this.sessionCatalogHandler = options.sessionCatalog;
163
174
 
164
175
  for (const rawEntry of options.operations) {
@@ -189,6 +200,20 @@ export class PluginProvider extends RuntimeProvider {
189
200
  return await this.sessionCatalogHandler?.(request);
190
201
  }
191
202
 
203
+ /**
204
+ * Resolves the concrete Gestalt subject for a verified hosted HTTP request,
205
+ * if the plugin opts into subject resolution.
206
+ */
207
+ async resolveHTTPSubject(
208
+ request: HTTPSubjectRequest,
209
+ context: HTTPSubjectResolutionContext,
210
+ ): Promise<Subject | null | undefined> {
211
+ return await this.httpSubjectResolver?.(
212
+ cloneHTTPSubjectRequest(request),
213
+ cloneHTTPSubjectResolutionContext(context),
214
+ );
215
+ }
216
+
192
217
  /**
193
218
  * Returns the static catalog emitted during provider startup.
194
219
  */
@@ -424,6 +449,21 @@ function cloneHTTPSecurityScheme(value: HTTPSecurityScheme): HTTPSecurityScheme
424
449
  if (value.description !== undefined) {
425
450
  output.description = value.description;
426
451
  }
452
+ if (value.signatureHeader !== undefined) {
453
+ output.signatureHeader = value.signatureHeader;
454
+ }
455
+ if (value.signaturePrefix !== undefined) {
456
+ output.signaturePrefix = value.signaturePrefix;
457
+ }
458
+ if (value.payloadTemplate !== undefined) {
459
+ output.payloadTemplate = value.payloadTemplate;
460
+ }
461
+ if (value.timestampHeader !== undefined) {
462
+ output.timestampHeader = value.timestampHeader;
463
+ }
464
+ if (value.maxAgeSeconds !== undefined) {
465
+ output.maxAgeSeconds = value.maxAgeSeconds;
466
+ }
427
467
  if (value.name !== undefined) {
428
468
  output.name = value.name;
429
469
  }
package/src/runtime.ts CHANGED
@@ -40,9 +40,12 @@ import {
40
40
  CatalogSchema as ProtoCatalogSchema,
41
41
  ConnectionMode as ProviderConnectionMode,
42
42
  GetSessionCatalogResponseSchema,
43
+ ResolveHTTPSubjectResponseSchema,
43
44
  OperationResultSchema,
44
45
  ProviderMetadataSchema,
46
+ type HTTPSubjectRequest as ProtoHTTPSubjectRequest,
45
47
  type RequestContext as ProtoRequestContext,
48
+ type ResolveHTTPSubjectRequest as ProtoResolveHTTPSubjectRequest,
46
49
  IntegrationProvider as IntegrationProviderService,
47
50
  StartProviderResponseSchema,
48
51
  type ExecuteRequest,
@@ -68,6 +71,11 @@ import {
68
71
  import { CacheProvider, isCacheProvider } from "./cache.ts";
69
72
  import { SecretsProvider, isSecretsProvider } from "./secrets.ts";
70
73
  import { catalogToYaml, type Catalog } from "./catalog.ts";
74
+ import {
75
+ HTTPSubjectResolutionError,
76
+ type HTTPSubjectRequest,
77
+ type HTTPSubjectResolutionContext,
78
+ } from "./http-subject.ts";
71
79
  import {
72
80
  PluginProvider,
73
81
  connectionModeToProtoValue,
@@ -519,6 +527,36 @@ export function createProviderService(
519
527
  ),
520
528
  );
521
529
  },
530
+ async resolveHTTPSubject(request: ProtoResolveHTTPSubjectRequest) {
531
+ let subject;
532
+ try {
533
+ subject = await provider.resolveHTTPSubject(
534
+ providerHTTPSubjectRequest(request.request),
535
+ providerHTTPSubjectResolutionContext(request.context),
536
+ );
537
+ } catch (error) {
538
+ if (error instanceof HTTPSubjectResolutionError) {
539
+ return create(ResolveHTTPSubjectResponseSchema, {
540
+ rejectStatus: error.status,
541
+ rejectMessage: error.message,
542
+ });
543
+ }
544
+ throw new ConnectError(
545
+ `resolve http subject: ${errorMessage(error)}`,
546
+ Code.Unknown,
547
+ );
548
+ }
549
+ return create(ResolveHTTPSubjectResponseSchema, subject
550
+ ? {
551
+ subject: {
552
+ id: subject.id,
553
+ kind: subject.kind,
554
+ displayName: subject.displayName,
555
+ authSource: subject.authSource,
556
+ },
557
+ }
558
+ : {});
559
+ },
522
560
  async getSessionCatalog(request: GetSessionCatalogRequest) {
523
561
  let catalog: Catalog | Record<string, unknown> | null | undefined;
524
562
  try {
@@ -752,6 +790,48 @@ function providerRequest(
752
790
  };
753
791
  }
754
792
 
793
+ function providerHTTPSubjectRequest(
794
+ request?: ProtoHTTPSubjectRequest,
795
+ ): HTTPSubjectRequest {
796
+ return {
797
+ binding: request?.binding ?? "",
798
+ method: request?.method ?? "",
799
+ path: request?.path ?? "",
800
+ contentType: request?.contentType ?? "",
801
+ headers: providerStringLists(request?.headers),
802
+ query: providerStringLists(request?.query),
803
+ params: objectFromUnknown(request?.params),
804
+ rawBody: new Uint8Array(request?.rawBody ?? new Uint8Array()),
805
+ securityScheme: request?.securityScheme ?? "",
806
+ verifiedSubject: request?.verifiedSubject ?? "",
807
+ verifiedClaims: {
808
+ ...(request?.verifiedClaims ?? {}),
809
+ },
810
+ };
811
+ }
812
+
813
+ function providerHTTPSubjectResolutionContext(
814
+ requestContext?: ProtoRequestContext,
815
+ ): HTTPSubjectResolutionContext {
816
+ const request = providerRequest("", {}, requestContext);
817
+ return {
818
+ subject: request.subject,
819
+ credential: request.credential,
820
+ access: request.access,
821
+ workflow: request.workflow,
822
+ };
823
+ }
824
+
825
+ function providerStringLists(
826
+ input: Record<string, { values?: string[] }> | undefined,
827
+ ): Record<string, string[]> {
828
+ const output: Record<string, string[]> = {};
829
+ for (const [key, value] of Object.entries(input ?? {})) {
830
+ output[key] = [...(value.values ?? [])];
831
+ }
832
+ return output;
833
+ }
834
+
755
835
  function providerRuntimeEntry(
756
836
  kind: ProviderKind,
757
837
  ): ProviderRuntimeEntry {
package/src/s3.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { connect } from "node:net";
2
-
3
1
  import { create } from "@bufbuild/protobuf";
4
2
  import { EmptySchema } from "@bufbuild/protobuf/wkt";
5
3
  import {
@@ -503,9 +501,9 @@ export class S3 {
503
501
  throw new Error(`${envName} is not set`);
504
502
  }
505
503
  const transport = createGrpcTransport({
506
- baseUrl: "http://localhost",
504
+ baseUrl: unixSocketBaseUrl(socketPath),
507
505
  nodeOptions: {
508
- createConnection: () => connect(socketPath),
506
+ path: socketPath,
509
507
  },
510
508
  });
511
509
  this.client = createClient(S3Service, transport);
@@ -740,6 +738,15 @@ export class S3Object {
740
738
  }
741
739
  }
742
740
 
741
+ function unixSocketBaseUrl(socketPath: string): string {
742
+ let hash = 0x811c9dc5;
743
+ for (const char of socketPath) {
744
+ hash ^= char.charCodeAt(0);
745
+ hash = Math.imul(hash, 0x01000193);
746
+ }
747
+ return `http://unix-${(hash >>> 0).toString(16)}.local`;
748
+ }
749
+
743
750
  async function invokeS3Provider<T>(label: string, fn: () => Promise<T>): Promise<T> {
744
751
  try {
745
752
  return await fn();