@highstate/backend 0.9.16 → 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.
- package/dist/chunk-NAAIDR4U.js +8499 -0
- package/dist/chunk-NAAIDR4U.js.map +1 -0
- package/dist/chunk-OU5OQBLB.js +74 -0
- package/dist/chunk-OU5OQBLB.js.map +1 -0
- package/dist/{chunk-WHALQHEZ.js → chunk-Y7DXREVO.js} +502 -774
- package/dist/chunk-Y7DXREVO.js.map +1 -0
- package/dist/highstate.manifest.json +4 -4
- package/dist/index.js +2979 -2233
- package/dist/index.js.map +1 -1
- package/dist/library/package-resolution-worker.js +7 -5
- package/dist/library/package-resolution-worker.js.map +1 -1
- package/dist/library/worker/main.js +40 -41
- package/dist/library/worker/main.js.map +1 -1
- package/dist/magic-string.es-5ABAC4JN.js +1292 -0
- package/dist/magic-string.es-5ABAC4JN.js.map +1 -0
- package/dist/shared/index.js +3 -216
- package/dist/shared/index.js.map +1 -1
- package/package.json +9 -6
- package/src/artifact/encryption.ts +47 -7
- package/src/artifact/factory.ts +2 -2
- package/src/artifact/local.ts +2 -6
- package/src/business/__traces__/secret/update-instance-secrets/create-and-delete-secrets-simultaneously.md +356 -0
- package/src/business/__traces__/secret/update-instance-secrets/create-new-secrets-for-instance.md +274 -0
- package/src/business/__traces__/secret/update-instance-secrets/delete-existing-secrets.md +223 -0
- package/src/business/__traces__/secret/update-instance-secrets/no-op-when-no-changes.md +147 -0
- package/src/business/__traces__/secret/update-instance-secrets/update-existing-secrets.md +280 -0
- package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration-when-other-exists.md +360 -0
- package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration.md +215 -0
- package/src/business/__traces__/worker/update-unit-registrations/create-multiple-workers-with-different-identities.md +427 -0
- package/src/business/__traces__/worker/update-unit-registrations/handle-nonexistent-registration-id-gracefully.md +217 -0
- package/src/business/__traces__/worker/update-unit-registrations/no-op-when-no-changes.md +132 -0
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-changes.md +454 -0
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-version-changes.md +426 -0
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-with-same-identity-reuses-service-account.md +372 -0
- package/src/business/__traces__/worker/update-unit-registrations/remove-one-of-multiple-unit-registrations.md +383 -0
- package/src/business/__traces__/worker/update-unit-registrations/remove-unit-registration.md +245 -0
- package/src/business/__traces__/worker/update-unit-registrations/update-existing-unit-registration-when-params-change.md +174 -0
- package/src/business/__traces__/worker/update-unit-registrations/update-params-and-image-simultaneously.md +432 -0
- package/src/business/__traces__/worker/update-unit-registrations/worker-with-multiple-registrations-not-deleted-when-one-removed.md +220 -0
- package/src/business/artifact.ts +2 -1
- package/src/business/index.ts +1 -0
- package/src/business/instance-lock.ts +3 -2
- package/src/business/instance-state.ts +202 -60
- package/src/business/project-unlock.ts +41 -23
- package/src/business/project.ts +299 -0
- package/src/business/secret.test.ts +178 -0
- package/src/business/secret.ts +139 -45
- package/src/business/worker.test.ts +614 -0
- package/src/business/worker.ts +289 -52
- package/src/common/clock.ts +18 -0
- package/src/common/index.ts +3 -0
- package/src/common/random.ts +68 -0
- package/src/common/test/index.ts +2 -0
- package/src/common/test/render.ts +98 -0
- package/src/common/test/tracer.ts +359 -0
- package/src/config.ts +5 -1
- package/src/hotstate/manager.ts +8 -8
- package/src/hotstate/validation.ts +0 -1
- package/src/library/abstractions.ts +20 -11
- package/src/library/local.ts +6 -13
- package/src/library/worker/evaluator.ts +30 -34
- package/src/library/worker/loader.lite.ts +13 -0
- package/src/library/worker/main.ts +8 -8
- package/src/library/worker/protocol.ts +0 -11
- package/src/lock/index.ts +1 -0
- package/src/lock/manager.ts +17 -2
- package/src/lock/test.ts +108 -0
- package/src/orchestrator/manager.ts +17 -36
- package/src/orchestrator/operation-workset.ts +34 -37
- package/src/orchestrator/operation.ts +129 -74
- package/src/project/abstractions.ts +27 -51
- package/src/project/evaluation.ts +248 -0
- package/src/project/index.ts +1 -1
- package/src/project/local.ts +75 -127
- package/src/pubsub/manager.ts +21 -13
- package/src/runner/abstractions.ts +29 -9
- package/src/runner/artifact-env.ts +3 -3
- package/src/runner/local.ts +29 -19
- package/src/runner/pulumi.ts +4 -1
- package/src/services.ts +77 -24
- package/src/shared/models/backend/library.ts +4 -4
- package/src/shared/models/backend/project.ts +25 -6
- package/src/shared/models/backend/unlock-method.ts +1 -1
- package/src/shared/models/base.ts +1 -84
- package/src/shared/models/project/api-key.ts +5 -2
- package/src/shared/models/project/artifact.ts +3 -33
- package/src/shared/models/project/index.ts +1 -2
- package/src/shared/models/project/lock.ts +3 -3
- package/src/shared/models/project/model.ts +14 -0
- package/src/shared/models/project/operation.ts +3 -3
- package/src/shared/models/project/page.ts +3 -3
- package/src/shared/models/project/secret.ts +4 -18
- package/src/shared/models/project/service-account.ts +2 -2
- package/src/shared/models/project/state.ts +32 -15
- package/src/shared/models/project/terminal.ts +4 -5
- package/src/shared/models/project/trigger.ts +1 -1
- package/src/shared/models/project/unlock-method.ts +9 -2
- package/src/shared/models/project/worker.ts +9 -7
- package/src/shared/resolvers/graph-resolver.ts +41 -26
- package/src/shared/resolvers/input.ts +47 -5
- package/src/shared/resolvers/validation.ts +23 -7
- package/src/shared/utils/args.ts +25 -0
- package/src/shared/utils/index.ts +1 -0
- package/src/state/abstractions.ts +98 -259
- package/src/state/encryption.ts +39 -0
- package/src/state/index.ts +1 -0
- package/src/state/local/backend.ts +29 -222
- package/src/state/local/collection.ts +105 -86
- package/src/state/manager.ts +358 -287
- package/src/state/memory/backend.ts +70 -0
- package/src/state/memory/collection.ts +270 -0
- package/src/state/memory/index.ts +2 -0
- package/src/state/repository/repository.index.ts +1 -1
- package/src/state/repository/repository.ts +71 -22
- package/src/state/test.ts +457 -0
- package/src/unlock/abstractions.ts +49 -0
- package/src/unlock/index.ts +2 -0
- package/src/unlock/memory.ts +32 -0
- package/src/worker/manager.ts +28 -0
- package/dist/chunk-RCB4AFGD.js +0 -159
- package/dist/chunk-RCB4AFGD.js.map +0 -1
- package/dist/chunk-WHALQHEZ.js.map +0 -1
- package/src/project/manager.ts +0 -574
- package/src/shared/models/project/component.ts +0 -45
- package/src/shared/models/project/instance.ts +0 -74
package/src/project/manager.ts
DELETED
|
@@ -1,574 +0,0 @@
|
|
|
1
|
-
import type { StateManager } from "../state"
|
|
2
|
-
import type { ProjectBackend } from "./abstractions"
|
|
3
|
-
import type { Logger } from "pino"
|
|
4
|
-
import type { InstanceEvaluationResult, LibraryBackend } from "../library"
|
|
5
|
-
import type { InstanceLockService } from "../business"
|
|
6
|
-
import type { PubSubManager } from "../pubsub"
|
|
7
|
-
import { randomUUID } from "node:crypto"
|
|
8
|
-
import { isUnitModel, type InstanceModel } from "@highstate/contract"
|
|
9
|
-
import {
|
|
10
|
-
type InputResolverNode,
|
|
11
|
-
type InputHashNode,
|
|
12
|
-
type InstanceModelPatch,
|
|
13
|
-
type CompositeInstance,
|
|
14
|
-
type ResolvedInstanceInput,
|
|
15
|
-
type HubModel,
|
|
16
|
-
type LibraryUpdate,
|
|
17
|
-
InputResolver,
|
|
18
|
-
InputHashResolver,
|
|
19
|
-
} from "../shared"
|
|
20
|
-
import { renderTree, type TreeNode } from "../common"
|
|
21
|
-
|
|
22
|
-
export type FullProjectModel = {
|
|
23
|
-
libraryId: string
|
|
24
|
-
instances: InstanceModel[]
|
|
25
|
-
hubs: HubModel[]
|
|
26
|
-
compositeInstances: CompositeInstance[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class ProjectManager {
|
|
30
|
-
private readonly watchedLibraries = new Set<string>()
|
|
31
|
-
private readonly libraryWatchers = new Map<string, AbortController>()
|
|
32
|
-
|
|
33
|
-
private constructor(
|
|
34
|
-
private readonly projectBackend: ProjectBackend,
|
|
35
|
-
private readonly libraryBackend: LibraryBackend,
|
|
36
|
-
private readonly stateManager: StateManager,
|
|
37
|
-
private readonly instanceLockService: InstanceLockService,
|
|
38
|
-
private readonly pubsubManager: PubSubManager,
|
|
39
|
-
private readonly logger: Logger,
|
|
40
|
-
) {}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Loads the full info of a project, including instances, hubs, and composite instances.
|
|
44
|
-
*
|
|
45
|
-
* Also filters out instances that are not in the library.
|
|
46
|
-
*
|
|
47
|
-
* @param projectId The ID of the project to load.
|
|
48
|
-
*/
|
|
49
|
-
async getProject(projectId: string): Promise<FullProjectModel> {
|
|
50
|
-
const [projectInfo, project] = await Promise.all([
|
|
51
|
-
this.projectBackend.getProjectInfo(projectId),
|
|
52
|
-
this.projectBackend.getProject(projectId),
|
|
53
|
-
])
|
|
54
|
-
|
|
55
|
-
// Ensure we're watching this library for changes
|
|
56
|
-
this.ensureLibraryWatched(projectInfo.libraryId)
|
|
57
|
-
|
|
58
|
-
const [compositeInstances, library] = await Promise.all([
|
|
59
|
-
this.stateManager.getCompositeInstanceRepository(projectId).getAllItems(),
|
|
60
|
-
this.libraryBackend.loadLibrary(projectInfo.libraryId),
|
|
61
|
-
])
|
|
62
|
-
|
|
63
|
-
const filteredInstances = project.instances.filter(
|
|
64
|
-
instance => instance.type in library.components,
|
|
65
|
-
)
|
|
66
|
-
const filteredCompositeInstances = compositeInstances
|
|
67
|
-
.filter(instance => instance.instance.type in library.components)
|
|
68
|
-
.map(instance => ({
|
|
69
|
-
...instance,
|
|
70
|
-
children: instance.children.filter(child => child.type in library.components),
|
|
71
|
-
}))
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
libraryId: projectInfo.libraryId,
|
|
75
|
-
instances: filteredInstances,
|
|
76
|
-
hubs: project.hubs,
|
|
77
|
-
compositeInstances: filteredCompositeInstances,
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async createInstance(projectId: string, instance: InstanceModel): Promise<InstanceModel> {
|
|
82
|
-
const createdInstance = await this.projectBackend.createInstance(projectId, instance)
|
|
83
|
-
await this.evaluateChangedCompositeInstances(projectId)
|
|
84
|
-
|
|
85
|
-
return createdInstance
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async updateInstance(
|
|
89
|
-
projectId: string,
|
|
90
|
-
instanceId: string,
|
|
91
|
-
patch: InstanceModelPatch,
|
|
92
|
-
): Promise<InstanceModel> {
|
|
93
|
-
const instance = await this.projectBackend.updateInstance(projectId, instanceId, patch)
|
|
94
|
-
// await this.evaluateChangedCompositeInstances(projectId)
|
|
95
|
-
|
|
96
|
-
return instance
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async renameInstance(
|
|
100
|
-
projectId: string,
|
|
101
|
-
instanceId: string,
|
|
102
|
-
newName: string,
|
|
103
|
-
): Promise<InstanceModel> {
|
|
104
|
-
const instance = await this.projectBackend.renameInstance(projectId, instanceId, newName)
|
|
105
|
-
await this.evaluateChangedCompositeInstances(projectId)
|
|
106
|
-
|
|
107
|
-
return instance
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async deleteInstance(projectId: string, instanceId: string): Promise<void> {
|
|
111
|
-
await Promise.all([
|
|
112
|
-
this.projectBackend.deleteInstance(projectId, instanceId),
|
|
113
|
-
this.stateManager.getCompositeInstanceRepository(projectId).delete(instanceId),
|
|
114
|
-
])
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async createManyNodes(
|
|
118
|
-
projectId: string,
|
|
119
|
-
instances: InstanceModel[],
|
|
120
|
-
hubs: HubModel[],
|
|
121
|
-
): Promise<void> {
|
|
122
|
-
await this.projectBackend.createManyNodes(projectId, instances, hubs)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private async evaluateChangedCompositeInstances(projectId: string): Promise<void> {
|
|
126
|
-
const { inputHashResolver, instances, library, evaluatedInputHashes } =
|
|
127
|
-
await this.prepareResolvers(projectId)
|
|
128
|
-
|
|
129
|
-
inputHashResolver.addAllNodesToWorkset()
|
|
130
|
-
await inputHashResolver.process()
|
|
131
|
-
|
|
132
|
-
const instanceIds = instances
|
|
133
|
-
.filter(instance => !isUnitModel(library.components[instance.type]))
|
|
134
|
-
.filter(
|
|
135
|
-
instance =>
|
|
136
|
-
evaluatedInputHashes[instance.id] !==
|
|
137
|
-
inputHashResolver.requireOutput(instance.id).inputHash,
|
|
138
|
-
)
|
|
139
|
-
.map(instance => instance.id)
|
|
140
|
-
|
|
141
|
-
await this.instanceLockService.tryLockInstances(
|
|
142
|
-
projectId,
|
|
143
|
-
instanceIds,
|
|
144
|
-
{
|
|
145
|
-
title: "Composite Instance Evaluation",
|
|
146
|
-
description: "This instance is now being evaluated",
|
|
147
|
-
icon: "logos:typescript-icon-round",
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
type: "evaluation",
|
|
151
|
-
evaluationId: randomUUID(),
|
|
152
|
-
},
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
this.logger.debug({ instanceIds }, "evaluating composite instances")
|
|
157
|
-
|
|
158
|
-
for (const instanceId of instanceIds) {
|
|
159
|
-
void this.pubsubManager.publish(`instance:state:patch:${projectId}`, {
|
|
160
|
-
patch: {
|
|
161
|
-
id: instanceId,
|
|
162
|
-
evaluationStatus: {
|
|
163
|
-
status: "evaluating",
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
})
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const [
|
|
170
|
-
{ instances, resolvedInputs, stateMap, inputHashResolver, libraryId },
|
|
171
|
-
existingCompositeInstances,
|
|
172
|
-
] = await Promise.all([
|
|
173
|
-
this.prepareResolvers(projectId),
|
|
174
|
-
this.stateManager.getCompositeInstanceRepository(projectId).getAllRecord(),
|
|
175
|
-
])
|
|
176
|
-
|
|
177
|
-
// Get child instance IDs for the instances being re-evaluated
|
|
178
|
-
const oldChildInstanceIds = new Set(
|
|
179
|
-
existingCompositeInstances
|
|
180
|
-
.filter(ci => instanceIds.includes(ci.instance.id))
|
|
181
|
-
.flatMap(ci => ci.childCompositeInstanceIds ?? []),
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
const results = await this.libraryBackend.evaluateCompositeInstances(
|
|
185
|
-
libraryId,
|
|
186
|
-
instances,
|
|
187
|
-
resolvedInputs,
|
|
188
|
-
instanceIds,
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
const newStates = results.map(result => {
|
|
192
|
-
const existingState = stateMap.get(result.instanceId)
|
|
193
|
-
const newState = existingState ?? { id: result.instanceId }
|
|
194
|
-
|
|
195
|
-
newState.evaluationStatus = result.success
|
|
196
|
-
? { status: "evaluated", message: ProjectManager.createSuccessEvaluationMessage(result) }
|
|
197
|
-
: { status: "error", message: result.error }
|
|
198
|
-
|
|
199
|
-
return newState
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
inputHashResolver.addAllNodesToWorkset()
|
|
203
|
-
await inputHashResolver.process()
|
|
204
|
-
|
|
205
|
-
const compositeInstances = results
|
|
206
|
-
.filter(result => result.success)
|
|
207
|
-
.flatMap(result =>
|
|
208
|
-
result.compositeInstances.map(instance => ({
|
|
209
|
-
...instance,
|
|
210
|
-
id: instance.instance.id,
|
|
211
|
-
inputHash:
|
|
212
|
-
// only store inputHash for top-level composite instances
|
|
213
|
-
instance.instance.id === result.instanceId
|
|
214
|
-
? inputHashResolver.requireOutput(instance.instance.id).inputHash
|
|
215
|
-
: "",
|
|
216
|
-
// only store childCompositeInstanceIds for top-level composite instances
|
|
217
|
-
childCompositeInstanceIds:
|
|
218
|
-
instance.instance.id === result.instanceId
|
|
219
|
-
? result.compositeInstances
|
|
220
|
-
.filter(ci => ci.instance.id !== result.instanceId)
|
|
221
|
-
.map(ci => ci.instance.id)
|
|
222
|
-
: undefined,
|
|
223
|
-
})),
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
const newChildInstanceIds = new Set(
|
|
227
|
-
results
|
|
228
|
-
.filter(result => result.success)
|
|
229
|
-
.flatMap(result =>
|
|
230
|
-
result.compositeInstances
|
|
231
|
-
.filter(instance => instance.instance.id !== result.instanceId)
|
|
232
|
-
.map(instance => instance.instance.id),
|
|
233
|
-
),
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
const deletedCompositeInstanceIds = new Set<string>()
|
|
237
|
-
|
|
238
|
-
// Find child instances that were deleted (existed before but don't exist now)
|
|
239
|
-
for (const oldChildId of oldChildInstanceIds) {
|
|
240
|
-
if (!newChildInstanceIds.has(oldChildId)) {
|
|
241
|
-
deletedCompositeInstanceIds.add(oldChildId)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
await this.stateManager
|
|
246
|
-
.getCompositeInstanceRepository(projectId)
|
|
247
|
-
.deleteMany(Array.from(deletedCompositeInstanceIds))
|
|
248
|
-
|
|
249
|
-
for (const state of newStates) {
|
|
250
|
-
void this.pubsubManager.publish(`instance:state:patch:${projectId}`, { patch: state })
|
|
251
|
-
|
|
252
|
-
if (state.evaluationStatus?.status === "error") {
|
|
253
|
-
this.logger.error(
|
|
254
|
-
{ projectId, instanceId: state.id },
|
|
255
|
-
`evaluation error for instance "%s": %s`,
|
|
256
|
-
state.evaluationStatus.message,
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
for (const instance of compositeInstances) {
|
|
262
|
-
void this.pubsubManager.publish(`composite-instance:${projectId}`, {
|
|
263
|
-
type: "updated",
|
|
264
|
-
instance,
|
|
265
|
-
})
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
for (const instanceId of deletedCompositeInstanceIds) {
|
|
269
|
-
void this.pubsubManager.publish(`composite-instance:${projectId}`, {
|
|
270
|
-
type: "deleted",
|
|
271
|
-
instanceId,
|
|
272
|
-
})
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const promises: Promise<void>[] = []
|
|
276
|
-
|
|
277
|
-
if (newStates.length > 0) {
|
|
278
|
-
promises.push(this.stateManager.getInstanceStateRepository(projectId).putMany(newStates))
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (compositeInstances.length > 0) {
|
|
282
|
-
promises.push(
|
|
283
|
-
this.stateManager.getCompositeInstanceRepository(projectId).putMany(compositeInstances),
|
|
284
|
-
)
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
this.logger.info(
|
|
288
|
-
{ projectId },
|
|
289
|
-
"instance evaluation completed, %d instances persisted",
|
|
290
|
-
compositeInstances.length,
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
this.logger.debug(
|
|
294
|
-
{ compositeInstanceIds: compositeInstances.map(instance => instance.instance.id) },
|
|
295
|
-
"persisted composite instances",
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
await Promise.all(promises)
|
|
299
|
-
} finally {
|
|
300
|
-
// TODO: implement detecting of lost evaluation locks
|
|
301
|
-
await this.instanceLockService.unlockInstancesUnconditionally(projectId, instanceIds)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
private async prepareResolvers(projectId: string) {
|
|
306
|
-
const [projectInfo, { instances, hubs }, states] = await Promise.all([
|
|
307
|
-
this.projectBackend.getProjectInfo(projectId),
|
|
308
|
-
this.projectBackend.getProject(projectId),
|
|
309
|
-
this.stateManager.getInstanceStateRepository(projectId).getAllItems(),
|
|
310
|
-
])
|
|
311
|
-
|
|
312
|
-
const library = await this.libraryBackend.loadLibrary(projectInfo.libraryId)
|
|
313
|
-
|
|
314
|
-
const filteredInstances = instances.filter(instance => instance.type in library.components)
|
|
315
|
-
const stateMap = new Map(states.map(state => [state.id, state]))
|
|
316
|
-
|
|
317
|
-
const inputResolverNodes = new Map<string, InputResolverNode>()
|
|
318
|
-
|
|
319
|
-
for (const instance of filteredInstances) {
|
|
320
|
-
inputResolverNodes.set(`instance:${instance.id}`, {
|
|
321
|
-
kind: "instance",
|
|
322
|
-
instance,
|
|
323
|
-
component: library.components[instance.type],
|
|
324
|
-
})
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
for (const hub of hubs) {
|
|
328
|
-
inputResolverNodes.set(`hub:${hub.id}`, { kind: "hub", hub })
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const inputResolver = new InputResolver(inputResolverNodes, this.logger)
|
|
332
|
-
inputResolver.addAllNodesToWorkset()
|
|
333
|
-
|
|
334
|
-
const inputHashInputs = new Map<string, InputHashNode>()
|
|
335
|
-
const resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>> = {}
|
|
336
|
-
|
|
337
|
-
await inputResolver.process()
|
|
338
|
-
|
|
339
|
-
for (const instance of filteredInstances) {
|
|
340
|
-
const output = inputResolver.requireOutput(`instance:${instance.id}`)
|
|
341
|
-
if (output.kind !== "instance") {
|
|
342
|
-
throw new Error("Expected instance node")
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
let sourceHash: string | undefined
|
|
346
|
-
if (isUnitModel(library.components[instance.type])) {
|
|
347
|
-
const resolvedUnits = await this.libraryBackend.getResolvedUnitSources(
|
|
348
|
-
projectInfo.libraryId,
|
|
349
|
-
[instance.type],
|
|
350
|
-
)
|
|
351
|
-
const resolvedUnit = resolvedUnits.find(unit => unit.unitType === instance.type)
|
|
352
|
-
|
|
353
|
-
if (resolvedUnit) {
|
|
354
|
-
sourceHash = resolvedUnit.sourceHash
|
|
355
|
-
} else {
|
|
356
|
-
this.logger.warn(`resolved unit source not found for type "%s"`, instance.type)
|
|
357
|
-
sourceHash = undefined
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
inputHashInputs.set(instance.id, {
|
|
362
|
-
instance,
|
|
363
|
-
component: library.components[instance.type],
|
|
364
|
-
resolvedInputs: output.resolvedInputs,
|
|
365
|
-
state: stateMap.get(instance.id),
|
|
366
|
-
sourceHash,
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
resolvedInputs[instance.id] = output.resolvedInputs
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const inputHashResolver = new InputHashResolver(inputHashInputs, this.logger)
|
|
373
|
-
|
|
374
|
-
return {
|
|
375
|
-
inputHashInputs,
|
|
376
|
-
inputHashResolver,
|
|
377
|
-
library,
|
|
378
|
-
libraryId: projectInfo.libraryId,
|
|
379
|
-
instances: filteredInstances,
|
|
380
|
-
stateMap,
|
|
381
|
-
resolvedInputs,
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Ensures a library is being watched for changes.
|
|
387
|
-
* Called when a project using this library is accessed.
|
|
388
|
-
*/
|
|
389
|
-
private ensureLibraryWatched(libraryId: string): void {
|
|
390
|
-
if (this.watchedLibraries.has(libraryId)) {
|
|
391
|
-
return
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
this.watchedLibraries.add(libraryId)
|
|
395
|
-
const abortController = new AbortController()
|
|
396
|
-
this.libraryWatchers.set(libraryId, abortController)
|
|
397
|
-
|
|
398
|
-
this.logger.debug({ libraryId }, "starting library watcher")
|
|
399
|
-
|
|
400
|
-
// Start watching this library in the background
|
|
401
|
-
void this.watchSingleLibrary(libraryId, abortController.signal)
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Watches a single library for changes and handles updates.
|
|
406
|
-
*/
|
|
407
|
-
private async watchSingleLibrary(libraryId: string, signal: AbortSignal): Promise<void> {
|
|
408
|
-
try {
|
|
409
|
-
for await (const updates of this.libraryBackend.watchLibrary(libraryId, signal)) {
|
|
410
|
-
try {
|
|
411
|
-
await this.handleLibraryUpdates(libraryId, updates)
|
|
412
|
-
} catch (error) {
|
|
413
|
-
this.logger.error({ error, libraryId }, "failed to handle library updates")
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
} catch (error) {
|
|
417
|
-
if (signal.aborted) {
|
|
418
|
-
this.logger.debug({ libraryId }, "library watcher stopped")
|
|
419
|
-
} else {
|
|
420
|
-
this.logger.error({ error, libraryId }, "library watcher failed")
|
|
421
|
-
}
|
|
422
|
-
} finally {
|
|
423
|
-
this.watchedLibraries.delete(libraryId)
|
|
424
|
-
this.libraryWatchers.delete(libraryId)
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
private async handleLibraryUpdates(libraryId: string, updates: LibraryUpdate[]): Promise<void> {
|
|
429
|
-
const changedComponents = new Set<string>()
|
|
430
|
-
|
|
431
|
-
for (const update of updates) {
|
|
432
|
-
switch (update.type) {
|
|
433
|
-
case "component-updated":
|
|
434
|
-
changedComponents.add(update.component.type)
|
|
435
|
-
break
|
|
436
|
-
case "component-removed":
|
|
437
|
-
changedComponents.add(update.componentType)
|
|
438
|
-
break
|
|
439
|
-
// Handle entity updates if needed
|
|
440
|
-
case "entity-updated":
|
|
441
|
-
case "entity-removed":
|
|
442
|
-
// Entity updates don't directly affect composite instance evaluation
|
|
443
|
-
break
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
if (changedComponents.size === 0) {
|
|
448
|
-
return
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
this.logger.info(
|
|
452
|
-
{ libraryId, changedComponents: Array.from(changedComponents) },
|
|
453
|
-
"library components changed, updating composite instances for affected projects",
|
|
454
|
-
)
|
|
455
|
-
|
|
456
|
-
// Get all projects and find those using this libraryId
|
|
457
|
-
const allProjectIds = await this.projectBackend.getProjectIds()
|
|
458
|
-
|
|
459
|
-
for (const projectId of allProjectIds) {
|
|
460
|
-
try {
|
|
461
|
-
// Check if this project uses the changed library
|
|
462
|
-
const projectInfo = await this.projectBackend.getProjectInfo(projectId)
|
|
463
|
-
if (projectInfo.libraryId !== libraryId) {
|
|
464
|
-
continue // Skip projects that don't use this library
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Load the updated library for filtering
|
|
468
|
-
const library = await this.libraryBackend.loadLibrary(libraryId)
|
|
469
|
-
const { instances } = await this.prepareResolvers(projectId)
|
|
470
|
-
|
|
471
|
-
const affectedInstances = instances.filter(
|
|
472
|
-
instance =>
|
|
473
|
-
changedComponents.has(instance.type) &&
|
|
474
|
-
library.components[instance.type] &&
|
|
475
|
-
!isUnitModel(library.components[instance.type]),
|
|
476
|
-
)
|
|
477
|
-
|
|
478
|
-
if (affectedInstances.length > 0) {
|
|
479
|
-
this.logger.info(
|
|
480
|
-
{
|
|
481
|
-
projectId,
|
|
482
|
-
libraryId,
|
|
483
|
-
affectedInstanceIds: affectedInstances.map(instance => instance.id),
|
|
484
|
-
},
|
|
485
|
-
"updating composite instances for project due to library changes",
|
|
486
|
-
)
|
|
487
|
-
|
|
488
|
-
await this.evaluateChangedCompositeInstances(projectId)
|
|
489
|
-
}
|
|
490
|
-
} catch (error) {
|
|
491
|
-
this.logger.error(
|
|
492
|
-
{ error, projectId, libraryId },
|
|
493
|
-
"failed to evaluate composite instances for project during library update",
|
|
494
|
-
)
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Cleanup method to stop all library watchers.
|
|
501
|
-
* Should be called when the ProjectManager is being shut down.
|
|
502
|
-
*/
|
|
503
|
-
dispose(): void {
|
|
504
|
-
for (const [libraryId, abortController] of this.libraryWatchers.entries()) {
|
|
505
|
-
this.logger.debug({ libraryId }, "stopping library watcher")
|
|
506
|
-
abortController.abort()
|
|
507
|
-
}
|
|
508
|
-
this.libraryWatchers.clear()
|
|
509
|
-
this.watchedLibraries.clear()
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
static create(
|
|
513
|
-
projectBackend: ProjectBackend,
|
|
514
|
-
libraryBackend: LibraryBackend,
|
|
515
|
-
stateManager: StateManager,
|
|
516
|
-
instanceLockService: InstanceLockService,
|
|
517
|
-
pubsubManager: PubSubManager,
|
|
518
|
-
logger: Logger,
|
|
519
|
-
): ProjectManager {
|
|
520
|
-
return new ProjectManager(
|
|
521
|
-
projectBackend,
|
|
522
|
-
libraryBackend,
|
|
523
|
-
stateManager,
|
|
524
|
-
instanceLockService,
|
|
525
|
-
pubsubManager,
|
|
526
|
-
logger.child({ service: "ProjectManager" }),
|
|
527
|
-
)
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
static createSuccessEvaluationMessage(
|
|
531
|
-
result: InstanceEvaluationResult & { success: true },
|
|
532
|
-
): string {
|
|
533
|
-
const treeNodes = new Map<string, TreeNode>()
|
|
534
|
-
|
|
535
|
-
// the order of composite instances are guaranteed to be topologically sorted
|
|
536
|
-
for (const compositeInstance of result.compositeInstances) {
|
|
537
|
-
const node: TreeNode = {
|
|
538
|
-
text: compositeInstance.id,
|
|
539
|
-
children: [],
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
treeNodes.set(compositeInstance.id, node)
|
|
543
|
-
|
|
544
|
-
const parentNode = compositeInstance.instance.parentId
|
|
545
|
-
? treeNodes.get(compositeInstance.instance.parentId)
|
|
546
|
-
: undefined
|
|
547
|
-
|
|
548
|
-
if (parentNode) {
|
|
549
|
-
parentNode.children.push(node)
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
for (const child of compositeInstance.children) {
|
|
553
|
-
const childNode: TreeNode = {
|
|
554
|
-
text: child.id,
|
|
555
|
-
children: [],
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
node.children.push(childNode)
|
|
559
|
-
treeNodes.set(child.id, childNode)
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// get the root node
|
|
564
|
-
const rootNode = treeNodes.get(result.instanceId)
|
|
565
|
-
if (!rootNode) {
|
|
566
|
-
return `Composite instance evaluation completed successfully, but failed to build the tree structure.`
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// render the tree structure
|
|
570
|
-
const tree = renderTree(rootNode)
|
|
571
|
-
|
|
572
|
-
return `Composite instance evaluation completed successfully.\n\nInstance Tree:\n${tree}`
|
|
573
|
-
}
|
|
574
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
|
|
3
|
-
export const metaSchema = z.object({
|
|
4
|
-
displayName: z.string().optional(),
|
|
5
|
-
description: z.string().optional(),
|
|
6
|
-
color: z.string().optional(),
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
export const entitySchema = z.object({
|
|
10
|
-
type: z.string(),
|
|
11
|
-
schema: z.any(), // TSchema from TypeBox
|
|
12
|
-
meta: metaSchema,
|
|
13
|
-
definitionHash: z.string(),
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
export const componentMetaSchema = metaSchema.extend({
|
|
17
|
-
primaryIcon: z.string().optional(),
|
|
18
|
-
primaryIconColor: z.string().optional(),
|
|
19
|
-
secondaryIcon: z.string().optional(),
|
|
20
|
-
secondaryIconColor: z.string().optional(),
|
|
21
|
-
category: z.string().optional(),
|
|
22
|
-
defaultNamePrefix: z.string().optional(),
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
export const componentArgumentSchema = z.object({
|
|
26
|
-
schema: z.any(), // ArgumentValueSchema from TypeBox
|
|
27
|
-
required: z.boolean(),
|
|
28
|
-
meta: componentMetaSchema,
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
export const componentInputSchema = z.object({
|
|
32
|
-
type: z.string(),
|
|
33
|
-
required: z.boolean(),
|
|
34
|
-
multiple: z.boolean(),
|
|
35
|
-
meta: componentMetaSchema,
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
export const componentModelSchema = z.object({
|
|
39
|
-
type: z.string(),
|
|
40
|
-
args: z.record(componentArgumentSchema),
|
|
41
|
-
inputs: z.record(componentInputSchema),
|
|
42
|
-
outputs: z.record(componentInputSchema),
|
|
43
|
-
meta: componentMetaSchema,
|
|
44
|
-
definitionHash: z.string(),
|
|
45
|
-
})
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
|
|
3
|
-
export const positionSchema = z.object({
|
|
4
|
-
x: z.number(),
|
|
5
|
-
y: z.number(),
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
export const instanceInputSchema = z.object({
|
|
9
|
-
instanceId: z.string(),
|
|
10
|
-
output: z.string(),
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
export const hubInstanceInputSchema = z.object({
|
|
14
|
-
hubId: z.string(),
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
export const instanceModelPatchSchema = z.object({
|
|
18
|
-
args: z.record(z.unknown()).optional(),
|
|
19
|
-
inputs: z.record(z.array(instanceInputSchema)).optional(),
|
|
20
|
-
hubInputs: z.record(z.array(hubInstanceInputSchema)).optional(),
|
|
21
|
-
injectionInputs: z.array(hubInstanceInputSchema).optional(),
|
|
22
|
-
position: positionSchema.optional(),
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
export const instanceModelSchema = z.object({
|
|
26
|
-
id: z.string(),
|
|
27
|
-
type: z.string(),
|
|
28
|
-
name: z.string(),
|
|
29
|
-
|
|
30
|
-
...instanceModelPatchSchema.shape,
|
|
31
|
-
resolvedInputs: z.record(z.array(instanceInputSchema)).optional(),
|
|
32
|
-
|
|
33
|
-
parentId: z.string().optional(),
|
|
34
|
-
outputs: z.record(z.array(instanceInputSchema)).optional(),
|
|
35
|
-
resolvedOutputs: z.record(z.array(instanceInputSchema)).optional(),
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
export const compositeInstanceSchema = z.object({
|
|
39
|
-
instance: instanceModelSchema,
|
|
40
|
-
children: z.array(instanceModelSchema),
|
|
41
|
-
childCompositeInstanceIds: z.array(z.string()).optional(),
|
|
42
|
-
inputHash: z.string().optional(),
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
export type CompositeInstance = z.infer<typeof compositeInstanceSchema>
|
|
46
|
-
|
|
47
|
-
export const compositeInstanceEventSchema = z.discriminatedUnion("type", [
|
|
48
|
-
z.object({
|
|
49
|
-
type: z.literal("updated"),
|
|
50
|
-
instance: compositeInstanceSchema,
|
|
51
|
-
}),
|
|
52
|
-
z.object({
|
|
53
|
-
type: z.literal("deleted"),
|
|
54
|
-
instanceId: z.string(),
|
|
55
|
-
}),
|
|
56
|
-
])
|
|
57
|
-
|
|
58
|
-
export const hubModelPatchSchema = z.object({
|
|
59
|
-
position: positionSchema.optional(),
|
|
60
|
-
inputs: z.array(instanceInputSchema).optional(),
|
|
61
|
-
injectionInputs: z.array(hubInstanceInputSchema).optional(),
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
export const hubModelSchema = z.object({
|
|
65
|
-
id: z.string().nanoid(),
|
|
66
|
-
position: positionSchema,
|
|
67
|
-
|
|
68
|
-
inputs: z.array(instanceInputSchema).optional(),
|
|
69
|
-
injectionInputs: z.array(hubInstanceInputSchema).optional(),
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
export type InstanceModelPatch = z.infer<typeof instanceModelPatchSchema>
|
|
73
|
-
export type HubModel = z.infer<typeof hubModelSchema>
|
|
74
|
-
export type HubModelPatch = z.infer<typeof hubModelPatchSchema>
|