@highstate/backend 0.9.16 → 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 (302) hide show
  1. package/dist/chunk-5WVU2AK4.js +1535 -0
  2. package/dist/chunk-5WVU2AK4.js.map +1 -0
  3. package/dist/chunk-I7BWSAN6.js +49 -0
  4. package/dist/chunk-I7BWSAN6.js.map +1 -0
  5. package/dist/{chunk-RCB4AFGD.js → chunk-VB4YL327.js} +51 -71
  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 +5 -4
  10. package/dist/index.js +7676 -6634
  11. package/dist/index.js.map +1 -1
  12. package/dist/library/package-resolution-worker.js +8 -6
  13. package/dist/library/package-resolution-worker.js.map +1 -1
  14. package/dist/library/worker/main.js +63 -58
  15. package/dist/library/worker/main.js.map +1 -1
  16. package/dist/shared/index.js +3 -216
  17. package/dist/shared/index.js.map +1 -1
  18. package/package.json +23 -11
  19. package/prisma/backend/_schema/layout.prisma +7 -0
  20. package/prisma/backend/_schema/library.prisma +17 -0
  21. package/prisma/backend/_schema/project.prisma +101 -0
  22. package/prisma/backend/_schema/pulumi.prisma +17 -0
  23. package/prisma/backend/postgresql/main.prisma +17 -0
  24. package/prisma/backend/sqlite/main.prisma +17 -0
  25. package/prisma/backend/sqlite/migrations/20250817070609_initiial/migration.sql +34 -0
  26. package/prisma/backend/sqlite/migrations/20250817104948_add_fields/migration.sql +59 -0
  27. package/prisma/backend/sqlite/migrations/20250818082732_add_models/migration.sql +41 -0
  28. package/prisma/backend/sqlite/migrations/20250818083106_a/migration.sql +19 -0
  29. package/prisma/backend/sqlite/migrations/20250818101945_hi/migration.sql +1 -0
  30. package/prisma/backend/sqlite/migrations/20250819082315_a/migration.sql +5 -0
  31. package/prisma/backend/sqlite/migrations/migration_lock.toml +3 -0
  32. package/prisma/project/api-key.prisma +27 -0
  33. package/prisma/project/artifact.prisma +52 -0
  34. package/prisma/project/custom-status.prisma +46 -0
  35. package/prisma/project/evaluation.prisma +35 -0
  36. package/prisma/project/instance.prisma +160 -0
  37. package/prisma/project/layout.prisma +23 -0
  38. package/prisma/project/lock.prisma +18 -0
  39. package/prisma/project/main.prisma +17 -0
  40. package/prisma/project/migrations/20250816081310_initial/migration.sql +300 -0
  41. package/prisma/project/migrations/20250816082523_test/migration.sql +72 -0
  42. package/prisma/project/migrations/20250818065643_update/migration.sql +42 -0
  43. package/prisma/project/migrations/20250818070758_a/migration.sql +8 -0
  44. package/prisma/project/migrations/20250818070913_a/migration.sql +8 -0
  45. package/prisma/project/migrations/20250818082720_add_motels/migration.sql +11 -0
  46. package/prisma/project/migrations/20250818112523_hello/migration.sql +35 -0
  47. package/prisma/project/migrations/20250819082305_a/migration.sql +14 -0
  48. package/prisma/project/migrations/20250819165004_add_missing_fields/migration.sql +216 -0
  49. package/prisma/project/migrations/20250819171309_a/migration.sql +22 -0
  50. package/prisma/project/migrations/20250820113949_a/migration.sql +66 -0
  51. package/prisma/project/migrations/20250820144256_b/migration.sql +31 -0
  52. package/prisma/project/migrations/20250820145547_a/migration.sql +24 -0
  53. package/prisma/project/migrations/20250820182517_b/migration.sql +2 -0
  54. package/prisma/project/migrations/20250821172324_a/migration.sql +2 -0
  55. package/prisma/project/migrations/20250822081339_a/migration.sql +219 -0
  56. package/prisma/project/migrations/20250822083742_b/migration.sql +1 -0
  57. package/prisma/project/migrations/20250822105134_boom/migration.sql +1 -0
  58. package/prisma/project/migrations/20250822141028_b/migration.sql +1 -0
  59. package/prisma/project/migrations/20250822142342_b/migration.sql +16 -0
  60. package/prisma/project/migrations/20250824072720_a/migration.sql +1 -0
  61. package/prisma/project/migrations/20250824093656_b/migration.sql +21 -0
  62. package/prisma/project/migrations/20250825082518_a/migration.sql +1 -0
  63. package/prisma/project/migrations/20250825085343_b/migration.sql +1 -0
  64. package/prisma/project/migrations/20250825091312_a/migration.sql +1 -0
  65. package/prisma/project/migrations/20250903095431_hi/migration.sql +44 -0
  66. package/prisma/project/migrations/20250903174255_a/migration.sql +24 -0
  67. package/prisma/project/migrations/20250908095205_hi/migration.sql +18 -0
  68. package/prisma/project/migrations/20250909155857_hi/migration.sql +15 -0
  69. package/prisma/project/migrations/migration_lock.toml +3 -0
  70. package/prisma/project/model.prisma +37 -0
  71. package/prisma/project/operation.prisma +148 -0
  72. package/prisma/project/page.prisma +41 -0
  73. package/prisma/project/secret.prisma +42 -0
  74. package/prisma/project/service-account.prisma +36 -0
  75. package/prisma/project/terminal.prisma +90 -0
  76. package/prisma/project/trigger.prisma +31 -0
  77. package/prisma/project/unlock-method.prisma +32 -0
  78. package/prisma/project/worker.prisma +138 -0
  79. package/src/artifact/abstractions.ts +13 -13
  80. package/src/artifact/encryption.ts +31 -15
  81. package/src/artifact/factory.ts +7 -10
  82. package/src/artifact/local.ts +33 -50
  83. package/src/business/api-key.ts +24 -36
  84. package/src/business/artifact.test.ts +978 -0
  85. package/src/business/artifact.ts +136 -215
  86. package/src/business/evaluation.ts +328 -0
  87. package/src/business/index.ts +5 -1
  88. package/src/business/instance-lock.test.ts +1060 -0
  89. package/src/business/instance-lock.ts +387 -77
  90. package/src/business/instance-state.test.ts +735 -0
  91. package/src/business/instance-state.ts +604 -217
  92. package/src/business/operation.test.ts +439 -0
  93. package/src/business/operation.ts +174 -208
  94. package/src/business/project-model.ts +258 -0
  95. package/src/business/project-unlock.ts +172 -112
  96. package/src/business/project.ts +407 -0
  97. package/src/business/secret.test.ts +513 -0
  98. package/src/business/secret.ts +194 -131
  99. package/src/business/settings.test.ts +695 -0
  100. package/src/business/settings.ts +855 -0
  101. package/src/business/terminal-session.ts +90 -0
  102. package/src/business/unit-extra.test.ts +539 -0
  103. package/src/business/unit-extra.ts +160 -0
  104. package/src/business/worker.test.ts +391 -0
  105. package/src/business/worker.ts +250 -114
  106. package/src/common/codebase.ts +65 -0
  107. package/src/common/index.ts +3 -2
  108. package/src/common/logger.ts +5 -0
  109. package/src/common/utils.ts +4 -3
  110. package/src/config.ts +15 -12
  111. package/src/database/_generated/backend/postgresql/client.ts +72 -0
  112. package/src/database/_generated/backend/postgresql/commonInputTypes.ts +350 -0
  113. package/src/database/_generated/backend/postgresql/enums.ts +13 -0
  114. package/src/database/_generated/backend/postgresql/internal/class.ts +320 -0
  115. package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +1238 -0
  116. package/src/database/_generated/backend/postgresql/models/Library.ts +1263 -0
  117. package/src/database/_generated/backend/postgresql/models/Project.ts +2175 -0
  118. package/src/database/_generated/backend/postgresql/models/ProjectModelStorage.ts +1263 -0
  119. package/src/database/_generated/backend/postgresql/models/ProjectSpace.ts +1602 -0
  120. package/src/database/_generated/backend/postgresql/models/PulumiBackend.ts +1263 -0
  121. package/src/database/_generated/backend/postgresql/models/UserWorkspaseLayout.ts +1065 -0
  122. package/src/database/_generated/backend/postgresql/models.ts +16 -0
  123. package/src/database/_generated/backend/postgresql/pjtg.ts +182 -0
  124. package/src/database/_generated/backend/sqlite/client.ts +72 -0
  125. package/src/database/_generated/backend/sqlite/commonInputTypes.ts +331 -0
  126. package/src/database/_generated/backend/sqlite/enums.ts +13 -0
  127. package/src/database/_generated/backend/sqlite/internal/class.ts +318 -0
  128. package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +1207 -0
  129. package/src/database/_generated/backend/sqlite/models/Library.ts +1261 -0
  130. package/src/database/_generated/backend/sqlite/models/Project.ts +2169 -0
  131. package/src/database/_generated/backend/sqlite/models/ProjectModelStorage.ts +1261 -0
  132. package/src/database/_generated/backend/sqlite/models/ProjectSpace.ts +1599 -0
  133. package/src/database/_generated/backend/sqlite/models/PulumiBackend.ts +1261 -0
  134. package/src/database/_generated/backend/sqlite/models/UserWorkspaseLayout.ts +1063 -0
  135. package/src/database/_generated/backend/sqlite/models.ts +16 -0
  136. package/src/database/_generated/backend/sqlite/pjtg.ts +182 -0
  137. package/src/database/_generated/project/client.ts +204 -0
  138. package/src/database/_generated/project/commonInputTypes.ts +827 -0
  139. package/src/database/_generated/project/enums.ts +104 -0
  140. package/src/database/_generated/project/internal/class.ts +479 -0
  141. package/src/database/_generated/project/internal/prismaNamespace.ts +2974 -0
  142. package/src/database/_generated/project/models/ApiKey.ts +1506 -0
  143. package/src/database/_generated/project/models/Artifact.ts +2051 -0
  144. package/src/database/_generated/project/models/HubModel.ts +1125 -0
  145. package/src/database/_generated/project/models/InstanceCustomStatus.ts +1713 -0
  146. package/src/database/_generated/project/models/InstanceEvaluationState.ts +1312 -0
  147. package/src/database/_generated/project/models/InstanceLock.ts +1268 -0
  148. package/src/database/_generated/project/models/InstanceModel.ts +1125 -0
  149. package/src/database/_generated/project/models/InstanceOperationState.ts +1707 -0
  150. package/src/database/_generated/project/models/InstanceState.ts +4613 -0
  151. package/src/database/_generated/project/models/Operation.ts +1647 -0
  152. package/src/database/_generated/project/models/OperationLog.ts +1455 -0
  153. package/src/database/_generated/project/models/Page.ts +1838 -0
  154. package/src/database/_generated/project/models/Secret.ts +1692 -0
  155. package/src/database/_generated/project/models/ServiceAccount.ts +2165 -0
  156. package/src/database/_generated/project/models/Terminal.ts +2038 -0
  157. package/src/database/_generated/project/models/TerminalSession.ts +1454 -0
  158. package/src/database/_generated/project/models/TerminalSessionLog.ts +1280 -0
  159. package/src/database/_generated/project/models/Trigger.ts +1430 -0
  160. package/src/database/_generated/project/models/UnlockMethod.ts +1220 -0
  161. package/src/database/_generated/project/models/UserCompositeViewport.ts +1280 -0
  162. package/src/database/_generated/project/models/UserProjectViewport.ts +1059 -0
  163. package/src/database/_generated/project/models/Worker.ts +1459 -0
  164. package/src/database/_generated/project/models/WorkerUnitRegistration.ts +1524 -0
  165. package/src/database/_generated/project/models/WorkerVersion.ts +1974 -0
  166. package/src/database/_generated/project/models/WorkerVersionLog.ts +1318 -0
  167. package/src/database/_generated/project/models.ts +35 -0
  168. package/src/database/_generated/project/pjtg.ts +182 -0
  169. package/src/database/abstractions.ts +19 -0
  170. package/src/database/factory.ts +37 -0
  171. package/src/database/index.ts +6 -0
  172. package/src/database/local/backend.ts +134 -0
  173. package/src/database/local/index.ts +3 -0
  174. package/src/database/local/meta.ts +46 -0
  175. package/src/database/local/prisma.config.ts +25 -0
  176. package/src/database/local/project.ts +39 -0
  177. package/src/database/manager.ts +181 -0
  178. package/src/database/migrate.ts +35 -0
  179. package/src/database/prisma.ts +56 -0
  180. package/src/database/well-known.ts +38 -0
  181. package/src/index.ts +4 -4
  182. package/src/library/abstractions.ts +21 -14
  183. package/src/library/factory.ts +1 -1
  184. package/src/library/local.ts +86 -38
  185. package/src/library/package-resolution-worker.ts +1 -1
  186. package/src/library/worker/evaluator.ts +61 -48
  187. package/src/library/worker/loader.lite.ts +14 -1
  188. package/src/library/worker/main.ts +9 -16
  189. package/src/library/worker/protocol.ts +0 -12
  190. package/src/lock/manager.ts +12 -7
  191. package/src/orchestrator/manager.ts +198 -131
  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 +235 -583
  203. package/src/orchestrator/operation.ts +446 -904
  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 +49 -25
  217. package/src/pubsub/memory.ts +31 -0
  218. package/src/runner/abstractions.ts +38 -26
  219. package/src/runner/artifact-env.ts +17 -6
  220. package/src/runner/factory.ts +6 -6
  221. package/src/runner/force-abort.ts +3 -6
  222. package/src/runner/local.ts +79 -72
  223. package/src/runner/pulumi.ts +26 -63
  224. package/src/services.ts +214 -103
  225. package/src/shared/models/backend/index.ts +3 -1
  226. package/src/shared/models/backend/library.ts +12 -4
  227. package/src/shared/models/backend/project.ts +43 -23
  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 -109
  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 -56
  236. package/src/shared/models/project/artifact.ts +15 -105
  237. package/src/shared/models/project/custom-status.ts +12 -0
  238. package/src/shared/models/project/index.ts +9 -9
  239. package/src/shared/models/project/lock.ts +10 -78
  240. package/src/shared/models/project/model.ts +32 -0
  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 -103
  244. package/src/shared/models/project/service-account.ts +12 -17
  245. package/src/shared/models/project/state.ts +100 -390
  246. package/src/shared/models/project/terminal.ts +75 -89
  247. package/src/shared/models/project/trigger.ts +13 -49
  248. package/src/shared/models/project/unlock-method.ts +21 -20
  249. package/src/shared/models/project/worker.ts +89 -88
  250. package/src/shared/resolvers/graph-resolver.ts +62 -26
  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 +48 -6
  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 +29 -9
  257. package/src/shared/utils/index.ts +1 -1
  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 +31 -0
  266. package/src/unlock/index.ts +2 -0
  267. package/src/unlock/memory.ts +27 -0
  268. package/src/worker/abstractions.ts +7 -4
  269. package/src/worker/docker.ts +14 -19
  270. package/src/worker/manager.ts +376 -79
  271. package/dist/chunk-RCB4AFGD.js.map +0 -1
  272. package/dist/chunk-WHALQHEZ.js +0 -2017
  273. package/dist/chunk-WHALQHEZ.js.map +0 -1
  274. package/src/business/backend-unlock.ts +0 -10
  275. package/src/common/performance.ts +0 -44
  276. package/src/hotstate/abstractions.ts +0 -48
  277. package/src/hotstate/factory.ts +0 -17
  278. package/src/hotstate/index.ts +0 -3
  279. package/src/hotstate/manager.ts +0 -192
  280. package/src/hotstate/memory.ts +0 -100
  281. package/src/hotstate/validation.ts +0 -101
  282. package/src/project/abstractions.ts +0 -102
  283. package/src/project/factory.ts +0 -11
  284. package/src/project/index.ts +0 -3
  285. package/src/project/local.ts +0 -469
  286. package/src/project/manager.ts +0 -574
  287. package/src/pubsub/local.ts +0 -36
  288. package/src/pubsub/validation.ts +0 -33
  289. package/src/shared/models/project/component.ts +0 -45
  290. package/src/shared/models/project/instance.ts +0 -74
  291. package/src/state/abstractions.ts +0 -450
  292. package/src/state/encryption.ts +0 -59
  293. package/src/state/factory.ts +0 -20
  294. package/src/state/index.ts +0 -6
  295. package/src/state/local/backend.ts +0 -299
  296. package/src/state/local/collection.ts +0 -342
  297. package/src/state/local/index.ts +0 -2
  298. package/src/state/manager.ts +0 -819
  299. package/src/state/repository/index.ts +0 -2
  300. package/src/state/repository/repository.index.ts +0 -193
  301. package/src/state/repository/repository.ts +0 -458
  302. /package/src/{state → database/local}/keyring.ts +0 -0
