@highstate/backend 0.9.18 → 0.9.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/dist/{chunk-OU5OQBLB.js → chunk-I7BWSAN6.js} +3 -28
  2. package/dist/{chunk-OU5OQBLB.js.map → chunk-I7BWSAN6.js.map} +1 -1
  3. package/dist/chunk-RC6Q3XQQ.js +1547 -0
  4. package/dist/chunk-RC6Q3XQQ.js.map +1 -0
  5. package/dist/chunk-VB4YL327.js +139 -0
  6. package/dist/chunk-VB4YL327.js.map +1 -0
  7. package/dist/database/local/prisma.config.js +26 -0
  8. package/dist/database/local/prisma.config.js.map +1 -0
  9. package/dist/highstate.manifest.json +2 -1
  10. package/dist/index.js +7590 -7289
  11. package/dist/index.js.map +1 -1
  12. package/dist/library/package-resolution-worker.js +1 -1
  13. package/dist/library/package-resolution-worker.js.map +1 -1
  14. package/dist/library/worker/main.js +35 -29
  15. package/dist/library/worker/main.js.map +1 -1
  16. package/dist/shared/index.js +2 -2
  17. package/package.json +18 -9
  18. package/prisma/backend/_schema/layout.prisma +7 -0
  19. package/prisma/backend/_schema/library.prisma +17 -0
  20. package/prisma/backend/_schema/project.prisma +101 -0
  21. package/prisma/backend/_schema/pulumi.prisma +17 -0
  22. package/prisma/backend/postgresql/main.prisma +17 -0
  23. package/prisma/backend/sqlite/main.prisma +17 -0
  24. package/prisma/backend/sqlite/migrations/20250817070609_initiial/migration.sql +34 -0
  25. package/prisma/backend/sqlite/migrations/20250817104948_add_fields/migration.sql +59 -0
  26. package/prisma/backend/sqlite/migrations/20250818082732_add_models/migration.sql +41 -0
  27. package/prisma/backend/sqlite/migrations/20250818083106_a/migration.sql +19 -0
  28. package/prisma/backend/sqlite/migrations/20250818101945_hi/migration.sql +1 -0
  29. package/prisma/backend/sqlite/migrations/20250819082315_a/migration.sql +5 -0
  30. package/prisma/backend/sqlite/migrations/migration_lock.toml +3 -0
  31. package/prisma/project/api-key.prisma +32 -0
  32. package/prisma/project/artifact.prisma +52 -0
  33. package/prisma/project/custom-status.prisma +46 -0
  34. package/prisma/project/evaluation.prisma +45 -0
  35. package/prisma/project/instance.prisma +157 -0
  36. package/prisma/project/layout.prisma +23 -0
  37. package/prisma/project/lock.prisma +18 -0
  38. package/prisma/project/main.prisma +17 -0
  39. package/prisma/project/migrations/20250816081310_initial/migration.sql +300 -0
  40. package/prisma/project/migrations/20250816082523_test/migration.sql +72 -0
  41. package/prisma/project/migrations/20250818065643_update/migration.sql +42 -0
  42. package/prisma/project/migrations/20250818070758_a/migration.sql +8 -0
  43. package/prisma/project/migrations/20250818070913_a/migration.sql +8 -0
  44. package/prisma/project/migrations/20250818082720_add_motels/migration.sql +11 -0
  45. package/prisma/project/migrations/20250818112523_hello/migration.sql +35 -0
  46. package/prisma/project/migrations/20250819082305_a/migration.sql +14 -0
  47. package/prisma/project/migrations/20250819165004_add_missing_fields/migration.sql +216 -0
  48. package/prisma/project/migrations/20250819171309_a/migration.sql +22 -0
  49. package/prisma/project/migrations/20250820113949_a/migration.sql +66 -0
  50. package/prisma/project/migrations/20250820144256_b/migration.sql +31 -0
  51. package/prisma/project/migrations/20250820145547_a/migration.sql +24 -0
  52. package/prisma/project/migrations/20250820182517_b/migration.sql +2 -0
  53. package/prisma/project/migrations/20250821172324_a/migration.sql +2 -0
  54. package/prisma/project/migrations/20250822081339_a/migration.sql +219 -0
  55. package/prisma/project/migrations/20250822083742_b/migration.sql +1 -0
  56. package/prisma/project/migrations/20250822105134_boom/migration.sql +1 -0
  57. package/prisma/project/migrations/20250822141028_b/migration.sql +1 -0
  58. package/prisma/project/migrations/20250822142342_b/migration.sql +16 -0
  59. package/prisma/project/migrations/20250824072720_a/migration.sql +1 -0
  60. package/prisma/project/migrations/20250824093656_b/migration.sql +21 -0
  61. package/prisma/project/migrations/20250825082518_a/migration.sql +1 -0
  62. package/prisma/project/migrations/20250825085343_b/migration.sql +1 -0
  63. package/prisma/project/migrations/20250825091312_a/migration.sql +1 -0
  64. package/prisma/project/migrations/20250903095431_hi/migration.sql +44 -0
  65. package/prisma/project/migrations/20250903174255_a/migration.sql +24 -0
  66. package/prisma/project/migrations/20250908095205_hi/migration.sql +18 -0
  67. package/prisma/project/migrations/20250909155857_hi/migration.sql +15 -0
  68. package/prisma/project/migrations/migration_lock.toml +3 -0
  69. package/prisma/project/model.prisma +37 -0
  70. package/prisma/project/operation.prisma +148 -0
  71. package/prisma/project/page.prisma +49 -0
  72. package/prisma/project/secret.prisma +54 -0
  73. package/prisma/project/service-account.prisma +42 -0
  74. package/prisma/project/terminal.prisma +107 -0
  75. package/prisma/project/trigger.prisma +37 -0
  76. package/prisma/project/unlock-method.prisma +46 -0
  77. package/prisma/project/worker.prisma +169 -0
  78. package/src/artifact/abstractions.ts +13 -13
  79. package/src/artifact/encryption.ts +30 -54
  80. package/src/artifact/factory.ts +6 -9
  81. package/src/artifact/local.ts +33 -46
  82. package/src/business/api-key.ts +24 -36
  83. package/src/business/artifact.test.ts +978 -0
  84. package/src/business/artifact.ts +136 -216
  85. package/src/business/evaluation.ts +328 -0
  86. package/src/business/index.ts +5 -2
  87. package/src/business/instance-lock.test.ts +1060 -0
  88. package/src/business/instance-lock.ts +387 -78
  89. package/src/business/instance-state.test.ts +735 -0
  90. package/src/business/instance-state.ts +582 -337
  91. package/src/business/operation.test.ts +439 -0
  92. package/src/business/operation.ts +174 -208
  93. package/src/business/project-model.ts +258 -0
  94. package/src/business/project-unlock.ts +168 -126
  95. package/src/business/project.ts +287 -179
  96. package/src/business/secret.test.ts +469 -130
  97. package/src/business/secret.ts +177 -217
  98. package/src/business/settings.test.ts +695 -0
  99. package/src/business/settings.ts +855 -0
  100. package/src/business/terminal-session.ts +90 -0
  101. package/src/business/unit-extra.test.ts +539 -0
  102. package/src/business/unit-extra.ts +160 -0
  103. package/src/business/worker.test.ts +356 -579
  104. package/src/business/worker.ts +238 -339
  105. package/src/common/codebase.ts +65 -0
  106. package/src/common/index.ts +3 -5
  107. package/src/common/logger.ts +5 -0
  108. package/src/common/utils.ts +4 -3
  109. package/src/config.ts +10 -11
  110. package/src/database/_generated/backend/postgresql/client.ts +72 -0
  111. package/src/database/_generated/backend/postgresql/commonInputTypes.ts +350 -0
  112. package/src/database/_generated/backend/postgresql/enums.ts +13 -0
  113. package/src/database/_generated/backend/postgresql/internal/class.ts +320 -0
  114. package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +1238 -0
  115. package/src/database/_generated/backend/postgresql/models/Library.ts +1263 -0
  116. package/src/database/_generated/backend/postgresql/models/Project.ts +2175 -0
  117. package/src/database/_generated/backend/postgresql/models/ProjectModelStorage.ts +1263 -0
  118. package/src/database/_generated/backend/postgresql/models/ProjectSpace.ts +1602 -0
  119. package/src/database/_generated/backend/postgresql/models/PulumiBackend.ts +1263 -0
  120. package/src/database/_generated/backend/postgresql/models/UserWorkspaseLayout.ts +1065 -0
  121. package/src/database/_generated/backend/postgresql/models.ts +16 -0
  122. package/src/database/_generated/backend/postgresql/pjtg.ts +182 -0
  123. package/src/database/_generated/backend/sqlite/client.ts +72 -0
  124. package/src/database/_generated/backend/sqlite/commonInputTypes.ts +331 -0
  125. package/src/database/_generated/backend/sqlite/enums.ts +13 -0
  126. package/src/database/_generated/backend/sqlite/internal/class.ts +318 -0
  127. package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +1207 -0
  128. package/src/database/_generated/backend/sqlite/models/Library.ts +1261 -0
  129. package/src/database/_generated/backend/sqlite/models/Project.ts +2169 -0
  130. package/src/database/_generated/backend/sqlite/models/ProjectModelStorage.ts +1261 -0
  131. package/src/database/_generated/backend/sqlite/models/ProjectSpace.ts +1599 -0
  132. package/src/database/_generated/backend/sqlite/models/PulumiBackend.ts +1261 -0
  133. package/src/database/_generated/backend/sqlite/models/UserWorkspaseLayout.ts +1063 -0
  134. package/src/database/_generated/backend/sqlite/models.ts +16 -0
  135. package/src/database/_generated/backend/sqlite/pjtg.ts +182 -0
  136. package/src/database/_generated/project/client.ts +204 -0
  137. package/src/database/_generated/project/commonInputTypes.ts +827 -0
  138. package/src/database/_generated/project/enums.ts +104 -0
  139. package/src/database/_generated/project/internal/class.ts +479 -0
  140. package/src/database/_generated/project/internal/prismaNamespace.ts +2974 -0
  141. package/src/database/_generated/project/models/ApiKey.ts +1506 -0
  142. package/src/database/_generated/project/models/Artifact.ts +2051 -0
  143. package/src/database/_generated/project/models/HubModel.ts +1125 -0
  144. package/src/database/_generated/project/models/InstanceCustomStatus.ts +1713 -0
  145. package/src/database/_generated/project/models/InstanceEvaluationState.ts +1312 -0
  146. package/src/database/_generated/project/models/InstanceLock.ts +1268 -0
  147. package/src/database/_generated/project/models/InstanceModel.ts +1125 -0
  148. package/src/database/_generated/project/models/InstanceOperationState.ts +1707 -0
  149. package/src/database/_generated/project/models/InstanceState.ts +4613 -0
  150. package/src/database/_generated/project/models/Operation.ts +1647 -0
  151. package/src/database/_generated/project/models/OperationLog.ts +1455 -0
  152. package/src/database/_generated/project/models/Page.ts +1838 -0
  153. package/src/database/_generated/project/models/Secret.ts +1692 -0
  154. package/src/database/_generated/project/models/ServiceAccount.ts +2165 -0
  155. package/src/database/_generated/project/models/Terminal.ts +2038 -0
  156. package/src/database/_generated/project/models/TerminalSession.ts +1454 -0
  157. package/src/database/_generated/project/models/TerminalSessionLog.ts +1280 -0
  158. package/src/database/_generated/project/models/Trigger.ts +1430 -0
  159. package/src/database/_generated/project/models/UnlockMethod.ts +1220 -0
  160. package/src/database/_generated/project/models/UserCompositeViewport.ts +1280 -0
  161. package/src/database/_generated/project/models/UserProjectViewport.ts +1059 -0
  162. package/src/database/_generated/project/models/Worker.ts +1459 -0
  163. package/src/database/_generated/project/models/WorkerUnitRegistration.ts +1524 -0
  164. package/src/database/_generated/project/models/WorkerVersion.ts +1974 -0
  165. package/src/database/_generated/project/models/WorkerVersionLog.ts +1318 -0
  166. package/src/database/_generated/project/models.ts +35 -0
  167. package/src/database/_generated/project/pjtg.ts +182 -0
  168. package/src/database/abstractions.ts +19 -0
  169. package/src/database/factory.ts +37 -0
  170. package/src/database/index.ts +6 -0
  171. package/src/database/local/backend.ts +134 -0
  172. package/src/database/local/index.ts +3 -0
  173. package/src/database/local/meta.ts +46 -0
  174. package/src/database/local/prisma.config.ts +25 -0
  175. package/src/database/local/project.ts +39 -0
  176. package/src/database/manager.ts +181 -0
  177. package/src/database/migrate.ts +35 -0
  178. package/src/database/prisma.ts +56 -0
  179. package/src/database/well-known.ts +38 -0
  180. package/src/index.ts +4 -4
  181. package/src/library/abstractions.ts +3 -5
  182. package/src/library/factory.ts +1 -1
  183. package/src/library/local.ts +81 -26
  184. package/src/library/package-resolution-worker.ts +1 -1
  185. package/src/library/worker/evaluator.ts +40 -23
  186. package/src/library/worker/loader.lite.ts +1 -1
  187. package/src/library/worker/main.ts +3 -10
  188. package/src/library/worker/protocol.ts +0 -1
  189. package/src/lock/index.ts +0 -1
  190. package/src/lock/manager.ts +0 -10
  191. package/src/orchestrator/manager.ts +190 -104
  192. package/src/orchestrator/operation-context.ts +357 -0
  193. package/src/orchestrator/operation-plan.destroy.test.md +357 -0
  194. package/src/orchestrator/operation-plan.destroy.test.ts +775 -0
  195. package/src/orchestrator/operation-plan.fixtures.ts +213 -0
  196. package/src/orchestrator/operation-plan.md +198 -0
  197. package/src/orchestrator/operation-plan.refresh.test.md +199 -0
  198. package/src/orchestrator/operation-plan.refresh.test.ts +367 -0
  199. package/src/orchestrator/operation-plan.ts +709 -0
  200. package/src/orchestrator/operation-plan.update.test.md +485 -0
  201. package/src/orchestrator/operation-plan.update.test.ts +1066 -0
  202. package/src/orchestrator/operation-workset.ts +233 -578
  203. package/src/orchestrator/operation.ts +440 -948
  204. package/src/orchestrator/plan-test-builder.ts +267 -0
  205. package/src/project-model/abstractions.ts +118 -0
  206. package/src/project-model/backends/codebase.ts +365 -0
  207. package/src/project-model/backends/database.ts +440 -0
  208. package/src/project-model/errors.ts +81 -0
  209. package/src/project-model/factory.ts +24 -0
  210. package/src/project-model/index.ts +4 -0
  211. package/src/project-model/utils.test.ts +544 -0
  212. package/src/project-model/utils.ts +242 -0
  213. package/src/pubsub/abstractions.ts +10 -1
  214. package/src/pubsub/factory.ts +4 -4
  215. package/src/pubsub/index.ts +1 -0
  216. package/src/pubsub/manager.ts +29 -13
  217. package/src/pubsub/memory.ts +31 -0
  218. package/src/runner/abstractions.ts +40 -41
  219. package/src/runner/artifact-env.ts +19 -8
  220. package/src/runner/factory.ts +6 -6
  221. package/src/runner/force-abort.ts +3 -6
  222. package/src/runner/local.ts +74 -67
  223. package/src/runner/pulumi.ts +23 -63
  224. package/src/services.ts +181 -123
  225. package/src/shared/models/backend/index.ts +3 -1
  226. package/src/shared/models/backend/library.ts +9 -1
  227. package/src/shared/models/backend/project.ts +43 -42
  228. package/src/shared/models/backend/pulumi.ts +14 -0
  229. package/src/shared/models/backend/unlock-method.ts +1 -1
  230. package/src/shared/models/backend/well-known.ts +58 -0
  231. package/src/shared/models/base.ts +40 -26
  232. package/src/shared/models/errors.ts +82 -1
  233. package/src/shared/models/index.ts +3 -2
  234. package/src/shared/models/prisma.ts +36 -0
  235. package/src/shared/models/project/api-key.ts +37 -59
  236. package/src/shared/models/project/artifact.ts +16 -76
  237. package/src/shared/models/project/custom-status.ts +12 -0
  238. package/src/shared/models/project/index.ts +8 -7
  239. package/src/shared/models/project/lock.ts +10 -78
  240. package/src/shared/models/project/model.ts +19 -1
  241. package/src/shared/models/project/operation.ts +235 -99
  242. package/src/shared/models/project/page.ts +37 -48
  243. package/src/shared/models/project/secret.ts +29 -89
  244. package/src/shared/models/project/service-account.ts +12 -17
  245. package/src/shared/models/project/state.ts +100 -407
  246. package/src/shared/models/project/terminal.ts +75 -88
  247. package/src/shared/models/project/trigger.ts +13 -49
  248. package/src/shared/models/project/unlock-method.ts +20 -26
  249. package/src/shared/models/project/worker.ts +89 -90
  250. package/src/shared/resolvers/graph-resolver.ts +21 -0
  251. package/src/shared/resolvers/index.ts +1 -1
  252. package/src/shared/resolvers/input-hash.ts +24 -14
  253. package/src/shared/resolvers/input.ts +9 -2
  254. package/src/shared/resolvers/registry.ts +5 -4
  255. package/src/shared/resolvers/state.ts +12 -1
  256. package/src/shared/resolvers/validation.ts +7 -3
  257. package/src/shared/utils/index.ts +1 -2
  258. package/src/shared/utils/promise-tracker.ts +30 -3
  259. package/src/terminal/abstractions.ts +1 -1
  260. package/src/terminal/docker.ts +3 -3
  261. package/src/terminal/manager.ts +102 -118
  262. package/src/test-utils/database.ts +119 -0
  263. package/src/test-utils/index.ts +2 -0
  264. package/src/test-utils/services.ts +134 -0
  265. package/src/unlock/abstractions.ts +5 -23
  266. package/src/unlock/memory.ts +9 -14
  267. package/src/worker/abstractions.ts +7 -4
  268. package/src/worker/docker.ts +14 -19
  269. package/src/worker/manager.ts +366 -97
  270. package/dist/chunk-NAAIDR4U.js +0 -8499
  271. package/dist/chunk-NAAIDR4U.js.map +0 -1
  272. package/dist/chunk-Y7DXREVO.js +0 -1745
  273. package/dist/chunk-Y7DXREVO.js.map +0 -1
  274. package/dist/magic-string.es-5ABAC4JN.js +0 -1292
  275. package/dist/magic-string.es-5ABAC4JN.js.map +0 -1
  276. package/src/business/__traces__/secret/update-instance-secrets/create-and-delete-secrets-simultaneously.md +0 -356
  277. package/src/business/__traces__/secret/update-instance-secrets/create-new-secrets-for-instance.md +0 -274
  278. package/src/business/__traces__/secret/update-instance-secrets/delete-existing-secrets.md +0 -223
  279. package/src/business/__traces__/secret/update-instance-secrets/no-op-when-no-changes.md +0 -147
  280. package/src/business/__traces__/secret/update-instance-secrets/update-existing-secrets.md +0 -280
  281. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration-when-other-exists.md +0 -360
  282. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration.md +0 -215
  283. package/src/business/__traces__/worker/update-unit-registrations/create-multiple-workers-with-different-identities.md +0 -427
  284. package/src/business/__traces__/worker/update-unit-registrations/handle-nonexistent-registration-id-gracefully.md +0 -217
  285. package/src/business/__traces__/worker/update-unit-registrations/no-op-when-no-changes.md +0 -132
  286. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-changes.md +0 -454
  287. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-version-changes.md +0 -426
  288. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-with-same-identity-reuses-service-account.md +0 -372
  289. package/src/business/__traces__/worker/update-unit-registrations/remove-one-of-multiple-unit-registrations.md +0 -383
  290. package/src/business/__traces__/worker/update-unit-registrations/remove-unit-registration.md +0 -245
  291. package/src/business/__traces__/worker/update-unit-registrations/update-existing-unit-registration-when-params-change.md +0 -174
  292. package/src/business/__traces__/worker/update-unit-registrations/update-params-and-image-simultaneously.md +0 -432
  293. package/src/business/__traces__/worker/update-unit-registrations/worker-with-multiple-registrations-not-deleted-when-one-removed.md +0 -220
  294. package/src/business/backend-unlock.ts +0 -10
  295. package/src/common/clock.ts +0 -18
  296. package/src/common/performance.ts +0 -44
  297. package/src/common/random.ts +0 -68
  298. package/src/common/test/index.ts +0 -2
  299. package/src/common/test/render.ts +0 -98
  300. package/src/common/test/tracer.ts +0 -359
  301. package/src/hotstate/abstractions.ts +0 -48
  302. package/src/hotstate/factory.ts +0 -17
  303. package/src/hotstate/index.ts +0 -3
  304. package/src/hotstate/manager.ts +0 -192
  305. package/src/hotstate/memory.ts +0 -100
  306. package/src/hotstate/validation.ts +0 -100
  307. package/src/lock/test.ts +0 -108
  308. package/src/project/abstractions.ts +0 -78
  309. package/src/project/evaluation.ts +0 -248
  310. package/src/project/factory.ts +0 -11
  311. package/src/project/index.ts +0 -3
  312. package/src/project/local.ts +0 -417
  313. package/src/pubsub/local.ts +0 -36
  314. package/src/pubsub/validation.ts +0 -33
  315. package/src/shared/utils/args.ts +0 -25
  316. package/src/state/abstractions.ts +0 -289
  317. package/src/state/encryption.ts +0 -98
  318. package/src/state/factory.ts +0 -20
  319. package/src/state/index.ts +0 -7
  320. package/src/state/local/backend.ts +0 -106
  321. package/src/state/local/collection.ts +0 -361
  322. package/src/state/local/index.ts +0 -2
  323. package/src/state/manager.ts +0 -890
  324. package/src/state/memory/backend.ts +0 -70
  325. package/src/state/memory/collection.ts +0 -270
  326. package/src/state/memory/index.ts +0 -2
  327. package/src/state/repository/index.ts +0 -2
  328. package/src/state/repository/repository.index.ts +0 -193
  329. package/src/state/repository/repository.ts +0 -507
  330. package/src/state/test.ts +0 -457
  331. /package/src/{state → database/local}/keyring.ts +0 -0
