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