@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
@@ -1,95 +1,114 @@
1
1
  import type { Logger } from "pino"
2
- import type { StateManager } from "../state"
3
- import type { InputUnlockMethod, Project } from "../shared"
4
- import type { RandomProvider } from "../common"
5
- import type { ProjectUnlockService } from "./project-unlock"
6
- import type { ProjectBackend, ProjectEvaluationSubsystem } from "../project"
7
- import type { LockManager } from "../lock"
8
- import type { PubSubManager } from "../pubsub"
2
+ import type { DatabaseManager, Project } from "../database"
9
3
  import type { LibraryBackend } from "../library"
4
+ import type { ProjectEvaluationSubsystem, ProjectModelBackend } from "../project-model"
5
+ import type { PubSubManager } from "../pubsub"
6
+ import type { ProjectModelService } from "./project-model"
7
+ import type { ProjectUnlockService } from "./project-unlock"
10
8
  import {
11
- isUnitModel,
12
9
  type HubModel,
13
10
  type HubModelPatch,
14
11
  type InstanceId,
15
12
  type InstanceModel,
16
13
  type InstanceModelPatch,
14
+ isUnitModel,
17
15
  } from "@highstate/contract"
18
-
19
- export type FullProjectModel = {
20
- instances: InstanceModel[]
21
- hubs: HubModel[]
22
- virtualInstances: InstanceModel[]
23
- }
24
-
25
- export class ProjectNotFoundError extends Error {
26
- constructor(readonly projectId: string) {
27
- super(`Project with ID "${projectId}" not found`)
28
- this.name = "ProjectNotFoundError"
29
- }
30
- }
31
-
32
- export class ProjectNameConflictError extends Error {
33
- constructor(readonly projectName: string) {
34
- super(`Project with name "${projectName}" already exists`)
35
- this.name = "ProjectNameConflictError"
36
- }
37
- }
16
+ import { createId } from "@paralleldrive/cuid2"
17
+ import { createProjectLogger } from "../common"
18
+ import { projectDatabaseVersion } from "../database/abstractions"
19
+ import {
20
+ type FullProjectModel,
21
+ forSchema,
22
+ type ProjectInput,
23
+ type ProjectModelStorageSpec,
24
+ ProjectNotFoundError,
25
+ type ProjectOutput,
26
+ projectOutputSchema,
27
+ type UnlockMethodInput,
28
+ } from "../shared"
29
+ import { includeForInstanceState, mapInstanceStateResult } from "./instance-state"
38
30
 
