@ftschopp/dynatable-core 1.4.2 → 1.4.4

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 (86) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/builders/query/create-query-builder.d.ts.map +1 -1
  3. package/dist/builders/query/create-query-builder.js +19 -2
  4. package/dist/builders/update/create-update-builder.d.ts.map +1 -1
  5. package/dist/builders/update/create-update-builder.js +25 -0
  6. package/dist/core/types.d.ts +5 -0
  7. package/dist/core/types.d.ts.map +1 -1
  8. package/dist/utils/model-utils.d.ts +9 -3
  9. package/dist/utils/model-utils.d.ts.map +1 -1
  10. package/dist/utils/model-utils.js +15 -7
  11. package/package.json +19 -2
  12. package/eslint.config.mjs +0 -4
  13. package/jest.config.js +0 -11
  14. package/src/builders/README.md +0 -339
  15. package/src/builders/batch-get/README.md +0 -43
  16. package/src/builders/batch-get/create-batch-get-builder.test.ts +0 -193
  17. package/src/builders/batch-get/create-batch-get-builder.ts +0 -108
  18. package/src/builders/batch-get/index.ts +0 -2
  19. package/src/builders/batch-get/types.ts +0 -28
  20. package/src/builders/batch-write/README.md +0 -204
  21. package/src/builders/batch-write/create-batch-write-builder.test.ts +0 -173
  22. package/src/builders/batch-write/create-batch-write-builder.ts +0 -49
  23. package/src/builders/batch-write/index.ts +0 -2
  24. package/src/builders/batch-write/types.ts +0 -33
  25. package/src/builders/delete/create-delete-builder.test.ts +0 -294
  26. package/src/builders/delete/create-delete-builder.ts +0 -100
  27. package/src/builders/delete/index.ts +0 -2
  28. package/src/builders/delete/types.ts +0 -17
  29. package/src/builders/get/create-get-builder.test.ts +0 -272
  30. package/src/builders/get/create-get-builder.ts +0 -117
  31. package/src/builders/get/index.ts +0 -2
  32. package/src/builders/get/types.ts +0 -39
  33. package/src/builders/index.ts +0 -14
  34. package/src/builders/put/create-put-builder.test.ts +0 -213
  35. package/src/builders/put/create-put-builder.ts +0 -160
  36. package/src/builders/put/index.ts +0 -2
  37. package/src/builders/put/types.ts +0 -24
  38. package/src/builders/query/create-query-builder.test.ts +0 -770
  39. package/src/builders/query/create-query-builder.ts +0 -455
  40. package/src/builders/query/index.ts +0 -2
  41. package/src/builders/query/types.ts +0 -109
  42. package/src/builders/scan/create-scan-builder.test.ts +0 -416
  43. package/src/builders/scan/create-scan-builder.ts +0 -265
  44. package/src/builders/scan/index.ts +0 -2
  45. package/src/builders/scan/types.ts +0 -88
  46. package/src/builders/shared/conditions.ts +0 -58
  47. package/src/builders/shared/index.ts +0 -4
  48. package/src/builders/shared/operators.test.ts +0 -270
  49. package/src/builders/shared/operators.ts +0 -208
  50. package/src/builders/shared/projection.ts +0 -28
  51. package/src/builders/shared/types.ts +0 -101
  52. package/src/builders/transact-get/README.md +0 -167
  53. package/src/builders/transact-get/create-transact-get-builder.test.ts +0 -239
  54. package/src/builders/transact-get/create-transact-get-builder.ts +0 -67
  55. package/src/builders/transact-get/index.ts +0 -2
  56. package/src/builders/transact-get/types.ts +0 -31
  57. package/src/builders/transact-write/README.md +0 -202
  58. package/src/builders/transact-write/create-transact-write-builder.test.ts +0 -288
  59. package/src/builders/transact-write/create-transact-write-builder.ts +0 -124
  60. package/src/builders/transact-write/index.ts +0 -2
  61. package/src/builders/transact-write/types.ts +0 -71
  62. package/src/builders/update/create-update-builder.test.ts +0 -573
  63. package/src/builders/update/create-update-builder.ts +0 -375
  64. package/src/builders/update/index.ts +0 -2
  65. package/src/builders/update/types.ts +0 -63
  66. package/src/core/types.test.ts +0 -641
  67. package/src/core/types.ts +0 -366
  68. package/src/entity/create-entity-api.ts +0 -224
  69. package/src/entity/index.ts +0 -19
  70. package/src/entity/middleware/factories.ts +0 -15
  71. package/src/entity/middleware/types.ts +0 -7
  72. package/src/entity/middleware/with-middleware.ts +0 -37
  73. package/src/entity/types.ts +0 -79
  74. package/src/entity/validation/key-validation.ts +0 -34
  75. package/src/index.ts +0 -23
  76. package/src/table.ts +0 -116
  77. package/src/utils/dynamodb-logger.test.ts +0 -246
  78. package/src/utils/dynamodb-logger.ts +0 -175
  79. package/src/utils/model-utils.test.ts +0 -452
  80. package/src/utils/model-utils.ts +0 -188
  81. package/src/utils/zod-utils.test.ts +0 -363
  82. package/src/utils/zod-utils.ts +0 -56
  83. package/tests/integration/instagram-clone.integration.test.ts +0 -1019
  84. package/tests/integration/pagination-timestamps.integration.test.ts +0 -374
  85. package/tests/integration/transactions.integration.test.ts +0 -528
  86. package/tsconfig.json +0 -12
