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.
- package/API_REFERENCE.md +306 -122
- package/README.md +69 -16
- package/package.json +30 -6
- package/dist/src/betterddb.d.ts +0 -137
- package/dist/src/betterddb.d.ts.map +0 -1
- package/dist/src/betterddb.js +0 -165
- package/dist/src/betterddb.js.map +0 -1
- package/dist/src/builders/batch-get-builder.d.ts +0 -16
- package/dist/src/builders/batch-get-builder.d.ts.map +0 -1
- package/dist/src/builders/batch-get-builder.js +0 -54
- package/dist/src/builders/batch-get-builder.js.map +0 -1
- package/dist/src/builders/create-builder.d.ts +0 -12
- package/dist/src/builders/create-builder.d.ts.map +0 -1
- package/dist/src/builders/create-builder.js +0 -84
- package/dist/src/builders/create-builder.js.map +0 -1
- package/dist/src/builders/delete-builder.d.ts +0 -18
- package/dist/src/builders/delete-builder.d.ts.map +0 -1
- package/dist/src/builders/delete-builder.js +0 -75
- package/dist/src/builders/delete-builder.js.map +0 -1
- package/dist/src/builders/get-builder.d.ts +0 -18
- package/dist/src/builders/get-builder.d.ts.map +0 -1
- package/dist/src/builders/get-builder.js +0 -76
- package/dist/src/builders/get-builder.js.map +0 -1
- package/dist/src/builders/index.d.ts +0 -8
- package/dist/src/builders/index.d.ts.map +0 -1
- package/dist/src/builders/index.js +0 -8
- package/dist/src/builders/index.js.map +0 -1
- package/dist/src/builders/query-builder.d.ts +0 -29
- package/dist/src/builders/query-builder.d.ts.map +0 -1
- package/dist/src/builders/query-builder.js +0 -171
- package/dist/src/builders/query-builder.js.map +0 -1
- package/dist/src/builders/scan-builder.d.ts +0 -21
- package/dist/src/builders/scan-builder.d.ts.map +0 -1
- package/dist/src/builders/scan-builder.js +0 -76
- package/dist/src/builders/scan-builder.js.map +0 -1
- package/dist/src/builders/update-builder.d.ts +0 -37
- package/dist/src/builders/update-builder.d.ts.map +0 -1
- package/dist/src/builders/update-builder.js +0 -301
- package/dist/src/builders/update-builder.js.map +0 -1
- package/dist/src/index.d.ts +0 -5
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -5
- package/dist/src/index.js.map +0 -1
- package/dist/src/operator.d.ts +0 -3
- package/dist/src/operator.d.ts.map +0 -1
- package/dist/src/operator.js +0 -28
- package/dist/src/operator.js.map +0 -1
- package/dist/src/types/index.d.ts +0 -2
- package/dist/src/types/index.d.ts.map +0 -1
- package/dist/src/types/index.js +0 -2
- package/dist/src/types/index.js.map +0 -1
- package/dist/src/types/paginated-result.d.ts +0 -6
- package/dist/src/types/paginated-result.d.ts.map +0 -1
- package/dist/src/types/paginated-result.js +0 -2
- package/dist/src/types/paginated-result.js.map +0 -1
- 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
|
|
20
|
+
- [Transaction Support](#transaction-support)
|
|
21
21
|
- [Advanced Features](#advanced-features)
|
|
22
22
|
- [Automatic Timestamps](#automatic-timestamps)
|
|
23
|
-
- [
|
|
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**:
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
//
|
|
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) => `
|
|
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:
|
|
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
|
-
|
|
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
|
|
139
|
+
type User = z.infer<typeof UserSchema>; // TypeScript type inference
|
|
123
140
|
```
|
|
124
141
|
|
|
125
142
|
### Schema with Computed Fields
|
|
126
143
|
|
|
127
|
-
|
|
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
|
|
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
|
-
- **
|
|
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
|
|
166
|
-
const
|
|
196
|
+
// Create as part of a transaction
|
|
197
|
+
const createItem = userDdb
|
|
167
198
|
.create({
|
|
168
|
-
id: "
|
|
169
|
-
name: "
|
|
170
|
-
email: "
|
|
199
|
+
id: "456",
|
|
200
|
+
name: "Bob",
|
|
201
|
+
email: "bob@example.com",
|
|
171
202
|
})
|
|
172
|
-
.
|
|
173
|
-
|
|
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
|
|
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:
|
|
188
|
-
- **
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
229
|
-
- **add(attributes: Record<
|
|
230
|
-
- **delete(attributes: Record<
|
|
231
|
-
- **
|
|
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
|
-
//
|
|
305
|
+
// Update with multiple operations
|
|
245
306
|
const user = await userDdb
|
|
246
|
-
.update({ id: "123"
|
|
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({
|
|
250
|
-
.
|
|
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(
|
|
266
|
-
- **
|
|
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
|
|
295
|
-
- **
|
|
296
|
-
- **
|
|
297
|
-
- **
|
|
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
|
-
- **
|
|
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
|
-
.
|
|
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("
|
|
332
|
-
.
|
|
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("
|
|
339
|
-
.filter("
|
|
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
|
|
368
|
-
- **
|
|
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(
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
393
|
-
puts?: T[];
|
|
394
|
-
deletes?: Partial<T>[]
|
|
395
|
-
}): Promise<void>
|
|
513
|
+
batchGet(keys: Partial<T>[]): BatchGetBuilder<T>
|
|
396
514
|
```
|
|
397
515
|
|
|
398
|
-
|
|
516
|
+
#### Example
|
|
399
517
|
|
|
400
518
|
```typescript
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
{ id: "
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
533
|
+
> **Note**: BetterDDB currently only supports batch get operations. Batch write operations are not yet implemented.
|
|
411
534
|
|
|
412
|
-
|
|
535
|
+
## Transaction Support
|
|
413
536
|
|
|
414
|
-
|
|
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
|
-
###
|
|
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
|
|
571
|
+
const deleteItem = userDdb
|
|
572
|
+
.delete({ id: "789", email: "charlie@example.com" })
|
|
573
|
+
.toTransactDelete();
|
|
436
574
|
|
|
437
|
-
// Execute transaction
|
|
438
|
-
await
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
Enables optimistic locking with version numbers.
|
|
642
|
+
Your schema should include these optional fields:
|
|
462
643
|
|
|
463
644
|
```typescript
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
### Counter
|
|
655
|
+
### Counter Support
|
|
474
656
|
|
|
475
|
-
|
|
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
|