@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,641 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- /**
3
- * Type System Tests & Examples
4
- *
5
- * This file comprehensively tests the type system including:
6
- * - Schema definition validation
7
- * - Type inference (InferModel, InferModelFromSchema, InferInputFromSchema)
8
- * - Automatic timestamp field inference
9
- * - Internal key hiding (PK, SK, GSI keys not exposed in public types)
10
- */
11
-
12
- import {
13
- SchemaDefinition,
14
- ModelDefinition,
15
- InferModel,
16
- InferModelFromSchema,
17
- InferInputFromSchema,
18
- TimestampFields,
19
- } from './types';
20
-
21
- // ============================================================================
22
- // Model Type Inference Tests (InferModel)
23
- // ============================================================================
24
-
25
- describe('Model Type Inference', () => {
26
- // Test model with keys and indexes
27
- const TestModel = {
28
- key: {
29
- PK: { type: String, value: 'USER#${userId}' },
30
- SK: { type: String, value: 'USER#${userId}' },
31
- },
32
- index: {
33
- GSI1PK: { type: String, value: 'EMAIL#${email}' },
34
- GSI1SK: { type: String, value: 'USER#${userId}' },
35
- },
36
- attributes: {
37
- userId: { type: String, required: true },
38
- email: { type: String, required: true },
39
- name: { type: String, required: true },
40
- age: { type: Number, required: false },
41
- },
42
- } as const satisfies ModelDefinition;
43
-
44
- type TestUser = InferModel<typeof TestModel>;
45
-
46
- it('should NOT expose PK, SK, GSI1PK, GSI1SK in inferred model type', () => {
47
- const user: TestUser = {
48
- userId: '123',
49
- email: 'test@example.com',
50
- name: 'John Doe',
51
- age: 30,
52
- };
53
-
54
- // These should be valid
55
- expect(user.userId).toBe('123');
56
- expect(user.email).toBe('test@example.com');
57
- expect(user.name).toBe('John Doe');
58
- expect(user.age).toBe(30);
59
-
60
- // TypeScript should NOT allow accessing PK, SK, GSI1PK, GSI1SK
61
- // @ts-expect-error - PK should not exist on user type
62
- const _pk = user.PK;
63
-
64
- // @ts-expect-error - SK should not exist on user type
65
- const _sk = user.SK;
66
-
67
- // @ts-expect-error - GSI1PK should not exist on user type
68
- const _gsi1pk = user.GSI1PK;
69
-
70
- // @ts-expect-error - GSI1SK should not exist on user type
71
- const _gsi1sk = user.GSI1SK;
72
-
73
- // Silence unused variable warnings
74
- expect(_pk).toBeUndefined();
75
- expect(_sk).toBeUndefined();
76
- expect(_gsi1pk).toBeUndefined();
77
- expect(_gsi1sk).toBeUndefined();
78
- });
79
-
80
- it('should only include business attributes in the model type', () => {
81
- const user: TestUser = {
82
- userId: '123',
83
- email: 'test@example.com',
84
- name: 'John Doe',
85
- };
86
-
87
- // Verify the type structure
88
- const keys = Object.keys(user);
89
- expect(keys).not.toContain('PK');
90
- expect(keys).not.toContain('SK');
91
- expect(keys).not.toContain('GSI1PK');
92
- expect(keys).not.toContain('GSI1SK');
93
- });
94
-
95
- it('should allow optional attributes to be omitted', () => {
96
- const userWithoutAge: TestUser = {
97
- userId: '456',
98
- email: 'jane@example.com',
99
- name: 'Jane Doe',
100
- };
101
-
102
- expect(userWithoutAge.age).toBeUndefined();
103
- });
104
-
105
- it('should require all required attributes', () => {
106
- // This should compile - all required fields present
107
- const validUser: TestUser = {
108
- userId: '789',
109
- email: 'bob@example.com',
110
- name: 'Bob Smith',
111
- };
112
-
113
- expect(validUser).toBeDefined();
114
-
115
- // These should fail at compile time:
116
-
117
- // @ts-expect-error - missing required field 'name'
118
- const invalidUser1: TestUser = {
119
- userId: '789',
120
- email: 'bob@example.com',
121
- };
122
-
123
- // @ts-expect-error - missing required field 'email'
124
- const invalidUser2: TestUser = {
125
- userId: '789',
126
- name: 'Bob Smith',
127
- };
128
-
129
- // Silence warnings
130
- expect(invalidUser1).toBeDefined();
131
- expect(invalidUser2).toBeDefined();
132
- });
133
- });
134
-
135
- // ============================================================================
136
- // Schema Definition Validation Tests
137
- // ============================================================================
138
-
139
- describe('Schema Definition Validation', () => {
140
- test('Valid schema with all required fields should compile', () => {
141
- const validSchema = {
142
- format: 'dynatable:1.0.0',
143
- version: '1.0.0',
144
- indexes: {
145
- primary: { hash: 'PK', sort: 'SK' },
146
- },
147
- models: {
148
- User: {
149
- key: {
150
- PK: { type: String, value: 'USER#${username}' },
151
- SK: { type: String, value: 'USER#${username}' },
152
- },
153
- attributes: {
154
- username: { type: String, required: true },
155
- },
156
- },
157
- },
158
- } as const satisfies SchemaDefinition;
159
-
160
- expect(validSchema).toBeDefined();
161
- expect(validSchema.format).toBe('dynatable:1.0.0');
162
- expect(validSchema.version).toBe('1.0.0');
163
- });
164
-
165
- test('Schema with multiple models should compile', () => {
166
- const multiModelSchema = {
167
- format: 'dynatable:1.0.0',
168
- version: '1.0.0',
169
- indexes: {
170
- primary: { hash: 'PK', sort: 'SK' },
171
- gs1: { hash: 'GSI1PK', sort: 'GSI1SK' },
172
- },
173
- models: {
174
- User: {
175
- key: {
176
- PK: { type: String, value: 'USER#${username}' },
177
- SK: { type: String, value: 'USER#${username}' },
178
- },
179
- attributes: {
180
- username: { type: String, required: true },
181
- email: { type: String, required: true },
182
- },
183
- },
184
- Post: {
185
- key: {
186
- PK: { type: String, value: 'USER#${userId}' },
187
- SK: { type: String, value: 'POST#${postId}' },
188
- },
189
- attributes: {
190
- userId: { type: String, required: true },
191
- postId: { type: String, generate: 'ulid' },
192
- title: { type: String, required: true },
193
- },
194
- },
195
- },
196
- } as const satisfies SchemaDefinition;
197
-
198
- expect(multiModelSchema.models).toHaveProperty('User');
199
- expect(multiModelSchema.models).toHaveProperty('Post');
200
- });
201
-
202
- test('Schema with secondary indexes should compile', () => {
203
- const schemaWithIndexes = {
204
- format: 'dynatable:1.0.0',
205
- version: '1.0.0',
206
- indexes: {
207
- primary: { hash: 'PK', sort: 'SK' },
208
- gs1: { hash: 'GSI1PK', sort: 'GSI1SK' },
209
- gs2: { hash: 'GSI2PK' }, // sort is optional
210
- },
211
- models: {
212
- Item: {
213
- key: {
214
- PK: { type: String, value: 'ITEM#${id}' },
215
- SK: { type: String, value: 'ITEM#${id}' },
216
- },
217
- index: {
218
- gs1PK: { type: String, value: 'TYPE#${type}' },
219
- gs1SK: { type: String, value: 'CREATED#${createdAt}' },
220
- },
221
- attributes: {
222
- id: { type: String, required: true },
223
- type: { type: String, required: true },
224
- createdAt: { type: String, required: true },
225
- },
226
- },
227
- },
228
- } as const satisfies SchemaDefinition;
229
-
230
- expect(schemaWithIndexes.indexes).toHaveProperty('primary');
231
- expect(schemaWithIndexes.indexes).toHaveProperty('gs1');
232
- expect(schemaWithIndexes.indexes).toHaveProperty('gs2');
233
- });
234
-
235
- test('Schema with all attribute types should compile', () => {
236
- const allTypesSchema = {
237
- format: 'dynatable:1.0.0',
238
- version: '1.0.0',
239
- indexes: {
240
- primary: { hash: 'PK', sort: 'SK' },
241
- },
242
- models: {
243
- CompleteModel: {
244
- key: {
245
- PK: { type: String, value: 'MODEL#${id}' },
246
- SK: { type: String, value: 'MODEL#${id}' },
247
- },
248
- attributes: {
249
- id: { type: String, required: true },
250
- name: { type: String, required: true },
251
- age: { type: Number, default: 0 },
252
- score: { type: Number },
253
- isActive: { type: Boolean, default: false },
254
- isVerified: { type: Boolean },
255
- createdAt: { type: Date },
256
- updatedAt: { type: Date },
257
- generatedId: { type: String, generate: 'ulid' },
258
- uuid: { type: String, generate: 'uuid' },
259
- },
260
- },
261
- },
262
- } as const satisfies SchemaDefinition;
263
-
264
- expect(allTypesSchema.models.CompleteModel.attributes).toHaveProperty('id');
265
- expect(allTypesSchema.models.CompleteModel.attributes).toHaveProperty('name');
266
- expect(allTypesSchema.models.CompleteModel.attributes).toHaveProperty('age');
267
- expect(allTypesSchema.models.CompleteModel.attributes).toHaveProperty('isActive');
268
- expect(allTypesSchema.models.CompleteModel.attributes).toHaveProperty('createdAt');
269
- });
270
-
271
- test('Schema with params should compile', () => {
272
- const schemaWithParams = {
273
- format: 'dynatable:1.0.0',
274
- version: '1.0.0',
275
- indexes: {
276
- primary: { hash: 'PK', sort: 'SK' },
277
- },
278
- models: {
279
- User: {
280
- key: {
281
- PK: { type: String, value: 'USER#${id}' },
282
- SK: { type: String, value: 'USER#${id}' },
283
- },
284
- attributes: {
285
- id: { type: String, required: true },
286
- },
287
- },
288
- },
289
- params: {
290
- timestamps: true,
291
- },
292
- } as const satisfies SchemaDefinition;
293
-
294
- expect(schemaWithParams.params?.timestamps).toBe(true);
295
- });
296
-
297
- test('Schema type validation enforces structure at compile time', () => {
298
- // This test verifies that TypeScript enforces the schema structure
299
- // If any of these commented examples were uncommented, they would fail to compile
300
-
301
- // Type checking happens at compile time, so we just verify the valid schema compiles
302
- const validSchema = {
303
- format: 'dynatable:1.0.0',
304
- version: '1.0.0',
305
- indexes: {
306
- primary: { hash: 'PK', sort: 'SK' },
307
- },
308
- models: {
309
- User: {
310
- key: {
311
- PK: { type: String, value: 'USER#${id}' },
312
- SK: { type: String, value: 'USER#${id}' },
313
- },
314
- attributes: {
315
- id: { type: String, required: true },
316
- },
317
- },
318
- },
319
- } as const satisfies SchemaDefinition;
320
-
321
- expect(validSchema.models.User.key).toHaveProperty('PK');
322
- expect(validSchema.models.User.key).toHaveProperty('SK');
323
- expect(validSchema.models.User.key.PK).toHaveProperty('type');
324
- expect(validSchema.models.User.key.PK).toHaveProperty('value');
325
- });
326
- });
327
-
328
- // ============================================================================
329
- // Timestamp Type Inference Tests
330
- // ============================================================================
331
-
332
- describe('Timestamp Type Inference', () => {
333
- test('InferModelFromSchema should include timestamp fields when timestamps: true', () => {
334
- const schemaWithTimestamps = {
335
- format: 'dynatable:1.0.0',
336
- version: '1.0.0',
337
- indexes: {
338
- primary: { hash: 'PK', sort: 'SK' },
339
- },
340
- models: {
341
- User: {
342
- key: {
343
- PK: { type: String, value: 'USER#${id}' },
344
- SK: { type: String, value: 'USER#${id}' },
345
- },
346
- attributes: {
347
- id: { type: String, required: true },
348
- name: { type: String, required: true },
349
- },
350
- },
351
- },
352
- params: {
353
- timestamps: true,
354
- },
355
- } as const satisfies SchemaDefinition;
356
-
357
- type UserModel = InferModelFromSchema<typeof schemaWithTimestamps, 'User'>;
358
-
359
- // This is a compile-time test - if it compiles, the types are correct
360
- const user: UserModel = {
361
- id: '123',
362
- name: 'John Doe',
363
- createdAt: '2024-01-01T00:00:00.000Z',
364
- updatedAt: '2024-01-01T00:00:00.000Z',
365
- };
366
-
367
- // Verify that the timestamp fields are required
368
- expect(user.createdAt).toBeDefined();
369
- expect(user.updatedAt).toBeDefined();
370
-
371
- // Type check: these should cause compilation errors if uncommented
372
- // const invalidUser: UserModel = {
373
- // id: '123',
374
- // name: 'John Doe',
375
- // // Missing createdAt and updatedAt
376
- // };
377
- });
378
-
379
- test('InferModelFromSchema should NOT include timestamp fields when timestamps: false', () => {
380
- const schemaWithoutTimestamps = {
381
- format: 'dynatable:1.0.0',
382
- version: '1.0.0',
383
- indexes: {
384
- primary: { hash: 'PK', sort: 'SK' },
385
- },
386
- models: {
387
- User: {
388
- key: {
389
- PK: { type: String, value: 'USER#${id}' },
390
- SK: { type: String, value: 'USER#${id}' },
391
- },
392
- attributes: {
393
- id: { type: String, required: true },
394
- name: { type: String, required: true },
395
- },
396
- },
397
- },
398
- params: {
399
- timestamps: false,
400
- },
401
- } as const satisfies SchemaDefinition;
402
-
403
- type UserModel = InferModelFromSchema<typeof schemaWithoutTimestamps, 'User'>;
404
-
405
- // Without timestamps, the model should not have createdAt/updatedAt
406
- const user: UserModel = {
407
- id: '123',
408
- name: 'John Doe',
409
- };
410
-
411
- expect(user.id).toBe('123');
412
- expect(user.name).toBe('John Doe');
413
- });
414
-
415
- test('InferModelFromSchema should NOT include timestamp fields when params is undefined', () => {
416
- const schemaWithoutParams = {
417
- format: 'dynatable:1.0.0',
418
- version: '1.0.0',
419
- indexes: {
420
- primary: { hash: 'PK', sort: 'SK' },
421
- },
422
- models: {
423
- User: {
424
- key: {
425
- PK: { type: String, value: 'USER#${id}' },
426
- SK: { type: String, value: 'USER#${id}' },
427
- },
428
- attributes: {
429
- id: { type: String, required: true },
430
- name: { type: String, required: true },
431
- },
432
- },
433
- },
434
- } as const satisfies SchemaDefinition;
435
-
436
- type UserModel = InferModelFromSchema<typeof schemaWithoutParams, 'User'>;
437
-
438
- const user: UserModel = {
439
- id: '123',
440
- name: 'John Doe',
441
- };
442
-
443
- expect(user.id).toBe('123');
444
- });
445
-
446
- test('InferInputFromSchema should NOT include timestamp fields when timestamps: true', () => {
447
- const schemaWithTimestamps = {
448
- format: 'dynatable:1.0.0',
449
- version: '1.0.0',
450
- indexes: {
451
- primary: { hash: 'PK', sort: 'SK' },
452
- },
453
- models: {
454
- User: {
455
- key: {
456
- PK: { type: String, value: 'USER#${id}' },
457
- SK: { type: String, value: 'USER#${id}' },
458
- },
459
- attributes: {
460
- id: { type: String, required: true },
461
- name: { type: String, required: true },
462
- },
463
- },
464
- },
465
- params: {
466
- timestamps: true,
467
- },
468
- } as const satisfies SchemaDefinition;
469
-
470
- type UserInput = InferInputFromSchema<typeof schemaWithTimestamps, 'User'>;
471
-
472
- // Input should NOT include timestamps (they're auto-generated)
473
- const input1: UserInput = {
474
- id: '123',
475
- name: 'John Doe',
476
- };
477
-
478
- expect(input1.id).toBe('123');
479
-
480
- // Verify timestamps are NOT in the input type by checking the type structure
481
- type InputKeys = keyof UserInput;
482
- const inputKeys: InputKeys[] = ['id', 'name'];
483
-
484
- // TypeScript compile-time test: these should cause errors if uncommented
485
- // const invalidInput: UserInput = {
486
- // id: '123',
487
- // name: 'John Doe',
488
- // createdAt: '2024-01-01T00:00:00.000Z', // Should error: createdAt doesn't exist
489
- // updatedAt: '2024-01-01T00:00:00.000Z', // Should error: updatedAt doesn't exist
490
- // };
491
-
492
- expect(inputKeys).toContain('id');
493
- });
494
-
495
- test('TimestampFields type should have correct structure', () => {
496
- const timestamps: TimestampFields = {
497
- createdAt: '2024-01-01T00:00:00.000Z',
498
- updatedAt: '2024-01-01T00:00:00.000Z',
499
- };
500
-
501
- expect(timestamps.createdAt).toBe('2024-01-01T00:00:00.000Z');
502
- expect(timestamps.updatedAt).toBe('2024-01-01T00:00:00.000Z');
503
- });
504
- });
505
-
506
- // ============================================================================
507
- // Nested Object & Array Type Inference Tests
508
- // ============================================================================
509
-
510
- describe('Nested Object and Array Type Inference', () => {
511
- const TransactionSchema = {
512
- format: 'dynatable:1.0.0',
513
- version: '1.0.0',
514
- indexes: { primary: { hash: 'PK', sort: 'SK' } },
515
- models: {
516
- Transaction: {
517
- key: {
518
- PK: { type: String, value: 'USER#${userId}' },
519
- SK: { type: String, value: 'TX#${txId}' },
520
- },
521
- attributes: {
522
- userId: { type: String, required: true },
523
- txId: { type: String, required: true },
524
- status: { type: String, required: true },
525
- // Nested object with schema
526
- address: {
527
- type: Object,
528
- schema: {
529
- street: { type: String },
530
- city: { type: String, required: true },
531
- country: { type: String, required: true },
532
- },
533
- },
534
- // Array of scalar items
535
- tags: {
536
- type: Array,
537
- items: { type: String },
538
- default: [],
539
- },
540
- // Array of objects with schema (like history in the real example)
541
- history: {
542
- type: Array,
543
- default: [],
544
- items: {
545
- type: Object,
546
- schema: {
547
- date: { type: String },
548
- description: { type: String },
549
- status: { type: String, required: true },
550
- },
551
- },
552
- },
553
- // Deeply nested object
554
- amount: {
555
- type: Object,
556
- schema: {
557
- value: { type: Number, required: true },
558
- currency: { type: String, required: true },
559
- breakdown: {
560
- type: Object,
561
- schema: {
562
- base: { type: Number, required: true },
563
- fees: { type: Number, required: true },
564
- },
565
- },
566
- },
567
- },
568
- },
569
- },
570
- },
571
- } as const satisfies SchemaDefinition;
572
-
573
- type Transaction = InferModelFromSchema<typeof TransactionSchema, 'Transaction'>;
574
-
575
- test('schema with nested Object attribute compiles', () => {
576
- expect(TransactionSchema.models.Transaction.attributes.address.type).toBe(Object);
577
- });
578
-
579
- test('schema with Array of objects compiles', () => {
580
- expect(TransactionSchema.models.Transaction.attributes.history.type).toBe(Array);
581
- });
582
-
583
- test('inferred type accepts valid nested object', () => {
584
- const tx: Transaction = {
585
- userId: 'u1',
586
- txId: 'tx1',
587
- status: 'PENDING',
588
- address: { city: 'Buenos Aires', country: 'AR' },
589
- tags: ['fast', 'verified'],
590
- history: [
591
- { date: '2024-01-01', description: 'Created', status: 'PENDING' },
592
- { status: 'COMPLETED' },
593
- ],
594
- amount: {
595
- value: 100,
596
- currency: 'USD',
597
- breakdown: { base: 90, fees: 10 },
598
- },
599
- };
600
-
601
- expect(tx.userId).toBe('u1');
602
- expect(tx.address?.city).toBe('Buenos Aires');
603
- expect(tx.history?.[0]?.status).toBe('PENDING');
604
- expect(tx.amount?.breakdown?.base).toBe(90);
605
- expect(tx.tags?.[0]).toBe('fast');
606
- });
607
-
608
- test('inferred nested object type requires its required fields', () => {
609
- type Address = NonNullable<Transaction['address']>;
610
-
611
- // city and country are required — this must compile
612
- const validAddress: Address = { city: 'BA', country: 'AR' };
613
- expect(validAddress.city).toBe('BA');
614
-
615
- // Verify the shape: optional `street`, required `city` and `country`
616
- type AddressKeys = keyof Required<Address>;
617
- const _keys: AddressKeys[] = ['street', 'city', 'country'];
618
- expect(_keys).toContain('city');
619
- });
620
-
621
- test('model with nested attributes satisfies ModelDefinition', () => {
622
- const model = {
623
- key: {
624
- PK: { type: String, value: 'ITEM#${id}' },
625
- SK: { type: String, value: 'ITEM#${id}' },
626
- },
627
- attributes: {
628
- id: { type: String, required: true },
629
- meta: {
630
- type: Object,
631
- schema: {
632
- createdBy: { type: String },
633
- tags: { type: Array, items: { type: String } },
634
- },
635
- },
636
- },
637
- } as const satisfies ModelDefinition;
638
-
639
- expect(model.attributes.meta.type).toBe(Object);
640
- });
641
- });