dynamodb-reactive 0.1.22 → 0.2.1

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.
Files changed (46) hide show
  1. package/README.md +378 -153
  2. package/dist/client-r_tnzwqi.d.ts +224 -0
  3. package/dist/client.cjs +1023 -0
  4. package/dist/client.cjs.map +1 -0
  5. package/dist/client.d.cts +6 -0
  6. package/dist/client.d.ts +4 -70
  7. package/dist/client.js +1013 -1
  8. package/dist/client.js.map +1 -1
  9. package/dist/core.cjs +232 -0
  10. package/dist/core.cjs.map +1 -0
  11. package/dist/core.d.cts +4 -0
  12. package/dist/core.d.ts +3 -3
  13. package/dist/core.js +222 -1
  14. package/dist/core.js.map +1 -1
  15. package/dist/index.cjs +232 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.cts +157 -0
  18. package/dist/index.d.ts +103 -56
  19. package/dist/index.js +222 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/infra.cjs +623 -0
  22. package/dist/infra.cjs.map +1 -0
  23. package/dist/infra.d.cts +119 -0
  24. package/dist/infra.d.ts +50 -102
  25. package/dist/infra.js +371 -144
  26. package/dist/infra.js.map +1 -1
  27. package/dist/react.cjs +1250 -0
  28. package/dist/react.cjs.map +1 -0
  29. package/dist/react.d.cts +68 -0
  30. package/dist/react.d.ts +68 -3
  31. package/dist/react.js +1237 -1
  32. package/dist/react.js.map +1 -1
  33. package/dist/server.cjs +2351 -0
  34. package/dist/server.cjs.map +1 -0
  35. package/dist/server.d.cts +364 -0
  36. package/dist/server.d.ts +58 -313
  37. package/dist/server.js +1855 -1208
  38. package/dist/server.js.map +1 -1
  39. package/dist/{table-tN3D4Tm-.d.ts → table-BijJ73pX.d.ts} +2 -34
  40. package/dist/{types-ISMJfyIv.d.ts → types-LMLk8JVC.d.ts} +56 -30
  41. package/package.json +79 -41
  42. package/dist/chunk-2KC44Q5I.js +0 -1103
  43. package/dist/chunk-2KC44Q5I.js.map +0 -1
  44. package/dist/chunk-ZAJA4KBE.js +0 -150
  45. package/dist/chunk-ZAJA4KBE.js.map +0 -1
  46. package/dist/react-D_NLHuuO.d.ts +0 -556
