dyno-table 1.0.0-alpha.1 → 1.1.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 (96) hide show
  1. package/README.md +752 -172
  2. package/dist/builder-types-C_PDZhnP.d.ts +118 -0
  3. package/dist/builder-types-DtwbqMeF.d.cts +118 -0
  4. package/dist/builders/condition-check-builder.cjs +394 -0
  5. package/dist/builders/condition-check-builder.cjs.map +1 -0
  6. package/dist/builders/condition-check-builder.d.cts +157 -0
  7. package/dist/builders/condition-check-builder.d.ts +157 -0
  8. package/dist/builders/condition-check-builder.js +392 -0
  9. package/dist/builders/condition-check-builder.js.map +1 -0
  10. package/dist/builders/delete-builder.cjs +405 -0
  11. package/dist/builders/delete-builder.cjs.map +1 -0
  12. package/dist/builders/delete-builder.d.cts +166 -0
  13. package/dist/builders/delete-builder.d.ts +166 -0
  14. package/dist/builders/delete-builder.js +403 -0
  15. package/dist/builders/delete-builder.js.map +1 -0
  16. package/dist/builders/paginator.cjs +199 -0
  17. package/dist/builders/paginator.cjs.map +1 -0
  18. package/dist/builders/paginator.d.cts +179 -0
  19. package/dist/builders/paginator.d.ts +179 -0
  20. package/dist/builders/paginator.js +197 -0
  21. package/dist/builders/paginator.js.map +1 -0
  22. package/dist/builders/put-builder.cjs +476 -0
  23. package/dist/builders/put-builder.cjs.map +1 -0
  24. package/dist/builders/put-builder.d.cts +274 -0
  25. package/dist/builders/put-builder.d.ts +274 -0
  26. package/dist/builders/put-builder.js +474 -0
  27. package/dist/builders/put-builder.js.map +1 -0
  28. package/dist/builders/query-builder.cjs +674 -0
  29. package/dist/builders/query-builder.cjs.map +1 -0
  30. package/dist/builders/query-builder.d.cts +6 -0
  31. package/dist/builders/query-builder.d.ts +6 -0
  32. package/dist/builders/query-builder.js +672 -0
  33. package/dist/builders/query-builder.js.map +1 -0
  34. package/dist/builders/transaction-builder.cjs +918 -0
  35. package/dist/builders/transaction-builder.cjs.map +1 -0
  36. package/dist/builders/transaction-builder.d.cts +511 -0
  37. package/dist/builders/transaction-builder.d.ts +511 -0
  38. package/dist/builders/transaction-builder.js +916 -0
  39. package/dist/builders/transaction-builder.js.map +1 -0
  40. package/dist/builders/update-builder.cjs +627 -0
  41. package/dist/builders/update-builder.cjs.map +1 -0
  42. package/dist/builders/update-builder.d.cts +365 -0
  43. package/dist/builders/update-builder.d.ts +365 -0
  44. package/dist/builders/update-builder.js +625 -0
  45. package/dist/builders/update-builder.js.map +1 -0
  46. package/dist/conditions--ld9a78i.d.ts +331 -0
  47. package/dist/conditions-ChhQWd6z.d.cts +331 -0
  48. package/dist/conditions.cjs +59 -0
  49. package/dist/conditions.cjs.map +1 -0
  50. package/dist/conditions.d.cts +3 -0
  51. package/dist/conditions.d.ts +3 -0
  52. package/dist/conditions.js +43 -0
  53. package/dist/conditions.js.map +1 -0
  54. package/dist/entity.cjs +211 -0
  55. package/dist/entity.cjs.map +1 -0
  56. package/dist/entity.d.cts +149 -0
  57. package/dist/entity.d.ts +149 -0
  58. package/dist/entity.js +207 -0
  59. package/dist/entity.js.map +1 -0
  60. package/dist/query-builder-Csror9Iu.d.ts +507 -0
  61. package/dist/query-builder-D2FM9rsu.d.cts +507 -0
  62. package/dist/standard-schema.cjs +4 -0
  63. package/dist/standard-schema.cjs.map +1 -0
  64. package/dist/standard-schema.d.cts +57 -0
  65. package/dist/standard-schema.d.ts +57 -0
  66. package/dist/standard-schema.js +3 -0
  67. package/dist/standard-schema.js.map +1 -0
  68. package/dist/table-BEhBPy2G.d.cts +364 -0
  69. package/dist/table-BW3cmUqr.d.ts +364 -0
  70. package/dist/{index.cjs → table.cjs} +108 -175
  71. package/dist/table.cjs.map +1 -0
  72. package/dist/table.d.cts +12 -0
  73. package/dist/table.d.ts +12 -0
  74. package/dist/{index.js → table.js} +107 -127
  75. package/dist/table.js.map +1 -0
  76. package/dist/types.cjs +4 -0
  77. package/dist/types.cjs.map +1 -0
  78. package/dist/types.d.cts +22 -0
  79. package/dist/types.d.ts +22 -0
  80. package/dist/types.js +3 -0
  81. package/dist/types.js.map +1 -0
  82. package/dist/utils/partition-key-template.cjs +19 -0
  83. package/dist/utils/partition-key-template.cjs.map +1 -0
  84. package/dist/utils/partition-key-template.d.cts +32 -0
  85. package/dist/utils/partition-key-template.d.ts +32 -0
  86. package/dist/utils/partition-key-template.js +17 -0
  87. package/dist/utils/partition-key-template.js.map +1 -0
  88. package/dist/utils/sort-key-template.cjs +19 -0
  89. package/dist/utils/sort-key-template.cjs.map +1 -0
  90. package/dist/utils/sort-key-template.d.cts +35 -0
  91. package/dist/utils/sort-key-template.d.ts +35 -0
  92. package/dist/utils/sort-key-template.js +17 -0
  93. package/dist/utils/sort-key-template.js.map +1 -0
  94. package/package.json +77 -7
  95. package/dist/index.d.cts +0 -2971
  96. package/dist/index.d.ts +0 -2971
