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