dyno-table 0.0.2 → 0.1.3
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 +679 -239
- package/dist/index.d.ts +2836 -362
- package/dist/index.js +3075 -963
- package/package.json +6 -11
package/dist/index.js
CHANGED
|
@@ -1,1177 +1,3289 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
// src/conditions.ts
|
|
2
|
+
var createComparisonCondition = (type) => (attr, value) => ({
|
|
3
|
+
type,
|
|
4
|
+
attr,
|
|
5
|
+
value
|
|
6
|
+
});
|
|
7
|
+
var eq = createComparisonCondition("eq");
|
|
8
|
+
var ne = createComparisonCondition("ne");
|
|
9
|
+
var lt = createComparisonCondition("lt");
|
|
10
|
+
var lte = createComparisonCondition("lte");
|
|
11
|
+
var gt = createComparisonCondition("gt");
|
|
12
|
+
var gte = createComparisonCondition("gte");
|
|
13
|
+
var between = (attr, lower, upper) => ({
|
|
14
|
+
type: "between",
|
|
15
|
+
attr,
|
|
16
|
+
value: [lower, upper]
|
|
17
|
+
});
|
|
18
|
+
var beginsWith = createComparisonCondition("beginsWith");
|
|
19
|
+
var contains = createComparisonCondition("contains");
|
|
20
|
+
var attributeExists = (attr) => ({
|
|
21
|
+
type: "attributeExists",
|
|
22
|
+
attr
|
|
23
|
+
});
|
|
24
|
+
var attributeNotExists = (attr) => ({
|
|
25
|
+
type: "attributeNotExists",
|
|
26
|
+
attr
|
|
27
|
+
});
|
|
28
|
+
var and = (...conditions) => ({
|
|
29
|
+
type: "and",
|
|
30
|
+
conditions
|
|
31
|
+
});
|
|
32
|
+
var or = (...conditions) => ({
|
|
33
|
+
type: "or",
|
|
34
|
+
conditions
|
|
35
|
+
});
|
|
36
|
+
var not = (condition) => ({
|
|
37
|
+
type: "not",
|
|
38
|
+
condition
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// src/expression.ts
|
|
42
|
+
var generateAttributeName = (params, attr) => {
|
|
43
|
+
for (const [existingName, existingAttr] of Object.entries(params.expressionAttributeNames)) {
|
|
44
|
+
if (existingAttr === attr) {
|
|
45
|
+
return existingName;
|
|
46
|
+
}
|
|
21
47
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
48
|
+
const attrName = `#${Object.keys(params.expressionAttributeNames).length}`;
|
|
49
|
+
params.expressionAttributeNames[attrName] = attr;
|
|
50
|
+
return attrName;
|
|
51
|
+
};
|
|
52
|
+
var generateValueName = (params, value) => {
|
|
53
|
+
const valueName = `:${params.valueCounter.count++}`;
|
|
54
|
+
params.expressionAttributeValues[valueName] = value;
|
|
55
|
+
return valueName;
|
|
56
|
+
};
|
|
57
|
+
var validateCondition = (condition, requiresAttr = true, requiresValue = true) => {
|
|
58
|
+
if (requiresAttr && !condition.attr) {
|
|
59
|
+
throw new Error(`Attribute is required for ${condition.type} condition`);
|
|
26
60
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
if (requiresValue && condition.value === void 0) {
|
|
62
|
+
throw new Error(`Value is required for ${condition.type} condition`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var buildComparisonExpression = (condition, operator, params) => {
|
|
66
|
+
validateCondition(condition);
|
|
67
|
+
if (!condition.attr) {
|
|
68
|
+
throw new Error(`Attribute is required for ${condition.type} condition`);
|
|
69
|
+
}
|
|
70
|
+
const attrName = generateAttributeName(params, condition.attr);
|
|
71
|
+
const valueName = generateValueName(params, condition.value);
|
|
72
|
+
return `${attrName} ${operator} ${valueName}`;
|
|
73
|
+
};
|
|
74
|
+
var buildBetweenExpression = (condition, params) => {
|
|
75
|
+
validateCondition(condition);
|
|
76
|
+
if (!condition.attr) {
|
|
77
|
+
throw new Error(`Attribute is required for ${condition.type} condition`);
|
|
78
|
+
}
|
|
79
|
+
if (!Array.isArray(condition.value) || condition.value.length !== 2) {
|
|
80
|
+
throw new Error("Between condition requires an array of two values");
|
|
81
|
+
}
|
|
82
|
+
const attrName = generateAttributeName(params, condition.attr);
|
|
83
|
+
const lowerName = generateValueName(params, condition.value[0]);
|
|
84
|
+
const upperName = generateValueName(params, condition.value[1]);
|
|
85
|
+
return `${attrName} BETWEEN ${lowerName} AND ${upperName}`;
|
|
86
|
+
};
|
|
87
|
+
var buildFunctionExpression = (functionName, condition, params) => {
|
|
88
|
+
validateCondition(condition);
|
|
89
|
+
if (!condition.attr) {
|
|
90
|
+
throw new Error(`Attribute is required for ${condition.type} condition`);
|
|
91
|
+
}
|
|
92
|
+
const attrName = generateAttributeName(params, condition.attr);
|
|
93
|
+
const valueName = generateValueName(params, condition.value);
|
|
94
|
+
return `${functionName}(${attrName}, ${valueName})`;
|
|
95
|
+
};
|
|
96
|
+
var buildAttributeFunction = (functionName, condition, params) => {
|
|
97
|
+
validateCondition(condition, true, false);
|
|
98
|
+
if (!condition.attr) {
|
|
99
|
+
throw new Error(`Attribute is required for ${condition.type} condition`);
|
|
100
|
+
}
|
|
101
|
+
const attrName = generateAttributeName(params, condition.attr);
|
|
102
|
+
return `${functionName}(${attrName})`;
|
|
103
|
+
};
|
|
104
|
+
var buildLogicalExpression = (operator, conditions, params) => {
|
|
105
|
+
if (!conditions || conditions.length === 0) {
|
|
106
|
+
throw new Error(`At least one condition is required for ${operator} expression`);
|
|
107
|
+
}
|
|
108
|
+
const expressions = conditions.map((c) => buildExpression(c, params));
|
|
109
|
+
return `(${expressions.join(` ${operator} `)})`;
|
|
110
|
+
};
|
|
111
|
+
var buildExpression = (condition, params) => {
|
|
112
|
+
if (!condition) return "";
|
|
113
|
+
try {
|
|
114
|
+
const expressionBuilders = {
|
|
115
|
+
eq: () => buildComparisonExpression(condition, "=", params),
|
|
116
|
+
ne: () => buildComparisonExpression(condition, "<>", params),
|
|
117
|
+
lt: () => buildComparisonExpression(condition, "<", params),
|
|
118
|
+
lte: () => buildComparisonExpression(condition, "<=", params),
|
|
119
|
+
gt: () => buildComparisonExpression(condition, ">", params),
|
|
120
|
+
gte: () => buildComparisonExpression(condition, ">=", params),
|
|
121
|
+
between: () => buildBetweenExpression(condition, params),
|
|
122
|
+
beginsWith: () => buildFunctionExpression("begins_with", condition, params),
|
|
123
|
+
contains: () => buildFunctionExpression("contains", condition, params),
|
|
124
|
+
attributeExists: () => buildAttributeFunction("attribute_exists", condition, params),
|
|
125
|
+
attributeNotExists: () => buildAttributeFunction("attribute_not_exists", condition, params),
|
|
126
|
+
and: () => {
|
|
127
|
+
if (!condition.conditions) {
|
|
128
|
+
throw new Error("Conditions array is required for AND operator");
|
|
129
|
+
}
|
|
130
|
+
return buildLogicalExpression("AND", condition.conditions, params);
|
|
131
|
+
},
|
|
132
|
+
or: () => {
|
|
133
|
+
if (!condition.conditions) {
|
|
134
|
+
throw new Error("Conditions array is required for OR operator");
|
|
135
|
+
}
|
|
136
|
+
return buildLogicalExpression("OR", condition.conditions, params);
|
|
137
|
+
},
|
|
138
|
+
not: () => {
|
|
139
|
+
if (!condition.condition) {
|
|
140
|
+
throw new Error("Condition is required for NOT operator");
|
|
141
|
+
}
|
|
142
|
+
return `NOT (${buildExpression(condition.condition, params)})`;
|
|
50
143
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
144
|
+
};
|
|
145
|
+
const builder = expressionBuilders[condition.type];
|
|
146
|
+
if (!builder) {
|
|
147
|
+
throw new Error(`Unknown condition type: ${condition.type}`);
|
|
148
|
+
}
|
|
149
|
+
return builder();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error instanceof Error) {
|
|
152
|
+
console.error(`Error building expression for condition type ${condition.type}:`, error.message);
|
|
153
|
+
} else {
|
|
154
|
+
console.error(`Error building expression for condition type ${condition.type}:`, error);
|
|
55
155
|
}
|
|
156
|
+
throw error;
|
|
56
157
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
158
|
+
};
|
|
159
|
+
var prepareExpressionParams = (condition) => {
|
|
160
|
+
if (!condition) return {};
|
|
161
|
+
const params = {
|
|
162
|
+
expressionAttributeNames: {},
|
|
163
|
+
expressionAttributeValues: {},
|
|
164
|
+
valueCounter: { count: 0 }
|
|
165
|
+
};
|
|
166
|
+
const expression = buildExpression(condition, params);
|
|
167
|
+
return {
|
|
168
|
+
expression,
|
|
169
|
+
names: Object.keys(params.expressionAttributeNames).length > 0 ? params.expressionAttributeNames : void 0,
|
|
170
|
+
values: Object.keys(params.expressionAttributeValues).length > 0 ? params.expressionAttributeValues : void 0
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/builders/paginator.ts
|
|
175
|
+
var Paginator = class {
|
|
176
|
+
queryBuilder;
|
|
177
|
+
pageSize;
|
|
178
|
+
currentPage = 0;
|
|
179
|
+
lastEvaluatedKey;
|
|
180
|
+
hasMorePages = true;
|
|
181
|
+
totalItemsRetrieved = 0;
|
|
182
|
+
overallLimit;
|
|
183
|
+
constructor(queryBuilder, pageSize) {
|
|
184
|
+
this.queryBuilder = queryBuilder;
|
|
185
|
+
this.pageSize = pageSize;
|
|
186
|
+
this.overallLimit = queryBuilder.getLimit();
|
|
69
187
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Gets the current page number (1-indexed).
|
|
190
|
+
* Use this method when you need to:
|
|
191
|
+
* - Track progress through dinosaur lists
|
|
192
|
+
* - Display habitat inspection status
|
|
193
|
+
* - Monitor security sweep progress
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* const paginator = new QueryBuilder(executor, eq('species', 'Tyrannosaurus'))
|
|
198
|
+
* .paginate(5);
|
|
199
|
+
*
|
|
200
|
+
* await paginator.getNextPage();
|
|
201
|
+
* console.log(`Reviewing T-Rex group ${paginator.getCurrentPage()}`);
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @returns The current page number, starting from 1
|
|
205
|
+
*/
|
|
206
|
+
getCurrentPage() {
|
|
207
|
+
return this.currentPage;
|
|
75
208
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Checks if there are more pages of dinosaurs or habitats to process.
|
|
211
|
+
* Use this method when you need to:
|
|
212
|
+
* - Check for more dinosaurs to review
|
|
213
|
+
* - Continue habitat inspections
|
|
214
|
+
* - Process security incidents
|
|
215
|
+
* - Complete feeding schedules
|
|
216
|
+
*
|
|
217
|
+
* This method takes into account both:
|
|
218
|
+
* - DynamoDB's lastEvaluatedKey mechanism
|
|
219
|
+
* - Any overall limit set on the query
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```ts
|
|
223
|
+
* // Process all security incidents
|
|
224
|
+
* const paginator = new QueryBuilder(executor, eq('type', 'SECURITY_BREACH'))
|
|
225
|
+
* .sortDescending()
|
|
226
|
+
* .paginate(10);
|
|
227
|
+
*
|
|
228
|
+
* while (paginator.hasNextPage()) {
|
|
229
|
+
* const page = await paginator.getNextPage();
|
|
230
|
+
* for (const incident of page.items) {
|
|
231
|
+
* await processSecurityBreach(incident);
|
|
232
|
+
* }
|
|
233
|
+
* console.log(`Processed incidents page ${page.page}`);
|
|
234
|
+
* }
|
|
235
|
+
* ```
|
|
236
|
+
*
|
|
237
|
+
* @returns true if there are more pages available, false otherwise
|
|
238
|
+
*/
|
|
239
|
+
hasNextPage() {
|
|
240
|
+
if (this.overallLimit !== void 0 && this.totalItemsRetrieved >= this.overallLimit) {
|
|
241
|
+
return false;
|
|
91
242
|
}
|
|
92
|
-
return
|
|
93
|
-
expression: conditions.join(" AND "),
|
|
94
|
-
attributes: this.formatAttributes(attributes)
|
|
95
|
-
};
|
|
243
|
+
return this.hasMorePages;
|
|
96
244
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Retrieves the next page of dinosaurs or habitats from DynamoDB.
|
|
247
|
+
* Use this method when you need to:
|
|
248
|
+
* - Process dinosaur groups systematically
|
|
249
|
+
* - Review habitat inspections in batches
|
|
250
|
+
* - Monitor security incidents in sequence
|
|
251
|
+
* - Schedule feeding rotations
|
|
252
|
+
*
|
|
253
|
+
* This method handles:
|
|
254
|
+
* - Automatic continuation between groups
|
|
255
|
+
* - Respect for park capacity limits
|
|
256
|
+
* - Group size adjustments for safety
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* const paginator = new QueryBuilder(executor, eq('species', 'Velociraptor'))
|
|
261
|
+
* .filter(op => op.eq('status', 'ACTIVE'))
|
|
262
|
+
* .paginate(5);
|
|
263
|
+
*
|
|
264
|
+
* // Check first raptor group
|
|
265
|
+
* const page1 = await paginator.getNextPage();
|
|
266
|
+
* console.log(`Found ${page1.items.length} active raptors`);
|
|
267
|
+
*
|
|
268
|
+
* // Continue inspection if more groups exist
|
|
269
|
+
* if (page1.hasNextPage) {
|
|
270
|
+
* const page2 = await paginator.getNextPage();
|
|
271
|
+
* console.log(`Inspecting raptor group ${page2.page}`);
|
|
272
|
+
*
|
|
273
|
+
* for (const raptor of page2.items) {
|
|
274
|
+
* await performHealthCheck(raptor);
|
|
275
|
+
* }
|
|
276
|
+
* }
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* @returns A promise that resolves to a PaginationResult containing:
|
|
280
|
+
* - items: The dinosaurs/habitats for this page
|
|
281
|
+
* - hasNextPage: Whether more groups exist
|
|
282
|
+
* - page: The current group number
|
|
283
|
+
* - lastEvaluatedKey: DynamoDB's continuation token
|
|
284
|
+
*/
|
|
285
|
+
async getNextPage() {
|
|
286
|
+
if (!this.hasNextPage()) {
|
|
287
|
+
return {
|
|
288
|
+
items: [],
|
|
289
|
+
hasNextPage: false,
|
|
290
|
+
page: this.currentPage
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
let effectivePageSize = this.pageSize;
|
|
294
|
+
if (this.overallLimit !== void 0) {
|
|
295
|
+
const remainingItems = this.overallLimit - this.totalItemsRetrieved;
|
|
296
|
+
if (remainingItems <= 0) {
|
|
297
|
+
return {
|
|
298
|
+
items: [],
|
|
299
|
+
hasNextPage: false,
|
|
300
|
+
page: this.currentPage
|
|
301
|
+
};
|
|
112
302
|
}
|
|
303
|
+
effectivePageSize = Math.min(effectivePageSize, remainingItems);
|
|
113
304
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
305
|
+
const query = this.queryBuilder.clone().limit(effectivePageSize);
|
|
306
|
+
if (this.lastEvaluatedKey) {
|
|
307
|
+
query.startFrom(this.lastEvaluatedKey);
|
|
308
|
+
}
|
|
309
|
+
const result = await query.execute();
|
|
310
|
+
this.currentPage += 1;
|
|
311
|
+
this.lastEvaluatedKey = result.lastEvaluatedKey;
|
|
312
|
+
this.totalItemsRetrieved += result.items.length;
|
|
313
|
+
this.hasMorePages = !!result.lastEvaluatedKey && (this.overallLimit === void 0 || this.totalItemsRetrieved < this.overallLimit);
|
|
118
314
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
315
|
+
items: result.items,
|
|
316
|
+
lastEvaluatedKey: result.lastEvaluatedKey,
|
|
317
|
+
hasNextPage: this.hasNextPage(),
|
|
318
|
+
page: this.currentPage
|
|
121
319
|
};
|
|
122
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Gets all remaining dinosaurs or habitats and combines them into a single array.
|
|
323
|
+
* Use this method when you need to:
|
|
324
|
+
* - Generate complete park inventory
|
|
325
|
+
* - Perform full security audit
|
|
326
|
+
* - Create comprehensive feeding schedule
|
|
327
|
+
* - Run park-wide health checks
|
|
328
|
+
*
|
|
329
|
+
* Note: Use with caution! This method:
|
|
330
|
+
* - Could overwhelm systems with large dinosaur populations
|
|
331
|
+
* - Makes multiple database requests
|
|
332
|
+
* - May cause system strain during peak hours
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* // Get complete carnivore inventory
|
|
337
|
+
* const paginator = new QueryBuilder(executor, eq('diet', 'CARNIVORE'))
|
|
338
|
+
* .filter(op => op.eq('status', 'ACTIVE'))
|
|
339
|
+
* .paginate(10);
|
|
340
|
+
*
|
|
341
|
+
* try {
|
|
342
|
+
* const allCarnivores = await paginator.getAllPages();
|
|
343
|
+
* console.log(`Park contains ${allCarnivores.length} active carnivores`);
|
|
344
|
+
*
|
|
345
|
+
* // Calculate total threat level
|
|
346
|
+
* const totalThreat = allCarnivores.reduce(
|
|
347
|
+
* (sum, dino) => sum + dino.stats.threatLevel,
|
|
348
|
+
* 0
|
|
349
|
+
* );
|
|
350
|
+
* console.log(`Total threat level: ${totalThreat}`);
|
|
351
|
+
* } catch (error) {
|
|
352
|
+
* console.error('Failed to complete carnivore census:', error);
|
|
353
|
+
* }
|
|
354
|
+
* ```
|
|
355
|
+
*
|
|
356
|
+
* @returns A promise that resolves to an array containing all remaining items
|
|
357
|
+
*/
|
|
358
|
+
async getAllPages() {
|
|
359
|
+
const allItems = [];
|
|
360
|
+
while (this.hasNextPage()) {
|
|
361
|
+
const result = await this.getNextPage();
|
|
362
|
+
allItems.push(...result.items);
|
|
363
|
+
}
|
|
364
|
+
return allItems;
|
|
365
|
+
}
|
|
123
366
|
};
|
|
124
367
|
|
|
125
|
-
// src/builders/
|
|
126
|
-
var
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.
|
|
368
|
+
// src/builders/query-builder.ts
|
|
369
|
+
var QueryBuilder = class _QueryBuilder {
|
|
370
|
+
keyCondition;
|
|
371
|
+
options = {};
|
|
372
|
+
selectedFields = /* @__PURE__ */ new Set();
|
|
373
|
+
executor;
|
|
374
|
+
constructor(executor, keyCondition) {
|
|
375
|
+
this.executor = executor;
|
|
376
|
+
this.keyCondition = keyCondition;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Sets the maximum number of items to return from the query.
|
|
380
|
+
* Use this method when you need to:
|
|
381
|
+
* - Limit the result set size
|
|
382
|
+
* - Implement manual pagination
|
|
383
|
+
* - Control data transfer size
|
|
384
|
+
*
|
|
385
|
+
* Note: This limit applies to the items that match the key condition
|
|
386
|
+
* before any filter expressions are applied.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* // Get first 10 orders for a user
|
|
391
|
+
* const result = await new QueryBuilder(executor, eq('userId', '123'))
|
|
392
|
+
* .limit(10)
|
|
393
|
+
* .execute();
|
|
394
|
+
* ```
|
|
395
|
+
*
|
|
396
|
+
* @param limit - Maximum number of items to return
|
|
397
|
+
* @returns The builder instance for method chaining
|
|
398
|
+
*/
|
|
399
|
+
limit(limit) {
|
|
400
|
+
this.options.limit = limit;
|
|
133
401
|
return this;
|
|
134
402
|
}
|
|
135
|
-
|
|
136
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Gets the current limit set on the query.
|
|
405
|
+
* This is used internally by the paginator to manage result sets.
|
|
406
|
+
*
|
|
407
|
+
* @returns The current limit or undefined if no limit is set
|
|
408
|
+
*/
|
|
409
|
+
getLimit() {
|
|
410
|
+
return this.options.limit;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Specifies a Global Secondary Index (GSI) to use for the query.
|
|
414
|
+
* Use this method when you need to:
|
|
415
|
+
* - Query data using non-primary key attributes
|
|
416
|
+
* - Access data through alternate access patterns
|
|
417
|
+
* - Optimize query performance for specific access patterns
|
|
418
|
+
*
|
|
419
|
+
* This method provides type safety by only allowing valid GSI names
|
|
420
|
+
* defined in your table configuration.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```ts
|
|
424
|
+
* // Query by status using a GSI
|
|
425
|
+
* const result = await new QueryBuilder(executor, eq('status', 'ACTIVE'))
|
|
426
|
+
* .useIndex('status-index')
|
|
427
|
+
* .execute();
|
|
428
|
+
*
|
|
429
|
+
* // Query by category and date range
|
|
430
|
+
* const result = await new QueryBuilder(executor, eq('category', 'books'))
|
|
431
|
+
* .useIndex('category-date-index')
|
|
432
|
+
* .filter(op => op.between('date', startDate, endDate))
|
|
433
|
+
* .execute();
|
|
434
|
+
* ```
|
|
435
|
+
*
|
|
436
|
+
* Note: Be aware that GSIs:
|
|
437
|
+
* - May have different projected attributes
|
|
438
|
+
* - Have eventually consistent reads only
|
|
439
|
+
* - May have different provisioned throughput
|
|
440
|
+
*
|
|
441
|
+
* @param indexName - The name of the GSI to use (type-safe based on table configuration)
|
|
442
|
+
* @returns The builder instance for method chaining
|
|
443
|
+
*/
|
|
444
|
+
/**
|
|
445
|
+
* Specifies a Global Secondary Index (GSI) to use for the query.
|
|
446
|
+
* Use this method when you need to:
|
|
447
|
+
* - Query dinosaurs by species or status
|
|
448
|
+
* - Search habitats by security level
|
|
449
|
+
* - Find incidents by date
|
|
450
|
+
* - List feeding schedules by time
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```typescript
|
|
454
|
+
* // Find all dinosaurs of a specific species
|
|
455
|
+
* builder
|
|
456
|
+
* .useIndex('species-status-index')
|
|
457
|
+
* .filter(op => op.eq('status', 'ACTIVE'));
|
|
458
|
+
*
|
|
459
|
+
* // Search high-security habitats
|
|
460
|
+
* builder
|
|
461
|
+
* .useIndex('security-level-index')
|
|
462
|
+
* .filter(op =>
|
|
463
|
+
* op.and([
|
|
464
|
+
* op.gt('securityLevel', 8),
|
|
465
|
+
* op.eq('status', 'OPERATIONAL')
|
|
466
|
+
* ])
|
|
467
|
+
* );
|
|
468
|
+
* ```
|
|
469
|
+
*
|
|
470
|
+
* @param indexName - The name of the GSI to use (type-safe based on table configuration)
|
|
471
|
+
* @returns The builder instance for method chaining
|
|
472
|
+
*/
|
|
473
|
+
useIndex(indexName) {
|
|
474
|
+
this.options.indexName = indexName;
|
|
137
475
|
return this;
|
|
138
476
|
}
|
|
139
|
-
|
|
140
|
-
|
|
477
|
+
/**
|
|
478
|
+
* Sets whether to use strongly consistent reads for the query.
|
|
479
|
+
* Use this method when you need to:
|
|
480
|
+
* - Get real-time dinosaur status updates
|
|
481
|
+
* - Monitor critical security systems
|
|
482
|
+
* - Track immediate habitat changes
|
|
483
|
+
* - Verify containment protocols
|
|
484
|
+
*
|
|
485
|
+
* Note:
|
|
486
|
+
* - Consistent reads are not available on GSIs
|
|
487
|
+
* - Consistent reads consume twice the throughput
|
|
488
|
+
* - Default is eventually consistent reads
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```ts
|
|
492
|
+
* // Check immediate dinosaur status
|
|
493
|
+
* const result = await new QueryBuilder(executor, eq('species', 'Velociraptor'))
|
|
494
|
+
* .filter(op => op.eq('status', 'ACTIVE'))
|
|
495
|
+
* .consistentRead()
|
|
496
|
+
* .execute();
|
|
497
|
+
*
|
|
498
|
+
* // Monitor security breaches
|
|
499
|
+
* const result = await new QueryBuilder(executor, eq('type', 'SECURITY_ALERT'))
|
|
500
|
+
* .useIndex('primary-index')
|
|
501
|
+
* .consistentRead(isEmergencyMode)
|
|
502
|
+
* .execute();
|
|
503
|
+
* ```
|
|
504
|
+
*
|
|
505
|
+
* @param consistentRead - Whether to use consistent reads (defaults to true)
|
|
506
|
+
* @returns The builder instance for method chaining
|
|
507
|
+
*/
|
|
508
|
+
consistentRead(consistentRead = true) {
|
|
509
|
+
this.options.consistentRead = consistentRead;
|
|
141
510
|
return this;
|
|
142
511
|
}
|
|
143
|
-
|
|
144
|
-
|
|
512
|
+
/**
|
|
513
|
+
* Adds a filter expression to the query.
|
|
514
|
+
* Use this method when you need to:
|
|
515
|
+
* - Filter results based on non-key attributes
|
|
516
|
+
* - Apply complex filtering conditions
|
|
517
|
+
* - Combine multiple filter conditions
|
|
518
|
+
*
|
|
519
|
+
* Note: Filter expressions are applied after the key condition,
|
|
520
|
+
* so they don't reduce the amount of data read from DynamoDB.
|
|
521
|
+
*
|
|
522
|
+
* @example
|
|
523
|
+
* ```ts
|
|
524
|
+
* // Simple filter
|
|
525
|
+
* builder.filter(op => op.eq('status', 'ACTIVE'))
|
|
526
|
+
*
|
|
527
|
+
* // Complex filter with multiple conditions
|
|
528
|
+
* builder.filter(op =>
|
|
529
|
+
* op.and([
|
|
530
|
+
* op.gt('amount', 1000),
|
|
531
|
+
* op.beginsWith('category', 'ELECTRONICS'),
|
|
532
|
+
* op.attributeExists('reviewDate')
|
|
533
|
+
* ])
|
|
534
|
+
* )
|
|
535
|
+
* ```
|
|
536
|
+
*
|
|
537
|
+
* @param condition - Either a Condition object or a callback function that builds the condition
|
|
538
|
+
* @returns The builder instance for method chaining
|
|
539
|
+
*/
|
|
540
|
+
/**
|
|
541
|
+
* Adds a filter expression to refine the query results.
|
|
542
|
+
* Use this method when you need to:
|
|
543
|
+
* - Filter dinosaurs by behavior patterns
|
|
544
|
+
* - Find habitats with specific conditions
|
|
545
|
+
* - Search for security incidents
|
|
546
|
+
* - Monitor feeding patterns
|
|
547
|
+
*
|
|
548
|
+
* @example
|
|
549
|
+
* ```typescript
|
|
550
|
+
* // Find aggressive carnivores
|
|
551
|
+
* builder.filter(op =>
|
|
552
|
+
* op.and([
|
|
553
|
+
* op.eq('diet', 'CARNIVORE'),
|
|
554
|
+
* op.gt('aggressionLevel', 7),
|
|
555
|
+
* op.eq('status', 'ACTIVE')
|
|
556
|
+
* ])
|
|
557
|
+
* );
|
|
558
|
+
*
|
|
559
|
+
* // Search suitable breeding habitats
|
|
560
|
+
* builder.filter(op =>
|
|
561
|
+
* op.and([
|
|
562
|
+
* op.between('temperature', 25, 30),
|
|
563
|
+
* op.lt('currentOccupants', 3),
|
|
564
|
+
* op.eq('quarantineStatus', 'CLEAR')
|
|
565
|
+
* ])
|
|
566
|
+
* );
|
|
567
|
+
* ```
|
|
568
|
+
*
|
|
569
|
+
* @param condition - Either a Condition object or a callback function that builds the condition
|
|
570
|
+
* @returns The builder instance for method chaining
|
|
571
|
+
*/
|
|
572
|
+
filter(condition) {
|
|
573
|
+
if (typeof condition === "function") {
|
|
574
|
+
const conditionOperator = {
|
|
575
|
+
eq,
|
|
576
|
+
ne,
|
|
577
|
+
lt,
|
|
578
|
+
lte,
|
|
579
|
+
gt,
|
|
580
|
+
gte,
|
|
581
|
+
between,
|
|
582
|
+
beginsWith,
|
|
583
|
+
contains,
|
|
584
|
+
attributeExists,
|
|
585
|
+
attributeNotExists,
|
|
586
|
+
and,
|
|
587
|
+
or,
|
|
588
|
+
not
|
|
589
|
+
};
|
|
590
|
+
this.options.filter = condition(conditionOperator);
|
|
591
|
+
} else {
|
|
592
|
+
this.options.filter = condition;
|
|
593
|
+
}
|
|
594
|
+
return this;
|
|
145
595
|
}
|
|
146
|
-
|
|
147
|
-
|
|
596
|
+
/**
|
|
597
|
+
* Specifies which attributes to return in the query results.
|
|
598
|
+
* Use this method when you need to:
|
|
599
|
+
* - Reduce data transfer by selecting specific attributes
|
|
600
|
+
* - Optimize response size
|
|
601
|
+
* - Focus on relevant attributes only
|
|
602
|
+
*
|
|
603
|
+
* Note: Using projection can significantly reduce the amount
|
|
604
|
+
* of data returned and lower your costs.
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* ```ts
|
|
608
|
+
* // Select single attribute
|
|
609
|
+
* builder.select('email')
|
|
610
|
+
*
|
|
611
|
+
* // Select multiple attributes
|
|
612
|
+
* builder.select(['id', 'name', 'email'])
|
|
613
|
+
*
|
|
614
|
+
* // Chain multiple select calls
|
|
615
|
+
* builder
|
|
616
|
+
* .select('id')
|
|
617
|
+
* .select(['name', 'email'])
|
|
618
|
+
* ```
|
|
619
|
+
*
|
|
620
|
+
* @param fields - A single field name or an array of field names to return
|
|
621
|
+
* @returns The builder instance for method chaining
|
|
622
|
+
*/
|
|
623
|
+
/**
|
|
624
|
+
* Specifies which attributes to return in the query results.
|
|
625
|
+
* Use this method when you need to:
|
|
626
|
+
* - Get specific dinosaur attributes
|
|
627
|
+
* - Retrieve habitat statistics
|
|
628
|
+
* - Monitor security metrics
|
|
629
|
+
* - Optimize response size
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* ```typescript
|
|
633
|
+
* // Get basic dinosaur info
|
|
634
|
+
* builder.select([
|
|
635
|
+
* 'species',
|
|
636
|
+
* 'status',
|
|
637
|
+
* 'stats.health',
|
|
638
|
+
* 'stats.aggressionLevel'
|
|
639
|
+
* ]);
|
|
640
|
+
*
|
|
641
|
+
* // Monitor habitat conditions
|
|
642
|
+
* builder
|
|
643
|
+
* .select('securityStatus')
|
|
644
|
+
* .select([
|
|
645
|
+
* 'currentOccupants',
|
|
646
|
+
* 'temperature',
|
|
647
|
+
* 'lastInspectionDate'
|
|
648
|
+
* ]);
|
|
649
|
+
* ```
|
|
650
|
+
*
|
|
651
|
+
* @param fields - A single field name or an array of field names to return
|
|
652
|
+
* @returns The builder instance for method chaining
|
|
653
|
+
*/
|
|
654
|
+
select(fields) {
|
|
655
|
+
if (typeof fields === "string") {
|
|
656
|
+
this.selectedFields.add(fields);
|
|
657
|
+
} else if (Array.isArray(fields)) {
|
|
658
|
+
for (const field of fields) {
|
|
659
|
+
this.selectedFields.add(field);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
this.options.projection = Array.from(this.selectedFields);
|
|
663
|
+
return this;
|
|
148
664
|
}
|
|
149
|
-
|
|
150
|
-
|
|
665
|
+
/**
|
|
666
|
+
* Sets the query to return items in ascending order by sort key.
|
|
667
|
+
* Use this method when you need to:
|
|
668
|
+
* - Retrieve items in natural order (e.g., timestamps, versions)
|
|
669
|
+
* - Implement chronological ordering
|
|
670
|
+
* - Get oldest items first
|
|
671
|
+
*
|
|
672
|
+
* Note: This is the default behavior if no sort order is specified.
|
|
673
|
+
*
|
|
674
|
+
* @example
|
|
675
|
+
* ```typescript
|
|
676
|
+
* // Get orders in chronological order
|
|
677
|
+
* const result = await new QueryBuilder(executor, eq('userId', '123'))
|
|
678
|
+
* .sortAscending()
|
|
679
|
+
* .execute();
|
|
680
|
+
*
|
|
681
|
+
* // Get events from oldest to newest
|
|
682
|
+
* const result = await new QueryBuilder(executor, eq('entityId', 'order-123'))
|
|
683
|
+
* .useIndex('entity-timestamp-index')
|
|
684
|
+
* .sortAscending()
|
|
685
|
+
* .execute();
|
|
686
|
+
* ```
|
|
687
|
+
*
|
|
688
|
+
* @returns The builder instance for method chaining
|
|
689
|
+
*/
|
|
690
|
+
/**
|
|
691
|
+
* Sets the query to return items in ascending order by sort key.
|
|
692
|
+
* Use this method when you need to:
|
|
693
|
+
* - List dinosaurs by age (youngest first)
|
|
694
|
+
* - View incidents chronologically
|
|
695
|
+
* - Track feeding schedule progression
|
|
696
|
+
* - Monitor habitat inspections
|
|
697
|
+
*
|
|
698
|
+
* @example
|
|
699
|
+
* ```typescript
|
|
700
|
+
* // List dinosaurs by age
|
|
701
|
+
* const result = await new QueryBuilder(executor, eq('species', 'Velociraptor'))
|
|
702
|
+
* .useIndex('age-index')
|
|
703
|
+
* .sortAscending()
|
|
704
|
+
* .execute();
|
|
705
|
+
*
|
|
706
|
+
* // View incidents chronologically
|
|
707
|
+
* const result = await new QueryBuilder(executor, eq('type', 'SECURITY_BREACH'))
|
|
708
|
+
* .useIndex('date-index')
|
|
709
|
+
* .sortAscending()
|
|
710
|
+
* .execute();
|
|
711
|
+
* ```
|
|
712
|
+
*
|
|
713
|
+
* @returns The builder instance for method chaining
|
|
714
|
+
*/
|
|
715
|
+
sortAscending() {
|
|
716
|
+
this.options.scanIndexForward = true;
|
|
717
|
+
return this;
|
|
151
718
|
}
|
|
152
|
-
|
|
153
|
-
|
|
719
|
+
/**
|
|
720
|
+
* Sets the query to return items in descending order by sort key.
|
|
721
|
+
* Use this method when you need to:
|
|
722
|
+
* - Get most recent security breaches
|
|
723
|
+
* - Find oldest dinosaurs first
|
|
724
|
+
* - Check latest habitat modifications
|
|
725
|
+
* - Monitor recent feeding events
|
|
726
|
+
*
|
|
727
|
+
* @example
|
|
728
|
+
* ```typescript
|
|
729
|
+
* // Get most recent security incidents
|
|
730
|
+
* const result = await new QueryBuilder(executor, eq('type', 'SECURITY_BREACH'))
|
|
731
|
+
* .useIndex('date-index')
|
|
732
|
+
* .sortDescending()
|
|
733
|
+
* .limit(10)
|
|
734
|
+
* .execute();
|
|
735
|
+
*
|
|
736
|
+
* // Check latest dinosaur activities
|
|
737
|
+
* const result = await new QueryBuilder(executor, eq('species', 'Velociraptor'))
|
|
738
|
+
* .useIndex('activity-time-index')
|
|
739
|
+
* .filter(op => op.eq('status', 'ACTIVE'))
|
|
740
|
+
* .sortDescending()
|
|
741
|
+
* .execute();
|
|
742
|
+
* ```
|
|
743
|
+
*
|
|
744
|
+
* @returns The builder instance for method chaining
|
|
745
|
+
*/
|
|
746
|
+
sortDescending() {
|
|
747
|
+
this.options.scanIndexForward = false;
|
|
748
|
+
return this;
|
|
154
749
|
}
|
|
155
|
-
|
|
156
|
-
|
|
750
|
+
/**
|
|
751
|
+
* Creates a paginator that handles DynamoDB pagination automatically.
|
|
752
|
+
* Use this method when you need to:
|
|
753
|
+
* - Browse large dinosaur collections
|
|
754
|
+
* - View habitat inspection history
|
|
755
|
+
* - Monitor security incidents
|
|
756
|
+
* - Track feeding patterns
|
|
757
|
+
*
|
|
758
|
+
* The paginator handles:
|
|
759
|
+
* - Tracking the last evaluated key
|
|
760
|
+
* - Managing page boundaries
|
|
761
|
+
* - Respecting overall query limits
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* ```typescript
|
|
765
|
+
* // List dinosaurs by species
|
|
766
|
+
* const paginator = new QueryBuilder(executor, eq('species', 'Velociraptor'))
|
|
767
|
+
* .filter(op => op.eq('status', 'ACTIVE'))
|
|
768
|
+
* .useIndex('species-index')
|
|
769
|
+
* .paginate(10);
|
|
770
|
+
*
|
|
771
|
+
* // Process pages of security incidents
|
|
772
|
+
* const paginator = new QueryBuilder(executor, eq('type', 'SECURITY_BREACH'))
|
|
773
|
+
* .filter(op => op.gt('severityLevel', 7))
|
|
774
|
+
* .sortDescending()
|
|
775
|
+
* .paginate(25);
|
|
776
|
+
*
|
|
777
|
+
* while (paginator.hasNextPage()) {
|
|
778
|
+
* const page = await paginator.getNextPage();
|
|
779
|
+
* console.log(`Processing incidents page ${page.page}, count: ${page.items.length}`);
|
|
780
|
+
* // Handle security incidents
|
|
781
|
+
* }
|
|
782
|
+
* ```
|
|
783
|
+
*
|
|
784
|
+
* @param pageSize - The number of items to return per page
|
|
785
|
+
* @returns A Paginator instance that manages the pagination state
|
|
786
|
+
* @see Paginator for more pagination control options
|
|
787
|
+
*/
|
|
788
|
+
paginate(pageSize) {
|
|
789
|
+
return new Paginator(this, pageSize);
|
|
157
790
|
}
|
|
158
|
-
|
|
159
|
-
|
|
791
|
+
/**
|
|
792
|
+
* Sets the starting point for the query using a previous lastEvaluatedKey.
|
|
793
|
+
* Use this method when you need to:
|
|
794
|
+
* - Implement manual dinosaur list pagination
|
|
795
|
+
* - Resume habitat inspection reviews
|
|
796
|
+
* - Continue security incident analysis
|
|
797
|
+
* - Store query position between sessions
|
|
798
|
+
*
|
|
799
|
+
* Note: This method is typically used for manual pagination.
|
|
800
|
+
* For automatic pagination, use the paginate() method instead.
|
|
801
|
+
*
|
|
802
|
+
* @example
|
|
803
|
+
* ```typescript
|
|
804
|
+
* // First batch of dinosaurs
|
|
805
|
+
* const result1 = await new QueryBuilder(executor, eq('species', 'Velociraptor'))
|
|
806
|
+
* .filter(op => op.eq('status', 'ACTIVE'))
|
|
807
|
+
* .limit(5)
|
|
808
|
+
* .execute();
|
|
809
|
+
*
|
|
810
|
+
* if (result1.lastEvaluatedKey) {
|
|
811
|
+
* // Continue listing dinosaurs
|
|
812
|
+
* const result2 = await new QueryBuilder(executor, eq('species', 'Velociraptor'))
|
|
813
|
+
* .filter(op => op.eq('status', 'ACTIVE'))
|
|
814
|
+
* .startFrom(result1.lastEvaluatedKey)
|
|
815
|
+
* .limit(5)
|
|
816
|
+
* .execute();
|
|
817
|
+
*
|
|
818
|
+
* console.log('Additional dinosaurs:', result2.items);
|
|
819
|
+
* }
|
|
820
|
+
* ```
|
|
821
|
+
*
|
|
822
|
+
* @param lastEvaluatedKey - The exclusive start key from a previous query result
|
|
823
|
+
* @returns The builder instance for method chaining
|
|
824
|
+
*/
|
|
825
|
+
startFrom(lastEvaluatedKey) {
|
|
826
|
+
this.options.lastEvaluatedKey = lastEvaluatedKey;
|
|
827
|
+
return this;
|
|
160
828
|
}
|
|
161
|
-
|
|
162
|
-
|
|
829
|
+
/**
|
|
830
|
+
* Creates a deep clone of this QueryBuilder instance.
|
|
831
|
+
* Use this method when you need to:
|
|
832
|
+
* - Query different dinosaur statuses
|
|
833
|
+
* - Check multiple habitat conditions
|
|
834
|
+
* - Monitor various security levels
|
|
835
|
+
* - Create report templates
|
|
836
|
+
*
|
|
837
|
+
* This is particularly useful when:
|
|
838
|
+
* - Implementing pagination (used internally by paginate())
|
|
839
|
+
* - Creating query templates
|
|
840
|
+
* - Running multiple variations of a query
|
|
841
|
+
*
|
|
842
|
+
* @example
|
|
843
|
+
* ```typescript
|
|
844
|
+
* // Create base dinosaur query
|
|
845
|
+
* const baseQuery = new QueryBuilder(executor, eq('species', 'Velociraptor'))
|
|
846
|
+
* .useIndex('status-index')
|
|
847
|
+
* .select(['id', 'status', 'location']);
|
|
848
|
+
*
|
|
849
|
+
* // Check active dinosaurs
|
|
850
|
+
* const activeRaptors = baseQuery.clone()
|
|
851
|
+
* .filter(op => op.eq('status', 'HUNTING'))
|
|
852
|
+
* .execute();
|
|
853
|
+
*
|
|
854
|
+
* // Check contained dinosaurs
|
|
855
|
+
* const containedRaptors = baseQuery.clone()
|
|
856
|
+
* .filter(op => op.eq('status', 'CONTAINED'))
|
|
857
|
+
* .execute();
|
|
858
|
+
*
|
|
859
|
+
* // Check sedated dinosaurs
|
|
860
|
+
* const sedatedRaptors = baseQuery.clone()
|
|
861
|
+
* .filter(op => op.eq('status', 'SEDATED'))
|
|
862
|
+
* .execute();
|
|
863
|
+
* ```
|
|
864
|
+
*
|
|
865
|
+
* @returns A new QueryBuilder instance with the same configuration
|
|
866
|
+
*/
|
|
867
|
+
clone() {
|
|
868
|
+
const clone = new _QueryBuilder(this.executor, this.keyCondition);
|
|
869
|
+
clone.options = { ...this.options };
|
|
870
|
+
clone.selectedFields = new Set(this.selectedFields);
|
|
871
|
+
return clone;
|
|
163
872
|
}
|
|
164
|
-
|
|
165
|
-
|
|
873
|
+
/**
|
|
874
|
+
* Executes the query against DynamoDB.
|
|
875
|
+
* Use this method when you need to:
|
|
876
|
+
* - Find specific dinosaur groups
|
|
877
|
+
* - Check habitat conditions
|
|
878
|
+
* - Monitor security incidents
|
|
879
|
+
* - Track feeding patterns
|
|
880
|
+
*
|
|
881
|
+
* The method returns both the matched items and, if there are more results,
|
|
882
|
+
* a lastEvaluatedKey that can be used with startFrom() to continue the query.
|
|
883
|
+
*
|
|
884
|
+
* @example
|
|
885
|
+
* ```typescript
|
|
886
|
+
* try {
|
|
887
|
+
* // Find active carnivores in specific habitat
|
|
888
|
+
* const result = await new QueryBuilder(executor, eq('habitatId', 'PADDOCK-A'))
|
|
889
|
+
* .useIndex('species-status-index')
|
|
890
|
+
* .filter(op =>
|
|
891
|
+
* op.and([
|
|
892
|
+
* op.eq('diet', 'CARNIVORE'),
|
|
893
|
+
* op.eq('status', 'ACTIVE'),
|
|
894
|
+
* op.gt('aggressionLevel', 7)
|
|
895
|
+
* ])
|
|
896
|
+
* )
|
|
897
|
+
* .sortDescending()
|
|
898
|
+
* .limit(5)
|
|
899
|
+
* .execute();
|
|
900
|
+
*
|
|
901
|
+
* console.log(`Found ${result.items.length} dangerous dinosaurs`);
|
|
902
|
+
*
|
|
903
|
+
* if (result.lastEvaluatedKey) {
|
|
904
|
+
* console.log('Additional threats detected');
|
|
905
|
+
* }
|
|
906
|
+
* } catch (error) {
|
|
907
|
+
* console.error('Security scan failed:', error);
|
|
908
|
+
* }
|
|
909
|
+
* ```
|
|
910
|
+
*
|
|
911
|
+
* @returns A promise that resolves to an object containing:
|
|
912
|
+
* - items: Array of items matching the query
|
|
913
|
+
* - lastEvaluatedKey: Token for continuing the query, if more items exist
|
|
914
|
+
*/
|
|
915
|
+
async execute() {
|
|
916
|
+
return this.executor(this.keyCondition, this.options);
|
|
166
917
|
}
|
|
167
|
-
|
|
168
|
-
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// src/utils/debug-expression.ts
|
|
921
|
+
function debugCommand(command) {
|
|
922
|
+
const result = {};
|
|
923
|
+
function replaceAliases(expressionString) {
|
|
924
|
+
if (!expressionString) {
|
|
925
|
+
return expressionString;
|
|
926
|
+
}
|
|
927
|
+
let replacedString = expressionString;
|
|
928
|
+
for (const alias in command.expressionAttributeNames) {
|
|
929
|
+
const attributeName = command.expressionAttributeNames[alias];
|
|
930
|
+
const regex = new RegExp(alias, "g");
|
|
931
|
+
replacedString = replacedString.replace(regex, attributeName);
|
|
932
|
+
}
|
|
933
|
+
for (const alias in command.expressionAttributeValues) {
|
|
934
|
+
let attributeValue = command.expressionAttributeValues[alias];
|
|
935
|
+
if (attributeValue instanceof Set) {
|
|
936
|
+
const array = Array.from(attributeValue);
|
|
937
|
+
attributeValue = `Set(${array.length}){${array.map((v) => JSON.stringify(v)).join(", ")}}`;
|
|
938
|
+
} else {
|
|
939
|
+
attributeValue = JSON.stringify(attributeValue);
|
|
940
|
+
}
|
|
941
|
+
const regex = new RegExp(alias, "g");
|
|
942
|
+
replacedString = replacedString.replace(regex, attributeValue);
|
|
943
|
+
}
|
|
944
|
+
return replacedString;
|
|
169
945
|
}
|
|
170
|
-
|
|
171
|
-
|
|
946
|
+
if (command.updateExpression) {
|
|
947
|
+
result.updateExpression = replaceAliases(command.updateExpression);
|
|
172
948
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return this;
|
|
949
|
+
if (command.conditionExpression) {
|
|
950
|
+
result.conditionExpression = replaceAliases(command.conditionExpression);
|
|
176
951
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return this;
|
|
952
|
+
if (command.filterExpression) {
|
|
953
|
+
result.filterExpression = replaceAliases(command.filterExpression);
|
|
180
954
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return this;
|
|
955
|
+
if (command.keyConditionExpression) {
|
|
956
|
+
result.keyConditionExpression = replaceAliases(command.keyConditionExpression);
|
|
184
957
|
}
|
|
185
|
-
|
|
186
|
-
|
|
958
|
+
if (command.projectionExpression) {
|
|
959
|
+
result.projectionExpression = replaceAliases(command.projectionExpression);
|
|
187
960
|
}
|
|
188
|
-
|
|
961
|
+
return {
|
|
962
|
+
raw: command,
|
|
963
|
+
readable: result
|
|
964
|
+
};
|
|
965
|
+
}
|
|
189
966
|
|
|
190
967
|
// src/builders/put-builder.ts
|
|
191
|
-
var PutBuilder = class
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
968
|
+
var PutBuilder = class {
|
|
969
|
+
item;
|
|
970
|
+
options = {};
|
|
971
|
+
executor;
|
|
972
|
+
tableName;
|
|
973
|
+
constructor(executor, item, tableName) {
|
|
974
|
+
this.executor = executor;
|
|
195
975
|
this.item = item;
|
|
976
|
+
this.tableName = tableName;
|
|
196
977
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
978
|
+
/**
|
|
979
|
+
* Adds a condition that must be satisfied for the put operation to succeed.
|
|
980
|
+
* Use this method when you need to:
|
|
981
|
+
* - Prevent overwriting existing items (optimistic locking)
|
|
982
|
+
* - Ensure items meet certain criteria before replacement
|
|
983
|
+
* - Implement complex business rules for item updates
|
|
984
|
+
*
|
|
985
|
+
* @example
|
|
986
|
+
* ```ts
|
|
987
|
+
* // Ensure item doesn't exist (insert only)
|
|
988
|
+
* builder.condition(op => op.attributeNotExists('id'))
|
|
989
|
+
*
|
|
990
|
+
* // Complex condition with version check
|
|
991
|
+
* builder.condition(op =>
|
|
992
|
+
* op.and([
|
|
993
|
+
* op.attributeExists('id'),
|
|
994
|
+
* op.eq('version', currentVersion),
|
|
995
|
+
* op.eq('status', 'ACTIVE')
|
|
996
|
+
* ])
|
|
997
|
+
* )
|
|
998
|
+
* ```
|
|
999
|
+
*
|
|
1000
|
+
* @param condition - Either a Condition object or a callback function that builds the condition
|
|
1001
|
+
* @returns The builder instance for method chaining
|
|
1002
|
+
*/
|
|
1003
|
+
/**
|
|
1004
|
+
* Adds a condition that must be satisfied for the put operation to succeed.
|
|
1005
|
+
* Use this method when you need to:
|
|
1006
|
+
* - Prevent duplicate dinosaur entries
|
|
1007
|
+
* - Ensure habitat requirements
|
|
1008
|
+
* - Validate security protocols
|
|
1009
|
+
*
|
|
1010
|
+
* @example
|
|
1011
|
+
* ```typescript
|
|
1012
|
+
* // Ensure unique dinosaur ID
|
|
1013
|
+
* builder.condition(op =>
|
|
1014
|
+
* op.attributeNotExists('id')
|
|
1015
|
+
* );
|
|
1016
|
+
*
|
|
1017
|
+
* // Verify habitat requirements
|
|
1018
|
+
* builder.condition(op =>
|
|
1019
|
+
* op.and([
|
|
1020
|
+
* op.eq('securityStatus', 'READY'),
|
|
1021
|
+
* op.attributeExists('lastInspection'),
|
|
1022
|
+
* op.gt('securityLevel', 5)
|
|
1023
|
+
* ])
|
|
1024
|
+
* );
|
|
1025
|
+
*
|
|
1026
|
+
* // Check breeding facility conditions
|
|
1027
|
+
* builder.condition(op =>
|
|
1028
|
+
* op.and([
|
|
1029
|
+
* op.between('temperature', 25, 30),
|
|
1030
|
+
* op.between('humidity', 60, 80),
|
|
1031
|
+
* op.eq('quarantineStatus', 'CLEAR')
|
|
1032
|
+
* ])
|
|
1033
|
+
* );
|
|
1034
|
+
* ```
|
|
1035
|
+
*
|
|
1036
|
+
* @param condition - Either a Condition object or a callback function that builds the condition
|
|
1037
|
+
* @returns The builder instance for method chaining
|
|
1038
|
+
*/
|
|
1039
|
+
condition(condition) {
|
|
1040
|
+
if (typeof condition === "function") {
|
|
1041
|
+
const conditionOperator = {
|
|
1042
|
+
eq,
|
|
1043
|
+
ne,
|
|
1044
|
+
lt,
|
|
1045
|
+
lte,
|
|
1046
|
+
gt,
|
|
1047
|
+
gte,
|
|
1048
|
+
between,
|
|
1049
|
+
beginsWith,
|
|
1050
|
+
contains,
|
|
1051
|
+
attributeExists,
|
|
1052
|
+
attributeNotExists,
|
|
1053
|
+
and,
|
|
1054
|
+
or,
|
|
1055
|
+
not
|
|
1056
|
+
};
|
|
1057
|
+
this.options.condition = condition(conditionOperator);
|
|
1058
|
+
} else {
|
|
1059
|
+
this.options.condition = condition;
|
|
1060
|
+
}
|
|
200
1061
|
return this;
|
|
201
1062
|
}
|
|
202
|
-
|
|
203
|
-
|
|
1063
|
+
/**
|
|
1064
|
+
* Sets whether to return the item's previous values (if it existed).
|
|
1065
|
+
* Use this method when you need to:
|
|
1066
|
+
* - Track dinosaur profile updates
|
|
1067
|
+
* - Monitor habitat modifications
|
|
1068
|
+
* - Maintain change history
|
|
1069
|
+
*
|
|
1070
|
+
* @example
|
|
1071
|
+
* ```ts
|
|
1072
|
+
* // Get previous dinosaur state
|
|
1073
|
+
* const result = await builder
|
|
1074
|
+
* .returnValues('ALL_OLD')
|
|
1075
|
+
* .execute();
|
|
1076
|
+
*
|
|
1077
|
+
* if (result) {
|
|
1078
|
+
* console.log('Previous profile:', {
|
|
1079
|
+
* species: result.species,
|
|
1080
|
+
* status: result.status,
|
|
1081
|
+
* stats: {
|
|
1082
|
+
* health: result.stats.health,
|
|
1083
|
+
* threatLevel: result.stats.threatLevel
|
|
1084
|
+
* }
|
|
1085
|
+
* });
|
|
1086
|
+
* }
|
|
1087
|
+
* ```
|
|
1088
|
+
*
|
|
1089
|
+
* @param returnValues - Use 'ALL_OLD' to return previous values, or 'NONE' (default)
|
|
1090
|
+
* @returns The builder instance for method chaining
|
|
1091
|
+
*/
|
|
1092
|
+
returnValues(returnValues) {
|
|
1093
|
+
this.options.returnValues = returnValues;
|
|
204
1094
|
return this;
|
|
205
1095
|
}
|
|
206
|
-
|
|
207
|
-
|
|
1096
|
+
/**
|
|
1097
|
+
* Generate the DynamoDB command parameters
|
|
1098
|
+
*/
|
|
1099
|
+
toDynamoCommand() {
|
|
1100
|
+
const { expression, names, values } = prepareExpressionParams(this.options.condition);
|
|
208
1101
|
return {
|
|
209
|
-
|
|
1102
|
+
tableName: this.tableName,
|
|
210
1103
|
item: this.item,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
} : void 0
|
|
1104
|
+
conditionExpression: expression,
|
|
1105
|
+
expressionAttributeNames: names,
|
|
1106
|
+
expressionAttributeValues: values,
|
|
1107
|
+
returnValues: this.options.returnValues
|
|
216
1108
|
};
|
|
217
1109
|
}
|
|
218
1110
|
/**
|
|
219
|
-
*
|
|
1111
|
+
* Adds this put operation to a transaction.
|
|
1112
|
+
* Use this method when you need to:
|
|
1113
|
+
* - Transfer dinosaurs between habitats
|
|
1114
|
+
* - Initialize new breeding programs
|
|
1115
|
+
* - Update multiple facility records
|
|
1116
|
+
*
|
|
1117
|
+
* @example
|
|
1118
|
+
* ```ts
|
|
1119
|
+
* const transaction = new TransactionBuilder();
|
|
1120
|
+
*
|
|
1121
|
+
* // Add dinosaur to new habitat
|
|
1122
|
+
* new PutBuilder(executor, {
|
|
1123
|
+
* id: 'TREX-002',
|
|
1124
|
+
* location: 'PADDOCK-B',
|
|
1125
|
+
* status: 'ACTIVE',
|
|
1126
|
+
* transferDate: new Date().toISOString()
|
|
1127
|
+
* }, 'dinosaurs')
|
|
1128
|
+
* .withTransaction(transaction);
|
|
1129
|
+
*
|
|
1130
|
+
* // Update habitat records
|
|
1131
|
+
* new UpdateBuilder(executor, 'habitats', { id: 'PADDOCK-B' })
|
|
1132
|
+
* .add('occupants', 1)
|
|
1133
|
+
* .set('lastTransfer', new Date().toISOString())
|
|
1134
|
+
* .withTransaction(transaction);
|
|
1135
|
+
*
|
|
1136
|
+
* // Execute transfer atomically
|
|
1137
|
+
* await transaction.execute();
|
|
1138
|
+
* ```
|
|
1139
|
+
*
|
|
1140
|
+
* @param transaction - The transaction builder to add this operation to
|
|
1141
|
+
* @returns The builder instance for method chaining
|
|
1142
|
+
*/
|
|
1143
|
+
withTransaction(transaction) {
|
|
1144
|
+
const command = this.toDynamoCommand();
|
|
1145
|
+
transaction.putWithCommand(command);
|
|
1146
|
+
return this;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Executes the put operation against DynamoDB.
|
|
1150
|
+
*
|
|
1151
|
+
* @example
|
|
1152
|
+
* ```ts
|
|
1153
|
+
* try {
|
|
1154
|
+
* // Put with condition and return old values
|
|
1155
|
+
* const result = await new PutBuilder(executor, newItem, 'myTable')
|
|
1156
|
+
* .condition(op => op.eq('version', 1))
|
|
1157
|
+
* .returnValues('ALL_OLD')
|
|
1158
|
+
* .execute();
|
|
1159
|
+
*
|
|
1160
|
+
* console.log('Put successful, old item:', result);
|
|
1161
|
+
* } catch (error) {
|
|
1162
|
+
* // Handle condition check failure or other errors
|
|
1163
|
+
* console.error('Put failed:', error);
|
|
1164
|
+
* }
|
|
1165
|
+
* ```
|
|
220
1166
|
*
|
|
221
|
-
* @returns
|
|
1167
|
+
* @returns A promise that resolves to the operation result (type depends on returnValues setting)
|
|
1168
|
+
* @throws Will throw an error if the condition check fails or other DynamoDB errors occur
|
|
222
1169
|
*/
|
|
223
1170
|
async execute() {
|
|
224
|
-
|
|
1171
|
+
const params = this.toDynamoCommand();
|
|
1172
|
+
return this.executor(params);
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Gets a human-readable representation of the put command
|
|
1176
|
+
* with all expression placeholders replaced by their actual values.
|
|
1177
|
+
* Use this method when you need to:
|
|
1178
|
+
* - Debug complex dinosaur transfers
|
|
1179
|
+
* - Verify habitat assignments
|
|
1180
|
+
* - Log security protocols
|
|
1181
|
+
* - Troubleshoot breeding program conditions
|
|
1182
|
+
*
|
|
1183
|
+
* @example
|
|
1184
|
+
* ```ts
|
|
1185
|
+
* const debugInfo = new PutBuilder(executor, {
|
|
1186
|
+
* id: 'RAPTOR-003',
|
|
1187
|
+
* species: 'Velociraptor',
|
|
1188
|
+
* status: 'QUARANTINE',
|
|
1189
|
+
* stats: {
|
|
1190
|
+
* health: 100,
|
|
1191
|
+
* aggressionLevel: 7,
|
|
1192
|
+
* age: 2
|
|
1193
|
+
* }
|
|
1194
|
+
* }, 'dinosaurs')
|
|
1195
|
+
* .condition(op =>
|
|
1196
|
+
* op.and([
|
|
1197
|
+
* op.attributeNotExists('id'),
|
|
1198
|
+
* op.eq('quarantineStatus', 'READY'),
|
|
1199
|
+
* op.gt('securityLevel', 8)
|
|
1200
|
+
* ])
|
|
1201
|
+
* )
|
|
1202
|
+
* .debug();
|
|
1203
|
+
*
|
|
1204
|
+
* console.log('Dinosaur transfer command:', debugInfo);
|
|
1205
|
+
* ```
|
|
1206
|
+
*
|
|
1207
|
+
* @returns A readable representation of the put command with resolved expressions
|
|
1208
|
+
*/
|
|
1209
|
+
debug() {
|
|
1210
|
+
const command = this.toDynamoCommand();
|
|
1211
|
+
return debugCommand(command);
|
|
225
1212
|
}
|
|
226
1213
|
};
|
|
227
1214
|
|
|
228
|
-
// src/builders/
|
|
229
|
-
var
|
|
230
|
-
|
|
231
|
-
|
|
1215
|
+
// src/builders/delete-builder.ts
|
|
1216
|
+
var DeleteBuilder = class {
|
|
1217
|
+
options = {
|
|
1218
|
+
returnValues: "ALL_OLD"
|
|
1219
|
+
};
|
|
1220
|
+
executor;
|
|
1221
|
+
tableName;
|
|
1222
|
+
key;
|
|
1223
|
+
constructor(executor, tableName, key) {
|
|
1224
|
+
this.executor = executor;
|
|
1225
|
+
this.tableName = tableName;
|
|
232
1226
|
this.key = key;
|
|
233
|
-
this.indexConfig = indexConfig;
|
|
234
|
-
this.onBuild = onBuild;
|
|
235
1227
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
1228
|
+
/**
|
|
1229
|
+
* Adds a condition that must be satisfied for the delete operation to succeed.
|
|
1230
|
+
* Use this method when you need to:
|
|
1231
|
+
* - Implement optimistic locking (e.g., version check)
|
|
1232
|
+
* - Ensure item exists before deletion
|
|
1233
|
+
* - Validate item state before deletion
|
|
1234
|
+
*
|
|
1235
|
+
* @example
|
|
1236
|
+
* ```ts
|
|
1237
|
+
* // Simple condition
|
|
1238
|
+
* builder.condition(op => op.attributeExists('id'))
|
|
1239
|
+
*
|
|
1240
|
+
* // Complex condition with version check
|
|
1241
|
+
* builder.condition(op =>
|
|
1242
|
+
* op.and([
|
|
1243
|
+
* op.eq('version', 1),
|
|
1244
|
+
* op.eq('status', 'ACTIVE')
|
|
1245
|
+
* ])
|
|
1246
|
+
* )
|
|
1247
|
+
* ```
|
|
1248
|
+
*
|
|
1249
|
+
* @param condition - Either a Condition object or a callback function that builds the condition
|
|
1250
|
+
* @returns The builder instance for method chaining
|
|
1251
|
+
*/
|
|
1252
|
+
/**
|
|
1253
|
+
* Adds a condition that must be satisfied for the delete operation to succeed.
|
|
1254
|
+
* Use this method when you need to:
|
|
1255
|
+
* - Ensure safe removal conditions
|
|
1256
|
+
* - Verify habitat status before deletion
|
|
1257
|
+
* - Implement safety protocols
|
|
1258
|
+
*
|
|
1259
|
+
* @example
|
|
1260
|
+
* ```typescript
|
|
1261
|
+
* // Ensure dinosaur can be safely removed
|
|
1262
|
+
* builder.condition(op =>
|
|
1263
|
+
* op.and([
|
|
1264
|
+
* op.eq('status', 'SEDATED'),
|
|
1265
|
+
* op.eq('location', 'MEDICAL_BAY'),
|
|
1266
|
+
* op.attributeExists('lastCheckup')
|
|
1267
|
+
* ])
|
|
1268
|
+
* );
|
|
1269
|
+
*
|
|
1270
|
+
* // Verify habitat is empty
|
|
1271
|
+
* builder.condition(op =>
|
|
1272
|
+
* op.and([
|
|
1273
|
+
* op.eq('occupants', 0),
|
|
1274
|
+
* op.eq('maintenanceStatus', 'COMPLETE'),
|
|
1275
|
+
* op.not(op.attributeExists('activeAlerts'))
|
|
1276
|
+
* ])
|
|
1277
|
+
* );
|
|
1278
|
+
* ```
|
|
1279
|
+
*
|
|
1280
|
+
* @param condition - Either a Condition object or a callback function that builds the condition
|
|
1281
|
+
* @returns The builder instance for method chaining
|
|
1282
|
+
*/
|
|
1283
|
+
condition(condition) {
|
|
1284
|
+
if (typeof condition === "function") {
|
|
1285
|
+
const conditionOperator = {
|
|
1286
|
+
eq,
|
|
1287
|
+
ne,
|
|
1288
|
+
lt,
|
|
1289
|
+
lte,
|
|
1290
|
+
gt,
|
|
1291
|
+
gte,
|
|
1292
|
+
between,
|
|
1293
|
+
beginsWith,
|
|
1294
|
+
contains,
|
|
1295
|
+
attributeExists,
|
|
1296
|
+
attributeNotExists,
|
|
1297
|
+
and,
|
|
1298
|
+
or,
|
|
1299
|
+
not
|
|
1300
|
+
};
|
|
1301
|
+
this.options.condition = condition(conditionOperator);
|
|
1302
|
+
} else {
|
|
1303
|
+
this.options.condition = condition;
|
|
1304
|
+
}
|
|
240
1305
|
return this;
|
|
241
1306
|
}
|
|
242
|
-
|
|
243
|
-
|
|
1307
|
+
/**
|
|
1308
|
+
* Sets whether to return the item's attribute values before deletion.
|
|
1309
|
+
* Use this method when you need to:
|
|
1310
|
+
* - Archive removed dinosaur data
|
|
1311
|
+
* - Track habitat decommissioning history
|
|
1312
|
+
* - Maintain removal audit logs
|
|
1313
|
+
*
|
|
1314
|
+
* @example
|
|
1315
|
+
* ```ts
|
|
1316
|
+
* // Archive dinosaur data before removal
|
|
1317
|
+
* const result = await builder
|
|
1318
|
+
* .returnValues('ALL_OLD')
|
|
1319
|
+
* .execute();
|
|
1320
|
+
*
|
|
1321
|
+
* if (result.item) {
|
|
1322
|
+
* console.log('Removed dinosaur data:', {
|
|
1323
|
+
* species: result.item.species,
|
|
1324
|
+
* age: result.item.age,
|
|
1325
|
+
* lastLocation: result.item.location
|
|
1326
|
+
* });
|
|
1327
|
+
* }
|
|
1328
|
+
* ```
|
|
1329
|
+
*
|
|
1330
|
+
* @param returnValues - Use 'ALL_OLD' to return all attributes of the deleted item
|
|
1331
|
+
* @returns The builder instance for method chaining
|
|
1332
|
+
*/
|
|
1333
|
+
returnValues(returnValues) {
|
|
1334
|
+
this.options.returnValues = returnValues;
|
|
244
1335
|
return this;
|
|
245
1336
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
1337
|
+
/**
|
|
1338
|
+
* Generate the DynamoDB command parameters
|
|
1339
|
+
*/
|
|
1340
|
+
toDynamoCommand() {
|
|
1341
|
+
const { expression, names, values } = prepareExpressionParams(this.options.condition);
|
|
249
1342
|
return {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
filter: filter.expression ? {
|
|
257
|
-
expression: filter.expression,
|
|
258
|
-
names: filter.attributes.names,
|
|
259
|
-
values: filter.attributes.values
|
|
260
|
-
} : void 0,
|
|
261
|
-
limit: this.limitValue,
|
|
262
|
-
indexName: this.indexNameValue
|
|
1343
|
+
tableName: this.tableName,
|
|
1344
|
+
key: this.key,
|
|
1345
|
+
conditionExpression: expression,
|
|
1346
|
+
expressionAttributeNames: names,
|
|
1347
|
+
expressionAttributeValues: values,
|
|
1348
|
+
returnValues: this.options.returnValues
|
|
263
1349
|
};
|
|
264
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Adds this delete operation to a transaction.
|
|
1353
|
+
* Use this method when you need to:
|
|
1354
|
+
* - Coordinate dinosaur transfers
|
|
1355
|
+
* - Manage habitat decommissioning
|
|
1356
|
+
* - Handle species relocations
|
|
1357
|
+
*
|
|
1358
|
+
* @example
|
|
1359
|
+
* ```ts
|
|
1360
|
+
* const transaction = new TransactionBuilder();
|
|
1361
|
+
*
|
|
1362
|
+
* // Remove dinosaur from old habitat
|
|
1363
|
+
* new DeleteBuilder(executor, 'dinosaurs', { id: 'RAPTOR-001' })
|
|
1364
|
+
* .condition(op => op.eq('status', 'SEDATED'))
|
|
1365
|
+
* .withTransaction(transaction);
|
|
1366
|
+
*
|
|
1367
|
+
* // Update old habitat occupancy
|
|
1368
|
+
* new UpdateBuilder(executor, 'habitats', { id: 'PADDOCK-A' })
|
|
1369
|
+
* .add('occupants', -1)
|
|
1370
|
+
* .withTransaction(transaction);
|
|
1371
|
+
*
|
|
1372
|
+
* // Execute transfer atomically
|
|
1373
|
+
* await transaction.execute();
|
|
1374
|
+
* ```
|
|
1375
|
+
*
|
|
1376
|
+
* @param transaction - The transaction builder to add this operation to
|
|
1377
|
+
*/
|
|
1378
|
+
withTransaction(transaction) {
|
|
1379
|
+
const command = this.toDynamoCommand();
|
|
1380
|
+
transaction.deleteWithCommand(command);
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Executes the delete operation against DynamoDB.
|
|
1384
|
+
*
|
|
1385
|
+
* @example
|
|
1386
|
+
* ```ts
|
|
1387
|
+
* // Delete with condition and retrieve old values
|
|
1388
|
+
* const result = await new DeleteBuilder(executor, 'myTable', { id: '123' })
|
|
1389
|
+
* .condition(op => op.eq('status', 'INACTIVE'))
|
|
1390
|
+
* .returnValues('ALL_OLD')
|
|
1391
|
+
* .execute();
|
|
1392
|
+
*
|
|
1393
|
+
* if (result.item) {
|
|
1394
|
+
* console.log('Deleted item:', result.item);
|
|
1395
|
+
* }
|
|
1396
|
+
* ```
|
|
1397
|
+
*
|
|
1398
|
+
* @returns A promise that resolves to an object containing the deleted item's attributes (if returnValues is 'ALL_OLD')
|
|
1399
|
+
*/
|
|
265
1400
|
async execute() {
|
|
266
|
-
|
|
1401
|
+
const params = this.toDynamoCommand();
|
|
1402
|
+
return this.executor(params);
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Gets a human-readable representation of the delete command
|
|
1406
|
+
* with all expression placeholders replaced by their actual values.
|
|
1407
|
+
* Use this method when you need to:
|
|
1408
|
+
* - Debug complex deletion conditions
|
|
1409
|
+
* - Verify safety checks
|
|
1410
|
+
* - Log removal operations
|
|
1411
|
+
* - Troubleshoot failed deletions
|
|
1412
|
+
*
|
|
1413
|
+
* @example
|
|
1414
|
+
* ```ts
|
|
1415
|
+
* const debugInfo = new DeleteBuilder(executor, 'dinosaurs', { id: 'TREX-001' })
|
|
1416
|
+
* .condition(op => op.and([
|
|
1417
|
+
* op.eq('status', 'SEDATED'),
|
|
1418
|
+
* op.eq('location', 'MEDICAL_BAY'),
|
|
1419
|
+
* op.gt('sedationLevel', 8)
|
|
1420
|
+
* op.eq('version', 1),
|
|
1421
|
+
* op.attributeExists('status')
|
|
1422
|
+
* ]))
|
|
1423
|
+
* .debug();
|
|
1424
|
+
*
|
|
1425
|
+
* console.log('Delete command:', debugInfo);
|
|
1426
|
+
* ```
|
|
1427
|
+
*
|
|
1428
|
+
* @returns A readable representation of the delete command with resolved expressions
|
|
1429
|
+
*/
|
|
1430
|
+
debug() {
|
|
1431
|
+
const command = this.toDynamoCommand();
|
|
1432
|
+
return debugCommand(command);
|
|
267
1433
|
}
|
|
268
1434
|
};
|
|
269
1435
|
|
|
270
1436
|
// src/builders/update-builder.ts
|
|
271
|
-
var UpdateBuilder = class
|
|
272
|
-
|
|
273
|
-
|
|
1437
|
+
var UpdateBuilder = class {
|
|
1438
|
+
updates = [];
|
|
1439
|
+
options = {
|
|
1440
|
+
returnValues: "ALL_NEW"
|
|
1441
|
+
};
|
|
1442
|
+
executor;
|
|
1443
|
+
tableName;
|
|
1444
|
+
key;
|
|
1445
|
+
constructor(executor, tableName, key) {
|
|
1446
|
+
this.executor = executor;
|
|
1447
|
+
this.tableName = tableName;
|
|
274
1448
|
this.key = key;
|
|
275
|
-
this.onBuild = onBuild;
|
|
276
1449
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
1450
|
+
set(valuesOrPath, value) {
|
|
1451
|
+
if (typeof valuesOrPath === "object") {
|
|
1452
|
+
for (const [key, value2] of Object.entries(valuesOrPath)) {
|
|
1453
|
+
this.updates.push({
|
|
1454
|
+
type: "SET",
|
|
1455
|
+
path: key,
|
|
1456
|
+
value: value2
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
} else {
|
|
1460
|
+
this.updates.push({
|
|
1461
|
+
type: "SET",
|
|
1462
|
+
path: valuesOrPath,
|
|
1463
|
+
value
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
280
1466
|
return this;
|
|
281
1467
|
}
|
|
282
|
-
|
|
283
|
-
|
|
1468
|
+
/**
|
|
1469
|
+
* Removes an attribute from the item.
|
|
1470
|
+
* Use this method when you need to:
|
|
1471
|
+
* - Delete attributes completely
|
|
1472
|
+
* - Remove nested attributes
|
|
1473
|
+
* - Clean up deprecated fields
|
|
1474
|
+
*
|
|
1475
|
+
* @example
|
|
1476
|
+
* ```typescript
|
|
1477
|
+
* // Remove simple attributes
|
|
1478
|
+
* builder
|
|
1479
|
+
* .remove('temporaryTag')
|
|
1480
|
+
* .remove('previousLocation');
|
|
1481
|
+
*
|
|
1482
|
+
* // Remove nested attributes
|
|
1483
|
+
* builder
|
|
1484
|
+
* .remove('metadata.testData')
|
|
1485
|
+
* .remove('stats.experimentalMetrics');
|
|
1486
|
+
* ```
|
|
1487
|
+
*
|
|
1488
|
+
* @param path - The path to the attribute to remove
|
|
1489
|
+
* @returns The builder instance for method chaining
|
|
1490
|
+
*/
|
|
1491
|
+
remove(path) {
|
|
1492
|
+
this.updates.push({
|
|
1493
|
+
type: "REMOVE",
|
|
1494
|
+
path
|
|
1495
|
+
});
|
|
284
1496
|
return this;
|
|
285
1497
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
1498
|
+
/**
|
|
1499
|
+
* Adds a value to a number attribute or adds elements to a set.
|
|
1500
|
+
* Use this method when you need to:
|
|
1501
|
+
* - Increment counters
|
|
1502
|
+
* - Add elements to a set atomically
|
|
1503
|
+
* - Update numerical statistics
|
|
1504
|
+
*
|
|
1505
|
+
* @example
|
|
1506
|
+
* ```typescript
|
|
1507
|
+
* // Increment counters
|
|
1508
|
+
* builder
|
|
1509
|
+
* .add('escapeAttempts', 1)
|
|
1510
|
+
* .add('feedingCount', 1);
|
|
1511
|
+
*
|
|
1512
|
+
* // Add to sets
|
|
1513
|
+
* builder
|
|
1514
|
+
* .add('knownBehaviors', new Set(['PACK_HUNTING', 'AMBUSH_TACTICS']))
|
|
1515
|
+
* .add('visitedZones', new Set(['ZONE_A', 'ZONE_B']));
|
|
1516
|
+
* ```
|
|
1517
|
+
*
|
|
1518
|
+
* @param path - The path to the attribute to update
|
|
1519
|
+
* @param value - The value to add (number or set)
|
|
1520
|
+
* @returns The builder instance for method chaining
|
|
1521
|
+
*/
|
|
1522
|
+
add(path, value) {
|
|
1523
|
+
this.updates.push({
|
|
1524
|
+
type: "ADD",
|
|
1525
|
+
path,
|
|
1526
|
+
value
|
|
1527
|
+
});
|
|
290
1528
|
return this;
|
|
291
1529
|
}
|
|
292
|
-
|
|
293
|
-
|
|
1530
|
+
/**
|
|
1531
|
+
* Removes elements from a set attribute.
|
|
1532
|
+
* Use this method when you need to:
|
|
1533
|
+
* - Remove specific elements from a set
|
|
1534
|
+
* - Update set-based attributes atomically
|
|
1535
|
+
* - Maintain set membership
|
|
1536
|
+
*
|
|
1537
|
+
* @example
|
|
1538
|
+
* ```typescript
|
|
1539
|
+
* // Remove from sets using arrays
|
|
1540
|
+
* builder.deleteElementsFromSet(
|
|
1541
|
+
* 'allowedHabitats',
|
|
1542
|
+
* ['JUNGLE', 'COASTAL']
|
|
1543
|
+
* );
|
|
1544
|
+
*
|
|
1545
|
+
* // Remove from sets using Set objects
|
|
1546
|
+
* builder.deleteElementsFromSet(
|
|
1547
|
+
* 'knownBehaviors',
|
|
1548
|
+
* new Set(['NOCTURNAL', 'TERRITORIAL'])
|
|
1549
|
+
* );
|
|
1550
|
+
*
|
|
1551
|
+
* // Remove from nested sets
|
|
1552
|
+
* builder.deleteElementsFromSet(
|
|
1553
|
+
* 'stats.compatibleSpecies',
|
|
1554
|
+
* ['VELOCIRAPTOR', 'DILOPHOSAURUS']
|
|
1555
|
+
* );
|
|
1556
|
+
* ```
|
|
1557
|
+
*
|
|
1558
|
+
* @param path - The path to the set attribute
|
|
1559
|
+
* @param value - Elements to remove (array or Set)
|
|
1560
|
+
* @returns The builder instance for method chaining
|
|
1561
|
+
*/
|
|
1562
|
+
deleteElementsFromSet(path, value) {
|
|
1563
|
+
let valuesToDelete;
|
|
1564
|
+
if (Array.isArray(value)) {
|
|
1565
|
+
valuesToDelete = new Set(value);
|
|
1566
|
+
} else {
|
|
1567
|
+
valuesToDelete = value;
|
|
1568
|
+
}
|
|
1569
|
+
this.updates.push({
|
|
1570
|
+
type: "DELETE",
|
|
1571
|
+
path,
|
|
1572
|
+
value: valuesToDelete
|
|
1573
|
+
});
|
|
294
1574
|
return this;
|
|
295
1575
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
1576
|
+
/**
|
|
1577
|
+
* Adds a condition that must be satisfied for the update to succeed.
|
|
1578
|
+
* Use this method when you need to:
|
|
1579
|
+
* - Implement optimistic locking
|
|
1580
|
+
* - Ensure item state before update
|
|
1581
|
+
* - Validate business rules
|
|
1582
|
+
* - Prevent concurrent modifications
|
|
1583
|
+
*
|
|
1584
|
+
* @example
|
|
1585
|
+
* ```typescript
|
|
1586
|
+
* // Simple condition
|
|
1587
|
+
* builder.condition(op =>
|
|
1588
|
+
* op.eq('status', 'ACTIVE')
|
|
1589
|
+
* );
|
|
1590
|
+
*
|
|
1591
|
+
* // Health check condition
|
|
1592
|
+
* builder.condition(op =>
|
|
1593
|
+
* op.and([
|
|
1594
|
+
* op.gt('health', 50),
|
|
1595
|
+
* op.eq('status', 'HUNTING')
|
|
1596
|
+
* ])
|
|
1597
|
+
* );
|
|
1598
|
+
*
|
|
1599
|
+
* // Complex security condition
|
|
1600
|
+
* builder.condition(op =>
|
|
1601
|
+
* op.and([
|
|
1602
|
+
* op.attributeExists('securitySystem'),
|
|
1603
|
+
* op.eq('containmentStatus', 'SECURE'),
|
|
1604
|
+
* op.lt('aggressionLevel', 8)
|
|
1605
|
+
* ])
|
|
1606
|
+
* );
|
|
1607
|
+
*
|
|
1608
|
+
* // Version check (optimistic locking)
|
|
1609
|
+
* builder.condition(op =>
|
|
1610
|
+
* op.eq('version', currentVersion)
|
|
1611
|
+
* );
|
|
1612
|
+
* ```
|
|
1613
|
+
*
|
|
1614
|
+
* @param condition - Either a Condition object or a callback function that builds the condition
|
|
1615
|
+
* @returns The builder instance for method chaining
|
|
1616
|
+
*/
|
|
1617
|
+
condition(condition) {
|
|
1618
|
+
if (typeof condition === "function") {
|
|
1619
|
+
const conditionOperator = {
|
|
1620
|
+
eq,
|
|
1621
|
+
ne,
|
|
1622
|
+
lt,
|
|
1623
|
+
lte,
|
|
1624
|
+
gt,
|
|
1625
|
+
gte,
|
|
1626
|
+
between,
|
|
1627
|
+
beginsWith,
|
|
1628
|
+
contains,
|
|
1629
|
+
attributeExists,
|
|
1630
|
+
attributeNotExists,
|
|
1631
|
+
and,
|
|
1632
|
+
or,
|
|
1633
|
+
not
|
|
1634
|
+
};
|
|
1635
|
+
this.options.condition = condition(conditionOperator);
|
|
1636
|
+
} else {
|
|
1637
|
+
this.options.condition = condition;
|
|
328
1638
|
}
|
|
1639
|
+
return this;
|
|
329
1640
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
1641
|
+
/**
|
|
1642
|
+
* Sets which item attributes to include in the response.
|
|
1643
|
+
* Use this method when you need to:
|
|
1644
|
+
* - Get the complete updated item
|
|
1645
|
+
* - Track changes to specific attributes
|
|
1646
|
+
* - Compare old and new values
|
|
1647
|
+
* - Monitor attribute modifications
|
|
1648
|
+
*
|
|
1649
|
+
* Available options:
|
|
1650
|
+
* - ALL_NEW: All attributes after the update
|
|
1651
|
+
* - UPDATED_NEW: Only updated attributes, new values
|
|
1652
|
+
* - ALL_OLD: All attributes before the update
|
|
1653
|
+
* - UPDATED_OLD: Only updated attributes, old values
|
|
1654
|
+
* - NONE: No attributes returned (default)
|
|
1655
|
+
*
|
|
1656
|
+
* @example
|
|
1657
|
+
* ```typescript
|
|
1658
|
+
* // Get complete updated dinosaur
|
|
1659
|
+
* const result = await builder
|
|
1660
|
+
* .set('status', 'SLEEPING')
|
|
1661
|
+
* .returnValues('ALL_NEW')
|
|
1662
|
+
* .execute();
|
|
1663
|
+
*
|
|
1664
|
+
* // Track specific attribute changes
|
|
1665
|
+
* const result = await builder
|
|
1666
|
+
* .set({
|
|
1667
|
+
* 'stats.health': 100,
|
|
1668
|
+
* 'stats.energy': 95
|
|
1669
|
+
* })
|
|
1670
|
+
* .returnValues('UPDATED_OLD')
|
|
1671
|
+
* .execute();
|
|
1672
|
+
*
|
|
1673
|
+
* if (result.item) {
|
|
1674
|
+
* console.log('Previous health:', result.item.stats?.health);
|
|
1675
|
+
* }
|
|
1676
|
+
* ```
|
|
1677
|
+
*
|
|
1678
|
+
* @param returnValues - Which attributes to return in the response
|
|
1679
|
+
* @returns The builder instance for method chaining
|
|
1680
|
+
*/
|
|
1681
|
+
returnValues(returnValues) {
|
|
1682
|
+
this.options.returnValues = returnValues;
|
|
1683
|
+
return this;
|
|
341
1684
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (attributes.names) {
|
|
349
|
-
for (const [alias, name] of Object.entries(attributes.names)) {
|
|
350
|
-
translated = translated.replace(new RegExp(alias, "g"), name);
|
|
1685
|
+
/**
|
|
1686
|
+
* Generate the DynamoDB command parameters
|
|
1687
|
+
*/
|
|
1688
|
+
toDynamoCommand() {
|
|
1689
|
+
if (this.updates.length === 0) {
|
|
1690
|
+
throw new Error("No update actions specified");
|
|
351
1691
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
1692
|
+
const expressionParams = {
|
|
1693
|
+
expressionAttributeNames: {},
|
|
1694
|
+
expressionAttributeValues: {},
|
|
1695
|
+
valueCounter: { count: 0 }
|
|
1696
|
+
};
|
|
1697
|
+
let updateExpression = "";
|
|
1698
|
+
const setUpdates = [];
|
|
1699
|
+
const removeUpdates = [];
|
|
1700
|
+
const addUpdates = [];
|
|
1701
|
+
const deleteUpdates = [];
|
|
1702
|
+
for (const update of this.updates) {
|
|
1703
|
+
switch (update.type) {
|
|
1704
|
+
case "SET":
|
|
1705
|
+
setUpdates.push(update);
|
|
1706
|
+
break;
|
|
1707
|
+
case "REMOVE":
|
|
1708
|
+
removeUpdates.push(update);
|
|
1709
|
+
break;
|
|
1710
|
+
case "ADD":
|
|
1711
|
+
addUpdates.push(update);
|
|
1712
|
+
break;
|
|
1713
|
+
case "DELETE":
|
|
1714
|
+
deleteUpdates.push(update);
|
|
1715
|
+
break;
|
|
1716
|
+
}
|
|
356
1717
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
Condition: ${translateExpression(condition, context.attributes)}`);
|
|
1718
|
+
if (setUpdates.length > 0) {
|
|
1719
|
+
updateExpression += "SET ";
|
|
1720
|
+
updateExpression += setUpdates.map((update) => {
|
|
1721
|
+
const attrName = generateAttributeName(expressionParams, update.path);
|
|
1722
|
+
const valueName = generateValueName(expressionParams, update.value);
|
|
1723
|
+
expressionParams.expressionAttributeValues[valueName] = update.value;
|
|
1724
|
+
return `${attrName} = ${valueName}`;
|
|
1725
|
+
}).join(", ");
|
|
1726
|
+
}
|
|
1727
|
+
if (removeUpdates.length > 0) {
|
|
1728
|
+
if (updateExpression) {
|
|
1729
|
+
updateExpression += " ";
|
|
1730
|
+
}
|
|
1731
|
+
updateExpression += "REMOVE ";
|
|
1732
|
+
updateExpression += removeUpdates.map((update) => {
|
|
1733
|
+
return generateAttributeName(expressionParams, update.path);
|
|
1734
|
+
}).join(", ");
|
|
375
1735
|
}
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
1736
|
+
if (addUpdates.length > 0) {
|
|
1737
|
+
if (updateExpression) {
|
|
1738
|
+
updateExpression += " ";
|
|
1739
|
+
}
|
|
1740
|
+
updateExpression += "ADD ";
|
|
1741
|
+
updateExpression += addUpdates.map((update) => {
|
|
1742
|
+
const attrName = generateAttributeName(expressionParams, update.path);
|
|
1743
|
+
const valueName = generateValueName(expressionParams, update.value);
|
|
1744
|
+
return `${attrName} ${valueName}`;
|
|
1745
|
+
}).join(", ");
|
|
379
1746
|
}
|
|
380
|
-
if (
|
|
381
|
-
|
|
382
|
-
|
|
1747
|
+
if (deleteUpdates.length > 0) {
|
|
1748
|
+
if (updateExpression) {
|
|
1749
|
+
updateExpression += " ";
|
|
1750
|
+
}
|
|
1751
|
+
updateExpression += "DELETE ";
|
|
1752
|
+
updateExpression += deleteUpdates.map((update) => {
|
|
1753
|
+
const attrName = generateAttributeName(expressionParams, update.path);
|
|
1754
|
+
const valueName = generateValueName(expressionParams, update.value);
|
|
1755
|
+
return `${attrName} ${valueName}`;
|
|
1756
|
+
}).join(", ");
|
|
383
1757
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
1758
|
+
let conditionExpression;
|
|
1759
|
+
if (this.options.condition) {
|
|
1760
|
+
conditionExpression = buildExpression(this.options.condition, expressionParams);
|
|
387
1761
|
}
|
|
1762
|
+
const { expressionAttributeNames, expressionAttributeValues } = expressionParams;
|
|
1763
|
+
return {
|
|
1764
|
+
tableName: this.tableName,
|
|
1765
|
+
key: this.key,
|
|
1766
|
+
updateExpression,
|
|
1767
|
+
conditionExpression,
|
|
1768
|
+
expressionAttributeNames: Object.keys(expressionAttributeNames).length > 0 ? expressionAttributeNames : void 0,
|
|
1769
|
+
expressionAttributeValues: Object.keys(expressionAttributeValues).length > 0 ? expressionAttributeValues : void 0,
|
|
1770
|
+
returnValues: this.options.returnValues
|
|
1771
|
+
};
|
|
388
1772
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
1773
|
+
/**
|
|
1774
|
+
* Adds this update operation to a transaction.
|
|
1775
|
+
* Use this method when you need to:
|
|
1776
|
+
* - Update items as part of a larger transaction
|
|
1777
|
+
* - Ensure multiple updates are atomic
|
|
1778
|
+
* - Coordinate updates across multiple items
|
|
1779
|
+
*
|
|
1780
|
+
* @example
|
|
1781
|
+
* ```typescript
|
|
1782
|
+
* const transaction = new TransactionBuilder(executor);
|
|
1783
|
+
*
|
|
1784
|
+
* // Update dinosaur status and habitat occupancy atomically
|
|
1785
|
+
* new UpdateBuilder(executor, 'dinosaurs', { id: 'TREX-001' })
|
|
1786
|
+
* .set('location', 'PADDOCK_A')
|
|
1787
|
+
* .set('status', 'CONTAINED')
|
|
1788
|
+
* .withTransaction(transaction);
|
|
1789
|
+
*
|
|
1790
|
+
* new UpdateBuilder(executor, 'habitats', { id: 'PADDOCK-A' })
|
|
1791
|
+
* .add('occupants', 1)
|
|
1792
|
+
* .set('lastOccupied', new Date().toISOString())
|
|
1793
|
+
* .withTransaction(transaction);
|
|
1794
|
+
*
|
|
1795
|
+
* // Execute all operations atomically
|
|
1796
|
+
* await transaction.execute();
|
|
1797
|
+
* ```
|
|
1798
|
+
*
|
|
1799
|
+
* @param transaction - The transaction builder to add this operation to
|
|
1800
|
+
* @returns The builder instance for method chaining
|
|
1801
|
+
*/
|
|
1802
|
+
withTransaction(transaction) {
|
|
1803
|
+
const command = this.toDynamoCommand();
|
|
1804
|
+
transaction.updateWithCommand(command);
|
|
396
1805
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
1806
|
+
/**
|
|
1807
|
+
* Gets a human-readable representation of the update command.
|
|
1808
|
+
* Use this method when you need to:
|
|
1809
|
+
* - Debug complex update expressions
|
|
1810
|
+
* - Verify attribute names and values
|
|
1811
|
+
* - Log update operations
|
|
1812
|
+
* - Troubleshoot condition expressions
|
|
1813
|
+
*
|
|
1814
|
+
* @example
|
|
1815
|
+
* ```typescript
|
|
1816
|
+
* // Create complex update
|
|
1817
|
+
* const builder = new UpdateBuilder(executor, 'dinosaurs', { id: 'RAPTOR-001' })
|
|
1818
|
+
* .set({
|
|
1819
|
+
* status: 'HUNTING',
|
|
1820
|
+
* 'stats.health': 95,
|
|
1821
|
+
* 'behavior.lastObserved': new Date().toISOString()
|
|
1822
|
+
* })
|
|
1823
|
+
* .add('huntingSuccesses', 1)
|
|
1824
|
+
* .condition(op => op.gt('health', 50));
|
|
1825
|
+
*
|
|
1826
|
+
* // Debug the update
|
|
1827
|
+
* const debugInfo = builder.debug();
|
|
1828
|
+
* console.log('Update operation:', debugInfo);
|
|
1829
|
+
* ```
|
|
1830
|
+
*
|
|
1831
|
+
* @returns A readable representation of the update command with resolved expressions
|
|
1832
|
+
*/
|
|
1833
|
+
debug() {
|
|
1834
|
+
const command = this.toDynamoCommand();
|
|
1835
|
+
return debugCommand(command);
|
|
405
1836
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
1837
|
+
/**
|
|
1838
|
+
* Executes the update operation against DynamoDB.
|
|
1839
|
+
* Use this method when you need to:
|
|
1840
|
+
* - Apply updates immediately
|
|
1841
|
+
* - Get the updated item values
|
|
1842
|
+
* - Handle conditional update failures
|
|
1843
|
+
*
|
|
1844
|
+
* @example
|
|
1845
|
+
* ```typescript
|
|
1846
|
+
* try {
|
|
1847
|
+
* // Update dinosaur status with conditions
|
|
1848
|
+
* const result = await new UpdateBuilder(executor, 'dinosaurs', { id: 'TREX-001' })
|
|
1849
|
+
* .set({
|
|
1850
|
+
* status: 'FEEDING',
|
|
1851
|
+
* lastMeal: new Date().toISOString(),
|
|
1852
|
+
* 'stats.hunger': 0
|
|
1853
|
+
* })
|
|
1854
|
+
* .add('feedingCount', 1)
|
|
1855
|
+
* .condition(op =>
|
|
1856
|
+
* op.and([
|
|
1857
|
+
* op.gt('stats.hunger', 80),
|
|
1858
|
+
* op.eq('status', 'HUNTING')
|
|
1859
|
+
* ])
|
|
1860
|
+
* )
|
|
1861
|
+
* .returnValues('ALL_NEW')
|
|
1862
|
+
* .execute();
|
|
1863
|
+
*
|
|
1864
|
+
* if (result.item) {
|
|
1865
|
+
* console.log('Updated dinosaur:', result.item);
|
|
1866
|
+
* }
|
|
1867
|
+
* } catch (error) {
|
|
1868
|
+
* // Handle condition check failure
|
|
1869
|
+
* console.error('Failed to update dinosaur:', error);
|
|
1870
|
+
* // Check if dinosaur wasn't hungry enough
|
|
1871
|
+
* if (error.name === 'ConditionalCheckFailedException') {
|
|
1872
|
+
* console.log('Dinosaur not ready for feeding');
|
|
1873
|
+
* }
|
|
1874
|
+
* }
|
|
1875
|
+
* ```
|
|
1876
|
+
*
|
|
1877
|
+
* @returns A promise that resolves to an object containing the updated item (if returnValues is set)
|
|
1878
|
+
* @throws {ConditionalCheckFailedException} If the condition check fails
|
|
1879
|
+
* @throws {Error} If the update operation fails for other reasons
|
|
1880
|
+
*/
|
|
1881
|
+
async execute() {
|
|
1882
|
+
const params = this.toDynamoCommand();
|
|
1883
|
+
return this.executor(params);
|
|
436
1884
|
}
|
|
437
1885
|
};
|
|
438
1886
|
|
|
439
|
-
// src/
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
1887
|
+
// src/utils/debug-transaction.ts
|
|
1888
|
+
function debugTransactionItem(item) {
|
|
1889
|
+
const result = {
|
|
1890
|
+
type: item.type,
|
|
1891
|
+
tableName: item.params.tableName
|
|
1892
|
+
};
|
|
1893
|
+
if ("key" in item.params) {
|
|
1894
|
+
result.key = item.params.key;
|
|
1895
|
+
}
|
|
1896
|
+
if (item.type === "Put") {
|
|
1897
|
+
result.item = item.params.item;
|
|
1898
|
+
}
|
|
1899
|
+
switch (item.type) {
|
|
1900
|
+
case "Put":
|
|
1901
|
+
case "Delete":
|
|
1902
|
+
case "ConditionCheck":
|
|
1903
|
+
result.readable = debugCommand(item.params).readable;
|
|
1904
|
+
break;
|
|
1905
|
+
case "Update":
|
|
1906
|
+
result.readable = debugCommand(item.params).readable;
|
|
1907
|
+
break;
|
|
1908
|
+
}
|
|
1909
|
+
return result;
|
|
1910
|
+
}
|
|
1911
|
+
function debugTransaction(items) {
|
|
1912
|
+
return items.map((item) => debugTransactionItem(item));
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// src/builders/transaction-builder.ts
|
|
1916
|
+
var TransactionBuilder = class {
|
|
1917
|
+
items = [];
|
|
1918
|
+
options = {};
|
|
1919
|
+
indexConfig;
|
|
1920
|
+
executor;
|
|
1921
|
+
constructor(executor, indexConfig) {
|
|
1922
|
+
this.executor = executor;
|
|
1923
|
+
this.indexConfig = indexConfig;
|
|
443
1924
|
}
|
|
444
1925
|
/**
|
|
445
|
-
*
|
|
1926
|
+
* Checks if an item with the same primary key already exists in the transaction
|
|
1927
|
+
* @private
|
|
446
1928
|
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
1929
|
+
checkForDuplicateItem(tableName, newItem) {
|
|
1930
|
+
const pkName = this.indexConfig.partitionKey;
|
|
1931
|
+
const skName = this.indexConfig.sortKey || "";
|
|
1932
|
+
const pkValue = newItem[pkName];
|
|
1933
|
+
const skValue = skName ? newItem[skName] : void 0;
|
|
1934
|
+
if (!pkValue) {
|
|
1935
|
+
throw new Error(`Primary key value for '${pkName}' is missing`);
|
|
1936
|
+
}
|
|
1937
|
+
const duplicateItem = this.items.find((item) => {
|
|
1938
|
+
let itemKey;
|
|
1939
|
+
let itemTableName;
|
|
1940
|
+
switch (item.type) {
|
|
1941
|
+
case "Put":
|
|
1942
|
+
itemTableName = item.params.tableName;
|
|
1943
|
+
itemKey = item.params.item;
|
|
1944
|
+
break;
|
|
1945
|
+
case "Update":
|
|
1946
|
+
case "Delete":
|
|
1947
|
+
case "ConditionCheck":
|
|
1948
|
+
itemTableName = item.params.tableName;
|
|
1949
|
+
itemKey = item.params.key;
|
|
1950
|
+
break;
|
|
1951
|
+
}
|
|
1952
|
+
if (itemTableName === tableName && itemKey) {
|
|
1953
|
+
const itemPkValue = itemKey[pkName];
|
|
1954
|
+
const itemSkValue = skName ? itemKey[skName] : void 0;
|
|
1955
|
+
if (itemPkValue === pkValue) {
|
|
1956
|
+
if (skValue === void 0 && itemSkValue === void 0) {
|
|
1957
|
+
return true;
|
|
1958
|
+
}
|
|
1959
|
+
if (skValue !== void 0 && itemSkValue !== void 0 && skValue === itemSkValue) {
|
|
1960
|
+
return true;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
return false;
|
|
1965
|
+
});
|
|
1966
|
+
if (duplicateItem) {
|
|
1967
|
+
throw new Error(
|
|
1968
|
+
`Duplicate item detected in transaction: Table=${tableName}, ${pkName}=${String(pkValue)}, ${skName}=${skValue !== void 0 ? String(skValue) : "undefined"}. DynamoDB transactions do not allow multiple operations on the same item.`
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
454
1971
|
}
|
|
455
1972
|
/**
|
|
456
|
-
*
|
|
1973
|
+
* Adds a put operation to the transaction.
|
|
1974
|
+
* Use this method when you need to:
|
|
1975
|
+
* - Insert new items as part of a transaction
|
|
1976
|
+
* - Replace existing items atomically
|
|
1977
|
+
* - Ensure items meet certain conditions before insertion
|
|
1978
|
+
*
|
|
1979
|
+
* The method automatically checks for duplicate items within the transaction
|
|
1980
|
+
* to prevent multiple operations on the same item.
|
|
1981
|
+
*
|
|
1982
|
+
* @example
|
|
1983
|
+
* ```typescript
|
|
1984
|
+
* // Simple put operation
|
|
1985
|
+
* transaction.put('orders', {
|
|
1986
|
+
* orderId: '123',
|
|
1987
|
+
* status: 'PENDING',
|
|
1988
|
+
* amount: 100
|
|
1989
|
+
* });
|
|
1990
|
+
*
|
|
1991
|
+
* // Conditional put operation
|
|
1992
|
+
* transaction.put(
|
|
1993
|
+
* 'inventory',
|
|
1994
|
+
* { productId: 'ABC', quantity: 50 },
|
|
1995
|
+
* op => op.attributeNotExists('productId')
|
|
1996
|
+
* );
|
|
1997
|
+
*
|
|
1998
|
+
* // Put with complex condition
|
|
1999
|
+
* transaction.put(
|
|
2000
|
+
* 'users',
|
|
2001
|
+
* { userId: '123', status: 'ACTIVE' },
|
|
2002
|
+
* op => op.and([
|
|
2003
|
+
* op.attributeNotExists('userId'),
|
|
2004
|
+
* op.beginsWith('status', 'ACTIVE')
|
|
2005
|
+
* ])
|
|
2006
|
+
* );
|
|
2007
|
+
* ```
|
|
2008
|
+
*
|
|
2009
|
+
* @param tableName - The name of the DynamoDB table
|
|
2010
|
+
* @param item - The item to put into the table
|
|
2011
|
+
* @param condition - Optional condition that must be satisfied
|
|
2012
|
+
* @returns The transaction builder for method chaining
|
|
2013
|
+
* @throws {Error} If a duplicate item is detected in the transaction
|
|
457
2014
|
*/
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
ExpressionAttributeValues: options.condition.values
|
|
2015
|
+
put(tableName, item, condition) {
|
|
2016
|
+
this.checkForDuplicateItem(tableName, item);
|
|
2017
|
+
const transactionItem = {
|
|
2018
|
+
type: "Put",
|
|
2019
|
+
params: {
|
|
2020
|
+
tableName,
|
|
2021
|
+
item
|
|
466
2022
|
}
|
|
467
2023
|
};
|
|
2024
|
+
if (condition) {
|
|
2025
|
+
const { expression, names, values } = prepareExpressionParams(condition);
|
|
2026
|
+
transactionItem.params.conditionExpression = expression;
|
|
2027
|
+
transactionItem.params.expressionAttributeNames = names;
|
|
2028
|
+
transactionItem.params.expressionAttributeValues = values;
|
|
2029
|
+
}
|
|
2030
|
+
this.items.push(transactionItem);
|
|
2031
|
+
return this;
|
|
468
2032
|
}
|
|
469
2033
|
/**
|
|
470
|
-
*
|
|
2034
|
+
* Adds a pre-configured put operation to the transaction.
|
|
2035
|
+
* Use this method when you need to:
|
|
2036
|
+
* - Reuse put commands from PutBuilder
|
|
2037
|
+
* - Add complex put operations with pre-configured parameters
|
|
2038
|
+
* - Integrate with existing put command configurations
|
|
2039
|
+
*
|
|
2040
|
+
* This method is particularly useful when working with PutBuilder
|
|
2041
|
+
* to maintain consistency in put operations across your application.
|
|
2042
|
+
*
|
|
2043
|
+
* @example
|
|
2044
|
+
* ```typescript
|
|
2045
|
+
* // Create a put command with PutBuilder
|
|
2046
|
+
* const putCommand = new PutBuilder(executor, newItem, 'users')
|
|
2047
|
+
* .condition(op => op.attributeNotExists('userId'))
|
|
2048
|
+
* .toDynamoCommand();
|
|
2049
|
+
*
|
|
2050
|
+
* // Add the command to the transaction
|
|
2051
|
+
* transaction.putWithCommand(putCommand);
|
|
2052
|
+
* ```
|
|
2053
|
+
*
|
|
2054
|
+
* @param command - The complete put command configuration
|
|
2055
|
+
* @returns The transaction builder for method chaining
|
|
2056
|
+
* @throws {Error} If a duplicate item is detected in the transaction
|
|
2057
|
+
* @see PutBuilder for creating put commands
|
|
471
2058
|
*/
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
2059
|
+
putWithCommand(command) {
|
|
2060
|
+
this.checkForDuplicateItem(command.tableName, command.item);
|
|
2061
|
+
const transactionItem = {
|
|
2062
|
+
type: "Put",
|
|
2063
|
+
params: command
|
|
477
2064
|
};
|
|
2065
|
+
this.items.push(transactionItem);
|
|
2066
|
+
return this;
|
|
478
2067
|
}
|
|
479
2068
|
/**
|
|
480
|
-
*
|
|
2069
|
+
* Adds a delete operation to the transaction.
|
|
2070
|
+
* Use this method when you need to:
|
|
2071
|
+
* - Remove items as part of a transaction
|
|
2072
|
+
* - Conditionally delete items
|
|
2073
|
+
* - Ensure items exist before deletion
|
|
2074
|
+
*
|
|
2075
|
+
* The method automatically checks for duplicate items within the transaction
|
|
2076
|
+
* to prevent multiple operations on the same item.
|
|
2077
|
+
*
|
|
2078
|
+
* @example
|
|
2079
|
+
* ```typescript
|
|
2080
|
+
* // Simple delete operation
|
|
2081
|
+
* transaction.delete('orders', {
|
|
2082
|
+
* pk: 'ORDER#123',
|
|
2083
|
+
* sk: 'METADATA'
|
|
2084
|
+
* });
|
|
2085
|
+
*
|
|
2086
|
+
* // Conditional delete operation
|
|
2087
|
+
* transaction.delete(
|
|
2088
|
+
* 'users',
|
|
2089
|
+
* { pk: 'USER#123' },
|
|
2090
|
+
* op => op.eq('status', 'INACTIVE')
|
|
2091
|
+
* );
|
|
2092
|
+
*
|
|
2093
|
+
* // Delete with complex condition
|
|
2094
|
+
* transaction.delete(
|
|
2095
|
+
* 'products',
|
|
2096
|
+
* { pk: 'PROD#ABC' },
|
|
2097
|
+
* op => op.and([
|
|
2098
|
+
* op.eq('status', 'DRAFT'),
|
|
2099
|
+
* op.lt('version', 5)
|
|
2100
|
+
* ])
|
|
2101
|
+
* );
|
|
2102
|
+
* ```
|
|
2103
|
+
*
|
|
2104
|
+
* @param tableName - The name of the DynamoDB table
|
|
2105
|
+
* @param key - The primary key of the item to delete
|
|
2106
|
+
* @param condition - Optional condition that must be satisfied
|
|
2107
|
+
* @returns The transaction builder for method chaining
|
|
2108
|
+
* @throws {Error} If a duplicate item is detected in the transaction
|
|
481
2109
|
*/
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
...options.update.values,
|
|
493
|
-
...options.condition?.values
|
|
494
|
-
},
|
|
495
|
-
...options.condition && {
|
|
496
|
-
ConditionExpression: options.condition.expression
|
|
497
|
-
},
|
|
498
|
-
...options.returnValues && {
|
|
499
|
-
ReturnValues: options.returnValues
|
|
2110
|
+
delete(tableName, key, condition) {
|
|
2111
|
+
this.checkForDuplicateItem(tableName, key);
|
|
2112
|
+
const transactionItem = {
|
|
2113
|
+
type: "Delete",
|
|
2114
|
+
params: {
|
|
2115
|
+
tableName,
|
|
2116
|
+
key: {
|
|
2117
|
+
pk: key.pk,
|
|
2118
|
+
sk: key.sk
|
|
2119
|
+
}
|
|
500
2120
|
}
|
|
501
2121
|
};
|
|
2122
|
+
if (condition) {
|
|
2123
|
+
const { expression, names, values } = prepareExpressionParams(condition);
|
|
2124
|
+
transactionItem.params.conditionExpression = expression;
|
|
2125
|
+
transactionItem.params.expressionAttributeNames = names;
|
|
2126
|
+
transactionItem.params.expressionAttributeValues = values;
|
|
2127
|
+
}
|
|
2128
|
+
this.items.push(transactionItem);
|
|
2129
|
+
return this;
|
|
502
2130
|
}
|
|
503
2131
|
/**
|
|
504
|
-
*
|
|
2132
|
+
* Adds a pre-configured delete operation to the transaction.
|
|
2133
|
+
* Use this method when you need to:
|
|
2134
|
+
* - Reuse delete commands from DeleteBuilder
|
|
2135
|
+
* - Add complex delete operations with pre-configured parameters
|
|
2136
|
+
* - Integrate with existing delete command configurations
|
|
2137
|
+
*
|
|
2138
|
+
* This method is particularly useful when working with DeleteBuilder
|
|
2139
|
+
* to maintain consistency in delete operations across your application.
|
|
2140
|
+
*
|
|
2141
|
+
* @example
|
|
2142
|
+
* ```typescript
|
|
2143
|
+
* // Create a delete command with DeleteBuilder
|
|
2144
|
+
* const deleteCommand = new DeleteBuilder(executor, 'users', { pk: 'USER#123' })
|
|
2145
|
+
* .condition(op => op.and([
|
|
2146
|
+
* op.attributeExists('pk'),
|
|
2147
|
+
* op.eq('status', 'INACTIVE')
|
|
2148
|
+
* ]))
|
|
2149
|
+
* .toDynamoCommand();
|
|
2150
|
+
*
|
|
2151
|
+
* // Add the command to the transaction
|
|
2152
|
+
* transaction.deleteWithCommand(deleteCommand);
|
|
2153
|
+
* ```
|
|
2154
|
+
*
|
|
2155
|
+
* @param command - The complete delete command configuration
|
|
2156
|
+
* @returns The transaction builder for method chaining
|
|
2157
|
+
* @throws {Error} If a duplicate item is detected in the transaction
|
|
2158
|
+
* @see DeleteBuilder for creating delete commands
|
|
505
2159
|
*/
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
ConditionExpression: options.condition.expression,
|
|
512
|
-
ExpressionAttributeNames: options.condition.names,
|
|
513
|
-
ExpressionAttributeValues: options.condition.values
|
|
514
|
-
}
|
|
2160
|
+
deleteWithCommand(command) {
|
|
2161
|
+
this.checkForDuplicateItem(command.tableName, command.key);
|
|
2162
|
+
const transactionItem = {
|
|
2163
|
+
type: "Delete",
|
|
2164
|
+
params: command
|
|
515
2165
|
};
|
|
2166
|
+
this.items.push(transactionItem);
|
|
2167
|
+
return this;
|
|
516
2168
|
}
|
|
517
2169
|
/**
|
|
518
|
-
*
|
|
2170
|
+
* Adds an update operation to the transaction.
|
|
2171
|
+
* Use this method when you need to:
|
|
2172
|
+
* - Modify existing items as part of a transaction
|
|
2173
|
+
* - Update multiple attributes atomically
|
|
2174
|
+
* - Apply conditional updates
|
|
2175
|
+
* - Perform complex attribute manipulations
|
|
2176
|
+
*
|
|
2177
|
+
* The method supports all DynamoDB update expressions:
|
|
2178
|
+
* - SET: Modify or add attributes
|
|
2179
|
+
* - REMOVE: Delete attributes
|
|
2180
|
+
* - ADD: Update numbers and sets
|
|
2181
|
+
* - DELETE: Remove elements from a set
|
|
2182
|
+
*
|
|
2183
|
+
* @example
|
|
2184
|
+
* ```typescript
|
|
2185
|
+
* // Simple update
|
|
2186
|
+
* transaction.update(
|
|
2187
|
+
* 'orders',
|
|
2188
|
+
* { pk: 'ORDER#123' },
|
|
2189
|
+
* 'SET #status = :status',
|
|
2190
|
+
* { '#status': 'status' },
|
|
2191
|
+
* { ':status': 'PROCESSING' }
|
|
2192
|
+
* );
|
|
2193
|
+
*
|
|
2194
|
+
* // Complex update with multiple operations
|
|
2195
|
+
* transaction.update(
|
|
2196
|
+
* 'products',
|
|
2197
|
+
* { pk: 'PROD#ABC' },
|
|
2198
|
+
* 'SET #qty = #qty - :amount, #status = :status REMOVE #oldAttr',
|
|
2199
|
+
* { '#qty': 'quantity', '#status': 'status', '#oldAttr': 'deprecated_field' },
|
|
2200
|
+
* { ':amount': 1, ':status': 'LOW_STOCK' }
|
|
2201
|
+
* );
|
|
2202
|
+
*
|
|
2203
|
+
* // Conditional update
|
|
2204
|
+
* transaction.update(
|
|
2205
|
+
* 'users',
|
|
2206
|
+
* { pk: 'USER#123' },
|
|
2207
|
+
* 'SET #lastLogin = :now',
|
|
2208
|
+
* { '#lastLogin': 'lastLoginDate' },
|
|
2209
|
+
* { ':now': new Date().toISOString() },
|
|
2210
|
+
* op => op.attributeExists('pk')
|
|
2211
|
+
* );
|
|
2212
|
+
* ```
|
|
2213
|
+
*
|
|
2214
|
+
* @param tableName - The name of the DynamoDB table
|
|
2215
|
+
* @param key - The primary key of the item to update
|
|
2216
|
+
* @param updateExpression - The update expression (SET, REMOVE, ADD, DELETE)
|
|
2217
|
+
* @param expressionAttributeNames - Map of attribute name placeholders to actual names
|
|
2218
|
+
* @param expressionAttributeValues - Map of value placeholders to actual values
|
|
2219
|
+
* @param condition - Optional condition that must be satisfied
|
|
2220
|
+
* @returns The transaction builder for method chaining
|
|
2221
|
+
* @throws {Error} If a duplicate item is detected in the transaction
|
|
519
2222
|
*/
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
2223
|
+
update(tableName, key, updateExpression, expressionAttributeNames, expressionAttributeValues, condition) {
|
|
2224
|
+
this.checkForDuplicateItem(tableName, key);
|
|
2225
|
+
const transactionItem = {
|
|
2226
|
+
type: "Update",
|
|
2227
|
+
params: {
|
|
2228
|
+
tableName,
|
|
2229
|
+
key: {
|
|
2230
|
+
pk: key.pk,
|
|
2231
|
+
sk: key.sk
|
|
528
2232
|
},
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
},
|
|
534
|
-
...options.filter && {
|
|
535
|
-
FilterExpression: options.filter.expression
|
|
536
|
-
},
|
|
537
|
-
IndexName: options.indexName,
|
|
538
|
-
Limit: options.limit,
|
|
539
|
-
ExclusiveStartKey: options.pageKey,
|
|
540
|
-
ConsistentRead: options.consistentRead
|
|
2233
|
+
updateExpression,
|
|
2234
|
+
expressionAttributeNames,
|
|
2235
|
+
expressionAttributeValues
|
|
2236
|
+
}
|
|
541
2237
|
};
|
|
2238
|
+
if (condition) {
|
|
2239
|
+
const { expression, names, values } = prepareExpressionParams(condition);
|
|
2240
|
+
transactionItem.params.conditionExpression = expression;
|
|
2241
|
+
transactionItem.params.expressionAttributeNames = {
|
|
2242
|
+
...transactionItem.params.expressionAttributeNames,
|
|
2243
|
+
...names
|
|
2244
|
+
};
|
|
2245
|
+
transactionItem.params.expressionAttributeValues = {
|
|
2246
|
+
...transactionItem.params.expressionAttributeValues,
|
|
2247
|
+
...values
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
this.items.push(transactionItem);
|
|
2251
|
+
return this;
|
|
542
2252
|
}
|
|
543
2253
|
/**
|
|
544
|
-
*
|
|
2254
|
+
* Adds a pre-configured update operation to the transaction.
|
|
2255
|
+
* Use this method when you need to:
|
|
2256
|
+
* - Reuse update commands from UpdateBuilder
|
|
2257
|
+
* - Add complex update operations with pre-configured parameters
|
|
2258
|
+
* - Integrate with existing update command configurations
|
|
2259
|
+
*
|
|
2260
|
+
* This method is particularly useful when working with UpdateBuilder
|
|
2261
|
+
* to maintain consistency in update operations across your application.
|
|
2262
|
+
*
|
|
2263
|
+
* @example
|
|
2264
|
+
* ```typescript
|
|
2265
|
+
* // Create an update command with UpdateBuilder
|
|
2266
|
+
* const updateCommand = new UpdateBuilder(executor, 'inventory', { pk: 'PROD#ABC' })
|
|
2267
|
+
* .set('quantity', ':qty')
|
|
2268
|
+
* .set('lastUpdated', ':now')
|
|
2269
|
+
* .values({
|
|
2270
|
+
* ':qty': 100,
|
|
2271
|
+
* ':now': new Date().toISOString()
|
|
2272
|
+
* })
|
|
2273
|
+
* .condition(op => op.gt('quantity', 0))
|
|
2274
|
+
* .toDynamoCommand();
|
|
2275
|
+
*
|
|
2276
|
+
* // Add the command to the transaction
|
|
2277
|
+
* transaction.updateWithCommand(updateCommand);
|
|
2278
|
+
* ```
|
|
2279
|
+
*
|
|
2280
|
+
* @param command - The complete update command configuration
|
|
2281
|
+
* @returns The transaction builder for method chaining
|
|
2282
|
+
* @throws {Error} If a duplicate item is detected in the transaction
|
|
2283
|
+
* @see UpdateBuilder for creating update commands
|
|
545
2284
|
*/
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
ExpressionAttributeNames: options.filter.names,
|
|
552
|
-
ExpressionAttributeValues: options.filter.values
|
|
553
|
-
},
|
|
554
|
-
IndexName: options.indexName,
|
|
555
|
-
Limit: options.limit,
|
|
556
|
-
ExclusiveStartKey: options.pageKey
|
|
2285
|
+
updateWithCommand(command) {
|
|
2286
|
+
this.checkForDuplicateItem(command.tableName, command.key);
|
|
2287
|
+
const transactionItem = {
|
|
2288
|
+
type: "Update",
|
|
2289
|
+
params: command
|
|
557
2290
|
};
|
|
2291
|
+
this.items.push(transactionItem);
|
|
2292
|
+
return this;
|
|
558
2293
|
}
|
|
559
2294
|
/**
|
|
560
|
-
*
|
|
2295
|
+
* Adds a condition check operation to the transaction.
|
|
2296
|
+
* Use this method when you need to:
|
|
2297
|
+
* - Validate item state without modifying it
|
|
2298
|
+
* - Ensure data consistency across tables
|
|
2299
|
+
* - Implement complex business rules
|
|
2300
|
+
* - Verify preconditions for other operations
|
|
2301
|
+
*
|
|
2302
|
+
* Condition checks are particularly useful for:
|
|
2303
|
+
* - Implementing optimistic locking
|
|
2304
|
+
* - Ensuring referential integrity
|
|
2305
|
+
* - Validating business rules atomically
|
|
2306
|
+
*
|
|
2307
|
+
* @example
|
|
2308
|
+
* ```typescript
|
|
2309
|
+
* // Check if order is in correct state
|
|
2310
|
+
* transaction.conditionCheck(
|
|
2311
|
+
* 'orders',
|
|
2312
|
+
* { pk: 'ORDER#123' },
|
|
2313
|
+
* op => op.eq('status', 'PENDING')
|
|
2314
|
+
* );
|
|
2315
|
+
*
|
|
2316
|
+
* // Complex condition check
|
|
2317
|
+
* transaction.conditionCheck(
|
|
2318
|
+
* 'inventory',
|
|
2319
|
+
* { pk: 'PROD#ABC' },
|
|
2320
|
+
* op => op.and([
|
|
2321
|
+
* op.gt('quantity', 0),
|
|
2322
|
+
* op.eq('status', 'ACTIVE'),
|
|
2323
|
+
* op.attributeExists('lastRestockDate')
|
|
2324
|
+
* ])
|
|
2325
|
+
* );
|
|
2326
|
+
*
|
|
2327
|
+
* // Check with multiple attributes
|
|
2328
|
+
* transaction.conditionCheck(
|
|
2329
|
+
* 'users',
|
|
2330
|
+
* { pk: 'USER#123' },
|
|
2331
|
+
* op => op.or([
|
|
2332
|
+
* op.eq('status', 'PREMIUM'),
|
|
2333
|
+
* op.gte('credits', 100)
|
|
2334
|
+
* ])
|
|
2335
|
+
* );
|
|
2336
|
+
* ```
|
|
2337
|
+
*
|
|
2338
|
+
* @param tableName - The name of the DynamoDB table
|
|
2339
|
+
* @param key - The primary key of the item to check
|
|
2340
|
+
* @param condition - The condition that must be satisfied
|
|
2341
|
+
* @returns The transaction builder for method chaining
|
|
2342
|
+
* @throws {Error} If a duplicate item is detected in the transaction
|
|
2343
|
+
* @throws {Error} If condition expression generation fails
|
|
561
2344
|
*/
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
});
|
|
580
|
-
return {
|
|
581
|
-
RequestItems: {
|
|
582
|
-
[this.tableName]: requests
|
|
2345
|
+
conditionCheck(tableName, key, condition) {
|
|
2346
|
+
this.checkForDuplicateItem(tableName, key);
|
|
2347
|
+
const { expression, names, values } = prepareExpressionParams(condition);
|
|
2348
|
+
if (!expression) {
|
|
2349
|
+
throw new Error("Failed to generate condition expression");
|
|
2350
|
+
}
|
|
2351
|
+
const transactionItem = {
|
|
2352
|
+
type: "ConditionCheck",
|
|
2353
|
+
params: {
|
|
2354
|
+
tableName,
|
|
2355
|
+
key: {
|
|
2356
|
+
pk: key.pk,
|
|
2357
|
+
sk: key.sk
|
|
2358
|
+
},
|
|
2359
|
+
conditionExpression: expression,
|
|
2360
|
+
expressionAttributeNames: names,
|
|
2361
|
+
expressionAttributeValues: values
|
|
583
2362
|
}
|
|
584
2363
|
};
|
|
2364
|
+
this.items.push(transactionItem);
|
|
2365
|
+
return this;
|
|
585
2366
|
}
|
|
586
2367
|
/**
|
|
587
|
-
*
|
|
2368
|
+
* Adds a pre-configured condition check operation to the transaction.
|
|
2369
|
+
* Use this method when you need to:
|
|
2370
|
+
* - Reuse condition checks from ConditionCheckBuilder
|
|
2371
|
+
* - Add complex condition checks with pre-configured parameters
|
|
2372
|
+
* - Integrate with existing condition check configurations
|
|
2373
|
+
*
|
|
2374
|
+
* This method is particularly useful when working with ConditionCheckBuilder
|
|
2375
|
+
* to maintain consistency in condition checks across your application.
|
|
2376
|
+
*
|
|
2377
|
+
* @example
|
|
2378
|
+
* ```typescript
|
|
2379
|
+
* // Create a condition check with ConditionCheckBuilder
|
|
2380
|
+
* const checkCommand = new ConditionCheckBuilder('inventory', { pk: 'PROD#ABC' })
|
|
2381
|
+
* .condition(op => op.and([
|
|
2382
|
+
* op.between('quantity', 10, 100),
|
|
2383
|
+
* op.beginsWith('category', 'ELECTRONICS'),
|
|
2384
|
+
* op.attributeExists('lastAuditDate')
|
|
2385
|
+
* ]))
|
|
2386
|
+
* .toDynamoCommand();
|
|
2387
|
+
*
|
|
2388
|
+
* // Add the command to the transaction
|
|
2389
|
+
* transaction.conditionCheckWithCommand(checkCommand);
|
|
2390
|
+
* ```
|
|
2391
|
+
*
|
|
2392
|
+
* @param command - The complete condition check command configuration
|
|
2393
|
+
* @returns The transaction builder for method chaining
|
|
2394
|
+
* @throws {Error} If a duplicate item is detected in the transaction
|
|
2395
|
+
* @see ConditionCheckBuilder for creating condition check commands
|
|
588
2396
|
*/
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
2397
|
+
conditionCheckWithCommand(command) {
|
|
2398
|
+
this.checkForDuplicateItem(command.tableName, command.key);
|
|
2399
|
+
const transactionItem = {
|
|
2400
|
+
type: "ConditionCheck",
|
|
2401
|
+
params: command
|
|
2402
|
+
};
|
|
2403
|
+
this.items.push(transactionItem);
|
|
2404
|
+
return this;
|
|
2405
|
+
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Sets options for the transaction execution.
|
|
2408
|
+
* Use this method when you need to:
|
|
2409
|
+
* - Enable idempotent transactions
|
|
2410
|
+
* - Track consumed capacity
|
|
2411
|
+
* - Monitor item collection metrics
|
|
2412
|
+
*
|
|
2413
|
+
* @example
|
|
2414
|
+
* ```typescript
|
|
2415
|
+
* // Enable idempotency and capacity tracking
|
|
2416
|
+
* transaction.withOptions({
|
|
2417
|
+
* clientRequestToken: 'unique-request-id-123',
|
|
2418
|
+
* returnConsumedCapacity: 'TOTAL'
|
|
2419
|
+
* });
|
|
2420
|
+
*
|
|
2421
|
+
* // Track item collection metrics
|
|
2422
|
+
* transaction.withOptions({
|
|
2423
|
+
* returnItemCollectionMetrics: 'SIZE'
|
|
2424
|
+
* });
|
|
2425
|
+
* ```
|
|
2426
|
+
*
|
|
2427
|
+
* Note: ClientRequestToken can be used to make transactions idempotent,
|
|
2428
|
+
* ensuring the same transaction is not executed multiple times.
|
|
2429
|
+
*
|
|
2430
|
+
* @param options - Configuration options for the transaction
|
|
2431
|
+
* @returns The transaction builder for method chaining
|
|
2432
|
+
*/
|
|
2433
|
+
withOptions(options) {
|
|
2434
|
+
this.options = { ...this.options, ...options };
|
|
2435
|
+
return this;
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Gets a human-readable representation of the transaction items.
|
|
2439
|
+
* Use this method when you need to:
|
|
2440
|
+
* - Debug complex transactions
|
|
2441
|
+
* - Verify operation parameters
|
|
2442
|
+
* - Log transaction details
|
|
2443
|
+
* - Troubleshoot condition expressions
|
|
2444
|
+
*
|
|
2445
|
+
* The method resolves all expression placeholders with their actual values,
|
|
2446
|
+
* making it easier to understand the transaction's operations.
|
|
2447
|
+
*
|
|
2448
|
+
* @example
|
|
2449
|
+
* ```typescript
|
|
2450
|
+
* // Add multiple operations
|
|
2451
|
+
* transaction
|
|
2452
|
+
* .put('orders', { orderId: '123', status: 'PENDING' })
|
|
2453
|
+
* .update('inventory',
|
|
2454
|
+
* { productId: 'ABC' },
|
|
2455
|
+
* 'SET quantity = quantity - :amount',
|
|
2456
|
+
* undefined,
|
|
2457
|
+
* { ':amount': 1 }
|
|
2458
|
+
* );
|
|
2459
|
+
*
|
|
2460
|
+
* // Debug the transaction
|
|
2461
|
+
* const debugInfo = transaction.debug();
|
|
2462
|
+
* console.log('Transaction operations:', debugInfo);
|
|
2463
|
+
* ```
|
|
2464
|
+
*
|
|
2465
|
+
* @returns An array of readable representations of the transaction items
|
|
2466
|
+
*/
|
|
2467
|
+
debug() {
|
|
2468
|
+
return debugTransaction(this.items);
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Executes all operations in the transaction atomically.
|
|
2472
|
+
* Use this method when you need to:
|
|
2473
|
+
* - Perform multiple operations atomically
|
|
2474
|
+
* - Ensure all-or-nothing execution
|
|
2475
|
+
* - Maintain data consistency across operations
|
|
2476
|
+
*
|
|
2477
|
+
* The transaction will only succeed if all operations succeed.
|
|
2478
|
+
* If any operation fails, the entire transaction is rolled back.
|
|
2479
|
+
*
|
|
2480
|
+
* @example
|
|
2481
|
+
* ```typescript
|
|
2482
|
+
* try {
|
|
2483
|
+
* // Build and execute transaction
|
|
2484
|
+
* await transaction
|
|
2485
|
+
* .put('orders', newOrder)
|
|
2486
|
+
* .update('inventory',
|
|
2487
|
+
* { productId: 'ABC' },
|
|
2488
|
+
* 'SET quantity = quantity - :qty',
|
|
2489
|
+
* undefined,
|
|
2490
|
+
* { ':qty': 1 }
|
|
2491
|
+
* )
|
|
2492
|
+
* .conditionCheck('products',
|
|
2493
|
+
* { productId: 'ABC' },
|
|
2494
|
+
* op => op.eq('status', 'ACTIVE')
|
|
2495
|
+
* )
|
|
2496
|
+
* .execute();
|
|
2497
|
+
*
|
|
2498
|
+
* console.log('Transaction completed successfully');
|
|
2499
|
+
* } catch (error) {
|
|
2500
|
+
* // Handle transaction failure
|
|
2501
|
+
* console.error('Transaction failed:', error);
|
|
2502
|
+
* }
|
|
2503
|
+
* ```
|
|
2504
|
+
*
|
|
2505
|
+
* @throws {Error} If no transaction items are specified
|
|
2506
|
+
* @throws {Error} If any operation in the transaction fails
|
|
2507
|
+
* @returns A promise that resolves when the transaction completes
|
|
2508
|
+
*/
|
|
2509
|
+
async execute() {
|
|
2510
|
+
if (this.items.length === 0) {
|
|
2511
|
+
throw new Error("No transaction items specified");
|
|
2512
|
+
}
|
|
2513
|
+
const transactItems = this.items.map((item) => {
|
|
2514
|
+
switch (item.type) {
|
|
2515
|
+
case "Put":
|
|
593
2516
|
return {
|
|
594
2517
|
Put: {
|
|
595
|
-
TableName:
|
|
596
|
-
Item: item.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
ExpressionAttributeValues: item.put.condition.values
|
|
601
|
-
}
|
|
2518
|
+
TableName: item.params.tableName,
|
|
2519
|
+
Item: item.params.item,
|
|
2520
|
+
ConditionExpression: item.params.conditionExpression,
|
|
2521
|
+
ExpressionAttributeNames: item.params.expressionAttributeNames,
|
|
2522
|
+
ExpressionAttributeValues: item.params.expressionAttributeValues
|
|
602
2523
|
}
|
|
603
2524
|
};
|
|
604
|
-
|
|
605
|
-
if (item.delete) {
|
|
2525
|
+
case "Delete":
|
|
606
2526
|
return {
|
|
607
2527
|
Delete: {
|
|
608
|
-
TableName:
|
|
609
|
-
Key: item.
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
ExpressionAttributeValues: item.delete.condition.values
|
|
614
|
-
}
|
|
2528
|
+
TableName: item.params.tableName,
|
|
2529
|
+
Key: item.params.key,
|
|
2530
|
+
ConditionExpression: item.params.conditionExpression,
|
|
2531
|
+
ExpressionAttributeNames: item.params.expressionAttributeNames,
|
|
2532
|
+
ExpressionAttributeValues: item.params.expressionAttributeValues
|
|
615
2533
|
}
|
|
616
2534
|
};
|
|
617
|
-
|
|
618
|
-
if (item.update) {
|
|
2535
|
+
case "Update":
|
|
619
2536
|
return {
|
|
620
2537
|
Update: {
|
|
621
|
-
TableName:
|
|
622
|
-
Key: item.
|
|
623
|
-
UpdateExpression: item.
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
2538
|
+
TableName: item.params.tableName,
|
|
2539
|
+
Key: item.params.key,
|
|
2540
|
+
UpdateExpression: item.params.updateExpression,
|
|
2541
|
+
ConditionExpression: item.params.conditionExpression,
|
|
2542
|
+
ExpressionAttributeNames: item.params.expressionAttributeNames,
|
|
2543
|
+
ExpressionAttributeValues: item.params.expressionAttributeValues
|
|
2544
|
+
}
|
|
2545
|
+
};
|
|
2546
|
+
case "ConditionCheck":
|
|
2547
|
+
return {
|
|
2548
|
+
ConditionCheck: {
|
|
2549
|
+
TableName: item.params.tableName,
|
|
2550
|
+
Key: item.params.key,
|
|
2551
|
+
ConditionExpression: item.params.conditionExpression,
|
|
2552
|
+
ExpressionAttributeNames: item.params.expressionAttributeNames,
|
|
2553
|
+
ExpressionAttributeValues: item.params.expressionAttributeValues
|
|
635
2554
|
}
|
|
636
2555
|
};
|
|
2556
|
+
default: {
|
|
2557
|
+
const exhaustiveCheck = item;
|
|
2558
|
+
throw new Error(`Unsupported transaction item type: ${String(exhaustiveCheck)}`);
|
|
637
2559
|
}
|
|
638
|
-
throw new Error("Invalid transaction item");
|
|
639
|
-
})
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Convert DynamoDB batch write response to our format
|
|
644
|
-
*/
|
|
645
|
-
fromBatchWriteResponse(response) {
|
|
646
|
-
return response.map((item) => {
|
|
647
|
-
if ("PutRequest" in item) {
|
|
648
|
-
return {
|
|
649
|
-
put: item.PutRequest.Item
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
if ("DeleteRequest" in item) {
|
|
653
|
-
return {
|
|
654
|
-
delete: item.DeleteRequest.Key
|
|
655
|
-
};
|
|
656
2560
|
}
|
|
657
|
-
throw new Error("Invalid batch write response item");
|
|
658
2561
|
});
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
var DynamoService = class {
|
|
666
|
-
constructor(client, tableName) {
|
|
667
|
-
this.client = client;
|
|
668
|
-
this.tableName = tableName;
|
|
669
|
-
this.converter = new DynamoConverter(tableName);
|
|
670
|
-
}
|
|
671
|
-
converter;
|
|
672
|
-
async put(options) {
|
|
673
|
-
try {
|
|
674
|
-
const params = this.converter.toPutCommand(options);
|
|
675
|
-
return await this.withRetry(async () => {
|
|
676
|
-
await this.client.put(params);
|
|
677
|
-
return options.item;
|
|
678
|
-
});
|
|
679
|
-
} catch (error) {
|
|
680
|
-
handleDynamoError(error, {
|
|
681
|
-
operation: "PUT",
|
|
682
|
-
tableName: this.tableName,
|
|
683
|
-
key: options.item,
|
|
684
|
-
expression: {
|
|
685
|
-
condition: options.condition?.expression
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
async update(options) {
|
|
691
|
-
try {
|
|
692
|
-
const params = this.converter.toUpdateCommand(options);
|
|
693
|
-
return await this.withRetry(() => this.client.update(params));
|
|
694
|
-
} catch (error) {
|
|
695
|
-
handleDynamoError(error, {
|
|
696
|
-
operation: "UPDATE",
|
|
697
|
-
tableName: this.tableName,
|
|
698
|
-
key: options.key,
|
|
699
|
-
expression: {
|
|
700
|
-
update: options.update.expression,
|
|
701
|
-
condition: options.condition?.expression
|
|
702
|
-
}
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
async delete(options) {
|
|
707
|
-
const params = this.converter.toDeleteCommand(options);
|
|
2562
|
+
const params = {
|
|
2563
|
+
TransactItems: transactItems,
|
|
2564
|
+
ClientRequestToken: this.options.clientRequestToken,
|
|
2565
|
+
ReturnConsumedCapacity: this.options.returnConsumedCapacity,
|
|
2566
|
+
ReturnItemCollectionMetrics: this.options.returnItemCollectionMetrics
|
|
2567
|
+
};
|
|
708
2568
|
try {
|
|
709
|
-
|
|
2569
|
+
await this.executor(params);
|
|
710
2570
|
} catch (error) {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
key: options.key,
|
|
715
|
-
expression: {
|
|
716
|
-
condition: params.ConditionExpression
|
|
717
|
-
}
|
|
718
|
-
});
|
|
2571
|
+
console.log(this.debug());
|
|
2572
|
+
console.error("Error executing transaction:", error);
|
|
2573
|
+
throw error;
|
|
719
2574
|
}
|
|
720
2575
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
operation: "GET",
|
|
728
|
-
tableName: this.tableName,
|
|
729
|
-
key
|
|
730
|
-
});
|
|
731
|
-
}
|
|
2576
|
+
};
|
|
2577
|
+
|
|
2578
|
+
// src/utils/chunk-array.ts
|
|
2579
|
+
function* chunkArray(array, size) {
|
|
2580
|
+
if (size <= 0) {
|
|
2581
|
+
throw new Error("Chunk size must be greater than 0");
|
|
732
2582
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
if (options.autoPaginate) {
|
|
736
|
-
return await this.executeWithAutoPagination(options);
|
|
737
|
-
}
|
|
738
|
-
const params = this.converter.toQueryCommand(options);
|
|
739
|
-
return await this.withRetry(() => this.client.query(params));
|
|
740
|
-
} catch (error) {
|
|
741
|
-
handleDynamoError(error, {
|
|
742
|
-
operation: "QUERY",
|
|
743
|
-
tableName: this.tableName,
|
|
744
|
-
expression: {
|
|
745
|
-
keyCondition: options.keyCondition?.expression,
|
|
746
|
-
filter: options.filter?.expression
|
|
747
|
-
}
|
|
748
|
-
});
|
|
749
|
-
}
|
|
2583
|
+
for (let i = 0; i < array.length; i += size) {
|
|
2584
|
+
yield array.slice(i, i + size);
|
|
750
2585
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
}
|
|
762
|
-
});
|
|
763
|
-
}
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// src/builders/condition-check-builder.ts
|
|
2589
|
+
var ConditionCheckBuilder = class {
|
|
2590
|
+
key;
|
|
2591
|
+
tableName;
|
|
2592
|
+
conditionExpression;
|
|
2593
|
+
constructor(tableName, key) {
|
|
2594
|
+
this.tableName = tableName;
|
|
2595
|
+
this.key = key;
|
|
764
2596
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
2597
|
+
/**
|
|
2598
|
+
* Adds a condition that must be satisfied for the check to succeed.
|
|
2599
|
+
* Use this method when you need to:
|
|
2600
|
+
* - Validate complex item states
|
|
2601
|
+
* - Check multiple attributes together
|
|
2602
|
+
* - Ensure safety conditions are met
|
|
2603
|
+
*
|
|
2604
|
+
* @example
|
|
2605
|
+
* ```typescript
|
|
2606
|
+
* // Check dinosaur health and behavior
|
|
2607
|
+
* builder.condition(op =>
|
|
2608
|
+
* op.and([
|
|
2609
|
+
* op.gt('stats.health', 50),
|
|
2610
|
+
* op.not(op.eq('status', 'SEDATED')),
|
|
2611
|
+
* op.lt('aggressionLevel', 8)
|
|
2612
|
+
* ])
|
|
2613
|
+
* );
|
|
2614
|
+
*
|
|
2615
|
+
* // Verify habitat conditions
|
|
2616
|
+
* builder.condition(op =>
|
|
2617
|
+
* op.and([
|
|
2618
|
+
* op.eq('powerStatus', 'ONLINE'),
|
|
2619
|
+
* op.between('temperature', 20, 30),
|
|
2620
|
+
* op.attributeExists('lastMaintenance')
|
|
2621
|
+
* ])
|
|
2622
|
+
* );
|
|
2623
|
+
*
|
|
2624
|
+
* // Check breeding conditions
|
|
2625
|
+
* builder.condition(op =>
|
|
2626
|
+
* op.and([
|
|
2627
|
+
* op.eq('species', 'VELOCIRAPTOR'),
|
|
2628
|
+
* op.gte('age', 3),
|
|
2629
|
+
* op.eq('geneticPurity', 100)
|
|
2630
|
+
* ])
|
|
2631
|
+
* );
|
|
2632
|
+
* ```
|
|
2633
|
+
*
|
|
2634
|
+
* @param condition - Either a Condition object or a callback function that builds the condition
|
|
2635
|
+
* @returns The builder instance for method chaining
|
|
2636
|
+
*/
|
|
2637
|
+
condition(condition) {
|
|
2638
|
+
if (typeof condition === "function") {
|
|
2639
|
+
const conditionOperator = {
|
|
2640
|
+
eq,
|
|
2641
|
+
ne,
|
|
2642
|
+
lt,
|
|
2643
|
+
lte,
|
|
2644
|
+
gt,
|
|
2645
|
+
gte,
|
|
2646
|
+
between,
|
|
2647
|
+
beginsWith,
|
|
2648
|
+
contains,
|
|
2649
|
+
attributeExists,
|
|
2650
|
+
attributeNotExists,
|
|
2651
|
+
and,
|
|
2652
|
+
or,
|
|
2653
|
+
not
|
|
2654
|
+
};
|
|
2655
|
+
this.conditionExpression = condition(conditionOperator);
|
|
2656
|
+
} else {
|
|
2657
|
+
this.conditionExpression = condition;
|
|
774
2658
|
}
|
|
2659
|
+
return this;
|
|
775
2660
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
2661
|
+
/**
|
|
2662
|
+
* Generates the DynamoDB command parameters for direct execution.
|
|
2663
|
+
* Use this method when you want to:
|
|
2664
|
+
* - Execute the condition check as a standalone operation
|
|
2665
|
+
* - Get the raw DynamoDB command for custom execution
|
|
2666
|
+
* - Inspect the generated command parameters
|
|
2667
|
+
*
|
|
2668
|
+
* @example
|
|
2669
|
+
* ```ts
|
|
2670
|
+
* const command = new ConditionCheckBuilder('myTable', { id: '123' })
|
|
2671
|
+
* .condition(op => op.attributeExists('status'))
|
|
2672
|
+
* .toDynamoCommand();
|
|
2673
|
+
* // Use command with DynamoDB client
|
|
2674
|
+
* ```
|
|
2675
|
+
*
|
|
2676
|
+
* @throws {Error} If no condition has been set
|
|
2677
|
+
* @returns The DynamoDB command parameters
|
|
2678
|
+
*/
|
|
2679
|
+
toDynamoCommand() {
|
|
2680
|
+
if (!this.conditionExpression) {
|
|
2681
|
+
throw new Error("Condition is required for condition check operations");
|
|
779
2682
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
} catch (error) {
|
|
784
|
-
handleDynamoError(error, {
|
|
785
|
-
operation: "TRANSACT_WRITE",
|
|
786
|
-
tableName: this.tableName
|
|
787
|
-
});
|
|
2683
|
+
const { expression, names, values } = prepareExpressionParams(this.conditionExpression);
|
|
2684
|
+
if (!expression) {
|
|
2685
|
+
throw new Error("Failed to generate condition expression");
|
|
788
2686
|
}
|
|
789
|
-
}
|
|
790
|
-
async executeWithAutoPagination(options) {
|
|
791
|
-
const allItems = [];
|
|
792
|
-
let lastEvaluatedKey;
|
|
793
|
-
do {
|
|
794
|
-
const result = await this.query({
|
|
795
|
-
...options,
|
|
796
|
-
pageKey: lastEvaluatedKey,
|
|
797
|
-
autoPaginate: false
|
|
798
|
-
});
|
|
799
|
-
if (result.Items) {
|
|
800
|
-
allItems.push(...result.Items);
|
|
801
|
-
}
|
|
802
|
-
lastEvaluatedKey = result.LastEvaluatedKey;
|
|
803
|
-
} while (lastEvaluatedKey);
|
|
804
2687
|
return {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
811
|
-
async processBatchWrite(items) {
|
|
812
|
-
const processUnprocessedItems = async (unprocessedItems2) => {
|
|
813
|
-
const params2 = this.converter.toBatchWriteCommand(unprocessedItems2);
|
|
814
|
-
const result = await this.client.batchWrite(params2);
|
|
815
|
-
const outstandingItems = result.UnprocessedItems?.[this.tableName];
|
|
816
|
-
if (outstandingItems && outstandingItems.length > 0) {
|
|
817
|
-
const remainingItems = this.converter.fromBatchWriteResponse(outstandingItems);
|
|
818
|
-
throw {
|
|
819
|
-
name: "UnprocessedItemsError",
|
|
820
|
-
unprocessedItems: remainingItems
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
return result;
|
|
2688
|
+
tableName: this.tableName,
|
|
2689
|
+
key: this.key,
|
|
2690
|
+
conditionExpression: expression,
|
|
2691
|
+
expressionAttributeNames: names,
|
|
2692
|
+
expressionAttributeValues: values
|
|
824
2693
|
};
|
|
825
|
-
const params = this.converter.toBatchWriteCommand(items);
|
|
826
|
-
const initialResult = await this.client.batchWrite(params);
|
|
827
|
-
const rawUnprocessedItems = initialResult.UnprocessedItems?.[this.tableName];
|
|
828
|
-
if (!rawUnprocessedItems || rawUnprocessedItems.length === 0) {
|
|
829
|
-
return initialResult;
|
|
830
|
-
}
|
|
831
|
-
const unprocessedItems = this.converter.fromBatchWriteResponse(rawUnprocessedItems);
|
|
832
|
-
return this.withRetry(() => processUnprocessedItems(unprocessedItems));
|
|
833
2694
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
2695
|
+
/**
|
|
2696
|
+
* Adds this condition check operation to a transaction.
|
|
2697
|
+
* Use this method when you need to:
|
|
2698
|
+
* - Verify habitat safety before transfers
|
|
2699
|
+
* - Ensure proper feeding conditions
|
|
2700
|
+
* - Validate security protocols
|
|
2701
|
+
*
|
|
2702
|
+
* @example
|
|
2703
|
+
* ```ts
|
|
2704
|
+
* const transaction = new TransactionBuilder();
|
|
2705
|
+
* new ConditionCheckBuilder('habitats', { id: 'PADDOCK-B' })
|
|
2706
|
+
* .condition(op => op.and([
|
|
2707
|
+
* op.eq('securityStatus', 'ACTIVE'),
|
|
2708
|
+
* op.lt('currentOccupants', 3),
|
|
2709
|
+
* op.eq('habitatType', 'CARNIVORE')
|
|
2710
|
+
* ]))
|
|
2711
|
+
* .withTransaction(transaction);
|
|
2712
|
+
* // Add dinosaur transfer operations
|
|
2713
|
+
* ```
|
|
2714
|
+
*
|
|
2715
|
+
* @param transaction - The transaction builder to add this operation to
|
|
2716
|
+
* @throws {Error} If no condition has been set
|
|
2717
|
+
* @returns The builder instance for method chaining
|
|
2718
|
+
*/
|
|
2719
|
+
withTransaction(transaction) {
|
|
2720
|
+
if (!this.conditionExpression) {
|
|
2721
|
+
throw new Error("Condition is required for condition check operations");
|
|
846
2722
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
return Array.from(
|
|
850
|
-
{ length: Math.ceil(array.length / size) },
|
|
851
|
-
(_, index) => array.slice(index * size, (index + 1) * size)
|
|
852
|
-
);
|
|
853
|
-
}
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
// src/builders/scan-builder.ts
|
|
857
|
-
var ScanBuilder = class extends OperationBuilder {
|
|
858
|
-
constructor(expressionBuilder, onBuild) {
|
|
859
|
-
super(expressionBuilder);
|
|
860
|
-
this.onBuild = onBuild;
|
|
861
|
-
}
|
|
862
|
-
limitValue;
|
|
863
|
-
indexNameValue;
|
|
864
|
-
pageKeyValue;
|
|
865
|
-
limit(value) {
|
|
866
|
-
this.limitValue = value;
|
|
2723
|
+
const command = this.toDynamoCommand();
|
|
2724
|
+
transaction.conditionCheckWithCommand(command);
|
|
867
2725
|
return this;
|
|
868
2726
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
2727
|
+
/**
|
|
2728
|
+
* Gets a human-readable representation of the condition check command
|
|
2729
|
+
* with all expression placeholders replaced by their actual values.
|
|
2730
|
+
* Use this method when you need to:
|
|
2731
|
+
* - Debug complex condition expressions
|
|
2732
|
+
* - Verify condition parameters
|
|
2733
|
+
* - Log safety checks
|
|
2734
|
+
* - Troubleshoot condition failures
|
|
2735
|
+
*
|
|
2736
|
+
* @example
|
|
2737
|
+
* ```ts
|
|
2738
|
+
* const debugInfo = new ConditionCheckBuilder('dinosaurs', { id: 'TREX-001' })
|
|
2739
|
+
* .condition(op => op.and([
|
|
2740
|
+
* op.between('stats.health', 50, 100),
|
|
2741
|
+
* op.not(op.eq('status', 'SEDATED')),
|
|
2742
|
+
* op.attributeExists('lastFeedingTime')
|
|
2743
|
+
* op.eq('version', 1)
|
|
2744
|
+
* ]))
|
|
2745
|
+
* .debug();
|
|
2746
|
+
* console.log(debugInfo);
|
|
2747
|
+
* ```
|
|
2748
|
+
*
|
|
2749
|
+
* @returns A readable representation of the condition check command with resolved expressions
|
|
2750
|
+
*/
|
|
2751
|
+
debug() {
|
|
2752
|
+
const command = this.toDynamoCommand();
|
|
2753
|
+
return debugCommand(command);
|
|
893
2754
|
}
|
|
894
2755
|
};
|
|
895
2756
|
|
|
896
|
-
// src/
|
|
897
|
-
var
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
this.
|
|
908
|
-
|
|
909
|
-
this.indexes = tableIndexes;
|
|
910
|
-
}
|
|
911
|
-
getIndexConfig(indexName) {
|
|
912
|
-
if (!indexName) {
|
|
913
|
-
return this.indexes.primary;
|
|
914
|
-
}
|
|
915
|
-
if (this.indexes[indexName]) {
|
|
916
|
-
return this.indexes[indexName];
|
|
917
|
-
}
|
|
918
|
-
throw new Error(`Index ${indexName} does not exist`);
|
|
919
|
-
}
|
|
920
|
-
put(item) {
|
|
921
|
-
return new PutBuilder(item, this.expressionBuilder, (operation) => this.executeOperation(operation));
|
|
922
|
-
}
|
|
923
|
-
update(key, data) {
|
|
924
|
-
const builder = new UpdateBuilder(key, this.expressionBuilder, (operation) => this.executeOperation(operation));
|
|
925
|
-
if (data) {
|
|
926
|
-
builder.setMany(data);
|
|
927
|
-
}
|
|
928
|
-
return builder;
|
|
929
|
-
}
|
|
930
|
-
query(key) {
|
|
931
|
-
return new QueryBuilder(
|
|
932
|
-
key,
|
|
933
|
-
this.getIndexConfig(),
|
|
934
|
-
this.expressionBuilder,
|
|
935
|
-
(operation) => this.executeOperation(operation)
|
|
936
|
-
);
|
|
937
|
-
}
|
|
938
|
-
async get(key, options) {
|
|
939
|
-
const indexConfig = this.getIndexConfig(options?.indexName);
|
|
940
|
-
const keyObject = this.buildKeyFromIndex(key, indexConfig);
|
|
941
|
-
const result = await this.dynamoService.get(keyObject, options);
|
|
942
|
-
return result.Item;
|
|
943
|
-
}
|
|
944
|
-
async delete(key) {
|
|
945
|
-
const operation = {
|
|
946
|
-
type: "delete",
|
|
2757
|
+
// src/builders/get-builder.ts
|
|
2758
|
+
var GetBuilder = class {
|
|
2759
|
+
/**
|
|
2760
|
+
* Creates a new GetBuilder instance.
|
|
2761
|
+
*
|
|
2762
|
+
* @param executor - Function that executes the get operation
|
|
2763
|
+
* @param key - Primary key of the item to retrieve
|
|
2764
|
+
* @param tableName - Name of the DynamoDB table
|
|
2765
|
+
*/
|
|
2766
|
+
constructor(executor, key, tableName) {
|
|
2767
|
+
this.executor = executor;
|
|
2768
|
+
this.params = {
|
|
2769
|
+
tableName,
|
|
947
2770
|
key
|
|
948
2771
|
};
|
|
949
|
-
return this.executeOperation(operation);
|
|
950
2772
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
returnValues: "ALL_NEW"
|
|
986
|
-
});
|
|
987
|
-
case "query":
|
|
988
|
-
return this.dynamoService.query({
|
|
989
|
-
keyCondition: operation.keyCondition,
|
|
990
|
-
filter: operation.filter,
|
|
991
|
-
limit: operation.limit,
|
|
992
|
-
indexName: operation.indexName
|
|
993
|
-
});
|
|
994
|
-
case "delete":
|
|
995
|
-
return this.dynamoService.delete({
|
|
996
|
-
key: operation.key
|
|
997
|
-
});
|
|
998
|
-
case "batchWrite":
|
|
999
|
-
return this.dynamoService.batchWrite(operation.operations);
|
|
1000
|
-
case "transactWrite":
|
|
1001
|
-
return this.dynamoService.transactWrite(operation.operations);
|
|
1002
|
-
case "scan":
|
|
1003
|
-
return this.dynamoService.scan({
|
|
1004
|
-
filter: operation.filter,
|
|
1005
|
-
limit: operation.limit,
|
|
1006
|
-
pageKey: operation.pageKey,
|
|
1007
|
-
indexName: operation.indexName
|
|
1008
|
-
});
|
|
1009
|
-
default:
|
|
1010
|
-
throw new Error("Unknown operation type");
|
|
2773
|
+
params;
|
|
2774
|
+
options = {};
|
|
2775
|
+
selectedFields = /* @__PURE__ */ new Set();
|
|
2776
|
+
/**
|
|
2777
|
+
* Specifies which attributes to return in the get results.
|
|
2778
|
+
* Use this method when you need to:
|
|
2779
|
+
* - Reduce data transfer by selecting specific dinosaur attributes
|
|
2780
|
+
* - Optimize response size for dinosaur records
|
|
2781
|
+
* - Focus on relevant dinosaur characteristics only
|
|
2782
|
+
*
|
|
2783
|
+
* @example
|
|
2784
|
+
* ```typescript
|
|
2785
|
+
* // Select single attribute
|
|
2786
|
+
* builder.select('species')
|
|
2787
|
+
*
|
|
2788
|
+
* // Select multiple attributes
|
|
2789
|
+
* builder.select(['id', 'species', 'diet'])
|
|
2790
|
+
*
|
|
2791
|
+
* // Chain multiple select calls
|
|
2792
|
+
* builder
|
|
2793
|
+
* .select('id')
|
|
2794
|
+
* .select(['species', 'diet'])
|
|
2795
|
+
* ```
|
|
2796
|
+
*
|
|
2797
|
+
* @param fields - A single field name or an array of field names to return
|
|
2798
|
+
* @returns The builder instance for method chaining
|
|
2799
|
+
*/
|
|
2800
|
+
select(fields) {
|
|
2801
|
+
if (typeof fields === "string") {
|
|
2802
|
+
this.selectedFields.add(fields);
|
|
2803
|
+
} else if (Array.isArray(fields)) {
|
|
2804
|
+
for (const field of fields) {
|
|
2805
|
+
this.selectedFields.add(field);
|
|
2806
|
+
}
|
|
1011
2807
|
}
|
|
2808
|
+
this.options.projection = Array.from(this.selectedFields);
|
|
2809
|
+
return this;
|
|
1012
2810
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
2811
|
+
/**
|
|
2812
|
+
* Sets whether to use strongly consistent reads for the get operation.
|
|
2813
|
+
* Use this method when you need:
|
|
2814
|
+
* - The most up-to-date dinosaur data
|
|
2815
|
+
* - To ensure you're reading the latest dinosaur status
|
|
2816
|
+
* - Critical safety information about dangerous species
|
|
2817
|
+
*
|
|
2818
|
+
* Note: Consistent reads consume twice the throughput
|
|
2819
|
+
*
|
|
2820
|
+
* @example
|
|
2821
|
+
* ```typescript
|
|
2822
|
+
* // Get the latest T-Rex data
|
|
2823
|
+
* const result = await new GetBuilder(executor, { pk: 'dinosaur#123', sk: 'profile' })
|
|
2824
|
+
* .consistentRead()
|
|
2825
|
+
* .execute();
|
|
2826
|
+
* ```
|
|
2827
|
+
*
|
|
2828
|
+
* @param consistentRead - Whether to use consistent reads (defaults to true)
|
|
2829
|
+
* @returns The builder instance for method chaining
|
|
2830
|
+
*/
|
|
2831
|
+
consistentRead(consistentRead = true) {
|
|
2832
|
+
this.params.consistentRead = consistentRead;
|
|
2833
|
+
return this;
|
|
1022
2834
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
2835
|
+
/**
|
|
2836
|
+
* Executes the get operation against DynamoDB.
|
|
2837
|
+
*
|
|
2838
|
+
* @example
|
|
2839
|
+
* ```typescript
|
|
2840
|
+
* try {
|
|
2841
|
+
* const result = await new GetBuilder(executor, { pk: 'dinosaur#123', sk: 'profile' })
|
|
2842
|
+
* .select(['species', 'name', 'diet'])
|
|
2843
|
+
* .consistentRead()
|
|
2844
|
+
* .execute();
|
|
2845
|
+
*
|
|
2846
|
+
* if (result.item) {
|
|
2847
|
+
* console.log('Dinosaur found:', result.item);
|
|
2848
|
+
* } else {
|
|
2849
|
+
* console.log('Dinosaur not found');
|
|
2850
|
+
* }
|
|
2851
|
+
* } catch (error) {
|
|
2852
|
+
* console.error('Error getting dinosaur:', error);
|
|
2853
|
+
* }
|
|
2854
|
+
* ```
|
|
2855
|
+
*
|
|
2856
|
+
* @returns A promise that resolves to an object containing:
|
|
2857
|
+
* - item: The retrieved dinosaur or undefined if not found
|
|
2858
|
+
*/
|
|
2859
|
+
async execute() {
|
|
2860
|
+
if (this.selectedFields.size > 0) {
|
|
2861
|
+
const expressionAttributeNames = {};
|
|
2862
|
+
const projectionParts = [];
|
|
2863
|
+
for (const path of this.selectedFields) {
|
|
2864
|
+
const attrName = `#attr${projectionParts.length}`;
|
|
2865
|
+
expressionAttributeNames[attrName] = path;
|
|
2866
|
+
projectionParts.push(attrName);
|
|
2867
|
+
}
|
|
2868
|
+
this.params.projectionExpression = projectionParts.join(", ");
|
|
2869
|
+
this.params.expressionAttributeNames = expressionAttributeNames;
|
|
1032
2870
|
}
|
|
2871
|
+
return this.executor(this.params);
|
|
1033
2872
|
}
|
|
1034
2873
|
};
|
|
1035
2874
|
|
|
1036
|
-
// src/
|
|
1037
|
-
var
|
|
1038
|
-
|
|
1039
|
-
|
|
2875
|
+
// src/table.ts
|
|
2876
|
+
var DDB_BATCH_WRITE_LIMIT = 25;
|
|
2877
|
+
var DDB_BATCH_GET_LIMIT = 100;
|
|
2878
|
+
var Table = class {
|
|
2879
|
+
dynamoClient;
|
|
2880
|
+
tableName;
|
|
2881
|
+
partitionKey;
|
|
2882
|
+
sortKey;
|
|
2883
|
+
gsis;
|
|
2884
|
+
constructor(config) {
|
|
2885
|
+
this.dynamoClient = config.client;
|
|
2886
|
+
this.tableName = config.tableName;
|
|
2887
|
+
this.partitionKey = config.indexes.partitionKey;
|
|
2888
|
+
this.sortKey = config.indexes.sortKey;
|
|
2889
|
+
this.gsis = config.indexes.gsis || {};
|
|
1040
2890
|
}
|
|
1041
2891
|
/**
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
1044
|
-
* @param
|
|
1045
|
-
* @returns
|
|
2892
|
+
* Creates a new item in the table, it will fail if the item already exists
|
|
2893
|
+
*
|
|
2894
|
+
* @param item The item to create
|
|
2895
|
+
* @returns A PutBuilder instance for chaining conditions and executing the put operation
|
|
1046
2896
|
*/
|
|
1047
|
-
|
|
1048
|
-
return
|
|
2897
|
+
create(item) {
|
|
2898
|
+
return this.put(item).condition((op) => op.attributeNotExists(this.partitionKey));
|
|
1049
2899
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
2900
|
+
get(keyCondition) {
|
|
2901
|
+
const executor = async (params) => {
|
|
2902
|
+
try {
|
|
2903
|
+
const result = await this.dynamoClient.get({
|
|
2904
|
+
TableName: params.tableName,
|
|
2905
|
+
Key: params.key,
|
|
2906
|
+
ProjectionExpression: params.projectionExpression,
|
|
2907
|
+
ExpressionAttributeNames: params.expressionAttributeNames,
|
|
2908
|
+
ConsistentRead: params.consistentRead
|
|
2909
|
+
});
|
|
2910
|
+
return {
|
|
2911
|
+
item: result.Item ? result.Item : void 0
|
|
2912
|
+
};
|
|
2913
|
+
} catch (error) {
|
|
2914
|
+
console.error("Error getting item:", error);
|
|
2915
|
+
throw error;
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
return new GetBuilder(executor, keyCondition, this.tableName);
|
|
1058
2919
|
}
|
|
1059
2920
|
/**
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
1062
|
-
* @
|
|
2921
|
+
* Updates an item in the table
|
|
2922
|
+
*
|
|
2923
|
+
* @param item The item to update
|
|
2924
|
+
* @returns A PutBuilder instance for chaining conditions and executing the put operation
|
|
1063
2925
|
*/
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1066
|
-
|
|
2926
|
+
put(item) {
|
|
2927
|
+
const executor = async (params) => {
|
|
2928
|
+
try {
|
|
2929
|
+
await this.dynamoClient.put({
|
|
2930
|
+
TableName: params.tableName,
|
|
2931
|
+
Item: params.item,
|
|
2932
|
+
ConditionExpression: params.conditionExpression,
|
|
2933
|
+
ExpressionAttributeNames: params.expressionAttributeNames,
|
|
2934
|
+
ExpressionAttributeValues: params.expressionAttributeValues,
|
|
2935
|
+
ReturnValues: params.returnValues
|
|
2936
|
+
});
|
|
2937
|
+
return params.item;
|
|
2938
|
+
} catch (error) {
|
|
2939
|
+
console.error("Error creating item:", error);
|
|
2940
|
+
throw error;
|
|
2941
|
+
}
|
|
2942
|
+
};
|
|
2943
|
+
return new PutBuilder(executor, item, this.tableName);
|
|
1067
2944
|
}
|
|
1068
2945
|
/**
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1071
|
-
* @returns True if the value is a primary key, false otherwise.
|
|
2946
|
+
* Creates a query builder for complex queries
|
|
2947
|
+
* If useIndex is called on the returned QueryBuilder, it will use the GSI configuration
|
|
1072
2948
|
*/
|
|
1073
|
-
|
|
1074
|
-
|
|
2949
|
+
query(keyCondition) {
|
|
2950
|
+
const pkAttributeName = "pk";
|
|
2951
|
+
const skAttributeName = "sk";
|
|
2952
|
+
let keyConditionExpression = eq(pkAttributeName, keyCondition.pk);
|
|
2953
|
+
if (keyCondition.sk) {
|
|
2954
|
+
const keyConditionOperator = {
|
|
2955
|
+
eq: (value) => eq(skAttributeName, value),
|
|
2956
|
+
lt: (value) => lt(skAttributeName, value),
|
|
2957
|
+
lte: (value) => lte(skAttributeName, value),
|
|
2958
|
+
gt: (value) => gt(skAttributeName, value),
|
|
2959
|
+
gte: (value) => gte(skAttributeName, value),
|
|
2960
|
+
between: (lower, upper) => between(skAttributeName, lower, upper),
|
|
2961
|
+
beginsWith: (value) => beginsWith(skAttributeName, value),
|
|
2962
|
+
and: (...conditions) => and(...conditions)
|
|
2963
|
+
};
|
|
2964
|
+
const skCondition = keyCondition.sk(keyConditionOperator);
|
|
2965
|
+
keyConditionExpression = and(eq(pkAttributeName, keyCondition.pk), skCondition);
|
|
2966
|
+
}
|
|
2967
|
+
const executor = async (originalKeyCondition, options) => {
|
|
2968
|
+
let finalKeyCondition = originalKeyCondition;
|
|
2969
|
+
if (options.indexName) {
|
|
2970
|
+
const gsiName = String(options.indexName);
|
|
2971
|
+
const gsi = this.gsis[gsiName];
|
|
2972
|
+
if (!gsi) {
|
|
2973
|
+
throw new Error(`GSI with name "${gsiName}" does not exist on table "${this.tableName}"`);
|
|
2974
|
+
}
|
|
2975
|
+
const gsiPkAttributeName = gsi.partitionKey;
|
|
2976
|
+
const gsiSkAttributeName = gsi.sortKey;
|
|
2977
|
+
let pkValue;
|
|
2978
|
+
let skValue;
|
|
2979
|
+
let extractedSkCondition;
|
|
2980
|
+
if (originalKeyCondition.type === "eq") {
|
|
2981
|
+
pkValue = originalKeyCondition.value;
|
|
2982
|
+
} else if (originalKeyCondition.type === "and" && originalKeyCondition.conditions) {
|
|
2983
|
+
const pkCondition = originalKeyCondition.conditions.find(
|
|
2984
|
+
(c) => c.type === "eq" && c.attr === pkAttributeName
|
|
2985
|
+
);
|
|
2986
|
+
if (pkCondition && pkCondition.type === "eq") {
|
|
2987
|
+
pkValue = pkCondition.value;
|
|
2988
|
+
}
|
|
2989
|
+
const skConditions = originalKeyCondition.conditions.filter((c) => c.attr === skAttributeName);
|
|
2990
|
+
if (skConditions.length > 0) {
|
|
2991
|
+
if (skConditions.length === 1) {
|
|
2992
|
+
extractedSkCondition = skConditions[0];
|
|
2993
|
+
if (extractedSkCondition && extractedSkCondition.type === "eq") {
|
|
2994
|
+
skValue = extractedSkCondition.value;
|
|
2995
|
+
}
|
|
2996
|
+
} else if (skConditions.length > 1) {
|
|
2997
|
+
extractedSkCondition = and(...skConditions);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
if (!pkValue) {
|
|
3002
|
+
throw new Error("Could not extract partition key value from key condition");
|
|
3003
|
+
}
|
|
3004
|
+
let gsiKeyCondition = eq(gsiPkAttributeName, pkValue);
|
|
3005
|
+
if (skValue && gsiSkAttributeName) {
|
|
3006
|
+
gsiKeyCondition = and(gsiKeyCondition, eq(gsiSkAttributeName, skValue));
|
|
3007
|
+
} else if (extractedSkCondition && gsiSkAttributeName) {
|
|
3008
|
+
if (extractedSkCondition.attr === skAttributeName) {
|
|
3009
|
+
const updatedSkCondition = {
|
|
3010
|
+
...extractedSkCondition,
|
|
3011
|
+
attr: gsiSkAttributeName
|
|
3012
|
+
};
|
|
3013
|
+
gsiKeyCondition = and(gsiKeyCondition, updatedSkCondition);
|
|
3014
|
+
} else {
|
|
3015
|
+
gsiKeyCondition = and(gsiKeyCondition, extractedSkCondition);
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
finalKeyCondition = gsiKeyCondition;
|
|
3019
|
+
}
|
|
3020
|
+
const expressionParams = {
|
|
3021
|
+
expressionAttributeNames: {},
|
|
3022
|
+
expressionAttributeValues: {},
|
|
3023
|
+
valueCounter: { count: 0 }
|
|
3024
|
+
};
|
|
3025
|
+
const keyConditionExpression2 = buildExpression(finalKeyCondition, expressionParams);
|
|
3026
|
+
let filterExpression;
|
|
3027
|
+
if (options.filter) {
|
|
3028
|
+
filterExpression = buildExpression(options.filter, expressionParams);
|
|
3029
|
+
}
|
|
3030
|
+
const projectionExpression = options.projection?.map((p) => generateAttributeName(expressionParams, p)).join(", ");
|
|
3031
|
+
const { expressionAttributeNames, expressionAttributeValues } = expressionParams;
|
|
3032
|
+
const { indexName, limit, consistentRead, scanIndexForward, lastEvaluatedKey } = options;
|
|
3033
|
+
const params = {
|
|
3034
|
+
TableName: this.tableName,
|
|
3035
|
+
KeyConditionExpression: keyConditionExpression2,
|
|
3036
|
+
FilterExpression: filterExpression,
|
|
3037
|
+
ExpressionAttributeNames: expressionAttributeNames,
|
|
3038
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
|
3039
|
+
IndexName: indexName,
|
|
3040
|
+
Limit: limit,
|
|
3041
|
+
ConsistentRead: consistentRead,
|
|
3042
|
+
ScanIndexForward: scanIndexForward,
|
|
3043
|
+
ProjectionExpression: projectionExpression,
|
|
3044
|
+
ExclusiveStartKey: lastEvaluatedKey
|
|
3045
|
+
};
|
|
3046
|
+
try {
|
|
3047
|
+
const result = await this.dynamoClient.query(params);
|
|
3048
|
+
return {
|
|
3049
|
+
items: result.Items,
|
|
3050
|
+
lastEvaluatedKey: result.LastEvaluatedKey
|
|
3051
|
+
};
|
|
3052
|
+
} catch (error) {
|
|
3053
|
+
console.log(debugCommand(params));
|
|
3054
|
+
console.error("Error querying items:", error);
|
|
3055
|
+
throw error;
|
|
3056
|
+
}
|
|
3057
|
+
};
|
|
3058
|
+
return new QueryBuilder(executor, keyConditionExpression);
|
|
1075
3059
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
3060
|
+
delete(keyCondition) {
|
|
3061
|
+
const executor = async (params) => {
|
|
3062
|
+
try {
|
|
3063
|
+
const result = await this.dynamoClient.delete({
|
|
3064
|
+
TableName: params.tableName,
|
|
3065
|
+
Key: params.key,
|
|
3066
|
+
ConditionExpression: params.conditionExpression,
|
|
3067
|
+
ExpressionAttributeNames: params.expressionAttributeNames,
|
|
3068
|
+
ExpressionAttributeValues: params.expressionAttributeValues,
|
|
3069
|
+
ReturnValues: params.returnValues
|
|
3070
|
+
});
|
|
3071
|
+
return {
|
|
3072
|
+
item: result.Attributes
|
|
3073
|
+
};
|
|
3074
|
+
} catch (error) {
|
|
3075
|
+
console.error("Error deleting item:", error);
|
|
3076
|
+
throw error;
|
|
3077
|
+
}
|
|
1086
3078
|
};
|
|
1087
|
-
|
|
1088
|
-
const builder = this.table.put(item).set(this.getTypeAttributeName(), this.getType()).whereNotExists(indexConfig.pkName);
|
|
1089
|
-
if (indexConfig.skName) {
|
|
1090
|
-
builder.whereNotExists(indexConfig.skName);
|
|
1091
|
-
}
|
|
1092
|
-
return builder;
|
|
3079
|
+
return new DeleteBuilder(executor, this.tableName, keyCondition);
|
|
1093
3080
|
}
|
|
1094
3081
|
/**
|
|
1095
|
-
* Updates an
|
|
1096
|
-
*
|
|
1097
|
-
* @param
|
|
1098
|
-
* @returns
|
|
3082
|
+
* Updates an item in the table
|
|
3083
|
+
*
|
|
3084
|
+
* @param keyCondition The primary key of the item to update
|
|
3085
|
+
* @returns An UpdateBuilder instance for chaining update operations and conditions
|
|
1099
3086
|
*/
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
3087
|
+
update(keyCondition) {
|
|
3088
|
+
const executor = async (params) => {
|
|
3089
|
+
try {
|
|
3090
|
+
const result = await this.dynamoClient.update({
|
|
3091
|
+
TableName: params.tableName,
|
|
3092
|
+
Key: params.key,
|
|
3093
|
+
UpdateExpression: params.updateExpression,
|
|
3094
|
+
ConditionExpression: params.conditionExpression,
|
|
3095
|
+
ExpressionAttributeNames: params.expressionAttributeNames,
|
|
3096
|
+
ExpressionAttributeValues: params.expressionAttributeValues,
|
|
3097
|
+
ReturnValues: params.returnValues
|
|
3098
|
+
});
|
|
3099
|
+
return {
|
|
3100
|
+
item: result.Attributes
|
|
3101
|
+
};
|
|
3102
|
+
} catch (error) {
|
|
3103
|
+
console.error("Error updating item:", error);
|
|
3104
|
+
throw error;
|
|
3105
|
+
}
|
|
1105
3106
|
};
|
|
1106
|
-
|
|
1107
|
-
if (!result.Attributes) return null;
|
|
1108
|
-
return this.findOne(key);
|
|
3107
|
+
return new UpdateBuilder(executor, this.tableName, keyCondition);
|
|
1109
3108
|
}
|
|
1110
3109
|
/**
|
|
1111
|
-
*
|
|
1112
|
-
* @param data - The record data.
|
|
1113
|
-
* @returns A PutBuilder instance to execute the put operation.
|
|
3110
|
+
* Creates a transaction builder for performing multiple operations atomically
|
|
1114
3111
|
*/
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
3112
|
+
transactionBuilder() {
|
|
3113
|
+
const executor = async (params) => {
|
|
3114
|
+
await this.dynamoClient.transactWrite(params);
|
|
3115
|
+
};
|
|
3116
|
+
return new TransactionBuilder(executor, {
|
|
3117
|
+
partitionKey: this.partitionKey,
|
|
3118
|
+
sortKey: this.sortKey
|
|
1120
3119
|
});
|
|
1121
3120
|
}
|
|
1122
3121
|
/**
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1125
|
-
* @
|
|
3122
|
+
* Executes a transaction using a callback function
|
|
3123
|
+
*
|
|
3124
|
+
* @param callback A function that receives a transaction context and performs operations on it
|
|
3125
|
+
* @param options Optional transaction options
|
|
3126
|
+
* @returns A promise that resolves when the transaction is complete
|
|
1126
3127
|
*/
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
3128
|
+
transaction(callback, options) {
|
|
3129
|
+
const executor = async () => {
|
|
3130
|
+
const transactionExecutor = async (params) => {
|
|
3131
|
+
await this.dynamoClient.transactWrite(params);
|
|
3132
|
+
};
|
|
3133
|
+
const transaction = new TransactionBuilder(transactionExecutor, {
|
|
3134
|
+
partitionKey: this.partitionKey,
|
|
3135
|
+
sortKey: this.sortKey
|
|
3136
|
+
});
|
|
3137
|
+
if (options) {
|
|
3138
|
+
transaction.withOptions(options);
|
|
3139
|
+
}
|
|
3140
|
+
const result = await callback(transaction);
|
|
3141
|
+
await transaction.execute();
|
|
3142
|
+
return result;
|
|
3143
|
+
};
|
|
3144
|
+
return executor();
|
|
1134
3145
|
}
|
|
1135
3146
|
/**
|
|
1136
|
-
*
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
3147
|
+
* Creates a condition check operation for use in transactions
|
|
3148
|
+
*
|
|
3149
|
+
* This is useful for when you require a transaction to succeed only when a specific condition is met on a
|
|
3150
|
+
* a record within the database that you are not directly updating.
|
|
3151
|
+
*
|
|
3152
|
+
* For example, you are updating a record and you want to ensure that another record exists and/or has a specific value before proceeding.
|
|
1139
3153
|
*/
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const item = results.Items?.[0];
|
|
1143
|
-
if (!item) {
|
|
1144
|
-
return null;
|
|
1145
|
-
}
|
|
1146
|
-
return item;
|
|
3154
|
+
conditionCheck(keyCondition) {
|
|
3155
|
+
return new ConditionCheckBuilder(this.tableName, keyCondition);
|
|
1147
3156
|
}
|
|
1148
3157
|
/**
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
* @
|
|
1152
|
-
* @
|
|
3158
|
+
* Performs a batch get operation to retrieve multiple items at once
|
|
3159
|
+
*
|
|
3160
|
+
* @param keys Array of primary keys to retrieve
|
|
3161
|
+
* @returns A promise that resolves to the retrieved items
|
|
1153
3162
|
*/
|
|
1154
|
-
async
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
|
|
3163
|
+
async batchGet(keys) {
|
|
3164
|
+
const allItems = [];
|
|
3165
|
+
const allUnprocessedKeys = [];
|
|
3166
|
+
for (const chunk of chunkArray(keys, DDB_BATCH_GET_LIMIT)) {
|
|
3167
|
+
const formattedKeys = chunk.map((key) => ({
|
|
3168
|
+
pk: key.pk,
|
|
3169
|
+
sk: key.sk
|
|
3170
|
+
}));
|
|
3171
|
+
const params = {
|
|
3172
|
+
RequestItems: {
|
|
3173
|
+
[this.tableName]: {
|
|
3174
|
+
Keys: formattedKeys
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
try {
|
|
3179
|
+
const result = await this.dynamoClient.batchGet(params);
|
|
3180
|
+
if (result.Responses?.[this.tableName]) {
|
|
3181
|
+
allItems.push(...result.Responses[this.tableName]);
|
|
3182
|
+
}
|
|
3183
|
+
const unprocessedKeysArray = result.UnprocessedKeys?.[this.tableName]?.Keys || [];
|
|
3184
|
+
const unprocessedKeys = unprocessedKeysArray.map((key) => ({
|
|
3185
|
+
pk: key.pk,
|
|
3186
|
+
sk: key.sk
|
|
3187
|
+
}));
|
|
3188
|
+
if (unprocessedKeys.length > 0) {
|
|
3189
|
+
allUnprocessedKeys.push(...unprocessedKeys);
|
|
3190
|
+
}
|
|
3191
|
+
} catch (error) {
|
|
3192
|
+
console.error("Error in batch get operation:", error);
|
|
3193
|
+
throw error;
|
|
3194
|
+
}
|
|
1158
3195
|
}
|
|
1159
|
-
return
|
|
3196
|
+
return {
|
|
3197
|
+
items: allItems,
|
|
3198
|
+
unprocessedKeys: allUnprocessedKeys
|
|
3199
|
+
};
|
|
1160
3200
|
}
|
|
1161
3201
|
/**
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1164
|
-
* @
|
|
3202
|
+
* Performs a batch write operation to put or delete multiple items at once
|
|
3203
|
+
*
|
|
3204
|
+
* @param operations Array of put or delete operations
|
|
3205
|
+
* @returns A promise that resolves to any unprocessed operations
|
|
1165
3206
|
*/
|
|
1166
|
-
|
|
1167
|
-
|
|
3207
|
+
async batchWrite(operations) {
|
|
3208
|
+
const allUnprocessedItems = [];
|
|
3209
|
+
for (const chunk of chunkArray(operations, DDB_BATCH_WRITE_LIMIT)) {
|
|
3210
|
+
const writeRequests = chunk.map((operation) => {
|
|
3211
|
+
if (operation.type === "put") {
|
|
3212
|
+
return {
|
|
3213
|
+
PutRequest: {
|
|
3214
|
+
Item: operation.item
|
|
3215
|
+
}
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
return {
|
|
3219
|
+
DeleteRequest: {
|
|
3220
|
+
Key: {
|
|
3221
|
+
pk: operation.key.pk,
|
|
3222
|
+
sk: operation.key.sk
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
};
|
|
3226
|
+
});
|
|
3227
|
+
const params = {
|
|
3228
|
+
RequestItems: {
|
|
3229
|
+
[this.tableName]: writeRequests
|
|
3230
|
+
}
|
|
3231
|
+
};
|
|
3232
|
+
try {
|
|
3233
|
+
const result = await this.dynamoClient.batchWrite(params);
|
|
3234
|
+
const unprocessedRequestsArray = result.UnprocessedItems?.[this.tableName] || [];
|
|
3235
|
+
if (unprocessedRequestsArray.length > 0) {
|
|
3236
|
+
const unprocessedItems = unprocessedRequestsArray.map((request) => {
|
|
3237
|
+
if (request?.PutRequest?.Item) {
|
|
3238
|
+
return {
|
|
3239
|
+
type: "put",
|
|
3240
|
+
item: request.PutRequest.Item
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
if (request?.DeleteRequest?.Key) {
|
|
3244
|
+
return {
|
|
3245
|
+
type: "delete",
|
|
3246
|
+
key: {
|
|
3247
|
+
pk: request.DeleteRequest.Key.pk,
|
|
3248
|
+
sk: request.DeleteRequest.Key.sk
|
|
3249
|
+
}
|
|
3250
|
+
};
|
|
3251
|
+
}
|
|
3252
|
+
throw new Error("Invalid unprocessed item format returned from DynamoDB");
|
|
3253
|
+
});
|
|
3254
|
+
allUnprocessedItems.push(...unprocessedItems);
|
|
3255
|
+
}
|
|
3256
|
+
} catch (error) {
|
|
3257
|
+
console.error("Error in batch write operation:", error);
|
|
3258
|
+
throw error;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
return {
|
|
3262
|
+
unprocessedItems: allUnprocessedItems
|
|
3263
|
+
};
|
|
1168
3264
|
}
|
|
1169
3265
|
};
|
|
1170
3266
|
export {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
Table
|
|
3267
|
+
ConditionCheckBuilder,
|
|
3268
|
+
DeleteBuilder,
|
|
3269
|
+
Paginator,
|
|
3270
|
+
PutBuilder,
|
|
3271
|
+
QueryBuilder,
|
|
3272
|
+
Table,
|
|
3273
|
+
TransactionBuilder,
|
|
3274
|
+
UpdateBuilder,
|
|
3275
|
+
and,
|
|
3276
|
+
attributeExists,
|
|
3277
|
+
attributeNotExists,
|
|
3278
|
+
beginsWith,
|
|
3279
|
+
between,
|
|
3280
|
+
contains,
|
|
3281
|
+
eq,
|
|
3282
|
+
gt,
|
|
3283
|
+
gte,
|
|
3284
|
+
lt,
|
|
3285
|
+
lte,
|
|
3286
|
+
ne,
|
|
3287
|
+
not,
|
|
3288
|
+
or
|
|
1177
3289
|
};
|