@highstate/backend 0.19.1 → 0.21.1
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-b05q6fm2.js +37 -0
- package/dist/{chunk-V2NILDHS.js → chunk-gxjwa93h.js} +704 -604
- package/dist/{chunk-X2WG3WGL.js → chunk-vzdz6chj.js} +18 -15
- package/dist/highstate.manifest.json +4 -4
- package/dist/index.js +7350 -3514
- package/dist/library/package-resolution-worker.js +121 -10
- package/dist/library/worker/main.js +31 -17
- package/dist/shared/index.js +254 -4
- package/package.json +19 -20
- 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/artifact/factory.ts +3 -2
- 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/find-package-json.test.ts +77 -0
- package/src/library/find-package-json.ts +149 -0
- package/src/library/package-resolution-worker.ts +7 -3
- 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 +230 -68
- package/src/orchestrator/operation.update.failure.test.ts +4 -0
- package/src/orchestrator/operation.update.skip.test.ts +196 -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/factory.ts +3 -3
- package/src/runner/force-abort.ts +7 -2
- package/src/runner/local.ts +116 -87
- package/src/runner/pulumi.ts +3 -5
- package/src/services.ts +53 -2
- 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 +59 -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/terminal/run.sh.ts +9 -4
- package/src/worker/manager.ts +97 -1
- package/LICENSE +0 -21
- package/dist/chunk-I7BWSAN6.js +0 -49
- package/dist/chunk-I7BWSAN6.js.map +0 -1
- package/dist/chunk-V2NILDHS.js.map +0 -1
- package/dist/chunk-X2WG3WGL.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/library/package-resolution-worker.js.map +0 -1
- package/dist/library/worker/main.js.map +0 -1
- package/dist/shared/index.js.map +0 -1
|
@@ -17,6 +17,8 @@ describe("RuntimeOperation - Update Short-Circuit", () => {
|
|
|
17
17
|
instanceStateService,
|
|
18
18
|
projectModelService,
|
|
19
19
|
unitExtraService,
|
|
20
|
+
entitySnapshotService,
|
|
21
|
+
unitOutputService,
|
|
20
22
|
createUnit,
|
|
21
23
|
createDeployedUnitState,
|
|
22
24
|
createContext,
|
|
@@ -62,6 +64,8 @@ describe("RuntimeOperation - Update Short-Circuit", () => {
|
|
|
62
64
|
instanceStateService,
|
|
63
65
|
projectModelService,
|
|
64
66
|
unitExtraService,
|
|
67
|
+
entitySnapshotService,
|
|
68
|
+
unitOutputService,
|
|
65
69
|
logger,
|
|
66
70
|
)
|
|
67
71
|
|
|
@@ -83,6 +87,7 @@ describe("RuntimeOperation - Update Short-Circuit", () => {
|
|
|
83
87
|
const skipOptions = skipCall?.[3]
|
|
84
88
|
expect(skipOptions?.instanceState).toEqual({
|
|
85
89
|
inputHash: expected.inputHash,
|
|
90
|
+
parentId: null,
|
|
86
91
|
})
|
|
87
92
|
|
|
88
93
|
expect(operationService.markOperationFinished).toHaveBeenCalledWith(
|
|
@@ -92,4 +97,195 @@ describe("RuntimeOperation - Update Short-Circuit", () => {
|
|
|
92
97
|
)
|
|
93
98
|
},
|
|
94
99
|
)
|
|
100
|
+
|
|
101
|
+
operationTest(
|
|
102
|
+
"updates parentId during short-circuit when unit moved between composites",
|
|
103
|
+
async ({
|
|
104
|
+
project,
|
|
105
|
+
logger,
|
|
106
|
+
runnerBackend,
|
|
107
|
+
libraryBackend,
|
|
108
|
+
artifactService,
|
|
109
|
+
instanceLockService,
|
|
110
|
+
operationService,
|
|
111
|
+
secretService,
|
|
112
|
+
instanceStateService,
|
|
113
|
+
projectModelService,
|
|
114
|
+
unitExtraService,
|
|
115
|
+
entitySnapshotService,
|
|
116
|
+
unitOutputService,
|
|
117
|
+
createComposite,
|
|
118
|
+
createUnit,
|
|
119
|
+
createDeployedUnitState,
|
|
120
|
+
createContext,
|
|
121
|
+
createOperation,
|
|
122
|
+
setupImmediateLocking,
|
|
123
|
+
setupPersistenceMocks,
|
|
124
|
+
expect,
|
|
125
|
+
}) => {
|
|
126
|
+
// arrange
|
|
127
|
+
const oldParent = createComposite("OldParent")
|
|
128
|
+
const newParent = createComposite("NewParent")
|
|
129
|
+
const unit = {
|
|
130
|
+
...createUnit("A"),
|
|
131
|
+
parentId: newParent.id,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const oldParentState = createDeployedUnitState(oldParent)
|
|
135
|
+
const newParentState = createDeployedUnitState(newParent)
|
|
136
|
+
const state = createDeployedUnitState(unit)
|
|
137
|
+
state.parentInstanceId = oldParent.id
|
|
138
|
+
|
|
139
|
+
const context = await createContext({
|
|
140
|
+
instances: [oldParent, newParent, unit],
|
|
141
|
+
states: [oldParentState, newParentState, state],
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const expected = await context.getUpToDateInputHashOutput(unit)
|
|
145
|
+
state.selfHash = expected.selfHash
|
|
146
|
+
state.dependencyOutputHash = expected.dependencyOutputHash
|
|
147
|
+
instanceStateService.getInstanceStates.mockResolvedValue([
|
|
148
|
+
oldParentState,
|
|
149
|
+
newParentState,
|
|
150
|
+
state,
|
|
151
|
+
])
|
|
152
|
+
|
|
153
|
+
setupImmediateLocking()
|
|
154
|
+
setupPersistenceMocks({ instances: [oldParent, newParent, unit] })
|
|
155
|
+
|
|
156
|
+
const operation = createOperation({
|
|
157
|
+
type: "update",
|
|
158
|
+
requestedInstanceIds: [],
|
|
159
|
+
phases: [
|
|
160
|
+
{
|
|
161
|
+
type: "update",
|
|
162
|
+
instances: [
|
|
163
|
+
{ id: newParent.id, message: "parent", parentId: undefined },
|
|
164
|
+
{ id: unit.id, message: "explicitly requested", parentId: newParent.id },
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const runtimeOperation = new RuntimeOperation(
|
|
171
|
+
project,
|
|
172
|
+
operation,
|
|
173
|
+
runnerBackend,
|
|
174
|
+
libraryBackend,
|
|
175
|
+
artifactService,
|
|
176
|
+
instanceLockService,
|
|
177
|
+
operationService,
|
|
178
|
+
secretService,
|
|
179
|
+
instanceStateService,
|
|
180
|
+
projectModelService,
|
|
181
|
+
unitExtraService,
|
|
182
|
+
entitySnapshotService,
|
|
183
|
+
unitOutputService,
|
|
184
|
+
logger,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
// act
|
|
188
|
+
await runtimeOperation.operateSafe()
|
|
189
|
+
|
|
190
|
+
// assert
|
|
191
|
+
expect(runnerBackend.update).not.toHaveBeenCalled()
|
|
192
|
+
|
|
193
|
+
const skipCall = instanceStateService.updateOperationState.mock.calls.find(
|
|
194
|
+
([, stateId, , options]) =>
|
|
195
|
+
stateId === unit.id && options.operationState?.status === "skipped",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
expect(skipCall).toBeDefined()
|
|
199
|
+
expect(skipCall?.[3].instanceState).toEqual({
|
|
200
|
+
inputHash: expected.inputHash,
|
|
201
|
+
parentId: newParent.id,
|
|
202
|
+
})
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
operationTest(
|
|
207
|
+
"does not short-circuit update for instances destroyed earlier in recreate",
|
|
208
|
+
async ({
|
|
209
|
+
project,
|
|
210
|
+
logger,
|
|
211
|
+
runnerBackend,
|
|
212
|
+
libraryBackend,
|
|
213
|
+
artifactService,
|
|
214
|
+
instanceLockService,
|
|
215
|
+
operationService,
|
|
216
|
+
secretService,
|
|
217
|
+
instanceStateService,
|
|
218
|
+
projectModelService,
|
|
219
|
+
unitExtraService,
|
|
220
|
+
entitySnapshotService,
|
|
221
|
+
unitOutputService,
|
|
222
|
+
createUnit,
|
|
223
|
+
createDeployedUnitState,
|
|
224
|
+
createContext,
|
|
225
|
+
createOperation,
|
|
226
|
+
setupImmediateLocking,
|
|
227
|
+
setupPersistenceMocks,
|
|
228
|
+
expect,
|
|
229
|
+
}) => {
|
|
230
|
+
// arrange
|
|
231
|
+
const unit = createUnit("A")
|
|
232
|
+
const state = createDeployedUnitState(unit)
|
|
233
|
+
|
|
234
|
+
const context = await createContext({ instances: [unit], states: [state] })
|
|
235
|
+
|
|
236
|
+
const expected = await context.getUpToDateInputHashOutput(unit)
|
|
237
|
+
state.selfHash = expected.selfHash
|
|
238
|
+
state.dependencyOutputHash = expected.dependencyOutputHash
|
|
239
|
+
instanceStateService.getInstanceStates.mockResolvedValue([state])
|
|
240
|
+
|
|
241
|
+
setupImmediateLocking()
|
|
242
|
+
setupPersistenceMocks({ instances: [unit] })
|
|
243
|
+
|
|
244
|
+
const operation = createOperation({
|
|
245
|
+
type: "recreate",
|
|
246
|
+
requestedInstanceIds: [unit.id],
|
|
247
|
+
phases: [
|
|
248
|
+
{
|
|
249
|
+
type: "destroy",
|
|
250
|
+
instances: [{ id: unit.id, message: "explicitly requested", parentId: undefined }],
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
type: "update",
|
|
254
|
+
instances: [{ id: unit.id, message: "explicitly requested", parentId: undefined }],
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const runtimeOperation = new RuntimeOperation(
|
|
260
|
+
project,
|
|
261
|
+
operation,
|
|
262
|
+
runnerBackend,
|
|
263
|
+
libraryBackend,
|
|
264
|
+
artifactService,
|
|
265
|
+
instanceLockService,
|
|
266
|
+
operationService,
|
|
267
|
+
secretService,
|
|
268
|
+
instanceStateService,
|
|
269
|
+
projectModelService,
|
|
270
|
+
unitExtraService,
|
|
271
|
+
entitySnapshotService,
|
|
272
|
+
unitOutputService,
|
|
273
|
+
logger,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
// act
|
|
277
|
+
await runtimeOperation.operateSafe()
|
|
278
|
+
|
|
279
|
+
// assert
|
|
280
|
+
expect(runnerBackend.destroy).toHaveBeenCalledTimes(1)
|
|
281
|
+
expect(runnerBackend.update).toHaveBeenCalledTimes(1)
|
|
282
|
+
|
|
283
|
+
const skipCall = instanceStateService.updateOperationState.mock.calls.find(
|
|
284
|
+
([, stateId, , options]) =>
|
|
285
|
+
stateId === unit.id && options.operationState?.status === "skipped",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
expect(skipCall).toBeUndefined()
|
|
289
|
+
},
|
|
290
|
+
)
|
|
95
291
|
})
|
|
@@ -19,6 +19,8 @@ describe("Operation - Update", () => {
|
|
|
19
19
|
instanceStateService,
|
|
20
20
|
projectModelService,
|
|
21
21
|
unitExtraService,
|
|
22
|
+
entitySnapshotService,
|
|
23
|
+
unitOutputService,
|
|
22
24
|
createUnit,
|
|
23
25
|
createDeployedUnitState,
|
|
24
26
|
createOperation,
|
|
@@ -90,6 +92,8 @@ describe("Operation - Update", () => {
|
|
|
90
92
|
instanceStateService,
|
|
91
93
|
projectModelService,
|
|
92
94
|
unitExtraService,
|
|
95
|
+
entitySnapshotService,
|
|
96
|
+
unitOutputService,
|
|
93
97
|
logger,
|
|
94
98
|
)
|
|
95
99
|
|
|
@@ -101,6 +101,7 @@ export class PlanTestBuilder {
|
|
|
101
101
|
status: "undeployed",
|
|
102
102
|
source: stateType === "ghost" ? "virtual" : "resident",
|
|
103
103
|
kind: instance.kind,
|
|
104
|
+
hasResourceHooks: false,
|
|
104
105
|
parentId: null,
|
|
105
106
|
parentInstanceId: instance.parentId ?? null,
|
|
106
107
|
selfHash: null,
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import type { VersionedName } from "@highstate/contract"
|
|
2
|
+
import type { ResolvedInstanceInput } from "../shared"
|
|
3
|
+
import { describe, expect, test } from "vitest"
|
|
4
|
+
import { resolveUnitInputValues } from "./unit-input-values"
|
|
5
|
+
|
|
6
|
+
describe("resolveUnitInputValues", () => {
|
|
7
|
+
test("returns captured values when input type matches output type", () => {
|
|
8
|
+
const dependencyInstanceId = "component.v1:dep"
|
|
9
|
+
const dependencyInstanceType = "component.v1" satisfies VersionedName
|
|
10
|
+
|
|
11
|
+
const library = {
|
|
12
|
+
components: {
|
|
13
|
+
"component.v1": {
|
|
14
|
+
outputs: {
|
|
15
|
+
out: { type: "parent.v1" },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
entities: {
|
|
20
|
+
"parent.v1": {
|
|
21
|
+
type: "parent.v1",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resolved: ResolvedInstanceInput = {
|
|
27
|
+
input: {
|
|
28
|
+
instanceId: dependencyInstanceId,
|
|
29
|
+
output: "out",
|
|
30
|
+
} as never,
|
|
31
|
+
type: "parent.v1",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const values = resolveUnitInputValues({
|
|
35
|
+
library: library as never,
|
|
36
|
+
inputName: "dep",
|
|
37
|
+
resolvedInput: resolved,
|
|
38
|
+
dependencyInstanceType,
|
|
39
|
+
captured: [{ ok: true, value: { a: 1 } }],
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
expect(values).toEqual([
|
|
43
|
+
{
|
|
44
|
+
value: { a: 1 },
|
|
45
|
+
source: { instanceId: dependencyInstanceId, output: "out" },
|
|
46
|
+
},
|
|
47
|
+
])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("extracts inclusion field when input type differs", () => {
|
|
51
|
+
const dependencyInstanceId = "component.v1:dep"
|
|
52
|
+
const dependencyInstanceType = "component.v1" satisfies VersionedName
|
|
53
|
+
|
|
54
|
+
const library = {
|
|
55
|
+
components: {
|
|
56
|
+
"component.v1": {
|
|
57
|
+
outputs: {
|
|
58
|
+
out: { type: "parent.v1" },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
entities: {
|
|
63
|
+
"parent.v1": {
|
|
64
|
+
type: "parent.v1",
|
|
65
|
+
inclusions: [{ type: "child.v1", field: "child" }],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const resolved: ResolvedInstanceInput = {
|
|
71
|
+
input: {
|
|
72
|
+
instanceId: dependencyInstanceId,
|
|
73
|
+
output: "out",
|
|
74
|
+
} as never,
|
|
75
|
+
type: "child.v1",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const values = resolveUnitInputValues({
|
|
79
|
+
library: library as never,
|
|
80
|
+
inputName: "dep",
|
|
81
|
+
resolvedInput: resolved,
|
|
82
|
+
dependencyInstanceType,
|
|
83
|
+
captured: [{ ok: true, value: { child: { v: 42 }, other: "x" } }],
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
expect(values).toEqual([
|
|
87
|
+
{
|
|
88
|
+
value: { v: 42 },
|
|
89
|
+
source: { instanceId: dependencyInstanceId, output: "out" },
|
|
90
|
+
},
|
|
91
|
+
])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("uses included entity from reconstructed parent value", () => {
|
|
95
|
+
const dependencyInstanceId = "component.v1:dep"
|
|
96
|
+
const dependencyInstanceType = "component.v1" satisfies VersionedName
|
|
97
|
+
|
|
98
|
+
const library = {
|
|
99
|
+
components: {
|
|
100
|
+
"component.v1": {
|
|
101
|
+
outputs: {
|
|
102
|
+
out: { type: "parent.v1" },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
entities: {
|
|
107
|
+
"parent.v1": {
|
|
108
|
+
type: "parent.v1",
|
|
109
|
+
inclusions: [{ type: "child.v1", field: "child" }],
|
|
110
|
+
},
|
|
111
|
+
"child.v1": {
|
|
112
|
+
type: "child.v1",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const resolved: ResolvedInstanceInput = {
|
|
118
|
+
input: {
|
|
119
|
+
instanceId: dependencyInstanceId,
|
|
120
|
+
output: "out",
|
|
121
|
+
} as never,
|
|
122
|
+
type: "child.v1",
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const captured = [
|
|
126
|
+
{
|
|
127
|
+
ok: true as const,
|
|
128
|
+
value: {
|
|
129
|
+
$meta: { type: "parent.v1", identity: "p1" },
|
|
130
|
+
child: { $meta: { type: "child.v1", identity: "c1" }, v: 41 },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
ok: true as const,
|
|
135
|
+
value: {
|
|
136
|
+
$meta: { type: "child.v1", identity: "c1", title: "Child" },
|
|
137
|
+
v: 42,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
const values = resolveUnitInputValues({
|
|
143
|
+
library: library as never,
|
|
144
|
+
inputName: "dep",
|
|
145
|
+
resolvedInput: resolved,
|
|
146
|
+
dependencyInstanceType,
|
|
147
|
+
captured,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
expect(values).toEqual([
|
|
151
|
+
{
|
|
152
|
+
value: { $meta: { type: "child.v1", identity: "c1" }, v: 41 },
|
|
153
|
+
source: { instanceId: dependencyInstanceId, output: "out" },
|
|
154
|
+
},
|
|
155
|
+
])
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test("throws when no matching inclusion exists", () => {
|
|
159
|
+
const dependencyInstanceId = "component.v1:dep"
|
|
160
|
+
const dependencyInstanceType = "component.v1" satisfies VersionedName
|
|
161
|
+
|
|
162
|
+
const library = {
|
|
163
|
+
components: {
|
|
164
|
+
"component.v1": {
|
|
165
|
+
outputs: {
|
|
166
|
+
out: { type: "parent.v1" },
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
entities: {
|
|
171
|
+
"parent.v1": {
|
|
172
|
+
type: "parent.v1",
|
|
173
|
+
inclusions: [{ type: "other.v1", field: "x" }],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const resolved: ResolvedInstanceInput = {
|
|
179
|
+
input: {
|
|
180
|
+
instanceId: dependencyInstanceId,
|
|
181
|
+
output: "out",
|
|
182
|
+
} as never,
|
|
183
|
+
type: "child.v1",
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
expect(() =>
|
|
187
|
+
resolveUnitInputValues({
|
|
188
|
+
library: library as never,
|
|
189
|
+
inputName: "dep",
|
|
190
|
+
resolvedInput: resolved,
|
|
191
|
+
dependencyInstanceType,
|
|
192
|
+
captured: [{ ok: true, value: { child: { v: 1 } } }],
|
|
193
|
+
}),
|
|
194
|
+
).toThrow(/no matching inclusion found/i)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test("returns captured values when effective output type is forwarded subtype", () => {
|
|
198
|
+
const dependencyInstanceId = "component.v1:dep"
|
|
199
|
+
const dependencyInstanceType = "component.v1" satisfies VersionedName
|
|
200
|
+
|
|
201
|
+
const library = {
|
|
202
|
+
components: {
|
|
203
|
+
"component.v1": {
|
|
204
|
+
outputs: {
|
|
205
|
+
out: { type: "parent.v1" },
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
entities: {
|
|
210
|
+
"parent.v1": {
|
|
211
|
+
type: "parent.v1",
|
|
212
|
+
},
|
|
213
|
+
"child.v1": {
|
|
214
|
+
type: "child.v1",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const resolved: ResolvedInstanceInput = {
|
|
220
|
+
input: {
|
|
221
|
+
instanceId: dependencyInstanceId,
|
|
222
|
+
output: "out",
|
|
223
|
+
} as never,
|
|
224
|
+
type: "child.v1",
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const values = resolveUnitInputValues({
|
|
228
|
+
library: library as never,
|
|
229
|
+
inputName: "dep",
|
|
230
|
+
resolvedInput: resolved,
|
|
231
|
+
dependencyInstanceType,
|
|
232
|
+
captured: [{ ok: true, value: { v: 42 } }],
|
|
233
|
+
effectiveOutputType: "child.v1",
|
|
234
|
+
effectiveRootOutputType: "child.v1",
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
expect(values).toEqual([
|
|
238
|
+
{
|
|
239
|
+
value: { v: 42 },
|
|
240
|
+
source: { instanceId: dependencyInstanceId, output: "out" },
|
|
241
|
+
},
|
|
242
|
+
])
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test("throws when captured value is an error", () => {
|
|
246
|
+
const dependencyInstanceId = "component.v1:dep"
|
|
247
|
+
const dependencyInstanceType = "component.v1" satisfies VersionedName
|
|
248
|
+
|
|
249
|
+
const library = {
|
|
250
|
+
components: {
|
|
251
|
+
"component.v1": {
|
|
252
|
+
outputs: {
|
|
253
|
+
out: { type: "parent.v1" },
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
entities: {
|
|
258
|
+
"parent.v1": {
|
|
259
|
+
type: "parent.v1",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const resolved: ResolvedInstanceInput = {
|
|
265
|
+
input: {
|
|
266
|
+
instanceId: dependencyInstanceId,
|
|
267
|
+
output: "out",
|
|
268
|
+
} as never,
|
|
269
|
+
type: "parent.v1",
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
expect(() =>
|
|
273
|
+
resolveUnitInputValues({
|
|
274
|
+
library: library as never,
|
|
275
|
+
inputName: "dep",
|
|
276
|
+
resolvedInput: resolved,
|
|
277
|
+
dependencyInstanceType,
|
|
278
|
+
captured: [
|
|
279
|
+
{
|
|
280
|
+
ok: false,
|
|
281
|
+
error: {
|
|
282
|
+
message: 'Missing required inclusion "child"',
|
|
283
|
+
snapshotId: "snap_1",
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
}),
|
|
288
|
+
).toThrow(/failed to reconstruct/i)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test("extracts values using explicit nested inclusion path", () => {
|
|
292
|
+
const dependencyInstanceId = "component.v1:dep"
|
|
293
|
+
const dependencyInstanceType = "component.v1" satisfies VersionedName
|
|
294
|
+
|
|
295
|
+
const library = {
|
|
296
|
+
components: {
|
|
297
|
+
"component.v1": {
|
|
298
|
+
outputs: {
|
|
299
|
+
out: { type: "network.v1" },
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
entities: {
|
|
304
|
+
"network.v1": {
|
|
305
|
+
type: "network.v1",
|
|
306
|
+
inclusions: [{ type: "peer.v1", field: "peer", multiple: false }],
|
|
307
|
+
},
|
|
308
|
+
"peer.v1": {
|
|
309
|
+
type: "peer.v1",
|
|
310
|
+
inclusions: [{ type: "endpoint.v1", field: "endpoints", multiple: true }],
|
|
311
|
+
},
|
|
312
|
+
"endpoint.v1": {
|
|
313
|
+
type: "endpoint.v1",
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const resolved: ResolvedInstanceInput = {
|
|
319
|
+
input: {
|
|
320
|
+
instanceId: dependencyInstanceId,
|
|
321
|
+
output: "out",
|
|
322
|
+
path: "peer.endpoints",
|
|
323
|
+
} as never,
|
|
324
|
+
type: "endpoint.v1",
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const values = resolveUnitInputValues({
|
|
328
|
+
library: library as never,
|
|
329
|
+
inputName: "dep",
|
|
330
|
+
resolvedInput: resolved,
|
|
331
|
+
dependencyInstanceType,
|
|
332
|
+
captured: [
|
|
333
|
+
{
|
|
334
|
+
ok: true,
|
|
335
|
+
value: {
|
|
336
|
+
peer: {
|
|
337
|
+
endpoints: [{ host: "a.example" }, { host: "b.example" }],
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
expect(values).toEqual([
|
|
345
|
+
{
|
|
346
|
+
value: { host: "a.example" },
|
|
347
|
+
source: {
|
|
348
|
+
instanceId: dependencyInstanceId,
|
|
349
|
+
output: "out",
|
|
350
|
+
path: "peer.endpoints",
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
value: { host: "b.example" },
|
|
355
|
+
source: {
|
|
356
|
+
instanceId: dependencyInstanceId,
|
|
357
|
+
output: "out",
|
|
358
|
+
path: "peer.endpoints",
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
])
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
test("throws when explicit path contains bracket syntax", () => {
|
|
365
|
+
const dependencyInstanceId = "component.v1:dep"
|
|
366
|
+
const dependencyInstanceType = "component.v1" satisfies VersionedName
|
|
367
|
+
|
|
368
|
+
const library = {
|
|
369
|
+
components: {
|
|
370
|
+
"component.v1": {
|
|
371
|
+
outputs: {
|
|
372
|
+
out: { type: "peer.v1" },
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
entities: {
|
|
377
|
+
"peer.v1": {
|
|
378
|
+
type: "peer.v1",
|
|
379
|
+
inclusions: [{ type: "endpoint.v1", field: "endpoints", multiple: true }],
|
|
380
|
+
},
|
|
381
|
+
"endpoint.v1": {
|
|
382
|
+
type: "endpoint.v1",
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const resolved: ResolvedInstanceInput = {
|
|
388
|
+
input: {
|
|
389
|
+
instanceId: dependencyInstanceId,
|
|
390
|
+
output: "out",
|
|
391
|
+
path: "endpoints[*]",
|
|
392
|
+
} as never,
|
|
393
|
+
type: "endpoint.v1",
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
expect(() =>
|
|
397
|
+
resolveUnitInputValues({
|
|
398
|
+
library: library as never,
|
|
399
|
+
inputName: "dep",
|
|
400
|
+
resolvedInput: resolved,
|
|
401
|
+
dependencyInstanceType,
|
|
402
|
+
captured: [{ ok: true, value: { endpoints: [{ host: "a.example" }] } }],
|
|
403
|
+
}),
|
|
404
|
+
).toThrow(/invalid input path segment/i)
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
test("applies explicit path even when output type matches input type", () => {
|
|
408
|
+
const dependencyInstanceId = "component.v1:dep"
|
|
409
|
+
const dependencyInstanceType = "component.v1" satisfies VersionedName
|
|
410
|
+
|
|
411
|
+
const library = {
|
|
412
|
+
components: {
|
|
413
|
+
"component.v1": {
|
|
414
|
+
outputs: {
|
|
415
|
+
out: { type: "network.v1" },
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
entities: {
|
|
420
|
+
"network.v1": {
|
|
421
|
+
type: "network.v1",
|
|
422
|
+
inclusions: [{ type: "peer.v1", field: "peer", multiple: false }],
|
|
423
|
+
},
|
|
424
|
+
"peer.v1": {
|
|
425
|
+
type: "peer.v1",
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const resolved: ResolvedInstanceInput = {
|
|
431
|
+
input: {
|
|
432
|
+
instanceId: dependencyInstanceId,
|
|
433
|
+
output: "out",
|
|
434
|
+
path: "peer",
|
|
435
|
+
} as never,
|
|
436
|
+
// same as output type on purpose: explicit path should still be applied first
|
|
437
|
+
type: "network.v1",
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
expect(() =>
|
|
441
|
+
resolveUnitInputValues({
|
|
442
|
+
library: library as never,
|
|
443
|
+
inputName: "dep",
|
|
444
|
+
resolvedInput: resolved,
|
|
445
|
+
dependencyInstanceType,
|
|
446
|
+
captured: [{ ok: true, value: { peer: { id: "p1" } } }],
|
|
447
|
+
}),
|
|
448
|
+
).toThrow(/resolved path type is "peer\.v1"/i)
|
|
449
|
+
})
|
|
450
|
+
})
|