@@ -1,25 +1,38 @@
1
- import type { StateBatch, StateManager } from "../state"
2
1
  import type { Logger } from "pino"
3
- import type { HotStateManager } from "../hotstate"
2
+ import type { DatabaseManager } from "../database"
4
3
  import type { PubSubManager } from "../pubsub"
5
- import type { RandomProvider } from "../common"
6
4
  import type { ProjectUnlockBackend } from "../unlock"
7
5
  import { randomBytes } from "node:crypto"
8
6
  import { armor, Decrypter, Encrypter } from "age-encryption"
9
- import { type InputUnlockMethod, type ProjectUnlockState, type UnlockMethod } from "../shared"
7
+ import { z } from "zod"
8
+ import { createProjectLogger } from "../common"
9
+ import {
10
+ CannotDeleteLastUnlockMethodError,
11
+ ProjectNotFoundError,
12
+ type ProjectUnlockState,
13
+ type ProjectUnlockSuite,
14
+ type UnlockMethodInput,
15
+ } from "../shared"
10
16
 
11
17
  type UnlockTask = {
12
18
  name: string
13
19
  handler: (projectId: string) => Promise<void> | void
14
20
  }
15
21
 
22
+ export const projectUnlockServiceConfig = z.object({
23
+ HIGHSTATE_ENCRYPTION_ENABLED: z.stringbool().default(true),
24
+ HIGHSTATE_DEV_AUTO_UNLOCK_PROJECT_IDS: z
25
+ .string()
26
+ .transform(val => val?.split(",") ?? [])
27
+ .default([]),
28
+ })
29
+
16
30
  export class ProjectUnlockService {
17
31
  constructor(
18
- private readonly stateManager: StateManager,
19
- private readonly hotStateManager: HotStateManager,
32
+ private readonly database: DatabaseManager,
20
33
  private readonly pubsubManager: PubSubManager,
21
34
  private readonly projectUnlockBackend: ProjectUnlockBackend,
22
- private readonly random: RandomProvider,
35
+ private readonly config: z.infer<typeof projectUnlockServiceConfig>,
23
36
  private readonly logger: Logger,
24
37
  ) {}
25
38
 
@@ -39,51 +52,54 @@ export class ProjectUnlockService {
39
52
  return { type: "unlocked" }
40
53
  }
41
54
 
42
- const unlockSuite = await this.stateManager.getProjectUnlockSuiteRepository().get(projectId)
55
+ const project = await this.database.backend.project.findUnique({
56
+ where: { id: projectId },
57
+ select: { unlockSuite: true },
58
+ })
59
+
60
+ if (!project) {
61
+ throw new ProjectNotFoundError(projectId)
62
+ }
63
+
43
64
  return {
44
65
  type: "locked",
45
- unlockSuite,
66
+ unlockSuite: project.unlockSuite,
46
67
  }
47
68
  }
