dyno-table 0.2.0-0 โ 1.0.0
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 +345 -161
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# ๐ฆ dyno-table
|
|
4
|
+
|
|
5
|
+
### **Tame Your DynamoDB Data with Type-Safe Precision**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/dyno-table)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
[](https://aws.amazon.com/dynamodb/)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<p align="center"><strong>A powerful, type-safe abstraction layer for DynamoDB single-table designs</strong><br/>
|
|
15
|
+
<em>Write cleaner, safer, and more maintainable DynamoDB code</em></p>
|
|
5
16
|
|
|
6
17
|
<img src="docs/images/geoff-the-dyno.png" width="400" height="250" alt="Geoff the Dyno" style="float: right; margin-left: 20px; margin-bottom: 20px;">
|
|
7
18
|
|
|
19
|
+
## ๐ฅ Why Developers Choose dyno-table
|
|
20
|
+
|
|
8
21
|
```ts
|
|
9
22
|
// Type-safe dinosaur tracking operations made simple
|
|
10
23
|
await dinoTable
|
|
@@ -18,80 +31,120 @@ await dinoTable
|
|
|
18
31
|
.execute();
|
|
19
32
|
```
|
|
20
33
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
## ๐ Why dyno-table Stands Out From The Pack
|
|
35
|
+
|
|
36
|
+
<table>
|
|
37
|
+
<tr>
|
|
38
|
+
<td width="50%">
|
|
39
|
+
<h3>๐ฆ Dinosaur-sized data made manageable</h3>
|
|
40
|
+
<p>Clean abstraction layer that simplifies complex DynamoDB patterns and makes single-table design approachable</p>
|
|
41
|
+
</td>
|
|
42
|
+
<td width="50%">
|
|
43
|
+
<h3>๐ก๏ธ Extinction-proof type safety</h3>
|
|
44
|
+
<p>Full TypeScript support with strict type checking that catches errors at compile time, not runtime</p>
|
|
45
|
+
</td>
|
|
46
|
+
</tr>
|
|
47
|
+
<tr>
|
|
48
|
+
<td>
|
|
49
|
+
<h3>โก Velociraptor-fast API</h3>
|
|
50
|
+
<p>Intuitive chainable builder pattern for complex operations that feels natural and reduces boilerplate</p>
|
|
51
|
+
</td>
|
|
52
|
+
</tr>
|
|
53
|
+
<tr>
|
|
54
|
+
<td width="50%">
|
|
55
|
+
<h3>๐ Jurassic-scale performance</h3>
|
|
56
|
+
<p>Automatic batch chunking and pagination handling that scales with your data without extra code</p>
|
|
57
|
+
</td>
|
|
58
|
+
<td width="50%">
|
|
59
|
+
<h3>๐งฉ Flexible schema validation</h3>
|
|
60
|
+
<p>Works with your favorite validation libraries including Zod, ArkType, and Valibot</p>
|
|
61
|
+
</td>
|
|
62
|
+
</tr>
|
|
63
|
+
</table>
|
|
30
64
|
|
|
31
65
|
## ๐ Table of Contents
|
|
32
66
|
|
|
33
|
-
- [
|
|
34
|
-
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- [
|
|
42
|
-
- [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
- [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- [
|
|
57
|
-
- [
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- [๐ฎ Future Roadmap](#-future-roadmap)
|
|
78
|
-
- [๐ค Contributing](#-contributing)
|
|
79
|
-
- [๐ฆ Running Examples](#-running-examples)
|
|
67
|
+
- [๐ฆ Installation](#-installation)
|
|
68
|
+
- [๐ Quick Start](#-quick-start)
|
|
69
|
+
- [1. Configure Your Jurassic Table](#1-configure-your-jurassic-table)
|
|
70
|
+
- [2. Perform Type-Safe Dinosaur Operations](#2-perform-type-safe-dinosaur-operations)
|
|
71
|
+
- [๐๏ธ Entity Pattern](#-entity-pattern-with-standard-schema-validators)
|
|
72
|
+
- [Defining Entities](#defining-entities)
|
|
73
|
+
- [Entity Features](#entity-features)
|
|
74
|
+
- [1. Schema Validation](#1-schema-validation)
|
|
75
|
+
- [2. CRUD Operations](#2-crud-operations)
|
|
76
|
+
- [3. Custom Queries](#3-custom-queries)
|
|
77
|
+
- [4. Defining GSI Access Patterns](#4-defining-gsi-access-patterns)
|
|
78
|
+
- [5. Lifecycle Hooks](#5-lifecycle-hooks)
|
|
79
|
+
- [Complete Entity Example](#complete-entity-example)
|
|
80
|
+
- [๐งฉ Advanced Features](#-advanced-features)
|
|
81
|
+
- [Transactional Operations](#transactional-operations)
|
|
82
|
+
- [Batch Processing](#batch-processing)
|
|
83
|
+
- [Pagination Made Simple](#pagination-made-simple)
|
|
84
|
+
- [๐ก๏ธ Type-Safe Query Building](#๏ธ-type-safe-query-building)
|
|
85
|
+
- [Comparison Operators](#comparison-operators)
|
|
86
|
+
- [Logical Operators](#logical-operators)
|
|
87
|
+
- [Query Operations](#query-operations)
|
|
88
|
+
- [Put Operations](#put-operations)
|
|
89
|
+
- [Update Operations](#update-operations)
|
|
90
|
+
- [Condition Operators](#condition-operators)
|
|
91
|
+
- [Multiple Operations](#multiple-operations)
|
|
92
|
+
- [๐ Type Safety Features](#-type-safety-features)
|
|
93
|
+
- [Nested Object Support](#nested-object-support)
|
|
94
|
+
- [Type-Safe Conditions](#type-safe-conditions)
|
|
95
|
+
- [๐ Batch Operations](#-batch-operations)
|
|
96
|
+
- [Batch Get](#batch-get)
|
|
97
|
+
- [Batch Write](#batch-write)
|
|
98
|
+
- [๐ Transaction Operations](#-transaction-operations)
|
|
99
|
+
- [Transaction Builder](#transaction-builder)
|
|
100
|
+
- [Transaction Options](#transaction-options)
|
|
101
|
+
- [๐จ Error Handling](#-error-handling)
|
|
102
|
+
- [๐ API Reference](#-api-reference)
|
|
103
|
+
- [Condition Operators](#condition-operators-1)
|
|
104
|
+
- [Comparison Operators](#comparison-operators-1)
|
|
105
|
+
- [Attribute Operators](#attribute-operators)
|
|
106
|
+
- [Logical Operators](#logical-operators-1)
|
|
107
|
+
- [Key Condition Operators](#key-condition-operators)
|
|
108
|
+
- [๐ฎ Future Roadmap](#-future-roadmap)
|
|
109
|
+
- [๐ค Contributing](#-contributing)
|
|
110
|
+
- [๐ฆ Running Examples](#-running-examples)
|
|
80
111
|
|
|
81
112
|
## ๐ฆ Installation
|
|
82
113
|
|
|
114
|
+
<div align="center">
|
|
115
|
+
|
|
116
|
+
### Get Started in Seconds
|
|
117
|
+
|
|
118
|
+
</div>
|
|
119
|
+
|
|
83
120
|
```bash
|
|
121
|
+
# Install the core library
|
|
84
122
|
npm install dyno-table
|
|
123
|
+
|
|
124
|
+
# Install required AWS SDK v3 peer dependencies
|
|
125
|
+
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
85
126
|
```
|
|
86
127
|
|
|
87
|
-
|
|
128
|
+
<details>
|
|
129
|
+
<summary><b>๐ Other Package Managers</b></summary>
|
|
88
130
|
|
|
89
131
|
```bash
|
|
90
|
-
|
|
132
|
+
# Using Yarn
|
|
133
|
+
yarn add dyno-table @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
134
|
+
|
|
135
|
+
# Using PNPM
|
|
136
|
+
pnpm add dyno-table @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
91
137
|
```
|
|
138
|
+
</details>
|
|
92
139
|
|
|
93
140
|
## ๐ Quick Start
|
|
94
141
|
|
|
142
|
+
<div align="center">
|
|
143
|
+
|
|
144
|
+
### From Zero to DynamoDB Hero in Minutes
|
|
145
|
+
|
|
146
|
+
</div>
|
|
147
|
+
|
|
95
148
|
### 1. Configure Your Jurassic Table
|
|
96
149
|
|
|
97
150
|
```ts
|
|
@@ -99,20 +152,20 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
|
99
152
|
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
|
|
100
153
|
import { Table } from "dyno-table/table";
|
|
101
154
|
|
|
102
|
-
// Configure AWS SDK clients
|
|
155
|
+
// Configure AWS SDK clients
|
|
103
156
|
const client = new DynamoDBClient({ region: "us-west-2" });
|
|
104
157
|
const docClient = DynamoDBDocument.from(client);
|
|
105
158
|
|
|
106
|
-
//
|
|
159
|
+
// Initialise table
|
|
107
160
|
const dinoTable = new Table({
|
|
108
161
|
client: docClient,
|
|
109
|
-
tableName: "JurassicPark",
|
|
162
|
+
tableName: "JurassicPark",
|
|
110
163
|
indexes: {
|
|
111
|
-
partitionKey: "pk",
|
|
112
|
-
sortKey: "sk",
|
|
164
|
+
partitionKey: "pk",
|
|
165
|
+
sortKey: "sk",
|
|
113
166
|
gsis: {
|
|
114
|
-
// Global Secondary Index for
|
|
115
|
-
|
|
167
|
+
// Global Secondary Index setup in an abstract to allow unique access patterns per Entity Type for single table design
|
|
168
|
+
gsi1: {
|
|
116
169
|
partitionKey: "gsi1pk",
|
|
117
170
|
sortKey: "gsi1sk",
|
|
118
171
|
},
|
|
@@ -121,62 +174,175 @@ const dinoTable = new Table({
|
|
|
121
174
|
});
|
|
122
175
|
```
|
|
123
176
|
|
|
124
|
-
### 2. Perform Type-Safe
|
|
177
|
+
### 2. Perform Type-Safe Operations directly on the table instance
|
|
178
|
+
|
|
179
|
+
<table>
|
|
180
|
+
<tr>
|
|
181
|
+
<td>
|
|
182
|
+
|
|
183
|
+
#### ๐ฆ Creating a new dinosaur specimen
|
|
125
184
|
|
|
126
|
-
**๐ฆ Creating a new dinosaur specimen**
|
|
127
185
|
```ts
|
|
128
|
-
// Add a new T-Rex
|
|
186
|
+
// Add a new T-Rex with complete type safety
|
|
129
187
|
const rex = await dinoTable
|
|
130
188
|
.create<Dinosaur>({
|
|
131
|
-
pk: "SPECIES#trex",
|
|
132
|
-
sk: "PROFILE#trex",
|
|
133
|
-
speciesId: "trex",
|
|
134
|
-
name: "Tyrannosaurus Rex",
|
|
135
|
-
diet: "carnivore",
|
|
136
|
-
length: 12.3,
|
|
137
|
-
discoveryYear: 1902
|
|
189
|
+
pk: "SPECIES#trex",
|
|
190
|
+
sk: "PROFILE#trex",
|
|
191
|
+
speciesId: "trex",
|
|
192
|
+
name: "Tyrannosaurus Rex",
|
|
193
|
+
diet: "carnivore",
|
|
194
|
+
length: 12.3,
|
|
195
|
+
discoveryYear: 1902
|
|
138
196
|
})
|
|
139
197
|
.execute();
|
|
140
198
|
```
|
|
141
199
|
|
|
142
|
-
|
|
200
|
+
</td>
|
|
201
|
+
<td>
|
|
202
|
+
|
|
203
|
+
#### ๐ Query with powerful conditions
|
|
204
|
+
|
|
143
205
|
```ts
|
|
144
|
-
// Find large carnivorous dinosaurs
|
|
206
|
+
// Find large carnivorous dinosaurs
|
|
145
207
|
const largeDinos = await dinoTable
|
|
146
208
|
.query<Dinosaur>({
|
|
147
|
-
pk: "SPECIES#trex",
|
|
148
|
-
sk: (op) => op.beginsWith("PROFILE#")
|
|
209
|
+
pk: "SPECIES#trex",
|
|
210
|
+
sk: (op) => op.beginsWith("PROFILE#")
|
|
149
211
|
})
|
|
150
212
|
.filter((op) => op.and(
|
|
151
|
-
op.gte("length", 10),
|
|
152
|
-
op.eq("diet", "carnivore")
|
|
213
|
+
op.gte("length", 10),
|
|
214
|
+
op.eq("diet", "carnivore")
|
|
153
215
|
))
|
|
154
|
-
.limit(10)
|
|
216
|
+
.limit(10)
|
|
155
217
|
.execute();
|
|
156
218
|
```
|
|
157
219
|
|
|
158
|
-
|
|
220
|
+
</td>
|
|
221
|
+
</tr>
|
|
222
|
+
<tr>
|
|
223
|
+
<td>
|
|
224
|
+
|
|
225
|
+
#### ๐ Update with type-safe operations
|
|
226
|
+
|
|
159
227
|
```ts
|
|
160
|
-
// Update a dinosaur's
|
|
228
|
+
// Update a dinosaur's classification
|
|
161
229
|
await dinoTable
|
|
162
230
|
.update<Dinosaur>({
|
|
163
|
-
pk: "SPECIES#trex",
|
|
164
|
-
sk: "PROFILE#trex"
|
|
231
|
+
pk: "SPECIES#trex",
|
|
232
|
+
sk: "PROFILE#trex"
|
|
165
233
|
})
|
|
166
|
-
.set("diet", "omnivore")
|
|
167
|
-
.add("discoveryYear", 1)
|
|
168
|
-
.remove("outdatedField")
|
|
169
|
-
.condition((op) =>
|
|
234
|
+
.set("diet", "omnivore")
|
|
235
|
+
.add("discoveryYear", 1)
|
|
236
|
+
.remove("outdatedField")
|
|
237
|
+
.condition((op) =>
|
|
238
|
+
op.attributeExists("discoverySite")
|
|
239
|
+
)
|
|
170
240
|
.execute();
|
|
171
241
|
```
|
|
172
242
|
|
|
243
|
+
</td>
|
|
244
|
+
<td>
|
|
245
|
+
|
|
246
|
+
#### ๐ Transactional operations
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
// Perform multiple operations atomically
|
|
250
|
+
await dinoTable.transaction(async (tx) => {
|
|
251
|
+
// Move dinosaur to new enclosure
|
|
252
|
+
await dinoTable
|
|
253
|
+
.delete({ pk: "ENCLOSURE#A", sk: "DINO#1" })
|
|
254
|
+
.withTransaction(tx);
|
|
255
|
+
|
|
256
|
+
await dinoTable
|
|
257
|
+
.create({ pk: "ENCLOSURE#B", sk: "DINO#1",
|
|
258
|
+
status: "ACTIVE" })
|
|
259
|
+
.withTransaction(tx);
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
</td>
|
|
264
|
+
</tr>
|
|
265
|
+
</table>
|
|
266
|
+
|
|
267
|
+
<div align="center">
|
|
268
|
+
<h3>๐ก See the difference with dyno-table</h3>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<table>
|
|
272
|
+
<tr>
|
|
273
|
+
<th>With dyno-table</th>
|
|
274
|
+
<th>Without dyno-table</th>
|
|
275
|
+
</tr>
|
|
276
|
+
<tr>
|
|
277
|
+
<td>
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
// Type-safe, clean, and intuitive
|
|
281
|
+
await dinoTable
|
|
282
|
+
.query<Dinosaur>({
|
|
283
|
+
pk: "SPECIES#trex"
|
|
284
|
+
})
|
|
285
|
+
.filter(op =>
|
|
286
|
+
op.contains("features", "feathers")
|
|
287
|
+
)
|
|
288
|
+
.execute();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
</td>
|
|
292
|
+
<td>
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
// Verbose, error-prone, no type safety
|
|
296
|
+
await docClient.send(new QueryCommand({
|
|
297
|
+
TableName: "JurassicPark",
|
|
298
|
+
KeyConditionExpression: "#pk = :pk",
|
|
299
|
+
FilterExpression: "contains(#features, :feathers)",
|
|
300
|
+
ExpressionAttributeNames: {
|
|
301
|
+
"#pk": "pk",
|
|
302
|
+
"#features": "features"
|
|
303
|
+
},
|
|
304
|
+
ExpressionAttributeValues: {
|
|
305
|
+
":pk": "SPECIES#trex",
|
|
306
|
+
":feathers": "feathers"
|
|
307
|
+
}
|
|
308
|
+
}));
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
</td>
|
|
312
|
+
</tr>
|
|
313
|
+
</table>
|
|
314
|
+
|
|
173
315
|
## ๐๏ธ Entity Pattern with Standard Schema validators
|
|
174
316
|
|
|
175
|
-
|
|
176
|
-
|
|
317
|
+
<div align="center">
|
|
318
|
+
|
|
319
|
+
### The Most Type-Safe Way to Model Your DynamoDB Data
|
|
320
|
+
|
|
321
|
+
</div>
|
|
177
322
|
|
|
178
|
-
|
|
179
|
-
|
|
323
|
+
<table>
|
|
324
|
+
<tr>
|
|
325
|
+
<td width="70%">
|
|
326
|
+
<p>The entity pattern provides a structured, type-safe way to work with DynamoDB items. It combines schema validation, key management, and repository operations into a cohesive abstraction.</p>
|
|
327
|
+
|
|
328
|
+
<p>โจ This library supports all <a href="https://github.com/standard-schema/standard-schema#what-schema-libraries-implement-the-spec">Standard Schema</a> validation libraries, including <strong>zod</strong>, <strong>arktype</strong>, and <strong>valibot</strong>, allowing you to choose your preferred validation tool!</p>
|
|
329
|
+
|
|
330
|
+
<p>You can find a full example implementation here of <a href="https://github.com/Kysumi/dyno-table/blob/main/examples/entity-example/src/dinosaur-entity.ts">Entities</a></p>
|
|
331
|
+
</td>
|
|
332
|
+
<td width="30%">
|
|
333
|
+
|
|
334
|
+
#### Entity Pattern Benefits
|
|
335
|
+
|
|
336
|
+
- ๐ก๏ธ **Type-safe operations**
|
|
337
|
+
- ๐งช **Schema validation**
|
|
338
|
+
- ๐ **Automatic key generation**
|
|
339
|
+
- ๐ฆ **Repository pattern**
|
|
340
|
+
- ๐ **Custom query builders**
|
|
341
|
+
- ๐ **Lifecycle hooks**
|
|
342
|
+
|
|
343
|
+
</td>
|
|
344
|
+
</tr>
|
|
345
|
+
</table>
|
|
180
346
|
|
|
181
347
|
### Defining Entities
|
|
182
348
|
|
|
@@ -251,7 +417,8 @@ const DinosaurEntity = defineEntity({
|
|
|
251
417
|
primaryKey: createIndex()
|
|
252
418
|
.input(z.object({ id: z.string(), diet: z.string(), species: z.string() }))
|
|
253
419
|
.partitionKey(({ diet }) => dinosaurPK({ diet }))
|
|
254
|
-
.
|
|
420
|
+
// could also be .withoutSortKey() if your table doesn't use sort keys
|
|
421
|
+
.sortKey(({ id, species }) => dinosaurSK({ species, id }))
|
|
255
422
|
});
|
|
256
423
|
```
|
|
257
424
|
|
|
@@ -315,8 +482,9 @@ const DinosaurEntity = defineEntity({
|
|
|
315
482
|
)
|
|
316
483
|
.query(({ input, entity }) => {
|
|
317
484
|
return entity
|
|
318
|
-
.
|
|
319
|
-
|
|
485
|
+
.query({
|
|
486
|
+
pk: dinosaurPK({diet: input.diet})
|
|
487
|
+
});
|
|
320
488
|
}),
|
|
321
489
|
|
|
322
490
|
bySpecies: createQuery
|
|
@@ -338,18 +506,22 @@ const carnivores = await dinosaurRepo.query.byDiet({ diet: "carnivore" }).execut
|
|
|
338
506
|
const trexes = await dinosaurRepo.query.bySpecies({ species: "Tyrannosaurus Rex" }).execute();
|
|
339
507
|
```
|
|
340
508
|
|
|
341
|
-
#### 4.
|
|
509
|
+
#### 4. Defining GSI access patterns
|
|
342
510
|
|
|
343
|
-
Define
|
|
511
|
+
Define GSI (LSI support coming later)
|
|
344
512
|
|
|
345
513
|
```ts
|
|
346
514
|
import { createIndex } from "dyno-table/entity";
|
|
347
515
|
|
|
348
|
-
// Define
|
|
516
|
+
// Define GSIs templates for querying by species
|
|
517
|
+
const gsi1PK = partitionKey`SPECIES#${"species"}`
|
|
518
|
+
const gsi1SK = sortKey`DINOSAUR#${"id"}`
|
|
519
|
+
|
|
520
|
+
// Implement typesafe generator for the GSI - This is used in create calls to ensure the GSI is generated
|
|
349
521
|
const speciesIndex = createIndex()
|
|
350
522
|
.input(dinosaurSchema)
|
|
351
|
-
.partitionKey(({ species }) =>
|
|
352
|
-
.sortKey(({ id }) =>
|
|
523
|
+
.partitionKey(({ species }) => gsi1PK({ species }))
|
|
524
|
+
.sortKey(({ id }) => gsi1SK({ id }));
|
|
353
525
|
|
|
354
526
|
const DinosaurEntity = defineEntity({
|
|
355
527
|
name: "Dinosaur",
|
|
@@ -367,49 +539,27 @@ const DinosaurEntity = defineEntity({
|
|
|
367
539
|
)
|
|
368
540
|
.query(({ input, entity }) => {
|
|
369
541
|
return entity
|
|
370
|
-
.
|
|
371
|
-
|
|
542
|
+
.query({
|
|
543
|
+
// Use the GSI template generator to avoid typos
|
|
544
|
+
pk: gsi1PK({species: input.species}),
|
|
372
545
|
})
|
|
373
|
-
|
|
546
|
+
// Use the template name as defined in the table instance
|
|
547
|
+
.useIndex("gsi1");
|
|
374
548
|
}),
|
|
375
549
|
},
|
|
376
550
|
});
|
|
377
551
|
```
|
|
378
552
|
|
|
379
|
-
#### 5. Lifecycle Hooks
|
|
380
|
-
|
|
381
|
-
Add hooks for pre/post processing:
|
|
382
|
-
|
|
383
|
-
```ts
|
|
384
|
-
const dinosaurHooks = {
|
|
385
|
-
afterGet: async (data: Dinosaur | undefined) => {
|
|
386
|
-
if (data) {
|
|
387
|
-
return {
|
|
388
|
-
...data,
|
|
389
|
-
displayName: `${data.name} (${data.species})`,
|
|
390
|
-
threatLevel: data.dangerLevel > 7 ? "HIGH" : "MODERATE",
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
return data;
|
|
394
|
-
},
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
const DinosaurEntity = defineEntity({
|
|
398
|
-
name: "Dinosaur",
|
|
399
|
-
schema: dinosaurSchema,
|
|
400
|
-
primaryKey,
|
|
401
|
-
hooks: dinosaurHooks,
|
|
402
|
-
});
|
|
403
|
-
```
|
|
404
|
-
|
|
405
553
|
### Complete Entity Example
|
|
406
554
|
|
|
407
|
-
Here's a complete example using Zod schemas directly:
|
|
555
|
+
Here's a complete example of using Zod schemas directly:
|
|
408
556
|
|
|
409
557
|
```ts
|
|
410
558
|
import { z } from "zod";
|
|
411
559
|
import { defineEntity, createQueries, createIndex } from "dyno-table/entity";
|
|
412
560
|
import { Table } from "dyno-table/table";
|
|
561
|
+
import { sortKey } from "dyno-table/utils/sort-key-template";
|
|
562
|
+
import { partitionKey } from "dyno-table/utils/partition-key-template";
|
|
413
563
|
|
|
414
564
|
// Define the schema with Zod
|
|
415
565
|
const dinosaurSchema = z.object({
|
|
@@ -432,8 +582,14 @@ const dinosaurSchema = z.object({
|
|
|
432
582
|
type Dinosaur = z.infer<typeof dinosaurSchema>;
|
|
433
583
|
|
|
434
584
|
// Define key templates
|
|
435
|
-
const dinosaurPK =
|
|
436
|
-
const dinosaurSK =
|
|
585
|
+
const dinosaurPK = partitionKey`DINOSAUR#${"id"}`;
|
|
586
|
+
const dinosaurSK = sortKey`STATUS#${"status"}`;
|
|
587
|
+
|
|
588
|
+
const gsi1PK = partitionKey`SPECIES#${"species"}`
|
|
589
|
+
const gsi1SK = sortKey`DINOSAUR#${"id"}`
|
|
590
|
+
|
|
591
|
+
const gsi2PK = partitionKey`ENCLOSURE#${"enclosureId"}`
|
|
592
|
+
const gsi2SK = sortKey`DINOSAUR#${"id"}`
|
|
437
593
|
|
|
438
594
|
// Create a primary index
|
|
439
595
|
const primaryKey = createIndex()
|
|
@@ -444,14 +600,14 @@ const primaryKey = createIndex()
|
|
|
444
600
|
// Create a GSI for querying by species
|
|
445
601
|
const speciesIndex = createIndex()
|
|
446
602
|
.input(dinosaurSchema)
|
|
447
|
-
.partitionKey(({ species }) =>
|
|
448
|
-
.sortKey(({ id }) =>
|
|
603
|
+
.partitionKey(({ species }) => gsi1PK({ species }))
|
|
604
|
+
.sortKey(({ id }) => gsiSK({ id }));
|
|
449
605
|
|
|
450
606
|
// Create a GSI for querying by enclosure
|
|
451
607
|
const enclosureIndex = createIndex()
|
|
452
608
|
.input(dinosaurSchema)
|
|
453
|
-
.partitionKey(({ enclosureId }) =>
|
|
454
|
-
.sortKey(({ id }) =>
|
|
609
|
+
.partitionKey(({ enclosureId }) => gsi2PK({ enclosureId }))
|
|
610
|
+
.sortKey(({ id }) => gsi2SK({ id }));
|
|
455
611
|
|
|
456
612
|
// Create query builders
|
|
457
613
|
const createQuery = createQueries<Dinosaur>();
|
|
@@ -462,8 +618,9 @@ const DinosaurEntity = defineEntity({
|
|
|
462
618
|
schema: dinosaurSchema,
|
|
463
619
|
primaryKey,
|
|
464
620
|
indexes: {
|
|
465
|
-
|
|
466
|
-
|
|
621
|
+
// These keys need to be named after the name of the GSI that is defined in your table instance
|
|
622
|
+
gsi1: speciesIndex,
|
|
623
|
+
gsi2: enclosureIndex,
|
|
467
624
|
},
|
|
468
625
|
queries: {
|
|
469
626
|
bySpecies: createQuery
|
|
@@ -474,10 +631,10 @@ const DinosaurEntity = defineEntity({
|
|
|
474
631
|
)
|
|
475
632
|
.query(({ input, entity }) => {
|
|
476
633
|
return entity
|
|
477
|
-
.
|
|
478
|
-
pk:
|
|
634
|
+
.query({
|
|
635
|
+
pk: gsi1PK({ species: input.species }),
|
|
479
636
|
})
|
|
480
|
-
.useIndex("
|
|
637
|
+
.useIndex("gsi1");
|
|
481
638
|
}),
|
|
482
639
|
|
|
483
640
|
byEnclosure: createQuery
|
|
@@ -488,10 +645,10 @@ const DinosaurEntity = defineEntity({
|
|
|
488
645
|
)
|
|
489
646
|
.query(({ input, entity }) => {
|
|
490
647
|
return entity
|
|
491
|
-
.
|
|
492
|
-
pk:
|
|
648
|
+
.query({
|
|
649
|
+
pk: gsi2PK({ enclosureId: input.enclosureId }),
|
|
493
650
|
})
|
|
494
|
-
.useIndex("
|
|
651
|
+
.useIndex("gsi2");
|
|
495
652
|
}),
|
|
496
653
|
|
|
497
654
|
dangerousInEnclosure: createQuery
|
|
@@ -503,10 +660,10 @@ const DinosaurEntity = defineEntity({
|
|
|
503
660
|
)
|
|
504
661
|
.query(({ input, entity }) => {
|
|
505
662
|
return entity
|
|
506
|
-
.
|
|
507
|
-
pk:
|
|
663
|
+
.query({
|
|
664
|
+
pk: gsi2PK({ enclosureId: input.enclosureId }),
|
|
508
665
|
})
|
|
509
|
-
.useIndex("
|
|
666
|
+
.useIndex("gsi2")
|
|
510
667
|
.filter((op) => op.gte("dangerLevel", input.minDangerLevel));
|
|
511
668
|
}),
|
|
512
669
|
},
|
|
@@ -548,7 +705,7 @@ async function main() {
|
|
|
548
705
|
|
|
549
706
|
**Key benefits:**
|
|
550
707
|
- ๐ซ Prevents accidental cross-type data access
|
|
551
|
-
- ๐ Automatically filters queries/scans to repository type
|
|
708
|
+
- ๐ Automatically filters queries/scans to a repository type
|
|
552
709
|
- ๐ก๏ธ Ensures consistent key structure across entities
|
|
553
710
|
- ๐ฆ Encapsulates domain-specific query logic
|
|
554
711
|
- ๐งช Validates data with Zod schemas
|
|
@@ -661,7 +818,6 @@ await dinoTable.transaction(
|
|
|
661
818
|
- ๐ Uses the same familiar API as non-transactional operations
|
|
662
819
|
- ๐ง Maintains consistent mental model for developers
|
|
663
820
|
- ๐ All operations within the callback are executed as a single transaction
|
|
664
|
-
- โ
All-or-nothing operations (ACID compliance)
|
|
665
821
|
- ๐ก๏ธ Prevents race conditions and data inconsistencies
|
|
666
822
|
- ๐ Supports up to 100 actions per transaction
|
|
667
823
|
|
|
@@ -1273,6 +1429,41 @@ pnpm test
|
|
|
1273
1429
|
pnpm build
|
|
1274
1430
|
```
|
|
1275
1431
|
|
|
1432
|
+
## ๐ฆ Release Process
|
|
1433
|
+
|
|
1434
|
+
This project uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated versioning and package publishing. The configuration is maintained in the `.releaserc.json` file. Releases are automatically triggered by commits to specific branches:
|
|
1435
|
+
|
|
1436
|
+
- **Main Channel**: Stable releases from the `main` branch
|
|
1437
|
+
- **Alpha Channel**: Pre-releases from the `alpha` branch
|
|
1438
|
+
|
|
1439
|
+
### Commit Message Format
|
|
1440
|
+
|
|
1441
|
+
We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages, which determines the release type:
|
|
1442
|
+
|
|
1443
|
+
- `fix: ...` - Patch release (bug fixes)
|
|
1444
|
+
- `feat: ...` - Minor release (new features)
|
|
1445
|
+
- `feat!: ...` or `fix!: ...` or any commit with `BREAKING CHANGE:` in the footer - Major release
|
|
1446
|
+
|
|
1447
|
+
### Release Workflow
|
|
1448
|
+
|
|
1449
|
+
1. For regular features and fixes:
|
|
1450
|
+
- Create a PR against the `main` branch
|
|
1451
|
+
- Once merged, a new release will be automatically published
|
|
1452
|
+
|
|
1453
|
+
2. For experimental features:
|
|
1454
|
+
- Create a PR against the `alpha` branch
|
|
1455
|
+
- Once merged, a new alpha release will be published with an alpha tag
|
|
1456
|
+
|
|
1457
|
+
### Installing Specific Channels
|
|
1458
|
+
|
|
1459
|
+
```bash
|
|
1460
|
+
# Install the latest stable version
|
|
1461
|
+
npm install dyno-table
|
|
1462
|
+
|
|
1463
|
+
# Install the latest alpha version
|
|
1464
|
+
npm install dyno-table@alpha
|
|
1465
|
+
```
|
|
1466
|
+
|
|
1276
1467
|
## ๐ฆ Running Examples
|
|
1277
1468
|
|
|
1278
1469
|
There's a few pre-configured example scripts in the `examples` directory.
|
|
@@ -1296,10 +1487,3 @@ npx tsx examples/[EXAMPLE_NAME].ts
|
|
|
1296
1487
|
```
|
|
1297
1488
|
|
|
1298
1489
|
To view the test table GUI in action: [DynamoDB Admin](http://localhost:8001/)
|
|
1299
|
-
|
|
1300
|
-
<br />
|
|
1301
|
-
To teardown the test table when you're done, run the following command:
|
|
1302
|
-
|
|
1303
|
-
```bash
|
|
1304
|
-
pnpm run local:teardown
|
|
1305
|
-
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dyno-table",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A TypeScript library to simplify working with DynamoDB",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -111,8 +111,11 @@
|
|
|
111
111
|
"devDependencies": {
|
|
112
112
|
"@babel/preset-typescript": "^7.26.0",
|
|
113
113
|
"@biomejs/biome": "1.9.4",
|
|
114
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
115
|
+
"@semantic-release/git": "^10.0.1",
|
|
114
116
|
"@types/node": "^20.17.11",
|
|
115
117
|
"rimraf": "^5.0.10",
|
|
118
|
+
"semantic-release": "24.2.3",
|
|
116
119
|
"tsup": "^8.3.5",
|
|
117
120
|
"typescript": "^5.7.2",
|
|
118
121
|
"vitest": "3.0.5"
|