@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,357 @@
1
+ import type { Logger } from "pino"
2
+ import type { InstanceStateService, ProjectModelService } from "../business"
3
+ import type { LibraryBackend } from "../library"
4
+ import {
5
+ type ComponentModel,
6
+ type InstanceId,
7
+ type InstanceModel,
8
+ isUnitModel,
9
+ } from "@highstate/contract"
10
+ import { unique } from "remeda"
11
+ import {
12
+ type InputHashNode,
13
+ type InputHashOutput,
14
+ InputHashResolver,
15
+ InputResolver,
16
+ type InputResolverNode,
17
+ type InstanceState,
18
+ isTransientInstanceOperationStatus,
19
+ type LibraryModel,
20
+ type ProjectOutput,
21
+ type ResolvedInstanceInput,
22
+ } from "../shared"
23
+ import { BetterLock } from "better-lock"
24
+
25
+ export class OperationContext {
26
+ private readonly instanceMap = new Map<InstanceId, InstanceModel>()
27
+ private readonly instanceChildrenMap = new Map<InstanceId, InstanceModel[]>()
28
+
29
+ private readonly stateMap = new Map<InstanceId, InstanceState>()
30
+ private readonly dependentStateIdMap = new Map<InstanceId, Set<InstanceId>>()
31
+ private readonly stateChildIdMap = new Map<InstanceId, InstanceId[]>()
32
+
33
+ public readonly unitSourceHashMap = new Map<string, number>()
34
+
35
+ public inputResolver!: InputResolver
36
+ public readonly inputResolverNodes = new Map<string, InputResolverNode>()
37
+
38
+ public inputHashResolver!: InputHashResolver
39
+ public readonly inputHashNodes = new Map<string, InputHashNode>()
40
+ private readonly inputHashResolverLock = new BetterLock()
41
+
42
+ private readonly resolvedInstanceInputs = new Map<
43
+ string,
44
+ Record<InstanceId, ResolvedInstanceInput[]>
45
+ >()
46
+
47
+ private instanceIdToStateIdMap: Record<InstanceId, string> | null = null
48
+
49
+ private constructor(
50
+ public readonly project: ProjectOutput,
51
+ public readonly library: LibraryModel,
52
+ private readonly logger: Logger,
53
+ ) {}
54
+
55
+ public getInstance(instanceId: InstanceId): InstanceModel {
56
+ const instance = this.instanceMap.get(instanceId)
57
+ if (!instance) {
58
+ throw new Error(`Instance with ID ${instanceId} not found in the operation context`)
59
+ }
60
+
61
+ return instance
62
+ }
63
+
64
+ public getInstanceIds(): IterableIterator<InstanceId> {
65
+ return this.instanceMap.keys()
66
+ }
67
+
68
+ public getInstanceChildren(instanceId: InstanceId): InstanceModel[] {
69
+ return this.instanceChildrenMap.get(instanceId) ?? []
70
+ }
71
+
72
+ public getStateChildIds(instanceId: InstanceId): InstanceId[] {
73
+ return this.stateChildIdMap.get(instanceId) ?? []
74
+ }
75
+
76
+ public getResolvedInputs(
77
+ instanceId: InstanceId,
78
+ ): Record<string, ResolvedInstanceInput[]> | undefined {
79
+ return this.resolvedInstanceInputs.get(instanceId)
80
+ }
81
+
82
+ public setState(state: InstanceState): void {
83
+ this.stateMap.set(state.instanceId, state)
84
+
85
+ if (state.parentInstanceId) {
86
+ let children = this.stateChildIdMap.get(state.parentInstanceId)
87
+ if (!children) {
88
+ children = []
89
+ this.stateChildIdMap.set(state.parentInstanceId, children)
90
+ }
91
+
92
+ children.push(state.instanceId)
93
+ }
94
+
95
+ // traverse resolvedInputs to build dependency relationships
96
+ if (state.resolvedInputs) {
97
+ for (const inputGroup of Object.values(state.resolvedInputs)) {
98
+ for (const input of inputGroup) {
99
+ if (input.instanceId) {
100
+ this.addDependentState(state.instanceId, input.instanceId)
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ private addDependentState(instanceId: InstanceId, dependencyId: InstanceId): void {
108
+ let dependentStates = this.dependentStateIdMap.get(dependencyId)
109
+
110
+ if (!dependentStates) {
111
+ dependentStates = new Set<InstanceId>()
112
+ this.dependentStateIdMap.set(dependencyId, dependentStates)
113
+ }
114
+
115
+ dependentStates.add(instanceId)
116
+ }
117
+
118
+ public getState(instanceId: InstanceId): InstanceState {
119
+ const state = this.stateMap.get(instanceId)
120
+ if (!state) {
121
+ throw new Error(`Instance state for "${instanceId}" not found in the operation context`)
122
+ }
123
+
124
+ return state
125
+ }
126
+
127
+ public getDependentStates(instanceId: InstanceId): InstanceState[] {
128
+ const dependentStateIds = this.dependentStateIdMap.get(instanceId)
129
+ if (!dependentStateIds) {
130
+ return []
131
+ }
132
+
133
+ return Array.from(dependentStateIds)
134
+ .map(id => this.stateMap.get(id))
135
+ .filter((state): state is InstanceState => !!state)
136
+ }
137
+
138
+ public getDependencies(instanceId: InstanceId): InstanceModel[] {
139
+ const dependencies: InstanceModel[] = []
140
+ const resolvedInputs = this.resolvedInstanceInputs.get(instanceId)
141
+
142
+ if (resolvedInputs) {
143
+ for (const inputGroup of Object.values(resolvedInputs)) {
144
+ for (const resolvedInput of inputGroup) {
145
+ if (resolvedInput.input.instanceId && resolvedInput.input.instanceId !== instanceId) {
146
+ dependencies.push(this.getInstance(resolvedInput.input.instanceId))
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ return dependencies
153
+ }
154
+
155
+ private addInstance(instance: InstanceModel): void {
156
+ if (this.instanceMap.has(instance.id)) {
157
+ throw new Error(`Found multiple instances with the same ID: ${instance.id}`)
158
+ }
159
+
160
+ if (!(instance.type in this.library.components)) {
161
+ this.logger.warn(
162
+ `ignoring instance "${instance.id}" because its type "${instance.type}" is not in the library`,
163
+ )
164
+ return
165
+ }
166
+
167
+ this.instanceMap.set(instance.id, instance)
168
+
169
+ if (instance.parentId) {
170
+ let children = this.instanceChildrenMap.get(instance.parentId)
171
+ if (!children) {
172
+ children = []
173
+ this.instanceChildrenMap.set(instance.parentId, children)
174
+ }
175
+
176
+ children.push(instance)
177
+ }
178
+ }
179
+
180
+ private getSourceHashIfApplicable(
181
+ instance: InstanceModel,
182
+ component: ComponentModel,
183
+ ): number | undefined {
184
+ if (isUnitModel(component)) {
185
+ return this.unitSourceHashMap.get(instance.type)
186
+ }
187
+
188
+ return undefined
189
+ }
190
+
191
+ public getUpToDateInputHashOutput(instance: InstanceModel): Promise<InputHashOutput> {
192
+ return this.inputHashResolverLock.acquire(async () => {
193
+ const component = this.library.components[instance.type]
194
+
195
+ this.inputHashNodes.set(instance.id, {
196
+ instance,
197
+ component,
198
+ // biome-ignore lint/style/noNonNullAssertion: я разрешаю
199
+ resolvedInputs: this.resolvedInstanceInputs.get(instance.id)!,
200
+ state: this.stateMap.get(instance.id),
201
+ sourceHash: this.getSourceHashIfApplicable(instance, component),
202
+ })
203
+
204
+ this.inputHashResolver.invalidateSingle(instance.id)
205
+ await this.inputHashResolver.process()
206
+
207
+ return this.inputHashResolver.requireOutput(instance.id)
208
+ })
209
+ }
210
+
211
+ public setStates(states: InstanceState[]): void {
212
+ for (const state of states) {
213
+ this.setState(state)
214
+ }
215
+ }
216
+
217
+ public getInstanceIdToStateIdMap(): Record<InstanceId, string> {
218
+ if (this.instanceIdToStateIdMap) {
219
+ return this.instanceIdToStateIdMap
220
+ }
221
+
222
+ // build map on first request and cache it
223
+ const map: Record<InstanceId, string> = {}
224
+ for (const [instanceId, state] of this.stateMap.entries()) {
225
+ map[instanceId] = state.id
226
+ }
227
+
228
+ this.instanceIdToStateIdMap = map
229
+ return map
230
+ }
231
+
232
+ getUnfinishedOperationStates(): InstanceState[] {
233
+ const unfinishedStates: InstanceState[] = []
234
+ for (const state of this.stateMap.values()) {
235
+ if (isTransientInstanceOperationStatus(state.lastOperationState?.status)) {
236
+ unfinishedStates.push(state)
237
+ }
238
+ }
239
+
240
+ return unfinishedStates
241
+ }
242
+
243
+ public static async load(
244
+ projectId: string,
245
+ libraryBackend: LibraryBackend,
246
+ instanceStateService: InstanceStateService,
247
+ projectModelService: ProjectModelService,
248
+ logger: Logger,
249
+ ): Promise<OperationContext> {
250
+ const [{ instances, virtualInstances, hubs, ghostInstances }, project] =
251
+ await projectModelService.getProjectModel(projectId, {
252
+ includeVirtualInstances: true,
253
+ includeGhostInstances: true,
254
+ })
255
+
256
+ const [library, states] = await Promise.all([
257
+ libraryBackend.loadLibrary(project.libraryId),
258
+ instanceStateService.getInstanceStates(projectId, {
259
+ includeEvaluationState: true,
260
+ includeParentInstanceId: true,
261
+ }),
262
+ ])
263
+
264
+ const context = new OperationContext(
265
+ project,
266
+ library,
267
+ logger.child({ service: "OperationContext" }),
268
+ )
269
+
270
+ // prepare instances
271
+ for (const instance of instances) {
272
+ context.addInstance(instance)
273
+ }
274
+
275
+ for (const instance of ghostInstances) {
276
+ context.addInstance(instance)
277
+ }
278
+
279
+ for (const instance of virtualInstances) {
280
+ const contextInstance = context.instanceMap.get(instance.id)
281
+
282
+ if (contextInstance) {
283
+ // use evaluated inputs and outputs for real instances from their virtual counterparts
284
+ contextInstance.inputs = instance.resolvedInputs
285
+ contextInstance.outputs = instance.outputs
286
+ contextInstance.resolvedOutputs = instance.resolvedOutputs
287
+ } else if (instance.parentId) {
288
+ // always use resolved inputs for virtual instances
289
+ instance.inputs = instance.resolvedInputs
290
+ context.addInstance(instance)
291
+ } else {
292
+ context.logger.warn(
293
+ `ignoring virtual instance "${instance.id}" because it is not in the project or is not a part of known composite instance`,
294
+ )
295
+ }
296
+ }
297
+
298
+ const unitSources = await libraryBackend.getResolvedUnitSources(
299
+ project.libraryId,
300
+ unique(Array.from(context.instanceMap.values()).map(i => i.type)),
301
+ )
302
+
303
+ for (const unitSource of unitSources) {
304
+ context.unitSourceHashMap.set(unitSource.unitType, unitSource.sourceHash)
305
+ }
306
+
307
+ context.setStates(states)
308
+
309
+ // prepare input resolver
310
+ for (const instance of context.instanceMap.values()) {
311
+ context.inputResolverNodes.set(`instance:${instance.id}`, {
312
+ kind: "instance",
313
+ instance,
314
+ component: library.components[instance.type],
315
+ })
316
+ }
317
+
318
+ for (const hub of hubs) {
319
+ context.inputResolverNodes.set(`hub:${hub.id}`, { kind: "hub", hub })
320
+ }
321
+
322
+ context.inputResolver = new InputResolver(context.inputResolverNodes, logger)
323
+ context.inputResolver.addAllNodesToWorkset()
324
+
325
+ await context.inputResolver.process()
326
+
327
+ // resolve inputs for all instances and pass outputs to input hash resolver
328
+ for (const instance of context.instanceMap.values()) {
329
+ const output = context.inputResolver.requireOutput(`instance:${instance.id}`)
330
+ if (output.kind !== "instance") {
331
+ throw new Error("Unexpected output kind")
332
+ }
333
+
334
+ context.resolvedInstanceInputs.set(instance.id, output.resolvedInputs)
335
+
336
+ if (instance.kind === "unit") {
337
+ const component = context.library.components[instance.type]
338
+
339
+ context.inputHashNodes.set(instance.id, {
340
+ instance,
341
+ component,
342
+ resolvedInputs: output.resolvedInputs,
343
+ state: context.stateMap.get(instance.id),
344
+ sourceHash: context.getSourceHashIfApplicable(instance, component),
345
+ })
346
+ }
347
+ }
348
+
349
+ // prepare input hash resolver
350
+ context.inputHashResolver = new InputHashResolver(context.inputHashNodes, logger)
351
+ context.inputHashResolver.addAllNodesToWorkset()
352
+
353
+ await context.inputHashResolver.process()
354
+
355
+ return context
356
+ }
357
+ }
@@ -0,0 +1,357 @@
1
+ # Destroy Phase Examples
2
+
3
+ **Legend:**
4
+
5
+ - `✅` = Deployed instance
6
+ - `🚀` = Explicitly requested instance
7
+ - `👻` = Ghost instance (virtual)
8
+
9
+ ### Example 1: Simple Dependency Chain
10
+
11
+ **Test**: `should include all dependents in linear chain when destroyDependentInstances enabled`
12
+
13
+ ```mermaid
14
+ graph RL
15
+ A["A 🚀"]
16
+ B["B ✅"]
17
+ C["C ✅"]
18
+
19
+ C --> B --> A
20
+ ```
21
+
22
+ **Options:**
23
+
24
+ - `destroyDependentInstances`: `true` **(default)**
25
+
26
+ **Decision Steps**:
27
+
28
+ 1. `A` explicitly requested;
29
+ 2. `B` depends on `A`, cascade enabled → `B` included (dependent of `A`);
30
+ 3. `C` depends on `B`, cascade enabled → `C` included (dependent of `B`).
31
+
32
+ **Destroy Phase**: `C`, `B`, `A`
33
+
34
+ ### Example 2: Simple Dependency Chain with Cascade Disabled
35
+
36
+ **Test**: `should not include dependents in linear chain when destroyDependentInstances disabled`
37
+
38
+ ```mermaid
39
+ graph RL
40
+ A["A 🚀"]
41
+ B["B ✅"]
42
+ C["C ✅"]
43
+
44
+ C --> B --> A
45
+ ```
46
+
47
+ **Options:**
48
+
49
+ - `destroyDependentInstances`: `false`
50
+
51
+ **Decision Steps**:
52
+
53
+ 1. `A` explicitly requested;
54
+ 2. `B` depends on `A`, cascade disabled → `B` excluded;
55
+ 3. `C` depends on `B`, cascade disabled → `C` excluded.
56
+
57
+ **Destroy Phase**: `A`
58
+
59
+ ### Example 3: Simple Dependency Chain - Middle Node Requested
60
+
61
+ **Test**: `should include all dependents when middle node requested with destroyDependentInstances enabled`
62
+
63
+ ```mermaid
64
+ graph RL
65
+ A["A ✅"]
66
+ B["B 🚀"]
67
+ C["C ✅"]
68
+
69
+ C --> B --> A
70
+ ```
71
+
72
+ **Decision Steps**:
73
+
74
+ 1. `B` explicitly requested;
75
+ 2. `C` depends on `B`, cascade enabled → `C` included (dependent of `B`);
76
+ 3. `A` does not depend on `B` → `A` excluded.
77
+
78
+ **Destroy Phase**: `C`, `B`
79
+
80
+ ### Example 4: Composite Boundary Isolation
81
+
82
+ **Test**: `should not propagate beyond compositional inclusion`
83
+
84
+ ```mermaid
85
+ graph RL
86
+ subgraph GrandParent
87
+ subgraph Parent
88
+ A["A 🚀"]
89
+ end
90
+ B["B ✅"]
91
+ C["C ✅"]
92
+ end
93
+
94
+ B --> A
95
+ ```
96
+
97
+ **Decision Steps**:
98
+
99
+ 1. `A` explicitly requested;
100
+ 2. `B` depends on `A`, cascade enabled → `B` included (dependent of `A`);
101
+ 3. `A` is child of `Parent` → `Parent` included (compositional);
102
+ 4. `Parent` is child of `GrandParent` → `GrandParent` NOT included (compositional boundary);
103
+ 5. `C` is sibling of `Parent` but `GrandParent` is not included → `C` NOT included.
104
+
105
+ **Destroy Phase**: `B`, `A`, `Parent`
106
+
107
+ ### Example 5: Substantive Composite with Mixed Child States
108
+
109
+ **Test**: `should include all children of substantive composite`
110
+
111
+ ```mermaid
112
+ graph RL
113
+ subgraph Parent["Parent 🚀"]
114
+ Child1["Child1 ✅"]
115
+ Child2["Child2 ✅"]
116
+ Child3["Child3 ✅"]
117
+ end
118
+ ```
119
+
120
+ **Decision Steps**:
121
+
122
+ 1. `Parent` explicitly requested (substantive composite);
123
+ 2. `Child1` is child of substantive composite → `Child1` included;
124
+ 3. `Child2` is child of substantive composite → `Child2` included;
125
+ 4. `Child3` is child of substantive composite → `Child3` included.
126
+
127
+ **Destroy Phase**: `Child1`, `Child2`, `Child3`, `Parent`
128
+
129
+ ### Example 6: Nested Composites with Dependencies
130
+
131
+ **Test**: `should handle complex nested hierarchy with dependencies`
132
+
133
+ ```mermaid
134
+ graph RL
135
+ subgraph GrandParent
136
+ subgraph Parent1
137
+ Child1["Child1 🚀"]
138
+ Child2["Child2 ✅"]
139
+ end
140
+ subgraph Parent2
141
+ Child3["Child3 ✅"]
142
+ end
143
+ end
144
+
145
+ Child1 --> Child3
146
+ ```
147
+
148
+ **Decision Steps**:
149
+
150
+ 1. `Child1` explicitly requested;
151
+ 2. No instances depend on `Child1` (Child1 depends on Child3, not vice versa);
152
+ 3. `Child1` is child of `Parent1` → `Parent1` included (compositional);
153
+ 4. `Parent1` is child of `GrandParent` → `GrandParent` NOT included (compositional boundary).
154
+
155
+ **Destroy Phase**: `Child1`, `Parent1`
156
+
157
+ ### Example 7: Request Child with Isolated Destroy
158
+
159
+ **Test**: `should not include siblings when child explicitly requested`
160
+
161
+ ```mermaid
162
+ graph RL
163
+ subgraph Parent
164
+ Child1["Child1 🚀"]
165
+ Child2["Child2 ✅"]
166
+ Child3["Child3 ✅"]
167
+ end
168
+ ```
169
+
170
+ **Decision Steps**:
171
+
172
+ 1. `Child1` explicitly requested;
173
+ 2. No instances depend on `Child1`;
174
+ 3. `Child1` is child of `Parent` → `Parent` included (compositional);
175
+ 4. `Child2` and `Child3` are siblings but not affected → excluded.
176
+
177
+ **Destroy Phase**: `Child1`, `Parent`
178
+
179
+ ### Example 8: Cross-Composite Dependencies
180
+
181
+ **Test**: `should handle dependencies crossing composite boundaries`
182
+
183
+ ```mermaid
184
+ graph RL
185
+ subgraph CompositeA
186
+ ChildA["ChildA 🚀"]
187
+ end
188
+ subgraph CompositeB
189
+ ChildB["ChildB ✅"]
190
+ end
191
+
192
+ ChildB --> ChildA
193
+ ```
194
+
195
+ **Decision Steps**:
196
+
197
+ 1. `ChildA` explicitly requested;
198
+ 2. `ChildB` depends on `ChildA`, cascade enabled → `ChildB` included (dependent of `ChildA`);
199
+ 3. `ChildA` is child of `CompositeA` → `CompositeA` included (compositional);
200
+ 4. `ChildB` is child of `CompositeB`, included due to external dependency → `CompositeB` included (substantive).
201
+
202
+ **Destroy Phase**: `ChildB`, `CompositeB`, `ChildA`, `CompositeA`
203
+
204
+ ### Example 9: Unrelated Instance Isolation
205
+
206
+ **Test**: `should not include unrelated instances that don't depend on destroyed instance`
207
+
208
+ ```mermaid
209
+ graph RL
210
+ subgraph Parent
211
+ Child1["Child1 ✅"]
212
+ Child2["Child2 ✅"]
213
+ end
214
+ UnrelatedX["UnrelatedX ✅"]
215
+ ExternalY["ExternalY 🚀"]
216
+
217
+ Child1 --> ExternalY
218
+ Child2 --> UnrelatedX
219
+ ```
220
+
221
+ **Decision Steps**:
222
+
223
+ 1. `ExternalY` explicitly requested;
224
+ 2. `Child1` depends on `ExternalY`, cascade enabled → `Child1` included (dependent of `ExternalY`);
225
+ 3. `Child1` is child of `Parent`, included due to external dependency → `Parent` becomes substantive;
226
+ 4. `Child2` is child of substantive composite → `Child2` included;
227
+ 5. `UnrelatedX` does not depend on any destroyed instance → `UnrelatedX` excluded.
228
+
229
+ **Destroy Phase**: `Child1`, `Child2`, `Parent`, `ExternalY`
230
+
231
+ ### Example 10: Multiple Explicit Requests
232
+
233
+ **Test**: `should handle multiple explicit requests with overlapping dependencies`
234
+
235
+ ```mermaid
236
+ graph RL
237
+ A["A 🚀"]
238
+ B["B ✅"]
239
+ C["C 🚀"]
240
+
241
+ C --> B --> A
242
+ ```
243
+
244
+ **Decision Steps**:
245
+
246
+ 1. `A` explicitly requested;
247
+ 2. `C` explicitly requested;
248
+ 3. `B` depends on `A`, cascade enabled → `B` included (dependent of `A`);
249
+ 4. `C` depends on `B`, `B` already included → no additional change.
250
+
251
+ **Destroy Phase**: `C`, `B`, `A`
252
+
253
+ ### Example 11: Deep Nesting with Boundary Isolation
254
+
255
+ **Test**: `should isolate boundaries in deep composite hierarchies`
256
+
257
+ ```mermaid
258
+ graph RL
259
+ subgraph GreatGrandParent
260
+ subgraph GrandParent
261
+ subgraph Parent
262
+ Child["Child 🚀"]
263
+ end
264
+ Uncle["Uncle ✅"]
265
+ end
266
+ GreatUncle["GreatUncle ✅"]
267
+ end
268
+ ```
269
+
270
+ **Decision Steps**:
271
+
272
+ 1. `Child` explicitly requested;
273
+ 2. No instances depend on `Child`;
274
+ 3. `Child` is child of `Parent` → `Parent` included (compositional);
275
+ 4. `Parent` is child of `GrandParent` → `GrandParent` NOT included (compositional boundary);
276
+ 5. `Uncle` and `GreatUncle` not affected.
277
+
278
+ **Destroy Phase**: `Child`, `Parent`
279
+
280
+ ### Example 12: Diamond Dependency Pattern
281
+
282
+ **Test**: `should handle diamond dependency correctly`
283
+
284
+ ```mermaid
285
+ graph RL
286
+ A["A 🚀"]
287
+ B["B ✅"]
288
+ C["C ✅"]
289
+ D["D ✅"]
290
+
291
+ D --> B
292
+ D --> C
293
+ B --> A
294
+ C --> A
295
+ ```
296
+
297
+ **Decision Steps**:
298
+
299
+ 1. `A` explicitly requested;
300
+ 2. `B` depends on `A`, cascade enabled → `B` included (dependent of `A`);
301
+ 3. `C` depends on `A`, cascade enabled → `C` included (dependent of `A`);
302
+ 4. `D` depends on `B`, cascade enabled → `D` included (dependent of `B`);
303
+ 5. `D` also depends on `C`, already included → no additional change.
304
+
305
+ **Destroy Phase**: `D`, `B`, `C`, `A`
306
+
307
+ ### Example 13: Dependency Chain with Partial Destruction Disabled
308
+
309
+ **Test**: `should include dependency chain and force siblings when partial destruction disabled`
310
+
311
+ ```mermaid
312
+ graph RL
313
+ subgraph Parent
314
+ Child1["Child1 ✅"]
315
+ Child2["Child2 ✅"]
316
+ end
317
+ ExternalX["ExternalX 🚀"]
318
+
319
+ Child1 --> ExternalX
320
+ ```
321
+
322
+ **Decision Steps**:
323
+
324
+ 1. `ExternalX` explicitly requested;
325
+ 2. `Child1` depends on `ExternalX`, cascade enabled → `Child1` included (dependent of `ExternalX`);
326
+ 3. `Parent` has child included due to external dependency, so `Parent` becomes substantive composite;
327
+ 4. `Child2` is sibling of `Child1` in substantive composite, partial destruction disabled → `Child2` included.
328
+
329
+ **Destroy Phase**: `Child1`, `Child2`, `Parent`, `ExternalX`
330
+
331
+ ### Example 14: Dependency Chain with Partial Destruction Enabled
332
+
333
+ **Test**: `should include dependency chain without forcing siblings when partial destruction enabled`
334
+
335
+ ```mermaid
336
+ graph RL
337
+ subgraph Parent
338
+ Child1["Child1 ✅"]
339
+ Child2["Child2 ✅"]
340
+ end
341
+ ExternalX["ExternalX 🚀"]
342
+
343
+ Child1 --> ExternalX
344
+ ```
345
+
346
+ **Options:**
347
+
348
+ - `allowPartialCompositeInstanceDestruction`: `true`
349
+
350
+ **Decision Steps**:
351
+
352
+ 1. `ExternalX` explicitly requested;
353
+ 2. `Child1` depends on `ExternalX`, cascade enabled → `Child1` included (dependent of `ExternalX`);
354
+ 3. `Parent` has child included due to external dependency, so `Parent` becomes substantive composite;
355
+ 4. `Child2` is sibling of `Child1` in substantive composite, partial destruction enabled → `Child2` excluded.
356
+
357
+ **Destroy Phase**: `Child1`, `Parent`, `ExternalX`