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.
Files changed (2) hide show
  1. package/README.md +220 -153
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,7 +1,30 @@
1
1
  # DynoQuery
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/dynoquery.svg)](https://www.npmjs.com/package/dynoquery)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 (Basic Usage)
43
+ ## Quick Start
20
44
 
21
- First, initialize the client with your table name.
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 your models to handle different types of data within your single table.
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#' }, // Resulting PK: USER#<id>
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
- #### Second-Level Objects (Single-Row Operations)
91
+ #### Item Objects (Single-Row Operations)
68
92
 
69
- For a more flexible way to work with individual rows, you can use `draft()` and `get()` to obtain an `Item` object, then call `save()` on it directly. You can also use a shorthand by passing a second argument to your model function (as shown in the examples below).
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. Create with properties in arguments
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 and update an existing row
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. Partial update without fetching
90
- await john.update('FRIEND#1', { Name: 'Alice', rank: 1 });
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 your GSIs easily.
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#' }, // pkName defaults to GSI1PK, skName defaults to GSI1SK
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
- // Query by Category PK: CAT#ELECTRONICS
120
- const electronics = db.findByCategory('ELECTRONICS');
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
- // If results match a registered item prefix, they are automatically mapped
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(); // Returns a Partition instance
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
- You can pass index queries directly to `create()` or `update()` to automatically populate GSI attributes. This works on the Partition level.
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
- // This will automatically set GSI1PK='CAT#ELECTRONICS' and GSI1SK='RANK#1'
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 persistence
157
- You can also use `setIndex()` to attach indices to an item so they are used automatically whenever you call `save()`. This is useful when you have an Item object (e.g., from `draft()` or `get()`).
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
- // save() will now automatically include GSI attributes from the attached index
185
+ // GSI attributes are included automatically
167
186
  await mouse.save();
168
187
  ```
169
188
 
170
189
  ### 4. Batch Operations
171
190
 
172
- DynoQuery provides `batchWrite` and `batchRead` for processing multiple items across partitions.
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 (Create/Replace multiple items)
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 (Fetch multiple items or index queries)
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(); // Second-level methods are available
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
- DynoQuery supports `transactWrite` and `transactRead` for atomic operations across multiple items.
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 (All operations succeed or all fail)
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
- // You can even set conditions for items in a transaction
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 (Read multiple items atomically)
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
- DynamoDB TTL allows you to automatically delete items after a certain timestamp. To use it in DynoQuery, configure `ttlAttributeName` when initializing the client.
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' // The attribute name which is set in DynamoDB to be used for TTL
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
- ## Optional Configuration Parameters
272
+ ## Configuration
273
+
274
+ ### DynoQuery Options
256
275
 
257
276
  | Parameter | Type | Default | Description |
258
277
  | :--- | :--- | :--- | :--- |
259
- | `pkName` | `string` | `'PK'` | Custom attribute name for Partition Key. |
260
- | `skName` | `string` | `'SK'` | Custom attribute name for Sort Key. |
261
- | `pkPrefix` | `string` | `''` | Global prefix for all partitions (useful for multitenancy, e.g., `TENANT#A#`). |
262
- | `ttlAttributeName` | `string` | - | Optional attribute name for DynamoDB TTL. |
263
- | `endpoint` | `string` | - | Optional endpoint for local development (e.g., `http://localhost:8000`). |
264
- | `credentials` | `object` | - | Custom AWS credentials (`{ accessKeyId, secretAccessKey, sessionToken? }`). |
265
-
266
- ### Example with Optional Parameters
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', // Custom PK name
273
- skName: 'SortKey', // Custom SK name
274
- pkPrefix: 'TENANT#A#', // Global prefix for multitenancy
275
- endpoint: 'http://localhost:8000', // For local DynamoDB
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
- ### Expression Builder (Filters & Conditions)
342
+ Use `ScanIndexForward: false` to return results in descending sort key order:
301
343
 
302
- Use `ExpressionBuilder` to build complex filter and condition expressions in a type-safe way.
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
- johnMeta.setCondition(condition);
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
- await johnMeta.save(); // Fails if version is not 1
320
-
321
- // 3. Raw Condition Expressions
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
- // Or using Item object
329
- johnMeta.setCondition(attr('name').exists());
330
- await johnMeta.save();
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
- - `create(params)`: Low-level PutCommand wrapper.
353
- - `get(params)`: Low-level GetCommand wrapper.
354
- - `update(params)`: Low-level UpdateCommand wrapper.
355
- - `delete(params)`: Low-level DeleteCommand wrapper.
356
- - `query(params)`: Low-level QueryCommand wrapper.
357
- - `scan(params)`: Low-level ScanCommand wrapper.
358
- - `batchWrite(items)`: Batch persists multiple `Item` objects.
359
- - `batchRead(items)`: Batch fetches multiple `Item` or `IndexQuery` objects.
360
- - `transactWrite(items)`: Performs atomic write operations (Put/Delete) for up to 100 items.
361
- - `transactRead(items)`: Performs atomic read operations (Get) for up to 100 items.
362
- - `[ModelName](id, skValue?)`: Returns a `Partition` instance for the given ID. If `skValue` is provided, returns an `Item` object directly.
363
- - `findBy[IndexName](id, skValue?)`: Returns an `IndexQuery` instance.
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
- - `get(skValue)`: Fetches data for a specific Sort Key value (returns a Promise).
367
- - `getAll(options?)`: Fetches items in the partition. Options: `{ Limit, ExclusiveStartKey, filterBuilder, FilterExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
368
- - `create(skValue, data, indices?, options?)`: Creates an item. `options`: `{ conditionBuilder, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
369
- - `update(skValue, data, indices?, options?)`: Partial update of an item. `options`: `{ conditionBuilder, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
370
- - `delete(skValue, options?)`: Deletes an item. `options`: `{ conditionBuilder, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
371
- - `draft(skValue, data?)`: Returns an `Item` object initialized with `data` (optional).
372
- - `draftDelete(skValue)`: Returns an `Item` object marked for deletion (for use with `batchWrite`).
373
- - `deleteAll()`: Deletes all items in the partition.
374
- - `getLastEvaluatedKey()`: Returns the pagination token from the last `getAll()`.
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
- - `get(skValue?)`: Get a single item from the index.
378
- - `getAll(options?)`: Query index. Options: `{ Limit, ScanIndexForward, ExclusiveStartKey, skValue, filterBuilder, FilterExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
379
- - `getLastEvaluatedKey()`: Returns the pagination token from the last `getAll()`.
380
-
381
- ### Item (returned by Partition.get or draft)
382
- - `save()`: Persists the current state of the item. Uses indices attached via `setIndex()` and internal `conditionBuilder`.
383
- - `ttl(timestamp)`: Sets the TTL attribute value. Only works if `ttlAttributeName` is configured in `DynoQueryConfig`.
384
- - `getData()`: Returns a clean data object containing only the database attributes (filters out internal state and methods).
385
- - `setIndex(indices)`: Attaches one or more `IndexQuery` objects to the item.
386
- - `setFilter(builder)`: Sets a filter expression builder for the item.
387
- - `setCondition(builder)`: Sets a condition for the item using an `ExpressionBuilder`.
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynoquery",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/devspikejs/dynoquery.git"