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.
Files changed (2) hide show
  1. package/README.md +345 -161
  2. package/package.json +4 -1
package/README.md CHANGED
@@ -1,10 +1,23 @@
1
- # ๐Ÿฆ– dyno-table [![npm version](https://img.shields.io/npm/v/dyno-table.svg?style=flat-square)](https://www.npmjs.com/package/dyno-table) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
1
+ <div align="center">
2
2
 
3
- **A type-safe, fluent interface for DynamoDB single-table designs**
4
- *Tame the NoSQL wilderness with a robust abstraction layer that brings order to DynamoDB operations*
3
+ # ๐Ÿฆ– dyno-table
4
+
5
+ ### **Tame Your DynamoDB Data with Type-Safe Precision**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/dyno-table.svg?style=for-the-badge)](https://www.npmjs.com/package/dyno-table)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-4.0%2B-blue?style=for-the-badge&logo=typescript)](https://www.typescriptlang.org/)
10
+ [![AWS DynamoDB](https://img.shields.io/badge/AWS-DynamoDB-orange?style=for-the-badge&logo=amazon-aws)](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
- > This README provides a concise overview of dyno-table's features with dinosaur-themed examples. For detailed documentation on specific features, please refer to the individual markdown files in the `docs/` directory.
22
-
23
- ## ๐ŸŒŸ Why dyno-table for your Dinosaur Data?
24
-
25
- - **๐Ÿฆ• Dinosaur-sized data made manageable** - Clean abstraction layer for complex DynamoDB patterns
26
- - **๐Ÿ›ก๏ธ Extinction-proof type safety** - Full TypeScript support with strict type checking
27
- - **โšก Velociraptor-fast API** - Chainable builder pattern for complex operations
28
- - **๐Ÿ”’ T-Rex-proof transactional safety** - ACID-compliant operations with easy-to-use transactions
29
- - **๐Ÿ“ˆ Jurassic-scale performance** - Automatic batch chunking and pagination handling
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
- - [๐Ÿฆ– dyno-table ](#-dyno-table--)
34
- - [๐ŸŒŸ Why dyno-table for your Dinosaur Data?](#-why-dyno-table-for-your-dinosaur-data)
35
- - [๐Ÿ“‘ Table of Contents](#-table-of-contents)
36
- - [๐Ÿ“ฆ Installation](#-installation)
37
- - [๐Ÿš€ Quick Start](#-quick-start)
38
- - [1. Configure Your Jurassic Table](#1-configure-your-jurassic-table)
39
- - [2. Perform Type-Safe Dinosaur Operations](#2-perform-type-safe-dinosaur-operations)
40
- - [๐Ÿ—๏ธ Entity Pattern](#๏ธ-entity-pattern-with-standard-schema-validators)
41
- - [Defining Entities](#defining-entities)
42
- - [Entity Features](#entity-features)
43
- - [1. Schema Validation](#1-schema-validation)
44
- - [2. CRUD Operations](#2-crud-operations)
45
- - [3. Custom Queries](#3-custom-queries)
46
- - [4. Indexes for Efficient Querying](#4-indexes-for-efficient-querying)
47
- - [5. Lifecycle Hooks](#5-lifecycle-hooks)
48
- - [Complete Entity Example](#complete-entity-example)
49
- - [๐Ÿงฉ Advanced Features](#-advanced-features)
50
- - [Transactional Operations](#transactional-operations)
51
- - [Batch Processing](#batch-processing)
52
- - [Pagination Made Simple](#pagination-made-simple)
53
- - [๐Ÿ›ก๏ธ Type-Safe Query Building](#๏ธ-type-safe-query-building)
54
- - [Comparison Operators](#comparison-operators)
55
- - [Logical Operators](#logical-operators)
56
- - [Query Operations](#query-operations)
57
- - [Put Operations](#put-operations)
58
- - [Update Operations](#update-operations)
59
- - [Condition Operators](#condition-operators)
60
- - [Multiple Operations](#multiple-operations)
61
- - [๐Ÿ”„ Type Safety Features](#-type-safety-features)
62
- - [Nested Object Support](#nested-object-support)
63
- - [Type-Safe Conditions](#type-safe-conditions)
64
- - [๐Ÿ”„ Batch Operations](#-batch-operations)
65
- - [Batch Get](#batch-get)
66
- - [Batch Write](#batch-write)
67
- - [๐Ÿ”’ Transaction Operations](#-transaction-operations)
68
- - [Transaction Builder](#transaction-builder)
69
- - [Transaction Options](#transaction-options)
70
- - [๐Ÿšจ Error Handling](#-error-handling)
71
- - [๐Ÿ“š API Reference](#-api-reference)
72
- - [Condition Operators](#condition-operators-1)
73
- - [Comparison Operators](#comparison-operators-1)
74
- - [Attribute Operators](#attribute-operators)
75
- - [Logical Operators](#logical-operators-1)
76
- - [Key Condition Operators](#key-condition-operators)
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
- *Note: Requires AWS SDK v3 as peer dependency*
128
+ <details>
129
+ <summary><b>๐Ÿ“‹ Other Package Managers</b></summary>
88
130
 
89
131
  ```bash
90
- npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
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 - your gateway to the prehistoric database
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
- // Initialize table with single-table design schema - your dinosaur park database
159
+ // Initialise table
107
160
  const dinoTable = new Table({
108
161
  client: docClient,
109
- tableName: "JurassicPark", // Your central dinosaur tracking system
162
+ tableName: "JurassicPark",
110
163
  indexes: {
111
- partitionKey: "pk", // Primary partition key for fast dinosaur lookups
112
- sortKey: "sk", // Sort key for organizing dinosaur data
164
+ partitionKey: "pk",
165
+ sortKey: "sk",
113
166
  gsis: {
114
- // Global Secondary Index for querying dinosaurs by species
115
- speciesId: {
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 Dinosaur Operations
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 to your collection with complete type safety
186
+ // Add a new T-Rex with complete type safety
129
187
  const rex = await dinoTable
130
188
  .create<Dinosaur>({
131
- pk: "SPECIES#trex", // Partition key identifies the species
132
- sk: "PROFILE#trex", // Sort key for the specific profile
133
- speciesId: "trex", // For GSI queries
134
- name: "Tyrannosaurus Rex", // Display name
135
- diet: "carnivore", // Dietary classification
136
- length: 12.3, // Size in meters
137
- discoveryYear: 1902 // When first discovered
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
- **๐Ÿ” Query for specific dinosaurs with conditions**
200
+ </td>
201
+ <td>
202
+
203
+ #### ๐Ÿ” Query with powerful conditions
204
+
143
205
  ```ts
144
- // Find large carnivorous dinosaurs in the T-Rex species
206
+ // Find large carnivorous dinosaurs
145
207
  const largeDinos = await dinoTable
146
208
  .query<Dinosaur>({
147
- pk: "SPECIES#trex", // Target the T-Rex species
148
- sk: (op) => op.beginsWith("PROFILE#") // Look in profile records
209
+ pk: "SPECIES#trex",
210
+ sk: (op) => op.beginsWith("PROFILE#")
149
211
  })
150
212
  .filter((op) => op.and(
151
- op.gte("length", 10), // Only dinosaurs longer than 10 meters
152
- op.eq("diet", "carnivore") // Must be carnivores
213
+ op.gte("length", 10),
214
+ op.eq("diet", "carnivore")
153
215
  ))
154
- .limit(10) // Limit to 10 results
216
+ .limit(10)
155
217
  .execute();
156
218
  ```
157
219
 
158
- **๐Ÿ”„ Update dinosaur classification**
220
+ </td>
221
+ </tr>
222
+ <tr>
223
+ <td>
224
+
225
+ #### ๐Ÿ”„ Update with type-safe operations
226
+
159
227
  ```ts
160
- // Update a dinosaur's diet classification based on new research
228
+ // Update a dinosaur's classification
161
229
  await dinoTable
162
230
  .update<Dinosaur>({
163
- pk: "SPECIES#trex", // Target the T-Rex species
164
- sk: "PROFILE#trex" // Specific profile to update
231
+ pk: "SPECIES#trex",
232
+ sk: "PROFILE#trex"
165
233
  })
166
- .set("diet", "omnivore") // New diet classification based on fossil evidence
167
- .add("discoveryYear", 1) // Adjust discovery year with new findings
168
- .remove("outdatedField") // Remove deprecated information
169
- .condition((op) => op.attributeExists("discoverySite")) // Only if discovery site is documented
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
- The entity pattern provides a structured, type-safe way to work with DynamoDB items.
176
- It combines schema validation, key management, and repository operations into a cohesive abstraction.
317
+ <div align="center">
318
+
319
+ ### The Most Type-Safe Way to Model Your DynamoDB Data
320
+
321
+ </div>
177
322
 
178
- โœจ This library supports all standard schema validation libraries, including **zod**, **arktype**, and **valibot**,
179
- allowing you to choose your preferred validation tool!
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
- .sortKey(({ id, species }) => dinosaurSK({ species, id }))
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
- .scan()
319
- .filter((op) => op.eq("diet", input.diet));
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. Indexes for Efficient Querying
509
+ #### 4. Defining GSI access patterns
342
510
 
343
- Define indexes for efficient access patterns:
511
+ Define GSI (LSI support coming later)
344
512
 
345
513
  ```ts
346
514
  import { createIndex } from "dyno-table/entity";
347
515
 
348
- // Define GSI for querying by species
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 }) => `SPECIES#${species}`)
352
- .sortKey(({ id }) => `DINOSAUR#${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
- .queryBuilder({
371
- pk: `SPECIES#${input.species}`,
542
+ .query({
543
+ // Use the GSI template generator to avoid typos
544
+ pk: gsi1PK({species: input.species}),
372
545
  })
373
- .useIndex("species");
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 = (id: string) => `DINOSAUR#${id}`;
436
- const dinosaurSK = (status: string) => `STATUS#${status}`;
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 }) => `SPECIES#${species}`)
448
- .sortKey(({ id }) => `DINOSAUR#${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 }) => `ENCLOSURE#${enclosureId}`)
454
- .sortKey(({ id }) => `DINOSAUR#${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
- species: speciesIndex,
466
- enclosure: enclosureIndex,
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
- .queryBuilder({
478
- pk: `SPECIES#${input.species}`,
634
+ .query({
635
+ pk: gsi1PK({ species: input.species }),
479
636
  })
480
- .useIndex("species");
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
- .queryBuilder({
492
- pk: `ENCLOSURE#${input.enclosureId}`,
648
+ .query({
649
+ pk: gsi2PK({ enclosureId: input.enclosureId }),
493
650
  })
494
- .useIndex("enclosure");
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
- .queryBuilder({
507
- pk: `ENCLOSURE#${input.enclosureId}`,
663
+ .query({
664
+ pk: gsi2PK({ enclosureId: input.enclosureId }),
508
665
  })
509
- .useIndex("enclosure")
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.2.0-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"