@highstate/backend-api 0.26.0 → 0.28.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highstate/backend-api",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "scripts": {
25
25
  "build": "highstate build",
26
+ "test": "bun run --bun vitest run",
26
27
  "typecheck": "tsgo --noEmit --skipLibCheck",
27
28
  "biome": "biome check --write --unsafe --error-on-warnings",
28
29
  "biome:check": "biome check --error-on-warnings"
@@ -40,19 +41,20 @@
40
41
  }
41
42
  },
42
43
  "dependencies": {
43
- "@bufbuild/protobuf": "^2.6.1",
44
- "@highstate/api": "0.25.0",
45
- "@highstate/backend": "0.25.0",
46
- "@highstate/cli": "0.25.0",
47
- "@highstate/contract": "0.25.0",
44
+ "@bufbuild/protobuf": "^2.14.0",
45
+ "@connectrpc/connect": "^2.1.2",
46
+ "@connectrpc/connect-node": "^2.1.2",
47
+ "@highstate/api": "0.27.0",
48
+ "@highstate/backend": "0.27.0",
49
+ "@highstate/cli": "0.27.0",
50
+ "@highstate/contract": "0.27.0",
48
51
  "abort-controller-x": "^0.4.3",
49
- "nice-grpc": "^2.1.12",
50
- "nice-grpc-common": "^2.0.2",
51
52
  "zod": "^4.0.5"
52
53
  },
53
54
  "devDependencies": {
54
- "@biomejs/biome": "2.2.0",
55
- "@typescript/native-preview": "^7.0.0-dev.20250920.1"
55
+ "@biomejs/biome": "2.5.0",
56
+ "@typescript/native-preview": "^7.0.0-dev.20250920.1",
57
+ "vitest": "^3.2.4"
56
58
  },
