@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 {
package/dist/esm/index.js CHANGED
@@ -26,7 +26,7 @@ import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
26
26
  import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
27
27
  import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
28
28
  import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
29
- import { generateIndexName, DEFAULT_SORT_KEY } from '@jaypie/fabric';
29
+ import { getGsiAttributeNames } from '@jaypie/fabric';
30
30
  import * as cr from 'aws-cdk-lib/custom-resources';
31
31
  import { Nextjs } from 'cdk-nextjs-standalone';
32
32
  import * as path from 'path';
@@ -1134,7 +1134,7 @@ function clearAllSecretsCaches() {
1134
1134
  class JaypieApiGateway extends Construct {
1135
1135
  constructor(scope, id, props) {
1136
1136
  super(scope, id);
1137
- const { certificate = true, handler, host: propsHost, name, roleTag = CDK$2.ROLE.API, zone: propsZone, } = props;
1137
+ const { certificate = true, deleteExistingRecord = false, handler, host: propsHost, name, roleTag = CDK$2.ROLE.API, zone: propsZone, } = props;
1138
1138
  // Determine zone from props or environment
1139
1139
  let zone = propsZone;
1140
1140
  if (!zone && process.env.CDK_ENV_API_HOSTED_ZONE) {
@@ -1180,7 +1180,7 @@ class JaypieApiGateway extends Construct {
1180
1180
  // * `...lambdaRestApiProps` cannot be moved to the first const destructuring because it needs to exclude the custom properties first.
1181
1181
  // Ignore the variables we already assigned to other properties
1182
1182
  /* eslint-disable @typescript-eslint/no-unused-vars */
1183
- certificate: _certificate, host: _host, name: _name, roleTag: _roleTag, zone: _zone, handler: _handler,
1183
+ certificate: _certificate, deleteExistingRecord: _deleteExistingRecord, host: _host, name: _name, roleTag: _roleTag, zone: _zone, handler: _handler,
1184
1184
  /* eslint-enable @typescript-eslint/no-unused-vars */
1185
1185
  ...lambdaRestApiProps } = props;
1186
1186
  this._api = new apiGateway.LambdaRestApi(this, apiGatewayName, {
@@ -1195,6 +1195,7 @@ class JaypieApiGateway extends Construct {
1195
1195
  });
1196
1196
  Tags.of(this._domainName).add(CDK$2.TAG.ROLE, roleTag);
1197
1197
  const record = new route53.ARecord(this, "AliasRecord", {
1198
+ deleteExisting: deleteExistingRecord,
1198
1199
  recordName: host,
1199
1200
  target: route53.RecordTarget.fromAlias(new route53Targets.ApiGatewayDomain(this._domainName)),
1200
1201
  zone: hostedZone,
@@ -2358,7 +2359,7 @@ const DEFAULT_MANAGED_RULES = [
2358
2359
  class JaypieDistribution extends Construct {
2359
2360
  constructor(scope, id, props) {
2360
2361
  super(scope, id);
2361
- const { certificate: certificateProp = true, defaultBehavior: propsDefaultBehavior, destination: destinationProp = true, handler, host: propsHost, logBucket: logBucketProp, originReadTimeout = 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;
2362
+ const { certificate: certificateProp = true, defaultBehavior: propsDefaultBehavior, deleteExistingRecord = false, destination: destinationProp = true, handler, host: propsHost, logBucket: logBucketProp, originReadTimeout = 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;
2362
2363
  // Validate environment variables
2363
2364
  if (process.env.CDK_ENV_API_SUBDOMAIN &&
2364
2365
  !isValidSubdomain(process.env.CDK_ENV_API_SUBDOMAIN)) {
@@ -2718,12 +2719,14 @@ class JaypieDistribution extends Construct {
2718
2719
  // Create DNS records if we have host and zone
2719
2720
  if (host && hostedZone) {
2720
2721
  const aRecord = new route53.ARecord(this, "AliasRecord", {
2722
+ deleteExisting: deleteExistingRecord,
2721
2723
  recordName: host,
2722
2724
  target: route53.RecordTarget.fromAlias(new route53Targets.CloudFrontTarget(this.distribution)),
2723
2725
  zone: hostedZone,
2724
2726
  });
2725
2727
  Tags.of(aRecord).add(CDK$2.TAG.ROLE, CDK$2.ROLE.NETWORKING);
2726
2728
  const aaaaRecord = new route53.AaaaRecord(this, "AaaaAliasRecord", {
2729
+ deleteExisting: deleteExistingRecord,
2727
2730
  recordName: host,
2728
2731
  target: route53.RecordTarget.fromAlias(new route53Targets.CloudFrontTarget(this.distribution)),
2729
2732
  zone: hostedZone,
@@ -2892,41 +2895,39 @@ class JaypieDnsRecord extends Construct {
2892
2895
  }
2893
2896
  }
2894
2897
 
2895
- //
2896
- //
2897
- // Constants
2898
- //
2899
- /** Composite key separator used in GSI partition keys */
2900
- const SEPARATOR = "#";
2901
2898
  //
2902
2899
  //
2903
2900
  // Helper Functions
2904
2901
  //
2905
2902
  /**
2906
- * Convert IndexDefinition[] from @jaypie/fabric to CDK GlobalSecondaryIndexPropsV2[]
2903
+ * Convert IndexDefinition[] from @jaypie/fabric to CDK GlobalSecondaryIndexPropsV2[].
2907
2904
  *
2908
- * @param indexes - Array of IndexDefinition from @jaypie/fabric
2909
- * @returns Array of CDK GlobalSecondaryIndexPropsV2
2905
+ * Uses `getGsiAttributeNames` as the single source of truth so runtime writes
2906
+ * and CDK provisioning agree on attribute names. Composite sk indexes
2907
+ * (sk.length > 1) get a dedicated STRING `{indexName}Sk` attribute; single-field
2908
+ * sk indexes reference the field directly (STRING in the general case, NUMBER
2909
+ * for the legacy `sequence` name).
2910
2910
  */
2911
2911
  function indexesToGsi(indexes) {
2912
2912
  return indexes.map((index) => {
2913
- // Generate index name from pk fields if not provided
2914
- const indexName = index.name ?? generateIndexName(index.pk);
2915
- // Sort key defaults to ["sequence"] if not provided
2916
- const skFields = index.sk ?? DEFAULT_SORT_KEY;
2913
+ const { pk, sk } = getGsiAttributeNames(index);
2914
+ let sortKey;
2915
+ if (sk) {
2916
+ if (sk === "sequence") {
2917
+ sortKey = { name: "sequence", type: dynamodb.AttributeType.NUMBER };
2918
+ }
2919
+ else {
2920
+ sortKey = { name: sk, type: dynamodb.AttributeType.STRING };
2921
+ }
2922
+ }
2917
2923
  return {
2918
- indexName,
2924
+ indexName: pk,
2919
2925
  partitionKey: {
2920
- name: indexName,
2926
+ name: pk,
2921
2927
  type: dynamodb.AttributeType.STRING,
2922
2928
  },
2923
2929
  projectionType: dynamodb.ProjectionType.ALL,
2924
- sortKey: skFields.length === 1 && skFields[0] === "sequence"
2925
- ? { name: "sequence", type: dynamodb.AttributeType.NUMBER }
2926
- : {
2927
- name: skFields.join(SEPARATOR),
2928
- type: dynamodb.AttributeType.STRING,
2929
- },
2930
+ ...(sortKey && { sortKey }),
2930
2931
  };
2931
2932
  });
2932
2933
  }
@@ -2938,8 +2939,7 @@ function indexesToGsi(indexes) {
2938
2939
  * DynamoDB table with Jaypie single-table design patterns.
2939
2940
  *
2940
2941
  * Creates a table with:
2941
- * - Partition key: `model` (String)
2942
- * - Sort key: `id` (String)
2942
+ * - Partition key: `id` (String), no sort key
2943
2943
  * - Billing: PAY_PER_REQUEST (on-demand)
2944
2944
  * - Removal policy: RETAIN in production, DESTROY otherwise
2945
2945
  * - No GSIs by default (use `indexes` prop to add them)
@@ -2950,13 +2950,11 @@ function indexesToGsi(indexes) {
2950
2950
  * const table = new JaypieDynamoDb(this, "myApp");
2951
2951
  *
2952
2952
  * @example
2953
- * // With explicit table name (overrides CDK-generated name)
2953
+ * // With fabricIndex() for GSIs
2954
+ * import { fabricIndex } from "@jaypie/fabric";
2954
2955
  * const table = new JaypieDynamoDb(this, "MyTable", {
2955
2956
  * tableName: "custom-table-name",
2956
- * indexes: [
2957
- * { pk: ["scope", "model"] },
2958
- * { pk: ["scope", "model", "type"], sparse: true },
2959
- * ],
2957
+ * indexes: [fabricIndex(), fabricIndex("alias"), fabricIndex("xid")],
2960
2958
  * });
2961
2959
  */
2962
2960
  class JaypieDynamoDb extends Construct {
@@ -2968,11 +2966,11 @@ class JaypieDynamoDb extends Construct {
2968
2966
  const constructId = isShorthand ? `JaypieDynamoDb-${id}` : id;
2969
2967
  super(scope, constructId);
2970
2968
  const { billing = dynamodb.Billing.onDemand(), indexes, partitionKey = {
2971
- name: "model",
2969
+ name: "id",
2972
2970
  type: dynamodb.AttributeType.STRING,
2973
2971
  }, project, removalPolicy = isProductionEnv()
2974
2972
  ? RemovalPolicy.RETAIN
2975
- : RemovalPolicy.DESTROY, roleTag = CDK$2.ROLE.STORAGE, service, sortKey = { name: "id", type: dynamodb.AttributeType.STRING }, vendorTag, ...restProps } = props;
2973
+ : RemovalPolicy.DESTROY, roleTag = CDK$2.ROLE.STORAGE, service, sortKey, vendorTag, ...restProps } = props;
2976
2974
  // Convert IndexDefinition[] to CDK GSI props
2977
2975
  const globalSecondaryIndexes = indexes ? indexesToGsi(indexes) : undefined;
2978
2976
  this._table = new dynamodb.TableV2(this, "Table", {
@@ -2980,7 +2978,7 @@ class JaypieDynamoDb extends Construct {
2980
2978
  globalSecondaryIndexes,
2981
2979
  partitionKey,
2982
2980
  removalPolicy,
2983
- sortKey,
2981
+ ...(sortKey && { sortKey }),
2984
2982
  ...restProps,
2985
2983
  });
2986
2984
  // Apply tags