@highstate/backend 0.9.16 → 0.9.19

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.
Files changed (302) hide show
  1. package/dist/chunk-5WVU2AK4.js +1535 -0
  2. package/dist/chunk-5WVU2AK4.js.map +1 -0
  3. package/dist/chunk-I7BWSAN6.js +49 -0
  4. package/dist/chunk-I7BWSAN6.js.map +1 -0
  5. package/dist/{chunk-RCB4AFGD.js → chunk-VB4YL327.js} +51 -71
  6. package/dist/chunk-VB4YL327.js.map +1 -0
  7. package/dist/database/local/prisma.config.js +26 -0
  8. package/dist/database/local/prisma.config.js.map +1 -0
  9. package/dist/highstate.manifest.json +5 -4
  10. package/dist/index.js +7676 -6634
  11. package/dist/index.js.map +1 -1
  12. package/dist/library/package-resolution-worker.js +8 -6
  13. package/dist/library/package-resolution-worker.js.map +1 -1
  14. package/dist/library/worker/main.js +63 -58
  15. package/dist/library/worker/main.js.map +1 -1
  16. package/dist/shared/index.js +3 -216
  17. package/dist/shared/index.js.map +1 -1
  18. package/package.json +23 -11
  19. package/prisma/backend/_schema/layout.prisma +7 -0
  20. package/prisma/backend/_schema/library.prisma +17 -0
  21. package/prisma/backend/_schema/project.prisma +101 -0
  22. package/prisma/backend/_schema/pulumi.prisma +17 -0
  23. package/prisma/backend/postgresql/main.prisma +17 -0
  24. package/prisma/backend/sqlite/main.prisma +17 -0
  25. package/prisma/backend/sqlite/migrations/20250817070609_initiial/migration.sql +34 -0
  26. package/prisma/backend/sqlite/migrations/20250817104948_add_fields/migration.sql +59 -0
  27. package/prisma/backend/sqlite/migrations/20250818082732_add_models/migration.sql +41 -0
  28. package/prisma/backend/sqlite/migrations/20250818083106_a/migration.sql +19 -0
  29. package/prisma/backend/sqlite/migrations/20250818101945_hi/migration.sql +1 -0
  30. package/prisma/backend/sqlite/migrations/20250819082315_a/migration.sql +5 -0
  31. package/prisma/backend/sqlite/migrations/migration_lock.toml +3 -0
  32. package/prisma/project/api-key.prisma +27 -0
  33. package/prisma/project/artifact.prisma +52 -0
  34. package/prisma/project/custom-status.prisma +46 -0
  35. package/prisma/project/evaluation.prisma +35 -0
  36. package/prisma/project/instance.prisma +160 -0
  37. package/prisma/project/layout.prisma +23 -0
  38. package/prisma/project/lock.prisma +18 -0
  39. package/prisma/project/main.prisma +17 -0
  40. package/prisma/project/migrations/20250816081310_initial/migration.sql +300 -0
  41. package/prisma/project/migrations/20250816082523_test/migration.sql +72 -0
  42. package/prisma/project/migrations/20250818065643_update/migration.sql +42 -0
  43. package/prisma/project/migrations/20250818070758_a/migration.sql +8 -0
  44. package/prisma/project/migrations/20250818070913_a/migration.sql +8 -0
  45. package/prisma/project/migrations/20250818082720_add_motels/migration.sql +11 -0
  46. package/prisma/project/migrations/20250818112523_hello/migration.sql +35 -0
  47. package/prisma/project/migrations/20250819082305_a/migration.sql +14 -0
  48. package/prisma/project/migrations/20250819165004_add_missing_fields/migration.sql +216 -0
  49. package/prisma/project/migrations/20250819171309_a/migration.sql +22 -0
  50. package/prisma/project/migrations/20250820113949_a/migration.sql +66 -0
  51. package/prisma/project/migrations/20250820144256_b/migration.sql +31 -0
  52. package/prisma/project/migrations/20250820145547_a/migration.sql +24 -0
  53. package/prisma/project/migrations/20250820182517_b/migration.sql +2 -0
  54. package/prisma/project/migrations/20250821172324_a/migration.sql +2 -0
  55. package/prisma/project/migrations/20250822081339_a/migration.sql +219 -0
  56. package/prisma/project/migrations/20250822083742_b/migration.sql +1 -0
  57. package/prisma/project/migrations/20250822105134_boom/migration.sql +1 -0
  58. package/prisma/project/migrations/20250822141028_b/migration.sql +1 -0
  59. package/prisma/project/migrations/20250822142342_b/migration.sql +16 -0
  60. package/prisma/project/migrations/20250824072720_a/migration.sql +1 -0
  61. package/prisma/project/migrations/20250824093656_b/migration.sql +21 -0
  62. package/prisma/project/migrations/20250825082518_a/migration.sql +1 -0
  63. package/prisma/project/migrations/20250825085343_b/migration.sql +1 -0
  64. package/prisma/project/migrations/20250825091312_a/migration.sql +1 -0
  65. package/prisma/project/migrations/20250903095431_hi/migration.sql +44 -0
  66. package/prisma/project/migrations/20250903174255_a/migration.sql +24 -0
  67. package/prisma/project/migrations/20250908095205_hi/migration.sql +18 -0
  68. package/prisma/project/migrations/20250909155857_hi/migration.sql +15 -0
  69. package/prisma/project/migrations/migration_lock.toml +3 -0
  70. package/prisma/project/model.prisma +37 -0
  71. package/prisma/project/operation.prisma +148 -0
  72. package/prisma/project/page.prisma +41 -0
  73. package/prisma/project/secret.prisma +42 -0
  74. package/prisma/project/service-account.prisma +36 -0
  75. package/prisma/project/terminal.prisma +90 -0
  76. package/prisma/project/trigger.prisma +31 -0
  77. package/prisma/project/unlock-method.prisma +32 -0
  78. package/prisma/project/worker.prisma +138 -0
  79. package/src/artifact/abstractions.ts +13 -13
  80. package/src/artifact/encryption.ts +31 -15
  81. package/src/artifact/factory.ts +7 -10
  82. package/src/artifact/local.ts +33 -50
  83. package/src/business/api-key.ts +24 -36
  84. package/src/business/artifact.test.ts +978 -0
  85. package/src/business/artifact.ts +136 -215
  86. package/src/business/evaluation.ts +328 -0
  87. package/src/business/index.ts +5 -1
  88. package/src/business/instance-lock.test.ts +1060 -0
  89. package/src/business/instance-lock.ts +387 -77
  90. package/src/business/instance-state.test.ts +735 -0
  91. package/src/business/instance-state.ts +604 -217
  92. package/src/business/operation.test.ts +439 -0
  93. package/src/business/operation.ts +174 -208
  94. package/src/business/project-model.ts +258 -0
  95. package/src/business/project-unlock.ts +172 -112
  96. package/src/business/project.ts +407 -0
  97. package/src/business/secret.test.ts +513 -0
  98. package/src/business/secret.ts +194 -131
  99. package/src/business/settings.test.ts +695 -0
  100. package/src/business/settings.ts +855 -0
  101. package/src/business/terminal-session.ts +90 -0
  102. package/src/business/unit-extra.test.ts +539 -0
  103. package/src/business/unit-extra.ts +160 -0
  104. package/src/business/worker.test.ts +391 -0
  105. package/src/business/worker.ts +250 -114
  106. package/src/common/codebase.ts +65 -0
  107. package/src/common/index.ts +3 -2
  108. package/src/common/logger.ts +5 -0
  109. package/src/common/utils.ts +4 -3
  110. package/src/config.ts +15 -12
  111. package/src/database/_generated/backend/postgresql/client.ts +72 -0
  112. package/src/database/_generated/backend/postgresql/commonInputTypes.ts +350 -0
  113. package/src/database/_generated/backend/postgresql/enums.ts +13 -0
  114. package/src/database/_generated/backend/postgresql/internal/class.ts +320 -0
  115. package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +1238 -0
  116. package/src/database/_generated/backend/postgresql/models/Library.ts +1263 -0
  117. package/src/database/_generated/backend/postgresql/models/Project.ts +2175 -0
  118. package/src/database/_generated/backend/postgresql/models/ProjectModelStorage.ts +1263 -0
  119. package/src/database/_generated/backend/postgresql/models/ProjectSpace.ts +1602 -0
  120. package/src/database/_generated/backend/postgresql/models/PulumiBackend.ts +1263 -0
  121. package/src/database/_generated/backend/postgresql/models/UserWorkspaseLayout.ts +1065 -0
  122. package/src/database/_generated/backend/postgresql/models.ts +16 -0
  123. package/src/database/_generated/backend/postgresql/pjtg.ts +182 -0
  124. package/src/database/_generated/backend/sqlite/client.ts +72 -0
  125. package/src/database/_generated/backend/sqlite/commonInputTypes.ts +331 -0
  126. package/src/database/_generated/backend/sqlite/enums.ts +13 -0
  127. package/src/database/_generated/backend/sqlite/internal/class.ts +318 -0
  128. package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +1207 -0
  129. package/src/database/_generated/backend/sqlite/models/Library.ts +1261 -0
  130. package/src/database/_generated/backend/sqlite/models/Project.ts +2169 -0
  131. package/src/database/_generated/backend/sqlite/models/ProjectModelStorage.ts +1261 -0
  132. package/src/database/_generated/backend/sqlite/models/ProjectSpace.ts +1599 -0
  133. package/src/database/_generated/backend/sqlite/models/PulumiBackend.ts +1261 -0
  134. package/src/database/_generated/backend/sqlite/models/UserWorkspaseLayout.ts +1063 -0
  135. package/src/database/_generated/backend/sqlite/models.ts +16 -0
  136. package/src/database/_generated/backend/sqlite/pjtg.ts +182 -0
  137. package/src/database/_generated/project/client.ts +204 -0
  138. package/src/database/_generated/project/commonInputTypes.ts +827 -0
  139. package/src/database/_generated/project/enums.ts +104 -0
  140. package/src/database/_generated/project/internal/class.ts +479 -0
  141. package/src/database/_generated/project/internal/prismaNamespace.ts +2974 -0
  142. package/src/database/_generated/project/models/ApiKey.ts +1506 -0
  143. package/src/database/_generated/project/models/Artifact.ts +2051 -0
  144. package/src/database/_generated/project/models/HubModel.ts +1125 -0
  145. package/src/database/_generated/project/models/InstanceCustomStatus.ts +1713 -0
  146. package/src/database/_generated/project/models/InstanceEvaluationState.ts +1312 -0
  147. package/src/database/_generated/project/models/InstanceLock.ts +1268 -0
  148. package/src/database/_generated/project/models/InstanceModel.ts +1125 -0
  149. package/src/database/_generated/project/models/InstanceOperationState.ts +1707 -0
  150. package/src/database/_generated/project/models/InstanceState.ts +4613 -0
  151. package/src/database/_generated/project/models/Operation.ts +1647 -0
  152. package/src/database/_generated/project/models/OperationLog.ts +1455 -0
  153. package/src/database/_generated/project/models/Page.ts +1838 -0
  154. package/src/database/_generated/project/models/Secret.ts +1692 -0
  155. package/src/database/_generated/project/models/ServiceAccount.ts +2165 -0
  156. package/src/database/_generated/project/models/Terminal.ts +2038 -0
  157. package/src/database/_generated/project/models/TerminalSession.ts +1454 -0
  158. package/src/database/_generated/project/models/TerminalSessionLog.ts +1280 -0
  159. package/src/database/_generated/project/models/Trigger.ts +1430 -0
  160. package/src/database/_generated/project/models/UnlockMethod.ts +1220 -0
  161. package/src/database/_generated/project/models/UserCompositeViewport.ts +1280 -0
  162. package/src/database/_generated/project/models/UserProjectViewport.ts +1059 -0
  163. package/src/database/_generated/project/models/Worker.ts +1459 -0
  164. package/src/database/_generated/project/models/WorkerUnitRegistration.ts +1524 -0
  165. package/src/database/_generated/project/models/WorkerVersion.ts +1974 -0
  166. package/src/database/_generated/project/models/WorkerVersionLog.ts +1318 -0
  167. package/src/database/_generated/project/models.ts +35 -0
  168. package/src/database/_generated/project/pjtg.ts +182 -0
  169. package/src/database/abstractions.ts +19 -0
  170. package/src/database/factory.ts +37 -0
  171. package/src/database/index.ts +6 -0
  172. package/src/database/local/backend.ts +134 -0
  173. package/src/database/local/index.ts +3 -0
  174. package/src/database/local/meta.ts +46 -0
  175. package/src/database/local/prisma.config.ts +25 -0
  176. package/src/database/local/project.ts +39 -0
  177. package/src/database/manager.ts +181 -0
  178. package/src/database/migrate.ts +35 -0
  179. package/src/database/prisma.ts +56 -0
  180. package/src/database/well-known.ts +38 -0
  181. package/src/index.ts +4 -4
  182. package/src/library/abstractions.ts +21 -14
  183. package/src/library/factory.ts +1 -1
  184. package/src/library/local.ts +86 -38
  185. package/src/library/package-resolution-worker.ts +1 -1
  186. package/src/library/worker/evaluator.ts +61 -48
  187. package/src/library/worker/loader.lite.ts +14 -1
  188. package/src/library/worker/main.ts +9 -16
  189. package/src/library/worker/protocol.ts +0 -12
  190. package/src/lock/manager.ts +12 -7
  191. package/src/orchestrator/manager.ts +198 -131
  192. package/src/orchestrator/operation-context.ts +357 -0
  193. package/src/orchestrator/operation-plan.destroy.test.md +357 -0
  194. package/src/orchestrator/operation-plan.destroy.test.ts +775 -0
  195. package/src/orchestrator/operation-plan.fixtures.ts +213 -0
  196. package/src/orchestrator/operation-plan.md +198 -0
  197. package/src/orchestrator/operation-plan.refresh.test.md +199 -0
  198. package/src/orchestrator/operation-plan.refresh.test.ts +367 -0
  199. package/src/orchestrator/operation-plan.ts +709 -0
  200. package/src/orchestrator/operation-plan.update.test.md +485 -0
  201. package/src/orchestrator/operation-plan.update.test.ts +1066 -0
  202. package/src/orchestrator/operation-workset.ts +235 -583
  203. package/src/orchestrator/operation.ts +446 -904
  204. package/src/orchestrator/plan-test-builder.ts +267 -0
  205. package/src/project-model/abstractions.ts +118 -0
  206. package/src/project-model/backends/codebase.ts +365 -0
  207. package/src/project-model/backends/database.ts +440 -0
  208. package/src/project-model/errors.ts +81 -0
  209. package/src/project-model/factory.ts +24 -0
  210. package/src/project-model/index.ts +4 -0
  211. package/src/project-model/utils.test.ts +544 -0
  212. package/src/project-model/utils.ts +242 -0
  213. package/src/pubsub/abstractions.ts +10 -1
  214. package/src/pubsub/factory.ts +4 -4
  215. package/src/pubsub/index.ts +1 -0
  216. package/src/pubsub/manager.ts +49 -25
  217. package/src/pubsub/memory.ts +31 -0
  218. package/src/runner/abstractions.ts +38 -26
  219. package/src/runner/artifact-env.ts +17 -6
  220. package/src/runner/factory.ts +6 -6
  221. package/src/runner/force-abort.ts +3 -6
  222. package/src/runner/local.ts +79 -72
  223. package/src/runner/pulumi.ts +26 -63
  224. package/src/services.ts +214 -103
  225. package/src/shared/models/backend/index.ts +3 -1
  226. package/src/shared/models/backend/library.ts +12 -4
  227. package/src/shared/models/backend/project.ts +43 -23
  228. package/src/shared/models/backend/pulumi.ts +14 -0
  229. package/src/shared/models/backend/unlock-method.ts +1 -1
  230. package/src/shared/models/backend/well-known.ts +58 -0
  231. package/src/shared/models/base.ts +40 -109
  232. package/src/shared/models/errors.ts +82 -1
  233. package/src/shared/models/index.ts +3 -2
  234. package/src/shared/models/prisma.ts +36 -0
  235. package/src/shared/models/project/api-key.ts +37 -56
  236. package/src/shared/models/project/artifact.ts +15 -105
  237. package/src/shared/models/project/custom-status.ts +12 -0
  238. package/src/shared/models/project/index.ts +9 -9
  239. package/src/shared/models/project/lock.ts +10 -78
  240. package/src/shared/models/project/model.ts +32 -0
  241. package/src/shared/models/project/operation.ts +222 -99
  242. package/src/shared/models/project/page.ts +37 -48
  243. package/src/shared/models/project/secret.ts +29 -103
  244. package/src/shared/models/project/service-account.ts +12 -17
  245. package/src/shared/models/project/state.ts +100 -390
  246. package/src/shared/models/project/terminal.ts +75 -89
  247. package/src/shared/models/project/trigger.ts +13 -49
  248. package/src/shared/models/project/unlock-method.ts +21 -20
  249. package/src/shared/models/project/worker.ts +89 -88
  250. package/src/shared/resolvers/graph-resolver.ts +62 -26
  251. package/src/shared/resolvers/index.ts +1 -1
  252. package/src/shared/resolvers/input-hash.ts +24 -14
  253. package/src/shared/resolvers/input.ts +48 -6
  254. package/src/shared/resolvers/registry.ts +5 -4
  255. package/src/shared/resolvers/state.ts +12 -1
  256. package/src/shared/resolvers/validation.ts +29 -9
  257. package/src/shared/utils/index.ts +1 -1
  258. package/src/shared/utils/promise-tracker.ts +30 -3
  259. package/src/terminal/abstractions.ts +1 -1
  260. package/src/terminal/docker.ts +3 -3
  261. package/src/terminal/manager.ts +102 -118
  262. package/src/test-utils/database.ts +119 -0
  263. package/src/test-utils/index.ts +2 -0
  264. package/src/test-utils/services.ts +134 -0
  265. package/src/unlock/abstractions.ts +31 -0
  266. package/src/unlock/index.ts +2 -0
  267. package/src/unlock/memory.ts +27 -0
  268. package/src/worker/abstractions.ts +7 -4
  269. package/src/worker/docker.ts +14 -19
  270. package/src/worker/manager.ts +376 -79
  271. package/dist/chunk-RCB4AFGD.js.map +0 -1
  272. package/dist/chunk-WHALQHEZ.js +0 -2017
  273. package/dist/chunk-WHALQHEZ.js.map +0 -1
  274. package/src/business/backend-unlock.ts +0 -10
  275. package/src/common/performance.ts +0 -44
  276. package/src/hotstate/abstractions.ts +0 -48
  277. package/src/hotstate/factory.ts +0 -17
  278. package/src/hotstate/index.ts +0 -3
  279. package/src/hotstate/manager.ts +0 -192
  280. package/src/hotstate/memory.ts +0 -100
  281. package/src/hotstate/validation.ts +0 -101
  282. package/src/project/abstractions.ts +0 -102
  283. package/src/project/factory.ts +0 -11
  284. package/src/project/index.ts +0 -3
  285. package/src/project/local.ts +0 -469
  286. package/src/project/manager.ts +0 -574
  287. package/src/pubsub/local.ts +0 -36
  288. package/src/pubsub/validation.ts +0 -33
  289. package/src/shared/models/project/component.ts +0 -45
  290. package/src/shared/models/project/instance.ts +0 -74
  291. package/src/state/abstractions.ts +0 -450
  292. package/src/state/encryption.ts +0 -59
  293. package/src/state/factory.ts +0 -20
  294. package/src/state/index.ts +0 -6
  295. package/src/state/local/backend.ts +0 -299
  296. package/src/state/local/collection.ts +0 -342
  297. package/src/state/local/index.ts +0 -2
  298. package/src/state/manager.ts +0 -819
  299. package/src/state/repository/index.ts +0 -2
  300. package/src/state/repository/repository.index.ts +0 -193
  301. package/src/state/repository/repository.ts +0 -458
  302. /package/src/{state → database/local}/keyring.ts +0 -0
