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 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 { NativeScalarAttributeValue } from "@aws-sdk/util-dynamodb";
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: NativeScalarAttributeValue) => number | bigint | boolean | import("@aws-sdk/util-dynamodb").NumberValue | ArrayBuffer | Blob | DataView<ArrayBufferLike> | Date | null | undefined;
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,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAEzE;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;6BACA,0BAA0B;6BAM1B,IAAI;CAC9B,CAAC"}
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
+ }