dyno-table 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1083 @@
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
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
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
+ };
52
+ }
53
+ addValue(attributes, value, prefix) {
54
+ const alias = this.generateAlias("value", prefix);
55
+ attributes.values[alias] = value;
56
+ return alias;
57
+ }
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
+ }
87
+ }
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
+ };
100
+ }
101
+ formatAttributes({
102
+ names,
103
+ values
104
+ }) {
105
+ return {
106
+ ...Object.keys(names).length && { names },
107
+ ...Object.keys(values).length && { values }
108
+ };
109
+ }
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
+ );
133
+ }
134
+ }
135
+ return {
136
+ expression: conditions.join(" AND "),
137
+ attributes: this.formatAttributes(attributes)
138
+ };
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
+ }
156
+ }
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
+ };
165
+ }
166
+ };
167
+
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;
177
+ }
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);
197
+ }
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;
206
+ }
207
+ build() {
208
+ const { expression, attributes } = this.buildConditionExpression();
209
+ return {
210
+ type: "put",
211
+ item: this.item,
212
+ condition: expression ? {
213
+ expression,
214
+ names: attributes.names,
215
+ values: attributes.values
216
+ } : void 0
217
+ };
218
+ }
219
+ async execute() {
220
+ return this.onBuild(this.build());
221
+ }
222
+ };
223
+
224
+ // 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;
231
+ }
232
+ limitValue;
233
+ indexNameValue;
234
+ limit(value) {
235
+ this.limitValue = value;
236
+ return this;
237
+ }
238
+ useIndex(indexName) {
239
+ this.indexNameValue = indexName;
240
+ return this;
241
+ }
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;
279
+ return this;
280
+ }
281
+ setMany(attribtues) {
282
+ this.updates = { ...this.updates, ...attribtues };
283
+ return this;
284
+ }
285
+ remove(...fields) {
286
+ for (const field of fields) {
287
+ this.updates[field] = null;
288
+ }
289
+ return this;
290
+ }
291
+ increment(field, by = 1) {
292
+ this.updates[field] = { $add: by };
293
+ return this;
294
+ }
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
+ };
312
+ }
313
+ async execute() {
314
+ return this.onBuild(this.build());
315
+ }
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
+ }
328
+ }
329
+ };
330
+ var ConditionalCheckFailedError = class extends DynamoError {
331
+ constructor(message, originalError, context) {
332
+ super(message, originalError, context);
333
+ this.name = "ConditionalCheckFailedError";
334
+ }
335
+ };
336
+ var ResourceNotFoundError = class extends DynamoError {
337
+ constructor(message, originalError, context) {
338
+ super(message, originalError, context);
339
+ this.name = "ResourceNotFoundError";
340
+ }
341
+ };
342
+
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
+ );
358
+ }
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
+ );
391
+ }
392
+ if (keyCondition) {
393
+ parts.push(
394
+ `
395
+ Key Condition: ${translateExpression(keyCondition, context.attributes)}`
396
+ );
397
+ }
398
+ }
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;
406
+ }
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);
415
+ }
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());
446
+ }
447
+ };
448
+
449
+ // src/dynamo/dynamo-converter.ts
450
+ var DynamoConverter = class {
451
+ constructor(tableName) {
452
+ this.tableName = tableName;
453
+ }
454
+ /**
455
+ * Converts our expression format to DynamoDB expression format
456
+ */
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
+ };
464
+ }
465
+ /**
466
+ * Convert our format to DynamoDB put command input
467
+ */
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
+ };
478
+ }
479
+ /**
480
+ * Convert our format to DynamoDB get command input
481
+ */
482
+ toGetCommand(options) {
483
+ return {
484
+ TableName: this.tableName,
485
+ Key: options.key,
486
+ ...options.indexName && { IndexName: options.indexName }
487
+ };
488
+ }
489
+ /**
490
+ * Convert our format to DynamoDB update command input
491
+ */
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
+ };
512
+ }
513
+ /**
514
+ * Convert our format to DynamoDB delete command input
515
+ */
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
+ };
526
+ }
527
+ /**
528
+ * Convert our format to DynamoDB query command input
529
+ */
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
+ };
552
+ }
553
+ /**
554
+ * Convert our format to DynamoDB scan command input
555
+ */
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
+ };
568
+ }
569
+ /**
570
+ * Convert our format to DynamoDB batch write command input
571
+ */
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
+ };
595
+ }
596
+ /**
597
+ * Convert our format to DynamoDB transact write command input
598
+ */
599
+ toTransactWriteCommand(items) {
600
+ 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
+ })
650
+ };
651
+ }
652
+ /**
653
+ * Convert DynamoDB batch write response to our format
654
+ */
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
+ });
669
+ }
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);
680
+ }
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
+ }
694
+ });
695
+ }
696
+ }
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
+ });
711
+ }
712
+ }
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
+ });
726
+ }
727
+ }
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
+ });
738
+ }
739
+ }
740
+ async query(options) {
741
+ try {
742
+ if (options.autoPaginate) {
743
+ return await this.executeWithAutoPagination(options);
744
+ }
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
754
+ }
755
+ });
756
+ }
757
+ }
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
768
+ }
769
+ });
770
+ }
771
+ }
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
+ });
783
+ }
784
+ }
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
+ );
790
+ }
791
+ try {
792
+ const params = this.converter.toTransactWriteCommand(items);
793
+ return await this.withRetry(() => this.client.transactWrite(params));
794
+ } catch (error) {
795
+ handleDynamoError(error, {
796
+ operation: "TRANSACT_WRITE",
797
+ tableName: this.tableName
798
+ });
799
+ }
800
+ }
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);
815
+ return {
816
+ Items: allItems,
817
+ Count: allItems.length,
818
+ ScannedCount: allItems.length,
819
+ LastEvaluatedKey: void 0
820
+ };
821
+ }
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;
841
+ }
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++;
860
+ }
861
+ }
862
+ }
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
+ );
868
+ }
869
+ };
870
+
871
+ // src/table.ts
872
+ 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`);
894
+ }
895
+ 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
931
+ };
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
942
+ };
943
+ }
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 };
957
+ }
958
+ return { delete: op.key };
959
+ })
960
+ };
961
+ return this.executeOperation(batchOperation);
962
+ }
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
994
+ });
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
+ }
1002
+ }
1003
+ buildKeyFromIndex(key, indexConfig) {
1004
+ this.validateKey(key, indexConfig);
1005
+ const keyObject = {
1006
+ [indexConfig.pkName]: key.pk
1007
+ };
1008
+ if (indexConfig.skName && key.sk) {
1009
+ keyObject[indexConfig.skName] = key.sk;
1010
+ }
1011
+ return keyObject;
1012
+ }
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
+ }
1023
+ }
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
1044
+ };
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);
1063
+ }
1064
+ async findOrFail(key) {
1065
+ const result = await this.findOne(key);
1066
+ if (!result) {
1067
+ throw new Error("Item not found");
1068
+ }
1069
+ return this.schema.parse(result);
1070
+ }
1071
+ query(key) {
1072
+ return this.table.query(key).where(this.getTypeAttributeName(), "=", this.getType());
1073
+ }
1074
+ };
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
+ });