@highstate/backend 0.9.15 → 0.9.18

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 (187) hide show
  1. package/dist/chunk-NAAIDR4U.js +8499 -0
  2. package/dist/chunk-NAAIDR4U.js.map +1 -0
  3. package/dist/chunk-OU5OQBLB.js +74 -0
  4. package/dist/chunk-OU5OQBLB.js.map +1 -0
  5. package/dist/chunk-Y7DXREVO.js +1745 -0
  6. package/dist/chunk-Y7DXREVO.js.map +1 -0
  7. package/dist/highstate.manifest.json +4 -4
  8. package/dist/index.js +7227 -2501
  9. package/dist/index.js.map +1 -1
  10. package/dist/library/package-resolution-worker.js +7 -5
  11. package/dist/library/package-resolution-worker.js.map +1 -1
  12. package/dist/library/worker/main.js +76 -185
  13. package/dist/library/worker/main.js.map +1 -1
  14. package/dist/magic-string.es-5ABAC4JN.js +1292 -0
  15. package/dist/magic-string.es-5ABAC4JN.js.map +1 -0
  16. package/dist/shared/index.js +3 -98
  17. package/dist/shared/index.js.map +1 -1
  18. package/package.json +31 -10
  19. package/src/artifact/abstractions.ts +46 -0
  20. package/src/artifact/encryption.ts +109 -0
  21. package/src/artifact/factory.ts +36 -0
  22. package/src/artifact/index.ts +3 -0
  23. package/src/artifact/local.ts +138 -0
  24. package/src/business/__traces__/secret/update-instance-secrets/create-and-delete-secrets-simultaneously.md +356 -0
  25. package/src/business/__traces__/secret/update-instance-secrets/create-new-secrets-for-instance.md +274 -0
  26. package/src/business/__traces__/secret/update-instance-secrets/delete-existing-secrets.md +223 -0
  27. package/src/business/__traces__/secret/update-instance-secrets/no-op-when-no-changes.md +147 -0
  28. package/src/business/__traces__/secret/update-instance-secrets/update-existing-secrets.md +280 -0
  29. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration-when-other-exists.md +360 -0
  30. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration.md +215 -0
  31. package/src/business/__traces__/worker/update-unit-registrations/create-multiple-workers-with-different-identities.md +427 -0
  32. package/src/business/__traces__/worker/update-unit-registrations/handle-nonexistent-registration-id-gracefully.md +217 -0
  33. package/src/business/__traces__/worker/update-unit-registrations/no-op-when-no-changes.md +132 -0
  34. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-changes.md +454 -0
  35. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-version-changes.md +426 -0
  36. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-with-same-identity-reuses-service-account.md +372 -0
  37. package/src/business/__traces__/worker/update-unit-registrations/remove-one-of-multiple-unit-registrations.md +383 -0
  38. package/src/business/__traces__/worker/update-unit-registrations/remove-unit-registration.md +245 -0
  39. package/src/business/__traces__/worker/update-unit-registrations/update-existing-unit-registration-when-params-change.md +174 -0
  40. package/src/business/__traces__/worker/update-unit-registrations/update-params-and-image-simultaneously.md +432 -0
  41. package/src/business/__traces__/worker/update-unit-registrations/worker-with-multiple-registrations-not-deleted-when-one-removed.md +220 -0
  42. package/src/business/api-key.ts +65 -0
  43. package/src/business/artifact.ts +289 -0
  44. package/src/business/backend-unlock.ts +10 -0
  45. package/src/business/index.ts +10 -0
  46. package/src/business/instance-lock.ts +125 -0
  47. package/src/business/instance-state.ts +434 -0
  48. package/src/business/operation.ts +251 -0
  49. package/src/business/project-unlock.ts +260 -0
  50. package/src/business/project.ts +299 -0
  51. package/src/business/secret.test.ts +178 -0
  52. package/src/business/secret.ts +281 -0
  53. package/src/business/worker.test.ts +614 -0
  54. package/src/business/worker.ts +398 -0
  55. package/src/common/clock.ts +18 -0
  56. package/src/common/index.ts +5 -1
  57. package/src/common/performance.ts +44 -0
  58. package/src/common/random.ts +68 -0
  59. package/src/common/test/index.ts +2 -0
  60. package/src/common/test/render.ts +98 -0
  61. package/src/common/test/tracer.ts +359 -0
  62. package/src/common/tree.ts +33 -0
  63. package/src/common/utils.ts +40 -1
  64. package/src/config.ts +19 -11
  65. package/src/hotstate/abstractions.ts +48 -0
  66. package/src/hotstate/factory.ts +17 -0
  67. package/src/{secret → hotstate}/index.ts +1 -0
  68. package/src/hotstate/manager.ts +192 -0
  69. package/src/hotstate/memory.ts +100 -0
  70. package/src/hotstate/validation.ts +100 -0
  71. package/src/index.ts +2 -1
  72. package/src/library/abstractions.ts +24 -28
  73. package/src/library/factory.ts +2 -2
  74. package/src/library/local.ts +91 -111
  75. package/src/library/worker/evaluator.ts +36 -73
  76. package/src/library/worker/loader.lite.ts +54 -0
  77. package/src/library/worker/main.ts +15 -66
  78. package/src/library/worker/protocol.ts +6 -33
  79. package/src/lock/abstractions.ts +6 -0
  80. package/src/lock/factory.ts +15 -0
  81. package/src/lock/index.ts +4 -0
  82. package/src/lock/manager.ts +97 -0
  83. package/src/lock/memory.ts +19 -0
  84. package/src/lock/test.ts +108 -0
  85. package/src/orchestrator/manager.ts +118 -90
  86. package/src/orchestrator/operation-workset.ts +181 -93
  87. package/src/orchestrator/operation.ts +1021 -283
  88. package/src/project/abstractions.ts +27 -38
  89. package/src/project/evaluation.ts +248 -0
  90. package/src/project/factory.ts +1 -1
  91. package/src/project/index.ts +1 -2
  92. package/src/project/local.ts +107 -103
  93. package/src/pubsub/abstractions.ts +13 -0
  94. package/src/pubsub/factory.ts +19 -0
  95. package/src/{workspace → pubsub}/index.ts +1 -0
  96. package/src/pubsub/local.ts +36 -0
  97. package/src/pubsub/manager.ts +108 -0
  98. package/src/pubsub/validation.ts +33 -0
  99. package/src/runner/abstractions.ts +155 -68
  100. package/src/runner/artifact-env.ts +160 -0
  101. package/src/runner/factory.ts +20 -5
  102. package/src/runner/force-abort.ts +117 -0
  103. package/src/runner/local.ts +292 -372
  104. package/src/{common → runner}/pulumi.ts +89 -37
  105. package/src/services.ts +251 -40
  106. package/src/shared/index.ts +3 -11
  107. package/src/shared/models/backend/index.ts +3 -0
  108. package/src/shared/{library.ts → models/backend/library.ts} +4 -4
  109. package/src/shared/models/backend/project.ts +82 -0
  110. package/src/shared/models/backend/unlock-method.ts +20 -0
  111. package/src/shared/models/base.ts +68 -0
  112. package/src/shared/models/errors.ts +5 -0
  113. package/src/shared/models/index.ts +4 -0
  114. package/src/shared/models/project/api-key.ts +65 -0
  115. package/src/shared/models/project/artifact.ts +83 -0
  116. package/src/shared/models/project/index.ts +13 -0
  117. package/src/shared/models/project/lock.ts +91 -0
  118. package/src/shared/models/project/model.ts +14 -0
  119. package/src/shared/{operation.ts → models/project/operation.ts} +29 -8
  120. package/src/shared/models/project/page.ts +57 -0
  121. package/src/shared/models/project/secret.ts +98 -0
  122. package/src/shared/models/project/service-account.ts +22 -0
  123. package/src/shared/models/project/state.ts +449 -0
  124. package/src/shared/models/project/terminal.ts +98 -0
  125. package/src/shared/models/project/trigger.ts +56 -0
  126. package/src/shared/models/project/unlock-method.ts +38 -0
  127. package/src/shared/models/project/worker.ts +107 -0
  128. package/src/shared/resolvers/graph-resolver.ts +61 -18
  129. package/src/shared/resolvers/index.ts +5 -0
  130. package/src/shared/resolvers/input-hash.ts +53 -15
  131. package/src/shared/resolvers/input.ts +47 -13
  132. package/src/shared/resolvers/registry.ts +3 -2
  133. package/src/shared/resolvers/state.ts +2 -2
  134. package/src/shared/resolvers/validation.ts +82 -25
  135. package/src/shared/utils/args.ts +25 -0
  136. package/src/shared/{async-batcher.ts → utils/async-batcher.ts} +13 -1
  137. package/src/shared/utils/hash.ts +6 -0
  138. package/src/shared/utils/index.ts +4 -0
  139. package/src/shared/utils/promise-tracker.ts +23 -0
  140. package/src/state/abstractions.ts +199 -131
  141. package/src/state/encryption.ts +98 -0
  142. package/src/state/factory.ts +3 -5
  143. package/src/state/index.ts +4 -0
  144. package/src/state/keyring.ts +22 -0
  145. package/src/state/local/backend.ts +106 -0
  146. package/src/state/local/collection.ts +361 -0
  147. package/src/state/local/index.ts +2 -0
  148. package/src/state/manager.ts +875 -18
  149. package/src/state/memory/backend.ts +70 -0
  150. package/src/state/memory/collection.ts +270 -0
  151. package/src/state/memory/index.ts +2 -0
  152. package/src/state/repository/index.ts +2 -0
  153. package/src/state/repository/repository.index.ts +193 -0
  154. package/src/state/repository/repository.ts +507 -0
  155. package/src/state/test.ts +457 -0
  156. package/src/terminal/{shared.ts → abstractions.ts} +3 -3
  157. package/src/terminal/docker.ts +18 -14
  158. package/src/terminal/factory.ts +3 -3
  159. package/src/terminal/index.ts +1 -1
  160. package/src/terminal/manager.ts +131 -79
  161. package/src/terminal/run.sh.ts +21 -11
  162. package/src/unlock/abstractions.ts +49 -0
  163. package/src/unlock/index.ts +2 -0
  164. package/src/unlock/memory.ts +32 -0
  165. package/src/worker/abstractions.ts +42 -0
  166. package/src/worker/docker.ts +83 -0
  167. package/src/worker/factory.ts +20 -0
  168. package/src/worker/index.ts +3 -0
  169. package/src/worker/manager.ts +167 -0
  170. package/dist/chunk-KTGKNSKM.js +0 -979
  171. package/dist/chunk-KTGKNSKM.js.map +0 -1
  172. package/dist/chunk-WXDYCRTT.js +0 -234
  173. package/dist/chunk-WXDYCRTT.js.map +0 -1
  174. package/src/library/worker/loader.ts +0 -114
  175. package/src/preferences/shared.ts +0 -1
  176. package/src/project/lock.ts +0 -39
  177. package/src/project/manager.ts +0 -433
  178. package/src/secret/abstractions.ts +0 -59
  179. package/src/secret/factory.ts +0 -22
  180. package/src/secret/local.ts +0 -152
  181. package/src/shared/project.ts +0 -62
  182. package/src/shared/state.ts +0 -247
  183. package/src/shared/terminal.ts +0 -14
  184. package/src/state/local.ts +0 -612
  185. package/src/workspace/abstractions.ts +0 -41
  186. package/src/workspace/factory.ts +0 -14
  187. package/src/workspace/local.ts +0 -54
