dyno-table 2.2.1 → 2.3.0

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.
Files changed (102) hide show
  1. package/README.md +200 -1860
  2. package/dist/builders.cjs +55 -0
  3. package/dist/builders.d.cts +4 -0
  4. package/dist/builders.d.ts +4 -0
  5. package/dist/builders.js +2 -0
  6. package/dist/chunk-2EWNZOUK.js +618 -0
  7. package/dist/chunk-2WIBY7PZ.js +46 -0
  8. package/dist/chunk-7UJJ7JXM.cjs +63 -0
  9. package/dist/chunk-DTFJJASK.js +3200 -0
  10. package/dist/chunk-EODPMYPE.js +558 -0
  11. package/dist/chunk-KA3VPIPS.cjs +560 -0
  12. package/dist/chunk-NTA6GDPP.cjs +622 -0
  13. package/dist/chunk-PB7BBCZO.cjs +32 -0
  14. package/dist/chunk-QVRMYGC4.js +29 -0
  15. package/dist/chunk-XYL43FDX.cjs +3217 -0
  16. package/dist/conditions.cjs +67 -62
  17. package/dist/conditions.js +1 -48
  18. package/dist/entity.cjs +14 -625
  19. package/dist/entity.d.cts +2 -10
  20. package/dist/entity.d.ts +2 -10
  21. package/dist/entity.js +2 -626
  22. package/dist/index-2cbm07Bi.d.ts +2797 -0
  23. package/dist/index-DlN8G9hd.d.cts +2797 -0
  24. package/dist/index.cjs +111 -4460
  25. package/dist/index.d.cts +2 -10
  26. package/dist/index.d.ts +2 -10
  27. package/dist/index.js +5 -4442
  28. package/dist/standard-schema.cjs +0 -2
  29. package/dist/standard-schema.js +0 -2
  30. package/dist/table.cjs +7 -3796
  31. package/dist/table.d.cts +163 -12
  32. package/dist/table.d.ts +163 -12
  33. package/dist/table.js +3 -3799
  34. package/dist/types.cjs +0 -2
  35. package/dist/types.js +0 -2
  36. package/dist/utils.cjs +10 -30
  37. package/dist/utils.js +1 -31
  38. package/package.json +6 -66
  39. package/dist/batch-builder-BiQDIZ7p.d.cts +0 -398
  40. package/dist/batch-builder-CNsLS6sR.d.ts +0 -398
  41. package/dist/builder-types-BTVhQSHI.d.cts +0 -169
  42. package/dist/builder-types-CzuLR4Th.d.ts +0 -169
  43. package/dist/builders/condition-check-builder.cjs +0 -422
  44. package/dist/builders/condition-check-builder.cjs.map +0 -1
  45. package/dist/builders/condition-check-builder.d.cts +0 -153
  46. package/dist/builders/condition-check-builder.d.ts +0 -153
  47. package/dist/builders/condition-check-builder.js +0 -420
  48. package/dist/builders/condition-check-builder.js.map +0 -1
  49. package/dist/builders/delete-builder.cjs +0 -484
  50. package/dist/builders/delete-builder.cjs.map +0 -1
  51. package/dist/builders/delete-builder.d.cts +0 -211
  52. package/dist/builders/delete-builder.d.ts +0 -211
  53. package/dist/builders/delete-builder.js +0 -482
  54. package/dist/builders/delete-builder.js.map +0 -1
  55. package/dist/builders/paginator.cjs +0 -193
  56. package/dist/builders/paginator.cjs.map +0 -1
  57. package/dist/builders/paginator.d.cts +0 -155
  58. package/dist/builders/paginator.d.ts +0 -155
  59. package/dist/builders/paginator.js +0 -191
  60. package/dist/builders/paginator.js.map +0 -1
  61. package/dist/builders/put-builder.cjs +0 -554
  62. package/dist/builders/put-builder.cjs.map +0 -1
  63. package/dist/builders/put-builder.d.cts +0 -319
  64. package/dist/builders/put-builder.d.ts +0 -319
  65. package/dist/builders/put-builder.js +0 -552
  66. package/dist/builders/put-builder.js.map +0 -1
  67. package/dist/builders/query-builder.cjs +0 -757
  68. package/dist/builders/query-builder.cjs.map +0 -1
  69. package/dist/builders/query-builder.d.cts +0 -6
  70. package/dist/builders/query-builder.d.ts +0 -6
  71. package/dist/builders/query-builder.js +0 -755
  72. package/dist/builders/query-builder.js.map +0 -1
  73. package/dist/builders/transaction-builder.cjs +0 -906
  74. package/dist/builders/transaction-builder.cjs.map +0 -1
  75. package/dist/builders/transaction-builder.d.cts +0 -464
  76. package/dist/builders/transaction-builder.d.ts +0 -464
  77. package/dist/builders/transaction-builder.js +0 -904
  78. package/dist/builders/transaction-builder.js.map +0 -1
  79. package/dist/builders/update-builder.cjs +0 -668
  80. package/dist/builders/update-builder.cjs.map +0 -1
  81. package/dist/builders/update-builder.d.cts +0 -374
  82. package/dist/builders/update-builder.d.ts +0 -374
  83. package/dist/builders/update-builder.js +0 -666
  84. package/dist/builders/update-builder.js.map +0 -1
  85. package/dist/conditions.cjs.map +0 -1
  86. package/dist/conditions.js.map +0 -1
  87. package/dist/entity.cjs.map +0 -1
  88. package/dist/entity.js.map +0 -1
  89. package/dist/index.cjs.map +0 -1
  90. package/dist/index.js.map +0 -1
  91. package/dist/query-builder-D3URwK9k.d.cts +0 -477
  92. package/dist/query-builder-cfEkU0_w.d.ts +0 -477
  93. package/dist/standard-schema.cjs.map +0 -1
  94. package/dist/standard-schema.js.map +0 -1
  95. package/dist/table-ClST8nkR.d.cts +0 -276
  96. package/dist/table-vE3cGoDy.d.ts +0 -276
  97. package/dist/table.cjs.map +0 -1
  98. package/dist/table.js.map +0 -1
  99. package/dist/types.cjs.map +0 -1
  100. package/dist/types.js.map +0 -1
  101. package/dist/utils.cjs.map +0 -1
  102. package/dist/utils.js.map +0 -1
package/dist/table.js CHANGED
@@ -1,3799 +1,3 @@
1
- // src/builders/batch-builder.ts
2
- var BatchError = class extends Error {
3
- operation;
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';