@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
@@ -0,0 +1,160 @@
1
+ import type { UnitPage, UnitTerminal, UnitTrigger } from "@highstate/contract"
2
+ import type { DatabaseManager, ProjectTransaction } from "../database"
3
+
4
+ export class UnitExtraService {
5
+ constructor(private readonly database: DatabaseManager) {}
6
+
7
+ /**
8
+ * Processes unit terminals within an existing transaction.
9
+ *
10
+ * @param tx The database transaction to use.
11
+ * @param stateId The ID of the instance state.
12
+ * @param unitTerminals The unit terminals to process.
13
+ * @returns Array of terminal IDs that are active for this instance.
14
+ */
15
+ async processUnitTerminals(
16
+ tx: ProjectTransaction,
17
+ stateId: string,
18
+ unitTerminals: UnitTerminal[],
19
+ ): Promise<string[]> {
20
+ const terminalIds: string[] = []
21
+
22
+ // upsert terminals
23
+ for (const unit of unitTerminals) {
24
+ const terminal = await tx.terminal.upsert({
25
+ where: { stateId_name: { stateId, name: unit.name } },
26
+ create: {
27
+ stateId,
28
+ name: unit.name,
29
+ meta: unit.meta ?? {},
30
+ spec: unit.spec,
31
+ status: "active",
32
+ },
33
+ update: {
34
+ meta: unit.meta ?? {},
35
+ spec: unit.spec,
36
+ status: "active",
37
+ },
38
+ select: { id: true },
39
+ })
40
+ terminalIds.push(terminal.id)
41
+ }
42
+
43
+ // mark dangling terminals as unavailable
44
+ const unitNames = unitTerminals.map(u => u.name)
45
+ await tx.terminal.updateMany({
46
+ where: {
47
+ stateId,
48
+ name: { notIn: unitNames },
49
+ status: "active",
50
+ },
51
+ data: { status: "unavailable" },
52
+ })
53
+
54
+ return terminalIds
55
+ }
56
+
57
+ /**
58
+ * Processes unit pages within an existing transaction.
59
+ *
60
+ * @param tx The database transaction to use.
61
+ * @param stateId The ID of the instance state.
62
+ * @param unitPages The unit pages to process.
63
+ * @returns Array of page IDs that exist for this instance.
64
+ */
65
+ async processUnitPages(
66
+ tx: ProjectTransaction,
67
+ stateId: string,
68
+ unitPages: UnitPage[],
69
+ ): Promise<string[]> {
70
+ const pageIds: string[] = []
71
+
72
+ // upsert pages
73
+ for (const unit of unitPages) {
74
+ const page = await tx.page.upsert({
75
+ where: { stateId_name: { stateId, name: unit.name } },
76
+ create: {
77
+ stateId,
78
+ name: unit.name,
79
+ meta: unit.meta ?? {},
80
+ content: unit.content,
81
+ },
82
+ update: {
83
+ meta: unit.meta ?? {},
84
+ content: unit.content,
85
+ },
86
+ select: { id: true },
87
+ })
88
+ pageIds.push(page.id)
89
+ }
90
+
91
+ // delete dangling pages
92
+ const unitNames = unitPages.map(u => u.name)
93
+ await tx.page.deleteMany({
94
+ where: {
95
+ stateId,
96
+ name: { notIn: unitNames },
97
+ },
98
+ })
99
+
100
+ return pageIds
101
+ }
102
+
103
+ /**
104
+ * Processes unit triggers within an existing transaction.
105
+ *
106
+ * @param tx The database transaction to use.
107
+ * @param stateId The ID of the instance state.
108
+ * @param unitTriggers The unit triggers to process.
109
+ * @returns Array of trigger IDs that exist for this instance.
110
+ */
111
+ async processUnitTriggers(
112
+ tx: ProjectTransaction,
113
+ stateId: string,
114
+ unitTriggers: UnitTrigger[],
115
+ ): Promise<string[]> {
116
+ const triggerIds: string[] = []
117
+
118
+ // upsert triggers
119
+ for (const unit of unitTriggers) {
120
+ const trigger = await tx.trigger.upsert({
121
+ where: { stateId_name: { stateId, name: unit.name } },
122
+ create: {
123
+ stateId,
124
+ name: unit.name,
125
+ meta: unit.meta ?? {},
126
+ spec: unit.spec,
127
+ },
128
+ update: {
129
+ meta: unit.meta ?? {},
130
+ spec: unit.spec,
131
+ },
132
+ select: { id: true },
133
+ })
134
+ triggerIds.push(trigger.id)
135
+ }
136
+
137
+ // delete dangling triggers
138
+ const unitNames = unitTriggers.map(u => u.name)
139
+ await tx.trigger.deleteMany({
140
+ where: {
141
+ stateId,
142
+ name: { notIn: unitNames },
143
+ },
144
+ })
145
+
146
+ return triggerIds
147
+ }
148
+
149
+ /**
150
+ * Gets all triggers for a specific instance.
151
+ *
152
+ * @param projectId The project ID.
153
+ * @param stateId The ID of the instance state to get triggers for.
154
+ */
155
+ async getInstanceTriggers(projectId: string, stateId: string) {
156
+ const database = await this.database.forProject(projectId)
157
+
158
+ return database.trigger.findMany({ where: { stateId } })
159
+ }
160
+ }
@@ -0,0 +1,391 @@
1
+ import type { UnitWorker } from "@highstate/contract"
2
+ import type { Worker, WorkerVersion } from "../database"
3
+ import type { PubSubManager } from "../pubsub"
4
+ import type { WorkerManager } from "../worker"
5
+ import { randomBytes } from "node:crypto"
6
+ import { createId } from "@paralleldrive/cuid2"
7
+ import { describe, vi } from "vitest"
8
+ import { extractDigestFromImage } from "../shared"
9
+ import { test } from "../test-utils"
10
+ import { WorkerService } from "./worker"
11
+
12
+ const workerTest = test.extend<{
13
+ workerService: WorkerService
14
+
15
+ createWorker(): Promise<Worker>
16
+
17
+ createWorkerVersion: (
18
+ worker: Worker,
19
+ overrides?: Pick<Partial<WorkerVersion>, "digest">,
20
+ ) => Promise<WorkerVersion>
21
+
22
+ createMockUnitWorker: (overrides?: Partial<UnitWorker>) => UnitWorker
23
+ }>({
24
+ workerService: async ({ database, logger }, use) => {
25
+ const workerManager = vi.mockObject({ syncWorkers: vi.fn() } as unknown as WorkerManager)
26
+
27
+ const workerService = new WorkerService(
28
+ database,
29
+ workerManager,
30
+ vi.mockObject({ subscribe: vi.fn(), publish: vi.fn() } as unknown as PubSubManager),
31
+ logger.child({ service: "WorkerService" }),
32
+ )
33
+
34
+ await use(workerService)
35
+ },
36
+
37
+ createWorker: async ({ projectDatabase }, use) => {
38
+ const createWorker = async () => {
39
+ return await projectDatabase.worker.create({
40
+ data: {
41
+ identity: `ghcr.io/org/${createId()}`,
42
+ serviceAccount: {
43
+ create: {
44
+ meta: {
45
+ title: "Test Worker Service Account",
46
+ },
47
+ },
48
+ },
49
+ },
50
+ })
51
+ }
52
+
53
+ await use(createWorker)
54
+ },
55
+
56
+ createWorkerVersion: async ({ projectDatabase }, use) => {
57
+ const createWorkerVersion = async (
58
+ worker: Worker,
59
+ overrides: Pick<Partial<WorkerVersion>, "digest"> = {},
60
+ ) => {
61
+ return await projectDatabase.workerVersion.create({
62
+ data: {
63
+ worker: { connect: worker },
64
+ digest: createId(),
65
+ meta: {
66
+ title: "Test Worker Version",
67
+ description: "Test worker version for testing purposes",
68
+ },
69
+ apiKey: {
70
+ create: {
71
+ meta: {
72
+ title: "Test Worker API Key",
73
+ },
74
+ serviceAccountId: worker.serviceAccountId,
75
+ token: createId(),
76
+ },
77
+ },
78
+ ...overrides,
79
+ },
80
+ })
81
+ }
82
+
83
+ await use(createWorkerVersion)
84
+ },
85
+
86
+ createMockUnitWorker: async ({}, use) => {
87
+ const createMockUnitWorker = (overrides: Partial<UnitWorker> = {}): UnitWorker => ({
88
+ name: "test-worker",
89
+ image: `ghcr.io/org/${createId()}@sha256:${randomBytes(32).toString("hex")}`,
90
+ params: { key: "value" },
91
+ ...overrides,
92
+ })
93
+
94
+ await use(createMockUnitWorker)
95
+ },
96
+ })
97
+
98
+ describe("updateUnitRegistrations", () => {
99
+ workerTest(
100
+ "creates new worker registrations for unit workers",
101
+ async ({
102
+ workerService,
103
+ project,
104
+ projectDatabase,
105
+ createInstanceState,
106
+ createMockUnitWorker,
107
+ expect,
108
+ }) => {
109
+ // arrange
110
+ const unitWorker = createMockUnitWorker()
111
+ const instance = await createInstanceState(project.id)
112
+
113
+ // act
114
+ await projectDatabase.$transaction(async tx => {
115
+ await workerService.updateUnitRegistrations(tx, project.id, instance.id, [unitWorker])
116
+ })
117
+
118
+ // assert
119
+ const registrations = await projectDatabase.workerUnitRegistration.findMany({
120
+ where: { stateId: instance.id },
121
+ })
122
+
123
+ // check that the registration was created with the correct data
124
+ expect(registrations).toHaveLength(1)
125
+ expect(registrations[0].name).toBe(unitWorker.name)
126
+ expect(registrations[0].params).toEqual(unitWorker.params)
127
+ expect(registrations[0].workerVersionId).toBeDefined()
128
+ expect(registrations[0].stateId).toBe(instance.id)
129
+ },
130
+ )
131
+
132
+ workerTest(
133
+ "updates existing worker registrations with new params",
134
+ async ({
135
+ workerService,
136
+ project,
137
+ projectDatabase,
138
+ createInstanceState,
139
+ createWorker,
140
+ createWorkerVersion,
141
+ createMockUnitWorker,
142
+ expect,
143
+ }) => {
144
+ // arrange
145
+ const unitWorker = createMockUnitWorker({ params: { key: "newValue" } })
146
+ const instance = await createInstanceState(project.id)
147
+
148
+ // create initial worker and version
149
+ const worker = await createWorker()
150
+ const workerVersion = await createWorkerVersion(worker, {
151
+ digest: extractDigestFromImage(unitWorker.image),
152
+ })
153
+
154
+ // create an initial registration
155
+ await projectDatabase.workerUnitRegistration.create({
156
+ data: {
157
+ stateId: instance.id,
158
+ name: unitWorker.name,
159
+ params: { key: "oldValue" },
160
+ workerVersionId: workerVersion.id,
161
+ },
162
+ })
163
+
164
+ // act
165
+ await projectDatabase.$transaction(async tx => {
166
+ await workerService.updateUnitRegistrations(tx, project.id, instance.id, [unitWorker])
167
+ })
168
+
169
+ // assert
170
+ const updatedRegistration = await projectDatabase.workerUnitRegistration.findFirst({
171
+ where: { stateId: instance.id, name: unitWorker.name },
172
+ })
173
+
174
+ expect(updatedRegistration).toBeDefined()
175
+ expect(updatedRegistration?.params).toEqual(unitWorker.params)
176
+ expect(updatedRegistration?.workerVersionId).toBe(workerVersion.id)
177
+ },
178
+ )
179
+
180
+ workerTest(
181
+ "creates new worker version while keeping existing one when both are in use",
182
+ async ({
183
+ workerService,
184
+ project,
185
+ projectDatabase,
186
+ createInstanceState,
187
+ createWorker,
188
+ createWorkerVersion,
189
+ createMockUnitWorker,
190
+ expect,
191
+ }) => {
192
+ // arrange
193
+ const oldDigest = randomBytes(32).toString("hex")
194
+ const newDigest = randomBytes(32).toString("hex")
195
+
196
+ const worker = await createWorker()
197
+ const oldWorkerVersion = await createWorkerVersion(worker, { digest: oldDigest })
198
+
199
+ // create two instances, both using the same worker version initially
200
+ const instance1 = await createInstanceState(project.id)
201
+ const instance2 = await createInstanceState(project.id)
202
+
203
+ // create registrations for both instances using the old version
204
+ await projectDatabase.workerUnitRegistration.create({
205
+ data: {
206
+ stateId: instance1.id,
207
+ name: "test-worker",
208
+ params: { key: "value1" },
209
+ workerVersionId: oldWorkerVersion.id,
210
+ },
211
+ })
212
+
213
+ await projectDatabase.workerUnitRegistration.create({
214
+ data: {
215
+ stateId: instance2.id,
216
+ name: "test-worker",
217
+ params: { key: "value2" },
218
+ workerVersionId: oldWorkerVersion.id,
219
+ },
220
+ })
221
+
222
+ // act - update only instance1 to use new worker version
223
+ const newUnitWorker = createMockUnitWorker({
224
+ image: `${worker.identity}@sha256:${newDigest}`,
225
+ params: { key: "newValue" },
226
+ })
227
+
228
+ await projectDatabase.$transaction(async tx => {
229
+ await workerService.updateUnitRegistrations(tx, project.id, instance1.id, [newUnitWorker])
230
+ })
231
+
232
+ // assert
233
+ const instance1Registration = await projectDatabase.workerUnitRegistration.findFirst({
234
+ where: { stateId: instance1.id, name: "test-worker" },
235
+ include: { workerVersion: true },
236
+ })
237
+
238
+ const instance2Registration = await projectDatabase.workerUnitRegistration.findFirst({
239
+ where: { stateId: instance2.id, name: "test-worker" },
240
+ include: { workerVersion: true },
241
+ })
242
+
243
+ // verify instance1 now uses new version
244
+ expect(instance1Registration?.workerVersion.digest).toBe(newDigest)
245
+ expect(instance1Registration?.params).toEqual({ key: "newValue" })
246
+
247
+ // verify instance2 still uses old version
248
+ expect(instance2Registration?.workerVersion.digest).toBe(oldDigest)
249
+ expect(instance2Registration?.params).toEqual({ key: "value2" })
250
+
251
+ // verify both worker versions still exist (no cleanup since both are in use)
252
+ const allVersions = await projectDatabase.workerVersion.findMany({
253
+ where: { workerId: worker.id },
254
+ })
255
+ expect(allVersions).toHaveLength(2)
256
+ expect(allVersions.map(v => v.digest).sort()).toEqual([oldDigest, newDigest].sort())
257
+ },
258
+ )
259
+
260
+ workerTest(
261
+ "creates new worker version and deletes old unused one",
262
+ async ({
263
+ workerService,
264
+ project,
265
+ projectDatabase,
266
+ createInstanceState,
267
+ createWorker,
268
+ createWorkerVersion,
269
+ createMockUnitWorker,
270
+ expect,
271
+ }) => {
272
+ // arrange
273
+ const oldDigest = randomBytes(32).toString("hex")
274
+ const newDigest = randomBytes(32).toString("hex")
275
+
276
+ const worker = await createWorker()
277
+ const oldWorkerVersion = await createWorkerVersion(worker, { digest: oldDigest })
278
+
279
+ const instance = await createInstanceState(project.id)
280
+
281
+ // create registration using old version
282
+ await projectDatabase.workerUnitRegistration.create({
283
+ data: {
284
+ stateId: instance.id,
285
+ name: "test-worker",
286
+ params: { key: "oldValue" },
287
+ workerVersionId: oldWorkerVersion.id,
288
+ },
289
+ })
290
+
291
+ // act - update to use new worker version
292
+ const newUnitWorker = createMockUnitWorker({
293
+ image: `${worker.identity}@sha256:${newDigest}`,
294
+ params: { key: "newValue" },
295
+ })
296
+
297
+ await projectDatabase.$transaction(async tx => {
298
+ await workerService.updateUnitRegistrations(tx, project.id, instance.id, [newUnitWorker])
299
+ })
300
+
301
+ // assert
302
+ const registration = await projectDatabase.workerUnitRegistration.findFirst({
303
+ where: { stateId: instance.id, name: "test-worker" },
304
+ include: { workerVersion: true },
305
+ })
306
+
307
+ // verify registration now uses new version
308
+ expect(registration?.workerVersion.digest).toBe(newDigest)
309
+ expect(registration?.params).toEqual({ key: "newValue" })
310
+
311
+ // verify old version was cleaned up
312
+ const oldVersion = await projectDatabase.workerVersion.findFirst({
313
+ where: { digest: oldDigest, workerId: worker.id },
314
+ })
315
+ expect(oldVersion).toBeNull()
316
+
317
+ // verify new version exists
318
+ const newVersion = await projectDatabase.workerVersion.findFirst({
319
+ where: { digest: newDigest, workerId: worker.id },
320
+ })
321
+ expect(newVersion).toBeDefined()
322
+ },
323
+ )
324
+
325
+ workerTest(
326
+ "removes all registrations and cleans up unused worker versions when unit workers list is empty",
327
+ async ({
328
+ workerService,
329
+ project,
330
+ projectDatabase,
331
+ createInstanceState,
332
+ createWorker,
333
+ createWorkerVersion,
334
+ expect,
335
+ }) => {
336
+ // arrange
337
+ const worker1 = await createWorker()
338
+ const worker2 = await createWorker()
339
+
340
+ const digest1 = randomBytes(32).toString("hex")
341
+ const digest2 = randomBytes(32).toString("hex")
342
+
343
+ const version1 = await createWorkerVersion(worker1, { digest: digest1 })
344
+ const version2 = await createWorkerVersion(worker2, { digest: digest2 })
345
+
346
+ const instance = await createInstanceState(project.id)
347
+
348
+ console.log(instance.id, version1.id, version2.id)
349
+
350
+ // create multiple registrations
351
+ await projectDatabase.workerUnitRegistration.createMany({
352
+ data: [
353
+ {
354
+ stateId: instance.id,
355
+ name: "worker1",
356
+ params: { key: "value1" },
357
+ workerVersionId: version1.id,
358
+ },
359
+ {
360
+ stateId: instance.id,
361
+ name: "worker2",
362
+ params: { key: "value2" },
363
+ workerVersionId: version2.id,
364
+ },
365
+ ],
366
+ })
367
+
368
+ // act - update with empty workers list
369
+ await projectDatabase.$transaction(async tx => {
370
+ await workerService.updateUnitRegistrations(tx, project.id, instance.id, [])
371
+ })
372
+
373
+ // assert
374
+ const remainingRegistrations = await projectDatabase.workerUnitRegistration.findMany({
375
+ where: { stateId: instance.id },
376
+ })
377
+ expect(remainingRegistrations).toHaveLength(0)
378
+
379
+ // verify unused worker versions were cleaned up
380
+ const remainingVersions = await projectDatabase.workerVersion.findMany({
381
+ where: {
382
+ OR: [
383
+ { digest: digest1, workerId: worker1.id },
384
+ { digest: digest2, workerId: worker2.id },
385
+ ],
386
+ },
387
+ })
388
+ expect(remainingVersions).toHaveLength(0)
389
+ },
390
+ )
391
+ })