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