@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
package/src/core/types.ts CHANGED
@@ -28,20 +28,68 @@
28
28
  // -------------------- Type Definitions --------------------
29
29
 
30
30
  /**
31
- * Attribute definition for model attributes
32
- *
33
- * @property type - The JavaScript constructor for the attribute type
34
- * @property [required] - Whether the attribute is required
35
- * @property [generate] - Auto-generation strategy ('ulid', 'uuid')
36
- * @property [default] - Default value or generator function
31
+ * Scalar attribute definition (String, Number, Boolean, Date)
37
32
  */
38
- export type AttributeDefinition = {
33
+ export type ScalarAttributeDefinition = {
39
34
  type: StringConstructor | NumberConstructor | BooleanConstructor | DateConstructor;
40
35
  required?: boolean;
41
36
  generate?: 'ulid' | 'uuid';
42
37
  default?: any;
38
+ nulls?: boolean;
39
+ };
40
+
41
+ /**
42
+ * Object attribute definition with a nested schema
43
+ *
44
+ * @example
45
+ * address: {
46
+ * type: Object,
47
+ * schema: {
48
+ * street: { type: String },
49
+ * city: { type: String, required: true },
50
+ * }
51
+ * }
52
+ */
53
+ export type ObjectAttributeDefinition = {
54
+ type: ObjectConstructor;
55
+ schema: Record<string, AttributeDefinition>;
56
+ required?: boolean;
57
+ default?: any;
58
+ nulls?: boolean;
43
59
  };
44
60
 
61
+ /**
62
+ * Array attribute definition with a typed items schema
63
+ *
64
+ * @example
65
+ * history: {
66
+ * type: Array,
67
+ * default: [],
68
+ * items: {
69
+ * type: Object,
70
+ * schema: {
71
+ * date: { type: String },
72
+ * status: { type: String },
73
+ * }
74
+ * }
75
+ * }
76
+ */
77
+ export type ArrayAttributeDefinition = {
78
+ type: ArrayConstructor;
79
+ items: AttributeDefinition;
80
+ required?: boolean;
81
+ default?: any;
82
+ nulls?: boolean;
83
+ };
84
+
85
+ /**
86
+ * Attribute definition for model attributes — supports scalars, nested objects, and arrays
87
+ */
88
+ export type AttributeDefinition =
89
+ | ScalarAttributeDefinition
90
+ | ObjectAttributeDefinition
91
+ | ArrayAttributeDefinition;
92
+
45
93
  /**
46
94
  * Key definition for primary and secondary indexes
47
95
  *
@@ -95,6 +143,7 @@ export type ModelDefinition = {
95
143
  export type SchemaParams = {
96
144
  isoDates?: boolean;
97
145
  timestamps?: boolean;
146
+ cleanInternalKeys?: boolean;
98
147
  };
99
148
 
100
149
  /**
@@ -113,17 +162,34 @@ export type SchemaDefinition = {
113
162
  params?: SchemaParams;
114
163
  };
115
164
 
116
- type InferAttr<T> = T extends StringConstructor
117
- ? string
118
- : T extends NumberConstructor
119
- ? number
120
- : T extends BooleanConstructor
121
- ? boolean
122
- : T extends DateConstructor
123
- ? Date
124
- : unknown;
165
+ /**
166
+ * Recursively infers the TypeScript type from an AttributeDefinition.
167
+ * - Object → infers the nested schema shape
168
+ * - Array → infers an array of the items type
169
+ * - Scalars → maps to the corresponding primitive
170
+ */
171
+ type InferAttr<A> = A extends { type: ObjectConstructor; schema: infer S }
172
+ ? InferObjectSchema<S>
173
+ : A extends { type: ArrayConstructor; items: infer I }
174
+ ? Array<InferAttr<I>>
175
+ : A extends { type: StringConstructor }
176
+ ? string
177
+ : A extends { type: NumberConstructor }
178
+ ? number
179
+ : A extends { type: BooleanConstructor }
180
+ ? boolean
181
+ : A extends { type: DateConstructor }
182
+ ? Date
183
+ : unknown;
125
184
 
126
- type IsOptional<T> = undefined extends T ? true : false;
185
+ /**
186
+ * Infers the shape of a nested object schema, respecting required/optional fields.
187
+ */
188
+ type InferObjectSchema<S> = {
189
+ [K in keyof S as S[K] extends { required: true } ? K : never]: InferAttr<S[K]>;
190
+ } & {
191
+ [K in keyof S as S[K] extends { required: true } ? never : K]?: InferAttr<S[K]>;
192
+ };
127
193
 
128
194
  /**
129
195
  * Non-generated attributes for input
@@ -134,7 +200,7 @@ type NonGeneratedAttributes<M extends ModelDefinition> = {
134
200
  ? never
135
201
  : M['attributes'][K] extends { required: true }
136
202
  ? K
137
- : never]: InferAttr<M['attributes'][K]['type']>;
203
+ : never]: InferAttr<M['attributes'][K]>;
138
204
  } & {
139
205
  [K in keyof M['attributes'] as M['attributes'][K] extends { generate: string }
140
206
  ? never
@@ -142,7 +208,7 @@ type NonGeneratedAttributes<M extends ModelDefinition> = {
142
208
  ? K
143
209
  : M['attributes'][K] extends { required: true }
144
210
  ? never
145
- : K]?: InferAttr<M['attributes'][K]['type']>;
211
+ : K]?: InferAttr<M['attributes'][K]>;
146
212
  };
147
213
 
148
214
  /**
@@ -151,7 +217,7 @@ type NonGeneratedAttributes<M extends ModelDefinition> = {
151
217
  type GeneratedAttributes<M extends ModelDefinition> = {
152
218
  [K in keyof M['attributes'] as M['attributes'][K] extends { generate: string }
153
219
  ? K
154
- : never]: InferAttr<M['attributes'][K]['type']>;
220
+ : never]: InferAttr<M['attributes'][K]>;
155
221
  };
156
222
 
157
223
  /**
@@ -288,3 +354,14 @@ type KeyTemplateVars<M extends ModelDefinition> = PrimaryKeyVars<M>;
288
354
  export type InferKeyInput<M extends ModelDefinition> = {
289
355
  [K in KeyTemplateVars<M>]: string;
290
356
  };
357
+
358
+ /**
359
+ * Extracts the item type from an array attribute.
360
+ *
361
+ * @example
362
+ * type StoryFrame = ArrayItem<StoryEntity['frames']>;
363
+ * // → { url: string; duration?: number; mediaType?: string }
364
+ *
365
+ * type HistoryEntry = ArrayItem<TransactionEntity['history']>;
366
+ */
367
+ export type ArrayItem<T extends readonly unknown[] | undefined> = NonNullable<T>[number];
@@ -0,0 +1,212 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
3
+ import { InferInput, InferKeyInput, InferModel, ModelDefinition } from '@/core/types';
4
+ import { applyPostDefaults, resolveKeys } from '@/utils/model-utils';
5
+ import { modelToZod } from '@/utils/zod-utils';
6
+ import {
7
+ createGetBuilder,
8
+ createPutBuilder,
9
+ createQueryBuilder,
10
+ createUpdateBuilder,
11
+ createDeleteBuilder,
12
+ createScanBuilder,
13
+ createBatchGetBuilder,
14
+ createBatchWriteBuilder,
15
+ WriteRequest,
16
+ } from '@/builders';
17
+ import { EntityAPI, EntityAPIOptions } from './types';
18
+ import { withMiddleware } from './middleware/with-middleware';
19
+ import { createCleanKeysMiddleware } from './middleware/factories';
20
+ import { validateKeyFields } from './validation/key-validation';
21
+
22
+ /**
23
+ * Creates an entity API instance with validation, key resolution, and builder creation.
24
+ *
25
+ * @param tableName - The name of the DynamoDB table
26
+ * @param modelName - The name of the model/entity
27
+ * @param model - The model definition
28
+ * @param client - DynamoDB client instance
29
+ * @param options - Optional configuration (logger, timestamps, cleanInternalKeys)
30
+ * @returns EntityAPI with get, put, query, scan, update, delete, batchGet, and batchWrite methods
31
+ */
32
+ export const createEntityAPI = <Model extends ModelDefinition>(
33
+ tableName: string,
34
+ modelName: string,
35
+ model: Model,
36
+ client: DynamoDBClient,
37
+ options: EntityAPIOptions = {}
38
+ ): EntityAPI<InferModel<Model>, InferInput<Model>, InferKeyInput<Model>> => {
39
+ const { logger, timestamps = false, cleanInternalKeys = false } = options;
40
+
41
+ // Build a Zod schema from the model
42
+ const zodSchema = modelToZod(model);
43
+
44
+ return {
45
+ get(key) {
46
+ // Validate key fields
47
+ validateKeyFields(modelName, model, key as Record<string, unknown>, 'get()');
48
+
49
+ // Resolve any key defaults or computed keys
50
+ const fullKey = resolveKeys(model, key);
51
+
52
+ const builder = createGetBuilder<InferKeyInput<Model>, InferModel<Model>>(
53
+ tableName,
54
+ fullKey,
55
+ client,
56
+ undefined,
57
+ logger
58
+ );
59
+
60
+ return withMiddleware(builder, createCleanKeysMiddleware(cleanInternalKeys));
61
+ },
62
+
63
+ put(item) {
64
+ // Validate full input data
65
+ const parsed = zodSchema.parse(item);
66
+
67
+ // Apply post-processing defaults from the model (including timestamps for new items)
68
+ const withDefaults = applyPostDefaults(model, parsed, {
69
+ isUpdate: false,
70
+ timestamps,
71
+ });
72
+
73
+ // Resolve keys again with defaults
74
+ const fullKey = resolveKeys(model, withDefaults);
75
+
76
+ // Combine keys and data into full item, adding _type field
77
+ const fullItem = {
78
+ ...withDefaults,
79
+ ...fullKey,
80
+ _type: modelName, // Add entity type identifier
81
+ };
82
+
83
+ return createPutBuilder(tableName, fullItem, client, [], false, 'NONE', false, logger);
84
+ },
85
+
86
+ query() {
87
+ // Query builder doesn't have execute() until after .where() is called
88
+ // The cleanInternalKeys will be handled in the builder itself
89
+ return createQueryBuilder<InferModel<Model>>(tableName, client, model, logger);
90
+ },
91
+
92
+ scan() {
93
+ const builder = createScanBuilder<InferModel<Model>>(
94
+ tableName,
95
+ client,
96
+ [],
97
+ [],
98
+ undefined,
99
+ false,
100
+ undefined,
101
+ undefined,
102
+ undefined,
103
+ logger
104
+ );
105
+
106
+ return withMiddleware(builder, createCleanKeysMiddleware(cleanInternalKeys));
107
+ },
108
+
109
+ update(key) {
110
+ // Validate key fields
111
+ validateKeyFields(modelName, model, key as Record<string, unknown>, 'update()');
112
+
113
+ // Resolve any key defaults or computed keys
114
+ const fullKey = resolveKeys(model, key);
115
+
116
+ const builder = createUpdateBuilder<InferModel<Model>>(
117
+ tableName,
118
+ fullKey as Partial<InferModel<Model>>,
119
+ client,
120
+ [],
121
+ { set: [], remove: [], add: [], delete: [] },
122
+ 'NONE',
123
+ 0,
124
+ timestamps,
125
+ logger
126
+ );
127
+
128
+ return withMiddleware(builder, createCleanKeysMiddleware(cleanInternalKeys));
129
+ },
130
+
131
+ delete(key) {
132
+ // Validate key fields
133
+ validateKeyFields(modelName, model, key as Record<string, unknown>, 'delete()');
134
+
135
+ // Resolve any key defaults or computed keys
136
+ const fullKey = resolveKeys(model, key);
137
+
138
+ const builder = createDeleteBuilder<InferModel<Model>>(
139
+ tableName,
140
+ fullKey as Partial<InferModel<Model>>,
141
+ client,
142
+ [],
143
+ 'NONE',
144
+ logger
145
+ );
146
+
147
+ return withMiddleware(builder, createCleanKeysMiddleware(cleanInternalKeys));
148
+ },
149
+
150
+ batchGet(keys) {
151
+ // Process all keys and validate them
152
+ const resolvedKeys = keys.map((key) => {
153
+ // Validate key fields
154
+ validateKeyFields(modelName, model, key as Record<string, unknown>, 'batchGet()');
155
+
156
+ // Resolve any key defaults or computed keys
157
+ return resolveKeys(model, key);
158
+ });
159
+
160
+ // Create the request items in the format expected by BatchGetItem
161
+ const requestItems = {
162
+ [tableName]: {
163
+ Keys: resolvedKeys,
164
+ },
165
+ };
166
+
167
+ const builder = createBatchGetBuilder<InferModel<Model>>(
168
+ requestItems,
169
+ client,
170
+ undefined,
171
+ logger
172
+ );
173
+
174
+ return withMiddleware(builder, createCleanKeysMiddleware(cleanInternalKeys));
175
+ },
176
+
177
+ batchWrite(items) {
178
+ // Validate and process all items
179
+ const processedItems = items.map((item) => {
180
+ // Validate full input data
181
+ const parsed = zodSchema.parse(item);
182
+
183
+ // Apply post-processing defaults from the model (including timestamps for new items)
184
+ const withDefaults = applyPostDefaults(model, parsed, {
185
+ isUpdate: false,
186
+ timestamps,
187
+ });
188
+
189
+ // Resolve keys again with defaults
190
+ const fullKey = resolveKeys(model, withDefaults);
191
+
192
+ // Combine keys and data into full item, adding _type field
193
+ return {
194
+ ...withDefaults,
195
+ ...fullKey,
196
+ _type: modelName, // Add entity type identifier
197
+ };
198
+ });
199
+
200
+ // Create the request items in the format expected by BatchWriteItem
201
+ const requestItems: Record<string, WriteRequest[]> = {
202
+ [tableName]: processedItems.map((item) => ({
203
+ PutRequest: {
204
+ Item: item,
205
+ },
206
+ })),
207
+ };
208
+
209
+ return createBatchWriteBuilder(requestItems, client, logger);
210
+ },
211
+ };
212
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Entity API module
3
+ *
4
+ * Provides high-level entity management with validation, key resolution, and builder creation.
5
+ */
6
+
7
+ // Core types
8
+ export type { EntityAPI, EntityAPIOptions } from './types';
9
+
10
+ // Middleware types and utilities
11
+ export type { ExecutionMiddleware } from './middleware/types';
12
+ export { withMiddleware } from './middleware/with-middleware';
13
+ export { createCleanKeysMiddleware } from './middleware/factories';
14
+
15
+ // Validation utilities
16
+ export { validateKeyFields } from './validation/key-validation';
17
+
18
+ // Main entity API factory
19
+ export { createEntityAPI } from './create-entity-api';
@@ -0,0 +1,15 @@
1
+ import { stripInternalKeys } from '@/utils/model-utils';
2
+ import { ExecutionMiddleware } from './types';
3
+
4
+ /**
5
+ * Creates middleware configuration for cleaning internal keys
6
+ */
7
+ export function createCleanKeysMiddleware(shouldClean: boolean): ExecutionMiddleware {
8
+ if (!shouldClean) {
9
+ return {};
10
+ }
11
+
12
+ return {
13
+ after: (result) => stripInternalKeys(result),
14
+ };
15
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Middleware hooks for builder execution
3
+ */
4
+ export type ExecutionMiddleware<T = any> = {
5
+ before?: () => void | Promise<void>;
6
+ after?: (result: T) => T | Promise<T>;
7
+ };
@@ -0,0 +1,37 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { ExecutionMiddleware } from './types';
3
+
4
+ /**
5
+ * Applies middleware hooks to a builder's execute method
6
+ */
7
+ export function withMiddleware<B extends { execute: () => Promise<any> }>(
8
+ builder: B,
9
+ middleware: ExecutionMiddleware
10
+ ): B {
11
+ const { before, after } = middleware;
12
+
13
+ // If no middleware hooks provided, return builder as-is
14
+ if (!before && !after) {
15
+ return builder;
16
+ }
17
+
18
+ return {
19
+ ...builder,
20
+ execute: async () => {
21
+ // Before execution hook
22
+ if (before) {
23
+ await before();
24
+ }
25
+
26
+ // Execute original builder
27
+ const result = await builder.execute();
28
+
29
+ // After execution hook
30
+ if (after) {
31
+ return await after(result);
32
+ }
33
+
34
+ return result;
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,79 @@
1
+ import { DynamoDBLogger } from '@/utils/dynamodb-logger';
2
+ import {
3
+ GetBuilder,
4
+ PutBuilder,
5
+ QueryBuilder,
6
+ UpdateBuilder,
7
+ DeleteBuilder,
8
+ ScanBuilder,
9
+ BatchGetBuilder,
10
+ BatchWriteBuilder,
11
+ } from '@/builders';
12
+
13
+ /**
14
+ * Options for creating the Entity API
15
+ */
16
+ export type EntityAPIOptions = {
17
+ logger?: DynamoDBLogger;
18
+ timestamps?: boolean;
19
+ cleanInternalKeys?: boolean;
20
+ };
21
+
22
+ /**
23
+ * Entity API interface for a model
24
+ */
25
+ export type EntityAPI<Model, Input, KeyInput> = {
26
+ /**
27
+ * Retrieves an item by its key.
28
+ * @param key - Partial or full key object to identify the item
29
+ * @returns GetBuilder configured for the item
30
+ */
31
+ get: (key: KeyInput) => GetBuilder<KeyInput, Model>;
32
+
33
+ /**
34
+ * Puts an item into the table after validation and applying defaults.
35
+ * @param item - The input data to put
36
+ * @returns PutBuilder configured for the item
37
+ */
38
+ put: (item: Input) => PutBuilder<Model>;
39
+
40
+ /**
41
+ * Queries items using key conditions.
42
+ * @returns QueryBuilder for building and executing the query
43
+ */
44
+ query: () => QueryBuilder<Model>;
45
+
46
+ /**
47
+ * Scans the entire table or index without key conditions.
48
+ * @returns ScanBuilder for building and executing the scan
49
+ */
50
+ scan: () => ScanBuilder<Model>;
51
+
52
+ /**
53
+ * Updates an item by its key.
54
+ * @param key - Partial or full key object to identify the item
55
+ * @returns UpdateBuilder configured for the item
56
+ */
57
+ update: (key: KeyInput) => UpdateBuilder<Model>;
58
+
59
+ /**
60
+ * Deletes an item by its key.
61
+ * @param key - Partial or full key object to identify the item
62
+ * @returns DeleteBuilder configured for the item
63
+ */
64
+ delete: (key: KeyInput) => DeleteBuilder<Model>;
65
+
66
+ /**
67
+ * Retrieves multiple items by their keys in a single batch operation.
68
+ * @param keys - Array of key objects to retrieve
69
+ * @returns BatchGetBuilder configured for the items
70
+ */
71
+ batchGet: (keys: KeyInput[]) => BatchGetBuilder<Model>;
72
+
73
+ /**
74
+ * Writes multiple items in a single batch operation (puts or deletes).
75
+ * @param items - Array of items to put
76
+ * @returns BatchWriteBuilder configured for the items
77
+ */
78
+ batchWrite: (items: Input[]) => BatchWriteBuilder;
79
+ };
@@ -0,0 +1,34 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { ModelDefinition } from '@/core/types';
4
+ import { extractTemplateVars } from '@/utils/model-utils';
5
+
6
+ /**
7
+ * Validates that all required key fields are present in the key object
8
+ * @throws Error if any required key fields are missing
9
+ */
10
+ export function validateKeyFields(
11
+ modelName: string,
12
+ model: ModelDefinition,
13
+ key: Record<string, unknown>,
14
+ operation?: string
15
+ ): void {
16
+ // Extract required fields from key templates
17
+ const requiredFields = new Set<string>();
18
+ if (model.key) {
19
+ for (const keyDef of Object.values(model.key)) {
20
+ extractTemplateVars(keyDef.value).forEach((field) => requiredFields.add(field));
21
+ }
22
+ }
23
+
24
+ // Check if all required fields are present
25
+ const missingFields = Array.from(requiredFields).filter((field) => key[field] === undefined);
26
+
27
+ if (missingFields.length > 0) {
28
+ const operationSuffix = operation ? ` for ${operation}` : '';
29
+ throw new Error(
30
+ `[${modelName}] Missing required key field(s)${operationSuffix}: ${missingFields.join(', ')}. ` +
31
+ `Required fields: ${Array.from(requiredFields).join(', ')}`
32
+ );
33
+ }
34
+ }
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export {
14
14
  type InferModelFromSchema,
15
15
  type InferInputFromSchema,
16
16
  type TimestampFields,
17
+ type ArrayItem,
17
18
  } from './core/types';
18
19
  export {
19
20
  createDynamoDBLogger,
package/src/table.ts CHANGED
@@ -25,14 +25,20 @@ export type TableConfig<S extends SchemaDefinition> = {
25
25
  schema: S;
26
26
  };
27
27
 
28
+ /**
29
+ * Helper type to force TypeScript to expand/simplify a type.
30
+ * This helps with type inference in callbacks by making the type "concrete".
31
+ */
32
+ type Simplify<T> = { [K in keyof T]: T[K] } & {};
33
+
28
34
  /**
29
35
  * Internal helper type to infer all entity APIs from schema definition
30
36
  */
31
37
  type EntityMap<S extends SchemaDefinition> = {
32
38
  [K in keyof S['models']]: EntityAPI<
33
- InferModelFromSchema<S, K>,
34
- InferInputFromSchema<S, K>,
35
- InferKeyInput<S['models'][K]>
39
+ Simplify<InferModelFromSchema<S, K>>,
40
+ Simplify<InferInputFromSchema<S, K>>,
41
+ Simplify<InferKeyInput<S['models'][K]>>
36
42
  >;
37
43
  };
38
44
 
@@ -65,6 +71,7 @@ export class Table<S extends SchemaDefinition> {
65
71
  rawEntities[modelName] = createEntityAPI(tableName, modelName, model, client, {
66
72
  logger,
67
73
  timestamps: schema.params?.timestamps ?? false,
74
+ cleanInternalKeys: schema.params?.cleanInternalKeys ?? false,
68
75
  });
69
76
  }
70
77