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