@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,375 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { Table } from '../../src/table';
3
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
4
+ import { QueryCommand } from '@aws-sdk/lib-dynamodb';
5
+ import { mockClient } from 'aws-sdk-client-mock';
6
+
7
+ const ddbMock = mockClient(DynamoDBClient);
8
+
9
+ /**
10
+ * Integration tests for Pagination and Timestamps features
11
+ */
12
+ describe('Pagination and Timestamps Integration Tests', () => {
13
+ const TestSchema = {
14
+ format: 'dynatable:1.0.0',
15
+ version: '0.0.1',
16
+ indexes: {
17
+ primary: { hash: 'pk', sort: 'sk' },
18
+ },
19
+ models: {
20
+ User: {
21
+ key: {
22
+ PK: { type: String, value: 'USER#${username}' },
23
+ SK: { type: String, value: 'USER#${username}' },
24
+ },
25
+ attributes: {
26
+ username: { type: String, required: true },
27
+ name: { type: String, required: true },
28
+ email: { type: String },
29
+ },
30
+ },
31
+ Post: {
32
+ key: {
33
+ PK: { type: String, value: 'USER#${username}' },
34
+ SK: { type: String, value: 'POST#${postId}' },
35
+ },
36
+ attributes: {
37
+ username: { type: String, required: true },
38
+ postId: { type: String, generate: 'ulid' },
39
+ title: { type: String, required: true },
40
+ content: { type: String },
41
+ },
42
+ },
43
+ },
44
+ params: {
45
+ isoDates: true,
46
+ timestamps: true, // Enable automatic timestamps
47
+ },
48
+ } as const;
49
+
50
+ const client = new DynamoDBClient({});
51
+ const table = new Table({
52
+ name: 'TestTable',
53
+ client,
54
+ schema: TestSchema,
55
+ });
56
+
57
+ beforeEach(() => {
58
+ ddbMock.reset();
59
+ jest.useFakeTimers();
60
+ jest.setSystemTime(new Date('2024-01-15T10:00:00.000Z'));
61
+ });
62
+
63
+ afterEach(() => {
64
+ jest.useRealTimers();
65
+ });
66
+
67
+ describe('Timestamps Feature', () => {
68
+ test('put should add createdAt and updatedAt timestamps', () => {
69
+ const params = table.entities.User.put({
70
+ username: 'alice',
71
+ name: 'Alice Smith',
72
+ email: 'alice@example.com',
73
+ }).dbParams();
74
+
75
+ expect(params.Item.createdAt).toEqual('2024-01-15T10:00:00.000Z');
76
+ expect(params.Item.updatedAt).toEqual('2024-01-15T10:00:00.000Z');
77
+ });
78
+
79
+ test('update should add updatedAt timestamp automatically', () => {
80
+ jest.setSystemTime(new Date('2024-01-15T11:30:00.000Z'));
81
+
82
+ const params = table.entities.User.update({
83
+ username: 'alice',
84
+ })
85
+ .set('name', 'Alice Johnson')
86
+ .dbParams();
87
+
88
+ expect(params.UpdateExpression).toContain('updatedAt');
89
+ expect(params.ExpressionAttributeValues).toMatchObject({
90
+ ':updatedAt_ts': '2024-01-15T11:30:00.000Z',
91
+ });
92
+ });
93
+
94
+ test('update should not add createdAt', () => {
95
+ const params = table.entities.User.update({
96
+ username: 'alice',
97
+ })
98
+ .set('name', 'Alice Johnson')
99
+ .dbParams();
100
+
101
+ expect(params.UpdateExpression).not.toContain('createdAt');
102
+ expect(params.ExpressionAttributeNames).not.toHaveProperty('#createdAt');
103
+ });
104
+
105
+ test('batchWrite should add timestamps to all items', () => {
106
+ const params = table.entities.User.batchWrite([
107
+ { username: 'alice', name: 'Alice Smith' },
108
+ { username: 'bob', name: 'Bob Jones' },
109
+ { username: 'charlie', name: 'Charlie Brown' },
110
+ ]).dbParams();
111
+
112
+ const items = params.RequestItems?.TestTable.map((req: any) => req.PutRequest.Item);
113
+
114
+ items?.forEach((item: any) => {
115
+ expect(item.createdAt).toEqual('2024-01-15T10:00:00.000Z');
116
+ expect(item.updatedAt).toEqual('2024-01-15T10:00:00.000Z');
117
+ });
118
+ });
119
+
120
+ test('timestamps should work with generated IDs', () => {
121
+ const params = table.entities.Post.put({
122
+ username: 'alice',
123
+ title: 'My First Post',
124
+ content: 'Hello World!',
125
+ }).dbParams();
126
+
127
+ expect(params.Item.postId).toBeDefined();
128
+ expect(params.Item.createdAt).toEqual('2024-01-15T10:00:00.000Z');
129
+ expect(params.Item.updatedAt).toEqual('2024-01-15T10:00:00.000Z');
130
+ });
131
+
132
+ test('update with multiple SET operations should include updatedAt', () => {
133
+ const params = table.entities.User.update({
134
+ username: 'alice',
135
+ })
136
+ .set('name', 'Alice Johnson')
137
+ .set('email', 'alice.j@example.com')
138
+ .dbParams();
139
+
140
+ const updateExpr = params.UpdateExpression;
141
+
142
+ // Should include updatedAt in the SET expression
143
+ expect(updateExpr).toContain('updatedAt');
144
+ expect(updateExpr).toContain('SET');
145
+
146
+ // Verify timestamp value is set
147
+ expect(params.ExpressionAttributeValues[':updatedAt_ts']).toEqual('2024-01-15T10:00:00.000Z');
148
+ });
149
+ });
150
+
151
+ describe('Pagination Feature', () => {
152
+ test('query should support startFrom for pagination', () => {
153
+ const lastKey = {
154
+ PK: 'USER#alice',
155
+ SK: 'POST#01234567890',
156
+ };
157
+
158
+ const params = table.entities.Post.query()
159
+ .where((attr, op) => op.eq(attr.username, 'alice'))
160
+ .limit(10)
161
+ .startFrom(lastKey)
162
+ .dbParams();
163
+
164
+ expect(params.ExclusiveStartKey).toEqual(lastKey);
165
+ expect(params.Limit).toBe(10);
166
+ });
167
+
168
+ test('executeWithPagination should return items and pagination metadata', async () => {
169
+ const mockItems = [
170
+ {
171
+ PK: 'USER#alice',
172
+ SK: 'POST#001',
173
+ username: 'alice',
174
+ postId: '001',
175
+ title: 'Post 1',
176
+ createdAt: new Date('2024-01-15T10:00:00.000Z'),
177
+ updatedAt: new Date('2024-01-15T10:00:00.000Z'),
178
+ },
179
+ {
180
+ PK: 'USER#alice',
181
+ SK: 'POST#002',
182
+ username: 'alice',
183
+ postId: '002',
184
+ title: 'Post 2',
185
+ createdAt: new Date('2024-01-15T10:05:00.000Z'),
186
+ updatedAt: new Date('2024-01-15T10:05:00.000Z'),
187
+ },
188
+ ];
189
+
190
+ const lastKey = {
191
+ PK: 'USER#alice',
192
+ SK: 'POST#002',
193
+ };
194
+
195
+ ddbMock.on(QueryCommand).resolves({
196
+ Items: mockItems,
197
+ LastEvaluatedKey: lastKey,
198
+ Count: 2,
199
+ ScannedCount: 2,
200
+ });
201
+
202
+ const result = await table.entities.Post.query()
203
+ .where((attr, op) => op.eq(attr.username, 'alice'))
204
+ .limit(2)
205
+ .executeWithPagination();
206
+
207
+ expect(result.items).toHaveLength(2);
208
+ expect(result.lastEvaluatedKey).toEqual(lastKey);
209
+ expect(result.count).toBe(2);
210
+ expect(result.scannedCount).toBe(2);
211
+ });
212
+
213
+ test('pagination workflow with multiple pages', async () => {
214
+ const page1 = [
215
+ {
216
+ PK: 'USER#alice',
217
+ SK: 'POST#001',
218
+ username: 'alice',
219
+ postId: '001',
220
+ title: 'Post 1',
221
+ },
222
+ {
223
+ PK: 'USER#alice',
224
+ SK: 'POST#002',
225
+ username: 'alice',
226
+ postId: '002',
227
+ title: 'Post 2',
228
+ },
229
+ ];
230
+ const page1LastKey = { PK: 'USER#alice', SK: 'POST#002' };
231
+
232
+ const page2 = [
233
+ {
234
+ PK: 'USER#alice',
235
+ SK: 'POST#003',
236
+ username: 'alice',
237
+ postId: '003',
238
+ title: 'Post 3',
239
+ },
240
+ {
241
+ PK: 'USER#alice',
242
+ SK: 'POST#004',
243
+ username: 'alice',
244
+ postId: '004',
245
+ title: 'Post 4',
246
+ },
247
+ ];
248
+ const page2LastKey = { PK: 'USER#alice', SK: 'POST#004' };
249
+
250
+ const page3 = [
251
+ {
252
+ PK: 'USER#alice',
253
+ SK: 'POST#005',
254
+ username: 'alice',
255
+ postId: '005',
256
+ title: 'Post 5',
257
+ },
258
+ ];
259
+
260
+ ddbMock
261
+ .on(QueryCommand)
262
+ .resolvesOnce({
263
+ Items: page1,
264
+ LastEvaluatedKey: page1LastKey,
265
+ Count: 2,
266
+ ScannedCount: 2,
267
+ })
268
+ .resolvesOnce({
269
+ Items: page2,
270
+ LastEvaluatedKey: page2LastKey,
271
+ Count: 2,
272
+ ScannedCount: 2,
273
+ })
274
+ .resolvesOnce({
275
+ Items: page3,
276
+ Count: 1,
277
+ ScannedCount: 1,
278
+ });
279
+
280
+ const allItems: any[] = [];
281
+ let lastKey: any;
282
+
283
+ // Page 1
284
+ const result1 = await table.entities.Post.query()
285
+ .where((attr, op) => op.eq(attr.username, 'alice'))
286
+ .limit(2)
287
+ .executeWithPagination();
288
+
289
+ allItems.push(...result1.items);
290
+ lastKey = result1.lastEvaluatedKey;
291
+ expect(lastKey).toBeDefined();
292
+
293
+ // Page 2
294
+ const result2 = await table.entities.Post.query()
295
+ .where((attr, op) => op.eq(attr.username, 'alice'))
296
+ .limit(2)
297
+ .startFrom(lastKey)
298
+ .executeWithPagination();
299
+
300
+ allItems.push(...result2.items);
301
+ lastKey = result2.lastEvaluatedKey;
302
+ expect(lastKey).toBeDefined();
303
+
304
+ // Page 3 (final)
305
+ const result3 = await table.entities.Post.query()
306
+ .where((attr, op) => op.eq(attr.username, 'alice'))
307
+ .limit(2)
308
+ .startFrom(lastKey)
309
+ .executeWithPagination();
310
+
311
+ allItems.push(...result3.items);
312
+ lastKey = result3.lastEvaluatedKey;
313
+ expect(lastKey).toBeUndefined();
314
+
315
+ // Verify all items collected
316
+ expect(allItems).toHaveLength(5);
317
+ expect(allItems.map((item) => item.postId)).toEqual(['001', '002', '003', '004', '005']);
318
+ });
319
+
320
+ test('pagination should work with filters', () => {
321
+ const lastKey = { PK: 'USER#alice', SK: 'POST#010' };
322
+
323
+ const params = table.entities.Post.query()
324
+ .where((attr, op) => op.eq(attr.username, 'alice'))
325
+ .limit(5)
326
+ .startFrom(lastKey)
327
+ .scanIndexForward(false)
328
+ .dbParams();
329
+
330
+ expect(params.ExclusiveStartKey).toEqual(lastKey);
331
+ expect(params.Limit).toBe(5);
332
+ expect(params.ScanIndexForward).toBe(false);
333
+ });
334
+ });
335
+
336
+ describe('Combined Timestamps and Pagination', () => {
337
+ test('paginated results should include timestamp fields', async () => {
338
+ const mockItems = [
339
+ {
340
+ PK: 'USER#alice',
341
+ SK: 'POST#001',
342
+ username: 'alice',
343
+ postId: '001',
344
+ title: 'Post 1',
345
+ createdAt: new Date('2024-01-15T10:00:00.000Z'),
346
+ updatedAt: new Date('2024-01-15T10:00:00.000Z'),
347
+ },
348
+ ];
349
+
350
+ ddbMock.on(QueryCommand).resolves({
351
+ Items: mockItems,
352
+ Count: 1,
353
+ ScannedCount: 1,
354
+ });
355
+
356
+ const result = await table.entities.Post.query()
357
+ .where((attr, op) => op.eq(attr.username, 'alice'))
358
+ .executeWithPagination();
359
+
360
+ //Fixme createdAt and updatedAt are missing in the result items, we need to infer them
361
+ expect((result.items[0] as any).createdAt).toBeDefined();
362
+ expect((result.items[0] as any).updatedAt).toBeDefined();
363
+ });
364
+
365
+ test('should be able to select timestamp fields in projection', () => {
366
+ const params = table.entities.Post.query()
367
+ .where((attr, op) => op.eq(attr.username, 'alice'))
368
+ .select(['postId', 'title', 'createdAt', 'updatedAt'] as any)
369
+ .dbParams();
370
+
371
+ expect(params.ProjectionExpression).toContain('createdAt');
372
+ expect(params.ProjectionExpression).toContain('updatedAt');
373
+ });
374
+ });
375
+ });