@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
@@ -0,0 +1,90 @@
1
+ import type { DatabaseManager, TerminalSessionLog } from "../database"
2
+ import {
3
+ type TerminalSessionOutput,
4
+ toTerminalSessionOutput,
5
+ } from "../shared/models/project/terminal"
6
+
7
+ /**
8
+ * Business service for managing terminal sessions
9
+ */
10
+ export class TerminalSessionService {
11
+ constructor(private readonly database: DatabaseManager) {}
12
+
13
+ /**
14
+ * Get terminal sessions for a specific instance
15
+ *
16
+ * @param projectId The project ID
17
+ * @param instanceId The instance ID to get sessions for
18
+ * @returns Array of terminal session outputs with metadata
19
+ */
20
+ async getInstanceTerminalSessions(
21
+ projectId: string,
22
+ instanceId: string,
23
+ ): Promise<TerminalSessionOutput[]> {
24
+ const database = await this.database.forProject(projectId)
25
+
26
+ const sessions = await database.terminalSession.findMany({
27
+ include: {
28
+ terminal: {
29
+ select: { meta: true },
30
+ },
31
+ },
32
+ where: {
33
+ terminal: {
34
+ stateId: instanceId,
35
+ },
36
+ },
37
+ orderBy: { startedAt: "desc" },
38
+ take: 20,
39
+ })
40
+
41
+ return sessions.map(session => ({
42
+ ...session,
43
+ projectId,
44
+ meta: session.terminal.meta,
45
+ }))
46
+ }
47
+
48
+ /**
49
+ * Get a specific terminal session by ID
50
+ *
51
+ * @param projectId The project ID
52
+ * @param sessionId The session ID to get
53
+ * @returns Terminal session output with metadata or null if not found
54
+ */
55
+ async getTerminalSession(
56
+ projectId: string,
57
+ sessionId: string,
58
+ ): Promise<TerminalSessionOutput | null> {
59
+ const database = await this.database.forProject(projectId)
60
+
61
+ const session = await database.terminalSession.findUnique({
62
+ where: { id: sessionId },
63
+ include: {
64
+ terminal: true,
65
+ },
66
+ })
67
+
68
+ if (!session) {
69
+ return null
70
+ }
71
+
72
+ return toTerminalSessionOutput(session.terminal, session)
73
+ }
74
+
75
+ /**
76
+ * Get session history (logs) for a specific terminal session
77
+ *
78
+ * @param projectId The project ID
79
+ * @param sessionId The session ID to get history for
80
+ * @returns Array of session logs ordered by creation time
81
+ */
82
+ async getSessionHistory(projectId: string, sessionId: string): Promise<TerminalSessionLog[]> {
83
+ const database = await this.database.forProject(projectId)
84
+
85
+ return await database.terminalSessionLog.findMany({
86
+ where: { sessionId },
87
+ orderBy: { id: "asc" },
88
+ })
89
+ }
90
+ }
@@ -0,0 +1,539 @@
1
+ import type { UnitPage, UnitTerminal, UnitTrigger } from "@highstate/contract"
2
+ import { describe, expect } from "vitest"
3
+ import { test } from "../test-utils"
4
+ import { UnitExtraService } from "./unit-extra"
5
+
6
+ describe("UnitExtraService", () => {
7
+ describe("processUnitTerminals", () => {
8
+ test("should create new terminals", async ({
9
+ database,
10
+ project,
11
+ createInstanceState,
12
+ projectDatabase,
13
+ }) => {
14
+ const service = new UnitExtraService(database)
15
+ const instance = await createInstanceState(project.id)
16
+
17
+ const unitTerminals: UnitTerminal[] = [
18
+ {
19
+ name: "ssh",
20
+ meta: {
21
+ title: "SSH Terminal",
22
+ description: "Secure shell access to the server",
23
+ icon: "mdi:terminal",
24
+ iconColor: "#4CAF50",
25
+ },
26
+ spec: {
27
+ image: "alpine:3.18",
28
+ command: ["ssh", "-o", "StrictHostKeyChecking=no"],
29
+ cwd: "/root",
30
+ env: {
31
+ TERM: "xterm-256color",
32
+ SSH_AUTH_SOCK: "/tmp/ssh-agent.sock",
33
+ },
34
+ files: {
35
+ "/root/.ssh/config": {
36
+ meta: {
37
+ name: "ssh-config",
38
+ },
39
+
40
+ content: {
41
+ type: "embedded",
42
+ value: "Host *\n ServerAliveInterval 60\n ServerAliveCountMax 3\n",
43
+ },
44
+ },
45
+ },
46
+ },
47
+ },
48
+ ]
49
+
50
+ await projectDatabase.$transaction(async tx => {
51
+ await service.processUnitTerminals(tx, instance.id, unitTerminals)
52
+ })
53
+ const terminals = await projectDatabase.terminal.findMany({ where: { stateId: instance.id } })
54
+
55
+ expect(terminals).toHaveLength(1)
56
+ expect(terminals[0].name).toBe("ssh")
57
+ expect(terminals[0].status).toBe("active")
58
+ expect(terminals[0].meta).toEqual({
59
+ title: "SSH Terminal",
60
+ description: "Secure shell access to the server",
61
+ icon: "mdi:terminal",
62
+ iconColor: "#4CAF50",
63
+ })
64
+ expect(terminals[0].spec).toMatchObject({
65
+ image: "alpine:3.18",
66
+ command: ["ssh", "-o", "StrictHostKeyChecking=no"],
67
+ })
68
+ })
69
+
70
+ test("should update existing terminals", async ({
71
+ database,
72
+ project,
73
+ createInstanceState,
74
+ projectDatabase,
75
+ }) => {
76
+ const service = new UnitExtraService(database)
77
+ const instance = await createInstanceState(project.id)
78
+
79
+ await projectDatabase.terminal.create({
80
+ data: {
81
+ stateId: instance.id,
82
+ name: "ssh",
83
+ meta: {
84
+ title: "Old Terminal",
85
+ icon: "mdi:console",
86
+ },
87
+ spec: {
88
+ image: "ubuntu:20.04",
89
+ command: ["bash"],
90
+ },
91
+ status: "active",
92
+ },
93
+ })
94
+
95
+ const unitTerminals: UnitTerminal[] = [
96
+ {
97
+ name: "ssh",
98
+ meta: {
99
+ title: "SSH Terminal",
100
+ description: "Updated terminal with SSH access",
101
+ icon: "mdi:terminal",
102
+ },
103
+ spec: {
104
+ image: "alpine:3.18",
105
+ command: ["ssh", "-i", "/root/.ssh/id_rsa"],
106
+ env: {
107
+ TERM: "xterm-256color",
108
+ },
109
+ },
110
+ },
111
+ ]
112
+
113
+ await projectDatabase.$transaction(async tx => {
114
+ await service.processUnitTerminals(tx, instance.id, unitTerminals)
115
+ })
116
+
117
+ const terminals = await projectDatabase.terminal.findMany({ where: { stateId: instance.id } })
118
+
119
+ expect(terminals).toHaveLength(1)
120
+ expect(terminals[0].meta).toEqual({
121
+ title: "SSH Terminal",
122
+ description: "Updated terminal with SSH access",
123
+ icon: "mdi:terminal",
124
+ })
125
+ expect(terminals[0].spec).toMatchObject({
126
+ image: "alpine:3.18",
127
+ command: ["ssh", "-i", "/root/.ssh/id_rsa"],
128
+ })
129
+ expect(terminals[0].status).toBe("active")
130
+ })
131
+
132
+ test("should mark dangling terminals as unavailable", async ({
133
+ database,
134
+ project,
135
+ createInstanceState,
136
+ }) => {
137
+ const service = new UnitExtraService(database)
138
+ const instance = await createInstanceState(project.id)
139
+
140
+ const db = await database.forProject(project.id)
141
+ await db.terminal.createMany({
142
+ data: [
143
+ {
144
+ stateId: instance.id,
145
+ name: "ssh",
146
+ meta: { title: "SSH Terminal" },
147
+ spec: { image: "alpine", command: ["ssh"] },
148
+ status: "active",
149
+ },
150
+ {
151
+ stateId: instance.id,
152
+ name: "backup",
153
+ meta: { title: "Backup Terminal" },
154
+ spec: { image: "restic", command: ["restic"] },
155
+ status: "active",
156
+ },
157
+ ],
158
+ })
159
+
160
+ const unitTerminals: UnitTerminal[] = [
161
+ {
162
+ name: "ssh",
163
+ meta: {
164
+ title: "SSH Terminal",
165
+ icon: "mdi:terminal",
166
+ },
167
+ spec: {
168
+ image: "alpine:3.18",
169
+ command: ["ssh"],
170
+ },
171
+ },
172
+ ]
173
+
174
+ await db.$transaction(async tx => {
175
+ await service.processUnitTerminals(tx, instance.id, unitTerminals)
176
+ })
177
+
178
+ const terminals = await db.terminal.findMany({
179
+ where: { stateId: instance.id },
180
+ orderBy: { name: "asc" },
181
+ })
182
+
183
+ expect(terminals).toHaveLength(2)
184
+ expect(terminals[0].name).toBe("backup")
185
+ expect(terminals[0].status).toBe("unavailable")
186
+ expect(terminals[1].name).toBe("ssh")
187
+ expect(terminals[1].status).toBe("active")
188
+ })
189
+
190
+ test("should not affect already unavailable terminals", async ({
191
+ database,
192
+ project,
193
+ createInstanceState,
194
+ projectDatabase,
195
+ }) => {
196
+ const service = new UnitExtraService(database)
197
+ const instance = await createInstanceState(project.id)
198
+
199
+ await projectDatabase.terminal.create({
200
+ data: {
201
+ stateId: instance.id,
202
+ name: "old-terminal",
203
+ meta: { title: "Old Terminal" },
204
+ spec: { image: "ubuntu", command: ["bash"] },
205
+ status: "unavailable",
206
+ },
207
+ })
208
+
209
+ await projectDatabase.$transaction(async tx => {
210
+ await service.processUnitTerminals(tx, instance.id, [])
211
+ })
212
+
213
+ const terminals = await projectDatabase.terminal.findMany({ where: { stateId: instance.id } })
214
+
215
+ expect(terminals).toHaveLength(1)
216
+ expect(terminals[0].status).toBe("unavailable")
217
+ })
218
+ })
219
+
220
+ describe("processUnitPages", () => {
221
+ test("should create new pages", async ({
222
+ database,
223
+ project,
224
+ createInstanceState,
225
+ projectDatabase,
226
+ }) => {
227
+ const service = new UnitExtraService(database)
228
+ const instance = await createInstanceState(project.id)
229
+
230
+ const unitPages: UnitPage[] = [
231
+ {
232
+ name: "dashboard",
233
+ meta: {
234
+ title: "Kubernetes Dashboard",
235
+ description: "Access to the Kubernetes dashboard with login token",
236
+ icon: "simple-icons:kubernetes",
237
+ iconColor: "#326CE5",
238
+ },
239
+ content: [
240
+ {
241
+ type: "markdown",
242
+ content: `# Kubernetes Dashboard
243
+
244
+ The dashboard is ready at [https://k8s.example.com](https://k8s.example.com)
245
+
246
+ To login, use the following token:`,
247
+ },
248
+ {
249
+ type: "qr",
250
+ content: "eyJhbGciOiJSUzI1NiIsImtpZCI6InRva2VuIn0...",
251
+ showContent: true,
252
+ language: "text",
253
+ },
254
+ ],
255
+ },
256
+ ]
257
+
258
+ await projectDatabase.$transaction(async tx => {
259
+ await service.processUnitPages(tx, instance.id, unitPages)
260
+ })
261
+
262
+ const pages = await projectDatabase.page.findMany({ where: { stateId: instance.id } })
263
+
264
+ expect(pages).toHaveLength(1)
265
+ expect(pages[0].name).toBe("dashboard")
266
+ expect(pages[0].meta).toEqual({
267
+ title: "Kubernetes Dashboard",
268
+ description: "Access to the Kubernetes dashboard with login token",
269
+ icon: "simple-icons:kubernetes",
270
+ iconColor: "#326CE5",
271
+ })
272
+ expect(pages[0].content).toHaveLength(2)
273
+ expect(pages[0].content[0]).toMatchObject({
274
+ type: "markdown",
275
+ content: expect.stringContaining("Kubernetes Dashboard"),
276
+ })
277
+ })
278
+
279
+ test("should update existing pages", async ({
280
+ database,
281
+ project,
282
+ createInstanceState,
283
+ projectDatabase,
284
+ }) => {
285
+ const service = new UnitExtraService(database)
286
+ const instance = await createInstanceState(project.id)
287
+
288
+ const db = await database.forProject(project.id)
289
+ await db.page.create({
290
+ data: {
291
+ stateId: instance.id,
292
+ name: "dashboard",
293
+ meta: {
294
+ title: "Old Dashboard",
295
+ icon: "mdi:view-dashboard",
296
+ },
297
+ content: [
298
+ {
299
+ type: "markdown",
300
+ content: "Old content",
301
+ },
302
+ ],
303
+ },
304
+ })
305
+
306
+ const unitPages: UnitPage[] = [
307
+ {
308
+ name: "dashboard",
309
+ meta: {
310
+ title: "Updated Dashboard",
311
+ description: "Updated dashboard with new features",
312
+ icon: "mdi:monitor-dashboard",
313
+ },
314
+ content: [
315
+ {
316
+ type: "markdown",
317
+ content: "# Updated Dashboard\n\nNew features available!",
318
+ },
319
+ ],
320
+ },
321
+ ]
322
+
323
+ await projectDatabase.$transaction(async tx => {
324
+ await service.processUnitPages(tx, instance.id, unitPages)
325
+ })
326
+
327
+ const pages = await projectDatabase.page.findMany({ where: { stateId: instance.id } })
328
+
329
+ expect(pages).toHaveLength(1)
330
+ expect(pages[0].meta).toEqual({
331
+ title: "Updated Dashboard",
332
+ description: "Updated dashboard with new features",
333
+ icon: "mdi:monitor-dashboard",
334
+ })
335
+ expect(pages[0].content).toHaveLength(1)
336
+ })
337
+
338
+ test("should delete dangling pages", async ({
339
+ database,
340
+ project,
341
+ createInstanceState,
342
+ projectDatabase,
343
+ }) => {
344
+ const service = new UnitExtraService(database)
345
+ const instance = await createInstanceState(project.id)
346
+
347
+ const db = await database.forProject(project.id)
348
+ await db.page.createMany({
349
+ data: [
350
+ {
351
+ stateId: instance.id,
352
+ name: "dashboard",
353
+ meta: { title: "Dashboard" },
354
+ content: [],
355
+ },
356
+ {
357
+ stateId: instance.id,
358
+ name: "settings",
359
+ meta: { title: "Settings" },
360
+ content: [],
361
+ },
362
+ ],
363
+ })
364
+
365
+ const unitPages: UnitPage[] = [
366
+ {
367
+ name: "dashboard",
368
+ meta: {
369
+ title: "Dashboard",
370
+ },
371
+ content: [
372
+ {
373
+ type: "markdown",
374
+ content: "Dashboard content",
375
+ },
376
+ ],
377
+ },
378
+ ]
379
+
380
+ await projectDatabase.$transaction(async tx => {
381
+ await service.processUnitPages(tx, instance.id, unitPages)
382
+ })
383
+
384
+ const pages = await projectDatabase.page.findMany({ where: { stateId: instance.id } })
385
+
386
+ expect(pages).toHaveLength(1)
387
+ expect(pages[0].name).toBe("dashboard")
388
+ })
389
+ })
390
+
391
+ describe("processUnitTriggers", () => {
392
+ test("should create new triggers", async ({
393
+ database,
394
+ project,
395
+ createInstanceState,
396
+ projectDatabase,
397
+ }) => {
398
+ const service = new UnitExtraService(database)
399
+ const instance = await createInstanceState(project.id)
400
+
401
+ const unitTriggers: UnitTrigger[] = [
402
+ {
403
+ name: "cleanup",
404
+ meta: {
405
+ title: "Cleanup Before Destroy",
406
+ description: "Runs backup and cleanup operations before instance destruction",
407
+ icon: "mdi:backup-restore",
408
+ },
409
+ spec: {
410
+ type: "before-destroy",
411
+ },
412
+ },
413
+ ]
414
+
415
+ await projectDatabase.$transaction(async tx => {
416
+ await service.processUnitTriggers(tx, instance.id, unitTriggers)
417
+ })
418
+
419
+ const triggers = await projectDatabase.trigger.findMany({ where: { stateId: instance.id } })
420
+
421
+ expect(triggers).toHaveLength(1)
422
+ expect(triggers[0].name).toBe("cleanup")
423
+ expect(triggers[0].meta).toEqual({
424
+ title: "Cleanup Before Destroy",
425
+ description: "Runs backup and cleanup operations before instance destruction",
426
+ icon: "mdi:backup-restore",
427
+ })
428
+ expect(triggers[0].spec).toEqual({
429
+ type: "before-destroy",
430
+ })
431
+ })
432
+
433
+ test("should update existing triggers", async ({
434
+ database,
435
+ project,
436
+ createInstanceState,
437
+ projectDatabase,
438
+ }) => {
439
+ const service = new UnitExtraService(database)
440
+ const instance = await createInstanceState(project.id)
441
+
442
+ const db = await database.forProject(project.id)
443
+ await db.trigger.create({
444
+ data: {
445
+ stateId: instance.id,
446
+ name: "cleanup",
447
+ meta: {
448
+ title: "Old Cleanup",
449
+ icon: "mdi:delete",
450
+ },
451
+ spec: {
452
+ type: "before-destroy",
453
+ },
454
+ },
455
+ })
456
+
457
+ const unitTriggers: UnitTrigger[] = [
458
+ {
459
+ name: "cleanup",
460
+ meta: {
461
+ title: "Advanced Cleanup",
462
+ description: "Enhanced cleanup with backup verification",
463
+ icon: "mdi:backup-restore",
464
+ iconColor: "#FF9800",
465
+ },
466
+ spec: {
467
+ type: "before-destroy",
468
+ },
469
+ },
470
+ ]
471
+
472
+ await projectDatabase.$transaction(async tx => {
473
+ await service.processUnitTriggers(tx, instance.id, unitTriggers)
474
+ })
475
+
476
+ const triggers = await projectDatabase.trigger.findMany({ where: { stateId: instance.id } })
477
+
478
+ expect(triggers).toHaveLength(1)
479
+ expect(triggers[0].meta).toEqual({
480
+ title: "Advanced Cleanup",
481
+ description: "Enhanced cleanup with backup verification",
482
+ icon: "mdi:backup-restore",
483
+ iconColor: "#FF9800",
484
+ })
485
+ expect(triggers[0].spec).toEqual({
486
+ type: "before-destroy",
487
+ })
488
+ })
489
+
490
+ test("should delete dangling triggers", async ({
491
+ database,
492
+ project,
493
+ createInstanceState,
494
+ projectDatabase,
495
+ }) => {
496
+ const service = new UnitExtraService(database)
497
+ const instance = await createInstanceState(project.id)
498
+
499
+ const db = await database.forProject(project.id)
500
+ await db.trigger.createMany({
501
+ data: [
502
+ {
503
+ stateId: instance.id,
504
+ name: "cleanup",
505
+ meta: { title: "Cleanup" },
506
+ spec: { type: "before-destroy" },
507
+ },
508
+ {
509
+ stateId: instance.id,
510
+ name: "backup",
511
+ meta: { title: "Backup" },
512
+ spec: { type: "before-destroy" },
513
+ },
514
+ ],
515
+ })
516
+
517
+ const unitTriggers: UnitTrigger[] = [
518
+ {
519
+ name: "cleanup",
520
+ meta: {
521
+ title: "Cleanup Before Destroy",
522
+ },
523
+ spec: {
524
+ type: "before-destroy",
525
+ },
526
+ },
527
+ ]
528
+
529
+ await projectDatabase.$transaction(async tx => {
530
+ await service.processUnitTriggers(tx, instance.id, unitTriggers)
531
+ })
532
+
533
+ const triggers = await projectDatabase.trigger.findMany({ where: { stateId: instance.id } })
534
+
535
+ expect(triggers).toHaveLength(1)
536
+ expect(triggers[0].name).toBe("cleanup")
537
+ })
538
+ })
539
+ })