39
31
  export class ProjectService {
40
32
  constructor(
41
- private readonly stateManager: StateManager,
33
+ private readonly database: DatabaseManager,
42
34
  private readonly projectUnlockService: ProjectUnlockService,
43
35
  private readonly projectEvaluationSubsystem: ProjectEvaluationSubsystem,
44
- private readonly projectBackend: ProjectBackend,
36
+ private readonly projectModelService: ProjectModelService,
37
+ private readonly projectModelBackends: Record<string, ProjectModelBackend>,
45
38
  private readonly libraryBackend: LibraryBackend,
46
- private readonly lockManager: LockManager,
47
39
  private readonly pubsubManager: PubSubManager,
48
- private readonly random: RandomProvider,
49
40
  private readonly logger: Logger,
50
41
  ) {}
51
42
 
43
+ /**
44
+ * Returns all projects in the system.
45
+ */
46
+ async getProjects(): Promise<ProjectOutput[]> {
47
+ return await this.database.backend.project.findMany({
48
+ select: forSchema(projectOutputSchema),
49
+ })
50
+ }
51
+
52
+ /**
53
+ * Creates a new project with the given input and unlock method.
54
+ *
55
+ * This will set up the project database and persist the unlock method.
56
+ *
57
+ * @param projectInput The input for the new project.
58
+ * @param unlockMethodInput The unlock method to use for the new project.
59
+ */
52
60
  async createProject(
53
- name: string,
54
- meta: Project["meta"],
55
- unlockMethod: InputUnlockMethod,
61
+ projectInput: ProjectInput,
62
+ unlockMethodInput: UnlockMethodInput,
56
63
  ): Promise<Project> {
57
- return await this.lockManager.acquire(["project-name", name], async () => {
58
- const existingProject = await this.stateManager.getProjectNameIndexRepository().get(name)
59
- if (existingProject) {
60
- throw new ProjectNameConflictError(name)
61
- }
62
-
63
- const batch = this.stateManager.batch()
64
-
65
- const project: Project = {
66
- id: this.random.uuidv7(),
67
- name,
68
- meta,
69
- }
70
-
71
- // 1. create the project model
72
- await this.projectBackend.createProjectModel(project)
73
-
74
- // 2. create the project in the state manager
75
- await this.stateManager.getProjectRepository().putItem(project, batch)
76
- await this.stateManager
77
- .getProjectNameIndexRepository()
78
- .indexRepository.put(project.name, project.id, batch)
79
-
80
- // 3. setup the project unlock state
81
- await this.projectUnlockService.setupProjectState(project.id, unlockMethod, batch)
64
+ // start by generating a random ID
65
+ const projectId = createId()
66
+ const logger = createProjectLogger(this.logger, projectId)
67
+
68
+ logger.info("creating new project")
69
+
70
+ // setup project database
71
+ const [encryptedMasterKey, unlockSuite] = await this.projectUnlockService.setupProjectDatabase(
72
+ projectId,
73
+ unlockMethodInput,
74
+ )
75
+
76
+ logger.info("project database set up")
77
+
78
+ // finally, create the project in the database
79
+ const project = await this.database.backend.project.create({
80
+ data: {
81
+ id: projectId,
82
+ name: projectInput.name,
83
+ meta: projectInput.meta,
84
+ databaseVersion: projectDatabaseVersion,
85
+ encryptedMasterKey,
86
+ unlockSuite,
87
+ spaceId: projectInput.spaceId,
88
+ modelStorageId: projectInput.modelStorageId,
89
+ libraryId: projectInput.libraryId,
90
+ pulumiBackendId: projectInput.pulumiBackendId,
91
+ },
92
+ })
82
93
 
83
- await batch.write()
94
+ logger.info("project successfully created")
84
95
 
85
- this.logger.info({ project }, "created project")
96
+ return project
97
+ }
86
98
 
87
- return project
99
+ /**
100
+ * Returns the project with the given ID.
101
+ *
102
+ * If the project does not exist, this will throw an error.
103
+ *
104
+ * @param projectId The ID of the project to get.
105
+ */
106
+ async getProjectOrThrow(projectId: string): Promise<ProjectOutput> {
107
+ const project = await this.database.backend.project.findUnique({
108
+ where: { id: projectId },
109
+ select: forSchema(projectOutputSchema),
88
110
  })
89
- }
90
111
 
91
- async getProjectOrThrow(projectId: string): Promise<Project> {
92
- const project = await this.stateManager.getProjectRepository().get(projectId)
93
112
  if (!project) {
94
113
  throw new ProjectNotFoundError(projectId)
95
114
  }
@@ -98,25 +117,18 @@ export class ProjectService {
98
117
  }
99
118
 
100
119
  /**
101
- * Get the project model containing instances, hubs + composite instances.
120
+ * Get the project model containing instances, hubs + virtual and ghost instances.
102
121
  *
103
122
  * @param projectId The ID of the project to get the model for.
104
123
  * @param signal Optional abort signal for cancellation.
105
124
  */
106
- async getProjectModel(projectId: string, signal?: AbortSignal): Promise<FullProjectModel> {
107
- const project = await this.getProjectOrThrow(projectId)
108
-
109
- const [library, { instances, hubs }, virtualInstances] = await Promise.all([
110
- this.libraryBackend.loadLibrary(project.libraryId),
111
- this.projectBackend.getProjectModel(project, signal),
112
- this.stateManager.getVirtualInstanceRepository(projectId).getAllItems(),
113
- ])
114
-
115
- return {
116
- instances: instances.filter(instance => instance.type in library.components),
117
- hubs,
118
- virtualInstances: virtualInstances.filter(instance => instance.type in library.components),
119
- }
125
+ async getProjectModel(projectId: string): Promise<FullProjectModel> {
126
+ const [projectModel] = await this.projectModelService.getProjectModel(projectId, {
127
+ includeVirtualInstances: true,
128
+ includeGhostInstances: true,
129
+ })
130
+
131
+ return projectModel
120
132
  }
121
133
 
122
134
  /**
@@ -131,24 +143,43 @@ export class ProjectService {
131
143
  instanceId: InstanceId,
132
144
  newName: string,
133
145
  ): Promise<InstanceModel> {
134
- return await this.lockManager.acquire(["project-nodes", projectId], async () => {
135
- try {
136
- const project = await this.getProjectOrThrow(projectId)
137
- const instance = await this.projectBackend.renameInstance(project, instanceId, newName)
138
-
139
- await this.pubsubManager.publish(["project-model", projectId], {
140
- updatedInstances: [instance],
141
- deletedInstanceIds: [instanceId],
146
+ try {
147
+ // rename the instance in the model
148
+ const { project, backend, spec } = await this.getProjectWithBackend(projectId)
149
+ const instance = await backend.renameInstance(project, spec, instanceId, newName)
150
+
151
+ // rename in the database state
152
+ const database = await this.database.forProject(projectId)
153
+
154
+ await database.$transaction(async tx => {
155
+ const state = await tx.instanceState.findUnique({
156
+ where: { instanceId },
157
+ select: { id: true },
142
158
  })
143
159
 
144
- void this.projectEvaluationSubsystem.evaluateProject(project)
160
+ if (!state) {
161
+ // ignore if state doesn't exist yet
162
+ return
163
+ }
145
164
 
146
- return instance
147
- } catch (error) {
148
- this.logger.error({ error, projectId, instanceId, newName }, "failed to rename instance")
149
- throw error
150
- }
151
- })
165
+ await tx.instanceState.update({
166
+ where: { id: state.id },
167
+ data: { instanceId: instance.id },
168
+ })
169
+ })
170
+
171
+ await this.pubsubManager.publish(["project-model", projectId], {
172
+ updatedInstances: [instance],
173
+ deletedInstanceIds: [instanceId],
174
+ })
175
+
176
+ void this.projectEvaluationSubsystem.evaluateProject(projectId)
177
+
178
+ return instance
179
+ } catch (error) {
180
+ this.logger.error({ error, projectId, instanceId, newName }, "failed to rename instance")
181
+ throw error
182
+ }
152
183
  }
153
184
 
154
185
  /**
@@ -163,30 +194,28 @@ export class ProjectService {
163
194
  instanceId: InstanceId,
164
195
  patch: InstanceModelPatch,
165
196
  ): Promise<InstanceModel> {
166
- return await this.lockManager.acquire(["project-nodes", projectId], async () => {
167
- try {
168
- const project = await this.getProjectOrThrow(projectId)
169
- const instance = await this.projectBackend.updateInstance(project, instanceId, patch)
170
- const library = await this.libraryBackend.loadLibrary(project.libraryId)
171
-
172
- await this.pubsubManager.publish(["project-model", projectId], {
173
- updatedInstances: [instance],
174
- })
175
-
176
- if (!isUnitModel(library.components[instance.type]) && patch.args) {
177
- // evaluate the project if arguments changed for composite instancer
178
- void this.projectEvaluationSubsystem.evaluateProject(project)
179
- } else if (patch.hubInputs || patch.injectionInputs || patch.inputs) {
180
- // TODO: only evaluate if inputs changed for composite instances
181
- void this.projectEvaluationSubsystem.evaluateProject(project)
182
- }
183
-
184
- return instance
185
- } catch (error) {
186
- this.logger.error({ error, projectId, instanceId }, "failed to update instance")
187
- throw error
197
+ try {
198
+ const { project, backend, spec } = await this.getProjectWithBackend(projectId)
199
+ const instance = await backend.updateInstance(project, spec, instanceId, patch)
200
+ const library = await this.libraryBackend.loadLibrary(project.libraryId)
201
+
202
+ await this.pubsubManager.publish(["project-model", projectId], {
203
+ updatedInstances: [instance],
204
+ })
205
+
206
+ if (!isUnitModel(library.components[instance.type]) && patch.args) {
207
+ // evaluate the project if arguments changed for composite instancer
208
+ void this.projectEvaluationSubsystem.evaluateProject(projectId)
209
+ } else if (patch.hubInputs || patch.injectionInputs || patch.inputs) {
210
+ // TODO: only evaluate if inputs changed for composite instances
211
+ void this.projectEvaluationSubsystem.evaluateProject(projectId)
188
212
  }
189
- })
213
+
214
+ return instance
215
+ } catch (error) {
216
+ this.logger.error({ error, projectId, instanceId }, "failed to update instance")
217
+ throw error
218
+ }
190
219
  }
191
220
 
192
221
  /**
@@ -196,21 +225,19 @@ export class ProjectService {
196
225
  * @param instanceId The ID of the instance to delete.
197
226
  */
198
227
  async deleteInstance(projectId: string, instanceId: InstanceId): Promise<void> {
199
- return await this.lockManager.acquire(["project-nodes", projectId], async () => {
200
- try {
201
- const project = await this.getProjectOrThrow(projectId)
202
- await this.projectBackend.deleteInstance(project, instanceId)
203
-
204
- await this.pubsubManager.publish(["project-model", projectId], {
205
- deletedInstanceIds: [instanceId],
206
- })
207
-
208
- void this.projectEvaluationSubsystem.evaluateProject(project)
209
- } catch (error) {
210
- this.logger.error({ error, projectId, instanceId }, "failed to delete instance")
211
- throw error
212
- }
213
- })
228
+ try {
229
+ const { project, backend, spec } = await this.getProjectWithBackend(projectId)
230
+ await backend.deleteInstance(project, spec, instanceId)
231
+
232
+ await this.pubsubManager.publish(["project-model", projectId], {
233
+ deletedInstanceIds: [instanceId],
234
+ })
235
+
236
+ void this.projectEvaluationSubsystem.evaluateProject(projectId)
237
+ } catch (error) {
238
+ this.logger.error({ error, projectId, instanceId }, "failed to delete instance")
239
+ throw error
240
+ }
214
241
  }
215
242
 
216
243
  /**
@@ -221,23 +248,23 @@ export class ProjectService {
221
248
  * @param patch The patch to apply to the hub.
222
249
  */
223
250
  async updateHub(projectId: string, hubId: string, patch: HubModelPatch): Promise<HubModel> {
224
- return await this.lockManager.acquire(["project-nodes", projectId], async () => {
225
- try {
226
- const project = await this.getProjectOrThrow(projectId)
227
- const hub = await this.projectBackend.updateHub(project, hubId, patch)
251
+ try {
252
+ const { project, backend, spec } = await this.getProjectWithBackend(projectId)
253
+ const hub = await backend.updateHub(project, spec, hubId, patch)
228
254
 
229
- await this.pubsubManager.publish(["project-model", projectId], {
230
- updatedHubs: [hub],
231
- })
232
-
233
- void this.projectEvaluationSubsystem.evaluateProject(project)
255
+ await this.pubsubManager.publish(["project-model", projectId], {
256
+ updatedHubs: [hub],
257
+ })
234
258
 
235
- return hub
236
- } catch (error) {
237
- this.logger.error({ error, projectId, hubId }, "failed to update hub")
238
- throw error
259
+ if (patch.inputs || patch.injectionInputs) {
260
+ void this.projectEvaluationSubsystem.evaluateProject(projectId)
239
261
  }
240
- })
262
+
263
+ return hub
264
+ } catch (error) {
265
+ this.logger.error({ error, projectId, hubId }, "failed to update hub")
266
+ throw error
267
+ }
241
268
  }
242
269
 
243
270
  /**
@@ -247,21 +274,19 @@ export class ProjectService {
247
274
  * @param hubId The ID of the hub to delete.
248
275
  */
249
276
  async deleteHub(projectId: string, hubId: string): Promise<void> {
250
- return await this.lockManager.acquire(["project-nodes", projectId], async () => {
251
- try {
252
- const project = await this.getProjectOrThrow(projectId)
253
- await this.projectBackend.deleteHub(project, hubId)
254
-
255
- await this.pubsubManager.publish(["project-model", projectId], {
256
- deletedHubIds: [hubId],
257
- })
258
-
259
- void this.projectEvaluationSubsystem.evaluateProject(project)
260
- } catch (error) {
261
- this.logger.error({ error, projectId, hubId }, "failed to delete hub")
262
- throw error
263
- }
264
- })
277
+ try {
278
+ const { project, backend, spec } = await this.getProjectWithBackend(projectId)
279
+ await backend.deleteHub(project, spec, hubId)
280
+
281
+ await this.pubsubManager.publish(["project-model", projectId], {
282
+ deletedHubIds: [hubId],
283
+ })
284
+
285
+ void this.projectEvaluationSubsystem.evaluateProject(projectId)
286
+ } catch (error) {
287
+ this.logger.error({ error, projectId, hubId }, "failed to delete hub")
288
+ throw error
289
+ }
265
290
  }
266
291
 
267
292
  /**
@@ -276,24 +301,107 @@ export class ProjectService {
276
301
  instances: InstanceModel[],
277
302
  hubs: HubModel[],
278
303
  ): Promise<void> {
279
- return await this.lockManager.acquire(["project-nodes", projectId], async () => {
280
- try {
281
- const project = await this.getProjectOrThrow(projectId)
282
- await this.projectBackend.createNodes(project, instances, hubs)
283
-
284
- await this.pubsubManager.publish(["project-model", projectId], {
285
- updatedHubs: hubs,
286
- updatedInstances: instances,
287
- })
288
-
289
- void this.projectEvaluationSubsystem.evaluateProject(project)
290
- } catch (error) {
291
- this.logger.error(
292
- { error, projectId, instanceCount: instances.length, hubCount: hubs.length },
293
- "failed to create many nodes",
304
+ try {
305
+ const database = await this.database.forProject(projectId)
306
+
307
+ const states = await database.$transaction(async tx => {
308
+ const { project, backend, spec } = await this.getProjectWithBackend(projectId)
309
+ await backend.createNodes(project, spec, instances, hubs)
310
+
311
+ // ensure instance states exist for created instances
312
+ return await Promise.all(
313
+ instances.map(async instance => {
314
+ const result = await tx.instanceState.upsert({
315
+ where: { instanceId: instance.id },
316
+ create: {
317
+ instanceId: instance.id,
318
+ kind: instance.kind,
319
+ source: "resident",
320
+ status: "undeployed",
321
+ },
322
+ update: {
323
+ // turn any virtual instance into resident
324
+ // the next evaluation will throw an error indicating the instance is now duplicate and will no longer produce this virtual instance
325
+ source: "resident",
326
+ },
327
+ // in case we restoring instance for existing state, to stream it correctly
328
+ include: includeForInstanceState({
329
+ includeEvaluationState: true,
330
+ includeExtra: true,
331
+ includeLastOperationState: true,
332
+ loadCustomStatuses: true,
333
+ }),
334
+ })
335
+
336
+ return mapInstanceStateResult(result)
337
+ }),
294
338
  )
295
- throw error
339
+ })
340
+
341
+ void this.pubsubManager.publish(["project-model", projectId], {
342
+ updatedHubs: hubs,
343
+ updatedInstances: instances,
344
+ })
345
+
346
+ for (const state of states) {
347
+ void this.pubsubManager.publish(["instance-state", projectId], { type: "updated", state })
296
348
  }
349
+
350
+ void this.projectEvaluationSubsystem.evaluateProject(projectId)
351
+ } catch (error) {
352
+ this.logger.error(
353
+ { error, projectId, instanceCount: instances.length, hubCount: hubs.length },
354
+ "failed to create many nodes",
355
+ )
356
+ throw error
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Get the project, backend, and storage spec in a single optimized SQL call.
362
+ *
363
+ * @param projectId The ID of the project.
364
+ * @returns Object containing project, backend, and spec.
365
+ */
366
+ private async getProjectWithBackend(projectId: string): Promise<{
367
+ project: ProjectOutput
368
+ backend: ProjectModelBackend
369
+ spec: ProjectModelStorageSpec
370
+ }> {
371
+ const result = await this.database.backend.project.findUnique({
372
+ where: { id: projectId },
373
+ select: {
374
+ ...forSchema(projectOutputSchema),
375
+ modelStorage: {
376
+ select: {
377
+ spec: true,
378
+ },
379
+ },
380
+ },
297
381
  })
382
+
383
+ if (!result) {
384
+ throw new ProjectNotFoundError(projectId)
385
+ }
386
+
387
+ const { modelStorage, ...project } = result
388
+ const spec = modelStorage.spec
389
+ const backend = this.getProjectModelBackend(spec.type)
390
+
391
+ return { project, backend, spec }
392
+ }
393
+
394
+ /**
395
+ * Get the appropriate project model backend for the given project.
396
+ *
397
+ * @param project The project to get the backend for.
398
+ * @returns The project model backend.
399
+ */
400
+ private getProjectModelBackend(type: string): ProjectModelBackend {
401
+ const backend = this.projectModelBackends[type]
402
+ if (!backend) {
403
+ throw new Error(`Project model backend not found for type: ${type}`)
404
+ }
405
+ return backend
298
406
  }
299
407
  }