package/dist/infra.cjs ADDED
@@ -0,0 +1,623 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var cdk2 = require('aws-cdk-lib');
6
+ var apigatewayv2 = require('aws-cdk-lib/aws-apigatewayv2');
7
+ var apigatewayv2Integrations = require('aws-cdk-lib/aws-apigatewayv2-integrations');
8
+ var dynamodb = require('aws-cdk-lib/aws-dynamodb');
9
+ var iam = require('aws-cdk-lib/aws-iam');
10
+ var lambda = require('aws-cdk-lib/aws-lambda');
11
+ var nodejs = require('aws-cdk-lib/aws-lambda-nodejs');
12
+ var logs = require('aws-cdk-lib/aws-logs');
13
+ var constructs = require('constructs');
14
+ var zod = require('zod');
15
+
16
+ function _interopNamespace(e) {
17
+ if (e && e.__esModule) return e;
18
+ var n = Object.create(null);
19
+ if (e) {
20
+ Object.keys(e).forEach(function (k) {
21
+ if (k !== 'default') {
22
+ var d = Object.getOwnPropertyDescriptor(e, k);
23
+ Object.defineProperty(n, k, d.get ? d : {
24
+ enumerable: true,
25
+ get: function () { return e[k]; }
26
+ });
27
+ }
28
+ });
29
+ }
30
+ n.default = e;
31
+ return Object.freeze(n);
32
+ }
33
+
34
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
35
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
36
+ var cdk2__namespace = /*#__PURE__*/_interopNamespace(cdk2);
37
+ var apigatewayv2__namespace = /*#__PURE__*/_interopNamespace(apigatewayv2);
38
+ var apigatewayv2Integrations__namespace = /*#__PURE__*/_interopNamespace(apigatewayv2Integrations);
39
+ var dynamodb__namespace = /*#__PURE__*/_interopNamespace(dynamodb);
40
+ var iam__namespace = /*#__PURE__*/_interopNamespace(iam);
41
+ var lambda__namespace = /*#__PURE__*/_interopNamespace(lambda);
42
+ var nodejs__namespace = /*#__PURE__*/_interopNamespace(nodejs);
43
+ var logs__namespace = /*#__PURE__*/_interopNamespace(logs);
44
+
45
+ // ../infra/src/engine.ts
46
+
47
+ // ../infra/src/handlers/lambda-handlers.ts
48
+ function generateEntryPointCode() {
49
+ return `// Auto-generated entry point for reactive Lambda handlers
50
+ // No user code required - all configuration comes from environment variables
51
+ import { createLambdaHandlers } from 'dynamodb-reactive/server';
52
+
53
+ const handlers = createLambdaHandlers();
54
+
55
+ export const connectHandler = handlers.connectHandler;
56
+ export const disconnectHandler = handlers.disconnectHandler;
57
+ export const messageHandler = handlers.messageHandler;
58
+ export const streamHandler = handlers.streamHandler;
59
+ `;
60
+ }
61
+ var FilterConditionSchema = zod.z.lazy(() => {
62
+ const comparison = zod.z.object({
63
+ type: zod.z.literal("comparison"),
64
+ operator: zod.z.enum(["eq", "ne", "gt", "gte", "lt", "lte"]),
65
+ field: zod.z.string().min(1),
66
+ value: zod.z.unknown()
67
+ }).strict();
68
+ const queryFunction = zod.z.union([
69
+ zod.z.object({
70
+ type: zod.z.literal("function"),
71
+ operator: zod.z.literal("between"),
72
+ field: zod.z.string().min(1),
73
+ value: zod.z.unknown(),
74
+ value2: zod.z.unknown()
75
+ }).strict(),
76
+ zod.z.object({
77
+ type: zod.z.literal("function"),
78
+ operator: zod.z.enum(["beginsWith", "contains"]),
79
+ field: zod.z.string().min(1),
80
+ value: zod.z.unknown()
81
+ }).strict()
82
+ ]);
83
+ const logical = zod.z.object({
84
+ type: zod.z.literal("logical"),
85
+ operator: zod.z.enum(["and", "or", "not"]),
86
+ conditions: zod.z.array(FilterConditionSchema).min(1)
87
+ }).strict().superRefine((condition, context) => {
88
+ if (condition.operator === "not" && condition.conditions.length !== 1) {
89
+ context.addIssue({
90
+ code: "custom",
91
+ path: ["conditions"],
92
+ message: "not requires exactly one condition"
93
+ });
94
+ }
95
+ });
96
+ return zod.z.union([comparison, queryFunction, logical]);
97
+ });
98
+ var QueryMetadataSchema = zod.z.object({
99
+ planVersion: zod.z.literal(1),
100
+ tableName: zod.z.string().min(1),
101
+ indexName: zod.z.string().min(1).optional(),
102
+ indexPkField: zod.z.string().min(1).optional(),
103
+ indexSkField: zod.z.string().min(1).optional(),
104
+ filterConditions: zod.z.array(FilterConditionSchema),
105
+ sortField: zod.z.string().min(1).optional(),
106
+ sortOrder: zod.z.enum(["asc", "desc"]).optional(),
107
+ limit: zod.z.number().int().positive().optional()
108
+ }).strict().superRefine((metadata, context) => {
109
+ if (metadata.indexName && !metadata.indexPkField) {
110
+ context.addIssue({
111
+ code: "custom",
112
+ path: ["indexPkField"],
113
+ message: "Index metadata requires its partition key field"
114
+ });
115
+ }
116
+ if (!metadata.indexName && (metadata.indexPkField || metadata.indexSkField)) {
117
+ context.addIssue({
118
+ code: "custom",
119
+ path: ["indexName"],
120
+ message: "Index key fields require an index name"
121
+ });
122
+ }
123
+ if (Boolean(metadata.sortField) !== Boolean(metadata.sortOrder)) {
124
+ context.addIssue({
125
+ code: "custom",
126
+ path: ["sortOrder"],
127
+ message: "Sort field and order must be specified together"
128
+ });
129
+ }
130
+ });
131
+ var DeliveryVersionSchema = zod.z.object({
132
+ generation: zod.z.number().int().nonnegative(),
133
+ fromVersion: zod.z.number().int().nonnegative(),
134
+ toVersion: zod.z.number().int().positive(),
135
+ messageId: zod.z.string().min(1)
136
+ });
137
+ var PendingDeliverySchema = zod.z.discriminatedUnion("type", [
138
+ DeliveryVersionSchema.extend({
139
+ type: zod.z.literal("patch"),
140
+ patches: zod.z.array(
141
+ zod.z.object({
142
+ op: zod.z.enum(["add", "remove", "replace", "move", "copy", "test"]),
143
+ path: zod.z.string(),
144
+ value: zod.z.unknown().optional(),
145
+ from: zod.z.string().optional()
146
+ })
147
+ )
148
+ }),
149
+ DeliveryVersionSchema.extend({
150
+ type: zod.z.literal("resync")
151
+ }).strict()
152
+ ]);
153
+ ({
154
+ /**
155
+ * ReactiveConnectionsV2 - Tracks active WebSocket connections
156
+ */
157
+ connections: zod.z.object({
158
+ connectionId: zod.z.string(),
159
+ status: zod.z.enum(["open", "closed"]),
160
+ context: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
161
+ authorizerContext: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
162
+ requestIdentity: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
163
+ connectedAt: zod.z.number(),
164
+ ttl: zod.z.number()
165
+ }),
166
+ /**
167
+ * ReactiveDependenciesV2 - The inverted index for O(1) lookups
168
+ */
169
+ dependencies: zod.z.object({
170
+ pk: zod.z.string(),
171
+ // Versioned encoded dependency key
172
+ sk: zod.z.string(),
173
+ // ConnectionID#SubscriptionID
174
+ connectionId: zod.z.string(),
175
+ subscriptionId: zod.z.string(),
176
+ tableName: zod.z.string(),
177
+ fieldName: zod.z.string(),
178
+ fieldValue: zod.z.string(),
179
+ generation: zod.z.number().int().nonnegative(),
180
+ registrationId: zod.z.string().min(1),
181
+ ttl: zod.z.number()
182
+ }),
183
+ /**
184
+ * ReactiveConnectionQueriesV2 - Stores subscription state for diffing
185
+ */
186
+ queries: zod.z.object({
187
+ pk: zod.z.string(),
188
+ // ConnectionID
189
+ sk: zod.z.string(),
190
+ // SubscriptionID
191
+ connectionId: zod.z.string(),
192
+ subscriptionId: zod.z.string(),
193
+ status: zod.z.enum(["registering", "active", "cancelled", "faulted"]),
194
+ generation: zod.z.number().int().nonnegative(),
195
+ registrationId: zod.z.string().min(1),
196
+ queryMetadata: QueryMetadataSchema,
197
+ lastResult: zod.z.array(zod.z.unknown()),
198
+ dependencies: zod.z.array(zod.z.string()),
199
+ version: zod.z.number().int().nonnegative(),
200
+ pendingDelivery: PendingDeliverySchema.optional(),
201
+ createdAt: zod.z.number(),
202
+ updatedAt: zod.z.number(),
203
+ ttl: zod.z.number()
204
+ })
205
+ });
206
+ var SystemTableNames = {
207
+ connections: "ReactiveConnectionsV2",
208
+ dependencies: "ReactiveDependenciesV2",
209
+ queries: "ReactiveConnectionQueriesV2"
210
+ };
211
+ var ReactiveSystemTables = class extends constructs.Construct {
212
+ /**
213
+ * ReactiveConnections table - tracks WebSocket connections
214
+ */
215
+ connectionsTable;
216
+ /**
217
+ * ReactiveDependencies table - the inverted index
218
+ */
219
+ dependenciesTable;
220
+ /**
221
+ * ReactiveConnectionQueries table - stores subscription state
222
+ */
223
+ queriesTable;
224
+ constructor(scope, id, props) {
225
+ super(scope, id);
226
+ if (cdk2__namespace.Token.isUnresolved(props.tablePrefix) || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(props.tablePrefix) || props.tablePrefix.length > 229) {
227
+ throw new Error(
228
+ "ReactiveSystemTables tablePrefix must be a nonempty concrete value of at most 229 letters, numbers, hyphens, or underscores, starting with a letter or number."
229
+ );
230
+ }
231
+ const tableName = (name) => `${props.tablePrefix}-${name}`;
232
+ const provisioningMode = props.provisioningMode ?? "ON_DEMAND";
233
+ const removalPolicy = props.removalPolicy ?? cdk2__namespace.RemovalPolicy.RETAIN;
234
+ const isOnDemand = provisioningMode === "ON_DEMAND";
235
+ const capacityProps = isOnDemand ? { billingMode: dynamodb__namespace.BillingMode.PAY_PER_REQUEST } : {
236
+ billingMode: dynamodb__namespace.BillingMode.PROVISIONED,
237
+ readCapacity: provisioningMode.readWrite,
238
+ writeCapacity: provisioningMode.readWrite
239
+ };
240
+ this.connectionsTable = new dynamodb__namespace.Table(this, "ConnectionsTable", {
241
+ tableName: tableName(SystemTableNames.connections),
242
+ partitionKey: {
243
+ name: "connectionId",
244
+ type: dynamodb__namespace.AttributeType.STRING
245
+ },
246
+ ...capacityProps,
247
+ removalPolicy,
248
+ timeToLiveAttribute: "ttl"
249
+ });
250
+ this.dependenciesTable = new dynamodb__namespace.Table(this, "DependenciesTable", {
251
+ tableName: tableName(SystemTableNames.dependencies),
252
+ partitionKey: {
253
+ name: "pk",
254
+ // Format: "TableName#FieldName#FieldValue"
255
+ type: dynamodb__namespace.AttributeType.STRING
256
+ },
257
+ sortKey: {
258
+ name: "sk",
259
+ // Format: "ConnectionID#SubscriptionID"
260
+ type: dynamodb__namespace.AttributeType.STRING
261
+ },
262
+ ...capacityProps,
263
+ removalPolicy,
264
+ timeToLiveAttribute: "ttl"
265
+ });
266
+ const gsiProps = isOnDemand ? {} : {
267
+ readCapacity: provisioningMode.readWrite,
268
+ writeCapacity: provisioningMode.readWrite
269
+ };
270
+ this.dependenciesTable.addGlobalSecondaryIndex({
271
+ indexName: "byConnectionId",
272
+ partitionKey: {
273
+ name: "connectionId",
274
+ type: dynamodb__namespace.AttributeType.STRING
275
+ },
276
+ projectionType: dynamodb__namespace.ProjectionType.KEYS_ONLY,
277
+ ...gsiProps
278
+ });
279
+ this.queriesTable = new dynamodb__namespace.Table(this, "QueriesTable", {
280
+ tableName: tableName(SystemTableNames.queries),
281
+ partitionKey: {
282
+ name: "pk",
283
+ // ConnectionID
284
+ type: dynamodb__namespace.AttributeType.STRING
285
+ },
286
+ sortKey: {
287
+ name: "sk",
288
+ // SubscriptionID
289
+ type: dynamodb__namespace.AttributeType.STRING
290
+ },
291
+ ...capacityProps,
292
+ removalPolicy,
293
+ timeToLiveAttribute: "ttl"
294
+ });
295
+ }
296
+ };
297
+
298
+ // ../infra/src/engine.ts
299
+ var PARTIQL_ACTIONS = [
300
+ "dynamodb:PartiQLInsert",
301
+ "dynamodb:PartiQLUpdate",
302
+ "dynamodb:PartiQLDelete"
303
+ ];
304
+ var ReactiveEngine = class extends constructs.Construct {
305
+ tables;
306
+ systemTables;
307
+ webSocketApi;
308
+ webSocketStage;
309
+ connectHandler;
310
+ disconnectHandler;
311
+ messageHandler;
312
+ streamHandler;
313
+ webSocketUrl;
314
+ callbackUrl;
315
+ constructor(scope, id, props) {
316
+ super(scope, id);
317
+ if (cdk2__namespace.Token.isUnresolved(props.resourcePrefix) || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(props.resourcePrefix) || props.resourcePrefix.length > 45) {
318
+ throw new Error(
319
+ "ReactiveEngine resourcePrefix must be a nonempty concrete value of at most 45 letters, numbers, hyphens, or underscores, starting with a letter or number."
320
+ );
321
+ }
322
+ if (props.connectAuthorizer && props.allowUnauthenticatedConnections) {
323
+ throw new Error(
324
+ "Specify either connectAuthorizer or allowUnauthenticatedConnections, not both."
325
+ );
326
+ }
327
+ if (props.connectAuthorizer instanceof apigatewayv2__namespace.WebSocketNoneAuthorizer) {
328
+ throw new Error(
329
+ "WebSocketNoneAuthorizer does not authenticate connections. Set allowUnauthenticatedConnections to true instead."
330
+ );
331
+ }
332
+ if (!props.connectAuthorizer && !props.allowUnauthenticatedConnections) {
333
+ throw new Error(
334
+ "WebSocket authentication is required. Provide connectAuthorizer or explicitly set allowUnauthenticatedConnections to true."
335
+ );
336
+ }
337
+ for (const binding of props.tables) {
338
+ this.validateTableName(binding);
339
+ this.validateStream(binding);
340
+ }
341
+ this.tables = [...props.tables];
342
+ const resourceName = (name) => `${props.resourcePrefix}-${name}`;
343
+ const memorySize = 128;
344
+ const timeout = cdk2__namespace.Duration.seconds(30);
345
+ this.systemTables = new ReactiveSystemTables(this, "SystemTables", {
346
+ ...props.systemTablesProps,
347
+ tablePrefix: props.resourcePrefix
348
+ });
349
+ this.webSocketApi = new apigatewayv2__namespace.WebSocketApi(this, "WebSocketApi", {
350
+ apiName: resourceName("ReactiveWebSocket")
351
+ });
352
+ this.webSocketStage = new apigatewayv2__namespace.WebSocketStage(
353
+ this,
354
+ "WebSocketStage",
355
+ {
356
+ webSocketApi: this.webSocketApi,
357
+ stageName: "prod",
358
+ autoDeploy: true
359
+ }
360
+ );
361
+ this.webSocketUrl = this.webSocketStage.url;
362
+ this.callbackUrl = this.webSocketStage.callbackUrl;
363
+ const cdkOutDir = path__namespace.join(process.cwd(), "cdk.out", ".generated");
364
+ fs__namespace.mkdirSync(cdkOutDir, { recursive: true });
365
+ const entryPointPath = path__namespace.join(cdkOutDir, "reactive-entry.ts");
366
+ fs__namespace.writeFileSync(entryPointPath, generateEntryPointCode());
367
+ const environment = {
368
+ ...props.environment,
369
+ CONNECTIONS_TABLE: this.systemTables.connectionsTable.tableName,
370
+ DEPENDENCIES_TABLE: this.systemTables.dependenciesTable.tableName,
371
+ QUERIES_TABLE: this.systemTables.queriesTable.tableName,
372
+ WEBSOCKET_ENDPOINT: this.callbackUrl,
373
+ LOG_LEVEL: props.environment?.LOG_LEVEL ?? "INFO"
374
+ };
375
+ const bundling = {
376
+ format: nodejs__namespace.OutputFormat.CJS,
377
+ minify: true,
378
+ sourceMap: false,
379
+ target: "node24"
380
+ };
381
+ this.connectHandler = new nodejs__namespace.NodejsFunction(this, "ConnectHandler", {
382
+ functionName: resourceName("ReactiveConnect"),
383
+ runtime: lambda__namespace.Runtime.NODEJS_24_X,
384
+ handler: "connectHandler",
385
+ entry: entryPointPath,
386
+ bundling,
387
+ environment,
388
+ memorySize,
389
+ timeout,
390
+ logGroup: this.createLogGroup("ConnectLogGroup")
391
+ });
392
+ this.disconnectHandler = new nodejs__namespace.NodejsFunction(
393
+ this,
394
+ "DisconnectHandler",
395
+ {
396
+ functionName: resourceName("ReactiveDisconnect"),
397
+ runtime: lambda__namespace.Runtime.NODEJS_24_X,
398
+ handler: "disconnectHandler",
399
+ entry: entryPointPath,
400
+ bundling,
401
+ environment,
402
+ memorySize,
403
+ timeout,
404
+ logGroup: this.createLogGroup("DisconnectLogGroup")
405
+ }
406
+ );
407
+ this.messageHandler = new nodejs__namespace.NodejsFunction(this, "MessageHandler", {
408
+ functionName: resourceName("ReactiveMessage"),
409
+ runtime: lambda__namespace.Runtime.NODEJS_24_X,
410
+ handler: "messageHandler",
411
+ entry: entryPointPath,
412
+ bundling,
413
+ environment,
414
+ memorySize,
415
+ timeout,
416
+ logGroup: this.createLogGroup("MessageLogGroup")
417
+ });
418
+ this.streamHandler = new nodejs__namespace.NodejsFunction(this, "StreamHandler", {
419
+ functionName: resourceName("ReactiveStream"),
420
+ runtime: lambda__namespace.Runtime.NODEJS_24_X,
421
+ handler: "streamHandler",
422
+ entry: entryPointPath,
423
+ bundling,
424
+ environment: {
425
+ ...environment,
426
+ USER_TABLES: props.tables.map(({ definition }) => definition.tableName).join(",")
427
+ },
428
+ memorySize,
429
+ timeout,
430
+ logGroup: this.createLogGroup("StreamLogGroup")
431
+ });
432
+ iam__namespace.Grant.addToPrincipal({
433
+ grantee: this.connectHandler,
434
+ actions: ["dynamodb:PutItem"],
435
+ resourceArns: [this.systemTables.connectionsTable.tableArn]
436
+ });
437
+ iam__namespace.Grant.addToPrincipal({
438
+ grantee: this.disconnectHandler,
439
+ actions: ["dynamodb:UpdateItem"],
440
+ resourceArns: [this.systemTables.connectionsTable.tableArn]
441
+ });
442
+ iam__namespace.Grant.addToPrincipal({
443
+ grantee: this.disconnectHandler,
444
+ actions: ["dynamodb:Query", "dynamodb:UpdateItem"],
445
+ resourceArns: [this.systemTables.queriesTable.tableArn]
446
+ });
447
+ iam__namespace.Grant.addToPrincipal({
448
+ grantee: this.disconnectHandler,
449
+ actions: ["dynamodb:DeleteItem"],
450
+ resourceArns: [this.systemTables.dependenciesTable.tableArn]
451
+ });
452
+ iam__namespace.Grant.addToPrincipal({
453
+ grantee: this.messageHandler,
454
+ actions: ["dynamodb:UpdateItem"],
455
+ resourceArns: [this.systemTables.connectionsTable.tableArn]
456
+ });
457
+ iam__namespace.Grant.addToPrincipal({
458
+ grantee: this.streamHandler,
459
+ actions: ["dynamodb:UpdateItem"],
460
+ resourceArns: [this.systemTables.connectionsTable.tableArn]
461
+ });
462
+ iam__namespace.Grant.addToPrincipal({
463
+ grantee: this.streamHandler,
464
+ actions: ["dynamodb:Query", "dynamodb:DeleteItem"],
465
+ resourceArns: [this.systemTables.dependenciesTable.tableArn]
466
+ });
467
+ iam__namespace.Grant.addToPrincipal({
468
+ grantee: this.streamHandler,
469
+ actions: [
470
+ "dynamodb:GetItem",
471
+ "dynamodb:Query",
472
+ "dynamodb:UpdateItem"
473
+ ],
474
+ resourceArns: [this.systemTables.queriesTable.tableArn]
475
+ });
476
+ const managementPolicy = new iam__namespace.PolicyStatement({
477
+ actions: ["execute-api:ManageConnections"],
478
+ resources: [
479
+ cdk2__namespace.Stack.of(this).formatArn({
480
+ service: "execute-api",
481
+ resource: this.webSocketApi.apiId,
482
+ resourceName: `${this.webSocketStage.stageName}/POST/@connections/*`,
483
+ arnFormat: cdk2__namespace.ArnFormat.SLASH_RESOURCE_NAME
484
+ })
485
+ ]
486
+ });
487
+ this.messageHandler.addToRolePolicy(managementPolicy);
488
+ this.streamHandler.addToRolePolicy(managementPolicy);
489
+ const connectAuthorizer = props.connectAuthorizer ? {
490
+ bind: (options) => {
491
+ const config = props.connectAuthorizer.bind(options);
492
+ if (config.authorizationType === "NONE") {
493
+ throw new Error(
494
+ "The connectAuthorizer bind result uses NONE authorization. Set allowUnauthenticatedConnections to true instead."
495
+ );
496
+ }
497
+ return config;
498
+ }
499
+ } : void 0;
500
+ this.webSocketApi.addRoute("$connect", {
501
+ authorizer: connectAuthorizer,
502
+ integration: new apigatewayv2Integrations__namespace.WebSocketLambdaIntegration(
503
+ "ConnectIntegration",
504
+ this.connectHandler
505
+ )
506
+ });
507
+ this.webSocketApi.addRoute("$disconnect", {
508
+ integration: new apigatewayv2Integrations__namespace.WebSocketLambdaIntegration(
509
+ "DisconnectIntegration",
510
+ this.disconnectHandler
511
+ )
512
+ });
513
+ this.webSocketApi.addRoute("$default", {
514
+ integration: new apigatewayv2Integrations__namespace.WebSocketLambdaIntegration(
515
+ "DefaultIntegration",
516
+ this.messageHandler
517
+ )
518
+ });
519
+ for (const [index, binding] of props.tables.entries()) {
520
+ this.setupStreamProcessing(binding, index);
521
+ }
522
+ new cdk2__namespace.CfnOutput(this, "WebSocketUrl", {
523
+ value: this.webSocketUrl,
524
+ description: "WebSocket URL for reactive connections"
525
+ });
526
+ }
527
+ /** Grant an external application handler access to reactive and user data. */
528
+ grantApplicationAccess(grantee) {
529
+ this.systemTables.connectionsTable.grantReadWriteData(grantee);
530
+ this.systemTables.dependenciesTable.grantReadWriteData(grantee);
531
+ this.systemTables.queriesTable.grantReadWriteData(grantee);
532
+ iam__namespace.Grant.addToPrincipal({
533
+ grantee,
534
+ actions: ["dynamodb:TransactWriteItems"],
535
+ resourceArns: [
536
+ this.systemTables.connectionsTable.tableArn,
537
+ this.systemTables.dependenciesTable.tableArn,
538
+ this.systemTables.queriesTable.tableArn
539
+ ]
540
+ });
541
+ for (const { table } of this.tables) {
542
+ table.grantReadWriteData(grantee);
543
+ iam__namespace.Grant.addToPrincipal({
544
+ grantee,
545
+ actions: ["dynamodb:PartiQLSelect"],
546
+ resourceArns: [table.tableArn, `${table.tableArn}/index/*`]
547
+ });
548
+ iam__namespace.Grant.addToPrincipal({
549
+ grantee,
550
+ actions: PARTIQL_ACTIONS,
551
+ resourceArns: [table.tableArn]
552
+ });
553
+ }
554
+ }
555
+ setupStreamProcessing(binding, index) {
556
+ const { table } = binding;
557
+ table.grantStreamRead(this.streamHandler);
558
+ iam__namespace.Grant.addToPrincipal({
559
+ grantee: this.streamHandler,
560
+ actions: ["dynamodb:PartiQLSelect"],
561
+ resourceArns: [table.tableArn, `${table.tableArn}/index/*`]
562
+ });
563
+ new lambda__namespace.EventSourceMapping(this, `UserTableStream${index}`, {
564
+ target: this.streamHandler,
565
+ eventSourceArn: table.tableStreamArn,
566
+ startingPosition: lambda__namespace.StartingPosition.TRIM_HORIZON,
567
+ batchSize: 100,
568
+ maxBatchingWindow: cdk2__namespace.Duration.seconds(5),
569
+ bisectBatchOnError: true,
570
+ reportBatchItemFailures: true
571
+ });
572
+ }
573
+ createLogGroup(id) {
574
+ return new logs__namespace.LogGroup(this, id, {
575
+ retention: logs__namespace.RetentionDays.ONE_DAY,
576
+ removalPolicy: cdk2__namespace.RemovalPolicy.DESTROY
577
+ });
578
+ }
579
+ validateStream(binding) {
580
+ const { definition, table } = binding;
581
+ if (!table.tableStreamArn) {
582
+ throw new Error(
583
+ `Table ${definition.tableName} does not have DynamoDB Streams enabled. NEW_AND_OLD_IMAGES is required.`
584
+ );
585
+ }
586
+ const child = table.node.defaultChild;
587
+ if (!(child instanceof dynamodb__namespace.CfnTable) && !(child instanceof dynamodb__namespace.CfnGlobalTable)) {
588
+ return;
589
+ }
590
+ const specification = cdk2__namespace.Stack.of(this).resolve(
591
+ child.streamSpecification
592
+ );
593
+ if (!specification || cdk2__namespace.Token.isUnresolved(specification) || typeof specification !== "object" || !("streamViewType" in specification)) {
594
+ return;
595
+ }
596
+ const streamViewType = specification.streamViewType;
597
+ if (!cdk2__namespace.Token.isUnresolved(streamViewType) && streamViewType !== dynamodb__namespace.StreamViewType.NEW_AND_OLD_IMAGES) {
598
+ throw new Error(
599
+ `Table ${definition.tableName} uses stream view ${streamViewType}; NEW_AND_OLD_IMAGES is required.`
600
+ );
601
+ }
602
+ }
603
+ validateTableName(binding) {
604
+ const { definition, table } = binding;
605
+ const child = table.node.defaultChild;
606
+ const childTableName = child && typeof child === "object" && "tableName" in child ? child.tableName : void 0;
607
+ const physicalTableName = !cdk2__namespace.Token.isUnresolved(table.tableName) ? table.tableName : childTableName && !cdk2__namespace.Token.isUnresolved(childTableName) ? childTableName : table.tableName;
608
+ if (!physicalTableName || cdk2__namespace.Token.isUnresolved(physicalTableName)) {
609
+ throw new Error(
610
+ `Table ${definition.tableName} has an unresolved physical table name. Set an explicit tableName equal to definition.tableName; separately bundled application code cannot coordinate CloudFormation-generated names.`
611
+ );
612
+ }
613
+ if (physicalTableName !== definition.tableName) {
614
+ throw new Error(
615
+ `Table binding name mismatch: definition.tableName is "${definition.tableName}" but the physical table name is "${physicalTableName}". Set an explicit matching tableName.`
616
+ );
617
+ }
618
+ }
619
+ };
620
+
621
+ exports.ReactiveEngine = ReactiveEngine;
622
+ //# sourceMappingURL=infra.cjs.map
623
+ //# sourceMappingURL=infra.cjs.map