betterddb 0.8.2 → 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 (56) hide show
  1. package/API_REFERENCE.md +306 -122
  2. package/README.md +69 -16
  3. package/package.json +30 -6
  4. package/dist/src/betterddb.d.ts +0 -137
  5. package/dist/src/betterddb.d.ts.map +0 -1
  6. package/dist/src/betterddb.js +0 -165
  7. package/dist/src/betterddb.js.map +0 -1
  8. package/dist/src/builders/batch-get-builder.d.ts +0 -16
  9. package/dist/src/builders/batch-get-builder.d.ts.map +0 -1
  10. package/dist/src/builders/batch-get-builder.js +0 -54
  11. package/dist/src/builders/batch-get-builder.js.map +0 -1
  12. package/dist/src/builders/create-builder.d.ts +0 -12
  13. package/dist/src/builders/create-builder.d.ts.map +0 -1
  14. package/dist/src/builders/create-builder.js +0 -84
  15. package/dist/src/builders/create-builder.js.map +0 -1
  16. package/dist/src/builders/delete-builder.d.ts +0 -18
  17. package/dist/src/builders/delete-builder.d.ts.map +0 -1
  18. package/dist/src/builders/delete-builder.js +0 -75
  19. package/dist/src/builders/delete-builder.js.map +0 -1
  20. package/dist/src/builders/get-builder.d.ts +0 -18
  21. package/dist/src/builders/get-builder.d.ts.map +0 -1
  22. package/dist/src/builders/get-builder.js +0 -76
  23. package/dist/src/builders/get-builder.js.map +0 -1
  24. package/dist/src/builders/index.d.ts +0 -8
  25. package/dist/src/builders/index.d.ts.map +0 -1
  26. package/dist/src/builders/index.js +0 -8
  27. package/dist/src/builders/index.js.map +0 -1
  28. package/dist/src/builders/query-builder.d.ts +0 -29
  29. package/dist/src/builders/query-builder.d.ts.map +0 -1
  30. package/dist/src/builders/query-builder.js +0 -171
  31. package/dist/src/builders/query-builder.js.map +0 -1
  32. package/dist/src/builders/scan-builder.d.ts +0 -21
  33. package/dist/src/builders/scan-builder.d.ts.map +0 -1
  34. package/dist/src/builders/scan-builder.js +0 -76
  35. package/dist/src/builders/scan-builder.js.map +0 -1
  36. package/dist/src/builders/update-builder.d.ts +0 -37
  37. package/dist/src/builders/update-builder.d.ts.map +0 -1
  38. package/dist/src/builders/update-builder.js +0 -301
  39. package/dist/src/builders/update-builder.js.map +0 -1
  40. package/dist/src/index.d.ts +0 -5
  41. package/dist/src/index.d.ts.map +0 -1
  42. package/dist/src/index.js +0 -5
  43. package/dist/src/index.js.map +0 -1
  44. package/dist/src/operator.d.ts +0 -3
  45. package/dist/src/operator.d.ts.map +0 -1
  46. package/dist/src/operator.js +0 -28
  47. package/dist/src/operator.js.map +0 -1
  48. package/dist/src/types/index.d.ts +0 -2
  49. package/dist/src/types/index.d.ts.map +0 -1
  50. package/dist/src/types/index.js +0 -2
  51. package/dist/src/types/index.js.map +0 -1
  52. package/dist/src/types/paginated-result.d.ts +0 -6
  53. package/dist/src/types/paginated-result.d.ts.map +0 -1
  54. package/dist/src/types/paginated-result.js +0 -2
  55. package/dist/src/types/paginated-result.js.map +0 -1
  56. package/dist/tsconfig.tsbuildinfo +0 -1
