@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.
- package/__tests__/request.test.ts +101 -0
- package/handlers.ts +11 -0
- package/package.json +2 -2
- package/request.ts +22 -9
- package/services.ts +12 -5
- package/types.ts +4 -0
|
@@ -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.
|
|
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.
|
|
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
|
|
51
|
-
if (
|
|
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
|
|
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.
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
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
|
|
72
|
-
|
|
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
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// JSON-
|
|
28
|
-
//
|
|
29
|
-
|
|
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
|
/**
|