@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,138 @@
1
+ model Worker {
2
+ /// The CUIDv2 of the worker.
3
+ id String @id @default(cuid(2))
4
+
5
+ /// The ID of the worker.
6
+ ///
7
+ /// This is the fully qualified image name without the tag or digest.
8
+ /// The format is `{<registry>/}[<namespace>/]<name>`.
9
+ ///
10
+ /// For example: `ghcr.io/highstate/worker` or `docker.io/library/ubuntu`.
11
+ identity String @unique
12
+
13
+ /// The ID of the service account this worker uses.
14
+ serviceAccountId String @unique
15
+
16
+ /// The time this worker first appeared in the system.
17
+ createdAt DateTime @default(now())
18
+
19
+ /// The service account impersonating this worker.
20
+ serviceAccount ServiceAccount @relation(fields: [serviceAccountId], references: [id])
21
+
22
+ /// The versions of this worker.
23
+ versions WorkerVersion[]
24
+ }
25
+
26
+ enum WorkerVersionStatus {
27
+ /// The status is unknown.
28
+ unknown
29
+
30
+ /// The worker is being started by one of the runtimes.
31
+ starting
32
+
33
+ /// The worker is running and serving registrations.
34
+ running
35
+
36
+ /// The worker is being stopping (after was starting/running and was disabled).
37
+ stopping
38
+
39
+ /// The worker is stopped and not serving registrations.
40
+ stopped
41
+
42
+ /// The worker failed to start/crashed more than the allowed number of times.
43
+ error
44
+ }
45
+
46
+ model WorkerVersion {
47
+ /// The CUIDv2 of the worker version.
48
+ id String @id @default(cuid(2))
49
+
50
+ /// The metadata of the worker version managed by the backend.
51
+ ///
52
+ /// [CommonObjectMeta]
53
+ meta Json
54
+
55
+ /// The current status of the worker version reported by the runtime.
56
+ status WorkerVersionStatus @default(unknown)
57
+
58
+ /// Whether this worker version is enabled and will be launched when project is unclocked.
59
+ enabled Boolean @default(true)
60
+
61
+ /// The ID of the runtime where this worker version currently runs.
62
+ runtimeId String?
63
+
64
+ /// The ID of the worker this version belongs to.
65
+ workerId String
66
+
67
+ /// The digest of the worker version used to identify it.
68
+ /// The format is raw SHA256 digest without the `sha256:` prefix.
69
+ digest String @unique
70
+
71
+ /// The ID of the API key this worker version uses.
72
+ apiKeyId String @unique
73
+
74
+ /// The time this worker version was created.
75
+ createdAt DateTime @default(now())
76
+
77
+ /// The time this worker version was last updated.
78
+ updatedAt DateTime @updatedAt
79
+
80
+ /// The worker this version belongs to.
81
+ worker Worker @relation(fields: [workerId], references: [id])
82
+
83
+ /// The API key this worker version uses.
84
+ apiKey ApiKey @relation(fields: [apiKeyId], references: [id])
85
+
86
+ /// The unit registrations for this worker version.
87
+ unitRegistrations WorkerUnitRegistration[]
88
+
89
+ /// The logs produced by this worker version.
90
+ logs WorkerVersionLog[]
91
+ }
92
+
93
+ model WorkerUnitRegistration {
94
+ /// The ID of the state of the unit instance requesting the registration.
95
+ stateId String
96
+
97
+ /// The name of the workor within the instance.
98
+ name String
99
+
100
+ /// The parameters of the registration passed by the unit.
101
+ ///
102
+ /// [WorkerUnitRegistrationParams]
103
+ params Json
104
+
105
+ /// The ID of the worker version this registration currently uses.
106
+ workerVersionId String
107
+
108
+ /// The time this registration was created.
109
+ createdAt DateTime @default(now())
110
+
111
+ /// The time this registration was last updated.
112
+ updatedAt DateTime @updatedAt
113
+
114
+ /// The unit instance requesting the registration.
115
+ state InstanceState @relation(fields: [stateId], references: [id])
116
+
117
+ /// The worker version this registration currently uses.
118
+ workerVersion WorkerVersion @relation(fields: [workerVersionId], references: [id])
119
+
120
+ @@id([stateId, name]) // the registration is identified by the instance and name
121
+ }
122
+
123
+ model WorkerVersionLog {
124
+ /// The ULID of the worker log. Also used to extract the timestamp.
125
+ id String @id @default(ulid())
126
+
127
+ /// The ID of the worker version that produced this log.
128
+ workerVersionId String
129
+
130
+ /// The log content.
131
+ content String
132
+
133
+ /// Whether this log is a system/runtime message (vs worker output).
134
+ isSystem Boolean @default(false)
135
+
136
+ /// The worker version that produced this log.
137
+ workerVersion WorkerVersion @relation(fields: [workerVersionId], references: [id], onDelete: Cascade)
138
+ }
@@ -1,46 +1,46 @@
1
1
  export interface ArtifactBackend {
2
2
  /**
3
- * Stores content and returns its hash.
4
- * If content with the same hash already exists, returns existing hash.
3
+ * Stores content in the backend.
4
+ * If content with the same id already exists, does nothing.
5
5
  *
6
6
  * @param projectId The project ID to which the content belongs.
7
- * @param hsah The content hash.
7
+ * @param artifactId The ID of the artifact to store.
8
8
  * @param chunkSize The size of each chunk to store. Only the last chunk may be smaller.
9
9
  * @param content The async iterable of content chunks.
10
10
  */
11
11
  store(
12
12
  projectId: string,
13
- hash: string,
13
+ artifactId: string,
14
14
  chunkSize: number,
15
15
  content: AsyncIterable<Uint8Array>,
16
16
  ): Promise<void>
17
17
 
18
18
  /**
19
- * Retrieves content by hash.
19
+ * Retrieves content by artifact ID.
20
20
  *
21
21
  * @param projectId The project ID to which the content belongs.
22
- * @param hash The content hash.
22
+ * @param artifactId The ID of the artifact to retrieve.
23
23
  * @param chunkSize The size of each chunk to retrieve. Only the last chunk may be smaller.
24
24
  */
25
25
  retrieve(
26
26
  projectId: string,
27
- hash: string,
27
+ artifactId: string,
28
28
  chunkSize: number,
29
29
  ): Promise<AsyncIterable<Uint8Array> | null>
30
30
 
31
31
  /**
32
- * Deletes content by hash.
32
+ * Deletes content by artifact ID.
33
33
  *
34
34
  * @param projectId The project ID to which the content belongs.
35
- * @param hash The content hash.
35
+ * @param artifactId The ID of the artifact to delete.
36
36
  */
37
- delete(projectId: string, hash: string): Promise<void>
37
+ delete(projectId: string, artifactId: string): Promise<void>
38
38
 
39
39
  /**
40
- * Checks if content exists by hash.
40
+ * Checks if content exists by artifact ID.
41
41
  *
42
42
  * @param projectId The project ID to which the content belongs.
43
- * @param hash The content hash.
43
+ * @param artifactId The ID of the artifact to check.
44
44
  */
45
- exists(projectId: string, hash: string): Promise<boolean>
45
+ exists(projectId: string, artifactId: string): Promise<boolean>
46
46
  }
