@eresearchqut/ddb-repository 1.5.8 โ 1.13.5
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 +190 -105
- package/dist/index.cjs +478 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +116 -0
- package/dist/index.d.mts +116 -0
- package/dist/index.mjs +474 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +33 -22
- package/dist/DynamoDbRepository.d.ts +0 -50
- package/dist/DynamoDbRepository.js +0 -244
- package/dist/consumed-capacity-middleware.d.ts +0 -10
- package/dist/consumed-capacity-middleware.js +0 -25
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -8
package/README.md
CHANGED
|
@@ -8,139 +8,244 @@ A TypeScript library providing a generic repository pattern implementation for A
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
10
|
- ๐ Generic repository pattern for type-safe DynamoDB operations
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
11
|
+
- ๐ Rich query support with filter expressions and projections
|
|
12
|
+
- ๐ฏ Batch get with automatic retry for unprocessed keys
|
|
13
|
+
- ๐ Paginated queries with limit and sort order control
|
|
14
|
+
- ๐๏ธ [JSON Pointer Repository](#jsonpointerrepository) for storing structured JSON documents
|
|
14
15
|
- ๐งช Fully tested with Jest and Testcontainers
|
|
15
16
|
- ๐ช Written in TypeScript with full type safety
|
|
16
17
|
- โก Built on top of AWS SDK v3
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
20
|
+
|
|
19
21
|
```sh
|
|
20
22
|
npm install @eresearchqut/ddb-repository
|
|
21
23
|
```
|
|
24
|
+
|
|
22
25
|
## Prerequisites
|
|
23
26
|
|
|
24
|
-
- Node.js
|
|
27
|
+
- Node.js LTS
|
|
25
28
|
- AWS credentials configured (for production use)
|
|
26
|
-
- DynamoDB table with appropriate schema
|
|
29
|
+
- DynamoDB table with the appropriate key schema
|
|
27
30
|
|
|
28
31
|
## Usage
|
|
29
32
|
|
|
30
33
|
### Basic Example
|
|
34
|
+
|
|
31
35
|
```typescript
|
|
32
|
-
import { DynamoDbRepository } from 'ddb-repository';
|
|
36
|
+
import { DynamoDbRepository, FilterOperator } from '@eresearchqut/ddb-repository';
|
|
33
37
|
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
interface UserKey {
|
|
40
|
+
id: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
interface User {
|
|
37
|
-
id: string;
|
|
38
|
-
name: string;
|
|
39
|
-
email
|
|
40
|
-
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
email?: string;
|
|
47
|
+
age?: number;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
// Initialize DynamoDB client
|
|
44
50
|
const client = new DynamoDBClient({ region: 'us-east-1' });
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
const userRepository = new DynamoDbRepository<UserKey, User>({
|
|
53
|
+
client,
|
|
54
|
+
tableName: 'users',
|
|
55
|
+
hashKey: 'id',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Write an item
|
|
59
|
+
await userRepository.putItem({ id: '123' }, { id: '123', name: 'Alice', email: 'alice@example.com' });
|
|
52
60
|
|
|
53
|
-
//
|
|
54
|
-
await userRepository.
|
|
55
|
-
|
|
56
|
-
name
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
// Read an item
|
|
62
|
+
const user = await userRepository.getItem({ id: '123' });
|
|
63
|
+
|
|
64
|
+
// Update specific fields (SET name, REMOVE email)
|
|
65
|
+
await userRepository.updateItem({ id: '123' }, { name: 'Alicia' }, ['email']);
|
|
66
|
+
|
|
67
|
+
// Delete an item
|
|
68
|
+
await userRepository.deleteItem({ id: '123' });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Querying Items
|
|
72
|
+
|
|
73
|
+
`getItems` performs a DynamoDB `Query` and returns all matching items. It paginates automatically and stops early once `limit` items are accumulated.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// All items for a partition key
|
|
77
|
+
const items = await userRepository.getItems({ id: 'user-123' });
|
|
78
|
+
|
|
79
|
+
// With filter expressions
|
|
80
|
+
const activeAdults = await userRepository.getItems({
|
|
81
|
+
id: 'user-123',
|
|
82
|
+
filterExpressions: [
|
|
83
|
+
{ attribute: 'age', operator: FilterOperator.GREATER_THAN_OR_EQUALS, value: 18 },
|
|
84
|
+
{ attribute: 'status', operator: FilterOperator.EQUALS, value: 'active' },
|
|
85
|
+
],
|
|
59
86
|
});
|
|
60
87
|
|
|
61
|
-
//
|
|
62
|
-
const
|
|
88
|
+
// With projection (only return selected attributes)
|
|
89
|
+
const names = await userRepository.getItems({
|
|
90
|
+
id: 'user-123',
|
|
91
|
+
projectedAttributes: ['id', 'name'],
|
|
92
|
+
});
|
|
63
93
|
|
|
64
|
-
//
|
|
65
|
-
await userRepository.
|
|
66
|
-
|
|
94
|
+
// With limit and sort order
|
|
95
|
+
const recent = await userRepository.getItems({
|
|
96
|
+
id: 'user-123',
|
|
97
|
+
limit: 10,
|
|
98
|
+
sortOrder: 'DESC',
|
|
67
99
|
});
|
|
68
100
|
|
|
69
|
-
//
|
|
70
|
-
await userRepository.
|
|
101
|
+
// Query a Global Secondary Index
|
|
102
|
+
const byStatus = await userRepository.getItems({
|
|
103
|
+
status: 'active',
|
|
104
|
+
index: 'status-index',
|
|
105
|
+
});
|
|
71
106
|
```
|
|
72
107
|
|
|
73
|
-
|
|
108
|
+
### Filter Operators
|
|
74
109
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
110
|
+
| Operator | DynamoDB Expression | Notes |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `EQUALS` | `#attr = :val` | |
|
|
113
|
+
| `NOT_EQUALS` | `#attr <> :val` | |
|
|
114
|
+
| `GREATER_THAN` | `#attr > :val` | |
|
|
115
|
+
| `GREATER_THAN_OR_EQUALS` | `#attr >= :val` | |
|
|
116
|
+
| `LESS_THAN` | `#attr < :val` | |
|
|
117
|
+
| `LESS_THAN_OR_EQUALS` | `#attr <= :val` | |
|
|
118
|
+
| `IN` | `#attr IN (:val0, :val1, ...)` | Pass an `Array` as value |
|
|
119
|
+
| `BETWEEN` | `#attr BETWEEN :val0 AND :val1` | Pass a 2-tuple as value |
|
|
120
|
+
| `BEGINS_WITH` | `begins_with(#attr, :val)` | String prefix match |
|
|
121
|
+
| `CONTAINS` | `contains(#attr, :val)` | Substring or set membership |
|
|
81
122
|
|
|
82
|
-
|
|
83
|
-
- `findById(id: string): Promise<T | null>` - Find item by partition key
|
|
84
|
-
- `update(id: string, updates: Partial<T>): Promise<T>` - Update an existing item
|
|
85
|
-
- `delete(id: string): Promise<void>` - Delete an item
|
|
86
|
-
- `findAll(): Promise<T[]>` - Retrieve all items (use with caution on large tables)
|
|
87
|
-
- `batchCreate(items: T[]): Promise<void>` - Create multiple items in batch
|
|
88
|
-
- `query(options: QueryOptions): Promise<T[]>` - Query items with custom conditions
|
|
123
|
+
All operators support `negate: true` to wrap the expression in `NOT (...)`.
|
|
89
124
|
|
|
90
|
-
|
|
125
|
+
### Batch Get
|
|
91
126
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
127
|
+
```typescript
|
|
128
|
+
const keys = [{ id: '1' }, { id: '2' }, { id: '3' }];
|
|
129
|
+
const items = await userRepository.batchGetItems(keys);
|
|
130
|
+
```
|
|
96
131
|
|
|
97
|
-
|
|
98
|
-
npm run lint
|
|
132
|
+
Automatically deduplicates keys, pages requests in chunks of 100, and retries any `UnprocessedKeys` with exponential back-off.
|
|
99
133
|
|
|
100
|
-
|
|
101
|
-
npm run lint:fix
|
|
134
|
+
### Composite Key Tables
|
|
102
135
|
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
```typescript
|
|
137
|
+
interface OrderKey {
|
|
138
|
+
customerId: string;
|
|
139
|
+
orderId: string;
|
|
140
|
+
}
|
|
105
141
|
|
|
106
|
-
|
|
107
|
-
|
|
142
|
+
interface Order {
|
|
143
|
+
customerId: string;
|
|
144
|
+
orderId: string;
|
|
145
|
+
total: number;
|
|
146
|
+
}
|
|
108
147
|
|
|
109
|
-
|
|
110
|
-
|
|
148
|
+
const orderRepository = new DynamoDbRepository<OrderKey, Order>({
|
|
149
|
+
client,
|
|
150
|
+
tableName: 'orders',
|
|
151
|
+
hashKey: 'customerId',
|
|
152
|
+
rangeKey: 'orderId',
|
|
153
|
+
});
|
|
111
154
|
|
|
112
|
-
|
|
113
|
-
|
|
155
|
+
await orderRepository.putItem(
|
|
156
|
+
{ customerId: 'c1', orderId: 'o1' },
|
|
157
|
+
{ customerId: 'c1', orderId: 'o1', total: 99.99 },
|
|
158
|
+
);
|
|
114
159
|
```
|
|
115
|
-
### Testing
|
|
116
160
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
161
|
+
### Consumed Capacity Middleware
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { consumedCapacityMiddleware } from '@eresearchqut/ddb-repository';
|
|
165
|
+
|
|
166
|
+
client.middlewareStack.add(
|
|
167
|
+
consumedCapacityMiddleware({
|
|
168
|
+
onConsumedCapacity: async (detail) => {
|
|
169
|
+
console.log('Consumed capacity:', detail.ConsumedCapacity);
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
120
173
|
```
|
|
121
174
|
|
|
122
|
-
|
|
175
|
+
## API Reference
|
|
123
176
|
|
|
124
|
-
|
|
177
|
+
### `DynamoDbRepository<K, T>`
|
|
125
178
|
|
|
126
|
-
|
|
127
|
-
|
|
179
|
+
#### Constructor options
|
|
180
|
+
|
|
181
|
+
| Option | Type | Required | Description |
|
|
182
|
+
|---|---|---|---|
|
|
183
|
+
| `client` | `DynamoDBClient` | โ
| AWS SDK v3 DynamoDB client |
|
|
184
|
+
| `tableName` | `string` | โ
| DynamoDB table name |
|
|
185
|
+
| `hashKey` | `string` | โ
| Partition key attribute name |
|
|
186
|
+
| `rangeKey` | `string` | | Sort key attribute name |
|
|
187
|
+
| `returnConsumedCapacity` | `ReturnConsumedCapacity` | | Defaults to `TOTAL` |
|
|
188
|
+
|
|
189
|
+
#### Methods
|
|
190
|
+
|
|
191
|
+
| Method | Signature | Description |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| `getItem` | `(key: K) => Promise<T \| undefined>` | Read a single item by key |
|
|
194
|
+
| `putItem` | `(key: K, record: T) => Promise<T>` | Write an item (create or replace) |
|
|
195
|
+
| `updateItem` | `(key: K, updates: Partial<T>, remove?: string[]) => Promise<T \| undefined>` | Partial update with optional attribute removal |
|
|
196
|
+
| `deleteItem` | `(key: K) => Promise<Partial<T> \| undefined>` | Delete an item |
|
|
197
|
+
| `getItems` | `(query: Query) => Promise<T[] \| undefined>` | Query items (auto-paginates) |
|
|
198
|
+
| `batchGetItems` | `(keys: K[], projectedQuery?: ProjectedQuery) => Promise<Array<T \| undefined>>` | Fetch multiple items by key |
|
|
199
|
+
|
|
200
|
+
## JsonPointerRepository
|
|
201
|
+
|
|
202
|
+
Stores JSON documents as individual per-pointer DynamoDB items using [RFC 6901 JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901) addressing.
|
|
203
|
+
|
|
204
|
+
See [JSON_POINTER_REPOSITORY.md](./JSON_POINTER_REPOSITORY.md) for full documentation.
|
|
205
|
+
|
|
206
|
+
### Quick Example
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { JsonPointerRepository } from '@eresearchqut/ddb-repository';
|
|
210
|
+
|
|
211
|
+
const docRepo = new JsonPointerRepository({
|
|
212
|
+
client,
|
|
213
|
+
tableName: 'documents', // requires hash key "id", range key "pointer"
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await docRepo.putDocument('doc-1', {
|
|
217
|
+
name: 'Alice',
|
|
218
|
+
address: { city: 'Melbourne' },
|
|
219
|
+
tags: ['admin', 'user'],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const doc = await docRepo.getDocument('doc-1');
|
|
223
|
+
// { name: 'Alice', address: { city: 'Melbourne' }, tags: ['admin', 'user'] }
|
|
224
|
+
|
|
225
|
+
const city = await docRepo.getAttribute('doc-1', '/address/city');
|
|
226
|
+
// 'Melbourne'
|
|
227
|
+
|
|
228
|
+
await docRepo.putAttribute('doc-1', '/address/postcode', '3000');
|
|
229
|
+
await docRepo.deleteAttribute('doc-1', '/tags/1');
|
|
230
|
+
await docRepo.deleteDocument('doc-1');
|
|
128
231
|
```
|
|
129
232
|
|
|
130
|
-
|
|
131
|
-
* coverage/lcov-report/index.html - Interactive HTML report
|
|
132
|
-
* coverage/lcov.info - LCOV format for CI/CD integration
|
|
233
|
+
## Development
|
|
133
234
|
|
|
134
|
-
|
|
235
|
+
```sh
|
|
236
|
+
npm install # install dependencies
|
|
237
|
+
npm run lint # lint
|
|
238
|
+
npm run lint:fix # lint with auto-fix
|
|
239
|
+
npm test # run integration tests (requires Docker)
|
|
240
|
+
npm run test:coverage # tests with coverage report
|
|
241
|
+
npm run build # compile to dist/
|
|
242
|
+
```
|
|
135
243
|
|
|
136
244
|
## Configuration
|
|
137
245
|
|
|
138
246
|
### AWS Credentials
|
|
139
247
|
|
|
140
|
-
|
|
141
|
-
- Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`)
|
|
142
|
-
- AWS credentials file (`~/.aws/credentials`)
|
|
143
|
-
- IAM role (when running on AWS infrastructure)
|
|
248
|
+
Configure via environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`), AWS credentials file (`~/.aws/credentials`), or IAM role.
|
|
144
249
|
|
|
145
250
|
### Required IAM Permissions
|
|
146
251
|
|
|
@@ -156,8 +261,7 @@ Ensure your AWS credentials are configured via:
|
|
|
156
261
|
"dynamodb:UpdateItem",
|
|
157
262
|
"dynamodb:DeleteItem",
|
|
158
263
|
"dynamodb:Query",
|
|
159
|
-
"dynamodb:
|
|
160
|
-
"dynamodb:BatchWriteItem"
|
|
264
|
+
"dynamodb:BatchGetItem"
|
|
161
265
|
],
|
|
162
266
|
"Resource": "arn:aws:dynamodb:*:*:table/your-table-name"
|
|
163
267
|
}
|
|
@@ -167,37 +271,18 @@ Ensure your AWS credentials are configured via:
|
|
|
167
271
|
|
|
168
272
|
## Contributing
|
|
169
273
|
|
|
170
|
-
Contributions are welcome! Please
|
|
171
|
-
|
|
274
|
+
Contributions are welcome! Please submit a Pull Request.
|
|
172
275
|
|
|
173
276
|
### Commit message convention
|
|
174
|
-
Semantic release uses conventional commits. Your commit messages should follow this format:
|
|
175
|
-
|
|
176
|
-
* feat: new feature โ triggers minor version bump (1.x.0)
|
|
177
|
-
* fix: bug fix โ triggers patch version bump (1.0.x)
|
|
178
|
-
* perf: performance improvement โ triggers patch version bump
|
|
179
|
-
* docs: documentation change โ no release
|
|
180
|
-
* chore: maintenance task โ no release
|
|
181
|
-
* BREAKING CHANGE: in footer โ triggers major version bump (x.0.0)
|
|
182
|
-
|
|
183
|
-
Example:
|
|
184
|
-
|
|
185
|
-
> feat: add batch write support
|
|
186
|
-
>
|
|
187
|
-
> Added support for batch write operations to improve performance
|
|
188
277
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
> feat: change repository API
|
|
192
|
-
>
|
|
193
|
-
> BREAKING CHANGE: The query method now returns a Promise instead of an Observable
|
|
278
|
+
Semantic release uses [Conventional Commits](https://www.conventionalcommits.org/):
|
|
194
279
|
|
|
280
|
+
- `feat:` โ minor version bump
|
|
281
|
+
- `fix:` / `perf:` โ patch version bump
|
|
282
|
+
- `docs:` / `chore:` โ no release
|
|
283
|
+
- `feat!:` or `BREAKING CHANGE:` footer โ major version bump
|
|
195
284
|
|
|
196
285
|
## License
|
|
197
286
|
|
|
198
287
|
MIT
|
|
199
288
|
|
|
200
|
-
## Support
|
|
201
|
-
|
|
202
|
-
For issues and questions, please open an issue on the GitHub repository.
|
|
203
|
-
|