@@ -1,161 +1,297 @@
1
+ import type { CommonObjectMeta, UnitWorker } from "@highstate/contract"
1
2
  import type { Logger } from "pino"
2
- import type { LockManager } from "../lock"
3
+ import type {
4
+ DatabaseManager,
5
+ ProjectTransaction,
6
+ Worker,
7
+ WorkerVersion,
8
+ WorkerVersionLog,
9
+ } from "../database"
10
+ import type { PubSubManager } from "../pubsub"
3
11
  import type { WorkerManager } from "../worker"
4
- import type { InstanceStateService } from "./instance-state"
5
12
  import { randomBytes } from "node:crypto"
6
- import { v7 as uuidv7 } from "uuid"
13
+ import { PrismaClientKnownRequestError } from "@prisma/client/runtime/client"
14
+ import { createProjectLogger } from "../common"
7
15
  import {
16
+ extractDigestFromImage,
8
17
  getWorkerIdentity,
9
- type InstanceState,
10
- type ProjectApiKey,
11
- type ServiceAccount,
12
- type UnitWorker,
13
- type Worker,
14
- type WorkerUnitRegistration,
18
+ type WorkerUnitRegistrationEvent,
15
19
  } from "../shared"
16
- import { SAME_KEY, type StateManager } from "../state"
20
+ import { WorkerVersionNotFoundError } from "../shared/models/errors"
17
21
 
