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