@michelangelo-ai/rpc 0.10.0 → 0.11.0

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.
@@ -1,5 +1,13 @@
1
+ import { create, toBinary } from '@bufbuild/protobuf';
2
+ import { anyPack } from '@bufbuild/protobuf/wkt';
1
3
  import { expect, it, vi } from 'vitest';
2
4
 
5
+ import { TypedStructSchema } from '../gen/michelangelo/api/typed_struct_pb';
6
+ import {
7
+ PipelineManifest_Type,
8
+ PipelineSchema,
9
+ PipelineType,
10
+ } from '../gen/michelangelo/api/v2/pipeline_pb';
3
11
  import { request } from '../request';
4
12
 
5
13
  vi.mock('../handlers', () => ({
@@ -50,3 +58,96 @@ it('handles arrays containing objects with protobuf internals', async () => {
50
58
 
51
59
  expect(await request('GetPipelineRun', {} as never)).toEqual({ items: [{ x: 1 }, { x: 2 }] });
52
60
  });
61
+
62
+ it('unpacks a registered Any payload (Pipeline) into a plain object', async () => {
63
+ const pipeline = create(PipelineSchema, {
64
+ metadata: { name: 'my-pipeline' },
65
+ spec: { type: PipelineType.DATA_PREP, commit: { branch: 'main' } },
66
+ });
67
+ mockHandler({
68
+ $typeName: 'michelangelo.api.v2.Revision',
69
+ spec: {
70
+ $typeName: 'michelangelo.api.v2.RevisionSpec',
71
+ revisionId: 'abc',
72
+ content: {
73
+ $typeName: 'google.protobuf.Any',
74
+ typeUrl: 'type.googleapis.com/michelangelo.api.v2.Pipeline',
75
+ value: toBinary(PipelineSchema, pipeline),
76
+ },
77
+ },
78
+ });
79
+
80
+ const result = (await request('GetPipelineRun', {} as never)) as {
81
+ spec: {
82
+ content: { metadata: { name: string }; spec: { type: number; commit: { branch: string } } };
83
+ };
84
+ };
85
+
86
+ expect(result.spec.content.metadata.name).toBe('my-pipeline');
87
+ expect(result.spec.content.spec.type).toBe(PipelineType.DATA_PREP);
88
+ expect(result.spec.content.spec.commit.branch).toBe('main');
89
+ expect(result.spec.content).not.toHaveProperty('$typeName');
90
+ });
91
+
92
+ it('unpacks a registered Any payload whose own fields contain a nested TypedStruct Any', async () => {
93
+ // The Pipeline packed into Revision.spec.content (a registry-typed Any) has its own
94
+ // manifest.content field, which is a TypedStruct. Both must unpack in one pass: toPlainObject
95
+ // recurses into the decoded Pipeline and finds the inner Any too.
96
+ const manifestContent = anyPack(
97
+ TypedStructSchema,
98
+ create(TypedStructSchema, {
99
+ typeUrl: 'type.googleapis.com/michelangelo.pipeline.dataprep.Config',
100
+ value: { source: 'hive' },
101
+ })
102
+ );
103
+ const pipeline = create(PipelineSchema, {
104
+ metadata: { name: 'my-pipeline' },
105
+ spec: {
106
+ type: PipelineType.DATA_PREP,
107
+ commit: { branch: 'main' },
108
+ manifest: {
109
+ type: PipelineManifest_Type.PIPELINE_MANIFEST_TYPE_YAML,
110
+ content: manifestContent,
111
+ },
112
+ },
113
+ });
114
+ mockHandler({
115
+ $typeName: 'michelangelo.api.v2.Revision',
116
+ spec: {
117
+ $typeName: 'michelangelo.api.v2.RevisionSpec',
118
+ revisionId: 'abc',
119
+ content: {
120
+ $typeName: 'google.protobuf.Any',
121
+ typeUrl: 'type.googleapis.com/michelangelo.api.v2.Pipeline',
122
+ value: toBinary(PipelineSchema, pipeline),
123
+ },
124
+ },
125
+ });
126
+
127
+ const result = (await request('GetPipelineRun', {} as never)) as {
128
+ spec: {
129
+ content: {
130
+ spec: { manifest: { content: { typeUrl: string; value: { source: string } } } };
131
+ };
132
+ };
133
+ };
134
+
135
+ expect(result.spec.content.spec.manifest.content).toEqual({
136
+ typeUrl: 'type.googleapis.com/michelangelo.pipeline.dataprep.Config',
137
+ value: { source: 'hive' },
138
+ });
139
+ });
140
+
141
+ it('leaves an unregistered Any payload untouched', async () => {
142
+ const bytes = new Uint8Array([9, 9]);
143
+ mockHandler({
144
+ $typeName: 'google.protobuf.Any',
145
+ typeUrl: 'type.googleapis.com/unknown.Type',
146
+ value: bytes,
147
+ });
148
+
149
+ expect(await request('GetPipelineRun', {} as never)).toEqual({
150
+ typeUrl: 'type.googleapis.com/unknown.Type',
151
+ value: bytes,
152
+ });
153
+ });
package/handlers.ts CHANGED
@@ -36,6 +36,7 @@ async function createHandlers() {
36
36
  const services = await getServices();
37
37
 
38
38
  return {
39
+ ListCluster: unary(services.ClusterService.listCluster),
39
40
  ListDeployment: unary(services.DeploymentService.listDeployment),
40
41
  GetDeployment: unary(services.DeploymentService.getDeployment),
41
42
  CreateDeployment: (record: Deployment, headers?: Record<string, string>) => {
@@ -45,6 +46,14 @@ async function createHandlers() {
45
46
  }
46
47
  return services.DeploymentService.createDeployment({ deployment: record }, headers);
47
48
  },
49
+ UpdateDeployment: (record: Deployment, headers?: Record<string, string>) => {
50
+ const actorName = headers?.['x-user-name'];
51
+ if (actorName && record.spec) {
52
+ record.spec.owner = create(UserInfoSchema, { name: actorName });
53
+ }
54
+ return services.DeploymentService.updateDeployment({ deployment: record }, headers);
55
+ },
56
+ DeleteDeployment: deleteCrd(services.DeploymentService.deleteDeployment),
48
57
  ListInferenceServer: unary(services.InferenceServerService.listInferenceServer),
49
58
  GetInferenceServer: unary(services.InferenceServerService.getInferenceServer),
50
59
  CreateInferenceServer: (record: InferenceServer, headers?: Record<string, string>) =>
@@ -79,6 +88,8 @@ async function createHandlers() {
79
88
  ListModel: unary(services.ModelService.listModel),
80
89
  GetModel: unary(services.ModelService.getModel),
81
90
  ListModelFamily: unary(services.ModelFamilyService.listModelFamily),
91
+ ListRevision: unary(services.RevisionService.listRevision),
92
+ GetRevision: unary(services.RevisionService.getRevision),
82
93
  } as const;
83
94
  }
84
95
 
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.10.0",
5
+ "version": "0.11.0",
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": "0.10.0"
20
+ "@michelangelo-ai/core": "0.11.0"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
package/request.ts CHANGED
@@ -2,6 +2,7 @@ import { fromBinary, toJson } from '@bufbuild/protobuf';
2
2
 
3
3
  import { TypedStructSchema } from './gen/michelangelo/api/typed_struct_pb';
4
4
  import { getRpcHandlers } from './handlers';
5
+ import { typeRegistry } from './services';
5
6
 
6
7
  import type { Any } from '@bufbuild/protobuf/wkt';
7
8
  import type { OmitTypeName, RpcHandlerType } from './types';
@@ -47,8 +48,8 @@ function toPlainObject(value: unknown): unknown {
47
48
  if (value instanceof Uint8Array) return value; // preserve bytes fields (e.g. google.protobuf.Any.value)
48
49
  if (Array.isArray(value)) return value.map(toPlainObject);
49
50
 
50
- const typedStruct = unpackTypedStructAny(value);
51
- if (typedStruct) return typedStruct;
51
+ const unpacked = unpackAny(value);
52
+ if (unpacked !== undefined) return unpacked;
52
53
 
53
54
  const result: Record<string, unknown> = {};
54
55
  for (const [key, val] of Object.entries(value)) {
@@ -58,16 +59,28 @@ function toPlainObject(value: unknown): unknown {
58
59
  return result;
59
60
  }
60
61
 
61
- const TYPED_STRUCT_TYPE_URL = `type.googleapis.com/${TypedStructSchema.typeName}`;
62
+ const TYPE_URL_PREFIX = 'type.googleapis.com/';
63
+ const TYPED_STRUCT_TYPE_URL = `${TYPE_URL_PREFIX}${TypedStructSchema.typeName}`;
62
64
 
63
65
  // google.protobuf.Any fields survive fromJson as { typeUrl, value: Uint8Array } — the binary
64
- // payload is useless to consumers. When the payload is a michelangelo.api.TypedStruct
65
- // (e.g. PipelineManifest.content), expand it to { typeUrl, value } where typeUrl names the
66
- // inner config type and value is its plain JSON. Other Any payloads are left untouched.
67
- function unpackTypedStructAny(value: object): unknown {
66
+ // payload is useless to consumers.
67
+ //
68
+ // - A michelangelo.api.TypedStruct payload (e.g. PipelineManifest.content) expands to
69
+ // { typeUrl, value } where typeUrl names the inner config type and value is its plain JSON.
70
+ // - Any other payload whose type is in the RPC type registry (e.g. a Pipeline inside
71
+ // Revision.spec.content) is decoded and flattened into a plain object shaped exactly like
72
+ // that message would be if it arrived as a top-level response, so column paths such as
73
+ // `spec.content.spec.type` read the same values (numeric enums, Timestamp objects) as they
74
+ // would on the base resource.
75
+ // - Unregistered payloads are left untouched.
76
+ function unpackAny(value: object): unknown {
68
77
  if (!('$typeName' in value) || value.$typeName !== 'google.protobuf.Any') return undefined;
69
78
  // cast: the $typeName check above identifies this as a google.protobuf.Any message
70
79
  const any = value as Any;
71
- if (any.typeUrl !== TYPED_STRUCT_TYPE_URL) return undefined;
72
- return toJson(TypedStructSchema, fromBinary(TypedStructSchema, any.value));
80
+ if (any.typeUrl === TYPED_STRUCT_TYPE_URL) {
81
+ return toJson(TypedStructSchema, fromBinary(TypedStructSchema, any.value));
82
+ }
83
+ const schema = typeRegistry.getMessage(any.typeUrl.replace(TYPE_URL_PREFIX, ''));
84
+ if (!schema) return undefined;
85
+ return toPlainObject(fromBinary(schema, any.value));
73
86
  }
package/services.ts CHANGED
@@ -8,13 +8,16 @@ import {
8
8
 
9
9
  import { createFetchTransport } from './create-fetch-transport';
10
10
  import { TypedStructSchema } from './gen/michelangelo/api/typed_struct_pb';
11
+ import { ClusterService } from './gen/michelangelo/api/v2/cluster_svc_pb';
11
12
  import { DeploymentService } from './gen/michelangelo/api/v2/deployment_svc_pb';
12
13
  import { InferenceServerService } from './gen/michelangelo/api/v2/inference_server_svc_pb';
13
14
  import { ModelFamilyService } from './gen/michelangelo/api/v2/model_family_svc_pb';
14
15
  import { ModelService } from './gen/michelangelo/api/v2/model_svc_pb';
16
+ import { PipelineSchema } from './gen/michelangelo/api/v2/pipeline_pb';
15
17
  import { PipelineRunService } from './gen/michelangelo/api/v2/pipeline_run_svc_pb';
16
18
  import { PipelineService } from './gen/michelangelo/api/v2/pipeline_svc_pb';
17
19
  import { ProjectService } from './gen/michelangelo/api/v2/project_svc_pb';
20
+ import { RevisionService } from './gen/michelangelo/api/v2/revision_svc_pb';
18
21
  import { TriggerRunService } from './gen/michelangelo/api/v2/trigger_run_svc_pb';
19
22
  import { packAnyFields } from './pack-any-fields';
20
23
  import { getRuntimeConfig } from './runtime-config';
@@ -22,12 +25,14 @@ import { getRuntimeConfig } from './runtime-config';
22
25
  import type { DescService } from '@bufbuild/protobuf';
23
26
  import type { FetchTransport, ServiceClient, Services } from './types';
24
27
 
25
- // These wrapper schemas are registered so criteria that wrap a matchValue in a
26
- // google.protobuf.Any (e.g. ListOptionsExt filters, packed by packAnyFields) can be
27
- // JSON-encoded protobuf-es resolves an Any's typeUrl against this registry to serialize
28
- // it as a well-known type.
29
- const typeRegistry = createRegistry(
28
+ // Every message type that can appear inside a google.protobuf.Any on the wire must be
29
+ // registered here — protobuf-es resolves an Any's typeUrl against this registry both when
30
+ // JSON-encoding requests (ListOptionsExt criteria packed by packAnyFields) and when decoding
31
+ // responses (fromJson throws on an unregistered typeUrl). PipelineSchema covers
32
+ // Revision.spec.content for Pipeline revisions.
33
+ export const typeRegistry = createRegistry(
30
34
  TypedStructSchema,
35
+ PipelineSchema,
31
36
  StringValueSchema,
32
37
  BoolValueSchema,
33
38
  Int64ValueSchema,
@@ -80,6 +85,7 @@ async function createServices(): Promise<Services> {
80
85
  const transport = createFetchTransport({ baseUrl: apiBaseUrl });
81
86
 
82
87
  return {
88
+ ClusterService: createServiceClient(ClusterService, transport),
83
89
  DeploymentService: createServiceClient(DeploymentService, transport),
84
90
  InferenceServerService: createServiceClient(InferenceServerService, transport),
85
91
  ProjectService: createServiceClient(ProjectService, transport),
@@ -88,6 +94,7 @@ async function createServices(): Promise<Services> {
88
94
  TriggerRunService: createServiceClient(TriggerRunService, transport),
89
95
  ModelService: createServiceClient(ModelService, transport),
90
96
  ModelFamilyService: createServiceClient(ModelFamilyService, transport),
97
+ RevisionService: createServiceClient(RevisionService, transport),
91
98
  } as const;
92
99
  }
93
100
 
package/types.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  MessageInitShape,
7
7
  MessageShape,
8
8
  } from '@bufbuild/protobuf';
9
+ import type { ClusterService } from './gen/michelangelo/api/v2/cluster_svc_pb';
9
10
  import type { DeploymentService } from './gen/michelangelo/api/v2/deployment_svc_pb';
10
11
  import type { InferenceServerService } from './gen/michelangelo/api/v2/inference_server_svc_pb';
11
12
  import type { ModelFamilyService } from './gen/michelangelo/api/v2/model_family_svc_pb';
@@ -13,6 +14,7 @@ import type { ModelService } from './gen/michelangelo/api/v2/model_svc_pb';
13
14
  import type { PipelineRunService } from './gen/michelangelo/api/v2/pipeline_run_svc_pb';
14
15
  import type { PipelineService } from './gen/michelangelo/api/v2/pipeline_svc_pb';
15
16
  import type { ProjectService } from './gen/michelangelo/api/v2/project_svc_pb';
17
+ import type { RevisionService } from './gen/michelangelo/api/v2/revision_svc_pb';
16
18
  import type { TriggerRunService } from './gen/michelangelo/api/v2/trigger_run_svc_pb';
17
19
  import type { getRpcHandlers } from './handlers';
18
20
 
@@ -61,6 +63,7 @@ export type ServiceClient<T extends DescService> = {
61
63
  };
62
64
 
63
65
  export type Services = {
66
+ ClusterService: ServiceClient<typeof ClusterService>;
64
67
  DeploymentService: ServiceClient<typeof DeploymentService>;
65
68
  InferenceServerService: ServiceClient<typeof InferenceServerService>;
66
69
  ProjectService: ServiceClient<typeof ProjectService>;
@@ -69,6 +72,7 @@ export type Services = {
69
72
  TriggerRunService: ServiceClient<typeof TriggerRunService>;
70
73
  ModelService: ServiceClient<typeof ModelService>;
71
74
  ModelFamilyService: ServiceClient<typeof ModelFamilyService>;
75
+ RevisionService: ServiceClient<typeof RevisionService>;
72
76
  };
73
77
 
74
78
  /**