@lafken/dynamo 0.10.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/LICENCE +21 -0
- package/README.md +503 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +19 -0
- package/lib/main/index.d.ts +1 -0
- package/lib/main/index.js +17 -0
- package/lib/main/table/index.d.ts +2 -0
- package/lib/main/table/index.js +18 -0
- package/lib/main/table/table.d.ts +107 -0
- package/lib/main/table/table.js +141 -0
- package/lib/main/table/table.types.d.ts +368 -0
- package/lib/main/table/table.types.js +15 -0
- package/lib/resolver/index.d.ts +1 -0
- package/lib/resolver/index.js +17 -0
- package/lib/resolver/resolver.d.ts +12 -0
- package/lib/resolver/resolver.js +48 -0
- package/lib/resolver/resolver.types.d.ts +13 -0
- package/lib/resolver/resolver.types.js +2 -0
- package/lib/resolver/table/external/external.d.ts +14 -0
- package/lib/resolver/table/external/external.js +15 -0
- package/lib/resolver/table/internal/internal.d.ts +19 -0
- package/lib/resolver/table/internal/internal.js +224 -0
- package/lib/resolver/table/table.types.d.ts +4 -0
- package/lib/resolver/table/table.types.js +2 -0
- package/lib/service/client/client.d.ts +3 -0
- package/lib/service/client/client.js +10 -0
- package/lib/service/index.d.ts +3 -0
- package/lib/service/index.js +19 -0
- package/lib/service/query-builder/base/base.d.ts +19 -0
- package/lib/service/query-builder/base/base.js +151 -0
- package/lib/service/query-builder/base/base.types.d.ts +37 -0
- package/lib/service/query-builder/base/base.types.js +2 -0
- package/lib/service/query-builder/base/base.utils.d.ts +22 -0
- package/lib/service/query-builder/base/base.utils.js +89 -0
- package/lib/service/query-builder/batch-write/batch-write.d.ts +15 -0
- package/lib/service/query-builder/batch-write/batch-write.js +51 -0
- package/lib/service/query-builder/batch-write/batch-write.types.d.ts +9 -0
- package/lib/service/query-builder/batch-write/batch-write.types.js +2 -0
- package/lib/service/query-builder/bulk-create/bulk-create.d.ts +6 -0
- package/lib/service/query-builder/bulk-create/bulk-create.js +26 -0
- package/lib/service/query-builder/bulk-create/bulk-create.types.d.ts +4 -0
- package/lib/service/query-builder/bulk-create/bulk-create.types.js +2 -0
- package/lib/service/query-builder/bulk-delete/bulk-delete.d.ts +6 -0
- package/lib/service/query-builder/bulk-delete/bulk-delete.js +27 -0
- package/lib/service/query-builder/bulk-delete/bulk-delete.types.d.ts +7 -0
- package/lib/service/query-builder/bulk-delete/bulk-delete.types.js +2 -0
- package/lib/service/query-builder/create/create.d.ts +6 -0
- package/lib/service/query-builder/create/create.js +22 -0
- package/lib/service/query-builder/create/create.types.d.ts +6 -0
- package/lib/service/query-builder/create/create.types.js +2 -0
- package/lib/service/query-builder/delete/delete.d.ts +13 -0
- package/lib/service/query-builder/delete/delete.js +33 -0
- package/lib/service/query-builder/delete/delete.types.d.ts +7 -0
- package/lib/service/query-builder/delete/delete.types.js +2 -0
- package/lib/service/query-builder/dynamo-index/dynamo-index.d.ts +15 -0
- package/lib/service/query-builder/dynamo-index/dynamo-index.js +90 -0
- package/lib/service/query-builder/dynamo-index/dynamo-index.types.d.ts +5 -0
- package/lib/service/query-builder/dynamo-index/dynamo-index.types.js +2 -0
- package/lib/service/query-builder/find/find.d.ts +13 -0
- package/lib/service/query-builder/find/find.js +63 -0
- package/lib/service/query-builder/find/find.types.d.ts +8 -0
- package/lib/service/query-builder/find/find.types.js +2 -0
- package/lib/service/query-builder/find-all/find-all.d.ts +10 -0
- package/lib/service/query-builder/find-all/find-all.js +19 -0
- package/lib/service/query-builder/find-one/find-one.d.ts +9 -0
- package/lib/service/query-builder/find-one/find-one.js +20 -0
- package/lib/service/query-builder/index.d.ts +1 -0
- package/lib/service/query-builder/index.js +17 -0
- package/lib/service/query-builder/query-builder.types.d.ts +243 -0
- package/lib/service/query-builder/query-builder.types.js +3 -0
- package/lib/service/query-builder/scan/scan.d.ts +15 -0
- package/lib/service/query-builder/scan/scan.js +57 -0
- package/lib/service/query-builder/scan/scan.types.d.ts +6 -0
- package/lib/service/query-builder/scan/scan.types.js +2 -0
- package/lib/service/query-builder/update/update.d.ts +15 -0
- package/lib/service/query-builder/update/update.js +101 -0
- package/lib/service/query-builder/update/update.types.d.ts +6 -0
- package/lib/service/query-builder/update/update.types.js +2 -0
- package/lib/service/query-builder/update/update.utils.d.ts +11 -0
- package/lib/service/query-builder/update/update.utils.js +22 -0
- package/lib/service/query-builder/upsert/upsert.d.ts +14 -0
- package/lib/service/query-builder/upsert/upsert.js +40 -0
- package/lib/service/query-builder/upsert/upsert.types.d.ts +7 -0
- package/lib/service/query-builder/upsert/upsert.types.js +2 -0
- package/lib/service/repository/index.d.ts +3 -0
- package/lib/service/repository/index.js +19 -0
- package/lib/service/repository/repository.d.ts +3 -0
- package/lib/service/repository/repository.js +89 -0
- package/lib/service/repository/repository.types.d.ts +23 -0
- package/lib/service/repository/repository.types.js +2 -0
- package/lib/service/repository/repository.utils.d.ts +9 -0
- package/lib/service/repository/repository.utils.js +17 -0
- package/lib/service/transaction/index.d.ts +2 -0
- package/lib/service/transaction/index.js +18 -0
- package/lib/service/transaction/transaction.d.ts +4 -0
- package/lib/service/transaction/transaction.js +38 -0
- package/lib/service/transaction/transaction.types.d.ts +5 -0
- package/lib/service/transaction/transaction.types.js +2 -0
- package/package.json +99 -0
package/LICENCE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aníbal Emilio Jorquera Cornejo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# @lafken/dynamo
|
|
2
|
+
|
|
3
|
+
Define and manage DynamoDB tables using TypeScript decorators. `@lafken/dynamo` lets you declare table schemas, indexes, streams, and TTL directly in your classes — and provides a type-safe repository for performing operations at runtime.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @lafken/dynamo
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
Define a table class with `@Table`, register it in the `DynamoResolver`, and use `createRepository` to interact with it:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { createApp } from '@lafken/main';
|
|
17
|
+
import { DynamoResolver } from '@lafken/dynamo/resolver';
|
|
18
|
+
import { Table, PartitionKey, SortKey, Field, type PrimaryPartition } from '@lafken/dynamo/main';
|
|
19
|
+
import { createRepository } from '@lafken/dynamo/service';
|
|
20
|
+
|
|
21
|
+
// 1. Define the table schema
|
|
22
|
+
@Table({ name: 'contacts' })
|
|
23
|
+
export class Contact {
|
|
24
|
+
@PartitionKey(String)
|
|
25
|
+
email: PrimaryPartition<string>;
|
|
26
|
+
|
|
27
|
+
@SortKey(String)
|
|
28
|
+
company: PrimaryPartition<string>;
|
|
29
|
+
|
|
30
|
+
@Field()
|
|
31
|
+
name: string;
|
|
32
|
+
|
|
33
|
+
@Field()
|
|
34
|
+
age: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Create a repository for runtime operations
|
|
38
|
+
export const contactRepository = createRepository(Contact);
|
|
39
|
+
|
|
40
|
+
// 3. Register the table in the resolver
|
|
41
|
+
createApp({
|
|
42
|
+
name: 'my-app',
|
|
43
|
+
resolvers: [
|
|
44
|
+
new DynamoResolver([Contact]),
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
### Defining a Table
|
|
52
|
+
|
|
53
|
+
Use the `@Table` decorator on a class to declare a DynamoDB table. Each property decorated with `@PartitionKey`, `@SortKey`, or `@Field` becomes an attribute in the table schema.
|
|
54
|
+
|
|
55
|
+
Every table requires exactly one `@PartitionKey`. A `@SortKey` is optional and creates a composite primary key. Both accept `String` or `Number` as the key type.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { Table, PartitionKey, SortKey, Field, type PrimaryPartition } from '@lafken/dynamo/main';
|
|
59
|
+
|
|
60
|
+
@Table({ name: 'events' })
|
|
61
|
+
export class EventLog {
|
|
62
|
+
@PartitionKey(String)
|
|
63
|
+
source: PrimaryPartition<string>;
|
|
64
|
+
|
|
65
|
+
@SortKey(Number)
|
|
66
|
+
timestamp: PrimaryPartition<number>;
|
|
67
|
+
|
|
68
|
+
@Field()
|
|
69
|
+
payload: string;
|
|
70
|
+
|
|
71
|
+
@Field({ type: Number })
|
|
72
|
+
severity: number;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The `@Field` decorator registers a regular attribute. Its type is inferred automatically, but can be overridden with the `type` option.
|
|
77
|
+
|
|
78
|
+
### Indexes
|
|
79
|
+
|
|
80
|
+
Secondary indexes enable alternative query patterns. Define them in the `indexes` option of `@Table`.
|
|
81
|
+
|
|
82
|
+
#### Local Secondary Index
|
|
83
|
+
|
|
84
|
+
Shares the same partition key as the table but uses a different sort key:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
@Table({
|
|
88
|
+
name: 'orders',
|
|
89
|
+
indexes: [
|
|
90
|
+
{
|
|
91
|
+
type: 'local',
|
|
92
|
+
name: 'orders_by_total',
|
|
93
|
+
sortKey: 'total',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
})
|
|
97
|
+
export class Order {
|
|
98
|
+
@PartitionKey(String)
|
|
99
|
+
customerId: PrimaryPartition<string>;
|
|
100
|
+
|
|
101
|
+
@SortKey(String)
|
|
102
|
+
orderId: PrimaryPartition<string>;
|
|
103
|
+
|
|
104
|
+
@Field()
|
|
105
|
+
total: number;
|
|
106
|
+
|
|
107
|
+
@Field()
|
|
108
|
+
status: string;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Global Secondary Index
|
|
113
|
+
|
|
114
|
+
Has its own partition key and optional sort key, enabling queries across the entire table:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
@Table({
|
|
118
|
+
name: 'orders',
|
|
119
|
+
indexes: [
|
|
120
|
+
{
|
|
121
|
+
type: 'global',
|
|
122
|
+
name: 'orders_by_status',
|
|
123
|
+
partitionKey: 'status',
|
|
124
|
+
sortKey: 'total',
|
|
125
|
+
projection: ['customerId', 'orderId'],
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
})
|
|
129
|
+
export class Order {
|
|
130
|
+
@PartitionKey(String)
|
|
131
|
+
customerId: PrimaryPartition<string>;
|
|
132
|
+
|
|
133
|
+
@SortKey(String)
|
|
134
|
+
orderId: PrimaryPartition<string>;
|
|
135
|
+
|
|
136
|
+
@Field()
|
|
137
|
+
total: number;
|
|
138
|
+
|
|
139
|
+
@Field()
|
|
140
|
+
status: string;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Use `projection` to control which attributes are included in the index. Pass an array of field names or `'ALL'` to project every attribute.
|
|
145
|
+
|
|
146
|
+
### TTL (Time to Live)
|
|
147
|
+
|
|
148
|
+
Enable automatic item expiration by specifying the `ttl` option with the name of a numeric field. DynamoDB will delete items whose TTL value (Unix timestamp in seconds) has passed:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
@Table({
|
|
152
|
+
name: 'sessions',
|
|
153
|
+
ttl: 'expiresAt',
|
|
154
|
+
})
|
|
155
|
+
export class Session {
|
|
156
|
+
@PartitionKey(String)
|
|
157
|
+
sessionId: PrimaryPartition<string>;
|
|
158
|
+
|
|
159
|
+
@Field()
|
|
160
|
+
userId: string;
|
|
161
|
+
|
|
162
|
+
@Field()
|
|
163
|
+
expiresAt: number;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Streams
|
|
168
|
+
|
|
169
|
+
Enable a DynamoDB Stream to capture item-level changes. When enabled, the stream is automatically connected to EventBridge via EventBridge Pipes, allowing other services to react to table changes in near real-time.
|
|
170
|
+
|
|
171
|
+
Use the `@lafken/event` package to consume and process these stream events.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
@Table({
|
|
175
|
+
name: 'notifications',
|
|
176
|
+
stream: {
|
|
177
|
+
enabled: true,
|
|
178
|
+
type: 'NEW_AND_OLD_IMAGES',
|
|
179
|
+
batchSize: 10,
|
|
180
|
+
maximumBatchingWindowInSeconds: 5,
|
|
181
|
+
},
|
|
182
|
+
})
|
|
183
|
+
export class Notification {
|
|
184
|
+
@PartitionKey(String)
|
|
185
|
+
id: PrimaryPartition<string>;
|
|
186
|
+
|
|
187
|
+
@Field()
|
|
188
|
+
channel: string;
|
|
189
|
+
|
|
190
|
+
@Field()
|
|
191
|
+
message: string;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Stream Filters
|
|
196
|
+
|
|
197
|
+
Apply filters to process only specific change events, reducing unnecessary invocations:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
@Table({
|
|
201
|
+
name: 'notifications',
|
|
202
|
+
stream: {
|
|
203
|
+
enabled: true,
|
|
204
|
+
type: 'NEW_IMAGE',
|
|
205
|
+
filters: {
|
|
206
|
+
eventName: ['INSERT'],
|
|
207
|
+
newImage: {
|
|
208
|
+
channel: ['email', 'sms'],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
})
|
|
213
|
+
export class Notification {
|
|
214
|
+
@PartitionKey(String)
|
|
215
|
+
id: PrimaryPartition<string>;
|
|
216
|
+
|
|
217
|
+
@Field()
|
|
218
|
+
channel: string;
|
|
219
|
+
|
|
220
|
+
@Field()
|
|
221
|
+
message: string;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Available filter criteria:
|
|
226
|
+
|
|
227
|
+
| Filter | Description |
|
|
228
|
+
| ----------- | ------------------------------------------------------ |
|
|
229
|
+
| `eventName` | Event types: `'INSERT'`, `'MODIFY'`, `'REMOVE'` |
|
|
230
|
+
| `keys` | Filter by partition/sort key values |
|
|
231
|
+
| `newImage` | Conditions on the new item (after INSERT or MODIFY) |
|
|
232
|
+
| `oldImage` | Conditions on the old item (before MODIFY or REMOVE) |
|
|
233
|
+
|
|
234
|
+
### Billing Mode
|
|
235
|
+
|
|
236
|
+
Tables default to `pay_per_request` (on-demand). For provisioned throughput, set `billingMode` and specify capacity units:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
@Table({
|
|
240
|
+
name: 'high-throughput',
|
|
241
|
+
billingMode: 'provisioned',
|
|
242
|
+
readCapacity: 100,
|
|
243
|
+
writeCapacity: 50,
|
|
244
|
+
})
|
|
245
|
+
export class HighThroughputTable {
|
|
246
|
+
@PartitionKey(String)
|
|
247
|
+
id: PrimaryPartition<string>;
|
|
248
|
+
|
|
249
|
+
@Field()
|
|
250
|
+
data: string;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Global Tables (Replicas)
|
|
255
|
+
|
|
256
|
+
Create multi-region replicas for global applications using the `replica` option:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
@Table({
|
|
260
|
+
name: 'global-config',
|
|
261
|
+
replica: [
|
|
262
|
+
{ regionName: 'eu-west-1', consistenceMode: 'EVENTUAL' },
|
|
263
|
+
{ regionName: 'ap-southeast-1' },
|
|
264
|
+
],
|
|
265
|
+
})
|
|
266
|
+
export class GlobalConfig {
|
|
267
|
+
@PartitionKey(String)
|
|
268
|
+
key: PrimaryPartition<string>;
|
|
269
|
+
|
|
270
|
+
@Field()
|
|
271
|
+
value: string;
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Repository
|
|
276
|
+
|
|
277
|
+
`createRepository` provides a type-safe API for DynamoDB operations at runtime. All methods return a builder that is executed by calling `.exec()`.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { createRepository } from '@lafken/dynamo/service';
|
|
281
|
+
|
|
282
|
+
export const contactRepository = createRepository(Contact);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### Create
|
|
286
|
+
|
|
287
|
+
Insert a new item into the table:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
await contactRepository
|
|
291
|
+
.create({
|
|
292
|
+
email: 'jane@example.com',
|
|
293
|
+
company: 'Acme',
|
|
294
|
+
name: 'Jane Doe',
|
|
295
|
+
age: 30,
|
|
296
|
+
})
|
|
297
|
+
.exec();
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### Find All
|
|
301
|
+
|
|
302
|
+
Query items using a key condition. Supports filtering, projections, pagination, and sort direction:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
const result = await contactRepository
|
|
306
|
+
.findAll({
|
|
307
|
+
keyCondition: {
|
|
308
|
+
partition: { email: 'jane@example.com' },
|
|
309
|
+
},
|
|
310
|
+
filter: {
|
|
311
|
+
age: { greaterThan: 25 },
|
|
312
|
+
},
|
|
313
|
+
projection: ['name', 'company'],
|
|
314
|
+
sortDirection: 'desc',
|
|
315
|
+
limit: 10,
|
|
316
|
+
})
|
|
317
|
+
.exec();
|
|
318
|
+
|
|
319
|
+
// result.data → matched items
|
|
320
|
+
// result.cursor → pagination cursor for the next page
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### Find One
|
|
324
|
+
|
|
325
|
+
Retrieve a single item matching a key condition:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
const item = await contactRepository
|
|
329
|
+
.findOne({
|
|
330
|
+
keyCondition: {
|
|
331
|
+
partition: { email: 'jane@example.com' },
|
|
332
|
+
sort: { company: 'Acme' },
|
|
333
|
+
},
|
|
334
|
+
})
|
|
335
|
+
.exec();
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### Scan
|
|
339
|
+
|
|
340
|
+
Scan the entire table with optional filters:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
const all = await contactRepository
|
|
344
|
+
.scan({
|
|
345
|
+
filter: {
|
|
346
|
+
age: { greaterThan: 18 },
|
|
347
|
+
},
|
|
348
|
+
limit: 50,
|
|
349
|
+
})
|
|
350
|
+
.exec();
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### Update
|
|
354
|
+
|
|
355
|
+
Update specific attributes of an existing item:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
await contactRepository
|
|
359
|
+
.update({
|
|
360
|
+
keyCondition: {
|
|
361
|
+
email: 'jane@example.com',
|
|
362
|
+
company: 'Acme',
|
|
363
|
+
},
|
|
364
|
+
setValues: {
|
|
365
|
+
age: 31,
|
|
366
|
+
},
|
|
367
|
+
replaceValues: {
|
|
368
|
+
name: 'Jane Smith',
|
|
369
|
+
},
|
|
370
|
+
})
|
|
371
|
+
.exec();
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Update supports three operation types:
|
|
375
|
+
|
|
376
|
+
| Operation | Description |
|
|
377
|
+
| --------------- | ---------------------------------------------------------------- |
|
|
378
|
+
| `setValues` | Update specific nested fields without overwriting the object |
|
|
379
|
+
| `replaceValues` | Replace entire attribute values |
|
|
380
|
+
| `removeValues` | Remove attributes from the item |
|
|
381
|
+
|
|
382
|
+
Numeric fields support `incrementValue` and `decrementValue`, and any field supports `ifNotExistValue` for conditional defaults:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
await contactRepository
|
|
386
|
+
.update({
|
|
387
|
+
keyCondition: { email: 'jane@example.com', company: 'Acme' },
|
|
388
|
+
setValues: {
|
|
389
|
+
age: { incrementValue: 1 },
|
|
390
|
+
},
|
|
391
|
+
})
|
|
392
|
+
.exec();
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### Upsert
|
|
396
|
+
|
|
397
|
+
Insert an item or update it if it already exists:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
await contactRepository
|
|
401
|
+
.upsert({
|
|
402
|
+
email: 'jane@example.com',
|
|
403
|
+
company: 'Acme',
|
|
404
|
+
name: 'Jane Doe',
|
|
405
|
+
age: 30,
|
|
406
|
+
})
|
|
407
|
+
.exec();
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### Delete
|
|
411
|
+
|
|
412
|
+
Remove an item by its primary key:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
await contactRepository
|
|
416
|
+
.delete({
|
|
417
|
+
email: 'jane@example.com',
|
|
418
|
+
company: 'Acme',
|
|
419
|
+
})
|
|
420
|
+
.exec();
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
#### Bulk Operations
|
|
424
|
+
|
|
425
|
+
Create or delete multiple items at once:
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// Bulk create
|
|
429
|
+
await contactRepository
|
|
430
|
+
.bulkCreate([
|
|
431
|
+
{ email: 'a@example.com', company: 'X', name: 'Alice', age: 28 },
|
|
432
|
+
{ email: 'b@example.com', company: 'Y', name: 'Bob', age: 35 },
|
|
433
|
+
])
|
|
434
|
+
.exec();
|
|
435
|
+
|
|
436
|
+
// Bulk delete
|
|
437
|
+
await contactRepository
|
|
438
|
+
.bulkDelete([
|
|
439
|
+
{ email: 'a@example.com', company: 'X' },
|
|
440
|
+
{ email: 'b@example.com', company: 'Y' },
|
|
441
|
+
])
|
|
442
|
+
.exec();
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
#### Querying an Index
|
|
446
|
+
|
|
447
|
+
Specify `indexName` in your query to use a secondary index:
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
const result = await orderRepository
|
|
451
|
+
.findAll({
|
|
452
|
+
keyCondition: {
|
|
453
|
+
partition: { status: 'pending' },
|
|
454
|
+
},
|
|
455
|
+
indexName: 'orders_by_status',
|
|
456
|
+
})
|
|
457
|
+
.exec();
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
If `indexName` is omitted, the repository automatically selects the best matching index based on the key condition attributes.
|
|
461
|
+
|
|
462
|
+
### Transactions
|
|
463
|
+
|
|
464
|
+
Group multiple write operations (create, update, upsert, delete) into an atomic transaction. All operations succeed or fail together:
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
import { transaction } from '@lafken/dynamo/service';
|
|
468
|
+
|
|
469
|
+
await transaction([
|
|
470
|
+
contactRepository.create({
|
|
471
|
+
email: 'new@example.com',
|
|
472
|
+
company: 'Acme',
|
|
473
|
+
name: 'New Contact',
|
|
474
|
+
age: 25,
|
|
475
|
+
}),
|
|
476
|
+
orderRepository.update({
|
|
477
|
+
keyCondition: { customerId: 'cust-1', orderId: 'ord-1' },
|
|
478
|
+
setValues: { status: 'confirmed' },
|
|
479
|
+
}),
|
|
480
|
+
contactRepository.delete({
|
|
481
|
+
email: 'old@example.com',
|
|
482
|
+
company: 'Acme',
|
|
483
|
+
}),
|
|
484
|
+
]);
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
> [!NOTE]
|
|
488
|
+
> Transaction builders are passed without calling `.exec()` — the `transaction` function handles execution internally.
|
|
489
|
+
|
|
490
|
+
### Extending the Table
|
|
491
|
+
|
|
492
|
+
The `DynamoResolver` supports an `extends` function for applying advanced CDKTN configuration to the generated table resource:
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
new DynamoResolver([
|
|
496
|
+
{
|
|
497
|
+
table: Contact,
|
|
498
|
+
extends: ({ table, scope }) => {
|
|
499
|
+
// Add alarms, policies, or any CDKTN construct
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
]);
|
|
503
|
+
```
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./main"), exports);
|
|
18
|
+
__exportStar(require("./resolver"), exports);
|
|
19
|
+
__exportStar(require("./service"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './table';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./table"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./table"), exports);
|
|
18
|
+
__exportStar(require("./table.types"), exports);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type DynamoTableProps, type FieldProps } from './table.types';
|
|
2
|
+
/**
|
|
3
|
+
* Class decorator that registers a class as a DynamoDB table resource.
|
|
4
|
+
*
|
|
5
|
+
* The decorated class represents a DynamoDB table and its schema.
|
|
6
|
+
* Use `@PartitionKey`, `@SortKey`, and `@Field` on the class
|
|
7
|
+
* properties to define the table structure. Options such as billing
|
|
8
|
+
* mode, indexes, streams, TTL, and replicas can be set through the
|
|
9
|
+
* decorator props.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam T - The class being decorated.
|
|
12
|
+
* @param props - Optional table configuration. If omitted, the class
|
|
13
|
+
* name is used as the table name with pay-per-request billing.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* @Table({ stream: { enabled: true, type: 'NEW_AND_OLD_IMAGES' } })
|
|
18
|
+
* export class UserTable {
|
|
19
|
+
* @PartitionKey(String)
|
|
20
|
+
* id: PrimaryPartition<string>;
|
|
21
|
+
*
|
|
22
|
+
* @SortKey(Number)
|
|
23
|
+
* createdAt: PrimaryPartition<string>;
|
|
24
|
+
*
|
|
25
|
+
* @Field()
|
|
26
|
+
* email: string;
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const Table: <T extends Function>(props?: DynamoTableProps<T>) => (constructor: T) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Property decorator that registers a class field as a DynamoDB
|
|
33
|
+
* table attribute.
|
|
34
|
+
*
|
|
35
|
+
* The attribute type is inferred from the property's TypeScript type
|
|
36
|
+
* by default, but can be overridden via the `type` option. Use this
|
|
37
|
+
* decorator for regular attributes that are neither the partition key
|
|
38
|
+
* nor the sort key.
|
|
39
|
+
*
|
|
40
|
+
* @param props - Optional field configuration.
|
|
41
|
+
* @param props.type - Explicit type override (`String`, `Number`, `Boolean`,
|
|
42
|
+
* a class, or an array wrapper).
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* @Table()
|
|
47
|
+
* export class OrderTable {
|
|
48
|
+
* @PartitionKey(String)
|
|
49
|
+
* id: string;
|
|
50
|
+
*
|
|
51
|
+
* @Field()
|
|
52
|
+
* status: string;
|
|
53
|
+
*
|
|
54
|
+
* @Field({ type: Number })
|
|
55
|
+
* total: number;
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare const Field: (props?: FieldProps) => (constructor: any, name: string) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Property decorator that marks a field as the DynamoDB table
|
|
62
|
+
* **partition key** (hash key).
|
|
63
|
+
*
|
|
64
|
+
* Every DynamoDB table requires exactly one partition key. The
|
|
65
|
+
* accepted types are `String` or `Number`.
|
|
66
|
+
*
|
|
67
|
+
* Internally registers the field as a regular `@Field` as well,
|
|
68
|
+
* so there is no need to apply both decorators.
|
|
69
|
+
*
|
|
70
|
+
* @param type - The key type (`String` or `Number`).
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* @Table()
|
|
75
|
+
* export class UserTable {
|
|
76
|
+
* @PartitionKey(String)
|
|
77
|
+
* id: string;
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare const PartitionKey: (type: StringConstructor | NumberConstructor) => (constructor: any, name: string) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Property decorator that marks a field as the DynamoDB table
|
|
84
|
+
* **sort key** (range key).
|
|
85
|
+
*
|
|
86
|
+
* A sort key is optional but, when present, works together with
|
|
87
|
+
* the partition key to form a composite primary key. The accepted
|
|
88
|
+
* types are `String` or `Number`.
|
|
89
|
+
*
|
|
90
|
+
* Internally registers the field as a regular `@Field` as well,
|
|
91
|
+
* so there is no need to apply both decorators.
|
|
92
|
+
*
|
|
93
|
+
* @param type - The key type (`String` or `Number`).
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* @Table()
|
|
98
|
+
* export class EventTable {
|
|
99
|
+
* @PartitionKey(String)
|
|
100
|
+
* userId: string;
|
|
101
|
+
*
|
|
102
|
+
* @SortKey(Number)
|
|
103
|
+
* timestamp: number;
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export declare const SortKey: (type: StringConstructor | NumberConstructor) => (constructor: any, name: string) => void;
|