@michelangelo-ai/rpc 0.4.0 → 0.5.0-rc.2

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.
@@ -29,7 +29,7 @@ describe('rpc handlers — mutation envelope wrapping', () => {
29
29
  const record = { metadata: { name: 'test-run' } };
30
30
  await handlers.CreatePipelineRun(record as never);
31
31
 
32
- expect(createPipelineRun).toHaveBeenCalledWith({ pipelineRun: record });
32
+ expect(createPipelineRun).toHaveBeenCalledWith({ pipelineRun: record }, undefined);
33
33
  });
34
34
 
35
35
  it('UpdatePipelineRun wraps the bare record in a pipelineRun envelope', async () => {
@@ -37,7 +37,7 @@ describe('rpc handlers — mutation envelope wrapping', () => {
37
37
  const record = { metadata: { name: 'updated-run' } };
38
38
  await handlers.UpdatePipelineRun(record as never);
39
39
 
40
- expect(updatePipelineRun).toHaveBeenCalledWith({ pipelineRun: record });
40
+ expect(updatePipelineRun).toHaveBeenCalledWith({ pipelineRun: record }, undefined);
41
41
  });
42
42
 
43
43
  it('UpdateTriggerRun wraps the bare record in a triggerRun envelope', async () => {
@@ -45,6 +45,23 @@ describe('rpc handlers — mutation envelope wrapping', () => {
45
45
  const record = { metadata: { name: 'kill-target' } };
46
46
  await handlers.UpdateTriggerRun(record as never);
47
47
 
48
- expect(updateTriggerRun).toHaveBeenCalledWith({ triggerRun: record });
48
+ expect(updateTriggerRun).toHaveBeenCalledWith({ triggerRun: record }, undefined);
49
+ });
50
+
51
+ it('CreatePipelineRun stamps spec.actor from x-user-name header', async () => {
52
+ const handlers = await getRpcHandlers();
53
+ const record = { metadata: { name: 'run-1' }, spec: { pipeline: { name: 'p1' } } };
54
+ await handlers.CreatePipelineRun(record as never, { 'x-user-name': 'jane' });
55
+
56
+ expect(record.spec).toHaveProperty('actor');
57
+ expect((record.spec as Record<string, unknown>).actor).toMatchObject({ name: 'jane' });
58
+ });
59
+
60
+ it('CreatePipelineRun leaves spec.actor unset when x-user-name header is absent', async () => {
61
+ const handlers = await getRpcHandlers();
62
+ const record = { metadata: { name: 'run-2' }, spec: { pipeline: { name: 'p1' } } };
63
+ await handlers.CreatePipelineRun(record as never);
64
+
65
+ expect((record.spec as Record<string, unknown>).actor).toBeUndefined();
49
66
  });
50
67
  });
@@ -16,10 +16,10 @@ export function createFetchTransport(options: FetchTransportOptions): FetchTrans
16
16
  };
17
17
 
18
18
  return {
19
- async callUnary(serviceName, methodName, request) {
19
+ async callUnary(serviceName, methodName, request, perRequestHeaders) {
20
20
  const response = await fetch(`${baseUrl}/${serviceName}/${methodName}`, {
21
21
  method: 'POST',
22
- headers,
22
+ headers: perRequestHeaders ? { ...headers, ...perRequestHeaders } : headers,
23
23
  body: JSON.stringify(request),
24
24
  });
25
25
 
package/handlers.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { create } from '@bufbuild/protobuf';
2
+
3
+ import { UserInfoSchema } from './gen/michelangelo/api/v2/user_pb';
1
4
  import { getServices } from './services';
2
5
 
3
6
  import type { PipelineRun } from './gen/michelangelo/api/v2/pipeline_run_pb';
@@ -28,12 +31,17 @@ async function createHandlers() {
28
31
  GetPipelineRun: unary(services.PipelineRunService.getPipelineRun),
29
32
  ListTriggerRun: unary(services.TriggerRunService.listTriggerRun),
30
33
  GetTriggerRun: unary(services.TriggerRunService.getTriggerRun),
31
- UpdateTriggerRun: (record: TriggerRun) =>
32
- services.TriggerRunService.updateTriggerRun({ triggerRun: record }),
33
- CreatePipelineRun: (record: PipelineRun) =>
34
- services.PipelineRunService.createPipelineRun({ pipelineRun: record }),
35
- UpdatePipelineRun: (record: PipelineRun) =>
36
- services.PipelineRunService.updatePipelineRun({ pipelineRun: record }),
34
+ UpdateTriggerRun: (record: TriggerRun, headers?: Record<string, string>) =>
35
+ services.TriggerRunService.updateTriggerRun({ triggerRun: record }, headers),
36
+ CreatePipelineRun: (record: PipelineRun, headers?: Record<string, string>) => {
37
+ const actorName = headers?.['x-user-name'];
38
+ if (actorName && record.spec) {
39
+ record.spec.actor = create(UserInfoSchema, { name: actorName });
40
+ }
41
+ return services.PipelineRunService.createPipelineRun({ pipelineRun: record }, headers);
42
+ },
43
+ UpdatePipelineRun: (record: PipelineRun, headers?: Record<string, string>) =>
44
+ services.PipelineRunService.updatePipelineRun({ pipelineRun: record }, headers),
37
45
  ListModel: unary(services.ModelService.listModel),
38
46
  } as const;
39
47
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@michelangelo-ai/rpc",
3
3
  "license": "Apache-2.0",
4
4
  "type": "module",
5
- "version": "0.4.0",
5
+ "version": "0.5.0-rc.2",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/michelangelo-ai/michelangelo"
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@bufbuild/protobuf": "^2.2.5",
20
- "@michelangelo-ai/core": "*"
20
+ "@michelangelo-ai/core": "0.5.0-rc.2"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
package/request.ts CHANGED
@@ -10,6 +10,7 @@ import type { OmitTypeName, RpcHandlerType } from './types';
10
10
  *
11
11
  * @param rpcId - The ID of the RPC handler to call.
12
12
  * @param args - The arguments to pass to the RPC handler.
13
+ * @param headers - Optional HTTP headers to send with the request (e.g. user identity).
13
14
  * @returns A promise that resolves to the RPC response as a plain object.
14
15
  *
15
16
  * @example
@@ -21,14 +22,15 @@ import type { OmitTypeName, RpcHandlerType } from './types';
21
22
  */
22
23
  export async function request<RpcId extends keyof RpcHandlerType>(
23
24
  rpcId: RpcId,
24
- args: OmitTypeName<Parameters<RpcHandlerType[RpcId]>[0]>
25
+ args: OmitTypeName<Parameters<RpcHandlerType[RpcId]>[0]>,
26
+ headers?: Record<string, string>
25
27
  ): Promise<OmitTypeName<Awaited<ReturnType<RpcHandlerType[RpcId]>>>> {
26
28
  const handlers = await getRpcHandlers();
27
29
  // cast: dynamic key lookup on handlers loses the specific RPC signature; we know RpcId is a valid
28
30
  // key with matching handler shape
29
- const handler = handlers[rpcId] as (a: unknown) => Promise<unknown>;
31
+ const handler = handlers[rpcId] as (a: unknown, h?: Record<string, string>) => Promise<unknown>;
30
32
  // cast: handler returns unknown via dynamic dispatch; RpcId determines the concrete return type
31
- const response = (await handler(args)) as Awaited<ReturnType<RpcHandlerType[RpcId]>>;
33
+ const response = (await handler(args, headers)) as Awaited<ReturnType<RpcHandlerType[RpcId]>>;
32
34
  // cast: toPlainObject returns unknown; RpcId determines the proto shape after stripping $typeName
33
35
  // fields
34
36
  return toPlainObject(response) as OmitTypeName<Awaited<ReturnType<RpcHandlerType[RpcId]>>>;
package/services.ts CHANGED
@@ -26,15 +26,23 @@ function createServiceClient<T extends DescService>(
26
26
  service: T,
27
27
  transport: FetchTransport
28
28
  ): ServiceClient<T> {
29
- const client: Record<string, (request: Record<string, unknown>) => Promise<unknown>> = {};
29
+ const client: Record<
30
+ string,
31
+ (request: Record<string, unknown>, headers?: Record<string, string>) => Promise<unknown>
32
+ > = {};
30
33
 
31
34
  for (const method of service.methods) {
32
35
  if (method.methodKind !== 'unary') continue;
33
36
 
34
- client[method.localName] = async (request) => {
37
+ client[method.localName] = async (request, headers) => {
35
38
  const message = create(method.input, request);
36
39
  const requestJson = toJson(method.input, message, { registry: typeRegistry });
37
- const responseJson = await transport.callUnary(service.typeName, method.name, requestJson);
40
+ const responseJson = await transport.callUnary(
41
+ service.typeName,
42
+ method.name,
43
+ requestJson,
44
+ headers
45
+ );
38
46
  return fromJson(method.output, responseJson, { registry: typeRegistry });
39
47
  };
40
48
  }
package/types.ts CHANGED
@@ -41,7 +41,12 @@ export interface FetchTransport {
41
41
  * Calls a unary RPC through Envoy's grpc_json_transcoder by POSTing JSON to
42
42
  * `/{serviceName}/{methodName}` and returning the parsed JSON response.
43
43
  */
44
- callUnary(serviceName: string, methodName: string, request: unknown): Promise<JsonValue>;
44
+ callUnary(
45
+ serviceName: string,
46
+ methodName: string,
47
+ request: unknown,
48
+ headers?: Record<string, string>
49
+ ): Promise<JsonValue>;
45
50
  }
46
51
 
47
52
  /**
@@ -50,7 +55,7 @@ export interface FetchTransport {
50
55
  */
51
56
  export type ServiceClient<T extends DescService> = {
52
57
  [K in keyof T['method']]: T['method'][K] extends DescMethodUnary<infer I, infer O>
53
- ? (request: MessageInitShape<I>) => Promise<MessageShape<O>>
58
+ ? (request: MessageInitShape<I>, headers?: Record<string, string>) => Promise<MessageShape<O>>
54
59
  : never;
55
60
  };
56
61
 
@@ -85,8 +90,11 @@ export type RpcHandlerType = Awaited<ReturnType<typeof getRpcHandlers>>;
85
90
  * // => (args: { projectId: string }) => Promise<Project>
86
91
  * ```
87
92
  */
88
- export type ExtractUnaryRpc<T> = T extends (args: Record<string, unknown>) => Promise<infer R>
89
- ? (args: Record<string, unknown>) => Promise<R>
93
+ export type ExtractUnaryRpc<T> = T extends (
94
+ args: Record<string, unknown>,
95
+ headers?: Record<string, string>
96
+ ) => Promise<infer R>
97
+ ? (args: Record<string, unknown>, headers?: Record<string, string>) => Promise<R>
90
98
  : never;
91
99
 
92
100
  /**