@ftschopp/dynatable-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +17 -0
  3. package/dist/builders/batch-get/create-batch-get-builder.d.ts +18 -0
  4. package/dist/builders/batch-get/create-batch-get-builder.d.ts.map +1 -0
  5. package/dist/builders/batch-get/create-batch-get-builder.js +71 -0
  6. package/dist/builders/batch-get/index.d.ts +3 -0
  7. package/dist/builders/batch-get/index.d.ts.map +1 -0
  8. package/dist/builders/batch-get/index.js +18 -0
  9. package/dist/builders/batch-get/types.d.ts +25 -0
  10. package/dist/builders/batch-get/types.d.ts.map +1 -0
  11. package/dist/builders/batch-get/types.js +2 -0
  12. package/dist/builders/batch-write/create-batch-write-builder.d.ts +12 -0
  13. package/dist/builders/batch-write/create-batch-write-builder.d.ts.map +1 -0
  14. package/dist/builders/batch-write/create-batch-write-builder.js +35 -0
  15. package/dist/builders/batch-write/index.d.ts +3 -0
  16. package/dist/builders/batch-write/index.d.ts.map +1 -0
  17. package/dist/builders/batch-write/index.js +18 -0
  18. package/dist/builders/batch-write/types.d.ts +30 -0
  19. package/dist/builders/batch-write/types.d.ts.map +1 -0
  20. package/dist/builders/batch-write/types.js +2 -0
  21. package/dist/builders/delete/create-delete-builder.d.ts +9 -0
  22. package/dist/builders/delete/create-delete-builder.d.ts.map +1 -0
  23. package/dist/builders/delete/create-delete-builder.js +72 -0
  24. package/dist/builders/delete/index.d.ts +3 -0
  25. package/dist/builders/delete/index.d.ts.map +1 -0
  26. package/dist/builders/delete/index.js +18 -0
  27. package/dist/builders/delete/types.d.ts +11 -0
  28. package/dist/builders/delete/types.d.ts.map +1 -0
  29. package/dist/builders/delete/types.js +2 -0
  30. package/dist/builders/get/create-get-builder.d.ts +12 -0
  31. package/dist/builders/get/create-get-builder.d.ts.map +1 -0
  32. package/dist/builders/get/create-get-builder.js +95 -0
  33. package/dist/builders/get/index.d.ts +3 -0
  34. package/dist/builders/get/index.d.ts.map +1 -0
  35. package/dist/builders/get/index.js +18 -0
  36. package/dist/builders/get/types.d.ts +28 -0
  37. package/dist/builders/get/types.d.ts.map +1 -0
  38. package/dist/builders/get/types.js +2 -0
  39. package/dist/builders/index.d.ts +12 -0
  40. package/dist/builders/index.d.ts.map +1 -0
  41. package/dist/builders/index.js +29 -0
  42. package/dist/builders/put/create-put-builder.d.ts +9 -0
  43. package/dist/builders/put/create-put-builder.d.ts.map +1 -0
  44. package/dist/builders/put/create-put-builder.js +104 -0
  45. package/dist/builders/put/index.d.ts +3 -0
  46. package/dist/builders/put/index.d.ts.map +1 -0
  47. package/dist/builders/put/index.js +18 -0
  48. package/dist/builders/put/types.d.ts +17 -0
  49. package/dist/builders/put/types.d.ts.map +1 -0
  50. package/dist/builders/put/types.js +2 -0
  51. package/dist/builders/query/create-query-builder.d.ts +9 -0
  52. package/dist/builders/query/create-query-builder.d.ts.map +1 -0
  53. package/dist/builders/query/create-query-builder.js +283 -0
  54. package/dist/builders/query/index.d.ts +3 -0
  55. package/dist/builders/query/index.d.ts.map +1 -0
  56. package/dist/builders/query/index.js +18 -0
  57. package/dist/builders/query/types.d.ts +79 -0
  58. package/dist/builders/query/types.d.ts.map +1 -0
  59. package/dist/builders/query/types.js +2 -0
  60. package/dist/builders/scan/create-scan-builder.d.ts +12 -0
  61. package/dist/builders/scan/create-scan-builder.d.ts.map +1 -0
  62. package/dist/builders/scan/create-scan-builder.js +103 -0
  63. package/dist/builders/scan/index.d.ts +3 -0
  64. package/dist/builders/scan/index.d.ts.map +1 -0
  65. package/dist/builders/scan/index.js +18 -0
  66. package/dist/builders/scan/types.d.ts +43 -0
  67. package/dist/builders/scan/types.d.ts.map +1 -0
  68. package/dist/builders/scan/types.js +2 -0
  69. package/dist/builders/shared/conditions.d.ts +18 -0
  70. package/dist/builders/shared/conditions.d.ts.map +1 -0
  71. package/dist/builders/shared/conditions.js +48 -0
  72. package/dist/builders/shared/index.d.ts +4 -0
  73. package/dist/builders/shared/index.d.ts.map +1 -0
  74. package/dist/builders/shared/index.js +19 -0
  75. package/dist/builders/shared/operators.d.ts +12 -0
  76. package/dist/builders/shared/operators.d.ts.map +1 -0
  77. package/dist/builders/shared/operators.js +197 -0
  78. package/dist/builders/shared/types.d.ts +77 -0
  79. package/dist/builders/shared/types.d.ts.map +1 -0
  80. package/dist/builders/shared/types.js +6 -0
  81. package/dist/builders/transact-get/create-transact-get-builder.d.ts +18 -0
  82. package/dist/builders/transact-get/create-transact-get-builder.d.ts.map +1 -0
  83. package/dist/builders/transact-get/create-transact-get-builder.js +60 -0
  84. package/dist/builders/transact-get/index.d.ts +3 -0
  85. package/dist/builders/transact-get/index.d.ts.map +1 -0
  86. package/dist/builders/transact-get/index.js +18 -0
  87. package/dist/builders/transact-get/types.d.ts +28 -0
  88. package/dist/builders/transact-get/types.d.ts.map +1 -0
  89. package/dist/builders/transact-get/types.js +2 -0
  90. package/dist/builders/transact-write/create-transact-write-builder.d.ts +18 -0
  91. package/dist/builders/transact-write/create-transact-write-builder.d.ts.map +1 -0
  92. package/dist/builders/transact-write/create-transact-write-builder.js +97 -0
  93. package/dist/builders/transact-write/index.d.ts +3 -0
  94. package/dist/builders/transact-write/index.d.ts.map +1 -0
  95. package/dist/builders/transact-write/index.js +18 -0
  96. package/dist/builders/transact-write/types.d.ts +34 -0
  97. package/dist/builders/transact-write/types.d.ts.map +1 -0
  98. package/dist/builders/transact-write/types.js +2 -0
  99. package/dist/builders/update/create-update-builder.d.ts +14 -0
  100. package/dist/builders/update/create-update-builder.d.ts.map +1 -0
  101. package/dist/builders/update/create-update-builder.js +180 -0
  102. package/dist/builders/update/index.d.ts +3 -0
  103. package/dist/builders/update/index.d.ts.map +1 -0
  104. package/dist/builders/update/index.js +18 -0
  105. package/dist/builders/update/types.d.ts +35 -0
  106. package/dist/builders/update/types.d.ts.map +1 -0
  107. package/dist/builders/update/types.js +2 -0
  108. package/dist/core/types.d.ts +224 -0
  109. package/dist/core/types.d.ts.map +1 -0
  110. package/dist/core/types.js +3 -0
  111. package/dist/entity.d.ts +73 -0
  112. package/dist/entity.d.ts.map +1 -0
  113. package/dist/entity.js +161 -0
  114. package/dist/index.d.ts +4 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +20 -0
  117. package/dist/table.d.ts +70 -0
  118. package/dist/table.d.ts.map +1 -0
  119. package/dist/table.js +69 -0
  120. package/dist/utils/dynamodb-logger.d.ts +118 -0
  121. package/dist/utils/dynamodb-logger.d.ts.map +1 -0
  122. package/dist/utils/dynamodb-logger.js +125 -0
  123. package/dist/utils/model-utils.d.ts +24 -0
  124. package/dist/utils/model-utils.d.ts.map +1 -0
  125. package/dist/utils/model-utils.js +88 -0
  126. package/dist/utils/zod-utils.d.ts +13 -0
  127. package/dist/utils/zod-utils.d.ts.map +1 -0
  128. package/dist/utils/zod-utils.js +35 -0
  129. package/eslint.config.mjs +4 -0
  130. package/jest.config.js +11 -0
  131. package/package.json +36 -0
  132. package/src/builders/README.md +272 -0
  133. package/src/builders/batch-get/README.md +98 -0
  134. package/src/builders/batch-get/create-batch-get-builder.test.ts +165 -0
  135. package/src/builders/batch-get/create-batch-get-builder.ts +106 -0
  136. package/src/builders/batch-get/index.ts +2 -0
  137. package/src/builders/batch-get/types.ts +29 -0
  138. package/src/builders/batch-write/README.md +204 -0
  139. package/src/builders/batch-write/create-batch-write-builder.test.ts +173 -0
  140. package/src/builders/batch-write/create-batch-write-builder.ts +49 -0
  141. package/src/builders/batch-write/index.ts +2 -0
  142. package/src/builders/batch-write/types.ts +33 -0
  143. package/src/builders/delete/create-delete-builder.test.ts +294 -0
  144. package/src/builders/delete/create-delete-builder.ts +100 -0
  145. package/src/builders/delete/index.ts +2 -0
  146. package/src/builders/delete/types.ts +11 -0
  147. package/src/builders/get/create-get-builder.test.ts +272 -0
  148. package/src/builders/get/create-get-builder.ts +140 -0
  149. package/src/builders/get/index.ts +2 -0
  150. package/src/builders/get/types.ts +30 -0
  151. package/src/builders/index.ts +14 -0
  152. package/src/builders/put/create-put-builder.test.ts +213 -0
  153. package/src/builders/put/create-put-builder.ts +151 -0
  154. package/src/builders/put/index.ts +2 -0
  155. package/src/builders/put/types.ts +18 -0
  156. package/src/builders/query/create-query-builder.test.ts +230 -0
  157. package/src/builders/query/create-query-builder.ts +353 -0
  158. package/src/builders/query/index.ts +2 -0
  159. package/src/builders/query/types.ts +95 -0
  160. package/src/builders/scan/create-scan-builder.test.ts +260 -0
  161. package/src/builders/scan/create-scan-builder.ts +217 -0
  162. package/src/builders/scan/index.ts +2 -0
  163. package/src/builders/scan/types.ts +49 -0
  164. package/src/builders/shared/conditions.ts +58 -0
  165. package/src/builders/shared/index.ts +3 -0
  166. package/src/builders/shared/operators.test.ts +270 -0
  167. package/src/builders/shared/operators.ts +200 -0
  168. package/src/builders/shared/types.ts +100 -0
  169. package/src/builders/transact-get/README.md +167 -0
  170. package/src/builders/transact-get/create-transact-get-builder.test.ts +239 -0
  171. package/src/builders/transact-get/create-transact-get-builder.ts +67 -0
  172. package/src/builders/transact-get/index.ts +2 -0
  173. package/src/builders/transact-get/types.ts +31 -0
  174. package/src/builders/transact-write/README.md +166 -0
  175. package/src/builders/transact-write/create-transact-write-builder.test.ts +288 -0
  176. package/src/builders/transact-write/create-transact-write-builder.ts +118 -0
  177. package/src/builders/transact-write/index.ts +2 -0
  178. package/src/builders/transact-write/types.ts +33 -0
  179. package/src/builders/update/create-update-builder.test.ts +333 -0
  180. package/src/builders/update/create-update-builder.ts +284 -0
  181. package/src/builders/update/index.ts +2 -0
  182. package/src/builders/update/types.ts +42 -0
  183. package/src/core/types.test.ts +506 -0
  184. package/src/core/types.ts +290 -0
  185. package/src/entity.ts +337 -0
  186. package/src/index.ts +22 -0
  187. package/src/table.ts +109 -0
  188. package/src/utils/dynamodb-logger.test.ts +246 -0
  189. package/src/utils/dynamodb-logger.ts +175 -0
  190. package/src/utils/model-utils.test.ts +232 -0
  191. package/src/utils/model-utils.ts +101 -0
  192. package/src/utils/zod-utils.test.ts +272 -0
  193. package/src/utils/zod-utils.ts +36 -0
  194. package/tests/integration/instagram-clone.integration.test.ts +966 -0
  195. package/tests/integration/pagination-timestamps.integration.test.ts +375 -0
  196. package/tests/integration/transactions.integration.test.ts +529 -0
  197. package/tsconfig.json +12 -0