57
59
  "repository": {
58
60
  "url": "https://github.com/highstate-io/highstate"
@@ -0,0 +1,96 @@
1
+ import type { ServiceImpl } from "@connectrpc/connect"
2
+ import type { InstanceStateService } from "@highstate/api/v1"
3
+ import type { Services } from "@highstate/backend"
4
+ import { instanceCustomStatusInputSchema } from "@highstate/backend/shared"
5
+ import { z } from "@highstate/contract"
6
+ import {
7
+ authenticateProject,
8
+ parseArgument,
9
+ toInstanceCustomStatus,
10
+ toInstanceState,
11
+ } from "../shared"
12
+
13
+ export function createInstanceStateService(
14
+ services: Services,
15
+ ): ServiceImpl<typeof InstanceStateService> {
16
+ return {
17
+ async listInstanceStates(request, context) {
18
+ const requestContext = await authenticateProject(services, request, context)
19
+ const page = await services.instanceStateService.getInstanceStates(
20
+ requestContext,
21
+ {
22
+ includeEvaluationState: request.includeEvaluationState,
23
+ includeLastOperationState: request.includeLastOperationState,
24
+ includeParentInstanceId: request.includeParentInstanceId,
25
+ includeExtra: request.includeExtra,
26
+ loadCustomStatuses: request.includeCustomStatuses,
27
+ },
28
+ { pageSize: request.pageSize, pageToken: request.pageToken },
29
+ )
30
+
31
+ return { states: page.items.map(toInstanceState), nextPageToken: page.nextPageToken }
32
+ },
33
+
34
+ async getInstanceState(request, context) {
35
+ const requestContext = await authenticateProject(services, request, context)
36
+ const stateId = parseArgument(request, "stateId", z.cuid2())
37
+ const state = await services.instanceStateService.getInstanceStateOrThrow(
38
+ requestContext,
39
+ stateId,
40
+ {
41
+ includeEvaluationState: request.includeEvaluationState,
42
+ includeLastOperationState: request.includeLastOperationState,
43
+ includeParentInstanceId: request.includeParentInstanceId,
44
+ includeExtra: request.includeExtra,
45
+ loadCustomStatuses: request.includeCustomStatuses,
46
+ },
47
+ )
48
+
49
+ return { state: toInstanceState(state) }
50
+ },
51
+
52
+ async updateCustomStatus(request, context) {
53
+ const requestContext = await authenticateProject(services, request, context)
54
+ const stateId = parseArgument(request, "stateId", z.cuid2())
55
+ const customStatus = parseArgument(request, "status", instanceCustomStatusInputSchema)
56
+
57
+ await services.instanceStateService.updateCustomStatus(
58
+ requestContext,
59
+ stateId,
60
+ requestContext.subject.serviceAccountId,
61
+ customStatus,
62
+ )
63
+
64
+ const state = await services.instanceStateService.getInstanceStateOrThrow(
65
+ requestContext,
66
+ stateId,
67
+ { loadCustomStatuses: true },
68
+ )
69
+ const status = state.customStatuses?.find(
70
+ value =>
71
+ value.name === customStatus.name &&
72
+ value.serviceAccountId === requestContext.subject.serviceAccountId,
73
+ )
74
+ if (!status) {
75
+ throw new Error("Updated custom status was not returned by the backend")
76
+ }
77
+
78
+ return { status: toInstanceCustomStatus(status) }
79
+ },
80
+
81
+ async removeCustomStatus(request, context) {
82
+ const requestContext = await authenticateProject(services, request, context)
83
+ const stateId = parseArgument(request, "stateId", z.cuid2())
84
+ const statusName = parseArgument(request, "statusName", z.string().min(1))
85
+
86
+ await services.instanceStateService.removeCustomStatus(
87
+ requestContext,
88
+ stateId,
89
+ requestContext.subject.serviceAccountId,
90
+ statusName,
91
+ )
92
+
93
+ return {}
94
+ },
95
+ }
96
+ }
@@ -0,0 +1,63 @@
1
+ import type { ServiceImpl } from "@connectrpc/connect"
2
+ import type { Services } from "@highstate/backend"
3
+ import { ComponentKind, type LibraryService } from "@highstate/api/v1"
4
+ import { ComponentNotFoundError } from "@highstate/backend/shared"
5
+ import { z } from "@highstate/contract"
6
+ import { authenticateProject, parseArgument, toComponent, toLibrary } from "../shared"
7
+
8
+ export function createLibraryService(services: Services): ServiceImpl<typeof LibraryService> {
9
+ return {
10
+ async getLibrary(request, context) {
11
+ const requestContext = await authenticateProject(services, request, context)
12
+ const library = await services.libraryService.getLibraryModel(requestContext, context.signal)
13
+
14
+ return {
15
+ library: toLibrary({
16
+ components: { ...library.components },
17
+ entities: { ...library.entities },
18
+ }),
19
+ }
20
+ },
21
+
22
+ async listComponents(request, context) {
23
+ const requestContext = await authenticateProject(services, request, context)
24
+ const page = await services.libraryService.getComponents(
25
+ requestContext,
26
+ { pageSize: request.pageSize, pageToken: request.pageToken },
27
+ context.signal,
28
+ )
29
+
30
+ return {
31
+ components: page.items.map(component => ({
32
+ type: component.type,
33
+ kind: component.kind === "unit" ? ComponentKind.UNIT : ComponentKind.COMPOSITE,
34
+ meta: {
35
+ title: component.meta.title,
36
+ description: component.meta.description,
37
+ color: component.meta.color,
38
+ icon: component.meta.icon,
39
+ iconColor: component.meta.iconColor,
40
+ secondaryIcon: component.meta.secondaryIcon,
41
+ secondaryIconColor: component.meta.secondaryIconColor,
42
+ category: component.meta.category,
43
+ defaultNamePrefix: component.meta.defaultNamePrefix,
44
+ },
45
+ })),
46
+ nextPageToken: page.nextPageToken,
47
+ }
48
+ },
49
+
50
+ async getComponent(request, context) {
51
+ const requestContext = await authenticateProject(services, request, context)
52
+ const type = parseArgument(request, "type", z.string().min(1))
53
+ const library = await services.libraryService.getLibraryModel(requestContext, context.signal)
54
+ const component = library.components[type]
55
+
56
+ if (!component) {
57
+ throw new ComponentNotFoundError(requestContext.projectId, type)
58
+ }
59
+
60
+ return { component: toComponent(component) }
61
+ },
62
+ }
63
+ }
@@ -0,0 +1,108 @@
1
+ import type { ServiceImpl } from "@connectrpc/connect"
2
+ import type { OperationService } from "@highstate/api/v1"
3
+ import type { Services } from "@highstate/backend"
4
+ import { operationLaunchInputSchema, operationPlanInputSchema } from "@highstate/backend/shared"
5
+ import { instanceIdSchema, z } from "@highstate/contract"
6
+ import {
7
+ authenticateProject,
8
+ fromOperationPhase,
9
+ fromOperationType,
10
+ parseValue,
11
+ toOperation,
12
+ toOperationLog,
13
+ toOperationPhase,
14
+ } from "../shared"
15
+
16
+ export function createOperationService(services: Services): ServiceImpl<typeof OperationService> {
17
+ return {
18
+ async planOperation(request, context) {
19
+ const requestContext = await authenticateProject(services, request, context)
20
+ const input = parseValue(
21
+ { ...request, projectId: requestContext.projectId, type: fromOperationType(request.type) },
22
+ "request",
23
+ operationPlanInputSchema,
24
+ )
25
+ const phases = await services.operationManager.plan(input)
26
+
27
+ return { phases: phases.map(toOperationPhase) }
28
+ },
29
+
30
+ async launchOperation(request, context) {
31
+ const requestContext = await authenticateProject(services, request, context)
32
+ const input = parseValue(
33
+ {
34
+ ...request,
35
+ projectId: requestContext.projectId,
36
+ type: fromOperationType(request.type),
37
+ plan: request.plan.length > 0 ? request.plan.map(fromOperationPhase) : undefined,
38
+ },
39
+ "request",
40
+ operationLaunchInputSchema,
41
+ )
42
+ const operation = await services.operationManager.launch(input)
43
+
44
+ return { operation: toOperation(operation) }
45
+ },
46
+
47
+ async getOperation(request, context) {
48
+ const requestContext = await authenticateProject(services, request, context)
49
+ const operationId = parseValue(request.operationId, "operationId", z.string().min(1))
50
+ const operation = await services.operationService.getOperationOrThrow(
51
+ requestContext,
52
+ operationId,
53
+ )
54
+
55
+ return { operation: toOperation(operation) }
56
+ },
57
+
58
+ async listOperations(request, context) {
59
+ const requestContext = await authenticateProject(services, request, context)
60
+ const page = await services.operationService.getOperations(requestContext, {
61
+ pageSize: request.pageSize,
62
+ pageToken: request.pageToken,
63
+ })
64
+
65
+ return { operations: page.items.map(toOperation), nextPageToken: page.nextPageToken }
66
+ },
67
+
68
+ async listOperationLogs(request, context) {
69
+ const requestContext = await authenticateProject(services, request, context)
70
+ const operationId = parseValue(request.operationId, "operationId", z.string().min(1))
71
+ const stateId = parseValue(request.stateId, "stateId", z.cuid2().optional())
72
+ await services.operationService.getOperationOrThrow(requestContext, operationId)
73
+
74
+ const page = await services.operationService.getOperationLogs(
75
+ requestContext,
76
+ operationId,
77
+ stateId,
78
+ { pageSize: request.pageSize, pageToken: request.pageToken },
79
+ )
80
+
81
+ return {
82
+ logs: page.items.map(log => toOperationLog(operationId, log)),
83
+ nextPageToken: page.nextPageToken,
84
+ }
85
+ },
86
+
87
+ async cancelOperation(request, context) {
88
+ const requestContext = await authenticateProject(services, request, context)
89
+ const operationId = parseValue(request.operationId, "operationId", z.string().min(1))
90
+ await services.operationService.getOperationOrThrow(requestContext, operationId)
91
+
92
+ services.operationManager.cancel(operationId)
93
+
94
+ return {}
95
+ },
96
+
97
+ async cancelInstanceOperation(request, context) {
98
+ const requestContext = await authenticateProject(services, request, context)
99
+ const operationId = parseValue(request.operationId, "operationId", z.string().min(1))
100
+ const instanceId = parseValue(request.instanceId, "instanceId", instanceIdSchema)
101
+ await services.operationService.getOperationOrThrow(requestContext, operationId)
102
+
103
+ services.operationManager.cancelInstance(operationId, instanceId)
104
+
105
+ return {}
106
+ },
107
+ }
108
+ }
@@ -1,22 +1,23 @@
1
- import type { PanelServiceImplementation } from "@highstate/api/panel.v1"
1
+ import type { ServiceImpl } from "@connectrpc/connect"
2
+ import type { PanelService } from "@highstate/api/v1"
2
3
  import type { Services } from "@highstate/backend"
3
4
  import { panelInputSchema } from "@highstate/backend/shared"
4
5
  import { z } from "@highstate/contract"
5
- import { authenticate, parseArgument } from "../shared"
6
+ import { authenticateProject, parseArgument } from "../shared"
6
7
 
7
- export function createPanelService(services: Services): PanelServiceImplementation {
8
+ export function createPanelService(services: Services): ServiceImpl<typeof PanelService> {
8
9
  return {
9
10
  async setUnitPanels(request, context) {
10
- const [projectId, apiKey] = await authenticate(services, context)
11
+ const requestContext = await authenticateProject(services, request, context)
11
12
  const workerVersionId = parseArgument(request, "workerVersionId", z.cuid2())
12
13
  const workerInstanceId = parseArgument(request, "workerInstanceId", z.cuid2())
13
14
  const stateId = parseArgument(request, "stateId", z.cuid2())
14
15
  const panels = parseArgument(request, "panels", z.array(panelInputSchema))
15
16
 
16
17
  const panelIds = await services.panelService.setUnitPanels(
17
- projectId,
18
+ requestContext,
18
19
  stateId,
19
- apiKey.id,
20
+ requestContext.subject.apiKeyId,
20
21
  workerVersionId,
21
22
  panels,
22
23
  workerInstanceId,
@@ -0,0 +1,131 @@
1
+ import type { ServiceImpl } from "@connectrpc/connect"
2
+ import type { Services } from "@highstate/backend"
3
+ import {
4
+ HubSchema,
5
+ InstanceSchema,
6
+ type ProjectModelService,
7
+ type UpdateHubRequest,
8
+ type UpdateInstanceRequest,
9
+ } from "@highstate/api/v1"
10
+ import { projectModelInstanceSchema } from "@highstate/backend/shared"
11
+ import { hubModelSchema, instanceIdSchema, z } from "@highstate/contract"
12
+ import {
13
+ authenticateProject,
14
+ fromHub,
15
+ fromInstance,
16
+ parseArgument,
17
+ parseValue,
18
+ toHub,
19
+ toHubPatch,
20
+ toInstance,
21
+ toInstancePatch,
22
+ toProjectModel,
23
+ validateUpdateMask,
24
+ } from "../shared"
25
+
26
+ type UpdateInstance = NonNullable<UpdateInstanceRequest["instance"]>
27
+ type UpdateHub = NonNullable<UpdateHubRequest["hub"]>
28
+
29
+ const instanceMutablePaths = new Set([
30
+ "arguments",
31
+ "inputs",
32
+ "hub_inputs",
33
+ "injection_inputs",
34
+ "position",
35
+ "position.x",
36
+ "position.y",
37
+ ])
38
+ const hubMutablePaths = new Set([
39
+ "position",
40
+ "position.x",
41
+ "position.y",
42
+ "inputs",
43
+ "injection_inputs",
44
+ ])
45
+
46
+ export function createProjectModelService(
47
+ services: Services,
48
+ ): ServiceImpl<typeof ProjectModelService> {
49
+ return {
50
+ async getProjectModel(request, context) {
51
+ const requestContext = await authenticateProject(services, request, context)
52
+ const [model] = await services.projectModelService.getProjectModel(requestContext, {
53
+ includeVirtualInstances: request.includeVirtualInstances,
54
+ includeGhostInstances: request.includeGhostInstances,
55
+ })
56
+
57
+ return { model: toProjectModel(model) }
58
+ },
59
+
60
+ async createNodes(request, context) {
61
+ const requestContext = await authenticateProject(services, request, context)
62
+ const instances = parseValue(
63
+ request.instances.map(fromInstance),
64
+ "instances",
65
+ projectModelInstanceSchema.array(),
66
+ )
67
+ const hubs = parseValue(request.hubs.map(fromHub), "hubs", hubModelSchema.array())
68
+
69
+ await services.projectService.createNodes(requestContext, instances, hubs)
70
+
71
+ return {}
72
+ },
73
+
74
+ async updateInstance(request, context) {
75
+ const requestContext = await authenticateProject(services, request, context)
76
+ const requestInstance = parseArgument(request, "instance", z.custom<UpdateInstance>())
77
+ const instanceId = parseArgument(requestInstance, "id", instanceIdSchema)
78
+ const paths = validateUpdateMask(request.updateMask, InstanceSchema, instanceMutablePaths)
79
+ const patch = toInstancePatch(requestInstance, paths)
80
+ const instance = await services.projectService.updateInstance(
81
+ requestContext,
82
+ instanceId,
83
+ patch,
84
+ )
85
+
86
+ return { instance: toInstance(instance) }
87
+ },
88
+
89
+ async renameInstance(request, context) {
90
+ const requestContext = await authenticateProject(services, request, context)
91
+ const instanceId = parseArgument(request, "instanceId", instanceIdSchema)
92
+ const newName = parseArgument(request, "newName", z.string().min(1))
93
+ const instance = await services.projectService.renameInstance(
94
+ requestContext,
95
+ instanceId,
96
+ newName,
97
+ )
98
+
99
+ return { instance: toInstance(instance) }
100
+ },
101
+
102
+ async deleteInstance(request, context) {
103
+ const requestContext = await authenticateProject(services, request, context)
104
+ const instanceId = parseArgument(request, "instanceId", instanceIdSchema)
105
+
106
+ await services.projectService.deleteInstance(requestContext, instanceId)
107
+
108
+ return {}
109
+ },
110
+
111
+ async updateHub(request, context) {
112
+ const requestContext = await authenticateProject(services, request, context)
113
+ const requestHub = parseArgument(request, "hub", z.custom<UpdateHub>())
114
+ const hubId = parseArgument(requestHub, "id", z.cuid2())
115
+ const paths = validateUpdateMask(request.updateMask, HubSchema, hubMutablePaths)
116
+ const patch = toHubPatch(requestHub, paths)
117
+ const hub = await services.projectService.updateHub(requestContext, hubId, patch)
118
+
119
+ return { hub: toHub(hub) }
120
+ },
121
+
122
+ async deleteHub(request, context) {
123
+ const requestContext = await authenticateProject(services, request, context)
124
+ const hubId = parseArgument(request, "hubId", z.cuid2())
125
+
126
+ await services.projectService.deleteHub(requestContext, hubId)
127
+
128
+ return {}
129
+ },
130
+ }
131
+ }
@@ -0,0 +1,46 @@
1
+ import type { ServiceImpl } from "@connectrpc/connect"
2
+ import type { ProjectService } from "@highstate/api/v1"
3
+ import type { Services } from "@highstate/backend"
4
+ import { authenticateBackend, authenticateProject, toProject } from "../shared"
5
+
6
+ export function createProjectService(services: Services): ServiceImpl<typeof ProjectService> {
7
+ return {
8
+ async getProject(request, context) {
9
+ const requestContext = await authenticateProject(services, request, context)
10
+ const [project, unlockState] = await Promise.all([
11
+ services.projectService.getProjectOrThrowCore(requestContext.projectId),
12
+ services.projectUnlockService.getProjectUnlockStateCore(requestContext.projectId),
13
+ ])
14
+
15
+ return {
16
+ project: toProject(project),
17
+ isLocked: unlockState.type === "locked",
18
+ }
19
+ },
20
+
21
+ async listProjects(request, context) {
22
+ const requestContext = await authenticateBackend(services, context)
23
+ const result = await services.projectService.getProjects(requestContext, {
24
+ pageSize: request.pageSize,
25
+ pageToken: request.pageToken,
26
+ })
27
+ const projects = await Promise.all(
28
+ result.items.map(async project => {
29
+ const unlockState = await services.projectUnlockService.getProjectUnlockStateCore(
30
+ project.id,
31
+ )
32
+
33
+ return {
34
+ project: toProject(project),
35
+ isLocked: unlockState.type === "locked",
36
+ }
37
+ }),
38
+ )
39
+
40
+ return {
41
+ projects,
42
+ nextPageToken: result.nextPageToken,
43
+ }
44
+ },
45
+ }
46
+ }
@@ -1,11 +1,12 @@
1
- import type { SecretServiceImplementation } from "@highstate/api/secret.v1"
1
+ import type { ServiceImpl } from "@connectrpc/connect"
2
+ import type { SecretService } from "@highstate/api/v1"
2
3
  import type { Services } from "@highstate/backend"
3
- import { authenticate } from "../shared"
4
+ import { authenticateProject } from "../shared"
4
5
 
5
- export function createSecretService(services: Services): SecretServiceImplementation {
6
+ export function createSecretService(services: Services): ServiceImpl<typeof SecretService> {
6
7
  return {
7
- async getSecretContent(_request, context) {
8
- const [_projectId] = await authenticate(services, context)
8
+ async getSecretContent(request, context) {
9
+ await authenticateProject(services, request, context)
9
10
 
10
11
  // TODO: validate secret access
11
12
 
@@ -1,31 +1,27 @@
1
- import type { WorkerServiceImplementation } from "@highstate/api/worker.v1"
1
+ import type { ServiceImpl } from "@connectrpc/connect"
2
2
  import type { Services } from "@highstate/backend"
3
+ import { create } from "@bufbuild/protobuf"
4
+ import { ConnectResponseSchema, type WorkerService } from "@highstate/api/worker.v1"
5
+ import { WorkerOwnershipError } from "@highstate/backend/shared"
3
6
  import { commonObjectMetaSchema, serviceAccountMetaSchema, z } from "@highstate/contract"
4
- import { authenticate, parseArgument } from "../shared"
7
+ import { authenticateProject, parseArgument, toJsonObject } from "../shared"
5
8
 
6
- const workerMetaUpdatePayloadSchema = z.union([
7
- commonObjectMetaSchema,
8
- z.object({
9
- workerMeta: commonObjectMetaSchema,
10
- serviceAccountMeta: serviceAccountMetaSchema,
11
- }),
12
- ])
13
-
14
- export function createWorkerService(services: Services): WorkerServiceImplementation {
9
+ export function createWorkerService(services: Services): ServiceImpl<typeof WorkerService> {
15
10
  return {
16
11
  async *connect(request, context) {
17
- const [projectId, apiKey] = await authenticate(services, context)
12
+ const requestContext = await authenticateProject(services, request, context)
13
+ const { projectId } = requestContext
18
14
 
19
15
  const workerVersionId = parseArgument(request, "workerVersionId", z.cuid2())
20
16
  const workerInstanceId = parseArgument(request, "workerInstanceId", z.cuid2())
21
17
  const dataEndpoint = parseArgument(request, "dataEndpoint", z.string().min(1))
22
18
  const database = await services.database.forProject(projectId)
23
19
  const workerVersion = await database.workerVersion.findFirst({
24
- where: { id: workerVersionId, apiKeyId: apiKey.id },
20
+ where: { id: workerVersionId, apiKeyId: requestContext.subject.apiKeyId },
25
21
  select: { id: true },
26
22
  })
27
23
  if (!workerVersion) {
28
- throw new Error(`Worker version "${workerVersionId}" is not owned by the API key`)
24
+ throw new WorkerOwnershipError(projectId, workerVersionId)
29
25
  }
30
26
 
31
27
  services.workerManager.assertWorkerInstance(projectId, workerVersionId, workerInstanceId)
@@ -43,15 +39,15 @@ export function createWorkerService(services: Services): WorkerServiceImplementa
43
39
  select: { stateId: true, params: true },
44
40
  })
45
41
  for (const registration of existingRegistrations) {
46
- yield {
42
+ yield create(ConnectResponseSchema, {
47
43
  event: {
48
- $case: "unitRegistration",
44
+ case: "unitRegistration",
49
45
  value: {
50
46
  stateId: registration.stateId,
51
- params: registration.params,
47
+ params: toJsonObject(registration.params),
52
48
  },
53
49
  },
54
- }
50
+ })
55
51
  }
56
52
  const registrationStream = await services.pubsubManager.subscribe([
57
53
  "worker-unit-registration",
@@ -60,19 +56,19 @@ export function createWorkerService(services: Services): WorkerServiceImplementa
60
56
  ])
61
57
  for await (const event of registrationStream) {
62
58
  if (event.type === "registered") {
63
- yield {
59
+ yield create(ConnectResponseSchema, {
64
60
  event: {
65
- $case: "unitRegistration",
66
- value: { stateId: event.stateId, params: event.params },
61
+ case: "unitRegistration",
62
+ value: { stateId: event.stateId, params: toJsonObject(event.params) },
67
63
  },
68
- }
64
+ })
69
65
  } else if (event.type === "deregistered") {
70
- yield {
66
+ yield create(ConnectResponseSchema, {
71
67
  event: {
72
- $case: "unitDeregistration",
68
+ case: "unitDeregistration",
73
69
  value: { stateId: event.stateId },
74
70
  },
75
- }
71
+ })
76
72
  }
77
73
  }
78
74
  } finally {
@@ -81,17 +77,18 @@ export function createWorkerService(services: Services): WorkerServiceImplementa
81
77
  },
82
78
 
83
79
  async updateWorkerVersionMeta(request, context) {
84
- const [projectId] = await authenticate(services, context)
80
+ const requestContext = await authenticateProject(services, request, context)
85
81
 
86
82
  const workerVersionId = parseArgument(request, "workerVersionId", z.string())
87
- const payload = parseArgument(request, "meta", workerMetaUpdatePayloadSchema)
88
-
89
- const workerMeta = "workerMeta" in payload ? payload.workerMeta : payload
90
- const serviceAccountMeta =
91
- "serviceAccountMeta" in payload ? payload.serviceAccountMeta : undefined
83
+ const workerMeta = parseArgument(request, "workerMeta", commonObjectMetaSchema)
84
+ const serviceAccountMeta = parseArgument(
85
+ request,
86
+ "serviceAccountMeta",
87
+ serviceAccountMetaSchema.optional(),
88
+ )
92
89
 
93
90
  await services.workerService.updateWorkerVersionMeta(
94
- projectId,
91
+ requestContext,
95
92
  workerVersionId,
96
93
  workerMeta,
97
94
  serviceAccountMeta,