@highstate/backend 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.
Files changed (331) hide show
  1. package/dist/{chunk-OU5OQBLB.js → chunk-I7BWSAN6.js} +3 -28
  2. package/dist/{chunk-OU5OQBLB.js.map → chunk-I7BWSAN6.js.map} +1 -1
  3. package/dist/chunk-RC6Q3XQQ.js +1547 -0
  4. package/dist/chunk-RC6Q3XQQ.js.map +1 -0
  5. package/dist/chunk-VB4YL327.js +139 -0
  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 +2 -1
  10. package/dist/index.js +7590 -7289
  11. package/dist/index.js.map +1 -1
  12. package/dist/library/package-resolution-worker.js +1 -1
  13. package/dist/library/package-resolution-worker.js.map +1 -1
  14. package/dist/library/worker/main.js +35 -29
  15. package/dist/library/worker/main.js.map +1 -1
  16. package/dist/shared/index.js +2 -2
  17. package/package.json +18 -9
  18. package/prisma/backend/_schema/layout.prisma +7 -0
  19. package/prisma/backend/_schema/library.prisma +17 -0
  20. package/prisma/backend/_schema/project.prisma +101 -0
  21. package/prisma/backend/_schema/pulumi.prisma +17 -0
  22. package/prisma/backend/postgresql/main.prisma +17 -0
  23. package/prisma/backend/sqlite/main.prisma +17 -0
  24. package/prisma/backend/sqlite/migrations/20250817070609_initiial/migration.sql +34 -0
  25. package/prisma/backend/sqlite/migrations/20250817104948_add_fields/migration.sql +59 -0
  26. package/prisma/backend/sqlite/migrations/20250818082732_add_models/migration.sql +41 -0
  27. package/prisma/backend/sqlite/migrations/20250818083106_a/migration.sql +19 -0
  28. package/prisma/backend/sqlite/migrations/20250818101945_hi/migration.sql +1 -0
  29. package/prisma/backend/sqlite/migrations/20250819082315_a/migration.sql +5 -0
  30. package/prisma/backend/sqlite/migrations/migration_lock.toml +3 -0
  31. package/prisma/project/api-key.prisma +32 -0
  32. package/prisma/project/artifact.prisma +52 -0
  33. package/prisma/project/custom-status.prisma +46 -0
  34. package/prisma/project/evaluation.prisma +45 -0
  35. package/prisma/project/instance.prisma +157 -0
  36. package/prisma/project/layout.prisma +23 -0
  37. package/prisma/project/lock.prisma +18 -0
  38. package/prisma/project/main.prisma +17 -0
  39. package/prisma/project/migrations/20250816081310_initial/migration.sql +300 -0
  40. package/prisma/project/migrations/20250816082523_test/migration.sql +72 -0
  41. package/prisma/project/migrations/20250818065643_update/migration.sql +42 -0
  42. package/prisma/project/migrations/20250818070758_a/migration.sql +8 -0
  43. package/prisma/project/migrations/20250818070913_a/migration.sql +8 -0
  44. package/prisma/project/migrations/20250818082720_add_motels/migration.sql +11 -0
  45. package/prisma/project/migrations/20250818112523_hello/migration.sql +35 -0
  46. package/prisma/project/migrations/20250819082305_a/migration.sql +14 -0
  47. package/prisma/project/migrations/20250819165004_add_missing_fields/migration.sql +216 -0
  48. package/prisma/project/migrations/20250819171309_a/migration.sql +22 -0
  49. package/prisma/project/migrations/20250820113949_a/migration.sql +66 -0
  50. package/prisma/project/migrations/20250820144256_b/migration.sql +31 -0
  51. package/prisma/project/migrations/20250820145547_a/migration.sql +24 -0
  52. package/prisma/project/migrations/20250820182517_b/migration.sql +2 -0
  53. package/prisma/project/migrations/20250821172324_a/migration.sql +2 -0
  54. package/prisma/project/migrations/20250822081339_a/migration.sql +219 -0
  55. package/prisma/project/migrations/20250822083742_b/migration.sql +1 -0
  56. package/prisma/project/migrations/20250822105134_boom/migration.sql +1 -0
  57. package/prisma/project/migrations/20250822141028_b/migration.sql +1 -0
  58. package/prisma/project/migrations/20250822142342_b/migration.sql +16 -0
  59. package/prisma/project/migrations/20250824072720_a/migration.sql +1 -0
  60. package/prisma/project/migrations/20250824093656_b/migration.sql +21 -0
  61. package/prisma/project/migrations/20250825082518_a/migration.sql +1 -0
  62. package/prisma/project/migrations/20250825085343_b/migration.sql +1 -0
  63. package/prisma/project/migrations/20250825091312_a/migration.sql +1 -0
  64. package/prisma/project/migrations/20250903095431_hi/migration.sql +44 -0
  65. package/prisma/project/migrations/20250903174255_a/migration.sql +24 -0
  66. package/prisma/project/migrations/20250908095205_hi/migration.sql +18 -0
  67. package/prisma/project/migrations/20250909155857_hi/migration.sql +15 -0
  68. package/prisma/project/migrations/migration_lock.toml +3 -0
  69. package/prisma/project/model.prisma +37 -0
  70. package/prisma/project/operation.prisma +148 -0
  71. package/prisma/project/page.prisma +49 -0
  72. package/prisma/project/secret.prisma +54 -0
  73. package/prisma/project/service-account.prisma +42 -0
  74. package/prisma/project/terminal.prisma +107 -0
  75. package/prisma/project/trigger.prisma +37 -0
  76. package/prisma/project/unlock-method.prisma +46 -0
  77. package/prisma/project/worker.prisma +169 -0
  78. package/src/artifact/abstractions.ts +13 -13
  79. package/src/artifact/encryption.ts +30 -54
  80. package/src/artifact/factory.ts +6 -9
  81. package/src/artifact/local.ts +33 -46
  82. package/src/business/api-key.ts +24 -36
  83. package/src/business/artifact.test.ts +978 -0
  84. package/src/business/artifact.ts +136 -216
  85. package/src/business/evaluation.ts +328 -0
  86. package/src/business/index.ts +5 -2
  87. package/src/business/instance-lock.test.ts +1060 -0
  88. package/src/business/instance-lock.ts +387 -78
  89. package/src/business/instance-state.test.ts +735 -0
  90. package/src/business/instance-state.ts +582 -337
  91. package/src/business/operation.test.ts +439 -0
  92. package/src/business/operation.ts +174 -208
  93. package/src/business/project-model.ts +258 -0
  94. package/src/business/project-unlock.ts +168 -126
  95. package/src/business/project.ts +287 -179
  96. package/src/business/secret.test.ts +469 -130
  97. package/src/business/secret.ts +177 -217
  98. package/src/business/settings.test.ts +695 -0
  99. package/src/business/settings.ts +855 -0
  100. package/src/business/terminal-session.ts +90 -0
  101. package/src/business/unit-extra.test.ts +539 -0
  102. package/src/business/unit-extra.ts +160 -0
  103. package/src/business/worker.test.ts +356 -579
  104. package/src/business/worker.ts +238 -339
  105. package/src/common/codebase.ts +65 -0
  106. package/src/common/index.ts +3 -5
  107. package/src/common/logger.ts +5 -0
  108. package/src/common/utils.ts +4 -3
  109. package/src/config.ts +10 -11
  110. package/src/database/_generated/backend/postgresql/client.ts +72 -0
  111. package/src/database/_generated/backend/postgresql/commonInputTypes.ts +350 -0
  112. package/src/database/_generated/backend/postgresql/enums.ts +13 -0
  113. package/src/database/_generated/backend/postgresql/internal/class.ts +320 -0
  114. package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +1238 -0
  115. package/src/database/_generated/backend/postgresql/models/Library.ts +1263 -0
  116. package/src/database/_generated/backend/postgresql/models/Project.ts +2175 -0
  117. package/src/database/_generated/backend/postgresql/models/ProjectModelStorage.ts +1263 -0
  118. package/src/database/_generated/backend/postgresql/models/ProjectSpace.ts +1602 -0
  119. package/src/database/_generated/backend/postgresql/models/PulumiBackend.ts +1263 -0
  120. package/src/database/_generated/backend/postgresql/models/UserWorkspaseLayout.ts +1065 -0
  121. package/src/database/_generated/backend/postgresql/models.ts +16 -0
  122. package/src/database/_generated/backend/postgresql/pjtg.ts +182 -0
  123. package/src/database/_generated/backend/sqlite/client.ts +72 -0
  124. package/src/database/_generated/backend/sqlite/commonInputTypes.ts +331 -0
  125. package/src/database/_generated/backend/sqlite/enums.ts +13 -0
  126. package/src/database/_generated/backend/sqlite/internal/class.ts +318 -0
  127. package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +1207 -0
  128. package/src/database/_generated/backend/sqlite/models/Library.ts +1261 -0
  129. package/src/database/_generated/backend/sqlite/models/Project.ts +2169 -0
  130. package/src/database/_generated/backend/sqlite/models/ProjectModelStorage.ts +1261 -0
  131. package/src/database/_generated/backend/sqlite/models/ProjectSpace.ts +1599 -0
  132. package/src/database/_generated/backend/sqlite/models/PulumiBackend.ts +1261 -0
  133. package/src/database/_generated/backend/sqlite/models/UserWorkspaseLayout.ts +1063 -0
  134. package/src/database/_generated/backend/sqlite/models.ts +16 -0
  135. package/src/database/_generated/backend/sqlite/pjtg.ts +182 -0
  136. package/src/database/_generated/project/client.ts +204 -0
  137. package/src/database/_generated/project/commonInputTypes.ts +827 -0
  138. package/src/database/_generated/project/enums.ts +104 -0
  139. package/src/database/_generated/project/internal/class.ts +479 -0
  140. package/src/database/_generated/project/internal/prismaNamespace.ts +2974 -0
  141. package/src/database/_generated/project/models/ApiKey.ts +1506 -0
  142. package/src/database/_generated/project/models/Artifact.ts +2051 -0
  143. package/src/database/_generated/project/models/HubModel.ts +1125 -0
  144. package/src/database/_generated/project/models/InstanceCustomStatus.ts +1713 -0
  145. package/src/database/_generated/project/models/InstanceEvaluationState.ts +1312 -0
  146. package/src/database/_generated/project/models/InstanceLock.ts +1268 -0
  147. package/src/database/_generated/project/models/InstanceModel.ts +1125 -0
  148. package/src/database/_generated/project/models/InstanceOperationState.ts +1707 -0
  149. package/src/database/_generated/project/models/InstanceState.ts +4613 -0
  150. package/src/database/_generated/project/models/Operation.ts +1647 -0
  151. package/src/database/_generated/project/models/OperationLog.ts +1455 -0
  152. package/src/database/_generated/project/models/Page.ts +1838 -0
  153. package/src/database/_generated/project/models/Secret.ts +1692 -0
  154. package/src/database/_generated/project/models/ServiceAccount.ts +2165 -0
  155. package/src/database/_generated/project/models/Terminal.ts +2038 -0
  156. package/src/database/_generated/project/models/TerminalSession.ts +1454 -0
  157. package/src/database/_generated/project/models/TerminalSessionLog.ts +1280 -0
  158. package/src/database/_generated/project/models/Trigger.ts +1430 -0
  159. package/src/database/_generated/project/models/UnlockMethod.ts +1220 -0
  160. package/src/database/_generated/project/models/UserCompositeViewport.ts +1280 -0
  161. package/src/database/_generated/project/models/UserProjectViewport.ts +1059 -0
  162. package/src/database/_generated/project/models/Worker.ts +1459 -0
  163. package/src/database/_generated/project/models/WorkerUnitRegistration.ts +1524 -0
  164. package/src/database/_generated/project/models/WorkerVersion.ts +1974 -0
  165. package/src/database/_generated/project/models/WorkerVersionLog.ts +1318 -0
  166. package/src/database/_generated/project/models.ts +35 -0
  167. package/src/database/_generated/project/pjtg.ts +182 -0
  168. package/src/database/abstractions.ts +19 -0
  169. package/src/database/factory.ts +37 -0
  170. package/src/database/index.ts +6 -0
  171. package/src/database/local/backend.ts +134 -0
  172. package/src/database/local/index.ts +3 -0
  173. package/src/database/local/meta.ts +46 -0
  174. package/src/database/local/prisma.config.ts +25 -0
  175. package/src/database/local/project.ts +39 -0
  176. package/src/database/manager.ts +181 -0
  177. package/src/database/migrate.ts +35 -0
  178. package/src/database/prisma.ts +56 -0
  179. package/src/database/well-known.ts +38 -0
  180. package/src/index.ts +4 -4
  181. package/src/library/abstractions.ts +3 -5
  182. package/src/library/factory.ts +1 -1
  183. package/src/library/local.ts +81 -26
  184. package/src/library/package-resolution-worker.ts +1 -1
  185. package/src/library/worker/evaluator.ts +40 -23
  186. package/src/library/worker/loader.lite.ts +1 -1
  187. package/src/library/worker/main.ts +3 -10
  188. package/src/library/worker/protocol.ts +0 -1
  189. package/src/lock/index.ts +0 -1
  190. package/src/lock/manager.ts +0 -10
  191. package/src/orchestrator/manager.ts +190 -104
  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 +233 -578
  203. package/src/orchestrator/operation.ts +440 -948
  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 +29 -13
  217. package/src/pubsub/memory.ts +31 -0
  218. package/src/runner/abstractions.ts +40 -41
  219. package/src/runner/artifact-env.ts +19 -8
  220. package/src/runner/factory.ts +6 -6
  221. package/src/runner/force-abort.ts +3 -6
  222. package/src/runner/local.ts +74 -67
  223. package/src/runner/pulumi.ts +23 -63
  224. package/src/services.ts +181 -123
  225. package/src/shared/models/backend/index.ts +3 -1
  226. package/src/shared/models/backend/library.ts +9 -1
  227. package/src/shared/models/backend/project.ts +43 -42
  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 -26
  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 -59
  236. package/src/shared/models/project/artifact.ts +16 -76
  237. package/src/shared/models/project/custom-status.ts +12 -0
  238. package/src/shared/models/project/index.ts +8 -7
  239. package/src/shared/models/project/lock.ts +10 -78
  240. package/src/shared/models/project/model.ts +19 -1
  241. package/src/shared/models/project/operation.ts +235 -99
  242. package/src/shared/models/project/page.ts +37 -48
  243. package/src/shared/models/project/secret.ts +29 -89
  244. package/src/shared/models/project/service-account.ts +12 -17
  245. package/src/shared/models/project/state.ts +100 -407
  246. package/src/shared/models/project/terminal.ts +75 -88
  247. package/src/shared/models/project/trigger.ts +13 -49
  248. package/src/shared/models/project/unlock-method.ts +20 -26
  249. package/src/shared/models/project/worker.ts +89 -90
  250. package/src/shared/resolvers/graph-resolver.ts +21 -0
  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 +9 -2
  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 +7 -3
  257. package/src/shared/utils/index.ts +1 -2
  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 +5 -23
  266. package/src/unlock/memory.ts +9 -14
  267. package/src/worker/abstractions.ts +7 -4
  268. package/src/worker/docker.ts +14 -19
  269. package/src/worker/manager.ts +366 -97
  270. package/dist/chunk-NAAIDR4U.js +0 -8499
  271. package/dist/chunk-NAAIDR4U.js.map +0 -1
  272. package/dist/chunk-Y7DXREVO.js +0 -1745
  273. package/dist/chunk-Y7DXREVO.js.map +0 -1
  274. package/dist/magic-string.es-5ABAC4JN.js +0 -1292
  275. package/dist/magic-string.es-5ABAC4JN.js.map +0 -1
  276. package/src/business/__traces__/secret/update-instance-secrets/create-and-delete-secrets-simultaneously.md +0 -356
  277. package/src/business/__traces__/secret/update-instance-secrets/create-new-secrets-for-instance.md +0 -274
  278. package/src/business/__traces__/secret/update-instance-secrets/delete-existing-secrets.md +0 -223
  279. package/src/business/__traces__/secret/update-instance-secrets/no-op-when-no-changes.md +0 -147
  280. package/src/business/__traces__/secret/update-instance-secrets/update-existing-secrets.md +0 -280
  281. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration-when-other-exists.md +0 -360
  282. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration.md +0 -215
  283. package/src/business/__traces__/worker/update-unit-registrations/create-multiple-workers-with-different-identities.md +0 -427
  284. package/src/business/__traces__/worker/update-unit-registrations/handle-nonexistent-registration-id-gracefully.md +0 -217
  285. package/src/business/__traces__/worker/update-unit-registrations/no-op-when-no-changes.md +0 -132
  286. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-changes.md +0 -454
  287. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-version-changes.md +0 -426
  288. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-with-same-identity-reuses-service-account.md +0 -372
  289. package/src/business/__traces__/worker/update-unit-registrations/remove-one-of-multiple-unit-registrations.md +0 -383
  290. package/src/business/__traces__/worker/update-unit-registrations/remove-unit-registration.md +0 -245
  291. package/src/business/__traces__/worker/update-unit-registrations/update-existing-unit-registration-when-params-change.md +0 -174
  292. package/src/business/__traces__/worker/update-unit-registrations/update-params-and-image-simultaneously.md +0 -432
  293. package/src/business/__traces__/worker/update-unit-registrations/worker-with-multiple-registrations-not-deleted-when-one-removed.md +0 -220
  294. package/src/business/backend-unlock.ts +0 -10
  295. package/src/common/clock.ts +0 -18
  296. package/src/common/performance.ts +0 -44
  297. package/src/common/random.ts +0 -68
  298. package/src/common/test/index.ts +0 -2
  299. package/src/common/test/render.ts +0 -98
  300. package/src/common/test/tracer.ts +0 -359
  301. package/src/hotstate/abstractions.ts +0 -48
  302. package/src/hotstate/factory.ts +0 -17
  303. package/src/hotstate/index.ts +0 -3
  304. package/src/hotstate/manager.ts +0 -192
  305. package/src/hotstate/memory.ts +0 -100
  306. package/src/hotstate/validation.ts +0 -100
  307. package/src/lock/test.ts +0 -108
  308. package/src/project/abstractions.ts +0 -78
  309. package/src/project/evaluation.ts +0 -248
  310. package/src/project/factory.ts +0 -11
  311. package/src/project/index.ts +0 -3
  312. package/src/project/local.ts +0 -417
  313. package/src/pubsub/local.ts +0 -36
  314. package/src/pubsub/validation.ts +0 -33
  315. package/src/shared/utils/args.ts +0 -25
  316. package/src/state/abstractions.ts +0 -289
  317. package/src/state/encryption.ts +0 -98
  318. package/src/state/factory.ts +0 -20
  319. package/src/state/index.ts +0 -7
  320. package/src/state/local/backend.ts +0 -106
  321. package/src/state/local/collection.ts +0 -361
  322. package/src/state/local/index.ts +0 -2
  323. package/src/state/manager.ts +0 -890
  324. package/src/state/memory/backend.ts +0 -70
  325. package/src/state/memory/collection.ts +0 -270
  326. package/src/state/memory/index.ts +0 -2
  327. package/src/state/repository/index.ts +0 -2
  328. package/src/state/repository/repository.index.ts +0 -193
  329. package/src/state/repository/repository.ts +0 -507
  330. package/src/state/test.ts +0 -457
  331. /package/src/{state → database/local}/keyring.ts +0 -0