@@ -1,528 +0,0 @@
1
- import { Table } from '../../src/table';
2
- import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
3
-
4
- /**
5
- * Instagram Clone Schema - same as demo.instagram.test.ts
6
- */
7
- const InstagramSchema = {
8
- format: 'dynatable:1.0.0',
9
- version: '1.0.0',
10
- indexes: {
11
- primary: { hash: 'PK', sort: 'SK' },
12
- gs1: { hash: 'GSI1PK', sort: 'GSI1SK' },
13
- },
14
- models: {
15
- User: {
16
- key: {
17
- PK: { type: String, value: 'USER#${username}' },
18
- SK: { type: String, value: 'USER#${username}' },
19
- },
20
- attributes: {
21
- username: { type: String, required: true },
22
- name: { type: String, required: true },
23
- followerCount: { type: Number, default: 0 },
24
- followingCount: { type: Number, default: 0 },
25
- },
26
- },
27
-
28
- Photo: {
29
- key: {
30
- PK: { type: String, value: 'UP#${username}' },
31
- SK: { type: String, value: 'PHOTO#${photoId}' },
32
- },
33
- attributes: {
34
- username: { type: String, required: true },
35
- photoId: { type: String, generate: 'ulid' },
36
- url: { type: String, required: true },
37
- likesCount: { type: Number, default: 0 },
38
- commentCount: { type: Number, default: 0 },
39
- },
40
- },
41
-
42
- Like: {
43
- key: {
44
- PK: { type: String, value: 'PL#${photoId}' },
45
- SK: { type: String, value: 'LIKE#${likingUsername}' },
46
- },
47
- index: {
48
- gs1PK: { type: String, value: 'PL#${photoId}' },
49
- gs1SK: { type: String, value: 'LIKE#${likeId}' },
50
- },
51
- attributes: {
52
- photoId: { type: String, required: true },
53
- likingUsername: { type: String, required: true },
54
- likeId: { type: String, generate: 'ulid' },
55
- },
56
- },
57
-
58
- Comment: {
59
- key: {
60
- PK: { type: String, value: 'PC#${photoId}' },
61
- SK: { type: String, value: 'COMMENT#${commentId}' },
62
- },
63
- attributes: {
64
- photoId: { type: String, required: true },
65
- commentId: { type: String, generate: 'ulid' },
66
- commentingUsername: { type: String, required: true },
67
- content: { type: String, required: true },
68
- },
69
- },
70
-
71
- Follow: {
72
- key: {
73
- PK: { type: String, value: 'FOLLOW#${followedUsername}' },
74
- SK: { type: String, value: 'FOLLOW#${followingUsername}' },
75
- },
76
- index: {
77
- gs1PK: { type: String, value: 'FOLLOW#${followingUsername}' },
78
- gs1SK: { type: String, value: 'FOLLOW#${followedUsername}' },
79
- },
80
- attributes: {
81
- followedUsername: { type: String, required: true },
82
- followingUsername: { type: String, required: true },
83
- },
84
- },
85
- },
86
- params: {
87
- timestamps: true,
88
- },
89
- } as const;
90
-
91
- const table = new Table({
92
- name: 'InstagramClone',
93
- client: new DynamoDBClient({}),
94
- schema: InstagramSchema,
95
- });
96
-
97
- jest.mock('ulid', () => ({
98
- ulid: jest.fn(() => '01K16ZP43BRX67DG50SHGZ11DS'),
99
- }));
100
-
101
- describe('Transactions Integration Tests - Instagram Schema', () => {
102
- describe('TransactWrite - Real World Use Cases', () => {
103
- test('Like a Photo (Put Like + Update Photo.likesCount)', () => {
104
- // This is an atomic operation: both must succeed or both fail
105
- const params = table
106
- .transactWrite()
107
- .addPut(
108
- table.entities.Like.put({
109
- photoId: 'photo123',
110
- likingUsername: 'juanca',
111
- })
112
- .ifNotExists()
113
- .dbParams()
114
- )
115
- .addUpdate(
116
- table.entities.Photo.update({
117
- username: 'alice',
118
- photoId: 'photo123',
119
- })
120
- .add('likesCount', 1)
121
- .dbParams()
122
- )
123
- .dbParams();
124
-
125
- expect(params.TransactItems).toHaveLength(2);
126
-
127
- // Verify Put operation
128
- expect(params.TransactItems[0]).toHaveProperty('Put');
129
- expect(params.TransactItems[0]?.Put.Item).toMatchObject({
130
- photoId: 'photo123',
131
- likingUsername: 'juanca',
132
- PK: 'PL#photo123',
133
- SK: 'LIKE#juanca',
134
- });
135
- expect(params.TransactItems[0]?.Put.ConditionExpression).toContain('attribute_not_exists');
136
-
137
- // Verify Update operation
138
- expect(params.TransactItems[1]).toHaveProperty('Update');
139
- expect(params.TransactItems[1]?.Update.Key).toEqual({
140
- PK: 'UP#alice',
141
- SK: 'PHOTO#photo123',
142
- });
143
- expect(params.TransactItems[1]?.Update.UpdateExpression).toContain(
144
- 'ADD #likesCount :likesCount_0'
145
- );
146
- // Should also include updatedAt timestamp
147
- expect(params.TransactItems[1]?.Update.UpdateExpression).toContain(
148
- 'SET #updatedAt = :updatedAt_ts'
149
- );
150
- });
151
-
152
- test('Follow a User (Put Follow + Update both user counts)', () => {
153
- const params = table
154
- .transactWrite()
155
- .addPut(
156
- table.entities.Follow.put({
157
- followedUsername: 'alice',
158
- followingUsername: 'bob',
159
- })
160
- .ifNotExists()
161
- .dbParams()
162
- )
163
- .addUpdate(
164
- table.entities.User.update({ username: 'alice' }).add('followerCount', 1).dbParams()
165
- )
166
- .addUpdate(
167
- table.entities.User.update({ username: 'bob' }).add('followingCount', 1).dbParams()
168
- )
169
- .dbParams();
170
-
171
- expect(params.TransactItems).toHaveLength(3);
172
-
173
- // Put Follow
174
- expect(params.TransactItems[0]?.Put.Item).toMatchObject({
175
- followedUsername: 'alice',
176
- followingUsername: 'bob',
177
- PK: 'FOLLOW#alice',
178
- SK: 'FOLLOW#bob',
179
- });
180
-
181
- // Update alice's followerCount
182
- expect(params.TransactItems[1]?.Update.Key).toEqual({
183
- PK: 'USER#alice',
184
- SK: 'USER#alice',
185
- });
186
- expect(params.TransactItems[1]?.Update.UpdateExpression).toContain('followerCount');
187
-
188
- // Update bob's followingCount
189
- expect(params.TransactItems[2]?.Update.Key).toEqual({
190
- PK: 'USER#bob',
191
- SK: 'USER#bob',
192
- });
193
- expect(params.TransactItems[2]?.Update.UpdateExpression).toContain('followingCount');
194
- });
195
-
196
- test('Comment on Photo (Put Comment + Update Photo.commentCount)', () => {
197
- const params = table
198
- .transactWrite()
199
- .addPut(
200
- table.entities.Comment.put({
201
- photoId: 'photo123',
202
- commentingUsername: 'bob',
203
- content: 'Great photo!',
204
- }).dbParams()
205
- )
206
- .addUpdate(
207
- table.entities.Photo.update({
208
- username: 'alice',
209
- photoId: 'photo123',
210
- })
211
- .add('commentCount', 1)
212
- .dbParams()
213
- )
214
- .dbParams();
215
-
216
- expect(params.TransactItems).toHaveLength(2);
217
-
218
- // Put Comment
219
- expect(params.TransactItems[0]?.Put.Item).toMatchObject({
220
- photoId: 'photo123',
221
- commentingUsername: 'bob',
222
- content: 'Great photo!',
223
- PK: 'PC#photo123',
224
- });
225
-
226
- // Update Photo commentCount
227
- expect(params.TransactItems[1]?.Update.UpdateExpression).toContain(
228
- 'ADD #commentCount :commentCount_0'
229
- );
230
- // Should also include updatedAt timestamp
231
- expect(params.TransactItems[1]?.Update.UpdateExpression).toContain(
232
- 'SET #updatedAt = :updatedAt_ts'
233
- );
234
- });
235
-
236
- test('Delete Photo with safety check (only if no likes or comments)', () => {
237
- const params = table
238
- .transactWrite()
239
- .addDelete(
240
- table.entities.Photo.delete({
241
- username: 'alice',
242
- photoId: 'photo123',
243
- })
244
- .where((attr, op) => op.and(op.eq(attr.likesCount, 0), op.eq(attr.commentCount, 0)))
245
- .dbParams()
246
- )
247
- .dbParams();
248
-
249
- expect(params.TransactItems).toHaveLength(1);
250
- expect(params.TransactItems[0]?.Delete.Key).toEqual({
251
- PK: 'UP#alice',
252
- SK: 'PHOTO#photo123',
253
- });
254
- expect(params.TransactItems[0]?.Delete.ConditionExpression).toMatch(
255
- /likesCount.*commentCount/
256
- );
257
- });
258
-
259
- test('Create User with admin existence check', () => {
260
- // Only create user if admin exists (business rule)
261
- const params = table
262
- .transactWrite()
263
- .addPut(
264
- table.entities.User.put({
265
- username: 'newuser',
266
- name: 'New User',
267
- })
268
- .ifNotExists()
269
- .dbParams()
270
- )
271
- .addConditionCheck({
272
- TableName: 'InstagramClone',
273
- Key: { PK: 'USER#admin', SK: 'USER#admin' },
274
- ConditionExpression: 'attribute_exists(#PK)',
275
- ExpressionAttributeNames: { '#PK': 'PK' },
276
- })
277
- .dbParams();
278
-
279
- expect(params.TransactItems).toHaveLength(2);
280
- expect(params.TransactItems[0]).toHaveProperty('Put');
281
- expect(params.TransactItems[1]).toHaveProperty('ConditionCheck');
282
- });
283
-
284
- test('Unlike Photo (Delete Like + Decrement Photo.likesCount)', () => {
285
- const params = table
286
- .transactWrite()
287
- .addDelete(
288
- table.entities.Like.delete({
289
- photoId: 'photo123',
290
- likingUsername: 'juanca',
291
- }).dbParams()
292
- )
293
- .addUpdate(
294
- table.entities.Photo.update({
295
- username: 'alice',
296
- photoId: 'photo123',
297
- })
298
- .add('likesCount', -1)
299
- .dbParams()
300
- )
301
- .dbParams();
302
-
303
- expect(params.TransactItems).toHaveLength(2);
304
-
305
- // Delete Like
306
- expect(params.TransactItems[0]?.Delete.Key).toEqual({
307
- PK: 'PL#photo123',
308
- SK: 'LIKE#juanca',
309
- });
310
-
311
- // Update Photo (decrement)
312
- expect(params.TransactItems[1]?.Update.Key).toEqual({
313
- PK: 'UP#alice',
314
- SK: 'PHOTO#photo123',
315
- });
316
- expect(params.TransactItems[1]?.Update.UpdateExpression).toContain(
317
- 'ADD #likesCount :likesCount_0'
318
- );
319
- expect(params.TransactItems[1]?.Update.UpdateExpression).toContain(
320
- 'SET #updatedAt = :updatedAt_ts'
321
- );
322
- expect(params.TransactItems[1]?.Update.ExpressionAttributeValues).toHaveProperty(
323
- ':likesCount_0'
324
- );
325
- });
326
-
327
- test('Unfollow User (Delete Follow + Update both counts)', () => {
328
- const params = table
329
- .transactWrite()
330
- .addDelete(
331
- table.entities.Follow.delete({
332
- followedUsername: 'alice',
333
- followingUsername: 'bob',
334
- }).dbParams()
335
- )
336
- .addUpdate(
337
- table.entities.User.update({ username: 'alice' })
338
- .add('followerCount', -1)
339
- .where((attr, op) => op.gte(attr.followerCount, 1))
340
- .dbParams()
341
- )
342
- .addUpdate(
343
- table.entities.User.update({ username: 'bob' })
344
- .add('followingCount', -1)
345
- .where((attr, op) => op.gte(attr.followingCount, 1))
346
- .dbParams()
347
- )
348
- .dbParams();
349
-
350
- expect(params.TransactItems).toHaveLength(3);
351
- });
352
-
353
- test('Builder immutability - chaining creates new instances', () => {
354
- const builder1 = table.transactWrite();
355
- const builder2 = builder1.addPut(
356
- table.entities.User.put({
357
- username: 'alice',
358
- name: 'Alice',
359
- }).dbParams()
360
- );
361
- const builder3 = builder2.addUpdate(
362
- table.entities.User.update({ username: 'bob' }).add('followerCount', 1).dbParams()
363
- );
364
-
365
- // Original builder unchanged
366
- expect(builder1.dbParams().TransactItems).toHaveLength(0);
367
- expect(builder2.dbParams().TransactItems).toHaveLength(1);
368
- expect(builder3.dbParams().TransactItems).toHaveLength(2);
369
- });
370
- });
371
-
372
- describe('TransactGet - Real World Use Cases', () => {
373
- test('Get User + Photo atomically', () => {
374
- const params = table
375
- .transactGet()
376
- .addGet(table.entities.User.get({ username: 'alice' }).dbParams())
377
- .addGet(
378
- table.entities.Photo.get({
379
- username: 'alice',
380
- photoId: 'photo123',
381
- }).dbParams()
382
- )
383
- .dbParams();
384
-
385
- expect(params.TransactItems).toHaveLength(2);
386
-
387
- // Get User
388
- expect(params.TransactItems[0]?.Get.Key).toEqual({
389
- PK: 'USER#alice',
390
- SK: 'USER#alice',
391
- });
392
-
393
- // Get Photo
394
- expect(params.TransactItems[1]?.Get.Key).toEqual({
395
- PK: 'UP#alice',
396
- SK: 'PHOTO#photo123',
397
- });
398
- });
399
-
400
- test('Get Follow relationship + both Users', () => {
401
- const params = table
402
- .transactGet()
403
- .addGet(
404
- table.entities.Follow.get({
405
- followedUsername: 'alice',
406
- followingUsername: 'bob',
407
- }).dbParams()
408
- )
409
- .addGet(table.entities.User.get({ username: 'alice' }).dbParams())
410
- .addGet(table.entities.User.get({ username: 'bob' }).dbParams())
411
- .dbParams();
412
-
413
- expect(params.TransactItems).toHaveLength(3);
414
-
415
- // Follow
416
- expect(params.TransactItems[0]?.Get.Key).toEqual({
417
- PK: 'FOLLOW#alice',
418
- SK: 'FOLLOW#bob',
419
- });
420
-
421
- // Users
422
- expect(params.TransactItems[1]?.Get.Key.PK).toBe('USER#alice');
423
- expect(params.TransactItems[2]?.Get.Key.PK).toBe('USER#bob');
424
- });
425
-
426
- test('Get Photo + Like + Comment atomically', () => {
427
- const params = table
428
- .transactGet()
429
- .addGet(
430
- table.entities.Photo.get({
431
- username: 'alice',
432
- photoId: 'photo123',
433
- }).dbParams()
434
- )
435
- .addGet(
436
- table.entities.Like.get({
437
- photoId: 'photo123',
438
- likingUsername: 'bob',
439
- }).dbParams()
440
- )
441
- .addGet(
442
- table.entities.Comment.get({
443
- photoId: 'photo123',
444
- commentId: 'comment456',
445
- }).dbParams()
446
- )
447
- .dbParams();
448
-
449
- expect(params.TransactItems).toHaveLength(3);
450
-
451
- // Photo
452
- expect(params.TransactItems[0]?.Get.Key).toEqual({
453
- PK: 'UP#alice',
454
- SK: 'PHOTO#photo123',
455
- });
456
-
457
- // Like
458
- expect(params.TransactItems[1]?.Get.Key).toEqual({
459
- PK: 'PL#photo123',
460
- SK: 'LIKE#bob',
461
- });
462
-
463
- // Comment
464
- expect(params.TransactItems[2]?.Get.Key).toEqual({
465
- PK: 'PC#photo123',
466
- SK: 'COMMENT#comment456',
467
- });
468
- });
469
-
470
- test('Builder immutability - chaining creates new instances', () => {
471
- const builder1 = table.transactGet();
472
- const builder2 = builder1.addGet(table.entities.User.get({ username: 'alice' }).dbParams());
473
- const builder3 = builder2.addGet(table.entities.User.get({ username: 'bob' }).dbParams());
474
-
475
- // Original builder unchanged
476
- expect(builder1.dbParams().TransactItems).toHaveLength(0);
477
- expect(builder2.dbParams().TransactItems).toHaveLength(1);
478
- expect(builder3.dbParams().TransactItems).toHaveLength(2);
479
- });
480
- });
481
-
482
- describe('Mixed Transaction Scenarios', () => {
483
- test('Complex social interaction - Like with notification', () => {
484
- // Like photo + increment count + update follower count
485
- const params = table
486
- .transactWrite()
487
- .addPut(
488
- table.entities.Like.put({
489
- photoId: 'photo123',
490
- likingUsername: 'charlie',
491
- })
492
- .ifNotExists()
493
- .dbParams()
494
- )
495
- .addUpdate(
496
- table.entities.Photo.update({
497
- username: 'alice',
498
- photoId: 'photo123',
499
- })
500
- .add('likesCount', 1)
501
- .dbParams()
502
- )
503
- .addUpdate(
504
- table.entities.User.update({ username: 'alice' })
505
- .add('followerCount', 0) // Just to demonstrate multiple updates
506
- .dbParams()
507
- )
508
- .dbParams();
509
-
510
- expect(params.TransactItems).toHaveLength(3);
511
- });
512
-
513
- test('Client request token for idempotency', () => {
514
- const params = table
515
- .transactWrite()
516
- .addPut(
517
- table.entities.User.put({
518
- username: 'alice',
519
- name: 'Alice',
520
- }).dbParams()
521
- )
522
- .withClientRequestToken('unique-request-id-12345')
523
- .dbParams();
524
-
525
- expect(params.ClientRequestToken).toBe('unique-request-id-12345');
526
- });
527
- });
528
- });
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../typescript-config/base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "baseUrl": ".",
6
- "paths": {
7
- "@/*": ["src/*"]
8
- }
9
- },
10
- "include": ["src", "eslint.config.mjs"],
11
- "exclude": ["**/*.test.ts", "node_modules", "dist"]
12
- }