@jaypie/constructs 1.2.41 → 1.2.43

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.
@@ -6,6 +6,15 @@ import * as route53 from "aws-cdk-lib/aws-route53";
6
6
  import { HostConfig } from "./helpers";
7
7
  export interface JaypieApiGatewayProps extends apiGateway.LambdaRestApiProps {
8
8
  certificate?: boolean | acm.ICertificate;
9
+ /**
10
+ * Force-delete any existing Route53 A record with the same name before
11
+ * creating the alias record. Useful when migrating from another construct
12
+ * (e.g., JaypieDistribution) that already owns the same hostname, where the
13
+ * default CloudFormation create-before-delete ordering would otherwise
14
+ * collide on the record name.
15
+ * @default false
16
+ */
17
+ deleteExistingRecord?: boolean;
9
18
  /**
10
19
  * The domain name for the API Gateway.
11
20
  *
@@ -66,6 +66,15 @@ export interface JaypieDistributionProps extends Omit<cloudfront.DistributionPro
66
66
  * Override default behavior (optional if handler is provided)
67
67
  */
68
68
  defaultBehavior?: cloudfront.BehaviorOptions;
69
+ /**
70
+ * Force-delete any existing Route53 A and AAAA records with the same name
71
+ * before creating the alias records. Useful when migrating from another
72
+ * construct (e.g., JaypieApiGateway) that already owns the same hostname,
73
+ * where the default CloudFormation create-before-delete ordering would
74
+ * otherwise collide on the record name.
75
+ * @default false
76
+ */
77
+ deleteExistingRecord?: boolean;
69
78
  /**
70
79
  * Log destination configuration for CloudFront access logs
71
80
  * - LambdaDestination: Use a specific Lambda destination for S3 notifications
@@ -6,25 +6,23 @@ export interface JaypieDynamoDbProps extends Omit<dynamodb.TablePropsV2, "global
6
6
  /**
7
7
  * Configure GSIs for the table using @jaypie/fabric IndexDefinition format.
8
8
  * - `undefined`: No GSIs (default)
9
- * - Array of IndexDefinition: Use the specified indexes
9
+ * - Array of IndexDefinition: Use the specified indexes (prefer fabricIndex())
10
10
  *
11
11
  * @example
12
12
  * // No GSIs (default)
13
13
  * new JaypieDynamoDb(this, "myTable");
14
14
  *
15
15
  * @example
16
- * // With custom indexes
16
+ * // With fabricIndex-shaped indexes
17
+ * import { fabricIndex } from "@jaypie/fabric";
17
18
  * new JaypieDynamoDb(this, "myTable", {
18
- * indexes: [
19
- * { pk: ["scope", "model"], sk: ["sequence"] },
20
- * { pk: ["scope", "model", "type"], sk: ["sequence"], sparse: true },
21
- * ],
19
+ * indexes: [fabricIndex(), fabricIndex("alias"), fabricIndex("xid")],
22
20
  * });
23
21
  */
24
22
  indexes?: IndexDefinition[];
25
23
  /**
26
24
  * Partition key attribute definition.
27
- * @default { name: "model", type: AttributeType.STRING }
25
+ * @default { name: "id", type: AttributeType.STRING }
28
26
  */
29
27
  partitionKey?: dynamodb.Attribute;
30
28
  /**
@@ -40,8 +38,8 @@ export interface JaypieDynamoDbProps extends Omit<dynamodb.TablePropsV2, "global
40
38
  */
41
39
  service?: string;
42
40
  /**
43
- * Sort key attribute definition.
44
- * @default { name: "id", type: AttributeType.STRING }
41
+ * Sort key attribute definition. Defaults to `undefined` (no sort key) —
42
+ * the Jaypie single-table pattern uses `id` as a unique partition key.
45
43
  */
46
44
  sortKey?: dynamodb.Attribute;
47
45
  /**
@@ -53,8 +51,7 @@ export interface JaypieDynamoDbProps extends Omit<dynamodb.TablePropsV2, "global
53
51
  * DynamoDB table with Jaypie single-table design patterns.
54
52
  *
55
53
  * Creates a table with:
56
- * - Partition key: `model` (String)
57
- * - Sort key: `id` (String)
54
+ * - Partition key: `id` (String), no sort key
58
55
  * - Billing: PAY_PER_REQUEST (on-demand)
59
56
  * - Removal policy: RETAIN in production, DESTROY otherwise
60
57
  * - No GSIs by default (use `indexes` prop to add them)
@@ -65,13 +62,11 @@ export interface JaypieDynamoDbProps extends Omit<dynamodb.TablePropsV2, "global
65
62
  * const table = new JaypieDynamoDb(this, "myApp");
66
63
  *
67
64
  * @example
68
- * // With explicit table name (overrides CDK-generated name)
65
+ * // With fabricIndex() for GSIs
66
+ * import { fabricIndex } from "@jaypie/fabric";
69
67
  * const table = new JaypieDynamoDb(this, "MyTable", {
70
68
  * tableName: "custom-table-name",
71
- * indexes: [
72
- * { pk: ["scope", "model"] },
73
- * { pk: ["scope", "model", "type"], sparse: true },
74
- * ],
69
+ * indexes: [fabricIndex(), fabricIndex("alias"), fabricIndex("xid")],
75
70
  * });
76
71
  */
77
72
  export declare class JaypieDynamoDb extends Construct implements dynamodb.ITableV2 {
@@ -1170,7 +1170,7 @@ function clearAllSecretsCaches() {
1170
1170
  class JaypieApiGateway extends constructs.Construct {
1171
1171
  constructor(scope, id, props) {
1172
1172
  super(scope, id);
1173
- const { certificate = true, handler, host: propsHost, name, roleTag = CDK$2.ROLE.API, zone: propsZone, } = props;
1173
+ const { certificate = true, deleteExistingRecord = false, handler, host: propsHost, name, roleTag = CDK$2.ROLE.API, zone: propsZone, } = props;
1174
1174
  // Determine zone from props or environment
1175
1175
  let zone = propsZone;
1176
1176
  if (!zone && process.env.CDK_ENV_API_HOSTED_ZONE) {
@@ -1216,7 +1216,7 @@ class JaypieApiGateway extends constructs.Construct {
1216
1216
  // * `...lambdaRestApiProps` cannot be moved to the first const destructuring because it needs to exclude the custom properties first.
1217
1217
  // Ignore the variables we already assigned to other properties
1218
1218
  /* eslint-disable @typescript-eslint/no-unused-vars */
1219
- certificate: _certificate, host: _host, name: _name, roleTag: _roleTag, zone: _zone, handler: _handler,
1219
+ certificate: _certificate, deleteExistingRecord: _deleteExistingRecord, host: _host, name: _name, roleTag: _roleTag, zone: _zone, handler: _handler,
1220
1220
  /* eslint-enable @typescript-eslint/no-unused-vars */
1221
1221
  ...lambdaRestApiProps } = props;
1222
1222
  this._api = new apiGateway__namespace.LambdaRestApi(this, apiGatewayName, {
@@ -1231,6 +1231,7 @@ class JaypieApiGateway extends constructs.Construct {
1231
1231
  });
1232
1232
  cdk.Tags.of(this._domainName).add(CDK$2.TAG.ROLE, roleTag);
1233
1233
  const record = new route53__namespace.ARecord(this, "AliasRecord", {
1234
+ deleteExisting: deleteExistingRecord,
1234
1235
  recordName: host,
1235
1236
  target: route53__namespace.RecordTarget.fromAlias(new route53Targets__namespace.ApiGatewayDomain(this._domainName)),
1236
1237
  zone: hostedZone,
@@ -2394,7 +2395,7 @@ const DEFAULT_MANAGED_RULES = [
2394
2395
  class JaypieDistribution extends constructs.Construct {
2395
2396
  constructor(scope, id, props) {
2396
2397
  super(scope, id);
2397
- const { certificate: certificateProp = true, defaultBehavior: propsDefaultBehavior, destination: destinationProp = true, handler, host: propsHost, logBucket: logBucketProp, originReadTimeout = cdk.Duration.seconds(CDK$2.DURATION.CLOUDFRONT_API), responseHeadersPolicy: responseHeadersPolicyProp, roleTag = CDK$2.ROLE.API, securityHeaders: securityHeadersProp, streaming = false, waf: wafProp = true, zone: propsZone, ...distributionProps } = props;
2398
+ const { certificate: certificateProp = true, defaultBehavior: propsDefaultBehavior, deleteExistingRecord = false, destination: destinationProp = true, handler, host: propsHost, logBucket: logBucketProp, originReadTimeout = cdk.Duration.seconds(CDK$2.DURATION.CLOUDFRONT_API), responseHeadersPolicy: responseHeadersPolicyProp, roleTag = CDK$2.ROLE.API, securityHeaders: securityHeadersProp, streaming = false, waf: wafProp = true, zone: propsZone, ...distributionProps } = props;
2398
2399
  // Validate environment variables
2399
2400
  if (process.env.CDK_ENV_API_SUBDOMAIN &&
2400
2401
  !isValidSubdomain(process.env.CDK_ENV_API_SUBDOMAIN)) {
@@ -2754,12 +2755,14 @@ class JaypieDistribution extends constructs.Construct {
2754
2755
  // Create DNS records if we have host and zone
2755
2756
  if (host && hostedZone) {
2756
2757
  const aRecord = new route53__namespace.ARecord(this, "AliasRecord", {
2758
+ deleteExisting: deleteExistingRecord,
2757
2759
  recordName: host,
2758
2760
  target: route53__namespace.RecordTarget.fromAlias(new route53Targets__namespace.CloudFrontTarget(this.distribution)),
2759
2761
  zone: hostedZone,
2760
2762
  });
2761
2763
  cdk.Tags.of(aRecord).add(CDK$2.TAG.ROLE, CDK$2.ROLE.NETWORKING);
2762
2764
  const aaaaRecord = new route53__namespace.AaaaRecord(this, "AaaaAliasRecord", {
2765
+ deleteExisting: deleteExistingRecord,
2763
2766
  recordName: host,
2764
2767
  target: route53__namespace.RecordTarget.fromAlias(new route53Targets__namespace.CloudFrontTarget(this.distribution)),
2765
2768
  zone: hostedZone,
@@ -2928,41 +2931,39 @@ class JaypieDnsRecord extends constructs.Construct {
2928
2931
  }
2929
2932
  }
2930
2933
 
2931
- //
2932
- //
2933
- // Constants
2934
- //
2935
- /** Composite key separator used in GSI partition keys */
2936
- const SEPARATOR = "#";
2937
2934
  //
2938
2935
  //
2939
2936
  // Helper Functions
2940
2937
  //
2941
2938
  /**
2942
- * Convert IndexDefinition[] from @jaypie/fabric to CDK GlobalSecondaryIndexPropsV2[]
2939
+ * Convert IndexDefinition[] from @jaypie/fabric to CDK GlobalSecondaryIndexPropsV2[].
2943
2940
  *
2944
- * @param indexes - Array of IndexDefinition from @jaypie/fabric
2945
- * @returns Array of CDK GlobalSecondaryIndexPropsV2
2941
+ * Uses `getGsiAttributeNames` as the single source of truth so runtime writes
2942
+ * and CDK provisioning agree on attribute names. Composite sk indexes
2943
+ * (sk.length > 1) get a dedicated STRING `{indexName}Sk` attribute; single-field
2944
+ * sk indexes reference the field directly (STRING in the general case, NUMBER
2945
+ * for the legacy `sequence` name).
2946
2946
  */
2947
2947
  function indexesToGsi(indexes) {
2948
2948
  return indexes.map((index) => {
2949
- // Generate index name from pk fields if not provided
2950
- const indexName = index.name ?? fabric.generateIndexName(index.pk);
2951
- // Sort key defaults to ["sequence"] if not provided
2952
- const skFields = index.sk ?? fabric.DEFAULT_SORT_KEY;
2949
+ const { pk, sk } = fabric.getGsiAttributeNames(index);
2950
+ let sortKey;
2951
+ if (sk) {
2952
+ if (sk === "sequence") {
2953
+ sortKey = { name: "sequence", type: dynamodb__namespace.AttributeType.NUMBER };
2954
+ }
2955
+ else {
2956
+ sortKey = { name: sk, type: dynamodb__namespace.AttributeType.STRING };
2957
+ }
2958
+ }
2953
2959
  return {
2954
- indexName,
2960
+ indexName: pk,
2955
2961
  partitionKey: {
2956
- name: indexName,
2962
+ name: pk,
2957
2963
  type: dynamodb__namespace.AttributeType.STRING,
2958
2964
  },
2959
2965
  projectionType: dynamodb__namespace.ProjectionType.ALL,
2960
- sortKey: skFields.length === 1 && skFields[0] === "sequence"
2961
- ? { name: "sequence", type: dynamodb__namespace.AttributeType.NUMBER }
2962
- : {
2963
- name: skFields.join(SEPARATOR),
2964
- type: dynamodb__namespace.AttributeType.STRING,
2965
- },
2966
+ ...(sortKey && { sortKey }),
2966
2967
  };
2967
2968
  });
2968
2969
  }
@@ -2974,8 +2975,7 @@ function indexesToGsi(indexes) {
2974
2975
  * DynamoDB table with Jaypie single-table design patterns.
2975
2976
  *
2976
2977
  * Creates a table with:
2977
- * - Partition key: `model` (String)
2978
- * - Sort key: `id` (String)
2978
+ * - Partition key: `id` (String), no sort key
2979
2979
  * - Billing: PAY_PER_REQUEST (on-demand)
2980
2980
  * - Removal policy: RETAIN in production, DESTROY otherwise
2981
2981
  * - No GSIs by default (use `indexes` prop to add them)
@@ -2986,13 +2986,11 @@ function indexesToGsi(indexes) {
2986
2986
  * const table = new JaypieDynamoDb(this, "myApp");
2987
2987
  *
2988
2988
  * @example
2989
- * // With explicit table name (overrides CDK-generated name)
2989
+ * // With fabricIndex() for GSIs
2990
+ * import { fabricIndex } from "@jaypie/fabric";
2990
2991
  * const table = new JaypieDynamoDb(this, "MyTable", {
2991
2992
  * tableName: "custom-table-name",
2992
- * indexes: [
2993
- * { pk: ["scope", "model"] },
2994
- * { pk: ["scope", "model", "type"], sparse: true },
2995
- * ],
2993
+ * indexes: [fabricIndex(), fabricIndex("alias"), fabricIndex("xid")],
2996
2994
  * });
2997
2995
  */
2998
2996
  class JaypieDynamoDb extends constructs.Construct {
@@ -3004,11 +3002,11 @@ class JaypieDynamoDb extends constructs.Construct {
3004
3002
  const constructId = isShorthand ? `JaypieDynamoDb-${id}` : id;
3005
3003
  super(scope, constructId);
3006
3004
  const { billing = dynamodb__namespace.Billing.onDemand(), indexes, partitionKey = {
3007
- name: "model",
3005
+ name: "id",
3008
3006
  type: dynamodb__namespace.AttributeType.STRING,
3009
3007
  }, project, removalPolicy = isProductionEnv()
3010
3008
  ? cdk.RemovalPolicy.RETAIN
3011
- : cdk.RemovalPolicy.DESTROY, roleTag = CDK$2.ROLE.STORAGE, service, sortKey = { name: "id", type: dynamodb__namespace.AttributeType.STRING }, vendorTag, ...restProps } = props;
3009
+ : cdk.RemovalPolicy.DESTROY, roleTag = CDK$2.ROLE.STORAGE, service, sortKey, vendorTag, ...restProps } = props;
3012
3010
  // Convert IndexDefinition[] to CDK GSI props
3013
3011
  const globalSecondaryIndexes = indexes ? indexesToGsi(indexes) : undefined;
3014
3012
  this._table = new dynamodb__namespace.TableV2(this, "Table", {
@@ -3016,7 +3014,7 @@ class JaypieDynamoDb extends constructs.Construct {
3016
3014
  globalSecondaryIndexes,
3017
3015
  partitionKey,
3018
3016
  removalPolicy,
3019
- sortKey,
3017
+ ...(sortKey && { sortKey }),
3020
3018
  ...restProps,
3021
3019
  });
3022
3020
  // Apply tags