@ftschopp/dynatable-core 1.0.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 (197) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +17 -0
  3. package/dist/builders/batch-get/create-batch-get-builder.d.ts +18 -0
  4. package/dist/builders/batch-get/create-batch-get-builder.d.ts.map +1 -0
  5. package/dist/builders/batch-get/create-batch-get-builder.js +71 -0
  6. package/dist/builders/batch-get/index.d.ts +3 -0
  7. package/dist/builders/batch-get/index.d.ts.map +1 -0
  8. package/dist/builders/batch-get/index.js +18 -0
  9. package/dist/builders/batch-get/types.d.ts +25 -0
  10. package/dist/builders/batch-get/types.d.ts.map +1 -0
  11. package/dist/builders/batch-get/types.js +2 -0
  12. package/dist/builders/batch-write/create-batch-write-builder.d.ts +12 -0
  13. package/dist/builders/batch-write/create-batch-write-builder.d.ts.map +1 -0
  14. package/dist/builders/batch-write/create-batch-write-builder.js +35 -0
  15. package/dist/builders/batch-write/index.d.ts +3 -0
  16. package/dist/builders/batch-write/index.d.ts.map +1 -0
  17. package/dist/builders/batch-write/index.js +18 -0
  18. package/dist/builders/batch-write/types.d.ts +30 -0
  19. package/dist/builders/batch-write/types.d.ts.map +1 -0
  20. package/dist/builders/batch-write/types.js +2 -0
  21. package/dist/builders/delete/create-delete-builder.d.ts +9 -0
  22. package/dist/builders/delete/create-delete-builder.d.ts.map +1 -0
  23. package/dist/builders/delete/create-delete-builder.js +72 -0
  24. package/dist/builders/delete/index.d.ts +3 -0
  25. package/dist/builders/delete/index.d.ts.map +1 -0
  26. package/dist/builders/delete/index.js +18 -0
  27. package/dist/builders/delete/types.d.ts +11 -0
  28. package/dist/builders/delete/types.d.ts.map +1 -0
  29. package/dist/builders/delete/types.js +2 -0
  30. package/dist/builders/get/create-get-builder.d.ts +12 -0
  31. package/dist/builders/get/create-get-builder.d.ts.map +1 -0
  32. package/dist/builders/get/create-get-builder.js +95 -0
  33. package/dist/builders/get/index.d.ts +3 -0
  34. package/dist/builders/get/index.d.ts.map +1 -0
  35. package/dist/builders/get/index.js +18 -0
  36. package/dist/builders/get/types.d.ts +28 -0
  37. package/dist/builders/get/types.d.ts.map +1 -0
  38. package/dist/builders/get/types.js +2 -0
  39. package/dist/builders/index.d.ts +12 -0
  40. package/dist/builders/index.d.ts.map +1 -0
  41. package/dist/builders/index.js +29 -0
  42. package/dist/builders/put/create-put-builder.d.ts +9 -0
  43. package/dist/builders/put/create-put-builder.d.ts.map +1 -0
  44. package/dist/builders/put/create-put-builder.js +104 -0
  45. package/dist/builders/put/index.d.ts +3 -0
  46. package/dist/builders/put/index.d.ts.map +1 -0
  47. package/dist/builders/put/index.js +18 -0
  48. package/dist/builders/put/types.d.ts +17 -0
  49. package/dist/builders/put/types.d.ts.map +1 -0
  50. package/dist/builders/put/types.js +2 -0
  51. package/dist/builders/query/create-query-builder.d.ts +9 -0
  52. package/dist/builders/query/create-query-builder.d.ts.map +1 -0
  53. package/dist/builders/query/create-query-builder.js +283 -0
  54. package/dist/builders/query/index.d.ts +3 -0
  55. package/dist/builders/query/index.d.ts.map +1 -0
  56. package/dist/builders/query/index.js +18 -0
  57. package/dist/builders/query/types.d.ts +79 -0
  58. package/dist/builders/query/types.d.ts.map +1 -0
  59. package/dist/builders/query/types.js +2 -0
  60. package/dist/builders/scan/create-scan-builder.d.ts +12 -0
  61. package/dist/builders/scan/create-scan-builder.d.ts.map +1 -0
  62. package/dist/builders/scan/create-scan-builder.js +103 -0
  63. package/dist/builders/scan/index.d.ts +3 -0
  64. package/dist/builders/scan/index.d.ts.map +1 -0
  65. package/dist/builders/scan/index.js +18 -0
  66. package/dist/builders/scan/types.d.ts +43 -0
  67. package/dist/builders/scan/types.d.ts.map +1 -0
  68. package/dist/builders/scan/types.js +2 -0
  69. package/dist/builders/shared/conditions.d.ts +18 -0
  70. package/dist/builders/shared/conditions.d.ts.map +1 -0
  71. package/dist/builders/shared/conditions.js +48 -0
  72. package/dist/builders/shared/index.d.ts +4 -0
  73. package/dist/builders/shared/index.d.ts.map +1 -0
  74. package/dist/builders/shared/index.js +19 -0
  75. package/dist/builders/shared/operators.d.ts +12 -0
  76. package/dist/builders/shared/operators.d.ts.map +1 -0
  77. package/dist/builders/shared/operators.js +197 -0
  78. package/dist/builders/shared/types.d.ts +77 -0
  79. package/dist/builders/shared/types.d.ts.map +1 -0
  80. package/dist/builders/shared/types.js +6 -0
  81. package/dist/builders/transact-get/create-transact-get-builder.d.ts +18 -0
  82. package/dist/builders/transact-get/create-transact-get-builder.d.ts.map +1 -0
  83. package/dist/builders/transact-get/create-transact-get-builder.js +60 -0
  84. package/dist/builders/transact-get/index.d.ts +3 -0
  85. package/dist/builders/transact-get/index.d.ts.map +1 -0
  86. package/dist/builders/transact-get/index.js +18 -0
  87. package/dist/builders/transact-get/types.d.ts +28 -0
  88. package/dist/builders/transact-get/types.d.ts.map +1 -0
  89. package/dist/builders/transact-get/types.js +2 -0
  90. package/dist/builders/transact-write/create-transact-write-builder.d.ts +18 -0
  91. package/dist/builders/transact-write/create-transact-write-builder.d.ts.map +1 -0
  92. package/dist/builders/transact-write/create-transact-write-builder.js +97 -0
  93. package/dist/builders/transact-write/index.d.ts +3 -0
  94. package/dist/builders/transact-write/index.d.ts.map +1 -0
  95. package/dist/builders/transact-write/index.js +18 -0
  96. package/dist/builders/transact-write/types.d.ts +34 -0
  97. package/dist/builders/transact-write/types.d.ts.map +1 -0
  98. package/dist/builders/transact-write/types.js +2 -0
  99. package/dist/builders/update/create-update-builder.d.ts +14 -0
  100. package/dist/builders/update/create-update-builder.d.ts.map +1 -0
  101. package/dist/builders/update/create-update-builder.js +180 -0
  102. package/dist/builders/update/index.d.ts +3 -0
  103. package/dist/builders/update/index.d.ts.map +1 -0
  104. package/dist/builders/update/index.js +18 -0
  105. package/dist/builders/update/types.d.ts +35 -0
  106. package/dist/builders/update/types.d.ts.map +1 -0
  107. package/dist/builders/update/types.js +2 -0
  108. package/dist/core/types.d.ts +224 -0
  109. package/dist/core/types.d.ts.map +1 -0
  110. package/dist/core/types.js +3 -0
  111. package/dist/entity.d.ts +73 -0
  112. package/dist/entity.d.ts.map +1 -0
  113. package/dist/entity.js +161 -0
  114. package/dist/index.d.ts +4 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +20 -0
  117. package/dist/table.d.ts +70 -0
  118. package/dist/table.d.ts.map +1 -0
  119. package/dist/table.js +69 -0
  120. package/dist/utils/dynamodb-logger.d.ts +118 -0
  121. package/dist/utils/dynamodb-logger.d.ts.map +1 -0
  122. package/dist/utils/dynamodb-logger.js +125 -0
  123. package/dist/utils/model-utils.d.ts +24 -0
  124. package/dist/utils/model-utils.d.ts.map +1 -0
  125. package/dist/utils/model-utils.js +88 -0
  126. package/dist/utils/zod-utils.d.ts +13 -0
  127. package/dist/utils/zod-utils.d.ts.map +1 -0
  128. package/dist/utils/zod-utils.js +35 -0
  129. package/eslint.config.mjs +4 -0
  130. package/jest.config.js +11 -0
  131. package/package.json +36 -0
  132. package/src/builders/README.md +272 -0
  133. package/src/builders/batch-get/README.md +98 -0
  134. package/src/builders/batch-get/create-batch-get-builder.test.ts +165 -0
  135. package/src/builders/batch-get/create-batch-get-builder.ts +106 -0
  136. package/src/builders/batch-get/index.ts +2 -0
  137. package/src/builders/batch-get/types.ts +29 -0
  138. package/src/builders/batch-write/README.md +204 -0
  139. package/src/builders/batch-write/create-batch-write-builder.test.ts +173 -0
  140. package/src/builders/batch-write/create-batch-write-builder.ts +49 -0
  141. package/src/builders/batch-write/index.ts +2 -0
  142. package/src/builders/batch-write/types.ts +33 -0
  143. package/src/builders/delete/create-delete-builder.test.ts +294 -0
  144. package/src/builders/delete/create-delete-builder.ts +100 -0
  145. package/src/builders/delete/index.ts +2 -0
  146. package/src/builders/delete/types.ts +11 -0
  147. package/src/builders/get/create-get-builder.test.ts +272 -0
  148. package/src/builders/get/create-get-builder.ts +140 -0
  149. package/src/builders/get/index.ts +2 -0
  150. package/src/builders/get/types.ts +30 -0
  151. package/src/builders/index.ts +14 -0
  152. package/src/builders/put/create-put-builder.test.ts +213 -0
  153. package/src/builders/put/create-put-builder.ts +151 -0
  154. package/src/builders/put/index.ts +2 -0
  155. package/src/builders/put/types.ts +18 -0
  156. package/src/builders/query/create-query-builder.test.ts +230 -0
  157. package/src/builders/query/create-query-builder.ts +353 -0
  158. package/src/builders/query/index.ts +2 -0
  159. package/src/builders/query/types.ts +95 -0
  160. package/src/builders/scan/create-scan-builder.test.ts +260 -0
  161. package/src/builders/scan/create-scan-builder.ts +217 -0
  162. package/src/builders/scan/index.ts +2 -0
  163. package/src/builders/scan/types.ts +49 -0
  164. package/src/builders/shared/conditions.ts +58 -0
  165. package/src/builders/shared/index.ts +3 -0
  166. package/src/builders/shared/operators.test.ts +270 -0
  167. package/src/builders/shared/operators.ts +200 -0
  168. package/src/builders/shared/types.ts +100 -0
  169. package/src/builders/transact-get/README.md +167 -0
  170. package/src/builders/transact-get/create-transact-get-builder.test.ts +239 -0
  171. package/src/builders/transact-get/create-transact-get-builder.ts +67 -0
  172. package/src/builders/transact-get/index.ts +2 -0
  173. package/src/builders/transact-get/types.ts +31 -0
  174. package/src/builders/transact-write/README.md +166 -0
  175. package/src/builders/transact-write/create-transact-write-builder.test.ts +288 -0
  176. package/src/builders/transact-write/create-transact-write-builder.ts +118 -0
  177. package/src/builders/transact-write/index.ts +2 -0
  178. package/src/builders/transact-write/types.ts +33 -0
  179. package/src/builders/update/create-update-builder.test.ts +333 -0
  180. package/src/builders/update/create-update-builder.ts +284 -0
  181. package/src/builders/update/index.ts +2 -0
  182. package/src/builders/update/types.ts +42 -0
  183. package/src/core/types.test.ts +506 -0
  184. package/src/core/types.ts +290 -0
  185. package/src/entity.ts +337 -0
  186. package/src/index.ts +22 -0
  187. package/src/table.ts +109 -0
  188. package/src/utils/dynamodb-logger.test.ts +246 -0
  189. package/src/utils/dynamodb-logger.ts +175 -0
  190. package/src/utils/model-utils.test.ts +232 -0
  191. package/src/utils/model-utils.ts +101 -0
  192. package/src/utils/zod-utils.test.ts +272 -0
  193. package/src/utils/zod-utils.ts +36 -0
  194. package/tests/integration/instagram-clone.integration.test.ts +966 -0
  195. package/tests/integration/pagination-timestamps.integration.test.ts +375 -0
  196. package/tests/integration/transactions.integration.test.ts +529 -0
  197. package/tsconfig.json +12 -0
