@highstate/backend 0.9.31 → 0.9.33
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-QSHSXLO2.js → chunk-FQMDUNWC.js} +24 -22
- package/dist/chunk-FQMDUNWC.js.map +1 -0
- package/dist/index.js +172 -23
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +1 -1
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +1 -1
- package/package.json +4 -4
- package/prisma/project/operation.prisma +1 -0
- package/src/business/project.ts +60 -0
- package/src/business/terminal-session.ts +3 -3
- package/src/database/_generated/project/enums.ts +1 -0
- package/src/database/_generated/project/internal/class.ts +4 -3
- package/src/library/worker/evaluator.ts +1 -1
- package/src/orchestrator/operation-context.ts +37 -10
- package/src/orchestrator/operation-plan.fixtures.ts +1 -0
- package/src/orchestrator/operation-plan.preview.test.ts +80 -0
- package/src/orchestrator/operation-plan.ts +40 -0
- package/src/orchestrator/operation-plan.update.test.ts +70 -0
- package/src/orchestrator/operation-workset.ts +12 -3
- package/src/orchestrator/operation.ts +52 -2
- package/src/orchestrator/plan-test-builder.ts +8 -8
- package/src/runner/local.ts +3 -1
- package/src/runner/pulumi.ts +6 -1
- package/src/shared/models/prisma.ts +1 -1
- package/src/shared/models/project/operation.ts +15 -1
- package/src/shared/models/project/state.ts +13 -0
- package/src/shared/resolvers/input-hash.ts +3 -0
- package/src/shared/resolvers/registry.ts +0 -5
- package/dist/chunk-QSHSXLO2.js.map +0 -1
- package/src/shared/resolvers/state.ts +0 -19
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe } from "vitest"
|
|
2
|
+
import { createOperationPlan } from "./operation-plan"
|
|
3
|
+
import { operationPlanTest } from "./operation-plan.fixtures"
|
|
4
|
+
|
|
5
|
+
describe("OperationPlan - Preview Operations", () => {
|
|
6
|
+
operationPlanTest(
|
|
7
|
+
"1. returns single preview phase for requested unit",
|
|
8
|
+
async ({ testBuilder, expect }) => {
|
|
9
|
+
const { context, operation } = await testBuilder()
|
|
10
|
+
.unit("App")
|
|
11
|
+
.request("preview", "App")
|
|
12
|
+
.build()
|
|
13
|
+
|
|
14
|
+
const plan = createOperationPlan(
|
|
15
|
+
context,
|
|
16
|
+
operation.type,
|
|
17
|
+
operation.requestedInstanceIds,
|
|
18
|
+
operation.options,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
22
|
+
[
|
|
23
|
+
{
|
|
24
|
+
"instances": [
|
|
25
|
+
{
|
|
26
|
+
"id": "component.v1:App",
|
|
27
|
+
"message": "explicitly requested",
|
|
28
|
+
"parentId": undefined,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
"type": "preview",
|
|
32
|
+
},
|
|
33
|
+
]
|
|
34
|
+
`)
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
operationPlanTest(
|
|
39
|
+
"2. throws when multiple instances are requested",
|
|
40
|
+
async ({ testBuilder, expect }) => {
|
|
41
|
+
const { context, operation } = await testBuilder()
|
|
42
|
+
.unit("A")
|
|
43
|
+
.unit("B")
|
|
44
|
+
.request("preview", "A", "B")
|
|
45
|
+
.build()
|
|
46
|
+
|
|
47
|
+
expect(() =>
|
|
48
|
+
createOperationPlan(
|
|
49
|
+
context,
|
|
50
|
+
operation.type,
|
|
51
|
+
operation.requestedInstanceIds,
|
|
52
|
+
operation.options,
|
|
53
|
+
),
|
|
54
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
55
|
+
"[Error: Preview operations can only target a single instance]",
|
|
56
|
+
)
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
operationPlanTest(
|
|
61
|
+
"3. throws when previewing composite instance",
|
|
62
|
+
async ({ testBuilder, expect }) => {
|
|
63
|
+
const { context, operation } = await testBuilder()
|
|
64
|
+
.composite("Group")
|
|
65
|
+
.request("preview", "Group")
|
|
66
|
+
.build()
|
|
67
|
+
|
|
68
|
+
expect(() =>
|
|
69
|
+
createOperationPlan(
|
|
70
|
+
context,
|
|
71
|
+
operation.type,
|
|
72
|
+
operation.requestedInstanceIds,
|
|
73
|
+
operation.options,
|
|
74
|
+
),
|
|
75
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
76
|
+
'[Error: Preview is not supported for composite instance "composite.v1:Group"]',
|
|
77
|
+
)
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
})
|
|
@@ -35,6 +35,38 @@ export function createOperationPlan(
|
|
|
35
35
|
requestedInstanceIds: string[],
|
|
36
36
|
options: OperationOptions,
|
|
37
37
|
): OperationPhase[] {
|
|
38
|
+
if (options.forceUpdateDependencies && options.ignoreDependencies) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"Operation options are invalid: forceUpdateDependencies and ignoreDependencies cannot both be enabled.",
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (type === "preview") {
|
|
45
|
+
if (requestedInstanceIds.length !== 1) {
|
|
46
|
+
throw new Error("Preview operations can only target a single instance")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const instanceId = requestedInstanceIds[0] as InstanceId
|
|
50
|
+
const instance = context.getInstance(instanceId)
|
|
51
|
+
|
|
52
|
+
if (instance.kind !== "unit") {
|
|
53
|
+
throw new Error(`Preview is not supported for composite instance "${instanceId}"`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
type: "preview",
|
|
59
|
+
instances: [
|
|
60
|
+
{
|
|
61
|
+
id: instanceId,
|
|
62
|
+
parentId: instance.parentId,
|
|
63
|
+
message: "explicitly requested",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
|
|
38
70
|
// initialize work state
|
|
39
71
|
const workState: WorkState = {
|
|
40
72
|
included: new Map(),
|
|
@@ -195,6 +227,10 @@ function processUpdateInclusions(
|
|
|
195
227
|
if (workState.included.has(instance.id)) {
|
|
196
228
|
const dependencies = context.getDependencies(instance.id)
|
|
197
229
|
for (const depInstance of dependencies) {
|
|
230
|
+
if (options.ignoreDependencies) {
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
|
|
198
234
|
const shouldInclude = options.forceUpdateDependencies || isOutdated(depInstance, context)
|
|
199
235
|
|
|
200
236
|
if (shouldInclude && !workState.included.has(depInstance.id)) {
|
|
@@ -237,6 +273,10 @@ function processRefreshInclusions(
|
|
|
237
273
|
if (workState.included.has(instance.id)) {
|
|
238
274
|
const dependencies = context.getDependencies(instance.id)
|
|
239
275
|
for (const depInstance of dependencies) {
|
|
276
|
+
if (options.ignoreDependencies) {
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
|
|
240
280
|
const shouldInclude = options.forceUpdateDependencies
|
|
241
281
|
|
|
242
282
|
if (shouldInclude && !workState.included.has(depInstance.id)) {
|
|
@@ -48,6 +48,47 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
48
48
|
},
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
+
operationPlanTest(
|
|
52
|
+
"1a. should ignore dependencies when option enabled",
|
|
53
|
+
async ({ testBuilder, expect }) => {
|
|
54
|
+
// arrange
|
|
55
|
+
const { context, operation } = await testBuilder()
|
|
56
|
+
.unit("A")
|
|
57
|
+
.unit("B")
|
|
58
|
+
.unit("C")
|
|
59
|
+
.depends("C", "B")
|
|
60
|
+
.depends("B", "A")
|
|
61
|
+
.states({ A: "upToDate", B: "changed", C: "upToDate" })
|
|
62
|
+
.options({ ignoreDependencies: true })
|
|
63
|
+
.request("update", "C")
|
|
64
|
+
.build()
|
|
65
|
+
|
|
66
|
+
// act
|
|
67
|
+
const plan = createOperationPlan(
|
|
68
|
+
context,
|
|
69
|
+
operation.type,
|
|
70
|
+
operation.requestedInstanceIds,
|
|
71
|
+
operation.options,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
// assert
|
|
75
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
76
|
+
[
|
|
77
|
+
{
|
|
78
|
+
"instances": [
|
|
79
|
+
{
|
|
80
|
+
"id": "component.v1:C",
|
|
81
|
+
"message": "explicitly requested",
|
|
82
|
+
"parentId": undefined,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
"type": "update",
|
|
86
|
+
},
|
|
87
|
+
]
|
|
88
|
+
`)
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
|
|
51
92
|
operationPlanTest(
|
|
52
93
|
"2. should not propagate beyond compositional inclusion",
|
|
53
94
|
async ({ testBuilder, expect }) => {
|
|
@@ -158,6 +199,35 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
158
199
|
},
|
|
159
200
|
)
|
|
160
201
|
|
|
202
|
+
operationPlanTest(
|
|
203
|
+
"3a. should reject conflicting dependency options",
|
|
204
|
+
async ({ testBuilder, expect }) => {
|
|
205
|
+
// arrange
|
|
206
|
+
const { context, operation } = await testBuilder()
|
|
207
|
+
.unit("A")
|
|
208
|
+
.unit("B")
|
|
209
|
+
.unit("C")
|
|
210
|
+
.depends("C", "B")
|
|
211
|
+
.depends("B", "A")
|
|
212
|
+
.states({ A: "upToDate", B: "upToDate", C: "upToDate" })
|
|
213
|
+
.options({ forceUpdateDependencies: true, ignoreDependencies: true })
|
|
214
|
+
.request("update", "C")
|
|
215
|
+
.build()
|
|
216
|
+
|
|
217
|
+
// act & assert
|
|
218
|
+
expect(() =>
|
|
219
|
+
createOperationPlan(
|
|
220
|
+
context,
|
|
221
|
+
operation.type,
|
|
222
|
+
operation.requestedInstanceIds,
|
|
223
|
+
operation.options,
|
|
224
|
+
),
|
|
225
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
226
|
+
"[Error: Operation options are invalid: forceUpdateDependencies and ignoreDependencies cannot both be enabled.]",
|
|
227
|
+
)
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
|
|
161
231
|
operationPlanTest(
|
|
162
232
|
"4. should include outdated children of substantive composite",
|
|
163
233
|
async ({ testBuilder, expect }) => {
|
|
@@ -120,18 +120,21 @@ export class OperationWorkset {
|
|
|
120
120
|
inputs => inputs.map(input => input.input),
|
|
121
121
|
)
|
|
122
122
|
|
|
123
|
+
const serializedResolvedInputs = this.context.serializeResolvedInputs(resolvedInputs)
|
|
124
|
+
|
|
123
125
|
return [
|
|
124
126
|
{
|
|
125
127
|
stateId: state.id,
|
|
126
128
|
operationId: this.operationId,
|
|
127
129
|
status: "pending",
|
|
128
130
|
model: instance,
|
|
129
|
-
resolvedInputs,
|
|
131
|
+
resolvedInputs: serializedResolvedInputs,
|
|
130
132
|
},
|
|
131
133
|
{
|
|
134
|
+
// preview runs still provision a pulumi stack; keep "attempted" so a later destroy removes it
|
|
132
135
|
status: state.status === "undeployed" ? "attempted" : state.status,
|
|
133
136
|
model: instance,
|
|
134
|
-
resolvedInputs,
|
|
137
|
+
resolvedInputs: serializedResolvedInputs,
|
|
135
138
|
},
|
|
136
139
|
]
|
|
137
140
|
}),
|
|
@@ -153,7 +156,7 @@ export class OperationWorkset {
|
|
|
153
156
|
options,
|
|
154
157
|
)
|
|
155
158
|
|
|
156
|
-
if (state.parentInstanceId) {
|
|
159
|
+
if (state.parentInstanceId && this.currentPhase !== "preview") {
|
|
157
160
|
// TODO: update all updates in single transaction
|
|
158
161
|
await this.recalculateCompositeInstanceState(state.parentInstanceId)
|
|
159
162
|
}
|
|
@@ -231,6 +234,8 @@ export class OperationWorkset {
|
|
|
231
234
|
|
|
232
235
|
getTransientStatusByOperationPhase(): InstanceOperationStatus {
|
|
233
236
|
switch (this.currentPhase) {
|
|
237
|
+
case "preview":
|
|
238
|
+
return "previewing"
|
|
234
239
|
case "update":
|
|
235
240
|
return "updating"
|
|
236
241
|
case "destroy":
|
|
@@ -242,6 +247,8 @@ export class OperationWorkset {
|
|
|
242
247
|
|
|
243
248
|
getStableStatusByOperationPhase(): InstanceOperationStatus {
|
|
244
249
|
switch (this.currentPhase) {
|
|
250
|
+
case "preview":
|
|
251
|
+
return "previewed"
|
|
245
252
|
case "update":
|
|
246
253
|
return "updated"
|
|
247
254
|
case "destroy":
|
|
@@ -255,6 +262,8 @@ export class OperationWorkset {
|
|
|
255
262
|
const state = this.context.getState(instanceId)
|
|
256
263
|
|
|
257
264
|
switch (this.currentPhase) {
|
|
265
|
+
case "preview":
|
|
266
|
+
return state.status // do not change instance status when previewing
|
|
258
267
|
case "update":
|
|
259
268
|
return "deployed"
|
|
260
269
|
case "destroy":
|
|
@@ -252,9 +252,60 @@ export class RuntimeOperation {
|
|
|
252
252
|
case "refresh": {
|
|
253
253
|
return this.refreshUnit(instance, state)
|
|
254
254
|
}
|
|
255
|
+
case "preview": {
|
|
256
|
+
return this.previewUnit(instance, state)
|
|
257
|
+
}
|
|
255
258
|
}
|
|
256
259
|
}
|
|
257
260
|
|
|
261
|
+
private previewUnit(instance: InstanceModel, state: InstanceState): Promise<void> {
|
|
262
|
+
return this.getInstancePromise(instance.id, async (logger, signal, forceSignal) => {
|
|
263
|
+
signal.throwIfAborted()
|
|
264
|
+
|
|
265
|
+
if (this.operation.status === "failing") {
|
|
266
|
+
throw new AbortError("The operation is failing, aborting preview branch")
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
logger.info("previewing unit")
|
|
270
|
+
|
|
271
|
+
await this.workset.updateState(instance.id, {
|
|
272
|
+
operationState: {
|
|
273
|
+
status: "previewing",
|
|
274
|
+
startedAt: new Date(),
|
|
275
|
+
},
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
signal.throwIfAborted()
|
|
279
|
+
|
|
280
|
+
const secrets = await this.secretService.getInstanceSecretValues(this.project.id, state.id)
|
|
281
|
+
signal.throwIfAborted()
|
|
282
|
+
|
|
283
|
+
const config = this.prepareUnitConfig(instance, secrets)
|
|
284
|
+
const artifactIds = this.collectArtifactIdsForInstance(instance)
|
|
285
|
+
const artifacts = await this.artifactService.getArtifactsByIds(this.project.id, artifactIds)
|
|
286
|
+
|
|
287
|
+
logger.debug({ count: artifactIds.length }, "artifact ids collected for preview")
|
|
288
|
+
|
|
289
|
+
await this.runnerBackend.preview({
|
|
290
|
+
projectId: this.project.id,
|
|
291
|
+
libraryId: this.project.libraryId,
|
|
292
|
+
stateId: state.id,
|
|
293
|
+
instanceType: instance.type,
|
|
294
|
+
instanceName: instance.name,
|
|
295
|
+
config,
|
|
296
|
+
refresh: this.operation.options.refresh,
|
|
297
|
+
secrets,
|
|
298
|
+
artifacts,
|
|
299
|
+
signal,
|
|
300
|
+
forceSignal,
|
|
301
|
+
debug: this.operation.options.debug,
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
await this.watchStateStream(state, instance.type, instance.name, logger)
|
|
305
|
+
logger.info("unit preview completed")
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
258
309
|
private getCompositePromise(instance: InstanceModel): Promise<void> {
|
|
259
310
|
return this.getInstancePromise(instance.id, async logger => {
|
|
260
311
|
let instanceState: InstanceStatePatch | undefined
|
|
@@ -353,7 +404,7 @@ export class RuntimeOperation {
|
|
|
353
404
|
|
|
354
405
|
logger.debug({ count: artifactIds.length }, "artifact ids collected from dependencies")
|
|
355
406
|
|
|
356
|
-
await this.runnerBackend
|
|
407
|
+
await this.runnerBackend.update({
|
|
357
408
|
projectId: this.project.id,
|
|
358
409
|
libraryId: this.project.libraryId,
|
|
359
410
|
stateId: state.id,
|
|
@@ -669,7 +720,6 @@ export class RuntimeOperation {
|
|
|
669
720
|
state: InstanceState,
|
|
670
721
|
): Promise<void> {
|
|
671
722
|
if (this.operation.type === "preview") {
|
|
672
|
-
// do not change instance status in preview mode
|
|
673
723
|
await this.workset.updateState(update.unitId, {
|
|
674
724
|
operationState: {
|
|
675
725
|
status: this.workset.getStableStatusByOperationPhase(),
|
|
@@ -209,14 +209,6 @@ export class PlanTestBuilder {
|
|
|
209
209
|
const instances = Array.from(this.instances.values())
|
|
210
210
|
const states = Array.from(this.stateMap.values())
|
|
211
211
|
|
|
212
|
-
// Copy resolvedInputs from instances to states for dependency tracking
|
|
213
|
-
states.forEach(state => {
|
|
214
|
-
const instance = instances.find(i => i.id === state.instanceId)
|
|
215
|
-
if (instance?.resolvedInputs) {
|
|
216
|
-
state.resolvedInputs = instance.resolvedInputs
|
|
217
|
-
}
|
|
218
|
-
})
|
|
219
|
-
|
|
220
212
|
// get requested instance IDs
|
|
221
213
|
const requestedInstanceIds = this.requestedInstanceNames.map(name => {
|
|
222
214
|
const instance = this.instances.get(name)
|
|
@@ -234,6 +226,14 @@ export class PlanTestBuilder {
|
|
|
234
226
|
// create context with instances and initial states
|
|
235
227
|
const context = await this.createContext(instances, states)
|
|
236
228
|
|
|
229
|
+
// copy resolvedInputs from instances to states for dependency tracking
|
|
230
|
+
states.forEach(state => {
|
|
231
|
+
const instance = instances.find(i => i.id === state.instanceId)
|
|
232
|
+
if (instance?.resolvedInputs) {
|
|
233
|
+
state.resolvedInputs = context.serializeResolvedInputs(instance.resolvedInputs)
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
237
|
// update "upToDate" states with correct input hashes from context
|
|
238
238
|
const updatedStates = states.map(state => {
|
|
239
239
|
const stateEntry = Array.from(this.stateMap.entries()).find(
|
package/src/runner/local.ts
CHANGED
|
@@ -208,7 +208,7 @@ export class LocalRunnerBackend implements RunnerBackend {
|
|
|
208
208
|
const outputs = await stack.outputs()
|
|
209
209
|
const completionUpdate = this.createCompletionStateUpdate("update", unitId, outputs)
|
|
210
210
|
|
|
211
|
-
if (outputs["$artifacts"]) {
|
|
211
|
+
if (!preview && outputs["$artifacts"]) {
|
|
212
212
|
const artifacts = z
|
|
213
213
|
.record(z.string(), unitArtifactSchema.array())
|
|
214
214
|
.parse(outputs["$artifacts"].value)
|
|
@@ -221,6 +221,8 @@ export class LocalRunnerBackend implements RunnerBackend {
|
|
|
221
221
|
Object.values(artifacts).flat(),
|
|
222
222
|
childLogger,
|
|
223
223
|
)
|
|
224
|
+
} else if (preview && outputs["$artifacts"]) {
|
|
225
|
+
childLogger.debug({ msg: "skipping artifact persistence for preview" })
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
this.emitStateUpdate(completionUpdate)
|
package/src/runner/pulumi.ts
CHANGED
|
@@ -111,7 +111,12 @@ export class LocalPulumiHost {
|
|
|
111
111
|
{
|
|
112
112
|
projectSettings: {
|
|
113
113
|
name: pulumiProjectName,
|
|
114
|
-
runtime:
|
|
114
|
+
runtime: {
|
|
115
|
+
name: "nodejs",
|
|
116
|
+
options: {
|
|
117
|
+
nodeargs: "--no-deprecation",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
115
120
|
main: "index.js",
|
|
116
121
|
},
|
|
117
122
|
stackSettings: stackConfig
|
|
@@ -24,7 +24,7 @@ declare global {
|
|
|
24
24
|
type InstanceModel = contract.InstanceModel
|
|
25
25
|
|
|
26
26
|
type InstanceArgs = Record<string, unknown>
|
|
27
|
-
type InstanceResolvedInputs = Record<string,
|
|
27
|
+
type InstanceResolvedInputs = Record<string, shared.StableInstanceInput[]>
|
|
28
28
|
type UnlockMethodMeta = shared.UnlockMethodMeta
|
|
29
29
|
type ProjectUnlockSuite = shared.ProjectUnlockSuite
|
|
30
30
|
|
|
@@ -4,7 +4,7 @@ import { instanceIdSchema, objectMetaSchema, z } from "@highstate/contract"
|
|
|
4
4
|
/**
|
|
5
5
|
* Phase type for operation execution.
|
|
6
6
|
*/
|
|
7
|
-
export const operationPhaseTypeSchema = z.enum(["destroy", "update", "refresh"])
|
|
7
|
+
export const operationPhaseTypeSchema = z.enum(["destroy", "preview", "update", "refresh"])
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Instance information for operation phase.
|
|
@@ -61,6 +61,20 @@ export const operationOptionsSchema = z
|
|
|
61
61
|
*/
|
|
62
62
|
forceUpdateDependencies: z.boolean().default(false),
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Ignore dependencies and operate only on explicitly requested instances.
|
|
66
|
+
*
|
|
67
|
+
* **Operation Behavior Impact:**
|
|
68
|
+
* - skips dependency inclusion even when dependencies are failed or undeployed;
|
|
69
|
+
* - caller must explicitly include every prerequisite instance to avoid failures;
|
|
70
|
+
* - complements on-demand or targeted updates where dependency safety is managed externally.
|
|
71
|
+
*
|
|
72
|
+
* **Usage with other options:**
|
|
73
|
+
* - mutually exclusive with `forceUpdateDependencies`;
|
|
74
|
+
* - independent of child/composite inclusion options.
|
|
75
|
+
*/
|
|
76
|
+
ignoreDependencies: z.boolean().default(false),
|
|
77
|
+
|
|
64
78
|
/**
|
|
65
79
|
* Force update all children of composite instances regardless of their state.
|
|
66
80
|
*
|
|
@@ -8,6 +8,18 @@ import type {
|
|
|
8
8
|
} from "../../../database"
|
|
9
9
|
import { z } from "zod"
|
|
10
10
|
|
|
11
|
+
export const stableInstanceInputSchema = z.object({
|
|
12
|
+
stateId: z.string(),
|
|
13
|
+
output: z.string(),
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The instance input that references state IDs instead of instance IDs.
|
|
18
|
+
*
|
|
19
|
+
* This provides a stable reference to an instance output that is not affected by instance ID changes.
|
|
20
|
+
*/
|
|
21
|
+
export type StableInstanceInput = z.infer<typeof stableInstanceInputSchema>
|
|
22
|
+
|
|
11
23
|
/**
|
|
12
24
|
* The instance state aggregate including all related states.
|
|
13
25
|
*/
|
|
@@ -109,6 +121,7 @@ export type {
|
|
|
109
121
|
} from "../../../database"
|
|
110
122
|
|
|
111
123
|
export const finalInstanceOperationStatuses: InstanceOperationStatus[] = [
|
|
124
|
+
"previewed",
|
|
112
125
|
"destroyed",
|
|
113
126
|
"updated",
|
|
114
127
|
"cancelled",
|
|
@@ -45,6 +45,9 @@ export class InputHashResolver extends GraphResolver<InputHashNode, InputHashOut
|
|
|
45
45
|
}: InputHashNode): InputHashOutput {
|
|
46
46
|
const inputHashSink: Uint8Array[] = []
|
|
47
47
|
|
|
48
|
+
// 0. include the instance id to reflect renames
|
|
49
|
+
inputHashSink.push(Buffer.from(instance.id))
|
|
50
|
+
|
|
48
51
|
// 1. include the component definition hash
|
|
49
52
|
inputHashSink.push(int32ToBytes(component.definitionHash))
|
|
50
53
|
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { Logger } from "pino"
|
|
2
|
-
import type { InstanceState } from "../models/project"
|
|
3
2
|
import type { DependentSetHandler, GraphResolver, ResolverOutputHandler } from "./graph-resolver"
|
|
4
3
|
import { InputResolver, type InputResolverNode, type InputResolverOutput } from "./input"
|
|
5
4
|
import { type InputHashNode, type InputHashOutput, InputHashResolver } from "./input-hash"
|
|
6
|
-
import { StateResolver } from "./state"
|
|
7
5
|
import { type ValidationNode, type ValidationOutput, ValidationResolver } from "./validation"
|
|
8
6
|
|
|
9
7
|
export type GraphResolverType = keyof GraphResolverMap
|
|
@@ -12,15 +10,12 @@ export type GraphResolverMap = {
|
|
|
12
10
|
InputResolver: [InputResolverNode, InputResolverOutput]
|
|
13
11
|
InputHashResolver: [InputHashNode, InputHashOutput]
|
|
14
12
|
ValidationResolver: [ValidationNode, ValidationOutput]
|
|
15
|
-
// biome-ignore lint/suspicious/noConfusingVoidType: it is return type
|
|
16
|
-
StateResolver: [InstanceState, void]
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
export const resolverFactories = {
|
|
20
16
|
InputResolver,
|
|
21
17
|
InputHashResolver,
|
|
22
18
|
ValidationResolver,
|
|
23
|
-
StateResolver,
|
|
24
19
|
} as Record<
|
|
25
20
|
GraphResolverType,
|
|
26
21
|
new (
|