@ftschopp/dynatable-core 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +528 -2
  3. package/dist/builders/delete/types.d.ts +6 -1
  4. package/dist/builders/delete/types.d.ts.map +1 -1
  5. package/dist/builders/get/types.d.ts +6 -1
  6. package/dist/builders/get/types.d.ts.map +1 -1
  7. package/dist/builders/put/types.d.ts +6 -1
  8. package/dist/builders/put/types.d.ts.map +1 -1
  9. package/dist/builders/query/create-query-builder.d.ts +1 -1
  10. package/dist/builders/query/create-query-builder.d.ts.map +1 -1
  11. package/dist/builders/query/types.d.ts +9 -10
  12. package/dist/builders/query/types.d.ts.map +1 -1
  13. package/dist/builders/transact-write/create-transact-write-builder.d.ts.map +1 -1
  14. package/dist/builders/transact-write/create-transact-write-builder.js +1 -1
  15. package/dist/builders/transact-write/types.d.ts +33 -6
  16. package/dist/builders/transact-write/types.d.ts.map +1 -1
  17. package/dist/builders/update/create-update-builder.d.ts.map +1 -1
  18. package/dist/builders/update/create-update-builder.js +30 -9
  19. package/dist/builders/update/types.d.ts +8 -2
  20. package/dist/builders/update/types.d.ts.map +1 -1
  21. package/dist/core/types.d.ts +96 -11
  22. package/dist/core/types.d.ts.map +1 -1
  23. package/dist/entity/create-entity-api.d.ts +15 -0
  24. package/dist/entity/create-entity-api.d.ts.map +1 -0
  25. package/dist/entity/create-entity-api.js +124 -0
  26. package/dist/entity/index.d.ts +12 -0
  27. package/dist/entity/index.d.ts.map +1 -0
  28. package/dist/entity/index.js +18 -0
  29. package/dist/entity/middleware/factories.d.ts +6 -0
  30. package/dist/entity/middleware/factories.d.ts.map +1 -0
  31. package/dist/entity/middleware/factories.js +15 -0
  32. package/dist/entity/middleware/types.d.ts +8 -0
  33. package/dist/entity/middleware/types.d.ts.map +1 -0
  34. package/dist/entity/middleware/types.js +2 -0
  35. package/dist/entity/middleware/with-middleware.d.ts +8 -0
  36. package/dist/entity/middleware/with-middleware.d.ts.map +1 -0
  37. package/dist/entity/middleware/with-middleware.js +29 -0
  38. package/dist/{entity.d.ts → entity/types.d.ts} +6 -17
  39. package/dist/entity/types.d.ts.map +1 -0
  40. package/dist/entity/types.js +2 -0
  41. package/dist/entity/validation/key-validation.d.ts +7 -0
  42. package/dist/entity/validation/key-validation.d.ts.map +1 -0
  43. package/dist/entity/validation/key-validation.js +25 -0
  44. package/dist/index.d.ts +1 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/table.d.ts +8 -1
  47. package/dist/table.d.ts.map +1 -1
  48. package/dist/table.js +1 -0
  49. package/dist/utils/model-utils.d.ts +7 -1
  50. package/dist/utils/model-utils.d.ts.map +1 -1
  51. package/dist/utils/model-utils.js +32 -3
  52. package/dist/utils/zod-utils.d.ts +3 -2
  53. package/dist/utils/zod-utils.d.ts.map +1 -1
  54. package/dist/utils/zod-utils.js +33 -11
  55. package/package.json +3 -2
  56. package/src/builders/README.md +68 -1
  57. package/src/builders/delete/types.ts +7 -1
  58. package/src/builders/get/types.ts +7 -1
  59. package/src/builders/put/types.ts +7 -1
  60. package/src/builders/query/create-query-builder.ts +4 -6
  61. package/src/builders/query/types.ts +9 -12
  62. package/src/builders/transact-write/README.md +37 -1
  63. package/src/builders/transact-write/create-transact-write-builder.ts +20 -14
  64. package/src/builders/transact-write/types.ts +44 -6
  65. package/src/builders/update/create-update-builder.test.ts +43 -0
  66. package/src/builders/update/create-update-builder.ts +47 -9
  67. package/src/builders/update/types.ts +9 -2
  68. package/src/core/types.test.ts +137 -0
  69. package/src/core/types.ts +97 -20
  70. package/src/entity/create-entity-api.ts +212 -0
  71. package/src/entity/index.ts +19 -0
  72. package/src/entity/middleware/factories.ts +15 -0
  73. package/src/entity/middleware/types.ts +7 -0
  74. package/src/entity/middleware/with-middleware.ts +37 -0
  75. package/src/entity/types.ts +79 -0
  76. package/src/entity/validation/key-validation.ts +34 -0
  77. package/src/index.ts +1 -0
  78. package/src/table.ts +10 -3
  79. package/src/utils/model-utils.test.ts +131 -1
  80. package/src/utils/model-utils.ts +35 -2
  81. package/src/utils/zod-utils.test.ts +97 -2
  82. package/src/utils/zod-utils.ts +31 -12
  83. package/dist/entity.d.ts.map +0 -1
  84. package/dist/entity.js +0 -161
  85. package/src/entity.ts +0 -337
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { applyPostDefaults } from './model-utils';
2
+ import { applyPostDefaults, stripInternalKeys } from './model-utils';
3
3
  import { ModelDefinition } from '../core/types';
