dyna-record 0.4.9 → 0.4.11
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 +138 -0
- package/dist/src/decorators/attributes/ObjectAttribute.d.ts +97 -0
- package/dist/src/decorators/attributes/ObjectAttribute.d.ts.map +1 -0
- package/dist/src/decorators/attributes/ObjectAttribute.js +157 -0
- package/dist/src/decorators/attributes/index.d.ts +3 -0
- package/dist/src/decorators/attributes/index.d.ts.map +1 -1
- package/dist/src/decorators/attributes/index.js +3 -1
- package/dist/src/decorators/attributes/serializers.d.ts +50 -2
- package/dist/src/decorators/attributes/serializers.d.ts.map +1 -1
- package/dist/src/decorators/attributes/serializers.js +98 -0
- package/dist/src/decorators/attributes/types.d.ts +246 -0
- package/dist/src/decorators/attributes/types.d.ts.map +1 -0
- package/dist/src/decorators/attributes/types.js +2 -0
- package/dist/src/metadata/types.d.ts +3 -3
- package/dist/src/metadata/types.d.ts.map +1 -1
- package/dist/src/operations/Update/types.d.ts +15 -1
- package/dist/src/operations/Update/types.d.ts.map +1 -1
- package/dist/src/query-utils/QueryBuilder.d.ts +24 -5
- package/dist/src/query-utils/QueryBuilder.d.ts.map +1 -1
- package/dist/src/query-utils/QueryBuilder.js +63 -18
- package/dist/src/query-utils/types.d.ts +31 -8
- package/dist/src/query-utils/types.d.ts.map +1 -1
- package/dist/src/types.d.ts +2 -2
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Note: ACID compliant according to DynamoDB [limitations](https://docs.aws.amazon
|
|
|
20
20
|
- [Create](#create)
|
|
21
21
|
- [FindById](#findbyid)
|
|
22
22
|
- [Query](#query)
|
|
23
|
+
- [Filtering on Object Attributes](#filtering-on-object-attributes)
|
|
23
24
|
- [Update](#update)
|
|
24
25
|
- [Delete](#delete)
|
|
25
26
|
- [Type Safety Features](#type-safety-features)
|
|
@@ -144,6 +145,7 @@ Use the attribute decorators below to define attributes on a model. The decorato
|
|
|
144
145
|
- [@DateAttribute](https://dyna-record.com/functions/DateAttribute.html)
|
|
145
146
|
- [@EnumAttribute](https://dyna-record.com/functions/EnumAttribute.html)
|
|
146
147
|
- [@IdAttribute](https://dyna-record.com/functions/IdAttribute.html)
|
|
148
|
+
- [@ObjectAttribute](https://dyna-record.com/functions/ObjectAttribute.html)
|
|
147
149
|
|
|
148
150
|
- 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.
|
|
149
151
|
- Set nullable attributes as optional for optimal type safety
|
|
@@ -165,6 +167,79 @@ class Student extends MyTable {
|
|
|
165
167
|
}
|
|
166
168
|
```
|
|
167
169
|
|
|
170
|
+
#### @ObjectAttribute
|
|
171
|
+
|
|
172
|
+
Use `@ObjectAttribute` to define structured, typed object attributes on an entity. Objects are validated at runtime and stored as native DynamoDB Map types.
|
|
173
|
+
|
|
174
|
+
Define the shape using an `ObjectSchema` and derive the TypeScript type with `InferObjectSchema`:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { Entity, ObjectAttribute } from "dyna-record";
|
|
178
|
+
import type { ObjectSchema, InferObjectSchema } from "dyna-record";
|
|
179
|
+
|
|
180
|
+
const addressSchema = {
|
|
181
|
+
street: { type: "string" },
|
|
182
|
+
city: { type: "string" },
|
|
183
|
+
zip: { type: "number", nullable: true },
|
|
184
|
+
tags: { type: "array", items: { type: "string" } },
|
|
185
|
+
category: { type: "enum", values: ["home", "work", "other"] },
|
|
186
|
+
createdDate: { type: "date" },
|
|
187
|
+
geo: {
|
|
188
|
+
type: "object",
|
|
189
|
+
fields: {
|
|
190
|
+
lat: { type: "number" },
|
|
191
|
+
lng: { type: "number" }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} as const satisfies ObjectSchema;
|
|
195
|
+
|
|
196
|
+
@Entity
|
|
197
|
+
class Store extends MyTable {
|
|
198
|
+
@ObjectAttribute({ alias: "Address", schema: addressSchema })
|
|
199
|
+
public readonly address: InferObjectSchema<typeof addressSchema>;
|
|
200
|
+
|
|
201
|
+
@ObjectAttribute({ alias: "Metadata", schema: metaSchema, nullable: true })
|
|
202
|
+
public readonly metadata?: InferObjectSchema<typeof metaSchema>;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- **Supported field types:** `"string"`, `"number"`, `"boolean"`, `"date"` (stored as ISO strings, exposed as `Date` objects), `"enum"` (via `values`), nested `"object"` (via `fields`), and `"array"` (via `items`)
|
|
207
|
+
- **Nullable fields:** Set `nullable: true` on individual fields within the schema to remove them
|
|
208
|
+
- **Nullable object attributes:** Set `nullable: true` on the decorator options to make the entire object optional
|
|
209
|
+
- **Alias support:** Use the `alias` option to map to a different DynamoDB attribute name
|
|
210
|
+
- **Storage:** Objects are stored as native DynamoDB Map types
|
|
211
|
+
- **Updates:** Updates replace the entire object (not a partial merge)
|
|
212
|
+
- **Filtering:** Object attributes support dot-path filtering in queries — see [Filtering on Object Attributes](#filtering-on-object-attributes)
|
|
213
|
+
|
|
214
|
+
##### Enum fields
|
|
215
|
+
|
|
216
|
+
Use `{ type: "enum", values: [...] }` to define a field that only accepts specific string values. The TypeScript type is inferred as a union of the provided values, and invalid values are rejected at runtime via Zod validation.
|
|
217
|
+
|
|
218
|
+
Enum fields can appear at any nesting level — top-level, inside nested objects, or as array items:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const schema = {
|
|
222
|
+
// Top-level enum: inferred as "active" | "inactive"
|
|
223
|
+
status: { type: "enum", values: ["active", "inactive"] },
|
|
224
|
+
|
|
225
|
+
// Nullable enum: inferred as "home" | "work" | "other" | undefined
|
|
226
|
+
category: { type: "enum", values: ["home", "work", "other"], nullable: true },
|
|
227
|
+
|
|
228
|
+
// Enum inside a nested object
|
|
229
|
+
geo: {
|
|
230
|
+
type: "object",
|
|
231
|
+
fields: {
|
|
232
|
+
accuracy: { type: "enum", values: ["precise", "approximate"] }
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// Array of enum values: inferred as ("admin" | "user")[]
|
|
237
|
+
roles: { type: "array", items: { type: "enum", values: ["admin", "user"] } }
|
|
238
|
+
} as const satisfies ObjectSchema;
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The schema must be declared with `as const satisfies ObjectSchema` so TypeScript preserves the literal string values for type inference. At runtime, providing an invalid value (e.g., `status: "unknown"`) throws a `ValidationError`.
|
|
242
|
+
|
|
168
243
|
### Foreign Keys
|
|
169
244
|
|
|
170
245
|
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.
|
|
@@ -496,6 +571,69 @@ const result = await Course.query(
|
|
|
496
571
|
);
|
|
497
572
|
```
|
|
498
573
|
|
|
574
|
+
#### Filtering on Object Attributes
|
|
575
|
+
|
|
576
|
+
When using `@ObjectAttribute`, you can filter on nested Map fields using **dot-path notation** and check List membership using the **`$contains`** operator.
|
|
577
|
+
|
|
578
|
+
##### Dot-path filtering on nested fields
|
|
579
|
+
|
|
580
|
+
Use dot notation to filter on fields within an `@ObjectAttribute`. All standard filter operators work with dot-paths: equality, `$beginsWith`, and `IN` (array of values).
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
// Equality on a nested field
|
|
584
|
+
const result = await Store.query("123", {
|
|
585
|
+
filter: { "address.city": "Springfield" }
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// $beginsWith on a nested field
|
|
589
|
+
const result = await Store.query("123", {
|
|
590
|
+
filter: { "address.street": { $beginsWith: "123" } }
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// IN on a nested field
|
|
594
|
+
const result = await Store.query("123", {
|
|
595
|
+
filter: { "address.city": ["Springfield", "Shelbyville"] }
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// Deeply nested fields
|
|
599
|
+
const result = await Store.query("123", {
|
|
600
|
+
filter: { "address.geo.lat": 40 }
|
|
601
|
+
});
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
##### `$contains` operator
|
|
605
|
+
|
|
606
|
+
Use `$contains` to check if a List attribute contains a specific element, or if a string attribute contains a substring. Works on both top-level attributes and nested fields via dot-path.
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
// Check if a List contains an element
|
|
610
|
+
const result = await Store.query("123", {
|
|
611
|
+
filter: { "address.tags": { $contains: "home" } }
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Check if a top-level string contains a substring
|
|
615
|
+
const result = await Store.query("123", {
|
|
616
|
+
filter: { name: { $contains: "john" } }
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
##### Combining dot-path and `$contains` with AND/OR
|
|
621
|
+
|
|
622
|
+
Dot-path filters and `$contains` work with all existing AND/OR filter combinations.
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
const result = await Store.query("123", {
|
|
626
|
+
filter: {
|
|
627
|
+
"address.city": "Springfield",
|
|
628
|
+
"address.geo.lat": 40,
|
|
629
|
+
$or: [
|
|
630
|
+
{ "address.tags": { $contains: "home" } },
|
|
631
|
+
{ name: { $beginsWith: "Main" } }
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
```
|
|
636
|
+
|
|
499
637
|
### Querying on an index
|
|
500
638
|
|
|
501
639
|
For querying based on secondary indexes, you can specify the index name in the options.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type DynaRecord from "../../DynaRecord";
|
|
2
|
+
import type { AttributeDecoratorContext, AttributeOptions } from "../types";
|
|
3
|
+
import type { ObjectSchema, InferObjectSchema } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* Options for the `@ObjectAttribute` decorator.
|
|
6
|
+
* Extends {@link AttributeOptions} with a required `schema` field describing the object shape.
|
|
7
|
+
*
|
|
8
|
+
* The schema supports all {@link FieldDef} types: primitives, enums, nested objects, and arrays.
|
|
9
|
+
*
|
|
10
|
+
* @template S The specific ObjectSchema type used for type inference
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* @ObjectAttribute({ alias: "Address", schema: addressSchema })
|
|
15
|
+
* public readonly address: InferObjectSchema<typeof addressSchema>;
|
|
16
|
+
*
|
|
17
|
+
* @ObjectAttribute({ alias: "Meta", schema: metaSchema, nullable: true })
|
|
18
|
+
* public readonly meta?: InferObjectSchema<typeof metaSchema>;
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export interface ObjectAttributeOptions<S extends ObjectSchema> extends AttributeOptions {
|
|
22
|
+
/**
|
|
23
|
+
* The {@link ObjectSchema} defining the structure of the object attribute.
|
|
24
|
+
*
|
|
25
|
+
* Must be declared with `as const satisfies ObjectSchema` for accurate type inference.
|
|
26
|
+
*/
|
|
27
|
+
schema: S;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A decorator for marking class fields as structured object attributes within the context of a single-table design entity.
|
|
31
|
+
*
|
|
32
|
+
* Objects are stored as native DynamoDB Map types and validated at runtime against the provided schema.
|
|
33
|
+
* The TypeScript type is inferred from the schema using {@link InferObjectSchema}.
|
|
34
|
+
*
|
|
35
|
+
* Can be set to nullable via decorator props.
|
|
36
|
+
*
|
|
37
|
+
* **Supported field types within the schema:**
|
|
38
|
+
* - `"string"`, `"number"`, `"boolean"` — primitives
|
|
39
|
+
* - `"enum"` — string literal unions, validated at runtime via `z.enum()`
|
|
40
|
+
* - `"object"` — nested objects (arbitrarily deep)
|
|
41
|
+
* - `"array"` — lists of any field type
|
|
42
|
+
*
|
|
43
|
+
* All field types support `nullable: true` to remove them
|
|
44
|
+
*
|
|
45
|
+
* @template T The class type that the decorator is applied to
|
|
46
|
+
* @template S The ObjectSchema type used for validation and type inference
|
|
47
|
+
* @template K The inferred TypeScript type from the schema
|
|
48
|
+
* @template P The decorator options type
|
|
49
|
+
* @param props An {@link ObjectAttributeOptions} object providing the `schema` and optional `alias` and `nullable` configuration.
|
|
50
|
+
* @returns A class field decorator function
|
|
51
|
+
*
|
|
52
|
+
* Usage example:
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const addressSchema = {
|
|
55
|
+
* street: { type: "string" },
|
|
56
|
+
* city: { type: "string" },
|
|
57
|
+
* zip: { type: "number", nullable: true },
|
|
58
|
+
* category: { type: "enum", values: ["home", "work", "other"] },
|
|
59
|
+
* geo: {
|
|
60
|
+
* type: "object",
|
|
61
|
+
* fields: {
|
|
62
|
+
* lat: { type: "number" },
|
|
63
|
+
* lng: { type: "number" },
|
|
64
|
+
* accuracy: { type: "enum", values: ["precise", "approximate"] }
|
|
65
|
+
* }
|
|
66
|
+
* }
|
|
67
|
+
* } as const satisfies ObjectSchema;
|
|
68
|
+
*
|
|
69
|
+
* class MyEntity extends MyTable {
|
|
70
|
+
* @ObjectAttribute({ alias: 'Address', schema: addressSchema })
|
|
71
|
+
* public address: InferObjectSchema<typeof addressSchema>;
|
|
72
|
+
*
|
|
73
|
+
* @ObjectAttribute({ alias: 'Meta', schema: metaSchema, nullable: true })
|
|
74
|
+
* public meta?: InferObjectSchema<typeof metaSchema>;
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* // TypeScript infers:
|
|
78
|
+
* // address.category → "home" | "work" | "other"
|
|
79
|
+
* // address.geo.accuracy → "precise" | "approximate"
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* Object attributes support filtering in queries using dot-path notation for nested fields
|
|
83
|
+
* and the {@link ContainsFilter | $contains} operator for List membership checks.
|
|
84
|
+
*
|
|
85
|
+
* ```typescript
|
|
86
|
+
* await MyEntity.query("123", {
|
|
87
|
+
* filter: { "address.city": "Springfield" }
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* await MyEntity.query("123", {
|
|
91
|
+
* filter: { "address.tags": { $contains: "home" } }
|
|
92
|
+
* });
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
declare function ObjectAttribute<T extends DynaRecord, const S extends ObjectSchema, P extends ObjectAttributeOptions<S>>(props: P): (_value: undefined, context: AttributeDecoratorContext<T, InferObjectSchema<S>, P>) => void;
|
|
96
|
+
export default ObjectAttribute;
|
|
97
|
+
//# sourceMappingURL=ObjectAttribute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ObjectAttribute.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/ObjectAttribute.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAY,MAAM,SAAS,CAAC;AAGzE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,YAAY,CAC5D,SAAQ,gBAAgB;IACxB;;;;OAIG;IACH,MAAM,EAAE,CAAC,CAAC;CACX;AAyED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,iBAAS,eAAe,CACtB,CAAC,SAAS,UAAU,EACpB,KAAK,CAAC,CAAC,SAAS,YAAY,EAC5B,CAAC,SAAS,sBAAsB,CAAC,CAAC,CAAC,EACnC,KAAK,EAAE,CAAC,YAEE,SAAS,WACR,yBAAyB,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,UAkBjE;AAED,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
const metadata_1 = __importDefault(require("../../metadata"));
|
|
8
|
+
const serializers_1 = require("./serializers");
|
|
9
|
+
/**
|
|
10
|
+
* Converts an {@link ObjectSchema} to a Zod schema for runtime validation.
|
|
11
|
+
*
|
|
12
|
+
* @param schema The object schema definition
|
|
13
|
+
* @returns A ZodType that validates objects matching the schema
|
|
14
|
+
*/
|
|
15
|
+
function objectSchemaToZod(schema) {
|
|
16
|
+
const shape = {};
|
|
17
|
+
for (const [key, fieldDef] of Object.entries(schema)) {
|
|
18
|
+
shape[key] = fieldDefToZod(fieldDef);
|
|
19
|
+
}
|
|
20
|
+
return zod_1.z.object(shape);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Converts a single {@link FieldDef} to the corresponding Zod type for runtime validation.
|
|
24
|
+
*
|
|
25
|
+
* Handles all field types:
|
|
26
|
+
* - `"object"` → recursively builds a `z.object()` via {@link objectSchemaToZod}
|
|
27
|
+
* - `"array"` → `z.array()` wrapping a recursive call for the `items` type
|
|
28
|
+
* - `"string"` → `z.string()`
|
|
29
|
+
* - `"number"` → `z.number()`
|
|
30
|
+
* - `"boolean"` → `z.boolean()`
|
|
31
|
+
* - `"enum"` → `z.enum(values)` for string literal validation
|
|
32
|
+
*
|
|
33
|
+
* When `nullable` is `true`, wraps the type with `.optional().nullable()`.
|
|
34
|
+
*
|
|
35
|
+
* @param fieldDef The field definition to convert
|
|
36
|
+
* @returns A ZodType that validates values matching the field definition
|
|
37
|
+
*/
|
|
38
|
+
function fieldDefToZod(fieldDef) {
|
|
39
|
+
let zodType;
|
|
40
|
+
switch (fieldDef.type) {
|
|
41
|
+
case "object":
|
|
42
|
+
zodType = objectSchemaToZod(fieldDef.fields);
|
|
43
|
+
break;
|
|
44
|
+
case "array":
|
|
45
|
+
zodType = zod_1.z.array(fieldDefToZod(fieldDef.items));
|
|
46
|
+
break;
|
|
47
|
+
case "string":
|
|
48
|
+
zodType = zod_1.z.string();
|
|
49
|
+
break;
|
|
50
|
+
case "number":
|
|
51
|
+
zodType = zod_1.z.number();
|
|
52
|
+
break;
|
|
53
|
+
case "boolean":
|
|
54
|
+
zodType = zod_1.z.boolean();
|
|
55
|
+
break;
|
|
56
|
+
case "date":
|
|
57
|
+
zodType = zod_1.z.date();
|
|
58
|
+
break;
|
|
59
|
+
case "enum":
|
|
60
|
+
zodType = zod_1.z.enum(fieldDef.values);
|
|
61
|
+
break;
|
|
62
|
+
default: {
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
64
|
+
const _exhaustiveCheck = fieldDef;
|
|
65
|
+
throw new Error("Unsupported field type");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (fieldDef.nullable === true) {
|
|
69
|
+
zodType = zodType.optional().nullable();
|
|
70
|
+
}
|
|
71
|
+
return zodType;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A decorator for marking class fields as structured object attributes within the context of a single-table design entity.
|
|
75
|
+
*
|
|
76
|
+
* Objects are stored as native DynamoDB Map types and validated at runtime against the provided schema.
|
|
77
|
+
* The TypeScript type is inferred from the schema using {@link InferObjectSchema}.
|
|
78
|
+
*
|
|
79
|
+
* Can be set to nullable via decorator props.
|
|
80
|
+
*
|
|
81
|
+
* **Supported field types within the schema:**
|
|
82
|
+
* - `"string"`, `"number"`, `"boolean"` — primitives
|
|
83
|
+
* - `"enum"` — string literal unions, validated at runtime via `z.enum()`
|
|
84
|
+
* - `"object"` — nested objects (arbitrarily deep)
|
|
85
|
+
* - `"array"` — lists of any field type
|
|
86
|
+
*
|
|
87
|
+
* All field types support `nullable: true` to remove them
|
|
88
|
+
*
|
|
89
|
+
* @template T The class type that the decorator is applied to
|
|
90
|
+
* @template S The ObjectSchema type used for validation and type inference
|
|
91
|
+
* @template K The inferred TypeScript type from the schema
|
|
92
|
+
* @template P The decorator options type
|
|
93
|
+
* @param props An {@link ObjectAttributeOptions} object providing the `schema` and optional `alias` and `nullable` configuration.
|
|
94
|
+
* @returns A class field decorator function
|
|
95
|
+
*
|
|
96
|
+
* Usage example:
|
|
97
|
+
* ```typescript
|
|
98
|
+
* const addressSchema = {
|
|
99
|
+
* street: { type: "string" },
|
|
100
|
+
* city: { type: "string" },
|
|
101
|
+
* zip: { type: "number", nullable: true },
|
|
102
|
+
* category: { type: "enum", values: ["home", "work", "other"] },
|
|
103
|
+
* geo: {
|
|
104
|
+
* type: "object",
|
|
105
|
+
* fields: {
|
|
106
|
+
* lat: { type: "number" },
|
|
107
|
+
* lng: { type: "number" },
|
|
108
|
+
* accuracy: { type: "enum", values: ["precise", "approximate"] }
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
* } as const satisfies ObjectSchema;
|
|
112
|
+
*
|
|
113
|
+
* class MyEntity extends MyTable {
|
|
114
|
+
* @ObjectAttribute({ alias: 'Address', schema: addressSchema })
|
|
115
|
+
* public address: InferObjectSchema<typeof addressSchema>;
|
|
116
|
+
*
|
|
117
|
+
* @ObjectAttribute({ alias: 'Meta', schema: metaSchema, nullable: true })
|
|
118
|
+
* public meta?: InferObjectSchema<typeof metaSchema>;
|
|
119
|
+
* }
|
|
120
|
+
*
|
|
121
|
+
* // TypeScript infers:
|
|
122
|
+
* // address.category → "home" | "work" | "other"
|
|
123
|
+
* // address.geo.accuracy → "precise" | "approximate"
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* Object attributes support filtering in queries using dot-path notation for nested fields
|
|
127
|
+
* and the {@link ContainsFilter | $contains} operator for List membership checks.
|
|
128
|
+
*
|
|
129
|
+
* ```typescript
|
|
130
|
+
* await MyEntity.query("123", {
|
|
131
|
+
* filter: { "address.city": "Springfield" }
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* await MyEntity.query("123", {
|
|
135
|
+
* filter: { "address.tags": { $contains: "home" } }
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
function ObjectAttribute(props) {
|
|
140
|
+
return function (_value, context) {
|
|
141
|
+
if (context.kind === "field") {
|
|
142
|
+
context.addInitializer(function () {
|
|
143
|
+
const { schema, ...restProps } = props;
|
|
144
|
+
const zodSchema = objectSchemaToZod(schema);
|
|
145
|
+
const serializers = (0, serializers_1.createObjectSerializer)(schema);
|
|
146
|
+
metadata_1.default.addEntityAttribute(this.constructor.name, {
|
|
147
|
+
attributeName: context.name.toString(),
|
|
148
|
+
nullable: props?.nullable,
|
|
149
|
+
type: zodSchema,
|
|
150
|
+
serializers,
|
|
151
|
+
...restProps
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
exports.default = ObjectAttribute;
|
|
@@ -7,5 +7,8 @@ export { default as BooleanAttribute } from "./BooleanAttribute";
|
|
|
7
7
|
export { default as NumberAttribute } from "./NumberAttribute";
|
|
8
8
|
export { default as EnumAttribute } from "./EnumAttribute";
|
|
9
9
|
export { default as IdAttribute } from "./IdAttribute";
|
|
10
|
+
export { default as ObjectAttribute } from "./ObjectAttribute";
|
|
11
|
+
export type { ObjectAttributeOptions } from "./ObjectAttribute";
|
|
10
12
|
export * from "./serializers";
|
|
13
|
+
export type { ObjectSchema, InferObjectSchema, FieldDef, PrimitiveFieldDef, ObjectFieldDef, ArrayFieldDef, EnumFieldDef, DateFieldDef } from "./types";
|
|
11
14
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,cAAc,eAAe,CAAC;AAC9B,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,YAAY,EACb,MAAM,SAAS,CAAC"}
|
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.IdAttribute = exports.EnumAttribute = exports.NumberAttribute = exports.BooleanAttribute = exports.StringAttribute = exports.DateAttribute = exports.ForeignKeyAttribute = exports.SortKeyAttribute = exports.PartitionKeyAttribute = void 0;
|
|
20
|
+
exports.ObjectAttribute = exports.IdAttribute = exports.EnumAttribute = exports.NumberAttribute = exports.BooleanAttribute = exports.StringAttribute = exports.DateAttribute = exports.ForeignKeyAttribute = exports.SortKeyAttribute = exports.PartitionKeyAttribute = void 0;
|
|
21
21
|
var PartitionKeyAttribute_1 = require("./PartitionKeyAttribute");
|
|
22
22
|
Object.defineProperty(exports, "PartitionKeyAttribute", { enumerable: true, get: function () { return __importDefault(PartitionKeyAttribute_1).default; } });
|
|
23
23
|
var SortKeyAttribute_1 = require("./SortKeyAttribute");
|
|
@@ -36,4 +36,6 @@ var EnumAttribute_1 = require("./EnumAttribute");
|
|
|
36
36
|
Object.defineProperty(exports, "EnumAttribute", { enumerable: true, get: function () { return __importDefault(EnumAttribute_1).default; } });
|
|
37
37
|
var IdAttribute_1 = require("./IdAttribute");
|
|
38
38
|
Object.defineProperty(exports, "IdAttribute", { enumerable: true, get: function () { return __importDefault(IdAttribute_1).default; } });
|
|
39
|
+
var ObjectAttribute_1 = require("./ObjectAttribute");
|
|
40
|
+
Object.defineProperty(exports, "ObjectAttribute", { enumerable: true, get: function () { return __importDefault(ObjectAttribute_1).default; } });
|
|
39
41
|
__exportStar(require("./serializers"), exports);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NativeAttributeValue } from "@aws-sdk/util-dynamodb";
|
|
2
|
+
import type { ObjectSchema } from "./types";
|
|
3
|
+
import type { Serializers } from "../../metadata/types";
|
|
2
4
|
/**
|
|
3
5
|
* Provides serialization and deserialization functions for date attributes when interfacing with a DynamoDB table, enabling the conversion between the table's string-based date representation and JavaScript's `Date` object. DynamoDb dos not support Date types naturally, this utility allows for Date attributes to be serialized to an entity and stored as ISO strings in Dynamo.
|
|
4
6
|
*
|
|
@@ -7,7 +9,53 @@ import type { NativeScalarAttributeValue } from "@aws-sdk/util-dynamodb";
|
|
|
7
9
|
*
|
|
8
10
|
*/
|
|
9
11
|
export declare const dateSerializer: {
|
|
10
|
-
toEntityAttribute: (val:
|
|
12
|
+
toEntityAttribute: (val: NativeAttributeValue) => any;
|
|
11
13
|
toTableAttribute: (val?: Date) => string | undefined;
|
|
12
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Recursively walks an {@link ObjectSchema} and converts the entity value to its
|
|
17
|
+
* DynamoDB representation.
|
|
18
|
+
*
|
|
19
|
+
* - `"date"` fields are converted from `Date` objects to ISO 8601 strings.
|
|
20
|
+
* - `"object"` fields recurse into their nested schema.
|
|
21
|
+
* - `"array"` fields map each item through the same conversion.
|
|
22
|
+
* - `null` and `undefined` values are stripped from the result so that nullable
|
|
23
|
+
* fields set to `null` are removed from the stored object rather than persisted
|
|
24
|
+
* as `null` in DynamoDB.
|
|
25
|
+
* - All other field types pass through unchanged.
|
|
26
|
+
*
|
|
27
|
+
* @param schema The {@link ObjectSchema} describing the object shape
|
|
28
|
+
* @param value The entity-level object value to convert
|
|
29
|
+
* @returns A new object suitable for DynamoDB storage
|
|
30
|
+
*/
|
|
31
|
+
export declare function objectToTableItem(schema: ObjectSchema, value: Record<string, unknown>): Record<string, unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Recursively walks an {@link ObjectSchema} and converts a DynamoDB table item
|
|
34
|
+
* back to its entity representation.
|
|
35
|
+
*
|
|
36
|
+
* - `"date"` fields are converted from ISO 8601 strings to `Date` objects.
|
|
37
|
+
* - `"object"` fields recurse into their nested schema.
|
|
38
|
+
* - `"array"` fields map each item through the same conversion.
|
|
39
|
+
* - `null` and `undefined` values are stripped from the result so that absent
|
|
40
|
+
* fields are represented as `undefined` (omitted) on the entity, consistent
|
|
41
|
+
* with root-level nullable attribute behaviour.
|
|
42
|
+
* - All other field types pass through unchanged.
|
|
43
|
+
*
|
|
44
|
+
* @param schema The {@link ObjectSchema} describing the object shape
|
|
45
|
+
* @param value The DynamoDB table item to convert
|
|
46
|
+
* @returns A new object with entity-level types (e.g. `Date` instead of string)
|
|
47
|
+
*/
|
|
48
|
+
export declare function tableItemToObject(schema: ObjectSchema, value: Record<string, unknown>): Record<string, unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* Creates a {@link Serializers} pair for an {@link ObjectSchema}.
|
|
51
|
+
*
|
|
52
|
+
* The returned serializers handle:
|
|
53
|
+
* - Converting `Date` fields to/from ISO 8601 strings for DynamoDB storage.
|
|
54
|
+
* - Stripping `null` and `undefined` values so that nullable fields set to
|
|
55
|
+
* `null` during updates are removed from the stored object.
|
|
56
|
+
*
|
|
57
|
+
* @param schema The {@link ObjectSchema} describing the object shape
|
|
58
|
+
* @returns A `Serializers` object with `toTableAttribute` and `toEntityAttribute` functions
|
|
59
|
+
*/
|
|
60
|
+
export declare function createObjectSerializer(schema: ObjectSchema): Serializers;
|
|
13
61
|
//# sourceMappingURL=serializers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializers.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/serializers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"serializers.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/serializers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,SAAS,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;6BACA,oBAAoB;6BAMpB,IAAI;CAC9B,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AAiBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AAiBD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAOxE"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dateSerializer = void 0;
|
|
4
|
+
exports.objectToTableItem = objectToTableItem;
|
|
5
|
+
exports.tableItemToObject = tableItemToObject;
|
|
6
|
+
exports.createObjectSerializer = createObjectSerializer;
|
|
4
7
|
/**
|
|
5
8
|
* Provides serialization and deserialization functions for date attributes when interfacing with a DynamoDB table, enabling the conversion between the table's string-based date representation and JavaScript's `Date` object. DynamoDb dos not support Date types naturally, this utility allows for Date attributes to be serialized to an entity and stored as ISO strings in Dynamo.
|
|
6
9
|
*
|
|
@@ -17,3 +20,98 @@ exports.dateSerializer = {
|
|
|
17
20
|
},
|
|
18
21
|
toTableAttribute: (val) => val?.toISOString() ?? undefined
|
|
19
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Recursively walks an {@link ObjectSchema} and converts the entity value to its
|
|
25
|
+
* DynamoDB representation.
|
|
26
|
+
*
|
|
27
|
+
* - `"date"` fields are converted from `Date` objects to ISO 8601 strings.
|
|
28
|
+
* - `"object"` fields recurse into their nested schema.
|
|
29
|
+
* - `"array"` fields map each item through the same conversion.
|
|
30
|
+
* - `null` and `undefined` values are stripped from the result so that nullable
|
|
31
|
+
* fields set to `null` are removed from the stored object rather than persisted
|
|
32
|
+
* as `null` in DynamoDB.
|
|
33
|
+
* - All other field types pass through unchanged.
|
|
34
|
+
*
|
|
35
|
+
* @param schema The {@link ObjectSchema} describing the object shape
|
|
36
|
+
* @param value The entity-level object value to convert
|
|
37
|
+
* @returns A new object suitable for DynamoDB storage
|
|
38
|
+
*/
|
|
39
|
+
function objectToTableItem(schema, value) {
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const [key, fieldDef] of Object.entries(schema)) {
|
|
42
|
+
const val = value[key];
|
|
43
|
+
if (val === undefined || val === null) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
result[key] = convertFieldToTableItem(fieldDef, val);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function convertFieldToTableItem(fieldDef, val) {
|
|
51
|
+
switch (fieldDef.type) {
|
|
52
|
+
case "date":
|
|
53
|
+
return val.toISOString();
|
|
54
|
+
case "object":
|
|
55
|
+
return objectToTableItem(fieldDef.fields, val);
|
|
56
|
+
case "array":
|
|
57
|
+
return val.map(item => convertFieldToTableItem(fieldDef.items, item));
|
|
58
|
+
default:
|
|
59
|
+
return val;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Recursively walks an {@link ObjectSchema} and converts a DynamoDB table item
|
|
64
|
+
* back to its entity representation.
|
|
65
|
+
*
|
|
66
|
+
* - `"date"` fields are converted from ISO 8601 strings to `Date` objects.
|
|
67
|
+
* - `"object"` fields recurse into their nested schema.
|
|
68
|
+
* - `"array"` fields map each item through the same conversion.
|
|
69
|
+
* - `null` and `undefined` values are stripped from the result so that absent
|
|
70
|
+
* fields are represented as `undefined` (omitted) on the entity, consistent
|
|
71
|
+
* with root-level nullable attribute behaviour.
|
|
72
|
+
* - All other field types pass through unchanged.
|
|
73
|
+
*
|
|
74
|
+
* @param schema The {@link ObjectSchema} describing the object shape
|
|
75
|
+
* @param value The DynamoDB table item to convert
|
|
76
|
+
* @returns A new object with entity-level types (e.g. `Date` instead of string)
|
|
77
|
+
*/
|
|
78
|
+
function tableItemToObject(schema, value) {
|
|
79
|
+
const result = {};
|
|
80
|
+
for (const [key, fieldDef] of Object.entries(schema)) {
|
|
81
|
+
const val = value[key];
|
|
82
|
+
if (val === undefined || val === null) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
result[key] = convertFieldToEntityValue(fieldDef, val);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function convertFieldToEntityValue(fieldDef, val) {
|
|
90
|
+
switch (fieldDef.type) {
|
|
91
|
+
case "date":
|
|
92
|
+
return new Date(val);
|
|
93
|
+
case "object":
|
|
94
|
+
return tableItemToObject(fieldDef.fields, val);
|
|
95
|
+
case "array":
|
|
96
|
+
return val.map(item => convertFieldToEntityValue(fieldDef.items, item));
|
|
97
|
+
default:
|
|
98
|
+
return val;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Creates a {@link Serializers} pair for an {@link ObjectSchema}.
|
|
103
|
+
*
|
|
104
|
+
* The returned serializers handle:
|
|
105
|
+
* - Converting `Date` fields to/from ISO 8601 strings for DynamoDB storage.
|
|
106
|
+
* - Stripping `null` and `undefined` values so that nullable fields set to
|
|
107
|
+
* `null` during updates are removed from the stored object.
|
|
108
|
+
*
|
|
109
|
+
* @param schema The {@link ObjectSchema} describing the object shape
|
|
110
|
+
* @returns A `Serializers` object with `toTableAttribute` and `toEntityAttribute` functions
|
|
111
|
+
*/
|
|
112
|
+
function createObjectSerializer(schema) {
|
|
113
|
+
return {
|
|
114
|
+
toTableAttribute: (val) => objectToTableItem(schema, val),
|
|
115
|
+
toEntityAttribute: (val) => tableItemToObject(schema, val)
|
|
116
|
+
};
|
|
117
|
+
}
|