@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,260 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
3
+ import { createScanBuilder } from './create-scan-builder';
4
+
5
+ describe('ScanBuilder', () => {
6
+ const client = new DynamoDBClient({});
7
+ const tableName = 'TestTable';
8
+
9
+ interface TestModel {
10
+ pk: string;
11
+ sk: string;
12
+ name?: string;
13
+ age?: number;
14
+ status?: string;
15
+ score?: number;
16
+ }
17
+
18
+ describe('Basic scan operations', () => {
19
+ test('should build params for basic scan', () => {
20
+ const params = createScanBuilder<TestModel>(tableName, client).dbParams();
21
+
22
+ expect(params.TableName).toBe(tableName);
23
+ expect(params.FilterExpression).toBeUndefined();
24
+ expect(params.ProjectionExpression).toBeUndefined();
25
+ expect(params.Limit).toBeUndefined();
26
+ });
27
+
28
+ test('should build params with filter', () => {
29
+ const params = createScanBuilder<TestModel>(tableName, client)
30
+ .filter((attr, op) => op.eq(attr.status, 'active'))
31
+ .dbParams();
32
+
33
+ expect(params.TableName).toBe(tableName);
34
+ expect(params.FilterExpression).toMatch(/#status = :status_\d+/);
35
+ expect(params.ExpressionAttributeNames).toEqual({
36
+ '#status': 'status',
37
+ });
38
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain('active');
39
+ });
40
+
41
+ test('should build params with multiple filters using AND', () => {
42
+ const params = createScanBuilder<TestModel>(tableName, client)
43
+ .filter((attr, op) => op.gt(attr.age, 18))
44
+ .filter((attr, op) => op.eq(attr.status, 'active'))
45
+ .dbParams();
46
+
47
+ expect(params.TableName).toBe(tableName);
48
+ expect(params.FilterExpression).toMatch(/\(#age > :age_\d+\) AND \(#status = :status_\d+\)/);
49
+ expect(params.ExpressionAttributeNames).toEqual({
50
+ '#age': 'age',
51
+ '#status': 'status',
52
+ });
53
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain(18);
54
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain('active');
55
+ });
56
+
57
+ test('should build params with OR filter', () => {
58
+ const params = createScanBuilder<TestModel>(tableName, client)
59
+ .filter((attr, op) => op.or(op.eq(attr.status, 'active'), op.eq(attr.status, 'pending')))
60
+ .dbParams();
61
+
62
+ expect(params.FilterExpression).toMatch(
63
+ /\(#status = :status_\d+\) OR \(#status = :status_\d+\)/
64
+ );
65
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain('active');
66
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain('pending');
67
+ });
68
+ });
69
+
70
+ describe('Projection', () => {
71
+ test('should build params with select', () => {
72
+ const params = createScanBuilder<TestModel>(tableName, client)
73
+ .select(['name', 'age', 'status'])
74
+ .dbParams();
75
+
76
+ expect(params.ProjectionExpression).toBe('name, age, status');
77
+ });
78
+
79
+ test('should build params with select and filter', () => {
80
+ const params = createScanBuilder<TestModel>(tableName, client)
81
+ .select(['name', 'age'])
82
+ .filter((attr, op) => op.gt(attr.age, 18))
83
+ .dbParams();
84
+
85
+ expect(params.ProjectionExpression).toBe('name, age');
86
+ expect(params.FilterExpression).toMatch(/#age > :age_\d+/);
87
+ });
88
+ });
89
+
90
+ describe('Limit', () => {
91
+ test('should build params with limit', () => {
92
+ const params = createScanBuilder<TestModel>(tableName, client).limit(100).dbParams();
93
+
94
+ expect(params.Limit).toBe(100);
95
+ });
96
+
97
+ test('should build params with limit and filter', () => {
98
+ const params = createScanBuilder<TestModel>(tableName, client)
99
+ .filter((attr, op) => op.eq(attr.status, 'active'))
100
+ .limit(50)
101
+ .dbParams();
102
+
103
+ expect(params.Limit).toBe(50);
104
+ expect(params.FilterExpression).toMatch(/#status = :status_\d+/);
105
+ });
106
+ });
107
+
108
+ describe('Consistent Read', () => {
109
+ test('should build params with consistent read', () => {
110
+ const params = createScanBuilder<TestModel>(tableName, client).consistentRead().dbParams();
111
+
112
+ expect(params.ConsistentRead).toBe(true);
113
+ });
114
+
115
+ test('should not set ConsistentRead by default', () => {
116
+ const params = createScanBuilder<TestModel>(tableName, client).dbParams();
117
+
118
+ expect(params.ConsistentRead).toBeUndefined();
119
+ });
120
+ });
121
+
122
+ describe('Index Scan', () => {
123
+ test('should build params with index name', () => {
124
+ const params = createScanBuilder<TestModel>(tableName, client).usingIndex('GSI1').dbParams();
125
+
126
+ expect(params.IndexName).toBe('GSI1');
127
+ });
128
+
129
+ test('should build params with index and filter', () => {
130
+ const params = createScanBuilder<TestModel>(tableName, client)
131
+ .usingIndex('GSI1')
132
+ .filter((attr, op) => op.gt(attr.score, 100))
133
+ .dbParams();
134
+
135
+ expect(params.IndexName).toBe('GSI1');
136
+ expect(params.FilterExpression).toMatch(/#score > :score_\d+/);
137
+ });
138
+ });
139
+
140
+ describe('Pagination', () => {
141
+ test('should build params with ExclusiveStartKey', () => {
142
+ const startKey = { pk: 'USER#1', sk: 'USER#1' };
143
+ const params = createScanBuilder<TestModel>(tableName, client).startFrom(startKey).dbParams();
144
+
145
+ expect(params.ExclusiveStartKey).toEqual(startKey);
146
+ });
147
+ });
148
+
149
+ describe('Parallel Scan', () => {
150
+ test('should build params with segment configuration', () => {
151
+ const params = createScanBuilder<TestModel>(tableName, client).segment(0, 4).dbParams();
152
+
153
+ expect(params.Segment).toBe(0);
154
+ expect(params.TotalSegments).toBe(4);
155
+ });
156
+
157
+ test('should build params with multiple segments', () => {
158
+ const params1 = createScanBuilder<TestModel>(tableName, client).segment(0, 3).dbParams();
159
+ const params2 = createScanBuilder<TestModel>(tableName, client).segment(1, 3).dbParams();
160
+ const params3 = createScanBuilder<TestModel>(tableName, client).segment(2, 3).dbParams();
161
+
162
+ expect(params1.Segment).toBe(0);
163
+ expect(params1.TotalSegments).toBe(3);
164
+ expect(params2.Segment).toBe(1);
165
+ expect(params2.TotalSegments).toBe(3);
166
+ expect(params3.Segment).toBe(2);
167
+ expect(params3.TotalSegments).toBe(3);
168
+ });
169
+ });
170
+
171
+ describe('Complex scenarios', () => {
172
+ test('should build params with all features', () => {
173
+ const params = createScanBuilder<TestModel>(tableName, client)
174
+ .filter((attr, op) => op.and(op.gt(attr.age, 18), op.eq(attr.status, 'active')))
175
+ .select(['name', 'age', 'status'])
176
+ .limit(100)
177
+ .consistentRead()
178
+ .usingIndex('GSI1')
179
+ .dbParams();
180
+
181
+ expect(params.TableName).toBe(tableName);
182
+ expect(params.FilterExpression).toMatch(/\(#age > :age_\d+\) AND \(#status = :status_\d+\)/);
183
+ expect(params.ProjectionExpression).toBe('name, age, status');
184
+ expect(params.Limit).toBe(100);
185
+ expect(params.ConsistentRead).toBe(true);
186
+ expect(params.IndexName).toBe('GSI1');
187
+ });
188
+
189
+ test('should build params for parallel scan with filter and limit', () => {
190
+ const params = createScanBuilder<TestModel>(tableName, client)
191
+ .filter((attr, op) => op.exists(attr.name))
192
+ .limit(50)
193
+ .segment(1, 4)
194
+ .dbParams();
195
+
196
+ expect(params.FilterExpression).toMatch(/attribute_exists\(#name\)/);
197
+ expect(params.Limit).toBe(50);
198
+ expect(params.Segment).toBe(1);
199
+ expect(params.TotalSegments).toBe(4);
200
+ });
201
+ });
202
+
203
+ describe('Immutability', () => {
204
+ test('should create new builder instance on each method call', () => {
205
+ const builder1 = createScanBuilder<TestModel>(tableName, client);
206
+ const builder2 = builder1.filter((attr, op) => op.eq(attr.status, 'active'));
207
+ const builder3 = builder2.limit(50);
208
+
209
+ expect(builder1.dbParams().FilterExpression).toBeUndefined();
210
+ expect(builder1.dbParams().Limit).toBeUndefined();
211
+
212
+ expect(builder2.dbParams().FilterExpression).toMatch(/#status = :status_\d+/);
213
+ expect(builder2.dbParams().Limit).toBeUndefined();
214
+
215
+ expect(builder3.dbParams().FilterExpression).toMatch(/#status = :status_\d+/);
216
+ expect(builder3.dbParams().Limit).toBe(50);
217
+ });
218
+ });
219
+
220
+ describe('Advanced operators', () => {
221
+ test('should support contains operator', () => {
222
+ const params = createScanBuilder<TestModel>(tableName, client)
223
+ .filter((attr, op) => op.contains(attr.name, 'John'))
224
+ .dbParams();
225
+
226
+ expect(params.FilterExpression).toMatch(/contains\(#name, :name_\d+\)/);
227
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain('John');
228
+ });
229
+
230
+ test('should support IN operator', () => {
231
+ const params = createScanBuilder<TestModel>(tableName, client)
232
+ .filter((attr, op) => op.in(attr.status, ['active', 'pending', 'approved']))
233
+ .dbParams();
234
+
235
+ expect(params.FilterExpression).toContain('IN');
236
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain('active');
237
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain('pending');
238
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain('approved');
239
+ });
240
+
241
+ test('should support size operator', () => {
242
+ const params = createScanBuilder<TestModel>(tableName, client)
243
+ .filter((attr, op) => op.size(attr.name).gt(10))
244
+ .dbParams();
245
+
246
+ expect(params.FilterExpression).toMatch(/size\(#name\) > :name_size_\d+/);
247
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain(10);
248
+ });
249
+
250
+ test('should support between operator', () => {
251
+ const params = createScanBuilder<TestModel>(tableName, client)
252
+ .filter((attr, op) => op.between(attr.age, 18, 65))
253
+ .dbParams();
254
+
255
+ expect(params.FilterExpression).toMatch(/#age BETWEEN :age_low_\d+ AND :age_high_\d+/);
256
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain(18);
257
+ expect(Object.values(params.ExpressionAttributeValues || {})).toContain(65);
258
+ });
259
+ });
260
+ });
@@ -0,0 +1,217 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
3
+ import { ScanCommand } from '@aws-sdk/lib-dynamodb';
4
+ import { buildExpression, AttrBuilder, Condition, createOpBuilder } from '../shared';
5
+ import { ScanBuilder } from './types';
6
+ import { DynamoDBLogger } from '../../utils/dynamodb-logger';
7
+
8
+ /**
9
+ * Creates a ScanBuilder for scanning a table or index.
10
+ */
11
+ export function createScanBuilder<Model>(
12
+ tableName: string,
13
+ client: DynamoDBClient,
14
+ filters: Condition[] = [],
15
+ projectionAttrs: (keyof Model)[] = [],
16
+ limitValue?: number,
17
+ isConsistentRead = false,
18
+ indexName?: string,
19
+ exclusiveStartKey?: Record<string, any>,
20
+ segmentConfig?: { segment: number; totalSegments: number },
21
+ logger?: DynamoDBLogger
22
+ ): ScanBuilder<Model> {
23
+ const build = (): ScanBuilder<Model> => ({
24
+ filter(fn) {
25
+ const attrs = new Proxy({} as AttrBuilder<Model>, {
26
+ get(_, prop: string) {
27
+ return { name: prop };
28
+ },
29
+ });
30
+ const opBuilder = createOpBuilder();
31
+ const condition = fn(attrs, opBuilder);
32
+ return createScanBuilder(
33
+ tableName,
34
+ client,
35
+ [...filters, condition],
36
+ projectionAttrs,
37
+ limitValue,
38
+ isConsistentRead,
39
+ indexName,
40
+ exclusiveStartKey,
41
+ segmentConfig,
42
+ logger
43
+ );
44
+ },
45
+
46
+ select(attrs) {
47
+ return createScanBuilder(
48
+ tableName,
49
+ client,
50
+ filters,
51
+ attrs,
52
+ limitValue,
53
+ isConsistentRead,
54
+ indexName,
55
+ exclusiveStartKey,
56
+ segmentConfig,
57
+ logger
58
+ );
59
+ },
60
+
61
+ limit(count) {
62
+ return createScanBuilder(
63
+ tableName,
64
+ client,
65
+ filters,
66
+ projectionAttrs,
67
+ count,
68
+ isConsistentRead,
69
+ indexName,
70
+ exclusiveStartKey,
71
+ segmentConfig,
72
+ logger
73
+ );
74
+ },
75
+
76
+ consistentRead() {
77
+ return createScanBuilder(
78
+ tableName,
79
+ client,
80
+ filters,
81
+ projectionAttrs,
82
+ limitValue,
83
+ true,
84
+ indexName,
85
+ exclusiveStartKey,
86
+ segmentConfig,
87
+ logger
88
+ );
89
+ },
90
+
91
+ usingIndex(index) {
92
+ return createScanBuilder(
93
+ tableName,
94
+ client,
95
+ filters,
96
+ projectionAttrs,
97
+ limitValue,
98
+ isConsistentRead,
99
+ index,
100
+ exclusiveStartKey,
101
+ segmentConfig,
102
+ logger
103
+ );
104
+ },
105
+
106
+ startFrom(key) {
107
+ return createScanBuilder(
108
+ tableName,
109
+ client,
110
+ filters,
111
+ projectionAttrs,
112
+ limitValue,
113
+ isConsistentRead,
114
+ indexName,
115
+ key,
116
+ segmentConfig,
117
+ logger
118
+ );
119
+ },
120
+
121
+ segment(segmentNumber, totalSegments) {
122
+ return createScanBuilder(
123
+ tableName,
124
+ client,
125
+ filters,
126
+ projectionAttrs,
127
+ limitValue,
128
+ isConsistentRead,
129
+ indexName,
130
+ exclusiveStartKey,
131
+ { segment: segmentNumber, totalSegments },
132
+ logger
133
+ );
134
+ },
135
+
136
+ dbParams() {
137
+ let filterExpression = '';
138
+ let expressionAttributeNames: Record<string, string> = {};
139
+ let expressionAttributeValues: Record<string, any> = {};
140
+
141
+ // Build FilterExpression from filters
142
+ if (filters.length > 0) {
143
+ const combinedFilter =
144
+ filters.length === 1 && filters[0]
145
+ ? filters[0]
146
+ : {
147
+ expression: '',
148
+ operator: 'AND' as const,
149
+ children: filters,
150
+ };
151
+
152
+ const result = buildExpression(combinedFilter);
153
+ filterExpression = result.expression;
154
+ expressionAttributeNames = result.names;
155
+ expressionAttributeValues = result.values;
156
+ }
157
+
158
+ // Build ProjectionExpression
159
+ let projectionExpression = '';
160
+ if (projectionAttrs.length > 0) {
161
+ projectionExpression = projectionAttrs.map((attr) => String(attr)).join(', ');
162
+ }
163
+
164
+ const params: any = {
165
+ TableName: tableName,
166
+ };
167
+
168
+ if (filterExpression) {
169
+ params.FilterExpression = filterExpression;
170
+ }
171
+
172
+ if (Object.keys(expressionAttributeNames).length > 0) {
173
+ params.ExpressionAttributeNames = expressionAttributeNames;
174
+ }
175
+
176
+ if (Object.keys(expressionAttributeValues).length > 0) {
177
+ params.ExpressionAttributeValues = expressionAttributeValues;
178
+ }
179
+
180
+ if (projectionExpression) {
181
+ params.ProjectionExpression = projectionExpression;
182
+ }
183
+
184
+ if (limitValue !== undefined) {
185
+ params.Limit = limitValue;
186
+ }
187
+
188
+ if (isConsistentRead) {
189
+ params.ConsistentRead = true;
190
+ }
191
+
192
+ if (indexName) {
193
+ params.IndexName = indexName;
194
+ }
195
+
196
+ if (exclusiveStartKey) {
197
+ params.ExclusiveStartKey = exclusiveStartKey;
198
+ }
199
+
200
+ if (segmentConfig) {
201
+ params.Segment = segmentConfig.segment;
202
+ params.TotalSegments = segmentConfig.totalSegments;
203
+ }
204
+
205
+ return params;
206
+ },
207
+
208
+ async execute() {
209
+ const params = build().dbParams();
210
+ const response = await client.send(new ScanCommand(params));
211
+ logger?.log('ScanCommand', params, response);
212
+ return (response.Items || []) as Model[];
213
+ },
214
+ });
215
+
216
+ return build();
217
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './create-scan-builder';
@@ -0,0 +1,49 @@
1
+ import { ExecutableBuilder, AttrBuilder, OpBuilder, Condition } from '../shared';
2
+
3
+ /**
4
+ * Builder for DynamoDB Scan operations.
5
+ * Scans the entire table or index without requiring key conditions.
6
+ */
7
+ export interface ScanBuilder<Model> extends ExecutableBuilder<Model[]> {
8
+ /**
9
+ * Adds a filter expression to the scan.
10
+ * Returns a new immutable builder.
11
+ */
12
+ filter(fn: (attr: AttrBuilder<Model>, op: OpBuilder) => Condition): ScanBuilder<Model>;
13
+
14
+ /**
15
+ * Adds a projection expression to only return specific attributes.
16
+ * Returns a new immutable builder.
17
+ */
18
+ select(attrs: (keyof Model)[]): ScanBuilder<Model>;
19
+
20
+ /**
21
+ * Sets the maximum number of items to evaluate (not necessarily return).
22
+ * Returns a new immutable builder.
23
+ */
24
+ limit(count: number): ScanBuilder<Model>;
25
+
26
+ /**
27
+ * Enables consistent read (eventual consistency is default).
28
+ * Returns a new immutable builder.
29
+ */
30
+ consistentRead(): ScanBuilder<Model>;
31
+
32
+ /**
33
+ * Specifies an index to scan.
34
+ * Returns a new immutable builder.
35
+ */
36
+ usingIndex(indexName: string): ScanBuilder<Model>;
37
+
38
+ /**
39
+ * Sets the starting position for the scan (pagination).
40
+ * Returns a new immutable builder.
41
+ */
42
+ startFrom(exclusiveStartKey: Record<string, any>): ScanBuilder<Model>;
43
+
44
+ /**
45
+ * Enables parallel scan by specifying segment and total segments.
46
+ * Returns a new immutable builder.
47
+ */
48
+ segment(segmentNumber: number, totalSegments: number): ScanBuilder<Model>;
49
+ }
@@ -0,0 +1,58 @@
1
+ import { Condition } from './types';
2
+
3
+ /**
4
+ * Builds a DynamoDB expression from a condition tree
5
+ */
6
+ export function buildExpression(condition: Condition): {
7
+ expression: string;
8
+ names: Record<string, string>;
9
+ values: Record<string, any>;
10
+ } {
11
+ const names: Record<string, string> = {};
12
+ const values: Record<string, any> = {};
13
+
14
+ function processCondition(cond: Condition): string {
15
+ // If it's a combinator (AND/OR)
16
+ if (cond.operator && cond.children) {
17
+ const childExpressions = cond.children
18
+ .map((child) => processCondition(child))
19
+ .filter((expr) => expr.length > 0);
20
+
21
+ if (childExpressions.length === 0) return '';
22
+ if (childExpressions.length === 1) return childExpressions[0]!;
23
+
24
+ const combined = childExpressions.map((expr) => `(${expr})`).join(` ${cond.operator} `);
25
+
26
+ return cond.isNegated ? `NOT (${combined})` : combined;
27
+ }
28
+
29
+ // It's a leaf condition
30
+ if (cond.names) {
31
+ Object.assign(names, cond.names);
32
+ }
33
+ if (cond.values) {
34
+ Object.assign(values, cond.values);
35
+ }
36
+
37
+ return cond.isNegated ? `NOT (${cond.expression})` : cond.expression;
38
+ }
39
+
40
+ const expression = processCondition(condition);
41
+
42
+ return { expression, names, values };
43
+ }
44
+
45
+ /**
46
+ * Combines multiple conditions into a DynamoDB-compatible expression object.
47
+ */
48
+ export function buildConditionExpr(conditions: Condition[]) {
49
+ const expression = conditions.map((c) => c.expression).join(' AND ');
50
+ const names = Object.assign({}, ...conditions.map((c) => c.names ?? {}));
51
+ const values = Object.assign({}, ...conditions.map((c) => c.values ?? {}));
52
+
53
+ return {
54
+ ConditionExpression: expression || undefined,
55
+ ExpressionAttributeNames: Object.keys(names).length ? names : undefined,
56
+ ExpressionAttributeValues: Object.keys(values).length ? values : undefined,
57
+ };
58
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './operators';
3
+ export * from './conditions';