dynoquery 0.2.0 → 0.2.1
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/README.md +220 -153
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
# DynoQuery
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/dynoquery)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
3
6
|
A lightweight wrapper for Amazon DynamoDB using the AWS SDK v3, specifically designed for **Single-Table Design** patterns.
|
|
4
7
|
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Quick Start](#quick-start)
|
|
13
|
+
- [1. Working with Partitions (Models)](#1-working-with-partitions-models)
|
|
14
|
+
- [2. Global Secondary Indexes (findBy)](#2-global-secondary-indexes-findby)
|
|
15
|
+
- [3. Creating Items with GSI Support](#3-creating-items-with-gsi-support)
|
|
16
|
+
- [4. Batch Operations](#4-batch-operations)
|
|
17
|
+
- [5. Transaction Operations](#5-transaction-operations)
|
|
18
|
+
- [6. Time To Live (TTL)](#6-time-to-live-ttl)
|
|
19
|
+
- [Configuration](#configuration)
|
|
20
|
+
- [DynoQuery Options](#dynoquery-options)
|
|
21
|
+
- [findBy Index Options](#findby-index-options)
|
|
22
|
+
- [Advanced Usage](#advanced-usage)
|
|
23
|
+
- [Pagination](#pagination)
|
|
24
|
+
- [Expression Builder](#expression-builder)
|
|
25
|
+
- [API Reference](#api-reference)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
5
28
|
## Installation
|
|
6
29
|
|
|
7
30
|
```bash
|
|
@@ -14,11 +37,12 @@ npm install dynoquery
|
|
|
14
37
|
- Optimized for **Single-Table Design** (Partitions and GSIs)
|
|
15
38
|
- Automatic result mapping to partition models
|
|
16
39
|
- Built-in caching for partition instances
|
|
40
|
+
- Batch and transaction support with automatic chunking
|
|
17
41
|
- TypeScript support
|
|
18
42
|
|
|
19
|
-
## Quick Start
|
|
43
|
+
## Quick Start
|
|
20
44
|
|
|
21
|
-
|
|
45
|
+
Initialize the client with your table name and define your models.
|
|
22
46
|
|
|
23
47
|
```typescript
|
|
24
48
|
import { DynoQuery } from 'dynoquery';
|
|
@@ -31,77 +55,70 @@ const db = new DynoQuery({
|
|
|
31
55
|
|
|
32
56
|
### 1. Working with Partitions (Models)
|
|
33
57
|
|
|
34
|
-
Define
|
|
58
|
+
Define models to handle different entity types within your single table. Each model maps to a PK prefix (e.g. `USER#john@example.com`).
|
|
35
59
|
|
|
36
60
|
```typescript
|
|
37
61
|
const db = new DynoQuery({
|
|
38
62
|
region: 'us-east-1',
|
|
39
63
|
tableName: 'MyTable',
|
|
40
64
|
models: {
|
|
41
|
-
User: { pkPrefix: 'USER#' },
|
|
42
|
-
Product: { pkPrefix: 'PROD#' }
|
|
65
|
+
User: { pkPrefix: 'USER#' }, // PK: USER#<id>
|
|
66
|
+
Product: { pkPrefix: 'PROD#' } // PK: PROD#<id>
|
|
43
67
|
}
|
|
44
68
|
});
|
|
45
69
|
|
|
46
70
|
async function userExample() {
|
|
47
|
-
const john = db.User('john@example.com');
|
|
48
|
-
|
|
71
|
+
const john = db.User('john@example.com'); // PK: USER#john@example.com
|
|
72
|
+
|
|
49
73
|
// Create an item (SK: PROFILE)
|
|
50
74
|
await john.create('PROFILE', { name: 'John Doe', email: 'john@example.com' });
|
|
51
|
-
|
|
75
|
+
|
|
52
76
|
// Get an item (uses cache if already loaded)
|
|
53
77
|
const profile = await john.get('PROFILE');
|
|
54
78
|
console.log(profile.name); // 'John Doe'
|
|
55
79
|
|
|
56
|
-
// Update an item (partial update)
|
|
80
|
+
// Update an item (partial update — merges with existing data)
|
|
57
81
|
await john.update('PROFILE', { theme: 'dark' });
|
|
58
82
|
|
|
59
83
|
// Delete an item
|
|
60
84
|
await john.delete('PROFILE');
|
|
61
|
-
|
|
85
|
+
|
|
62
86
|
// Fetch all items in this partition
|
|
63
87
|
const allData = await john.getAll();
|
|
64
88
|
}
|
|
65
89
|
```
|
|
66
90
|
|
|
67
|
-
####
|
|
91
|
+
#### Item Objects (Single-Row Operations)
|
|
68
92
|
|
|
69
|
-
For
|
|
93
|
+
For fine-grained control over individual rows, use `draft()` to get an `Item` object and call `save()` on it directly. Pass a second argument to the model function as a shorthand.
|
|
70
94
|
|
|
71
95
|
```typescript
|
|
72
|
-
const john = db.User('john@example.com');
|
|
96
|
+
const john = db.User('john@example.com'); // PK: USER#john@example.com
|
|
73
97
|
|
|
74
|
-
// 1. Create a new row
|
|
98
|
+
// 1. Create a new row via draft
|
|
75
99
|
const johnMeta = john.draft('METADATA');
|
|
76
100
|
johnMeta.name = 'John Doe';
|
|
77
101
|
johnMeta.email = 'johndoe@johnmail.com';
|
|
78
102
|
await johnMeta.save();
|
|
79
103
|
|
|
80
|
-
// 2.
|
|
104
|
+
// 2. Draft with initial properties
|
|
81
105
|
const johnStat = john.draft('STAT', { views: 100 });
|
|
82
106
|
await johnStat.save();
|
|
83
107
|
|
|
84
|
-
// 3. Get
|
|
108
|
+
// 3. Get an existing row, modify, and save
|
|
85
109
|
const johnBio = await john.get('BIO');
|
|
86
110
|
johnBio.birthYear = 1986;
|
|
87
111
|
await johnBio.save();
|
|
88
112
|
|
|
89
|
-
// 4.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// 5. Set properties during initialization
|
|
93
|
-
const johnPref = john.draft('PREF', { theme: 'dark' });
|
|
113
|
+
// 4. Shorthand: pass SK as second argument to get an Item directly
|
|
114
|
+
const johnPref = db.User('john@example.com', 'PREF'); // PK: USER#john@example.com, SK: PREF
|
|
115
|
+
johnPref.theme = 'dark';
|
|
94
116
|
await johnPref.save();
|
|
95
|
-
|
|
96
|
-
// 6. Shorthand access (returns an Item object directly)
|
|
97
|
-
const johnMeta = db.User('john@example.com', 'METADATA');
|
|
98
|
-
johnMeta.name = 'John Doe';
|
|
99
|
-
await johnMeta.save();
|
|
100
117
|
```
|
|
101
118
|
|
|
102
119
|
### 2. Global Secondary Indexes (findBy)
|
|
103
120
|
|
|
104
|
-
Use `findBy` to query
|
|
121
|
+
Use `findBy` to query GSIs. By default, the GSI PK attribute name is `{indexName}PK` and the SK attribute name is `{indexName}SK`.
|
|
105
122
|
|
|
106
123
|
```typescript
|
|
107
124
|
const db = new DynoQuery({
|
|
@@ -111,23 +128,22 @@ const db = new DynoQuery({
|
|
|
111
128
|
Product: { pkPrefix: 'PROD#' }
|
|
112
129
|
},
|
|
113
130
|
findBy: {
|
|
114
|
-
Category: { indexName: 'GSI1', pkPrefix: 'CAT#' }
|
|
131
|
+
Category: { indexName: 'GSI1', pkPrefix: 'CAT#' }
|
|
132
|
+
// pkName defaults to 'GSI1PK', skName defaults to 'GSI1SK'
|
|
115
133
|
}
|
|
116
134
|
});
|
|
117
135
|
|
|
118
136
|
async function indexExample() {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
137
|
+
const electronics = db.findByCategory('ELECTRONICS'); // GSI1PK: CAT#ELECTRONICS
|
|
138
|
+
|
|
122
139
|
// Get all items in this category
|
|
123
140
|
const items = await electronics.getAll();
|
|
124
|
-
|
|
125
|
-
//
|
|
141
|
+
|
|
142
|
+
// Results are automatically mapped to registered models
|
|
126
143
|
items.forEach(async item => {
|
|
127
144
|
if (item.__model === 'Product') {
|
|
128
|
-
const productPartition = item.getPartition();
|
|
129
|
-
|
|
130
|
-
// Now you can also edit and save directly if it matches a item
|
|
145
|
+
const productPartition = item.getPartition();
|
|
146
|
+
|
|
131
147
|
item.price = 45;
|
|
132
148
|
await item.save();
|
|
133
149
|
}
|
|
@@ -135,55 +151,58 @@ async function indexExample() {
|
|
|
135
151
|
}
|
|
136
152
|
```
|
|
137
153
|
|
|
154
|
+
> **Note:** When passing a `skValue` to `getAll()`, it filters using `begins_with` on the sort key. For example, `getAll({ skValue: 'RANK#' })` returns all items whose GSI SK starts with `RANK#`.
|
|
155
|
+
|
|
138
156
|
### 3. Creating Items with GSI Support
|
|
139
157
|
|
|
140
|
-
|
|
158
|
+
Pass index query objects to `create()` or `update()` to automatically populate GSI attributes.
|
|
141
159
|
|
|
142
160
|
#### Using Partition.create() and Partition.update()
|
|
161
|
+
|
|
143
162
|
```typescript
|
|
144
|
-
const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
|
|
163
|
+
const electronics = db.findByCategory('ELECTRONICS', 'RANK#1'); // GSI1PK: CAT#ELECTRONICS, GSI1SK: RANK#1
|
|
145
164
|
|
|
146
|
-
|
|
147
|
-
await db.Product('p123').create('INFO', {
|
|
165
|
+
await db.Product('p123').create('INFO', { // PK: PROD#p123
|
|
148
166
|
name: 'Gaming Mouse',
|
|
149
167
|
price: 50
|
|
150
168
|
}, [electronics]);
|
|
151
169
|
|
|
152
170
|
// Partial update with GSI support
|
|
153
|
-
await db.Product('p123').update('INFO', { price: 45 }, [electronics]);
|
|
171
|
+
await db.Product('p123').update('INFO', { price: 45 }, [electronics]); // PK: PROD#p123
|
|
154
172
|
```
|
|
155
173
|
|
|
156
|
-
#### Using setIndex() for
|
|
157
|
-
|
|
174
|
+
#### Using setIndex() for Persistence
|
|
175
|
+
|
|
176
|
+
Attach indices to an `Item` so they are included automatically on every `save()`.
|
|
158
177
|
|
|
159
178
|
```typescript
|
|
160
|
-
const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
|
|
161
|
-
const mouse = db.Product('p123', 'INFO');
|
|
179
|
+
const electronics = db.findByCategory('ELECTRONICS', 'RANK#1'); // GSI1PK: CAT#ELECTRONICS, GSI1SK: RANK#1
|
|
180
|
+
const mouse = db.Product('p123', 'INFO'); // PK: PROD#p123, SK: INFO
|
|
162
181
|
|
|
163
182
|
mouse.setIndex(electronics);
|
|
164
183
|
mouse.price = 50;
|
|
165
184
|
|
|
166
|
-
//
|
|
185
|
+
// GSI attributes are included automatically
|
|
167
186
|
await mouse.save();
|
|
168
187
|
```
|
|
169
188
|
|
|
170
189
|
### 4. Batch Operations
|
|
171
190
|
|
|
172
|
-
|
|
191
|
+
`batchWrite` and `batchRead` handle multiple items across partitions. Requests are automatically chunked to respect DynamoDB limits (25 per write, 100 per read).
|
|
173
192
|
|
|
174
193
|
```typescript
|
|
175
|
-
// 1. Batch Write (
|
|
176
|
-
const user1 = db.User('john@example.com', 'METADATA');
|
|
194
|
+
// 1. Batch Write (create/replace multiple items)
|
|
195
|
+
const user1 = db.User('john@example.com', 'METADATA'); // PK: USER#john@example.com, SK: METADATA
|
|
177
196
|
user1.name = 'John Doe';
|
|
178
197
|
|
|
179
|
-
const user2 = db.User('jane@example.com', 'METADATA');
|
|
198
|
+
const user2 = db.User('jane@example.com', 'METADATA'); // PK: USER#jane@example.com, SK: METADATA
|
|
180
199
|
user2.name = 'Jane Doe';
|
|
181
200
|
|
|
182
201
|
await db.batchWrite([user1, user2]);
|
|
183
202
|
|
|
184
|
-
// 2. Batch Read (
|
|
185
|
-
const userDraft = db.User('john@example.com', 'METADATA');
|
|
186
|
-
const categoryQuery = db.findByCategory('ELECTRONICS', 'p123');
|
|
203
|
+
// 2. Batch Read (fetch multiple items or index queries)
|
|
204
|
+
const userDraft = db.User('john@example.com', 'METADATA'); // PK: USER#john@example.com, SK: METADATA
|
|
205
|
+
const categoryQuery = db.findByCategory('ELECTRONICS', 'p123'); // GSI1PK: CAT#ELECTRONICS, GSI1SK: p123
|
|
187
206
|
|
|
188
207
|
const results = await db.batchRead([userDraft, categoryQuery]);
|
|
189
208
|
|
|
@@ -191,35 +210,35 @@ results.forEach(item => {
|
|
|
191
210
|
console.log(item.__model); // Automatically mapped to models
|
|
192
211
|
if (item.__model === 'User') {
|
|
193
212
|
item.status = 'active';
|
|
194
|
-
item.save();
|
|
213
|
+
item.save();
|
|
195
214
|
}
|
|
196
215
|
});
|
|
197
216
|
|
|
198
217
|
// 3. Batch Delete
|
|
199
|
-
const userToDelete = db.User('john@example.com').draftDelete('METADATA');
|
|
218
|
+
const userToDelete = db.User('john@example.com').draftDelete('METADATA'); // PK: USER#john@example.com
|
|
200
219
|
await db.batchWrite([userToDelete]);
|
|
201
220
|
```
|
|
202
221
|
|
|
203
222
|
### 5. Transaction Operations
|
|
204
223
|
|
|
205
|
-
|
|
224
|
+
`transactWrite` and `transactRead` perform atomic operations. Requests are automatically chunked to respect DynamoDB's 100-item limit per transaction.
|
|
206
225
|
|
|
207
226
|
```typescript
|
|
208
|
-
// 1. Transaction Write (
|
|
209
|
-
const user1 = db.User('john@example.com', 'METADATA');
|
|
227
|
+
// 1. Transaction Write (all operations succeed or all fail)
|
|
228
|
+
const user1 = db.User('john@example.com', 'METADATA'); // PK: USER#john@example.com, SK: METADATA
|
|
210
229
|
user1.name = 'John Doe';
|
|
211
230
|
|
|
212
|
-
const userToDelete = db.User('olduser@example.com').draftDelete('METADATA');
|
|
231
|
+
const userToDelete = db.User('olduser@example.com').draftDelete('METADATA'); // PK: USER#olduser@example.com
|
|
213
232
|
|
|
214
|
-
//
|
|
215
|
-
const criticalItem = db.User('admin@example.com', 'METADATA');
|
|
233
|
+
// Conditional write: only succeeds if status is 'ACTIVE'
|
|
234
|
+
const criticalItem = db.User('admin@example.com', 'METADATA'); // PK: USER#admin@example.com, SK: METADATA
|
|
216
235
|
criticalItem.lastLogin = new Date().toISOString();
|
|
217
236
|
criticalItem.setCondition(attr('status').equals('ACTIVE'));
|
|
218
237
|
|
|
219
238
|
await db.transactWrite([user1, userToDelete, criticalItem]);
|
|
220
239
|
|
|
221
|
-
// 2. Transaction Read (
|
|
222
|
-
const userDraft = db.User('john@example.com', 'METADATA');
|
|
240
|
+
// 2. Transaction Read (read multiple items atomically)
|
|
241
|
+
const userDraft = db.User('john@example.com', 'METADATA'); // PK: USER#john@example.com, SK: METADATA
|
|
223
242
|
const items = await db.transactRead([userDraft]);
|
|
224
243
|
|
|
225
244
|
if (items[0]) {
|
|
@@ -229,50 +248,52 @@ if (items[0]) {
|
|
|
229
248
|
|
|
230
249
|
### 6. Time To Live (TTL)
|
|
231
250
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
> **Note:** DynoQuery does not enable TTL in DynamoDB. It only sets the values using the `ttl()` function. You must manually enable TTL for your table in the AWS Console or via CLI/CloudFormation first.
|
|
251
|
+
Configure `ttlAttributeName` to enable TTL support. DynoQuery sets the TTL value on items — you must separately enable TTL on the DynamoDB table itself (via the AWS Console, CLI, or CloudFormation).
|
|
235
252
|
|
|
236
253
|
```typescript
|
|
237
254
|
const db = new DynoQuery({
|
|
238
255
|
region: 'us-east-1',
|
|
239
256
|
tableName: 'MyTable',
|
|
240
|
-
ttlAttributeName: 'expireAt'
|
|
257
|
+
ttlAttributeName: 'expireAt'
|
|
241
258
|
});
|
|
242
259
|
|
|
243
260
|
async function ttlExample() {
|
|
244
|
-
const session = db.User('john@example.com').draft('SESSION');
|
|
245
|
-
|
|
246
|
-
// Set TTL to 1 hour from now (timestamp in seconds)
|
|
261
|
+
const session = db.User('john@example.com').draft('SESSION'); // PK: USER#john@example.com
|
|
262
|
+
|
|
263
|
+
// Set TTL to 1 hour from now (Unix timestamp in seconds)
|
|
247
264
|
const ttl = Math.floor(Date.now() / 1000) + 3600;
|
|
248
265
|
session.ttl(ttl);
|
|
249
|
-
|
|
266
|
+
|
|
250
267
|
session.data = 'some session data';
|
|
251
268
|
await session.save();
|
|
252
269
|
}
|
|
253
270
|
```
|
|
254
271
|
|
|
255
|
-
##
|
|
272
|
+
## Configuration
|
|
273
|
+
|
|
274
|
+
### DynoQuery Options
|
|
256
275
|
|
|
257
276
|
| Parameter | Type | Default | Description |
|
|
258
277
|
| :--- | :--- | :--- | :--- |
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
265
|
-
|
|
266
|
-
|
|
278
|
+
| `tableName` | `string` | - | The DynamoDB table name. |
|
|
279
|
+
| `region` | `string` | - | AWS region. |
|
|
280
|
+
| `pkName` | `string` | `'PK'` | Attribute name for the table Partition Key. |
|
|
281
|
+
| `skName` | `string` | `'SK'` | Attribute name for the table Sort Key. |
|
|
282
|
+
| `pkPrefix` | `string` | `''` | Global prefix for all partition keys (useful for multitenancy, e.g., `TENANT#A#`). |
|
|
283
|
+
| `ttlAttributeName` | `string` | - | Attribute name configured for DynamoDB TTL. |
|
|
284
|
+
| `endpoint` | `string` | - | Custom endpoint for local development (e.g., `http://localhost:8000`). |
|
|
285
|
+
| `credentials` | `object` | - | AWS credentials `{ accessKeyId, secretAccessKey, sessionToken? }`. |
|
|
286
|
+
| `models` | `object` | - | Map of model names to `{ pkPrefix: string }`. |
|
|
287
|
+
| `findBy` | `object` | - | Map of index names to index config (see below). |
|
|
267
288
|
|
|
268
289
|
```typescript
|
|
269
290
|
const db = new DynoQuery({
|
|
270
291
|
region: 'us-east-1',
|
|
271
292
|
tableName: 'MyTable',
|
|
272
|
-
pkName: 'PartitionKey',
|
|
273
|
-
skName: 'SortKey',
|
|
274
|
-
pkPrefix: 'TENANT#A#',
|
|
275
|
-
endpoint: 'http://localhost:8000',
|
|
293
|
+
pkName: 'PartitionKey',
|
|
294
|
+
skName: 'SortKey',
|
|
295
|
+
pkPrefix: 'TENANT#A#',
|
|
296
|
+
endpoint: 'http://localhost:8000',
|
|
276
297
|
credentials: {
|
|
277
298
|
accessKeyId: 'MY_ACCESS_KEY',
|
|
278
299
|
secretAccessKey: 'MY_SECRET_KEY'
|
|
@@ -280,111 +301,157 @@ const db = new DynoQuery({
|
|
|
280
301
|
});
|
|
281
302
|
```
|
|
282
303
|
|
|
304
|
+
### findBy Index Options
|
|
305
|
+
|
|
306
|
+
| Parameter | Type | Default | Description |
|
|
307
|
+
| :--- | :--- | :--- | :--- |
|
|
308
|
+
| `indexName` | `string` | Required | The DynamoDB index name (e.g., `'GSI1'`). |
|
|
309
|
+
| `pkName` | `string` | `'{indexName}PK'` | Attribute name for the GSI partition key. |
|
|
310
|
+
| `skName` | `string` | `'{indexName}SK'` | Attribute name for the GSI sort key. |
|
|
311
|
+
| `pkPrefix` | `string` | `''` | Prefix applied to PK values for this index. |
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const db = new DynoQuery({
|
|
315
|
+
// ...
|
|
316
|
+
findBy: {
|
|
317
|
+
// GSI1PK / GSI1SK auto-derived from indexName
|
|
318
|
+
Category: { indexName: 'GSI1', pkPrefix: 'CAT#' },
|
|
319
|
+
|
|
320
|
+
// Override attribute names explicitly
|
|
321
|
+
Status: { indexName: 'StatusIndex', pkName: 'StatusPK', skName: 'StatusSK' }
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
283
326
|
## Advanced Usage
|
|
284
327
|
|
|
285
328
|
### Pagination
|
|
286
329
|
|
|
287
|
-
Both `Partition.getAll()` and `IndexQuery.getAll()` support pagination
|
|
330
|
+
Both `Partition.getAll()` and `IndexQuery.getAll()` support pagination via `Limit` and `ExclusiveStartKey`.
|
|
288
331
|
|
|
289
332
|
```typescript
|
|
290
|
-
const index = db.findByCategory('ELECTRONICS');
|
|
333
|
+
const index = db.findByCategory('ELECTRONICS'); // GSI1PK: CAT#ELECTRONICS
|
|
291
334
|
const items = await index.getAll({ Limit: 10 });
|
|
292
335
|
|
|
293
336
|
const token = index.getLastEvaluatedKey();
|
|
294
337
|
if (token) {
|
|
295
|
-
// Fetch next page
|
|
296
338
|
const nextItems = await index.getAll({ Limit: 10, ExclusiveStartKey: token });
|
|
297
339
|
}
|
|
298
340
|
```
|
|
299
341
|
|
|
300
|
-
|
|
342
|
+
Use `ScanIndexForward: false` to return results in descending sort key order:
|
|
301
343
|
|
|
302
|
-
|
|
344
|
+
```typescript
|
|
345
|
+
const latest = await index.getAll({ Limit: 5, ScanIndexForward: false });
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Expression Builder
|
|
349
|
+
|
|
350
|
+
Use `attr()` and `ExpressionBuilder` to build type-safe filter and condition expressions.
|
|
303
351
|
|
|
304
352
|
```typescript
|
|
305
353
|
import { attr, ExpressionBuilder } from 'dynoquery';
|
|
306
354
|
|
|
307
355
|
// 1. Filtering in queries
|
|
308
356
|
const builder = attr('age').greaterThan(25).and(attr('status').equals('ACTIVE'));
|
|
309
|
-
const activeUsers = await db.User('some-id').getAll({ filterBuilder: builder });
|
|
310
|
-
|
|
311
|
-
// 2. Conditional Updates
|
|
312
|
-
const johnMeta = db.User('john@example.com', 'METADATA');
|
|
313
|
-
const condition = attr('version').equals(1);
|
|
357
|
+
const activeUsers = await db.User('some-id').getAll({ filterBuilder: builder }); // PK: USER#some-id
|
|
314
358
|
|
|
315
|
-
|
|
359
|
+
// 2. Conditional save — fails if version is not 1
|
|
360
|
+
const johnMeta = db.User('john@example.com', 'METADATA'); // PK: USER#john@example.com, SK: METADATA
|
|
361
|
+
johnMeta.setCondition(attr('version').equals(1));
|
|
316
362
|
johnMeta.name = 'John New Name';
|
|
317
363
|
johnMeta.version = 2;
|
|
364
|
+
await johnMeta.save();
|
|
318
365
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
await db.User('john@example.com').update({ status: 'INACTIVE' }, [], {
|
|
323
|
-
ConditionExpression: '#v = :v',
|
|
324
|
-
ExpressionAttributeNames: { '#v': 'version' },
|
|
325
|
-
ExpressionAttributeValues: { ':v': 2 }
|
|
366
|
+
// 3. Conditional create/update on the Partition level
|
|
367
|
+
await db.User('john@example.com').create('METADATA', { name: 'John' }, [], { // PK: USER#john@example.com
|
|
368
|
+
conditionBuilder: attr('email').notExists()
|
|
326
369
|
});
|
|
327
370
|
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
await
|
|
331
|
-
|
|
332
|
-
// 4. Supported Operators
|
|
333
|
-
// - attr('name').equals('val') / notEquals('val')
|
|
334
|
-
// - .lessThan(val) / lessThanOrEqual(val)
|
|
335
|
-
// - .greaterThan(val) / greaterThanOrEqual(val)
|
|
336
|
-
// - .between(start, end)
|
|
337
|
-
// - .in([val1, val2])
|
|
338
|
-
// - logical: .and(otherBuilder), .or(otherBuilder), ExpressionBuilder.not(builder)
|
|
339
|
-
|
|
340
|
-
// 4. Supported Functions
|
|
341
|
-
// - attr('field').exists()
|
|
342
|
-
// - attr('field').notExists()
|
|
343
|
-
// - attr('field').type('S')
|
|
344
|
-
// - attr('field').beginsWith('prefix')
|
|
345
|
-
// - attr('field').contains('value')
|
|
346
|
-
// - attr('field').size().greaterThan(5)
|
|
371
|
+
// 4. NOT combinator
|
|
372
|
+
const notPremium = ExpressionBuilder.not(attr('tier').equals('PREMIUM'));
|
|
373
|
+
const users = await db.User('some-id').getAll({ filterBuilder: notPremium }); // PK: USER#some-id
|
|
347
374
|
```
|
|
348
375
|
|
|
376
|
+
**Supported operators:**
|
|
377
|
+
|
|
378
|
+
| Method | DynamoDB Expression |
|
|
379
|
+
| :--- | :--- |
|
|
380
|
+
| `.equals(val)` | `= val` |
|
|
381
|
+
| `.notEquals(val)` | `<> val` |
|
|
382
|
+
| `.lessThan(val)` | `< val` |
|
|
383
|
+
| `.lessThanOrEqual(val)` | `<= val` |
|
|
384
|
+
| `.greaterThan(val)` | `> val` |
|
|
385
|
+
| `.greaterThanOrEqual(val)` | `>= val` |
|
|
386
|
+
| `.between(start, end)` | `BETWEEN start AND end` |
|
|
387
|
+
| `.in([val1, val2])` | `IN (val1, val2)` |
|
|
388
|
+
| `.and(otherBuilder)` | `(a) AND (b)` |
|
|
389
|
+
| `.or(otherBuilder)` | `(a) OR (b)` |
|
|
390
|
+
| `ExpressionBuilder.not(builder)` | `NOT (a)` |
|
|
391
|
+
|
|
392
|
+
**Supported functions:**
|
|
393
|
+
|
|
394
|
+
| Method | DynamoDB Function |
|
|
395
|
+
| :--- | :--- |
|
|
396
|
+
| `attr('field').exists()` | `attribute_exists(field)` |
|
|
397
|
+
| `attr('field').notExists()` | `attribute_not_exists(field)` |
|
|
398
|
+
| `attr('field').type('S')` | `attribute_type(field, 'S')` |
|
|
399
|
+
| `attr('field').beginsWith('prefix')` | `begins_with(field, 'prefix')` |
|
|
400
|
+
| `attr('field').contains('value')` | `contains(field, 'value')` |
|
|
401
|
+
| `attr('field').size().greaterThan(5)` | `size(field) > 5` |
|
|
402
|
+
|
|
349
403
|
## API Reference
|
|
350
404
|
|
|
351
405
|
### DynoQuery
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
406
|
+
|
|
407
|
+
| Method | Description |
|
|
408
|
+
| :--- | :--- |
|
|
409
|
+
| `[ModelName](id, skValue?)` | Returns a `Partition` for the given ID. If `skValue` is provided, returns an `Item` directly. |
|
|
410
|
+
| `findBy[IndexName](id, skValue?)` | Returns an `IndexQuery` for the given ID. |
|
|
411
|
+
| `batchWrite(items)` | Persists multiple `Item` objects. Auto-chunks at 25 per DynamoDB limit. |
|
|
412
|
+
| `batchRead(items)` | Fetches multiple `Item` objects. Auto-chunks at 100 per DynamoDB limit. |
|
|
413
|
+
| `transactWrite(items)` | Atomic write (Put/Delete) for up to 100 items per chunk. |
|
|
414
|
+
| `transactRead(items)` | Atomic read (Get) for up to 100 items per chunk. |
|
|
415
|
+
| `create(params)` | Low-level `PutCommand` wrapper. |
|
|
416
|
+
| `get(params)` | Low-level `GetCommand` wrapper. |
|
|
417
|
+
| `update(params)` | Low-level `UpdateCommand` wrapper. |
|
|
418
|
+
| `delete(params)` | Low-level `DeleteCommand` wrapper. |
|
|
419
|
+
| `query(params)` | Low-level `QueryCommand` wrapper. |
|
|
420
|
+
| `scan(params)` | Low-level `ScanCommand` wrapper. |
|
|
364
421
|
|
|
365
422
|
### Partition
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
423
|
+
|
|
424
|
+
| Method | Description |
|
|
425
|
+
| :--- | :--- |
|
|
426
|
+
| `get(skValue)` | Fetches a single item by SK. Returns an `Item` or `null`. |
|
|
427
|
+
| `getAll(options?)` | Queries all items in the partition. Options: `Limit`, `ExclusiveStartKey`, `filterBuilder`, `FilterExpression`, `ExpressionAttributeNames`, `ExpressionAttributeValues`. |
|
|
428
|
+
| `create(skValue, data, indices?, options?)` | Creates/replaces an item. `options`: `conditionBuilder`, `ConditionExpression`, `ExpressionAttributeNames`, `ExpressionAttributeValues`. |
|
|
429
|
+
| `update(skValue, data, indices?, options?)` | Merges `data` with existing item and saves. Same `options` as `create`. |
|
|
430
|
+
| `delete(skValue, options?)` | Deletes an item by SK. Same `options` as `create`. |
|
|
431
|
+
| `draft(skValue, data?)` | Returns an unsaved `Item` initialized with optional `data`. |
|
|
432
|
+
| `draftDelete(skValue)` | Returns an `Item` marked for deletion (for use with `batchWrite`). |
|
|
433
|
+
| `deleteAll()` | Deletes all items in the partition. |
|
|
434
|
+
| `getLastEvaluatedKey()` | Returns the pagination token from the last `getAll()`. |
|
|
375
435
|
|
|
376
436
|
### IndexQuery
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
437
|
+
|
|
438
|
+
| Method | Description |
|
|
439
|
+
| :--- | :--- |
|
|
440
|
+
| `getAll(options?)` | Queries the index. Options: `Limit`, `ScanIndexForward`, `ExclusiveStartKey`, `skValue` (uses `begins_with`), `filterBuilder`, `FilterExpression`, `ExpressionAttributeNames`, `ExpressionAttributeValues`. |
|
|
441
|
+
| `get(skValue?)` | Returns the first item matching the optional SK prefix. |
|
|
442
|
+
| `getLastEvaluatedKey()` | Returns the pagination token from the last `getAll()`. |
|
|
443
|
+
|
|
444
|
+
### Item
|
|
445
|
+
|
|
446
|
+
| Method | Description |
|
|
447
|
+
| :--- | :--- |
|
|
448
|
+
| `save()` | Persists the item. Includes any attached indices and condition. |
|
|
449
|
+
| `ttl(timestamp)` | Sets the TTL attribute value (requires `ttlAttributeName` in config). |
|
|
450
|
+
| `getData()` | Returns a plain object with only the item's data attributes. |
|
|
451
|
+
| `setIndex(index)` | Attaches one or more `IndexQuery` objects (included on every `save()`). |
|
|
452
|
+
| `setFilter(builder)` | Sets a filter expression builder for the item. |
|
|
453
|
+
| `setCondition(builder)` | Sets a condition expression applied on `save()`. |
|
|
454
|
+
| `getPartition()` | Returns the parent `Partition` instance. |
|
|
388
455
|
|
|
389
456
|
## License
|
|
390
457
|
|