18
22
  export class WorkerService {
19
23
  constructor(
20
- private readonly stateManager: StateManager,
24
+ private readonly database: DatabaseManager,
21
25
  private readonly workerManager: WorkerManager,
22
- private readonly lockManager: LockManager,
23
- private readonly instanceStateService: InstanceStateService,
26
+ private readonly pubsubManager: PubSubManager,
24
27
  private readonly logger: Logger,
25
28
  ) {}
26
29
 
27
- async handleUnitWorker(
30
+ /**
31
+ * Updates unit worker registrations for an instance in a single transaction
32
+ * creates workers and worker versions as needed and cleans up removed registrations
33
+ *
34
+ * @param tx The transaction to use for database operations.
35
+ * @param projectId The ID of the project containing the instance.
36
+ * @param stateId The ID of the instance state to update registrations for.
37
+ * @param unitWorkers The list workers provided by the unit.
38
+ */
39
+ async updateUnitRegistrations(
40
+ tx: ProjectTransaction,
28
41
  projectId: string,
29
- state: InstanceState,
30
- unitWorker: UnitWorker,
42
+ stateId: string,
43
+ unitWorkers: UnitWorker[],
31
44
  ): Promise<void> {
32
- await this.lockManager.acquire(["worker-image", projectId, unitWorker.image], async () => {
33
- const existingRegistrations = await this.stateManager
34
- .getWorkerRegistrationRepository(projectId)
35
- .getManyItems(state.extra?.workerRegistrationIds ?? [])
36
-
37
- const existingReg = existingRegistrations.find(reg => reg.image === unitWorker.image)
38
- if (existingReg && JSON.stringify(existingReg.params) === JSON.stringify(unitWorker.params)) {
39
- this.logger.debug(
40
- `instance "%s" already registered with worker "%s" with the same parameters`,
41
- state.id,
42
- unitWorker.image,
43
- )
44
- return
45
- }
45
+ // parse images first
46
+ const parsedWorkers = unitWorkers.map(w => {
47
+ const digest = extractDigestFromImage(w.image)
48
+ const identity = getWorkerIdentity(w.image)
46
49
 
47
- let workerId = existingReg?.workerId
48
- if (!workerId) {
49
- workerId = await this.ensureWorkerCreated(projectId, unitWorker)
50
- }
50
+ return { ...w, digest, identity }
51
+ })
51
52
 
52
- const registration: WorkerUnitRegistration = {
53
- id: existingReg?.id ?? uuidv7(),
54
- meta: existingReg?.meta ?? {},
55
- image: unitWorker.image,
56
- params: unitWorker.params,
57
- instanceId: state.id,
58
- workerId,
59
- }
53
+ const logger = createProjectLogger(this.logger, projectId)
54
+
55
+ const eventsToPublish: { workerVersionId: string; event: WorkerUnitRegistrationEvent }[] = []
56
+
57
+ // query all registrations for the instance
58
+ const existingRegistrations = await tx.workerUnitRegistration.findMany({
59
+ where: { stateId },
60
+ select: { stateId: true, name: true, params: true, workerVersionId: true },
61
+ })
60
62
 
61
- const batch = this.stateManager.batch()
63
+ // the set of names we want to keep
64
+ const desiredNames = new Set(parsedWorkers.map(w => w.name))
62
65
 
63
- await this.stateManager
64
- .getWorkerRegistrationRepository(projectId)
65
- .putItem(registration, batch)
66
+ for (const worker of parsedWorkers) {
67
+ const workerRecord = await this.ensureWorker(tx, worker.identity)
68
+ const workerVersionRecord = await this.ensureWorkerVersion(tx, workerRecord, worker.digest)
66
69
 
67
- if (!existingReg) {
68
- // update the index if this is a new registration
69
- await this.stateManager
70
- .getWorkerRegistrationIndexRepository(projectId, workerId)
71
- .indexRepository.put(registration.id, SAME_KEY, batch)
70
+ const existing = existingRegistrations.find(r => r.name === worker.name)
71
+ const stringifiedParams = JSON.stringify(worker.params)
72
72
 
73
- // update the instance state with the new registration ID
74
- await this.instanceStateService.patchInstanceState(projectId, {
75
- id: state.id,
76
- extra: {
77
- workerRegistrationIds: [...(state.extra?.workerRegistrationIds ?? []), registration.id],
73
+ // create a new registration if it doesn't exist
74
+ if (!existing) {
75
+ await tx.workerUnitRegistration.create({
76
+ data: {
77
+ stateId,
78
+ name: worker.name,
79
+ params: worker.params,
80
+ workerVersionId: workerVersionRecord.id,
78
81
  },
79
82
  })
83
+
84
+ eventsToPublish.push({
85
+ workerVersionId: workerVersionRecord.id,
86
+ event: { type: "registered", instanceId: stateId, params: worker.params },
87
+ })
88
+
89
+ continue
80
90
  }
81
91
 
82
- await batch.write()
83
- })
84
- }
92
+ const paramsChanged = JSON.stringify(existing.params) !== stringifiedParams
93
+ const versionChanged = existing.workerVersionId !== workerVersionRecord.id
85
94
 
86
- private async ensureWorkerCreated(projectId: string, unitWorker: UnitWorker): Promise<string> {
87
- const identity = getWorkerIdentity(unitWorker.image)
95
+ // update existing registration if params or version changed
96
+ if (paramsChanged || versionChanged) {
97
+ await tx.workerUnitRegistration.update({
98
+ where: { stateId_name: { stateId, name: worker.name } },
99
+ data: {
100
+ params: worker.params,
101
+ workerVersionId: workerVersionRecord.id,
102
+ },
103
+ })
88
104
 
89
- return await this.lockManager.acquire(["worker-image", projectId, identity], async () => {
90
- // it is not critical to fetch all workers since their count is not expected to be high
91
- const workers = await this.stateManager.getWorkerRepository(projectId).getAllItems()
105
+ // deregister from old worker version if it changed
106
+ if (versionChanged) {
107
+ eventsToPublish.push({
108
+ workerVersionId: existing.workerVersionId,
109
+ event: { type: "deregistered", instanceId: stateId },
110
+ })
111
+ }
92
112
 
93
- const existingWorker = workers.find(worker => worker.image === unitWorker.image)
94
- if (existingWorker) {
95
- this.logger.debug(`worker with image "%s" already exists, reusing it`, unitWorker.image)
96
- return existingWorker.id
113
+ eventsToPublish.push({
114
+ workerVersionId: workerVersionRecord.id,
115
+ event: { type: "registered", instanceId: stateId, params: worker.params },
116
+ })
97
117
  }
118
+ }
98
119
 
99
- let serviceAccountId: string | undefined
100
-
101
- const siblingWorker = workers.find(worker => worker.identity === identity)
102
- if (siblingWorker) {
103
- this.logger.debug(
104
- `sibling worker with identity "%s" already exists, using its service account`,
105
- identity,
106
- )
120
+ // remove registrations that are no longer desired
121
+ for (const registration of existingRegistrations) {
122
+ if (!desiredNames.has(registration.name)) {
123
+ await tx.workerUnitRegistration.delete({
124
+ where: { stateId_name: { stateId, name: registration.name } },
125
+ })
107
126
 
108
- serviceAccountId = siblingWorker.serviceAccountId
127
+ eventsToPublish.push({
128
+ workerVersionId: registration.workerVersionId,
129
+ event: { type: "deregistered", instanceId: stateId },
130
+ })
109
131
  }
132
+ }
110
133
 
111
- const batch = this.stateManager.batch()
134
+ await this.cleanupUnusedWorkerVersions(tx)
112
135
 
113
- // create an empty service account if it is the first worker with this identity
114
- // the meta of the account will be updated by the worker itself
115
- if (!serviceAccountId) {
116
- const serviceAccount: ServiceAccount = {
117
- id: uuidv7(),
118
- meta: {},
119
- }
136
+ // publish events after transaction commits
137
+ for (const { workerVersionId, event } of eventsToPublish) {
138
+ void this.pubsubManager.publish(
139
+ ["worker-unit-registration", projectId, workerVersionId],
140
+ event,
141
+ )
142
+ }
120
143
 
121
- serviceAccountId = serviceAccount.id
122
- await this.stateManager
123
- .getServiceAccountRepository(projectId)
124
- .putItem(serviceAccount, batch)
125
- }
144
+ // ensure all worker versions are started
145
+ void this.workerManager.syncWorkers(projectId)
126
146
 
127
- const apiKey: ProjectApiKey = {
128
- id: uuidv7(),
129
- meta: {},
130
- token: randomBytes(32).toString("hex"),
131
- scopes: [
132
- {
133
- type: "service-account",
134
- actions: ["full"],
135
- serviceAccountIds: [serviceAccountId],
136
- },
137
- ],
138
- }
147
+ logger.info(`updated worker registrations for instance state "%s"`, stateId)
148
+ }
149
+
150
+ private async ensureWorker(tx: ProjectTransaction, identity: string): Promise<Worker> {
151
+ const existing = await tx.worker.findUnique({ where: { identity } })
152
+ if (existing) {
153
+ return existing
154
+ }
139
155
 
140
- await this.stateManager.getApiKeyRepository(projectId).putItem(apiKey, batch)
156
+ // create a new service account for the worker
157
+ const serviceAccount = await tx.serviceAccount.create({
158
+ select: { id: true },
159
+ data: {
160
+ meta: {
161
+ // this generic meta should be replaced by the worker when it starts
162
+ title: "Worker Service Account",
163
+ description: `Automatically created for worker "${identity}".`,
164
+ },
165
+ },
166
+ })
141
167
 
142
- const worker: Worker = {
143
- id: uuidv7(),
144
- meta: {},
145
- status: "starting",
146
- failedStartAttempts: 5,
168
+ return await tx.worker.create({
169
+ data: {
147
170
  identity,
148
- image: unitWorker.image,
149
- serviceAccountId,
171
+ serviceAccountId: serviceAccount.id,
172
+ },
173
+ })
174
+ }
175
+
176
+ private async ensureWorkerVersion(
177
+ tx: ProjectTransaction,
178
+ worker: Worker,
179
+ digest: string,
180
+ ): Promise<WorkerVersion> {
181
+ const existing = await tx.workerVersion.findUnique({ where: { digest } })
182
+ if (existing) {
183
+ return existing
184
+ }
185
+
186
+ // create an API key for the worker granting full access to its service account
187
+ const apiKey = await tx.apiKey.create({
188
+ data: {
189
+ meta: {
190
+ title: `Worker API Key for "${worker.identity}"`,
191
+ description: `Automatically created for worker "${worker.identity}" with digest "${digest}".`,
192
+ },
193
+ serviceAccountId: worker.serviceAccountId,
194
+ token: randomBytes(32).toString("hex"),
195
+ },
196
+ })
197
+
198
+ return await tx.workerVersion.create({
199
+ data: {
200
+ workerId: worker.id,
201
+ digest,
202
+ meta: {
203
+ title: "Worker Version",
204
+ description: `Worker version with digest ${digest}`,
205
+ },
150
206
  apiKeyId: apiKey.id,
207
+ },
208
+ })
209
+ }
210
+
211
+ /**
212
+ * Performs cleanup of unused worker versions and syncs workers for a project.
213
+ * This method should be called after operations that may leave unused workers.
214
+ *
215
+ * @param projectId The ID of the project to cleanup and sync workers for.
216
+ */
217
+ async cleanupWorkerUsageAndSync(projectId: string): Promise<void> {
218
+ const database = await this.database.forProject(projectId)
219
+
220
+ await database.$transaction(async tx => {
221
+ await this.cleanupUnusedWorkerVersions(tx)
222
+ })
223
+
224
+ // ensure all worker versions are started
225
+ void this.workerManager.syncWorkers(projectId)
226
+ }
227
+
228
+ private async cleanupUnusedWorkerVersions(tx: ProjectTransaction): Promise<void> {
229
+ const unused = await tx.workerVersion.findMany({
230
+ where: {
231
+ unitRegistrations: { none: {} },
232
+ },
233
+ select: { id: true },
234
+ })
235
+
236
+ if (unused.length === 0) {
237
+ return
238
+ }
239
+
240
+ await tx.workerVersion.deleteMany({
241
+ where: { id: { in: unused.map(u => u.id) } },
242
+ })
243
+ }
244
+
245
+ /**
246
+ * Updates the metadata for a worker version.
247
+ *
248
+ * @param projectId The ID of the project.
249
+ * @param workerVersionId The ID of the worker version to update.
250
+ * @param meta The new metadata to set.
251
+ */
252
+ async updateWorkerVersionMeta(
253
+ projectId: string,
254
+ workerVersionId: string,
255
+ meta: CommonObjectMeta,
256
+ ): Promise<void> {
257
+ const database = await this.database.forProject(projectId)
258
+ const logger = createProjectLogger(this.logger, projectId)
259
+
260
+ try {
261
+ await database.workerVersion.update({
262
+ where: { id: workerVersionId },
263
+ data: { meta },
264
+ })
265
+ } catch (error) {
266
+ if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") {
267
+ throw new WorkerVersionNotFoundError(projectId, workerVersionId)
151
268
  }
152
269
 
153
- await this.stateManager.getWorkerRepository(projectId).putItem(worker, batch)
154
- await batch.write()
270
+ throw error
271
+ }
155
272
 
156
- this.workerManager.startWorker(projectId, worker)
273
+ logger.info(`updated worker version metadata for "%s"`, workerVersionId)
274
+ }
275
+
276
+ /**
277
+ * Gets logs for a worker version.
278
+ *
279
+ * @param projectId The ID of the project.
280
+ * @param workerVersionId The ID of the worker version to get logs for.
281
+ */
282
+ async getWorkerVersionLogs(
283
+ projectId: string,
284
+ workerVersionId: string,
285
+ ): Promise<WorkerVersionLog[]> {
286
+ const database = await this.database.forProject(projectId)
157
287
 
158
- return worker.id
288
+ return await database.workerVersionLog.findMany({
289
+ where: {
290
+ workerVersionId,
291
+ },
292
+ orderBy: {
293
+ id: "asc",
294
+ },
159
295
  })
160
296
  }
161
297
  }
@@ -0,0 +1,65 @@
1
+ import type { Logger } from "pino"
2
+ import { mkdir } from "node:fs/promises"
3
+ import { dirname, relative } from "node:path"
4
+ import { resolvePackageJSON } from "pkg-types"
5
+ import z from "zod"
6
+
7
+ export const codebaseConfig = z.object({
8
+ HIGHSTATE_CODEBASE_PATH: z.string().optional(),
9
+ })
10
+
11
+ let codebasePath: Promise<string> | undefined
12
+ let codebaseHighstatePath: Promise<string> | undefined
13
+
14
+ async function _getCodebasePath(
15
+ config: z.infer<typeof codebaseConfig>,
16
+ logger: Logger,
17
+ ): Promise<string> {
18
+ if (config.HIGHSTATE_CODEBASE_PATH) {
19
+ return config.HIGHSTATE_CODEBASE_PATH
20
+ }
21
+
22
+ const packageJson = await resolvePackageJSON()
23
+ const path = dirname(packageJson)
24
+
25
+ if (path !== process.cwd()) {
26
+ const relativePath = relative(process.cwd(), path)
27
+ logger.info(`detected "%s" as codebase path`, relativePath)
28
+ }
29
+
30
+ return path
31
+ }
32
+
33
+ export async function getCodebasePath(
34
+ config: z.infer<typeof codebaseConfig>,
35
+ logger: Logger,
36
+ ): Promise<string> {
37
+ if (!codebasePath) {
38
+ codebasePath = _getCodebasePath(config, logger)
39
+ }
40
+
41
+ return codebasePath
42
+ }
43
+
44
+ async function _getCodebaseHighstatePath(
45
+ config: z.infer<typeof codebaseConfig>,
46
+ logger: Logger,
47
+ ): Promise<string> {
48
+ const path = await getCodebasePath(config, logger)
49
+
50
+ const highstatePath = `${path}/.highstate`
51
+ await mkdir(highstatePath, { recursive: true })
52
+
53
+ return highstatePath
54
+ }
55
+
56
+ export async function getCodebaseHighstatePath(
57
+ config: z.infer<typeof codebaseConfig>,
58
+ logger: Logger,
59
+ ): Promise<string> {
60
+ if (!codebaseHighstatePath) {
61
+ codebaseHighstatePath = _getCodebaseHighstatePath(config, logger)
62
+ }
63
+
64
+ return codebaseHighstatePath
65
+ }
@@ -1,4 +1,5 @@
1
- export * from "./utils"
1
+ export * from "./codebase"
2
2
  export * from "./local"
3
+ export * from "./logger"
3
4
  export * from "./tree"
4
- export * from "./performance"
5
+ export * from "./utils"
@@ -0,0 +1,5 @@
1
+ import type { Logger } from "pino"
2
+
3
+ export function createProjectLogger(logger: Logger, projectId: string): Logger {
4
+ return logger.child({ projectId }, { msgPrefix: `[project:${projectId}] ` })
5
+ }
@@ -25,8 +25,9 @@ export async function runWithRetryOnError<T>(
25
25
  }
26
26
 
27
27
  export class AbortError extends Error {
28
- constructor(options?: ErrorOptions) {
29
- super("Operation aborted", options)
28
+ constructor(message: string, options?: ErrorOptions) {
29
+ super(message, options)
30
+ this.name = "AbortError"
30
31
  }
31
32
  }
32
33
 
@@ -58,7 +59,7 @@ export function isAbortErrorLike(error: unknown): boolean {
58
59
 
59
60
  export function tryWrapAbortErrorLike(error: unknown): unknown {
60
61
  if (isAbortErrorLike(error)) {
61
- return new AbortError({ cause: error })
62
+ return new AbortError("Operation aborted", { cause: error })
62
63
  }
63
64
 
64
65
  return error
package/src/config.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { z } from "zod"
2
- import { libraryBackendConfig } from "./library"
3
- import { projectBackendConfig } from "./project"
4
- import { terminalBackendConfig } from "./terminal"
5
- import { runnerBackendConfig } from "./runner"
6
- import { stateBackendConfig, stateManagerConfig } from "./state"
7
2
  import { artifactBackendConfig } from "./artifact"
8
- import { pubSubBackendConfig } from "./pubsub"
3
+ import { projectUnlockServiceConfig } from "./business"
4
+ import { codebaseConfig } from "./common"
5
+ import { databaseConfig } from "./database"
6
+ import { libraryBackendConfig } from "./library"
9
7
  import { lockBackendConfig } from "./lock"
10
- import { hotStateBackendConfig } from "./hotstate"
8
+ import { pubSubBackendConfig } from "./pubsub"
9
+ import { runnerBackendConfig } from "./runner"
10
+ import { terminalBackendConfig } from "./terminal"
11
11
  import { workerBackendConfig, workerManagerConfig } from "./worker"
12
12
 
13
13
  const loggerConfig = z.object({
@@ -15,13 +15,12 @@ const loggerConfig = z.object({
15
15
  })
16
16
 
17
17
  const configSchema = z.object({
18
- ...hotStateBackendConfig.shape,
18
+ ...codebaseConfig.shape,
19
+ ...databaseConfig.shape,
19
20
  ...pubSubBackendConfig.shape,
20
21
  ...lockBackendConfig.shape,
21
22
  ...libraryBackendConfig.shape,
22
- ...projectBackendConfig.shape,
23
- ...stateBackendConfig.shape,
24
- ...stateManagerConfig.shape,
23
+ ...projectUnlockServiceConfig.shape,
25
24
  ...runnerBackendConfig.shape,
26
25
  ...terminalBackendConfig.shape,
27
26
  ...workerBackendConfig.shape,
@@ -40,5 +39,9 @@ export async function loadConfig(
40
39
  await import("dotenv/config")
41
40
  }
42
41
 
43
- return configSchema.parse(env)
42
+ try {
43
+ return configSchema.parse(env)
44
+ } catch (error) {
45
+ throw new Error("Failed to parse backend configuration", { cause: error })
46
+ }
44
47
  }
@@ -0,0 +1,72 @@
1
+
2
+ /* !!! This is code generated by Prisma. Do not edit directly. !!! */
3
+ /* eslint-disable */
4
+ // @ts-nocheck
5
+ /**
6
+ * This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
7
+ *
8
+ * 🟢 You can import this file directly.
9
+ */
10
+
11
+ import * as process from 'node:process'
12
+ import * as path from 'node:path'
13
+ import { fileURLToPath } from 'node:url'
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
15
+
16
+ import * as runtime from "@prisma/client/runtime/client"
17
+ import * as $Enums from "./enums.ts"
18
+ import * as $Class from "./internal/class.ts"
19
+ import * as Prisma from "./internal/prismaNamespace.ts"
20
+
21
+ export * as $Enums from './enums.ts'
22
+ /**
23
+ * ## Prisma Client
24
+ *
25
+ * Type-safe database client for TypeScript
26
+ * @example
27
+ * ```
28
+ * const prisma = new PrismaClient()
29
+ * // Fetch zero or more UserWorkspaseLayouts
30
+ * const userWorkspaseLayouts = await prisma.userWorkspaseLayout.findMany()
31
+ * ```
32
+ *
33
+ * Read more in our [docs](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client).
34
+ */
35
+ export const PrismaClient = $Class.getPrismaClientClass(__dirname)
36
+ export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
37
+ export { Prisma }
38
+
39
+
40
+
41
+ /**
42
+ * Model UserWorkspaseLayout
43
+ *
44
+ */
45
+ export type UserWorkspaseLayout = Prisma.UserWorkspaseLayoutModel
46
+ /**
47
+ * Model Library
48
+ *
49
+ */
50
+ export type Library = Prisma.LibraryModel
51
+ /**
52
+ * Model Project
53
+ *
54
+ */
55
+ export type Project = Prisma.ProjectModel
56
+ /**
57
+ * Model ProjectSpace
58
+ *
59
+ */
60
+ export type ProjectSpace = Prisma.ProjectSpaceModel
61
+ /**
62
+ * Model ProjectModelStorage
63
+ *
64
+ */
65
+ export type ProjectModelStorage = Prisma.ProjectModelStorageModel
66
+ /**
67
+ * Model PulumiBackend
68
+ *
69
+ */
70
+ export type PulumiBackend = Prisma.PulumiBackendModel
71
+
72
+