@@ -1,7 +1,17 @@
1
- import type { ConfigMap, OpMap, OpType, Stack, WhoAmIResult } from "@pulumi/pulumi/automation"
2
1
  import type { Logger } from "pino"
2
+ import type { StateManager } from "../state"
3
+ import {
4
+ type ConfigMap,
5
+ type OpMap,
6
+ type OpType,
7
+ type Stack,
8
+ type WhoAmIResult,
9
+ } from "@pulumi/pulumi/automation/index.js"
3
10
  import { BetterLock } from "better-lock"
4
- import { AbortError, runWithRetryOnError } from "./utils"
11
+ import { v4 as uuidv4, v7 as uuidv7 } from "uuid"
12
+ import { formatSecretDescriptor, WellKnownSecretDescriptors, type Secret } from "../shared"
13
+ import { AbortError, errorToString, runWithRetryOnError } from "../common/utils"
14
+ import { createForceAbortableCommand } from "./force-abort"
5
15
 
6
16
  export type RunOptions = {
7
17
  projectId: string
@@ -18,7 +28,10 @@ export type RunLocalOptions = RunOptions & {
18
28
  export class LocalPulumiHost {
19
29
  private lock = new BetterLock()
20
30
 
21
- private constructor(private readonly logger: Logger) {}
31
+ private constructor(
32
+ private readonly stateManager: StateManager,
33
+ private readonly logger: Logger,
34
+ ) {}
22
35
 
23
36
  async getCurrentUser(): Promise<WhoAmIResult | null> {
24
37
  const { LocalWorkspace } = await import("@pulumi/pulumi/automation/index.js")
@@ -55,10 +68,11 @@ export class LocalPulumiHost {
55
68
  runtime: "nodejs",
56
69
  },
57
70
  envVars: {
58
- PULUMI_CONFIG_PASSPHRASE: this.getPassword(projectId),
71
+ PULUMI_CONFIG_PASSPHRASE: await this.getPassword(projectId),
59
72
  PULUMI_K8S_AWAIT_ALL: "true",
60
73
  ...envVars,
61
74
  },
75
+ pulumiCommand: await createForceAbortableCommand(),
62
76
  },
63
77
  )
64
78
 
@@ -90,6 +104,8 @@ export class LocalPulumiHost {
90
104
  return await this.lock.acquire(`${pulumiProjectName}.${pulumiStackName}`, async () => {
91
105
  const { LocalWorkspace } = await import("@pulumi/pulumi/automation/index.js")
92
106
 
107
+ console.log({ projectPath })
108
+
93
109
  const stack = await LocalWorkspace.createOrSelectStack(
94
110
  {
95
111
  stackName: pulumiStackName,
@@ -99,6 +115,7 @@ export class LocalPulumiHost {
99
115
  projectSettings: {
100
116
  name: pulumiProjectName,
101
117
  runtime: "nodejs",
118
+ main: "index.js",
102
119
  },
103
120
  stackSettings: stackConfig
104
121
  ? {
@@ -108,10 +125,11 @@ export class LocalPulumiHost {
108
125
  }
109
126
  : undefined,
110
127
  envVars: {
111
- PULUMI_CONFIG_PASSPHRASE: this.getPassword(projectId),
128
+ PULUMI_CONFIG_PASSPHRASE: await this.getPassword(projectId),
112
129
  PULUMI_K8S_AWAIT_ALL: "true",
113
130
  ...envVars,
114
131
  },
132
+ pulumiCommand: await createForceAbortableCommand(),
115
133
  },
116
134
  )
117
135
 
@@ -132,30 +150,50 @@ export class LocalPulumiHost {
132
150
  })
133
151
  }
134
152
 
135
- private sharedPassword: string = process.env.PULUMI_CONFIG_PASSPHRASE ?? ""
136
- private passwords = new Map<string, string>()
153
+ private async getPassword(projectId: string): Promise<string> {
154
+ return await this.lock.acquire(`pulumi-password.${projectId}`, async () => {
155
+ const secretRepo = this.stateManager.getSecretRepository(projectId)
156
+ const secretContentRepo = this.stateManager.getSecretContentRepository(projectId)
157
+ const indexRepo = this.stateManager.getSecretIndexRepository(projectId)
137
158
 
138
- hasPassword(projectId: string) {
139
- return !!this.sharedPassword || this.passwords.has(projectId)
140
- }
159
+ const contentKey = formatSecretDescriptor(WellKnownSecretDescriptors.PulumiPassword)
160
+ const pulumiPasswordContent = await secretContentRepo.get(contentKey)
161
+ if (typeof pulumiPasswordContent === "string") {
162
+ return pulumiPasswordContent
163
+ }
141
164
 
142
- setPassword(projectId: string, password: string) {
143
- this.passwords.set(projectId, password)
144
- }
165
+ this.logger.info({ projectId }, "generating new Pulumi password")
145
166
 
146
- removePassword(projectId: string) {
147
- this.passwords.delete(projectId)
148
- }
167
+ const pulumiPassword = uuidv4()
168
+ const batch = this.stateManager.batch()
169
+
170
+ const pulumiPasswordSecret: Secret = {
171
+ id: uuidv7(),
172
+ descriptor: WellKnownSecretDescriptors.PulumiPassword,
173
+ meta: {
174
+ title: "Pulumi Password",
175
+ description: "The password used to encrypt the Pulumi state.",
176
+ icon: "devicon:pulumi",
177
+ },
178
+ }
149
179
 
150
- private getPassword(projectId: string) {
151
- return this.sharedPassword || this.passwords.get(projectId) || ""
180
+ await Promise.all([
181
+ secretRepo.putItem(pulumiPasswordSecret, batch),
182
+ secretContentRepo.put(contentKey, pulumiPassword, batch),
183
+ indexRepo.indexRepository.put(contentKey, pulumiPasswordSecret.id, batch),
184
+ ])
185
+
186
+ await batch.write()
187
+
188
+ return pulumiPassword
189
+ })
152
190
  }
153
191
 
154
192
  async tryUnlockStack(stack: Stack, error: unknown) {
155
193
  if (error instanceof Error && error.message.includes("the stack is currently locked")) {
156
194
  // TODO: kill the process if the hostname matches the current hostname
157
195
 
158
- this.logger.warn({ stackName: stack.name }, "inlocking stack")
196
+ this.logger.warn({ stackName: stack.name }, "unlocking stack")
159
197
  await stack.cancel()
160
198
  return true
161
199
  }
@@ -163,24 +201,8 @@ export class LocalPulumiHost {
163
201
  return false
164
202
  }
165
203
 
166
- static create(logger: Logger) {
167
- return new LocalPulumiHost(logger.child({ service: "LocalPulumiHost" }))
168
- }
169
- }
170
-
171
- export function valueToString(value: unknown): string {
172
- if (typeof value === "string") {
173
- return value
174
- }
175
-
176
- return JSON.stringify(value)
177
- }
178
-
179
- export function stringToValue(value: string): unknown {
180
- try {
181
- return JSON.parse(value)
182
- } catch {
183
- return value
204
+ static create(stateManager: StateManager, logger: Logger) {
205
+ return new LocalPulumiHost(stateManager, logger.child({ service: "LocalPulumiHost" }))
184
206
  }
185
207
  }
186
208
 
@@ -228,3 +250,33 @@ export function calculateTotalResources(opMap: OpMap | undefined): number {
228
250
 
229
251
  return total
230
252
  }
253
+
254
+ export async function pulumiErrorToString(error: unknown): Promise<string> {
255
+ const { CommandError } = await import("@pulumi/pulumi/automation/index.js")
256
+
257
+ if (error instanceof CommandError) {
258
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
259
+ const stderr = error["commandResult"].stderr as string
260
+ let diagnosticsStart = stderr.indexOf("\u001b[38;5;13m\u001b[1mDiagnostics:")
261
+
262
+ if (diagnosticsStart === -1) {
263
+ diagnosticsStart = stderr.indexOf("Diagnostics:")
264
+ }
265
+
266
+ if (diagnosticsStart !== -1) {
267
+ return stderr.slice(diagnosticsStart)
268
+ }
269
+ }
270
+
271
+ return errorToString(error)
272
+ }
273
+
274
+ export function getOperationSummary(stdout: string): string | null {
275
+ const summaryStart = stdout.indexOf("\u001b[38;5;13m\u001b[1mOutputs:")
276
+
277
+ if (summaryStart !== -1) {
278
+ return stdout.slice(summaryStart)
279
+ }
280
+
281
+ return null
282
+ }
package/src/services.ts CHANGED
@@ -1,34 +1,77 @@
1
1
  import type { RunnerBackend } from "./runner"
2
2
  import { pino, type Logger } from "pino"
3
- import { createWorkspaceBackend, type WorkspaceBackend } from "./workspace"
3
+ import { MemoryProjectUnlockBackend, type ProjectUnlockBackend } from "./unlock"
4
+ import { createWorkerBackend, WorkerManager, type WorkerBackend } from "./worker"
5
+ import { createHotStateBackend, HotStateManager, type HotStateBackend } from "./hotstate"
6
+ import { createPubSubBackend, PubSubManager, type PubSubBackend } from "./pubsub"
7
+ import { createLockBackend, type LockBackend, LockManager } from "./lock"
4
8
  import { createStateBackend, StateManager, type StateBackend } from "./state"
5
9
  import { type Config, loadConfig } from "./config"
6
10
  import { type LibraryBackend, createLibraryBackend } from "./library"
7
11
  import { OperationManager } from "./orchestrator"
8
- import {
9
- type ProjectBackend,
10
- ProjectLockManager,
11
- ProjectManager,
12
- createProjectBackend,
13
- } from "./project"
12
+ import { type ProjectBackend, ProjectEvaluationSubsystem, createProjectBackend } from "./project"
14
13
  import { createRunnerBackend } from "./runner"
15
- import { type SecretBackend, createSecretBackend } from "./secret"
16
14
  import { type TerminalBackend, TerminalManager, createTerminalBackend } from "./terminal"
17
- import { LocalPulumiHost } from "./common"
15
+ import { type ArtifactBackend, createArtifactBackend, ArtifactService } from "./artifact"
16
+ import {
17
+ InstanceLockService,
18
+ OperationService,
19
+ SecretService,
20
+ ProjectUnlockService,
21
+ InstanceStateService,
22
+ ApiKeyService,
23
+ WorkerService,
24
+ ProjectService,
25
+ } from "./business"
26
+ import {
27
+ CryptoRandomProvider,
28
+ SystemClockProvider,
29
+ type ClockProvider,
30
+ type RandomProvider,
31
+ } from "./common"
18
32
 
19
33
  export type Services = {
34
+ readonly clock: ClockProvider
35
+ readonly random: RandomProvider
20
36
  readonly logger: Logger
37
+
38
+ readonly projectUnlockBackend: ProjectUnlockBackend
39
+
40
+ readonly hotStateBackend: HotStateBackend
41
+ readonly hotStateManager: HotStateManager
42
+
43
+ readonly pubsubBackend: PubSubBackend
44
+ readonly pubsubManager: PubSubManager
45
+
46
+ readonly lockBackend: LockBackend
47
+ readonly lockManager: LockManager
48
+
21
49
  readonly libraryBackend: LibraryBackend
22
- readonly secretBackend: SecretBackend
23
50
  readonly runnerBackend: RunnerBackend
24
51
  readonly projectBackend: ProjectBackend
25
- readonly projectManager: ProjectManager
52
+ readonly projectEvaluationSubsystem: ProjectEvaluationSubsystem
26
53
  readonly stateBackend: StateBackend
27
54
  readonly stateManager: StateManager
28
55
  readonly operationManager: OperationManager
56
+
29
57
  readonly terminalBackend: TerminalBackend
30
58
  readonly terminalManager: TerminalManager
31
- readonly workspaceBackend: WorkspaceBackend
59
+
60
+ readonly workerBackend: WorkerBackend
61
+ readonly workerManager: WorkerManager
62
+
63
+ readonly artifactBackend: ArtifactBackend
64
+ readonly artifactManager: ArtifactService
65
+
66
+ // business services
67
+ readonly instanceLockService: InstanceLockService
68
+ readonly projectUnlockService: ProjectUnlockService
69
+ readonly operationService: OperationService
70
+ readonly instanceStateService: InstanceStateService
71
+ readonly secretService: SecretService
72
+ readonly apiKeyService: ApiKeyService
73
+ readonly workerService: WorkerService
74
+ readonly projectService: ProjectService
32
75
  }
33
76
 
34
77
  export interface CreateServicesOptions {
@@ -46,79 +89,247 @@ export interface CreateServicesOptions {
46
89
  export async function createServices({
47
90
  config,
48
91
  services: {
92
+ clock,
93
+ random,
49
94
  logger,
95
+
96
+ projectUnlockBackend,
97
+
98
+ hotStateBackend,
99
+ hotStateManager,
100
+
101
+ pubsubBackend,
102
+ pubsubManager,
103
+
104
+ lockBackend,
105
+ lockManager,
106
+
50
107
  libraryBackend,
51
- secretBackend,
52
108
  runnerBackend,
53
109
  projectBackend,
54
- projectManager,
110
+ projectEvaluationSubsystem,
55
111
  stateBackend,
56
112
  stateManager,
57
113
  operationManager,
114
+
58
115
  terminalBackend,
59
116
  terminalManager,
60
- workspaceBackend,
117
+
118
+ workerBackend,
119
+ workerManager,
120
+
121
+ artifactBackend,
122
+ artifactManager,
123
+
124
+ // business services
125
+ instanceLockService,
126
+ projectUnlockService,
127
+ operationService,
128
+ secretService,
129
+ instanceStateService,
130
+ apiKeyService,
131
+ workerService,
132
+ projectService,
61
133
  } = {},
62
134
  }: CreateServicesOptions = {}): Promise<Services> {
63
135
  config ??= await loadConfig()
64
136
 
65
- logger ??= pino({
66
- name: config.HIGHSTATE_BACKEND_LOGGER_NAME,
67
- level: config.HIGHSTATE_BACKEND_LOGGER_LEVEL,
68
- errorKey: "error",
69
- })
137
+ clock ??= new SystemClockProvider()
138
+ random ??= new CryptoRandomProvider()
139
+ logger ??= pino({ level: config.HIGHSTATE_LOG_LEVEL, errorKey: "error" })
70
140
 
71
- const localPulumiHost = LocalPulumiHost.create(logger)
72
- const projectLockManager = new ProjectLockManager()
141
+ projectUnlockBackend ??= new MemoryProjectUnlockBackend()
73
142
 
74
- stateManager ??= new StateManager()
143
+ pubsubBackend ??= createPubSubBackend(config, logger)
144
+ pubsubManager ??= new PubSubManager(pubsubBackend, logger)
145
+
146
+ lockBackend ??= createLockBackend(config)
147
+ lockManager ??= new LockManager(lockBackend)
148
+
149
+ hotStateBackend ??= createHotStateBackend(config)
150
+ hotStateManager ??= new HotStateManager(hotStateBackend)
151
+
152
+ stateBackend ??= await createStateBackend(config, logger)
153
+ stateManager ??= await StateManager.create(
154
+ config,
155
+ stateBackend,
156
+ projectUnlockBackend,
157
+ clock,
158
+ random,
159
+ logger,
160
+ )
75
161
 
76
162
  libraryBackend ??= await createLibraryBackend(config, logger)
77
- secretBackend ??= await createSecretBackend(config, localPulumiHost, logger)
78
- stateBackend ??= await createStateBackend(config, localPulumiHost, logger)
79
- runnerBackend ??= createRunnerBackend(config, localPulumiHost, libraryBackend)
163
+
164
+ artifactBackend ??= await createArtifactBackend(config, stateManager, logger)
165
+ artifactManager ??= new ArtifactService(stateManager, artifactBackend, lockManager, logger)
166
+
167
+ runnerBackend ??= createRunnerBackend(
168
+ config,
169
+ libraryBackend,
170
+ artifactManager,
171
+ artifactBackend,
172
+ stateManager,
173
+ logger,
174
+ )
175
+
80
176
  projectBackend ??= await createProjectBackend(config)
81
177
 
82
- terminalBackend ??= createTerminalBackend(config, logger)
83
- terminalManager ??= TerminalManager.create(terminalBackend, stateBackend, runnerBackend, logger)
178
+ instanceLockService ??= new InstanceLockService(
179
+ stateManager,
180
+ lockManager,
181
+ pubsubManager,
182
+ logger.child({ service: "InstanceLockService" }),
183
+ )
184
+
185
+ projectUnlockService ??= new ProjectUnlockService(
186
+ stateManager,
187
+ hotStateManager,
188
+ pubsubManager,
189
+ projectUnlockBackend,
190
+ random,
191
+ logger.child({ service: "StateUnlockService" }),
192
+ )
193
+
194
+ operationService ??= new OperationService(
195
+ stateManager,
196
+ hotStateManager,
197
+ pubsubManager,
198
+ logger.child({ service: "OperationService" }),
199
+ )
84
200
 
85
- operationManager ??= await OperationManager.create(
201
+ instanceStateService ??= new InstanceStateService(
202
+ stateManager,
203
+ hotStateManager,
204
+ pubsubManager,
205
+ lockManager,
86
206
  runnerBackend,
87
- stateBackend,
207
+ artifactManager,
208
+ logger.child({ service: "InstanceService" }),
209
+ )
210
+
211
+ secretService ??= new SecretService(
212
+ stateManager,
88
213
  libraryBackend,
214
+ instanceStateService,
215
+ random,
216
+ logger.child({ service: "SecretService" }),
217
+ )
218
+
219
+ apiKeyService ??= new ApiKeyService(stateManager, lockManager)
220
+
221
+ projectEvaluationSubsystem ??= new ProjectEvaluationSubsystem(
89
222
  projectBackend,
90
- secretBackend,
91
- projectLockManager,
223
+ libraryBackend,
224
+ stateManager,
225
+ instanceLockService,
226
+ instanceStateService,
227
+ pubsubManager,
228
+ lockManager,
229
+ random,
230
+ logger,
231
+ )
232
+
233
+ terminalBackend ??= createTerminalBackend(config, logger)
234
+ terminalManager ??= TerminalManager.create(
235
+ terminalBackend,
236
+ stateManager,
237
+ pubsubManager,
238
+ projectUnlockService,
239
+ logger,
240
+ )
241
+
242
+ workerBackend ??= createWorkerBackend(config, logger)
243
+ workerManager ??= new WorkerManager(
244
+ config,
245
+ workerBackend,
92
246
  stateManager,
247
+ projectUnlockService,
248
+ lockManager,
249
+ apiKeyService,
93
250
  logger,
94
251
  )
95
252
 
96
- projectManager ??= ProjectManager.create(
253
+ workerService ??= new WorkerService(
254
+ stateManager,
255
+ workerManager,
256
+ lockManager,
257
+ random,
258
+ logger.child({ service: "WorkerService" }),
259
+ )
260
+
261
+ projectService ??= new ProjectService(
262
+ stateManager,
263
+ projectUnlockService,
264
+ projectEvaluationSubsystem,
97
265
  projectBackend,
98
- stateBackend,
99
266
  libraryBackend,
100
- projectLockManager,
267
+ lockManager,
268
+ pubsubManager,
269
+ random,
270
+ logger.child({ service: "ProjectService" }),
271
+ )
272
+
273
+ operationManager ??= new OperationManager(
274
+ runnerBackend,
275
+ libraryBackend,
276
+ projectBackend,
277
+ artifactManager,
101
278
  stateManager,
279
+ instanceLockService,
280
+ projectUnlockService,
281
+ operationService,
282
+ secretService,
283
+ instanceStateService,
284
+ pubsubManager,
285
+ workerService,
102
286
  logger,
103
287
  )
104
288
 
105
- workspaceBackend ??= await createWorkspaceBackend(config)
106
-
107
289
  logger.info("services created")
108
290
 
109
291
  return {
292
+ clock,
293
+ random,
110
294
  logger,
295
+
296
+ projectUnlockBackend,
297
+
298
+ hotStateBackend,
299
+ hotStateManager,
300
+
301
+ pubsubBackend,
302
+ pubsubManager,
303
+
304
+ lockBackend,
305
+ lockManager,
306
+
111
307
  libraryBackend,
112
- secretBackend,
113
308
  runnerBackend,
114
309
  projectBackend,
115
- projectManager,
310
+ projectEvaluationSubsystem,
116
311
  stateBackend,
117
312
  stateManager,
118
313
  operationManager,
314
+
119
315
  terminalBackend,
120
316
  terminalManager,
121
- workspaceBackend,
317
+
318
+ workerBackend,
319
+ workerManager,
320
+
321
+ artifactBackend,
322
+ artifactManager,
323
+
324
+ // business services
325
+ instanceLockService,
326
+ projectUnlockService,
327
+ operationService,
328
+ instanceStateService,
329
+ secretService,
330
+ apiKeyService,
331
+ workerService,
332
+ projectService,
122
333
  }
123
334
  }
124
335
 
@@ -1,11 +1,3 @@
1
- export * from "./project"
2
- export * from "./state"
3
- export * from "./operation"
4
- export * from "./resolvers/graph-resolver"
5
- export * from "./resolvers/registry"
6
- export * from "./resolvers/input"
7
- export * from "./resolvers/input-hash"
8
- export * from "./resolvers/validation"
9
- export * from "./terminal"
10
- export * from "./library"
11
- export * from "./async-batcher"
1
+ export * from "./models"
2
+ export * from "./resolvers"
3
+ export * from "./utils"
@@ -0,0 +1,3 @@
1
+ export * from "./project"
2
+ export * from "./library"
3
+ export * from "./unlock-method"
@@ -1,8 +1,8 @@
1
- import type { ComponentModel, Entity } from "@highstate/contract"
1
+ import type { ComponentModel, EntityModel } from "@highstate/contract"
2
2
 
3
3
  export type LibraryModel = {
4
4
  components: Record<string, ComponentModel>
5
- entities: Record<string, Entity>
5
+ entities: Record<string, EntityModel>
6
6
  }
7
7
 
8
8
  export type LibraryUpdate =
@@ -15,7 +15,7 @@ export type LibraryUpdate =
15
15
  }
16
16
  | {
17
17
  type: "entity-updated"
18
- entity: Entity
18
+ entity: EntityModel
19
19
  }
20
20
  | {
21
21
  type: "component-removed"
@@ -60,7 +60,7 @@ export function diffLibraries(oldLibrary: LibraryModel, newLibrary: LibraryModel
60
60
 
61
61
  export function applyLibraryUpdate(
62
62
  components: Record<string, ComponentModel>,
63
- entities: Record<string, Entity>,
63
+ entities: Record<string, EntityModel>,
64
64
  update: LibraryUpdate,
65
65
  ): void {
66
66
  switch (update.type) {
@@ -0,0 +1,82 @@
1
+ import { z } from "zod"
2
+ import { hubModelSchema, instanceModelSchema, objectMetaSchema } from "@highstate/contract"
3
+
4
+ export const projectSchema = z.object({
5
+ /**
6
+ * The identifier of the project unique across the world.
7
+ */
8
+ id: z.uuidv7(),
9
+
10
+ /**
11
+ * The metadata of the project.
12
+ */
13
+ meta: objectMetaSchema
14
+ .required({ title: true })
15
+ .pick({ title: true, description: true, icon: true, iconColor: true }),
16
+
17
+ /**
18
+ * The user-provided name of the project unique across its scope.
19
+ */
20
+ name: z
21
+ .string()
22
+ .min(1)
23
+ .max(64)
24
+ .regex(/^[a-z][a-z0-9-.]*$/),
25
+
26
+ /**
27
+ * The ID of the project scope (e.g., organization or user or something else not decided yet).
28
+ *
29
+ * For local deployments, this is `undefined`.
30
+ */
31
+ scopeId: z.uuidv7().optional(),
32
+
33
+ /**
34
+ * The identifier of the library used by the project.
35
+ *
36
+ * For local deployment is `undefined`, for cloud deployment will be the library ID.
37
+ */
38
+ libraryId: z.uuidv7().optional(),
39
+ })
40
+
41
+ export type Project = z.infer<typeof projectSchema>
42
+
43
+ export const projectModelSchema = z.object({
44
+ instances: z.array(instanceModelSchema),
45
+ hubs: z.array(hubModelSchema),
46
+ })
47
+
48
+ export type ProjectModel = z.infer<typeof projectModelSchema>
49
+
50
+ /**
51
+ * The project unlock suite is something that the frontend
52
+ * needs to unlock the project and provide the backend
53
+ * with the necessary information to decrypt the master key.
54
+ */
55
+ export const projectUnlockSuiteSchema = z.object({
56
+ /**
57
+ * The list of encrypted AGE identities that can be used to decrypt the master key of the project.
58
+ *
59
+ * The frontend should try to decrypt at least one of these identities
60
+ * using the password or passkey.
61
+ */
62
+ encryptedIdentities: z.array(z.string()),
63
+
64
+ /**
65
+ * Whether one of the identities is a passkey and user should be asked to use it.
66
+ */
67
+ hasPasskey: z.boolean(),
68
+ })
69
+
70
+ export type ProjectUnlockSuite = z.infer<typeof projectUnlockSuiteSchema>
71
+
72
+ export const projectUnlockStateSchema = z.discriminatedUnion("type", [
73
+ z.object({
74
+ type: z.literal("locked"),
75
+ unlockSuite: projectUnlockSuiteSchema.optional(),
76
+ }),
77
+ z.object({
78
+ type: z.literal("unlocked"),
79
+ }),
80
+ ])
81
+
82
+ export type ProjectUnlockState = z.infer<typeof projectUnlockStateSchema>