dynamodb-reactive 0.1.22 → 0.2.2
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 +437 -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 +634 -0
- package/dist/infra.cjs.map +1 -0
- package/dist/infra.d.cts +126 -0
- package/dist/infra.d.ts +57 -102
- package/dist/infra.js +385 -147
- 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 +2382 -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 +1882 -1204
- 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,22 @@ 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
|
+
];
|
|
273
|
+
var DEFAULT_STREAM_BATCH_SIZE = 100;
|
|
274
|
+
var MAX_STREAM_BATCH_SIZE = 1e3;
|
|
110
275
|
var ReactiveEngine = class extends Construct {
|
|
276
|
+
tables;
|
|
111
277
|
systemTables;
|
|
112
278
|
webSocketApi;
|
|
113
279
|
webSocketStage;
|
|
@@ -119,16 +285,45 @@ var ReactiveEngine = class extends Construct {
|
|
|
119
285
|
callbackUrl;
|
|
120
286
|
constructor(scope, id, props) {
|
|
121
287
|
super(scope, id);
|
|
122
|
-
|
|
288
|
+
if (cdk2.Token.isUnresolved(props.resourcePrefix) || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(props.resourcePrefix) || props.resourcePrefix.length > 45) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
"ReactiveEngine resourcePrefix must be a nonempty concrete value of at most 45 letters, numbers, hyphens, or underscores, starting with a letter or number."
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if (props.connectAuthorizer && props.allowUnauthenticatedConnections) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
"Specify either connectAuthorizer or allowUnauthenticatedConnections, not both."
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
if (props.connectAuthorizer instanceof apigatewayv2.WebSocketNoneAuthorizer) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
"WebSocketNoneAuthorizer does not authenticate connections. Set allowUnauthenticatedConnections to true instead."
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
if (!props.connectAuthorizer && !props.allowUnauthenticatedConnections) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
"WebSocket authentication is required. Provide connectAuthorizer or explicitly set allowUnauthenticatedConnections to true."
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
if (props.streamBatchSize !== void 0 && (!Number.isInteger(props.streamBatchSize) || props.streamBatchSize < 1 || props.streamBatchSize > MAX_STREAM_BATCH_SIZE)) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`ReactiveEngine streamBatchSize must be an integer from 1 to ${MAX_STREAM_BATCH_SIZE}.`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
for (const binding of props.tables) {
|
|
314
|
+
this.validateTableName(binding);
|
|
315
|
+
this.validateStream(binding);
|
|
316
|
+
}
|
|
317
|
+
this.tables = [...props.tables];
|
|
318
|
+
const resourceName = (name) => `${props.resourcePrefix}-${name}`;
|
|
123
319
|
const memorySize = 128;
|
|
124
320
|
const timeout = cdk2.Duration.seconds(30);
|
|
125
|
-
const logRetention = logs.RetentionDays.ONE_DAY;
|
|
126
321
|
this.systemTables = new ReactiveSystemTables(this, "SystemTables", {
|
|
127
|
-
|
|
128
|
-
|
|
322
|
+
...props.systemTablesProps,
|
|
323
|
+
tablePrefix: props.resourcePrefix
|
|
129
324
|
});
|
|
130
325
|
this.webSocketApi = new apigatewayv2.WebSocketApi(this, "WebSocketApi", {
|
|
131
|
-
apiName:
|
|
326
|
+
apiName: resourceName("ReactiveWebSocket")
|
|
132
327
|
});
|
|
133
328
|
this.webSocketStage = new apigatewayv2.WebSocketStage(
|
|
134
329
|
this,
|
|
@@ -141,88 +336,145 @@ var ReactiveEngine = class extends Construct {
|
|
|
141
336
|
);
|
|
142
337
|
this.webSocketUrl = this.webSocketStage.url;
|
|
143
338
|
this.callbackUrl = this.webSocketStage.callbackUrl;
|
|
144
|
-
const entryPointCode = generateEntryPointCode();
|
|
145
339
|
const cdkOutDir = path.join(process.cwd(), "cdk.out", ".generated");
|
|
146
340
|
fs.mkdirSync(cdkOutDir, { recursive: true });
|
|
147
|
-
const entryPointPath = path.join(
|
|
148
|
-
|
|
149
|
-
`reactive-entry-${id}-${Date.now()}.ts`
|
|
150
|
-
);
|
|
151
|
-
fs.writeFileSync(entryPointPath, entryPointCode);
|
|
341
|
+
const entryPointPath = path.join(cdkOutDir, "reactive-entry.ts");
|
|
342
|
+
fs.writeFileSync(entryPointPath, generateEntryPointCode());
|
|
152
343
|
const environment = {
|
|
344
|
+
...props.environment,
|
|
153
345
|
CONNECTIONS_TABLE: this.systemTables.connectionsTable.tableName,
|
|
154
346
|
DEPENDENCIES_TABLE: this.systemTables.dependenciesTable.tableName,
|
|
155
347
|
QUERIES_TABLE: this.systemTables.queriesTable.tableName,
|
|
156
348
|
WEBSOCKET_ENDPOINT: this.callbackUrl,
|
|
157
|
-
...props.environment,
|
|
158
349
|
LOG_LEVEL: props.environment?.LOG_LEVEL ?? "INFO"
|
|
159
350
|
};
|
|
351
|
+
const bundling = {
|
|
352
|
+
format: nodejs.OutputFormat.CJS,
|
|
353
|
+
minify: true,
|
|
354
|
+
sourceMap: false,
|
|
355
|
+
target: "node24"
|
|
356
|
+
};
|
|
160
357
|
this.connectHandler = new nodejs.NodejsFunction(this, "ConnectHandler", {
|
|
161
|
-
functionName:
|
|
162
|
-
runtime: lambda.Runtime.
|
|
358
|
+
functionName: resourceName("ReactiveConnect"),
|
|
359
|
+
runtime: lambda.Runtime.NODEJS_24_X,
|
|
163
360
|
handler: "connectHandler",
|
|
164
361
|
entry: entryPointPath,
|
|
362
|
+
bundling,
|
|
165
363
|
environment,
|
|
166
364
|
memorySize,
|
|
167
365
|
timeout,
|
|
168
|
-
|
|
366
|
+
logGroup: this.createLogGroup("ConnectLogGroup")
|
|
169
367
|
});
|
|
170
368
|
this.disconnectHandler = new nodejs.NodejsFunction(
|
|
171
369
|
this,
|
|
172
370
|
"DisconnectHandler",
|
|
173
371
|
{
|
|
174
|
-
functionName:
|
|
175
|
-
runtime: lambda.Runtime.
|
|
372
|
+
functionName: resourceName("ReactiveDisconnect"),
|
|
373
|
+
runtime: lambda.Runtime.NODEJS_24_X,
|
|
176
374
|
handler: "disconnectHandler",
|
|
177
375
|
entry: entryPointPath,
|
|
376
|
+
bundling,
|
|
178
377
|
environment,
|
|
179
378
|
memorySize,
|
|
180
379
|
timeout,
|
|
181
|
-
|
|
380
|
+
logGroup: this.createLogGroup("DisconnectLogGroup")
|
|
182
381
|
}
|
|
183
382
|
);
|
|
184
383
|
this.messageHandler = new nodejs.NodejsFunction(this, "MessageHandler", {
|
|
185
|
-
functionName:
|
|
186
|
-
runtime: lambda.Runtime.
|
|
384
|
+
functionName: resourceName("ReactiveMessage"),
|
|
385
|
+
runtime: lambda.Runtime.NODEJS_24_X,
|
|
187
386
|
handler: "messageHandler",
|
|
188
387
|
entry: entryPointPath,
|
|
388
|
+
bundling,
|
|
189
389
|
environment,
|
|
190
390
|
memorySize,
|
|
191
391
|
timeout,
|
|
192
|
-
|
|
392
|
+
logGroup: this.createLogGroup("MessageLogGroup")
|
|
193
393
|
});
|
|
194
394
|
this.streamHandler = new nodejs.NodejsFunction(this, "StreamHandler", {
|
|
195
|
-
functionName:
|
|
196
|
-
runtime: lambda.Runtime.
|
|
395
|
+
functionName: resourceName("ReactiveStream"),
|
|
396
|
+
runtime: lambda.Runtime.NODEJS_24_X,
|
|
197
397
|
handler: "streamHandler",
|
|
198
398
|
entry: entryPointPath,
|
|
399
|
+
bundling,
|
|
199
400
|
environment: {
|
|
200
401
|
...environment,
|
|
201
|
-
USER_TABLES: props.tables.map((
|
|
402
|
+
USER_TABLES: props.tables.map(({ definition }) => definition.tableName).join(",")
|
|
202
403
|
},
|
|
203
|
-
memorySize,
|
|
204
|
-
timeout,
|
|
205
|
-
|
|
404
|
+
memorySize: props.streamHandlerProps?.memorySize ?? memorySize,
|
|
405
|
+
timeout: props.streamHandlerProps?.timeout ?? timeout,
|
|
406
|
+
logGroup: this.createLogGroup("StreamLogGroup")
|
|
407
|
+
});
|
|
408
|
+
iam.Grant.addToPrincipal({
|
|
409
|
+
grantee: this.connectHandler,
|
|
410
|
+
actions: ["dynamodb:PutItem"],
|
|
411
|
+
resourceArns: [this.systemTables.connectionsTable.tableArn]
|
|
412
|
+
});
|
|
413
|
+
iam.Grant.addToPrincipal({
|
|
414
|
+
grantee: this.disconnectHandler,
|
|
415
|
+
actions: ["dynamodb:UpdateItem"],
|
|
416
|
+
resourceArns: [this.systemTables.connectionsTable.tableArn]
|
|
417
|
+
});
|
|
418
|
+
iam.Grant.addToPrincipal({
|
|
419
|
+
grantee: this.disconnectHandler,
|
|
420
|
+
actions: ["dynamodb:Query", "dynamodb:UpdateItem"],
|
|
421
|
+
resourceArns: [this.systemTables.queriesTable.tableArn]
|
|
422
|
+
});
|
|
423
|
+
iam.Grant.addToPrincipal({
|
|
424
|
+
grantee: this.disconnectHandler,
|
|
425
|
+
actions: ["dynamodb:DeleteItem"],
|
|
426
|
+
resourceArns: [this.systemTables.dependenciesTable.tableArn]
|
|
427
|
+
});
|
|
428
|
+
iam.Grant.addToPrincipal({
|
|
429
|
+
grantee: this.messageHandler,
|
|
430
|
+
actions: ["dynamodb:UpdateItem"],
|
|
431
|
+
resourceArns: [this.systemTables.connectionsTable.tableArn]
|
|
432
|
+
});
|
|
433
|
+
iam.Grant.addToPrincipal({
|
|
434
|
+
grantee: this.streamHandler,
|
|
435
|
+
actions: ["dynamodb:UpdateItem"],
|
|
436
|
+
resourceArns: [this.systemTables.connectionsTable.tableArn]
|
|
437
|
+
});
|
|
438
|
+
iam.Grant.addToPrincipal({
|
|
439
|
+
grantee: this.streamHandler,
|
|
440
|
+
actions: ["dynamodb:Query", "dynamodb:DeleteItem"],
|
|
441
|
+
resourceArns: [this.systemTables.dependenciesTable.tableArn]
|
|
442
|
+
});
|
|
443
|
+
iam.Grant.addToPrincipal({
|
|
444
|
+
grantee: this.streamHandler,
|
|
445
|
+
actions: [
|
|
446
|
+
"dynamodb:GetItem",
|
|
447
|
+
"dynamodb:Query",
|
|
448
|
+
"dynamodb:UpdateItem"
|
|
449
|
+
],
|
|
450
|
+
resourceArns: [this.systemTables.queriesTable.tableArn]
|
|
206
451
|
});
|
|
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
452
|
const managementPolicy = new iam.PolicyStatement({
|
|
218
453
|
actions: ["execute-api:ManageConnections"],
|
|
219
454
|
resources: [
|
|
220
|
-
|
|
455
|
+
cdk2.Stack.of(this).formatArn({
|
|
456
|
+
service: "execute-api",
|
|
457
|
+
resource: this.webSocketApi.apiId,
|
|
458
|
+
resourceName: `${this.webSocketStage.stageName}/POST/@connections/*`,
|
|
459
|
+
arnFormat: cdk2.ArnFormat.SLASH_RESOURCE_NAME
|
|
460
|
+
})
|
|
221
461
|
]
|
|
222
462
|
});
|
|
223
463
|
this.messageHandler.addToRolePolicy(managementPolicy);
|
|
224
464
|
this.streamHandler.addToRolePolicy(managementPolicy);
|
|
465
|
+
const connectAuthorizer = props.connectAuthorizer ? {
|
|
466
|
+
bind: (options) => {
|
|
467
|
+
const config = props.connectAuthorizer.bind(options);
|
|
468
|
+
if (config.authorizationType === "NONE") {
|
|
469
|
+
throw new Error(
|
|
470
|
+
"The connectAuthorizer bind result uses NONE authorization. Set allowUnauthenticatedConnections to true instead."
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
return config;
|
|
474
|
+
}
|
|
475
|
+
} : void 0;
|
|
225
476
|
this.webSocketApi.addRoute("$connect", {
|
|
477
|
+
authorizer: connectAuthorizer,
|
|
226
478
|
integration: new apigatewayv2Integrations.WebSocketLambdaIntegration(
|
|
227
479
|
"ConnectIntegration",
|
|
228
480
|
this.connectHandler
|
|
@@ -240,126 +492,112 @@ var ReactiveEngine = class extends Construct {
|
|
|
240
492
|
this.messageHandler
|
|
241
493
|
)
|
|
242
494
|
});
|
|
243
|
-
for (const
|
|
244
|
-
this.setupStreamProcessing(
|
|
495
|
+
for (const [index, binding] of props.tables.entries()) {
|
|
496
|
+
this.setupStreamProcessing(
|
|
497
|
+
binding,
|
|
498
|
+
index,
|
|
499
|
+
props.streamBatchSize ?? DEFAULT_STREAM_BATCH_SIZE
|
|
500
|
+
);
|
|
245
501
|
}
|
|
246
502
|
new cdk2.CfnOutput(this, "WebSocketUrl", {
|
|
247
503
|
value: this.webSocketUrl,
|
|
248
504
|
description: "WebSocket URL for reactive connections"
|
|
249
505
|
});
|
|
250
506
|
}
|
|
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/*`]
|
|
507
|
+
/** Grant an external application handler access to reactive and user data. */
|
|
508
|
+
grantApplicationAccess(grantee) {
|
|
509
|
+
this.systemTables.connectionsTable.grantReadWriteData(grantee);
|
|
510
|
+
this.systemTables.dependenciesTable.grantReadWriteData(grantee);
|
|
511
|
+
this.systemTables.queriesTable.grantReadWriteData(grantee);
|
|
512
|
+
iam.Grant.addToPrincipal({
|
|
513
|
+
grantee,
|
|
514
|
+
actions: ["dynamodb:TransactWriteItems"],
|
|
515
|
+
resourceArns: [
|
|
516
|
+
this.systemTables.connectionsTable.tableArn,
|
|
517
|
+
this.systemTables.dependenciesTable.tableArn,
|
|
518
|
+
this.systemTables.queriesTable.tableArn
|
|
519
|
+
]
|
|
274
520
|
});
|
|
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`);
|
|
521
|
+
for (const { table } of this.tables) {
|
|
522
|
+
table.grantReadWriteData(grantee);
|
|
523
|
+
iam.Grant.addToPrincipal({
|
|
524
|
+
grantee,
|
|
525
|
+
actions: ["dynamodb:PartiQLSelect"],
|
|
526
|
+
resourceArns: [table.tableArn, `${table.tableArn}/index/*`]
|
|
527
|
+
});
|
|
528
|
+
iam.Grant.addToPrincipal({
|
|
529
|
+
grantee,
|
|
530
|
+
actions: PARTIQL_ACTIONS,
|
|
531
|
+
resourceArns: [table.tableArn]
|
|
532
|
+
});
|
|
295
533
|
}
|
|
534
|
+
}
|
|
535
|
+
setupStreamProcessing(binding, index, batchSize) {
|
|
536
|
+
const { table } = binding;
|
|
296
537
|
table.grantStreamRead(this.streamHandler);
|
|
297
|
-
|
|
298
|
-
|
|
538
|
+
iam.Grant.addToPrincipal({
|
|
539
|
+
grantee: this.streamHandler,
|
|
540
|
+
actions: ["dynamodb:PartiQLSelect"],
|
|
541
|
+
resourceArns: [table.tableArn, `${table.tableArn}/index/*`]
|
|
542
|
+
});
|
|
543
|
+
new lambda.EventSourceMapping(this, `UserTableStream${index}`, {
|
|
299
544
|
target: this.streamHandler,
|
|
300
545
|
eventSourceArn: table.tableStreamArn,
|
|
301
546
|
startingPosition: lambda.StartingPosition.TRIM_HORIZON,
|
|
302
|
-
batchSize
|
|
303
|
-
maxBatchingWindow: cdk2.Duration.seconds(5)
|
|
547
|
+
batchSize,
|
|
548
|
+
maxBatchingWindow: cdk2.Duration.seconds(5),
|
|
549
|
+
bisectBatchOnError: true,
|
|
550
|
+
reportBatchItemFailures: true
|
|
304
551
|
});
|
|
305
552
|
}
|
|
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;
|
|
553
|
+
createLogGroup(id) {
|
|
554
|
+
return new logs.LogGroup(this, id, {
|
|
555
|
+
retention: logs.RetentionDays.ONE_DAY,
|
|
556
|
+
removalPolicy: cdk2.RemovalPolicy.DESTROY
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
validateStream(binding) {
|
|
560
|
+
const { definition, table } = binding;
|
|
322
561
|
if (!table.tableStreamArn) {
|
|
323
562
|
throw new Error(
|
|
324
|
-
`Table ${
|
|
563
|
+
`Table ${definition.tableName} does not have DynamoDB Streams enabled. NEW_AND_OLD_IMAGES is required.`
|
|
325
564
|
);
|
|
326
565
|
}
|
|
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
|
-
}
|
|
566
|
+
const child = table.node.defaultChild;
|
|
567
|
+
if (!(child instanceof dynamodb.CfnTable) && !(child instanceof dynamodb.CfnGlobalTable)) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const specification = cdk2.Stack.of(this).resolve(
|
|
571
|
+
child.streamSpecification
|
|
344
572
|
);
|
|
573
|
+
if (!specification || cdk2.Token.isUnresolved(specification) || typeof specification !== "object" || !("streamViewType" in specification)) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const streamViewType = specification.streamViewType;
|
|
577
|
+
if (!cdk2.Token.isUnresolved(streamViewType) && streamViewType !== dynamodb.StreamViewType.NEW_AND_OLD_IMAGES) {
|
|
578
|
+
throw new Error(
|
|
579
|
+
`Table ${definition.tableName} uses stream view ${streamViewType}; NEW_AND_OLD_IMAGES is required.`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
345
582
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
583
|
+
validateTableName(binding) {
|
|
584
|
+
const { definition, table } = binding;
|
|
585
|
+
const child = table.node.defaultChild;
|
|
586
|
+
const childTableName = child && typeof child === "object" && "tableName" in child ? child.tableName : void 0;
|
|
587
|
+
const physicalTableName = !cdk2.Token.isUnresolved(table.tableName) ? table.tableName : childTableName && !cdk2.Token.isUnresolved(childTableName) ? childTableName : table.tableName;
|
|
588
|
+
if (!physicalTableName || cdk2.Token.isUnresolved(physicalTableName)) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
`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.`
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
if (physicalTableName !== definition.tableName) {
|
|
594
|
+
throw new Error(
|
|
595
|
+
`Table binding name mismatch: definition.tableName is "${definition.tableName}" but the physical table name is "${physicalTableName}". Set an explicit matching tableName.`
|
|
596
|
+
);
|
|
597
|
+
}
|
|
359
598
|
}
|
|
360
|
-
|
|
361
|
-
}
|
|
599
|
+
};
|
|
362
600
|
|
|
363
|
-
export {
|
|
601
|
+
export { ReactiveEngine };
|
|
364
602
|
//# sourceMappingURL=infra.js.map
|
|
365
603
|
//# sourceMappingURL=infra.js.map
|