@@ -0,0 +1,181 @@
1
+ import type { Logger } from "pino"
2
+ import type { ProjectUnlockBackend } from "../unlock"
3
+ import type { BackendDatabase, ProjectDatabase } from "./prisma"
4
+ import { LRUCache } from "lru-cache"
5
+ import z from "zod"
6
+ import { createProjectLogger } from "../common"
7
+ import { BackendError, ProjectLockedError, ProjectNotFoundError } from "../shared"
8
+ import { type ProjectDatabaseBackend, projectDatabaseVersion } from "./abstractions"
9
+ import { migrateDatabase } from "./migrate"
10
+
11
+ export const databaseManagerConfig = z.object({
12
+ HIGHSTATE_ENCRYPTION_ENABLED: z.stringbool().default(true),
13
+ })
14
+
15
+ export interface DatabaseManager {
16
+ /**
17
+ * The backend database instance.
18
+ */
19
+ readonly backend: BackendDatabase
20
+
21
+ /**
22
+ * Whether the encryption is enabled for the backend and project databases.
23
+ */
24
+ readonly isEncryptionEnabled: boolean
25
+
26
+ /**
27
+ * Returns the master key of the project with the given ID.
28
+ *
29
+ * If the project does not exist or is not unlocked, this will throw an error.
30
+ *
31
+ * Returns `undefined` if the encryption is not enabled.
32
+ *
33
+ * @param projectId The ID of the project to get the master key for.
34
+ * @returns A Promise that resolves to the master key of the project as a Uint8Array.
35
+ */
36
+ getProjectMasterKey(projectId: string): Promise<Buffer | undefined>
37
+
38
+ /**
39
+ * Sets up the database for the project with the given ID.
40
+ *
41
+ * Assumes that no database exists yet.
42
+ *
43
+ * The project must be unlocked before calling this method.
44
+ *
45
+ * @param projectId The ID of the project to set up the database for.
46
+ */
47
+ setupDatabase(projectId: string): Promise<ProjectDatabase>
48
+
49
+ /**
50
+ * Returns the database client for the project with the given ID.
51
+ *
52
+ * Automatically migrates the project database if necessary.
53
+ *
54
+ * If the project does not exist or not unlocked, this will throw an error.
55
+ *
56
+ * @param projectId The ID of the project to get the database client for.
57
+ */
58
+ forProject(projectId: string): Promise<ProjectDatabase>
59
+ }
60
+
61
+ export class DatabaseManagerImpl implements DatabaseManager {
62
+ constructor(
63
+ readonly backend: BackendDatabase,
64
+ private readonly projectUnlockBackend: ProjectUnlockBackend,
65
+ private readonly projectDatabaseBackend: ProjectDatabaseBackend,
66
+ private readonly config: z.infer<typeof databaseManagerConfig>,
67
+ private readonly logger: Logger,
68
+ ) {}
69
+
70
+ // store the master keys in memory cache for 30 seconds
71
+ private readonly projectMasterKeys = new LRUCache<string, Buffer>({
72
+ ttl: 30_000,
73
+ ttlAutopurge: true,
74
+ })
75
+
76
+ // TODO: auto unload project databases after some time
77
+ private readonly projectDatabases = new Map<string, ProjectDatabase>()
78
+
79
+ get isEncryptionEnabled(): boolean {
80
+ return this.config.HIGHSTATE_ENCRYPTION_ENABLED
81
+ }
82
+
83
+ async getProjectMasterKey(projectId: string): Promise<Buffer | undefined> {
84
+ if (!this.isEncryptionEnabled) {
85
+ return undefined
86
+ }
87
+
88
+ const cachedInfo = this.projectMasterKeys.get(projectId)
89
+ if (cachedInfo) {
90
+ return cachedInfo
91
+ }
92
+
93
+ const masterKey = await this.projectUnlockBackend.getProjectMasterKey(projectId)
94
+ if (!masterKey) {
95
+ throw new ProjectLockedError(projectId)
96
+ }
97
+
98
+ this.projectMasterKeys.set(projectId, masterKey)
99
+ return masterKey
100
+ }
101
+
102
+ async setupDatabase(projectId: string): Promise<ProjectDatabase> {
103
+ const logger = createProjectLogger(this.logger, projectId)
104
+ const masterKey = await this.getProjectMasterKey(projectId)
105
+ const hexMasterKey = masterKey?.toString("hex")
106
+
107
+ const [database, databaseUrl] = await this.projectDatabaseBackend.openProjectDatabase(
108
+ projectId,
109
+ hexMasterKey,
110
+ )
111
+
112
+ // can safely apply migrations here, because no one knows about the database yet
113
+ await migrateDatabase(databaseUrl, "project", hexMasterKey, logger)
114
+
115
+ this.projectDatabases.set(projectId, database)
116
+
117
+ return database
118
+ }
119
+
120
+ async forProject(projectId: string): Promise<ProjectDatabase> {
121
+ const cachedDatabase = this.projectDatabases.get(projectId)
122
+ if (cachedDatabase) {
123
+ return cachedDatabase
124
+ }
125
+
126
+ const masterKey = await this.getProjectMasterKey(projectId)
127
+ const hexMasterKey = masterKey?.toString("hex")
128
+
129
+ // TODO: is it really necessary to migrate the database inside the transaction?
130
+ let database = await this.backend.$transaction(async tx => {
131
+ const databaseEntity = await tx.project.findUnique({
132
+ where: { id: projectId },
133
+ select: { databaseVersion: true },
134
+ })
135
+
136
+ if (!databaseEntity) {
137
+ throw new ProjectNotFoundError(projectId)
138
+ }
139
+
140
+ if (databaseEntity.databaseVersion > projectDatabaseVersion) {
141
+ throw new BackendError(
142
+ `Project database version (${databaseEntity.databaseVersion}) is newer than expected (${projectDatabaseVersion}).`,
143
+ )
144
+ }
145
+
146
+ if (databaseEntity.databaseVersion === projectDatabaseVersion) {
147
+ return
148
+ }
149
+
150
+ const [database, databaseUrl] = await this.projectDatabaseBackend.openProjectDatabase(
151
+ projectId,
152
+ hexMasterKey,
153
+ )
154
+
155
+ if (databaseEntity.databaseVersion < projectDatabaseVersion) {
156
+ await migrateDatabase(databaseUrl, "project", hexMasterKey, this.logger)
157
+ }
158
+
159
+ await tx.project.update({
160
+ where: { id: projectId },
161
+ data: { databaseVersion: projectDatabaseVersion },
162
+ })
163
+
164
+ return database
165
+ })
166
+
167
+ if (!database) {
168
+ // open database if was not migrated in the transaction
169
+ const [_database] = await this.projectDatabaseBackend.openProjectDatabase(
170
+ projectId,
171
+ hexMasterKey,
172
+ )
173
+
174
+ database = _database
175
+ }
176
+
177
+ this.projectDatabases.set(projectId, database)
178
+
179
+ return database
180
+ }
181
+ }
@@ -0,0 +1,35 @@
1
+ import type { Logger } from "pino"
2
+ import { join } from "node:path"
3
+ import { fileURLToPath } from "node:url"
4
+ import { execa } from "execa"
5
+ import { resolve } from "import-meta-resolve"
6
+ import { detectPackageManager } from "nypm"
7
+
8
+ export async function migrateDatabase(
9
+ databaseUrl: string,
10
+ schemaPath: "backend/sqlite" | "project",
11
+ masterKey: string | undefined,
12
+ logger: Logger,
13
+ ): Promise<void> {
14
+ logger.info("applying database migrations")
15
+
16
+ const backendIndexPath = resolve("@highstate/backend", import.meta.url)
17
+ const backendRootPath = join(fileURLToPath(backendIndexPath), "..")
18
+
19
+ const packageManager = await detectPackageManager(backendRootPath)
20
+ if (!packageManager) {
21
+ throw new Error("Could not detect package manager to run migrations")
22
+ }
23
+
24
+ const hasCorepack = await execa`"corepack" --version`.then(() => true).catch(() => false)
25
+ const command = hasCorepack ? `corepack ${packageManager.command}` : packageManager.command
26
+
27
+ await execa({
28
+ cwd: backendRootPath,
29
+ env: {
30
+ HIGHSTATE_MIGRATION_DATABASE_SCHEMA_PATH: schemaPath,
31
+ HIGHSTATE_MIGRATION_DATABASE_URL: databaseUrl,
32
+ HIGHSTATE_MIGRATION_DATABASE_ENCRYPTION_KEY: masterKey ?? "",
33
+ },
34
+ })`${command} migrate`
35
+ }
@@ -0,0 +1,56 @@
1
+ import type * as runtime from "@prisma/client/runtime/client"
2
+ import { PrismaClient as BackendDatabase } from "./_generated/backend/postgresql/client"
3
+ import { PrismaClient as ProjectDatabase } from "./_generated/project/client"
4
+
5
+ export type BackendTransaction = Omit<BackendDatabase, runtime.ITXClientDenyList>
6
+ export type ProjectTransaction = Omit<ProjectDatabase, runtime.ITXClientDenyList>
7
+
8
+ export type {
9
+ Library,
10
+ Project,
11
+ ProjectSpace,
12
+ PulumiBackend,
13
+ } from "./_generated/backend/postgresql/client"
14
+ export type {
15
+ ApiKey,
16
+ Artifact,
17
+ InstanceCustomStatus,
18
+ InstanceEvaluationState,
19
+ InstanceEvaluationStatus,
20
+ InstanceLock,
21
+ InstanceOperationState,
22
+ InstanceOperationStatus,
23
+ InstanceSource,
24
+ InstanceState,
25
+ InstanceStatus,
26
+ Operation,
27
+ OperationLog,
28
+ OperationStatus,
29
+ OperationType,
30
+ Page,
31
+ Secret,
32
+ ServiceAccount,
33
+ Terminal,
34
+ TerminalSession,
35
+ TerminalSessionLog,
36
+ TerminalStatus,
37
+ Trigger,
38
+ UnlockMethod,
39
+ Worker,
40
+ WorkerUnitRegistration,
41
+ WorkerVersion,
42
+ WorkerVersionLog,
43
+ } from "./_generated/project/client"
44
+ export type {
45
+ InstanceEvaluationStateUncheckedCreateInput,
46
+ InstanceEvaluationStateUpdateInput,
47
+ InstanceOperationStateCreateInput,
48
+ InstanceOperationStateCreateManyInput,
49
+ InstanceOperationStateUpdateInput,
50
+ InstanceStateInclude,
51
+ InstanceStateUpdateInput,
52
+ OperationUpdateInput,
53
+ } from "./_generated/project/models"
54
+ export { DbNull } from "./_generated/project/internal/prismaNamespace"
55
+
56
+ export { BackendDatabase, ProjectDatabase }
@@ -0,0 +1,38 @@
1
+ import type { BackendDatabase } from "./prisma"
2
+ import {
3
+ codebaseLibrary,
4
+ codebaseProjectModelStorage,
5
+ databaseProjectModelStorage,
6
+ globalProjectSpace,
7
+ hostPulumiBackend,
8
+ } from "../shared/models/backend/well-known"
9
+
10
+ export async function ensureWellKnownEntitiesCreated(database: BackendDatabase): Promise<void> {
11
+ await database.$transaction([
12
+ database.projectSpace.upsert({
13
+ where: { id: globalProjectSpace.id },
14
+ create: globalProjectSpace,
15
+ update: globalProjectSpace,
16
+ }),
17
+ database.library.upsert({
18
+ where: { id: codebaseLibrary.id },
19
+ create: codebaseLibrary,
20
+ update: codebaseLibrary,
21
+ }),
22
+ database.pulumiBackend.upsert({
23
+ where: { id: hostPulumiBackend.id },
24
+ create: hostPulumiBackend,
25
+ update: hostPulumiBackend,
26
+ }),
27
+ database.projectModelStorage.upsert({
28
+ where: { id: codebaseProjectModelStorage.id },
29
+ create: codebaseProjectModelStorage,
30
+ update: codebaseProjectModelStorage,
31
+ }),
32
+ database.projectModelStorage.upsert({
33
+ where: { id: databaseProjectModelStorage.id },
34
+ create: databaseProjectModelStorage,
35
+ update: databaseProjectModelStorage,
36
+ }),
37
+ ])
38
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
- export * from "./library"
1
+ export * from "./business"
2
2
  export * from "./config"
3
+ export * from "./database"
4
+ export * from "./library"
3
5
  export * from "./orchestrator"
4
- export * from "./terminal"
5
6
  export * from "./services"
6
- export * from "./state"
7
- export * from "./business"
7
+ export * from "./terminal"
@@ -1,4 +1,4 @@
1
- import type { InstanceModel, CompositeInstance } from "@highstate/contract"
1
+ import type { InstanceId, InstanceModel } from "@highstate/contract"
2
2
  import type { LibraryModel, LibraryUpdate, ResolvedInstanceInput } from "../shared"
3
3
 
4
4
  export type ResolvedUnitSource = {
@@ -8,15 +8,18 @@ export type ResolvedUnitSource = {
8
8
  allowedDependencies: string[]
9
9
  }
10
10
 
11
- export type InstanceEvaluationResult =
11
+ export type ProjectEvaluationResult =
12
12
  | {
13
13
  success: true
14
- instanceId: string
15
- compositeInstances: CompositeInstance[]
14
+ virtualInstances: InstanceModel[]
15
+
16
+ /**
17
+ * The mapping of top-level composite instance IDs to error messages if any.
18
+ */
19
+ topLevelErrors: Record<InstanceId, string>
16
20
  }
17
21
  | {
18
22
  success: false
19
- instanceId: string
20
23
  error: string
21
24
  }
22
25
 
@@ -24,42 +27,46 @@ export interface LibraryBackend {
24
27
  /**
25
28
  * Loads the library.
26
29
  */
27
- loadLibrary(libraryId: string, signal?: AbortSignal): Promise<LibraryModel>
30
+ loadLibrary(libraryId: string | undefined, signal?: AbortSignal): Promise<LibraryModel>
28
31
 
29
32
  /**
30
33
  * Watches the library for changes.
31
34
  */
32
- watchLibrary(libraryId: string, signal?: AbortSignal): AsyncIterable<LibraryUpdate[]>
35
+ watchLibrary(libraryId: string | undefined, signal?: AbortSignal): AsyncIterable<LibraryUpdate[]>
33
36
 
34
37
  /**
35
38
  * Gets the resolved unit sources for the given unit types.
36
39
  *
37
40
  * If the packages for these units are not resolved, it will resolve them and include in watch list.
38
41
  */
39
- getResolvedUnitSources(libraryId: string, unitTypes: string[]): Promise<ResolvedUnitSource[]>
42
+ getResolvedUnitSources(
43
+ libraryId: string | undefined,
44
+ unitTypes: string[],
45
+ ): Promise<ResolvedUnitSource[]>
40
46
 
41
47
  /**
42
48
  * Watches the resolved unit sources for changes.
43
49
  * Returns an async iterable that emits each resolved unit source whenever it changes.
44
50
  * Does not emit the resolved unit sources for units that have not changed even if the library was reloaded.
51
+ *
52
+ * @param libraryId The library ID to watch for resolved unit sources.
53
+ * @param signal Optional AbortSignal to cancel the watch.
45
54
  */
46
55
  watchResolvedUnitSources(
47
- libraryId: string,
56
+ libraryId: string | undefined,
48
57
  signal?: AbortSignal,
49
58
  ): AsyncIterable<ResolvedUnitSource>
50
59
 
51
60
  /**
52
- * Evaluates the instances and returns the evaluated composite instances.
61
+ * Evaluates the composite instances of the project and returns evaluated virtual instances.
53
62
  *
54
63
  * @param libraryId The library ID to use for evaluation.
55
64
  * @param allInstances The all instances of the project.
56
65
  * @param resolvedInputs The resolved inputs of the instances.
57
- * @param instanceIds The instance ids to evaluate.
58
66
  */
59
67
  evaluateCompositeInstances(
60
- libraryId: string,
68
+ libraryId: string | undefined,
61
69
  allInstances: InstanceModel[],
62
70
  resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,
63
- instanceIds: string[],
64
- ): Promise<InstanceEvaluationResult[]>
71
+ ): Promise<ProjectEvaluationResult>
65
72
  }
@@ -1,5 +1,5 @@
1
- import type { LibraryBackend } from "./abstractions"
2
1
  import type { Logger } from "pino"
2
+ import type { LibraryBackend } from "./abstractions"
3
3
  import { z } from "zod"
4
4
  import { LocalLibraryBackend, localLibraryBackendConfig } from "./local"
5
5
 
@@ -1,24 +1,24 @@
1
- import type { InstanceEvaluationResult, LibraryBackend, ResolvedUnitSource } from "./abstractions"
2
1
  import type { Logger } from "pino"
2
+ import type { LibraryBackend, ProjectEvaluationResult, ResolvedUnitSource } from "./abstractions"
3
3
  import type {
4
4
  PackageResolutionResponse,
5
5
  PackageResolutionWorkerData,
6
6
  } from "./package-resolution-worker"
7
- import type { WorkerData, WorkerResponse } from "./worker/protocol"
8
- import { fileURLToPath } from "node:url"
7
+ import type { WorkerData } from "./worker/protocol"
9
8
  import { EventEmitter, on } from "node:events"
10
- import { Worker } from "node:worker_threads"
11
- import { resolve } from "node:path"
12
9
  import { readFile } from "node:fs/promises"
10
+ import { resolve } from "node:path"
11
+ import { fileURLToPath } from "node:url"
12
+ import { Worker } from "node:worker_threads"
13
13
  import { type InstanceModel, isUnitModel } from "@highstate/contract"
14
- import Watcher from "watcher"
14
+ import { decode } from "@msgpack/msgpack"
15
15
  import { BetterLock } from "better-lock"
16
16
  import { resolve as importMetaResolve } from "import-meta-resolve"
17
- import { z } from "zod"
17
+ import { addDependency, installDependencies, runScript } from "nypm"
18
18
  import { readPackageJSON } from "pkg-types"
19
- import { runScript, installDependencies, addDependency } from "nypm"
20
19
  import { flatMap, groupBy, map, pipe, unique } from "remeda"
21
- import { decode } from "@msgpack/msgpack"
20
+ import Watcher from "watcher"
21
+ import { z } from "zod"
22
22
  import { resolveMainLocalProject, stringArrayType } from "../common"
23
23
  import {
24
24
  diffLibraries,
@@ -28,7 +28,7 @@ import {
28
28
  } from "../shared"
29
29
 
30
30
  export const localLibraryBackendConfig = z.object({
31
- HIGHSTATE_LIBRARY_BACKEND_LOCAL_PACKAGES: stringArrayType.default("@highstate/library"),
31
+ HIGHSTATE_LIBRARY_BACKEND_LOCAL_PACKAGES: stringArrayType.default(() => ["@highstate/library"]),
32
32
  HIGHSTATE_LIBRARY_BACKEND_LOCAL_WATCH_PATHS: stringArrayType.optional(),
33
33
  })
34
34
 
@@ -48,6 +48,11 @@ type LibraryPackage = {
48
48
  dependents: Set<string>
49
49
  }
50
50
 
51
+ type RebuildState = {
52
+ inProgress: boolean
53
+ pending: boolean
54
+ }
55
+
51
56
  export class LocalLibraryBackend implements LibraryBackend {
52
57
  private readonly watcher: Watcher
53
58
 
@@ -58,6 +63,7 @@ export class LocalLibraryBackend implements LibraryBackend {
58
63
 
59
64
  private readonly packages = new Map<string, LibraryPackage>()
60
65
  private readonly resolvedUnitSources = new Map<string, ResolvedUnitSource>()
66
+ private readonly rebuildStates = new Map<string, RebuildState>()
61
67
 
62
68
  private constructor(
63
69
  private readonly libraryPackages: string[],
@@ -67,7 +73,7 @@ export class LocalLibraryBackend implements LibraryBackend {
67
73
  this.watcher = new Watcher(watchPaths, {
68
74
  recursive: true,
69
75
  ignoreInitial: true,
70
- ignore: /\.git|node_modules|dist/,
76
+ ignore: /\.git|node_modules|dist|\.highstate/,
71
77
  })
72
78
 
73
79
  this.watcher.on("all", (event: string, path: string) => {
@@ -148,26 +154,15 @@ export class LocalLibraryBackend implements LibraryBackend {
148
154
  _libraryId: string,
149
155
  allInstances: InstanceModel[],
150
156
  resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,
151
- instanceIds: string[],
152
- ): Promise<InstanceEvaluationResult[]> {
153
- this.logger.info("evaluating %d composite instances", instanceIds.length)
154
-
157
+ ): Promise<ProjectEvaluationResult> {
155
158
  const worker = this.createLibraryWorker({
156
159
  libraryModulePaths: this.libraryPackages,
157
160
  allInstances,
158
161
  resolvedInputs,
159
- instanceIds,
160
162
  })
161
163
 
162
- for await (const [event] of on(worker, "message")) {
163
- const eventData = event as WorkerResponse
164
-
165
- if (eventData.type === "error") {
166
- throw new Error(`Worker error: ${eventData.error}`)
167
- }
168
-
169
- this.logger.info("composite instances evaluated successfully")
170
- return eventData.results
164
+ for await (const [event] of on(worker, "message", { signal: AbortSignal.timeout(10_000) })) {
165
+ return event as ProjectEvaluationResult
171
166
  }
172
167
 
173
168
  throw new Error("Worker ended without sending any response")
@@ -267,7 +262,7 @@ export class LocalLibraryBackend implements LibraryBackend {
267
262
 
268
263
  this.resolvedUnitSources.set(unit.type, newResolvedSource)
269
264
  this.eventEmitter.emit("resolvedUnitSource", newResolvedSource)
270
- this.logger.debug(`updated source for unit: "%s"`, unit.type)
265
+ this.logger.trace(`updated source for unit: "%s"`, unit.type)
271
266
  }
272
267
  }
273
268
 
@@ -475,22 +470,75 @@ export class LocalLibraryBackend implements LibraryBackend {
475
470
  }
476
471
  }
477
472
 
478
- private async handleFileEvent(path: string): Promise<void> {
479
- await this.lock.acquire(async () => {
480
- const libraryPackage = this.packages.values().find(pkg => path.startsWith(pkg.rootPath))
473
+ private handleFileEvent(path: string): void {
474
+ const libraryPackage = this.packages.values().find(pkg => path.startsWith(pkg.rootPath))
481
475
 
482
- if (!libraryPackage) {
483
- return
484
- }
476
+ if (!libraryPackage) {
477
+ return
478
+ }
479
+
480
+ void this.schedulePackageRebuild(libraryPackage.name)
481
+ }
485
482
 
486
- const builtPackages = new Set<string>()
487
- await this.rebuildLibraryPackage(libraryPackage, false, false, builtPackages)
483
+ private async schedulePackageRebuild(packageName: string): Promise<void> {
484
+ const state = this.rebuildStates.get(packageName) ?? { inProgress: false, pending: false }
485
+
486
+ // if both in progress and pending, discard this request
487
+ if (state.inProgress && state.pending) {
488
+ this.logger.debug(`rebuild in progress and already pending for "%s", discarding`, packageName)
489
+ return
490
+ }
491
+
492
+ // if in progress but no pending, mark as pending
493
+ if (state.inProgress && !state.pending) {
494
+ state.pending = true
495
+ this.rebuildStates.set(packageName, state)
496
+ this.logger.debug(`rebuild in progress, scheduling pending rebuild for "%s"`, packageName)
497
+ return
498
+ }
499
+
500
+ // not in progress, start immediately
501
+ await this.executePackageRebuild(packageName)
502
+ }
488
503
 
489
- if (this.libraryPackages.some(pkg => builtPackages.has(pkg))) {
490
- this.logger.info("reloading library due to file change in package: %s", libraryPackage.name)
491
- await this.reloadLibrary()
504
+ private async executePackageRebuild(packageName: string): Promise<void> {
505
+ const state = this.rebuildStates.get(packageName) ?? { inProgress: false, pending: false }
506
+
507
+ do {
508
+ // mark as in progress, clear pending
509
+ state.inProgress = true
510
+ state.pending = false
511
+ this.rebuildStates.set(packageName, state)
512
+
513
+ try {
514
+ await this.lock.acquire(async () => {
515
+ const libraryPackage = this.packages.get(packageName)
516
+
517
+ if (!libraryPackage) {
518
+ this.logger.warn(`package not found for rebuild: "%s"`, packageName)
519
+ return
520
+ }
521
+
522
+ const builtPackages = new Set<string>()
523
+ await this.rebuildLibraryPackage(libraryPackage, false, false, builtPackages)
524
+
525
+ if (this.libraryPackages.some(pkg => builtPackages.has(pkg))) {
526
+ this.logger.info(
527
+ "reloading library due to file change in package: %s",
528
+ libraryPackage.name,
529
+ )
530
+ await this.reloadLibrary()
531
+ }
532
+ })
533
+ } finally {
534
+ state.inProgress = false
492
535
  }
493
- })
536
+
537
+ // continue if another rebuild was requested while we were working
538
+ } while (state.pending)
539
+
540
+ // clean up if no longer needed
541
+ this.rebuildStates.delete(packageName)
494
542
  }
495
543
 
496
544
  private createLibraryWorker(workerData: WorkerData): Worker {
@@ -64,7 +64,7 @@ for (const packageName of packageNames) {
64
64
  }
65
65
  }
66
66
 
67
- parentPort!.postMessage({
67
+ parentPort?.postMessage({
68
68
  type: "result",
69
69
  results,
70
70
  })