@go-to-k/cdkd 0.280.17 → 0.280.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as requireConfigString, $t as processStackMessages, A as red, At as runDockerStreaming, B as isTerminationProtectionPropagationError, Bt as resolveSkipPrefix, C as isStatefulRecreateTargetSync, Ct as parseBootstrapMarker, D as cyan, Dn as normalizeAwsError, Dt as formatDockerLoginError, E as bold, Et as buildDockerImage, F as ProviderRegistry, Ft as getDefaultStateBucketName, G as normalizeAwsTagsToCfn, Gt as warnDeprecatedNoPrefixCliFlag, H as cfnRefValueFromPhysicalId, Ht as resolveStateBucketWithDefaultAndSource, I as findActionableSilentDrops, It as getLegacyStateBucketName, J as configStringRefusal, Jt as MIGRATE_TMP_PREFIX, K as resolveExplicitPhysicalId, Kt as CFN_TEMPLATE_BODY_LIMIT, L as CloudControlProvider, Lt as resolveApp, M as IAMRoleProvider, Mt as getDockerImageBySourceHash, N as collectInlinePolicyNamesManagedBySiblings, Nt as Synthesizer, O as gray, On as withErrorHandling, Ot as getDockerCmd, P as clearOnUpdateRemoval, Pt as synthesisStatusMessage, Q as requireConfigObject, Qt as AssemblyReader, R as slowCcOperationTimeoutMs, Rt as resolveAutoAssetStorage, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, Sn as StackTerminationProtectionError, St as getBootstrapMarkerKey, T as formatResourceLine, Tt as validateContainerRepoName, U as refStateLookupFromResource, Ut as resolveUseCdkBootstrapAssets, V as IntrinsicFunctionResolver, Vt as resolveStateBucketWithDefault, W as WAFv2WebACLProvider, Wt as stateBucketExistenceConfirmed, X as replayWarn, Xt as uploadCfnTemplate, Y as readConfigString, Yt as findLargeInlineResources, Z as requireConfigArray, Zt as expectedOwnerParam, _ as createPreDeleteFinalSnapshot, _n as PartialFailureError, _t as loadPublishableAssetManifest, a as DeploymentEventsStore, an as setAwsClients, at as isThrottlingError, b as unsupportedFinalSnapshotError, bn as ResourceUpdateNotSupportedError, bt as BOOTSTRAP_MARKER_PREFIX, c as replayFailedOperations, ct as LockManager, d as IMPLICIT_DELETE_DEPENDENCIES, dn as LocalInvokeBuildError$1, dt as shouldRetainResource, et as applyRoleArnIfSet, f as computeImplicitDeleteEdges, fn as LocalMigrateError, ft as AssetPublisher, g as ccRoutedFinalSnapshotError, gn as NestedStackChildDirectDestroyError, gt as createAssetRedirectResolver, h as buildFinalSnapshotIdentifier, hn as MissingCdkCliError, ht as buildAssetRedirectMap, i as DeploymentEventsReader, in as resetAwsClients, it as isRetryableTransientError, j as yellow, jt as AssetManifestLoader, k as green, kt as runDockerForeground, l as replayRollback, lt as S3StateBackend, m as PRE_DELETE_SNAPSHOT_TYPES, mt as WorkGraph, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as AwsClients, nt as describeTypeWithThrottleRetry, o as planFailedOps, ot as DagBuilder, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalStartServiceError, pt as stringifyValue, q as assertRegionMatch, qt as CFN_TEMPLATE_URL_LIMIT, r as DeployEngine, rn as getAwsClients, rt as withRetry, s as planRollback, sn as CdkdError, st as TemplateParser, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resolveBucketRegion, tt as DiffCalculator, u as withResourceDeadline, un as DeployCancelledError, ut as rebuildClientForBucketRegion, v as isFinalSnapshotError, vn as ProvisioningError, vt as rewriteTemplateAssetReferences, w as renderStatefulReason, wt as validateAssetBucketName, x as extractDeploymentEventError, xn as StackHasActiveImportsError, xt as ensureAssetStorage, y as refusesFinalSnapshot, yn as ResourceTimeoutError, yt as AssetModeResolver, z as disableInstanceApiTermination, zt as resolveCaptureObservedState } from "./deploy-engine-tCcs8UGn.js";
2
+ import { $ as requireConfigString, $t as processStackMessages, A as red, At as runDockerStreaming, B as isTerminationProtectionPropagationError, Bt as resolveSkipPrefix, C as isStatefulRecreateTargetSync, Ct as parseBootstrapMarker, D as cyan, Dn as normalizeAwsError, Dt as formatDockerLoginError, E as bold, Et as buildDockerImage, F as ProviderRegistry, Ft as getDefaultStateBucketName, G as normalizeAwsTagsToCfn, Gt as warnDeprecatedNoPrefixCliFlag, H as cfnRefValueFromPhysicalId, Ht as resolveStateBucketWithDefaultAndSource, I as findActionableSilentDrops, It as getLegacyStateBucketName, J as configStringRefusal, Jt as MIGRATE_TMP_PREFIX, K as resolveExplicitPhysicalId, Kt as CFN_TEMPLATE_BODY_LIMIT, L as CloudControlProvider, Lt as resolveApp, M as IAMRoleProvider, Mt as getDockerImageBySourceHash, N as collectInlinePolicyNamesManagedBySiblings, Nt as Synthesizer, O as gray, On as withErrorHandling, Ot as getDockerCmd, P as clearOnUpdateRemoval, Pt as synthesisStatusMessage, Q as requireConfigObject, Qt as AssemblyReader, R as slowCcOperationTimeoutMs, Rt as resolveAutoAssetStorage, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, Sn as StackTerminationProtectionError, St as getBootstrapMarkerKey, T as formatResourceLine, Tt as validateContainerRepoName, U as refStateLookupFromResource, Ut as resolveUseCdkBootstrapAssets, V as IntrinsicFunctionResolver, Vt as resolveStateBucketWithDefault, W as WAFv2WebACLProvider, Wt as stateBucketExistenceConfirmed, X as replayWarn, Xt as uploadCfnTemplate, Y as readConfigString, Yt as findLargeInlineResources, Z as requireConfigArray, Zt as expectedOwnerParam, _ as createPreDeleteFinalSnapshot, _n as PartialFailureError, _t as loadPublishableAssetManifest, a as DeploymentEventsStore, an as setAwsClients, at as isThrottlingError, b as unsupportedFinalSnapshotError, bn as ResourceUpdateNotSupportedError, bt as BOOTSTRAP_MARKER_PREFIX, c as replayFailedOperations, ct as LockManager, d as IMPLICIT_DELETE_DEPENDENCIES, dn as LocalInvokeBuildError$1, dt as shouldRetainResource, et as applyRoleArnIfSet, f as computeImplicitDeleteEdges, fn as LocalMigrateError, ft as AssetPublisher, g as ccRoutedFinalSnapshotError, gn as NestedStackChildDirectDestroyError, gt as createAssetRedirectResolver, h as buildFinalSnapshotIdentifier, hn as MissingCdkCliError, ht as buildAssetRedirectMap, i as DeploymentEventsReader, in as resetAwsClients, it as isRetryableTransientError, j as yellow, jt as AssetManifestLoader, k as green, kt as runDockerForeground, l as replayRollback, lt as S3StateBackend, m as PRE_DELETE_SNAPSHOT_TYPES, mt as WorkGraph, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as AwsClients, nt as describeTypeWithThrottleRetry, o as planFailedOps, ot as DagBuilder, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalStartServiceError, pt as stringifyValue, q as assertRegionMatch, qt as CFN_TEMPLATE_URL_LIMIT, r as DeployEngine, rn as getAwsClients, rt as withRetry, s as planRollback, sn as CdkdError, st as TemplateParser, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resolveBucketRegion, tt as DiffCalculator, u as withResourceDeadline, un as DeployCancelledError, ut as rebuildClientForBucketRegion, v as isFinalSnapshotError, vn as ProvisioningError, vt as rewriteTemplateAssetReferences, w as renderStatefulReason, wt as validateAssetBucketName, x as extractDeploymentEventError, xn as StackHasActiveImportsError, xt as ensureAssetStorage, y as refusesFinalSnapshot, yn as ResourceTimeoutError, yt as AssetModeResolver, z as disableInstanceApiTermination, zt as resolveCaptureObservedState } from "./deploy-engine-r4N9-tZD.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as withSkipPrefix, i as runStackBuffered, n as getLogger, o as PATTERN_B_NAME_OPTIONS, p as withStackName, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-BYMEE-BS.js";
4
- import { t as ASGProvider } from "./asg-provider-DQLjoTQ3.js";
4
+ import { t as ASGProvider } from "./asg-provider-B62D31jG.js";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -27873,6 +27873,78 @@ const TARGET_GROUP_ATTRIBUTE_DEFAULTS = {
27873
27873
  "proxy_protocol_v2.enabled": "false"
27874
27874
  };