package/API_REFERENCE.md CHANGED
@@ -17,10 +17,10 @@ This document provides a comprehensive reference for the BetterDDB library, expl
17
17
  - [Filtering](#filtering)
18
18
  - [Scan Operations](#scan-operations)
19
19
  - [Batch Operations](#batch-operations)
20
- - [Transaction Operations](#transaction-operations)
20
+ - [Transaction Support](#transaction-support)
21
21
  - [Advanced Features](#advanced-features)
22
22
  - [Automatic Timestamps](#automatic-timestamps)
23
- - [Versioning](#versioning)
23
+ - [Counter Support](#counter-support)
24
24
 
25
25
  ## Initialization
26
26
 
@@ -42,16 +42,23 @@ export interface BetterDDBOptions<T> {
42
42
  keys: KeysConfig<T>;
43
43
  client: DynamoDBDocumentClient;
44
44
  counter?: boolean;
45
+ /**
46
+ * If true, automatically inject timestamp fields:
47
+ * - On create, sets both `createdAt` and `updatedAt`
48
+ * - On update, sets `updatedAt`
49
+ *
50
+ * (T should include these fields if enabled.)
51
+ */
45
52
  timestamps?: boolean;
46
53
  }
47
54
  ```
48
55
 
49
- - **schema**: Zod schema for validation
56
+ - **schema**: Zod schema for validation and TypeScript type inference
50
57
  - **tableName**: Your DynamoDB table name
51
58
  - **entityType**: Optional type discriminator for multi-entity tables
52
59
  - **keys**: Configuration for primary key, sort key, and GSIs
53
60
  - **client**: AWS SDK v3 DynamoDB document client
54
- - **counter**: Whether to use a counter field for optimistic locking
61
+ - **counter**: Feature flag for counter support (future feature)
55
62
  - **timestamps**: Whether to automatically manage `createdAt` and `updatedAt` timestamps
56
63
 
57
64
  #### Example
@@ -61,19 +68,22 @@ import { BetterDDB } from "betterddb";
61
68
  import { z } from "zod";
62
69
  import { DynamoDBDocumentClient, DynamoDB } from "@aws-sdk/lib-dynamodb";
63
70
 
64
- // Define schema
65
- const UserSchema = z.object({
66
- id: z.string(),
67
- name: z.string(),
68
- email: z.string().email(),
69
- createdAt: z.string().optional(),
70
- updatedAt: z.string().optional(),
71
- });
71
+ // Define schema with optional timestamp fields
72
+ const UserSchema = z
73
+ .object({
74
+ id: z.string(),
75
+ name: z.string(),
76
+ email: z.string().email(),
77
+ createdAt: z.string().optional(),
78
+ updatedAt: z.string().optional(),
79
+ })
80
+ .passthrough(); // Allow computed fields (pk, sk, gsi keys)
72
81
 
73
- // Init client
82
+ // Initialize DynamoDB client
74
83
  const client = DynamoDBDocumentClient.from(
75
84
  new DynamoDB({
76
85
  region: "us-east-1",
86
+ // For local development, add: endpoint: "http://localhost:4566"
77
87
  }),
78
88
  );
79
89
 
@@ -81,6 +91,7 @@ const client = DynamoDBDocumentClient.from(
81
91
  const userDdb = new BetterDDB({
82
92
  schema: UserSchema,
83
93
  tableName: "Users",
94
+ entityType: "USER",
84
95
  keys: {
85
96
  primary: {
86
97
  name: "pk",
@@ -88,14 +99,18 @@ const userDdb = new BetterDDB({
88
99
  },
89
100
  sort: {
90
101
  name: "sk",
91
- definition: { build: (raw) => `PROFILE` },
102
+ definition: { build: (raw) => `EMAIL#${raw.email}` },
92
103
  },
93
104
  gsis: {
94
105
  EmailIndex: {
95
106
  name: "EmailIndex",
96
107
  primary: {
97
108
  name: "gsi1pk",
98
- definition: "email",
109
+ definition: { build: (raw) => `USER#${raw.email}` },
110
+ },
111
+ sort: {
112
+ name: "gsi1sk",
113
+ definition: { build: (raw) => `USER#${raw.email}` },
99
114
  },
100
115
  },
101
116
  },
@@ -107,34 +122,49 @@ const userDdb = new BetterDDB({
107
122
 
108
123
  ## Schema Definition
109
124
 
110
- BetterDDB uses [Zod](https://github.com/colinhacks/zod) for schema validation and type inference. Your schema defines both runtime validation rules and TypeScript types.
125
+ BetterDDB uses [Zod](https://github.com/colinhacks/zod) for schema validation and TypeScript type inference. Your schema defines both runtime validation rules and TypeScript types.
111
126
 
112
- ### Basic Schema
127
+ ### Basic Schema with Type Inference
113
128
 
114
129
  ```typescript
115
- const Schema = z.object({
130
+ import { z } from "zod";
131
+
132
+ const UserSchema = z.object({
116
133
  id: z.string(),
117
134
  name: z.string(),
118
135
  email: z.string().email(),
119
136
  age: z.number().optional(),
120
137
  });
121
138
 
122
- type Entity = z.infer<typeof Schema>; // TypeScript type inference
139
+ type User = z.infer<typeof UserSchema>; // TypeScript type inference
123
140
  ```
124
141
 
125
142
  ### Schema with Computed Fields
126
143
 
127
- To allow for computed fields (like keys), use `.passthrough()`:
144
+ DynamoDB tables often have computed fields like partition keys, sort keys, and GSI keys. Use `.passthrough()` to allow these fields while still validating your core attributes:
128
145
 
129
146
  ```typescript
130
- const Schema = z
147
+ const UserSchema = z
131
148
  .object({
132
149
  id: z.string(),
133
150
  name: z.string(),
151
+ email: z.string().email(),
152
+ // Optional fields for automatic timestamps
153
+ createdAt: z.string().optional(),
154
+ updatedAt: z.string().optional(),
134
155
  })
135
- .passthrough();
156
+ .passthrough(); // Allow computed fields like pk, sk, gsi1pk, etc.
157
+
158
+ type User = z.infer<typeof UserSchema>;
136
159
  ```
137
160
 
161
+ ### Schema Best Practices
162
+
163
+ - Use `.passthrough()` to allow DynamoDB keys and indexes
164
+ - Include optional `createdAt` and `updatedAt` fields if using `timestamps: true`
165
+ - Define strict validation rules for your core business data
166
+ - Use Zod's rich validation API (`.email()`, `.min()`, `.max()`, etc.)
167
+
138
168
  ## CRUD Operations
139
169
 
140
170
  ### Create
@@ -147,13 +177,14 @@ create(item: T): CreateBuilder<T>
147
177
 
148
178
  #### CreateBuilder Methods
149
179
 
150
- - **execute()**: Performs the create operation and returns the created item
151
- - **withCondition(condition: string, expressionAttrs?: Record<string, any>)**: Adds a condition expression
180
+ - **execute()**: Performs the create operation, validates the item, computes keys and indexes, and returns the created item
181
+ - **transactWrite(ops: TransactWriteItem | TransactWriteItem[])**: Adds additional transaction items to include when executing this create as part of a transaction
182
+ - **toTransactPut()**: Converts the create operation to a `TransactWriteItem` for use in DynamoDB transactions
152
183
 
153
184
  #### Example
154
185
 
155
186
  ```typescript
156
- // Simple create
187
+ // Simple create with automatic validation
157
188
  const user = await userDdb
158
189
  .create({
159
190
  id: "123",
@@ -162,20 +193,26 @@ const user = await userDdb
162
193
  })
163
194
  .execute();
164
195
 
165
- // Create with condition
166
- const user = await userDdb
196
+ // Create as part of a transaction
197
+ const createItem = userDdb
167
198
  .create({
168
- id: "123",
169
- name: "Alice",
170
- email: "alice@example.com",
199
+ id: "456",
200
+ name: "Bob",
201
+ email: "bob@example.com",
171
202
  })
172
- .withCondition("attribute_not_exists(#pk)", { "#pk": "pk" })
173
- .execute();
203
+ .toTransactPut();
204
+
205
+ // Use with transactWrite (AWS SDK TransactWriteCommand)
206
+ await client.send(
207
+ new TransactWriteCommand({
208
+ TransactItems: [createItem /* other transaction items */],
209
+ }),
210
+ );
174
211
  ```
175
212
 
176
213
  ### Read
177
214
 
178
- Retrieves items from the DynamoDB table.
215
+ Retrieves an item from the DynamoDB table by its primary key.
179
216
 
180
217
  ```typescript
181
218
  get(key: Partial<T>): GetBuilder<T>
@@ -183,71 +220,106 @@ get(key: Partial<T>): GetBuilder<T>
183
220
 
184
221
  #### GetBuilder Methods
185
222
 
186
- - **execute()**: Performs the get operation and returns the item
187
- - **withProjection(attributes: string[])**: Specifies which attributes to return
188
- - **withConsistentRead(consistent: boolean)**: Uses consistent read
223
+ - **execute()**: Performs the get operation and returns the item or `null` if not found
224
+ - **withProjection(attributes: (keyof T)[])**: Specifies which attributes to return using type-safe property names
225
+ - **transactGet(ops: TransactGetItem | TransactGetItem[])**: Adds additional transaction items for use in DynamoDB transactions
226
+ - **toTransactGet()**: Converts the get operation to a `TransactGetItem` for use in DynamoDB transactions
189
227
 
190
228
  #### Example
191
229
 
192
230
  ```typescript
193
- // Simple get
231
+ // Simple get - returns null if item doesn't exist
194
232
  const user = await userDdb.get({ id: "123" }).execute();
233
+ if (user) {
234
+ console.log(user.name);
235
+ }
195
236
 
196
- // Get with projection
197
- const userNameOnly = await userDdb
237
+ // Get with projection (type-safe)
238
+ const partialUser = await userDdb
198
239
  .get({ id: "123" })
199
- .withProjection(["name", "email"])
240
+ .withProjection(["name", "email"]) // Only these attributes are returned
200
241
  .execute();
242
+
243
+ // Get as part of a transaction
244
+ const getItem = userDdb.get({ id: "123" }).toTransactGet();
201
245
  ```
202
246
 
203
247
  ### Batch Get
204
248
 
205
- Retrieves multiple items in a single operation.
249
+ Retrieves multiple items in a single operation. Duplicate keys are automatically deduplicated.
206
250
 
207
251
  ```typescript
208
252
  batchGet(keys: Partial<T>[]): BatchGetBuilder<T>
209
253
  ```
210
254
 
255
+ #### BatchGetBuilder Methods
256
+
257
+ - **execute()**: Performs the batch get operation and returns an array of items
258
+
211
259
  #### Example
212
260
 
213
261
  ```typescript
214
- const users = await userDdb.batchGet([{ id: "123" }, { id: "456" }]).execute();
262
+ // Retrieve multiple users
263
+ const users = await userDdb
264
+ .batchGet([
265
+ { id: "123" },
266
+ { id: "456" },
267
+ { id: "123" }, // Duplicate - will be deduplicated automatically
268
+ ])
269
+ .execute();
270
+
271
+ // Returns an array of User objects
272
+ users.forEach((user) => {
273
+ console.log(user.name);
274
+ });
215
275
  ```
216
276
 
217
277
  ### Update
218
278
 
219
- Updates an existing item with automatic validation.
279
+ Updates an existing item with automatic validation and type safety.
220
280
 
221
281
  ```typescript
222
- update(key: Partial<T>, expectedVersion?: number): UpdateBuilder<T>
282
+ update(key: Partial<T>): UpdateBuilder<T>
223
283
  ```
224
284
 
225
285
  #### UpdateBuilder Methods
226
286
 
227
- - **set(attributes: Partial<T>)**: Sets attributes to specific values
228
- - **remove(attributes: string[])**: Removes attributes
229
- - **add(attributes: Record<string, number>)**: Adds numeric values to attributes
230
- - **delete(attributes: Record<string, any[]>)**: Removes elements from sets
231
- - **withCondition(condition: string, expressionAttrs?: Record<string, any>)**: Adds a condition expression
287
+ - **set(attributes: Partial<T>)**: Sets attributes to specific values (empty strings and undefined values for optional fields are automatically removed)
288
+ - **remove(attributes: (keyof T)[])**: Removes attributes from the item
289
+ - **add(attributes: Partial<Record<keyof T, number | Set<NativeAttributeValue>>>)**: Adds numeric values to attributes or elements to sets
290
+ - **delete(attributes: Partial<Record<keyof T, Set<NativeAttributeValue>>>)**: Removes elements from sets
291
+ - **setCondition(expression: string, attributeValues: Record<string, NativeAttributeValue>, attributeNames: Record<string, string>)**: Adds a condition expression with attribute names and values
292
+ - **transactWrite(ops: TransactWriteItem | TransactWriteItem[])**: Adds additional transaction items
293
+ - **toTransactUpdate()**: Converts the update to a `TransactWriteItem` for use in DynamoDB transactions
232
294
  - **execute()**: Performs the update operation and returns the updated item
233
- - **toTransactUpdate()**: Converts the update to a transaction operation
234
295
 
235
296
  #### Example
236
297
 
237
298
  ```typescript
238
299
  // Simple update
239
300
  const user = await userDdb
240
- .update({ id: "123" })
301
+ .update({ id: "123", email: "alice@example.com" })
241
302
  .set({ name: "Alice Smith" })
242
303
  .execute();
243
304
 
244
- // Complex update
305
+ // Update with multiple operations
245
306
  const user = await userDdb
246
- .update({ id: "123" }, 1) // Version check
247
- .set({ name: "Alice Smith" })
307
+ .update({ id: "123", email: "alice@example.com" })
308
+ .set({ name: "Alice Smith", age: 30 })
248
309
  .remove(["oldField"])
249
- .add({ age: 1 })
250
- .withCondition("attribute_exists(#id)", { "#id": "id" })
310
+ .add({ points: 10 }) // Increment numeric attribute
311
+ .delete({ tags: new Set(["old"]) }) // Remove elements from a set
312
+ .execute();
313
+
314
+ // Update with condition
315
+ const user = await userDdb
316
+ .update({ id: "123", email: "alice@example.com" })
317
+ .set({ name: "Alice Smith" })
318
+ .setCondition(
319
+ "attribute_exists(#id) AND #status = :active",
320
+ { ":active": "active" },
321
+ { "#id": "id", "#status": "status" },
322
+ )
251
323
  .execute();
252
324
  ```
253
325
 
@@ -262,28 +334,33 @@ delete(key: Partial<T>): DeleteBuilder<T>
262
334
  #### DeleteBuilder Methods
263
335
 
264
336
  - **execute()**: Performs the delete operation
265
- - **withCondition(condition: string, expressionAttrs?: Record<string, any>)**: Adds a condition expression
266
- - **toTransactDelete()**: Converts the delete to a transaction operation
337
+ - **withCondition(expression: string, attributeValues: Record<string, NativeAttributeValue>)**: Adds a condition expression with attribute values
338
+ - **transactWrite(ops: TransactWriteItem | TransactWriteItem[])**: Adds additional transaction items
339
+ - **toTransactDelete()**: Converts the delete to a `TransactWriteItem` for use in DynamoDB transactions
267
340
 
268
341
  #### Example
269
342
 
270
343
  ```typescript
271
344
  // Simple delete
272
- await userDdb.delete({ id: "123" }).execute();
345
+ await userDdb.delete({ id: "123", email: "alice@example.com" }).execute();
273
346
 
274
347
  // Delete with condition
275
348
  await userDdb
276
- .delete({ id: "123" })
349
+ .delete({ id: "123", email: "alice@example.com" })
277
350
  .withCondition("#status = :status", {
278
- "#status": "status",
279
351
  ":status": "inactive",
280
352
  })
281
353
  .execute();
354
+
355
+ // Delete as part of a transaction
356
+ const deleteItem = userDdb
357
+ .delete({ id: "123", email: "alice@example.com" })
358
+ .toTransactDelete();
282
359
  ```
283
360
 
284
361
  ## Query Operations
285
362
 
286
- Performs a DynamoDB query operation with a fluent interface.
363
+ Performs a DynamoDB query operation with a fluent, type-safe interface.
287
364
 
288
365
  ```typescript
289
366
  query(keyCondition: Partial<T>): QueryBuilder<T>
@@ -291,60 +368,84 @@ query(keyCondition: Partial<T>): QueryBuilder<T>
291
368
 
292
369
  ### QueryBuilder Methods
293
370
 
294
- - **execute()**: Performs the query operation and returns the results
295
- - **where(operator: string, value: any)**: Adds a condition on the sort key
296
- - **filter(attribute: keyof T, operator: string, value: any)**: Adds a filter condition
297
- - **usingIndex(indexName: string)**: Specifies a GSI to query
371
+ - **execute()**: Performs the query operation and returns a `PaginatedResult<T>` with `items` and `lastEvaluatedKey`
372
+ - **usingIndex(indexName: string)**: Specifies a Global Secondary Index (GSI) to query
373
+ - **sortAscending()**: Changes the sort order to ascending (default)
374
+ - **sortDescending()**: Changes the sort order to descending
375
+ - **where(operator: Operator, values: Partial<T> | [Partial<T>, Partial<T>])**: Adds a condition on the sort key using the `Operator` enum
376
+ - **filter(attribute: keyof T, operator: Operator, values: unknown)**: Adds a filter condition using the `Operator` enum
298
377
  - **limitResults(limit: number)**: Limits the number of results
299
- - **scanDescending()**: Changes the scan direction to descending
300
- - **startFrom(lastEvaluatedKey: Record<string, any>)**: Enables pagination
378
+ - **startFrom(lastKey: Record<string, NativeAttributeValue>)**: Enables pagination using the `lastEvaluatedKey` from a previous result
301
379
 
302
380
  ### Basic Queries
303
381
 
304
382
  ```typescript
383
+ import { Operator } from "betterddb";
384
+
305
385
  // Query by primary key
306
386
  const results = await userDdb.query({ id: "123" }).execute();
307
387
 
308
- // Query with sort key condition
388
+ // Query with sort key condition using Operator enum
389
+ const results = await userDdb
390
+ .query({ id: "123" })
391
+ .where(Operator.BEGINS_WITH, { email: "alice" })
392
+ .execute();
393
+
394
+ // Query with multiple filters
309
395
  const results = await userDdb
310
396
  .query({ id: "123" })
311
- .where("begins_with", { email: "a" })
397
+ .filter("name", Operator.BEGINS_WITH, "Alice")
398
+ .filter("age", Operator.GT, 21)
399
+ .limitResults(10)
312
400
  .execute();
313
401
  ```
314
402
 
315
403
  ### Using Indexes
316
404
 
317
405
  ```typescript
318
- // Query using GSI
406
+ // Query using a Global Secondary Index (GSI)
407
+ const results = await userDdb
408
+ .query({ email: "alice@example.com" })
409
+ .usingIndex("EmailIndex") // Must be defined in keys.gsis
410
+ .limitResults(5)
411
+ .execute();
412
+
413
+ // Query using GSI with sort key condition
319
414
  const results = await userDdb
320
415
  .query({ email: "alice@example.com" })
321
416
  .usingIndex("EmailIndex")
417
+ .where(Operator.BEGINS_WITH, { email: "alice" })
322
418
  .execute();
323
419
  ```
324
420
 
325
421
  ### Filtering
326
422
 
423
+ Supported operators: `EQ`, `NE`, `LT`, `LTE`, `GT`, `GTE`, `BEGINS_WITH`, `BETWEEN`, `CONTAINS`.
424
+
327
425
  ```typescript
328
- // Query with filter
426
+ // Query with various filter operators
329
427
  const results = await userDdb
330
428
  .query({ id: "123" })
331
- .filter("age", ">", 21)
332
- .limitResults(10)
429
+ .filter("status", Operator.EQ, "active")
430
+ .filter("age", Operator.BETWEEN, [18, 65])
431
+ .filter("tags", Operator.CONTAINS, "premium")
333
432
  .execute();
334
433
 
335
- // Multiple filters
434
+ // Multiple filters are ANDed together
336
435
  const results = await userDdb
337
436
  .query({ id: "123" })
338
- .filter("age", ">", 21)
339
- .filter("name", "begins_with", "A")
437
+ .filter("name", Operator.BEGINS_WITH, "A")
438
+ .filter("email", Operator.CONTAINS, "example.com")
340
439
  .execute();
341
440
  ```
342
441
 
343
442
  ### Pagination
344
443
 
345
444
  ```typescript
445
+ // First page
346
446
  const firstPage = await userDdb.query({ id: "123" }).limitResults(10).execute();
347
447
 
448
+ // Second page using lastEvaluatedKey
348
449
  if (firstPage.lastEvaluatedKey) {
349
450
  const secondPage = await userDdb
350
451
  .query({ id: "123" })
@@ -356,7 +457,7 @@ if (firstPage.lastEvaluatedKey) {
356
457
 
357
458
  ## Scan Operations
358
459
 
359
- Performs a DynamoDB scan operation.
460
+ Performs a DynamoDB scan operation with filtering and pagination.
360
461
 
361
462
  ```typescript
362
463
  scan(): ScanBuilder<T>
@@ -364,60 +465,95 @@ scan(): ScanBuilder<T>
364
465
 
365
466
  ### ScanBuilder Methods
366
467
 
367
- - **execute()**: Performs the scan operation and returns the results
368
- - **filter(attribute: keyof T, operator: string, value: any)**: Adds a filter condition
468
+ - **execute()**: Performs the scan operation and returns a `PaginatedResult<T>` with `items` and `lastEvaluatedKey`
469
+ - **where(attribute: keyof T, operator: Operator, values: unknown)**: Adds a filter condition using the `Operator` enum
369
470
  - **limitResults(limit: number)**: Limits the number of results
370
- - **startFrom(lastEvaluatedKey: Record<string, any>)**: Enables pagination
371
- - **usingIndex(indexName: string)**: Specifies a GSI to scan
471
+ - **startFrom(lastKey: Record<string, NativeAttributeValue>)**: Enables pagination using the `lastEvaluatedKey` from a previous result
372
472
 
373
473
  ### Example
374
474
 
375
475
  ```typescript
476
+ import { Operator } from "betterddb";
477
+
376
478
  // Simple scan
377
479
  const results = await userDdb.scan().execute();
378
480
 
379
- // Scan with filter
481
+ // Scan with filter using Operator enum
380
482
  const results = await userDdb
381
483
  .scan()
382
- .filter("status", "==", "active")
484
+ .where("status", Operator.EQ, "active")
383
485
  .limitResults(100)
384
486
  .execute();
487
+
488
+ // Scan with multiple filters
489
+ const results = await userDdb
490
+ .scan()
491
+ .where("age", Operator.GTE, 18)
492
+ .where("name", Operator.BEGINS_WITH, "A")
493
+ .execute();
494
+
495
+ // Paginated scan
496
+ const firstPage = await userDdb.scan().limitResults(50).execute();
497
+ if (firstPage.lastEvaluatedKey) {
498
+ const secondPage = await userDdb
499
+ .scan()
500
+ .limitResults(50)
501
+ .startFrom(firstPage.lastEvaluatedKey)
502
+ .execute();
503
+ }
385
504
  ```
386
505
 
387
506
  ## Batch Operations
388
507
 
389
- Performs multiple write operations in a single request.
508
+ ### Batch Get
509
+
510
+ Retrieves multiple items by their primary keys in a single operation. Duplicate keys are automatically deduplicated.
390
511
 
391
512
  ```typescript
392
- batchWrite(operations: {
393
- puts?: T[];
394
- deletes?: Partial<T>[]
395
- }): Promise<void>
513
+ batchGet(keys: Partial<T>[]): BatchGetBuilder<T>
396
514
  ```
397
515
 
398
- ### Example
516
+ #### Example
399
517
 
400
518
  ```typescript
401
- await userDdb.batchWrite({
402
- puts: [
403
- { id: "123", name: "Alice", email: "alice@example.com" },
404
- { id: "456", name: "Bob", email: "bob@example.com" },
405
- ],
406
- deletes: [{ id: "789" }],
519
+ // Retrieve multiple users
520
+ const users = await userDdb
521
+ .batchGet([
522
+ { id: "123", email: "alice@example.com" },
523
+ { id: "456", email: "bob@example.com" },
524
+ ])
525
+ .execute();
526
+
527
+ // Batch get returns an array of items
528
+ users.forEach((user) => {
529
+ console.log(user.name);
407
530
  });
408
531
  ```
409
532
 
410
- ## Transaction Operations
533
+ > **Note**: BetterDDB currently only supports batch get operations. Batch write operations are not yet implemented.
411
534
 
412
- Performs multiple operations atomically.
535
+ ## Transaction Support
413
536
 
414
- ```typescript
415
- transactWrite(transactItems: any[]): Promise<void>
416
- ```
537
+ BetterDDB provides builder methods to create transaction items that can be used with AWS SDK's `TransactWriteCommand` and `TransactGetCommand`.
417
538
 
418
- ### Example
539
+ ### Transaction Item Builders
540
+
541
+ Each builder class provides methods to convert operations to transaction items:
542
+
543
+ - **CreateBuilder**: `toTransactPut()` – Creates a `Put` transaction item
544
+ - **UpdateBuilder**: `toTransactUpdate()` – Creates an `Update` transaction item
545
+ - **DeleteBuilder**: `toTransactDelete()` – Creates a `Delete` transaction item
546
+ - **GetBuilder**: `toTransactGet()` – Creates a `Get` transaction item
547
+
548
+ ### Adding Additional Transaction Items
549
+
550
+ Each builder also has a `transactWrite` method (or `transactGet` for GetBuilder) to include additional transaction items when executing the operation as part of a transaction.
551
+
552
+ ### Example: Transaction Write
419
553
 
420
554
  ```typescript
555
+ import { TransactWriteCommand } from "@aws-sdk/lib-dynamodb";
556
+
421
557
  // Build transaction items
422
558
  const createItem = userDdb
423
559
  .create({
@@ -428,24 +564,71 @@ const createItem = userDdb
428
564
  .toTransactPut();
429
565
 
430
566
  const updateItem = userDdb
431
- .update({ id: "456" })
567
+ .update({ id: "456", email: "bob@example.com" })
432
568
  .set({ name: "Bob Smith" })
433
569
  .toTransactUpdate();
434
570
 
435
- const deleteItem = userDdb.delete({ id: "789" }).toTransactDelete();
571
+ const deleteItem = userDdb
572
+ .delete({ id: "789", email: "charlie@example.com" })
573
+ .toTransactDelete();
436
574
 
437
- // Execute transaction
438
- await userDdb.transactWrite([createItem, updateItem, deleteItem]);
575
+ // Execute transaction using AWS SDK
576
+ await client.send(
577
+ new TransactWriteCommand({
578
+ TransactItems: [createItem, updateItem, deleteItem],
579
+ }),
580
+ );
581
+ ```
582
+
583
+ ### Example: Transaction Get
584
+
585
+ ```typescript
586
+ import { TransactGetCommand } from "@aws-sdk/lib-dynamodb";
587
+
588
+ const getItem1 = userDdb
589
+ .get({ id: "123", email: "alice@example.com" })
590
+ .toTransactGet();
591
+
592
+ const getItem2 = userDdb
593
+ .get({ id: "456", email: "bob@example.com" })
594
+ .toTransactGet();
595
+
596
+ await client.send(
597
+ new TransactGetCommand({
598
+ TransactItems: [getItem1, getItem2],
599
+ }),
600
+ );
601
+ ```
602
+
603
+ ### Example: Builder with Additional Transaction Items
604
+
605
+ ```typescript
606
+ // Create an item while also updating another item in the same transaction
607
+ const createBuilder = userDdb
608
+ .create({
609
+ id: "123",
610
+ name: "Alice",
611
+ email: "alice@example.com",
612
+ })
613
+ .transactWrite([
614
+ userDdb
615
+ .update({ id: "456", email: "bob@example.com" })
616
+ .set({ referrer: "alice" })
617
+ .toTransactUpdate(),
618
+ ]);
619
+
620
+ // This will execute both operations as a single transaction
621
+ await createBuilder.execute();
439
622
  ```
440
623
 
441
624
  ## Advanced Features
442
625
 
443
626
  ### Automatic Timestamps
444
627
 
445
- When enabled, BetterDDB automatically manages:
628
+ When enabled, BetterDDB automatically manages timestamp fields:
446
629
 
447
- - **createdAt**: Set on item creation
448
- - **updatedAt**: Set on item creation and updated on every update
630
+ - **createdAt**: Set to current ISO timestamp on item creation
631
+ - **updatedAt**: Set to current ISO timestamp on item creation and updated on every update
449
632
 
450
633
  Enable with:
451
634
 
@@ -456,31 +639,32 @@ const ddb = new BetterDDB({
456
639
  });
457
640
  ```
458
641
 
459
- ### Versioning
460
-
461
- Enables optimistic locking with version numbers.
642
+ Your schema should include these optional fields:
462
643
 
463
644
  ```typescript
464
- // Update with version checking
465
- const user = await userDdb
466
- .update({ id: "123" }, 1) // Expected version
467
- .set({ name: "Alice Smith" })
468
- .execute();
645
+ const UserSchema = z
646
+ .object({
647
+ id: z.string(),
648
+ name: z.string(),
649
+ createdAt: z.string().optional(),
650
+ updatedAt: z.string().optional(),
651
+ })
652
+ .passthrough();
469
653
  ```
470
654
 
471
- This will only update if the current version is 1, then increment it to 2.
472
-
473
- ### Counter
655
+ ### Counter Support
474
656
 
475
- Enables automatic counter incrementing for items.
657
+ The `counter` option is a feature flag for future counter functionality. When enabled, it may provide automatic counter incrementing for items.
476
658
 
477
659
  ```typescript
478
660
  const ddb = new BetterDDB({
479
661
  // ...other options
480
- counter: true,
662
+ counter: true, // Feature flag for counter support
481
663
  });
482
664
  ```
483
665
 
666
+ > **Note**: Counter functionality is not yet implemented. This option is reserved for future development.
667
+
484
668
  ## Key Management
485
669
 
486
670
  ### Key Definitions