@@ -0,0 +1,290 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ /**
4
+ * Type definitions for DynamoDB schema and model inference
5
+ *
6
+ * Provides comprehensive type utilities for:
7
+ * - Defining DynamoDB table schemas with primary and secondary indexes
8
+ * - Inferring strongly-typed input and output models from schema definitions
9
+ * - Automatic key generation and template variable extraction
10
+ * - Type-safe attribute handling with support for defaults and auto-generation
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const schema: SchemaDefinition = {
15
+ * format: 'dynatable:1.0.0',
16
+ * version: '1.0.0',
17
+ * indexes: { primary: { hash: "PK", sort: "SK" } },
18
+ * models: {
19
+ * User: {
20
+ * key: { PK: { type: String, value: "USER#${id}" }, SK: { type: String, value: "PROFILE" } },
21
+ * attributes: { id: { type: String, required: true }, name: { type: String } }
22
+ * }
23
+ * }
24
+ * };
25
+ * ```
26
+ */
27
+
28
+ // -------------------- Type Definitions --------------------
29
+
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
37
+ */
38
+ export type AttributeDefinition = {
39
+ type: StringConstructor | NumberConstructor | BooleanConstructor | DateConstructor;
40
+ required?: boolean;
41
+ generate?: 'ulid' | 'uuid';
42
+ default?: any;
43
+ };
44
+
45
+ /**
46
+ * Key definition for primary and secondary indexes
47
+ *
48
+ * @property type - Always String for DynamoDB keys
49
+ * @property value - Template string for key generation
50
+ */
51
+ export type KeyDefinition = {
52
+ type: StringConstructor;
53
+ value: string;
54
+ };
55
+
56
+ /**
57
+ * Primary key definition - requires both PK and SK (uppercase)
58
+ */
59
+ export type PrimaryKeyDefinition = {
60
+ PK: KeyDefinition;
61
+ SK: KeyDefinition;
62
+ };
63
+
64
+ /**
65
+ * Index definition with hash and optional sort key
66
+ */
67
+ export type IndexDefinition = {
68
+ hash: string;
69
+ sort?: string;
70
+ };
71
+
72
+ /**
73
+ * Indexes configuration - requires at least a primary index
74
+ */
75
+ export type IndexesDefinition = {
76
+ primary: IndexDefinition;
77
+ [indexName: string]: IndexDefinition;
78
+ };
79
+
80
+ /**
81
+ * Model definition containing keys, indexes, and attributes
82
+ * - key: REQUIRED, must have pk and sk
83
+ * - attributes: REQUIRED, model attributes
84
+ * - index: OPTIONAL, secondary index keys
85
+ */
86
+ export type ModelDefinition = {
87
+ key: PrimaryKeyDefinition;
88
+ index?: Record<string, KeyDefinition>;
89
+ attributes: Record<string, AttributeDefinition>;
90
+ };
91
+
92
+ /**
93
+ * Schema parameters configuration
94
+ */
95
+ export type SchemaParams = {
96
+ isoDates?: boolean;
97
+ timestamps?: boolean;
98
+ };
99
+
100
+ /**
101
+ * Complete schema definition for a DynamoDB table
102
+ * - format: Table schema format version (e.g., "dynatable:1.0.0")
103
+ * - version: Schema version (e.g., "1.0.0")
104
+ * - indexes: Index definitions (requires at least 'primary')
105
+ * - models: Model definitions (each requires 'key' and 'attributes')
106
+ * - params: Optional schema parameters
107
+ */
108
+ export type SchemaDefinition = {
109
+ format: string;
110
+ version: string;
111
+ indexes: IndexesDefinition;
112
+ models: Record<string, ModelDefinition>;
113
+ params?: SchemaParams;
114
+ };
115
+
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;
125
+
126
+ type IsOptional<T> = undefined extends T ? true : false;
127
+
128
+ /**
129
+ * Non-generated attributes for input
130
+ * Splits into required and optional based on the 'required' field
131
+ */
132
+ type NonGeneratedAttributes<M extends ModelDefinition> = {
133
+ [K in keyof M['attributes'] as M['attributes'][K] extends { generate: string }
134
+ ? never
135
+ : M['attributes'][K] extends { required: true }
136
+ ? K
137
+ : never]: InferAttr<M['attributes'][K]['type']>;
138
+ } & {
139
+ [K in keyof M['attributes'] as M['attributes'][K] extends { generate: string }
140
+ ? never
141
+ : M['attributes'][K] extends { required: false }
142
+ ? K
143
+ : M['attributes'][K] extends { required: true }
144
+ ? never
145
+ : K]?: InferAttr<M['attributes'][K]['type']>;
146
+ };
147
+
148
+ /**
149
+ * Generated-only attributes for internal use
150
+ */
151
+ type GeneratedAttributes<M extends ModelDefinition> = {
152
+ [K in keyof M['attributes'] as M['attributes'][K] extends { generate: string }
153
+ ? K
154
+ : never]: InferAttr<M['attributes'][K]['type']>;
155
+ };
156
+
157
+ /**
158
+ * Template string variable extraction
159
+ */
160
+ type ExtractTemplateVars<S extends string> = S extends `${string}\${${infer Var}}${infer Rest}`
161
+ ? Var | ExtractTemplateVars<Rest>
162
+ : never;
163
+
164
+ /**
165
+ * Extract template variables from primary keys (PK and SK)
166
+ */
167
+ type PrimaryKeyVars<M extends ModelDefinition> =
168
+ | ExtractTemplateVars<M['key']['PK']['value']>
169
+ | ExtractTemplateVars<M['key']['SK']['value']>;
170
+
171
+ /**
172
+ * Extract template variables from index keys (if they exist)
173
+ */
174
+ type IndexKeyVars<M extends ModelDefinition> =
175
+ M['index'] extends Record<string, KeyDefinition>
176
+ ? {
177
+ [K in keyof M['index']]: ExtractTemplateVars<M['index'][K]['value']>;
178
+ }[keyof M['index']]
179
+ : never;
180
+
181
+ /**
182
+ * Extract all template variables from primary and index keys
183
+ */
184
+ type KeyVars<M extends ModelDefinition> = PrimaryKeyVars<M> | IndexKeyVars<M>;
185
+
186
+ type IsGenerated<M extends ModelDefinition, K extends string> = K extends keyof M['attributes']
187
+ ? M['attributes'][K] extends { generate: string }
188
+ ? true
189
+ : false
190
+ : false;
191
+
192
+ type FilterNonGeneratedKeyVars<
193
+ M extends ModelDefinition,
194
+ K extends string = KeyVars<M>,
195
+ > = K extends string ? (IsGenerated<M, K> extends true ? never : K) : never;
196
+
197
+ /**
198
+ * Keys required in input
199
+ */
200
+ type RequiredKeys<M extends ModelDefinition> = {
201
+ [K in keyof NonGeneratedAttributes<M>]: M['attributes'][K] extends {
202
+ required: true;
203
+ }
204
+ ? K
205
+ : never;
206
+ }[keyof NonGeneratedAttributes<M>];
207
+
208
+ /**
209
+ * Input type used for put/update
210
+ *
211
+ * @deprecated Use InferInputFromSchema when possible to get timestamp inference
212
+ */
213
+ export type InferInput<M extends ModelDefinition> = {
214
+ [K in keyof NonGeneratedAttributes<M> as K extends RequiredKeys<M>
215
+ ? K
216
+ : never]: NonGeneratedAttributes<M>[K];
217
+ } & {
218
+ [K in keyof NonGeneratedAttributes<M> as K extends RequiredKeys<M>
219
+ ? never
220
+ : K]?: NonGeneratedAttributes<M>[K];
221
+ } & {
222
+ [K in FilterNonGeneratedKeyVars<M>]: string;
223
+ };
224
+
225
+ /**
226
+ * Infers the input type from a complete schema definition
227
+ * When timestamps are enabled, createdAt and updatedAt are NOT included (auto-generated)
228
+ */
229
+ export type InferInputFromSchema<
230
+ S extends SchemaDefinition,
231
+ ModelName extends keyof S['models'],
232
+ > = InferInput<S['models'][ModelName]>;
233
+
234
+ /**
235
+ * Full model type after applying defaults and keys
236
+ */
237
+ type ModelAttributes<M extends ModelDefinition> = NonGeneratedAttributes<M> &
238
+ GeneratedAttributes<M>;
239
+
240
+ /**
241
+ * Timestamp fields that are automatically added when timestamps are enabled
242
+ */
243
+ export type TimestampFields = {
244
+ createdAt: string;
245
+ updatedAt: string;
246
+ };
247
+
248
+ /**
249
+ * Infers the model type without exposing internal DynamoDB keys (PK, SK, GSI1PK, etc.)
250
+ * Only includes business attributes and generated fields
251
+ *
252
+ * @deprecated Use InferModelFromSchema when possible to get timestamp inference
253
+ */
254
+ export type InferModel<M extends ModelDefinition> = ModelAttributes<M>;
255
+
256
+ /**
257
+ * Infers the model type from a complete schema definition
258
+ * Automatically includes timestamp fields (createdAt, updatedAt) when params.timestamps is true
259
+ */
260
+ export type InferModelFromSchema<
261
+ S extends SchemaDefinition,
262
+ ModelName extends keyof S['models'],
263
+ > = S['params'] extends { timestamps: true }
264
+ ? ModelAttributes<S['models'][ModelName]> & TimestampFields
265
+ : ModelAttributes<S['models'][ModelName]>;
266
+
267
+ /**
268
+ * Internal type that includes DynamoDB keys - used internally by builders
269
+ * Includes pk, sk, and any index keys
270
+ * @internal
271
+ */
272
+ export type InferModelWithKeys<M extends ModelDefinition> = ModelAttributes<M> & {
273
+ [K in keyof M['key']]: string;
274
+ } & (M['index'] extends Record<string, KeyDefinition>
275
+ ? {
276
+ [K in keyof M['index']]: string;
277
+ }
278
+ : Record<string, never>);
279
+
280
+ /**
281
+ * Extract template variables from primary keys only
282
+ */
283
+ type KeyTemplateVars<M extends ModelDefinition> = PrimaryKeyVars<M>;
284
+
285
+ /**
286
+ * Input for get/delete operations (only key template vars)
287
+ */
288
+ export type InferKeyInput<M extends ModelDefinition> = {
289
+ [K in KeyTemplateVars<M>]: string;
290
+ };
package/src/entity.ts ADDED
@@ -0,0 +1,337 @@
1
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2
+ import { InferInput, InferKeyInput, InferModel, ModelDefinition } from './core/types';
3
+ import { applyPostDefaults, resolveKeys, extractTemplateVars } from './utils/model-utils';
4
+ import { modelToZod } from './utils/zod-utils';
5
+ import {
6
+ createGetBuilder,
7
+ createPutBuilder,
8
+ createQueryBuilder,
9
+ createUpdateBuilder,
10
+ createDeleteBuilder,
11
+ createScanBuilder,
12
+ createBatchGetBuilder,
13
+ createBatchWriteBuilder,
14
+ GetBuilder,
15
+ PutBuilder,
16
+ QueryBuilder,
17
+ UpdateBuilder,
18
+ DeleteBuilder,
19
+ ScanBuilder,
20
+ BatchGetBuilder,
21
+ BatchWriteBuilder,
22
+ WriteRequest,
23
+ } from './builders';
24
+ import { DynamoDBLogger } from './utils/dynamodb-logger';
25
+
26
+ /**
27
+ * Options for creating the Entity API
28
+ */
29
+ export type EntityAPIOptions = {
30
+ logger?: DynamoDBLogger;
31
+ timestamps?: boolean;
32
+ };
33
+
34
+ /**
35
+ * Entity API interface for a model
36
+ */
37
+ export type EntityAPI<Model, Input, KeyInput, ModelDef extends ModelDefinition = any> = {
38
+ /**
39
+ * Retrieves an item by its key.
40
+ * @param key - Partial or full key object to identify the item
41
+ * @returns GetBuilder configured for the item
42
+ */
43
+ get: (key: KeyInput) => GetBuilder<KeyInput, Model>;
44
+
45
+ /**
46
+ * Puts an item into the table after validation and applying defaults.
47
+ * @param item - The input data to put
48
+ * @returns PutBuilder configured for the item
49
+ */
50
+ put: (item: Input) => PutBuilder<Model>;
51
+
52
+ /**
53
+ * Queries items using key conditions.
54
+ * @returns QueryBuilder for building and executing the query
55
+ */
56
+ query: () => QueryBuilder<Model, ModelDef>;
57
+
58
+ /**
59
+ * Scans the entire table or index without key conditions.
60
+ * @returns ScanBuilder for building and executing the scan
61
+ */
62
+ scan: () => ScanBuilder<Model>;
63
+
64
+ /**
65
+ * Updates an item by its key.
66
+ * @param key - Partial or full key object to identify the item
67
+ * @returns UpdateBuilder configured for the item
68
+ */
69
+ update: (key: KeyInput) => UpdateBuilder<Model>;
70
+
71
+ /**
72
+ * Deletes an item by its key.
73
+ * @param key - Partial or full key object to identify the item
74
+ * @returns DeleteBuilder configured for the item
75
+ */
76
+ delete: (key: KeyInput) => DeleteBuilder<Model>;
77
+
78
+ /**
79
+ * Retrieves multiple items by their keys in a single batch operation.
80
+ * @param keys - Array of key objects to retrieve
81
+ * @returns BatchGetBuilder configured for the items
82
+ */
83
+ batchGet: (keys: KeyInput[]) => BatchGetBuilder<Model>;
84
+
85
+ /**
86
+ * Writes multiple items in a single batch operation (puts or deletes).
87
+ * @param items - Array of items to put
88
+ * @returns BatchWriteBuilder configured for the items
89
+ */
90
+ batchWrite: (items: Input[]) => BatchWriteBuilder;
91
+ };
92
+
93
+ /**
94
+ * Creates an entity API instance with validation, key resolution, and builder creation.
95
+ *
96
+ * @param modelName - The name of the model/entity
97
+ * @param model - The model definition
98
+ * @param client - DynamoDB client instance
99
+ * @param options - Optional configuration (logger, timestamps)
100
+ * @returns EntityAPI with get and put methods
101
+ */
102
+ export const createEntityAPI = <Model extends ModelDefinition>(
103
+ tableName: string,
104
+ modelName: string,
105
+ model: Model,
106
+ client: DynamoDBClient,
107
+ options: EntityAPIOptions = {}
108
+ ): EntityAPI<InferModel<Model>, InferInput<Model>, InferKeyInput<Model>, Model> => {
109
+ const { logger, timestamps = false } = options;
110
+
111
+ // Build a Zod schema from the model
112
+ const zodSchema = modelToZod(model);
113
+
114
+ return {
115
+ get(key) {
116
+ // Extract required fields from key templates
117
+ const requiredFields = new Set<string>();
118
+ if (model.key) {
119
+ for (const keyDef of Object.values(model.key)) {
120
+ extractTemplateVars(keyDef.value).forEach((field) => requiredFields.add(field));
121
+ }
122
+ }
123
+
124
+ // Check if all required fields are present
125
+ const keyRecord = key as Record<string, unknown>;
126
+ const missingFields = Array.from(requiredFields).filter(
127
+ (field) => keyRecord[field] === undefined
128
+ );
129
+
130
+ if (missingFields.length > 0) {
131
+ throw new Error(
132
+ `[${modelName}] Missing required key field(s) for get(): ${missingFields.join(', ')}. ` +
133
+ `Required fields: ${Array.from(requiredFields).join(', ')}`
134
+ );
135
+ }
136
+
137
+ // Resolve any key defaults or computed keys
138
+ const fullKey = resolveKeys(model, key);
139
+
140
+ return createGetBuilder<InferKeyInput<Model>, InferModel<Model>>(
141
+ tableName,
142
+ fullKey,
143
+ client,
144
+ undefined,
145
+ logger
146
+ );
147
+ },
148
+
149
+ put(item) {
150
+ // Validate full input data
151
+ const parsed = zodSchema.parse(item);
152
+
153
+ // Apply post-processing defaults from the model (including timestamps for new items)
154
+ const withDefaults = applyPostDefaults(model, parsed, {
155
+ isUpdate: false,
156
+ timestamps,
157
+ });
158
+
159
+ // Resolve keys again with defaults
160
+ const fullKey = resolveKeys(model, withDefaults);
161
+
162
+ // Combine keys and data into full item, adding _type field
163
+ const fullItem = {
164
+ ...withDefaults,
165
+ ...fullKey,
166
+ _type: modelName, // Add entity type identifier
167
+ };
168
+
169
+ return createPutBuilder(tableName, fullItem, client, [], false, 'NONE', false, logger);
170
+ },
171
+
172
+ query() {
173
+ return createQueryBuilder<InferModel<Model>, Model>(tableName, client, model, logger);
174
+ },
175
+
176
+ scan() {
177
+ return createScanBuilder<InferModel<Model>>(
178
+ tableName,
179
+ client,
180
+ [],
181
+ [],
182
+ undefined,
183
+ false,
184
+ undefined,
185
+ undefined,
186
+ undefined,
187
+ logger
188
+ );
189
+ },
190
+
191
+ update(key) {
192
+ // Extract required fields from key templates
193
+ const requiredFields = new Set<string>();
194
+ if (model.key) {
195
+ for (const keyDef of Object.values(model.key)) {
196
+ extractTemplateVars(keyDef.value).forEach((field) => requiredFields.add(field));
197
+ }
198
+ }
199
+
200
+ // Check if all required fields are present
201
+ const keyRecord = key as Record<string, unknown>;
202
+ const missingFields = Array.from(requiredFields).filter(
203
+ (field) => keyRecord[field] === undefined
204
+ );
205
+
206
+ if (missingFields.length > 0) {
207
+ throw new Error(
208
+ `[${modelName}] Missing required key field(s) for update(): ${missingFields.join(', ')}. ` +
209
+ `Required fields: ${Array.from(requiredFields).join(', ')}`
210
+ );
211
+ }
212
+
213
+ // Resolve any key defaults or computed keys
214
+ const fullKey = resolveKeys(model, key);
215
+
216
+ return createUpdateBuilder<InferModel<Model>>(
217
+ tableName,
218
+ fullKey as Partial<InferModel<Model>>,
219
+ client,
220
+ [],
221
+ { set: [], remove: [], add: [], delete: [] },
222
+ 'NONE',
223
+ 0,
224
+ timestamps,
225
+ logger
226
+ );
227
+ },
228
+
229
+ delete(key) {
230
+ // Extract required fields from key templates
231
+ const requiredFields = new Set<string>();
232
+ if (model.key) {
233
+ for (const keyDef of Object.values(model.key)) {
234
+ extractTemplateVars(keyDef.value).forEach((field) => requiredFields.add(field));
235
+ }
236
+ }
237
+
238
+ // Check if all required fields are present
239
+ const keyRecord = key as Record<string, unknown>;
240
+ const missingFields = Array.from(requiredFields).filter(
241
+ (field) => keyRecord[field] === undefined
242
+ );
243
+
244
+ if (missingFields.length > 0) {
245
+ throw new Error(
246
+ `[${modelName}] Missing required key field(s) for delete(): ${missingFields.join(', ')}. ` +
247
+ `Required fields: ${Array.from(requiredFields).join(', ')}`
248
+ );
249
+ }
250
+
251
+ // Resolve any key defaults or computed keys
252
+ const fullKey = resolveKeys(model, key);
253
+
254
+ return createDeleteBuilder<InferModel<Model>>(
255
+ tableName,
256
+ fullKey as Partial<InferModel<Model>>,
257
+ client,
258
+ [],
259
+ 'NONE',
260
+ logger
261
+ );
262
+ },
263
+
264
+ batchGet(keys) {
265
+ // Extract required fields from key templates
266
+ const requiredFields = new Set<string>();
267
+ if (model.key) {
268
+ for (const keyDef of Object.values(model.key)) {
269
+ extractTemplateVars(keyDef.value).forEach((field) => requiredFields.add(field));
270
+ }
271
+ }
272
+
273
+ // Process all keys and validate them
274
+ const resolvedKeys = keys.map((key) => {
275
+ // Check if all required fields are present
276
+ const keyRecord = key as Record<string, unknown>;
277
+ const missingFields = Array.from(requiredFields).filter(
278
+ (field) => keyRecord[field] === undefined
279
+ );
280
+
281
+ if (missingFields.length > 0) {
282
+ throw new Error(
283
+ `[${modelName}] Missing required key field(s) for batchGet(): ${missingFields.join(', ')}. ` +
284
+ `Required fields: ${Array.from(requiredFields).join(', ')}`
285
+ );
286
+ }
287
+
288
+ // Resolve any key defaults or computed keys
289
+ return resolveKeys(model, key);
290
+ });
291
+
292
+ // Create the request items in the format expected by BatchGetItem
293
+ const requestItems = {
294
+ [tableName]: {
295
+ Keys: resolvedKeys,
296
+ },
297
+ };
298
+
299
+ return createBatchGetBuilder<InferModel<Model>>(requestItems, client, undefined, logger);
300
+ },
301
+
302
+ batchWrite(items) {
303
+ // Validate and process all items
304
+ const processedItems = items.map((item) => {
305
+ // Validate full input data
306
+ const parsed = zodSchema.parse(item);
307
+
308
+ // Apply post-processing defaults from the model (including timestamps for new items)
309
+ const withDefaults = applyPostDefaults(model, parsed, {
310
+ isUpdate: false,
311
+ timestamps,
312
+ });
313
+
314
+ // Resolve keys again with defaults
315
+ const fullKey = resolveKeys(model, withDefaults);
316
+
317
+ // Combine keys and data into full item, adding _type field
318
+ return {
319
+ ...withDefaults,
320
+ ...fullKey,
321
+ _type: modelName, // Add entity type identifier
322
+ };
323
+ });
324
+
325
+ // Create the request items in the format expected by BatchWriteItem
326
+ const requestItems: Record<string, WriteRequest[]> = {
327
+ [tableName]: processedItems.map((item) => ({
328
+ PutRequest: {
329
+ Item: item,
330
+ },
331
+ })),
332
+ };
333
+
334
+ return createBatchWriteBuilder(requestItems, client, logger);
335
+ },
336
+ };
337
+ };
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ export * from './table';
2
+ export {
3
+ type SchemaDefinition,
4
+ type ModelDefinition,
5
+ type PrimaryKeyDefinition,
6
+ type KeyDefinition,
7
+ type AttributeDefinition,
8
+ type IndexDefinition,
9
+ type IndexesDefinition,
10
+ type SchemaParams,
11
+ type InferInput,
12
+ type InferModel,
13
+ type InferKeyInput,
14
+ type InferModelFromSchema,
15
+ type InferInputFromSchema,
16
+ type TimestampFields,
17
+ } from './core/types';
18
+ export {
19
+ createDynamoDBLogger,
20
+ type DynamoDBLogger,
21
+ type DynamoDBLoggerConfig,
22
+ } from './utils/dynamodb-logger';