package/README.md CHANGED
@@ -1,107 +1,172 @@
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
- // Type-safe DynamoDB operations made simple
10
- await table
22
+ // Type-safe dinosaur tracking operations made simple
23
+ await dinoTable
11
24
  .update<Dinosaur>({
12
25
  pk: 'SPECIES#trex',
13
26
  sk: 'PROFILE#001'
14
27
  })
15
- .set('diet', 'Carnivore')
16
- .add('sightings', 1)
17
- .condition(op => op.eq('status', 'ACTIVE'))
28
+ .set('diet', 'Carnivore') // Update dietary classification
29
+ .add('sightings', 1) // Increment sighting counter
30
+ .condition(op => op.eq('status', 'ACTIVE')) // Only if dinosaur is active
18
31
  .execute();
19
32
  ```
20
33
 
21
- ## 🌟 Why dyno-table?
22
-
23
- - **🧩 Single-table design made simple** - Clean abstraction layer for complex DynamoDB patterns
24
- - **🛡️ Type-safe operations** - Full TypeScript support with strict type checking
25
- - **⚡ Fluent API** - Chainable builder pattern for complex operations
26
- - **🔒 Transactional safety** - ACID-compliant operations with easy-to-use transactions
27
- - **📈 Scalability built-in** - 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>
28
64
 
29
65
  ## 📑 Table of Contents
30
66
 
31
- - [🦖 dyno-table ](#-dyno-table--)
32
- - [🌟 Why dyno-table?](#-why-dyno-table)
33
- - [📑 Table of Contents](#-table-of-contents)
34
- - [📦 Installation](#-installation)
35
- - [🚀 Quick Start](#-quick-start)
36
- - [1. Configure Your Table](#1-configure-your-table)
37
- - [2. Perform Type-Safe Operations](#2-perform-type-safe-operations)
38
- - [🧩 Advanced Features](#-advanced-features)
39
- - [Transactional Operations](#transactional-operations)
40
- - [Batch Processing](#batch-processing)
41
- - [Pagination Made Simple](#pagination-made-simple)
42
- - [🛡️ Type-Safe Query Building](#️-type-safe-query-building)
43
- - [Comparison Operators](#comparison-operators)
44
- - [Logical Operators](#logical-operators)
45
- - [Query Operations](#query-operations)
46
- - [Put Operations](#put-operations)
47
- - [Update Operations](#update-operations)
48
- - [Condition Operators](#condition-operators)
49
- - [Multiple Operations](#multiple-operations)
50
- - [🔄 Type Safety Features](#-type-safety-features)
51
- - [Nested Object Support](#nested-object-support)
52
- - [Type-Safe Conditions](#type-safe-conditions)
53
- - [🔄 Batch Operations](#-batch-operations)
54
- - [Batch Get](#batch-get)
55
- - [Batch Write](#batch-write)
56
- - [🔒 Transaction Operations](#-transaction-operations)
57
- - [Transaction Builder](#transaction-builder)
58
- - [Transaction Options](#transaction-options)
59
- - [🏗️ Entity Pattern Best Practices (Coming Soon TM)](#️-entity-pattern-best-practices-coming-soon-tm)
60
- - [🚨 Error Handling](#-error-handling)
61
- - [📚 API Reference](#-api-reference)
62
- - [Condition Operators](#condition-operators-1)
63
- - [Comparison Operators](#comparison-operators-1)
64
- - [Attribute Operators](#attribute-operators)
65
- - [Logical Operators](#logical-operators-1)
66
- - [Key Condition Operators](#key-condition-operators)
67
- - [🔮 Future Roadmap](#-future-roadmap)
68
- - [🤝 Contributing](#-contributing)
69
- - [🦔 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)
70
111
 
71
112
  ## 📦 Installation
72
113
 
114
+ <div align="center">
115
+
116
+ ### Get Started in Seconds
117
+
118
+ </div>
119
+
73
120
  ```bash
121
+ # Install the core library
74
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
75
126
  ```
76
127
 
77
- *Note: Requires AWS SDK v3 as peer dependency*
128
+ <details>
129
+ <summary><b>📋 Other Package Managers</b></summary>
78
130
 
79
131
  ```bash
80
- 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
81
137
  ```
138
+ </details>
82
139
 
83
140
  ## 🚀 Quick Start
84
141
 
85
- ### 1. Configure Your Table
142
+ <div align="center">
143
+
144
+ ### From Zero to DynamoDB Hero in Minutes
145
+
146
+ </div>
147
+
148
+ ### 1. Configure Your Jurassic Table
149
+
150
+ > **Note:** dyno-table does not create or manage the actual DynamoDB table for you. We recommend using infrastructure as code tools like Terraform, OpenTofu, SST, or AWS CDK to provision and manage your DynamoDB tables.
86
151
 
87
152
  ```ts
88
153
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
89
154
  import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
90
- import { Table } from "dyno-table";
155
+ import { Table } from "dyno-table/table";
91
156
 
92
157
  // Configure AWS SDK clients
93
158
  const client = new DynamoDBClient({ region: "us-west-2" });
94
159
  const docClient = DynamoDBDocument.from(client);
95
160
 
