@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
@@ -0,0 +1,281 @@
1
+ import type { Logger } from "pino"
2
+ import type { LibraryBackend } from "../library"
3
+ import type { StateManager } from "../state"
4
+ import type { InstanceStateService } from "./instance-state"
5
+ import type { RandomProvider } from "../common"
6
+ import {
7
+ HighstateSignature,
8
+ isUnitModel,
9
+ parseInstanceId,
10
+ type UnitSecretModel,
11
+ } from "@highstate/contract"
12
+ import { isNonNullish } from "remeda"
13
+ import { formatSecretDescriptor, type Project, type Secret, type SecretDescriptor } from "../shared"
14
+
15
+ export class SecretService {
16
+ constructor(
17
+ private readonly stateManager: StateManager,
18
+ private readonly libraryBackend: LibraryBackend,
19
+ private readonly instanceStateService: InstanceStateService,
20
+ private readonly random: RandomProvider,
21
+ private readonly logger: Logger,
22
+ ) {}
23
+
24
+ /**
25
+ * Updates secrets for a specific instance, handling both creation/updates and deletions.
26
+ * Also updates the instance state's secretNames field.
27
+ *
28
+ * @param project The project to which the instance belongs.
29
+ * @param instanceId The instance ID.
30
+ * @param changedSecretValues The secrets to create or update.
31
+ * @param deletedSecretNames The names of secrets to delete.
32
+ * @param invalidateState Whether to invalidate the instance state after updating secrets.
33
+ */
34
+ async updateInstanceSecrets(
35
+ project: Project,
36
+ instanceId: string,
37
+ changedSecretValues: Record<string, unknown>,
38
+ deletedSecretNames: string[],
39
+ invalidateState = true,
40
+ ): Promise<void> {
41
+ const library = await this.libraryBackend.loadLibrary(project.libraryId)
42
+
43
+ const [type] = parseInstanceId(instanceId)
44
+ const component = library.components[type]
45
+ if (!component) {
46
+ throw new Error(`Component type ${type} not found in library`)
47
+ }
48
+
49
+ if (!isUnitModel(component)) {
50
+ throw new Error(`Component type ${type} is not a unit model`)
51
+ }
52
+
53
+ // validate changed secrets
54
+ for (const secretName of Object.keys(changedSecretValues)) {
55
+ if (!component.secrets[secretName]) {
56
+ throw new Error(`Secret ${secretName} not found in component ${type}`)
57
+ }
58
+ }
59
+
60
+ // validate deleted secrets
61
+ for (const secretName of deletedSecretNames) {
62
+ if (!component.secrets[secretName]) {
63
+ throw new Error(`Secret ${secretName} not found in component ${type}`)
64
+ }
65
+ }
66
+
67
+ // use a batch for atomic operations
68
+ await using batch = this.stateManager.batch()
69
+
70
+ const secretRepo = this.stateManager.getSecretRepository(project.id)
71
+ const secretContentRepo = this.stateManager.getSecretContentRepository(project.id)
72
+ const indexRepo = this.stateManager.getSecretIndexRepository(project.id)
73
+
74
+ // process changed secrets
75
+ for (const [secretName, value] of Object.entries(changedSecretValues)) {
76
+ const descriptor: SecretDescriptor = { type: "instance", instanceId, secretName }
77
+ const indexKey = formatSecretDescriptor(descriptor)
78
+
79
+ // check if secret already exists
80
+ const existingSecret = await indexRepo.get(indexKey)
81
+
82
+ // create or update secret info
83
+ const secret: Secret = {
84
+ id: existingSecret?.id ?? this.random.uuidv7(),
85
+ descriptor: descriptor,
86
+ meta: {
87
+ ...existingSecret?.meta,
88
+ ...component.secrets[secretName].meta,
89
+ icon: component.secrets[secretName].meta.icon ?? component.meta.icon,
90
+ },
91
+ }
92
+
93
+ // store secret info and content
94
+ await secretRepo.putItem(secret, batch)
95
+ await secretContentRepo.put(indexKey, value, batch)
96
+
97
+ // update index mapping
98
+ await indexRepo.indexRepository.put(indexKey, secret.id, batch)
99
+ }
100
+
101
+ // process deleted secrets
102
+ for (const secretName of deletedSecretNames) {
103
+ const indexKey = formatSecretDescriptor({ type: "instance", instanceId, secretName })
104
+
105
+ // get the secret ID from index
106
+ const secretId = await indexRepo.indexRepository.get(indexKey)
107
+ if (secretId) {
108
+ // delete secret info, content, and index entry
109
+ await secretRepo.delete(secretId, batch)
110
+ await secretContentRepo.delete(indexKey, batch)
111
+ await indexRepo.indexRepository.delete(indexKey, batch)
112
+ }
113
+ }
114
+
115
+ // write all changes atomically
116
+ await batch.write()
117
+
118
+ // update instance state secretNames
119
+ await this.instanceStateService.updateStateSecretNames(
120
+ project.id,
121
+ instanceId,
122
+ Object.keys(changedSecretValues),
123
+ deletedSecretNames,
124
+ invalidateState,
125
+ )
126
+
127
+ this.logger.info(
128
+ {
129
+ projectId: project.id,
130
+ instanceId,
131
+ changedCount: Object.keys(changedSecretValues).length,
132
+ deletedCount: deletedSecretNames.length,
133
+ },
134
+ `updated instance secrets for instance "%s"`,
135
+ instanceId,
136
+ )
137
+ }
138
+
139
+ /**
140
+ * Gets the values of all secrets for a specific instance.
141
+ *
142
+ * @param project The project to which the instance belongs.
143
+ * @param instanceId The instance ID.
144
+ * @returns A record of secret key-value pairs.
145
+ */
146
+ async getInstanceSecretValues(
147
+ project: Project,
148
+ instanceId: string,
149
+ ): Promise<Record<string, unknown>> {
150
+ const { indexKeys, secrets, secretContentMap } = await this.getInstanceSecretData(
151
+ project,
152
+ instanceId,
153
+ )
154
+
155
+ // build the result mapping secret names to values
156
+ const values: Record<string, unknown> = {}
157
+ for (const descriptor of indexKeys) {
158
+ const secret = secrets[descriptor]
159
+ if (!secret) {
160
+ continue
161
+ }
162
+
163
+ const content = secretContentMap[secret.id]
164
+ if (content) {
165
+ values[descriptor] = content
166
+ }
167
+ }
168
+
169
+ return secrets
170
+ }
171
+
172
+ /**
173
+ * Gets the secrets for a specific instance, including metadata.
174
+ * Will create missing secrets with empty values.
175
+ *
176
+ * @param project The project to which the instance belongs.
177
+ * @param instanceId The instance ID.
178
+ * @return A record of secret key-value pairs with metadata.
179
+ */
180
+ async getUnitSecrets(
181
+ project: Project,
182
+ instanceId: string,
183
+ ): Promise<Record<string, UnitSecretModel>> {
184
+ const { descriptors, component, secrets, secretContentMap } = await this.getInstanceSecretData(
185
+ project,
186
+ instanceId,
187
+ )
188
+
189
+ const missingDescriptors: SecretDescriptor[] = []
190
+ const result: Record<string, UnitSecretModel> = {}
191
+
192
+ for (const descriptor of descriptors) {
193
+ const secret = secrets[formatSecretDescriptor(descriptor)]
194
+
195
+ if (secret) {
196
+ result[descriptor.secretName] = {
197
+ [HighstateSignature.Secret]: true,
198
+ id: secret.id,
199
+ value: secretContentMap[secret.id],
200
+ }
201
+ } else {
202
+ missingDescriptors.push(descriptor)
203
+ }
204
+ }
205
+
206
+ const batch = this.stateManager.batch()
207
+
208
+ // create missing secrets with empty values
209
+ for (const descriptor of missingDescriptors) {
210
+ const secret: Secret = {
211
+ id: this.random.uuidv7(),
212
+ meta: {
213
+ ...component.secrets[descriptor.secretName].meta,
214
+ icon: component.secrets[descriptor.secretName].meta.icon ?? component.meta.icon,
215
+ },
216
+ descriptor,
217
+ }
218
+
219
+ const rawDescriptor = formatSecretDescriptor(descriptor)
220
+
221
+ await this.stateManager.getSecretRepository(project.id).putItem(secret, batch)
222
+ await this.stateManager.getSecretContentRepository(project.id).put(rawDescriptor, null, batch)
223
+
224
+ await this.stateManager
225
+ .getSecretIndexRepository(project.id)
226
+ .indexRepository.put(rawDescriptor, secret.id, batch)
227
+
228
+ result[descriptor.secretName] = {
229
+ [HighstateSignature.Secret]: true,
230
+ id: secret.id,
231
+ value: null,
232
+ }
233
+ }
234
+
235
+ await batch.write()
236
+
237
+ return result
238
+ }
239
+
240
+ private async getInstanceSecretData(project: Project, instanceId: string) {
241
+ const library = await this.libraryBackend.loadLibrary(project.libraryId)
242
+
243
+ const [type] = parseInstanceId(instanceId)
244
+ const component = library.components[type]
245
+ if (!component) {
246
+ throw new Error(`Component type ${type} not found in library`)
247
+ }
248
+
249
+ if (!isUnitModel(component)) {
250
+ throw new Error(`Component type ${type} is not a unit model`)
251
+ }
252
+
253
+ const descriptors = Object.keys(component.secrets).map(secretName => ({
254
+ type: "instance" as const,
255
+ instanceId,
256
+ secretName,
257
+ }))
258
+
259
+ const indexKeys = descriptors.map(formatSecretDescriptor)
260
+
261
+ const secrets = await this.stateManager
262
+ .getSecretIndexRepository(project.id)
263
+ .getManyRecord(indexKeys)
264
+
265
+ const secretIds = Object.values(secrets)
266
+ .map(secret => secret?.id)
267
+ .filter(isNonNullish)
268
+
269
+ const secretContentMap = await this.stateManager
270
+ .getSecretContentRepository(project.id)
271
+ .getManyRecord(secretIds)
272
+
273
+ return {
274
+ indexKeys,
275
+ descriptors,
276
+ component,
277
+ secrets,
278
+ secretContentMap,
279
+ }
280
+ }
281
+ }