typekro 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/alchemy/deployers.d.ts +16 -1
- package/dist/alchemy/deployers.d.ts.map +1 -1
- package/dist/alchemy/deployers.js +131 -18
- package/dist/alchemy/deployers.js.map +1 -1
- package/dist/alchemy/deployment.d.ts +1 -1
- package/dist/alchemy/deployment.d.ts.map +1 -1
- package/dist/alchemy/deployment.js +1 -1
- package/dist/alchemy/deployment.js.map +1 -1
- package/dist/alchemy/kro-delete.d.ts +66 -0
- package/dist/alchemy/kro-delete.d.ts.map +1 -0
- package/dist/alchemy/kro-delete.js +183 -0
- package/dist/alchemy/kro-delete.js.map +1 -0
- package/dist/alchemy/resource-registration.d.ts +16 -0
- package/dist/alchemy/resource-registration.d.ts.map +1 -1
- package/dist/alchemy/resource-registration.js +138 -24
- package/dist/alchemy/resource-registration.js.map +1 -1
- package/dist/alchemy/types.d.ts +8 -4
- package/dist/alchemy/types.d.ts.map +1 -1
- package/dist/compositions/typekro-runtime/typekro-runtime.d.ts +1 -1
- package/dist/compositions/typekro-runtime/typekro-runtime.d.ts.map +1 -1
- package/dist/compositions/typekro-runtime/typekro-runtime.js +39 -7
- package/dist/compositions/typekro-runtime/typekro-runtime.js.map +1 -1
- package/dist/core/composition/context.d.ts +58 -2
- package/dist/core/composition/context.d.ts.map +1 -1
- package/dist/core/composition/context.js +4 -0
- package/dist/core/composition/context.js.map +1 -1
- package/dist/core/composition/imperative.d.ts +9 -0
- package/dist/core/composition/imperative.d.ts.map +1 -1
- package/dist/core/composition/imperative.js +538 -54
- package/dist/core/composition/imperative.js.map +1 -1
- package/dist/core/composition/nested-status-cel.d.ts +34 -1
- package/dist/core/composition/nested-status-cel.d.ts.map +1 -1
- package/dist/core/composition/nested-status-cel.js +379 -41
- package/dist/core/composition/nested-status-cel.js.map +1 -1
- package/dist/core/composition-debugger.d.ts +1 -1
- package/dist/core/composition-debugger.d.ts.map +1 -1
- package/dist/core/composition-debugger.js.map +1 -1
- package/dist/core/constants/brands.d.ts +1 -1
- package/dist/core/constants/brands.d.ts.map +1 -1
- package/dist/core/constants/brands.js +1 -1
- package/dist/core/constants/brands.js.map +1 -1
- package/dist/core/containers/build.d.ts +4 -4
- package/dist/core/containers/build.d.ts.map +1 -1
- package/dist/core/containers/build.js +44 -16
- package/dist/core/containers/build.js.map +1 -1
- package/dist/core/containers/registries/types.d.ts +1 -1
- package/dist/core/containers/registries/types.d.ts.map +1 -1
- package/dist/core/dependencies/resolver.d.ts +19 -0
- package/dist/core/dependencies/resolver.d.ts.map +1 -1
- package/dist/core/dependencies/resolver.js +261 -1
- package/dist/core/dependencies/resolver.js.map +1 -1
- package/dist/core/deployment/client-provider-manager.d.ts +9 -3
- package/dist/core/deployment/client-provider-manager.d.ts.map +1 -1
- package/dist/core/deployment/client-provider-manager.js +12 -0
- package/dist/core/deployment/client-provider-manager.js.map +1 -1
- package/dist/core/deployment/crd-manager.d.ts +24 -0
- package/dist/core/deployment/crd-manager.d.ts.map +1 -1
- package/dist/core/deployment/crd-manager.js +79 -1
- package/dist/core/deployment/crd-manager.js.map +1 -1
- package/dist/core/deployment/deployment-state-discovery.d.ts +116 -0
- package/dist/core/deployment/deployment-state-discovery.d.ts.map +1 -0
- package/dist/core/deployment/deployment-state-discovery.js +400 -0
- package/dist/core/deployment/deployment-state-discovery.js.map +1 -0
- package/dist/core/deployment/direct-factory.d.ts +65 -6
- package/dist/core/deployment/direct-factory.d.ts.map +1 -1
- package/dist/core/deployment/direct-factory.js +551 -56
- package/dist/core/deployment/direct-factory.js.map +1 -1
- package/dist/core/deployment/engine.d.ts +64 -3
- package/dist/core/deployment/engine.d.ts.map +1 -1
- package/dist/core/deployment/engine.js +194 -27
- package/dist/core/deployment/engine.js.map +1 -1
- package/dist/core/deployment/event-filter.js +1 -1
- package/dist/core/deployment/event-filter.js.map +1 -1
- package/dist/core/deployment/event-monitor.d.ts +11 -0
- package/dist/core/deployment/event-monitor.d.ts.map +1 -1
- package/dist/core/deployment/event-monitor.js +14 -0
- package/dist/core/deployment/event-monitor.js.map +1 -1
- package/dist/core/deployment/handle-tracing.d.ts +14 -0
- package/dist/core/deployment/handle-tracing.d.ts.map +1 -0
- package/dist/core/deployment/handle-tracing.js +38 -0
- package/dist/core/deployment/handle-tracing.js.map +1 -0
- package/dist/core/deployment/kro-factory.d.ts +136 -3
- package/dist/core/deployment/kro-factory.d.ts.map +1 -1
- package/dist/core/deployment/kro-factory.js +945 -268
- package/dist/core/deployment/kro-factory.js.map +1 -1
- package/dist/core/deployment/kro-readiness.d.ts.map +1 -1
- package/dist/core/deployment/kro-readiness.js +21 -12
- package/dist/core/deployment/kro-readiness.js.map +1 -1
- package/dist/core/deployment/nested-composition-status.d.ts +1 -1
- package/dist/core/deployment/nested-composition-status.d.ts.map +1 -1
- package/dist/core/deployment/nested-composition-status.js +96 -53
- package/dist/core/deployment/nested-composition-status.js.map +1 -1
- package/dist/core/deployment/resource-applier.d.ts +15 -2
- package/dist/core/deployment/resource-applier.d.ts.map +1 -1
- package/dist/core/deployment/resource-applier.js +75 -25
- package/dist/core/deployment/resource-applier.js.map +1 -1
- package/dist/core/deployment/resource-tagging.d.ts +220 -0
- package/dist/core/deployment/resource-tagging.d.ts.map +1 -0
- package/dist/core/deployment/resource-tagging.js +292 -0
- package/dist/core/deployment/resource-tagging.js.map +1 -0
- package/dist/core/deployment/rollback-manager.d.ts +25 -4
- package/dist/core/deployment/rollback-manager.d.ts.map +1 -1
- package/dist/core/deployment/rollback-manager.js +70 -57
- package/dist/core/deployment/rollback-manager.js.map +1 -1
- package/dist/core/deployment/shared-utilities.d.ts +6 -0
- package/dist/core/deployment/shared-utilities.d.ts.map +1 -1
- package/dist/core/deployment/shared-utilities.js +32 -2
- package/dist/core/deployment/shared-utilities.js.map +1 -1
- package/dist/core/deployment/singleton-owner-drift.d.ts +16 -0
- package/dist/core/deployment/singleton-owner-drift.d.ts.map +1 -0
- package/dist/core/deployment/singleton-owner-drift.js +54 -0
- package/dist/core/deployment/singleton-owner-drift.js.map +1 -0
- package/dist/core/deployment/strategies/alchemy-strategy.d.ts +3 -1
- package/dist/core/deployment/strategies/alchemy-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/alchemy-strategy.js +121 -18
- package/dist/core/deployment/strategies/alchemy-strategy.js.map +1 -1
- package/dist/core/deployment/strategies/base-strategy.d.ts +9 -3
- package/dist/core/deployment/strategies/base-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/base-strategy.js +32 -4
- package/dist/core/deployment/strategies/base-strategy.js.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.d.ts +12 -4
- package/dist/core/deployment/strategies/direct-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.js +112 -8
- package/dist/core/deployment/strategies/direct-strategy.js.map +1 -1
- package/dist/core/errors.d.ts +2 -2
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/expressions/analysis/cache.d.ts +2 -2
- package/dist/core/expressions/analysis/cache.d.ts.map +1 -1
- package/dist/core/expressions/analysis/cache.js +4 -0
- package/dist/core/expressions/analysis/cache.js.map +1 -1
- package/dist/core/expressions/analysis/fn-toString-self-test.d.ts.map +1 -1
- package/dist/core/expressions/analysis/fn-toString-self-test.js +0 -1
- package/dist/core/expressions/analysis/fn-toString-self-test.js.map +1 -1
- package/dist/core/expressions/analysis/shared-types.d.ts +2 -2
- package/dist/core/expressions/analysis/shared-types.d.ts.map +1 -1
- package/dist/core/expressions/analysis/source-map.d.ts.map +1 -1
- package/dist/core/expressions/analysis/source-map.js +1 -0
- package/dist/core/expressions/analysis/source-map.js.map +1 -1
- package/dist/core/expressions/analysis/types.d.ts +7 -7
- package/dist/core/expressions/analysis/types.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts +37 -3
- package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-helpers.js +370 -7
- package/dist/core/expressions/composition/composition-analyzer-helpers.js.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts +2 -2
- package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-ternary.js +264 -29
- package/dist/core/expressions/composition/composition-analyzer-ternary.js.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-traversal.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-traversal.js +78 -6
- package/dist/core/expressions/composition/composition-analyzer-traversal.js.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-types.d.ts +60 -0
- package/dist/core/expressions/composition/composition-analyzer-types.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer.d.ts +1 -1
- package/dist/core/expressions/composition/composition-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer.js +158 -8
- package/dist/core/expressions/composition/composition-analyzer.js.map +1 -1
- package/dist/core/expressions/composition/expression-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/composition/expression-analyzer.js +21 -4
- package/dist/core/expressions/composition/expression-analyzer.js.map +1 -1
- package/dist/core/expressions/composition/imperative-analyzer.d.ts +1 -1
- package/dist/core/expressions/composition/imperative-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/composition/imperative-analyzer.js +31 -7
- package/dist/core/expressions/composition/imperative-analyzer.js.map +1 -1
- package/dist/core/expressions/composition/scope-manager.d.ts +2 -2
- package/dist/core/expressions/composition/scope-manager.d.ts.map +1 -1
- package/dist/core/expressions/composition/scope-manager.js.map +1 -1
- package/dist/core/expressions/conditional/conditional-expression-processor.d.ts +3 -3
- package/dist/core/expressions/conditional/conditional-expression-processor.d.ts.map +1 -1
- package/dist/core/expressions/conditional/conditional-expression-processor.js.map +1 -1
- package/dist/core/expressions/conditional/conditional-integration.d.ts.map +1 -1
- package/dist/core/expressions/conditional/conditional-integration.js.map +1 -1
- package/dist/core/expressions/context/context-aware-generator.d.ts +5 -5
- package/dist/core/expressions/context/context-aware-generator.d.ts.map +1 -1
- package/dist/core/expressions/context/context-aware-generator.js.map +1 -1
- package/dist/core/expressions/context/context-detector.d.ts +3 -3
- package/dist/core/expressions/context/context-detector.d.ts.map +1 -1
- package/dist/core/expressions/context/context-detector.js.map +1 -1
- package/dist/core/expressions/context/context-validator.d.ts +6 -6
- package/dist/core/expressions/context/context-validator.d.ts.map +1 -1
- package/dist/core/expressions/context/context-validator.js +2 -2
- package/dist/core/expressions/context/context-validator.js.map +1 -1
- package/dist/core/expressions/factory/cel-conversion-engine.d.ts +4 -4
- package/dist/core/expressions/factory/cel-conversion-engine.d.ts.map +1 -1
- package/dist/core/expressions/factory/cel-conversion-engine.js.map +1 -1
- package/dist/core/expressions/factory/dependency-tracker.d.ts +2 -2
- package/dist/core/expressions/factory/dependency-tracker.d.ts.map +1 -1
- package/dist/core/expressions/factory/dependency-tracker.js +21 -5
- package/dist/core/expressions/factory/dependency-tracker.js.map +1 -1
- package/dist/core/expressions/factory/factory-integration.d.ts +2 -4
- package/dist/core/expressions/factory/factory-integration.d.ts.map +1 -1
- package/dist/core/expressions/factory/factory-integration.js +0 -6
- package/dist/core/expressions/factory/factory-integration.js.map +1 -1
- package/dist/core/expressions/factory/factory-pattern-handler.d.ts +3 -3
- package/dist/core/expressions/factory/factory-pattern-handler.d.ts.map +1 -1
- package/dist/core/expressions/factory/factory-pattern-handler.js +1 -0
- package/dist/core/expressions/factory/factory-pattern-handler.js.map +1 -1
- package/dist/core/expressions/factory/migration-helpers.js.map +1 -1
- package/dist/core/expressions/factory/resource-analyzer.d.ts +4 -4
- package/dist/core/expressions/factory/resource-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/factory/resource-analyzer.js.map +1 -1
- package/dist/core/expressions/factory/resource-type-validator.d.ts +5 -5
- package/dist/core/expressions/factory/resource-type-validator.d.ts.map +1 -1
- package/dist/core/expressions/factory/resource-type-validator.js.map +1 -1
- package/dist/core/expressions/factory/status-builder-analyzer.d.ts +6 -7
- package/dist/core/expressions/factory/status-builder-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/factory/status-builder-analyzer.js +0 -3
- package/dist/core/expressions/factory/status-builder-analyzer.js.map +1 -1
- package/dist/core/expressions/factory/status-builder-types.d.ts +1 -1
- package/dist/core/expressions/factory/status-builder-types.d.ts.map +1 -1
- package/dist/core/expressions/factory/status-cel-generation.d.ts +1 -1
- package/dist/core/expressions/factory/status-cel-generation.d.ts.map +1 -1
- package/dist/core/expressions/factory/status-cel-generation.js +1 -1
- package/dist/core/expressions/factory/status-cel-generation.js.map +1 -1
- package/dist/core/expressions/factory/status-field-analysis.d.ts +3 -3
- package/dist/core/expressions/factory/status-field-analysis.d.ts.map +1 -1
- package/dist/core/expressions/factory/status-field-analysis.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.d.ts +5 -5
- package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.js +1 -1
- package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.d.ts +10 -10
- package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.js +5 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-ast.d.ts +2 -2
- package/dist/core/expressions/magic-proxy/magic-proxy-ast.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-ast.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-detector.d.ts +5 -5
- package/dist/core/expressions/magic-proxy/magic-proxy-detector.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-detector.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-types.d.ts +2 -2
- package/dist/core/expressions/magic-proxy/magic-proxy-types.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/optionality-handler.d.ts +1 -2
- package/dist/core/expressions/magic-proxy/optionality-handler.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/optionality-handler.js +2 -15
- package/dist/core/expressions/magic-proxy/optionality-handler.js.map +1 -1
- package/dist/core/expressions/validation/compile-time-checker.d.ts +1 -1
- package/dist/core/expressions/validation/compile-time-checker.d.ts.map +1 -1
- package/dist/core/expressions/validation/compile-time-checker.js.map +1 -1
- package/dist/core/expressions/validation/compile-time-types.d.ts +2 -2
- package/dist/core/expressions/validation/compile-time-types.d.ts.map +1 -1
- package/dist/core/expressions/validation/kubernetes-field-types.d.ts +4 -4
- package/dist/core/expressions/validation/kubernetes-field-types.d.ts.map +1 -1
- package/dist/core/expressions/validation/kubernetes-field-types.js.map +1 -1
- package/dist/core/expressions/validation/resource-field-utils.d.ts +10 -10
- package/dist/core/expressions/validation/resource-field-utils.d.ts.map +1 -1
- package/dist/core/expressions/validation/resource-field-utils.js.map +1 -1
- package/dist/core/expressions/validation/resource-validation.d.ts +3 -3
- package/dist/core/expressions/validation/resource-validation.d.ts.map +1 -1
- package/dist/core/expressions/validation/resource-validation.js.map +1 -1
- package/dist/core/expressions/validation/type-inference-types.d.ts +2 -2
- package/dist/core/expressions/validation/type-inference-types.d.ts.map +1 -1
- package/dist/core/expressions/validation/type-safety.d.ts +2 -2
- package/dist/core/expressions/validation/type-safety.d.ts.map +1 -1
- package/dist/core/expressions/validation/type-safety.js +1 -0
- package/dist/core/expressions/validation/type-safety.js.map +1 -1
- package/dist/core/kubernetes/bun-api-client.js.map +1 -1
- package/dist/core/kubernetes/bun-http-library.d.ts.map +1 -1
- package/dist/core/kubernetes/bun-http-library.js +29 -3
- package/dist/core/kubernetes/bun-http-library.js.map +1 -1
- package/dist/core/kubernetes/client-provider.d.ts +12 -0
- package/dist/core/kubernetes/client-provider.d.ts.map +1 -1
- package/dist/core/kubernetes/client-provider.js +35 -0
- package/dist/core/kubernetes/client-provider.js.map +1 -1
- package/dist/core/metadata/resource-metadata.d.ts +46 -1
- package/dist/core/metadata/resource-metadata.d.ts.map +1 -1
- package/dist/core/metadata/resource-metadata.js.map +1 -1
- package/dist/core/proxy/create-resource.d.ts +15 -0
- package/dist/core/proxy/create-resource.d.ts.map +1 -1
- package/dist/core/proxy/create-resource.js +56 -2
- package/dist/core/proxy/create-resource.js.map +1 -1
- package/dist/core/readiness/registry.js +2 -2
- package/dist/core/readiness/registry.js.map +1 -1
- package/dist/core/references/cel-evaluator.d.ts +1 -4
- package/dist/core/references/cel-evaluator.d.ts.map +1 -1
- package/dist/core/references/cel-evaluator.js +3 -7
- package/dist/core/references/cel-evaluator.js.map +1 -1
- package/dist/core/references/cel.d.ts +70 -0
- package/dist/core/references/cel.d.ts.map +1 -1
- package/dist/core/references/cel.js +188 -8
- package/dist/core/references/cel.js.map +1 -1
- package/dist/core/references/external-refs.d.ts.map +1 -1
- package/dist/core/references/external-refs.js +3 -0
- package/dist/core/references/external-refs.js.map +1 -1
- package/dist/core/references/resolver.d.ts.map +1 -1
- package/dist/core/references/resolver.js +28 -17
- package/dist/core/references/resolver.js.map +1 -1
- package/dist/core/references/schema-proxy.d.ts +18 -10
- package/dist/core/references/schema-proxy.d.ts.map +1 -1
- package/dist/core/references/schema-proxy.js +174 -23
- package/dist/core/references/schema-proxy.js.map +1 -1
- package/dist/core/runtime-patches/crd-schema-fix.d.ts.map +1 -1
- package/dist/core/runtime-patches/crd-schema-fix.js +4 -1
- package/dist/core/runtime-patches/crd-schema-fix.js.map +1 -1
- package/dist/core/serialization/cel-optimizer.d.ts.map +1 -1
- package/dist/core/serialization/cel-optimizer.js +2 -0
- package/dist/core/serialization/cel-optimizer.js.map +1 -1
- package/dist/core/serialization/cel-references.d.ts +75 -1
- package/dist/core/serialization/cel-references.d.ts.map +1 -1
- package/dist/core/serialization/cel-references.js +723 -145
- package/dist/core/serialization/cel-references.js.map +1 -1
- package/dist/core/serialization/core.d.ts +13 -8
- package/dist/core/serialization/core.d.ts.map +1 -1
- package/dist/core/serialization/core.js +973 -12
- package/dist/core/serialization/core.js.map +1 -1
- package/dist/core/serialization/kro-post-processing.d.ts +46 -0
- package/dist/core/serialization/kro-post-processing.d.ts.map +1 -0
- package/dist/core/serialization/kro-post-processing.js +150 -0
- package/dist/core/serialization/kro-post-processing.js.map +1 -0
- package/dist/core/serialization/schema.d.ts +62 -3
- package/dist/core/serialization/schema.d.ts.map +1 -1
- package/dist/core/serialization/schema.js +819 -6
- package/dist/core/serialization/schema.js.map +1 -1
- package/dist/core/serialization/status-analysis-pipeline.d.ts +1 -1
- package/dist/core/serialization/status-analysis-pipeline.d.ts.map +1 -1
- package/dist/core/serialization/status-analysis-pipeline.js.map +1 -1
- package/dist/core/serialization/yaml.d.ts +3 -2
- package/dist/core/serialization/yaml.d.ts.map +1 -1
- package/dist/core/serialization/yaml.js +404 -56
- package/dist/core/serialization/yaml.js.map +1 -1
- package/dist/core/singleton/singleton.d.ts +16 -0
- package/dist/core/singleton/singleton.d.ts.map +1 -0
- package/dist/core/singleton/singleton.js +135 -0
- package/dist/core/singleton/singleton.js.map +1 -0
- package/dist/core/types/common.d.ts +2 -2
- package/dist/core/types/common.d.ts.map +1 -1
- package/dist/core/types/composable.d.ts +1 -1
- package/dist/core/types/composable.d.ts.map +1 -1
- package/dist/core/types/deployment.d.ts +126 -6
- package/dist/core/types/deployment.d.ts.map +1 -1
- package/dist/core/types/deployment.js +1 -1
- package/dist/core/types/deployment.js.map +1 -1
- package/dist/core/types/kubernetes.d.ts +25 -17
- package/dist/core/types/kubernetes.d.ts.map +1 -1
- package/dist/core/types/references.d.ts +1 -1
- package/dist/core/types/references.d.ts.map +1 -1
- package/dist/core/types/references.js.map +1 -1
- package/dist/core/types/resource-graph.d.ts +1 -1
- package/dist/core/types/resource-graph.d.ts.map +1 -1
- package/dist/core/types/schema.d.ts +1 -1
- package/dist/core/types/schema.d.ts.map +1 -1
- package/dist/core/types/serialization.d.ts +62 -6
- package/dist/core/types/serialization.d.ts.map +1 -1
- package/dist/core/validation/cel-validator.d.ts +15 -2
- package/dist/core/validation/cel-validator.d.ts.map +1 -1
- package/dist/core/validation/cel-validator.js +144 -63
- package/dist/core/validation/cel-validator.js.map +1 -1
- package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts +2 -41
- package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts.map +1 -1
- package/dist/factories/apisix/compositions/apisix-bootstrap.js +262 -217
- package/dist/factories/apisix/compositions/apisix-bootstrap.js.map +1 -1
- package/dist/factories/apisix/index.d.ts +2 -2
- package/dist/factories/apisix/index.js +2 -2
- package/dist/factories/apisix/resources/helm.d.ts +2 -2
- package/dist/factories/apisix/resources/helm.d.ts.map +1 -1
- package/dist/factories/apisix/resources/helm.js.map +1 -1
- package/dist/factories/apisix/types.d.ts +21 -11
- package/dist/factories/apisix/types.d.ts.map +1 -1
- package/dist/factories/apisix/types.js +106 -4
- package/dist/factories/apisix/types.js.map +1 -1
- package/dist/factories/apisix/utils/admin-credentials.d.ts +5 -3
- package/dist/factories/apisix/utils/admin-credentials.d.ts.map +1 -1
- package/dist/factories/apisix/utils/admin-credentials.js +14 -10
- package/dist/factories/apisix/utils/admin-credentials.js.map +1 -1
- package/dist/factories/apisix/utils/helm-values-mapper.d.ts.map +1 -1
- package/dist/factories/apisix/utils/helm-values-mapper.js +4 -2
- package/dist/factories/apisix/utils/helm-values-mapper.js.map +1 -1
- package/dist/factories/cert-manager/resources/challenges.js.map +1 -1
- package/dist/factories/cert-manager/types.d.ts +3 -3
- package/dist/factories/cert-manager/types.d.ts.map +1 -1
- package/dist/factories/cilium/compositions/cilium-bootstrap.d.ts +4 -4
- package/dist/factories/cilium/types.d.ts +3 -3
- package/dist/factories/cilium/types.d.ts.map +1 -1
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts +1 -0
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts.map +1 -1
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.js +48 -0
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.js.map +1 -1
- package/dist/factories/cnpg/resources/cluster.js +1 -1
- package/dist/factories/cnpg/resources/cluster.js.map +1 -1
- package/dist/factories/cnpg/resources/helm.d.ts.map +1 -1
- package/dist/factories/cnpg/resources/helm.js +1 -0
- package/dist/factories/cnpg/resources/helm.js.map +1 -1
- package/dist/factories/cnpg/resources/pooler.js +1 -1
- package/dist/factories/cnpg/resources/pooler.js.map +1 -1
- package/dist/factories/cnpg/types.d.ts +9 -8
- package/dist/factories/cnpg/types.d.ts.map +1 -1
- package/dist/factories/cnpg/types.js +11 -0
- package/dist/factories/cnpg/types.js.map +1 -1
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.d.ts.map +1 -1
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.js +153 -41
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.js.map +1 -1
- package/dist/factories/external-dns/resources/dns-endpoint.js +1 -1
- package/dist/factories/external-dns/resources/dns-endpoint.js.map +1 -1
- package/dist/factories/external-dns/resources/helm.d.ts +1 -1
- package/dist/factories/external-dns/resources/helm.d.ts.map +1 -1
- package/dist/factories/external-dns/resources/helm.js +17 -10
- package/dist/factories/external-dns/resources/helm.js.map +1 -1
- package/dist/factories/external-dns/types.d.ts +5 -2
- package/dist/factories/external-dns/types.d.ts.map +1 -1
- package/dist/factories/external-dns/types.js.map +1 -1
- package/dist/factories/flux/git-repository.d.ts.map +1 -1
- package/dist/factories/flux/git-repository.js +1 -1
- package/dist/factories/flux/git-repository.js.map +1 -1
- package/dist/factories/flux/kustomize/kustomization.d.ts +2 -2
- package/dist/factories/flux/kustomize/kustomization.d.ts.map +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.d.ts +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.d.ts.map +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.js +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.js.map +1 -1
- package/dist/factories/helm/helm-release.d.ts +3 -2
- package/dist/factories/helm/helm-release.d.ts.map +1 -1
- package/dist/factories/helm/helm-release.js +1 -0
- package/dist/factories/helm/helm-release.js.map +1 -1
- package/dist/factories/helm/helm-repository.d.ts +1 -1
- package/dist/factories/helm/helm-repository.d.ts.map +1 -1
- package/dist/factories/helm/helm-repository.js +6 -4
- package/dist/factories/helm/helm-repository.js.map +1 -1
- package/dist/factories/helm/readiness-evaluators.d.ts +6 -6
- package/dist/factories/helm/readiness-evaluators.d.ts.map +1 -1
- package/dist/factories/helm/readiness-evaluators.js +15 -9
- package/dist/factories/helm/readiness-evaluators.js.map +1 -1
- package/dist/factories/helm/types.d.ts +5 -1
- package/dist/factories/helm/types.d.ts.map +1 -1
- package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts +1 -0
- package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts.map +1 -1
- package/dist/factories/inngest/compositions/inngest-bootstrap.js +4 -3
- package/dist/factories/inngest/compositions/inngest-bootstrap.js.map +1 -1
- package/dist/factories/inngest/resources/helm.js +1 -1
- package/dist/factories/inngest/resources/helm.js.map +1 -1
- package/dist/factories/inngest/types.d.ts +5 -4
- package/dist/factories/inngest/types.d.ts.map +1 -1
- package/dist/factories/inngest/types.js +2 -0
- package/dist/factories/inngest/types.js.map +1 -1
- package/dist/factories/kro/kro-custom-resource.js +1 -1
- package/dist/factories/kro/kro-custom-resource.js.map +1 -1
- package/dist/factories/kubernetes/config/config-map.d.ts +2 -2
- package/dist/factories/kubernetes/config/config-map.d.ts.map +1 -1
- package/dist/factories/kubernetes/config/secret.d.ts +2 -2
- package/dist/factories/kubernetes/config/secret.d.ts.map +1 -1
- package/dist/factories/kubernetes/config/secret.js +11 -1
- package/dist/factories/kubernetes/config/secret.js.map +1 -1
- package/dist/factories/kubernetes/networking/service.js +1 -1
- package/dist/factories/kubernetes/networking/service.js.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-directory.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-directory.js +9 -0
- package/dist/factories/kubernetes/yaml/yaml-directory.js.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.js +9 -0
- package/dist/factories/kubernetes/yaml/yaml-file.js.map +1 -1
- package/dist/factories/pebble/resources/helm.js.map +1 -1
- package/dist/factories/pebble/types.d.ts +2 -2
- package/dist/factories/searxng/compositions/index.d.ts +2 -0
- package/dist/factories/searxng/compositions/index.d.ts.map +1 -0
- package/dist/factories/searxng/compositions/index.js +2 -0
- package/dist/factories/searxng/compositions/index.js.map +1 -0
- package/dist/factories/searxng/compositions/searxng-bootstrap.d.ts +66 -0
- package/dist/factories/searxng/compositions/searxng-bootstrap.d.ts.map +1 -0
- package/dist/factories/searxng/compositions/searxng-bootstrap.js +275 -0
- package/dist/factories/searxng/compositions/searxng-bootstrap.js.map +1 -0
- package/dist/factories/searxng/index.d.ts +29 -0
- package/dist/factories/searxng/index.d.ts.map +1 -0
- package/dist/factories/searxng/index.js +27 -0
- package/dist/factories/searxng/index.js.map +1 -0
- package/dist/factories/searxng/resources/index.d.ts +2 -0
- package/dist/factories/searxng/resources/index.d.ts.map +1 -0
- package/dist/factories/searxng/resources/index.js +2 -0
- package/dist/factories/searxng/resources/index.js.map +1 -0
- package/dist/factories/searxng/resources/searxng.d.ts +65 -0
- package/dist/factories/searxng/resources/searxng.d.ts.map +1 -0
- package/dist/factories/searxng/resources/searxng.js +188 -0
- package/dist/factories/searxng/resources/searxng.js.map +1 -0
- package/dist/factories/searxng/types.d.ts +127 -0
- package/dist/factories/searxng/types.d.ts.map +1 -0
- package/dist/factories/searxng/types.js +177 -0
- package/dist/factories/searxng/types.js.map +1 -0
- package/dist/factories/searxng/utils/settings-builder.d.ts +47 -0
- package/dist/factories/searxng/utils/settings-builder.d.ts.map +1 -0
- package/dist/factories/searxng/utils/settings-builder.js +53 -0
- package/dist/factories/searxng/utils/settings-builder.js.map +1 -0
- package/dist/factories/simple/config/config-map.d.ts +2 -2
- package/dist/factories/simple/config/config-map.d.ts.map +1 -1
- package/dist/factories/simple/config/secret.d.ts +2 -2
- package/dist/factories/simple/config/secret.d.ts.map +1 -1
- package/dist/factories/simple/config/secret.js +28 -0
- package/dist/factories/simple/config/secret.js.map +1 -1
- package/dist/factories/simple/helm/index.d.ts +1 -1
- package/dist/factories/simple/helm/index.d.ts.map +1 -1
- package/dist/factories/simple/helm/index.js.map +1 -1
- package/dist/factories/simple/storage/persistent-volume.js.map +1 -1
- package/dist/factories/simple/types.d.ts +11 -1
- package/dist/factories/simple/types.d.ts.map +1 -1
- package/dist/factories/simple/workloads/deployment.d.ts.map +1 -1
- package/dist/factories/simple/workloads/deployment.js +3 -0
- package/dist/factories/simple/workloads/deployment.js.map +1 -1
- package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts +1 -0
- package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts.map +1 -1
- package/dist/factories/valkey/compositions/valkey-bootstrap.js +116 -0
- package/dist/factories/valkey/compositions/valkey-bootstrap.js.map +1 -1
- package/dist/factories/valkey/resources/valkey.js +1 -1
- package/dist/factories/valkey/resources/valkey.js.map +1 -1
- package/dist/factories/valkey/types.d.ts +6 -5
- package/dist/factories/valkey/types.d.ts.map +1 -1
- package/dist/factories/valkey/types.js +10 -0
- package/dist/factories/valkey/types.js.map +1 -1
- package/dist/factories/webapp/compositions/web-app-with-processing.d.ts +95 -12
- package/dist/factories/webapp/compositions/web-app-with-processing.d.ts.map +1 -1
- package/dist/factories/webapp/compositions/web-app-with-processing.js +185 -26
- package/dist/factories/webapp/compositions/web-app-with-processing.js.map +1 -1
- package/dist/factories/webapp/index.d.ts +3 -4
- package/dist/factories/webapp/index.d.ts.map +1 -1
- package/dist/factories/webapp/index.js +3 -4
- package/dist/factories/webapp/index.js.map +1 -1
- package/dist/factories/webapp/types.d.ts +60 -2
- package/dist/factories/webapp/types.d.ts.map +1 -1
- package/dist/factories/webapp/types.js +80 -3
- package/dist/factories/webapp/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/shared/brands.d.ts +18 -8
- package/dist/shared/brands.d.ts.map +1 -1
- package/dist/shared/brands.js +19 -9
- package/dist/shared/brands.js.map +1 -1
- package/dist/utils/cel-escape.d.ts +12 -0
- package/dist/utils/cel-escape.d.ts.map +1 -0
- package/dist/utils/cel-escape.js +19 -0
- package/dist/utils/cel-escape.js.map +1 -0
- package/package.json +7 -2
|
@@ -5,9 +5,26 @@
|
|
|
5
5
|
* simple schemas, and provides utility functions for generating schemas
|
|
6
6
|
* from resource maps.
|
|
7
7
|
*/
|
|
8
|
+
import { escapeCelString } from '../../utils/cel-escape.js';
|
|
9
|
+
import { KUBERNETES_REF_SCHEMA_MARKER_SOURCE } from '../../shared/brands.js';
|
|
10
|
+
import { createCompositionContext, runWithCompositionContext } from '../composition/context.js';
|
|
11
|
+
import { getComponentLogger } from '../logging/index.js';
|
|
12
|
+
import { getMetadataField } from '../metadata/index.js';
|
|
8
13
|
import { pascalCase } from '../../utils/string.js';
|
|
9
14
|
import { separateStatusFields } from '../validation/cel-validator.js';
|
|
10
15
|
import { serializeStatusMappingsToCel } from './cel-references.js';
|
|
16
|
+
const logger = getComponentLogger('schema-defaults');
|
|
17
|
+
const SCHEMA_MARKER_PATTERN_SOURCE = KUBERNETES_REF_SCHEMA_MARKER_SOURCE;
|
|
18
|
+
function deriveResourceIdAliases(resourceId) {
|
|
19
|
+
const aliases = [];
|
|
20
|
+
for (const match of resourceId.matchAll(/\d+/g)) {
|
|
21
|
+
const suffix = resourceId.slice((match.index ?? 0) + match[0].length);
|
|
22
|
+
if (suffix && /^[A-Z]/.test(suffix)) {
|
|
23
|
+
aliases.push(suffix.charAt(0).toLowerCase() + suffix.slice(1));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return aliases;
|
|
27
|
+
}
|
|
11
28
|
// ---------------------------------------------------------------------------
|
|
12
29
|
// Arktype JSON AST → Kro type helpers (private)
|
|
13
30
|
// ---------------------------------------------------------------------------
|
|
@@ -27,6 +44,20 @@ function getKroTypeFromJson(node) {
|
|
|
27
44
|
if (nodeObj.domain === 'number' && nodeObj.divisor === 1) {
|
|
28
45
|
return 'integer';
|
|
29
46
|
}
|
|
47
|
+
// Map / Record types — arktype represents `Record<string, V>` as
|
|
48
|
+
// `{ domain: "object", index: [{ signature: "string", value: V }] }`.
|
|
49
|
+
// KRO SimpleSchema uses `map[string]<value-type>` notation for these.
|
|
50
|
+
if (nodeObj.domain === 'object' &&
|
|
51
|
+
Array.isArray(nodeObj.index) &&
|
|
52
|
+
nodeObj.index.length === 1) {
|
|
53
|
+
const indexEntry = nodeObj.index[0];
|
|
54
|
+
if (indexEntry.signature === 'string') {
|
|
55
|
+
return `map[string]${getKroTypeFromJson(indexEntry.value)}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (nodeObj.domain === 'object' || nodeObj.required || nodeObj.optional) {
|
|
59
|
+
return 'object';
|
|
60
|
+
}
|
|
30
61
|
}
|
|
31
62
|
// Case 2: Array → union of literals
|
|
32
63
|
if (Array.isArray(node)) {
|
|
@@ -35,6 +66,9 @@ function getKroTypeFromJson(node) {
|
|
|
35
66
|
node.some((branch) => branch.unit === false)) {
|
|
36
67
|
return 'boolean';
|
|
37
68
|
}
|
|
69
|
+
if (node.some((branch) => !branch || typeof branch !== 'object' || !('unit' in branch))) {
|
|
70
|
+
return 'object';
|
|
71
|
+
}
|
|
38
72
|
const enumValues = node.map((branch) => {
|
|
39
73
|
if (typeof branch.unit === 'string') {
|
|
40
74
|
return branch.unit;
|
|
@@ -85,7 +119,6 @@ function arktypeJsonToKroFields(node) {
|
|
|
85
119
|
if (typeof childNode === 'object' &&
|
|
86
120
|
childNode !== null &&
|
|
87
121
|
(childNode.required || childNode.optional)) {
|
|
88
|
-
// Nested object — recurse to produce a nested schema
|
|
89
122
|
fields[prop.key] = arktypeJsonToKroFields(childNode);
|
|
90
123
|
}
|
|
91
124
|
else {
|
|
@@ -95,6 +128,643 @@ function arktypeJsonToKroFields(node) {
|
|
|
95
128
|
return fields;
|
|
96
129
|
}
|
|
97
130
|
// ---------------------------------------------------------------------------
|
|
131
|
+
// Nullish coalescing default extraction
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
/**
|
|
134
|
+
* Extract literal default values from `spec.field ?? literalValue` patterns
|
|
135
|
+
* in a composition function's source code.
|
|
136
|
+
*
|
|
137
|
+
* **FAST PATH — NOT AUTHORITATIVE.** This regex-based extraction runs first
|
|
138
|
+
* to catch the common case where a composition uses `spec.X ?? 'literal'`
|
|
139
|
+
* with a literal right-hand side. It is intentionally conservative:
|
|
140
|
+
*
|
|
141
|
+
* - Both `??` and the literal must be on the same source line.
|
|
142
|
+
* - The literal must be a string, number, boolean, or minified !0/!1 form.
|
|
143
|
+
* - The right-hand side cannot be a constant, variable, template literal,
|
|
144
|
+
* function call, or expression involving other spec fields.
|
|
145
|
+
*
|
|
146
|
+
* Anything this regex misses is caught by {@link resolveDefaultsByReExecution}
|
|
147
|
+
* (Phase 2), which actually executes the composition function with optional
|
|
148
|
+
* fields set to `undefined` and observes the concrete resolved values. Phase 2
|
|
149
|
+
* is the authoritative source; this function only exists to short-circuit
|
|
150
|
+
* the common case so the heavy re-execution doesn't need to compare every
|
|
151
|
+
* field for every composition.
|
|
152
|
+
*
|
|
153
|
+
* In KRO mode, `??` doesn't trigger for proxy objects (they're truthy).
|
|
154
|
+
* Instead, we detect these patterns via fn.toString() and add
|
|
155
|
+
* `| default=<value>` annotations to the KRO SimpleSchema so that KRO
|
|
156
|
+
* uses the intended default when the field isn't provided in the CR.
|
|
157
|
+
*
|
|
158
|
+
* NOTE: `.orValue()` is NOT supported in KRO resource templates (only in
|
|
159
|
+
* status CEL). Schema `| default=` is the correct mechanism for resource
|
|
160
|
+
* template defaults.
|
|
161
|
+
*
|
|
162
|
+
* Handles:
|
|
163
|
+
* - `spec.field ?? 'string'` or `spec.field ?? "string"`
|
|
164
|
+
* - `spec.field ?? 123` (numbers)
|
|
165
|
+
* - `spec.field ?? true/false` (booleans, including minified !0/!1)
|
|
166
|
+
* - `spec.parent?.child ?? value` (optional chaining)
|
|
167
|
+
*/
|
|
168
|
+
export function extractNullishDefaults(fnSource) {
|
|
169
|
+
const defaults = {};
|
|
170
|
+
const pattern = /spec\.([\w]+(?:\??\.\w+)*)\s*\?\?\s*(?:(['"])([^'"]*)\2|(-?\d+(?:\.\d+)?)|true|false|!0|!1)(?=\s*[;,)}\n])/g;
|
|
171
|
+
let match = pattern.exec(fnSource);
|
|
172
|
+
while (match !== null) {
|
|
173
|
+
const fieldPath = match[1]?.replace(/\?\./g, '.');
|
|
174
|
+
if (!fieldPath) {
|
|
175
|
+
match = pattern.exec(fnSource);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const stringValue = match[3];
|
|
179
|
+
const numericValue = match[4];
|
|
180
|
+
if (stringValue !== undefined) {
|
|
181
|
+
defaults[fieldPath] = stringValue;
|
|
182
|
+
}
|
|
183
|
+
else if (numericValue !== undefined) {
|
|
184
|
+
defaults[fieldPath] = Number(numericValue);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const fullMatch = match[0];
|
|
188
|
+
if (fullMatch.endsWith('true') || fullMatch.endsWith('!0'))
|
|
189
|
+
defaults[fieldPath] = true;
|
|
190
|
+
else if (fullMatch.endsWith('false') || fullMatch.endsWith('!1'))
|
|
191
|
+
defaults[fieldPath] = false;
|
|
192
|
+
}
|
|
193
|
+
match = pattern.exec(fnSource);
|
|
194
|
+
}
|
|
195
|
+
return defaults;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Placeholder value passed in place of required spec fields during the
|
|
199
|
+
* defaults-extraction re-execution. Any captured leaf value that IS this
|
|
200
|
+
* string, CONTAINS this string as a substring (from template-literal
|
|
201
|
+
* interpolation), or is NaN (from arithmetic coercion of the string) is
|
|
202
|
+
* filtered out by the sentinel-detection logic in `extractDefaultsByComparison`
|
|
203
|
+
* so it never gets recorded as a real schema default.
|
|
204
|
+
*
|
|
205
|
+
* The choice of a string (rather than a Symbol or object) is deliberate:
|
|
206
|
+
* - String concatenation preserves the token so template-literal usage
|
|
207
|
+
* produces a clearly-identifiable captured value (e.g. `"__typekro_default__-cfg"`).
|
|
208
|
+
* - Truthiness matches the real proxy behavior (proxies are truthy), so
|
|
209
|
+
* `if (spec.requiredField)` takes the same branch in both runs.
|
|
210
|
+
* - Numeric contexts coerce to NaN in both runs (proxies also coerce to
|
|
211
|
+
* NaN because functions have no numeric value), so arithmetic-derived
|
|
212
|
+
* NaN values are symmetric across runs and don't produce spurious defaults.
|
|
213
|
+
*
|
|
214
|
+
* If the propagation ever drifts out of this sentinel's shape (e.g. someone
|
|
215
|
+
* introduces a Symbol-based sentinel), update `isSentinelDerivedValue` below
|
|
216
|
+
* to match.
|
|
217
|
+
*/
|
|
218
|
+
export const REQUIRED_FIELD_SENTINEL = '__typekro_default__';
|
|
219
|
+
/**
|
|
220
|
+
* Check whether a captured value is derived from the required-field sentinel
|
|
221
|
+
* and therefore should NOT be recorded as a real schema default. Handles:
|
|
222
|
+
* - Direct sentinel or substring (template-literal propagation)
|
|
223
|
+
* - NaN (numeric-coercion propagation — both runs produce NaN symmetrically,
|
|
224
|
+
* but the defaults run's NaN must be filtered out of the defaults map)
|
|
225
|
+
*/
|
|
226
|
+
function isSentinelDerivedValue(v) {
|
|
227
|
+
if (typeof v === 'string')
|
|
228
|
+
return v.includes(REQUIRED_FIELD_SENTINEL);
|
|
229
|
+
if (typeof v === 'number')
|
|
230
|
+
return Number.isNaN(v);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Resolve ?? defaults by re-executing the composition function with undefined
|
|
235
|
+
* for optional spec fields. This triggers ?? fallbacks naturally, resolving
|
|
236
|
+
* imported constants from the closure (e.g., DEFAULT_SEARXNG_IMAGE).
|
|
237
|
+
*
|
|
238
|
+
* Also detects ternary conditional sections (spec.field ? section : '') by
|
|
239
|
+
* comparing resource strings between proxy-run and defaults-run.
|
|
240
|
+
*
|
|
241
|
+
* Runs in a temporary, isolated composition context. Resource registrations
|
|
242
|
+
* are contained within the temp context. However, composition functions with
|
|
243
|
+
* external side effects (API calls, logging, metrics) will fire during this
|
|
244
|
+
* re-execution. Composition functions should be side-effect-free beyond
|
|
245
|
+
* resource creation for this mechanism to work safely.
|
|
246
|
+
*/
|
|
247
|
+
function resolveDefaultsByReExecution(compositionFn, specType, proxyResources) {
|
|
248
|
+
try {
|
|
249
|
+
const specJson = specType.json;
|
|
250
|
+
if (!specJson)
|
|
251
|
+
return undefined;
|
|
252
|
+
const defaultsSpec = {};
|
|
253
|
+
for (const p of specJson.required ?? [])
|
|
254
|
+
defaultsSpec[p.key] = REQUIRED_FIELD_SENTINEL;
|
|
255
|
+
for (const p of specJson.optional ?? [])
|
|
256
|
+
defaultsSpec[p.key] = undefined;
|
|
257
|
+
// Build the set of optional top-level field names. Only optional fields
|
|
258
|
+
// can be ternary-controlled (required fields always have a value).
|
|
259
|
+
const optionalFieldNames = new Set((specJson.optional ?? []).map(p => p.key));
|
|
260
|
+
const tempCtx = createCompositionContext('defaults-extraction');
|
|
261
|
+
runWithCompositionContext(tempCtx, () => {
|
|
262
|
+
compositionFn(defaultsSpec);
|
|
263
|
+
});
|
|
264
|
+
const defaults = {};
|
|
265
|
+
const ternaryConditionals = [];
|
|
266
|
+
const ternaryConditionFieldHints = extractTernaryConditionFieldHints(compositionFn.toString());
|
|
267
|
+
// Match resources by RESOURCE ID rather than by insertion order. Compositions
|
|
268
|
+
// that use `if (!spec.optional) { createResource(...) }` produce different
|
|
269
|
+
// resource counts between runs — positional matching silently pairs
|
|
270
|
+
// unrelated resources (e.g., the defaults run's auto-Secret against the
|
|
271
|
+
// proxy run's Deployment) and corrupts both default extraction and
|
|
272
|
+
// ternary detection. Key-based matching is robust against conditional
|
|
273
|
+
// resource creation: resources present in only one run are simply skipped
|
|
274
|
+
// for comparison.
|
|
275
|
+
const proxyEntries = Object.entries(proxyResources);
|
|
276
|
+
const defaultsEntries = Object.entries(tempCtx.resources);
|
|
277
|
+
const defaultsMap = new Map(defaultsEntries);
|
|
278
|
+
if (proxyEntries.length !== defaultsEntries.length) {
|
|
279
|
+
// Different resource counts between runs are expected when the
|
|
280
|
+
// composition uses conditional `createResource` patterns — the
|
|
281
|
+
// defaults run takes different branches than the proxy run.
|
|
282
|
+
// Key-based matching handles this correctly, so downgrade to debug.
|
|
283
|
+
logger.debug('Re-execution produced a different number of resources than the proxy run — matching by resource ID', {
|
|
284
|
+
proxyResourceCount: proxyEntries.length,
|
|
285
|
+
defaultsResourceCount: defaultsEntries.length,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
for (const [id, proxyRes] of proxyEntries) {
|
|
289
|
+
const defaultsRes = defaultsMap.get(id);
|
|
290
|
+
if (!defaultsRes)
|
|
291
|
+
continue; // resource only in proxy run — skip
|
|
292
|
+
extractDefaultsByComparison(proxyRes, defaultsRes, defaults);
|
|
293
|
+
extractTernaryConditionals(proxyRes, defaultsRes, ternaryConditionals, optionalFieldNames, ternaryConditionFieldHints);
|
|
294
|
+
}
|
|
295
|
+
const hasResults = Object.keys(defaults).length > 0 || ternaryConditionals.length > 0;
|
|
296
|
+
return hasResults ? { defaults, ternaryConditionals } : undefined;
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
// Best-effort: composition functions with undefined spec fields may throw
|
|
300
|
+
// (e.g., accessing spec.nested.field without optional chaining). Log at
|
|
301
|
+
// debug level for troubleshooting but don't fail schema generation.
|
|
302
|
+
logger.debug('Defaults re-execution failed', {
|
|
303
|
+
error: err instanceof Error ? err.message : String(err),
|
|
304
|
+
});
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Recursively compare proxy-run resource values (with __KUBERNETES_REF__ markers)
|
|
310
|
+
* against defaults-run values (with resolved constants) to find field defaults.
|
|
311
|
+
*
|
|
312
|
+
* When a proxy value is `__KUBERNETES_REF___schema___spec.FIELD__` and the
|
|
313
|
+
* corresponding defaults value is a concrete scalar, we record FIELD → value.
|
|
314
|
+
*/
|
|
315
|
+
function extractDefaultsByComparison(proxyVal, defaultsVal, result) {
|
|
316
|
+
// KubernetesRef proxies are functions whose toString() returns a marker string.
|
|
317
|
+
// Coerce to string to check for __KUBERNETES_REF__ markers.
|
|
318
|
+
const proxyStr = typeof proxyVal === 'function' || typeof proxyVal === 'string'
|
|
319
|
+
? String(proxyVal) : null;
|
|
320
|
+
// Only match when the proxy value IS the marker (exact, single reference).
|
|
321
|
+
// If the marker is embedded inside a larger string (template literal), the
|
|
322
|
+
// value is a transformation, not a default — e.g., `\`has-${spec.extra}\``
|
|
323
|
+
// produces `"has-<marker>"` which is NOT a default for `extra`.
|
|
324
|
+
const exactMarkerMatch = proxyStr?.match(new RegExp(`^${SCHEMA_MARKER_PATTERN_SOURCE}$`));
|
|
325
|
+
const exactFieldPath = exactMarkerMatch?.[1];
|
|
326
|
+
if (exactFieldPath?.startsWith('spec.')) {
|
|
327
|
+
if (typeof defaultsVal === 'string' ||
|
|
328
|
+
typeof defaultsVal === 'number' ||
|
|
329
|
+
typeof defaultsVal === 'boolean') {
|
|
330
|
+
// Skip values derived from the required-field sentinel — these aren't
|
|
331
|
+
// real defaults. Covers: string containing the sentinel token
|
|
332
|
+
// (template-literal propagation), NaN (arithmetic-coercion
|
|
333
|
+
// propagation), and the sentinel itself.
|
|
334
|
+
if (isSentinelDerivedValue(defaultsVal))
|
|
335
|
+
return;
|
|
336
|
+
const fieldKey = exactFieldPath.slice('spec.'.length);
|
|
337
|
+
if (fieldKey) {
|
|
338
|
+
result[fieldKey] = defaultsVal;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// If the proxy is a string but contains the marker as part of larger content,
|
|
344
|
+
// skip the defaults check (recursion into arrays/objects still applies below).
|
|
345
|
+
if (proxyStr?.includes('__KUBERNETES_REF___schema___')) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (Array.isArray(proxyVal) && Array.isArray(defaultsVal)) {
|
|
349
|
+
for (let i = 0; i < Math.min(proxyVal.length, defaultsVal.length); i++) {
|
|
350
|
+
extractDefaultsByComparison(proxyVal[i], defaultsVal[i], result);
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (proxyVal && typeof proxyVal === 'object' && defaultsVal && typeof defaultsVal === 'object') {
|
|
355
|
+
for (const key of Object.keys(proxyVal)) {
|
|
356
|
+
extractDefaultsByComparison(proxyVal[key], defaultsVal[key], result);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Detect ternary conditional sections by finding markers in the proxy-run
|
|
362
|
+
* string that are completely ABSENT from the defaults-run string.
|
|
363
|
+
*
|
|
364
|
+
* When `spec.redisUrl ? \`redis: ...\` : ''` is evaluated:
|
|
365
|
+
* - Proxy run (truthy): the redis section with markers is included
|
|
366
|
+
* - Defaults run (falsy): the redis section is empty — the marker is absent
|
|
367
|
+
*
|
|
368
|
+
* For each such "orphan" marker, we extract the surrounding section
|
|
369
|
+
* (from the previous newline-at-start-of-key to the marker's end) as a
|
|
370
|
+
* ternary-controlled conditional.
|
|
371
|
+
*
|
|
372
|
+
* Ternary test-field hints
|
|
373
|
+
* ------------------------
|
|
374
|
+
* Re-execution only shows that a marker-containing section disappeared;
|
|
375
|
+
* it does not leave runtime metadata for the ternary TEST expression. The
|
|
376
|
+
* caller supplies a best-effort map derived from the composition source so
|
|
377
|
+
* common decoupled patterns can guard on the tested field:
|
|
378
|
+
*
|
|
379
|
+
* spec.redisUrl ? `redis:\n url: ${spec.redisUrl}` : ''
|
|
380
|
+
*
|
|
381
|
+
* spec.enableRedis ? `redis:\n url: ${spec.connectionString}` : ''
|
|
382
|
+
*
|
|
383
|
+
* If no hint is available, we fall back to the referenced field, preserving
|
|
384
|
+
* the historical behavior for `spec.redisUrl ? ... ${spec.redisUrl} ...`.
|
|
385
|
+
*
|
|
386
|
+
* FRAGILE: this walks line-by-line through the proxy string and backs
|
|
387
|
+
* through parent YAML keys by regex-matching `^\s*\w+:\s*$` and comparing
|
|
388
|
+
* against a set of lines from the defaults string. It assumes the
|
|
389
|
+
* composition constructs YAML-ish content directly (typically via template
|
|
390
|
+
* literal) with 2-space indentation, canonical key: value syntax, and no
|
|
391
|
+
* block scalars (`|`, `>`). Compositions that emit flow-style YAML, that
|
|
392
|
+
* use tab indentation, that quote keys, or that assemble the string
|
|
393
|
+
* through multiple splice operations will not match cleanly. The
|
|
394
|
+
* extracted `proxySection` is later consumed by
|
|
395
|
+
* `applyTernaryConditionalsToResources` via exact substring match — keep
|
|
396
|
+
* both functions' assumptions in sync when updating either side.
|
|
397
|
+
*
|
|
398
|
+
* Tracked for replacement with AST-based detection in
|
|
399
|
+
* https://github.com/yehudacohen/typekro/issues/57
|
|
400
|
+
*/
|
|
401
|
+
function extractTernaryConditionals(proxyVal, defaultsVal, result, optionalFieldNames, conditionFieldHints) {
|
|
402
|
+
const proxyStr = typeof proxyVal === 'string' ? proxyVal : null;
|
|
403
|
+
const defaultsStr = typeof defaultsVal === 'string' ? defaultsVal : null;
|
|
404
|
+
if (proxyStr && defaultsStr && proxyStr !== defaultsStr && proxyStr.length > defaultsStr.length) {
|
|
405
|
+
// Find all __KUBERNETES_REF__ markers in the proxy string
|
|
406
|
+
const markerPattern = new RegExp(SCHEMA_MARKER_PATTERN_SOURCE, 'g');
|
|
407
|
+
let match = markerPattern.exec(proxyStr);
|
|
408
|
+
while (match !== null) {
|
|
409
|
+
const fieldPath = match[1];
|
|
410
|
+
if (!fieldPath?.startsWith('spec.')) {
|
|
411
|
+
match = markerPattern.exec(proxyStr);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const field = fieldPath.slice('spec.'.length);
|
|
415
|
+
const conditionField = conditionFieldHints.get(field) ?? field;
|
|
416
|
+
const marker = match[0];
|
|
417
|
+
// Only optional fields can be ternary-controlled. Required fields always
|
|
418
|
+
// have a value (the sentinel in the defaults run), so their markers are
|
|
419
|
+
// always "absent" from the defaults string but they're substitutions,
|
|
420
|
+
// not conditionals.
|
|
421
|
+
const topLevelField = conditionField.split('.')[0];
|
|
422
|
+
if (!topLevelField) {
|
|
423
|
+
match = markerPattern.exec(proxyStr);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (!optionalFieldNames.has(topLevelField)) {
|
|
427
|
+
match = markerPattern.exec(proxyStr);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
// Check if this marker is a TERNARY conditional (section absent from defaults)
|
|
431
|
+
// vs a ?? fallback (same key exists with a different value in defaults).
|
|
432
|
+
// Find the YAML key before the marker (e.g., `url: ` before the redisUrl marker).
|
|
433
|
+
const lineStart = proxyStr.lastIndexOf('\n', match.index) + 1;
|
|
434
|
+
const line = proxyStr.slice(lineStart, match.index + marker.length);
|
|
435
|
+
const keyMatch = line.match(/(\w+):\s*$/)?.[1] || line.match(/^\s*(\w+):/)?.[1];
|
|
436
|
+
const keyExistsInDefaults = keyMatch
|
|
437
|
+
? new RegExp(`\\b${keyMatch}\\s*:`).test(defaultsStr)
|
|
438
|
+
: false;
|
|
439
|
+
// If the key exists in defaults, this is a ?? fallback (handled elsewhere).
|
|
440
|
+
// If the key is absent, this is a ternary conditional (section not present).
|
|
441
|
+
if (!keyExistsInDefaults && !defaultsStr.includes(marker)) {
|
|
442
|
+
// Extract the section containing this marker.
|
|
443
|
+
// Walk backward from the marker to find the start of the conditional
|
|
444
|
+
// section. Include parent YAML keys at ALL nesting levels (not just one)
|
|
445
|
+
// so that `redis:\n connection:\n url: ${ref}` captures all three lines.
|
|
446
|
+
let sectionStart = match.index;
|
|
447
|
+
while (sectionStart > 0 && proxyStr[sectionStart - 1] !== '\n') {
|
|
448
|
+
sectionStart--;
|
|
449
|
+
}
|
|
450
|
+
// Walk back through preceding blank lines
|
|
451
|
+
while (sectionStart > 0 && proxyStr[sectionStart - 1] === '\n') {
|
|
452
|
+
sectionStart--;
|
|
453
|
+
}
|
|
454
|
+
// Walk back through parent YAML keys (lines with `key:` and no value).
|
|
455
|
+
// Keep walking as long as the previous line is a key-only line that
|
|
456
|
+
// doesn't exist as a complete line in the defaults. Set membership
|
|
457
|
+
// (not substring match) avoids false stops on coincidental substrings.
|
|
458
|
+
const defaultsLines = new Set(defaultsStr.split('\n'));
|
|
459
|
+
const allLines = proxyStr.slice(0, sectionStart).split('\n');
|
|
460
|
+
while (allLines.length > 0) {
|
|
461
|
+
const prevLine = allLines[allLines.length - 1];
|
|
462
|
+
if (prevLine === undefined)
|
|
463
|
+
break;
|
|
464
|
+
const isKeyOnlyLine = /^\s*\w+:\s*$/.test(prevLine);
|
|
465
|
+
const lineExistsInDefaults = defaultsLines.has(prevLine);
|
|
466
|
+
if (isKeyOnlyLine && !lineExistsInDefaults) {
|
|
467
|
+
sectionStart -= prevLine.length + 1; // +1 for the \n
|
|
468
|
+
allLines.pop();
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// Section ends after the marker
|
|
475
|
+
const sectionEnd = match.index + marker.length;
|
|
476
|
+
const proxySection = proxyStr.slice(sectionStart, sectionEnd);
|
|
477
|
+
// Verify the section, when removed from proxy string, makes it closer to defaults
|
|
478
|
+
if (proxySection.includes(marker)) {
|
|
479
|
+
result.push({
|
|
480
|
+
proxySection,
|
|
481
|
+
falsyValue: '',
|
|
482
|
+
conditionField,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
match = markerPattern.exec(proxyStr);
|
|
487
|
+
}
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
// Recurse into arrays and objects
|
|
491
|
+
if (Array.isArray(proxyVal) && Array.isArray(defaultsVal)) {
|
|
492
|
+
for (let i = 0; i < Math.min(proxyVal.length, defaultsVal.length); i++) {
|
|
493
|
+
extractTernaryConditionals(proxyVal[i], defaultsVal[i], result, optionalFieldNames, conditionFieldHints);
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (proxyVal && typeof proxyVal === 'object' && defaultsVal && typeof defaultsVal === 'object') {
|
|
498
|
+
for (const key of Object.keys(proxyVal)) {
|
|
499
|
+
extractTernaryConditionals(proxyVal[key], defaultsVal[key], result, optionalFieldNames, conditionFieldHints);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function extractTernaryConditionFieldHints(source) {
|
|
504
|
+
const hints = new Map();
|
|
505
|
+
const ternaryTemplatePattern = /spec\.([A-Za-z_$][\w$]*(?:\?\.[A-Za-z_$][\w$]*|\.[A-Za-z_$][\w$]*)*)\s*\?\s*`([\s\S]*?)`\s*:\s*(?:''|""|``)/g;
|
|
506
|
+
let match = ternaryTemplatePattern.exec(source);
|
|
507
|
+
while (match) {
|
|
508
|
+
const conditionField = normalizeSpecFieldPath(match[1]);
|
|
509
|
+
const consequent = match[2] ?? '';
|
|
510
|
+
if (conditionField) {
|
|
511
|
+
const refPattern = /\$\{\s*spec\.([A-Za-z_$][\w$]*(?:\?\.[A-Za-z_$][\w$]*|\.[A-Za-z_$][\w$]*)*)/g;
|
|
512
|
+
let refMatch = refPattern.exec(consequent);
|
|
513
|
+
while (refMatch) {
|
|
514
|
+
const referencedField = normalizeSpecFieldPath(refMatch[1]);
|
|
515
|
+
if (referencedField && !hints.has(referencedField)) {
|
|
516
|
+
hints.set(referencedField, conditionField);
|
|
517
|
+
}
|
|
518
|
+
refMatch = refPattern.exec(consequent);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
match = ternaryTemplatePattern.exec(source);
|
|
522
|
+
}
|
|
523
|
+
return hints;
|
|
524
|
+
}
|
|
525
|
+
function normalizeSpecFieldPath(path) {
|
|
526
|
+
return path?.replace(/\?\./g, '.');
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Apply extracted nullish defaults to a KRO SimpleSchema spec fields object.
|
|
530
|
+
* Appends `| default=<value>` to the field's type string.
|
|
531
|
+
*
|
|
532
|
+
* The two-phase default extraction uses this function twice:
|
|
533
|
+
* - Phase 1 (regex, `extractNullishDefaults`) — fast but may misfire on
|
|
534
|
+
* edge cases (multi-line expressions, nested parens). Called with
|
|
535
|
+
* `overwrite: false` so it doesn't clobber existing annotations.
|
|
536
|
+
* - Phase 2 (re-execution, `resolveDefaultsByReExecution`) — slower but
|
|
537
|
+
* authoritative. Called with `overwrite: true` so it REPLACES any
|
|
538
|
+
* Phase 1 annotation for the same field, ensuring a wrong Phase 1
|
|
539
|
+
* result doesn't survive when Phase 2 produces a corrected value.
|
|
540
|
+
*/
|
|
541
|
+
function applyNullishDefaults(specFields, defaults, overwrite = false) {
|
|
542
|
+
for (const [path, value] of Object.entries(defaults)) {
|
|
543
|
+
const parts = path.split('.');
|
|
544
|
+
let current = specFields;
|
|
545
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
546
|
+
const part = parts[i];
|
|
547
|
+
if (!part)
|
|
548
|
+
continue;
|
|
549
|
+
if (current[part] && typeof current[part] === 'object') {
|
|
550
|
+
current = current[part];
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
current = undefined;
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (!current)
|
|
558
|
+
continue;
|
|
559
|
+
const fieldName = parts[parts.length - 1];
|
|
560
|
+
if (!fieldName)
|
|
561
|
+
continue;
|
|
562
|
+
const existingType = current[fieldName];
|
|
563
|
+
if (typeof existingType !== 'string')
|
|
564
|
+
continue;
|
|
565
|
+
// Escape special characters in string defaults so they survive
|
|
566
|
+
// the KRO SimpleSchema default= annotation format. Without this,
|
|
567
|
+
// multiline strings (e.g., YAML config blobs) break the parser.
|
|
568
|
+
const defaultStr = typeof value === 'string'
|
|
569
|
+
? `"${escapeCelString(value)}"`
|
|
570
|
+
: String(value);
|
|
571
|
+
const hasDefault = existingType.includes('default=');
|
|
572
|
+
if (!hasDefault) {
|
|
573
|
+
current[fieldName] = `${existingType} | default=${defaultStr}`;
|
|
574
|
+
}
|
|
575
|
+
else if (overwrite) {
|
|
576
|
+
// Strip any existing `| default=...` segment and replace with the
|
|
577
|
+
// authoritative Phase 2 value. The segment may appear as `| default="x"`,
|
|
578
|
+
// `| default=123`, or `| default=true` — match to the end of the value
|
|
579
|
+
// or to the next `|` (for additional annotations chained after it).
|
|
580
|
+
const withoutDefault = existingType.replace(/\s*\|\s*default=(?:"[^"]*"|[^|]*)/, '');
|
|
581
|
+
current[fieldName] = `${withoutDefault} | default=${defaultStr}`;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Infer the KRO SimpleSchema type string for a literal default value.
|
|
587
|
+
*/
|
|
588
|
+
function kroTypeForLiteral(value) {
|
|
589
|
+
if (typeof value === 'string') {
|
|
590
|
+
return `string | default="${escapeCelString(value)}"`;
|
|
591
|
+
}
|
|
592
|
+
if (typeof value === 'number') {
|
|
593
|
+
return Number.isInteger(value)
|
|
594
|
+
? `integer | default=${value}`
|
|
595
|
+
: `number | default=${value}`;
|
|
596
|
+
}
|
|
597
|
+
if (typeof value === 'boolean') {
|
|
598
|
+
return `boolean | default=${value}`;
|
|
599
|
+
}
|
|
600
|
+
return 'string';
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* For each default whose path doesn't exist in `specFields`, CREATE the
|
|
604
|
+
* missing leaf (and any intermediate object nodes along the way) with
|
|
605
|
+
* an appropriate KRO type string + default annotation.
|
|
606
|
+
*
|
|
607
|
+
* This is the complement to {@link applyNullishDefaults}, which only
|
|
608
|
+
* annotates fields that already exist. Adding missing fields enables the
|
|
609
|
+
* "propagate inner composition `?? defaults` to the outer schema"
|
|
610
|
+
* mechanism: when an inner composition reads `spec.X?.Y ?? <literal>`
|
|
611
|
+
* and the outer schema doesn't declare Y, we auto-declare it with the
|
|
612
|
+
* inner's default so KRO can resolve the reference at apply time.
|
|
613
|
+
*
|
|
614
|
+
* **Scope rules:**
|
|
615
|
+
* - We only auto-create a missing field if EITHER the full path's parent
|
|
616
|
+
* already exists as an object in `specFields` (we're filling in a
|
|
617
|
+
* single missing leaf under a known object), OR the top-level parent
|
|
618
|
+
* (the first segment) doesn't exist at all (we're adding a whole new
|
|
619
|
+
* optional field). We do NOT create intermediate objects where the
|
|
620
|
+
* outer has explicitly declared a non-object type.
|
|
621
|
+
* - Existing fields are never modified — use `applyNullishDefaults`
|
|
622
|
+
* for that.
|
|
623
|
+
*/
|
|
624
|
+
function addMissingDefaultFields(specFields, defaults) {
|
|
625
|
+
for (const [path, value] of Object.entries(defaults)) {
|
|
626
|
+
const parts = path.split('.');
|
|
627
|
+
if (parts.length === 0)
|
|
628
|
+
continue;
|
|
629
|
+
// Walk existing intermediate objects.
|
|
630
|
+
let current = specFields;
|
|
631
|
+
let i = 0;
|
|
632
|
+
for (; i < parts.length - 1; i++) {
|
|
633
|
+
const part = parts[i];
|
|
634
|
+
if (!part)
|
|
635
|
+
continue;
|
|
636
|
+
const next = current[part];
|
|
637
|
+
if (next && typeof next === 'object' && !Array.isArray(next)) {
|
|
638
|
+
current = next;
|
|
639
|
+
}
|
|
640
|
+
else if (next === undefined) {
|
|
641
|
+
// Create missing intermediate object node.
|
|
642
|
+
const obj = {};
|
|
643
|
+
current[part] = obj;
|
|
644
|
+
current = obj;
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
// Intermediate path conflicts with an existing scalar — bail.
|
|
648
|
+
current = undefined;
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (!current)
|
|
653
|
+
continue;
|
|
654
|
+
const leafName = parts[parts.length - 1];
|
|
655
|
+
if (!leafName)
|
|
656
|
+
continue;
|
|
657
|
+
if (leafName in current)
|
|
658
|
+
continue; // existing — skip
|
|
659
|
+
current[leafName] = kroTypeForLiteral(value);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function remapDefaultPath(path, mappings) {
|
|
663
|
+
if (Object.hasOwn(mappings, '')) {
|
|
664
|
+
const mappedPrefix = mappings[''];
|
|
665
|
+
return mappedPrefix ? `${mappedPrefix}.${path}` : path;
|
|
666
|
+
}
|
|
667
|
+
const matches = Object.keys(mappings)
|
|
668
|
+
.filter((prefix) => path === prefix || path.startsWith(`${prefix}.`))
|
|
669
|
+
.sort((left, right) => right.length - left.length);
|
|
670
|
+
const prefix = matches[0];
|
|
671
|
+
if (prefix === undefined)
|
|
672
|
+
return undefined;
|
|
673
|
+
const mappedPrefix = mappings[prefix];
|
|
674
|
+
if (mappedPrefix === undefined)
|
|
675
|
+
return undefined;
|
|
676
|
+
const suffix = path.slice(prefix.length);
|
|
677
|
+
if (mappedPrefix === '') {
|
|
678
|
+
return suffix.startsWith('.') ? suffix.slice(1) : suffix;
|
|
679
|
+
}
|
|
680
|
+
return `${mappedPrefix}${suffix}`;
|
|
681
|
+
}
|
|
682
|
+
function remapNullishDefaults(defaults, mappings) {
|
|
683
|
+
const remapped = {};
|
|
684
|
+
for (const [path, value] of Object.entries(defaults)) {
|
|
685
|
+
const mappedPath = remapDefaultPath(path, mappings);
|
|
686
|
+
if (mappedPath !== undefined) {
|
|
687
|
+
remapped[mappedPath] = value;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return remapped;
|
|
691
|
+
}
|
|
692
|
+
function schemaSpecMarker(path) {
|
|
693
|
+
return `__KUBERNETES_REF___schema___spec.${path}__`;
|
|
694
|
+
}
|
|
695
|
+
function remapTernaryConditionals(conditionals, mappings) {
|
|
696
|
+
return conditionals.flatMap((conditional) => {
|
|
697
|
+
const conditionField = remapDefaultPath(conditional.conditionField, mappings);
|
|
698
|
+
if (conditionField === undefined)
|
|
699
|
+
return [];
|
|
700
|
+
const proxySection = conditional.proxySection.replace(new RegExp(SCHEMA_MARKER_PATTERN_SOURCE, 'g'), (marker, fieldPath) => {
|
|
701
|
+
if (!fieldPath.startsWith('spec.'))
|
|
702
|
+
return marker;
|
|
703
|
+
const remappedPath = remapDefaultPath(fieldPath.slice('spec.'.length), mappings);
|
|
704
|
+
return remappedPath === undefined ? marker : schemaSpecMarker(remappedPath);
|
|
705
|
+
});
|
|
706
|
+
return [{ ...conditional, conditionField, proxySection }];
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Collect optional spec fields that don't have | default= annotations.
|
|
711
|
+
* These fields need omit() wrapping in resource templates (KRO 0.9+)
|
|
712
|
+
* so they're removed from the K8s resource when not provided, rather
|
|
713
|
+
* than failing with an invalid zero value.
|
|
714
|
+
*
|
|
715
|
+
* Recurses into ALL object types so that both top-level optionals
|
|
716
|
+
* (`env?`, `resources?`) and nested optionals (`database.storageClass?`,
|
|
717
|
+
* `cache.replicas?`) are collected. Returns dotted field paths
|
|
718
|
+
* (e.g., `['baseUrl', 'env', 'database.storageClass', 'cache', 'cache.replicas']`).
|
|
719
|
+
*
|
|
720
|
+
* **Both parent and children are tracked** when a whole object is
|
|
721
|
+
* optional: if the user provides `cache: { shards: 1 }` (present but
|
|
722
|
+
* with `replicas` absent), `has(cache)` is true and we must still
|
|
723
|
+
* guard `cache.replicas` with its own omit. The ancestor resolution
|
|
724
|
+
* in `maybeWrapWithOmit` prefers the deepest matching prefix, so a
|
|
725
|
+
* ref to `cache.replicas` gets `has(cache.replicas) ? ... : omit()`
|
|
726
|
+
* while a ref to `cache.shards` (required scalar with no default,
|
|
727
|
+
* not in the set) falls back to the ancestor `cache` guard.
|
|
728
|
+
*/
|
|
729
|
+
function collectOmitFields(specFields, specType) {
|
|
730
|
+
const omitFields = [];
|
|
731
|
+
walk(specFields, specType.json, '');
|
|
732
|
+
return omitFields;
|
|
733
|
+
function walk(fields, node, pathPrefix) {
|
|
734
|
+
if (!node || typeof node !== 'object')
|
|
735
|
+
return;
|
|
736
|
+
const nodeObj = node;
|
|
737
|
+
const optionalKeys = new Set((nodeObj.optional ?? []).map((p) => p.key));
|
|
738
|
+
const allEntries = [...(nodeObj.required ?? []), ...(nodeObj.optional ?? [])];
|
|
739
|
+
for (const entry of allEntries) {
|
|
740
|
+
const fieldName = entry.key;
|
|
741
|
+
const fullPath = pathPrefix ? `${pathPrefix}.${fieldName}` : fieldName;
|
|
742
|
+
const existingType = fields[fieldName];
|
|
743
|
+
const isOptional = optionalKeys.has(fieldName);
|
|
744
|
+
// Scalar optional without default → needs its own omit() guard.
|
|
745
|
+
if (isOptional &&
|
|
746
|
+
typeof existingType === 'string' &&
|
|
747
|
+
!existingType.includes('default=')) {
|
|
748
|
+
omitFields.push(fullPath);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
// Object (optional or required) → record the path if optional,
|
|
752
|
+
// AND recurse to catch nested optional leaves. Even when the
|
|
753
|
+
// object is required, its children may have their own optional
|
|
754
|
+
// markers. Even when the object is optional, its children still
|
|
755
|
+
// need their own guards because a present-but-sparse object
|
|
756
|
+
// (e.g., `cache: { shards: 1 }` with `replicas` absent) must
|
|
757
|
+
// not fail on ref-time `cache.replicas` access.
|
|
758
|
+
if (typeof existingType === 'object' && existingType !== null) {
|
|
759
|
+
if (isOptional) {
|
|
760
|
+
omitFields.push(fullPath);
|
|
761
|
+
}
|
|
762
|
+
walk(existingType, entry.value, fullPath);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
// ---------------------------------------------------------------------------
|
|
98
768
|
// Public API
|
|
99
769
|
// ---------------------------------------------------------------------------
|
|
100
770
|
/**
|
|
@@ -111,6 +781,112 @@ export function arktypeToKroSchema(name, schemaDefinition, resources, statusMapp
|
|
|
111
781
|
if (!specFields.name) {
|
|
112
782
|
specFields.name = `string | default="${name}"`;
|
|
113
783
|
}
|
|
784
|
+
// Extract ?? defaults and add | default= annotations to the schema.
|
|
785
|
+
// KRO uses these when a field isn't provided in the CR instance.
|
|
786
|
+
//
|
|
787
|
+
// Phase 1: fn.toString() regex — extracts literal ?? fallbacks quickly,
|
|
788
|
+
// for BOTH the outer composition AND every nested composition in the
|
|
789
|
+
// tree. See the nested-composition branch below for why.
|
|
790
|
+
// Phase 2: Defaults resolution run — calls the composition function with
|
|
791
|
+
// undefined for optional fields, triggering ?? and resolving imported
|
|
792
|
+
// constants from the closure. Compares resolved resources with the
|
|
793
|
+
// proxy-run resources (markers) to extract field → default mappings.
|
|
794
|
+
const statusMeta = statusMappings;
|
|
795
|
+
const compositionFn = statusMeta?.__originalCompositionFn;
|
|
796
|
+
// Phase 1a: regex extraction for the outer composition's own defaults.
|
|
797
|
+
if (typeof compositionFn === 'function') {
|
|
798
|
+
const regexDefaults = extractNullishDefaults(compositionFn.toString());
|
|
799
|
+
applyNullishDefaults(specFields, regexDefaults);
|
|
800
|
+
// Missing-field variant: also ADD any default path whose first
|
|
801
|
+
// segment exists in specFields but whose full path doesn't. Handles
|
|
802
|
+
// cases where the outer's own spec has `cache: { ... }` and reads
|
|
803
|
+
// `spec.cache?.foo ?? bar` — the user expects `cache.foo` to be
|
|
804
|
+
// optional with a default, but they forgot to declare it. We auto-
|
|
805
|
+
// declare it for them.
|
|
806
|
+
addMissingDefaultFields(specFields, regexDefaults);
|
|
807
|
+
}
|
|
808
|
+
// Phase 1b: regex extraction for every nested composition in the tree.
|
|
809
|
+
//
|
|
810
|
+
// When an inner composition uses `spec.X?.Y ?? <literal>` and the outer
|
|
811
|
+
// passes its own `spec.X` proxy through (i.e., `innerComp({ X: spec.X, ... })`),
|
|
812
|
+
// the inner's KRO-mode emission produces `${schema.spec.X.Y}` in the
|
|
813
|
+
// flattened resource templates. The OUTER schema typically doesn't
|
|
814
|
+
// declare Y — KRO rejects the RGD with "undefined field".
|
|
815
|
+
//
|
|
816
|
+
// We scan every nested composition's fn source for these `??` defaults
|
|
817
|
+
// and ADD the missing fields to the outer specFields with the literal
|
|
818
|
+
// as a KRO default. KRO then resolves the field using the default at
|
|
819
|
+
// apply time, matching what direct-mode JS does via the `??` operator.
|
|
820
|
+
//
|
|
821
|
+
// This is the framework-side counterpart to the user workaround of
|
|
822
|
+
// explicitly mirroring inner fields in the outer schema. It preserves
|
|
823
|
+
// the "composition looks like native TypeScript" design goal — users
|
|
824
|
+
// shouldn't have to repeat every inner schema detail.
|
|
825
|
+
// `__nestedCompositionFns` was stored via Reflect.set. Read it via
|
|
826
|
+
// getOwnPropertyDescriptor to bypass the Enhanced proxy's get-trap
|
|
827
|
+
// (same pattern as `__nestedStatusCel`). The descriptor's value is
|
|
828
|
+
// the real Map, not a proxy.
|
|
829
|
+
const ternaryConditionals = [];
|
|
830
|
+
const nestedFnsRaw = statusMappings
|
|
831
|
+
? Object.getOwnPropertyDescriptor(statusMappings, '__nestedCompositionFns')?.value
|
|
832
|
+
: undefined;
|
|
833
|
+
const nestedSpecMappingsRaw = statusMappings
|
|
834
|
+
? Object.getOwnPropertyDescriptor(statusMappings, '__nestedCompositionSpecMappings')?.value
|
|
835
|
+
: undefined;
|
|
836
|
+
const nestedDefinitionsRaw = statusMappings
|
|
837
|
+
? Object.getOwnPropertyDescriptor(statusMappings, '__nestedCompositionDefinitions')?.value
|
|
838
|
+
: undefined;
|
|
839
|
+
const nestedResourcesRaw = statusMappings
|
|
840
|
+
? Object.getOwnPropertyDescriptor(statusMappings, '__nestedCompositionResources')?.value
|
|
841
|
+
: undefined;
|
|
842
|
+
if (nestedFnsRaw instanceof Map) {
|
|
843
|
+
for (const [baseId, fn] of nestedFnsRaw) {
|
|
844
|
+
if (typeof fn !== 'function')
|
|
845
|
+
continue;
|
|
846
|
+
const specMappings = nestedSpecMappingsRaw instanceof Map
|
|
847
|
+
? nestedSpecMappingsRaw.get(baseId)
|
|
848
|
+
: undefined;
|
|
849
|
+
if (!specMappings || typeof specMappings !== 'object')
|
|
850
|
+
continue;
|
|
851
|
+
const extractedDefaults = extractNullishDefaults(fn.toString());
|
|
852
|
+
const nestedDefinition = nestedDefinitionsRaw instanceof Map
|
|
853
|
+
? nestedDefinitionsRaw.get(baseId)
|
|
854
|
+
: undefined;
|
|
855
|
+
const nestedResources = nestedResourcesRaw instanceof Map
|
|
856
|
+
? nestedResourcesRaw.get(baseId)
|
|
857
|
+
: undefined;
|
|
858
|
+
const reExecutionResult = nestedDefinition && nestedResources
|
|
859
|
+
? resolveDefaultsByReExecution(fn, nestedDefinition.spec, nestedResources)
|
|
860
|
+
: undefined;
|
|
861
|
+
const reExecutionDefaults = reExecutionResult?.defaults ?? {};
|
|
862
|
+
const innerDefaults = remapNullishDefaults({ ...extractedDefaults, ...reExecutionDefaults }, specMappings);
|
|
863
|
+
// Non-destructive: don't overwrite existing outer defaults.
|
|
864
|
+
applyNullishDefaults(specFields, innerDefaults, false);
|
|
865
|
+
// Also add any fields the outer doesn't declare — the inner's
|
|
866
|
+
// `?? <literal>` is the only signal we have that the field should
|
|
867
|
+
// exist with a default.
|
|
868
|
+
addMissingDefaultFields(specFields, innerDefaults);
|
|
869
|
+
ternaryConditionals.push(...remapTernaryConditionals(reExecutionResult?.ternaryConditionals ?? [], specMappings));
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// Phase 2: resolve imported constants + detect ternary conditionals.
|
|
873
|
+
// Phase 2 is AUTHORITATIVE — if it produces a value for a field that
|
|
874
|
+
// Phase 1 also annotated, Phase 2 wins. This corrects Phase 1 misfires
|
|
875
|
+
// on edge cases (multi-line expressions, nested parens) without breaking
|
|
876
|
+
// the common case where both phases agree.
|
|
877
|
+
if (typeof compositionFn === 'function' && resources) {
|
|
878
|
+
const reExecutionResult = resolveDefaultsByReExecution(compositionFn, schemaDefinition.spec, resources);
|
|
879
|
+
if (reExecutionResult) {
|
|
880
|
+
applyNullishDefaults(specFields, reExecutionResult.defaults, true);
|
|
881
|
+
ternaryConditionals.push(...reExecutionResult.ternaryConditionals);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// Collect optional fields that DON'T have | default= after ?? extraction
|
|
885
|
+
// AND aren't already handled by ternary conditionals.
|
|
886
|
+
// These need omit() wrapping in resource templates (KRO 0.9+).
|
|
887
|
+
const ternaryFieldNames = new Set(ternaryConditionals.map(t => t.conditionField));
|
|
888
|
+
const omitFields = collectOmitFields(specFields, schemaDefinition.spec)
|
|
889
|
+
.filter(f => !ternaryFieldNames.has(f));
|
|
114
890
|
// Filter internal fields (prefixed with __) before classification.
|
|
115
891
|
// These are TypeKro metadata (e.g., __nestedStatusCel, __originalCompositionFn)
|
|
116
892
|
// that should never appear in the KRO schema.
|
|
@@ -125,25 +901,62 @@ export function arktypeToKroSchema(name, schemaDefinition, resources, statusMapp
|
|
|
125
901
|
// Separate static and dynamic status fields.
|
|
126
902
|
// Dynamic = references non-schema resource fields (status, metadata, spec) → KRO CEL
|
|
127
903
|
// Static = references only schema.spec.* or literal values → TypeKro runtime hydration
|
|
128
|
-
|
|
129
|
-
//
|
|
130
|
-
// Build the set of known resource IDs for schema.spec → resource.spec mapping
|
|
904
|
+
// Classification is transitive through nestedStatusCel: a nested composition
|
|
905
|
+
// reference whose inner analyzed value is schema-only/literal is treated as static.
|
|
131
906
|
const resourceIds = resources ? new Set(Object.keys(resources)) : undefined;
|
|
907
|
+
const resourceAliases = resources ? new Map() : undefined;
|
|
908
|
+
if (resources && resourceAliases) {
|
|
909
|
+
for (const [resourceId, resource] of Object.entries(resources)) {
|
|
910
|
+
resourceAliases.set(resourceId, resourceId);
|
|
911
|
+
for (const alias of deriveResourceIdAliases(resourceId)) {
|
|
912
|
+
if (!resourceAliases.has(alias)) {
|
|
913
|
+
resourceAliases.set(alias, resourceId);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const aliases = getMetadataField(resource, 'resourceAliases');
|
|
917
|
+
if (aliases) {
|
|
918
|
+
for (const alias of aliases) {
|
|
919
|
+
resourceAliases.set(alias, resourceId);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
const { dynamicFields } = separateStatusFields(userStatusMappings, nestedStatusCel, resourceIds);
|
|
132
925
|
const statusCelExpressions = Object.keys(dynamicFields).length > 0
|
|
133
|
-
? serializeStatusMappingsToCel(dynamicFields, nestedStatusCel, resourceIds)
|
|
926
|
+
? serializeStatusMappingsToCel(dynamicFields, nestedStatusCel, resourceIds, resourceAliases)
|
|
134
927
|
: {};
|
|
135
928
|
// Extract just the version part for the schema (Kro expects v1alpha1, not kro.run/v1alpha1)
|
|
136
929
|
const schemaApiVersion = schemaDefinition.apiVersion.includes('/')
|
|
137
930
|
? schemaDefinition.apiVersion.split('/')[1] || schemaDefinition.apiVersion
|
|
138
931
|
: schemaDefinition.apiVersion;
|
|
139
|
-
|
|
932
|
+
const schemaGroup = schemaDefinition.group ?? (schemaDefinition.apiVersion.includes('/')
|
|
933
|
+
? schemaDefinition.apiVersion.split('/')[0]
|
|
934
|
+
: undefined);
|
|
935
|
+
const schema = {
|
|
140
936
|
apiVersion: schemaApiVersion,
|
|
141
937
|
kind: schemaDefinition.kind,
|
|
938
|
+
...(schemaGroup && { group: schemaGroup }),
|
|
142
939
|
spec: specFields,
|
|
143
940
|
status: {
|
|
144
941
|
...statusCelExpressions,
|
|
145
942
|
},
|
|
146
943
|
};
|
|
944
|
+
// Attach metadata as non-enumerable properties — they're typed on
|
|
945
|
+
// KroSimpleSchemaWithMetadata so consumers can read them without casts,
|
|
946
|
+
// but non-enumerable so they don't appear in the schema YAML.
|
|
947
|
+
if (ternaryConditionals.length > 0) {
|
|
948
|
+
Object.defineProperty(schema, '__ternaryConditionals', {
|
|
949
|
+
value: ternaryConditionals,
|
|
950
|
+
enumerable: false,
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
if (omitFields.length > 0) {
|
|
954
|
+
Object.defineProperty(schema, '__omitFields', {
|
|
955
|
+
value: omitFields,
|
|
956
|
+
enumerable: false,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
return schema;
|
|
147
960
|
}
|
|
148
961
|
/**
|
|
149
962
|
* Generate a minimal Kro schema from a resource map (no Arktype).
|