dyno-table 2.6.0 → 2.6.1

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