@highstate/backend 0.18.0 → 0.20.0
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-JT4KWE3B.js → chunk-52MY2TCE.js} +348 -19
- package/dist/chunk-52MY2TCE.js.map +1 -0
- package/dist/{chunk-I7BWSAN6.js → chunk-UAWBPTDW.js} +3 -3
- package/dist/{chunk-I7BWSAN6.js.map → chunk-UAWBPTDW.js.map} +1 -1
- package/dist/highstate.manifest.json +4 -4
- package/dist/index.js +4159 -785
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +5 -2
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +2 -2
- package/package.json +7 -7
- package/prisma/backend/_schema/object.prisma +12 -0
- package/prisma/backend/sqlite/migrations/20260222113554_add_object_tracking/migration.sql +7 -0
- package/prisma/project/artifact.prisma +3 -0
- package/prisma/project/entity.prisma +125 -0
- package/prisma/project/instance.prisma +6 -0
- package/prisma/project/migrations/20260301210131_add_entity_tracking/migration.sql +70 -0
- package/prisma/project/migrations/20260302212734_add_resource_hooks_flag/migration.sql +1 -0
- package/prisma/project/operation.prisma +3 -0
- package/src/business/artifact.test.ts +22 -2
- package/src/business/artifact.ts +7 -1
- package/src/business/entity-snapshot.test.ts +684 -0
- package/src/business/entity-snapshot.ts +904 -0
- package/src/business/evaluation.test.ts +56 -0
- package/src/business/evaluation.ts +102 -22
- package/src/business/global-search.test.ts +344 -0
- package/src/business/global-search.ts +902 -0
- package/src/business/index.ts +4 -0
- package/src/business/instance-lock.ts +58 -74
- package/src/business/instance-state.test.ts +15 -1
- package/src/business/instance-state.ts +37 -14
- package/src/business/object-ref-index.test.ts +140 -0
- package/src/business/object-ref-index.ts +193 -0
- package/src/business/operation.test.ts +15 -1
- package/src/business/operation.ts +4 -0
- package/src/business/project-model.ts +154 -13
- package/src/business/project-unlock.ts +25 -2
- package/src/business/project.ts +9 -0
- package/src/business/secret.test.ts +35 -2
- package/src/business/secret.ts +32 -9
- package/src/business/settings.ts +761 -0
- package/src/business/unit-output.test.ts +477 -0
- package/src/business/unit-output.ts +461 -0
- package/src/business/worker.ts +55 -4
- package/src/database/_generated/backend/postgresql/browser.ts +6 -0
- package/src/database/_generated/backend/postgresql/client.ts +6 -0
- package/src/database/_generated/backend/postgresql/internal/class.ts +23 -5
- package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +89 -5
- package/src/database/_generated/backend/postgresql/internal/prismaNamespaceBrowser.ts +9 -0
- package/src/database/_generated/backend/postgresql/models/Object.ts +1076 -0
- package/src/database/_generated/backend/postgresql/models.ts +1 -0
- package/src/database/_generated/backend/sqlite/browser.ts +6 -0
- package/src/database/_generated/backend/sqlite/client.ts +6 -0
- package/src/database/_generated/backend/sqlite/internal/class.ts +23 -5
- package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +89 -5
- package/src/database/_generated/backend/sqlite/internal/prismaNamespaceBrowser.ts +9 -0
- package/src/database/_generated/backend/sqlite/models/Object.ts +1074 -0
- package/src/database/_generated/backend/sqlite/models.ts +1 -0
- package/src/database/_generated/project/browser.ts +23 -0
- package/src/database/_generated/project/client.ts +23 -0
- package/src/database/_generated/project/commonInputTypes.ts +87 -53
- package/src/database/_generated/project/enums.ts +8 -0
- package/src/database/_generated/project/internal/class.ts +53 -5
- package/src/database/_generated/project/internal/prismaNamespace.ts +367 -13
- package/src/database/_generated/project/internal/prismaNamespaceBrowser.ts +48 -1
- package/src/database/_generated/project/models/Artifact.ts +199 -11
- package/src/database/_generated/project/models/Entity.ts +1274 -0
- package/src/database/_generated/project/models/EntitySnapshot.ts +2389 -0
- package/src/database/_generated/project/models/EntitySnapshotContent.ts +1260 -0
- package/src/database/_generated/project/models/EntitySnapshotReference.ts +1449 -0
- package/src/database/_generated/project/models/InstanceState.ts +361 -1
- package/src/database/_generated/project/models/Operation.ts +148 -3
- package/src/database/_generated/project/models/OperationLog.ts +0 -4
- package/src/database/_generated/project/models.ts +4 -0
- package/src/database/migration.ts +3 -0
- package/src/library/worker/evaluator.ts +7 -1
- package/src/orchestrator/manager.ts +7 -0
- package/src/orchestrator/operation-context.captured-outputs.test.ts +118 -0
- package/src/orchestrator/operation-context.ts +154 -16
- package/src/orchestrator/operation-plan.destroy.test.md +33 -12
- package/src/orchestrator/operation-plan.destroy.test.ts +140 -2
- package/src/orchestrator/operation-plan.fixtures.ts +2 -0
- package/src/orchestrator/operation-plan.md +4 -1
- package/src/orchestrator/operation-plan.ts +286 -92
- package/src/orchestrator/operation-plan.update.test.md +286 -11
- package/src/orchestrator/operation-plan.update.test.ts +656 -5
- package/src/orchestrator/operation-workset.ts +72 -22
- package/src/orchestrator/operation.cancel.test.ts +4 -0
- package/src/orchestrator/operation.composite.test.ts +341 -0
- package/src/orchestrator/operation.destroy.test.ts +4 -0
- package/src/orchestrator/operation.output-validation.failure.test.ts +124 -0
- package/src/orchestrator/operation.preview.test.ts +4 -0
- package/src/orchestrator/operation.refresh.test.ts +4 -0
- package/src/orchestrator/operation.test-utils.ts +52 -13
- package/src/orchestrator/operation.ts +228 -68
- package/src/orchestrator/operation.update.failure.test.ts +4 -0
- package/src/orchestrator/operation.update.skip.test.ts +110 -0
- package/src/orchestrator/operation.update.test.ts +4 -0
- package/src/orchestrator/plan-test-builder.ts +1 -0
- package/src/orchestrator/unit-input-values.test.ts +450 -0
- package/src/orchestrator/unit-input-values.ts +281 -0
- package/src/pubsub/manager.ts +3 -0
- package/src/runner/abstractions.ts +23 -54
- package/src/runner/local.ts +109 -85
- package/src/services.ts +52 -1
- package/src/shared/models/prisma.ts +1 -0
- package/src/shared/models/project/entity.ts +121 -0
- package/src/shared/models/project/index.ts +1 -0
- package/src/shared/models/project/operation.ts +61 -3
- package/src/shared/models/project/state.ts +10 -0
- package/src/shared/models/project/worker.ts +7 -0
- package/src/shared/resolvers/effective-output-type.test.ts +494 -0
- package/src/shared/resolvers/effective-output-type.ts +162 -0
- package/src/shared/resolvers/index.ts +1 -0
- package/src/shared/resolvers/input.ts +61 -9
- package/src/shared/utils/index.ts +1 -0
- package/src/shared/utils/stable-json.ts +41 -0
- package/src/terminal/manager.ts +6 -0
- package/src/worker/manager.ts +97 -1
- package/dist/chunk-JT4KWE3B.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Logger } from "pino"
|
|
2
2
|
import type { ArtifactService } from "../artifact"
|
|
3
3
|
import type {
|
|
4
|
+
EntitySnapshotService,
|
|
4
5
|
InstanceLockService,
|
|
5
6
|
InstanceStatePatch,
|
|
6
7
|
InstanceStateService,
|
|
@@ -8,6 +9,7 @@ import type {
|
|
|
8
9
|
ProjectModelService,
|
|
9
10
|
SecretService,
|
|
10
11
|
UnitExtraService,
|
|
12
|
+
UnitOutputService,
|
|
11
13
|
} from "../business"
|
|
12
14
|
import type { Operation, OperationUpdateInput, Project } from "../database"
|
|
13
15
|
import type { LibraryBackend } from "../library"
|
|
@@ -18,7 +20,7 @@ import {
|
|
|
18
20
|
parseInstanceId,
|
|
19
21
|
type TriggerInvocation,
|
|
20
22
|
type UnitConfig,
|
|
21
|
-
type
|
|
23
|
+
type UnitInputValue,
|
|
22
24
|
type VersionedName,
|
|
23
25
|
} from "@highstate/contract"
|
|
24
26
|
import { createId } from "@paralleldrive/cuid2"
|
|
@@ -30,15 +32,18 @@ import {
|
|
|
30
32
|
type OperationPhase,
|
|
31
33
|
PromiseTracker,
|
|
32
34
|
type ResolvedInstanceInput,
|
|
35
|
+
resolveEffectiveOutputType,
|
|
33
36
|
waitAll,
|
|
34
37
|
} from "../shared"
|
|
35
38
|
import { OperationContext } from "./operation-context"
|
|
36
39
|
import { createOperationPlan } from "./operation-plan"
|
|
37
40
|
import { OperationWorkset } from "./operation-workset"
|
|
41
|
+
import { resolveUnitInputValues } from "./unit-input-values"
|
|
38
42
|
|
|
39
43
|
export class RuntimeOperation {
|
|
40
44
|
private readonly instancePromiseMap = new Map<InstanceId, Promise<void>>()
|
|
41
45
|
private readonly promiseTracker = new PromiseTracker()
|
|
46
|
+
private readonly pendingGhostDeletionIds = new Set<InstanceId>()
|
|
42
47
|
|
|
43
48
|
private workset!: OperationWorkset
|
|
44
49
|
private context!: OperationContext
|
|
@@ -57,6 +62,8 @@ export class RuntimeOperation {
|
|
|
57
62
|
private readonly instanceStateService: InstanceStateService,
|
|
58
63
|
private readonly projectModelService: ProjectModelService,
|
|
59
64
|
private readonly unitExtraService: UnitExtraService,
|
|
65
|
+
private readonly entitySnapshotService: EntitySnapshotService,
|
|
66
|
+
private readonly unitOutputService: UnitOutputService,
|
|
60
67
|
private readonly logger: Logger,
|
|
61
68
|
) {}
|
|
62
69
|
|
|
@@ -92,17 +99,21 @@ export class RuntimeOperation {
|
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
this.logger.error({ error }, "an error occurred while running the operation")
|
|
102
|
+
console.error(error)
|
|
95
103
|
|
|
96
104
|
await this.updateOperation({ status: "failed" })
|
|
97
105
|
await this.writeOperationLog(errorToString(error))
|
|
98
106
|
} finally {
|
|
99
107
|
try {
|
|
108
|
+
this.promiseTracker.track(this.flushGhostInstanceDeletions())
|
|
100
109
|
this.promiseTracker.track(this.ensureInstancesUnlocked())
|
|
101
110
|
this.promiseTracker.track(this.ensureOperationStatesFinalized())
|
|
102
111
|
|
|
103
112
|
// ensure that all promises are resolved even if the operation failed
|
|
104
113
|
await this.promiseTracker.waitForAll()
|
|
105
114
|
} catch (error) {
|
|
115
|
+
console.error(error)
|
|
116
|
+
|
|
106
117
|
this.logger.error(
|
|
107
118
|
{ error },
|
|
108
119
|
"one of the tracked promises failed after the operation failed",
|
|
@@ -111,6 +122,17 @@ export class RuntimeOperation {
|
|
|
111
122
|
}
|
|
112
123
|
}
|
|
113
124
|
|
|
125
|
+
private async flushGhostInstanceDeletions(): Promise<void> {
|
|
126
|
+
if (this.pendingGhostDeletionIds.size === 0) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const instanceIds = Array.from(this.pendingGhostDeletionIds)
|
|
131
|
+
this.pendingGhostDeletionIds.clear()
|
|
132
|
+
|
|
133
|
+
this.instanceStateService.publishGhostInstanceDeletion(this.project.id, instanceIds)
|
|
134
|
+
}
|
|
135
|
+
|
|
114
136
|
private async operate(): Promise<void> {
|
|
115
137
|
this.logger.info("starting operation")
|
|
116
138
|
|
|
@@ -120,6 +142,7 @@ export class RuntimeOperation {
|
|
|
120
142
|
this.libraryBackend,
|
|
121
143
|
this.instanceStateService,
|
|
122
144
|
this.projectModelService,
|
|
145
|
+
this.entitySnapshotService,
|
|
123
146
|
this.logger,
|
|
124
147
|
)
|
|
125
148
|
|
|
@@ -143,6 +166,7 @@ export class RuntimeOperation {
|
|
|
143
166
|
this.workset = new OperationWorkset(
|
|
144
167
|
this.project,
|
|
145
168
|
this.operation.id,
|
|
169
|
+
this.operation.type,
|
|
146
170
|
plan,
|
|
147
171
|
this.context,
|
|
148
172
|
this.instanceStateService,
|
|
@@ -205,7 +229,7 @@ export class RuntimeOperation {
|
|
|
205
229
|
|
|
206
230
|
// lauch all instances in this phase
|
|
207
231
|
for (const instanceId of this.workset.phaseAffectedInstanceIds) {
|
|
208
|
-
const instance = this.
|
|
232
|
+
const instance = this.getPhaseInstance(instanceId)
|
|
209
233
|
const state = this.context.getState(instanceId)
|
|
210
234
|
const promise = this.getInstancePromiseForOperation(instance, state)
|
|
211
235
|
|
|
@@ -291,7 +315,7 @@ export class RuntimeOperation {
|
|
|
291
315
|
const secrets = await this.secretService.getInstanceSecretValues(this.project.id, state.id)
|
|
292
316
|
signal.throwIfAborted()
|
|
293
317
|
|
|
294
|
-
const config = this.prepareUnitConfig(instance, secrets)
|
|
318
|
+
const config = this.prepareUnitConfig(instance, state.id, secrets)
|
|
295
319
|
const artifactIds = this.collectArtifactIdsForInstance(instance)
|
|
296
320
|
const artifacts = await this.artifactService.getArtifactsByIds(this.project.id, artifactIds)
|
|
297
321
|
|
|
@@ -299,13 +323,13 @@ export class RuntimeOperation {
|
|
|
299
323
|
|
|
300
324
|
await this.runnerBackend.preview({
|
|
301
325
|
projectId: this.project.id,
|
|
326
|
+
operationId: this.operation.id,
|
|
302
327
|
libraryId: this.project.libraryId,
|
|
303
328
|
stateId: state.id,
|
|
304
329
|
instanceType: instance.type,
|
|
305
330
|
instanceName: instance.name,
|
|
306
331
|
config,
|
|
307
332
|
refresh: this.operation.options.refresh,
|
|
308
|
-
secrets,
|
|
309
333
|
artifacts,
|
|
310
334
|
signal,
|
|
311
335
|
forceSignal,
|
|
@@ -351,7 +375,7 @@ export class RuntimeOperation {
|
|
|
351
375
|
for (const child of children) {
|
|
352
376
|
logger.debug(`waiting for child "%s"`, child)
|
|
353
377
|
|
|
354
|
-
const instance = this.
|
|
378
|
+
const instance = this.getPhaseInstance(child)
|
|
355
379
|
const state = this.context.getState(child)
|
|
356
380
|
const promise = this.getInstancePromiseForOperation(instance, state)
|
|
357
381
|
|
|
@@ -406,7 +430,8 @@ export class RuntimeOperation {
|
|
|
406
430
|
// ignore when side effects are requested
|
|
407
431
|
!this.operation.options.refresh &&
|
|
408
432
|
!this.operation.options.deleteUnreachableResources &&
|
|
409
|
-
!this.operation.options.forceUpdateDependencies
|
|
433
|
+
!this.operation.options.forceUpdateDependencies &&
|
|
434
|
+
!this.operation.options.forceUpdateChildren
|
|
410
435
|
) {
|
|
411
436
|
const expected = await this.context.getUpToDateInputHashOutput(instance)
|
|
412
437
|
|
|
@@ -426,6 +451,7 @@ export class RuntimeOperation {
|
|
|
426
451
|
},
|
|
427
452
|
instanceState: {
|
|
428
453
|
inputHash: expected.inputHash,
|
|
454
|
+
parentId: instance.parentId ? this.context.getState(instance.parentId).id : null,
|
|
429
455
|
},
|
|
430
456
|
})
|
|
431
457
|
|
|
@@ -448,7 +474,7 @@ export class RuntimeOperation {
|
|
|
448
474
|
|
|
449
475
|
signal.throwIfAborted()
|
|
450
476
|
|
|
451
|
-
const config = this.prepareUnitConfig(instance, secrets)
|
|
477
|
+
const config = this.prepareUnitConfig(instance, state.id, secrets)
|
|
452
478
|
|
|
453
479
|
// collect artifacts authorized for this instance
|
|
454
480
|
const artifactIds = this.collectArtifactIdsForInstance(instance)
|
|
@@ -458,6 +484,7 @@ export class RuntimeOperation {
|
|
|
458
484
|
|
|
459
485
|
await this.runnerBackend.update({
|
|
460
486
|
projectId: this.project.id,
|
|
487
|
+
operationId: this.operation.id,
|
|
461
488
|
libraryId: this.project.libraryId,
|
|
462
489
|
stateId: state.id,
|
|
463
490
|
instanceType: instance.type,
|
|
@@ -465,7 +492,6 @@ export class RuntimeOperation {
|
|
|
465
492
|
config,
|
|
466
493
|
refresh: this.operation.options.refresh,
|
|
467
494
|
deleteUnreachable: this.operation.options.deleteUnreachableResources,
|
|
468
|
-
secrets,
|
|
469
495
|
artifacts,
|
|
470
496
|
signal,
|
|
471
497
|
forceSignal,
|
|
@@ -541,14 +567,14 @@ export class RuntimeOperation {
|
|
|
541
567
|
|
|
542
568
|
await this.runnerBackend.update({
|
|
543
569
|
projectId: this.project.id,
|
|
570
|
+
operationId: this.operation.id,
|
|
544
571
|
stateId: state.id,
|
|
545
572
|
libraryId: this.project.libraryId,
|
|
546
573
|
instanceType: instance.type,
|
|
547
574
|
instanceName: instance.name,
|
|
548
|
-
config: this.prepareUnitConfig(instance, secrets, invokedTriggers),
|
|
575
|
+
config: this.prepareUnitConfig(instance, state.id, secrets, invokedTriggers),
|
|
549
576
|
refresh: this.operation.options.refresh,
|
|
550
577
|
deleteUnreachable: this.operation.options.deleteUnreachableResources,
|
|
551
|
-
secrets,
|
|
552
578
|
signal,
|
|
553
579
|
forceSignal,
|
|
554
580
|
debug: this.operation.options.debug,
|
|
@@ -571,7 +597,7 @@ export class RuntimeOperation {
|
|
|
571
597
|
continue
|
|
572
598
|
}
|
|
573
599
|
|
|
574
|
-
const instance = this.
|
|
600
|
+
const instance = this.getPhaseInstance(dependent.instanceId)
|
|
575
601
|
dependentPromises.push(this.getInstancePromiseForOperation(instance, dependent))
|
|
576
602
|
}
|
|
577
603
|
|
|
@@ -604,6 +630,7 @@ export class RuntimeOperation {
|
|
|
604
630
|
|
|
605
631
|
await this.runnerBackend.destroy({
|
|
606
632
|
projectId: this.project.id,
|
|
633
|
+
operationId: this.operation.id,
|
|
607
634
|
stateId: state.id,
|
|
608
635
|
libraryId: this.project.libraryId,
|
|
609
636
|
instanceType: type,
|
|
@@ -613,6 +640,7 @@ export class RuntimeOperation {
|
|
|
613
640
|
forceSignal,
|
|
614
641
|
deleteUnreachable: this.operation.options.deleteUnreachableResources,
|
|
615
642
|
forceDeleteState: this.operation.options.forceDeleteState,
|
|
643
|
+
hasResourceHooks: state.hasResourceHooks ?? false,
|
|
616
644
|
debug: this.operation.options.debug,
|
|
617
645
|
})
|
|
618
646
|
|
|
@@ -639,6 +667,7 @@ export class RuntimeOperation {
|
|
|
639
667
|
|
|
640
668
|
await this.runnerBackend.refresh({
|
|
641
669
|
projectId: this.project.id,
|
|
670
|
+
operationId: this.operation.id,
|
|
642
671
|
stateId: state.id,
|
|
643
672
|
libraryId: this.project.libraryId,
|
|
644
673
|
instanceType: type,
|
|
@@ -663,6 +692,7 @@ export class RuntimeOperation {
|
|
|
663
692
|
): Promise<void> {
|
|
664
693
|
const stream = this.runnerBackend.watch({
|
|
665
694
|
projectId: this.project.id,
|
|
695
|
+
operationId: this.operation.id,
|
|
666
696
|
stateId: state.id,
|
|
667
697
|
libraryId: this.project.libraryId,
|
|
668
698
|
instanceType,
|
|
@@ -673,12 +703,18 @@ export class RuntimeOperation {
|
|
|
673
703
|
let update: UnitStateUpdate | undefined
|
|
674
704
|
|
|
675
705
|
for await (update of stream) {
|
|
706
|
+
let handlerError: Error | null = null
|
|
707
|
+
|
|
676
708
|
try {
|
|
677
|
-
await this.handleUnitStateUpdate(update, state)
|
|
709
|
+
handlerError = await this.handleUnitStateUpdate(update, state)
|
|
678
710
|
} catch (error) {
|
|
679
711
|
logger.error({ error }, "failed to handle unit state update")
|
|
680
712
|
}
|
|
681
713
|
|
|
714
|
+
if (handlerError) {
|
|
715
|
+
throw handlerError
|
|
716
|
+
}
|
|
717
|
+
|
|
682
718
|
if (update.type === "error") {
|
|
683
719
|
// rethrow the error to stop the execution of dependent units
|
|
684
720
|
throw new Error(
|
|
@@ -698,80 +734,121 @@ export class RuntimeOperation {
|
|
|
698
734
|
|
|
699
735
|
private prepareUnitConfig(
|
|
700
736
|
instance: InstanceModel,
|
|
737
|
+
stateId: string,
|
|
701
738
|
secrets: Record<string, unknown>,
|
|
702
739
|
invokedTriggers: TriggerInvocation[] = [],
|
|
703
740
|
): UnitConfig {
|
|
704
741
|
const resolvedInputs = this.context.getResolvedInputs(instance.id)
|
|
742
|
+
const component = this.context.library.components[instance.type]!
|
|
743
|
+
|
|
744
|
+
const unfoldedInputs = mapValues(resolvedInputs ?? {}, (input, inputName) =>
|
|
745
|
+
input.flatMap(value => this.getUnitInputValues(inputName, value)),
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
for (const [inputName, inputSpec] of Object.entries(component.inputs)) {
|
|
749
|
+
if (inputSpec.multiple) {
|
|
750
|
+
continue
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const values = unfoldedInputs[inputName] ?? []
|
|
754
|
+
if (values.length > 1) {
|
|
755
|
+
throw new Error(
|
|
756
|
+
`Input "${inputName}" of instance "${instance.id}" expects a single value, but ${values.length} values were resolved after unfolding.`,
|
|
757
|
+
)
|
|
758
|
+
}
|
|
759
|
+
}
|
|
705
760
|
|
|
706
761
|
return {
|
|
707
762
|
instanceId: instance.id,
|
|
763
|
+
stateId,
|
|
708
764
|
args: instance.args ?? {},
|
|
709
|
-
inputs:
|
|
710
|
-
input.map(value => this.getUnitInputRef(inputName, value)),
|
|
711
|
-
),
|
|
765
|
+
inputs: unfoldedInputs,
|
|
712
766
|
invokedTriggers,
|
|
713
|
-
|
|
714
|
-
stateIdMap: this.context.getInstanceIdToStateIdMap(instance.id),
|
|
767
|
+
secretValues: secrets,
|
|
715
768
|
importBasePath: this.libraryBackend.importPath,
|
|
716
769
|
}
|
|
717
770
|
}
|
|
718
771
|
|
|
719
|
-
private
|
|
720
|
-
const
|
|
721
|
-
const
|
|
772
|
+
private getUnitInputValues(inputName: string, input: ResolvedInstanceInput): UnitInputValue[] {
|
|
773
|
+
const dependencyInstance = this.context.getInstance(input.input.instanceId)
|
|
774
|
+
const captured = this.context.getCapturedOutputValues(
|
|
775
|
+
input.input.instanceId,
|
|
776
|
+
input.input.output,
|
|
777
|
+
)
|
|
722
778
|
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
throw new Error(
|
|
726
|
-
`Output "${input.input.output}" is not defined on component "${instance.type}"`,
|
|
727
|
-
)
|
|
728
|
-
}
|
|
779
|
+
const dependencyComponent = this.context.library.components[dependencyInstance.type]
|
|
780
|
+
const fallbackType = dependencyComponent?.outputs[input.input.output]?.type ?? input.type
|
|
729
781
|
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
782
|
+
const getInstanceContext = (instanceId: string) => {
|
|
783
|
+
const resolvedOutput = this.context.inputResolver.outputs.get(`instance:${instanceId}`)
|
|
784
|
+
if (resolvedOutput && resolvedOutput.kind === "instance") {
|
|
785
|
+
return {
|
|
786
|
+
instance: resolvedOutput.instance,
|
|
787
|
+
component: resolvedOutput.component,
|
|
788
|
+
entities: resolvedOutput.entities,
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
try {
|
|
793
|
+
const instance = this.context.getInstance(instanceId as InstanceId)
|
|
794
|
+
const component = this.context.library.components[instance.type]
|
|
734
795
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
796
|
+
if (!component) {
|
|
797
|
+
return undefined
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
instance,
|
|
802
|
+
component,
|
|
803
|
+
entities: this.context.library.entities,
|
|
804
|
+
}
|
|
805
|
+
} catch {
|
|
806
|
+
return undefined
|
|
740
807
|
}
|
|
741
808
|
}
|
|
742
809
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
)
|
|
749
|
-
}
|
|
810
|
+
const effectiveOutputType = resolveEffectiveOutputType({
|
|
811
|
+
input: input.input,
|
|
812
|
+
fallbackType,
|
|
813
|
+
getInstanceContext,
|
|
814
|
+
})
|
|
750
815
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
816
|
+
const effectiveRootOutputType = resolveEffectiveOutputType({
|
|
817
|
+
input: {
|
|
818
|
+
...input.input,
|
|
819
|
+
path: undefined,
|
|
820
|
+
},
|
|
821
|
+
fallbackType,
|
|
822
|
+
getInstanceContext,
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
return resolveUnitInputValues({
|
|
826
|
+
library: this.context.library,
|
|
827
|
+
inputName,
|
|
828
|
+
resolvedInput: input,
|
|
829
|
+
dependencyInstanceType: dependencyInstance.type,
|
|
830
|
+
captured,
|
|
831
|
+
effectiveOutputType,
|
|
832
|
+
effectiveRootOutputType,
|
|
833
|
+
})
|
|
756
834
|
}
|
|
757
835
|
|
|
758
836
|
private async handleUnitStateUpdate(
|
|
759
837
|
update: UnitStateUpdate,
|
|
760
838
|
state: InstanceState,
|
|
761
|
-
): Promise<
|
|
839
|
+
): Promise<Error | null> {
|
|
762
840
|
switch (update.type) {
|
|
763
841
|
case "message":
|
|
764
842
|
this.handleUnitMessage(update, state)
|
|
765
|
-
return
|
|
843
|
+
return null
|
|
766
844
|
case "progress":
|
|
767
845
|
await this.handleUnitProgress(update)
|
|
768
|
-
return
|
|
846
|
+
return null
|
|
769
847
|
case "error":
|
|
770
848
|
await this.handleUnitError(update, state)
|
|
771
|
-
return
|
|
849
|
+
return null
|
|
772
850
|
case "completion":
|
|
773
|
-
await this.handleUnitCompletion(update, state)
|
|
774
|
-
return
|
|
851
|
+
return await this.handleUnitCompletion(update, state)
|
|
775
852
|
}
|
|
776
853
|
}
|
|
777
854
|
|
|
@@ -811,7 +888,7 @@ export class RuntimeOperation {
|
|
|
811
888
|
private async handleUnitCompletion(
|
|
812
889
|
update: TypedUnitStateUpdate<"completion">,
|
|
813
890
|
state: InstanceState,
|
|
814
|
-
): Promise<
|
|
891
|
+
): Promise<Error | null> {
|
|
815
892
|
if (this.operation.type === "preview") {
|
|
816
893
|
await this.workset.updateState(update.unitId, {
|
|
817
894
|
operationState: {
|
|
@@ -819,24 +896,75 @@ export class RuntimeOperation {
|
|
|
819
896
|
finishedAt: new Date(),
|
|
820
897
|
},
|
|
821
898
|
})
|
|
822
|
-
return
|
|
899
|
+
return null
|
|
823
900
|
}
|
|
824
901
|
|
|
825
|
-
const instance = this.
|
|
902
|
+
const instance = this.getPhaseInstance(update.unitId)
|
|
903
|
+
|
|
904
|
+
if (update.rawOutputs && update.operationType !== "destroy") {
|
|
905
|
+
this.context.updateCapturedOutputValuesFromUnitOutputs({
|
|
906
|
+
instanceId: instance.id,
|
|
907
|
+
instanceType: instance.type,
|
|
908
|
+
outputs: update.rawOutputs,
|
|
909
|
+
})
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const parsed = update.rawOutputs
|
|
913
|
+
? await this.unitOutputService.parseUnitOutputs({
|
|
914
|
+
libraryId: this.context.project.libraryId,
|
|
915
|
+
instanceType: instance.type,
|
|
916
|
+
outputs: update.rawOutputs,
|
|
917
|
+
})
|
|
918
|
+
: {
|
|
919
|
+
outputHash: null,
|
|
920
|
+
statusFields: null,
|
|
921
|
+
terminals: null,
|
|
922
|
+
pages: null,
|
|
923
|
+
triggers: null,
|
|
924
|
+
secrets: null,
|
|
925
|
+
workers: null,
|
|
926
|
+
exportedArtifactIds: null,
|
|
927
|
+
hasResourceHooks: false,
|
|
928
|
+
entitySnapshotError: null,
|
|
929
|
+
entitySnapshotPayload: null,
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (parsed.entitySnapshotError) {
|
|
933
|
+
await this.operationService.appendLog(
|
|
934
|
+
this.project.id,
|
|
935
|
+
this.operation.id,
|
|
936
|
+
state.id,
|
|
937
|
+
`Failed to parse unit outputs: ${parsed.entitySnapshotError}`,
|
|
938
|
+
)
|
|
939
|
+
|
|
940
|
+
await this.workset.updateState(update.unitId, {
|
|
941
|
+
instanceState: {
|
|
942
|
+
status: "failed",
|
|
943
|
+
},
|
|
944
|
+
operationState: {
|
|
945
|
+
status: "failed",
|
|
946
|
+
finishedAt: new Date(),
|
|
947
|
+
},
|
|
948
|
+
})
|
|
949
|
+
|
|
950
|
+
return new Error(
|
|
951
|
+
`Failed to parse unit outputs for unit "${instance.id}": ${parsed.entitySnapshotError}`,
|
|
952
|
+
)
|
|
953
|
+
}
|
|
826
954
|
|
|
827
955
|
const data: InstanceStatePatch = {
|
|
828
956
|
status: this.workset.getNextStableInstanceStatus(instance.id),
|
|
829
|
-
statusFields:
|
|
957
|
+
statusFields: parsed.statusFields,
|
|
830
958
|
}
|
|
831
959
|
|
|
832
|
-
const artifactIds =
|
|
833
|
-
? Object.values(
|
|
960
|
+
const artifactIds = parsed.exportedArtifactIds
|
|
961
|
+
? Object.values(parsed.exportedArtifactIds).flat()
|
|
834
962
|
: []
|
|
835
963
|
|
|
836
964
|
if (update.operationType !== "destroy") {
|
|
837
965
|
// давайте еще больше усложним и без того сложную штуку
|
|
838
966
|
// set output hash before calculating input hash to capture up-to-date output hash for dependencies
|
|
839
|
-
state.outputHash =
|
|
967
|
+
state.outputHash = parsed.outputHash
|
|
840
968
|
|
|
841
969
|
// recalculate the input and output hashes for the instance
|
|
842
970
|
const { selfHash, inputHash, dependencyOutputHash } =
|
|
@@ -845,9 +973,13 @@ export class RuntimeOperation {
|
|
|
845
973
|
data.selfHash = selfHash
|
|
846
974
|
data.inputHash = inputHash
|
|
847
975
|
data.dependencyOutputHash = dependencyOutputHash
|
|
848
|
-
data.outputHash =
|
|
976
|
+
data.outputHash = parsed.outputHash
|
|
849
977
|
|
|
850
|
-
data.exportedArtifactIds =
|
|
978
|
+
data.exportedArtifactIds = parsed.exportedArtifactIds
|
|
979
|
+
|
|
980
|
+
if (update.rawOutputs) {
|
|
981
|
+
data.hasResourceHooks = parsed.hasResourceHooks
|
|
982
|
+
}
|
|
851
983
|
|
|
852
984
|
// also update the parent ID
|
|
853
985
|
if (instance.parentId) {
|
|
@@ -865,6 +997,7 @@ export class RuntimeOperation {
|
|
|
865
997
|
data.model = null
|
|
866
998
|
data.resolvedInputs = null
|
|
867
999
|
data.exportedArtifactIds = null
|
|
1000
|
+
data.hasResourceHooks = false
|
|
868
1001
|
}
|
|
869
1002
|
|
|
870
1003
|
// update the operation state
|
|
@@ -883,23 +1016,34 @@ export class RuntimeOperation {
|
|
|
883
1016
|
// also do not write unit extra data for non-last phases of the instance
|
|
884
1017
|
unitExtra: this.workset.isLastPhaseForInstance(instance.id)
|
|
885
1018
|
? {
|
|
886
|
-
pages:
|
|
887
|
-
terminals:
|
|
888
|
-
triggers:
|
|
889
|
-
workers:
|
|
890
|
-
secrets:
|
|
1019
|
+
pages: parsed.pages ?? [],
|
|
1020
|
+
terminals: parsed.terminals ?? [],
|
|
1021
|
+
triggers: parsed.triggers ?? [],
|
|
1022
|
+
workers: parsed.workers ?? [],
|
|
1023
|
+
secrets: parsed.secrets ?? {},
|
|
891
1024
|
artifactIds,
|
|
892
1025
|
}
|
|
893
1026
|
: undefined,
|
|
894
1027
|
})
|
|
895
1028
|
|
|
1029
|
+
if (update.operationType !== "destroy" && parsed.entitySnapshotPayload) {
|
|
1030
|
+
await this.entitySnapshotService.persistUnitEntitySnapshots({
|
|
1031
|
+
projectId: this.project.id,
|
|
1032
|
+
operationId: this.operation.id,
|
|
1033
|
+
stateId: state.id,
|
|
1034
|
+
payload: parsed.entitySnapshotPayload,
|
|
1035
|
+
})
|
|
1036
|
+
}
|
|
1037
|
+
|
|
896
1038
|
if (
|
|
897
1039
|
update.operationType === "destroy" &&
|
|
898
1040
|
this.workset.isLastPhaseForInstance(instance.id) &&
|
|
899
1041
|
this.context.isGhostInstance(instance.id)
|
|
900
1042
|
) {
|
|
901
|
-
this.
|
|
1043
|
+
this.pendingGhostDeletionIds.add(instance.id)
|
|
902
1044
|
}
|
|
1045
|
+
|
|
1046
|
+
return null
|
|
903
1047
|
}
|
|
904
1048
|
|
|
905
1049
|
private getInstancePromise(
|
|
@@ -1055,4 +1199,20 @@ export class RuntimeOperation {
|
|
|
1055
1199
|
|
|
1056
1200
|
return Array.from(artifactIds)
|
|
1057
1201
|
}
|
|
1202
|
+
|
|
1203
|
+
private getPhaseInstance(instanceId: InstanceId): InstanceModel {
|
|
1204
|
+
const modelInstance = this.context.tryGetInstance(instanceId)
|
|
1205
|
+
if (modelInstance) {
|
|
1206
|
+
return modelInstance
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
if (this.workset.currentPhase === "destroy") {
|
|
1210
|
+
const destroyInstance = this.context.tryGetInstanceForDestroy(instanceId)
|
|
1211
|
+
if (destroyInstance) {
|
|
1212
|
+
return destroyInstance
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
throw new Error(`Instance with ID ${instanceId} not found in the operation context`)
|
|
1217
|
+
}
|
|
1058
1218
|
}
|
|
@@ -18,6 +18,8 @@ describe("Operation - Update Failure", () => {
|
|
|
18
18
|
instanceStateService,
|
|
19
19
|
projectModelService,
|
|
20
20
|
unitExtraService,
|
|
21
|
+
entitySnapshotService,
|
|
22
|
+
unitOutputService,
|
|
21
23
|
createUnit,
|
|
22
24
|
createDeployedUnitState,
|
|
23
25
|
createOperation,
|
|
@@ -60,6 +62,8 @@ describe("Operation - Update Failure", () => {
|
|
|
60
62
|
instanceStateService,
|
|
61
63
|
projectModelService,
|
|
62
64
|
unitExtraService,
|
|
65
|
+
entitySnapshotService,
|
|
66
|
+
unitOutputService,
|
|
63
67
|
logger,
|
|
64
68
|
)
|
|
65
69
|
|