@highstate/backend 0.7.2 → 0.7.3
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.
- package/dist/{index.mjs → index.js} +1254 -915
- package/dist/library/source-resolution-worker.js +55 -0
- package/dist/library/worker/main.js +207 -0
- package/dist/{terminal-CqIsctlZ.mjs → library-BW5oPM7V.js} +210 -87
- package/dist/shared/index.js +6 -0
- package/dist/utils-ByadNcv4.js +102 -0
- package/package.json +14 -18
- package/src/common/index.ts +3 -0
- package/src/common/local.ts +22 -0
- package/src/common/pulumi.ts +230 -0
- package/src/common/utils.ts +137 -0
- package/src/config.ts +40 -0
- package/src/index.ts +6 -0
- package/src/library/abstractions.ts +83 -0
- package/src/library/factory.ts +20 -0
- package/src/library/index.ts +2 -0
- package/src/library/local.ts +404 -0
- package/src/library/source-resolution-worker.ts +96 -0
- package/src/library/worker/evaluator.ts +119 -0
- package/src/library/worker/loader.ts +93 -0
- package/src/library/worker/main.ts +82 -0
- package/src/library/worker/protocol.ts +38 -0
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/manager.ts +165 -0
- package/src/orchestrator/operation-workset.ts +483 -0
- package/src/orchestrator/operation.ts +647 -0
- package/src/preferences/shared.ts +1 -0
- package/src/project/abstractions.ts +89 -0
- package/src/project/factory.ts +11 -0
- package/src/project/index.ts +4 -0
- package/src/project/local.ts +412 -0
- package/src/project/lock.ts +39 -0
- package/src/project/manager.ts +374 -0
- package/src/runner/abstractions.ts +146 -0
- package/src/runner/factory.ts +22 -0
- package/src/runner/index.ts +2 -0
- package/src/runner/local.ts +698 -0
- package/src/secret/abstractions.ts +59 -0
- package/src/secret/factory.ts +22 -0
- package/src/secret/index.ts +2 -0
- package/src/secret/local.ts +152 -0
- package/src/services.ts +133 -0
- package/src/shared/index.ts +10 -0
- package/src/shared/library.ts +77 -0
- package/src/shared/operation.ts +85 -0
- package/src/shared/project.ts +62 -0
- package/src/shared/resolvers/graph-resolver.ts +111 -0
- package/src/shared/resolvers/input-hash.ts +77 -0
- package/src/shared/resolvers/input.ts +314 -0
- package/src/shared/resolvers/registry.ts +10 -0
- package/src/shared/resolvers/validation.ts +94 -0
- package/src/shared/state.ts +262 -0
- package/src/shared/terminal.ts +13 -0
- package/src/state/abstractions.ts +222 -0
- package/src/state/factory.ts +22 -0
- package/src/state/index.ts +3 -0
- package/src/state/local.ts +605 -0
- package/src/state/manager.ts +33 -0
- package/src/terminal/docker.ts +90 -0
- package/src/terminal/factory.ts +20 -0
- package/src/terminal/index.ts +3 -0
- package/src/terminal/manager.ts +330 -0
- package/src/terminal/run.sh.ts +37 -0
- package/src/terminal/shared.ts +50 -0
- package/src/workspace/abstractions.ts +41 -0
- package/src/workspace/factory.ts +14 -0
- package/src/workspace/index.ts +2 -0
- package/src/workspace/local.ts +54 -0
- package/dist/index.d.ts +0 -760
- package/dist/library/worker/main.mjs +0 -164
- package/dist/runner/source-resolution-worker.mjs +0 -22
- package/dist/shared/index.d.ts +0 -85
- package/dist/shared/index.mjs +0 -54
- package/dist/terminal-Cm2WqcyB.d.ts +0 -1589
@@ -0,0 +1,89 @@
|
|
1
|
+
import type { InstanceModel } from "@highstate/contract"
|
2
|
+
import type { HubModel, HubModelPatch, InstanceModelPatch } from "../shared"
|
3
|
+
|
4
|
+
export type ProjectModel = {
|
5
|
+
instances: InstanceModel[]
|
6
|
+
hubs: HubModel[]
|
7
|
+
}
|
8
|
+
|
9
|
+
export interface ProjectBackend {
|
10
|
+
/**
|
11
|
+
* List the ids of the available projects.
|
12
|
+
*/
|
13
|
+
getProjectIds(): Promise<string[]>
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Get the instances and hubs of the project.
|
17
|
+
*
|
18
|
+
* @param projectId The ID of the project.
|
19
|
+
*/
|
20
|
+
getProject(projectId: string, signal?: AbortSignal): Promise<ProjectModel>
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Create an empty project.
|
24
|
+
*
|
25
|
+
* @param projectId The ID of the project.
|
26
|
+
*/
|
27
|
+
createProject(projectId: string): Promise<void>
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Create the instance of the project.
|
31
|
+
*
|
32
|
+
* @param projectId The ID of the project.
|
33
|
+
* @param instance The instance to update.
|
34
|
+
*/
|
35
|
+
createInstance(projectId: string, instance: InstanceModel): Promise<InstanceModel>
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Rename the instance of the project.
|
39
|
+
* Changes its id and updates all references to the instance.
|
40
|
+
* Potentially dangerous, but safe when the instance is not yet created.
|
41
|
+
*/
|
42
|
+
renameInstance(projectId: string, instanceId: string, newName: string): Promise<InstanceModel>
|
43
|
+
|
44
|
+
/**
|
45
|
+
* Patches the instance of the project.
|
46
|
+
*
|
47
|
+
* @param projectId The ID of the project.
|
48
|
+
* @param instanceId The ID of the instance to update.
|
49
|
+
* @param patch The patch to apply to the instance.
|
50
|
+
*/
|
51
|
+
updateInstance(
|
52
|
+
projectId: string,
|
53
|
+
instanceId: string,
|
54
|
+
patch: InstanceModelPatch,
|
55
|
+
): Promise<InstanceModel>
|
56
|
+
|
57
|
+
/**
|
58
|
+
* Delete the instance of the project.
|
59
|
+
*
|
60
|
+
* @param projectId The ID of the project.
|
61
|
+
* @param instanceId The ID of the instance to delete.
|
62
|
+
*/
|
63
|
+
deleteInstance(projectId: string, instanceId: string): Promise<void>
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Create a hub in the project.
|
67
|
+
*
|
68
|
+
* @param projectId The ID of the project.
|
69
|
+
* @param hub The hub to create.
|
70
|
+
*/
|
71
|
+
createHub(projectId: string, hub: HubModel): Promise<HubModel>
|
72
|
+
|
73
|
+
/**
|
74
|
+
* Patches the hub of the project.
|
75
|
+
*
|
76
|
+
* @param projectId The ID of the project.
|
77
|
+
* @param hubId The ID of the hub to update.
|
78
|
+
* @param patch The patch to apply to the hub.
|
79
|
+
*/
|
80
|
+
updateHub(projectId: string, hubId: string, patch: HubModelPatch): Promise<HubModel>
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Delete the hub of the project.
|
84
|
+
*
|
85
|
+
* @param projectId The ID of the project.
|
86
|
+
* @param hubId The ID of the hub to delete.
|
87
|
+
*/
|
88
|
+
deleteHub(projectId: string, hubId: string): Promise<void>
|
89
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { z } from "zod"
|
2
|
+
import { localProjectBackendConfig, LocalProjectBackend } from "./local"
|
3
|
+
|
4
|
+
export const projectBackendConfig = z.object({
|
5
|
+
HIGHSTATE_BACKEND_PROJECT_TYPE: z.enum(["local"]).default("local"),
|
6
|
+
...localProjectBackendConfig.shape,
|
7
|
+
})
|
8
|
+
|
9
|
+
export function createProjectBackend(config: z.infer<typeof projectBackendConfig>) {
|
10
|
+
return LocalProjectBackend.create(config)
|
11
|
+
}
|
@@ -0,0 +1,412 @@
|
|
1
|
+
import type { ProjectBackend, ProjectModel } from "./abstractions"
|
2
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises"
|
3
|
+
import { resolve } from "node:path"
|
4
|
+
import { z } from "zod"
|
5
|
+
import {
|
6
|
+
getInstanceId,
|
7
|
+
type HubInput,
|
8
|
+
type InstanceInput,
|
9
|
+
type InstanceModel,
|
10
|
+
} from "@highstate/contract"
|
11
|
+
import {
|
12
|
+
type HubModel,
|
13
|
+
type HubModelPatch,
|
14
|
+
hubModelSchema,
|
15
|
+
type InstanceModelPatch,
|
16
|
+
instanceModelSchema,
|
17
|
+
} from "../shared"
|
18
|
+
import { resolveMainLocalProject } from "../common"
|
19
|
+
|
20
|
+
export const localProjectBackendConfig = z.object({
|
21
|
+
HIGHSTATE_BACKEND_PROJECT_PROJECTS_DIR: z.string().optional(),
|
22
|
+
})
|
23
|
+
|
24
|
+
const projectModelSchema = z.object({
|
25
|
+
instances: z.record(instanceModelSchema),
|
26
|
+
hubs: z.record(hubModelSchema),
|
27
|
+
})
|
28
|
+
|
29
|
+
/**
|
30
|
+
* A project backend that stores the projects locally on disk.
|
31
|
+
*
|
32
|
+
* By default, the projects are stored in the `projects` directory near the "package.json" file.
|
33
|
+
*/
|
34
|
+
export class LocalProjectBackend implements ProjectBackend {
|
35
|
+
constructor(private readonly projectsDir: string) {}
|
36
|
+
|
37
|
+
async getProjectIds(): Promise<string[]> {
|
38
|
+
try {
|
39
|
+
const files = await readdir(this.projectsDir)
|
40
|
+
|
41
|
+
return files.filter(file => file.endsWith(".json")).map(file => file.replace(/\.json$/, ""))
|
42
|
+
} catch (error) {
|
43
|
+
throw new Error("Failed to get project names", { cause: error })
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
async getProject(projectId: string): Promise<ProjectModel> {
|
48
|
+
try {
|
49
|
+
const project = await this.loadProject(projectId)
|
50
|
+
|
51
|
+
return {
|
52
|
+
instances: Object.values(project.instances),
|
53
|
+
hubs: Object.values(project.hubs),
|
54
|
+
}
|
55
|
+
} catch (error) {
|
56
|
+
throw new Error("Failed to get project instances", { cause: error })
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
async createProject(projectId: string): Promise<void> {
|
61
|
+
try {
|
62
|
+
await this.withProject(projectId, () => {
|
63
|
+
// Create an empty project
|
64
|
+
return { instances: {}, hubs: {} }
|
65
|
+
})
|
66
|
+
} catch (error) {
|
67
|
+
throw new Error("Failed to create project", { cause: error })
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
async createInstance(projectId: string, instance: InstanceModel): Promise<InstanceModel> {
|
72
|
+
try {
|
73
|
+
return await this.withProject(projectId, project => {
|
74
|
+
if (project.instances[instance.id]) {
|
75
|
+
throw new Error(`Instance ${instance.id} already exists`)
|
76
|
+
}
|
77
|
+
|
78
|
+
project.instances[instance.id] = instance
|
79
|
+
return instance
|
80
|
+
})
|
81
|
+
} catch (error) {
|
82
|
+
throw new Error("Failed to create project instance", { cause: error })
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
async updateInstance(
|
87
|
+
projectId: string,
|
88
|
+
instanceId: string,
|
89
|
+
patch: InstanceModelPatch,
|
90
|
+
): Promise<InstanceModel> {
|
91
|
+
try {
|
92
|
+
return await this.withInstance(projectId, instanceId, instance => {
|
93
|
+
if (patch.args) {
|
94
|
+
instance.args = patch.args
|
95
|
+
}
|
96
|
+
|
97
|
+
if (patch.position) {
|
98
|
+
instance.position = patch.position
|
99
|
+
}
|
100
|
+
|
101
|
+
if (patch.inputs) {
|
102
|
+
if (Object.keys(patch.inputs).length > 0) {
|
103
|
+
instance.inputs = patch.inputs
|
104
|
+
} else {
|
105
|
+
delete instance.inputs
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
if (patch.hubInputs) {
|
110
|
+
if (Object.keys(patch.hubInputs).length > 0) {
|
111
|
+
instance.hubInputs = patch.hubInputs
|
112
|
+
} else {
|
113
|
+
delete instance.hubInputs
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
if (patch.injectionInputs) {
|
118
|
+
if (patch.injectionInputs.length > 0) {
|
119
|
+
instance.injectionInputs = patch.injectionInputs
|
120
|
+
} else {
|
121
|
+
delete instance.injectionInputs
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
return instance
|
126
|
+
})
|
127
|
+
} catch (error) {
|
128
|
+
throw new Error("Failed to update project instance", { cause: error })
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
async deleteInstance(projectId: string, instanceId: string): Promise<void> {
|
133
|
+
try {
|
134
|
+
await this.withProject(projectId, project => {
|
135
|
+
if (!project.instances[instanceId]) {
|
136
|
+
throw new Error(`Instance ${instanceId} not found`)
|
137
|
+
}
|
138
|
+
|
139
|
+
delete project.instances[instanceId]
|
140
|
+
|
141
|
+
// Delete all inputs of instances that reference deleted instance
|
142
|
+
for (const otherInstance of Object.values(project.instances)) {
|
143
|
+
if (!otherInstance.inputs) {
|
144
|
+
continue
|
145
|
+
}
|
146
|
+
|
147
|
+
this.deleteInstanceReferences(otherInstance.inputs, instanceId)
|
148
|
+
|
149
|
+
if (Object.keys(otherInstance.inputs).length === 0) {
|
150
|
+
delete otherInstance.inputs
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
// Delete all inputs of hubs that reference deleted instance
|
155
|
+
for (const hub of Object.values(project.hubs)) {
|
156
|
+
if (!hub.inputs) {
|
157
|
+
continue
|
158
|
+
}
|
159
|
+
|
160
|
+
hub.inputs = hub.inputs.filter(input => input.instanceId !== instanceId)
|
161
|
+
|
162
|
+
if (hub.inputs.length === 0) {
|
163
|
+
delete hub.inputs
|
164
|
+
}
|
165
|
+
}
|
166
|
+
})
|
167
|
+
} catch (error) {
|
168
|
+
throw new Error("Failed to delete project instance", { cause: error })
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
private deleteInstanceReferences(
|
173
|
+
inputs: Record<string, InstanceInput[]>,
|
174
|
+
instanceId: string,
|
175
|
+
): void {
|
176
|
+
for (const [inputKey, input] of Object.entries(inputs)) {
|
177
|
+
inputs[inputKey] = input.filter(inputItem => inputItem.instanceId !== instanceId)
|
178
|
+
|
179
|
+
if (inputs[inputKey].length === 0) {
|
180
|
+
delete inputs[inputKey]
|
181
|
+
}
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
async renameInstance(
|
186
|
+
projectId: string,
|
187
|
+
instanceId: string,
|
188
|
+
newName: string,
|
189
|
+
): Promise<InstanceModel> {
|
190
|
+
try {
|
191
|
+
return await this.withProject(projectId, project => {
|
192
|
+
// Rename the instance
|
193
|
+
const instance = project.instances[instanceId]
|
194
|
+
if (!instance) {
|
195
|
+
throw new Error(`Instance ${instanceId} not found`)
|
196
|
+
}
|
197
|
+
|
198
|
+
const newInstanceId = getInstanceId(instance.type, newName)
|
199
|
+
if (project.instances[newInstanceId]) {
|
200
|
+
throw new Error(`Instance ${newInstanceId} already exists`)
|
201
|
+
}
|
202
|
+
|
203
|
+
delete project.instances[instanceId]
|
204
|
+
instance.id = newInstanceId
|
205
|
+
instance.name = newName
|
206
|
+
project.instances[newInstanceId] = instance
|
207
|
+
|
208
|
+
// Update all references to the instance from other instances
|
209
|
+
for (const otherInstance of Object.values(project.instances)) {
|
210
|
+
for (const inputs of Object.values(otherInstance.inputs ?? {})) {
|
211
|
+
this.renameInstanceReferences(inputs, instanceId, instance.id)
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
// Update all references to the instance from hubs
|
216
|
+
for (const hub of Object.values(project.hubs)) {
|
217
|
+
this.renameInstanceReferences(hub.inputs ?? [], instanceId, instance.id)
|
218
|
+
}
|
219
|
+
|
220
|
+
return instance
|
221
|
+
})
|
222
|
+
} catch (error) {
|
223
|
+
throw new Error("Failed to rename project instance", { cause: error })
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
private renameInstanceReferences(
|
228
|
+
inputs: InstanceInput[],
|
229
|
+
instanceId: string,
|
230
|
+
newInstanceId: string,
|
231
|
+
): void {
|
232
|
+
for (const input of inputs) {
|
233
|
+
if (input.instanceId === instanceId) {
|
234
|
+
input.instanceId = newInstanceId
|
235
|
+
}
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
async createHub(projectId: string, hub: HubModel): Promise<HubModel> {
|
240
|
+
try {
|
241
|
+
return await this.withProject(projectId, project => {
|
242
|
+
if (project.hubs[hub.id]) {
|
243
|
+
throw new Error(`Hub ${hub.id} already exists`)
|
244
|
+
}
|
245
|
+
|
246
|
+
project.hubs[hub.id] = hub
|
247
|
+
return hub
|
248
|
+
})
|
249
|
+
} catch (error) {
|
250
|
+
throw new Error("Failed to create project hub", { cause: error })
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
async updateHub(projectId: string, hubId: string, patch: HubModelPatch): Promise<HubModel> {
|
255
|
+
try {
|
256
|
+
return await this.withProject(projectId, project => {
|
257
|
+
const hub = project.hubs[hubId]
|
258
|
+
if (!hub) {
|
259
|
+
throw new Error(`Hub ${hubId} not found`)
|
260
|
+
}
|
261
|
+
|
262
|
+
if (patch.position) {
|
263
|
+
hub.position = patch.position
|
264
|
+
}
|
265
|
+
|
266
|
+
if (patch.inputs) {
|
267
|
+
if (patch.inputs.length > 0) {
|
268
|
+
hub.inputs = patch.inputs
|
269
|
+
} else {
|
270
|
+
delete hub.inputs
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
if (patch.injectionInputs) {
|
275
|
+
if (patch.injectionInputs.length > 0) {
|
276
|
+
hub.injectionInputs = patch.injectionInputs
|
277
|
+
} else {
|
278
|
+
delete hub.injectionInputs
|
279
|
+
}
|
280
|
+
}
|
281
|
+
|
282
|
+
return hub
|
283
|
+
})
|
284
|
+
} catch (error) {
|
285
|
+
throw new Error("Failed to update project hub", { cause: error })
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
async deleteHub(projectId: string, hubId: string): Promise<void> {
|
290
|
+
try {
|
291
|
+
await this.withProject(projectId, project => {
|
292
|
+
if (!project.hubs[hubId]) {
|
293
|
+
throw new Error(`Hub ${hubId} not found`)
|
294
|
+
}
|
295
|
+
|
296
|
+
delete project.hubs[hubId]
|
297
|
+
|
298
|
+
// Delete all inputs of instances that reference deleted hub
|
299
|
+
for (const instance of Object.values(project.instances)) {
|
300
|
+
if (instance.hubInputs) {
|
301
|
+
this.deleteHubReferences(instance.hubInputs, hubId)
|
302
|
+
|
303
|
+
if (Object.keys(instance.hubInputs).length === 0) {
|
304
|
+
delete instance.hubInputs
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
if (instance.injectionInputs) {
|
309
|
+
instance.injectionInputs = instance.injectionInputs.filter(
|
310
|
+
input => input.hubId !== hubId,
|
311
|
+
)
|
312
|
+
|
313
|
+
if (instance.injectionInputs.length === 0) {
|
314
|
+
delete instance.injectionInputs
|
315
|
+
}
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
// Delete all inputs of hubs that reference deleted hub
|
320
|
+
for (const otherHub of Object.values(project.hubs)) {
|
321
|
+
if (!otherHub.injectionInputs) {
|
322
|
+
continue
|
323
|
+
}
|
324
|
+
|
325
|
+
otherHub.injectionInputs = otherHub.injectionInputs.filter(input => input.hubId !== hubId)
|
326
|
+
|
327
|
+
if (otherHub.injectionInputs.length === 0) {
|
328
|
+
delete otherHub.injectionInputs
|
329
|
+
}
|
330
|
+
}
|
331
|
+
})
|
332
|
+
} catch (error) {
|
333
|
+
throw new Error("Failed to delete project hub", { cause: error })
|
334
|
+
}
|
335
|
+
}
|
336
|
+
|
337
|
+
private deleteHubReferences(inputs: Record<string, HubInput[]>, hubId: string): void {
|
338
|
+
for (const [inputKey, input] of Object.entries(inputs)) {
|
339
|
+
inputs[inputKey] = input.filter(inputItem => inputItem.hubId !== hubId)
|
340
|
+
|
341
|
+
if (inputs[inputKey].length === 0) {
|
342
|
+
delete inputs[inputKey]
|
343
|
+
}
|
344
|
+
}
|
345
|
+
}
|
346
|
+
|
347
|
+
private getProjectPath(projectId: string) {
|
348
|
+
return `${this.projectsDir}/${projectId}.json`
|
349
|
+
}
|
350
|
+
|
351
|
+
private async loadProject(projectId: string) {
|
352
|
+
const projectPath = this.getProjectPath(projectId)
|
353
|
+
|
354
|
+
try {
|
355
|
+
const content = await readFile(projectPath, "utf-8")
|
356
|
+
|
357
|
+
return projectModelSchema.parse(JSON.parse(content))
|
358
|
+
} catch (error) {
|
359
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
360
|
+
return { instances: {}, hubs: {} }
|
361
|
+
}
|
362
|
+
|
363
|
+
throw error
|
364
|
+
}
|
365
|
+
}
|
366
|
+
|
367
|
+
private async writeProject(projectId: string, project: z.infer<typeof projectModelSchema>) {
|
368
|
+
const projectPath = this.getProjectPath(projectId)
|
369
|
+
const content = JSON.stringify(project, undefined, 2)
|
370
|
+
|
371
|
+
await writeFile(projectPath, content)
|
372
|
+
}
|
373
|
+
|
374
|
+
private async withInstance<T>(
|
375
|
+
projectId: string,
|
376
|
+
instanceId: string,
|
377
|
+
callback: (instance: InstanceModel) => T,
|
378
|
+
): Promise<T> {
|
379
|
+
return await this.withProject(projectId, project => {
|
380
|
+
const instance = project.instances[instanceId]
|
381
|
+
if (!instance) {
|
382
|
+
throw new Error(`Instance ${instanceId} not found`)
|
383
|
+
}
|
384
|
+
|
385
|
+
return callback(instance)
|
386
|
+
})
|
387
|
+
}
|
388
|
+
|
389
|
+
private async withProject<T>(
|
390
|
+
projectId: string,
|
391
|
+
callback: (project: z.infer<typeof projectModelSchema>) => T,
|
392
|
+
): Promise<T> {
|
393
|
+
const project = await this.loadProject(projectId)
|
394
|
+
|
395
|
+
const result = callback(project)
|
396
|
+
await this.writeProject(projectId, project)
|
397
|
+
|
398
|
+
return result
|
399
|
+
}
|
400
|
+
|
401
|
+
public static async create(config: z.infer<typeof localProjectBackendConfig>) {
|
402
|
+
let projectsPath = config.HIGHSTATE_BACKEND_PROJECT_PROJECTS_DIR
|
403
|
+
if (!projectsPath) {
|
404
|
+
const [mainProjectPath] = await resolveMainLocalProject()
|
405
|
+
projectsPath = resolve(mainProjectPath, "projects")
|
406
|
+
}
|
407
|
+
|
408
|
+
await mkdir(projectsPath, { recursive: true })
|
409
|
+
|
410
|
+
return new LocalProjectBackend(projectsPath)
|
411
|
+
}
|
412
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import type { BetterLock as BetterLockType } from "better-lock/dist/better_lock"
|
2
|
+
import { BetterLock } from "better-lock"
|
3
|
+
|
4
|
+
export class ProjectLock {
|
5
|
+
constructor(private readonly lock: BetterLockType, private readonly projectId: string) {}
|
6
|
+
|
7
|
+
canImmediatelyAcquireLock(instanceId: string): boolean {
|
8
|
+
return this.lock.canAcquire(`${this.projectId}/${instanceId}`)
|
9
|
+
}
|
10
|
+
|
11
|
+
canImmediatelyAcquireLocks(instanceIds: string[]): boolean {
|
12
|
+
for (const instanceId of instanceIds) {
|
13
|
+
if (!this.canImmediatelyAcquireLock(instanceId)) {
|
14
|
+
return false
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
return true
|
19
|
+
}
|
20
|
+
|
21
|
+
lockInstance<T>(instanceId: string, fn: () => Promise<T>): Promise<T> {
|
22
|
+
return this.lock.acquire(`${this.projectId}/${instanceId}`, fn)
|
23
|
+
}
|
24
|
+
|
25
|
+
lockInstances<T>(instanceIds: string[], fn: () => Promise<T>): Promise<T> {
|
26
|
+
return this.lock.acquire(
|
27
|
+
instanceIds.map(id => `${this.projectId}/${id}`),
|
28
|
+
fn,
|
29
|
+
)
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
export class ProjectLockManager {
|
34
|
+
private readonly lock = new BetterLock()
|
35
|
+
|
36
|
+
getLock(projectId: string) {
|
37
|
+
return new ProjectLock(this.lock, projectId)
|
38
|
+
}
|
39
|
+
}
|