@jaypie/constructs 1.2.18 → 1.2.20

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.
@@ -29,4 +29,7 @@ export { JaypieStack, JaypieStackProps } from "./JaypieStack";
29
29
  export { JaypieStaticWebBucket, JaypieStaticWebBucketProps, } from "./JaypieStaticWebBucket";
30
30
  export { JaypieTraceSigningKeySecret } from "./JaypieTraceSigningKeySecret";
31
31
  export { JaypieWebDeploymentBucket } from "./JaypieWebDeploymentBucket";
32
+ export { JaypieWebSocket, JaypieWebSocketProps } from "./JaypieWebSocket";
33
+ export { JaypieWebSocketLambda } from "./JaypieWebSocketLambda";
34
+ export { JaypieWebSocketTable, JaypieWebSocketTableProps, } from "./JaypieWebSocketTable";
32
35
  export * from "./helpers";
@@ -93,7 +93,7 @@ export declare class JaypieDynamoDb extends Construct implements dynamodb.ITable
93
93
  * Default Jaypie GSI definitions from @jaypie/fabric.
94
94
  * Pass to `indexes` prop to create all standard GSIs.
95
95
  */
96
- static readonly DEFAULT_INDEXES: any;
96
+ static readonly DEFAULT_INDEXES: IndexDefinition[];
97
97
  private readonly _table;
98
98
  constructor(scope: Construct, id: string, props?: JaypieDynamoDbProps);
