typekro 0.13.0 → 0.15.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.
Files changed (85) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/alchemy/deployers.d.ts +2 -2
  3. package/dist/alchemy/deployers.d.ts.map +1 -1
  4. package/dist/alchemy/deployers.js +12 -5
  5. package/dist/alchemy/deployers.js.map +1 -1
  6. package/dist/alchemy/index.d.ts +4 -2
  7. package/dist/alchemy/index.d.ts.map +1 -1
  8. package/dist/alchemy/index.js +4 -3
  9. package/dist/alchemy/index.js.map +1 -1
  10. package/dist/alchemy/resource-registration.d.ts +81 -19
  11. package/dist/alchemy/resource-registration.d.ts.map +1 -1
  12. package/dist/alchemy/resource-registration.js +296 -147
  13. package/dist/alchemy/resource-registration.js.map +1 -1
  14. package/dist/alchemy/types.d.ts +76 -8
  15. package/dist/alchemy/types.d.ts.map +1 -1
  16. package/dist/alchemy/wrapper.d.ts +0 -9
  17. package/dist/alchemy/wrapper.d.ts.map +1 -1
  18. package/dist/alchemy/wrapper.js +0 -15
  19. package/dist/alchemy/wrapper.js.map +1 -1
  20. package/dist/core/constants/brands.d.ts +1 -1
  21. package/dist/core/constants/brands.d.ts.map +1 -1
  22. package/dist/core/constants/brands.js +1 -1
  23. package/dist/core/constants/brands.js.map +1 -1
  24. package/dist/core/containers/image.d.ts +40 -43
  25. package/dist/core/containers/image.d.ts.map +1 -1
  26. package/dist/core/containers/image.js +52 -34
  27. package/dist/core/containers/image.js.map +1 -1
  28. package/dist/core/containers/index.d.ts +8 -12
  29. package/dist/core/containers/index.d.ts.map +1 -1
  30. package/dist/core/containers/index.js +8 -12
  31. package/dist/core/containers/index.js.map +1 -1
  32. package/dist/core/deployment/direct-factory.d.ts +14 -2
  33. package/dist/core/deployment/direct-factory.d.ts.map +1 -1
  34. package/dist/core/deployment/direct-factory.js +117 -21
  35. package/dist/core/deployment/direct-factory.js.map +1 -1
  36. package/dist/core/deployment/engine.d.ts +8 -5
  37. package/dist/core/deployment/engine.d.ts.map +1 -1
  38. package/dist/core/deployment/engine.js +36 -12
  39. package/dist/core/deployment/engine.js.map +1 -1
  40. package/dist/core/deployment/kro-factory.d.ts +33 -14
  41. package/dist/core/deployment/kro-factory.d.ts.map +1 -1
  42. package/dist/core/deployment/kro-factory.js +130 -164
  43. package/dist/core/deployment/kro-factory.js.map +1 -1
  44. package/dist/core/deployment/shared-utilities.d.ts +15 -4
  45. package/dist/core/deployment/shared-utilities.d.ts.map +1 -1
  46. package/dist/core/deployment/shared-utilities.js +47 -14
  47. package/dist/core/deployment/shared-utilities.js.map +1 -1
  48. package/dist/core/deployment/strategies/direct-strategy.d.ts.map +1 -1
  49. package/dist/core/deployment/strategies/direct-strategy.js +1 -2
  50. package/dist/core/deployment/strategies/direct-strategy.js.map +1 -1
  51. package/dist/core/deployment/strategies/index.d.ts +0 -1
  52. package/dist/core/deployment/strategies/index.d.ts.map +1 -1
  53. package/dist/core/deployment/strategies/index.js +0 -1
  54. package/dist/core/deployment/strategies/index.js.map +1 -1
  55. package/dist/core/types/deployment.d.ts +28 -42
  56. package/dist/core/types/deployment.d.ts.map +1 -1
  57. package/dist/core/types/resource-graph.d.ts +2 -6
  58. package/dist/core/types/resource-graph.d.ts.map +1 -1
  59. package/dist/core/types/schema.d.ts +0 -1
  60. package/dist/core/types/schema.d.ts.map +1 -1
  61. package/dist/core/types/serialization.d.ts +3 -7
  62. package/dist/core/types/serialization.d.ts.map +1 -1
  63. package/dist/factories/kubernetes/yaml/yaml-directory.d.ts.map +1 -1
  64. package/dist/factories/kubernetes/yaml/yaml-directory.js +2 -13
  65. package/dist/factories/kubernetes/yaml/yaml-directory.js.map +1 -1
  66. package/dist/factories/kubernetes/yaml/yaml-file.d.ts.map +1 -1
  67. package/dist/factories/kubernetes/yaml/yaml-file.js +2 -18
  68. package/dist/factories/kubernetes/yaml/yaml-file.js.map +1 -1
  69. package/dist/shared/brands.d.ts +0 -9
  70. package/dist/shared/brands.d.ts.map +1 -1
  71. package/dist/shared/brands.js +0 -9
  72. package/dist/shared/brands.js.map +1 -1
  73. package/package.json +6 -3
  74. package/dist/alchemy/deployment.d.ts +0 -12
  75. package/dist/alchemy/deployment.d.ts.map +0 -1
  76. package/dist/alchemy/deployment.js +0 -12
  77. package/dist/alchemy/deployment.js.map +0 -1
  78. package/dist/core/containers/resolve.d.ts +0 -14
  79. package/dist/core/containers/resolve.d.ts.map +0 -1
  80. package/dist/core/containers/resolve.js +0 -88
  81. package/dist/core/containers/resolve.js.map +0 -1
  82. package/dist/core/deployment/strategies/alchemy-strategy.d.ts +0 -109
  83. package/dist/core/deployment/strategies/alchemy-strategy.d.ts.map +0 -1
  84. package/dist/core/deployment/strategies/alchemy-strategy.js +0 -556
  85. package/dist/core/deployment/strategies/alchemy-strategy.js.map +0 -1
