dyno-table 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,577 +0,0 @@
1
- import { GetBuilder, PutBuilder, QueryBuilder, ScanBuilder, DeleteBuilder, UpdateBuilder, TransactionBuilder, BatchBuilder, ConditionCheckBuilder, buildExpression, generateAttributeName } from './chunk-NYJGW3XH.js';
2
- import { ConfigurationErrors, OperationErrors } from './chunk-FF7FYGDH.js';
3
- import { eq, and, beginsWith, between, gte, gt, lte, lt } from './chunk-2WIBY7PZ.js';
4
-
5
- // src/utils/chunk-array.ts
6
- function* chunkArray(array, size) {
7
- if (size <= 0) {
8
- throw ConfigurationErrors.invalidChunkSize(size);
9
- }
10
- for (let i = 0; i < array.length; i += size) {
11
- yield array.slice(i, i + size);
12
- }
13
- }
14
-
15
- // src/table.ts
16
- var DDB_BATCH_WRITE_LIMIT = 25;
17
- var DDB_BATCH_GET_LIMIT = 100;
18
- var Table = class {
19
- dynamoClient;
20
- tableName;
21
- /**
22
- * The column name of the partitionKey for the Table
23
- */
24
- partitionKey;
25
- /**
26
- * The column name of the sortKey for the Table
27
- */
28
- sortKey;
29
- /**
30
- * The Global Secondary Indexes that are configured on this table
31
- */
32
- gsis;
33
- constructor(config) {
34
- this.dynamoClient = config.client;
35
- this.tableName = config.tableName;
36
- this.partitionKey = config.indexes.partitionKey;
37
- this.sortKey = config.indexes.sortKey;
38
- this.gsis = config.indexes.gsis || {};
39
- }
40
- getIndexAttributeNames() {
41
- const names = /* @__PURE__ */ new Set();
42
- for (const gsi of Object.values(this.gsis)) {
43
- names.add(gsi.partitionKey);
44
- if (gsi.sortKey) {
45
- names.add(gsi.sortKey);
46
- }
47
- }
48
- return Array.from(names);
49
- }
50
- createKeyForPrimaryIndex(keyCondition) {
51
- const primaryCondition = { [this.partitionKey]: keyCondition.pk };
52
- if (this.sortKey) {
53
- if (!keyCondition.sk) {
54
- throw ConfigurationErrors.sortKeyRequired(this.tableName, this.partitionKey, this.sortKey);
55
- }
56
- primaryCondition[this.sortKey] = keyCondition.sk;
57
- }
58
- return primaryCondition;
59
- }
60
- /**
61
- * Creates a new item in the table, it will fail if the item already exists.
62
- *
63
- * By default, this method returns the input values passed to the create operation
64
- * upon successful creation.
65
- *
66
- * You can customise the return behaviour by chaining the `.returnValues()` method:
67
- *
68
- * @param item The item to create
69
- * @returns A PutBuilder instance for chaining additional conditions and executing the create operation
70
- *
71
- * @example
72
- * ```ts
73
- * // Create with default behavior (returns input values)
74
- * const result = await table.create({
75
- * id: 'user-123',
76
- * name: 'John Doe',
77
- * email: 'john@example.com'
78
- * }).execute();
79
- * console.log(result); // Returns the input object
80
- *
81
- * // Create with no return value for better performance
82
- * await table.create(userData).returnValues('NONE').execute();
83
- *
84
- * // Create and get fresh data from dynamodb using a strongly consistent read
85
- * const freshData = await table.create(userData).returnValues('CONSISTENT').execute();
86
- *
87
- * // Create and get previous values (if the item was overwritten)
88
- * const oldData = await table.create(userData).returnValues('ALL_OLD').execute();
89
- * ```
90
- */
91
- create(item) {
92
- return this.put(item).condition((op) => op.attributeNotExists(this.partitionKey)).returnValues("INPUT");
93
- }
94
- get(keyCondition) {
95
- const indexAttributeNames = this.getIndexAttributeNames();
96
- const executor = async (params) => {
97
- try {
98
- const result = await this.dynamoClient.get({
99
- TableName: params.tableName,
100
- Key: this.createKeyForPrimaryIndex(keyCondition),
101
- ProjectionExpression: params.projectionExpression,
102
- ExpressionAttributeNames: params.expressionAttributeNames,
103
- ConsistentRead: params.consistentRead
104
- });
105
- return {
106
- item: result.Item ? result.Item : void 0
107
- };
108
- } catch (error) {
109
- throw OperationErrors.getFailed(params.tableName, keyCondition, error instanceof Error ? error : void 0);
110
- }
111
- };
112
- return new GetBuilder(executor, keyCondition, this.tableName, indexAttributeNames);
113
- }
114
- /**
115
- * Updates an item in the table
116
- *
117
- * @param item The item to update
118
- * @returns A PutBuilder instance for chaining conditions and executing the put operation
119
- */
120
- put(item) {
121
- const executor = async (params) => {
122
- try {
123
- const result = await this.dynamoClient.put({
124
- TableName: params.tableName,
125
- Item: params.item,
126
- ConditionExpression: params.conditionExpression,
127
- ExpressionAttributeNames: params.expressionAttributeNames,
128
- ExpressionAttributeValues: params.expressionAttributeValues,
129
- // CONSISTENT and INPUT are not valid ReturnValues for DDB, so we set NONE as we are not interested in its
130
- // response and will be handling these cases separately
131
- ReturnValues: params.returnValues === "CONSISTENT" || params.returnValues === "INPUT" ? "NONE" : params.returnValues
132
- });
133
- if (params.returnValues === "INPUT") {
134
- return params.item;
135
- }
136
- if (params.returnValues === "CONSISTENT") {
137
- const getResult = await this.dynamoClient.get({
138
- TableName: params.tableName,
139
- Key: this.createKeyForPrimaryIndex({
140
- pk: params.item[this.partitionKey],
141
- ...this.sortKey && { sk: params.item[this.sortKey] }
142
- }),
143
- ConsistentRead: true
144
- });
145
- return getResult.Item;
146
- }
147
- return result.Attributes;
148
- } catch (error) {
149
- throw OperationErrors.putFailed(params.tableName, params.item, error instanceof Error ? error : void 0);
150
- }
151
- };
152
- return new PutBuilder(executor, item, this.tableName);
153
- }
154
- /**
155
- * Creates a query builder for complex queries
156
- * If useIndex is called on the returned QueryBuilder, it will use the GSI configuration
157
- */
158
- query(keyCondition) {
159
- const indexAttributeNames = this.getIndexAttributeNames();
160
- const pkAttributeName = this.partitionKey;
161
- const skAttributeName = this.sortKey;
162
- let keyConditionExpression = eq(pkAttributeName, keyCondition.pk);
163
- if (keyCondition.sk) {
164
- if (!skAttributeName) {
165
- throw ConfigurationErrors.sortKeyNotDefined(this.tableName, pkAttributeName);
166
- }
167
- const keyConditionOperator = {
168
- eq: (value) => eq(skAttributeName, value),
169
- lt: (value) => lt(skAttributeName, value),
170
- lte: (value) => lte(skAttributeName, value),
171
- gt: (value) => gt(skAttributeName, value),
172
- gte: (value) => gte(skAttributeName, value),
173
- between: (lower, upper) => between(skAttributeName, lower, upper),
174
- beginsWith: (value) => beginsWith(skAttributeName, value),
175
- and: (...conditions) => and(...conditions)
176
- };
177
- const skCondition = keyCondition.sk(keyConditionOperator);
178
- keyConditionExpression = and(eq(pkAttributeName, keyCondition.pk), skCondition);
179
- }
180
- const executor = async (originalKeyCondition, options) => {
181
- let finalKeyCondition = originalKeyCondition;
182
- if (options.indexName) {
183
- const gsiName = String(options.indexName);
184
- const gsi = this.gsis[gsiName];
185
- if (!gsi) {
186
- throw ConfigurationErrors.gsiNotFound(gsiName, this.tableName, Object.keys(this.gsis));
187
- }
188
- const gsiPkAttributeName = gsi.partitionKey;
189
- const gsiSkAttributeName = gsi.sortKey;
190
- let pkValue;
191
- let skValue;
192
- let extractedSkCondition;
193
- if (originalKeyCondition.type === "eq") {
194
- pkValue = originalKeyCondition.value;
195
- } else if (originalKeyCondition.type === "and" && originalKeyCondition.conditions) {
196
- const pkCondition = originalKeyCondition.conditions.find(
197
- (c) => c.type === "eq" && c.attr === pkAttributeName
198
- );
199
- if (pkCondition && pkCondition.type === "eq") {
200
- pkValue = pkCondition.value;
201
- }
202
- const skConditions = originalKeyCondition.conditions.filter((c) => c.attr === skAttributeName);
203
- if (skConditions.length > 0) {
204
- if (skConditions.length === 1) {
205
- extractedSkCondition = skConditions[0];
206
- if (extractedSkCondition && extractedSkCondition.type === "eq") {
207
- skValue = extractedSkCondition.value;
208
- }
209
- } else if (skConditions.length > 1) {
210
- extractedSkCondition = and(...skConditions);
211
- }
212
- }
213
- }
214
- if (!pkValue) {
215
- throw ConfigurationErrors.pkExtractionFailed(this.tableName, options.indexName, originalKeyCondition);
216
- }
217
- let gsiKeyCondition = eq(gsiPkAttributeName, pkValue);
218
- if (skValue && gsiSkAttributeName) {
219
- gsiKeyCondition = and(gsiKeyCondition, eq(gsiSkAttributeName, skValue));
220
- } else if (extractedSkCondition && gsiSkAttributeName) {
221
- if (extractedSkCondition.attr === skAttributeName) {
222
- const updatedSkCondition = {
223
- ...extractedSkCondition,
224
- attr: gsiSkAttributeName
225
- };
226
- gsiKeyCondition = and(gsiKeyCondition, updatedSkCondition);
227
- } else {
228
- gsiKeyCondition = and(gsiKeyCondition, extractedSkCondition);
229
- }
230
- }
231
- finalKeyCondition = gsiKeyCondition;
232
- }
233
- const expressionParams = {
234
- expressionAttributeNames: {},
235
- expressionAttributeValues: {},
236
- valueCounter: { count: 0 }
237
- };
238
- const keyConditionExpression2 = buildExpression(finalKeyCondition, expressionParams);
239
- let filterExpression;
240
- if (options.filter) {
241
- filterExpression = buildExpression(options.filter, expressionParams);
242
- }
243
- const projectionExpression = options.projection?.map((p) => generateAttributeName(expressionParams, p)).join(", ");
244
- const { expressionAttributeNames, expressionAttributeValues } = expressionParams;
245
- const { indexName, limit, consistentRead, scanIndexForward, lastEvaluatedKey } = options;
246
- const params = {
247
- TableName: this.tableName,
248
- KeyConditionExpression: keyConditionExpression2,
249
- FilterExpression: filterExpression,
250
- ExpressionAttributeNames: expressionAttributeNames,
251
- ExpressionAttributeValues: expressionAttributeValues,
252
- IndexName: indexName,
253
- Limit: limit,
254
- ConsistentRead: consistentRead,
255
- ScanIndexForward: scanIndexForward,
256
- ProjectionExpression: projectionExpression,
257
- ExclusiveStartKey: lastEvaluatedKey
258
- };
259
- try {
260
- const result = await this.dynamoClient.query(params);
261
- return {
262
- items: result.Items,
263
- lastEvaluatedKey: result.LastEvaluatedKey
264
- };
265
- } catch (error) {
266
- throw OperationErrors.queryFailed(
267
- this.tableName,
268
- { indexName, keyConditionExpression: keyConditionExpression2, filterExpression },
269
- error instanceof Error ? error : void 0
270
- );
271
- }
272
- };
273
- return new QueryBuilder(executor, keyConditionExpression, indexAttributeNames);
274
- }
275
- /**
276
- * Creates a scan builder for scanning the entire table
277
- * Use this when you need to:
278
- * - Process all items in a table
279
- * - Apply filters to a large dataset
280
- * - Use a GSI for scanning
281
- *
282
- * @returns A ScanBuilder instance for chaining operations
283
- */
284
- scan() {
285
- const executor = async (options) => {
286
- const expressionParams = {
287
- expressionAttributeNames: {},
288
- expressionAttributeValues: {},
289
- valueCounter: { count: 0 }
290
- };
291
- let filterExpression;
292
- if (options.filter) {
293
- filterExpression = buildExpression(options.filter, expressionParams);
294
- }
295
- const projectionExpression = options.projection?.map((p) => generateAttributeName(expressionParams, p)).join(", ");
296
- const { expressionAttributeNames, expressionAttributeValues } = expressionParams;
297
- const { indexName, limit, consistentRead, lastEvaluatedKey } = options;
298
- const params = {
299
- TableName: this.tableName,
300
- FilterExpression: filterExpression,
301
- ExpressionAttributeNames: Object.keys(expressionAttributeNames).length > 0 ? expressionAttributeNames : void 0,
302
- ExpressionAttributeValues: Object.keys(expressionAttributeValues).length > 0 ? expressionAttributeValues : void 0,
303
- IndexName: indexName,
304
- Limit: limit,
305
- ConsistentRead: consistentRead,
306
- ProjectionExpression: projectionExpression,
307
- ExclusiveStartKey: lastEvaluatedKey
308
- };
309
- try {
310
- const result = await this.dynamoClient.scan(params);
311
- return {
312
- items: result.Items,
313
- lastEvaluatedKey: result.LastEvaluatedKey
314
- };
315
- } catch (error) {
316
- throw OperationErrors.scanFailed(
317
- this.tableName,
318
- { indexName: options.indexName, filterExpression },
319
- error instanceof Error ? error : void 0
320
- );
321
- }
322
- };
323
- return new ScanBuilder(executor);
324
- }
325
- delete(keyCondition) {
326
- const executor = async (params) => {
327
- try {
328
- const result = await this.dynamoClient.delete({
329
- TableName: params.tableName,
330
- Key: this.createKeyForPrimaryIndex(keyCondition),
331
- ConditionExpression: params.conditionExpression,
332
- ExpressionAttributeNames: params.expressionAttributeNames,
333
- ExpressionAttributeValues: params.expressionAttributeValues,
334
- ReturnValues: params.returnValues
335
- });
336
- return {
337
- item: result.Attributes
338
- };
339
- } catch (error) {
340
- throw OperationErrors.deleteFailed(params.tableName, keyCondition, error instanceof Error ? error : void 0);
341
- }
342
- };
343
- return new DeleteBuilder(executor, this.tableName, keyCondition);
344
- }
345
- /**
346
- * Updates an item in the table
347
- *
348
- * @param keyCondition The primary key of the item to update
349
- * @returns An UpdateBuilder instance for chaining update operations and conditions
350
- */
351
- update(keyCondition) {
352
- const executor = async (params) => {
353
- try {
354
- const result = await this.dynamoClient.update({
355
- TableName: params.tableName,
356
- Key: this.createKeyForPrimaryIndex(keyCondition),
357
- UpdateExpression: params.updateExpression,
358
- ConditionExpression: params.conditionExpression,
359
- ExpressionAttributeNames: params.expressionAttributeNames,
360
- ExpressionAttributeValues: params.expressionAttributeValues,
361
- ReturnValues: params.returnValues
362
- });
363
- return {
364
- item: result.Attributes
365
- };
366
- } catch (error) {
367
- throw OperationErrors.updateFailed(params.tableName, keyCondition, error instanceof Error ? error : void 0);
368
- }
369
- };
370
- return new UpdateBuilder(executor, this.tableName, keyCondition);
371
- }
372
- /**
373
- * Creates a transaction builder for performing multiple operations atomically
374
- */
375
- transactionBuilder() {
376
- const executor = async (params) => {
377
- await this.dynamoClient.transactWrite(params);
378
- };
379
- return new TransactionBuilder(executor, {
380
- partitionKey: this.partitionKey,
381
- sortKey: this.sortKey
382
- });
383
- }
384
- /**
385
- * Creates a batch builder for performing multiple operations efficiently with optional type inference
386
- *
387
- * @example Basic Usage
388
- * ```typescript
389
- * const batch = table.batchBuilder();
390
- *
391
- * // Add operations
392
- * userRepo.create(newUser).withBatch(batch);
393
- * orderRepo.get({ id: 'order-1' }).withBatch(batch);
394
- *
395
- * // Execute operations
396
- * const result = await batch.execute();
397
- * ```
398
- *
399
- * @example Typed Usage
400
- * ```typescript
401
- * // Define entity types for the batch
402
- * const batch = table.batchBuilder<{
403
- * User: UserEntity;
404
- * Order: OrderEntity;
405
- * Product: ProductEntity;
406
- * }>();
407
- *
408
- * // Add operations with type information
409
- * userRepo.create(newUser).withBatch(batch, 'User');
410
- * orderRepo.get({ id: 'order-1' }).withBatch(batch, 'Order');
411
- * productRepo.delete({ id: 'old-product' }).withBatch(batch, 'Product');
412
- *
413
- * // Execute and get typed results
414
- * const result = await batch.execute();
415
- * const users: UserEntity[] = result.reads.itemsByType.User;
416
- * const orders: OrderEntity[] = result.reads.itemsByType.Order;
417
- * ```
418
- */
419
- batchBuilder() {
420
- const batchWriteExecutor = async (operations) => {
421
- return this.batchWrite(operations);
422
- };
423
- const batchGetExecutor = async (keys) => {
424
- return this.batchGet(keys);
425
- };
426
- return new BatchBuilder(batchWriteExecutor, batchGetExecutor, {
427
- partitionKey: this.partitionKey,
428
- sortKey: this.sortKey
429
- });
430
- }
431
- /**
432
- * Executes a transaction using a callback function
433
- *
434
- * @param callback A function that receives a transaction context and performs operations on it
435
- * @param options Optional transaction options
436
- * @returns A promise that resolves when the transaction is complete
437
- */
438
- async transaction(callback, options) {
439
- const transactionExecutor = async (params) => {
440
- await this.dynamoClient.transactWrite(params);
441
- };
442
- const transaction = new TransactionBuilder(transactionExecutor, {
443
- partitionKey: this.partitionKey,
444
- sortKey: this.sortKey
445
- });
446
- if (options) {
447
- transaction.withOptions(options);
448
- }
449
- const result = await callback(transaction);
450
- await transaction.execute();
451
- return result;
452
- }
453
- /**
454
- * Creates a condition check operation for use in transactions
455
- *
456
- * This is useful for when you require a transaction to succeed only when a specific condition is met on a
457
- * a record within the database that you are not directly updating.
458
- *
459
- * For example, you are updating a record and you want to ensure that another record exists and/or has a specific value before proceeding.
460
- */
461
- conditionCheck(keyCondition) {
462
- return new ConditionCheckBuilder(this.tableName, keyCondition);
463
- }
464
- /**
465
- * Performs a batch get operation to retrieve multiple items at once
466
- *
467
- * @param keys Array of primary keys to retrieve
468
- * @returns A promise that resolves to the retrieved items
469
- */
470
- async batchGet(keys) {
471
- const allItems = [];
472
- const allUnprocessedKeys = [];
473
- for (const chunk of chunkArray(keys, DDB_BATCH_GET_LIMIT)) {
474
- const formattedKeys = chunk.map((key) => ({
475
- [this.partitionKey]: key.pk,
476
- ...this.sortKey ? { [this.sortKey]: key.sk } : {}
477
- }));
478
- const params = {
479
- RequestItems: {
480
- [this.tableName]: {
481
- Keys: formattedKeys
482
- }
483
- }
484
- };
485
- try {
486
- const result = await this.dynamoClient.batchGet(params);
487
- if (result.Responses?.[this.tableName]) {
488
- allItems.push(...result.Responses[this.tableName]);
489
- }
490
- const unprocessedKeysArray = result.UnprocessedKeys?.[this.tableName]?.Keys || [];
491
- const unprocessedKeys = unprocessedKeysArray.map((key) => ({
492
- pk: key[this.partitionKey],
493
- sk: this.sortKey ? key[this.sortKey] : void 0
494
- }));
495
- if (unprocessedKeys.length > 0) {
496
- allUnprocessedKeys.push(...unprocessedKeys);
497
- }
498
- } catch (error) {
499
- throw OperationErrors.batchGetFailed(
500
- this.tableName,
501
- { requestedKeys: keys.length },
502
- error instanceof Error ? error : void 0
503
- );
504
- }
505
- }
506
- return {
507
- items: allItems,
508
- unprocessedKeys: allUnprocessedKeys
509
- };
510
- }
511
- /**
512
- * Performs a batch write operation to put or delete multiple items at once
513
- *
514
- * @param operations Array of put or delete operations
515
- * @returns A promise that resolves to any unprocessed operations
516
- */
517
- async batchWrite(operations) {
518
- const allUnprocessedItems = [];
519
- for (const chunk of chunkArray(operations, DDB_BATCH_WRITE_LIMIT)) {
520
- const writeRequests = chunk.map((operation) => {
521
- if (operation.type === "put") {
522
- return {
523
- PutRequest: {
524
- Item: operation.item
525
- }
526
- };
527
- }
528
- return {
529
- DeleteRequest: {
530
- Key: this.createKeyForPrimaryIndex(operation.key)
531
- }
532
- };
533
- });
534
- const params = {
535
- RequestItems: {
536
- [this.tableName]: writeRequests
537
- }
538
- };
539
- try {
540
- const result = await this.dynamoClient.batchWrite(params);
541
- const unprocessedRequestsArray = result.UnprocessedItems?.[this.tableName] || [];
542
- if (unprocessedRequestsArray.length > 0) {
543
- const unprocessedItems = unprocessedRequestsArray.map((request) => {
544
- if (request?.PutRequest?.Item) {
545
- return {
546
- type: "put",
547
- item: request.PutRequest.Item
548
- };
549
- }
550
- if (request?.DeleteRequest?.Key) {
551
- return {
552
- type: "delete",
553
- key: {
554
- pk: request.DeleteRequest.Key[this.partitionKey],
555
- sk: this.sortKey ? request.DeleteRequest.Key[this.sortKey] : void 0
556
- }
557
- };
558
- }
559
- throw new Error("Invalid unprocessed item format returned from DynamoDB");
560
- });
561
- allUnprocessedItems.push(...unprocessedItems);
562
- }
563
- } catch (error) {
564
- throw OperationErrors.batchWriteFailed(
565
- this.tableName,
566
- { requestedOperations: operations.length },
567
- error instanceof Error ? error : void 0
568
- );
569
- }
570
- }
571
- return {
572
- unprocessedItems: allUnprocessedItems
573
- };
574
- }
575
- };
576
-
577
- export { Table };
@@ -1,63 +0,0 @@
1
- 'use strict';
2
-
3
- // src/conditions.ts
4
- var createComparisonCondition = (type) => (attr, value) => ({
5
- type,
6
- attr,
7
- value
8
- });
9
- var eq = createComparisonCondition("eq");
10
- var ne = createComparisonCondition("ne");
11
- var lt = createComparisonCondition("lt");
12
- var lte = createComparisonCondition("lte");
13
- var gt = createComparisonCondition("gt");
14
- var gte = createComparisonCondition("gte");
15
- var between = (attr, lower, upper) => ({
16
- type: "between",
17
- attr,
18
- value: [lower, upper]
19
- });
20
- var inArray = (attr, values) => ({
21
- type: "in",
22
- attr,
23
- value: values
24
- });
25
- var beginsWith = createComparisonCondition("beginsWith");
26
- var contains = createComparisonCondition("contains");
27
- var attributeExists = (attr) => ({
28
- type: "attributeExists",
29
- attr
30
- });
31
- var attributeNotExists = (attr) => ({
32
- type: "attributeNotExists",
33
- attr
34
- });
35
- var and = (...conditions) => ({
36
- type: "and",
37
- conditions
38
- });
39
- var or = (...conditions) => ({
40
- type: "or",
41
- conditions
42
- });
43
- var not = (condition) => ({
44
- type: "not",
45
- condition
46
- });
47
-
48
- exports.and = and;
49
- exports.attributeExists = attributeExists;
50
- exports.attributeNotExists = attributeNotExists;
51
- exports.beginsWith = beginsWith;
52
- exports.between = between;
53
- exports.contains = contains;
54
- exports.createComparisonCondition = createComparisonCondition;
55
- exports.eq = eq;
56
- exports.gt = gt;
57
- exports.gte = gte;
58
- exports.inArray = inArray;
59
- exports.lt = lt;
60
- exports.lte = lte;
61
- exports.ne = ne;
62
- exports.not = not;
63
- exports.or = or;