4
4
 
5
5
  describe('applyPostDefaults - Timestamps', () => {
@@ -230,3 +230,133 @@ describe('applyPostDefaults - Timestamps', () => {
230
230
  });
231
231
  });
232
232
  });
233
+
234
+ describe('stripInternalKeys', () => {
235
+ test('should remove PK, SK, and _type from object', () => {
236
+ const input = {
237
+ PK: 'USER#alice',
238
+ SK: 'USER#alice',
239
+ _type: 'User',
240
+ username: 'alice',
241
+ name: 'Alice',
242
+ email: 'alice@example.com',
243
+ };
244
+
245
+ const result = stripInternalKeys(input);
246
+
247
+ expect(result).toEqual({
248
+ username: 'alice',
249
+ name: 'Alice',
250
+ email: 'alice@example.com',
251
+ });
252
+ });
253
+
254
+ test('should handle object without internal keys', () => {
255
+ const input = {
256
+ username: 'alice',
257
+ name: 'Alice',
258
+ email: 'alice@example.com',
259
+ };
260
+
261
+ const result = stripInternalKeys(input);
262
+
263
+ expect(result).toEqual(input);
264
+ });
265
+
266
+ test('should handle array of objects', () => {
267
+ const input = [
268
+ {
269
+ PK: 'USER#alice',
270
+ SK: 'USER#alice',
271
+ _type: 'User',
272
+ username: 'alice',
273
+ name: 'Alice',
274
+ },
275
+ {
276
+ PK: 'USER#bob',
277
+ SK: 'USER#bob',
278
+ _type: 'User',
279
+ username: 'bob',
280
+ name: 'Bob',
281
+ },
282
+ ];
283
+
284
+ const result = stripInternalKeys(input);
285
+
286
+ expect(result).toEqual([
287
+ { username: 'alice', name: 'Alice' },
288
+ { username: 'bob', name: 'Bob' },
289
+ ]);
290
+ });
291
+
292
+ test('should handle undefined', () => {
293
+ const result = stripInternalKeys(undefined);
294
+ expect(result).toBeUndefined();
295
+ });
296
+
297
+ test('should handle null', () => {
298
+ const result = stripInternalKeys(null);
299
+ expect(result).toBeNull();
300
+ });
301
+
302
+ test('should handle empty object', () => {
303
+ const input = {};
304
+ const result = stripInternalKeys(input);
305
+ expect(result).toEqual({});
306
+ });
307
+
308
+ test('should handle empty array', () => {
309
+ const input: any[] = [];
310
+ const result = stripInternalKeys(input);
311
+ expect(result).toEqual([]);
312
+ });
313
+
314
+ test('should preserve other fields with similar names', () => {
315
+ const input = {
316
+ PK: 'USER#alice',
317
+ SK: 'USER#alice',
318
+ _type: 'User',
319
+ PKG: 'package-name',
320
+ SKIP: 'skip-value',
321
+ type: 'customer',
322
+ username: 'alice',
323
+ };
324
+
325
+ const result = stripInternalKeys(input);
326
+
327
+ expect(result).toEqual({
328
+ PKG: 'package-name',
329
+ SKIP: 'skip-value',
330
+ type: 'customer',
331
+ username: 'alice',
332
+ });
333
+ });
334
+
335
+ test('should handle nested objects (does not recurse)', () => {
336
+ const input = {
337
+ PK: 'USER#alice',
338
+ SK: 'USER#alice',
339
+ _type: 'User',
340
+ username: 'alice',
341
+ metadata: {
342
+ PK: 'METADATA#1',
343
+ SK: 'METADATA#1',
344
+ _type: 'Metadata',
345
+ value: 'test',
346
+ },
347
+ };
348
+
349
+ const result = stripInternalKeys(input);
350
+
351
+ // stripInternalKeys does not recurse into nested objects
352
+ expect(result).toEqual({
353
+ username: 'alice',
354
+ metadata: {
355
+ PK: 'METADATA#1',
356
+ SK: 'METADATA#1',
357
+ _type: 'Metadata',
358
+ value: 'test',
359
+ },
360
+ });
361
+ });
362
+ });
@@ -71,9 +71,10 @@ export const applyPostDefaults = <M extends ModelDefinition>(
71
71
 
72
72
  for (const [key, attr] of Object.entries(model.attributes)) {
73
73
  if (result[key] === undefined) {
74
- if (attr.generate === 'ulid') {
74
+ const generate = 'generate' in attr ? attr.generate : undefined;
75
+ if (generate === 'ulid') {
75
76
  result[key] = ulid();
76
- } else if (attr.generate === 'uuid') {
77
+ } else if (generate === 'uuid') {
77
78
  result[key] = crypto.randomUUID();
78
79
  } else if (attr.default !== undefined) {
79
80
  result[key] = typeof attr.default === 'function' ? attr.default() : attr.default;
@@ -99,3 +100,35 @@ export const applyPostDefaults = <M extends ModelDefinition>(
99
100
 
100
101
  return result as InferModel<M>;
101
102
  };
103
+
104
+ /**
105
+ * Internal DynamoDB keys that should be stripped when cleanInternalKeys is enabled
106
+ */
107
+ const INTERNAL_KEYS = ['PK', 'SK', '_type'] as const;
108
+
109
+ /**
110
+ * Removes internal DynamoDB keys from an item or array of items
111
+ * @param data - Single item or array of items from DynamoDB
112
+ * @returns Data with internal keys removed
113
+ */
114
+ export const stripInternalKeys = <T>(data: T | T[] | undefined): T | T[] | undefined => {
115
+ if (data === undefined || data === null) {
116
+ return data;
117
+ }
118
+
119
+ if (Array.isArray(data)) {
120
+ return data.map((item) => stripInternalKeys(item)) as T[];
121
+ }
122
+
123
+ if (typeof data === 'object') {
124
+ const cleaned: any = {};
125
+ for (const [key, value] of Object.entries(data)) {
126
+ if (!INTERNAL_KEYS.includes(key as any)) {
127
+ cleaned[key] = value;
128
+ }
129
+ }
130
+ return cleaned as T;
131
+ }
132
+
133
+ return data;
134
+ };
@@ -71,9 +71,9 @@ describe('zod-utils', () => {
71
71
  expect(() => zodType.parse(undefined)).toThrow();
72
72
  });
73
73
 
74
- it('should return z.unknown() for unknown types', () => {
74
+ it('should return z.unknown() for unrecognized types', () => {
75
75
  const attr: AttributeDefinition = {
76
- type: Array as any,
76
+ type: Map as any,
77
77
  required: true,
78
78
  };
79
79
  const zodType = typeToZod(attr);
@@ -83,6 +83,101 @@ describe('zod-utils', () => {
83
83
  expect(() => zodType.parse(123)).not.toThrow();
84
84
  expect(() => zodType.parse([])).not.toThrow();
85
85
  });
86
+
87
+ it('should convert Object type with schema to z.looseObject()', () => {
88
+ const attr: AttributeDefinition = {
89
+ type: Object,
90
+ schema: {
91
+ city: { type: String, required: true },
92
+ zip: { type: String },
93
+ },
94
+ required: true,
95
+ };
96
+ const zodType = typeToZod(attr);
97
+
98
+ expect(() => zodType.parse({ city: 'BA' })).not.toThrow();
99
+ expect(() => zodType.parse({ city: 'BA', zip: '1000' })).not.toThrow();
100
+ expect(() => zodType.parse({ zip: '1000' })).toThrow(); // city required
101
+ expect(() => zodType.parse({})).toThrow();
102
+ });
103
+
104
+ it('should convert Object type without schema to z.record()', () => {
105
+ const attr: AttributeDefinition = {
106
+ type: Object,
107
+ schema: {},
108
+ };
109
+ const zodType = typeToZod(attr);
110
+ expect(() => zodType.parse({})).not.toThrow();
111
+ });
112
+
113
+ it('should convert Array type with scalar items', () => {
114
+ const attr: AttributeDefinition = {
115
+ type: Array,
116
+ items: { type: String },
117
+ required: true,
118
+ };
119
+ const zodType = typeToZod(attr);
120
+
121
+ expect(() => zodType.parse(['a', 'b'])).not.toThrow();
122
+ expect(() => zodType.parse([])).not.toThrow();
123
+ expect(() => zodType.parse([1, 2])).toThrow(); // numbers, not strings
124
+ });
125
+
126
+ it('should convert Array type with object items (like history)', () => {
127
+ const attr: AttributeDefinition = {
128
+ type: Array,
129
+ items: {
130
+ type: Object,
131
+ schema: {
132
+ date: { type: String },
133
+ status: { type: String, required: true },
134
+ },
135
+ },
136
+ required: true,
137
+ };
138
+ const zodType = typeToZod(attr);
139
+
140
+ expect(() => zodType.parse([{ status: 'PENDING' }])).not.toThrow();
141
+ expect(() =>
142
+ zodType.parse([{ date: '2024-01-01', status: 'DONE' }])
143
+ ).not.toThrow();
144
+ expect(() => zodType.parse([{ date: '2024-01-01' }])).toThrow(); // missing status
145
+ });
146
+
147
+ it('should handle optional Object attribute', () => {
148
+ const attr: AttributeDefinition = {
149
+ type: Object,
150
+ schema: { name: { type: String, required: true } },
151
+ };
152
+ const zodType = typeToZod(attr);
153
+
154
+ expect(() => zodType.parse(undefined)).not.toThrow();
155
+ expect(() => zodType.parse({ name: 'test' })).not.toThrow();
156
+ });
157
+
158
+ it('should handle deeply nested objects', () => {
159
+ const attr: AttributeDefinition = {
160
+ type: Object,
161
+ required: true,
162
+ schema: {
163
+ value: { type: Number, required: true },
164
+ breakdown: {
165
+ type: Object,
166
+ schema: {
167
+ base: { type: Number, required: true },
168
+ fees: { type: Number, required: true },
169
+ },
170
+ },
171
+ },
172
+ };
173
+ const zodType = typeToZod(attr);
174
+
175
+ expect(() =>
176
+ zodType.parse({ value: 100, breakdown: { base: 90, fees: 10 } })
177
+ ).not.toThrow();
178
+ expect(() => zodType.parse({ value: 100 })).not.toThrow(); // breakdown optional
179
+ expect(() => zodType.parse({})).toThrow(); // value required
180
+ });
86
181
  });
87
182
 
88
183
  describe('modelToZod', () => {
@@ -5,19 +5,38 @@ import { AttributeDefinition, ModelDefinition } from '@/core/types';
5
5
  import { z, ZodObject, ZodType } from 'zod';
6
6
 
7
7
  /**
8
- * Converts an attribute definition to a Zod schema type
8
+ * Converts an attribute definition to a Zod schema type.
9
+ * Supports scalars, nested objects (with schema), and arrays (with items).
9
10
  */
10
11
  export const typeToZod = (attr: AttributeDefinition): ZodType => {
11
- const zodType: ZodType =
12
- attr.type === String
13
- ? z.string()
14
- : attr.type === Number
15
- ? z.number()
16
- : attr.type === Boolean
17
- ? z.boolean()
18
- : attr.type === Date
19
- ? z.date()
20
- : z.unknown();
12
+ let zodType: ZodType;
13
+
14
+ if (attr.type === Object) {
15
+ const schema = (attr as { schema?: Record<string, AttributeDefinition> }).schema;
16
+ if (schema) {
17
+ const shape: Record<string, ZodType> = {};
18
+ for (const [key, nestedAttr] of Object.entries(schema)) {
19
+ shape[key] = typeToZod(nestedAttr);
20
+ }
21
+ zodType = z.looseObject(shape);
22
+ } else {
23
+ zodType = z.record(z.string(), z.unknown());
24
+ }
25
+ } else if (attr.type === Array) {
26
+ const items = (attr as { items?: AttributeDefinition }).items;
27
+ zodType = items ? z.array(typeToZod(items)) : z.array(z.unknown());
28
+ } else {
29
+ zodType =
30
+ attr.type === String
31
+ ? z.string()
32
+ : attr.type === Number
33
+ ? z.number()
34
+ : attr.type === Boolean
35
+ ? z.boolean()
36
+ : attr.type === Date
37
+ ? z.date()
38
+ : z.unknown();
39
+ }
21
40
 
22
41
  return attr.required ? zodType : zodType.optional();
23
42
  };
@@ -32,5 +51,5 @@ export const modelToZod = (model: ModelDefinition): ZodObject<any> => {
32
51
  for (const [key, attr] of Object.entries(model.attributes)) {
33
52
  shape[key] = typeToZod(attr);
34
53
  }
35
- return z.object(shape).passthrough();
54
+ return z.looseObject(shape);
36
55
  };
@@ -1 +0,0 @@
1
- {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../src/entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGtF,OAAO,EASL,UAAU,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,aAAa,EACb,WAAW,EACX,eAAe,EACf,iBAAiB,EAElB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,SAAS,eAAe,GAAG,GAAG,IAAI;IACtF;;;;OAIG;IACH,GAAG,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAEpD;;;;OAIG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,UAAU,CAAC,KAAK,CAAC,CAAC;IAExC;;;OAGG;IACH,KAAK,EAAE,MAAM,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAE3C;;;OAGG;IACH,IAAI,EAAE,MAAM,WAAW,CAAC,KAAK,CAAC,CAAC;IAE/B;;;;OAIG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,aAAa,CAAC,KAAK,CAAC,CAAC;IAEhD;;;;OAIG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,aAAa,CAAC,KAAK,CAAC,CAAC;IAEhD;;;;OAIG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,eAAe,CAAC,KAAK,CAAC,CAAC;IAEvD;;;;OAIG;IACH,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,iBAAiB,CAAC;CACnD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,SAAS,eAAe,EAC3D,WAAW,MAAM,EACjB,WAAW,MAAM,EACjB,OAAO,KAAK,EACZ,QAAQ,cAAc,EACtB,UAAS,gBAAqB,KAC7B,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,KAAK,CAqO7E,CAAC"}
package/dist/entity.js DELETED
@@ -1,161 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createEntityAPI = void 0;
4
- const model_utils_1 = require("./utils/model-utils");
5
- const zod_utils_1 = require("./utils/zod-utils");
6
- const builders_1 = require("./builders");
7
- /**
8
- * Creates an entity API instance with validation, key resolution, and builder creation.
9
- *
10
- * @param modelName - The name of the model/entity
11
- * @param model - The model definition
12
- * @param client - DynamoDB client instance
13
- * @param options - Optional configuration (logger, timestamps)
14
- * @returns EntityAPI with get and put methods
15
- */
16
- const createEntityAPI = (tableName, modelName, model, client, options = {}) => {
17
- const { logger, timestamps = false } = options;
18
- // Build a Zod schema from the model
19
- const zodSchema = (0, zod_utils_1.modelToZod)(model);
20
- return {
21
- get(key) {
22
- // Extract required fields from key templates
23
- const requiredFields = new Set();
24
- if (model.key) {
25
- for (const keyDef of Object.values(model.key)) {
26
- (0, model_utils_1.extractTemplateVars)(keyDef.value).forEach((field) => requiredFields.add(field));
27
- }
28
- }
29
- // Check if all required fields are present
30
- const keyRecord = key;
31
- const missingFields = Array.from(requiredFields).filter((field) => keyRecord[field] === undefined);
32
- if (missingFields.length > 0) {
33
- throw new Error(`[${modelName}] Missing required key field(s) for get(): ${missingFields.join(', ')}. ` +
34
- `Required fields: ${Array.from(requiredFields).join(', ')}`);
35
- }
36
- // Resolve any key defaults or computed keys
37
- const fullKey = (0, model_utils_1.resolveKeys)(model, key);
38
- return (0, builders_1.createGetBuilder)(tableName, fullKey, client, undefined, logger);
39
- },
40
- put(item) {
41
- // Validate full input data
42
- const parsed = zodSchema.parse(item);
43
- // Apply post-processing defaults from the model (including timestamps for new items)
44
- const withDefaults = (0, model_utils_1.applyPostDefaults)(model, parsed, {
45
- isUpdate: false,
46
- timestamps,
47
- });
48
- // Resolve keys again with defaults
49
- const fullKey = (0, model_utils_1.resolveKeys)(model, withDefaults);
50
- // Combine keys and data into full item, adding _type field
51
- const fullItem = {
52
- ...withDefaults,
53
- ...fullKey,
54
- _type: modelName, // Add entity type identifier
55
- };
56
- return (0, builders_1.createPutBuilder)(tableName, fullItem, client, [], false, 'NONE', false, logger);
57
- },
58
- query() {
59
- return (0, builders_1.createQueryBuilder)(tableName, client, model, logger);
60
- },
61
- scan() {
62
- return (0, builders_1.createScanBuilder)(tableName, client, [], [], undefined, false, undefined, undefined, undefined, logger);
63
- },
64
- update(key) {
65
- // Extract required fields from key templates
66
- const requiredFields = new Set();
67
- if (model.key) {
68
- for (const keyDef of Object.values(model.key)) {
69
- (0, model_utils_1.extractTemplateVars)(keyDef.value).forEach((field) => requiredFields.add(field));
70
- }
71
- }
72
- // Check if all required fields are present
73
- const keyRecord = key;
74
- const missingFields = Array.from(requiredFields).filter((field) => keyRecord[field] === undefined);
75
- if (missingFields.length > 0) {
76
- throw new Error(`[${modelName}] Missing required key field(s) for update(): ${missingFields.join(', ')}. ` +
77
- `Required fields: ${Array.from(requiredFields).join(', ')}`);
78
- }
79
- // Resolve any key defaults or computed keys
80
- const fullKey = (0, model_utils_1.resolveKeys)(model, key);
81
- return (0, builders_1.createUpdateBuilder)(tableName, fullKey, client, [], { set: [], remove: [], add: [], delete: [] }, 'NONE', 0, timestamps, logger);
82
- },
83
- delete(key) {
84
- // Extract required fields from key templates
85
- const requiredFields = new Set();
86
- if (model.key) {
87
- for (const keyDef of Object.values(model.key)) {
88
- (0, model_utils_1.extractTemplateVars)(keyDef.value).forEach((field) => requiredFields.add(field));
89
- }
90
- }
91
- // Check if all required fields are present
92
- const keyRecord = key;
93
- const missingFields = Array.from(requiredFields).filter((field) => keyRecord[field] === undefined);
94
- if (missingFields.length > 0) {
95
- throw new Error(`[${modelName}] Missing required key field(s) for delete(): ${missingFields.join(', ')}. ` +
96
- `Required fields: ${Array.from(requiredFields).join(', ')}`);
97
- }
98
- // Resolve any key defaults or computed keys
99
- const fullKey = (0, model_utils_1.resolveKeys)(model, key);
100
- return (0, builders_1.createDeleteBuilder)(tableName, fullKey, client, [], 'NONE', logger);
101
- },
102
- batchGet(keys) {
103
- // Extract required fields from key templates
104
- const requiredFields = new Set();
105
- if (model.key) {
106
- for (const keyDef of Object.values(model.key)) {
107
- (0, model_utils_1.extractTemplateVars)(keyDef.value).forEach((field) => requiredFields.add(field));
108
- }
109
- }
110
- // Process all keys and validate them
111
- const resolvedKeys = keys.map((key) => {
112
- // Check if all required fields are present
113
- const keyRecord = key;
114
- const missingFields = Array.from(requiredFields).filter((field) => keyRecord[field] === undefined);
115
- if (missingFields.length > 0) {
116
- throw new Error(`[${modelName}] Missing required key field(s) for batchGet(): ${missingFields.join(', ')}. ` +
117
- `Required fields: ${Array.from(requiredFields).join(', ')}`);
118
- }
119
- // Resolve any key defaults or computed keys
120
- return (0, model_utils_1.resolveKeys)(model, key);
121
- });
122
- // Create the request items in the format expected by BatchGetItem
123
- const requestItems = {
124
- [tableName]: {
125
- Keys: resolvedKeys,
126
- },
127
- };
128
- return (0, builders_1.createBatchGetBuilder)(requestItems, client, undefined, logger);
129
- },
130
- batchWrite(items) {
131
- // Validate and process all items
132
- const processedItems = items.map((item) => {
133
- // Validate full input data
134
- const parsed = zodSchema.parse(item);
135
- // Apply post-processing defaults from the model (including timestamps for new items)
136
- const withDefaults = (0, model_utils_1.applyPostDefaults)(model, parsed, {
137
- isUpdate: false,
138
- timestamps,
139
- });
140
- // Resolve keys again with defaults
141
- const fullKey = (0, model_utils_1.resolveKeys)(model, withDefaults);
142
- // Combine keys and data into full item, adding _type field
143
- return {
144
- ...withDefaults,
145
- ...fullKey,
146
- _type: modelName, // Add entity type identifier
147
- };
148
- });
149
- // Create the request items in the format expected by BatchWriteItem
150
- const requestItems = {
151
- [tableName]: processedItems.map((item) => ({
152
- PutRequest: {
153
- Item: item,
154
- },
155
- })),
156
- };
157
- return (0, builders_1.createBatchWriteBuilder)(requestItems, client, logger);
158
- },
159
- };
160
- };
161
- exports.createEntityAPI = createEntityAPI;