@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,440 @@
1
+ import type { Logger } from "pino"
2
+ import type { DatabaseManager } from "../../database"
3
+ import type { ProjectModel, ProjectModelStorageSpec, ProjectOutput } from "../../shared"
4
+ import type { ProjectModelBackend } from "../abstractions"
5
+ import {
6
+ getInstanceId,
7
+ type HubModel,
8
+ type HubModelPatch,
9
+ hubModelSchema,
10
+ type InstanceModel,
11
+ type InstanceModelPatch,
12
+ instanceModelSchema,
13
+ } from "@highstate/contract"
14
+ import {
15
+ ProjectModelHubAlreadyExistsError,
16
+ ProjectModelHubNotFoundError,
17
+ ProjectModelInstanceAlreadyExistsError,
18
+ ProjectModelInstanceNotFoundError,
19
+ ProjectModelOperationError,
20
+ } from "../errors"
21
+ import {
22
+ applyHubPatch,
23
+ applyInstancePatch,
24
+ cleanupHubReferences,
25
+ cleanupInstanceReferences,
26
+ updateInstanceReferences,
27
+ } from "../utils"
28
+
29
+ /**
30
+ * A project model backend that stores the project models in the project database.
31
+ *
32
+ * Uses the InstanceModel and HubModel tables in the project database.
33
+ */
34
+ export class DatabaseProjectModelBackend implements ProjectModelBackend {
35
+ constructor(
36
+ private readonly database: DatabaseManager,
37
+ private readonly logger: Logger,
38
+ ) {}
39
+
40
+ async getProjectModel(
41
+ project: ProjectOutput,
42
+ spec: ProjectModelStorageSpec,
43
+ ): Promise<ProjectModel> {
44
+ assertDatabaseSpec(spec)
45
+
46
+ try {
47
+ const projectDatabase = await this.database.forProject(project.id)
48
+
49
+ const [instanceRecords, hubRecords] = await Promise.all([
50
+ projectDatabase.instanceModel.findMany({
51
+ select: { model: true },
52
+ }),
53
+ projectDatabase.hubModel.findMany({
54
+ select: { model: true },
55
+ }),
56
+ ])
57
+
58
+ const instances = instanceRecords.map(record => instanceModelSchema.parse(record.model))
59
+ const hubs = hubRecords.map(record => hubModelSchema.parse(record.model))
60
+
61
+ return { instances, hubs }
62
+ } catch (error) {
63
+ throw new ProjectModelOperationError("get project model", project.id, error)
64
+ }
65
+ }
66
+
67
+ async createProjectModel(project: ProjectOutput, spec: ProjectModelStorageSpec): Promise<void> {
68
+ assertDatabaseSpec(spec)
69
+
70
+ try {
71
+ // for database storage, creating an empty project model means ensuring tables exist
72
+ // the tables are created automatically by Prisma migrations, so nothing to do here
73
+ this.logger.info({ projectId: project.id }, "created project model")
74
+ } catch (error) {
75
+ throw new ProjectModelOperationError("create project model", project.id, error)
76
+ }
77
+ }
78
+
79
+ async updateInstance(
80
+ project: ProjectOutput,
81
+ spec: ProjectModelStorageSpec,
82
+ instanceId: string,
83
+ patch: InstanceModelPatch,
84
+ ): Promise<InstanceModel> {
85
+ assertDatabaseSpec(spec)
86
+
87
+ try {
88
+ const projectDatabase = await this.database.forProject(project.id)
89
+
90
+ const existingRecord = await projectDatabase.instanceModel.findUnique({
91
+ where: { id: instanceId },
92
+ })
93
+
94
+ if (!existingRecord) {
95
+ throw new ProjectModelInstanceNotFoundError(project.id, instanceId)
96
+ }
97
+
98
+ const instance = instanceModelSchema.parse(existingRecord.model)
99
+
100
+ // apply patch
101
+ applyInstancePatch(instance, patch)
102
+
103
+ // update in database
104
+ await projectDatabase.instanceModel.update({
105
+ where: { id: instanceId },
106
+ data: { model: instance },
107
+ })
108
+
109
+ this.logger.info({ projectId: project.id, instanceId }, "updated instance in project model")
110
+
111
+ return instance
112
+ } catch (error) {
113
+ throw new ProjectModelOperationError("update instance", project.id, error)
114
+ }
115
+ }
116
+
117
+ async deleteInstance(
118
+ project: ProjectOutput,
119
+ spec: ProjectModelStorageSpec,
120
+ instanceId: string,
121
+ ): Promise<void> {
122
+ assertDatabaseSpec(spec)
123
+
124
+ try {
125
+ const projectDatabase = await this.database.forProject(project.id)
126
+
127
+ // this is a multi-node operation, so we need a transaction
128
+ await projectDatabase.$transaction(async tx => {
129
+ const existingRecord = await tx.instanceModel.findUnique({
130
+ where: { id: instanceId },
131
+ })
132
+
133
+ if (!existingRecord) {
134
+ throw new ProjectModelInstanceNotFoundError(project.id, instanceId)
135
+ }
136
+
137
+ // delete the instance
138
+ await tx.instanceModel.delete({
139
+ where: { id: instanceId },
140
+ })
141
+
142
+ // get all instances and hubs to clean up references
143
+ const [instanceRecords, hubRecords] = await Promise.all([
144
+ tx.instanceModel.findMany({ select: { id: true, model: true } }),
145
+ tx.hubModel.findMany({ select: { id: true, model: true } }),
146
+ ])
147
+
148
+ const instances = instanceRecords.map(record => instanceModelSchema.parse(record.model))
149
+ const hubs = hubRecords.map(record => hubModelSchema.parse(record.model))
150
+
151
+ // clean up references
152
+ cleanupInstanceReferences(instances, hubs, instanceId)
153
+
154
+ // update modified instances and hubs back to database
155
+ await Promise.all([
156
+ ...instances.map(instance =>
157
+ tx.instanceModel.update({
158
+ where: { id: instance.id },
159
+ data: { model: instance },
160
+ }),
161
+ ),
162
+ ...hubs.map(hub =>
163
+ tx.hubModel.update({
164
+ where: { id: hub.id },
165
+ data: { model: hub },
166
+ }),
167
+ ),
168
+ ])
169
+ })
170
+
171
+ this.logger.info({ projectId: project.id, instanceId }, "deleted instance from project model")
172
+ } catch (error) {
173
+ throw new ProjectModelOperationError("delete instance", project.id, error)
174
+ }
175
+ }
176
+
177
+ async renameInstance(
178
+ project: ProjectOutput,
179
+ spec: ProjectModelStorageSpec,
180
+ instanceId: string,
181
+ newName: string,
182
+ ): Promise<InstanceModel> {
183
+ assertDatabaseSpec(spec)
184
+
185
+ try {
186
+ const projectDatabase = await this.database.forProject(project.id)
187
+
188
+ // this is a multi-node operation, so we need a transaction
189
+ const renamedInstance = await projectDatabase.$transaction(async tx => {
190
+ const existingRecord = await tx.instanceModel.findUnique({
191
+ where: { id: instanceId },
192
+ })
193
+
194
+ if (!existingRecord) {
195
+ throw new ProjectModelInstanceNotFoundError(project.id, instanceId)
196
+ }
197
+
198
+ const instance = instanceModelSchema.parse(existingRecord.model)
199
+ const newInstanceId = getInstanceId(instance.type, newName)
200
+
201
+ // check if new instance ID already exists
202
+ const conflictRecord = await tx.instanceModel.findUnique({
203
+ where: { id: newInstanceId },
204
+ })
205
+
206
+ if (conflictRecord) {
207
+ throw new ProjectModelInstanceAlreadyExistsError(project.id, newInstanceId)
208
+ }
209
+
210
+ // update instance
211
+ instance.id = newInstanceId
212
+ instance.name = newName
213
+
214
+ // delete old record and create new one
215
+ await tx.instanceModel.delete({ where: { id: instanceId } })
216
+ await tx.instanceModel.create({
217
+ data: { id: newInstanceId, model: instance },
218
+ })
219
+
220
+ // get all instances and hubs to update references
221
+ const [instanceRecords, hubRecords] = await Promise.all([
222
+ tx.instanceModel.findMany({ select: { id: true, model: true } }),
223
+ tx.hubModel.findMany({ select: { id: true, model: true } }),
224
+ ])
225
+
226
+ const instances = instanceRecords.map(record => instanceModelSchema.parse(record.model))
227
+ const hubs = hubRecords.map(record => hubModelSchema.parse(record.model))
228
+
229
+ // update references
230
+ updateInstanceReferences(instances, hubs, instanceId, newInstanceId)
231
+
232
+ // update modified instances and hubs back to database
233
+ await Promise.all([
234
+ ...instances.map(inst =>
235
+ tx.instanceModel.update({
236
+ where: { id: inst.id },
237
+ data: { model: inst },
238
+ }),
239
+ ),
240
+ ...hubs.map(hub =>
241
+ tx.hubModel.update({
242
+ where: { id: hub.id },
243
+ data: { model: hub },
244
+ }),
245
+ ),
246
+ ])
247
+
248
+ return instance
249
+ })
250
+
251
+ this.logger.info(
252
+ { projectId: project.id, oldInstanceId: instanceId, newInstanceId: renamedInstance.id },
253
+ "renamed instance in project model",
254
+ )
255
+
256
+ return renamedInstance
257
+ } catch (error) {
258
+ throw new ProjectModelOperationError("rename instance", project.id, error)
259
+ }
260
+ }
261
+
262
+ async updateHub(
263
+ project: ProjectOutput,
264
+ spec: ProjectModelStorageSpec,
265
+ hubId: string,
266
+ patch: HubModelPatch,
267
+ ): Promise<HubModel> {
268
+ assertDatabaseSpec(spec)
269
+
270
+ try {
271
+ const projectDatabase = await this.database.forProject(project.id)
272
+
273
+ const existingRecord = await projectDatabase.hubModel.findUnique({
274
+ where: { id: hubId },
275
+ })
276
+
277
+ if (!existingRecord) {
278
+ throw new ProjectModelHubNotFoundError(project.id, hubId)
279
+ }
280
+
281
+ const hub = hubModelSchema.parse(existingRecord.model)
282
+
283
+ // apply patch
284
+ applyHubPatch(hub, patch)
285
+
286
+ // update in database
287
+ await projectDatabase.hubModel.update({
288
+ where: { id: hubId },
289
+ data: { model: hub },
290
+ })
291
+
292
+ this.logger.info({ projectId: project.id, hubId }, "updated hub in project model")
293
+
294
+ return hub
295
+ } catch (error) {
296
+ throw new ProjectModelOperationError("update hub", project.id, error)
297
+ }
298
+ }
299
+
300
+ async deleteHub(
301
+ project: ProjectOutput,
302
+ spec: ProjectModelStorageSpec,
303
+ hubId: string,
304
+ ): Promise<void> {
305
+ assertDatabaseSpec(spec)
306
+
307
+ try {
308
+ const projectDatabase = await this.database.forProject(project.id)
309
+
310
+ // this is a multi-node operation, so we need a transaction
311
+ await projectDatabase.$transaction(async tx => {
312
+ const existingRecord = await tx.hubModel.findUnique({
313
+ where: { id: hubId },
314
+ })
315
+
316
+ if (!existingRecord) {
317
+ throw new ProjectModelHubNotFoundError(project.id, hubId)
318
+ }
319
+
320
+ // delete the hub
321
+ await tx.hubModel.delete({
322
+ where: { id: hubId },
323
+ })
324
+
325
+ // get all instances and hubs to clean up references
326
+ const [instanceRecords, hubRecords] = await Promise.all([
327
+ tx.instanceModel.findMany({ select: { id: true, model: true } }),
328
+ tx.hubModel.findMany({ select: { id: true, model: true } }),
329
+ ])
330
+
331
+ const instances = instanceRecords.map(record => instanceModelSchema.parse(record.model))
332
+ const hubs = hubRecords.map(record => hubModelSchema.parse(record.model))
333
+
334
+ // clean up references
335
+ cleanupHubReferences(instances, hubs, hubId)
336
+
337
+ // update modified instances and hubs back to database
338
+ await Promise.all([
339
+ ...instances.map(instance =>
340
+ tx.instanceModel.update({
341
+ where: { id: instance.id },
342
+ data: { model: instance },
343
+ }),
344
+ ),
345
+ ...hubs.map(hub =>
346
+ tx.hubModel.update({
347
+ where: { id: hub.id },
348
+ data: { model: hub },
349
+ }),
350
+ ),
351
+ ])
352
+ })
353
+
354
+ this.logger.info({ projectId: project.id, hubId }, "deleted hub from project model")
355
+ } catch (error) {
356
+ throw new ProjectModelOperationError("delete hub", project.id, error)
357
+ }
358
+ }
359
+
360
+ async createNodes(
361
+ project: ProjectOutput,
362
+ spec: ProjectModelStorageSpec,
363
+ instances: InstanceModel[],
364
+ hubs: HubModel[],
365
+ ): Promise<void> {
366
+ assertDatabaseSpec(spec)
367
+
368
+ try {
369
+ const projectDatabase = await this.database.forProject(project.id)
370
+
371
+ // this is a multi-node operation, so we need a transaction
372
+ await projectDatabase.$transaction(async tx => {
373
+ // check for conflicts
374
+ const [conflictingInstance, conflictingHub] = await Promise.all([
375
+ instances.length > 0
376
+ ? tx.instanceModel.findFirst({
377
+ where: { id: { in: instances.map(i => i.id) } },
378
+ select: { id: true },
379
+ })
380
+ : null,
381
+ hubs.length > 0
382
+ ? tx.hubModel.findFirst({
383
+ where: { id: { in: hubs.map(h => h.id) } },
384
+ select: { id: true },
385
+ })
386
+ : null,
387
+ ])
388
+
389
+ if (conflictingInstance) {
390
+ throw new ProjectModelInstanceAlreadyExistsError(project.id, conflictingInstance.id)
391
+ }
392
+
393
+ if (conflictingHub) {
394
+ throw new ProjectModelHubAlreadyExistsError(project.id, conflictingHub.id)
395
+ }
396
+
397
+ // create instances and hubs using createMany
398
+ await Promise.all([
399
+ instances.length > 0
400
+ ? tx.instanceModel.createMany({
401
+ data: instances.map(instance => ({
402
+ id: instance.id,
403
+ model: instance,
404
+ })),
405
+ })
406
+ : Promise.resolve(),
407
+ hubs.length > 0
408
+ ? tx.hubModel.createMany({
409
+ data: hubs.map(hub => ({
410
+ id: hub.id,
411
+ model: hub,
412
+ })),
413
+ })
414
+ : Promise.resolve(),
415
+ ])
416
+ })
417
+
418
+ this.logger.info(
419
+ { projectId: project.id, instanceCount: instances.length, hubCount: hubs.length },
420
+ "created nodes in project model",
421
+ )
422
+ } catch (error) {
423
+ throw new ProjectModelOperationError("create nodes", project.id, error)
424
+ }
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Type guard and casting helper for database storage spec.
430
+ *
431
+ * @param spec The project model storage specification.
432
+ * @throws Error if spec is not database type.
433
+ */
434
+ function assertDatabaseSpec(
435
+ spec: ProjectModelStorageSpec,
436
+ ): asserts spec is Extract<ProjectModelStorageSpec, { type: "database" }> {
437
+ if (spec.type !== "database") {
438
+ throw new Error(`Expected database spec, got ${spec.type}`)
439
+ }
440
+ }
@@ -0,0 +1,81 @@
1
+ import { BackendError } from "../shared"
2
+
3
+ /**
4
+ * Base error class for all project model related errors.
5
+ */
6
+ export class ProjectModelError extends BackendError {
7
+ constructor(message: string, cause?: unknown) {
8
+ super(message, cause)
9
+ this.name = "ProjectModelError"
10
+ }
11
+ }
12
+
13
+ /**
14
+ * Error thrown when a project model backend is not found for the given type.
15
+ */
16
+ export class ProjectModelBackendNotFoundError extends ProjectModelError {
17
+ constructor(backendType: string) {
18
+ super(`Project model backend "${backendType}" not found`)
19
+ this.name = "ProjectModelBackendNotFoundError"
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Error thrown when a project model is not found.
25
+ */
26
+ export class ProjectModelNotFoundError extends ProjectModelError {
27
+ constructor(projectId: string) {
28
+ super(`Project model not found for project "${projectId}"`)
29
+ this.name = "ProjectModelNotFoundError"
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Error thrown when an instance is not found in the project model.
35
+ */
36
+ export class ProjectModelInstanceNotFoundError extends ProjectModelError {
37
+ constructor(projectId: string, instanceId: string) {
38
+ super(`Instance "${instanceId}" not found in project "${projectId}"`)
39
+ this.name = "ProjectModelInstanceNotFoundError"
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Error thrown when a hub is not found in the project model.
45
+ */
46
+ export class ProjectModelHubNotFoundError extends ProjectModelError {
47
+ constructor(projectId: string, hubId: string) {
48
+ super(`Hub "${hubId}" not found in project "${projectId}"`)
49
+ this.name = "ProjectModelHubNotFoundError"
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Error thrown when attempting to create an instance that already exists.
55
+ */
56
+ export class ProjectModelInstanceAlreadyExistsError extends ProjectModelError {
57
+ constructor(projectId: string, instanceId: string) {
58
+ super(`Instance "${instanceId}" already exists in project "${projectId}"`)
59
+ this.name = "ProjectModelInstanceAlreadyExistsError"
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Error thrown when attempting to create a hub that already exists.
65
+ */
66
+ export class ProjectModelHubAlreadyExistsError extends ProjectModelError {
67
+ constructor(projectId: string, hubId: string) {
68
+ super(`Hub "${hubId}" already exists in project "${projectId}"`)
69
+ this.name = "ProjectModelHubAlreadyExistsError"
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Error thrown when a project model operation fails.
75
+ */
76
+ export class ProjectModelOperationError extends ProjectModelError {
77
+ constructor(operation: string, projectId: string, cause?: unknown) {
78
+ super(`Failed to ${operation} for project "${projectId}"`, cause)
79
+ this.name = "ProjectModelOperationError"
80
+ }
81
+ }
@@ -0,0 +1,24 @@
1
+ import type { Logger } from "pino"
2
+ import type { DatabaseManager } from "../database"
3
+ import type { ProjectModelBackend } from "./abstractions"
4
+ import { CodebaseProjectModelBackend } from "./backends/codebase"
5
+ import { DatabaseProjectModelBackend } from "./backends/database"
6
+
7
+ export async function createProjectModelBackends(
8
+ database: DatabaseManager,
9
+ logger: Logger,
10
+ ): Promise<Record<string, ProjectModelBackend>> {
11
+ const codebaseBackend = await CodebaseProjectModelBackend.create(
12
+ logger.child({ backend: "CodebaseProjectModelBackend" }),
13
+ )
14
+
15
+ const databaseBackend = new DatabaseProjectModelBackend(
16
+ database,
17
+ logger.child({ backend: "DatabaseProjectModelBackend" }),
18
+ )
19
+
20
+ return {
21
+ codebase: codebaseBackend,
22
+ database: databaseBackend,
23
+ }
24
+ }
@@ -0,0 +1,4 @@
1
+ export type { ProjectModelBackend } from "./abstractions"
2
+ export * from "../business/evaluation"
3
+ export * from "./errors"
4
+ export { createProjectModelBackends } from "./factory"