@@ -0,0 +1,272 @@
1
+ # Builders Architecture
2
+
3
+ This directory contains the builder pattern implementation for DynamoDB operations.
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ builders/
9
+ ├── shared/ # Shared utilities and types
10
+ │ ├── types.ts # Base types (Condition, OpBuilder, etc.)
11
+ │ ├── operators.ts # Condition operators (eq, ne, lt, etc.)
12
+ │ └── conditions.ts # Condition expression builders
13
+ ├── get/ # GET operation builder
14
+ │ ├── types.ts
15
+ │ ├── create-get-builder.ts
16
+ │ └── index.ts
17
+ ├── put/ # PUT operation builder
18
+ │ ├── types.ts
19
+ │ ├── create-put-builder.ts
20
+ │ └── index.ts
21
+ └── query/ # QUERY operation builder
22
+ ├── types.ts
23
+ ├── create-query-builder.ts
24
+ └── index.ts
25
+ ```
26
+
27
+ ## Design Principles
28
+
29
+ 1. **Modularity**: Each operation has its own directory with dedicated types and implementation
30
+ 2. **Reusability**: Common utilities are centralized in `shared/`
31
+ 3. **Scalability**: Easy to add new operations (query, scan, delete, etc.) by following the same pattern
32
+ 4. **Type Safety**: Full TypeScript support with proper type inference
33
+
34
+ ## Adding a New Builder
35
+
36
+ To add a new operation (e.g., `query`):
37
+
38
+ 1. Create a new directory: `builders/query/`
39
+ 2. Add three files:
40
+ - `types.ts` - Interface definition extending `OperationBuilder` or `ExecutableBuilder`
41
+ - `create-query-builder.ts` - Implementation with immutable builder pattern
42
+ - `index.ts` - Export all public APIs
43
+ 3. Import shared utilities from `../shared`
44
+ 4. Export from main `builders/index.ts`
45
+
46
+ ## Usage Examples
47
+
48
+ ### GET Operation
49
+
50
+ ```typescript
51
+ import { createGetBuilder } from './builders';
52
+
53
+ const getBuilder = createGetBuilder(tableName, key, client)
54
+ .select(['name', 'email'])
55
+ .consistentRead();
56
+
57
+ const item = await getBuilder.execute();
58
+ ```
59
+
60
+ ### PUT Operation
61
+
62
+ ```typescript
63
+ import { createPutBuilder } from './builders';
64
+
65
+ const putBuilder = createPutBuilder(tableName, item, client).ifNotExists().returning('ALL_NEW');
66
+
67
+ const result = await putBuilder.execute();
68
+ ```
69
+
70
+ ### QUERY Operation
71
+
72
+ ```typescript
73
+ import { createQueryBuilder } from './builders';
74
+
75
+ // Basic query with partition key
76
+ const query1 = createQueryBuilder(tableName, client, model)
77
+ .where((attr, op) => op.eq(attr.username, 'juanca'))
78
+ .execute();
79
+
80
+ // Query with AND filter
81
+ const query2 = createQueryBuilder(tableName, client, model)
82
+ .where((attr, op) => op.and(op.eq(attr.username, 'juanca'), op.gt(attr.likesCount, 10)))
83
+ .limit(10)
84
+ .scanIndexForward(false)
85
+ .select(['id', 'title', 'createdAt'])
86
+ .execute();
87
+
88
+ // Query with OR filter
89
+ const query3 = createQueryBuilder(tableName, client, model)
90
+ .where((attr, op) =>
91
+ op.and(
92
+ op.eq(attr.username, 'juanca'),
93
+ op.or(op.gt(attr.likesCount, 100), op.gt(attr.commentCount, 50))
94
+ )
95
+ )
96
+ .execute();
97
+
98
+ // Complex nested conditions
99
+ const query4 = createQueryBuilder(tableName, client, model)
100
+ .where((attr, op) =>
101
+ op.and(
102
+ op.eq(attr.username, 'juanca'),
103
+ op.or(
104
+ op.and(op.gt(attr.likesCount, 100), op.lt(attr.commentCount, 10)),
105
+ op.gt(attr.likesCount, 500)
106
+ )
107
+ )
108
+ )
109
+ .execute();
110
+
111
+ // Query using a secondary index
112
+ const query5 = createQueryBuilder(tableName, client, model)
113
+ .where((attr, op) => op.eq(attr.status, 'active'))
114
+ .useIndex('GSI1')
115
+ .execute();
116
+ ```
117
+
118
+ ## Type-Safe Queries
119
+
120
+ The Query Builder provides full TypeScript type safety with an intuitive API:
121
+
122
+ ```typescript
123
+ // Your model type
124
+ type Photo = {
125
+ username: string; // Used in partition key template
126
+ photoId: string; // Used in sort key template
127
+ url: string;
128
+ likesCount: number;
129
+ commentCount: number;
130
+ };
131
+
132
+ // Type-safe query - TypeScript knows all fields and their types!
133
+ const photos = await createQueryBuilder<Photo>(tableName, client, model)
134
+ .where((attr, op) =>
135
+ op.and(
136
+ op.eq(attr.username, 'juanca'), // ✓ Key field - goes to KeyConditionExpression
137
+ op.gt(attr.likesCount, 10) // ✓ Non-key field - goes to FilterExpression
138
+ )
139
+ )
140
+ .select(['username', 'url', 'likesCount']) // ✓ Only Photo keys allowed
141
+ .execute();
142
+
143
+ // TypeScript will catch errors at compile time:
144
+ // .where((attr, op) => op.eq(attr.invalid, 'x')) // ✗ Property 'invalid' does not exist
145
+ // .where((attr, op) => op.eq(attr.username, 123)) // ✗ Argument of type 'number' not assignable
146
+ // .select(['invalidField']) // ✗ Type error!
147
+ ```
148
+
149
+ ### Important: Automatic Separation of Key Conditions vs Filters
150
+
151
+ The query builder **automatically separates** your conditions:
152
+
153
+ **KeyConditionExpression** (efficient, uses indexes):
154
+
155
+ - Fields used in partition key (pk) or sort key (sk) templates
156
+ - Evaluated during the query at the index level
157
+ - Very efficient - only reads matching items
158
+
159
+ **FilterExpression** (less efficient, post-processing):
160
+
161
+ - All other fields not in key templates
162
+ - Applied AFTER items are retrieved
163
+ - OR operators automatically go here (DynamoDB requirement)
164
+
165
+ ```typescript
166
+ // ✓ The builder handles this automatically!
167
+ .where((attr, op) => op.and(
168
+ op.eq(attr.username, 'juanca'), // → KeyConditionExpression (username is in pk template)
169
+ op.gt(attr.likesCount, 10) // → FilterExpression (likesCount is not a key)
170
+ ))
171
+
172
+ // Generates:
173
+ // KeyConditionExpression: "#username = :username_0"
174
+ // FilterExpression: "#likesCount > :likesCount_1"
175
+ ```
176
+
177
+ ## Important: Operator Isolation
178
+
179
+ Each builder creates its own isolated instance of operators with independent counters. This prevents naming conflicts when building multiple queries concurrently or reusing builder code.
180
+
181
+ ```typescript
182
+ // ✓ Each builder has isolated state
183
+ const query1 = createQueryBuilder(table, client).where((attr, op) => op.eq(attr.id, '1')); // Uses :id_0
184
+
185
+ const query2 = createQueryBuilder(table, client).where((attr, op) => op.eq(attr.id, '2')); // Also uses :id_0 (isolated counter)
186
+
187
+ // No conflicts! Each builder has its own counter starting at 0
188
+ ```
189
+
190
+ For advanced use cases where you need to manually create operators, use `createOpBuilder()`:
191
+
192
+ ```typescript
193
+ import { createOpBuilder } from './builders/shared';
194
+
195
+ const op = createOpBuilder(); // Creates isolated operator instance
196
+ const condition = op.eq(attr, value);
197
+ ```
198
+
199
+ ## Available Operators
200
+
201
+ The `op` parameter in `.where()` provides these operators:
202
+
203
+ **Comparison Operators:**
204
+
205
+ - `op.eq(attr, value)` - Equals (=)
206
+ - `op.ne(attr, value)` - Not equals (<>)
207
+ - `op.lt(attr, value)` - Less than (<)
208
+ - `op.lte(attr, value)` - Less than or equal (<=)
209
+ - `op.gt(attr, value)` - Greater than (>)
210
+ - `op.gte(attr, value)` - Greater than or equal (>=)
211
+ - `op.between(attr, low, high)` - Between two values
212
+ - `op.beginsWith(attr, prefix)` - Begins with a string (for string fields)
213
+
214
+ **Logical Operators:**
215
+
216
+ - `op.and(...conditions)` - Combines conditions with AND
217
+ - `op.or(...conditions)` - Combines conditions with OR
218
+ - `op.not(condition)` - Negates a condition
219
+
220
+ ### Example Usage
221
+
222
+ ```typescript
223
+ // Basic equality
224
+ .where((attr, op) => op.eq(attr.username, 'juanca'))
225
+
226
+ // Comparison operators
227
+ .where((attr, op) => op.and(
228
+ op.eq(attr.username, 'juanca'),
229
+ op.gt(attr.age, 18),
230
+ op.lt(attr.score, 100)
231
+ ))
232
+
233
+ // String operations
234
+ .where((attr, op) => op.beginsWith(attr.email, 'admin@'))
235
+
236
+ // Between operator
237
+ .where((attr, op) => op.between(attr.createdAt, '2024-01-01', '2024-12-31'))
238
+
239
+ // OR conditions
240
+ .where((attr, op) => op.or(
241
+ op.eq(attr.status, 'active'),
242
+ op.eq(attr.status, 'pending')
243
+ ))
244
+
245
+ // Complex nested conditions
246
+ .where((attr, op) => op.and(
247
+ op.eq(attr.org, 'acme'),
248
+ op.or(
249
+ op.and(
250
+ op.gt(attr.score, 80),
251
+ op.eq(attr.verified, true)
252
+ ),
253
+ op.eq(attr.role, 'admin')
254
+ )
255
+ ))
256
+
257
+ // NOT operator
258
+ .where((attr, op) => op.not(op.eq(attr.status, 'deleted')))
259
+
260
+ // Complete example with all features
261
+ await table.entities.Photo.query()
262
+ .where((attr, op) => op.and(
263
+ op.eq(attr.username, 'juanca'), // Key field
264
+ op.or(
265
+ op.gt(attr.likesCount, 100), // Filter field
266
+ op.gt(attr.commentCount, 50) // Filter field
267
+ )
268
+ ))
269
+ .limit(50)
270
+ .scanIndexForward(false)
271
+ .execute();
272
+ ```
@@ -0,0 +1,98 @@
1
+ # BatchGet Builder
2
+
3
+ The `BatchGetBuilder` allows retrieving multiple items from one or more tables in a single request to DynamoDB.
4
+
5
+ ## Features
6
+
7
+ - Retrieve multiple items by their keys
8
+ - Supports attribute projection (select only specific fields)
9
+ - Supports consistent read
10
+ - Can retrieve items from multiple tables in a single request
11
+
12
+ ## Basic Usage
13
+
14
+ ```typescript
15
+ import { createBatchGetBuilder } from './builders/batch-get';
16
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
17
+
18
+ const client = new DynamoDBClient({});
19
+
20
+ // Retrieve multiple users
21
+ const result = await createBatchGetBuilder(
22
+ {
23
+ Users: {
24
+ Keys: [
25
+ { pk: 'USER#alice', sk: 'USER#alice' },
26
+ { pk: 'USER#bob', sk: 'USER#bob' },
27
+ { pk: 'USER#charlie', sk: 'USER#charlie' },
28
+ ],
29
+ },
30
+ },
31
+ client
32
+ ).execute();
33
+ ```
34
+
35
+ ## With Attribute Projection
36
+
37
+ ```typescript
38
+ interface User {
39
+ username: string;
40
+ name: string;
41
+ followerCount: number;
42
+ }
43
+
44
+ const result = await createBatchGetBuilder<User>(
45
+ {
46
+ Users: {
47
+ Keys: [
48
+ { pk: 'USER#alice', sk: 'USER#alice' },
49
+ { pk: 'USER#bob', sk: 'USER#bob' },
50
+ ],
51
+ },
52
+ },
53
+ client
54
+ )
55
+ .select(['username', 'name', 'followerCount'])
56
+ .execute();
57
+ ```
58
+
59
+ ## With Consistent Read
60
+
61
+ ```typescript
62
+ const result = await createBatchGetBuilder(
63
+ {
64
+ Users: {
65
+ Keys: [
66
+ { pk: 'USER#alice', sk: 'USER#alice' },
67
+ { pk: 'USER#bob', sk: 'USER#bob' },
68
+ ],
69
+ },
70
+ },
71
+ client
72
+ )
73
+ .consistentRead()
74
+ .execute();
75
+ ```
76
+
77
+ ## Using the Table Class
78
+
79
+ ```typescript
80
+ // Retrieve multiple users using the Table class
81
+ const result = await table
82
+ .batchGet({
83
+ InstagramClone: {
84
+ Keys: [
85
+ { pk: 'USER#alice', sk: 'USER#alice' },
86
+ { pk: 'USER#bob', sk: 'USER#bob' },
87
+ ],
88
+ },
89
+ })
90
+ .select(['username', 'name'])
91
+ .execute();
92
+ ```
93
+
94
+ ## DynamoDB Limitations
95
+
96
+ - Maximum 100 items per request
97
+ - Maximum 16 MB of data per request
98
+ - Unprocessed items are returned in `UnprocessedKeys` and must be retried
@@ -0,0 +1,165 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
3
+ import { createBatchGetBuilder } from './create-batch-get-builder';
4
+
5
+ interface User {
6
+ pk: string;
7
+ sk: string;
8
+ username: string;
9
+ name: string;
10
+ followerCount: number;
11
+ followingCount: number;
12
+ }
13
+
14
+ describe('BatchGetBuilder', () => {
15
+ const client = new DynamoDBClient({});
16
+
17
+ describe('dbParams', () => {
18
+ test('should build basic BatchGetItem params', () => {
19
+ const requestItems = {
20
+ Users: {
21
+ Keys: [
22
+ { pk: 'USER#alice', sk: 'USER#alice' },
23
+ { pk: 'USER#bob', sk: 'USER#bob' },
24
+ ],
25
+ },
26
+ };
27
+
28
+ const builder = createBatchGetBuilder(requestItems, client);
29
+ const params = builder.dbParams();
30
+
31
+ expect(params).toEqual({
32
+ RequestItems: {
33
+ Users: {
34
+ Keys: [
35
+ { pk: 'USER#alice', sk: 'USER#alice' },
36
+ { pk: 'USER#bob', sk: 'USER#bob' },
37
+ ],
38
+ },
39
+ },
40
+ });
41
+ });
42
+
43
+ test('should build BatchGetItem params with projection', () => {
44
+ const requestItems = {
45
+ Users: {
46
+ Keys: [
47
+ { pk: 'USER#alice', sk: 'USER#alice' },
48
+ { pk: 'USER#bob', sk: 'USER#bob' },
49
+ ],
50
+ },
51
+ };
52
+
53
+ const builder = createBatchGetBuilder<User>(requestItems, client).select([
54
+ 'username',
55
+ 'name',
56
+ 'followerCount',
57
+ ]);
58
+ const params = builder.dbParams();
59
+
60
+ expect(params).toEqual({
61
+ RequestItems: {
62
+ Users: {
63
+ Keys: [
64
+ { pk: 'USER#alice', sk: 'USER#alice' },
65
+ { pk: 'USER#bob', sk: 'USER#bob' },
66
+ ],
67
+ ProjectionExpression: 'username, name, followerCount',
68
+ },
69
+ },
70
+ });
71
+ });
72
+
73
+ test('should build BatchGetItem params with consistent read', () => {
74
+ const requestItems = {
75
+ Users: {
76
+ Keys: [
77
+ { pk: 'USER#alice', sk: 'USER#alice' },
78
+ { pk: 'USER#bob', sk: 'USER#bob' },
79
+ ],
80
+ },
81
+ };
82
+
83
+ const builder = createBatchGetBuilder(requestItems, client).consistentRead();
84
+ const params = builder.dbParams();
85
+
86
+ expect(params).toEqual({
87
+ RequestItems: {
88
+ Users: {
89
+ Keys: [
90
+ { pk: 'USER#alice', sk: 'USER#alice' },
91
+ { pk: 'USER#bob', sk: 'USER#bob' },
92
+ ],
93
+ ConsistentRead: true,
94
+ },
95
+ },
96
+ });
97
+ });
98
+
99
+ test('should build BatchGetItem params with multiple tables', () => {
100
+ const requestItems = {
101
+ Users: {
102
+ Keys: [
103
+ { pk: 'USER#alice', sk: 'USER#alice' },
104
+ { pk: 'USER#bob', sk: 'USER#bob' },
105
+ ],
106
+ },
107
+ Photos: {
108
+ Keys: [
109
+ { pk: 'UP#alice', sk: 'PHOTO#photo1' },
110
+ { pk: 'UP#bob', sk: 'PHOTO#photo2' },
111
+ ],
112
+ },
113
+ };
114
+
115
+ const builder = createBatchGetBuilder(requestItems, client);
116
+ const params = builder.dbParams();
117
+
118
+ expect(params).toEqual({
119
+ RequestItems: {
120
+ Users: {
121
+ Keys: [
122
+ { pk: 'USER#alice', sk: 'USER#alice' },
123
+ { pk: 'USER#bob', sk: 'USER#bob' },
124
+ ],
125
+ },
126
+ Photos: {
127
+ Keys: [
128
+ { pk: 'UP#alice', sk: 'PHOTO#photo1' },
129
+ { pk: 'UP#bob', sk: 'PHOTO#photo2' },
130
+ ],
131
+ },
132
+ },
133
+ });
134
+ });
135
+
136
+ test('should build BatchGetItem params with projection and consistent read', () => {
137
+ const requestItems = {
138
+ Users: {
139
+ Keys: [
140
+ { pk: 'USER#alice', sk: 'USER#alice' },
141
+ { pk: 'USER#bob', sk: 'USER#bob' },
142
+ ],
143
+ },
144
+ };
145
+
146
+ const builder = createBatchGetBuilder<User>(requestItems, client)
147
+ .select(['username', 'name'])
148
+ .consistentRead();
149
+ const params = builder.dbParams();
150
+
151
+ expect(params).toEqual({
152
+ RequestItems: {
153
+ Users: {
154
+ Keys: [
155
+ { pk: 'USER#alice', sk: 'USER#alice' },
156
+ { pk: 'USER#bob', sk: 'USER#bob' },
157
+ ],
158
+ ProjectionExpression: 'username, name',
159
+ ConsistentRead: true,
160
+ },
161
+ },
162
+ });
163
+ });
164
+ });
165
+ });
@@ -0,0 +1,106 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
3
+ import {
4
+ BatchGetCommand,
5
+ BatchGetCommandInput,
6
+ BatchGetCommandOutput,
7
+ } from '@aws-sdk/lib-dynamodb';
8
+ import { BatchGetBuilder } from './types';
9
+ import { DynamoDBLogger } from '../../utils/dynamodb-logger';
10
+
11
+ /**
12
+ * Creates a BatchGetBuilder to retrieve multiple items by their keys.
13
+ *
14
+ * @param requestItems - An object where keys are table names and values are objects containing Keys array
15
+ * @param client - DynamoDB client instance
16
+ * @param options - Optional configuration (projection, consistentRead)
17
+ * @param logger - Optional logger instance
18
+ */
19
+ export function createBatchGetBuilder<Model>(
20
+ requestItems: Record<string, { Keys: any[] }>,
21
+ client: DynamoDBClient,
22
+ options?: {
23
+ projection?: (keyof Model)[];
24
+ consistentRead?: boolean;
25
+ },
26
+ logger?: DynamoDBLogger
27
+ ): BatchGetBuilder<Model> {
28
+ const projection = options?.projection ?? [];
29
+ const isConsistent = options?.consistentRead ?? false;
30
+
31
+ return {
32
+ /**
33
+ * Select only specific attributes to retrieve.
34
+ */
35
+ select(attrs) {
36
+ return createBatchGetBuilder(
37
+ requestItems,
38
+ client,
39
+ {
40
+ projection: attrs,
41
+ consistentRead: isConsistent,
42
+ },
43
+ logger
44
+ );
45
+ },
46
+
47
+ /**
48
+ * Enable strongly consistent read.
49
+ */
50
+ consistentRead() {
51
+ return createBatchGetBuilder(
52
+ requestItems,
53
+ client,
54
+ {
55
+ projection,
56
+ consistentRead: true,
57
+ },
58
+ logger
59
+ );
60
+ },
61
+
62
+ /**
63
+ * Build the underlying DynamoDB input parameters.
64
+ */
65
+ dbParams(): BatchGetCommandInput {
66
+ // Apply projection and consistentRead to all tables
67
+ const enhancedRequestItems = Object.entries(requestItems).reduce(
68
+ (acc, [tableName, tableRequest]) => {
69
+ acc[tableName] = {
70
+ Keys: tableRequest.Keys,
71
+ ...(projection.length > 0 && {
72
+ ProjectionExpression: projection.join(', '),
73
+ }),
74
+ ...(isConsistent && { ConsistentRead: true }),
75
+ };
76
+ return acc;
77
+ },
78
+ {} as Record<string, any>
79
+ );
80
+
81
+ return {
82
+ RequestItems: enhancedRequestItems,
83
+ };
84
+ },
85
+
86
+ /**
87
+ * Execute the BatchGetItem command and return the items.
88
+ */
89
+ async execute(): Promise<Record<string, Model[]>> {
90
+ const params = this.dbParams();
91
+ const result: BatchGetCommandOutput = await client.send(new BatchGetCommand(params));
92
+ logger?.log('BatchGetCommand', params, result);
93
+
94
+ // Transform the response into a more usable format
95
+ const responses: Record<string, Model[]> = {};
96
+
97
+ if (result.Responses) {
98
+ for (const [tableName, items] of Object.entries(result.Responses)) {
99
+ responses[tableName] = items as unknown as Model[];
100
+ }
101
+ }
102
+
103
+ return responses;
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './create-batch-get-builder';
@@ -0,0 +1,29 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { BatchGetItemCommandInput } from '@aws-sdk/client-dynamodb';
3
+
4
+ /**
5
+ * BatchGetBuilder allows you to retrieve multiple items from one or more tables
6
+ * in a single request.
7
+ */
8
+ export type BatchGetBuilder<Model> = {
9
+ /**
10
+ * Select only specific attributes to retrieve for all items.
11
+ */
12
+ select(attrs: (keyof Model)[]): BatchGetBuilder<Model>;
13
+
14
+ /**
15
+ * Enable strongly consistent read for all items.
16
+ */
17
+ consistentRead(): BatchGetBuilder<Model>;
18
+
19
+ /**
20
+ * Build the underlying DynamoDB BatchGetItem input parameters.
21
+ */
22
+ dbParams(): BatchGetItemCommandInput;
23
+
24
+ /**
25
+ * Execute the BatchGetItem command and return the items.
26
+ * Returns an object mapping table names to arrays of items.
27
+ */
28
+ execute(): Promise<Record<string, Model[]>>;
29
+ };