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