@@ -11,7 +11,6 @@ import { preserveNonEnumerableProperties } from '../../utils/helpers.js';
11
11
  import { isKubernetesRef } from '../../utils/type-guards.js';
12
12
  import { applyAspects } from '../aspects/apply.js';
13
13
  import { createCompositionContext, runWithCompositionContext } from '../composition/context.js';
14
- import { hasContainerImageRefs, resolveContainerImages } from '../containers/index.js';
15
14
  import { buildNestedCompositionAliasTargets } from '../composition/nested-status-cel.js';
16
15
  import { DEFAULT_DEPLOYMENT_TIMEOUT, DEFAULT_KRO_INSTANCE_TIMEOUT, DEFAULT_RGD_TIMEOUT, } from '../config/defaults.js';
17
16
  import { CEL_EXPRESSION_BRAND, KUBERNETES_REF_SCHEMA_MARKER_SOURCE } from '../constants/brands.js';
@@ -19,6 +18,7 @@ import { CRDInstanceError, ensureError, ResourceGraphFactoryError, TypeKroError,
19
18
  import { applyAnalysisToResources } from '../expressions/composition/composition-analyzer.js';
20
19
  import { createBunCompatibleKubernetesObjectApi } from '../kubernetes/index.js';
21
20
  import { getComponentLogger } from '../logging/index.js';
21
+ import { createAlchemyResourceId } from '../../alchemy/utilities.js';
22
22
  // Dependency inversion: kroCustomResource, resourceGraphDefinition, and
23
23
  // alchemy bridge are injected via FactoryOptions providers (Phase 3.5)
24
24
  // instead of dynamic import() from higher layers.
@@ -33,7 +33,7 @@ import { DirectDeploymentEngine } from './engine.js';
33
33
  import { logHandleSnapshot } from './handle-tracing.js';
34
34
  import { isNotFoundError } from './k8s-helpers.js';
35
35
  import { waitForKroInstanceReady as waitForKroInstanceReadyShared } from './kro-readiness.js';
36
- import { convertToKubernetesName, generateInstanceName, getSingletonInstanceName, pluralizeKind, validateAlchemyScope, validateSpec, } from './shared-utilities.js';
36
+ import { convertToKubernetesName, extractSerializableKubeConfigOptions, generateInstanceName, getSingletonInstanceName, pluralizeKind, validateSpec, } from './shared-utilities.js';
37
37
  import { joinYamlDocuments, singletonOwnerInstanceYamls, singletonRgdYamls, } from './singleton-gitops.js';
38
38
  import { assertNoDeployedSingletonSpecDrift, singletonSpecFingerprintAnnotationValue, } from './singleton-owner-drift.js';
39
39
  /**
@@ -105,14 +105,12 @@ export class KroResourceFactoryImpl {
105
105
  mode = 'kro';
106
106
  name;
107
107
  namespace;
108
- isAlchemyManaged;
109
108
  rgdName;
110
109
  schema;
111
110
  resources;
112
111
  closures;
113
112
  schemaDefinition;
114
113
  statusMappings;
115
- alchemyScope;
116
114
  singletonDefinitions;
117
115
  singletonOwnerStatuses = new Map();
118
116
  logger = getComponentLogger('kro-factory');
@@ -143,12 +141,9 @@ export class KroResourceFactoryImpl {
143
141
  // instead of dynamic import() from factories/ and alchemy/ layers.
144
142
  kroCustomResourceProvider;
145
143
  rgdProvider;
146
- alchemyBridge;
147
144
  constructor(name, resources, schemaDefinition, statusMappings, options = {}) {
148
145
  this.name = name;
149
146
  this.namespace = options.namespace || 'default';
150
- this.alchemyScope = options.alchemyScope;
151
- this.isAlchemyManaged = !!options.alchemyScope;
152
147
  this.rgdName = convertToKubernetesName(name); // Convert to valid Kubernetes resource name
153
148
  this.resources = resources;
154
149
  this.closures = options.closures || {};
@@ -165,7 +160,6 @@ export class KroResourceFactoryImpl {
165
160
  // Injected providers — fall back to dynamic import() for backward compatibility
166
161
  this.kroCustomResourceProvider = options.kroCustomResourceProvider;
167
162
  this.rgdProvider = options.rgdProvider;
168
- this.alchemyBridge = options.alchemyBridge;
169
163
  // Validate closures for Kro mode - detect KubernetesRef inputs and raise clear errors
170
164
  this.validateClosuresForKroMode();
171
165
  }
@@ -397,69 +391,50 @@ export class KroResourceFactoryImpl {
397
391
  });
398
392
  // Execute closures before RGD creation (Kro mode requirement)
399
393
  await this.executeClosuresBeforeRGD(spec);
400
- // Resolve any container() image refs to literal URIs (build + push via buildContainer) BEFORE the
401
- // RGD is serialized — Kro reconciles in-cluster and can't build images, so it must see a literal.
402
- await this.resolveContainerImagesBeforeRGD();
403
394
  await this.ensureSingletonOwners(spec);
404
- if (this.isAlchemyManaged) {
405
- return this.deployWithAlchemy(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
406
- }
407
- else {
408
- return this.deployDirect(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
409
- }
395
+ return this.deployDirect(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
410
396
  }
411
397
  /**
412
- * Build + substitute any `container()` image references in the resource set to literal image URIs,
413
- * delegating to `buildContainer` (no duplicated build logic). Runs client-side before RGD
414
- * serialization; a no-op when nothing references a container image. Mutates `this.resources` so the
415
- * subsequent `buildRgdYaml` serializes literal images (Kro never sees a build).
398
+ * Discover the singleton-owner definitions this composition depends on (deduped by key): from
399
+ * re-executing the composition fn under a discovery context, plus any injected definitions.
400
+ * Shared by {@link ensureSingletonOwners} (imperative deploy) and {@link toAlchemyResources}.
416
401
  */
417
- async resolveContainerImagesBeforeRGD() {
418
- const resources = Object.values(this.resources);
419
- if (!hasContainerImageRefs(resources))
420
- return;
421
- await resolveContainerImages(resources);
422
- }
423
- async ensureSingletonOwners(spec) {
424
- const discoveredSingletons = new Map();
402
+ discoverSingletonDefinitions(spec) {
403
+ const discovered = new Map();
425
404
  if (this.factoryOptions.compositionFn) {
426
- const singletonContext = createCompositionContext('singleton-owner-discovery');
427
- runWithCompositionContext(singletonContext, () => {
405
+ const ctx = createCompositionContext('singleton-owner-discovery');
406
+ runWithCompositionContext(ctx, () => {
428
407
  this.factoryOptions.compositionFn?.(spec);
429
408
  });
430
- for (const [key, definition] of singletonContext.singletonDefinitions ?? []) {
431
- discoveredSingletons.set(key, definition);
409
+ for (const [key, definition] of ctx.singletonDefinitions ?? []) {
410
+ discovered.set(key, definition);
432
411
  }
433
412
  }
434
413
  for (const definition of this.singletonDefinitions) {
435
- if (!discoveredSingletons.has(definition.key)) {
436
- discoveredSingletons.set(definition.key, definition);
437
- }
414
+ if (!discovered.has(definition.key))
415
+ discovered.set(definition.key, definition);
438
416
  }
439
- if (discoveredSingletons.size === 0)
440
- return;
441
- const singletonRecords = new Map();
442
- for (const definition of discoveredSingletons.values()) {
443
- if (!singletonRecords.has(definition.key)) {
444
- singletonRecords.set(definition.key, definition);
445
- }
446
- }
447
- for (const definition of singletonRecords.values()) {
417
+ return Array.from(discovered.values());
418
+ }
419
+ /** Build the KRO factory that owns a singleton's RGD + instance (its registry namespace). */
420
+ singletonFactoryFor(definition) {
421
+ return definition.composition.factory('kro', {
422
+ namespace: definition.registryNamespace,
423
+ waitForReady: true,
424
+ ...(this.factoryOptions.timeout !== undefined ? { timeout: this.factoryOptions.timeout } : {}),
425
+ ...(this.factoryOptions.kubeConfig !== undefined
426
+ ? { kubeConfig: this.factoryOptions.kubeConfig }
427
+ : {}),
428
+ ...(this.factoryOptions.skipTLSVerify !== undefined
429
+ ? { skipTLSVerify: this.factoryOptions.skipTLSVerify }
430
+ : {}),
431
+ });
432
+ }
433
+ async ensureSingletonOwners(spec) {
434
+ for (const definition of this.discoverSingletonDefinitions(spec)) {
448
435
  await this.ensureTargetNamespace(definition.registryNamespace);
449
436
  const singletonInstanceName = getSingletonInstanceName(definition.id);
450
- const singletonFactory = definition.composition.factory('kro', {
451
- namespace: definition.registryNamespace,
452
- waitForReady: true,
453
- ...(this.factoryOptions.timeout !== undefined
454
- ? { timeout: this.factoryOptions.timeout }
455
- : {}),
456
- ...(this.factoryOptions.kubeConfig !== undefined
457
- ? { kubeConfig: this.factoryOptions.kubeConfig }
458
- : {}),
459
- ...(this.factoryOptions.skipTLSVerify !== undefined
460
- ? { skipTLSVerify: this.factoryOptions.skipTLSVerify }
461
- : {}),
462
- });
437
+ const singletonFactory = this.singletonFactoryFor(definition);
463
438
  try {
464
439
  this.logger.info('Ensuring singleton owner boundary', {
465
440
  singletonId: definition.id,
@@ -558,7 +533,6 @@ export class KroResourceFactoryImpl {
558
533
  const deploymentContext = {
559
534
  kubernetesApi: createBunCompatibleKubernetesObjectApi(kubeConfig),
560
535
  kubeConfig: kubeConfig,
561
- ...(this.alchemyScope && { alchemyScope: this.alchemyScope }),
562
536
  namespace: this.namespace,
563
537
  deployedResources: new Map(), // Empty for pre-RGD execution
564
538
  resolveReference: async (ref) => {
@@ -669,78 +643,6 @@ export class KroResourceFactoryImpl {
669
643
  await deploymentEngine.dispose();
670
644
  }
671
645
  }
672
- /**
673
- * Deploy using type-safe alchemy resource wrapping
674
- *
675
- * In alchemy mode, the RGD gets one typed alchemy Resource and each instance gets another
676
- */
677
- async deployWithAlchemy(spec, instanceNameOverride, singletonSpecFingerprint) {
678
- validateAlchemyScope(this.alchemyScope, 'KRO Alchemy deployment');
679
- const alchemyScope = this.alchemyScope;
680
- // Use static registration functions
681
- // Create deployer instance using DirectDeploymentEngine with KRO mode
682
- const kroEngine = new DirectDeploymentEngine(this.getKubeConfig(), undefined, undefined, DeploymentMode.KRO);
683
- const kubeConfigOptions = this.extractKubeConfigOptionsForAlchemy();
684
- const kroDeletion = this.createAlchemyKroDeletionOptions();
685
- try {
686
- // 1. Ensure RGD is deployed via alchemy (once per factory). Reuse the
687
- // normal serializer so externalRef/forEach/includeWhen/readyWhen and
688
- // singleton owner boundaries match non-Alchemy KRO deploys.
689
- const rgdManifest = yaml.load(this.buildRgdYaml());
690
- // Register RGD type dynamically
691
- const rgdFactory = this.rgdProvider ??
692
- (await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
693
- const rgdEnhanced = rgdFactory(rgdManifest);
694
- const bridge = this.alchemyBridge ?? (await import('../../alchemy/deployment.js'));
695
- const RGDProvider = bridge.ensureResourceTypeRegistered(rgdEnhanced);
696
- const rgdId = bridge.createAlchemyResourceId(rgdEnhanced, this.namespace);
697
- await alchemyScope.run(async () => {
698
- await RGDProvider(rgdId, {
699
- resource: rgdEnhanced,
700
- namespace: this.namespace,
701
- deploymentStrategy: 'kro',
702
- kubeConfigOptions,
703
- kroDeletion,
704
- options: {
705
- waitForReady: true,
706
- timeout: DEFAULT_RGD_TIMEOUT, // RGD should be ready quickly
707
- },
708
- });
709
- });
710
- await this.waitForCRDReadyWithEngine(kroEngine);
711
- // 2. Create instance via alchemy (once per deploy call)
712
- await this.ensureTargetNamespace();
713
- const instanceName = instanceNameOverride ?? generateInstanceName(spec, this.name);
714
- const crdInstanceManifest = this.createCustomResourceInstance(instanceName, spec, singletonSpecFingerprint);
715
- // Register CRD instance type dynamically
716
- // Cast required: crdInstanceManifest is a plain KubernetesResource, but alchemy functions
717
- // expect Enhanced<unknown, unknown>. They only access kind/metadata.name for type inference.
718
- const crdAsEnhanced = crdInstanceManifest;
719
- const CRDInstanceProvider = bridge.ensureResourceTypeRegistered(crdAsEnhanced);
720
- const instanceId = bridge.createAlchemyResourceId(crdAsEnhanced, this.namespace);
721
- await alchemyScope.run(async () => {
722
- await CRDInstanceProvider(instanceId, {
723
- resource: crdAsEnhanced,
724
- namespace: this.namespace,
725
- deploymentStrategy: 'kro',
726
- kubeConfigOptions,
727
- kroDeletion,
728
- options: {
729
- waitForReady: false,
730
- timeout: this.factoryOptions.timeout ?? DEFAULT_DEPLOYMENT_TIMEOUT,
731
- },
732
- });
733
- });
734
- if (this.factoryOptions.waitForReady ?? true) {
735
- await this.waitForKroInstanceReady(instanceName, this.factoryOptions.timeout || DEFAULT_KRO_INSTANCE_TIMEOUT);
736
- }
737
- // Create Enhanced proxy for the deployed instance
738
- return await this.createEnhancedProxy(spec, instanceName);
739
- }
740
- finally {
741
- await kroEngine.dispose();
742
- }
743
- }
744
646
  /**
745
647
  * Get all deployed instances
746
648
  */
@@ -985,41 +887,106 @@ export class KroResourceFactoryImpl {
985
887
  // KRO's finalizer processing handles deleting all child resources
986
888
  // (including Namespaces) via its applyset — no manual cleanup needed.
987
889
  }
988
- extractKubeConfigOptionsForAlchemy() {
989
- const kc = this.getKubeConfig();
990
- const cluster = kc.getCurrentCluster();
991
- const user = typeof kc.getCurrentUser === 'function' ? kc.getCurrentUser() : undefined;
992
- const context = typeof kc.getCurrentContext === 'function' ? kc.getCurrentContext() : undefined;
993
- const finalSkipTLS = this.factoryOptions.skipTLSVerify === true ? true : (cluster?.skipTLSVerify ?? false);
994
- return {
995
- skipTLSVerify: finalSkipTLS,
996
- ...(cluster?.server && { server: cluster.server }),
997
- ...(context && { context }),
998
- ...(cluster && {
999
- cluster: {
1000
- name: cluster.name,
1001
- server: cluster.server,
1002
- skipTLSVerify: finalSkipTLS,
1003
- ...(cluster.caData && { caData: cluster.caData }),
1004
- ...(cluster.caFile && { caFile: cluster.caFile }),
1005
- },
1006
- }),
1007
- ...(user && {
1008
- user: {
1009
- name: user.name,
1010
- ...(user.token && { token: user.token }),
1011
- ...(user.certData && { certData: user.certData }),
1012
- ...(user.certFile && { certFile: user.certFile }),
1013
- ...(user.keyData && { keyData: user.keyData }),
1014
- ...(user.keyFile && { keyFile: user.keyFile }),
1015
- ...(user.exec ? { exec: user.exec } : {}),
1016
- ...(user.authProvider
1017
- ? { authProvider: user.authProvider }
1018
- : {}),
890
+ /**
891
+ * Emit this factory's KRO deployment as declarative alchemy **v2** resources — the v2
892
+ * analog of the removed imperative `deployWithAlchemy(scope)`. Returns, in dependency order, a
893
+ * declaration for each discovered **singleton owner** (its own RGD + instance — mirroring
894
+ * `ensureSingletonOwners` in the imperative `deploy()` path), then the **RGD** (shared, deployed
895
+ * once), then the **CR instance** (one per `spec`, which `dependsOn` the RGD + the singletons),
896
+ * matching the old integration's per-resource state granularity. The caller instantiates
897
+ * each with `KroResource` inside their Stack (RGD first so reverse-topo removes the
898
+ * instance before the shared RGD):
899
+ *
900
+ * ```ts
901
+ * for (const { id, props } of await factory.toAlchemyResources(spec)) {
902
+ * yield* KroResource(id, props);
903
+ * }
904
+ * ```
905
+ *
906
+ * Each declaration carries serialized `kubeConfigOptions` (so reconcile can reconnect to the
907
+ * cluster after state rehydration) and `kroDeletion` (so delete is finalizer-safe / shared-RGD
908
+ * aware). The actual deploy/teardown runs in `kroProvider`'s reconcile/delete.
909
+ */
910
+ async toAlchemyResources(spec, opts) {
911
+ const kubeConfigOptions = this.extractKubeConfigOptionsForAlchemy();
912
+ const kroDeletion = this.createAlchemyKroDeletionOptions();
913
+ // 0. Singleton owners. The imperative `deploy()` ensures shared singleton owners (each its own
914
+ // RGD + instance, in its registry namespace) via `ensureSingletonOwners` BEFORE the main
915
+ // instance; emit them as declarations too so the declarative path has the same boundaries.
916
+ // Their deterministic ids/names mean alchemy dedupes a singleton shared across compositions.
917
+ // (The runtime spec-drift check from `ensureSingletonOwners` is omitted here — `toAlchemyResources`
918
+ // stays cluster-free — but the spec-fingerprint annotation it relied on is still emitted.)
919
+ const singletonDeclarations = [];
920
+ const singletonInstanceIds = [];
921
+ for (const definition of this.discoverSingletonDefinitions(spec)) {
922
+ const singletonFactory = this.singletonFactoryFor(definition);
923
+ try {
924
+ const decls = await singletonFactory.toAlchemyResources(definition.spec, {
925
+ instanceNameOverride: getSingletonInstanceName(definition.id),
926
+ singletonSpecFingerprint: singletonSpecFingerprintAnnotationValue(definition.specFingerprint),
927
+ });
928
+ singletonDeclarations.push(...decls);
929
+ // `toAlchemyResources` returns the owner's OWN CR instance LAST (after any of ITS nested
930
+ // singletons + its RGD), so the consumer must wait on `decls[last]` — using "first decl
931
+ // with a dependency" would wrongly point at a nested singleton's instance.
932
+ const ownerInstance = decls[decls.length - 1];
933
+ if (ownerInstance)
934
+ singletonInstanceIds.push(ownerInstance.id);
935
+ }
936
+ finally {
937
+ await singletonFactory.dispose?.();
938
+ }
939
+ }
940
+ // 1. RGD declaration (deployed once per factory; shared by all instances). Reuse the normal
941
+ // serializer so externalRef/forEach/includeWhen/readyWhen + singleton boundaries match
942
+ // non-alchemy KRO deploys.
943
+ const rgdManifest = yaml.load(this.buildRgdYaml());
944
+ const rgdFactory = this.rgdProvider ??
945
+ (await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
946
+ const rgdEnhanced = rgdFactory(rgdManifest);
947
+ const rgdId = createAlchemyResourceId(rgdEnhanced, this.namespace);
948
+ const rgdDeclaration = {
949
+ id: rgdId,
950
+ dependsOn: [],
951
+ props: {
952
+ resource: rgdEnhanced,
953
+ namespace: this.namespace,
954
+ deploymentStrategy: 'kro',
955
+ kubeConfigOptions,
956
+ kroDeletion,
957
+ options: { waitForReady: true, timeout: DEFAULT_RGD_TIMEOUT },
958
+ },
959
+ };
960
+ // 2. CR instance declaration (one per deploy call). Depends on the RGD so alchemy applies the
961
+ // instance only after the RGD's CRD is established (else the CR apply races a missing kind).
962
+ const instanceName = opts?.instanceNameOverride ?? generateInstanceName(spec, this.name);
963
+ const crdInstanceManifest = this.createCustomResourceInstance(instanceName, spec, opts?.singletonSpecFingerprint);
964
+ // Cast: a plain KubernetesResource is fine — the alchemy path only reads kind/metadata.
965
+ const crdAsEnhanced = crdInstanceManifest;
966
+ const instanceDeclaration = {
967
+ id: createAlchemyResourceId(crdAsEnhanced, this.namespace),
968
+ // Wait for the RGD's CRD AND for any singleton owners (mirrors `ensureSingletonOwners` running
969
+ // before the main deploy), so the instance applies only once its dependencies exist.
970
+ dependsOn: [rgdId, ...singletonInstanceIds],
971
+ props: {
972
+ resource: crdAsEnhanced,
973
+ namespace: this.namespace,
974
+ deploymentStrategy: 'kro',
975
+ kubeConfigOptions,
976
+ kroDeletion,
977
+ options: {
978
+ waitForReady: false,
979
+ timeout: this.factoryOptions.timeout ?? DEFAULT_DEPLOYMENT_TIMEOUT,
1019
980
  },
1020
- }),
981
+ },
1021
982
  };
983
+ return [...singletonDeclarations, rgdDeclaration, instanceDeclaration];
984
+ }
985
+ /** Serialize the factory's cluster connection so an alchemy resource can reconnect after rehydration. */
986
+ extractKubeConfigOptionsForAlchemy() {
987
+ return extractSerializableKubeConfigOptions(this.getKubeConfig(), this.factoryOptions.skipTLSVerify === true ? true : undefined);
1022
988
  }
989
+ /** Build the finalizer-safe, shared-RGD-aware deletion metadata for this factory's instances. */
1023
990
  createAlchemyKroDeletionOptions() {
1024
991
  return {
1025
992
  apiVersion: this.schemaDefinition.apiVersion,
@@ -1040,7 +1007,6 @@ export class KroResourceFactoryImpl {
1040
1007
  return {
1041
1008
  name: this.name,
1042
1009
  mode: this.mode,
1043
- isAlchemyManaged: this.isAlchemyManaged,
1044
1010
  namespace: this.namespace,
1045
1011
  instanceCount: instances.length,
1046
1012
  health: rgdStatus.phase === 'ready' ? 'healthy' : 'degraded',