dyna-record 0.5.3 → 0.6.1
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/README.md +237 -46
- package/dist/src/DynaRecord.d.ts +72 -61
- package/dist/src/DynaRecord.d.ts.map +1 -1
- package/dist/src/DynaRecord.js +7 -2
- package/dist/src/decorators/Entity.d.ts +12 -6
- package/dist/src/decorators/Entity.d.ts.map +1 -1
- package/dist/src/decorators/Entity.js +9 -5
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/operations/Query/types.d.ts +305 -10
- package/dist/src/operations/Query/types.d.ts.map +1 -1
- package/dist/src/operations/types.d.ts +43 -0
- package/dist/src/operations/types.d.ts.map +1 -1
- package/dist/src/query-utils/QueryBuilder.d.ts.map +1 -1
- package/dist/src/query-utils/QueryBuilder.js +4 -0
- package/dist/src/types.d.ts +10 -1
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Dyna-Record
|
|
2
2
|
|
|
3
|
-
[API Documentation](https://dyna-record.com/)
|
|
3
|
+
[API Documentation](https://docs.dyna-record.com/)
|
|
4
4
|
|
|
5
5
|
[Medium Article](https://medium.com/@drewdavis888/unlock-relational-data-modeling-in-dynamodb-with-dyna-record-5b9cce27c3ce)
|
|
6
6
|
|
|
@@ -21,6 +21,7 @@ Note: ACID compliant according to DynamoDB [limitations](https://docs.aws.amazon
|
|
|
21
21
|
- [FindById](#findbyid)
|
|
22
22
|
- [Query](#query)
|
|
23
23
|
- [Filtering on Object Attributes](#filtering-on-object-attributes)
|
|
24
|
+
- [Typed Query Filters](#typed-query-filters)
|
|
24
25
|
- [Update](#update)
|
|
25
26
|
- [Updating Object Attributes](#updating-object-attributes)
|
|
26
27
|
- [Delete](#delete)
|
|
@@ -50,9 +51,9 @@ Entities in Dyna-Record represent your DynamoDB table structure and relationship
|
|
|
50
51
|
|
|
51
52
|
### Table
|
|
52
53
|
|
|
53
|
-
[Docs](https://dyna-record.com/functions/Table.html)
|
|
54
|
+
[Docs](https://docs.dyna-record.com/functions/Table.html)
|
|
54
55
|
|
|
55
|
-
Create a table class that extends [DynaRecord base class](https://dyna-record.com/classes/default.html) and is decorated with the [Table decorator](https://dyna-record.com/functions/Table.html). At a minimum, the table class must define the [PartitionKeyAttribute](https://dyna-record.com/functions/PartitionKeyAttribute.html) and [SortKeyAttribute](https://dyna-record.com/functions/SortKeyAttribute.html).
|
|
56
|
+
Create a table class that extends [DynaRecord base class](https://docs.dyna-record.com/classes/default.html) and is decorated with the [Table decorator](https://docs.dyna-record.com/functions/Table.html). At a minimum, the table class must define the [PartitionKeyAttribute](https://docs.dyna-record.com/functions/PartitionKeyAttribute.html) and [SortKeyAttribute](https://docs.dyna-record.com/functions/SortKeyAttribute.html).
|
|
56
57
|
|
|
57
58
|
#### Basic usage
|
|
58
59
|
|
|
@@ -107,56 +108,64 @@ abstract class MyTable extends DynaRecord {
|
|
|
107
108
|
|
|
108
109
|
### Entity
|
|
109
110
|
|
|
110
|
-
[Docs](https://dyna-record.com/functions/Entity.html)
|
|
111
|
+
[Docs](https://docs.dyna-record.com/functions/Entity.html)
|
|
111
112
|
|
|
112
113
|
Each entity must extend the Table class. To support single table design patterns, they must extend the same tables class.
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
Each entity **must** declare its `type` property as a string literal matching the class name. This enables compile-time type safety for query filters and return types. Omitting this declaration will produce a compile error at the `@Entity` decorator.
|
|
116
|
+
|
|
117
|
+
By default, each entity will have [default attributes](https://docs.dyna-record.com/types/_internal_.DefaultFields.html)
|
|
115
118
|
|
|
116
119
|
- The partition key defined on the [table](#table) class
|
|
117
120
|
- The sort key defined on the [table](#table) class
|
|
118
|
-
- [id](https://dyna-record.com/classes/default.html#id) - The id for the model. This will be an autogenerated uuid unless [IdAttribute](<(https://dyna-record.com/functions/IdAttribute.html)>) is set on a non-nullable entity attribute.
|
|
119
|
-
- [type](https://dyna-record.com/classes/default.html#type) - The type of the entity. Value is the entity class name
|
|
120
|
-
- [createdAt](https://dyna-record.com/classes/default.html#
|
|
121
|
-
- [updatedAt](https://dyna-record.com/classes/default.html#updatedAt) - Timestamp of when the entity was updated last
|
|
121
|
+
- [id](https://docs.dyna-record.com/classes/default.html#id) - The id for the model. This will be an autogenerated uuid unless [IdAttribute](<(https://docs.dyna-record.com/functions/IdAttribute.html)>) is set on a non-nullable entity attribute.
|
|
122
|
+
- [type](https://docs.dyna-record.com/classes/default.html#type) - The type of the entity. Value is the entity class name. Must be declared as a string literal via `declare readonly type: "ClassName"`.
|
|
123
|
+
- [createdAt](https://docs.dyna-record.com/classes/default.html#createdAt) - The timestamp of when the entity was created
|
|
124
|
+
- [updatedAt](https://docs.dyna-record.com/classes/default.html#updatedAt) - Timestamp of when the entity was updated last
|
|
122
125
|
|
|
123
126
|
```typescript
|
|
124
127
|
import { Entity } from "dyna-record";
|
|
125
128
|
|
|
126
129
|
@Entity
|
|
127
130
|
class Student extends MyTable {
|
|
131
|
+
declare readonly type: "Student";
|
|
128
132
|
// ...
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
@Entity
|
|
132
136
|
class Course extends MyTable {
|
|
133
|
-
|
|
137
|
+
declare readonly type: "Course";
|
|
138
|
+
// ...
|
|
134
139
|
}
|
|
135
140
|
```
|
|
136
141
|
|
|
142
|
+
> **Note:** `declare readonly type` is a pure TypeScript type annotation with zero runtime impact. The ORM sets `type` to the class name automatically. The declaration simply tells TypeScript the exact literal type, enabling typed query filters and return type narrowing.
|
|
143
|
+
|
|
137
144
|
### Attributes
|
|
138
145
|
|
|
139
146
|
Use the attribute decorators below to define attributes on a model. The decorator maps class properties to DynamoDB table attributes.
|
|
140
147
|
|
|
141
148
|
- Attribute decorators
|
|
142
149
|
|
|
143
|
-
- [@StringAttribute](https://dyna-record.com/functions/StringAttribute.html)
|
|
144
|
-
- [@NumberAttribute](https://dyna-record.com/functions/NumberAttribute.html)
|
|
145
|
-
- [@BooleanAttribute](https://dyna-record.com/functions/BooleanAttribute.html)
|
|
146
|
-
- [@DateAttribute](https://dyna-record.com/functions/DateAttribute.html)
|
|
147
|
-
- [@EnumAttribute](https://dyna-record.com/functions/EnumAttribute.html)
|
|
148
|
-
- [@IdAttribute](https://dyna-record.com/functions/IdAttribute.html)
|
|
149
|
-
- [@ObjectAttribute](https://dyna-record.com/functions/ObjectAttribute.html)
|
|
150
|
+
- [@StringAttribute](https://docs.dyna-record.com/functions/StringAttribute.html)
|
|
151
|
+
- [@NumberAttribute](https://docs.dyna-record.com/functions/NumberAttribute.html)
|
|
152
|
+
- [@BooleanAttribute](https://docs.dyna-record.com/functions/BooleanAttribute.html)
|
|
153
|
+
- [@DateAttribute](https://docs.dyna-record.com/functions/DateAttribute.html)
|
|
154
|
+
- [@EnumAttribute](https://docs.dyna-record.com/functions/EnumAttribute.html)
|
|
155
|
+
- [@IdAttribute](https://docs.dyna-record.com/functions/IdAttribute.html)
|
|
156
|
+
- [@ObjectAttribute](https://docs.dyna-record.com/functions/ObjectAttribute.html)
|
|
150
157
|
|
|
151
|
-
- The [alias](https://dyna-record.com/interfaces/AttributeOptions.html#alias) option allows you to specify the attribute name as it appears in the DynamoDB table, different from your class property name.
|
|
158
|
+
- The [alias](https://docs.dyna-record.com/interfaces/AttributeOptions.html#alias) option allows you to specify the attribute name as it appears in the DynamoDB table, different from your class property name.
|
|
152
159
|
- Set nullable attributes as optional for optimal type safety
|
|
153
|
-
- Attempting to remove a non-nullable attribute will result in a [NullConstrainViolationError](https://dyna-record.com/classes/NullConstraintViolationError.html)
|
|
160
|
+
- Attempting to remove a non-nullable attribute will result in a [NullConstrainViolationError](https://docs.dyna-record.com/classes/NullConstraintViolationError.html)
|
|
154
161
|
|
|
155
162
|
```typescript
|
|
156
163
|
import { Entity, Attribute } from "dyna-record";
|
|
157
164
|
|
|
158
165
|
@Entity
|
|
159
166
|
class Student extends MyTable {
|
|
167
|
+
declare readonly type: "Student";
|
|
168
|
+
|
|
160
169
|
@StringAttribute({ alias: "Username" }) // Sets alias if field in Dynamo is different then on the model
|
|
161
170
|
public username: string;
|
|
162
171
|
|
|
@@ -196,6 +205,8 @@ const addressSchema = {
|
|
|
196
205
|
|
|
197
206
|
@Entity
|
|
198
207
|
class Store extends MyTable {
|
|
208
|
+
declare readonly type: "Store";
|
|
209
|
+
|
|
199
210
|
@ObjectAttribute({ alias: "Address", schema: addressSchema })
|
|
200
211
|
public readonly address: InferObjectSchema<typeof addressSchema>;
|
|
201
212
|
}
|
|
@@ -240,11 +251,11 @@ The schema must be declared with `as const satisfies ObjectSchema` so TypeScript
|
|
|
240
251
|
|
|
241
252
|
### Foreign Keys
|
|
242
253
|
|
|
243
|
-
Define foreign keys in order to support [@BelongsTo](https://dyna-record.com/functions/BelongsTo.html) relationships. A foreign key is required for [@HasOne](https://dyna-record.com/functions/HasOne.html) and [@HasMany](https://dyna-record.com/functions/HasMany.html) relationships.
|
|
254
|
+
Define foreign keys in order to support [@BelongsTo](https://docs.dyna-record.com/functions/BelongsTo.html) relationships. A foreign key is required for [@HasOne](https://docs.dyna-record.com/functions/HasOne.html) and [@HasMany](https://docs.dyna-record.com/functions/HasMany.html) relationships.
|
|
244
255
|
|
|
245
|
-
- The [alias](https://dyna-record.com/interfaces/AttributeOptions.html#alias) option allows you to specify the attribute name as it appears in the DynamoDB table, different from your class property name.
|
|
256
|
+
- The [alias](https://docs.dyna-record.com/interfaces/AttributeOptions.html#alias) option allows you to specify the attribute name as it appears in the DynamoDB table, different from your class property name.
|
|
246
257
|
- Set nullable foreign key attributes as optional for optimal type safety
|
|
247
|
-
- Attempting to remove an entity from a non-nullable foreign key will result in a [NullConstrainViolationError](https://dyna-record.com/classes/NullConstraintViolationError.html)
|
|
258
|
+
- Attempting to remove an entity from a non-nullable foreign key will result in a [NullConstrainViolationError](https://docs.dyna-record.com/classes/NullConstraintViolationError.html)
|
|
248
259
|
- Always provide the referenced entity class to `@ForeignKeyAttribute` (for example `@ForeignKeyAttribute(() => Customer)`); this allows DynaRecord to enforce referential integrity even when no relationship decorator is defined.
|
|
249
260
|
- `Create` and `Update` automatically add DynamoDB condition checks for standalone foreign keys (those without a relationship decorator) to ensure the referenced entity exists, enabling referential integrity even when no denormalised access pattern is required.
|
|
250
261
|
|
|
@@ -259,6 +270,8 @@ import {
|
|
|
259
270
|
|
|
260
271
|
@Entity
|
|
261
272
|
class Assignment extends MyTable {
|
|
273
|
+
declare readonly type: "Assignment";
|
|
274
|
+
|
|
262
275
|
@ForeignKeyAttribute(() => Course)
|
|
263
276
|
public readonly courseId: ForeignKey<Course>;
|
|
264
277
|
|
|
@@ -268,6 +281,8 @@ class Assignment extends MyTable {
|
|
|
268
281
|
|
|
269
282
|
@Entity
|
|
270
283
|
class Course extends MyTable {
|
|
284
|
+
declare readonly type: "Course";
|
|
285
|
+
|
|
271
286
|
@ForeignKeyAttribute(() => Teacher, { nullable: true })
|
|
272
287
|
public readonly teacherId?: NullableForeignKey<Teacher>; // Set as optional
|
|
273
288
|
|
|
@@ -278,16 +293,16 @@ class Course extends MyTable {
|
|
|
278
293
|
|
|
279
294
|
### Relationships
|
|
280
295
|
|
|
281
|
-
Dyna-Record supports defining relationships between entities such as [@HasOne](https://dyna-record.com/functions/HasOne.html), [@HasMany](https://dyna-record.com/functions/HasMany.html), [@BelongsTo](https://dyna-record.com/functions/BelongsTo.html) and [@HasAndBelongsToMany](https://dyna-record.com/functions/HasAndBelongsToMany.html). It does this by de-normalizing records to each of its related entities partitions.
|
|
296
|
+
Dyna-Record supports defining relationships between entities such as [@HasOne](https://docs.dyna-record.com/functions/HasOne.html), [@HasMany](https://docs.dyna-record.com/functions/HasMany.html), [@BelongsTo](https://docs.dyna-record.com/functions/BelongsTo.html) and [@HasAndBelongsToMany](https://docs.dyna-record.com/functions/HasAndBelongsToMany.html). It does this by de-normalizing records to each of its related entities partitions.
|
|
282
297
|
|
|
283
|
-
A relationship can be defined as nullable or non-nullable. Non-nullable relationships will be enforced via transactions and violations will result in [NullConstraintViolationError](https://dyna-record.com/classes/NullConstraintViolationError.html)
|
|
298
|
+
A relationship can be defined as nullable or non-nullable. Non-nullable relationships will be enforced via transactions and violations will result in [NullConstraintViolationError](https://docs.dyna-record.com/classes/NullConstraintViolationError.html)
|
|
284
299
|
|
|
285
|
-
- [@ForeignKeyAttribute](https://dyna-record.com/functions/ForeignKeyAttribute.html) is used to define a foreign key that links to another entity
|
|
286
|
-
- Relationship decorators ([@HasOne](#hasone), [@HasMany](#hasmany), [@BelongsTo](https://dyna-record.com/functions/BelongsTo.html), [@HasAndBelongsToMany](#hasandbelongstomany)) define how entities relate to each other.
|
|
300
|
+
- [@ForeignKeyAttribute](https://docs.dyna-record.com/functions/ForeignKeyAttribute.html) is used to define a foreign key that links to another entity
|
|
301
|
+
- Relationship decorators ([@HasOne](#hasone), [@HasMany](#hasmany), [@BelongsTo](https://docs.dyna-record.com/functions/BelongsTo.html), [@HasAndBelongsToMany](#hasandbelongstomany)) define how entities relate to each other.
|
|
287
302
|
|
|
288
303
|
#### HasOne
|
|
289
304
|
|
|
290
|
-
[Docs](https://dyna-record.com/functions/HasOne.html)
|
|
305
|
+
[Docs](https://docs.dyna-record.com/functions/HasOne.html)
|
|
291
306
|
|
|
292
307
|
```typescript
|
|
293
308
|
import {
|
|
@@ -300,6 +315,8 @@ import {
|
|
|
300
315
|
|
|
301
316
|
@Entity
|
|
302
317
|
class Assignment extends MyTable {
|
|
318
|
+
declare readonly type: "Assignment";
|
|
319
|
+
|
|
303
320
|
// 'assignmentId' must be defined on associated model
|
|
304
321
|
@HasOne(() => Grade, { foreignKey: "assignmentId" })
|
|
305
322
|
public readonly grade: Grade;
|
|
@@ -307,6 +324,8 @@ class Assignment extends MyTable {
|
|
|
307
324
|
|
|
308
325
|
@Entity
|
|
309
326
|
class Grade extends MyTable {
|
|
327
|
+
declare readonly type: "Grade";
|
|
328
|
+
|
|
310
329
|
@ForeignKeyAttribute(() => Assignment)
|
|
311
330
|
public readonly assignmentId: ForeignKey<Assignment>;
|
|
312
331
|
|
|
@@ -318,13 +337,15 @@ class Grade extends MyTable {
|
|
|
318
337
|
|
|
319
338
|
### HasMany
|
|
320
339
|
|
|
321
|
-
[Docs](https://dyna-record.com/functions/HasMany.html)
|
|
340
|
+
[Docs](https://docs.dyna-record.com/functions/HasMany.html)
|
|
322
341
|
|
|
323
342
|
```typescript
|
|
324
343
|
import { Entity, NullableForeignKey, BelongsTo, HasMany } from "dyna-record";
|
|
325
344
|
|
|
326
345
|
@Entity
|
|
327
346
|
class Teacher extends MyTable {
|
|
347
|
+
declare readonly type: "Teacher";
|
|
348
|
+
|
|
328
349
|
// 'teacherId' must be defined on associated model
|
|
329
350
|
@HasMany(() => Course, { foreignKey: "teacherId" })
|
|
330
351
|
public readonly courses: Course[];
|
|
@@ -332,6 +353,8 @@ class Teacher extends MyTable {
|
|
|
332
353
|
|
|
333
354
|
@Entity
|
|
334
355
|
class Course extends MyTable {
|
|
356
|
+
declare readonly type: "Course";
|
|
357
|
+
|
|
335
358
|
@ForeignKeyAttribute(() => Teacher, { nullable: true })
|
|
336
359
|
public readonly teacherId?: NullableForeignKey<Teacher>; // Mark as optional
|
|
337
360
|
|
|
@@ -350,6 +373,8 @@ import { Entity, NullableForeignKey, BelongsTo, HasMany } from "dyna-record";
|
|
|
350
373
|
|
|
351
374
|
@Entity
|
|
352
375
|
class Teacher extends MyTable {
|
|
376
|
+
declare readonly type: "Teacher";
|
|
377
|
+
|
|
353
378
|
// 'teacherId' must be defined on associated model
|
|
354
379
|
@HasMany(() => Course, { foreignKey: "teacherId", uniDirectional: true })
|
|
355
380
|
public readonly courses: Course[];
|
|
@@ -357,6 +382,8 @@ class Teacher extends MyTable {
|
|
|
357
382
|
|
|
358
383
|
@Entity
|
|
359
384
|
class Course extends MyTable {
|
|
385
|
+
declare readonly type: "Course";
|
|
386
|
+
|
|
360
387
|
@ForeignKeyAttribute(() => Teacher, { nullable: true })
|
|
361
388
|
public readonly teacherId?: NullableForeignKey<Teacher>; // Mark as optional
|
|
362
389
|
}
|
|
@@ -364,9 +391,9 @@ class Course extends MyTable {
|
|
|
364
391
|
|
|
365
392
|
### HasAndBelongsToMany
|
|
366
393
|
|
|
367
|
-
[Docs](https://dyna-record.com/functions/HasAndBelongsToMany.html)
|
|
394
|
+
[Docs](https://docs.dyna-record.com/functions/HasAndBelongsToMany.html)
|
|
368
395
|
|
|
369
|
-
HasAndBelongsToMany relationships require a [JoinTable](https://dyna-record.com/classes/JoinTable.html) class. This represents a virtual table to support the relationship
|
|
396
|
+
HasAndBelongsToMany relationships require a [JoinTable](https://docs.dyna-record.com/classes/JoinTable.html) class. This represents a virtual table to support the relationship
|
|
370
397
|
|
|
371
398
|
```typescript
|
|
372
399
|
import {
|
|
@@ -383,6 +410,8 @@ class StudentCourse extends JoinTable<Student, Course> {
|
|
|
383
410
|
|
|
384
411
|
@Entity
|
|
385
412
|
class Course extends MyTable {
|
|
413
|
+
declare readonly type: "Course";
|
|
414
|
+
|
|
386
415
|
@HasAndBelongsToMany(() => Student, {
|
|
387
416
|
targetKey: "courses",
|
|
388
417
|
through: () => ({ joinTable: StudentCourse, foreignKey: "courseId" })
|
|
@@ -392,6 +421,8 @@ class Course extends MyTable {
|
|
|
392
421
|
|
|
393
422
|
@Entity
|
|
394
423
|
class Student extends OtherTable {
|
|
424
|
+
declare readonly type: "Student";
|
|
425
|
+
|
|
395
426
|
@HasAndBelongsToMany(() => Course, {
|
|
396
427
|
targetKey: "students",
|
|
397
428
|
through: () => ({ joinTable: StudentCourse, foreignKey: "studentId" })
|
|
@@ -404,9 +435,9 @@ class Student extends OtherTable {
|
|
|
404
435
|
|
|
405
436
|
### Create
|
|
406
437
|
|
|
407
|
-
[Docs](https://dyna-record.com/classes/default.html#create)
|
|
438
|
+
[Docs](https://docs.dyna-record.com/classes/default.html#create)
|
|
408
439
|
|
|
409
|
-
The create method is used to insert a new record into a DynamoDB table. This method automatically handles key generation (using UUIDs or custom id field if [IdAttribute](<(https://dyna-record.com/functions/IdAttribute.html)>) is set), timestamps for [createdAt](https://dyna-record.com/classes/default.html#createdAt) and [updatedAt](https://dyna-record.com/classes/default.html#updatedAt) fields, and the management of relationships between entities. It leverages AWS SDK's [TransactWriteCommand](https://www.google.com/search?q=aws+transact+write+command&oq=aws+transact+write+command&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIGCAEQRRg7MgYIAhBFGDvSAQgzMjAzajBqN6gCALACAA&sourceid=chrome&ie=UTF-8) for transactional integrity, ensuring either complete success or rollback in case of any failure. The method handles conditional checks to ensure data integrity and consistency during creation. If a foreignKey is set on create, dyna-record will de-normalize the data required in order to support the relationship
|
|
440
|
+
The create method is used to insert a new record into a DynamoDB table. This method automatically handles key generation (using UUIDs or custom id field if [IdAttribute](<(https://docs.dyna-record.com/functions/IdAttribute.html)>) is set), timestamps for [createdAt](https://docs.dyna-record.com/classes/default.html#createdAt) and [updatedAt](https://docs.dyna-record.com/classes/default.html#updatedAt) fields, and the management of relationships between entities. It leverages AWS SDK's [TransactWriteCommand](https://www.google.com/search?q=aws+transact+write+command&oq=aws+transact+write+command&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIGCAEQRRg7MgYIAhBFGDvSAQgzMjAzajBqN6gCALACAA&sourceid=chrome&ie=UTF-8) for transactional integrity, ensuring either complete success or rollback in case of any failure. The method handles conditional checks to ensure data integrity and consistency during creation. If a foreignKey is set on create, dyna-record will de-normalize the data required in order to support the relationship
|
|
410
441
|
|
|
411
442
|
To use the create method, call it on the model class you wish to create a new record for. Pass the properties of the new record as an object argument to the method. Only attributes defined on the model can be configured, and will be enforced via types and runtime schema validation.
|
|
412
443
|
|
|
@@ -451,20 +482,20 @@ const grade: Grade = await Grade.create(
|
|
|
451
482
|
|
|
452
483
|
#### Error handling
|
|
453
484
|
|
|
454
|
-
The method is designed to throw errors under various conditions, such as transaction cancellation due to failed conditional checks. For instance, if you attempt to create a `Grade` for an `Assignment` that already has one, the method throws a [TransactionWriteFailedError](https://dyna-record.com/classes/TransactionWriteFailedError.html).
|
|
485
|
+
The method is designed to throw errors under various conditions, such as transaction cancellation due to failed conditional checks. For instance, if you attempt to create a `Grade` for an `Assignment` that already has one, the method throws a [TransactionWriteFailedError](https://docs.dyna-record.com/classes/TransactionWriteFailedError.html).
|
|
455
486
|
|
|
456
487
|
#### Notes
|
|
457
488
|
|
|
458
|
-
- Automatic Timestamp Management: The [createdAt](https://dyna-record.com/classes/default.html#createdAt) and [updatedAt](https://dyna-record.com/classes/default.html#updatedAt) fields are managed automatically and reflect the time of creation and the last update, respectively.
|
|
459
|
-
- Automatic ID Generation: Each entity created gets a unique [id](https://dyna-record.com/classes/default.html#id) generated by the uuidv4 method.
|
|
460
|
-
- This can be customized [IdAttribute](<(https://dyna-record.com/functions/IdAttribute.html)>) to support custom id attributes
|
|
489
|
+
- Automatic Timestamp Management: The [createdAt](https://docs.dyna-record.com/classes/default.html#createdAt) and [updatedAt](https://docs.dyna-record.com/classes/default.html#updatedAt) fields are managed automatically and reflect the time of creation and the last update, respectively.
|
|
490
|
+
- Automatic ID Generation: Each entity created gets a unique [id](https://docs.dyna-record.com/classes/default.html#id) generated by the uuidv4 method.
|
|
491
|
+
- This can be customized [IdAttribute](<(https://docs.dyna-record.com/functions/IdAttribute.html)>) to support custom id attributes
|
|
461
492
|
- Relationship Management: The ORM manages entity relationships through DynamoDB's single-table design patterns, creating and maintaining the necessary links between related entities.
|
|
462
493
|
- Conditional Checks: To ensure data integrity, the create method performs various conditional checks, such as verifying the existence of entities that new records relate to.
|
|
463
494
|
- Error Handling: Errors during the creation process are handled gracefully, with specific errors thrown for different failure scenarios, such as conditional check failures or transaction cancellations.
|
|
464
495
|
|
|
465
496
|
### FindById
|
|
466
497
|
|
|
467
|
-
[Docs](https://dyna-record.com/classes/default.html#findById)
|
|
498
|
+
[Docs](https://docs.dyna-record.com/classes/default.html#findById)
|
|
468
499
|
|
|
469
500
|
Retrieve a single record by its primary key.
|
|
470
501
|
|
|
@@ -498,7 +529,7 @@ const course = await Course.findById("123", {
|
|
|
498
529
|
|
|
499
530
|
### Query
|
|
500
531
|
|
|
501
|
-
[Docs](https://dyna-record.com/classes/default.html#query)
|
|
532
|
+
[Docs](https://docs.dyna-record.com/classes/default.html#query)
|
|
502
533
|
|
|
503
534
|
The query method is a versatile tool for querying data from DynamoDB tables using primary key conditions and various optional filters. This method enables fetching multiple items that match specific criteria, making it ideal for situations where more than one item needs to be retrieved based on attributes of the primary key (partition key and sort key).
|
|
504
535
|
|
|
@@ -528,7 +559,7 @@ const result = await Customer.query("123", {
|
|
|
528
559
|
|
|
529
560
|
##### Query by primary key
|
|
530
561
|
|
|
531
|
-
To be more precise to the underlying data, you can specify the partition key and sort key directly. The keys here will be the partition and sort keys defined on the [table](#table) class.
|
|
562
|
+
To be more precise to the underlying data, you can specify the partition key and sort key directly. The keys here will be the partition and sort keys defined on the [table](#table) class. The `sk` value is typed to only accept valid entity names from the partition.
|
|
532
563
|
|
|
533
564
|
```typescript
|
|
534
565
|
const orders = await Customer.query({
|
|
@@ -556,9 +587,8 @@ const result = await Course.query(
|
|
|
556
587
|
updatedAt: { $beginsWith: "2023-02-15" }
|
|
557
588
|
},
|
|
558
589
|
{
|
|
559
|
-
type:
|
|
560
|
-
createdAt: { $beginsWith: "2021-09-15T" }
|
|
561
|
-
type: "Assignment"
|
|
590
|
+
type: "Assignment",
|
|
591
|
+
createdAt: { $beginsWith: "2021-09-15T" }
|
|
562
592
|
},
|
|
563
593
|
{
|
|
564
594
|
id: "123"
|
|
@@ -632,6 +662,163 @@ const result = await Store.query("123", {
|
|
|
632
662
|
});
|
|
633
663
|
```
|
|
634
664
|
|
|
665
|
+
#### Typed Query Filters
|
|
666
|
+
|
|
667
|
+
Query filters are strongly typed based on the entities in the queried partition. A partition includes the entity itself plus all entities reachable through its declared relationships (`@HasMany`, `@HasOne`, `@BelongsTo`, `@HasAndBelongsToMany`). For example, if `Customer` has `@HasMany(() => Order)` and `@HasOne(() => ContactInformation)`, then Customer's partition entities are `Customer`, `Order`, and `ContactInformation`.
|
|
668
|
+
|
|
669
|
+
The type system validates:
|
|
670
|
+
|
|
671
|
+
- **Filter attribute keys**: Only attributes that exist on the entity or its related entities are accepted. Relationship property names, partition keys, and sort keys are excluded.
|
|
672
|
+
- **`type` field values**: The `type` field only accepts entity names from the partition — the entity itself and its declared relationships. Entities from other tables or unrelated entities on the same table are rejected.
|
|
673
|
+
- **Sort key values**: Both `skCondition` and the `sk` property in key conditions only accept entity names from the partition. This matches dyna-record's single-table design where sort key values always start with an entity class name.
|
|
674
|
+
- **`type` narrowing in `$or`**: Each `$or` element is independently narrowed. When an `$or` block specifies `type: "Order"`, only Order's attributes are allowed in that block.
|
|
675
|
+
- **Dot-path keys**: Nested `@ObjectAttribute` fields are available as typed filter keys using dot notation (e.g., `"address.city"`).
|
|
676
|
+
|
|
677
|
+
##### Filter key validation
|
|
678
|
+
|
|
679
|
+
```typescript
|
|
680
|
+
// Valid: 'name' exists on Customer, 'lastFour' on PaymentMethod
|
|
681
|
+
await Customer.query("123", {
|
|
682
|
+
filter: { name: "John", lastFour: "1234" }
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// Error: 'nonExistent' is not an attribute on any entity in Customer's partition
|
|
686
|
+
await Customer.query("123", {
|
|
687
|
+
filter: { nonExistent: "value" } // Compile error
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// Error: 'orders' is a relationship property, not a filterable attribute
|
|
691
|
+
await Customer.query("123", {
|
|
692
|
+
filter: { orders: "value" } // Compile error
|
|
693
|
+
});
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
##### Type field narrowing
|
|
697
|
+
|
|
698
|
+
```typescript
|
|
699
|
+
// Valid entity names only
|
|
700
|
+
await Customer.query("123", {
|
|
701
|
+
filter: { type: "Order" } // OK: "Order" is in Customer's partition
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
await Customer.query("123", {
|
|
705
|
+
filter: { type: "NonExistent" } // Compile error
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// Array form (IN operator) accepts valid entity names
|
|
709
|
+
await Customer.query("123", {
|
|
710
|
+
filter: { type: ["Order", "PaymentMethod"] }
|
|
711
|
+
});
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
##### $or element narrowing
|
|
715
|
+
|
|
716
|
+
Each `$or` element narrows independently based on its own `type` value:
|
|
717
|
+
|
|
718
|
+
```typescript
|
|
719
|
+
await Customer.query("123", {
|
|
720
|
+
filter: {
|
|
721
|
+
$or: [
|
|
722
|
+
{ type: "Order", orderDate: "2023" }, // OK: orderDate is on Order
|
|
723
|
+
{ type: "PaymentMethod", lastFour: "1234" } // OK: lastFour is on PaymentMethod
|
|
724
|
+
]
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
// Error in $or: lastFour is not an attribute on Order
|
|
729
|
+
await Customer.query("123", {
|
|
730
|
+
filter: {
|
|
731
|
+
$or: [
|
|
732
|
+
{ type: "Order", lastFour: "1234" } // Compile error
|
|
733
|
+
]
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
##### Return type narrowing
|
|
739
|
+
|
|
740
|
+
When querying a partition with no filter or sort key condition, the return type is a union of the entity itself and all its related entities:
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
// Return type: Array<EntityAttributesInstance<Customer> | EntityAttributesInstance<Order>
|
|
744
|
+
// | EntityAttributesInstance<PaymentMethod> | EntityAttributesInstance<ContactInformation>>
|
|
745
|
+
const results = await Customer.query("123");
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
When the filter specifies a `type` value, the return type automatically narrows to only the matching entities:
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
// Return type: Array<EntityAttributesInstance<Order>>
|
|
752
|
+
const orders = await Customer.query("123", {
|
|
753
|
+
filter: { type: "Order" }
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
orders[0]?.orderDate; // OK: orderDate is accessible
|
|
757
|
+
|
|
758
|
+
// Return type: Array<EntityAttributesInstance<Order> | EntityAttributesInstance<PaymentMethod>>
|
|
759
|
+
const mixed = await Customer.query("123", {
|
|
760
|
+
filter: { type: ["Order", "PaymentMethod"] }
|
|
761
|
+
});
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
##### Sort key validation and narrowing
|
|
765
|
+
|
|
766
|
+
Sort key values are typed to only accept valid entity names from the partition, matching dyna-record's single-table design where SK values always start with an entity class name. This applies to both the `skCondition` option and the `sk` property in key conditions:
|
|
767
|
+
|
|
768
|
+
```typescript
|
|
769
|
+
// Both forms validate sort key values against partition entity names
|
|
770
|
+
|
|
771
|
+
// skCondition option (string form)
|
|
772
|
+
await Customer.query("123", { skCondition: "Order" }); // OK
|
|
773
|
+
await Customer.query("123", { skCondition: "Order#123" }); // OK
|
|
774
|
+
await Customer.query("123", { skCondition: { $beginsWith: "Order" } }); // OK
|
|
775
|
+
await Customer.query("123", { skCondition: "NonExistent" }); // Compile error
|
|
776
|
+
|
|
777
|
+
// sk property in key conditions (object form)
|
|
778
|
+
await Customer.query({ pk: "Customer#123", sk: "Order" }); // OK
|
|
779
|
+
await Customer.query({ pk: "Customer#123", sk: "Order#001" }); // OK
|
|
780
|
+
await Customer.query({ pk: "Customer#123", sk: { $beginsWith: "Order" } }); // OK
|
|
781
|
+
await Customer.query({ pk: "Customer#123", sk: "NonExistent" }); // Compile error
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
**Return type narrowing** works with `skCondition` when the value is an exact entity name or `$beginsWith` with an entity name:
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
// skCondition narrows the return type
|
|
788
|
+
const orders = await Customer.query("123", { skCondition: "Order" });
|
|
789
|
+
// orders is Array<EntityAttributesInstance<Order>>
|
|
790
|
+
|
|
791
|
+
const orders2 = await Customer.query("123", {
|
|
792
|
+
skCondition: { $beginsWith: "Order" }
|
|
793
|
+
});
|
|
794
|
+
// orders2 is Array<EntityAttributesInstance<Order>>
|
|
795
|
+
|
|
796
|
+
// Suffix prevents narrowing (delimiter is configurable)
|
|
797
|
+
const specific = await Customer.query("123", { skCondition: "Order#123" });
|
|
798
|
+
// specific is QueryResults<Customer> (full union)
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
When using the object key form (`{ pk: "...", sk: "..." }`), sort key values are **validated** but the return type is **not narrowed**. Use `filter: { type: "Order" }` alongside key conditions for return type narrowing:
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
// sk is validated but does NOT narrow the return type
|
|
805
|
+
const results = await Customer.query({ pk: "Customer#123", sk: "Order" });
|
|
806
|
+
// results is QueryResults<Customer> (full union)
|
|
807
|
+
|
|
808
|
+
// Combine with filter type for return type narrowing
|
|
809
|
+
const orders = await Customer.query(
|
|
810
|
+
{ pk: "Customer#123", sk: { $beginsWith: "Order" } },
|
|
811
|
+
{ filter: { type: "Order" } }
|
|
812
|
+
);
|
|
813
|
+
// orders is Array<EntityAttributesInstance<Order>>
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
> **Note:** Return type narrowing applies to the top-level `type` filter field, `type` values within `$or` elements, and to the `skCondition` option. When `$or` elements specify `type` values, the return type narrows to the union of those entity types. The `sk` property in key conditions validates values but does not narrow return types. Index queries (`{ indexName: "..." }`) use untyped filters.
|
|
817
|
+
>
|
|
818
|
+
> **Filter key narrowing:** When no `type` is specified, the return type automatically narrows based on which entities have the filtered attributes. For example, `filter: { orderDate: "2023" }` narrows to `Order` if only `Order` has `orderDate`. In `$or` blocks, each element narrows independently — by `type` if present, or by filter keys otherwise — and the return type is the union across all blocks.
|
|
819
|
+
>
|
|
820
|
+
> **AND intersection:** Since DynamoDB ANDs top-level filter conditions with `$or` blocks, the return type reflects this. When both top-level conditions and `$or` blocks independently narrow to specific entity sets, the return type is their intersection. If no entity satisfies both (e.g., `{ orderDate: "2023", $or: [{ lastFour: "1234" }] }` where `orderDate` is on `Order` and `lastFour` is on `PaymentMethod`), the return type is `never[]` — correctly indicating that no records can match.
|
|
821
|
+
|
|
635
822
|
### Querying on an index
|
|
636
823
|
|
|
637
824
|
For querying based on secondary indexes, you can specify the index name in the options.
|
|
@@ -648,7 +835,7 @@ const result = await Customer.query(
|
|
|
648
835
|
|
|
649
836
|
### Update
|
|
650
837
|
|
|
651
|
-
[Docs](https://dyna-record.com/classes/default.html#update)
|
|
838
|
+
[Docs](https://docs.dyna-record.com/classes/default.html#update)
|
|
652
839
|
|
|
653
840
|
The update method enables modifications to existing items in a DynamoDB table. It supports updating simple attributes, handling nullable fields, and managing relationships between entities, including updating and removing foreign keys. Only attributes defined on the model can be updated, and will be enforced via types and runtime schema validation.
|
|
654
841
|
|
|
@@ -663,7 +850,7 @@ await Customer.update("123", {
|
|
|
663
850
|
|
|
664
851
|
#### Removing attributes
|
|
665
852
|
|
|
666
|
-
Note: Attempting to remove a non nullable attribute will result in a [NullConstraintViolationError](https://dyna-record.com/classes/NullConstraintViolationError.html)
|
|
853
|
+
Note: Attempting to remove a non nullable attribute will result in a [NullConstraintViolationError](https://docs.dyna-record.com/classes/NullConstraintViolationError.html)
|
|
667
854
|
|
|
668
855
|
```typescript
|
|
669
856
|
await ContactInformation.update("123", {
|
|
@@ -686,7 +873,7 @@ await PaymentMethod.update("123", {
|
|
|
686
873
|
|
|
687
874
|
Nullable foreign key references can be removed by setting them to null
|
|
688
875
|
|
|
689
|
-
Note: Attempting to remove a non nullable foreign key will result in a [NullConstraintViolationError](https://dyna-record.com/classes/NullConstraintViolationError.html)
|
|
876
|
+
Note: Attempting to remove a non nullable foreign key will result in a [NullConstraintViolationError](https://docs.dyna-record.com/classes/NullConstraintViolationError.html)
|
|
690
877
|
|
|
691
878
|
```typescript
|
|
692
879
|
await Pet.update("123", {
|
|
@@ -780,7 +967,7 @@ const updatedInstance = await paymentMethodInstance.update(
|
|
|
780
967
|
|
|
781
968
|
### Delete
|
|
782
969
|
|
|
783
|
-
[Docs](https://dyna-record.com/classes/default.html#delete)
|
|
970
|
+
[Docs](https://docs.dyna-record.com/classes/default.html#delete)
|
|
784
971
|
|
|
785
972
|
The delete method is used to remove an entity from a DynamoDB table, along with handling the deletion of associated items in relationships (like HasMany, HasOne, BelongsTo) to maintain the integrity of the database schema.
|
|
786
973
|
|
|
@@ -821,9 +1008,13 @@ If deleting an entity or its relationships fails due to database constraints or
|
|
|
821
1008
|
|
|
822
1009
|
Dyna-Record integrates type safety into your DynamoDB interactions, reducing runtime errors and enhancing code quality.
|
|
823
1010
|
|
|
1011
|
+
- **Entity Type Declaration**: The `@Entity` decorator enforces that each entity declares `readonly type` as a string literal matching the class name (`declare readonly type: "MyEntity"`). This is required for compile-time query type safety.
|
|
824
1012
|
- **Attribute Type Enforcement**: Ensures that the data types of attributes match their definitions in your entities.
|
|
825
1013
|
- **Method Parameter Checking**: Validates method parameters against entity definitions, preventing invalid operations.
|
|
826
1014
|
- **Relationship Integrity**: Automatically manages the consistency of relationships between entities, ensuring data integrity.
|
|
1015
|
+
- **Typed Query Filters**: Query filter keys are validated against the attributes of entities in the partition. Invalid keys, relationship property names, and non-existent attributes produce compile errors. The `type` field only accepts valid entity class names.
|
|
1016
|
+
- **Return Type Narrowing**: When a query filter specifies a `type` value, the return type is automatically narrowed to only the matching entity types instead of the full partition union.
|
|
1017
|
+
- **`$or` Element Narrowing**: Each element in a `$or` filter array is independently type-checked based on its own `type` field, preventing attribute mismatches.
|
|
827
1018
|
|
|
828
1019
|
## Best Practices
|
|
829
1020
|
|