96
- // Initialize table with single-table design schema
161
+ // Initialise table
97
162
  const dinoTable = new Table({
98
163
  client: docClient,
99
- tableName: "DinosaurPark",
164
+ tableName: "JurassicPark",
100
165
  indexes: {
101
166
  partitionKey: "pk",
102
167
  sortKey: "sk",
103
168
  gsis: {
104
- speciesId: {
169
+ gsi1: {
105
170
  partitionKey: "gsi1pk",
106
171
  sortKey: "gsi1sk",
107
172
  },
@@ -110,10 +175,16 @@ const dinoTable = new Table({
110
175
  });
111
176
  ```
112
177
 
113
- ### 2. Perform Type-Safe Operations
178
+ ### 2. Perform Type-Safe Operations directly on the table instance
179
+
180
+ <table>
181
+ <tr>
182
+ <td>
183
+
184
+ #### 🦖 Creating a new dinosaur specimen
114
185
 
115
- **🦖 Creating a new dinosaur**
116
186
  ```ts
187
+ // Add a new T-Rex with complete type safety
117
188
  const rex = await dinoTable
118
189
  .create<Dinosaur>({
119
190
  pk: "SPECIES#trex",
@@ -127,8 +198,13 @@ const rex = await dinoTable
127
198
  .execute();
128
199
  ```
129
200
 
130
- **🔍 Query with conditions**
201
+ </td>
202
+ <td>
203
+
204
+ #### 🔍 Query with powerful conditions
205
+
131
206
  ```ts
207
+ // Find large carnivorous dinosaurs
132
208
  const largeDinos = await dinoTable
133
209
  .query<Dinosaur>({
134
210
  pk: "SPECIES#trex",
@@ -142,109 +218,599 @@ const largeDinos = await dinoTable
142
218
  .execute();
143
219
  ```
144
220
 
145
- **🔄 Complex update operation**
221
+ </td>
222
+ </tr>
223
+ <tr>
224
+ <td>
225
+
226
+ #### 🔄 Update with type-safe operations
227
+
146
228
  ```ts
229
+ // Update a dinosaur's classification
147
230
  await dinoTable
148
231
  .update<Dinosaur>({
149
- pk: "SPECIES#trex",
150
- sk: "PROFILE#trex"
232
+ pk: "SPECIES#trex",
233
+ sk: "PROFILE#trex"
151
234
  })
152
235
  .set("diet", "omnivore")
153
236
  .add("discoveryYear", 1)
154
237
  .remove("outdatedField")
155
- .condition((op) => op.attributeExists("discoverySite"))
238
+ .condition((op) =>
239
+ op.attributeExists("discoverySite")
240
+ )
241
+ .execute();
242
+ ```
243
+
244
+ </td>
245
+ <td>
246
+
247
+ #### 🔒 Transactional operations
248
+
249
+ ```ts
250
+ // Perform multiple operations atomically
251
+ await dinoTable.transaction(async (tx) => {
252
+ // Move dinosaur to new enclosure
253
+ await dinoTable
254
+ .delete({ pk: "ENCLOSURE#A", sk: "DINO#1" })
255
+ .withTransaction(tx);
256
+
257
+ await dinoTable
258
+ .create({ pk: "ENCLOSURE#B", sk: "DINO#1",
259
+ status: "ACTIVE" })
260
+ .withTransaction(tx);
261
+ });
262
+ ```
263
+
264
+ </td>
265
+ </tr>
266
+ </table>
267
+
268
+ <div align="center">
269
+ <h3>💡 See the difference with dyno-table</h3>
270
+ </div>
271
+
272
+ <table>
273
+ <tr>
274
+ <th>With dyno-table</th>
275
+ <th>Without dyno-table</th>
276
+ </tr>
277
+ <tr>
278
+ <td>
279
+
280
+ ```ts
281
+ // Type-safe, clean, and intuitive
282
+ await dinoTable
283
+ .query<Dinosaur>({
284
+ pk: "SPECIES#trex"
285
+ })
286
+ .filter(op =>
287
+ op.contains("features", "feathers")
288
+ )
156
289
  .execute();
157
290
  ```
158
291
 
292
+ </td>
293
+ <td>
294
+
295
+ ```ts
296
+ // Verbose, error-prone, no type safety
297
+ await docClient.send(new QueryCommand({
298
+ TableName: "JurassicPark",
299
+ KeyConditionExpression: "#pk = :pk",
300
+ FilterExpression: "contains(#features, :feathers)",
301
+ ExpressionAttributeNames: {
302
+ "#pk": "pk",
303
+ "#features": "features"
304
+ },
305
+ ExpressionAttributeValues: {
306
+ ":pk": "SPECIES#trex",
307
+ ":feathers": "feathers"
308
+ }
309
+ }));
310
+ ```
311
+
312
+ </td>
313
+ </tr>
314
+ </table>
315
+
316
+ ## 🏗️ Entity Pattern with Standard Schema validators
317
+
318
+ <div align="center">
319
+
320
+ ### The Most Type-Safe Way to Model Your DynamoDB Data
321
+
322
+ </div>
323
+
324
+ <table>
325
+ <tr>
326
+ <td width="70%">
327
+ <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>
328
+
329
+ <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>
330
+
331
+ <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>
332
+ </td>
333
+ <td width="30%">
334
+
335
+ #### Entity Pattern Benefits
336
+
337
+ - 🛡️ **Type-safe operations**
338
+ - 🧪 **Schema validation**
339
+ - 🔑 **Automatic key generation**
340
+ - 📦 **Repository pattern**
341
+ - 🔍 **Custom query builders**
342
+ - 🔄 **Lifecycle hooks**
343
+
344
+ </td>
345
+ </tr>
346
+ </table>
347
+
348
+ ### Defining Entities
349
+
350
+ Entities are defined using the `defineEntity` function, which takes a configuration object that includes a schema, primary key definition, and optional indexes and queries.
351
+
352
+ ```ts
353
+ import { z } from "zod";
354
+ import { defineEntity, createIndex } from "dyno-table/entity";
355
+
356
+ // Define your schema using Zod
357
+ const dinosaurSchema = z.object({
358
+ id: z.string(),
359
+ species: z.string(),
360
+ name: z.string(),
361
+ diet: z.enum(["carnivore", "herbivore", "omnivore"]),
362
+ dangerLevel: z.number().int().min(1).max(10),
363
+ height: z.number().positive(),
364
+ weight: z.number().positive(),
365
+ status: z.enum(["active", "inactive", "sick", "deceased"]),
366
+ createdAt: z.string().optional(),
367
+ updatedAt: z.string().optional(),
368
+ });
369
+
370
+ // Infer the type from the schema
371
+ type Dinosaur = z.infer<typeof dinosaurSchema>;
372
+
373
+ // Define key templates for Dinosaur entity
374
+ const dinosaurPK = partitionKey`ENTITY#DINOSAUR#DIET#${"diet"}`;
375
+ const dinosaurSK = sortKey`ID#${"id"}#SPECIES#${"species"}`;
376
+
377
+ // Create a primary index for Dinosaur entity
378
+ const primaryKey = createIndex()
379
+ .input(z.object({ id: z.string(), diet: z.string(), species: z.string() }))
380
+ .partitionKey(({ diet }) => dinosaurPK({ diet }))
381
+ .sortKey(({ id, species }) => dinosaurSK({ species, id }));
382
+
383
+ // Define the entity
384
+ const DinosaurEntity = defineEntity({
385
+ name: "Dinosaur",
386
+ schema: dinosaurSchema,
387
+ primaryKey,
388
+ });
389
+
390
+ // Create a repository
391
+ const dinosaurRepo = DinosaurEntity.createRepository(table);
392
+ ```
393
+
394
+ ### Entity Features
395
+
396
+ #### 1. Schema Validation
397
+
398
+ Entities use Zod schemas to validate data before operations:
399
+
400
+ ```ts
401
+ // Define a schema with Zod
402
+ const dinosaurSchema = z.object({
403
+ id: z.string(),
404
+ species: z.string(),
405
+ name: z.string(),
406
+ diet: z.enum(["carnivore", "herbivore", "omnivore"]),
407
+ dangerLevel: z.number().int().min(1).max(10),
408
+ height: z.number().positive(),
409
+ weight: z.number().positive(),
410
+ status: z.enum(["active", "inactive", "sick", "deceased"]),
411
+ tags: z.array(z.string()).optional(),
412
+ });
413
+
414
+ // Create an entity with the schema
415
+ const DinosaurEntity = defineEntity({
416
+ name: "Dinosaur",
417
+ schema: dinosaurSchema,
418
+ primaryKey: createIndex()
419
+ .input(z.object({ id: z.string(), diet: z.string(), species: z.string() }))
420
+ .partitionKey(({ diet }) => dinosaurPK({ diet }))
421
+ // could also be .withoutSortKey() if your table doesn't use sort keys
422
+ .sortKey(({ id, species }) => dinosaurSK({ species, id }))
423
+ });
424
+ ```
425
+
426
+ #### 2. CRUD Operations
427
+
428
+ Entities provide type-safe CRUD operations:
429
+
430
+ ```ts
431
+ // Create a new dinosaur
432
+ await dinosaurRepo.create({
433
+ id: "dino-001",
434
+ species: "Tyrannosaurus Rex",
435
+ name: "Rexy",
436
+ diet: "carnivore",
437
+ dangerLevel: 10,
438
+ height: 5.2,
439
+ weight: 7000,
440
+ status: "active",
441
+ }).execute();
442
+
443
+ // Get a dinosaur
444
+ const dino = await dinosaurRepo.get({
445
+ id: "dino-001",
446
+ diet: "carnivore",
447
+ species: "Tyrannosaurus Rex",
448
+ }).execute();
449
+
450
+ // Update a dinosaur
451
+ await dinosaurRepo.update(
452
+ { id: "dino-001", diet: "carnivore", species: "Tyrannosaurus Rex" },
453
+ { weight: 7200, status: "sick" }
454
+ ).execute();
455
+
456
+ // Delete a dinosaur
457
+ await dinosaurRepo.delete({
458
+ id: "dino-001",
459
+ diet: "carnivore",
460
+ species: "Tyrannosaurus Rex",
461
+ }).execute();
462
+ ```
463
+
464
+ #### 3. Custom Queries
465
+
466
+ Define custom queries with input validation:
467
+
468
+ ```ts
469
+ import { createQueries } from "dyno-table/entity";
470
+
471
+ const createQuery = createQueries<Dinosaur>();
472
+
473
+ const DinosaurEntity = defineEntity({
474
+ name: "Dinosaur",
475
+ schema: dinosaurSchema,
476
+ primaryKey,
477
+ queries: {
478
+ byDiet: createQuery
479
+ .input(
480
+ z.object({
481
+ diet: z.enum(["carnivore", "herbivore", "omnivore"]),
482
+ })
483
+ )
484
+ .query(({ input, entity }) => {
485
+ return entity
486
+ .query({
487
+ pk: dinosaurPK({diet: input.diet})
488
+ });
489
+ }),
490
+
491
+ bySpecies: createQuery
492
+ .input(
493
+ z.object({
494
+ species: z.string(),
495
+ })
496
+ )
497
+ .query(({ input, entity }) => {
498
+ return entity
499
+ .scan()
500
+ .filter((op) => op.eq("species", input.species));
501
+ }),
502
+ },
503
+ });
504
+
505
+ // Use the custom queries
506
+ const carnivores = await dinosaurRepo.query.byDiet({ diet: "carnivore" }).execute();
507
+ const trexes = await dinosaurRepo.query.bySpecies({ species: "Tyrannosaurus Rex" }).execute();
508
+ ```
509
+
510
+ #### 4. Defining GSI access patterns
511
+
512
+ Define GSI (LSI support coming later)
513
+
514
+ ```ts
515
+ import { createIndex } from "dyno-table/entity";
516
+
517
+ // Define GSIs templates for querying by species
518
+ const gsi1PK = partitionKey`SPECIES#${"species"}`
519
+ const gsi1SK = sortKey`DINOSAUR#${"id"}`
520
+
521
+ // Implement typesafe generator for the GSI - This is used in create calls to ensure the GSI is generated
522
+ const speciesIndex = createIndex()
523
+ .input(dinosaurSchema)
524
+ .partitionKey(({ species }) => gsi1PK({ species }))
525
+ .sortKey(({ id }) => gsi1SK({ id }));
526
+
527
+ const DinosaurEntity = defineEntity({
528
+ name: "Dinosaur",
529
+ schema: dinosaurSchema,
530
+ primaryKey,
531
+ indexes: {
532
+ species: speciesIndex,
533
+ },
534
+ queries: {
535
+ bySpecies: createQuery
536
+ .input(
537
+ z.object({
538
+ species: z.string(),
539
+ })
540
+ )
541
+ .query(({ input, entity }) => {
542
+ return entity
543
+ .query({
544
+ // Use the GSI template generator to avoid typos
545
+ pk: gsi1PK({species: input.species}),
546
+ })
547
+ // Use the template name as defined in the table instance
548
+ .useIndex("gsi1");
549
+ }),
550
+ },
551
+ });
552
+ ```
553
+
554
+ ### Complete Entity Example
555
+
556
+ Here's a complete example of using Zod schemas directly:
557
+
558
+ ```ts
559
+ import { z } from "zod";
560
+ import { defineEntity, createQueries, createIndex } from "dyno-table/entity";
561
+ import { Table } from "dyno-table/table";
562
+ import { sortKey } from "dyno-table/utils/sort-key-template";
563
+ import { partitionKey } from "dyno-table/utils/partition-key-template";
564
+
565
+ // Define the schema with Zod
566
+ const dinosaurSchema = z.object({
567
+ id: z.string(),
568
+ species: z.string(),
569
+ name: z.string(),
570
+ enclosureId: z.string(),
571
+ diet: z.enum(["carnivore", "herbivore", "omnivore"]),
572
+ dangerLevel: z.number().int().min(1).max(10),
573
+ height: z.number().positive(),
574
+ weight: z.number().positive(),
575
+ status: z.enum(["active", "inactive", "sick", "deceased"]),
576
+ trackingChipId: z.string().optional(),
577
+ lastFed: z.string().optional(),
578
+ createdAt: z.string().optional(),
579
+ updatedAt: z.string().optional(),
580
+ });
581
+
582
+ // Infer the type from the schema
583
+ type Dinosaur = z.infer<typeof dinosaurSchema>;
584
+
585
+ // Define key templates
586
+ const dinosaurPK = partitionKey`DINOSAUR#${"id"}`;
587
+ const dinosaurSK = sortKey`STATUS#${"status"}`;
588
+
589
+ const gsi1PK = partitionKey`SPECIES#${"species"}`
590
+ const gsi1SK = sortKey`DINOSAUR#${"id"}`
591
+
592
+ const gsi2PK = partitionKey`ENCLOSURE#${"enclosureId"}`
593
+ const gsi2SK = sortKey`DINOSAUR#${"id"}`
594
+
595
+ // Create a primary index
596
+ const primaryKey = createIndex()
597
+ .input(dinosaurSchema)
598
+ .partitionKey(({ id }) => dinosaurPK(id))
599
+ .sortKey(({ status }) => dinosaurSK(status));
600
+
601
+ // Create a GSI for querying by species
602
+ const speciesIndex = createIndex()
603
+ .input(dinosaurSchema)
604
+ .partitionKey(({ species }) => gsi1PK({ species }))
605
+ .sortKey(({ id }) => gsiSK({ id }));
606
+
607
+ // Create a GSI for querying by enclosure
608
+ const enclosureIndex = createIndex()
609
+ .input(dinosaurSchema)
610
+ .partitionKey(({ enclosureId }) => gsi2PK({ enclosureId }))
611
+ .sortKey(({ id }) => gsi2SK({ id }));
612
+
613
+ // Create query builders
614
+ const createQuery = createQueries<Dinosaur>();
615
+
616
+ // Define the entity
617
+ const DinosaurEntity = defineEntity({
618
+ name: "Dinosaur",
619
+ schema: dinosaurSchema,
620
+ primaryKey,
621
+ indexes: {
622
+ // These keys need to be named after the name of the GSI that is defined in your table instance
623
+ gsi1: speciesIndex,
624
+ gsi2: enclosureIndex,
625
+ },
626
+ queries: {
627
+ bySpecies: createQuery
628
+ .input(
629
+ z.object({
630
+ species: z.string(),
631
+ })
632
+ )
633
+ .query(({ input, entity }) => {
634
+ return entity
635
+ .query({
636
+ pk: gsi1PK({ species: input.species }),
637
+ })
638
+ .useIndex("gsi1");
639
+ }),
640
+
641
+ byEnclosure: createQuery
642
+ .input(
643
+ z.object({
644
+ enclosureId: z.string(),
645
+ })
646
+ )
647
+ .query(({ input, entity }) => {
648
+ return entity
649
+ .query({
650
+ pk: gsi2PK({ enclosureId: input.enclosureId }),
651
+ })
652
+ .useIndex("gsi2");
653
+ }),
654
+
655
+ dangerousInEnclosure: createQuery
656
+ .input(
657
+ z.object({
658
+ enclosureId: z.string(),
659
+ minDangerLevel: z.number().int().min(1).max(10),
660
+ })
661
+ )
662
+ .query(({ input, entity }) => {
663
+ return entity
664
+ .query({
665
+ pk: gsi2PK({ enclosureId: input.enclosureId }),
666
+ })
667
+ .useIndex("gsi2")
668
+ .filter((op) => op.gte("dangerLevel", input.minDangerLevel));
669
+ }),
670
+ },
671
+ });
672
+
673
+ // Create a repository
674
+ const dinosaurRepo = DinosaurEntity.createRepository(table);
675
+
676
+ // Use the repository
677
+ async function main() {
678
+ // Create a dinosaur
679
+ await dinosaurRepo
680
+ .create({
681
+ id: "dino-001",
682
+ species: "Tyrannosaurus Rex",
683
+ name: "Rexy",
684
+ enclosureId: "enc-001",
685
+ diet: "carnivore",
686
+ dangerLevel: 10,
687
+ height: 5.2,
688
+ weight: 7000,
689
+ status: "active",
690
+ trackingChipId: "TRX-001",
691
+ })
692
+ .execute();
693
+
694
+ // Query dinosaurs by species
695
+ const trexes = await dinosaurRepo.query.bySpecies({
696
+ species: "Tyrannosaurus Rex"
697
+ }).execute();
698
+
699
+ // Query dangerous dinosaurs in an enclosure
700
+ const dangerousDinos = await dinosaurRepo.query.dangerousInEnclosure({
701
+ enclosureId: "enc-001",
702
+ minDangerLevel: 8,
703
+ }).execute();
704
+ }
705
+ ```
706
+
707
+ **Key benefits:**
708
+ - 🚫 Prevents accidental cross-type data access
709
+ - 🔍 Automatically filters queries/scans to a repository type
710
+ - 🛡️ Ensures consistent key structure across entities
711
+ - 📦 Encapsulates domain-specific query logic
712
+ - 🧪 Validates data with Zod schemas
713
+ - 🔄 Provides type inference from schemas
714
+
159
715
  ## 🧩 Advanced Features
160
716
 
161
717
  ### Transactional Operations
162
718
 
163
719
  **Safe dinosaur transfer between enclosures**
164
720
  ```ts
165
- // Start a transaction session for transferring a dinosaur
721
+ // Start a transaction session for transferring a T-Rex to a new enclosure
722
+ // Critical for safety: All operations must succeed or none will be applied
166
723
  await dinoTable.transaction(async (tx) => {
167
724
  // All operations are executed as a single transaction (up to 100 operations)
725
+ // This ensures the dinosaur transfer is atomic - preventing half-completed transfers
168
726
 
169
- // Check if destination enclosure is ready and compatible
727
+ // STEP 1: Check if destination enclosure is ready and compatible with the dinosaur
728
+ // We must verify the enclosure is prepared and suitable for a carnivore
170
729
  await dinoTable
171
730
  .conditionCheck({
172
- pk: "ENCLOSURE#B",
173
- sk: "STATUS"
731
+ pk: "ENCLOSURE#B", // Target enclosure B
732
+ sk: "STATUS" // Check the enclosure status record
174
733
  })
175
734
  .condition(op => op.and(
176
- op.eq("status", "READY"),
177
- op.eq("diet", "Carnivore") // Ensure enclosure matches dinosaur diet
735
+ op.eq("status", "READY"), // Enclosure must be in READY state
736
+ op.eq("diet", "Carnivore") // Must support carnivorous dinosaurs
178
737
  ))
179
738
  .withTransaction(tx);
180
739
 
181
- // Remove dinosaur from current enclosure
740
+ // STEP 2: Remove dinosaur from current enclosure
741
+ // Only proceed if the dinosaur is healthy enough for transfer
182
742
  await dinoTable
183
743
  .delete<Dinosaur>({
184
- pk: "ENCLOSURE#A",
185
- sk: "DINO#001"
744
+ pk: "ENCLOSURE#A", // Source enclosure A
745
+ sk: "DINO#001" // T-Rex with ID 001
186
746
  })
187
747
  .condition(op => op.and(
188
- op.eq("status", "HEALTHY"),
189
- op.gte("health", 80) // Only transfer healthy dinosaurs
748
+ op.eq("status", "HEALTHY"), // Dinosaur must be in HEALTHY state
749
+ op.gte("health", 80) // Health must be at least 80%
190
750
  ))
191
751
  .withTransaction(tx);
192
752
 
193
- // Add dinosaur to new enclosure
753
+ // STEP 3: Add dinosaur to new enclosure
754
+ // Create a fresh record in the destination enclosure
194
755
  await dinoTable
195
756
  .create<Dinosaur>({
196
- pk: "ENCLOSURE#B",
197
- sk: "DINO#001",
198
- name: "Rex",
199
- species: "Tyrannosaurus",
200
- diet: "Carnivore",
201
- status: "HEALTHY",
202
- health: 100,
203
- enclosureId: "B",
204
- lastFed: new Date().toISOString()
757
+ pk: "ENCLOSURE#B", // Destination enclosure B
758
+ sk: "DINO#001", // Same dinosaur ID for tracking
759
+ name: "Rex", // Dinosaur name
760
+ species: "Tyrannosaurus", // Species classification
761
+ diet: "Carnivore", // Dietary requirements
762
+ status: "HEALTHY", // Current health status
763
+ health: 100, // Reset health to 100% after transfer
764
+ enclosureId: "B", // Update enclosure reference
765
+ lastFed: new Date().toISOString() // Reset feeding clock
205
766
  })
206
767
  .withTransaction(tx);
207
768
 
208
- // Update enclosure occupancy tracking
769
+ // STEP 4: Update enclosure occupancy tracking
770
+ // Keep accurate count of dinosaurs in each enclosure
209
771
  await dinoTable
210
772
  .update<Dinosaur>({
211
- pk: "ENCLOSURE#B",
212
- sk: "OCCUPANCY"
773
+ pk: "ENCLOSURE#B", // Target enclosure B
774
+ sk: "OCCUPANCY" // Occupancy tracking record
213
775
  })
214
- .add("currentOccupants", 1)
215
- .set("lastUpdated", new Date().toISOString())
776
+ .add("currentOccupants", 1) // Increment occupant count
777
+ .set("lastUpdated", new Date().toISOString()) // Update timestamp
216
778
  .withTransaction(tx);
217
779
  });
218
780
 
219
- // Transaction with feeding and health monitoring
781
+ // Transaction for dinosaur feeding and health monitoring
782
+ // Ensures feeding status and schedule are updated atomically
220
783
  await dinoTable.transaction(
221
784
  async (tx) => {
222
- // Update dinosaur health and feeding status
785
+ // STEP 1: Update Stegosaurus health and feeding status
786
+ // Record that the dinosaur has been fed and update its health metrics
223
787
  await dinoTable
224
788
  .update<Dinosaur>({
225
- pk: "ENCLOSURE#D",
226
- sk: "DINO#003"
789
+ pk: "ENCLOSURE#D", // Herbivore enclosure D
790
+ sk: "DINO#003" // Stegosaurus with ID 003
227
791
  })
228
792
  .set({
229
- status: "HEALTHY",
230
- lastFed: new Date().toISOString(),
231
- health: 100
793
+ status: "HEALTHY", // Update health status
794
+ lastFed: new Date().toISOString(), // Record feeding time
795
+ health: 100 // Reset health to 100%
232
796
  })
233
- .deleteElementsFromSet("tags", ["needs_feeding"])
797
+ .deleteElementsFromSet("tags", ["needs_feeding"]) // Remove feeding alert tag
234
798
  .withTransaction(tx);
235
799
 
236
- // Update enclosure feeding schedule
800
+ // STEP 2: Update enclosure feeding schedule
801
+ // Schedule next feeding time for tomorrow
237
802
  await dinoTable
238
803
  .update<Dinosaur>({
239
- pk: "ENCLOSURE#D",
240
- sk: "SCHEDULE"
804
+ pk: "ENCLOSURE#D", // Same herbivore enclosure
805
+ sk: "SCHEDULE" // Feeding schedule record
241
806
  })
242
- .set("nextFeedingTime", new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString())
807
+ .set("nextFeedingTime", new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()) // 24 hours from now
243
808
  .withTransaction(tx);
244
809
  },
245
810
  {
246
- clientRequestToken: "feeding-session-001",
247
- returnConsumedCapacity: "TOTAL"
811
+ // Transaction options for tracking and idempotency
812
+ clientRequestToken: "feeding-session-001", // Prevents duplicate feeding operations
813
+ returnConsumedCapacity: "TOTAL" // Track capacity usage for park operations
248
814
  }
249
815
  );
250
816
  ```
@@ -253,7 +819,6 @@ await dinoTable.transaction(
253
819
  - 🔄 Uses the same familiar API as non-transactional operations
254
820
  - 🧠 Maintains consistent mental model for developers
255
821
  - 🔒 All operations within the callback are executed as a single transaction
256
- - ✅ All-or-nothing operations (ACID compliance)
257
822
  - 🛡️ Prevents race conditions and data inconsistencies
258
823
  - 📊 Supports up to 100 actions per transaction
259
824
 
@@ -261,35 +826,42 @@ await dinoTable.transaction(
261
826
 
262
827
  **Efficient dinosaur park management with bulk operations**
263
828
  ```ts
264
- // Batch health check for multiple dinosaurs
829
+ // SCENARIO 1: Morning health check for multiple dinosaurs across enclosures
830
+ // Retrieve health status for multiple dinosaurs in a single operation
265
831
  const healthCheckKeys = [
266
- { pk: "ENCLOSURE#A", sk: "DINO#001" }, // T-Rex
267
- { pk: "ENCLOSURE#B", sk: "DINO#002" }, // Velociraptor
268
- { pk: "ENCLOSURE#C", sk: "DINO#003" } // Stegosaurus
832
+ { pk: "ENCLOSURE#A", sk: "DINO#001" }, // T-Rex in Paddock A
833
+ { pk: "ENCLOSURE#B", sk: "DINO#002" }, // Velociraptor in Paddock B
834
+ { pk: "ENCLOSURE#C", sk: "DINO#003" } // Stegosaurus in Paddock C
269
835
  ];
270
836
 
837
+ // Perform batch get operation to retrieve all dinosaurs at once
838
+ // This is much more efficient than individual gets
271
839
  const { items: dinosaurs, unprocessedKeys } = await dinoTable.batchGet<Dinosaur>(healthCheckKeys);
272
840
  console.log(`Health check completed for ${dinosaurs.length} dinosaurs`);
841
+
842
+ // Process health check results and identify any dinosaurs needing attention
273
843
  dinosaurs.forEach(dino => {
274
844
  if (dino.health < 80) {
275
845
  console.log(`Health alert for ${dino.name} in Enclosure ${dino.enclosureId}`);
846
+ // In a real application, you might trigger alerts or schedule veterinary visits
276
847
  }
277
848
  });
278
849
 
279
- // Batch update feeding schedule for herbivore group
850
+ // SCENARIO 2: Adding new herbivores to the park after quarantine
851
+ // Prepare data for multiple new herbivores joining the collection
280
852
  const newHerbivores = [
281
853
  {
282
854
  pk: "ENCLOSURE#D", sk: "DINO#004",
283
- name: "Triceratops Alpha",
855
+ name: "Triceratops Alpha", // Three-horned herbivore
284
856
  species: "Triceratops",
285
857
  diet: "Herbivore",
286
858
  status: "HEALTHY",
287
- health: 95,
288
- lastFed: new Date().toISOString()
859
+ health: 95, // Excellent health after quarantine
860
+ lastFed: new Date().toISOString() // Just fed before joining main enclosure
289
861
  },
290
862
  {
291
863
  pk: "ENCLOSURE#D", sk: "DINO#005",
292
- name: "Brachy",
864
+ name: "Brachy", // Long-necked herbivore
293
865
  species: "Brachiosaurus",
294
866
  diet: "Herbivore",
295
867
  status: "HEALTHY",
@@ -298,91 +870,117 @@ const newHerbivores = [
298
870
  }
299
871
  ];
300
872
 
301
- // Add new herbivores to enclosure
873
+ // Add all new herbivores to the enclosure in a single batch operation
874
+ // More efficient than individual writes and ensures consistent state
302
875
  await dinoTable.batchWrite(
303
876
  newHerbivores.map(dino => ({
304
- type: "put",
305
- item: dino
877
+ type: "put", // Create or replace operation
878
+ item: dino // Full dinosaur record
306
879
  }))
307
880
  );
308
881
 
309
- // Mixed operations: relocate dinosaurs and update enclosure status
882
+ // SCENARIO 3: Releasing a dinosaur from quarantine to general population
883
+ // Multiple related operations performed as a batch
310
884
  await dinoTable.batchWrite([
311
- // Remove dinosaur from quarantine
312
- { type: "delete", key: { pk: "ENCLOSURE#QUARANTINE", sk: "DINO#006" } },
313
- // Add recovered dinosaur to main enclosure
885
+ // Step 1: Remove dinosaur from quarantine enclosure
886
+ {
887
+ type: "delete",
888
+ key: { pk: "ENCLOSURE#QUARANTINE", sk: "DINO#006" }
889
+ },
890
+
891
+ // Step 2: Add recovered dinosaur to main raptor enclosure
314
892
  {
315
893
  type: "put",
316
894
  item: {
317
895
  pk: "ENCLOSURE#E", sk: "DINO#006",
318
- name: "Raptor Beta",
896
+ name: "Raptor Beta", // Juvenile Velociraptor
319
897
  species: "Velociraptor",
320
898
  diet: "Carnivore",
321
- status: "HEALTHY",
899
+ status: "HEALTHY", // Now healthy after treatment
322
900
  health: 100,
323
901
  lastFed: new Date().toISOString()
324
902
  }
325
903
  },
326
- // Clear quarantine status
327
- { type: "delete", key: { pk: "ENCLOSURE#QUARANTINE", sk: "STATUS#DINO#006" } }
904
+
905
+ // Step 3: Clear quarantine status record
906
+ {
907
+ type: "delete",
908
+ key: { pk: "ENCLOSURE#QUARANTINE", sk: "STATUS#DINO#006" }
909
+ }
328
910
  ]);
329
911
 
330
- // Handle large-scale park operations
331
- // (25 items per batch write, 100 items per batch get)
912
+ // SCENARIO 4: Daily park-wide health monitoring
913
+ // Handle large-scale operations across all dinosaurs
914
+ // The library automatically handles chunking for large batches:
915
+ // - 25 items per batch write
916
+ // - 100 items per batch get
332
917
  const dailyHealthUpdates = generateDinosaurHealthUpdates(); // Hundreds of updates
333
- await dinoTable.batchWrite(dailyHealthUpdates); // Automatically chunked
918
+ await dinoTable.batchWrite(dailyHealthUpdates); // Automatically chunked into multiple requests
334
919
  ```
335
920
 
336
921
  ### Pagination Made Simple
337
922
 
338
- **Efficient dinosaur record browsing**
923
+ **Efficient dinosaur record browsing for park management**
339
924
  ```ts
340
- // Create a paginator for viewing herbivores by health status
925
+ // SCENARIO 1: Herbivore health monitoring with pagination
926
+ // Create a paginator for viewing healthy herbivores in manageable chunks
927
+ // Perfect for veterinary staff doing routine health checks
341
928
  const healthyHerbivores = dinoTable
342
929
  .query<Dinosaur>({
343
- pk: "DIET#herbivore",
344
- sk: op => op.beginsWith("STATUS#HEALTHY")
930
+ pk: "DIET#herbivore", // Target all herbivorous dinosaurs
931
+ sk: op => op.beginsWith("STATUS#HEALTHY") // Only those with HEALTHY status
345
932
  })
346
933
  .filter((op) => op.and(
347
- op.gte("health", 90),
348
- op.attributeExists("lastFed")
934
+ op.gte("health", 90), // Only those with excellent health (90%+)
935
+ op.attributeExists("lastFed") // Must have feeding records
349
936
  ))
350
- .paginate(5); // View 5 dinosaurs at a time
937
+ .paginate(5); // Process in small batches of 5 dinosaurs
351
938
 
352
- // Monitor all enclosures page by page
939
+ // Iterate through all pages of results - useful for processing large datasets
940
+ // without loading everything into memory at once
941
+ console.log("🦕 Beginning herbivore health inspection rounds...");
353
942
  while (healthyHerbivores.hasNextPage()) {
943
+ // Get the next page of dinosaurs
354
944
  const page = await healthyHerbivores.getNextPage();
355
945
  console.log(`Checking herbivores page ${page.page}, found ${page.items.length} dinosaurs`);
946
+
947
+ // Process each dinosaur in the current page
356
948
  page.items.forEach(dino => {
357
949
  console.log(`${dino.name}: Health ${dino.health}%, Last fed: ${dino.lastFed}`);
950
+ // In a real app, you might update health records or schedule next checkup
358
951
  });
359
952
  }
360
953
 
361
- // Get all carnivores for daily feeding schedule
954
+ // SCENARIO 2: Preparing carnivore feeding schedule
955
+ // Get all carnivores at once for daily feeding planning
956
+ // This approach loads all matching items into memory
362
957
  const carnivoreSchedule = await dinoTable
363
958
  .query<Dinosaur>({
364
- pk: "DIET#carnivore",
365
- sk: op => op.beginsWith("ENCLOSURE#")
959
+ pk: "DIET#carnivore", // Target all carnivorous dinosaurs
960
+ sk: op => op.beginsWith("ENCLOSURE#") // Organized by enclosure
366
961
  })
367
- .filter(op => op.attributeExists("lastFed"))
368
- .paginate(10)
369
- .getAllPages();
962
+ .filter(op => op.attributeExists("lastFed")) // Only those with feeding records
963
+ .paginate(10) // Process in pages of 10
964
+ .getAllPages(); // But collect all results at once
370
965
 
371
966
  console.log(`Scheduling feeding for ${carnivoreSchedule.length} carnivores`);
967
+ // Now we can sort and organize feeding times based on species, size, etc.
372
968
 
373
- // Limited view for visitor information kiosk
969
+ // SCENARIO 3: Visitor information kiosk with limited display
970
+ // Create a paginated view for the public-facing dinosaur information kiosk
374
971
  const visitorKiosk = dinoTable
375
972
  .query<Dinosaur>({
376
- pk: "VISITOR_VIEW",
377
- sk: op => op.beginsWith("SPECIES#")
973
+ pk: "VISITOR_VIEW", // Special partition for visitor-facing data
974
+ sk: op => op.beginsWith("SPECIES#") // Organized by species
378
975
  })
379
- .filter(op => op.eq("status", "ON_DISPLAY"))
380
- .limit(12) // Show max 12 dinosaurs per view
381
- .paginate(4); // Display 4 at a time
976
+ .filter(op => op.eq("status", "ON_DISPLAY")) // Only show dinosaurs currently on display
977
+ .limit(12) // Show maximum 12 dinosaurs total
978
+ .paginate(4); // Display 4 at a time for easy viewing
382
979
 
383
- // Get first page for kiosk display
980
+ // Get first page for initial kiosk display
384
981
  const firstPage = await visitorKiosk.getNextPage();
385
- console.log(`Now showing: ${firstPage.items.map(d => d.name).join(", ")}`);
982
+ console.log(`🦖 Now showing: ${firstPage.items.map(d => d.name).join(", ")}`);
983
+ // Visitors can press "Next" to see more dinosaurs in the collection
386
984
  ```
387
985
 
388
986
  ## 🛡️ Type-Safe Query Building
@@ -670,24 +1268,6 @@ const result = await table.transaction(
670
1268
  );
671
1269
  ```
672
1270
 
673
- ## 🏗️ Entity Pattern Best Practices (Coming Soon TM)
674
-
675
- The entity implementation provides automatic type isolation:
676
-
677
- ```ts
678
- // All operations are automatically scoped to DINOSAUR type
679
- const dinosaur = await dinoEntity.get("SPECIES#trex", "PROFILE#trex");
680
- // Returns Dinosaur | undefined
681
-
682
- // Cross-type operations are prevented at compile time
683
- dinoEntity.create({ /* invalid shape */ }); // TypeScript error
684
- ```
685
-
686
- **Key benefits:**
687
- - 🚫 Prevents accidental cross-type data access
688
- - 🔍 Automatically filters queries/scans to repository type
689
- - 🛡️ Ensures consistent key structure across entities
690
- - 📦 Encapsulates domain-specific query logic
691
1271
 
692
1272
  ## 🚨 Error Handling
693
1273