dyno-table 0.0.1 → 0.1.2

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