48
69
 
49
70
  /**
50
- * Sets up the project state by creating a master key and unlock suite with the provided unlock method.
71
+ * Sets up the project database by creating a master key and unlock suite with the provided unlock method.
72
+ *
73
+ * Creates databases, configures encryption and persists the unlock method inside the project database.
74
+ *
75
+ * Then returns the encrypted master key and the unlock suite for further persisting in the backend database.
51
76
  *
52
77
  * @param projectId The ID of the project to create the state for.
53
78
  * @param unlockMethod The unlock method to use to encrypt the master key. Should be provided by the frontend.
54
79
  */
55
- async setupProjectState(
80
+ async setupProjectDatabase(
56
81
  projectId: string,
57
- inputUnlockMethod: InputUnlockMethod,
58
- batch: StateBatch,
59
- ): Promise<void> {
60
- const unlockMethod: UnlockMethod = {
61
- id: this.random.uuidv7(),
62
- ...inputUnlockMethod,
63
- }
64
-
82
+ unlockMethodInput: UnlockMethodInput,
83
+ ): Promise<[encryptedMasterKey: string, unlockSuite: ProjectUnlockSuite]> {
65
84
  // generate a new master key for the project
66
85
  const masterKey = randomBytes(32)
67
86
 
68
- // generate a new project namespace
69
- const namespace = this.random.uuidv4()
70
- await this.stateManager.getProjectNamespaceRepository().put(projectId, namespace, batch)
71
-
72
- // unlock the project
73
- await this.projectUnlockBackend.unlockProject(
74
- projectId,
75
- masterKey.toString("base64"),
76
- namespace,
77
- )
78
-
79
- // store the master key in the backend
80
- await this.updateUnlockSuite(projectId, [unlockMethod], batch)
81
- await this.updateEncryptedMasterKey(projectId, [unlockMethod], batch, masterKey)
82
-
83
- // persist the unlock method as any other project data
84
- await this.stateManager
85
- .getUnlockMethodRepository(projectId)
86
- .put(unlockMethod.id, unlockMethod, batch)
87
+ // set the master key to setup the database encryption
88
+ await this.projectUnlockBackend.unlockProject(projectId, masterKey)
89
+
90
+ const encryptedMasterKey = await this.encryptProjectMasterKey(projectId, [unlockMethodInput])
91
+
92
+ const database = await this.database.setupDatabase(projectId)
93
+
94
+ // persist unlock method (now we can do it since the database is set up and unlocked)
95
+ await database.unlockMethod.create({ data: unlockMethodInput })
96
+
97
+ const unlockSuite: ProjectUnlockSuite = {
98
+ encryptedIdentities: [unlockMethodInput.encryptedIdentity],
99
+ hasPasskey: unlockMethodInput.type === "passkey",
100
+ }
101
+
102
+ return [encryptedMasterKey, unlockSuite]
87
103
  }
88
104
 
89
105
  /**
@@ -103,12 +119,24 @@ export class ProjectUnlockService {
103
119
  }
104
120
 
105
121
  // load the encrypted master key for the project
106
- const armoredMasterKey = await this.stateManager.getProjectMasterKeyRepository().get(projectId)
107
- if (!armoredMasterKey) {
108
- throw new Error(`Project ${projectId} does not have a master key set.`)
122
+ const project = await this.database.backend.project.findUnique({
123
+ where: { id: projectId },
124
+ select: { encryptedMasterKey: true },
125
+ })
126
+
127
+ if (!project) {
128
+ throw new ProjectNotFoundError(projectId)
109
129
  }
110
130
 
111
- const encryptedMasterKey = armor.decode(armoredMasterKey)
131
+ if (!this.config.HIGHSTATE_ENCRYPTION_ENABLED) {
132
+ // no cryptography, just unlock with an empty master key
133
+ await this.projectUnlockBackend.unlockProject(projectId, Buffer.alloc(0))
134
+ await this.pubsubManager.publish(["project-unlock-state", projectId], { type: "unlocked" })
135
+ await this.runUnlockTasks(projectId)
136
+ return
137
+ }
138
+
139
+ const encryptedMasterKey = armor.decode(project.encryptedMasterKey)
112
140
 
113
141
  const decrypter = new Decrypter()
114
142
  decrypter.addIdentity(decryptedIdentity)
@@ -116,30 +144,8 @@ export class ProjectUnlockService {
116
144
  // decrypt the master key using the provided identity
117
145
  const masterKey = await decrypter.decrypt(encryptedMasterKey)
118
146
 
119
- // fetch the namespace for the project
120
- const namespace = await this.stateManager.getProjectNamespaceRepository().get(projectId)
121
- if (!namespace) {
122
- throw new Error(`Project ${projectId} does not have a namespace set.`)
123
- }
124
-
125
147
  // unlock the project in the backend
126
- await this.projectUnlockBackend.unlockProject(
127
- projectId,
128
- Buffer.from(masterKey).toString("base64"),
129
- namespace,
130
- )
131
-
132
- // load instance states to the hot state
133
- // TODO: this should be done by something else, ideally lazy-loaded when needed
134
- const instanceStates = await this.stateManager
135
- .getInstanceStateRepository(projectId)
136
- .getAllItems()
137
-
138
- await this.hotStateManager.hmset(
139
- ["instance-states", projectId],
140
- instanceStates.map(state => [state.id, state]),
141
- )
142
-
148
+ await this.projectUnlockBackend.unlockProject(projectId, Buffer.from(masterKey))
143
149
  await this.pubsubManager.publish(["project-unlock-state", projectId], { type: "unlocked" })
144
150
 
145
151
  // run unlock tasks
@@ -161,24 +167,37 @@ export class ProjectUnlockService {
161
167
  * The project must be unlocked.
162
168
  *
163
169
  * @param projectId The ID of the project to add the unlock method to.
164
- * @param unlockMethod The unlock method to add. Should be provided by the frontend.
170
+ * @param inputUnlockMethod The unlock method to add. Should be provided by the frontend.
165
171
  */
166
- async addProjectUnlockMethod(projectId: string, unlockMethod: UnlockMethod): Promise<void> {
167
- await using batch = this.stateManager.batch()
168
-
169
- // add the unlock method to the repository
170
- await this.stateManager
171
- .getUnlockMethodRepository(projectId)
172
- .put(unlockMethod.id, unlockMethod, batch)
173
-
174
- // get all existing unlock methods
175
- const existingMethods = await this.stateManager
176
- .getUnlockMethodRepository(projectId)
177
- .getAllItems()
178
-
179
- // update the encrypted master identity set and master key
180
- await this.updateUnlockSuite(projectId, existingMethods, batch)
181
- await this.updateEncryptedMasterKey(projectId, existingMethods, batch)
172
+ async addProjectUnlockMethod(
173
+ projectId: string,
174
+ inputUnlockMethod: UnlockMethodInput,
175
+ ): Promise<void> {
176
+ const database = await this.database.forProject(projectId)
177
+
178
+ await database.$transaction(async tx => {
179
+ // 1. fetch all unlock method recipients for the project
180
+ const unlockMethods = await tx.unlockMethod.findMany({
181
+ select: { type: true, recipient: true, encryptedIdentity: true },
182
+ })
183
+
184
+ const allUnlockMethods = [...unlockMethods, inputUnlockMethod]
185
+
186
+ // 2. encrypt the project data for all recipients + the new recipient
187
+ const encryptedMasterKey = await this.encryptProjectMasterKey(projectId, allUnlockMethods)
188
+
189
+ // 3. persist the new unlock method
190
+ await tx.unlockMethod.create({ data: inputUnlockMethod })
191
+
192
+ // 4. update the project with the new master key and unlock suite
193
+ await this.database.backend.project.update({
194
+ where: { id: projectId },
195
+ data: {
196
+ encryptedMasterKey,
197
+ unlockSuite: ProjectUnlockService.createUnlockSuite(allUnlockMethods),
198
+ },
199
+ })
200
+ })
182
201
  }
183
202
 
184
203
  /**
@@ -189,25 +208,34 @@ export class ProjectUnlockService {
189
208
  * @param unlockMethodId The ID of the unlock method to remove.
190
209
  */
191
210
  async removeProjectUnlockMethod(projectId: string, unlockMethodId: string): Promise<void> {
192
- await using batch = this.stateManager.batch()
193
-
194
- // get all existing unlock methods
195
- let existingMethods = await this.stateManager.getUnlockMethodRepository(projectId).getAllItems()
196
-
197
- // do not allow removing the last unlock method under any circumstances
198
- if (existingMethods.length === 1) {
199
- throw new Error("Rejected removing the last unlock method!")
200
- }
211
+ const database = await this.database.forProject(projectId)
201
212
 
202
- // remove the unlock method from the repository
203
- await this.stateManager.getUnlockMethodRepository(projectId).delete(unlockMethodId, batch)
204
- existingMethods = existingMethods.filter(method => method.id !== unlockMethodId)
213
+ await database.$transaction(async tx => {
214
+ // 1. fetch all unlock methods except the one to remove
215
+ const unlockMethods = await tx.unlockMethod.findMany({
216
+ where: { id: { not: unlockMethodId } },
217
+ select: { type: true, recipient: true, encryptedIdentity: true },
218
+ })
205
219
 
206
- // update the encrypted master identity set and master key
207
- await this.updateUnlockSuite(projectId, existingMethods, batch)
208
- await this.updateEncryptedMasterKey(projectId, existingMethods, batch)
220
+ if (unlockMethods.length === 0) {
221
+ throw new CannotDeleteLastUnlockMethodError(projectId)
222
+ }
209
223
 
210
- await batch.write()
224
+ // 2. encrypt the project data for remaining recipients
225
+ const encryptedMasterKey = await this.encryptProjectMasterKey(projectId, unlockMethods)
226
+
227
+ // 3. delete the unlock method
228
+ await tx.unlockMethod.delete({ where: { id: unlockMethodId } })
229
+
230
+ // 4. update the project with the new master key and unlock suite
231
+ await this.database.backend.project.update({
232
+ where: { id: projectId },
233
+ data: {
234
+ encryptedMasterKey,
235
+ unlockSuite: ProjectUnlockService.createUnlockSuite(unlockMethods),
236
+ },
237
+ })
238
+ })
211
239
  }
212
240
 
213
241
  /**
@@ -221,40 +249,54 @@ export class ProjectUnlockService {
221
249
  this.unlockTasks.push({ name, handler })
222
250
  }
223
251
 
224
- private async updateUnlockSuite(
252
+ private async encryptProjectMasterKey(
225
253
  projectId: string,
226
- unlockMethods: UnlockMethod[],
227
- batch: StateBatch,
228
- ): Promise<void> {
229
- // write new encrypted master identity set
230
- await this.stateManager.getProjectUnlockSuiteRepository().put(
231
- projectId,
232
- {
233
- encryptedIdentities: unlockMethods.map(method => method.encryptedIdentity),
234
- hasPasskey: unlockMethods.some(method => method.type === "passkey"),
235
- },
236
- batch,
237
- )
238
- }
239
-
240
- private async updateEncryptedMasterKey(
241
- projectId: string,
242
- unlockMethods: UnlockMethod[],
243
- batch: StateBatch,
244
- masterKey?: Uint8Array,
245
- ): Promise<void> {
246
- masterKey ??= await this.stateManager.getProjectMasterKey(projectId)
254
+ unlockMethods: { recipient: string }[],
255
+ ): Promise<string> {
256
+ const masterKey = await this.database.getProjectMasterKey(projectId)
257
+ if (!masterKey) {
258
+ // окак
259
+ return "encryption disabled"
260
+ }
247
261
 
262
+ // encrypt the master key for all unlock methods
248
263
  const encrypter = new Encrypter()
249
- for (const method of unlockMethods) {
250
- encrypter.addRecipient(method.recipient)
264
+ for (const unlockMethod of unlockMethods) {
265
+ encrypter.addRecipient(unlockMethod.recipient)
251
266
  }
252
267
 
253
- // encrypt the master key for all unlock methods
254
268
  const encryptedMasterKey = await encrypter.encrypt(masterKey)
255
-
256
- // set the encrypted master key in the backend
257
269
  const armoredMasterKey = armor.encode(encryptedMasterKey)
258
- await this.stateManager.getProjectMasterKeyRepository().put(projectId, armoredMasterKey, batch)
270
+
271
+ return armoredMasterKey
272
+ }
273
+
274
+ /**
275
+ * Auto-unlocks the projects for the development environment.
276
+ */
277
+ async autoUnlockProjects(): Promise<void> {
278
+ // just mark the projects as unlocked with an empty master key and run unlock tasks
279
+ for (const projectId of this.config.HIGHSTATE_DEV_AUTO_UNLOCK_PROJECT_IDS) {
280
+ const logger = createProjectLogger(this.logger, projectId)
281
+
282
+ try {
283
+ logger.info("auto-unlocking project (dev mode)")
284
+
285
+ await this.projectUnlockBackend.unlockProject(projectId, Buffer.alloc(0))
286
+ await this.pubsubManager.publish(["project-unlock-state", projectId], { type: "unlocked" })
287
+ await this.runUnlockTasks(projectId)
288
+ } catch (error) {
289
+ logger.error({ error }, "failed to auto-unlock project")
290
+ }
291
+ }
292
+ }
293
+
294
+ private static createUnlockSuite(
295
+ unlockMethods: { type: string; encryptedIdentity: string }[],
296
+ ): ProjectUnlockSuite {
297
+ return {
298
+ encryptedIdentities: unlockMethods.map(method => method.encryptedIdentity),
299
+ hasPasskey: unlockMethods.some(method => method.type === "passkey"),
300
+ }
259
301
  }
260
302
  }