@@ -1,45 +1,54 @@
1
- import type { EncryptionBackend, StateManager } from "../state"
1
+ import type { DatabaseManager } from "../database"
2
2
  import type { ArtifactBackend } from "./abstractions"
3
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha"
4
+ import { managedNonce } from "@noble/ciphers/webcrypto"
3
5
 
4
6
  const nonceSize = 24
5
7
 
8
+ /**
9
+ * The ArtifactBackend decorator that adds encryption and key obfuscation to the artifact storage.
10
+ */
6
11
  export class EncryptionArtifactBackend implements ArtifactBackend {
7
12
  constructor(
8
13
  private readonly artifactBackend: ArtifactBackend,
9
- private readonly stateManager: StateManager,
14
+ private readonly database: DatabaseManager,
10
15
  ) {}
11
16
 
12
17
  async store(
13
18
  projectId: string,
14
- hash: string,
19
+ artifactId: string,
15
20
  chunkSize: number,
16
21
  content: AsyncIterable<Uint8Array>,
17
22
  ): Promise<void> {
18
- const encryptionBackend = this.stateManager.getEncryptionBackend(projectId)
19
- const encryptedContent = this.getEncryptedContent(encryptionBackend, content)
23
+ const encryptedContent = this.getEncryptedContent(projectId, content)
20
24
 
21
- await this.artifactBackend.store(projectId, hash, chunkSize + nonceSize, encryptedContent)
25
+ await this.artifactBackend.store(projectId, artifactId, chunkSize + nonceSize, encryptedContent)
22
26
  }
23
27
 
24
28
  private async *getEncryptedContent(
25
- encryptionBackend: EncryptionBackend,
29
+ projectId: string,
26
30
  content: AsyncIterable<Uint8Array>,
27
31
  ): AsyncIterable<Uint8Array> {
32
+ const masterKey = await this.database.getProjectMasterKey(projectId)
33
+ if (!masterKey) {
34
+ throw new Error(`No master key found for project ${projectId}`)
35
+ }
36
+
37
+ const xchacha = managedNonce(xchacha20poly1305)(masterKey)
38
+
28
39
  for await (const chunk of content) {
29
- yield await encryptionBackend.encrypt(chunk)
40
+ yield xchacha.encrypt(chunk)
30
41
  }
31
42
  }
32
43
 
33
44
  async retrieve(
34
45
  projectId: string,
35
- hash: string,
46
+ artifactId: string,
36
47
  chunkSize: number,
37
48
  ): Promise<AsyncIterable<Uint8Array> | null> {
38
- const encryptionBackend = this.stateManager.getEncryptionBackend(projectId)
39
-
40
49
  const encryptedContent = await this.artifactBackend.retrieve(
41
50
  projectId,
42
- hash,
51
+ artifactId,
43
52
  chunkSize + nonceSize,
44
53
  )
45
54
 
@@ -47,15 +56,22 @@ export class EncryptionArtifactBackend implements ArtifactBackend {
47
56
  return null
48
57
  }
49
58
 
50
- return this.getDecryptedContent(encryptionBackend, encryptedContent)
59
+ return this.getDecryptedContent(projectId, encryptedContent)
51
60
  }
52
61
 
53
62
  private async *getDecryptedContent(
54
- encryptionBackend: EncryptionBackend,
63
+ projectId: string,
55
64
  encryptedContent: AsyncIterable<Uint8Array>,
56
65
  ): AsyncIterable<Uint8Array> {
66
+ const masterKey = await this.database.getProjectMasterKey(projectId)
67
+ if (!masterKey) {
68
+ throw new Error(`No master key found for project ${projectId}`)
69
+ }
70
+
71
+ const xchacha = managedNonce(xchacha20poly1305)(masterKey)
72
+
57
73
  for await (const chunk of encryptedContent) {
58
- yield await encryptionBackend.decrypt(chunk)
74
+ yield xchacha.decrypt(chunk)
59
75
  }
60
76
  }
61
77
 
@@ -1,19 +1,18 @@
1
- import type { ArtifactBackend } from "./abstractions"
2
1
  import type { Logger } from "pino"
3
- import type { StateManager } from "../state"
2
+ import type { DatabaseManager } from "../database"
3
+ import type { ArtifactBackend } from "./abstractions"
4
4
  import { z } from "zod"
5
- import { LocalArtifactBackend, localArtifactBackendConfig } from "./local"
6
5
  import { EncryptionArtifactBackend } from "./encryption"
6
+ import { LocalArtifactBackend, localArtifactBackendConfig } from "./local"
7
7
 
8
8
  export const artifactBackendConfig = z.object({
9
9
  HIGHSTATE_ARTIFACT_BACKEND_TYPE: z.enum(["local"]).default("local"),
10
- HIGHSTATE_ARTIFACT_ENABLE_ENCRYPTION: z.coerce.boolean().default(true),
11
10
  ...localArtifactBackendConfig.shape,
12
11
  })
13
12
 
14
13
  export async function createArtifactBackend(
15
14
  config: z.infer<typeof artifactBackendConfig> & Record<string, unknown>,
16
- stateManager: StateManager,
15
+ database: DatabaseManager,
17
16
  logger: Logger,
18
17
  ): Promise<ArtifactBackend> {
19
18
  let backend: ArtifactBackend
@@ -22,14 +21,12 @@ export async function createArtifactBackend(
22
21
 
23
22
  switch (config.HIGHSTATE_ARTIFACT_BACKEND_TYPE) {
24
23
  case "local": {
25
- backend = await LocalArtifactBackend.create(config, fileExtension, stateManager, logger)
24
+ backend = await LocalArtifactBackend.create(config, fileExtension, logger)
26
25
  }
27
26
  }
28
27
 
29
- if (config.HIGHSTATE_ARTIFACT_ENABLE_ENCRYPTION) {
30
- backend = new EncryptionArtifactBackend(backend, stateManager)
31
- } else {
32
- logger.warn("artifact encryption is disabled, this is not recommended")
28
+ if (database.isEncryptionEnabled) {
29
+ backend = new EncryptionArtifactBackend(backend, database)
33
30
  }
34
31
 
35
32
  return backend
@@ -1,61 +1,54 @@
1
1
  import type { Logger } from "pino"
2
- import type { StateManager } from "../state"
2
+ import type z from "zod"
3
+ import type { ArtifactBackend } from "./abstractions"
3
4
  import { createReadStream, createWriteStream } from "node:fs"
4
- import { access, mkdir, readdir, rmdir, unlink } from "node:fs/promises"
5
+ import { access, mkdir, readdir, rm, unlink } from "node:fs/promises"
5
6
  import { join, resolve } from "node:path"
6
- import { z } from "zod"
7
- import { type ArtifactBackend } from "./abstractions"
7
+ import { codebaseConfig, createProjectLogger, getCodebaseHighstatePath } from "../common"
8
8
 
9
- export const localArtifactBackendConfig = z.object({
10
- HIGHSTATE_ARTIFACT_BACKEND_LOCAL_DIR: z.string().optional(),
11
- })
9
+ export const localArtifactBackendConfig = codebaseConfig
12
10
 
13
11
  /**
14
12
  * A local artifact backend that stores artifacts in the filesystem.
15
13
  *
16
- * The default artifact location is `~/.highstate/artifacts`.
17
- *
18
14
  * File structure:
19
- * - `{artifactDir}/{first2chars}/{hash}` - actual artifact content files
15
+ * - `{codebase}/.highstate/projects/{projectId}/artifacts/{id}.{extension}`
20
16
  */
21
17
  export class LocalArtifactBackend implements ArtifactBackend {
22
18
  constructor(
23
- private readonly storageDir: string,
24
- private readonly fileExtension: string,
25
- private readonly stateManager: StateManager,
19
+ private readonly hsCodebasePath: string,
20
+ private readonly extension: string,
26
21
  private readonly logger: Logger,
27
- ) {
28
- this.logger.debug({ msg: "initialized", baseDir: storageDir })
29
- }
22
+ ) {}
30
23
 
31
24
  async store(
32
25
  projectId: string,
33
- hash: string,
26
+ artifactId: string,
34
27
  chunkSize: number,
35
28
  content: AsyncIterable<Uint8Array>,
36
29
  ): Promise<void> {
37
- const [baseDir, fileName] = this.getArtifactPath(projectId, hash)
30
+ const logger = createProjectLogger(this.logger, projectId)
31
+ const [baseDir, fileName] = this.getArtifactPath(projectId, artifactId)
38
32
  await mkdir(baseDir, { recursive: true })
39
33
 
40
34
  // check if the artifact already exists
41
35
  try {
42
36
  await access(fileName)
43
- this.logger.debug({ msg: "artifact already exists", hash })
44
-
37
+ logger.debug({ artifactId }, "artifact already exists")
45
38
  return
46
39
  } catch {
47
40
  // artifact does not exist, continue with storing
48
41
  }
49
42
 
50
43
  const file = createWriteStream(fileName, { highWaterMark: chunkSize })
51
- this.logger.debug({ msg: "opened file for writing", hash, fileName })
44
+ logger.debug({ artifactId, fileName }, "opened file for writing")
52
45
 
53
46
  for await (const chunk of content) {
54
47
  file.write(chunk)
55
48
  }
56
49
 
57
50
  file.end()
58
- this.logger.info({ msg: "artifact stored", hash, fileName })
51
+ logger.info({ artifactId, fileName }, "artifact stored")
59
52
  }
60
53
 
61
54
  async retrieve(
@@ -63,41 +56,41 @@ export class LocalArtifactBackend implements ArtifactBackend {
63
56
  hash: string,
64
57
  chunkSize: number,
65
58
  ): Promise<AsyncIterable<Uint8Array> | null> {
59
+ const logger = createProjectLogger(this.logger, projectId)
66
60
  const [, fileName] = this.getArtifactPath(projectId, hash)
67
61
 
68
62
  try {
69
63
  return Promise.resolve(createReadStream(fileName, { highWaterMark: chunkSize }))
70
64
  } catch (error) {
71
- this.logger.debug({ msg: "artifact retrieval failed", hash, error })
72
-
65
+ logger.debug({ hash, error }, "artifact retrieval failed")
73
66
  return null
74
67
  }
75
68
  }
76
69
 
77
70
  async delete(projectId: string, hash: string): Promise<void> {
71
+ const logger = createProjectLogger(this.logger, projectId)
78
72
  const [baseDir, fileName] = this.getArtifactPath(projectId, hash)
79
73
 
80
74
  try {
81
75
  await unlink(fileName)
82
- await this.deleteDirectoryIfEmpty(baseDir)
83
-
84
- this.logger.info({ msg: "artifact deleted", hash, fileName })
76
+ await this.deleteDirectoryIfEmpty(baseDir, logger)
77
+ logger.info({ hash, fileName }, "artifact deleted")
85
78
  } catch (error) {
86
- this.logger.error({ msg: "artifact deletion failed", hash, fileName, error })
79
+ logger.error({ hash, fileName, error }, "artifact deletion failed")
87
80
  }
88
81
  }
89
82
 
90
- async deleteDirectoryIfEmpty(dirPath: string): Promise<void> {
83
+ async deleteDirectoryIfEmpty(dirPath: string, logger: Logger): Promise<void> {
91
84
  try {
92
85
  const files = await readdir(dirPath)
93
86
  if (files.length === 0) {
94
- await rmdir(dirPath)
95
- this.logger.info({ msg: "deleted empty directory", dirPath })
87
+ await rm(dirPath)
88
+ logger.info({ dirPath }, "deleted empty directory")
96
89
  } else {
97
- this.logger.debug({ msg: "directory not empty, skipping deletion", dirPath, files })
90
+ logger.debug({ dirPath, fileCount: files.length }, "directory not empty, skipping deletion")
98
91
  }
99
92
  } catch (error) {
100
- this.logger.error({ msg: "failed to delete directory", dirPath, error })
93
+ logger.error({ dirPath, error }, "failed to delete directory")
101
94
  }
102
95
  }
103
96
 
@@ -113,30 +106,20 @@ export class LocalArtifactBackend implements ArtifactBackend {
113
106
  }
114
107
 
115
108
  private getArtifactPath(projectId: string, hash: string): [baseDir: string, fileName: string] {
116
- const hashedProjectId = this.stateManager.getHashedProjectId(projectId)
117
- const baseDir = resolve(this.storageDir, hashedProjectId, hash.substring(0, 2))
118
- const fileName = join(baseDir, `${hash}${this.fileExtension}`)
109
+ const baseDir = resolve(this.hsCodebasePath, "projects", projectId, "artifacts")
110
+ const fileName = join(baseDir, `${hash}${this.extension}`)
119
111
 
120
112
  return [baseDir, fileName]
121
113
  }
122
114
 
123
115
  static async create(
124
- config: z.infer<typeof localArtifactBackendConfig>,
125
- fileExtension: string,
126
- stateManager: StateManager,
116
+ config: z.infer<typeof codebaseConfig>,
117
+ extension: string,
127
118
  logger: Logger,
128
119
  ): Promise<LocalArtifactBackend> {
129
- const childLogger = logger.child({
130
- backend: "ArtifactBackend",
131
- service: "LocalArtifactBackend",
132
- })
133
-
134
- let location = config.HIGHSTATE_ARTIFACT_BACKEND_LOCAL_DIR
135
- if (!location) {
136
- location = resolve(process.env.HOME!, ".highstate", "artifacts")
137
- }
120
+ const serviceLogger = logger.child({ service: "LocalArtifactBackend" })
121
+ const codebasePath = await getCodebaseHighstatePath(config, logger)
138
122
 
139
- await mkdir(location, { recursive: true })
140
- return new LocalArtifactBackend(location, fileExtension, stateManager, childLogger)
123
+ return new LocalArtifactBackend(codebasePath, extension, serviceLogger)
141
124
  }
142
125
  }
@@ -1,12 +1,13 @@
1
- import type { LockManager } from "../lock"
2
- import type { StateManager } from "../state"
1
+ import type { Logger } from "pino"
2
+ import type { ApiKey, DatabaseManager } from "../database"
3
3
  import { randomBytes } from "node:crypto"
4
- import { AccessError, type ProjectApiKey } from "../shared"
4
+ import { createProjectLogger } from "../common"
5
+ import { AccessError } from "../shared"
5
6
 
6
7
  export class ApiKeyService {
7
8
  constructor(
8
- private readonly stateManager: StateManager,
9
- private readonly lockManager: LockManager,
9
+ private readonly database: DatabaseManager,
10
+ private readonly logger: Logger,
10
11
  ) {}
11
12
 
12
13
  /**
@@ -15,35 +16,20 @@ export class ApiKeyService {
15
16
  * @param projectId The ID of the project containing the API key.
16
17
  * @param apiKeyId The ID of the API key to regenerate.
17
18
  */
18
- async regenerateToken(projectId: string, apiKeyId: string): Promise<ProjectApiKey> {
19
- return await this.lockManager.acquire([["api-key", projectId, apiKeyId]], async () => {
20
- const apiKey = await this.stateManager.getApiKeyRepository(projectId).get(apiKeyId)
21
- if (!apiKey) {
22
- throw new Error(`API key with ID "${apiKeyId}" not found in project "${projectId}"`)
23
- }
24
-
25
- const batch = this.stateManager.batch()
26
-
27
- // delete the old token from the index
28
- await this.stateManager
29
- .getApiKeyTokenIndexRepository(projectId)
30
- .indexRepository.delete(apiKey.token, batch)
31
-
32
- // generate a new token
33
- apiKey.token = randomBytes(32).toString("hex")
34
-
35
- // update the API key with the new token and update the index
36
- await Promise.all([
37
- this.stateManager.getApiKeyRepository(projectId).putItem(apiKey, batch),
38
- this.stateManager
39
- .getApiKeyTokenIndexRepository(projectId)
40
- .indexRepository.put(apiKey.token, apiKey.id, batch),
41
- ])
19
+ async regenerateToken(projectId: string, apiKeyId: string): Promise<ApiKey> {
20
+ const logger = createProjectLogger(this.logger, projectId)
21
+ const database = await this.database.forProject(projectId)
22
+
23
+ const apiKey = await database.apiKey.update({
24
+ where: { id: apiKeyId },
25
+ data: {
26
+ token: randomBytes(32).toString("hex"),
27
+ },
28
+ })
42
29
 
43
- await batch.write()
30
+ logger.info(`regenerated API key token with ID "%s",`, apiKeyId)
44
31
 
45
- return apiKey
46
- })
32
+ return apiKey
47
33
  }
48
34
 
49
35
  /**
@@ -54,10 +40,12 @@ export class ApiKeyService {
54
40
  * @returns The ProjectApiKey object if found.
55
41
  * @throws AccessError if the token is not valid for the project.
56
42
  */
57
- async getApiKeyByToken(projectId: string, token: string): Promise<ProjectApiKey> {
58
- const apiKey = await this.stateManager.getApiKeyTokenIndexRepository(projectId).get(token)
59
- if (!apiKey || apiKey.token !== token) {
60
- throw new AccessError(`Token is not valid for project "${projectId}"`)
43
+ async getApiKeyByToken(projectId: string, token: string): Promise<ApiKey> {
44
+ const database = await this.database.forProject(projectId)
45
+ const apiKey = await database.apiKey.findUnique({ where: { token } })
46
+
47
+ if (!apiKey) {
48
+ throw new AccessError(`API key token is not valid for project "${projectId}"`)
61
49
  }
62
50
 
63
51
  return apiKey