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.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
|