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