dyno-table 0.0.1 → 0.0.2
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 +344 -0
- package/dist/index.d.ts +183 -62
- package/dist/index.js +230 -136
- package/package.json +18 -18
- package/readme.md +0 -132
package/README.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# 🦖 dyno-table
|
|
2
|
+
|
|
3
|
+
A powerful, type-safe, and fluent DynamoDB table abstraction layer for Node.js applications.
|
|
4
|
+
|
|
5
|
+
Allows you to work with DynamoDB in a single table design pattern
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- **Type-safe operations**: Ensures type safety for all DynamoDB operations.
|
|
10
|
+
- **Builders for operations**: Provides builders for put, update, delete, query, and scan operations.
|
|
11
|
+
- **Transaction support**: Supports transactional operations.
|
|
12
|
+
- **Batch operations**: Handles batch write operations with automatic chunking for large datasets.
|
|
13
|
+
- **Conditional operations**: Supports conditional puts, updates, and deletes.
|
|
14
|
+
- **Repository pattern**: Provides a base repository class for implementing the repository pattern.
|
|
15
|
+
- **Error handling**: Custom error classes for handling DynamoDB errors gracefully.
|
|
16
|
+
|
|
17
|
+
## 📦 Installation
|
|
18
|
+
|
|
19
|
+
Get started with Dyno Table by installing it via npm:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install dyno-table
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🦕 Getting Started
|
|
26
|
+
|
|
27
|
+
### Setting Up the Table
|
|
28
|
+
|
|
29
|
+
First, set up the `Table` instance with your DynamoDB client and table configuration.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
|
|
33
|
+
import { Table } from "dyno-table";
|
|
34
|
+
import { docClient } from "./ddb-client"; // Your DynamoDB client instance
|
|
35
|
+
|
|
36
|
+
const table = new Table({
|
|
37
|
+
client: docClient,
|
|
38
|
+
tableName: "DinoTable",
|
|
39
|
+
tableIndexes: {
|
|
40
|
+
primary: {
|
|
41
|
+
pkName: "pk",
|
|
42
|
+
skName: "sk",
|
|
43
|
+
},
|
|
44
|
+
GSI1: {
|
|
45
|
+
pkName: "GSI1PK",
|
|
46
|
+
skName: "GSI1SK",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### CRUD Operations
|
|
53
|
+
|
|
54
|
+
#### Create (Put)
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// Simple put
|
|
58
|
+
const dino = {
|
|
59
|
+
pk: "SPECIES#trex",
|
|
60
|
+
sk: "PROFILE#001",
|
|
61
|
+
name: "Rex",
|
|
62
|
+
diet: "Carnivore",
|
|
63
|
+
length: 40,
|
|
64
|
+
type: "DINOSAUR",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
await table.put(dino).execute();
|
|
68
|
+
|
|
69
|
+
// Conditional put
|
|
70
|
+
await table
|
|
71
|
+
.put(dino)
|
|
72
|
+
.whereNotExists("pk") // Only insert if dinosaur doesn't exist
|
|
73
|
+
.whereNotExists("sk")
|
|
74
|
+
.execute();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Read (Get)
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const key = { pk: "SPECIES#trex", sk: "PROFILE#001" };
|
|
81
|
+
const result = await table.get(key);
|
|
82
|
+
console.log(result);
|
|
83
|
+
|
|
84
|
+
// Get with specific index
|
|
85
|
+
const result = await table.get(key, { indexName: "GSI1" });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### Update
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// Simple update
|
|
92
|
+
const updates = { length: 42, diet: "Carnivore" };
|
|
93
|
+
await table.update(key).setMany(updates).execute();
|
|
94
|
+
|
|
95
|
+
// Advanced update operations
|
|
96
|
+
await table
|
|
97
|
+
.update(key)
|
|
98
|
+
.set("diet", "Omnivore") // Set a single field
|
|
99
|
+
.set({ length: 45, name: "Rexy" }) // Set multiple fields
|
|
100
|
+
.remove("optional_field") // Remove fields
|
|
101
|
+
.increment("sightings", 1) // Increment a number
|
|
102
|
+
.whereEquals("length", 42) // Conditional update
|
|
103
|
+
.execute();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Delete
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// Simple delete
|
|
110
|
+
await table.delete(key).execute();
|
|
111
|
+
|
|
112
|
+
// Conditional delete
|
|
113
|
+
await table
|
|
114
|
+
.delete(key)
|
|
115
|
+
.whereExists("pk")
|
|
116
|
+
.whereEquals("type", "DINOSAUR")
|
|
117
|
+
.execute();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Query Operations
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// Basic query
|
|
124
|
+
const result = await table
|
|
125
|
+
.query({ pk: "SPECIES#trex" })
|
|
126
|
+
.execute();
|
|
127
|
+
|
|
128
|
+
// Advanced query with conditions
|
|
129
|
+
const result = await table
|
|
130
|
+
.query({
|
|
131
|
+
pk: "SPECIES#velociraptor",
|
|
132
|
+
sk: { operator: "begins_with", value: "PROFILE#" }
|
|
133
|
+
})
|
|
134
|
+
.where("type", "=", "DINOSAUR")
|
|
135
|
+
.whereGreaterThan("length", 6)
|
|
136
|
+
.limit(10)
|
|
137
|
+
.useIndex("GSI1")
|
|
138
|
+
.execute();
|
|
139
|
+
|
|
140
|
+
// Available query conditions:
|
|
141
|
+
// .where(field, operator, value) // Generic condition
|
|
142
|
+
// .whereEquals(field, value) // Equality check
|
|
143
|
+
// .whereBetween(field, start, end) // Range check
|
|
144
|
+
// .whereIn(field, values) // IN check
|
|
145
|
+
// .whereLessThan(field, value) // < check
|
|
146
|
+
// .whereLessThanOrEqual(field, value) // <= check
|
|
147
|
+
// .whereGreaterThan(field, value) // > check
|
|
148
|
+
// .whereGreaterThanOrEqual(field, value) // >= check
|
|
149
|
+
// .whereNotEqual(field, value) // <> check
|
|
150
|
+
// .whereBeginsWith(field, value) // begins_with check
|
|
151
|
+
// .whereContains(field, value) // contains check
|
|
152
|
+
// .whereNotContains(field, value) // not_contains check
|
|
153
|
+
// .whereExists(field) // attribute_exists check
|
|
154
|
+
// .whereNotExists(field) // attribute_not_exists check
|
|
155
|
+
// .whereAttributeType(field, type) // attribute_type check
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Scan Operations
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
// Basic scan
|
|
162
|
+
const result = await table.scan().execute();
|
|
163
|
+
|
|
164
|
+
// Filtered scan
|
|
165
|
+
const result = await table
|
|
166
|
+
.scan()
|
|
167
|
+
.whereEquals("type", "DINOSAUR")
|
|
168
|
+
.where("length", ">", 20)
|
|
169
|
+
.limit(20)
|
|
170
|
+
.execute();
|
|
171
|
+
|
|
172
|
+
// Scan supports all the same conditions as Query operations
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Batch Operations
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// Batch write (put)
|
|
179
|
+
const dinos = [
|
|
180
|
+
{ pk: "SPECIES#trex", sk: "PROFILE#001", name: "Rex", length: 40 },
|
|
181
|
+
{ pk: "SPECIES#raptor", sk: "PROFILE#001", name: "Blue", length: 6 },
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
await table.batchWrite(
|
|
185
|
+
dinos.map((dino) => ({ type: "put", item: dino }))
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Batch write (delete)
|
|
189
|
+
await table.batchWrite([
|
|
190
|
+
{ type: "delete", key: { pk: "SPECIES#trex", sk: "PROFILE#001" } },
|
|
191
|
+
{ type: "delete", key: { pk: "SPECIES#raptor", sk: "PROFILE#001" } },
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
// Batch operations automatically handle chunking for large datasets
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Pagination
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
// Limit to 10 items per page
|
|
201
|
+
const paginator = await table.query({ pk: "SPECIES#trex" }).limit(10).paginate();
|
|
202
|
+
// const paginator = await table.scan().limit(10).paginate();
|
|
203
|
+
|
|
204
|
+
while (paginator.hasNextPage()) {
|
|
205
|
+
const page = await paginator.getPage();
|
|
206
|
+
console.log(page);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Transaction Operations
|
|
211
|
+
|
|
212
|
+
Two ways to perform transactions:
|
|
213
|
+
|
|
214
|
+
#### Using withTransaction
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
await table.withTransaction(async (trx) => {
|
|
218
|
+
table.put(trex).withTransaction(trx);
|
|
219
|
+
table.put(raptor).withTransaction(trx);
|
|
220
|
+
table.delete(brontoKey).withTransaction(trx);
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### Using TransactionBuilder
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const transaction = new TransactionBuilder();
|
|
228
|
+
|
|
229
|
+
transaction
|
|
230
|
+
.addOperation({
|
|
231
|
+
put: { item: trex }
|
|
232
|
+
})
|
|
233
|
+
.addOperation({
|
|
234
|
+
put: { item: raptor }
|
|
235
|
+
})
|
|
236
|
+
.addOperation({
|
|
237
|
+
delete: { key: brontoKey }
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await table.transactWrite(transaction);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Repository Pattern
|
|
244
|
+
|
|
245
|
+
Create a repository by extending the `BaseRepository` class.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { BaseRepository } from "dyno-table";
|
|
249
|
+
|
|
250
|
+
type DinoRecord = {
|
|
251
|
+
id: string;
|
|
252
|
+
name: string;
|
|
253
|
+
diet: string;
|
|
254
|
+
length: number;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
class DinoRepository extends BaseRepository<DinoRecord> {
|
|
258
|
+
protected createPrimaryKey(data: DinoRecord) {
|
|
259
|
+
return {
|
|
260
|
+
pk: `SPECIES#${data.id}`,
|
|
261
|
+
sk: `PROFILE#${data.id}`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
protected getType() {
|
|
266
|
+
return "DINOSAUR";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Add custom methods
|
|
270
|
+
async findByDiet(diet: string) {
|
|
271
|
+
return this.scan()
|
|
272
|
+
.whereEquals("diet", diet)
|
|
273
|
+
.execute();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async findLargerThan(length: number) {
|
|
277
|
+
return this.scan()
|
|
278
|
+
.whereGreaterThan("length", length)
|
|
279
|
+
.execute();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Repository Operations
|
|
285
|
+
|
|
286
|
+
The repository pattern in dyno-table not only provides a clean abstraction but also ensures data isolation through type-scoping. All operations available on the `Table` class are also available on your repository, but they're automatically scoped to the repository's type.
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
const dinoRepo = new DinoRepository(table);
|
|
290
|
+
|
|
291
|
+
// Query all T-Rexes - automatically includes type="DINOSAUR" condition
|
|
292
|
+
const rexes = await dinoRepo
|
|
293
|
+
.query({ pk: "SPECIES#trex" })
|
|
294
|
+
.execute();
|
|
295
|
+
|
|
296
|
+
// Scan for large carnivores - automatically includes type="DINOSAUR"
|
|
297
|
+
const largeCarnivores = await dinoRepo
|
|
298
|
+
.scan()
|
|
299
|
+
.whereEquals("diet", "Carnivore")
|
|
300
|
+
.whereGreaterThan("length", 30)
|
|
301
|
+
.execute();
|
|
302
|
+
|
|
303
|
+
// Put operation, the type attribute is automatically along with the primary key/secondary key is created
|
|
304
|
+
await dinoRepo.create({
|
|
305
|
+
id: "trex",
|
|
306
|
+
name: "Rex",
|
|
307
|
+
diet: "Carnivore",
|
|
308
|
+
length: 40
|
|
309
|
+
}).execute();
|
|
310
|
+
|
|
311
|
+
// Update operation
|
|
312
|
+
await dinoRepo
|
|
313
|
+
.update({ pk: "SPECIES#trex", sk: "PROFILE#001" })
|
|
314
|
+
.set("diet", "Omnivore")
|
|
315
|
+
.execute();
|
|
316
|
+
|
|
317
|
+
// Delete operation
|
|
318
|
+
await dinoRepo
|
|
319
|
+
.delete({ pk: "SPECIES#trex", sk: "PROFILE#001" })
|
|
320
|
+
.execute();
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
This type-scoping ensures that:
|
|
324
|
+
- Each repository only accesses its own data type
|
|
325
|
+
- Queries automatically include type filtering
|
|
326
|
+
- Put operations automatically include the type attribute
|
|
327
|
+
- Updates and deletes are constrained to the correct type
|
|
328
|
+
|
|
329
|
+
This pattern is particularly useful in single-table designs where multiple entity types share the same table. Each repository provides a type-safe, isolated view of its own data while preventing accidental cross-type operations.
|
|
330
|
+
|
|
331
|
+
## Contributing 🤝
|
|
332
|
+
```bash
|
|
333
|
+
# Installing the dependencies
|
|
334
|
+
pnpm i
|
|
335
|
+
|
|
336
|
+
# Installing the peerDependencies manually
|
|
337
|
+
pnpm i @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Developing
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
docker run -p 8000:8000 amazon/dynamodb-local
|
|
344
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import * as _aws_sdk_lib_dynamodb from '@aws-sdk/lib-dynamodb';
|
|
2
1
|
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
|
|
3
|
-
import { z } from 'zod';
|
|
4
2
|
|
|
5
3
|
interface ExpressionAttributes {
|
|
6
4
|
names?: Record<string, string>;
|
|
@@ -94,6 +92,13 @@ interface DynamoDeleteOperation {
|
|
|
94
92
|
key: PrimaryKeyWithoutExpression;
|
|
95
93
|
condition?: DynamoExpression;
|
|
96
94
|
}
|
|
95
|
+
interface DynamoScanOperation {
|
|
96
|
+
type: "scan";
|
|
97
|
+
filter?: DynamoExpression;
|
|
98
|
+
limit?: number;
|
|
99
|
+
pageKey?: Record<string, unknown>;
|
|
100
|
+
indexName?: string;
|
|
101
|
+
}
|
|
97
102
|
interface DynamoBatchWriteOperation {
|
|
98
103
|
type: "batchWrite";
|
|
99
104
|
operations: DynamoBatchWriteItem[];
|
|
@@ -116,7 +121,7 @@ interface DynamoTransactOperation {
|
|
|
116
121
|
};
|
|
117
122
|
}>;
|
|
118
123
|
}
|
|
119
|
-
type DynamoOperation = DynamoPutOperation | DynamoUpdateOperation | DynamoQueryOperation | DynamoDeleteOperation | DynamoBatchWriteOperation | DynamoTransactOperation;
|
|
124
|
+
type DynamoOperation = DynamoPutOperation | DynamoUpdateOperation | DynamoQueryOperation | DynamoDeleteOperation | DynamoBatchWriteOperation | DynamoTransactOperation | DynamoScanOperation;
|
|
120
125
|
type PrimaryKeyWithoutExpression = {
|
|
121
126
|
pk: string;
|
|
122
127
|
sk?: string;
|
|
@@ -129,72 +134,121 @@ type BatchWriteOperation = {
|
|
|
129
134
|
key: PrimaryKeyWithoutExpression;
|
|
130
135
|
};
|
|
131
136
|
|
|
132
|
-
|
|
137
|
+
interface DynamoRecord {
|
|
138
|
+
[key: string]: unknown;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
type StringKeys<T> = Extract<keyof T, string>;
|
|
142
|
+
/**
|
|
143
|
+
* Base builder class for DynamoDB operations that supports condition expressions
|
|
144
|
+
* @see https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html
|
|
145
|
+
*/
|
|
146
|
+
declare abstract class OperationBuilder<T extends DynamoRecord, TOperation extends DynamoOperation> {
|
|
133
147
|
protected expressionBuilder: IExpressionBuilder;
|
|
134
148
|
protected conditions: Array<{
|
|
135
|
-
field:
|
|
149
|
+
field: keyof T;
|
|
136
150
|
operator: ConditionOperator;
|
|
137
151
|
value?: unknown;
|
|
138
152
|
}>;
|
|
139
153
|
constructor(expressionBuilder: IExpressionBuilder);
|
|
140
|
-
where(field:
|
|
141
|
-
whereExists(field:
|
|
142
|
-
whereNotExists(field:
|
|
143
|
-
whereEquals(field:
|
|
144
|
-
whereBetween(field:
|
|
145
|
-
whereIn(field:
|
|
154
|
+
where<K extends keyof T>(field: K, operator: FilterOperator, value: T[K] | T[K][]): this;
|
|
155
|
+
whereExists<K extends StringKeys<T>>(field: K): this;
|
|
156
|
+
whereNotExists<K extends keyof T>(field: K): this;
|
|
157
|
+
whereEquals<K extends keyof T>(field: K, value: T[K]): this;
|
|
158
|
+
whereBetween<K extends keyof T>(field: K, start: T[K], end: T[K]): this;
|
|
159
|
+
whereIn<K extends keyof T>(field: K, values: T[K][]): this;
|
|
160
|
+
whereLessThan<K extends keyof T>(field: K, value: T[K]): this;
|
|
161
|
+
whereLessThanOrEqual<K extends keyof T>(field: K, value: T[K]): this;
|
|
162
|
+
whereGreaterThan<K extends keyof T>(field: K, value: T[K]): this;
|
|
163
|
+
whereGreaterThanOrEqual<K extends keyof T>(field: K, value: T[K]): this;
|
|
164
|
+
whereNotEqual<K extends keyof T>(field: K, value: T[K]): this;
|
|
165
|
+
whereBeginsWith<K extends keyof T>(field: K, value: T[K]): this;
|
|
166
|
+
whereContains<K extends keyof T>(field: K, value: T[K]): this;
|
|
167
|
+
whereNotContains<K extends keyof T>(field: K, value: T[K]): this;
|
|
168
|
+
whereAttributeType<K extends keyof T>(field: K, value: "S" | "SS" | "N" | "NS" | "B" | "BS" | "BOOL" | "NULL" | "M" | "L"): this;
|
|
169
|
+
whereSize<K extends keyof T>(field: K, value: T[K]): this;
|
|
146
170
|
protected buildConditionExpression(): ExpressionResult;
|
|
147
|
-
abstract build():
|
|
171
|
+
abstract build(): TOperation;
|
|
148
172
|
}
|
|
149
173
|
|
|
150
|
-
declare class PutBuilder extends OperationBuilder<DynamoPutOperation> {
|
|
151
|
-
private readonly item;
|
|
174
|
+
declare class PutBuilder<T extends DynamoRecord> extends OperationBuilder<T, DynamoPutOperation> {
|
|
152
175
|
private readonly onBuild;
|
|
153
|
-
|
|
176
|
+
private item;
|
|
177
|
+
constructor(item: T, expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoPutOperation) => Promise<T>);
|
|
178
|
+
set<K extends keyof T>(field: K, value: T[K]): this;
|
|
179
|
+
setMany(attributes: Partial<T>): this;
|
|
154
180
|
build(): DynamoPutOperation;
|
|
155
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Runs the put operation to insert the provided attributes into the table.
|
|
183
|
+
*
|
|
184
|
+
* @returns The provided attributes. This does not load the model from the DB after insert
|
|
185
|
+
*/
|
|
186
|
+
execute(): Promise<T>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface DynamoQueryResponse {
|
|
190
|
+
Items?: Record<string, unknown>[];
|
|
191
|
+
Count?: number;
|
|
192
|
+
ScannedCount?: number;
|
|
193
|
+
LastEvaluatedKey?: Record<string, unknown>;
|
|
156
194
|
}
|
|
157
195
|
|
|
158
|
-
declare class QueryBuilder extends OperationBuilder<DynamoQueryOperation> {
|
|
196
|
+
declare class QueryBuilder<T extends DynamoRecord> extends OperationBuilder<T, DynamoQueryOperation> {
|
|
159
197
|
private readonly key;
|
|
160
198
|
private readonly indexConfig;
|
|
161
199
|
private readonly onBuild;
|
|
162
200
|
private limitValue?;
|
|
163
201
|
private indexNameValue?;
|
|
164
|
-
constructor(key: PrimaryKey, indexConfig: TableIndexConfig, expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoQueryOperation) => Promise<
|
|
165
|
-
Items?: Record<string, unknown>[];
|
|
166
|
-
Count?: number;
|
|
167
|
-
ScannedCount?: number;
|
|
168
|
-
LastEvaluatedKey?: Record<string, unknown>;
|
|
169
|
-
}>);
|
|
202
|
+
constructor(key: PrimaryKey, indexConfig: TableIndexConfig, expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoQueryOperation) => Promise<DynamoQueryResponse>);
|
|
170
203
|
limit(value: number): this;
|
|
171
204
|
useIndex(indexName: string): this;
|
|
172
205
|
build(): DynamoQueryOperation;
|
|
173
|
-
execute(): Promise<
|
|
174
|
-
Items?: Record<string, unknown>[];
|
|
175
|
-
Count?: number;
|
|
176
|
-
ScannedCount?: number;
|
|
177
|
-
LastEvaluatedKey?: Record<string, unknown>;
|
|
178
|
-
}>;
|
|
206
|
+
execute(): Promise<DynamoQueryResponse>;
|
|
179
207
|
}
|
|
180
208
|
|
|
181
|
-
declare class UpdateBuilder extends OperationBuilder<DynamoUpdateOperation> {
|
|
209
|
+
declare class UpdateBuilder<T extends DynamoRecord> extends OperationBuilder<T, DynamoUpdateOperation> {
|
|
182
210
|
private readonly key;
|
|
183
211
|
private readonly onBuild;
|
|
184
212
|
private updates;
|
|
185
213
|
constructor(key: PrimaryKeyWithoutExpression, expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoUpdateOperation) => Promise<{
|
|
186
|
-
Attributes?:
|
|
214
|
+
Attributes?: T;
|
|
187
215
|
}>);
|
|
188
|
-
set(field:
|
|
189
|
-
setMany(
|
|
190
|
-
remove(...fields:
|
|
191
|
-
increment(field:
|
|
216
|
+
set<K extends keyof T>(field: K, value: T[K]): this;
|
|
217
|
+
setMany(attributes: Partial<T>): this;
|
|
218
|
+
remove(...fields: Array<keyof T>): this;
|
|
219
|
+
increment<K extends keyof T>(field: K, by?: number): this;
|
|
192
220
|
build(): DynamoUpdateOperation;
|
|
193
221
|
execute(): Promise<{
|
|
194
|
-
Attributes?:
|
|
222
|
+
Attributes?: T;
|
|
195
223
|
}>;
|
|
196
224
|
}
|
|
197
225
|
|
|
226
|
+
declare class ScanBuilder<T extends DynamoRecord> extends OperationBuilder<T, DynamoScanOperation> {
|
|
227
|
+
private readonly onBuild;
|
|
228
|
+
private limitValue?;
|
|
229
|
+
private indexNameValue?;
|
|
230
|
+
private pageKeyValue?;
|
|
231
|
+
constructor(expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoScanOperation) => Promise<DynamoQueryResponse>);
|
|
232
|
+
limit(value: number): this;
|
|
233
|
+
useIndex(indexName: string): this;
|
|
234
|
+
startKey(key: Record<string, unknown>): this;
|
|
235
|
+
build(): {
|
|
236
|
+
type: "scan";
|
|
237
|
+
filter: {
|
|
238
|
+
expression: string;
|
|
239
|
+
names: Record<string, string> | undefined;
|
|
240
|
+
values: Record<string, unknown> | undefined;
|
|
241
|
+
} | undefined;
|
|
242
|
+
limit: number | undefined;
|
|
243
|
+
pageKey: Record<string, unknown> | undefined;
|
|
244
|
+
indexName: string | undefined;
|
|
245
|
+
};
|
|
246
|
+
execute(): Promise<DynamoQueryResponse>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
type IndexConfig = Record<string, TableIndexConfig> & {
|
|
250
|
+
primary: TableIndexConfig;
|
|
251
|
+
};
|
|
198
252
|
declare class Table {
|
|
199
253
|
private readonly dynamoService;
|
|
200
254
|
private readonly expressionBuilder;
|
|
@@ -202,22 +256,18 @@ declare class Table {
|
|
|
202
256
|
constructor({ client, tableName, tableIndexes, expressionBuilder, }: {
|
|
203
257
|
client: DynamoDBDocument;
|
|
204
258
|
tableName: string;
|
|
205
|
-
tableIndexes:
|
|
259
|
+
tableIndexes: IndexConfig;
|
|
206
260
|
expressionBuilder?: ExpressionBuilder;
|
|
207
261
|
});
|
|
208
262
|
getIndexConfig(indexName?: string): TableIndexConfig;
|
|
209
|
-
put(item:
|
|
210
|
-
update(key: PrimaryKeyWithoutExpression, data?:
|
|
211
|
-
query(key: PrimaryKey): QueryBuilder
|
|
263
|
+
put<T extends DynamoRecord>(item: T): PutBuilder<T>;
|
|
264
|
+
update<T extends DynamoRecord>(key: PrimaryKeyWithoutExpression, data?: Partial<T>): UpdateBuilder<T>;
|
|
265
|
+
query<T extends DynamoRecord>(key: PrimaryKey): QueryBuilder<T>;
|
|
212
266
|
get(key: PrimaryKeyWithoutExpression, options?: {
|
|
213
267
|
indexName?: string;
|
|
214
268
|
}): Promise<Record<string, any> | undefined>;
|
|
215
269
|
delete(key: PrimaryKeyWithoutExpression): Promise<unknown>;
|
|
216
|
-
scan
|
|
217
|
-
limit?: number;
|
|
218
|
-
pageKey?: Record<string, unknown>;
|
|
219
|
-
indexName?: string;
|
|
220
|
-
}): Promise<_aws_sdk_lib_dynamodb.ScanCommandOutput>;
|
|
270
|
+
scan<T extends DynamoRecord>(): ScanBuilder<T>;
|
|
221
271
|
batchWrite(operations: BatchWriteOperation[]): Promise<unknown>;
|
|
222
272
|
transactWrite(operations: Array<{
|
|
223
273
|
put?: {
|
|
@@ -255,29 +305,100 @@ declare class Table {
|
|
|
255
305
|
private validateKey;
|
|
256
306
|
}
|
|
257
307
|
|
|
258
|
-
|
|
259
|
-
declare abstract class BaseRepository<TSchema extends z.ZodType> {
|
|
308
|
+
declare abstract class BaseRepository<TData extends DynamoRecord> {
|
|
260
309
|
protected readonly table: Table;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
310
|
+
constructor(table: Table);
|
|
311
|
+
/**
|
|
312
|
+
* Templates out the primary key for the record, it is consumed for create, put, update and delete actions
|
|
313
|
+
*
|
|
314
|
+
* Unfortunately, TypeScript is not inferring the TData type when implmenting this method in a subclass.
|
|
315
|
+
* https://github.com/microsoft/TypeScript/issues/32082
|
|
316
|
+
*/
|
|
317
|
+
protected abstract createPrimaryKey(data: TData): PrimaryKeyWithoutExpression;
|
|
268
318
|
/**
|
|
269
319
|
* Default attribute applied to ALL records that get stored in DDB
|
|
320
|
+
* Defines the type of the record.
|
|
321
|
+
* For example User, Post, Comment etc.
|
|
322
|
+
*
|
|
323
|
+
* This helps to ensure isolation of models when using a single table design
|
|
324
|
+
* @returns The type of the record as a string.
|
|
270
325
|
*/
|
|
271
326
|
protected abstract getType(): string;
|
|
327
|
+
/**
|
|
328
|
+
* Used to define the attribute name for the type.
|
|
329
|
+
* @returns The type attribute name as a string.
|
|
330
|
+
*/
|
|
272
331
|
protected abstract getTypeAttributeName(): string;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Hook method called before inserting a record.
|
|
334
|
+
* Subclasses can override this method to modify the data before insertion.
|
|
335
|
+
* @param data - The record data.
|
|
336
|
+
* @returns The modified record data.
|
|
337
|
+
*/
|
|
338
|
+
protected beforeInsert(data: TData): TData;
|
|
339
|
+
/**
|
|
340
|
+
* Hook method called before updating a record.
|
|
341
|
+
* Subclasses can override this method to modify the data before updating.
|
|
342
|
+
* @param data - The partial record data to be updated.
|
|
343
|
+
* @returns The modified partial record data.
|
|
344
|
+
*/
|
|
345
|
+
protected beforeUpdate(data: Partial<TData>): Partial<TData>;
|
|
346
|
+
/**
|
|
347
|
+
* Checks if a record exists in the table.
|
|
348
|
+
* @param key - The primary key of the record.
|
|
349
|
+
* @returns A promise that resolves to true if the record exists, false otherwise.
|
|
350
|
+
*/
|
|
351
|
+
exists(key: PrimaryKeyWithoutExpression): Promise<boolean>;
|
|
352
|
+
/**
|
|
353
|
+
* Type guard to check if the value is a primary key.
|
|
354
|
+
* @param value - The value to check.
|
|
355
|
+
* @returns True if the value is a primary key, false otherwise.
|
|
356
|
+
*/
|
|
357
|
+
protected isPrimaryKey(value: PrimaryKeyWithoutExpression | TData): value is PrimaryKeyWithoutExpression;
|
|
358
|
+
/**
|
|
359
|
+
* Creates a new record in the table.
|
|
360
|
+
* @param data - The record data.
|
|
361
|
+
* @returns A PutBuilder instance to execute the put operation.
|
|
362
|
+
*/
|
|
363
|
+
create(data: TData): PutBuilder<TData>;
|
|
364
|
+
/**
|
|
365
|
+
* Updates an existing record in the table.
|
|
366
|
+
* @param key - The primary key of the record.
|
|
367
|
+
* @param updates - The partial record data to be updated.
|
|
368
|
+
* @returns A promise that resolves to the updated record or null if the record does not exist.
|
|
369
|
+
*/
|
|
370
|
+
update(key: PrimaryKeyWithoutExpression, updates: Partial<TData>): Promise<TData | null>;
|
|
371
|
+
/**
|
|
372
|
+
* Upserts (inserts or updates) a record in the table.
|
|
373
|
+
* @param data - The record data.
|
|
374
|
+
* @returns A PutBuilder instance to execute the put operation.
|
|
375
|
+
*/
|
|
376
|
+
upsert(data: TData): PutBuilder<TData>;
|
|
377
|
+
/**
|
|
378
|
+
* Deletes a record from the table.
|
|
379
|
+
* @param keyOrDTO - The primary key or the record data.
|
|
380
|
+
* @returns A promise that resolves when the record is deleted.
|
|
381
|
+
*/
|
|
382
|
+
delete(keyOrDTO: PrimaryKeyWithoutExpression | TData): Promise<void>;
|
|
383
|
+
/**
|
|
384
|
+
* Finds a single record by its primary key.
|
|
385
|
+
* @param key - The primary key of the record.
|
|
386
|
+
* @returns A promise that resolves to the record or null if the record does not exist.
|
|
387
|
+
*/
|
|
388
|
+
findOne(key: PrimaryKey): Promise<TData | null>;
|
|
389
|
+
/**
|
|
390
|
+
* Finds a single record by its primary key or throws an error if the record does not exist.
|
|
391
|
+
* @param key - The primary key of the record.
|
|
392
|
+
* @returns A promise that resolves to the record.
|
|
393
|
+
* @throws An error if the record does not exist.
|
|
394
|
+
*/
|
|
395
|
+
findOrFail(key: PrimaryKeyWithoutExpression): Promise<TData>;
|
|
396
|
+
/**
|
|
397
|
+
* Creates a query builder for querying records by their primary key.
|
|
398
|
+
* @param key - The primary key of the record.
|
|
399
|
+
* @returns A QueryBuilder instance to build and execute the query.
|
|
400
|
+
*/
|
|
401
|
+
query(key: PrimaryKey): QueryBuilder<TData>;
|
|
281
402
|
}
|
|
282
403
|
|
|
283
404
|
interface RetryStrategy {
|