@highstate/backend-api 0.21.1 → 0.26.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,5 @@
1
1
  {
2
2
  "sourceHashes": {
3
- "./dist/index.js": 3068331074
3
+ "./dist/index.js": 575062251
4
4
  }
5
5
  }
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // src/index.ts
3
3
  import { rm } from "fs/promises";
4
4
  import { InstanceServiceDefinition } from "@highstate/api/instance.v1";
5
+ import { PanelServiceDefinition } from "@highstate/api/panel.v1";
5
6
  import { SecretServiceDefinition } from "@highstate/api/secret.v1";
6
7
  import { WorkerServiceDefinition } from "@highstate/api/worker.v1";
7
8
  import { createServer } from "nice-grpc";
@@ -73,6 +74,23 @@ function createInstanceService(services) {
73
74
  };
74
75
  }
75
76
 
77
+ // src/handlers/panel.ts
78
+ import { panelInputSchema } from "@highstate/backend/shared";
79
+ import { z as z2 } from "@highstate/contract";
80
+ function createPanelService(services) {
81
+ return {
82
+ async setUnitPanels(request, context) {
83
+ const [projectId, apiKey] = await authenticate(services, context);
84
+ const workerVersionId = parseArgument(request, "workerVersionId", z2.cuid2());
85
+ const workerInstanceId = parseArgument(request, "workerInstanceId", z2.cuid2());
86
+ const stateId = parseArgument(request, "stateId", z2.cuid2());
87
+ const panels = parseArgument(request, "panels", z2.array(panelInputSchema));
88
+ const panelIds = await services.panelService.setUnitPanels(projectId, stateId, apiKey.id, workerVersionId, panels, workerInstanceId);
89
+ return { panelIds };
90
+ }
91
+ };
92
+ }
93
+
76
94
  // src/handlers/secret.ts