@@ -0,0 +1,735 @@
1
+ import type { InstanceId, InstanceModel } from "@highstate/contract"
2
+ import type { ArtifactService } from "../artifact"
3
+ import type { PubSubManager } from "../pubsub"
4
+ import type { RunnerBackend } from "../runner"
5
+ import type { ProjectService } from "./project"
6
+ import type { SecretService } from "./secret"
7
+ import type { UnitExtraService } from "./unit-extra"
8
+ import type { WorkerService } from "./worker"
9
+ import { parseInstanceId } from "@highstate/contract"
10
+ import { createId } from "@paralleldrive/cuid2"
11
+ import { describe, type MockedObject, vi } from "vitest"
12
+ import { InstanceLockedError, InstanceStateNotFoundError } from "../shared"
13
+ import { test } from "../test-utils"
14
+ import { InstanceStateService } from "./instance-state"
15
+
16
+ const instanceStateTest = test.extend<{
17
+ pubsubManager: MockedObject<PubSubManager>
18
+ projectService: MockedObject<ProjectService>
19
+ runnerBackend: MockedObject<RunnerBackend>
20
+ workerService: MockedObject<WorkerService>
21
+ artifactService: MockedObject<ArtifactService>
22
+ unitExtraService: MockedObject<UnitExtraService>
23
+ secretService: MockedObject<SecretService>
24
+ instanceStateService: InstanceStateService
25
+ }>({
26
+ pubsubManager: async ({}, use) => {
27
+ const pubsubManager = vi.mockObject({
28
+ publish: vi.fn().mockResolvedValue(undefined),
29
+ subscribe: vi.fn(),
30
+ publishMany: vi.fn().mockResolvedValue(undefined),
31
+ } as unknown as PubSubManager)
32
+
33
+ await use(pubsubManager)
34
+ },
35
+
36
+ projectService: async ({}, use) => {
37
+ const projectService = vi.mockObject({
38
+ getProjectOrThrow: vi.fn(),
39
+ } as unknown as ProjectService)
40
+
41
+ await use(projectService)
42
+ },
43
+
44
+ runnerBackend: async ({}, use) => {
45
+ const runnerBackend = vi.mockObject({
46
+ deleteState: vi.fn().mockResolvedValue(undefined),
47
+ } as unknown as RunnerBackend)
48
+
49
+ await use(runnerBackend)
50
+ },
51
+
52
+ workerService: async ({}, use) => {
53
+ const workerService = vi.mockObject({
54
+ cleanupWorkerUsageAndSync: vi.fn().mockResolvedValue(undefined),
55
+ } as unknown as WorkerService)
56
+
57
+ await use(workerService)
58
+ },
59
+
60
+ artifactService: async ({}, use) => {
61
+ const artifactService = vi.mockObject({
62
+ collectGarbage: vi.fn().mockResolvedValue(undefined),
63
+ } as unknown as ArtifactService)
64
+
65
+ await use(artifactService)
66
+ },
67
+
68
+ unitExtraService: async ({}, use) => {
69
+ const unitExtraService = vi.mockObject({} as unknown as UnitExtraService)
70
+ await use(unitExtraService)
71
+ },
72
+
73
+ secretService: async ({}, use) => {
74
+ const secretService = vi.mockObject({} as unknown as SecretService)
75
+ await use(secretService)
76
+ },
77
+
78
+ instanceStateService: async (
79
+ {
80
+ database,
81
+ pubsubManager,
82
+ runnerBackend,
83
+ workerService,
84
+ artifactService,
85
+ unitExtraService,
86
+ secretService,
87
+ logger,
88
+ },
89
+ use,
90
+ ) => {
91
+ const service = new InstanceStateService(
92
+ database,
93
+ pubsubManager,
94
+ runnerBackend,
95
+ workerService,
96
+ artifactService,
97
+ unitExtraService,
98
+ secretService,
99
+ logger.child({ service: "InstanceStateService" }),
100
+ )
101
+
102
+ await use(service)
103
+ },
104
+ })
105
+
106
+ describe("getInstanceStates", () => {
107
+ instanceStateTest(
108
+ "successfully retrieves instance states",
109
+ async ({ instanceStateService, project, createInstanceState, expect }) => {
110
+ // arrange
111
+ const instanceState1 = await createInstanceState(project.id)
112
+ const instanceState2 = await createInstanceState(project.id)
113
+
114
+ // act
115
+ const instanceStates = await instanceStateService.getInstanceStates(project.id)
116
+
117
+ // assert
118
+ expect(instanceStates).toHaveLength(2)
119
+ expect(instanceStates.map(i => i.id)).toEqual([instanceState1.id, instanceState2.id])
120
+ expect(instanceStates[0].secretNames).toEqual([])
121
+ expect(instanceStates[0].lastOperationState).toBeUndefined()
122
+ },
123
+ )
124
+
125
+ instanceStateTest(
126
+ "includes evaluation states when requested",
127
+ async ({ instanceStateService, projectDatabase, project, createInstanceState, expect }) => {
128
+ // arrange
129
+ const instanceState = await createInstanceState(project.id)
130
+
131
+ await projectDatabase.instanceEvaluationState.create({
132
+ data: {
133
+ stateId: instanceState.id,
134
+ status: "evaluated",
135
+ model: {
136
+ id: "test.v1:example",
137
+ kind: "unit",
138
+ type: "test.v1",
139
+ name: "example",
140
+ args: {},
141
+ inputs: {},
142
+ },
143
+ },
144
+ })
145
+
146
+ // act
147
+ const instanceStates = await instanceStateService.getInstanceStates(project.id, {
148
+ includeEvaluationState: true,
149
+ })
150
+
151
+ // assert
152
+ const instanceWithEvaluation = instanceStates.find(state => state.id === instanceState.id)
153
+ expect(instanceWithEvaluation).toBeDefined()
154
+ expect(instanceWithEvaluation?.evaluationState).toBeDefined()
155
+ expect(instanceWithEvaluation?.evaluationState?.status).toBe("evaluated")
156
+ },
157
+ )
158
+ })
159
+
160
+ describe("forgetInstanceState", () => {
161
+ instanceStateTest(
162
+ "successfully deletes instance with no operation state",
163
+ async ({
164
+ instanceStateService,
165
+ projectDatabase,
166
+ project,
167
+ createInstanceState,
168
+ projectService,
169
+ pubsubManager,
170
+ workerService,
171
+ artifactService,
172
+ expect,
173
+ }) => {
174
+ // arrange
175
+ const instanceState = await createInstanceState(project.id)
176
+ projectService.getProjectOrThrow.mockResolvedValue(project)
177
+
178
+ // act
179
+ await instanceStateService.forgetInstanceState(
180
+ project.id,
181
+ instanceState.instanceId as InstanceId,
182
+ )
183
+
184
+ // assert
185
+ const updatedState = await projectDatabase.instanceState.findUnique({
186
+ where: { id: instanceState.id },
187
+ })
188
+ expect(updatedState?.status).toBe("undeployed")
189
+ expect(updatedState?.statusFields).toBeNull()
190
+ expect(updatedState?.inputHash).toBeNull()
191
+ expect(updatedState?.outputHash).toBeNull()
192
+ expect(updatedState?.dependencyOutputHash).toBeNull()
193
+
194
+ // verify side effects were called
195
+ expect(workerService.cleanupWorkerUsageAndSync).toHaveBeenCalledWith(project.id)
196
+ expect(artifactService.collectGarbage).toHaveBeenCalledWith(project.id)
197
+ expect(pubsubManager.publish).toHaveBeenCalledWith(
198
+ ["instance-state", project.id],
199
+ expect.objectContaining({
200
+ type: "patched",
201
+ stateId: instanceState.id,
202
+ patch: expect.objectContaining({
203
+ status: "undeployed",
204
+ }),
205
+ }),
206
+ )
207
+ },
208
+ )
209
+
210
+ instanceStateTest(
211
+ "successfully deletes instance with destroyed operation state",
212
+ async ({
213
+ instanceStateService,
214
+ projectDatabase,
215
+ project,
216
+ createInstanceState,
217
+ projectService,
218
+ expect,
219
+ }) => {
220
+ // arrange
221
+ const instanceState = await createInstanceState(project.id)
222
+
223
+ // create operation with destroyed status
224
+ const operationId = createId()
225
+ await projectDatabase.operation.create({
226
+ data: {
227
+ id: operationId,
228
+ meta: { title: "Test Operation" },
229
+ type: "destroy",
230
+ options: {},
231
+ requestedInstanceIds: [instanceState.instanceId],
232
+ startedAt: new Date(),
233
+ },
234
+ })
235
+
236
+ await projectDatabase.instanceOperationState.create({
237
+ data: {
238
+ operationId,
239
+ stateId: instanceState.id,
240
+ status: "updated",
241
+ model: {} as InstanceModel,
242
+ resolvedInputs: {},
243
+ },
244
+ })
245
+
246
+ projectService.getProjectOrThrow.mockResolvedValue(project)
247
+
248
+ // act
249
+ await instanceStateService.forgetInstanceState(
250
+ project.id,
251
+ instanceState.instanceId as InstanceId,
252
+ )
253
+
254
+ // assert
255
+ const updatedState = await projectDatabase.instanceState.findUnique({
256
+ where: { id: instanceState.id },
257
+ })
258
+ expect(updatedState?.status).toBe("undeployed")
259
+ },
260
+ )
261
+
262
+ instanceStateTest(
263
+ "successfully deletes instance with active operation state",
264
+ async ({
265
+ instanceStateService,
266
+ projectDatabase,
267
+ project,
268
+ createInstanceState,
269
+ projectService,
270
+ expect,
271
+ }) => {
272
+ // arrange
273
+ const instanceState = await createInstanceState(project.id)
274
+
275
+ // create operation with pending status
276
+ const operationId = createId()
277
+ await projectDatabase.operation.create({
278
+ data: {
279
+ id: operationId,
280
+ meta: { title: "Test Operation" },
281
+ type: "update",
282
+ options: {},
283
+ requestedInstanceIds: [instanceState.instanceId],
284
+ startedAt: new Date(),
285
+ },
286
+ })
287
+
288
+ await projectDatabase.instanceOperationState.create({
289
+ data: {
290
+ operationId,
291
+ stateId: instanceState.id,
292
+ status: "updating",
293
+ model: {} as InstanceModel,
294
+ resolvedInputs: {},
295
+ },
296
+ })
297
+
298
+ projectService.getProjectOrThrow.mockResolvedValue(project)
299
+
300
+ // act
301
+ await instanceStateService.forgetInstanceState(
302
+ project.id,
303
+ instanceState.instanceId as InstanceId,
304
+ )
305
+
306
+ // assert
307
+ const updatedState = await projectDatabase.instanceState.findUnique({
308
+ where: { id: instanceState.id },
309
+ })
310
+ expect(updatedState?.status).toBe("undeployed")
311
+ },
312
+ )
313
+
314
+ instanceStateTest(
315
+ "throws error when instance has active locks",
316
+ async ({
317
+ instanceStateService,
318
+ projectDatabase,
319
+ project,
320
+ createInstanceState,
321
+ projectService,
322
+ expect,
323
+ }) => {
324
+ // arrange
325
+ const instanceState = await createInstanceState(project.id)
326
+
327
+ await projectDatabase.instanceLock.create({
328
+ data: {
329
+ stateId: instanceState.id,
330
+ token: "test-lock-token",
331
+ meta: { title: "Test Lock" },
332
+ },
333
+ })
334
+
335
+ projectService.getProjectOrThrow.mockResolvedValue(project)
336
+
337
+ // act & assert
338
+ await expect(
339
+ instanceStateService.forgetInstanceState(
340
+ project.id,
341
+ instanceState.instanceId as InstanceId,
342
+ ),
343
+ ).rejects.toThrow(InstanceLockedError)
344
+ },
345
+ )
346
+
347
+ instanceStateTest(
348
+ "handles terminal data when clearTerminalData=true",
349
+ async ({
350
+ instanceStateService,
351
+ projectDatabase,
352
+ project,
353
+ createInstanceState,
354
+ projectService,
355
+ expect,
356
+ }) => {
357
+ // arrange
358
+ const instanceState = await createInstanceState(project.id)
359
+
360
+ // create service account for terminal
361
+ const serviceAccount = await projectDatabase.serviceAccount.create({
362
+ data: {
363
+ id: createId(),
364
+ meta: { title: "Test Service Account" },
365
+ },
366
+ })
367
+
368
+ await projectDatabase.terminal.create({
369
+ data: {
370
+ id: createId(),
371
+ stateId: instanceState.id,
372
+ name: "test-terminal",
373
+ serviceAccountId: serviceAccount.id,
374
+ meta: { title: "Test Terminal" },
375
+ status: "active",
376
+ spec: { image: "test:latest" },
377
+ },
378
+ })
379
+
380
+ projectService.getProjectOrThrow.mockResolvedValue(project)
381
+
382
+ // act
383
+ await instanceStateService.forgetInstanceState(
384
+ project.id,
385
+ instanceState.instanceId as InstanceId,
386
+ {
387
+ clearTerminalData: true,
388
+ },
389
+ )
390
+
391
+ // assert
392
+ const terminals = await projectDatabase.terminal.findMany({
393
+ where: { stateId: instanceState.id },
394
+ })
395
+ expect(terminals).toHaveLength(0)
396
+ },
397
+ )
398
+
399
+ instanceStateTest(
400
+ "marks terminals as unavailable when clearTerminalData=false",
401
+ async ({
402
+ instanceStateService,
403
+ projectDatabase,
404
+ project,
405
+ createInstanceState,
406
+ projectService,
407
+ expect,
408
+ }) => {
409
+ // arrange
410
+ const instanceState = await createInstanceState(project.id)
411
+
412
+ // create service account for terminal
413
+ const serviceAccount = await projectDatabase.serviceAccount.create({
414
+ data: {
415
+ id: createId(),
416
+ meta: { title: "Test Service Account" },
417
+ },
418
+ })
419
+
420
+ const terminalId = createId()
421
+ await projectDatabase.terminal.create({
422
+ data: {
423
+ id: terminalId,
424
+ stateId: instanceState.id,
425
+ name: "test-terminal",
426
+ serviceAccountId: serviceAccount.id,
427
+ meta: { title: "Test Terminal" },
428
+ status: "active",
429
+ spec: { image: "test:latest" },
430
+ },
431
+ })
432
+
433
+ projectService.getProjectOrThrow.mockResolvedValue(project)
434
+
435
+ // act
436
+ await instanceStateService.forgetInstanceState(
437
+ project.id,
438
+ instanceState.instanceId as InstanceId,
439
+ {
440
+ clearTerminalData: false,
441
+ },
442
+ )
443
+
444
+ // assert
445
+ const terminal = await projectDatabase.terminal.findUnique({
446
+ where: { id: terminalId },
447
+ })
448
+ expect(terminal?.status).toBe("unavailable")
449
+ },
450
+ )
451
+
452
+ instanceStateTest(
453
+ "deletes secrets when deleteSecrets=true",
454
+ async ({
455
+ instanceStateService,
456
+ projectDatabase,
457
+ project,
458
+ createInstanceState,
459
+ projectService,
460
+ expect,
461
+ }) => {
462
+ // arrange
463
+ const instanceState = await createInstanceState(project.id)
464
+
465
+ await projectDatabase.secret.create({
466
+ data: {
467
+ id: createId(),
468
+ stateId: instanceState.id,
469
+ name: "test-secret",
470
+ meta: { title: "Test Secret" },
471
+ content: "secret-value",
472
+ },
473
+ })
474
+
475
+ projectService.getProjectOrThrow.mockResolvedValue(project)
476
+
477
+ // act
478
+ await instanceStateService.forgetInstanceState(
479
+ project.id,
480
+ instanceState.instanceId as InstanceId,
481
+ {
482
+ deleteSecrets: true,
483
+ },
484
+ )
485
+
486
+ // assert
487
+ const secrets = await projectDatabase.secret.findMany({
488
+ where: { stateId: instanceState.id },
489
+ })
490
+ expect(secrets).toHaveLength(0)
491
+ },
492
+ )
493
+
494
+ instanceStateTest(
495
+ "keeps secrets when deleteSecrets=false",
496
+ async ({
497
+ instanceStateService,
498
+ projectDatabase,
499
+ project,
500
+ createInstanceState,
501
+ projectService,
502
+ expect,
503
+ }) => {
504
+ // arrange
505
+ const instanceState = await createInstanceState(project.id)
506
+
507
+ const secretId = createId()
508
+ await projectDatabase.secret.create({
509
+ data: {
510
+ id: secretId,
511
+ stateId: instanceState.id,
512
+ name: "test-secret",
513
+ meta: { title: "Test Secret" },
514
+ content: "secret-value",
515
+ },
516
+ })
517
+
518
+ projectService.getProjectOrThrow.mockResolvedValue(project)
519
+
520
+ // act
521
+ await instanceStateService.forgetInstanceState(
522
+ project.id,
523
+ instanceState.instanceId as InstanceId,
524
+ {
525
+ deleteSecrets: false,
526
+ },
527
+ )
528
+
529
+ // assert
530
+ const secret = await projectDatabase.secret.findUnique({
531
+ where: { id: secretId },
532
+ })
533
+ expect(secret).toBeDefined()
534
+ expect(secret?.stateId).toBe(instanceState.id)
535
+ },
536
+ )
537
+
538
+ instanceStateTest(
539
+ "recursively deletes child instances",
540
+ async ({
541
+ instanceStateService,
542
+ projectDatabase,
543
+ project,
544
+ createInstanceState,
545
+ projectService,
546
+ pubsubManager,
547
+ expect,
548
+ }) => {
549
+ // arrange
550
+ const parentInstance = await createInstanceState(project.id, "composite.v1", "composite")
551
+ const childInstance = await createInstanceState(project.id)
552
+
553
+ // set both instances to deployed status and establish parent-child relationship
554
+ await projectDatabase.instanceState.update({
555
+ where: { id: parentInstance.id },
556
+ data: { status: "deployed" },
557
+ })
558
+
559
+ await projectDatabase.instanceState.update({
560
+ where: { id: childInstance.id },
561
+ data: { parentId: parentInstance.id, status: "deployed" },
562
+ })
563
+
564
+ projectService.getProjectOrThrow.mockResolvedValue(project)
565
+
566
+ // act
567
+ await instanceStateService.forgetInstanceState(
568
+ project.id,
569
+ parentInstance.instanceId as InstanceId,
570
+ )
571
+
572
+ // assert
573
+ const updatedParent = await projectDatabase.instanceState.findUnique({
574
+ where: { id: parentInstance.id },
575
+ })
576
+ const updatedChild = await projectDatabase.instanceState.findUnique({
577
+ where: { id: childInstance.id },
578
+ })
579
+
580
+ expect(updatedParent?.status).toBe("undeployed")
581
+ expect(updatedChild?.status).toBe("undeployed")
582
+
583
+ // verify both instances got state update events
584
+ expect(pubsubManager.publish).toHaveBeenCalledTimes(2)
585
+ },
586
+ )
587
+
588
+ instanceStateTest(
589
+ "calls Pulumi deleteState for unit instances",
590
+ async ({
591
+ instanceStateService,
592
+ project,
593
+ createInstanceState,
594
+ projectService,
595
+ runnerBackend,
596
+ expect,
597
+ }) => {
598
+ // arrange
599
+ const instanceState = await createInstanceState(project.id, "server.v1", "unit")
600
+ projectService.getProjectOrThrow.mockResolvedValue(project)
601
+
602
+ // act
603
+ await instanceStateService.forgetInstanceState(
604
+ project.id,
605
+ instanceState.instanceId as InstanceId,
606
+ )
607
+
608
+ // assert
609
+ const [instanceType, instanceName] = parseInstanceId(instanceState.instanceId as InstanceId)
610
+ expect(runnerBackend.deleteState).toHaveBeenCalledWith({
611
+ projectId: project.id,
612
+ stateId: instanceState.id,
613
+ libraryId: project.libraryId,
614
+ instanceName,
615
+ instanceType,
616
+ })
617
+ },
618
+ )
619
+
620
+ instanceStateTest(
621
+ "throws error for missing instance",
622
+ async ({ instanceStateService, project, projectService, expect }) => {
623
+ // arrange
624
+ const nonexistentInstanceId: InstanceId = "server.v1:nonexistent"
625
+ projectService.getProjectOrThrow.mockResolvedValue(project)
626
+
627
+ // act & assert
628
+ await expect(
629
+ instanceStateService.forgetInstanceState(project.id, nonexistentInstanceId),
630
+ ).rejects.toThrow(InstanceStateNotFoundError)
631
+ },
632
+ )
633
+ })
634
+
635
+ describe("replaceCustomStatus", () => {
636
+ instanceStateTest(
637
+ "successfully replaces custom status",
638
+ async ({ instanceStateService, projectDatabase, project, createInstanceState, expect }) => {
639
+ // arrange
640
+ const instanceState = await createInstanceState(project.id)
641
+ const serviceAccountId = createId()
642
+
643
+ // create service account first
644
+ await projectDatabase.serviceAccount.create({
645
+ data: {
646
+ id: serviceAccountId,
647
+ meta: { title: "Test Service Account" },
648
+ },
649
+ })
650
+
651
+ const status = {
652
+ instanceId: instanceState.instanceId,
653
+ name: "health",
654
+ meta: { title: "Health Status" },
655
+ value: "healthy",
656
+ title: "Health Status",
657
+ message: "Service is running normally",
658
+ order: 10,
659
+ }
660
+
661
+ // act
662
+ await instanceStateService.updateCustomStatus(
663
+ project.id,
664
+ instanceState.id,
665
+ serviceAccountId,
666
+ status,
667
+ )
668
+
669
+ // assert
670
+ const customStatus = await projectDatabase.instanceCustomStatus.findUnique({
671
+ where: {
672
+ stateId_serviceAccountId_name: {
673
+ stateId: instanceState.id,
674
+ serviceAccountId,
675
+ name: status.name,
676
+ },
677
+ },
678
+ })
679
+
680
+ expect(customStatus).toBeDefined()
681
+ expect(customStatus?.value).toBe("healthy")
682
+ expect(customStatus?.message).toBe("Service is running normally")
683
+ expect(customStatus?.order).toBe(10)
684
+ },
685
+ )
686
+ })
687
+
688
+ describe("removeCustomStatus", () => {
689
+ instanceStateTest(
690
+ "successfully removes custom status",
691
+ async ({ instanceStateService, projectDatabase, project, createInstanceState, expect }) => {
692
+ // arrange
693
+ const instanceState = await createInstanceState(project.id)
694
+
695
+ // create service account first
696
+ const serviceAccount = await projectDatabase.serviceAccount.create({
697
+ data: {
698
+ meta: { title: "Test Service Account" },
699
+ },
700
+ })
701
+
702
+ await projectDatabase.instanceCustomStatus.create({
703
+ data: {
704
+ stateId: instanceState.id,
705
+ serviceAccountId: serviceAccount.id,
706
+ name: "health",
707
+ meta: { title: "Health Status" },
708
+ value: "healthy",
709
+ order: 10,
710
+ },
711
+ })
712
+
713
+ // act
714
+ await instanceStateService.removeCustomStatus(
715
+ project.id,
716
+ instanceState.id,
717
+ serviceAccount.id,
718
+ "health",
719
+ )
720
+
721
+ // assert
722
+ const customStatus = await projectDatabase.instanceCustomStatus.findUnique({
723
+ where: {
724
+ stateId_serviceAccountId_name: {
725
+ stateId: instanceState.id,
726
+ serviceAccountId: serviceAccount.id,
727
+ name: "health",
728
+ },
729
+ },
730
+ })
731
+
732
+ expect(customStatus).toBeNull()
733
+ },
734
+ )
735
+ })