@highstate/backend-api 0.9.18 → 0.9.20

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": 1898191380
3
+ "./dist/index.js": 1156633087
4
4
  }
5
5
  }
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { rm } from 'node:fs/promises';
2
2
  import { createServer } from 'nice-grpc';
3
- import '@highstate/backend';
4
3
  import { InstanceServiceDefinition } from '@highstate/api/instance.v1';
5
4
  import { SecretServiceDefinition } from '@highstate/api/secret.v1';
6
5
  import { WorkerServiceDefinition } from '@highstate/api/worker.v1';
7
6
  import { ServerError, Status } from 'nice-grpc-common';
7
+ import { AccessError, instanceCustomStatusInputSchema } from '@highstate/backend/shared';
8
8
  import { isAbortError } from 'abort-controller-x';
9
- import { AccessError, workerSchema, instanceCustomStatusSchema } from '@highstate/backend/shared';
9
+ import { z, commonObjectMetaSchema } from '@highstate/contract';
10
10
 
11
11
  // src/index.ts
12
12
  async function authenticate(services, context) {
@@ -51,21 +51,27 @@ function parseArgument(request, argumentName, schema) {
51
51
  function createInstanceService(services) {
52
52
  return {
53
53
  async updateCustomStatus(request, context) {
54
- const [projectId] = await authenticate(services, context);
55
- const customStatus = parseArgument(request, "status", instanceCustomStatusSchema);
54
+ const [projectId, apiKey] = await authenticate(services, context);
55
+ const stateId = parseArgument(request, "stateId", z.cuid2());
56
+ const customStatus = parseArgument(request, "status", instanceCustomStatusInputSchema);
56
57
  await services.instanceStateService.updateCustomStatus(
57
58
  projectId,
58
- request.instanceId,
59
+ stateId,
60
+ apiKey.serviceAccountId,
59
61
  customStatus
60
62
  );
61
63
  return {};
62
64
  },
63
- async removeCustomStatus(request, _context) {
65
+ async removeCustomStatus(request, context) {
66
+ const [projectId, apiKey] = await authenticate(services, context);
67
+ const stateId = parseArgument(request, "stateId", z.cuid2());
64
68
  await services.instanceStateService.removeCustomStatus(
65
- "",
66
- request.instanceId,
69
+ projectId,
70
+ stateId,
71
+ apiKey.serviceAccountId,
67
72
  request.statusName
68
73
  );
74
+ return {};
69
75
  }
70
76
  };
71
77
  }
@@ -86,37 +92,58 @@ function createWorkerService(services) {
86
92
  return {
87
93
  async *connect(request, context) {
88
94
  const [projectId] = await authenticate(services, context);
89
- const workerId = parseArgument(request, "workerId", workerSchema.shape.id);
90
- const registrationStream = services.pubsubManager.subscribe([
91
- "worker-unit-registration",
92
- projectId,
93
- workerId
94
- ]);
95
- await services.workerManager.setWorkerRunning(projectId, workerId);
96
- await services.workerManager.writeSystemMessage(projectId, workerId, "worker connected");
97
- const registrations = await services.stateManager.getWorkerRegistrationIndexRepository(projectId, workerId).getAllItems();
98
- for (const registration of registrations) {
95
+ const workerVersionId = parseArgument(request, "workerVersionId", z.cuid2());
96
+ services.workerManager.setWorkerRunning(projectId, workerVersionId);
97
+ const database = await services.database.forProject(projectId);
98
+ const existingRegistrations = await database.workerUnitRegistration.findMany({
99
+ where: { workerVersionId },
100
+ select: { stateId: true, params: true }
101
+ });
102
+ for (const registration of existingRegistrations) {
99
103
  yield {
100
104
  event: {
101
105
  $case: "unitRegistration",
102
106
  value: {
103
- instanceId: registration.id,
107
+ stateId: registration.stateId,
104
108
  params: registration.params
105
109
  }
106
110
  }
107
111
  };
108
112
  }
113
+ const registrationStream = await services.pubsubManager.subscribe([
114
+ "worker-unit-registration",
115
+ projectId,
116
+ workerVersionId
117
+ ]);
109
118
  for await (const event of registrationStream) {
110
- yield {
111
- event: {
112
- $case: "unitRegistration",
113
- value: {
114
- instanceId: event.instanceId,
115
- params: event.params
119
+ if (event.type === "registered") {
120
+ yield {
121
+ event: {
122
+ $case: "unitRegistration",
123
+ value: {
124
+ instanceId: event.instanceId,
125
+ params: event.params
126
+ }
116
127
  }
117
- }
118
- };
128
+ };
129
+ } else if (event.type === "deregistered") {
130
+ yield {
131
+ event: {
132
+ $case: "unitDeregistration",
133
+ value: {
134
+ instanceId: event.instanceId
135
+ }
136
+ }
137
+ };
138
+ }
119
139
  }
140
+ },
141
+ async updateWorkerVersionMeta(request, context) {
142
+ const [projectId] = await authenticate(services, context);
143
+ const workerVersionId = parseArgument(request, "workerVersionId", z.string());
144
+ const meta = parseArgument(request, "meta", commonObjectMetaSchema);
145
+ await services.workerService.updateWorkerVersionMeta(projectId, workerVersionId, meta);
146
+ return {};
120
147
  }
121
148
  };
122
149
  }
@@ -128,7 +155,7 @@ async function startBackedApi(services) {
128
155
  server.add(InstanceServiceDefinition, createInstanceService(services));
129
156
  server.add(SecretServiceDefinition, createSecretService(services));
130
157
  server.add(WorkerServiceDefinition, createWorkerService(services));
131
- const uid = process.geteuid();
158
+ const uid = process.geteuid?.();
132
159
  const sockPath = `/run/user/${uid}/highstate.sock`;
133
160
  try {
134
161
  await rm(sockPath, { force: true });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/authentication.ts","../src/shared/error-handling.ts","../src/shared/validation.ts","../src/handlers/instance.ts","../src/handlers/secret.ts","../src/handlers/worker.ts","../src/index.ts"],"names":["ServerError","Status"],"mappings":"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","file":"index.js","sourcesContent":["import type { Services } from \"@highstate/backend\"\nimport type { ProjectApiKey } from \"@highstate/backend/shared\"\nimport { ServerError, Status, type CallContext } from \"nice-grpc-common\"\n\nexport async function authenticate(\n services: Services,\n context: CallContext,\n): Promise<[projectId: string, apiKey: ProjectApiKey]> {\n const token = context.metadata.get(\"api-key\")\n if (!token) {\n throw new ServerError(Status.UNAUTHENTICATED, \"No API key provided\")\n }\n\n const projectId = context.metadata.get(\"project-id\")\n if (!projectId) {\n throw new ServerError(Status.UNAUTHENTICATED, \"No project ID provided\")\n }\n\n const apiKey = await services.apiKeyService.getApiKeyByToken(projectId, token)\n\n return [projectId, apiKey]\n}\n","import type { Services } from \"@highstate/backend\"\nimport { ServerError, Status, type CallContext, type ServerMiddlewareCall } from \"nice-grpc-common\"\nimport { isAbortError } from \"abort-controller-x\"\nimport { AccessError } from \"@highstate/backend/shared\"\n\nexport function createErrorHandlingMiddleware(services: Services) {\n return async function* errorHandlingMiddleware<TRequest, TResponse>(\n call: ServerMiddlewareCall<TRequest, TResponse>,\n context: CallContext,\n ) {\n try {\n return yield* call.next(call.request, context)\n } catch (error) {\n if (error instanceof ServerError || isAbortError(error)) {\n throw error\n }\n\n if (error instanceof AccessError) {\n services.logger.info({ error }, \"access denied\")\n throw new ServerError(Status.UNAUTHENTICATED, \"Access denied\")\n }\n\n services.logger.error({ error }, \"unexpected error\")\n throw new ServerError(Status.INTERNAL, \"An unexpected error occurred\")\n }\n }\n}\n","import type { z } from \"zod\"\nimport { ServerError, Status } from \"nice-grpc-common\"\n\nexport function parseArgument<\n TRequest,\n TArgumentName extends string & keyof TRequest,\n TSchema extends z.ZodType,\n>(request: TRequest, argumentName: TArgumentName, schema: TSchema): z.infer<TSchema> {\n const result = schema.safeParse(request[argumentName])\n if (!result.success) {\n throw new ServerError(\n Status.INVALID_ARGUMENT,\n `Invalid argument \"${argumentName}\": ${result.error.message}`,\n )\n }\n\n return result.data\n}\n","import type { Services } from \"@highstate/backend\"\nimport type { InstanceServiceImplementation } from \"@highstate/api/instance.v1\"\nimport { instanceCustomStatusSchema } from \"@highstate/backend/shared\"\nimport { authenticate, parseArgument } from \"../shared\"\n\nexport function createInstanceService(services: Services): InstanceServiceImplementation {\n return {\n async updateCustomStatus(request, context) {\n const [projectId] = await authenticate(services, context)\n\n // TODO: validate instance access\n\n const customStatus = parseArgument(request, \"status\", instanceCustomStatusSchema)\n\n await services.instanceStateService.updateCustomStatus(\n projectId,\n request.instanceId,\n customStatus,\n )\n\n return {}\n },\n\n async removeCustomStatus(request, _context) {\n await services.instanceStateService.removeCustomStatus(\n \"\",\n request.instanceId,\n request.statusName,\n )\n },\n }\n}\n","import type { SecretServiceImplementation } from \"@highstate/api/secret.v1\"\nimport type { Services } from \"@highstate/backend\"\nimport { authenticate } from \"../shared\"\n\nexport function createSecretService(services: Services): SecretServiceImplementation {\n return {\n async getSecretContent(request, context) {\n const [projectId] = await authenticate(services, context)\n\n // TODO: validate secret access\n\n const content = await services.stateManager\n .getSecretContentRepository(projectId)\n .get(request.secretId)\n\n return {\n content,\n }\n },\n }\n}\n","import type { Services } from \"@highstate/backend\"\nimport type { WorkerServiceImplementation } from \"@highstate/api/worker.v1\"\nimport { workerSchema } from \"@highstate/backend/shared\"\nimport { authenticate, parseArgument } from \"../shared\"\n\nexport function createWorkerService(services: Services): WorkerServiceImplementation {\n return {\n async *connect(request, context) {\n const [projectId] = await authenticate(services, context)\n\n // TODO: check worker access\n const workerId = parseArgument(request, \"workerId\", workerSchema.shape.id)\n\n const registrationStream = services.pubsubManager.subscribe([\n \"worker-unit-registration\",\n projectId,\n workerId,\n ])\n\n // update worker status\n await services.workerManager.setWorkerRunning(projectId, workerId)\n await services.workerManager.writeSystemMessage(projectId, workerId, \"worker connected\")\n\n // emit existing registrations\n const registrations = await services.stateManager\n .getWorkerRegistrationIndexRepository(projectId, workerId)\n .getAllItems()\n\n for (const registration of registrations) {\n yield {\n event: {\n $case: \"unitRegistration\",\n value: {\n instanceId: registration.id,\n params: registration.params,\n },\n },\n }\n }\n\n // emit new registrations\n for await (const event of registrationStream) {\n yield {\n event: {\n $case: \"unitRegistration\",\n value: {\n instanceId: event.instanceId,\n params: event.params,\n },\n },\n }\n }\n },\n }\n}\n","import { rm } from \"node:fs/promises\"\nimport { createServer } from \"nice-grpc\"\nimport { type Services } from \"@highstate/backend\"\nimport { InstanceServiceDefinition } from \"@highstate/api/instance.v1\"\nimport { SecretServiceDefinition } from \"@highstate/api/secret.v1\"\nimport { WorkerServiceDefinition } from \"@highstate/api/worker.v1\"\nimport { createErrorHandlingMiddleware } from \"./shared\"\nimport { createInstanceService } from \"./handlers/instance\"\nimport { createSecretService } from \"./handlers/secret\"\nimport { createWorkerService } from \"./handlers/worker\"\n\nexport async function startBackedApi(services: Services) {\n const server = createServer()\n server.use(createErrorHandlingMiddleware(services))\n\n server.add(InstanceServiceDefinition, createInstanceService(services))\n server.add(SecretServiceDefinition, createSecretService(services))\n server.add(WorkerServiceDefinition, createWorkerService(services))\n\n const uid = process.geteuid!()\n const sockPath = `/run/user/${uid}/highstate.sock`\n\n try {\n await rm(sockPath, { force: true })\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) {\n services.logger.error({ error }, \"failed to remove existing socket file\")\n }\n }\n\n await server.listen(`unix:${sockPath}`)\n\n services.workerManager.config.HIGHSTATE_WORKER_API_PATH = sockPath\n services.logger.info(`api listening at %s`, sockPath)\n}\n"]}
1
+ {"version":3,"sources":["../src/shared/authentication.ts","../src/shared/error-handling.ts","../src/shared/validation.ts","../src/handlers/instance.ts","../src/handlers/secret.ts","../src/handlers/worker.ts","../src/index.ts"],"names":["ServerError","Status","z"],"mappings":"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","file":"index.js","sourcesContent":["import type { ApiKey, Services } from \"@highstate/backend\"\nimport { type CallContext, ServerError, Status } from \"nice-grpc-common\"\n\nexport async function authenticate(\n services: Services,\n context: CallContext,\n): Promise<[projectId: string, apiKey: ApiKey]> {\n const token = context.metadata.get(\"api-key\")\n if (!token) {\n throw new ServerError(Status.UNAUTHENTICATED, \"No API key provided\")\n }\n\n const projectId = context.metadata.get(\"project-id\")\n if (!projectId) {\n throw new ServerError(Status.UNAUTHENTICATED, \"No project ID provided\")\n }\n\n const apiKey = await services.apiKeyService.getApiKeyByToken(projectId, token)\n\n return [projectId, apiKey]\n}\n","import type { Services } from \"@highstate/backend\"\nimport { AccessError } from \"@highstate/backend/shared\"\nimport { isAbortError } from \"abort-controller-x\"\nimport { type CallContext, ServerError, type ServerMiddlewareCall, Status } from \"nice-grpc-common\"\n\nexport function createErrorHandlingMiddleware(services: Services) {\n return async function* errorHandlingMiddleware<TRequest, TResponse>(\n call: ServerMiddlewareCall<TRequest, TResponse>,\n context: CallContext,\n ) {\n try {\n return yield* call.next(call.request, context)\n } catch (error) {\n if (error instanceof ServerError || isAbortError(error)) {\n throw error\n }\n\n if (error instanceof AccessError) {\n services.logger.info({ error }, \"access denied\")\n throw new ServerError(Status.UNAUTHENTICATED, \"Access denied\")\n }\n\n services.logger.error({ error }, \"unexpected error\")\n throw new ServerError(Status.INTERNAL, \"An unexpected error occurred\")\n }\n }\n}\n","import type { z } from \"zod\"\nimport { ServerError, Status } from \"nice-grpc-common\"\n\nexport function parseArgument<\n TRequest,\n TArgumentName extends string & keyof TRequest,\n TSchema extends z.ZodType,\n>(request: TRequest, argumentName: TArgumentName, schema: TSchema): z.infer<TSchema> {\n const result = schema.safeParse(request[argumentName])\n if (!result.success) {\n throw new ServerError(\n Status.INVALID_ARGUMENT,\n `Invalid argument \"${argumentName}\": ${result.error.message}`,\n )\n }\n\n return result.data\n}\n","import type { InstanceServiceImplementation } from \"@highstate/api/instance.v1\"\nimport type { Services } from \"@highstate/backend\"\nimport { instanceCustomStatusInputSchema } from \"@highstate/backend/shared\"\nimport { z } from \"@highstate/contract\"\nimport { authenticate, parseArgument } from \"../shared\"\n\nexport function createInstanceService(services: Services): InstanceServiceImplementation {\n return {\n async updateCustomStatus(request, context) {\n const [projectId, apiKey] = await authenticate(services, context)\n\n // TODO: validate instance access\n\n const stateId = parseArgument(request, \"stateId\", z.cuid2())\n const customStatus = parseArgument(request, \"status\", instanceCustomStatusInputSchema)\n\n await services.instanceStateService.updateCustomStatus(\n projectId,\n stateId,\n apiKey.serviceAccountId,\n customStatus,\n )\n\n return {}\n },\n\n async removeCustomStatus(request, context) {\n const [projectId, apiKey] = await authenticate(services, context)\n\n // TODO: validate instance access\n\n const stateId = parseArgument(request, \"stateId\", z.cuid2())\n\n await services.instanceStateService.removeCustomStatus(\n projectId,\n stateId,\n apiKey.serviceAccountId,\n request.statusName,\n )\n\n return {}\n },\n }\n}\n","import type { SecretServiceImplementation } from \"@highstate/api/secret.v1\"\nimport type { Services } from \"@highstate/backend\"\nimport { authenticate } from \"../shared\"\n\nexport function createSecretService(services: Services): SecretServiceImplementation {\n return {\n async getSecretContent(request, context) {\n const [projectId] = await authenticate(services, context)\n\n // TODO: validate secret access\n\n const content = await services.stateManager\n .getSecretContentRepository(projectId)\n .get(request.secretId)\n\n return {\n content,\n }\n },\n }\n}\n","import type { WorkerServiceImplementation } from \"@highstate/api/worker.v1\"\nimport type { Services } from \"@highstate/backend\"\nimport { commonObjectMetaSchema, z } from \"@highstate/contract\"\nimport { authenticate, parseArgument } from \"../shared\"\n\nexport function createWorkerService(services: Services): WorkerServiceImplementation {\n return {\n async *connect(request, context) {\n const [projectId] = await authenticate(services, context)\n\n const workerVersionId = parseArgument(request, \"workerVersionId\", z.cuid2())\n\n // set worker as running\n services.workerManager.setWorkerRunning(projectId, workerVersionId)\n\n // get existing registrations for this worker version\n const database = await services.database.forProject(projectId)\n const existingRegistrations = await database.workerUnitRegistration.findMany({\n where: { workerVersionId },\n select: { stateId: true, params: true },\n })\n\n // emit existing registrations\n for (const registration of existingRegistrations) {\n yield {\n event: {\n $case: \"unitRegistration\",\n value: {\n stateId: registration.stateId,\n params: registration.params,\n },\n },\n }\n }\n\n // subscribe to new registration events for this worker version\n const registrationStream = await services.pubsubManager.subscribe([\n \"worker-unit-registration\",\n projectId,\n workerVersionId,\n ])\n\n // emit new registration/deregistration events\n for await (const event of registrationStream) {\n if (event.type === \"registered\") {\n yield {\n event: {\n $case: \"unitRegistration\",\n value: {\n instanceId: event.instanceId,\n params: event.params,\n },\n },\n }\n } else if (event.type === \"deregistered\") {\n yield {\n event: {\n $case: \"unitDeregistration\",\n value: {\n instanceId: event.instanceId,\n },\n },\n }\n }\n }\n },\n\n async updateWorkerVersionMeta(request, context) {\n const [projectId] = await authenticate(services, context)\n\n const workerVersionId = parseArgument(request, \"workerVersionId\", z.string())\n const meta = parseArgument(request, \"meta\", commonObjectMetaSchema)\n\n await services.workerService.updateWorkerVersionMeta(projectId, workerVersionId, meta)\n\n return {}\n },\n }\n}\n","import { rm } from \"node:fs/promises\"\nimport { createServer } from \"nice-grpc\"\nimport type { Services } from \"@highstate/backend\"\nimport { InstanceServiceDefinition } from \"@highstate/api/instance.v1\"\nimport { SecretServiceDefinition } from \"@highstate/api/secret.v1\"\nimport { WorkerServiceDefinition } from \"@highstate/api/worker.v1\"\nimport { createErrorHandlingMiddleware } from \"./shared\"\nimport { createInstanceService } from \"./handlers/instance\"\nimport { createSecretService } from \"./handlers/secret\"\nimport { createWorkerService } from \"./handlers/worker\"\n\nexport async function startBackedApi(services: Services) {\n const server = createServer()\n server.use(createErrorHandlingMiddleware(services))\n\n server.add(InstanceServiceDefinition, createInstanceService(services))\n server.add(SecretServiceDefinition, createSecretService(services))\n server.add(WorkerServiceDefinition, createWorkerService(services))\n\n const uid = process.geteuid?.()\n const sockPath = `/run/user/${uid}/highstate.sock`\n\n try {\n await rm(sockPath, { force: true })\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) {\n services.logger.error({ error }, \"failed to remove existing socket file\")\n }\n }\n\n await server.listen(`unix:${sockPath}`)\n\n services.workerManager.config.HIGHSTATE_WORKER_API_PATH = sockPath\n services.logger.info(`api listening at %s`, sockPath)\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highstate/backend-api",
3
- "version": "0.9.18",
3
+ "version": "0.9.20",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -17,8 +17,7 @@
17
17
  "access": "public"
18
18
  },
19
19
  "scripts": {
20
- "build": "highstate build",
21
- "generate-sdk": "./scripts/generate-sdk.sh"
20
+ "build": "highstate build"
22
21
  },
23
22
  "peerDependencies": {
24
23
  "@pulumi/pulumi": "^3.163.0",
@@ -32,18 +31,16 @@
32
31
  "optional": true
33
32
  }
34
33
  },
35
- "devDependencies": {
36
- "ts-proto": "^2.7.5"
37
- },
38
34
  "dependencies": {
39
35
  "@bufbuild/protobuf": "^2.6.1",
40
- "@highstate/api": "^0.9.18",
41
- "@highstate/backend": "^0.9.18",
42
- "@highstate/cli": "^0.9.18",
36
+ "@highstate/api": "^0.9.20",
37
+ "@highstate/backend": "^0.9.20",
38
+ "@highstate/cli": "^0.9.20",
39
+ "@highstate/contract": "^0.9.20",
43
40
  "abort-controller-x": "^0.4.3",
44
41
  "nice-grpc": "^2.1.12",
45
42
  "nice-grpc-common": "^2.0.2",
46
43
  "zod": "^4.0.5"
47
44
  },
48
- "gitHead": "9ebcd7da56b00b8ca08bf52cc8438f527338cd64"
45
+ "gitHead": "4bf9183450c2c6f51d6a99d77efc379ff5c7b7ef"
49
46
  }
@@ -1,32 +1,44 @@
1
- import type { Services } from "@highstate/backend"
2
1
  import type { InstanceServiceImplementation } from "@highstate/api/instance.v1"
3
- import { instanceCustomStatusSchema } from "@highstate/backend/shared"
2
+ import type { Services } from "@highstate/backend"
3
+ import { instanceCustomStatusInputSchema } from "@highstate/backend/shared"
4
+ import { z } from "@highstate/contract"
4
5
  import { authenticate, parseArgument } from "../shared"
5
6
 
6
7
  export function createInstanceService(services: Services): InstanceServiceImplementation {
7
8
  return {
8
9
  async updateCustomStatus(request, context) {
9
- const [projectId] = await authenticate(services, context)
10
+ const [projectId, apiKey] = await authenticate(services, context)
10
11
 
11
12
  // TODO: validate instance access
12
13
 
13
- const customStatus = parseArgument(request, "status", instanceCustomStatusSchema)
14
+ const stateId = parseArgument(request, "stateId", z.cuid2())
15
+ const customStatus = parseArgument(request, "status", instanceCustomStatusInputSchema)
14
16
 
15
17
  await services.instanceStateService.updateCustomStatus(
16
18
  projectId,
17
- request.instanceId,
19
+ stateId,
20
+ apiKey.serviceAccountId,
18
21
  customStatus,
19
22
  )
20
23
 
21
24
  return {}
22
25
  },
23
26
 
24
- async removeCustomStatus(request, _context) {
27
+ async removeCustomStatus(request, context) {
28
+ const [projectId, apiKey] = await authenticate(services, context)
29
+
30
+ // TODO: validate instance access
31
+
32
+ const stateId = parseArgument(request, "stateId", z.cuid2())
33
+
25
34
  await services.instanceStateService.removeCustomStatus(
26
- "",
27
- request.instanceId,
35
+ projectId,
36
+ stateId,
37
+ apiKey.serviceAccountId,
28
38
  request.statusName,
29
39
  )
40
+
41
+ return {}
30
42
  },
31
43
  }
32
44
  }
@@ -1,6 +1,6 @@
1
- import type { Services } from "@highstate/backend"
2
1
  import type { WorkerServiceImplementation } from "@highstate/api/worker.v1"
3
- import { workerSchema } from "@highstate/backend/shared"
2
+ import type { Services } from "@highstate/backend"
3
+ import { commonObjectMetaSchema, z } from "@highstate/contract"
4
4
  import { authenticate, parseArgument } from "../shared"
5
5
 
6
6
  export function createWorkerService(services: Services): WorkerServiceImplementation {
@@ -8,48 +8,72 @@ export function createWorkerService(services: Services): WorkerServiceImplementa
8
8
  async *connect(request, context) {
9
9
  const [projectId] = await authenticate(services, context)
10
10
 
11
- // TODO: check worker access
12
- const workerId = parseArgument(request, "workerId", workerSchema.shape.id)
11
+ const workerVersionId = parseArgument(request, "workerVersionId", z.cuid2())
13
12
 
14
- const registrationStream = services.pubsubManager.subscribe([
15
- "worker-unit-registration",
16
- projectId,
17
- workerId,
18
- ])
13
+ // set worker as running
14
+ services.workerManager.setWorkerRunning(projectId, workerVersionId)
19
15
 
20
- // update worker status
21
- await services.workerManager.setWorkerRunning(projectId, workerId)
22
- await services.workerManager.writeSystemMessage(projectId, workerId, "worker connected")
16
+ // get existing registrations for this worker version
17
+ const database = await services.database.forProject(projectId)
18
+ const existingRegistrations = await database.workerUnitRegistration.findMany({
19
+ where: { workerVersionId },
20
+ select: { stateId: true, params: true },
21
+ })
23
22
 
24
23
  // emit existing registrations
25
- const registrations = await services.stateManager
26
- .getWorkerRegistrationIndexRepository(projectId, workerId)
27
- .getAllItems()
28
-
29
- for (const registration of registrations) {
24
+ for (const registration of existingRegistrations) {
30
25
  yield {
31
26
  event: {
32
27
  $case: "unitRegistration",
33
28
  value: {
34
- instanceId: registration.id,
29
+ stateId: registration.stateId,
35
30
  params: registration.params,
36
31
  },
37
32
  },
38
33
  }
39
34
  }
40
35
 
41
- // emit new registrations
36
+ // subscribe to new registration events for this worker version
37
+ const registrationStream = await services.pubsubManager.subscribe([
38
+ "worker-unit-registration",
39
+ projectId,
40
+ workerVersionId,
41
+ ])
42
+
43
+ // emit new registration/deregistration events
42
44
  for await (const event of registrationStream) {
43
- yield {
44
- event: {
45
- $case: "unitRegistration",
46
- value: {
47
- instanceId: event.instanceId,
48
- params: event.params,
45
+ if (event.type === "registered") {
46
+ yield {
47
+ event: {
48
+ $case: "unitRegistration",
49
+ value: {
50
+ instanceId: event.instanceId,
51
+ params: event.params,
52
+ },
49
53
  },
50
- },
54
+ }
55
+ } else if (event.type === "deregistered") {
56
+ yield {
57
+ event: {
58
+ $case: "unitDeregistration",
59
+ value: {
60
+ instanceId: event.instanceId,
61
+ },
62
+ },
63
+ }
51
64
  }
52
65
  }
53
66
  },
67
+
68
+ async updateWorkerVersionMeta(request, context) {
69
+ const [projectId] = await authenticate(services, context)
70
+
71
+ const workerVersionId = parseArgument(request, "workerVersionId", z.string())
72
+ const meta = parseArgument(request, "meta", commonObjectMetaSchema)
73
+
74
+ await services.workerService.updateWorkerVersionMeta(projectId, workerVersionId, meta)
75
+
76
+ return {}
77
+ },
54
78
  }
55
79
  }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { rm } from "node:fs/promises"
2
2
  import { createServer } from "nice-grpc"
3
- import { type Services } from "@highstate/backend"
3
+ import type { Services } from "@highstate/backend"
4
4
  import { InstanceServiceDefinition } from "@highstate/api/instance.v1"
5
5
  import { SecretServiceDefinition } from "@highstate/api/secret.v1"
6
6
  import { WorkerServiceDefinition } from "@highstate/api/worker.v1"
@@ -17,7 +17,7 @@ export async function startBackedApi(services: Services) {
17
17
  server.add(SecretServiceDefinition, createSecretService(services))
18
18
  server.add(WorkerServiceDefinition, createWorkerService(services))
19
19
 
20
- const uid = process.geteuid!()
20
+ const uid = process.geteuid?.()
21
21
  const sockPath = `/run/user/${uid}/highstate.sock`
22
22
 
23
23
  try {
@@ -1,11 +1,10 @@
1
- import type { Services } from "@highstate/backend"
2
- import type { ProjectApiKey } from "@highstate/backend/shared"
3
- import { ServerError, Status, type CallContext } from "nice-grpc-common"
1
+ import type { ApiKey, Services } from "@highstate/backend"
2
+ import { type CallContext, ServerError, Status } from "nice-grpc-common"
4
3
 
5
4
  export async function authenticate(
6
5
  services: Services,
7
6
  context: CallContext,
8
- ): Promise<[projectId: string, apiKey: ProjectApiKey]> {
7
+ ): Promise<[projectId: string, apiKey: ApiKey]> {
9
8
  const token = context.metadata.get("api-key")
10
9
  if (!token) {
11
10
  throw new ServerError(Status.UNAUTHENTICATED, "No API key provided")
@@ -1,7 +1,7 @@
1
1
  import type { Services } from "@highstate/backend"
2
- import { ServerError, Status, type CallContext, type ServerMiddlewareCall } from "nice-grpc-common"
3
- import { isAbortError } from "abort-controller-x"
4
2
  import { AccessError } from "@highstate/backend/shared"
3
+ import { isAbortError } from "abort-controller-x"
4
+ import { type CallContext, ServerError, type ServerMiddlewareCall, Status } from "nice-grpc-common"
5
5
 
6
6
  export function createErrorHandlingMiddleware(services: Services) {
7
7
  return async function* errorHandlingMiddleware<TRequest, TResponse>(