77
95
  function createSecretService(services) {
78
96
  return {
@@ -84,10 +102,10 @@ function createSecretService(services) {
84
102
  }
85
103
 
86
104
  // src/handlers/worker.ts
87
- import { commonObjectMetaSchema, serviceAccountMetaSchema, z as z2 } from "@highstate/contract";
88
- var workerMetaUpdatePayloadSchema = z2.union([
105
+ import { commonObjectMetaSchema, serviceAccountMetaSchema, z as z3 } from "@highstate/contract";
106
+ var workerMetaUpdatePayloadSchema = z3.union([
89
107
  commonObjectMetaSchema,
90
- z2.object({
108
+ z3.object({
91
109
  workerMeta: commonObjectMetaSchema,
92
110
  serviceAccountMeta: serviceAccountMetaSchema
93
111
  })
@@ -95,56 +113,66 @@ var workerMetaUpdatePayloadSchema = z2.union([
95
113
  function createWorkerService(services) {
96
114
  return {
97
115
  async* connect(request, context) {
98
- const [projectId] = await authenticate(services, context);
99
- const workerVersionId = parseArgument(request, "workerVersionId", z2.cuid2());
100
- services.workerManager.setWorkerRunning(projectId, workerVersionId);
116
+ const [projectId, apiKey] = await authenticate(services, context);
117
+ const workerVersionId = parseArgument(request, "workerVersionId", z3.cuid2());
118
+ const workerInstanceId = parseArgument(request, "workerInstanceId", z3.cuid2());
119
+ const dataEndpoint = parseArgument(request, "dataEndpoint", z3.string().min(1));
101
120
  const database = await services.database.forProject(projectId);
102
- const existingRegistrations = await database.workerUnitRegistration.findMany({
103
- where: { workerVersionId },
104
- select: { stateId: true, params: true }
121
+ const workerVersion = await database.workerVersion.findFirst({
122
+ where: { id: workerVersionId, apiKeyId: apiKey.id },
123
+ select: { id: true }
105
124
  });
106
- for (const registration of existingRegistrations) {
107
- yield {
108
- event: {
109
- $case: "unitRegistration",
110
- value: {
111
- stateId: registration.stateId,
112
- params: registration.params
113
- }
114
- }
115
- };
125
+ if (!workerVersion) {
126
+ throw new Error(`Worker version "${workerVersionId}" is not owned by the API key`);
116
127
  }
117
- const registrationStream = await services.pubsubManager.subscribe([
118
- "worker-unit-registration",
119
- projectId,
120
- workerVersionId
121
- ]);
122
- for await (const event of registrationStream) {
123
- if (event.type === "registered") {
128
+ services.workerManager.assertWorkerInstance(projectId, workerVersionId, workerInstanceId);
129
+ services.panelEndpointManager.connect(projectId, workerVersionId, workerInstanceId, dataEndpoint);
130
+ try {
131
+ await services.workerManager.setWorkerRunning(projectId, workerVersionId, workerInstanceId);
132
+ const existingRegistrations = await database.workerUnitRegistration.findMany({
133
+ where: { workerVersionId },
134
+ select: { stateId: true, params: true }
135
+ });
136
+ for (const registration of existingRegistrations) {
124
137
  yield {
125
138
  event: {
126
139
  $case: "unitRegistration",
127
140
  value: {
128
- instanceId: event.instanceId,
129
- params: event.params
141
+ stateId: registration.stateId,
142
+ params: registration.params
130
143
  }
131
144
  }
132
145
  };
133
- } else if (event.type === "deregistered") {
134
- yield {
135
- event: {
136
- $case: "unitDeregistration",
137
- value: {
138
- instanceId: event.instanceId
146
+ }
147
+ const registrationStream = await services.pubsubManager.subscribe([
148
+ "worker-unit-registration",
149
+ projectId,
150
+ workerVersionId
151
+ ]);
152
+ for await (const event of registrationStream) {
153
+ if (event.type === "registered") {
154
+ yield {
155
+ event: {
156
+ $case: "unitRegistration",
157
+ value: { stateId: event.stateId, params: event.params }
139
158
  }
140
- }
141
- };
159
+ };
160
+ } else if (event.type === "deregistered") {
161
+ yield {
162
+ event: {
163
+ $case: "unitDeregistration",
164
+ value: { stateId: event.stateId }
165
+ }
166
+ };
167
+ }
142
168
  }
169
+ } finally {
170
+ services.panelEndpointManager.disconnect(workerInstanceId);
143
171
  }
144
172
  },
145
173
  async updateWorkerVersionMeta(request, context) {
146
174
  const [projectId] = await authenticate(services, context);
147
- const workerVersionId = parseArgument(request, "workerVersionId", z2.string());
175
+ const workerVersionId = parseArgument(request, "workerVersionId", z3.string());
148
176
  const payload = parseArgument(request, "meta", workerMetaUpdatePayloadSchema);
149
177
  const workerMeta = "workerMeta" in payload ? payload.workerMeta : payload;
150
178
  const serviceAccountMeta = "serviceAccountMeta" in payload ? payload.serviceAccountMeta : undefined;
@@ -159,6 +187,7 @@ async function startBackedApi(services) {
159
187
  const server = createServer();
160
188
  server.use(createErrorHandlingMiddleware(services));
161
189
  server.add(InstanceServiceDefinition, createInstanceService(services));
190
+ server.add(PanelServiceDefinition, createPanelService(services));
162
191
  server.add(SecretServiceDefinition, createSecretService(services));
163
192
  server.add(WorkerServiceDefinition, createWorkerService(services));
164
193
  const uid = process.geteuid?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highstate/backend-api",
3
- "version": "0.21.1",
3
+ "version": "0.26.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -41,10 +41,10 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@bufbuild/protobuf": "^2.6.1",
44
- "@highstate/api": "0.20.0",
45
- "@highstate/backend": "0.20.0",
46
- "@highstate/cli": "0.20.0",
47
- "@highstate/contract": "0.20.0",
44
+ "@highstate/api": "0.25.0",
45
+ "@highstate/backend": "0.25.0",
46
+ "@highstate/cli": "0.25.0",
47
+ "@highstate/contract": "0.25.0",
48
48
  "abort-controller-x": "^0.4.3",
49
49
  "nice-grpc": "^2.1.12",
50
50
  "nice-grpc-common": "^2.0.2",
@@ -0,0 +1,28 @@
1
+ import type { PanelServiceImplementation } from "@highstate/api/panel.v1"
2
+ import type { Services } from "@highstate/backend"
3
+ import { panelInputSchema } from "@highstate/backend/shared"
4
+ import { z } from "@highstate/contract"
5
+ import { authenticate, parseArgument } from "../shared"
6
+
7
+ export function createPanelService(services: Services): PanelServiceImplementation {
8
+ return {
9
+ async setUnitPanels(request, context) {
10
+ const [projectId, apiKey] = await authenticate(services, context)
11
+ const workerVersionId = parseArgument(request, "workerVersionId", z.cuid2())
12
+ const workerInstanceId = parseArgument(request, "workerInstanceId", z.cuid2())
13
+ const stateId = parseArgument(request, "stateId", z.cuid2())
14
+ const panels = parseArgument(request, "panels", z.array(panelInputSchema))
15
+
16
+ const panelIds = await services.panelService.setUnitPanels(
17
+ projectId,
18
+ stateId,
19
+ apiKey.id,
20
+ workerVersionId,
21
+ panels,
22
+ workerInstanceId,
23
+ )
24
+
25
+ return { panelIds }
26
+ },
27
+ }
28
+ }
@@ -14,62 +14,69 @@ const workerMetaUpdatePayloadSchema = z.union([
14
14
  export function createWorkerService(services: Services): WorkerServiceImplementation {
15
15
  return {
16
16
  async *connect(request, context) {
17
- const [projectId] = await authenticate(services, context)
17
+ const [projectId, apiKey] = await authenticate(services, context)
18
18
 
19
19
  const workerVersionId = parseArgument(request, "workerVersionId", z.cuid2())
20
-
21
- // set worker as running
22
- services.workerManager.setWorkerRunning(projectId, workerVersionId)
23
-
24
- // get existing registrations for this worker version
20
+ const workerInstanceId = parseArgument(request, "workerInstanceId", z.cuid2())
21
+ const dataEndpoint = parseArgument(request, "dataEndpoint", z.string().min(1))
25
22
  const database = await services.database.forProject(projectId)
26
- const existingRegistrations = await database.workerUnitRegistration.findMany({
27
- where: { workerVersionId },
28
- select: { stateId: true, params: true },
23
+ const workerVersion = await database.workerVersion.findFirst({
24
+ where: { id: workerVersionId, apiKeyId: apiKey.id },
25
+ select: { id: true },
29
26
  })
30
-
31
- // emit existing registrations
32
- for (const registration of existingRegistrations) {
33
- yield {
34
- event: {
35
- $case: "unitRegistration",
36
- value: {
37
- stateId: registration.stateId,
38
- params: registration.params,
39
- },
40
- },
41
- }
27
+ if (!workerVersion) {
28
+ throw new Error(`Worker version "${workerVersionId}" is not owned by the API key`)
42
29
  }
43
30
 
44
- // subscribe to new registration events for this worker version
45
- const registrationStream = await services.pubsubManager.subscribe([
46
- "worker-unit-registration",
31
+ services.workerManager.assertWorkerInstance(projectId, workerVersionId, workerInstanceId)
32
+ services.panelEndpointManager.connect(
47
33
  projectId,
48
34
  workerVersionId,
49
- ])
35
+ workerInstanceId,
36
+ dataEndpoint,
37
+ )
50
38
 
51
- // emit new registration/deregistration events
52
- for await (const event of registrationStream) {
53
- if (event.type === "registered") {
39
+ try {
40
+ await services.workerManager.setWorkerRunning(projectId, workerVersionId, workerInstanceId)
41
+ const existingRegistrations = await database.workerUnitRegistration.findMany({
42
+ where: { workerVersionId },
43
+ select: { stateId: true, params: true },
44
+ })
45
+ for (const registration of existingRegistrations) {
54
46
  yield {
55
47
  event: {
56
48
  $case: "unitRegistration",
57
49
  value: {
58
- instanceId: event.instanceId,
59
- params: event.params,
50
+ stateId: registration.stateId,
51
+ params: registration.params,
60
52
  },
61
53
  },
62
54
  }
63
- } else if (event.type === "deregistered") {
64
- yield {
65
- event: {
66
- $case: "unitDeregistration",
67
- value: {
68
- instanceId: event.instanceId,
55
+ }
56
+ const registrationStream = await services.pubsubManager.subscribe([
57
+ "worker-unit-registration",
58
+ projectId,
59
+ workerVersionId,
60
+ ])
61
+ for await (const event of registrationStream) {
62
+ if (event.type === "registered") {
63
+ yield {
64
+ event: {
65
+ $case: "unitRegistration",
66
+ value: { stateId: event.stateId, params: event.params },
69
67
  },
70
- },
68
+ }
69
+ } else if (event.type === "deregistered") {
70
+ yield {
71
+ event: {
72
+ $case: "unitDeregistration",
73
+ value: { stateId: event.stateId },
74
+ },
75
+ }
71
76
  }
72
77
  }
78
+ } finally {
79
+ services.panelEndpointManager.disconnect(workerInstanceId)
73
80
  }
74
81
  },
75
82
 
package/src/index.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import type { Services } from "@highstate/backend"
2
2
  import { rm } from "node:fs/promises"
3
3
  import { InstanceServiceDefinition } from "@highstate/api/instance.v1"
4
+ import { PanelServiceDefinition } from "@highstate/api/panel.v1"
4
5
  import { SecretServiceDefinition } from "@highstate/api/secret.v1"
5
6
  import { WorkerServiceDefinition } from "@highstate/api/worker.v1"
6
7
  import { createServer } from "nice-grpc"
7
8
  import { createInstanceService } from "./handlers/instance"
9
+ import { createPanelService } from "./handlers/panel"
8
10
  import { createSecretService } from "./handlers/secret"
9
11
  import { createWorkerService } from "./handlers/worker"
10
12
  import { createErrorHandlingMiddleware } from "./shared"
@@ -14,6 +16,7 @@ export async function startBackedApi(services: Services) {
14
16
  server.use(createErrorHandlingMiddleware(services))
15
17
 
16
18
  server.add(InstanceServiceDefinition, createInstanceService(services))
19
+ server.add(PanelServiceDefinition, createPanelService(services))
17
20
  server.add(SecretServiceDefinition, createSecretService(services))
18
21
  server.add(WorkerServiceDefinition, createWorkerService(services))
19
22
 
@@ -29,7 +32,6 @@ export async function startBackedApi(services: Services) {
29
32
  }
30
33
 
31
34
  await server.listen(`unix:${sockPath}`)
32
-
33
35
  services.workerManager.config.HIGHSTATE_WORKER_API_PATH = sockPath
34
36
  services.logger.info(`api listening at %s`, sockPath)
35
37
  }