@highstate/backend 0.9.18 → 0.9.19
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-5WVU2AK4.js +1535 -0
- package/dist/chunk-5WVU2AK4.js.map +1 -0
- package/dist/{chunk-OU5OQBLB.js → chunk-I7BWSAN6.js} +3 -28
- package/dist/{chunk-OU5OQBLB.js.map → chunk-I7BWSAN6.js.map} +1 -1
- package/dist/chunk-VB4YL327.js +139 -0
- package/dist/chunk-VB4YL327.js.map +1 -0
- package/dist/database/local/prisma.config.js +26 -0
- package/dist/database/local/prisma.config.js.map +1 -0
- package/dist/highstate.manifest.json +2 -1
- package/dist/index.js +7587 -7291
- package/dist/index.js.map +1 -1
- package/dist/library/package-resolution-worker.js +1 -1
- package/dist/library/package-resolution-worker.js.map +1 -1
- package/dist/library/worker/main.js +35 -29
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +2 -2
- package/package.json +18 -9
- package/prisma/backend/_schema/layout.prisma +7 -0
- package/prisma/backend/_schema/library.prisma +17 -0
- package/prisma/backend/_schema/project.prisma +101 -0
- package/prisma/backend/_schema/pulumi.prisma +17 -0
- package/prisma/backend/postgresql/main.prisma +17 -0
- package/prisma/backend/sqlite/main.prisma +17 -0
- package/prisma/backend/sqlite/migrations/20250817070609_initiial/migration.sql +34 -0
- package/prisma/backend/sqlite/migrations/20250817104948_add_fields/migration.sql +59 -0
- package/prisma/backend/sqlite/migrations/20250818082732_add_models/migration.sql +41 -0
- package/prisma/backend/sqlite/migrations/20250818083106_a/migration.sql +19 -0
- package/prisma/backend/sqlite/migrations/20250818101945_hi/migration.sql +1 -0
- package/prisma/backend/sqlite/migrations/20250819082315_a/migration.sql +5 -0
- package/prisma/backend/sqlite/migrations/migration_lock.toml +3 -0
- package/prisma/project/api-key.prisma +27 -0
- package/prisma/project/artifact.prisma +52 -0
- package/prisma/project/custom-status.prisma +46 -0
- package/prisma/project/evaluation.prisma +35 -0
- package/prisma/project/instance.prisma +160 -0
- package/prisma/project/layout.prisma +23 -0
- package/prisma/project/lock.prisma +18 -0
- package/prisma/project/main.prisma +17 -0
- package/prisma/project/migrations/20250816081310_initial/migration.sql +300 -0
- package/prisma/project/migrations/20250816082523_test/migration.sql +72 -0
- package/prisma/project/migrations/20250818065643_update/migration.sql +42 -0
- package/prisma/project/migrations/20250818070758_a/migration.sql +8 -0
- package/prisma/project/migrations/20250818070913_a/migration.sql +8 -0
- package/prisma/project/migrations/20250818082720_add_motels/migration.sql +11 -0
- package/prisma/project/migrations/20250818112523_hello/migration.sql +35 -0
- package/prisma/project/migrations/20250819082305_a/migration.sql +14 -0
- package/prisma/project/migrations/20250819165004_add_missing_fields/migration.sql +216 -0
- package/prisma/project/migrations/20250819171309_a/migration.sql +22 -0
- package/prisma/project/migrations/20250820113949_a/migration.sql +66 -0
- package/prisma/project/migrations/20250820144256_b/migration.sql +31 -0
- package/prisma/project/migrations/20250820145547_a/migration.sql +24 -0
- package/prisma/project/migrations/20250820182517_b/migration.sql +2 -0
- package/prisma/project/migrations/20250821172324_a/migration.sql +2 -0
- package/prisma/project/migrations/20250822081339_a/migration.sql +219 -0
- package/prisma/project/migrations/20250822083742_b/migration.sql +1 -0
- package/prisma/project/migrations/20250822105134_boom/migration.sql +1 -0
- package/prisma/project/migrations/20250822141028_b/migration.sql +1 -0
- package/prisma/project/migrations/20250822142342_b/migration.sql +16 -0
- package/prisma/project/migrations/20250824072720_a/migration.sql +1 -0
- package/prisma/project/migrations/20250824093656_b/migration.sql +21 -0
- package/prisma/project/migrations/20250825082518_a/migration.sql +1 -0
- package/prisma/project/migrations/20250825085343_b/migration.sql +1 -0
- package/prisma/project/migrations/20250825091312_a/migration.sql +1 -0
- package/prisma/project/migrations/20250903095431_hi/migration.sql +44 -0
- package/prisma/project/migrations/20250903174255_a/migration.sql +24 -0
- package/prisma/project/migrations/20250908095205_hi/migration.sql +18 -0
- package/prisma/project/migrations/20250909155857_hi/migration.sql +15 -0
- package/prisma/project/migrations/migration_lock.toml +3 -0
- package/prisma/project/model.prisma +37 -0
- package/prisma/project/operation.prisma +148 -0
- package/prisma/project/page.prisma +41 -0
- package/prisma/project/secret.prisma +42 -0
- package/prisma/project/service-account.prisma +36 -0
- package/prisma/project/terminal.prisma +90 -0
- package/prisma/project/trigger.prisma +31 -0
- package/prisma/project/unlock-method.prisma +32 -0
- package/prisma/project/worker.prisma +138 -0
- package/src/artifact/abstractions.ts +13 -13
- package/src/artifact/encryption.ts +30 -54
- package/src/artifact/factory.ts +6 -9
- package/src/artifact/local.ts +33 -46
- package/src/business/api-key.ts +24 -36
- package/src/business/artifact.test.ts +978 -0
- package/src/business/artifact.ts +136 -216
- package/src/business/evaluation.ts +328 -0
- package/src/business/index.ts +5 -2
- package/src/business/instance-lock.test.ts +1060 -0
- package/src/business/instance-lock.ts +387 -78
- package/src/business/instance-state.test.ts +735 -0
- package/src/business/instance-state.ts +582 -337
- package/src/business/operation.test.ts +439 -0
- package/src/business/operation.ts +174 -208
- package/src/business/project-model.ts +258 -0
- package/src/business/project-unlock.ts +168 -126
- package/src/business/project.ts +287 -179
- package/src/business/secret.test.ts +465 -130
- package/src/business/secret.ts +186 -217
- package/src/business/settings.test.ts +695 -0
- package/src/business/settings.ts +855 -0
- package/src/business/terminal-session.ts +90 -0
- package/src/business/unit-extra.test.ts +539 -0
- package/src/business/unit-extra.ts +160 -0
- package/src/business/worker.test.ts +356 -579
- package/src/business/worker.ts +238 -339
- package/src/common/codebase.ts +65 -0
- package/src/common/index.ts +3 -5
- package/src/common/logger.ts +5 -0
- package/src/common/utils.ts +4 -3
- package/src/config.ts +10 -11
- package/src/database/_generated/backend/postgresql/client.ts +72 -0
- package/src/database/_generated/backend/postgresql/commonInputTypes.ts +350 -0
- package/src/database/_generated/backend/postgresql/enums.ts +13 -0
- package/src/database/_generated/backend/postgresql/internal/class.ts +320 -0
- package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +1238 -0
- package/src/database/_generated/backend/postgresql/models/Library.ts +1263 -0
- package/src/database/_generated/backend/postgresql/models/Project.ts +2175 -0
- package/src/database/_generated/backend/postgresql/models/ProjectModelStorage.ts +1263 -0
- package/src/database/_generated/backend/postgresql/models/ProjectSpace.ts +1602 -0
- package/src/database/_generated/backend/postgresql/models/PulumiBackend.ts +1263 -0
- package/src/database/_generated/backend/postgresql/models/UserWorkspaseLayout.ts +1065 -0
- package/src/database/_generated/backend/postgresql/models.ts +16 -0
- package/src/database/_generated/backend/postgresql/pjtg.ts +182 -0
- package/src/database/_generated/backend/sqlite/client.ts +72 -0
- package/src/database/_generated/backend/sqlite/commonInputTypes.ts +331 -0
- package/src/database/_generated/backend/sqlite/enums.ts +13 -0
- package/src/database/_generated/backend/sqlite/internal/class.ts +318 -0
- package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +1207 -0
- package/src/database/_generated/backend/sqlite/models/Library.ts +1261 -0
- package/src/database/_generated/backend/sqlite/models/Project.ts +2169 -0
- package/src/database/_generated/backend/sqlite/models/ProjectModelStorage.ts +1261 -0
- package/src/database/_generated/backend/sqlite/models/ProjectSpace.ts +1599 -0
- package/src/database/_generated/backend/sqlite/models/PulumiBackend.ts +1261 -0
- package/src/database/_generated/backend/sqlite/models/UserWorkspaseLayout.ts +1063 -0
- package/src/database/_generated/backend/sqlite/models.ts +16 -0
- package/src/database/_generated/backend/sqlite/pjtg.ts +182 -0
- package/src/database/_generated/project/client.ts +204 -0
- package/src/database/_generated/project/commonInputTypes.ts +827 -0
- package/src/database/_generated/project/enums.ts +104 -0
- package/src/database/_generated/project/internal/class.ts +479 -0
- package/src/database/_generated/project/internal/prismaNamespace.ts +2974 -0
- package/src/database/_generated/project/models/ApiKey.ts +1506 -0
- package/src/database/_generated/project/models/Artifact.ts +2051 -0
- package/src/database/_generated/project/models/HubModel.ts +1125 -0
- package/src/database/_generated/project/models/InstanceCustomStatus.ts +1713 -0
- package/src/database/_generated/project/models/InstanceEvaluationState.ts +1312 -0
- package/src/database/_generated/project/models/InstanceLock.ts +1268 -0
- package/src/database/_generated/project/models/InstanceModel.ts +1125 -0
- package/src/database/_generated/project/models/InstanceOperationState.ts +1707 -0
- package/src/database/_generated/project/models/InstanceState.ts +4613 -0
- package/src/database/_generated/project/models/Operation.ts +1647 -0
- package/src/database/_generated/project/models/OperationLog.ts +1455 -0
- package/src/database/_generated/project/models/Page.ts +1838 -0
- package/src/database/_generated/project/models/Secret.ts +1692 -0
- package/src/database/_generated/project/models/ServiceAccount.ts +2165 -0
- package/src/database/_generated/project/models/Terminal.ts +2038 -0
- package/src/database/_generated/project/models/TerminalSession.ts +1454 -0
- package/src/database/_generated/project/models/TerminalSessionLog.ts +1280 -0
- package/src/database/_generated/project/models/Trigger.ts +1430 -0
- package/src/database/_generated/project/models/UnlockMethod.ts +1220 -0
- package/src/database/_generated/project/models/UserCompositeViewport.ts +1280 -0
- package/src/database/_generated/project/models/UserProjectViewport.ts +1059 -0
- package/src/database/_generated/project/models/Worker.ts +1459 -0
- package/src/database/_generated/project/models/WorkerUnitRegistration.ts +1524 -0
- package/src/database/_generated/project/models/WorkerVersion.ts +1974 -0
- package/src/database/_generated/project/models/WorkerVersionLog.ts +1318 -0
- package/src/database/_generated/project/models.ts +35 -0
- package/src/database/_generated/project/pjtg.ts +182 -0
- package/src/database/abstractions.ts +19 -0
- package/src/database/factory.ts +37 -0
- package/src/database/index.ts +6 -0
- package/src/database/local/backend.ts +134 -0
- package/src/database/local/index.ts +3 -0
- package/src/database/local/meta.ts +46 -0
- package/src/database/local/prisma.config.ts +25 -0
- package/src/database/local/project.ts +39 -0
- package/src/database/manager.ts +181 -0
- package/src/database/migrate.ts +35 -0
- package/src/database/prisma.ts +56 -0
- package/src/database/well-known.ts +38 -0
- package/src/index.ts +4 -4
- package/src/library/abstractions.ts +3 -5
- package/src/library/factory.ts +1 -1
- package/src/library/local.ts +81 -26
- package/src/library/package-resolution-worker.ts +1 -1
- package/src/library/worker/evaluator.ts +40 -23
- package/src/library/worker/loader.lite.ts +1 -1
- package/src/library/worker/main.ts +3 -10
- package/src/library/worker/protocol.ts +0 -1
- package/src/lock/index.ts +0 -1
- package/src/lock/manager.ts +0 -10
- package/src/orchestrator/manager.ts +190 -104
- package/src/orchestrator/operation-context.ts +357 -0
- package/src/orchestrator/operation-plan.destroy.test.md +357 -0
- package/src/orchestrator/operation-plan.destroy.test.ts +775 -0
- package/src/orchestrator/operation-plan.fixtures.ts +213 -0
- package/src/orchestrator/operation-plan.md +198 -0
- package/src/orchestrator/operation-plan.refresh.test.md +199 -0
- package/src/orchestrator/operation-plan.refresh.test.ts +367 -0
- package/src/orchestrator/operation-plan.ts +709 -0
- package/src/orchestrator/operation-plan.update.test.md +485 -0
- package/src/orchestrator/operation-plan.update.test.ts +1066 -0
- package/src/orchestrator/operation-workset.ts +233 -578
- package/src/orchestrator/operation.ts +435 -948
- package/src/orchestrator/plan-test-builder.ts +267 -0
- package/src/project-model/abstractions.ts +118 -0
- package/src/project-model/backends/codebase.ts +365 -0
- package/src/project-model/backends/database.ts +440 -0
- package/src/project-model/errors.ts +81 -0
- package/src/project-model/factory.ts +24 -0
- package/src/project-model/index.ts +4 -0
- package/src/project-model/utils.test.ts +544 -0
- package/src/project-model/utils.ts +242 -0
- package/src/pubsub/abstractions.ts +10 -1
- package/src/pubsub/factory.ts +4 -4
- package/src/pubsub/index.ts +1 -0
- package/src/pubsub/manager.ts +29 -13
- package/src/pubsub/memory.ts +31 -0
- package/src/runner/abstractions.ts +33 -41
- package/src/runner/artifact-env.ts +19 -8
- package/src/runner/factory.ts +6 -6
- package/src/runner/force-abort.ts +3 -6
- package/src/runner/local.ts +64 -67
- package/src/runner/pulumi.ts +23 -63
- package/src/services.ts +181 -123
- package/src/shared/models/backend/index.ts +3 -1
- package/src/shared/models/backend/library.ts +9 -1
- package/src/shared/models/backend/project.ts +43 -42
- package/src/shared/models/backend/pulumi.ts +14 -0
- package/src/shared/models/backend/unlock-method.ts +1 -1
- package/src/shared/models/backend/well-known.ts +58 -0
- package/src/shared/models/base.ts +40 -26
- package/src/shared/models/errors.ts +82 -1
- package/src/shared/models/index.ts +3 -2
- package/src/shared/models/prisma.ts +36 -0
- package/src/shared/models/project/api-key.ts +37 -59
- package/src/shared/models/project/artifact.ts +16 -76
- package/src/shared/models/project/custom-status.ts +12 -0
- package/src/shared/models/project/index.ts +8 -7
- package/src/shared/models/project/lock.ts +10 -78
- package/src/shared/models/project/model.ts +19 -1
- package/src/shared/models/project/operation.ts +222 -99
- package/src/shared/models/project/page.ts +37 -48
- package/src/shared/models/project/secret.ts +29 -89
- package/src/shared/models/project/service-account.ts +12 -17
- package/src/shared/models/project/state.ts +100 -407
- package/src/shared/models/project/terminal.ts +75 -88
- package/src/shared/models/project/trigger.ts +13 -49
- package/src/shared/models/project/unlock-method.ts +20 -26
- package/src/shared/models/project/worker.ts +89 -90
- package/src/shared/resolvers/graph-resolver.ts +21 -0
- package/src/shared/resolvers/index.ts +1 -1
- package/src/shared/resolvers/input-hash.ts +24 -14
- package/src/shared/resolvers/input.ts +1 -1
- package/src/shared/resolvers/registry.ts +5 -4
- package/src/shared/resolvers/state.ts +12 -1
- package/src/shared/resolvers/validation.ts +7 -3
- package/src/shared/utils/index.ts +1 -2
- package/src/shared/utils/promise-tracker.ts +30 -3
- package/src/terminal/abstractions.ts +1 -1
- package/src/terminal/docker.ts +3 -3
- package/src/terminal/manager.ts +102 -118
- package/src/test-utils/database.ts +119 -0
- package/src/test-utils/index.ts +2 -0
- package/src/test-utils/services.ts +134 -0
- package/src/unlock/abstractions.ts +5 -23
- package/src/unlock/memory.ts +9 -14
- package/src/worker/abstractions.ts +7 -4
- package/src/worker/docker.ts +14 -19
- package/src/worker/manager.ts +366 -97
- package/dist/chunk-NAAIDR4U.js +0 -8499
- package/dist/chunk-NAAIDR4U.js.map +0 -1
- package/dist/chunk-Y7DXREVO.js +0 -1745
- package/dist/chunk-Y7DXREVO.js.map +0 -1
- package/dist/magic-string.es-5ABAC4JN.js +0 -1292
- package/dist/magic-string.es-5ABAC4JN.js.map +0 -1
- package/src/business/__traces__/secret/update-instance-secrets/create-and-delete-secrets-simultaneously.md +0 -356
- package/src/business/__traces__/secret/update-instance-secrets/create-new-secrets-for-instance.md +0 -274
- package/src/business/__traces__/secret/update-instance-secrets/delete-existing-secrets.md +0 -223
- package/src/business/__traces__/secret/update-instance-secrets/no-op-when-no-changes.md +0 -147
- package/src/business/__traces__/secret/update-instance-secrets/update-existing-secrets.md +0 -280
- package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration-when-other-exists.md +0 -360
- package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration.md +0 -215
- package/src/business/__traces__/worker/update-unit-registrations/create-multiple-workers-with-different-identities.md +0 -427
- package/src/business/__traces__/worker/update-unit-registrations/handle-nonexistent-registration-id-gracefully.md +0 -217
- package/src/business/__traces__/worker/update-unit-registrations/no-op-when-no-changes.md +0 -132
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-changes.md +0 -454
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-version-changes.md +0 -426
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-with-same-identity-reuses-service-account.md +0 -372
- package/src/business/__traces__/worker/update-unit-registrations/remove-one-of-multiple-unit-registrations.md +0 -383
- package/src/business/__traces__/worker/update-unit-registrations/remove-unit-registration.md +0 -245
- package/src/business/__traces__/worker/update-unit-registrations/update-existing-unit-registration-when-params-change.md +0 -174
- package/src/business/__traces__/worker/update-unit-registrations/update-params-and-image-simultaneously.md +0 -432
- package/src/business/__traces__/worker/update-unit-registrations/worker-with-multiple-registrations-not-deleted-when-one-removed.md +0 -220
- package/src/business/backend-unlock.ts +0 -10
- package/src/common/clock.ts +0 -18
- package/src/common/performance.ts +0 -44
- package/src/common/random.ts +0 -68
- package/src/common/test/index.ts +0 -2
- package/src/common/test/render.ts +0 -98
- package/src/common/test/tracer.ts +0 -359
- package/src/hotstate/abstractions.ts +0 -48
- package/src/hotstate/factory.ts +0 -17
- package/src/hotstate/index.ts +0 -3
- package/src/hotstate/manager.ts +0 -192
- package/src/hotstate/memory.ts +0 -100
- package/src/hotstate/validation.ts +0 -100
- package/src/lock/test.ts +0 -108
- package/src/project/abstractions.ts +0 -78
- package/src/project/evaluation.ts +0 -248
- package/src/project/factory.ts +0 -11
- package/src/project/index.ts +0 -3
- package/src/project/local.ts +0 -417
- package/src/pubsub/local.ts +0 -36
- package/src/pubsub/validation.ts +0 -33
- package/src/shared/utils/args.ts +0 -25
- package/src/state/abstractions.ts +0 -289
- package/src/state/encryption.ts +0 -98
- package/src/state/factory.ts +0 -20
- package/src/state/index.ts +0 -7
- package/src/state/local/backend.ts +0 -106
- package/src/state/local/collection.ts +0 -361
- package/src/state/local/index.ts +0 -2
- package/src/state/manager.ts +0 -890
- package/src/state/memory/backend.ts +0 -70
- package/src/state/memory/collection.ts +0 -270
- package/src/state/memory/index.ts +0 -2
- package/src/state/repository/index.ts +0 -2
- package/src/state/repository/repository.index.ts +0 -193
- package/src/state/repository/repository.ts +0 -507
- package/src/state/test.ts +0 -457
- /package/src/{state → database/local}/keyring.ts +0 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import type { Logger } from "pino"
|
|
2
|
+
import type { InstanceStateService, ProjectModelService } from "../business"
|
|
3
|
+
import type { LibraryBackend } from "../library"
|
|
4
|
+
import {
|
|
5
|
+
type ComponentModel,
|
|
6
|
+
type InstanceId,
|
|
7
|
+
type InstanceModel,
|
|
8
|
+
isUnitModel,
|
|
9
|
+
} from "@highstate/contract"
|
|
10
|
+
import { unique } from "remeda"
|
|
11
|
+
import {
|
|
12
|
+
type InputHashNode,
|
|
13
|
+
type InputHashOutput,
|
|
14
|
+
InputHashResolver,
|
|
15
|
+
InputResolver,
|
|
16
|
+
type InputResolverNode,
|
|
17
|
+
type InstanceState,
|
|
18
|
+
isTransientInstanceOperationStatus,
|
|
19
|
+
type LibraryModel,
|
|
20
|
+
type ProjectOutput,
|
|
21
|
+
type ResolvedInstanceInput,
|
|
22
|
+
} from "../shared"
|
|
23
|
+
import { BetterLock } from "better-lock"
|
|
24
|
+
|
|
25
|
+
export class OperationContext {
|
|
26
|
+
private readonly instanceMap = new Map<InstanceId, InstanceModel>()
|
|
27
|
+
private readonly instanceChildrenMap = new Map<InstanceId, InstanceModel[]>()
|
|
28
|
+
|
|
29
|
+
private readonly stateMap = new Map<InstanceId, InstanceState>()
|
|
30
|
+
private readonly dependentStateIdMap = new Map<InstanceId, Set<InstanceId>>()
|
|
31
|
+
private readonly stateChildIdMap = new Map<InstanceId, InstanceId[]>()
|
|
32
|
+
|
|
33
|
+
public readonly unitSourceHashMap = new Map<string, number>()
|
|
34
|
+
|
|
35
|
+
public inputResolver!: InputResolver
|
|
36
|
+
public readonly inputResolverNodes = new Map<string, InputResolverNode>()
|
|
37
|
+
|
|
38
|
+
public inputHashResolver!: InputHashResolver
|
|
39
|
+
public readonly inputHashNodes = new Map<string, InputHashNode>()
|
|
40
|
+
private readonly inputHashResolverLock = new BetterLock()
|
|
41
|
+
|
|
42
|
+
private readonly resolvedInstanceInputs = new Map<
|
|
43
|
+
string,
|
|
44
|
+
Record<InstanceId, ResolvedInstanceInput[]>
|
|
45
|
+
>()
|
|
46
|
+
|
|
47
|
+
private instanceIdToStateIdMap: Record<InstanceId, string> | null = null
|
|
48
|
+
|
|
49
|
+
private constructor(
|
|
50
|
+
public readonly project: ProjectOutput,
|
|
51
|
+
public readonly library: LibraryModel,
|
|
52
|
+
private readonly logger: Logger,
|
|
53
|
+
) {}
|
|
54
|
+
|
|
55
|
+
public getInstance(instanceId: InstanceId): InstanceModel {
|
|
56
|
+
const instance = this.instanceMap.get(instanceId)
|
|
57
|
+
if (!instance) {
|
|
58
|
+
throw new Error(`Instance with ID ${instanceId} not found in the operation context`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return instance
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public getInstanceIds(): IterableIterator<InstanceId> {
|
|
65
|
+
return this.instanceMap.keys()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public getInstanceChildren(instanceId: InstanceId): InstanceModel[] {
|
|
69
|
+
return this.instanceChildrenMap.get(instanceId) ?? []
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public getStateChildIds(instanceId: InstanceId): InstanceId[] {
|
|
73
|
+
return this.stateChildIdMap.get(instanceId) ?? []
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public getResolvedInputs(
|
|
77
|
+
instanceId: InstanceId,
|
|
78
|
+
): Record<string, ResolvedInstanceInput[]> | undefined {
|
|
79
|
+
return this.resolvedInstanceInputs.get(instanceId)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public setState(state: InstanceState): void {
|
|
83
|
+
this.stateMap.set(state.instanceId, state)
|
|
84
|
+
|
|
85
|
+
if (state.parentInstanceId) {
|
|
86
|
+
let children = this.stateChildIdMap.get(state.parentInstanceId)
|
|
87
|
+
if (!children) {
|
|
88
|
+
children = []
|
|
89
|
+
this.stateChildIdMap.set(state.parentInstanceId, children)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
children.push(state.instanceId)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// traverse resolvedInputs to build dependency relationships
|
|
96
|
+
if (state.resolvedInputs) {
|
|
97
|
+
for (const inputGroup of Object.values(state.resolvedInputs)) {
|
|
98
|
+
for (const input of inputGroup) {
|
|
99
|
+
if (input.instanceId) {
|
|
100
|
+
this.addDependentState(state.instanceId, input.instanceId)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private addDependentState(instanceId: InstanceId, dependencyId: InstanceId): void {
|
|
108
|
+
let dependentStates = this.dependentStateIdMap.get(dependencyId)
|
|
109
|
+
|
|
110
|
+
if (!dependentStates) {
|
|
111
|
+
dependentStates = new Set<InstanceId>()
|
|
112
|
+
this.dependentStateIdMap.set(dependencyId, dependentStates)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
dependentStates.add(instanceId)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public getState(instanceId: InstanceId): InstanceState {
|
|
119
|
+
const state = this.stateMap.get(instanceId)
|
|
120
|
+
if (!state) {
|
|
121
|
+
throw new Error(`Instance state for "${instanceId}" not found in the operation context`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return state
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public getDependentStates(instanceId: InstanceId): InstanceState[] {
|
|
128
|
+
const dependentStateIds = this.dependentStateIdMap.get(instanceId)
|
|
129
|
+
if (!dependentStateIds) {
|
|
130
|
+
return []
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return Array.from(dependentStateIds)
|
|
134
|
+
.map(id => this.stateMap.get(id))
|
|
135
|
+
.filter((state): state is InstanceState => !!state)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public getDependencies(instanceId: InstanceId): InstanceModel[] {
|
|
139
|
+
const dependencies: InstanceModel[] = []
|
|
140
|
+
const resolvedInputs = this.resolvedInstanceInputs.get(instanceId)
|
|
141
|
+
|
|
142
|
+
if (resolvedInputs) {
|
|
143
|
+
for (const inputGroup of Object.values(resolvedInputs)) {
|
|
144
|
+
for (const resolvedInput of inputGroup) {
|
|
145
|
+
if (resolvedInput.input.instanceId && resolvedInput.input.instanceId !== instanceId) {
|
|
146
|
+
dependencies.push(this.getInstance(resolvedInput.input.instanceId))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return dependencies
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private addInstance(instance: InstanceModel): void {
|
|
156
|
+
if (this.instanceMap.has(instance.id)) {
|
|
157
|
+
throw new Error(`Found multiple instances with the same ID: ${instance.id}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!(instance.type in this.library.components)) {
|
|
161
|
+
this.logger.warn(
|
|
162
|
+
`ignoring instance "${instance.id}" because its type "${instance.type}" is not in the library`,
|
|
163
|
+
)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.instanceMap.set(instance.id, instance)
|
|
168
|
+
|
|
169
|
+
if (instance.parentId) {
|
|
170
|
+
let children = this.instanceChildrenMap.get(instance.parentId)
|
|
171
|
+
if (!children) {
|
|
172
|
+
children = []
|
|
173
|
+
this.instanceChildrenMap.set(instance.parentId, children)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
children.push(instance)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private getSourceHashIfApplicable(
|
|
181
|
+
instance: InstanceModel,
|
|
182
|
+
component: ComponentModel,
|
|
183
|
+
): number | undefined {
|
|
184
|
+
if (isUnitModel(component)) {
|
|
185
|
+
return this.unitSourceHashMap.get(instance.type)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return undefined
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public getUpToDateInputHashOutput(instance: InstanceModel): Promise<InputHashOutput> {
|
|
192
|
+
return this.inputHashResolverLock.acquire(async () => {
|
|
193
|
+
const component = this.library.components[instance.type]
|
|
194
|
+
|
|
195
|
+
this.inputHashNodes.set(instance.id, {
|
|
196
|
+
instance,
|
|
197
|
+
component,
|
|
198
|
+
// biome-ignore lint/style/noNonNullAssertion: я разрешаю
|
|
199
|
+
resolvedInputs: this.resolvedInstanceInputs.get(instance.id)!,
|
|
200
|
+
state: this.stateMap.get(instance.id),
|
|
201
|
+
sourceHash: this.getSourceHashIfApplicable(instance, component),
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
this.inputHashResolver.invalidateSingle(instance.id)
|
|
205
|
+
await this.inputHashResolver.process()
|
|
206
|
+
|
|
207
|
+
return this.inputHashResolver.requireOutput(instance.id)
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public setStates(states: InstanceState[]): void {
|
|
212
|
+
for (const state of states) {
|
|
213
|
+
this.setState(state)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
public getInstanceIdToStateIdMap(): Record<InstanceId, string> {
|
|
218
|
+
if (this.instanceIdToStateIdMap) {
|
|
219
|
+
return this.instanceIdToStateIdMap
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// build map on first request and cache it
|
|
223
|
+
const map: Record<InstanceId, string> = {}
|
|
224
|
+
for (const [instanceId, state] of this.stateMap.entries()) {
|
|
225
|
+
map[instanceId] = state.id
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.instanceIdToStateIdMap = map
|
|
229
|
+
return map
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
getUnfinishedOperationStates(): InstanceState[] {
|
|
233
|
+
const unfinishedStates: InstanceState[] = []
|
|
234
|
+
for (const state of this.stateMap.values()) {
|
|
235
|
+
if (isTransientInstanceOperationStatus(state.lastOperationState?.status)) {
|
|
236
|
+
unfinishedStates.push(state)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return unfinishedStates
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public static async load(
|
|
244
|
+
projectId: string,
|
|
245
|
+
libraryBackend: LibraryBackend,
|
|
246
|
+
instanceStateService: InstanceStateService,
|
|
247
|
+
projectModelService: ProjectModelService,
|
|
248
|
+
logger: Logger,
|
|
249
|
+
): Promise<OperationContext> {
|
|
250
|
+
const [{ instances, virtualInstances, hubs, ghostInstances }, project] =
|
|
251
|
+
await projectModelService.getProjectModel(projectId, {
|
|
252
|
+
includeVirtualInstances: true,
|
|
253
|
+
includeGhostInstances: true,
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
const [library, states] = await Promise.all([
|
|
257
|
+
libraryBackend.loadLibrary(project.libraryId),
|
|
258
|
+
instanceStateService.getInstanceStates(projectId, {
|
|
259
|
+
includeEvaluationState: true,
|
|
260
|
+
includeParentInstanceId: true,
|
|
261
|
+
}),
|
|
262
|
+
])
|
|
263
|
+
|
|
264
|
+
const context = new OperationContext(
|
|
265
|
+
project,
|
|
266
|
+
library,
|
|
267
|
+
logger.child({ service: "OperationContext" }),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
// prepare instances
|
|
271
|
+
for (const instance of instances) {
|
|
272
|
+
context.addInstance(instance)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for (const instance of ghostInstances) {
|
|
276
|
+
context.addInstance(instance)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (const instance of virtualInstances) {
|
|
280
|
+
const contextInstance = context.instanceMap.get(instance.id)
|
|
281
|
+
|
|
282
|
+
if (contextInstance) {
|
|
283
|
+
// use evaluated inputs and outputs for real instances from their virtual counterparts
|
|
284
|
+
contextInstance.inputs = instance.resolvedInputs
|
|
285
|
+
contextInstance.outputs = instance.outputs
|
|
286
|
+
contextInstance.resolvedOutputs = instance.resolvedOutputs
|
|
287
|
+
} else if (instance.parentId) {
|
|
288
|
+
// always use resolved inputs for virtual instances
|
|
289
|
+
instance.inputs = instance.resolvedInputs
|
|
290
|
+
context.addInstance(instance)
|
|
291
|
+
} else {
|
|
292
|
+
context.logger.warn(
|
|
293
|
+
`ignoring virtual instance "${instance.id}" because it is not in the project or is not a part of known composite instance`,
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const unitSources = await libraryBackend.getResolvedUnitSources(
|
|
299
|
+
project.libraryId,
|
|
300
|
+
unique(Array.from(context.instanceMap.values()).map(i => i.type)),
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
for (const unitSource of unitSources) {
|
|
304
|
+
context.unitSourceHashMap.set(unitSource.unitType, unitSource.sourceHash)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
context.setStates(states)
|
|
308
|
+
|
|
309
|
+
// prepare input resolver
|
|
310
|
+
for (const instance of context.instanceMap.values()) {
|
|
311
|
+
context.inputResolverNodes.set(`instance:${instance.id}`, {
|
|
312
|
+
kind: "instance",
|
|
313
|
+
instance,
|
|
314
|
+
component: library.components[instance.type],
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
for (const hub of hubs) {
|
|
319
|
+
context.inputResolverNodes.set(`hub:${hub.id}`, { kind: "hub", hub })
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
context.inputResolver = new InputResolver(context.inputResolverNodes, logger)
|
|
323
|
+
context.inputResolver.addAllNodesToWorkset()
|
|
324
|
+
|
|
325
|
+
await context.inputResolver.process()
|
|
326
|
+
|
|
327
|
+
// resolve inputs for all instances and pass outputs to input hash resolver
|
|
328
|
+
for (const instance of context.instanceMap.values()) {
|
|
329
|
+
const output = context.inputResolver.requireOutput(`instance:${instance.id}`)
|
|
330
|
+
if (output.kind !== "instance") {
|
|
331
|
+
throw new Error("Unexpected output kind")
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
context.resolvedInstanceInputs.set(instance.id, output.resolvedInputs)
|
|
335
|
+
|
|
336
|
+
if (instance.kind === "unit") {
|
|
337
|
+
const component = context.library.components[instance.type]
|
|
338
|
+
|
|
339
|
+
context.inputHashNodes.set(instance.id, {
|
|
340
|
+
instance,
|
|
341
|
+
component,
|
|
342
|
+
resolvedInputs: output.resolvedInputs,
|
|
343
|
+
state: context.stateMap.get(instance.id),
|
|
344
|
+
sourceHash: context.getSourceHashIfApplicable(instance, component),
|
|
345
|
+
})
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// prepare input hash resolver
|
|
350
|
+
context.inputHashResolver = new InputHashResolver(context.inputHashNodes, logger)
|
|
351
|
+
context.inputHashResolver.addAllNodesToWorkset()
|
|
352
|
+
|
|
353
|
+
await context.inputHashResolver.process()
|
|
354
|
+
|
|
355
|
+
return context
|
|
356
|
+
}
|
|
357
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# Destroy Phase Examples
|
|
2
|
+
|
|
3
|
+
**Legend:**
|
|
4
|
+
|
|
5
|
+
- `✅` = Deployed instance
|
|
6
|
+
- `🚀` = Explicitly requested instance
|
|
7
|
+
- `👻` = Ghost instance (virtual)
|
|
8
|
+
|
|
9
|
+
### Example 1: Simple Dependency Chain
|
|
10
|
+
|
|
11
|
+
**Test**: `should include all dependents in linear chain when destroyDependentInstances enabled`
|
|
12
|
+
|
|
13
|
+
```mermaid
|
|
14
|
+
graph RL
|
|
15
|
+
A["A 🚀"]
|
|
16
|
+
B["B ✅"]
|
|
17
|
+
C["C ✅"]
|
|
18
|
+
|
|
19
|
+
C --> B --> A
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Options:**
|
|
23
|
+
|
|
24
|
+
- `destroyDependentInstances`: `true` **(default)**
|
|
25
|
+
|
|
26
|
+
**Decision Steps**:
|
|
27
|
+
|
|
28
|
+
1. `A` explicitly requested;
|
|
29
|
+
2. `B` depends on `A`, cascade enabled → `B` included (dependent of `A`);
|
|
30
|
+
3. `C` depends on `B`, cascade enabled → `C` included (dependent of `B`).
|
|
31
|
+
|
|
32
|
+
**Destroy Phase**: `C`, `B`, `A`
|
|
33
|
+
|
|
34
|
+
### Example 2: Simple Dependency Chain with Cascade Disabled
|
|
35
|
+
|
|
36
|
+
**Test**: `should not include dependents in linear chain when destroyDependentInstances disabled`
|
|
37
|
+
|
|
38
|
+
```mermaid
|
|
39
|
+
graph RL
|
|
40
|
+
A["A 🚀"]
|
|
41
|
+
B["B ✅"]
|
|
42
|
+
C["C ✅"]
|
|
43
|
+
|
|
44
|
+
C --> B --> A
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Options:**
|
|
48
|
+
|
|
49
|
+
- `destroyDependentInstances`: `false`
|
|
50
|
+
|
|
51
|
+
**Decision Steps**:
|
|
52
|
+
|
|
53
|
+
1. `A` explicitly requested;
|
|
54
|
+
2. `B` depends on `A`, cascade disabled → `B` excluded;
|
|
55
|
+
3. `C` depends on `B`, cascade disabled → `C` excluded.
|
|
56
|
+
|
|
57
|
+
**Destroy Phase**: `A`
|
|
58
|
+
|
|
59
|
+
### Example 3: Simple Dependency Chain - Middle Node Requested
|
|
60
|
+
|
|
61
|
+
**Test**: `should include all dependents when middle node requested with destroyDependentInstances enabled`
|
|
62
|
+
|
|
63
|
+
```mermaid
|
|
64
|
+
graph RL
|
|
65
|
+
A["A ✅"]
|
|
66
|
+
B["B 🚀"]
|
|
67
|
+
C["C ✅"]
|
|
68
|
+
|
|
69
|
+
C --> B --> A
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Decision Steps**:
|
|
73
|
+
|
|
74
|
+
1. `B` explicitly requested;
|
|
75
|
+
2. `C` depends on `B`, cascade enabled → `C` included (dependent of `B`);
|
|
76
|
+
3. `A` does not depend on `B` → `A` excluded.
|
|
77
|
+
|
|
78
|
+
**Destroy Phase**: `C`, `B`
|
|
79
|
+
|
|
80
|
+
### Example 4: Composite Boundary Isolation
|
|
81
|
+
|
|
82
|
+
**Test**: `should not propagate beyond compositional inclusion`
|
|
83
|
+
|
|
84
|
+
```mermaid
|
|
85
|
+
graph RL
|
|
86
|
+
subgraph GrandParent
|
|
87
|
+
subgraph Parent
|
|
88
|
+
A["A 🚀"]
|
|
89
|
+
end
|
|
90
|
+
B["B ✅"]
|
|
91
|
+
C["C ✅"]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
B --> A
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Decision Steps**:
|
|
98
|
+
|
|
99
|
+
1. `A` explicitly requested;
|
|
100
|
+
2. `B` depends on `A`, cascade enabled → `B` included (dependent of `A`);
|
|
101
|
+
3. `A` is child of `Parent` → `Parent` included (compositional);
|
|
102
|
+
4. `Parent` is child of `GrandParent` → `GrandParent` NOT included (compositional boundary);
|
|
103
|
+
5. `C` is sibling of `Parent` but `GrandParent` is not included → `C` NOT included.
|
|
104
|
+
|
|
105
|
+
**Destroy Phase**: `B`, `A`, `Parent`
|
|
106
|
+
|
|
107
|
+
### Example 5: Substantive Composite with Mixed Child States
|
|
108
|
+
|
|
109
|
+
**Test**: `should include all children of substantive composite`
|
|
110
|
+
|
|
111
|
+
```mermaid
|
|
112
|
+
graph RL
|
|
113
|
+
subgraph Parent["Parent 🚀"]
|
|
114
|
+
Child1["Child1 ✅"]
|
|
115
|
+
Child2["Child2 ✅"]
|
|
116
|
+
Child3["Child3 ✅"]
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Decision Steps**:
|
|
121
|
+
|
|
122
|
+
1. `Parent` explicitly requested (substantive composite);
|
|
123
|
+
2. `Child1` is child of substantive composite → `Child1` included;
|
|
124
|
+
3. `Child2` is child of substantive composite → `Child2` included;
|
|
125
|
+
4. `Child3` is child of substantive composite → `Child3` included.
|
|
126
|
+
|
|
127
|
+
**Destroy Phase**: `Child1`, `Child2`, `Child3`, `Parent`
|
|
128
|
+
|
|
129
|
+
### Example 6: Nested Composites with Dependencies
|
|
130
|
+
|
|
131
|
+
**Test**: `should handle complex nested hierarchy with dependencies`
|
|
132
|
+
|
|
133
|
+
```mermaid
|
|
134
|
+
graph RL
|
|
135
|
+
subgraph GrandParent
|
|
136
|
+
subgraph Parent1
|
|
137
|
+
Child1["Child1 🚀"]
|
|
138
|
+
Child2["Child2 ✅"]
|
|
139
|
+
end
|
|
140
|
+
subgraph Parent2
|
|
141
|
+
Child3["Child3 ✅"]
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
Child1 --> Child3
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Decision Steps**:
|
|
149
|
+
|
|
150
|
+
1. `Child1` explicitly requested;
|
|
151
|
+
2. No instances depend on `Child1` (Child1 depends on Child3, not vice versa);
|
|
152
|
+
3. `Child1` is child of `Parent1` → `Parent1` included (compositional);
|
|
153
|
+
4. `Parent1` is child of `GrandParent` → `GrandParent` NOT included (compositional boundary).
|
|
154
|
+
|
|
155
|
+
**Destroy Phase**: `Child1`, `Parent1`
|
|
156
|
+
|
|
157
|
+
### Example 7: Request Child with Isolated Destroy
|
|
158
|
+
|
|
159
|
+
**Test**: `should not include siblings when child explicitly requested`
|
|
160
|
+
|
|
161
|
+
```mermaid
|
|
162
|
+
graph RL
|
|
163
|
+
subgraph Parent
|
|
164
|
+
Child1["Child1 🚀"]
|
|
165
|
+
Child2["Child2 ✅"]
|
|
166
|
+
Child3["Child3 ✅"]
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Decision Steps**:
|
|
171
|
+
|
|
172
|
+
1. `Child1` explicitly requested;
|
|
173
|
+
2. No instances depend on `Child1`;
|
|
174
|
+
3. `Child1` is child of `Parent` → `Parent` included (compositional);
|
|
175
|
+
4. `Child2` and `Child3` are siblings but not affected → excluded.
|
|
176
|
+
|
|
177
|
+
**Destroy Phase**: `Child1`, `Parent`
|
|
178
|
+
|
|
179
|
+
### Example 8: Cross-Composite Dependencies
|
|
180
|
+
|
|
181
|
+
**Test**: `should handle dependencies crossing composite boundaries`
|
|
182
|
+
|
|
183
|
+
```mermaid
|
|
184
|
+
graph RL
|
|
185
|
+
subgraph CompositeA
|
|
186
|
+
ChildA["ChildA 🚀"]
|
|
187
|
+
end
|
|
188
|
+
subgraph CompositeB
|
|
189
|
+
ChildB["ChildB ✅"]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
ChildB --> ChildA
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Decision Steps**:
|
|
196
|
+
|
|
197
|
+
1. `ChildA` explicitly requested;
|
|
198
|
+
2. `ChildB` depends on `ChildA`, cascade enabled → `ChildB` included (dependent of `ChildA`);
|
|
199
|
+
3. `ChildA` is child of `CompositeA` → `CompositeA` included (compositional);
|
|
200
|
+
4. `ChildB` is child of `CompositeB`, included due to external dependency → `CompositeB` included (substantive).
|
|
201
|
+
|
|
202
|
+
**Destroy Phase**: `ChildB`, `CompositeB`, `ChildA`, `CompositeA`
|
|
203
|
+
|
|
204
|
+
### Example 9: Unrelated Instance Isolation
|
|
205
|
+
|
|
206
|
+
**Test**: `should not include unrelated instances that don't depend on destroyed instance`
|
|
207
|
+
|
|
208
|
+
```mermaid
|
|
209
|
+
graph RL
|
|
210
|
+
subgraph Parent
|
|
211
|
+
Child1["Child1 ✅"]
|
|
212
|
+
Child2["Child2 ✅"]
|
|
213
|
+
end
|
|
214
|
+
UnrelatedX["UnrelatedX ✅"]
|
|
215
|
+
ExternalY["ExternalY 🚀"]
|
|
216
|
+
|
|
217
|
+
Child1 --> ExternalY
|
|
218
|
+
Child2 --> UnrelatedX
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Decision Steps**:
|
|
222
|
+
|
|
223
|
+
1. `ExternalY` explicitly requested;
|
|
224
|
+
2. `Child1` depends on `ExternalY`, cascade enabled → `Child1` included (dependent of `ExternalY`);
|
|
225
|
+
3. `Child1` is child of `Parent`, included due to external dependency → `Parent` becomes substantive;
|
|
226
|
+
4. `Child2` is child of substantive composite → `Child2` included;
|
|
227
|
+
5. `UnrelatedX` does not depend on any destroyed instance → `UnrelatedX` excluded.
|
|
228
|
+
|
|
229
|
+
**Destroy Phase**: `Child1`, `Child2`, `Parent`, `ExternalY`
|
|
230
|
+
|
|
231
|
+
### Example 10: Multiple Explicit Requests
|
|
232
|
+
|
|
233
|
+
**Test**: `should handle multiple explicit requests with overlapping dependencies`
|
|
234
|
+
|
|
235
|
+
```mermaid
|
|
236
|
+
graph RL
|
|
237
|
+
A["A 🚀"]
|
|
238
|
+
B["B ✅"]
|
|
239
|
+
C["C 🚀"]
|
|
240
|
+
|
|
241
|
+
C --> B --> A
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Decision Steps**:
|
|
245
|
+
|
|
246
|
+
1. `A` explicitly requested;
|
|
247
|
+
2. `C` explicitly requested;
|
|
248
|
+
3. `B` depends on `A`, cascade enabled → `B` included (dependent of `A`);
|
|
249
|
+
4. `C` depends on `B`, `B` already included → no additional change.
|
|
250
|
+
|
|
251
|
+
**Destroy Phase**: `C`, `B`, `A`
|
|
252
|
+
|
|
253
|
+
### Example 11: Deep Nesting with Boundary Isolation
|
|
254
|
+
|
|
255
|
+
**Test**: `should isolate boundaries in deep composite hierarchies`
|
|
256
|
+
|
|
257
|
+
```mermaid
|
|
258
|
+
graph RL
|
|
259
|
+
subgraph GreatGrandParent
|
|
260
|
+
subgraph GrandParent
|
|
261
|
+
subgraph Parent
|
|
262
|
+
Child["Child 🚀"]
|
|
263
|
+
end
|
|
264
|
+
Uncle["Uncle ✅"]
|
|
265
|
+
end
|
|
266
|
+
GreatUncle["GreatUncle ✅"]
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Decision Steps**:
|
|
271
|
+
|
|
272
|
+
1. `Child` explicitly requested;
|
|
273
|
+
2. No instances depend on `Child`;
|
|
274
|
+
3. `Child` is child of `Parent` → `Parent` included (compositional);
|
|
275
|
+
4. `Parent` is child of `GrandParent` → `GrandParent` NOT included (compositional boundary);
|
|
276
|
+
5. `Uncle` and `GreatUncle` not affected.
|
|
277
|
+
|
|
278
|
+
**Destroy Phase**: `Child`, `Parent`
|
|
279
|
+
|
|
280
|
+
### Example 12: Diamond Dependency Pattern
|
|
281
|
+
|
|
282
|
+
**Test**: `should handle diamond dependency correctly`
|
|
283
|
+
|
|
284
|
+
```mermaid
|
|
285
|
+
graph RL
|
|
286
|
+
A["A 🚀"]
|
|
287
|
+
B["B ✅"]
|
|
288
|
+
C["C ✅"]
|
|
289
|
+
D["D ✅"]
|
|
290
|
+
|
|
291
|
+
D --> B
|
|
292
|
+
D --> C
|
|
293
|
+
B --> A
|
|
294
|
+
C --> A
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Decision Steps**:
|
|
298
|
+
|
|
299
|
+
1. `A` explicitly requested;
|
|
300
|
+
2. `B` depends on `A`, cascade enabled → `B` included (dependent of `A`);
|
|
301
|
+
3. `C` depends on `A`, cascade enabled → `C` included (dependent of `A`);
|
|
302
|
+
4. `D` depends on `B`, cascade enabled → `D` included (dependent of `B`);
|
|
303
|
+
5. `D` also depends on `C`, already included → no additional change.
|
|
304
|
+
|
|
305
|
+
**Destroy Phase**: `D`, `B`, `C`, `A`
|
|
306
|
+
|
|
307
|
+
### Example 13: Dependency Chain with Partial Destruction Disabled
|
|
308
|
+
|
|
309
|
+
**Test**: `should include dependency chain and force siblings when partial destruction disabled`
|
|
310
|
+
|
|
311
|
+
```mermaid
|
|
312
|
+
graph RL
|
|
313
|
+
subgraph Parent
|
|
314
|
+
Child1["Child1 ✅"]
|
|
315
|
+
Child2["Child2 ✅"]
|
|
316
|
+
end
|
|
317
|
+
ExternalX["ExternalX 🚀"]
|
|
318
|
+
|
|
319
|
+
Child1 --> ExternalX
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Decision Steps**:
|
|
323
|
+
|
|
324
|
+
1. `ExternalX` explicitly requested;
|
|
325
|
+
2. `Child1` depends on `ExternalX`, cascade enabled → `Child1` included (dependent of `ExternalX`);
|
|
326
|
+
3. `Parent` has child included due to external dependency, so `Parent` becomes substantive composite;
|
|
327
|
+
4. `Child2` is sibling of `Child1` in substantive composite, partial destruction disabled → `Child2` included.
|
|
328
|
+
|
|
329
|
+
**Destroy Phase**: `Child1`, `Child2`, `Parent`, `ExternalX`
|
|
330
|
+
|
|
331
|
+
### Example 14: Dependency Chain with Partial Destruction Enabled
|
|
332
|
+
|
|
333
|
+
**Test**: `should include dependency chain without forcing siblings when partial destruction enabled`
|
|
334
|
+
|
|
335
|
+
```mermaid
|
|
336
|
+
graph RL
|
|
337
|
+
subgraph Parent
|
|
338
|
+
Child1["Child1 ✅"]
|
|
339
|
+
Child2["Child2 ✅"]
|
|
340
|
+
end
|
|
341
|
+
ExternalX["ExternalX 🚀"]
|
|
342
|
+
|
|
343
|
+
Child1 --> ExternalX
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Options:**
|
|
347
|
+
|
|
348
|
+
- `allowPartialCompositeInstanceDestruction`: `true`
|
|
349
|
+
|
|
350
|
+
**Decision Steps**:
|
|
351
|
+
|
|
352
|
+
1. `ExternalX` explicitly requested;
|
|
353
|
+
2. `Child1` depends on `ExternalX`, cascade enabled → `Child1` included (dependent of `ExternalX`);
|
|
354
|
+
3. `Parent` has child included due to external dependency, so `Parent` becomes substantive composite;
|
|
355
|
+
4. `Child2` is sibling of `Child1` in substantive composite, partial destruction enabled → `Child2` excluded.
|
|
356
|
+
|
|
357
|
+
**Destroy Phase**: `Child1`, `Parent`, `ExternalX`
|