@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
package/src/core/types.test.ts
DELETED
|
@@ -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
|
-
});
|