99
99
  /**
@@ -0,0 +1,115 @@
1
+ import { Construct } from "constructs";
2
+ import * as acm from "aws-cdk-lib/aws-certificatemanager";
3
+ import * as apigatewayv2 from "aws-cdk-lib/aws-apigatewayv2";
4
+ import * as iam from "aws-cdk-lib/aws-iam";
5
+ import * as lambda from "aws-cdk-lib/aws-lambda";
6
+ import * as logs from "aws-cdk-lib/aws-logs";
7
+ import * as route53 from "aws-cdk-lib/aws-route53";
8
+ import { HostConfig } from "./helpers";
9
+ export interface JaypieWebSocketProps {
10
+ /**
11
+ * Certificate configuration.
12
+ * - true: Create certificate at stack level (default, reusable)
13
+ * - false: No certificate (use regional endpoint)
14
+ * - ICertificate: Use provided certificate
15
+ * - string: Import certificate from ARN
16
+ */
17
+ certificate?: boolean | acm.ICertificate | string;
18
+ /**
19
+ * Lambda handler for $connect route (connection established).
20
+ * Use this to validate connections (e.g., auth tokens) and store connection IDs.
21
+ */
22
+ connect?: lambda.IFunction;
23
+ /**
24
+ * Lambda handler for $default route (catches unmatched messages).
25
+ * Use this as the main message handler.
26
+ */
27
+ default?: lambda.IFunction;
28
+ /**
29
+ * Lambda handler for $disconnect route (connection closed).
30
+ * Use this to clean up connection IDs from storage.
31
+ */
32
+ disconnect?: lambda.IFunction;
33
+ /**
34
+ * Single Lambda handler for all routes.
35
+ * Alternative to providing separate connect/disconnect/default handlers.
36
+ * The handler receives routeKey in the context to determine which route was invoked.
37
+ */
38
+ handler?: lambda.IFunction;
39
+ /**
40
+ * The domain name for the WebSocket API.
41
+ *
42
+ * Supports both string and config object:
43
+ * - String: used directly as the domain name (e.g., "ws.example.com")
44
+ * - Object: passed to envHostname() to construct the domain name
45
+ * - { subdomain, domain, env, component }
46
+ *
47
+ * @example
48
+ * // Direct string
49
+ * host: "ws.example.com"
50
+ *
51
+ * @example
52
+ * // Config object - resolves using envHostname()
53
+ * host: { component: "ws" }
54
+ */
55
+ host?: string | HostConfig;
56
+ /**
57
+ * Log retention for WebSocket API access logs.
58
+ * @default logs.RetentionDays.THREE_MONTHS
59
+ */
60
+ logRetention?: logs.RetentionDays;
61
+ /**
62
+ * Construct name (used for resource naming).
63
+ */
64
+ name?: string;
65
+ /**
66
+ * Role tag for tagging resources.
67
+ * @default CDK.ROLE.API
68
+ */
69
+ roleTag?: string;
70
+ /**
71
+ * Additional named routes beyond $connect, $disconnect, and $default.
72
+ * Keys are route keys (e.g., "sendMessage", "subscribe").
73
+ */
74
+ routes?: Record<string, lambda.IFunction>;
75
+ /**
76
+ * Stage name for the WebSocket API.
77
+ * @default "production"
78
+ */
79
+ stageName?: string;
80
+ /**
81
+ * Route53 hosted zone for DNS records.
82
+ * - string: Zone domain name (looked up or imported)
83
+ * - IHostedZone: Use provided hosted zone
84
+ */
85
+ zone?: string | route53.IHostedZone;
86
+ }
87
+ export declare class JaypieWebSocket extends Construct {
88
+ private readonly _api;
89
+ private readonly _certificate?;
90
+ private readonly _domainName?;
91
+ private readonly _host?;
92
+ private readonly _stage;
93
+ constructor(scope: Construct, id: string, props?: JaypieWebSocketProps);
94
+ get api(): apigatewayv2.WebSocketApi;
95
+ get apiId(): string;
96
+ get certificate(): acm.ICertificate | undefined;
97
+ get domainName(): string | undefined;
98
+ /**
99
+ * The WebSocket endpoint URL.
100
+ * Uses custom domain if configured, otherwise returns the default stage URL.
101
+ */
102
+ get endpoint(): string;
103
+ get host(): string | undefined;
104
+ get stage(): apigatewayv2.WebSocketStage;
105
+ /**
106
+ * The callback URL for API Gateway Management API.
107
+ * Use this URL to send messages to connected clients.
108
+ */
109
+ get callbackUrl(): string;
110
+ /**
111
+ * Grant a Lambda function permission to manage WebSocket connections
112
+ * (post to connections, delete connections).
113
+ */
114
+ grantManageConnections(grantee: lambda.IFunction): iam.Grant;
115
+ }
@@ -0,0 +1,26 @@
1
+ import { Construct } from "constructs";
2
+ import { JaypieLambda, JaypieLambdaProps } from "./JaypieLambda.js";
3
+ /**
4
+ * JaypieWebSocketLambda - A Lambda function optimized for WebSocket handlers.
5
+ *
6
+ * Provides sensible defaults for WebSocket event handling:
7
+ * - 30 second timeout (same as API handlers)
8
+ * - API role tag
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const handler = new JaypieWebSocketLambda(this, "ChatHandler", {
13
+ * code: "dist/handlers",
14
+ * handler: "chat.handler",
15
+ * secrets: ["MONGODB_URI"],
16
+ * });
17
+ *
18
+ * new JaypieWebSocket(this, "Chat", {
19
+ * host: "ws.example.com",
20
+ * handler,
21
+ * });
22
+ * ```
23
+ */
24
+ export declare class JaypieWebSocketLambda extends JaypieLambda {
25
+ constructor(scope: Construct, id: string, props: JaypieLambdaProps);
26
+ }
@@ -0,0 +1,100 @@
1
+ import { Construct } from "constructs";
2
+ import { Duration } from "aws-cdk-lib";
3
+ import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
4
+ import * as iam from "aws-cdk-lib/aws-iam";
5
+ import * as lambda from "aws-cdk-lib/aws-lambda";
6
+ export interface JaypieWebSocketTableProps {
7
+ /**
8
+ * Explicit table name. If not provided, uses CDK-generated name.
9
+ */
10
+ tableName?: string;
11
+ /**
12
+ * Time-to-live duration for connections.
13
+ * Connections will be automatically deleted after this duration.
14
+ * @default Duration.hours(24)
15
+ */
16
+ ttl?: Duration;
17
+ /**
18
+ * Whether to create a GSI for looking up connections by user ID.
19
+ * @default false
20
+ */
21
+ userIndex?: boolean;
22
+ /**
23
+ * Role tag for tagging resources.
24
+ * @default CDK.ROLE.STORAGE
25
+ */
26
+ roleTag?: string;
27
+ }
28
+ /**
29
+ * JaypieWebSocketTable - DynamoDB table for storing WebSocket connection IDs.
30
+ *
31
+ * Provides a simple table structure for tracking active WebSocket connections:
32
+ * - Partition key: connectionId (String)
33
+ * - TTL attribute: expiresAt (for automatic cleanup)
34
+ * - Optional GSI: userId-index (for looking up connections by user)
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const connectionTable = new JaypieWebSocketTable(this, "Connections");
39
+ *
40
+ * const ws = new JaypieWebSocket(this, "Chat", {
41
+ * host: "ws.example.com",
42
+ * handler: chatHandler,
43
+ * });
44
+ *
45
+ * // Grant Lambda access to the table
46
+ * connectionTable.grantReadWriteData(chatHandler);
47
+ *
48
+ * // Pass table name to Lambda
49
+ * chatHandler.addEnvironment("CONNECTION_TABLE", connectionTable.tableName);
50
+ * ```
51
+ *
52
+ * @example
53
+ * // With user index for looking up all connections for a user
54
+ * const connectionTable = new JaypieWebSocketTable(this, "Connections", {
55
+ * userIndex: true,
56
+ * ttl: Duration.hours(12),
57
+ * });
58
+ */
59
+ export declare class JaypieWebSocketTable extends Construct {
60
+ private readonly _table;
61
+ private readonly _ttlDuration;
62
+ constructor(scope: Construct, id: string, props?: JaypieWebSocketTableProps);
63
+ /**
64
+ * The underlying DynamoDB TableV2 construct.
65
+ */
66
+ get table(): dynamodb.TableV2;
67
+ /**
68
+ * The name of the DynamoDB table.
69
+ */
70
+ get tableName(): string;
71
+ /**
72
+ * The ARN of the DynamoDB table.
73
+ */
74
+ get tableArn(): string;
75
+ /**
76
+ * TTL duration for connections in seconds.
77
+ * Use this to calculate expiresAt when storing connections.
78
+ */
79
+ get ttlSeconds(): number;
80
+ /**
81
+ * Grant read permissions to the table.
82
+ */
83
+ grantReadData(grantee: iam.IGrantable): iam.Grant;
84
+ /**
85
+ * Grant write permissions to the table.
86
+ */
87
+ grantWriteData(grantee: iam.IGrantable): iam.Grant;
88
+ /**
89
+ * Grant read and write permissions to the table.
90
+ */
91
+ grantReadWriteData(grantee: iam.IGrantable): iam.Grant;
92
+ /**
93
+ * Add the table name to a Lambda function's environment variables.
94
+ * Also grants read/write access to the table.
95
+ */
96
+ connectLambda(lambdaFunction: lambda.IFunction, options?: {
97
+ envKey?: string;
98
+ readOnly?: boolean;
99
+ }): void;
100
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -29,4 +29,7 @@ export { JaypieStack, JaypieStackProps } from "./JaypieStack";
29
29
  export { JaypieStaticWebBucket, JaypieStaticWebBucketProps, } from "./JaypieStaticWebBucket";
30
30
  export { JaypieTraceSigningKeySecret } from "./JaypieTraceSigningKeySecret";
31
31
  export { JaypieWebDeploymentBucket } from "./JaypieWebDeploymentBucket";
32
+ export { JaypieWebSocket, JaypieWebSocketProps } from "./JaypieWebSocket";
33
+ export { JaypieWebSocketLambda } from "./JaypieWebSocketLambda";
34
+ export { JaypieWebSocketTable, JaypieWebSocketTableProps, } from "./JaypieWebSocketTable";
32
35
  export * from "./helpers";
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as cdk from 'aws-cdk-lib';
2
- import { Tags, Stack, Fn, CfnOutput, SecretValue, Duration, RemovalPolicy, CfnStack } from 'aws-cdk-lib';
2
+ import { Tags, Stack, Fn, CfnOutput, SecretValue, Duration, RemovalPolicy, CfnStack, ArnFormat } from 'aws-cdk-lib';
3
3
  import * as s3 from 'aws-cdk-lib/aws-s3';
4
4
  import { Bucket, StorageClass, BucketAccessControl, EventType } from 'aws-cdk-lib/aws-s3';
5
5
  import { Construct } from 'constructs';
@@ -10,6 +10,7 @@ import * as route53Targets from 'aws-cdk-lib/aws-route53-targets';
10
10
  import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
11
11
  import { DatadogLambda } from 'datadog-cdk-constructs-v2';
12
12
  import { ConfigurationError } from '@jaypie/errors';
13
+ import * as iam from 'aws-cdk-lib/aws-iam';
13
14
  import { Role, PolicyStatement, Policy, FederatedPrincipal, Effect, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
14
15
  import * as acm from 'aws-cdk-lib/aws-certificatemanager';
15
16
  import * as lambda from 'aws-cdk-lib/aws-lambda';
@@ -31,6 +32,8 @@ import * as path from 'path';
31
32
  import { Trail, ReadWriteType } from 'aws-cdk-lib/aws-cloudtrail';
32
33
  import { CfnPermissionSet, CfnAssignment } from 'aws-cdk-lib/aws-sso';
33
34
  import { CfnApplication } from 'aws-cdk-lib/aws-sam';
35
+ import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
36
+ import * as apigatewayv2Integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
34
37
 
35
38
  const CDK$2 = {
36
39
  ACCOUNT: {
@@ -4118,5 +4121,376 @@ class JaypieTraceSigningKeySecret extends JaypieEnvSecret {
4118
4121
  }
4119
4122
  }
4120
4123
 
4121
- export { CDK$2 as CDK, JaypieAccountLoggingBucket, JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieCertificate, JaypieDatadogBucket, JaypieDatadogForwarder, JaypieDatadogSecret, JaypieDistribution, JaypieDnsRecord, JaypieDynamoDb, JaypieEnvSecret, JaypieEventsRule, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMongoDbSecret, JaypieNextJs, JaypieOpenAiSecret, JaypieOrganizationTrail, JaypieQueuedLambda, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieStaticWebBucket, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, addDatadogLayers, clearAllCertificateCaches, clearAllSecretsCaches, clearCertificateCache, clearSecretsCache, constructEnvName, constructStackName, constructTagger, envHostname, extendDatadogRole, isEnv, isProductionEnv, isSandboxEnv, isValidHostname$1 as isValidHostname, isValidSubdomain, jaypieLambdaEnv, mergeDomain, resolveCertificate, resolveDatadogForwarderFunction, resolveDatadogLayers, resolveDatadogLoggingDestination, resolveEnvironment, resolveHostedZone, resolveParamsAndSecrets, resolveSecrets };
4124
+ //
4125
+ //
4126
+ // Main
4127
+ //
4128
+ class JaypieWebSocket extends Construct {
4129
+ constructor(scope, id, props = {}) {
4130
+ super(scope, id);
4131
+ const { certificate = true, connect, default: defaultHandler, disconnect, handler, host: propsHost, logRetention = logs.RetentionDays.THREE_MONTHS, name, roleTag = CDK$2.ROLE.API, routes = {}, stageName = "production", zone: propsZone, } = props;
4132
+ // Validate: either handler OR individual handlers, not both
4133
+ const hasIndividualHandlers = connect || disconnect || defaultHandler;
4134
+ if (handler && hasIndividualHandlers) {
4135
+ throw new Error("Cannot specify both 'handler' and individual route handlers (connect/disconnect/default)");
4136
+ }
4137
+ // Determine zone from props or environment
4138
+ let zone = propsZone;
4139
+ if (!zone && process.env.CDK_ENV_HOSTED_ZONE) {
4140
+ zone = process.env.CDK_ENV_HOSTED_ZONE;
4141
+ }
4142
+ // Determine host from props or environment
4143
+ let host;
4144
+ if (typeof propsHost === "string") {
4145
+ host = propsHost;
4146
+ }
4147
+ else if (typeof propsHost === "object") {
4148
+ // Resolve host from HostConfig using envHostname()
4149
+ host = envHostname(propsHost);
4150
+ }
4151
+ else if (process.env.CDK_ENV_WS_HOST_NAME) {
4152
+ host = process.env.CDK_ENV_WS_HOST_NAME;
4153
+ }
4154
+ else if (process.env.CDK_ENV_WS_SUBDOMAIN &&
4155
+ process.env.CDK_ENV_HOSTED_ZONE) {
4156
+ host = mergeDomain(process.env.CDK_ENV_WS_SUBDOMAIN, process.env.CDK_ENV_HOSTED_ZONE);
4157
+ }
4158
+ const apiName = name || constructEnvName("WebSocket");
4159
+ // Create WebSocket API
4160
+ this._api = new apigatewayv2.WebSocketApi(this, "Api", {
4161
+ apiName,
4162
+ });
4163
+ Tags.of(this._api).add(CDK$2.TAG.ROLE, roleTag);
4164
+ // Add routes with Lambda integrations
4165
+ const connectHandler = handler || connect;
4166
+ const disconnectHandler = handler || disconnect;
4167
+ const defaultRouteHandler = handler || defaultHandler;
4168
+ if (connectHandler) {
4169
+ this._api.addRoute("$connect", {
4170
+ integration: new apigatewayv2Integrations.WebSocketLambdaIntegration("ConnectIntegration", connectHandler),
4171
+ });
4172
+ }
4173
+ if (disconnectHandler) {
4174
+ this._api.addRoute("$disconnect", {
4175
+ integration: new apigatewayv2Integrations.WebSocketLambdaIntegration("DisconnectIntegration", disconnectHandler),
4176
+ });
4177
+ }
4178
+ if (defaultRouteHandler) {
4179
+ this._api.addRoute("$default", {
4180
+ integration: new apigatewayv2Integrations.WebSocketLambdaIntegration("DefaultIntegration", defaultRouteHandler),
4181
+ });
4182
+ }
4183
+ // Add custom routes
4184
+ for (const [routeKey, routeHandler] of Object.entries(routes)) {
4185
+ this._api.addRoute(routeKey, {
4186
+ integration: new apigatewayv2Integrations.WebSocketLambdaIntegration(`${routeKey}Integration`, routeHandler),
4187
+ });
4188
+ }
4189
+ // Create log group for access logs
4190
+ // Note: logGroup is created for future use when API Gateway v2 WebSocket
4191
+ // access logging is fully supported in CDK
4192
+ new logs.LogGroup(this, "AccessLogs", {
4193
+ removalPolicy: RemovalPolicy.DESTROY,
4194
+ retention: logRetention,
4195
+ });
4196
+ // Create stage
4197
+ this._stage = new apigatewayv2.WebSocketStage(this, "Stage", {
4198
+ autoDeploy: true,
4199
+ stageName,
4200
+ webSocketApi: this._api,
4201
+ });
4202
+ Tags.of(this._stage).add(CDK$2.TAG.ROLE, roleTag);
4203
+ // Set up custom domain if host and zone are provided
4204
+ let hostedZone;
4205
+ let certificateToUse;
4206
+ if (host && zone) {
4207
+ hostedZone = resolveHostedZone(this, { zone });
4208
+ // Use resolveCertificate to create certificate at stack level (enables reuse)
4209
+ certificateToUse = resolveCertificate(this, {
4210
+ certificate,
4211
+ domainName: host,
4212
+ roleTag: CDK$2.ROLE.HOSTING,
4213
+ zone: hostedZone,
4214
+ });
4215
+ this._certificate = certificateToUse;
4216
+ this._host = host;
4217
+ if (certificateToUse) {
4218
+ // Create custom domain
4219
+ this._domainName = new apigatewayv2.DomainName(this, "DomainName", {
4220
+ certificate: certificateToUse,
4221
+ domainName: host,
4222
+ });
4223
+ Tags.of(this._domainName).add(CDK$2.TAG.ROLE, roleTag);
4224
+ // Map domain to stage
4225
+ new apigatewayv2.ApiMapping(this, "ApiMapping", {
4226
+ api: this._api,
4227
+ domainName: this._domainName,
4228
+ stage: this._stage,
4229
+ });
4230
+ // Create DNS record
4231
+ new route53.ARecord(this, "AliasRecord", {
4232
+ recordName: host,
4233
+ target: route53.RecordTarget.fromAlias(new route53Targets.ApiGatewayv2DomainProperties(this._domainName.regionalDomainName, this._domainName.regionalHostedZoneId)),
4234
+ zone: hostedZone,
4235
+ });
4236
+ // Also create AAAA record for IPv6
4237
+ new route53.AaaaRecord(this, "AaaaAliasRecord", {
4238
+ recordName: host,
4239
+ target: route53.RecordTarget.fromAlias(new route53Targets.ApiGatewayv2DomainProperties(this._domainName.regionalDomainName, this._domainName.regionalHostedZoneId)),
4240
+ zone: hostedZone,
4241
+ });
4242
+ }
4243
+ }
4244
+ // Grant all handlers permission to manage connections
4245
+ const allHandlers = new Set();
4246
+ if (connectHandler)
4247
+ allHandlers.add(connectHandler);
4248
+ if (disconnectHandler)
4249
+ allHandlers.add(disconnectHandler);
4250
+ if (defaultRouteHandler)
4251
+ allHandlers.add(defaultRouteHandler);
4252
+ Object.values(routes).forEach((h) => allHandlers.add(h));
4253
+ for (const lambdaHandler of allHandlers) {
4254
+ this.grantManageConnections(lambdaHandler);
4255
+ }
4256
+ }
4257
+ //
4258
+ //
4259
+ // Public accessors
4260
+ //
4261
+ get api() {
4262
+ return this._api;
4263
+ }
4264
+ get apiId() {
4265
+ return this._api.apiId;
4266
+ }
4267
+ get certificate() {
4268
+ return this._certificate;
4269
+ }
4270
+ get domainName() {
4271
+ return this._domainName?.name;
4272
+ }
4273
+ /**
4274
+ * The WebSocket endpoint URL.
4275
+ * Uses custom domain if configured, otherwise returns the default stage URL.
4276
+ */
4277
+ get endpoint() {
4278
+ if (this._host) {
4279
+ return `wss://${this._host}`;
4280
+ }
4281
+ return this._stage.url;
4282
+ }
4283
+ get host() {
4284
+ return this._host;
4285
+ }
4286
+ get stage() {
4287
+ return this._stage;
4288
+ }
4289
+ /**
4290
+ * The callback URL for API Gateway Management API.
4291
+ * Use this URL to send messages to connected clients.
4292
+ */
4293
+ get callbackUrl() {
4294
+ if (this._host) {
4295
+ return `https://${this._host}`;
4296
+ }
4297
+ // Extract callback URL from stage URL
4298
+ // Stage URL: wss://abc123.execute-api.us-east-1.amazonaws.com/production
4299
+ // Callback URL: https://abc123.execute-api.us-east-1.amazonaws.com/production
4300
+ return this._stage.url.replace("wss://", "https://");
4301
+ }
4302
+ //
4303
+ //
4304
+ // Public methods
4305
+ //
4306
+ /**
4307
+ * Grant a Lambda function permission to manage WebSocket connections
4308
+ * (post to connections, delete connections).
4309
+ */
4310
+ grantManageConnections(grantee) {
4311
+ return iam.Grant.addToPrincipal({
4312
+ actions: ["execute-api:ManageConnections"],
4313
+ grantee: grantee.grantPrincipal,
4314
+ resourceArns: [
4315
+ Stack.of(this).formatArn({
4316
+ arnFormat: ArnFormat.SLASH_RESOURCE_SLASH_RESOURCE_NAME,
4317
+ resource: this._api.apiId,
4318
+ resourceName: `${this._stage.stageName}/POST/@connections/*`,
4319
+ service: "execute-api",
4320
+ }),
4321
+ ],
4322
+ });
4323
+ }
4324
+ }
4325
+
4326
+ /**
4327
+ * JaypieWebSocketLambda - A Lambda function optimized for WebSocket handlers.
4328
+ *
4329
+ * Provides sensible defaults for WebSocket event handling:
4330
+ * - 30 second timeout (same as API handlers)
4331
+ * - API role tag
4332
+ *
4333
+ * @example
4334
+ * ```typescript
4335
+ * const handler = new JaypieWebSocketLambda(this, "ChatHandler", {
4336
+ * code: "dist/handlers",
4337
+ * handler: "chat.handler",
4338
+ * secrets: ["MONGODB_URI"],
4339
+ * });
4340
+ *
4341
+ * new JaypieWebSocket(this, "Chat", {
4342
+ * host: "ws.example.com",
4343
+ * handler,
4344
+ * });
4345
+ * ```
4346
+ */
4347
+ class JaypieWebSocketLambda extends JaypieLambda {
4348
+ constructor(scope, id, props) {
4349
+ super(scope, id, {
4350
+ roleTag: CDK$2.ROLE.API,
4351
+ timeout: Duration.seconds(CDK$2.DURATION.EXPRESS_API),
4352
+ ...props,
4353
+ });
4354
+ }
4355
+ }
4356
+
4357
+ //
4358
+ //
4359
+ // Main
4360
+ //
4361
+ /**
4362
+ * JaypieWebSocketTable - DynamoDB table for storing WebSocket connection IDs.
4363
+ *
4364
+ * Provides a simple table structure for tracking active WebSocket connections:
4365
+ * - Partition key: connectionId (String)
4366
+ * - TTL attribute: expiresAt (for automatic cleanup)
4367
+ * - Optional GSI: userId-index (for looking up connections by user)
4368
+ *
4369
+ * @example
4370
+ * ```typescript
4371
+ * const connectionTable = new JaypieWebSocketTable(this, "Connections");
4372
+ *
4373
+ * const ws = new JaypieWebSocket(this, "Chat", {
4374
+ * host: "ws.example.com",
4375
+ * handler: chatHandler,
4376
+ * });
4377
+ *
4378
+ * // Grant Lambda access to the table
4379
+ * connectionTable.grantReadWriteData(chatHandler);
4380
+ *
4381
+ * // Pass table name to Lambda
4382
+ * chatHandler.addEnvironment("CONNECTION_TABLE", connectionTable.tableName);
4383
+ * ```
4384
+ *
4385
+ * @example
4386
+ * // With user index for looking up all connections for a user
4387
+ * const connectionTable = new JaypieWebSocketTable(this, "Connections", {
4388
+ * userIndex: true,
4389
+ * ttl: Duration.hours(12),
4390
+ * });
4391
+ */
4392
+ class JaypieWebSocketTable extends Construct {
4393
+ constructor(scope, id, props = {}) {
4394
+ super(scope, id);
4395
+ const { roleTag = CDK$2.ROLE.STORAGE, tableName, ttl = Duration.hours(24), userIndex = false, } = props;
4396
+ this._ttlDuration = ttl;
4397
+ // Build global secondary indexes
4398
+ const globalSecondaryIndexes = [];
4399
+ if (userIndex) {
4400
+ globalSecondaryIndexes.push({
4401
+ indexName: "userId-index",
4402
+ partitionKey: { name: "userId", type: dynamodb.AttributeType.STRING },
4403
+ sortKey: { name: "connectedAt", type: dynamodb.AttributeType.STRING },
4404
+ });
4405
+ }
4406
+ // Create the table
4407
+ this._table = new dynamodb.TableV2(this, "Table", {
4408
+ billing: dynamodb.Billing.onDemand(),
4409
+ globalSecondaryIndexes: globalSecondaryIndexes.length > 0 ? globalSecondaryIndexes : undefined,
4410
+ partitionKey: {
4411
+ name: "connectionId",
4412
+ type: dynamodb.AttributeType.STRING,
4413
+ },
4414
+ removalPolicy: RemovalPolicy.DESTROY,
4415
+ tableName: tableName || constructEnvName("WebSocketConnections"),
4416
+ timeToLiveAttribute: "expiresAt",
4417
+ });
4418
+ Tags.of(this._table).add(CDK$2.TAG.ROLE, roleTag);
4419
+ }
4420
+ //
4421
+ //
4422
+ // Public accessors
4423
+ //
4424
+ /**
4425
+ * The underlying DynamoDB TableV2 construct.
4426
+ */
4427
+ get table() {
4428
+ return this._table;
4429
+ }
4430
+ /**
4431
+ * The name of the DynamoDB table.
4432
+ */
4433
+ get tableName() {
4434
+ return this._table.tableName;
4435
+ }
4436
+ /**
4437
+ * The ARN of the DynamoDB table.
4438
+ */
4439
+ get tableArn() {
4440
+ return this._table.tableArn;
4441
+ }
4442
+ /**
4443
+ * TTL duration for connections in seconds.
4444
+ * Use this to calculate expiresAt when storing connections.
4445
+ */
4446
+ get ttlSeconds() {
4447
+ return this._ttlDuration.toSeconds();
4448
+ }
4449
+ //
4450
+ //
4451
+ // Grant methods
4452
+ //
4453
+ /**
4454
+ * Grant read permissions to the table.
4455
+ */
4456
+ grantReadData(grantee) {
4457
+ return this._table.grantReadData(grantee);
4458
+ }
4459
+ /**
4460
+ * Grant write permissions to the table.
4461
+ */
4462
+ grantWriteData(grantee) {
4463
+ return this._table.grantWriteData(grantee);
4464
+ }
4465
+ /**
4466
+ * Grant read and write permissions to the table.
4467
+ */
4468
+ grantReadWriteData(grantee) {
4469
+ return this._table.grantReadWriteData(grantee);
4470
+ }
4471
+ //
4472
+ //
4473
+ // Convenience methods
4474
+ //
4475
+ /**
4476
+ * Add the table name to a Lambda function's environment variables.
4477
+ * Also grants read/write access to the table.
4478
+ */
4479
+ connectLambda(lambdaFunction, options = {}) {
4480
+ const { envKey = "CONNECTION_TABLE", readOnly = false } = options;
4481
+ // Add environment variable
4482
+ if ("addEnvironment" in lambdaFunction) {
4483
+ lambdaFunction.addEnvironment(envKey, this.tableName);
4484
+ }
4485
+ // Grant permissions
4486
+ if (readOnly) {
4487
+ this.grantReadData(lambdaFunction.grantPrincipal);
4488
+ }
4489
+ else {
4490
+ this.grantReadWriteData(lambdaFunction.grantPrincipal);
4491
+ }
4492
+ }
4493
+ }
4494
+
4495
+ export { CDK$2 as CDK, JaypieAccountLoggingBucket, JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieCertificate, JaypieDatadogBucket, JaypieDatadogForwarder, JaypieDatadogSecret, JaypieDistribution, JaypieDnsRecord, JaypieDynamoDb, JaypieEnvSecret, JaypieEventsRule, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMongoDbSecret, JaypieNextJs, JaypieOpenAiSecret, JaypieOrganizationTrail, JaypieQueuedLambda, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieStaticWebBucket, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, JaypieWebSocket, JaypieWebSocketLambda, JaypieWebSocketTable, addDatadogLayers, clearAllCertificateCaches, clearAllSecretsCaches, clearCertificateCache, clearSecretsCache, constructEnvName, constructStackName, constructTagger, envHostname, extendDatadogRole, isEnv, isProductionEnv, isSandboxEnv, isValidHostname$1 as isValidHostname, isValidSubdomain, jaypieLambdaEnv, mergeDomain, resolveCertificate, resolveDatadogForwarderFunction, resolveDatadogLayers, resolveDatadogLoggingDestination, resolveEnvironment, resolveHostedZone, resolveParamsAndSecrets, resolveSecrets };
4122
4496
  //# sourceMappingURL=index.js.map