27875
27875
  /**
27876
+ * Documented AWS defaults for the BOOLEAN / ENUM-valued LoadBalancer and
27877
+ * Listener attributes, used to reset a removed `LoadBalancerAttributes` /
27878
+ * `ListenerAttributes` entry.
27879
+ *
27880
+ * Unlike ModifyTargetGroupAttributes — which rejects an empty `Value` for
27881
+ * EVERY key — these two APIs accept `Value: ''` for the numeric and
27882
+ * free-form string attributes and REJECT it only where the value is
27883
+ * validated against a fixed set. Live A/B 2026-08-11 (issue #1609 item 1),
27884
+ * via the `alb` integ's removal phase:
27885
+ *
27886
+ * idle_timeout.timeout_seconds -> `Value: ''` ACCEPTED (numeric)
27887
+ * deletion_protection.enabled -> "The value of 'deletion_protection.enabled'
27888
+ * must be 'true' or 'false', but was ''"
27889
+ * routing.http.response.server.enabled (Listener)
27890
+ * -> same rejection, same shape
27891
+ *
27892
+ * A rejection fails the whole Modify* call, so ONE removed boolean took the
27893
+ * entire deploy down (and then the rollback with it). Hence: send the
27894
+ * documented default for the validated keys, and keep the empty string for
27895
+ * everything else — which is both these APIs' own "clear the override"
27896
+ * signal and the behaviour every non-boolean key already relied on.
27897
+ *
27898
+ * That fallback is the deliberate DIVERGENCE from
27899
+ * TARGET_GROUP_ATTRIBUTE_DEFAULTS, whose unknown-key arm warns and retains:
27900
+ * there the empty string is never valid, here it is valid for the majority
27901
+ * of keys, so falling back to it preserves working behaviour instead of
27902
+ * silently retaining a value the template asked to drop.
27903
+ *
27904
+ * A key is listed ONLY when ONE unconditional default is established for it —
27905
+ * documented in the SDK model, or (for
27906
+ * `routing.http.response.server.enabled`, which the model leaves unstated)
27907
+ * proven by the `alb` integ's live removal readback. Two exclusion classes
27908
+ * matter, and both are the difference between failing loudly and writing a
27909
+ * wrong value silently:
27910
+ *
27911
+ * - **Default depends on the LOAD BALANCER** — `load_balancing.cross_zone.enabled`
27912
+ * is always-on and unconfigurable for an ALB but defaults to false on an
27913
+ * NLB / GWLB, and `ipv6.deny_all_igw_traffic` is "false for internet-facing
27914
+ * load balancers and true for internal load balancers". cdkd knows neither
27915
+ * the type nor the scheme at diff time, so a hardcoded entry would send the
27916
+ * WRONG value for half of all load balancers — and since it is a valid
27917
+ * boolean, AWS accepts it. For `ipv6.deny_all_igw_traffic` that means
27918
+ * silently un-blocking internet-gateway access on an INTERNAL load balancer.
27919
+ * - **No documented default at all** — `dns_record.client_routing_policy`
27920
+ * enumerates its possible values but states no default.
27921
+ *
27922
+ * Both classes keep the empty-string behaviour they have always had, so a
27923
+ * removal there fails loudly (for a validated key) instead of guessing.
27924
+ *
27925
+ * Source: the `@aws-sdk/client-elastic-load-balancing-v2` model docs for
27926
+ * `ModifyLoadBalancerAttributes` / `ModifyListenerAttributes`, checked per key;
27927
+ * `routing.http.response.server.enabled`'s default is additionally LIVE-proven
27928
+ * by the `alb` integ's removal readback rather than taken from the docs.
27929
+ */
27930
+ const LOAD_BALANCER_ATTRIBUTE_DEFAULTS = {
27931
+ "deletion_protection.enabled": "false",
27932
+ "access_logs.s3.enabled": "false",
27933
+ "connection_logs.s3.enabled": "false",
27934
+ "health_check_logs.s3.enabled": "false",
27935
+ "routing.http.desync_mitigation_mode": "defensive",
27936
+ "routing.http.drop_invalid_header_fields.enabled": "false",
27937
+ "routing.http.preserve_host_header.enabled": "false",
27938
+ "routing.http.x_amzn_tls_version_and_cipher_suite.enabled": "false",
27939
+ "routing.http.xff_client_port.enabled": "false",
27940
+ "routing.http.xff_header_processing.mode": "append",
27941
+ "routing.http2.enabled": "true",
27942
+ "waf.fail_open.enabled": "false",
27943
+ "zonal_shift.config.enabled": "false"
27944
+ };
27945
+ /** @see LOAD_BALANCER_ATTRIBUTE_DEFAULTS — same rule, Listener key table. */
27946
+ const LISTENER_ATTRIBUTE_DEFAULTS = { "routing.http.response.server.enabled": "true" };
27947
+ /**
27876
27948
  * AWS ELBv2 Provider
27877
27949
  *
27878
27950
  * Implements resource provisioning for ELBv2 resources:
@@ -28085,7 +28157,7 @@ var ELBv2Provider = class {
28085
28157
  return out;
28086
28158
  };
28087
28159
  if (JSON.stringify(stripHandled(properties)) !== JSON.stringify(stripHandled(previousProperties))) throw new ResourceUpdateNotSupportedError("AWS::ElasticLoadBalancingV2::LoadBalancer", logicalId, "ELBv2 LoadBalancer Name / Type / Scheme are immutable on AWS — none of the ELBv2 Modify* / Set* APIs accept these fields; they are fixed at creation. cdkd handles LoadBalancerAttributes / Subnets / SubnetMappings / SecurityGroups / IpAddressType / Tags in-place; for Name / Type / Scheme re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.");
28088
- const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["LoadBalancerAttributes"]), this.normalizeAttributes(previousProperties["LoadBalancerAttributes"]));
28160
+ const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["LoadBalancerAttributes"]), this.normalizeAttributes(previousProperties["LoadBalancerAttributes"]), this.attributeRemovalResolver(LOAD_BALANCER_ATTRIBUTE_DEFAULTS));
28089
28161
  if (submittedAttrs.length > 0) {
28090
28162
  await this.getClient().send(new ModifyLoadBalancerAttributesCommand({
28091
28163
  LoadBalancerArn: physicalId,
@@ -28309,10 +28381,13 @@ var ELBv2Provider = class {
28309
28381
  UnhealthyThresholdCount: properties["UnhealthyThresholdCount"] !== void 0 ? Number(properties["UnhealthyThresholdCount"]) : void 0,
28310
28382
  ...matcher && { Matcher: matcher }
28311
28383
  }));
28312
- const tgAttrDiff = this.diffAttributes(this.normalizeAttributes(properties["TargetGroupAttributes"]), this.normalizeAttributes(previousProperties["TargetGroupAttributes"]), (key) => {
28313
- const fallback = TARGET_GROUP_ATTRIBUTE_DEFAULTS[key];
28314
- if (fallback === void 0) this.logger.warn(`TargetGroup attribute ${key} was removed from the template but has no documented default cdkd can reset it to — the live value is retained. Set the attribute explicitly to change it.`);
28315
- return fallback;
28384
+ const tgAttrDiff = this.diffAttributes(this.normalizeAttributes(properties["TargetGroupAttributes"]), this.normalizeAttributes(previousProperties["TargetGroupAttributes"]), (key, currentValue) => {
28385
+ const fallback = Object.hasOwn(TARGET_GROUP_ATTRIBUTE_DEFAULTS, key) ? TARGET_GROUP_ATTRIBUTE_DEFAULTS[key] : void 0;
28386
+ if (fallback === void 0) {
28387
+ this.logger.warn(`TargetGroup attribute ${key} was removed from the template but has no documented default cdkd can reset it to — the live value is retained. Set the attribute explicitly to change it.`);
28388
+ return;
28389
+ }
28390
+ return currentValue === fallback ? void 0 : fallback;
28316
28391
  });
28317
28392
  if (tgAttrDiff.length > 0) {
28318
28393
  await this.getClient().send(new ModifyTargetGroupAttributesCommand({
@@ -28444,7 +28519,7 @@ var ELBv2Provider = class {
28444
28519
  ...alpnPolicy && alpnPolicy.length > 0 && { AlpnPolicy: alpnPolicy },
28445
28520
  ...mutualAuth !== void 0 && { MutualAuthentication: mutualAuth }
28446
28521
  }));
28447
- const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["ListenerAttributes"]), this.normalizeAttributes(previousProperties["ListenerAttributes"]));
28522
+ const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["ListenerAttributes"]), this.normalizeAttributes(previousProperties["ListenerAttributes"]), this.attributeRemovalResolver(LISTENER_ATTRIBUTE_DEFAULTS));
28448
28523
  if (submittedAttrs.length > 0) {
28449
28524
  await withRetry(() => this.getClient().send(new ModifyListenerAttributesCommand({
28450
28525
  ListenerArn: physicalId,
@@ -28522,14 +28597,28 @@ var ELBv2Provider = class {
28522
28597
  /**
28523
28598
  * Key-diff two normalized `{Key, Value}` attribute lists into the payload
28524
28599
  * for a Modify*Attributes call: changed values from `newAttrs` win, and
28525
- * keys present only in `oldAttrs` are pushed back through `removalValue` —
28526
- * by default the empty string, which ModifyLoadBalancerAttributes /
28527
- * ModifyListenerAttributes accept as "clear the override". A resolver
28528
- * returning `undefined` SKIPS the entry (the resolver owns any warn).
28529
- * An empty return means nothing changed and the call should be skipped.
28530
- * Shared by the LoadBalancer / TargetGroup / Listener update paths.
28531
- */
28532
- diffAttributes(newAttrs, oldAttrs, removalValue = () => "") {
28600
+ * keys present only in `oldAttrs` are pushed back through `removalValue`.
28601
+ * A resolver returning `undefined` SKIPS the entry (the resolver owns any
28602
+ * warn). An empty return means nothing changed and the call should be
28603
+ * skipped. Shared by the LoadBalancer / TargetGroup / Listener update
28604
+ * paths and all three pass their OWN resolver, because the three APIs
28605
+ * disagree about what resets an attribute:
28606
+ *
28607
+ * - LoadBalancer / Listener: the empty string clears a numeric or
28608
+ * free-form-string override, but a BOOLEAN / ENUM key rejects it and
28609
+ * fails the whole call, so those take a documented default
28610
+ * (`LOAD_BALANCER_ATTRIBUTE_DEFAULTS` / `LISTENER_ATTRIBUTE_DEFAULTS`).
28611
+ * - TargetGroup: the empty string is rejected for EVERY key, so the
28612
+ * documented default is the only reset and an unknown key warns and
28613
+ * retains (`TARGET_GROUP_ATTRIBUTE_DEFAULTS`).
28614
+ *
28615
+ * All three behaviours were live-verified 2026-08-11. `removalValue` is
28616
+ * REQUIRED rather than defaulted: there is no reset value that is correct
28617
+ * for all three APIs, so a caller that forgets to pass one should fail to
28618
+ * compile instead of silently inheriting the empty string that two of the
28619
+ * three reject.
28620
+ */
28621
+ diffAttributes(newAttrs, oldAttrs, removalValue) {
28533
28622
  const newAttrMap = new Map(newAttrs.map((a) => [a.Key, a.Value]));
28534
28623
  const oldAttrMap = new Map(oldAttrs.map((a) => [a.Key, a.Value]));
28535
28624
  const submitted = [];
@@ -28537,8 +28626,8 @@ var ELBv2Provider = class {
28537
28626
  Key: k,
28538
28627
  Value: v
28539
28628
  });
28540
- for (const [k] of oldAttrMap) if (!newAttrMap.has(k)) {
28541
- const value = removalValue(k);
28629
+ for (const [k, currentValue] of oldAttrMap) if (!newAttrMap.has(k)) {
28630
+ const value = removalValue(k, currentValue);
28542
28631
  if (value !== void 0) submitted.push({
28543
28632
  Key: k,
28544
28633
  Value: value
@@ -28547,6 +28636,40 @@ var ELBv2Provider = class {
28547
28636
  return submitted;
28548
28637
  }
28549
28638
  /**
28639
+ * Build the `removalValue` resolver for the LoadBalancer / Listener attribute
28640
+ * arms: reset a removed key to its documented default, or to `''` (which
28641
+ * those two APIs accept as "clear the override") when no default is known.
28642
+ *
28643
+ * **A key already AT its default is SKIPPED, and that is a safety property,
28644
+ * not an optimization.** The previous side is not always a template: `cdkd
28645
+ * drift --revert` calls `update(..., newProperties, outcome.awsProperties)`
28646
+ * (`src/cli/commands/drift.ts`), so `oldAttrs` can be the FULL
28647
+ * `readCurrentState` snapshot — every attribute AWS reports, including the
28648
+ * ~18 the user never templated. Against a state record with no
28649
+ * `observedProperties` the desired side is the template's one or two keys,
28650
+ * so every untemplated key looks REMOVED and would be "reset".
28651
+ *
28652
+ * Writing a documented default there would silently disable deletion
28653
+ * protection, access / connection logs, HTTP/2, WAF fail-open and zonal
28654
+ * shift on a live load balancer. Before the defaults table existed this was
28655
+ * accidentally safe — `''` fails validation, so the whole revert aborted and
28656
+ * changed nothing — so introducing the table without this guard would have
28657
+ * converted a loud refusal into a silent destructive write.
28658
+ *
28659
+ * The skip is exact rather than heuristic: a key the user never templated is
28660
+ * BY DEFINITION sitting at its default, so it is skipped; a key the template
28661
+ * really did set is set to a NON-default value (otherwise templating it
28662
+ * would be a no-op), so its reset still fires. A template that set a key to
28663
+ * exactly its default and then dropped it is skipped too — correctly, since
28664
+ * there is nothing to change.
28665
+ */
28666
+ attributeRemovalResolver(defaults) {
28667
+ return (key, currentValue) => {
28668
+ const fallback = Object.hasOwn(defaults, key) ? defaults[key] : "";
28669
+ return currentValue === fallback ? void 0 : fallback;
28670
+ };
28671
+ }
28672
+ /**
28550
28673
  * Normalize a CFn `Targets` value (an array of `{ Id, Port?,
28551
28674
  * AvailabilityZone? }` objects) into the SDK's `TargetDescription[]` shape.
28552
28675
  * Entries missing an `Id` are dropped (Id is the only required member);
@@ -34664,6 +34787,7 @@ const MUTABLE_GRAPHQL_API_CONFIG_PROPERTIES = [
34664
34787
  */
34665
34788
  var AppSyncProvider = class {
34666
34789
  client;
34790
+ s3Client;
34667
34791
  providerRegion = process.env["AWS_REGION"];
34668
34792
  logger = getLogger().child("AppSyncProvider");
34669
34793
  /**
@@ -34714,7 +34838,12 @@ var AppSyncProvider = class {
34714
34838
  "ServiceRoleArn",
34715
34839
  "DynamoDBConfig",
34716
34840
  "LambdaConfig",
34717
- "HttpConfig"
34841
+ "HttpConfig",
34842
+ "ElasticsearchConfig",
34843
+ "EventBridgeConfig",
34844
+ "MetricsConfig",
34845
+ "OpenSearchServiceConfig",
34846
+ "RelationalDatabaseConfig"
34718
34847
  ])],
34719
34848
  ["AWS::AppSync::Resolver", /* @__PURE__ */ new Set([
34720
34849
  "ApiId",
@@ -34726,7 +34855,14 @@ var AppSyncProvider = class {
34726
34855
  "Kind",
34727
34856
  "PipelineConfig",
34728
34857
  "Runtime",
34729
- "Code"
34858
+ "Code",
34859
+ "CachingConfig",
34860
+ "CodeS3Location",
34861
+ "MaxBatchSize",
34862
+ "MetricsConfig",
34863
+ "RequestMappingTemplateS3Location",
34864
+ "ResponseMappingTemplateS3Location",
34865
+ "SyncConfig"
34730
34866
  ])],
34731
34867
  ["AWS::AppSync::ApiKey", /* @__PURE__ */ new Set([
34732
34868
  "ApiId",
@@ -34734,11 +34870,23 @@ var AppSyncProvider = class {
34734
34870
  "Expires"
34735
34871
  ])]
34736
34872
  ]);
34737
- unhandledByDesign = /* @__PURE__ */ new Map([["AWS::AppSync::DataSource", /* @__PURE__ */ new Map([["ElasticsearchConfig", "Legacy Elasticsearch alias; use OpenSearchServiceConfig (AppSync deprecated the Elasticsearch DataSource type in favor of OpenSearch)"]])]]);
34738
34873
  getClient() {
34739
34874
  if (!this.client) this.client = new AppSyncClient(this.providerRegion ? { region: this.providerRegion } : {});
34740
34875
  return this.client;
34741
34876
  }
34877
+ /**
34878
+ * Lazy S3 client for the `*S3Location` properties (Resolver
34879
+ * `CodeS3Location` / `RequestMappingTemplateS3Location` /
34880
+ * `ResponseMappingTemplateS3Location`, GraphQLSchema
34881
+ * `DefinitionS3Location`) — CloudFormation reads the S3 object and inlines
34882
+ * its body, and cdkd mirrors that. Same construction as
34883
+ * `StepFunctionsProvider.getS3Client` (the sibling `DefinitionS3Location`
34884
+ * implementation).
34885
+ */
34886
+ getS3Client() {
34887
+ if (!this.s3Client) this.s3Client = new S3Client(this.providerRegion ? { region: this.providerRegion } : {});
34888
+ return this.s3Client;
34889
+ }
34742
34890
  async create(logicalId, resourceType, properties, context) {
34743
34891
  switch (resourceType) {
34744
34892
  case "AWS::AppSync::GraphQLApi": return this.createGraphQLApi(logicalId, resourceType, properties, context);
@@ -34757,11 +34905,16 @@ var AppSyncProvider = class {
34757
34905
  * `XrayEnabled` / `LogConfig`) + `TagResource` / `UntagResource`
34758
34906
  * for `Tags` diff. `Name` is immutable on AWS.
34759
34907
  * - `DataSource` → `UpdateDataSourceCommand` (`Description` /
34760
- * `ServiceRoleArn` / `DynamoDBConfig` / `LambdaConfig` / `HttpConfig`).
34908
+ * `ServiceRoleArn` / the per-type configs — `DynamoDBConfig` /
34909
+ * `LambdaConfig` / `HttpConfig` / `ElasticsearchConfig` /
34910
+ * `OpenSearchServiceConfig` / `EventBridgeConfig` /
34911
+ * `RelationalDatabaseConfig` — / `MetricsConfig`).
34761
34912
  * `ApiId` / `Name` / `Type` are immutable identity fields.
34762
34913
  * - `Resolver` → `UpdateResolverCommand` (`DataSourceName` /
34763
34914
  * `RequestMappingTemplate` / `ResponseMappingTemplate` / `Kind` /
34764
- * `PipelineConfig` / `Runtime` / `Code`). `ApiId` / `TypeName` /
34915
+ * `PipelineConfig` / `Runtime` / `Code` / `CachingConfig` /
34916
+ * `SyncConfig` / `MaxBatchSize` / `MetricsConfig` + the three
34917
+ * `*S3Location` fetch-and-inline pairs). `ApiId` / `TypeName` /
34765
34918
  * `FieldName` are immutable identity fields.
34766
34919
  * - `ApiKey` → `UpdateApiKeyCommand` (`Description` / `Expires`).
34767
34920
  * `ApiId` is immutable; the AWS-generated key id is immutable.
@@ -34853,7 +35006,23 @@ var AppSyncProvider = class {
34853
35006
  async updateGraphQLSchema(logicalId, physicalId, resourceType, properties, previousProperties) {
34854
35007
  const newDef = properties["Definition"];
34855
35008
  const oldDef = previousProperties["Definition"];
34856
- if (newDef === void 0 || newDef === oldDef) return {
35009
+ const newLoc = properties["DefinitionS3Location"];
35010
+ const oldLoc = previousProperties["DefinitionS3Location"];
35011
+ let definition;
35012
+ if (newDef !== void 0) {
35013
+ if (newLoc !== void 0) this.logger.warn(`${resourceType} ${logicalId}: both Definition and DefinitionS3Location are set — using the inline Definition and ignoring DefinitionS3Location (CFn treats them as alternatives)`);
35014
+ if (newDef === oldDef) return {
35015
+ physicalId,
35016
+ wasReplaced: false
35017
+ };
35018
+ definition = newDef;
35019
+ } else if (newLoc !== void 0) {
35020
+ if (this.deepEqual(newLoc, oldLoc)) return {
35021
+ physicalId,
35022
+ wasReplaced: false
35023
+ };
35024
+ definition = await this.fetchS3LocationBody(newLoc, resourceType, logicalId, "DefinitionS3Location");
35025
+ } else return {
34857
35026
  physicalId,
34858
35027
  wasReplaced: false
34859
35028
  };
@@ -34861,7 +35030,7 @@ var AppSyncProvider = class {
34861
35030
  try {
34862
35031
  await this.getClient().send(new StartSchemaCreationCommand({
34863
35032
  apiId,
34864
- definition: Buffer.from(newDef, "utf-8")
35033
+ definition: Buffer.from(definition, "utf-8")
34865
35034
  }));
34866
35035
  } catch (error) {
34867
35036
  throw this.wrapUpdateError(error, resourceType, logicalId, physicalId, "GraphqlSchema");
@@ -34884,17 +35053,18 @@ var AppSyncProvider = class {
34884
35053
  const [apiId, name] = physicalId.split("|");
34885
35054
  if (!apiId || !name) throw new ProvisioningError(`Invalid DataSource physical ID format: ${physicalId}`, resourceType, logicalId, physicalId);
34886
35055
  const type = properties["Type"] ?? previousProperties["Type"];
34887
- const newDesc = properties["Description"];
34888
- const oldDesc = previousProperties["Description"];
34889
- const newRole = properties["ServiceRoleArn"];
34890
- const oldRole = previousProperties["ServiceRoleArn"];
34891
- const newDDB = properties["DynamoDBConfig"];
34892
- const oldDDB = previousProperties["DynamoDBConfig"];
34893
- const newLambda = properties["LambdaConfig"];
34894
- const oldLambda = previousProperties["LambdaConfig"];
34895
- const newHttp = properties["HttpConfig"];
34896
- const oldHttp = previousProperties["HttpConfig"];
34897
- if (!(newDesc !== oldDesc || newRole !== oldRole || !this.deepEqual(newDDB, oldDDB) || !this.deepEqual(newLambda, oldLambda) || !this.deepEqual(newHttp, oldHttp))) return {
35056
+ if (![
35057
+ "Description",
35058
+ "ServiceRoleArn",
35059
+ "DynamoDBConfig",
35060
+ "LambdaConfig",
35061
+ "HttpConfig",
35062
+ "ElasticsearchConfig",
35063
+ "OpenSearchServiceConfig",
35064
+ "EventBridgeConfig",
35065
+ "RelationalDatabaseConfig",
35066
+ "MetricsConfig"
35067
+ ].some((key) => (key in properties || key in previousProperties) && !this.deepEqual(properties[key], previousProperties[key]))) return {
34898
35068
  physicalId,
34899
35069
  wasReplaced: false
34900
35070
  };
@@ -34903,15 +35073,7 @@ var AppSyncProvider = class {
34903
35073
  name,
34904
35074
  type
34905
35075
  };
34906
- if (newDesc !== void 0) input.description = newDesc;
34907
- if (newRole !== void 0) input.serviceRoleArn = newRole;
34908
- if (newDDB !== void 0) input.dynamodbConfig = {
34909
- tableName: newDDB["TableName"],
34910
- awsRegion: newDDB["AwsRegion"],
34911
- useCallerCredentials: newDDB["UseCallerCredentials"]
34912
- };
34913
- if (newLambda !== void 0) input.lambdaConfig = { lambdaFunctionArn: newLambda["LambdaFunctionArn"] };
34914
- if (newHttp !== void 0) input.httpConfig = { endpoint: newHttp["Endpoint"] };
35076
+ this.applyDataSourceConfig(input, resourceType, logicalId, properties);
34915
35077
  try {
34916
35078
  await this.getClient().send(new UpdateDataSourceCommand(input));
34917
35079
  } catch (error) {
@@ -34942,8 +35104,15 @@ var AppSyncProvider = class {
34942
35104
  "Kind",
34943
35105
  "PipelineConfig",
34944
35106
  "Runtime",
34945
- "Code"
34946
- ].some((key) => !this.deepEqual(properties[key], previousProperties[key]))) return {
35107
+ "Code",
35108
+ "CachingConfig",
35109
+ "SyncConfig",
35110
+ "MaxBatchSize",
35111
+ "MetricsConfig",
35112
+ "CodeS3Location",
35113
+ "RequestMappingTemplateS3Location",
35114
+ "ResponseMappingTemplateS3Location"
35115
+ ].some((key) => (key in properties || key in previousProperties) && !this.deepEqual(properties[key], previousProperties[key]))) return {
34947
35116
  physicalId,
34948
35117
  wasReplaced: false
34949
35118
  };
@@ -34952,20 +35121,7 @@ var AppSyncProvider = class {
34952
35121
  typeName,
34953
35122
  fieldName
34954
35123
  };
34955
- const effectiveKind = properties["Kind"] ?? previousProperties["Kind"] ?? "UNIT";
34956
- if (effectiveKind === "UNIT" && properties["DataSourceName"] !== void 0) input.dataSourceName = properties["DataSourceName"];
34957
- if (properties["RequestMappingTemplate"] !== void 0) input.requestMappingTemplate = properties["RequestMappingTemplate"];
34958
- if (properties["ResponseMappingTemplate"] !== void 0) input.responseMappingTemplate = properties["ResponseMappingTemplate"];
34959
- if (properties["Kind"] !== void 0) input.kind = properties["Kind"];
34960
- if (effectiveKind === "PIPELINE" && properties["PipelineConfig"] !== void 0) input.pipelineConfig = { functions: properties["PipelineConfig"]["Functions"] };
34961
- if (properties["Runtime"] !== void 0) {
34962
- const runtime = properties["Runtime"];
34963
- input.runtime = {
34964
- name: runtime["Name"],
34965
- runtimeVersion: runtime["RuntimeVersion"]
34966
- };
34967
- }
34968
- if (properties["Code"] !== void 0) input.code = properties["Code"];
35124
+ await this.applyResolverConfig(input, resourceType, logicalId, properties, previousProperties, true);
34969
35125
  try {
34970
35126
  await this.getClient().send(new UpdateResolverCommand(input));
34971
35127
  } catch (error) {
@@ -35086,6 +35242,11 @@ var AppSyncProvider = class {
35086
35242
  */
35087
35243
  getDriftUnknownPaths(resourceType) {
35088
35244
  if (resourceType === "AWS::AppSync::GraphQLSchema") return ["DefinitionS3Location"];
35245
+ if (resourceType === "AWS::AppSync::Resolver") return [
35246
+ "CodeS3Location",
35247
+ "RequestMappingTemplateS3Location",
35248
+ "ResponseMappingTemplateS3Location"
35249
+ ];
35089
35250
  return [];
35090
35251
  }
35091
35252
  /**
@@ -35098,15 +35259,15 @@ var AppSyncProvider = class {
35098
35259
  * possibly-malformed value"). An ABSENT container keeps returning
35099
35260
  * `undefined`, which is a legitimate "not configured".
35100
35261
  */
35101
- asObject(value, path, options) {
35262
+ asObject(value, path, options, resourceType = "AWS::AppSync::GraphQLApi") {
35102
35263
  if (value == null) return void 0;
35103
35264
  if (typeof value === "object" && !Array.isArray(value)) return value;
35104
- const message = `AWS::AppSync::GraphQLApi ${path} must be an object, got ${Array.isArray(value) ? "array" : typeof value} — cdkd refuses to drop it silently`;
35265
+ const message = `${resourceType} ${path} must be an object, got ${Array.isArray(value) ? "array" : typeof value} — cdkd refuses to drop it silently`;
35105
35266
  if (options?.onUnusable) {
35106
35267
  options.onUnusable(message);
35107
35268
  return;
35108
35269
  }
35109
- throw new ProvisioningError(message, "AWS::AppSync::GraphQLApi", options?.logicalId ?? "");
35270
+ throw new ProvisioningError(message, resourceType, options?.logicalId ?? "");
35110
35271
  }
35111
35272
  /**
35112
35273
  * NOTE: `awsRegion` / `defaultAction` are passed through VERBATIM, including
@@ -35284,6 +35445,211 @@ var AppSyncProvider = class {
35284
35445
  throw this.wrapUpdateError(error, resourceType, logicalId, apiId, isUpdate ? "GraphqlApi (env vars)" : "GraphqlApi (env vars, post-create)");
35285
35446
  }
35286
35447
  }
35448
+ /** CFn `CachingConfig` (`Ttl` / `CachingKeys`) -> SDK `cachingConfig`. */
35449
+ toSdkCachingConfig(value, logicalId) {
35450
+ const cfn = this.asObject(value, "CachingConfig", { logicalId }, "AWS::AppSync::Resolver");
35451
+ if (!cfn) return void 0;
35452
+ const out = { ttl: cfn["Ttl"] };
35453
+ if (cfn["CachingKeys"] !== void 0) out.cachingKeys = requireConfigArray(cfn["CachingKeys"], "AWS::AppSync::Resolver CachingConfig.CachingKeys");
35454
+ return out;
35455
+ }
35456
+ /**
35457
+ * CFn `SyncConfig` (`ConflictDetection` / `ConflictHandler` /
35458
+ * `LambdaConflictHandlerConfig.LambdaConflictHandlerArn`) -> SDK
35459
+ * `syncConfig`.
35460
+ */
35461
+ toSdkSyncConfig(value, logicalId) {
35462
+ const cfn = this.asObject(value, "SyncConfig", { logicalId }, "AWS::AppSync::Resolver");
35463
+ if (!cfn) return void 0;
35464
+ const out = {};
35465
+ if (cfn["ConflictDetection"] !== void 0) out.conflictDetection = cfn["ConflictDetection"];
35466
+ if (cfn["ConflictHandler"] !== void 0) out.conflictHandler = cfn["ConflictHandler"];
35467
+ const lambdaHandler = this.asObject(cfn["LambdaConflictHandlerConfig"], "SyncConfig.LambdaConflictHandlerConfig", { logicalId }, "AWS::AppSync::Resolver");
35468
+ if (lambdaHandler) out.lambdaConflictHandlerConfig = { lambdaConflictHandlerArn: lambdaHandler["LambdaConflictHandlerArn"] };
35469
+ return out;
35470
+ }
35471
+ /**
35472
+ * Parse the `s3://bucket/key` URL the AppSync `*S3Location` string
35473
+ * properties carry.
35474
+ *
35475
+ * CDK's asset bindings (`appsync.Code.fromAsset`, `aws-s3-assets` Asset
35476
+ * `s3ObjectUrl`) emit exactly this form, and the CloudFormation examples
35477
+ * for these properties use it too. Anything else (an `https://` object
35478
+ * URL, a bare bucket, a non-string) is refused with a clear error naming
35479
+ * the property — never silently dropped.
35480
+ */
35481
+ parseS3LocationUrl(location, resourceType, logicalId, propertyPath) {
35482
+ if (typeof location !== "string") throw new ProvisioningError(`${resourceType} ${propertyPath} must be a string s3://bucket/key URL, got ${Array.isArray(location) ? "array" : typeof location} — check for an unresolved intrinsic`, resourceType, logicalId);
35483
+ const match = /^s3:\/\/([^/]+)\/(.+)$/.exec(location);
35484
+ if (!match || !match[1] || !match[2]) throw new ProvisioningError(`${resourceType} ${propertyPath} must be an s3://bucket/key URL (got '${location}')`, resourceType, logicalId);
35485
+ return {
35486
+ bucket: match[1],
35487
+ key: match[2]
35488
+ };
35489
+ }
35490
+ /**
35491
+ * Fetch an `*S3Location` object body as a UTF-8 string.
35492
+ *
35493
+ * CloudFormation's behavior for these properties is to download the S3
35494
+ * object and pass its CONTENT as the corresponding inline member
35495
+ * (`code` / `requestMappingTemplate` / `responseMappingTemplate` /
35496
+ * `definition`); the URL itself never reaches AppSync. Mirrors
35497
+ * `StepFunctionsProvider.fetchDefinitionFromS3`, including the
35498
+ * empty-body refusal (AWS would reject '' with a generic validation
35499
+ * error naming nothing).
35500
+ */
35501
+ async fetchS3LocationBody(location, resourceType, logicalId, propertyPath) {
35502
+ const { bucket, key } = this.parseS3LocationUrl(location, resourceType, logicalId, propertyPath);
35503
+ this.logger.debug(`Fetching ${propertyPath} for ${logicalId} from s3://${bucket}/${key}`);
35504
+ let body;
35505
+ try {
35506
+ const resp = await this.getS3Client().send(new GetObjectCommand({
35507
+ Bucket: bucket,
35508
+ Key: key
35509
+ }));
35510
+ if (!resp.Body) throw new Error(`object returned no body`);
35511
+ body = await resp.Body.transformToString();
35512
+ } catch (error) {
35513
+ if (error instanceof ProvisioningError) throw error;
35514
+ throw new ProvisioningError(`Failed to fetch ${propertyPath} s3://${bucket}/${key} for ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, error instanceof Error ? error : void 0);
35515
+ }
35516
+ if (body.length === 0) throw new ProvisioningError(`${propertyPath} object s3://${bucket}/${key} returned an empty body`, resourceType, logicalId);
35517
+ return body;
35518
+ }
35519
+ /**
35520
+ * Resolve an inline-or-S3 property pair to the inline SDK value.
35521
+ *
35522
+ * Precedence: the INLINE property wins when both are present, matching
35523
+ * the repo's established S3Location precedent (StepFunctions
35524
+ * `DefinitionString` over `DefinitionS3Location`, AppSync GraphQLSchema
35525
+ * `Definition` over `DefinitionS3Location`) — CFn treats the pair as
35526
+ * alternatives and CDK's bindings only ever emit one, so both-present is
35527
+ * a hand-authored template; it is honored inline-first with a loud
35528
+ * warning rather than refused.
35529
+ */
35530
+ async resolveInlineOrS3Template(inline, s3Location, inlineKey, s3Key, resourceType, logicalId) {
35531
+ if (inline !== void 0) {
35532
+ if (s3Location !== void 0) this.logger.warn(`${resourceType} ${logicalId}: both ${inlineKey} and ${s3Key} are set — using the inline ${inlineKey} and ignoring ${s3Key} (CFn treats them as alternatives)`);
35533
+ return inline;
35534
+ }
35535
+ if (s3Location !== void 0) return this.fetchS3LocationBody(s3Location, resourceType, logicalId, s3Key);
35536
+ }
35537
+ /**
35538
+ * Fill the mutable members shared by `CreateResolver` and `UpdateResolver`
35539
+ * from the CFn property bag — the single mapper both paths use so they
35540
+ * cannot diverge (the `applyGraphQLApiConfig` pattern).
35541
+ *
35542
+ * REMOVAL semantics: unlike `UpdateGraphqlApi` (where an omitted member is
35543
+ * "no change" and removal needs reset sentinels), `UpdateResolver` is a
35544
+ * FULL-REPLACE write — the desired bag is mapped in its entirety and an
35545
+ * omitted member is cleared server-side, so "absent in template => omitted
35546
+ * from the update input => cleared by AppSync" and no sentinel is needed.
35547
+ * That semantic is pinned live by the integ fixture's REMOVAL phase
35548
+ * (tests/integration/appsync/verify.sh drops the resolver's MetricsConfig
35549
+ * and asserts AWS reset it while a sibling DataSource keeps its own).
35550
+ * MetricsConfig is the one member A/B'd against real AWS; the other
35551
+ * members rest on the same UpdateResolver full-replace API contract, not
35552
+ * on a per-member live probe (the #1160 doctrine's per-member A/B was
35553
+ * deliberately not paid here — recorded in the PR body).
35554
+ *
35555
+ * Resolver shape is `Kind`-discriminated:
35556
+ * - Kind=UNIT -> DataSourceName applies, PipelineConfig is N/A.
35557
+ * - Kind=PIPELINE -> PipelineConfig applies, DataSourceName is N/A
35558
+ * (AWS rejects the call if it's set).
35559
+ * The gate applies ONLY on update (`forUpdate`), where the effective Kind
35560
+ * falls back to the state-recorded previous value and finally AWS's
35561
+ * default 'UNIT' — an update must not re-send the other kind's member on a
35562
+ * live resolver. On CREATE both members forward UNCONDITIONALLY when
35563
+ * present: gating there would silently drop a crossed shape (the very
35564
+ * silent-drop class this batch closes) where CFn parity is AWS's own loud
35565
+ * rejection — the same forward-don't-gate doctrine as
35566
+ * `applyDataSourceConfig`'s per-type sub-configs.
35567
+ */
35568
+ async applyResolverConfig(input, resourceType, logicalId, properties, previousProperties, forUpdate = false) {
35569
+ const effectiveKind = properties["Kind"] ?? previousProperties?.["Kind"] ?? "UNIT";
35570
+ if ((!forUpdate || effectiveKind === "UNIT") && properties["DataSourceName"] !== void 0) input.dataSourceName = properties["DataSourceName"];
35571
+ if (properties["Kind"] !== void 0) input.kind = properties["Kind"];
35572
+ if ((!forUpdate || effectiveKind === "PIPELINE") && properties["PipelineConfig"] !== void 0) input.pipelineConfig = { functions: properties["PipelineConfig"]["Functions"] };
35573
+ if (properties["Runtime"] !== void 0) {
35574
+ const runtime = properties["Runtime"];
35575
+ input.runtime = {
35576
+ name: runtime["Name"],
35577
+ runtimeVersion: runtime["RuntimeVersion"]
35578
+ };
35579
+ }
35580
+ const requestTemplate = await this.resolveInlineOrS3Template(properties["RequestMappingTemplate"], properties["RequestMappingTemplateS3Location"], "RequestMappingTemplate", "RequestMappingTemplateS3Location", resourceType, logicalId);
35581
+ if (requestTemplate !== void 0) input.requestMappingTemplate = requestTemplate;
35582
+ const responseTemplate = await this.resolveInlineOrS3Template(properties["ResponseMappingTemplate"], properties["ResponseMappingTemplateS3Location"], "ResponseMappingTemplate", "ResponseMappingTemplateS3Location", resourceType, logicalId);
35583
+ if (responseTemplate !== void 0) input.responseMappingTemplate = responseTemplate;
35584
+ const code = await this.resolveInlineOrS3Template(properties["Code"], properties["CodeS3Location"], "Code", "CodeS3Location", resourceType, logicalId);
35585
+ if (code !== void 0) input.code = code;
35586
+ const caching = this.toSdkCachingConfig(properties["CachingConfig"], logicalId);
35587
+ if (caching) input.cachingConfig = caching;
35588
+ const sync = this.toSdkSyncConfig(properties["SyncConfig"], logicalId);
35589
+ if (sync) input.syncConfig = sync;
35590
+ if (properties["MaxBatchSize"] !== void 0) input.maxBatchSize = properties["MaxBatchSize"];
35591
+ if (properties["MetricsConfig"] !== void 0) input.metricsConfig = properties["MetricsConfig"];
35592
+ }
35593
+ /**
35594
+ * Fill the mutable members shared by `CreateDataSource` and
35595
+ * `UpdateDataSource` from the CFn property bag — single mapper for both
35596
+ * paths (the `applyGraphQLApiConfig` pattern).
35597
+ *
35598
+ * REMOVAL semantics: `UpdateDataSource` is a FULL-REPLACE write like
35599
+ * `UpdateResolver` — an omitted member is cleared server-side, so removal
35600
+ * needs no reset sentinel (see `applyResolverConfig`'s note; pinned live
35601
+ * by the integ fixture's REMOVAL phase).
35602
+ *
35603
+ * The per-type sub-configs (`DynamoDBConfig` / `LambdaConfig` /
35604
+ * `HttpConfig` / `ElasticsearchConfig` / `OpenSearchServiceConfig` /
35605
+ * `EventBridgeConfig` / `RelationalDatabaseConfig`) are mutually exclusive
35606
+ * on the `Type` discriminator. The write side deliberately FORWARDS
35607
+ * whichever block the template carries rather than gating on `Type`:
35608
+ * gating would silently drop a mismatched block (the very silent-drop
35609
+ * class this batch closes), while AWS rejects a crossed shape with a loud
35610
+ * validation error. The read side (`readDataSource`) gates on the
35611
+ * response's own discriminator instead.
35612
+ */
35613
+ applyDataSourceConfig(input, resourceType, logicalId, properties) {
35614
+ if (properties["Description"] !== void 0) input.description = properties["Description"];
35615
+ if (properties["ServiceRoleArn"] !== void 0) input.serviceRoleArn = properties["ServiceRoleArn"];
35616
+ const dynamo = this.asObject(properties["DynamoDBConfig"], "DynamoDBConfig", { logicalId }, resourceType);
35617
+ if (dynamo) input.dynamodbConfig = {
35618
+ tableName: dynamo["TableName"],
35619
+ awsRegion: dynamo["AwsRegion"],
35620
+ ...dynamo["UseCallerCredentials"] !== void 0 ? { useCallerCredentials: dynamo["UseCallerCredentials"] } : {}
35621
+ };
35622
+ const lambda = this.asObject(properties["LambdaConfig"], "LambdaConfig", { logicalId }, resourceType);
35623
+ if (lambda) input.lambdaConfig = { lambdaFunctionArn: lambda["LambdaFunctionArn"] };
35624
+ const http = this.asObject(properties["HttpConfig"], "HttpConfig", { logicalId }, resourceType);
35625
+ if (http) input.httpConfig = { endpoint: http["Endpoint"] };
35626
+ const es = this.asObject(properties["ElasticsearchConfig"], "ElasticsearchConfig", { logicalId }, resourceType);
35627
+ if (es) input.elasticsearchConfig = {
35628
+ endpoint: es["Endpoint"],
35629
+ awsRegion: es["AwsRegion"]
35630
+ };
35631
+ const openSearch = this.asObject(properties["OpenSearchServiceConfig"], "OpenSearchServiceConfig", { logicalId }, resourceType);
35632
+ if (openSearch) input.openSearchServiceConfig = {
35633
+ endpoint: openSearch["Endpoint"],
35634
+ awsRegion: openSearch["AwsRegion"]
35635
+ };
35636
+ const eventBridge = this.asObject(properties["EventBridgeConfig"], "EventBridgeConfig", { logicalId }, resourceType);
35637
+ if (eventBridge) input.eventBridgeConfig = { eventBusArn: eventBridge["EventBusArn"] };
35638
+ const relational = this.asObject(properties["RelationalDatabaseConfig"], "RelationalDatabaseConfig", { logicalId }, resourceType);
35639
+ if (relational) {
35640
+ const rdsConfig = { relationalDatabaseSourceType: relational["RelationalDatabaseSourceType"] };
35641
+ const rdsEndpoint = this.asObject(relational["RdsHttpEndpointConfig"], "RelationalDatabaseConfig.RdsHttpEndpointConfig", { logicalId }, resourceType);
35642
+ if (rdsEndpoint) rdsConfig.rdsHttpEndpointConfig = {
35643
+ awsRegion: rdsEndpoint["AwsRegion"],
35644
+ dbClusterIdentifier: rdsEndpoint["DbClusterIdentifier"],
35645
+ ...rdsEndpoint["DatabaseName"] !== void 0 ? { databaseName: rdsEndpoint["DatabaseName"] } : {},
35646
+ ...rdsEndpoint["Schema"] !== void 0 ? { schema: rdsEndpoint["Schema"] } : {},
35647
+ ...rdsEndpoint["AwsSecretStoreArn"] !== void 0 ? { awsSecretStoreArn: rdsEndpoint["AwsSecretStoreArn"] } : {}
35648
+ };
35649
+ input.relationalDatabaseConfig = rdsConfig;
35650
+ }
35651
+ if (properties["MetricsConfig"] !== void 0) input.metricsConfig = properties["MetricsConfig"];
35652
+ }
35287
35653
  async createGraphQLApi(logicalId, resourceType, properties, context) {
35288
35654
  this.logger.debug(`Creating GraphQL API ${logicalId}`);
35289
35655
  const name = properties["Name"];
@@ -35368,11 +35734,19 @@ var AppSyncProvider = class {
35368
35734
  const definition = properties["Definition"];
35369
35735
  const definitionS3Location = properties["DefinitionS3Location"];
35370
35736
  try {
35371
- if (definition) await this.getClient().send(new StartSchemaCreationCommand({
35372
- apiId,
35373
- definition: new TextEncoder().encode(definition)
35374
- }));
35375
- else if (definitionS3Location) this.logger.warn(`S3-based schema definition for ${logicalId} - using inline only`);
35737
+ if (definition) {
35738
+ if (definitionS3Location !== void 0) this.logger.warn(`${resourceType} ${logicalId}: both Definition and DefinitionS3Location are set — using the inline Definition and ignoring DefinitionS3Location (CFn treats them as alternatives)`);
35739
+ await this.getClient().send(new StartSchemaCreationCommand({
35740
+ apiId,
35741
+ definition: new TextEncoder().encode(definition)
35742
+ }));
35743
+ } else if (definitionS3Location !== void 0) {
35744
+ const body = await this.fetchS3LocationBody(definitionS3Location, resourceType, logicalId, "DefinitionS3Location");
35745
+ await this.getClient().send(new StartSchemaCreationCommand({
35746
+ apiId,
35747
+ definition: new TextEncoder().encode(body)
35748
+ }));
35749
+ }
35376
35750
  this.logger.debug(`Successfully started schema creation for ${logicalId}`);
35377
35751
  return {
35378
35752
  physicalId: apiId,
@@ -35395,18 +35769,7 @@ var AppSyncProvider = class {
35395
35769
  name,
35396
35770
  type
35397
35771
  };
35398
- if (properties["Description"]) input.description = properties["Description"];
35399
- if (properties["ServiceRoleArn"]) input.serviceRoleArn = properties["ServiceRoleArn"];
35400
- if (properties["DynamoDBConfig"]) {
35401
- const config = properties["DynamoDBConfig"];
35402
- input.dynamodbConfig = {
35403
- tableName: config["TableName"],
35404
- awsRegion: config["AwsRegion"],
35405
- useCallerCredentials: config["UseCallerCredentials"]
35406
- };
35407
- }
35408
- if (properties["LambdaConfig"]) input.lambdaConfig = { lambdaFunctionArn: properties["LambdaConfig"]["LambdaFunctionArn"] };
35409
- if (properties["HttpConfig"]) input.httpConfig = { endpoint: properties["HttpConfig"]["Endpoint"] };
35772
+ this.applyDataSourceConfig(input, resourceType, logicalId, properties);
35410
35773
  await this.getClient().send(new CreateDataSourceCommand(input));
35411
35774
  const physicalId = `${apiId}|${name}`;
35412
35775
  this.logger.debug(`Successfully created DataSource ${logicalId}: ${physicalId}`);
@@ -35457,19 +35820,7 @@ var AppSyncProvider = class {
35457
35820
  typeName,
35458
35821
  fieldName
35459
35822
  };
35460
- if (properties["DataSourceName"]) input.dataSourceName = properties["DataSourceName"];
35461
- if (properties["RequestMappingTemplate"]) input.requestMappingTemplate = properties["RequestMappingTemplate"];
35462
- if (properties["ResponseMappingTemplate"]) input.responseMappingTemplate = properties["ResponseMappingTemplate"];
35463
- if (properties["Kind"]) input.kind = properties["Kind"];
35464
- if (properties["PipelineConfig"]) input.pipelineConfig = { functions: properties["PipelineConfig"]["Functions"] };
35465
- if (properties["Runtime"]) {
35466
- const runtime = properties["Runtime"];
35467
- input.runtime = {
35468
- name: runtime["Name"],
35469
- runtimeVersion: runtime["RuntimeVersion"]
35470
- };
35471
- }
35472
- if (properties["Code"]) input.code = properties["Code"];
35823
+ await this.applyResolverConfig(input, resourceType, logicalId, properties);
35473
35824
  await this.getClient().send(new CreateResolverCommand(input));
35474
35825
  const physicalId = `${apiId}|${typeName}|${fieldName}`;
35475
35826
  this.logger.debug(`Successfully created Resolver ${logicalId}: ${physicalId}`);
@@ -35568,10 +35919,13 @@ var AppSyncProvider = class {
35568
35919
  * CDK's `aws:*` auto-tags are filtered out and the result key is
35569
35920
  * omitted when no user tags remain.
35570
35921
  * - `DataSource` → `GetDataSource` (Name, Type, Description,
35571
- * ServiceRoleArn, DynamoDBConfig, LambdaConfig, HttpConfig). The
35922
+ * ServiceRoleArn, the per-type configs — DynamoDBConfig, LambdaConfig,
35923
+ * HttpConfig, ElasticsearchConfig, OpenSearchServiceConfig,
35924
+ * EventBridgeConfig, RelationalDatabaseConfig — and MetricsConfig). The
35572
35925
  * `ApiId` cdkd holds is recovered from the `apiId|name` physicalId.
35573
35926
  * - `Resolver` → `GetResolver` (TypeName, FieldName, DataSourceName,
35574
- * request/response templates, Kind, PipelineConfig, Runtime, Code).
35927
+ * request/response templates, Kind, PipelineConfig, Runtime, Code,
35928
+ * CachingConfig, SyncConfig, MaxBatchSize, MetricsConfig).
35575
35929
  * - `ApiKey` → `ListApiKeys` filtered by id (no `GetApiKey` SDK call;
35576
35930
  * AppSync only exposes list-based access). Surfaces Description and
35577
35931
  * Expires.
@@ -35749,6 +36103,35 @@ var AppSyncProvider = class {
35749
36103
  }
35750
36104
  if (ds.lambdaConfig?.lambdaFunctionArn !== void 0) result["LambdaConfig"] = { LambdaFunctionArn: ds.lambdaConfig.lambdaFunctionArn };
35751
36105
  if (ds.httpConfig?.endpoint !== void 0) result["HttpConfig"] = { Endpoint: ds.httpConfig.endpoint };
36106
+ if (ds.elasticsearchConfig) {
36107
+ const es = {};
36108
+ if (ds.elasticsearchConfig.endpoint !== void 0) es["Endpoint"] = ds.elasticsearchConfig.endpoint;
36109
+ if (ds.elasticsearchConfig.awsRegion !== void 0) es["AwsRegion"] = ds.elasticsearchConfig.awsRegion;
36110
+ if (Object.keys(es).length > 0) result["ElasticsearchConfig"] = es;
36111
+ }
36112
+ if (ds.openSearchServiceConfig) {
36113
+ const os = {};
36114
+ if (ds.openSearchServiceConfig.endpoint !== void 0) os["Endpoint"] = ds.openSearchServiceConfig.endpoint;
36115
+ if (ds.openSearchServiceConfig.awsRegion !== void 0) os["AwsRegion"] = ds.openSearchServiceConfig.awsRegion;
36116
+ if (Object.keys(os).length > 0) result["OpenSearchServiceConfig"] = os;
36117
+ }
36118
+ if (ds.eventBridgeConfig?.eventBusArn !== void 0) result["EventBridgeConfig"] = { EventBusArn: ds.eventBridgeConfig.eventBusArn };
36119
+ if (ds.relationalDatabaseConfig) {
36120
+ const rds = {};
36121
+ if (ds.relationalDatabaseConfig.relationalDatabaseSourceType !== void 0) rds["RelationalDatabaseSourceType"] = ds.relationalDatabaseConfig.relationalDatabaseSourceType;
36122
+ const endpoint = ds.relationalDatabaseConfig.rdsHttpEndpointConfig;
36123
+ if (endpoint) {
36124
+ const ep = {};
36125
+ if (endpoint.awsRegion !== void 0) ep["AwsRegion"] = endpoint.awsRegion;
36126
+ if (endpoint.dbClusterIdentifier !== void 0) ep["DbClusterIdentifier"] = endpoint.dbClusterIdentifier;
36127
+ if (endpoint.databaseName !== void 0) ep["DatabaseName"] = endpoint.databaseName;
36128
+ if (endpoint.schema !== void 0) ep["Schema"] = endpoint.schema;
36129
+ if (endpoint.awsSecretStoreArn !== void 0) ep["AwsSecretStoreArn"] = endpoint.awsSecretStoreArn;
36130
+ if (Object.keys(ep).length > 0) rds["RdsHttpEndpointConfig"] = ep;
36131
+ }
36132
+ if (Object.keys(rds).length > 0) result["RelationalDatabaseConfig"] = rds;
36133
+ }
36134
+ if (ds.metricsConfig !== void 0) result["MetricsConfig"] = ds.metricsConfig;
35752
36135
  return result;
35753
36136
  }
35754
36137
  async readResolver(physicalId) {
@@ -35783,6 +36166,21 @@ var AppSyncProvider = class {
35783
36166
  result["RequestMappingTemplate"] = resolver.requestMappingTemplate ?? "";
35784
36167
  result["ResponseMappingTemplate"] = resolver.responseMappingTemplate ?? "";
35785
36168
  }
36169
+ if (resolver.cachingConfig) {
36170
+ const caching = {};
36171
+ if (resolver.cachingConfig.ttl !== void 0) caching["Ttl"] = resolver.cachingConfig.ttl;
36172
+ if (resolver.cachingConfig.cachingKeys !== void 0) caching["CachingKeys"] = [...resolver.cachingConfig.cachingKeys];
36173
+ if (Object.keys(caching).length > 0) result["CachingConfig"] = caching;
36174
+ }
36175
+ if (resolver.syncConfig) {
36176
+ const sync = {};
36177
+ if (resolver.syncConfig.conflictDetection !== void 0) sync["ConflictDetection"] = resolver.syncConfig.conflictDetection;
36178
+ if (resolver.syncConfig.conflictHandler !== void 0) sync["ConflictHandler"] = resolver.syncConfig.conflictHandler;
36179
+ if (resolver.syncConfig.lambdaConflictHandlerConfig?.lambdaConflictHandlerArn !== void 0) sync["LambdaConflictHandlerConfig"] = { LambdaConflictHandlerArn: resolver.syncConfig.lambdaConflictHandlerConfig.lambdaConflictHandlerArn };
36180
+ if (Object.keys(sync).length > 0) result["SyncConfig"] = sync;
36181
+ }
36182
+ if (resolver.maxBatchSize !== void 0) result["MaxBatchSize"] = resolver.maxBatchSize;
36183
+ if (resolver.metricsConfig !== void 0) result["MetricsConfig"] = resolver.metricsConfig;
35786
36184
  return result;
35787
36185
  }
35788
36186
  async readApiKey(physicalId) {
@@ -69522,7 +69920,7 @@ function createMigrateCommand() {
69522
69920
  */
69523
69921
  function buildProgram() {
69524
69922
  const program = new Command();
69525
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.17");
69923
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.19");
69526
69924
  program.hook("preAction", (_thisCommand, actionCommand) => {
69527
69925
  const { profile } = actionCommand.optsWithGlobals();
69528
69926
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;