@highstate/backend 0.9.18 → 0.9.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/dist/chunk-5WVU2AK4.js +1535 -0
  2. package/dist/chunk-5WVU2AK4.js.map +1 -0
  3. package/dist/{chunk-OU5OQBLB.js → chunk-I7BWSAN6.js} +3 -28
  4. package/dist/{chunk-OU5OQBLB.js.map → chunk-I7BWSAN6.js.map} +1 -1
  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 +7587 -7291
  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 +27 -0
  32. package/prisma/project/artifact.prisma +52 -0
  33. package/prisma/project/custom-status.prisma +46 -0
  34. package/prisma/project/evaluation.prisma +35 -0
  35. package/prisma/project/instance.prisma +160 -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 +41 -0
  72. package/prisma/project/secret.prisma +42 -0
  73. package/prisma/project/service-account.prisma +36 -0
  74. package/prisma/project/terminal.prisma +90 -0
  75. package/prisma/project/trigger.prisma +31 -0
  76. package/prisma/project/unlock-method.prisma +32 -0
  77. package/prisma/project/worker.prisma +138 -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 +465 -130
  97. package/src/business/secret.ts +186 -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 +435 -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 +33 -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 +64 -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 +222 -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 +1 -1
  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,365 @@
1
+ import type { Logger } from "pino"
2
+ import type { ProjectModel, ProjectModelStorageSpec, ProjectOutput } from "../../shared"
3
+ import type { ProjectModelBackend } from "../abstractions"
4
+ import { constants } from "node:fs"
5
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises"
6
+ import { resolve } from "node:path"
7
+ import {
8
+ getInstanceId,
9
+ type HubModel,
10
+ type HubModelPatch,
11
+ hubModelSchema,
12
+ type InstanceModel,
13
+ type InstanceModelPatch,
14
+ instanceModelSchema,
15
+ } from "@highstate/contract"
16
+ import { BetterLock } from "better-lock"
17
+ import { parse, stringify } from "yaml"
18
+ import { z } from "zod"
19
+ import { resolveMainLocalProject } from "../../common"
20
+ import {
21
+ ProjectModelHubAlreadyExistsError,
22
+ ProjectModelHubNotFoundError,
23
+ ProjectModelInstanceAlreadyExistsError,
24
+ ProjectModelInstanceNotFoundError,
25
+ ProjectModelOperationError,
26
+ } from "../errors"
27
+ import {
28
+ applyHubPatch,
29
+ applyInstancePatch,
30
+ cleanupHubReferences,
31
+ cleanupInstanceReferences,
32
+ updateInstanceReferences,
33
+ } from "../utils"
34
+
35
+ const codebaseProjectDataSchema = z.object({
36
+ instances: z.record(z.string(), instanceModelSchema),
37
+ hubs: z.record(z.string(), hubModelSchema),
38
+ })
39
+
40
+ /**
41
+ * A project model backend that stores the project models locally on disk.
42
+ *
43
+ * By default, the project models are stored in the `projects` directory near the "package.json" file.
44
+ */
45
+ export class CodebaseProjectModelBackend implements ProjectModelBackend {
46
+ private readonly lock = new BetterLock()
47
+
48
+ constructor(
49
+ private readonly projectsDir: string,
50
+ private readonly logger: Logger,
51
+ ) {}
52
+
53
+ async getProjectModel(
54
+ project: ProjectOutput,
55
+ spec: ProjectModelStorageSpec,
56
+ ): Promise<ProjectModel> {
57
+ assertCodebaseSpec(spec)
58
+
59
+ try {
60
+ const projectData = await this.loadProject(project.name)
61
+
62
+ return {
63
+ instances: Object.values(projectData.instances),
64
+ hubs: Object.values(projectData.hubs),
65
+ }
66
+ } catch (error) {
67
+ throw new ProjectModelOperationError("get project model", project.id, error)
68
+ }
69
+ }
70
+
71
+ async createProjectModel(project: ProjectOutput, spec: ProjectModelStorageSpec): Promise<void> {
72
+ assertCodebaseSpec(spec)
73
+
74
+ try {
75
+ const projectPath = this.getProjectPath(project.name)
76
+
77
+ // check if project file already exists
78
+ try {
79
+ await access(projectPath, constants.F_OK)
80
+ throw new Error(`Project "${project.name}" already exists`)
81
+ } catch (error) {
82
+ // if access throws ENOENT, file doesn't exist (which is what we want)
83
+ if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
84
+ throw error
85
+ }
86
+ }
87
+
88
+ await this.withProject(project.name, () => {
89
+ // create an empty project
90
+ return { instances: {}, hubs: {} }
91
+ })
92
+
93
+ this.logger.info(
94
+ { projectId: project.id, projectName: project.name },
95
+ "created project model",
96
+ )
97
+ } catch (error) {
98
+ throw new ProjectModelOperationError("create project model", project.id, error)
99
+ }
100
+ }
101
+
102
+ async updateInstance(
103
+ project: ProjectOutput,
104
+ spec: ProjectModelStorageSpec,
105
+ instanceId: string,
106
+ patch: InstanceModelPatch,
107
+ ): Promise<InstanceModel> {
108
+ assertCodebaseSpec(spec)
109
+
110
+ try {
111
+ return await this.withInstance(project.name, instanceId, instance => {
112
+ applyInstancePatch(instance, patch)
113
+ return instance
114
+ })
115
+ } catch (error) {
116
+ throw new ProjectModelOperationError("update instance", project.id, error)
117
+ }
118
+ }
119
+
120
+ async deleteInstance(
121
+ project: ProjectOutput,
122
+ spec: ProjectModelStorageSpec,
123
+ instanceId: string,
124
+ ): Promise<void> {
125
+ assertCodebaseSpec(spec)
126
+
127
+ try {
128
+ await this.withProject(project.name, projectData => {
129
+ if (!projectData.instances[instanceId]) {
130
+ throw new ProjectModelInstanceNotFoundError(project.id, instanceId)
131
+ }
132
+
133
+ delete projectData.instances[instanceId]
134
+
135
+ cleanupInstanceReferences(
136
+ Object.values(projectData.instances),
137
+ Object.values(projectData.hubs),
138
+ instanceId,
139
+ )
140
+ })
141
+
142
+ this.logger.info({ projectId: project.id, instanceId }, "deleted instance from project model")
143
+ } catch (error) {
144
+ throw new ProjectModelOperationError("delete instance", project.id, error)
145
+ }
146
+ }
147
+
148
+ async renameInstance(
149
+ project: ProjectOutput,
150
+ spec: ProjectModelStorageSpec,
151
+ instanceId: string,
152
+ newName: string,
153
+ ): Promise<InstanceModel> {
154
+ assertCodebaseSpec(spec)
155
+
156
+ try {
157
+ return await this.withProject(project.name, projectData => {
158
+ // rename the instance
159
+ const instance = projectData.instances[instanceId]
160
+ if (!instance) {
161
+ throw new ProjectModelInstanceNotFoundError(project.id, instanceId)
162
+ }
163
+
164
+ const newInstanceId = getInstanceId(instance.type, newName)
165
+ if (projectData.instances[newInstanceId]) {
166
+ throw new ProjectModelInstanceAlreadyExistsError(project.id, newInstanceId)
167
+ }
168
+
169
+ delete projectData.instances[instanceId]
170
+ instance.id = newInstanceId
171
+ instance.name = newName
172
+ projectData.instances[newInstanceId] = instance
173
+
174
+ updateInstanceReferences(
175
+ Object.values(projectData.instances),
176
+ Object.values(projectData.hubs),
177
+ instanceId,
178
+ instance.id,
179
+ )
180
+
181
+ this.logger.info(
182
+ { projectId: project.id, oldInstanceId: instanceId, newInstanceId: instance.id },
183
+ "renamed instance in project model",
184
+ )
185
+
186
+ return instance
187
+ })
188
+ } catch (error) {
189
+ throw new ProjectModelOperationError("rename instance", project.id, error)
190
+ }
191
+ }
192
+
193
+ async updateHub(
194
+ project: ProjectOutput,
195
+ spec: ProjectModelStorageSpec,
196
+ hubId: string,
197
+ patch: HubModelPatch,
198
+ ): Promise<HubModel> {
199
+ assertCodebaseSpec(spec)
200
+
201
+ try {
202
+ return await this.withProject(project.name, projectData => {
203
+ const hub = projectData.hubs[hubId]
204
+ if (!hub) {
205
+ throw new ProjectModelHubNotFoundError(project.id, hubId)
206
+ }
207
+
208
+ applyHubPatch(hub, patch)
209
+
210
+ this.logger.info({ projectId: project.id, hubId }, "updated hub in project model")
211
+
212
+ return hub
213
+ })
214
+ } catch (error) {
215
+ throw new ProjectModelOperationError("update hub", project.id, error)
216
+ }
217
+ }
218
+
219
+ async deleteHub(
220
+ project: ProjectOutput,
221
+ spec: ProjectModelStorageSpec,
222
+ hubId: string,
223
+ ): Promise<void> {
224
+ assertCodebaseSpec(spec)
225
+
226
+ try {
227
+ await this.withProject(project.name, projectData => {
228
+ if (!projectData.hubs[hubId]) {
229
+ throw new ProjectModelHubNotFoundError(project.id, hubId)
230
+ }
231
+
232
+ delete projectData.hubs[hubId]
233
+
234
+ cleanupHubReferences(
235
+ Object.values(projectData.instances),
236
+ Object.values(projectData.hubs),
237
+ hubId,
238
+ )
239
+ })
240
+
241
+ this.logger.info({ projectId: project.id, hubId }, "deleted hub from project model")
242
+ } catch (error) {
243
+ throw new ProjectModelOperationError("delete hub", project.id, error)
244
+ }
245
+ }
246
+
247
+ async createNodes(
248
+ project: ProjectOutput,
249
+ spec: ProjectModelStorageSpec,
250
+ instances: InstanceModel[],
251
+ hubs: HubModel[],
252
+ ): Promise<void> {
253
+ assertCodebaseSpec(spec)
254
+
255
+ try {
256
+ await this.withProject(project.name, projectData => {
257
+ // ensure that instances and hubs do not conflict with existing ones
258
+ for (const instance of instances) {
259
+ if (projectData.instances[instance.id]) {
260
+ throw new ProjectModelInstanceAlreadyExistsError(project.id, instance.id)
261
+ }
262
+ projectData.instances[instance.id] = instance
263
+ }
264
+
265
+ for (const hub of hubs) {
266
+ if (projectData.hubs[hub.id]) {
267
+ throw new ProjectModelHubAlreadyExistsError(project.id, hub.id)
268
+ }
269
+ projectData.hubs[hub.id] = hub
270
+ }
271
+
272
+ return {}
273
+ })
274
+
275
+ this.logger.info(
276
+ { projectId: project.id, instanceCount: instances.length, hubCount: hubs.length },
277
+ "created nodes in project model",
278
+ )
279
+ } catch (error) {
280
+ throw new ProjectModelOperationError("create nodes", project.id, error)
281
+ }
282
+ }
283
+
284
+ private getProjectPath(projectName: string) {
285
+ return `${this.projectsDir}/${projectName}.yaml`
286
+ }
287
+
288
+ private async loadProject(projectName: string) {
289
+ const projectPath = this.getProjectPath(projectName)
290
+
291
+ try {
292
+ const content = await readFile(projectPath, "utf-8")
293
+
294
+ return codebaseProjectDataSchema.parse(parse(content))
295
+ } catch (error) {
296
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
297
+ return { instances: {}, hubs: {} }
298
+ }
299
+
300
+ throw error
301
+ }
302
+ }
303
+
304
+ private async writeProject(
305
+ projectName: string,
306
+ project: z.infer<typeof codebaseProjectDataSchema>,
307
+ ) {
308
+ const projectPath = this.getProjectPath(projectName)
309
+ const content = stringify(project, undefined, 2)
310
+
311
+ await writeFile(projectPath, content)
312
+ }
313
+
314
+ private async withInstance<T>(
315
+ projectName: string,
316
+ instanceId: string,
317
+ callback: (instance: InstanceModel) => T,
318
+ ): Promise<T> {
319
+ return await this.withProject(projectName, projectData => {
320
+ const instance = projectData.instances[instanceId]
321
+ if (!instance) {
322
+ throw new Error(`Instance ${instanceId} not found`)
323
+ }
324
+
325
+ return callback(instance)
326
+ })
327
+ }
328
+
329
+ private async withProject<T>(
330
+ projectName: string,
331
+ callback: (project: z.infer<typeof codebaseProjectDataSchema>) => T,
332
+ ): Promise<T> {
333
+ return await this.lock.acquire(projectName, async () => {
334
+ const projectData = await this.loadProject(projectName)
335
+
336
+ const result = callback(projectData)
337
+ await this.writeProject(projectName, projectData)
338
+
339
+ return result
340
+ })
341
+ }
342
+
343
+ public static async create(logger: Logger): Promise<CodebaseProjectModelBackend> {
344
+ const [mainProjectPath] = await resolveMainLocalProject()
345
+ const projectsPath = resolve(mainProjectPath, "projects")
346
+
347
+ await mkdir(projectsPath, { recursive: true })
348
+
349
+ return new CodebaseProjectModelBackend(projectsPath, logger)
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Type guard and casting helper for codebase storage spec.
355
+ *
356
+ * @param spec The project model storage specification.
357
+ * @throws Error if spec is not codebase type.
358
+ */
359
+ function assertCodebaseSpec(
360
+ spec: ProjectModelStorageSpec,
361
+ ): asserts spec is Extract<ProjectModelStorageSpec, { type: "codebase" }> {
362
+ if (spec.type !== "codebase") {
363
+ throw new Error(`Expected codebase spec, got ${spec.type}`)
364
+ }
365
+ }