dyno-table 0.1.7 → 0.2.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/LICENSE +21 -0
  2. package/README.md +681 -160
  3. package/dist/builder-types-C_PDZhnP.d.ts +118 -0
  4. package/dist/builder-types-DtwbqMeF.d.cts +118 -0
  5. package/dist/builders/condition-check-builder.cjs +394 -0
  6. package/dist/builders/condition-check-builder.cjs.map +1 -0
  7. package/dist/builders/condition-check-builder.d.cts +157 -0
  8. package/dist/builders/condition-check-builder.d.ts +157 -0
  9. package/dist/builders/condition-check-builder.js +392 -0
  10. package/dist/builders/condition-check-builder.js.map +1 -0
  11. package/dist/builders/delete-builder.cjs +405 -0
  12. package/dist/builders/delete-builder.cjs.map +1 -0
  13. package/dist/builders/delete-builder.d.cts +166 -0
  14. package/dist/builders/delete-builder.d.ts +166 -0
  15. package/dist/builders/delete-builder.js +403 -0
  16. package/dist/builders/delete-builder.js.map +1 -0
  17. package/dist/builders/paginator.cjs +199 -0
  18. package/dist/builders/paginator.cjs.map +1 -0
  19. package/dist/builders/paginator.d.cts +179 -0
  20. package/dist/builders/paginator.d.ts +179 -0
  21. package/dist/builders/paginator.js +197 -0
  22. package/dist/builders/paginator.js.map +1 -0
  23. package/dist/builders/put-builder.cjs +476 -0
  24. package/dist/builders/put-builder.cjs.map +1 -0
  25. package/dist/builders/put-builder.d.cts +274 -0
  26. package/dist/builders/put-builder.d.ts +274 -0
  27. package/dist/builders/put-builder.js +474 -0
  28. package/dist/builders/put-builder.js.map +1 -0
  29. package/dist/builders/query-builder.cjs +674 -0
  30. package/dist/builders/query-builder.cjs.map +1 -0
  31. package/dist/builders/query-builder.d.cts +6 -0
  32. package/dist/builders/query-builder.d.ts +6 -0
  33. package/dist/builders/query-builder.js +672 -0
  34. package/dist/builders/query-builder.js.map +1 -0
  35. package/dist/builders/transaction-builder.cjs +894 -0
  36. package/dist/builders/transaction-builder.cjs.map +1 -0
  37. package/dist/builders/transaction-builder.d.cts +511 -0
  38. package/dist/builders/transaction-builder.d.ts +511 -0
  39. package/dist/builders/transaction-builder.js +892 -0
  40. package/dist/builders/transaction-builder.js.map +1 -0
  41. package/dist/builders/update-builder.cjs +627 -0
  42. package/dist/builders/update-builder.cjs.map +1 -0
  43. package/dist/builders/update-builder.d.cts +365 -0
  44. package/dist/builders/update-builder.d.ts +365 -0
  45. package/dist/builders/update-builder.js +625 -0
  46. package/dist/builders/update-builder.js.map +1 -0
  47. package/dist/conditions--ld9a78i.d.ts +331 -0
  48. package/dist/conditions-ChhQWd6z.d.cts +331 -0
  49. package/dist/conditions.cjs +59 -0
  50. package/dist/conditions.cjs.map +1 -0
  51. package/dist/conditions.d.cts +3 -0
  52. package/dist/conditions.d.ts +3 -0
  53. package/dist/conditions.js +43 -0
  54. package/dist/conditions.js.map +1 -0
  55. package/dist/entity.cjs +228 -0
  56. package/dist/entity.cjs.map +1 -0
  57. package/dist/entity.d.cts +149 -0
  58. package/dist/entity.d.ts +149 -0
  59. package/dist/entity.js +224 -0
  60. package/dist/entity.js.map +1 -0
  61. package/dist/query-builder-Csror9Iu.d.ts +507 -0
  62. package/dist/query-builder-D2FM9rsu.d.cts +507 -0
  63. package/dist/standard-schema.cjs +4 -0
  64. package/dist/standard-schema.cjs.map +1 -0
  65. package/dist/standard-schema.d.cts +57 -0
  66. package/dist/standard-schema.d.ts +57 -0
  67. package/dist/standard-schema.js +3 -0
  68. package/dist/standard-schema.js.map +1 -0
  69. package/dist/table-BEhBPy2G.d.cts +364 -0
  70. package/dist/table-BW3cmUqr.d.ts +364 -0
  71. package/dist/{index.js → table.cjs} +330 -354
  72. package/dist/table.cjs.map +1 -0
  73. package/dist/table.d.cts +12 -0
  74. package/dist/table.d.ts +12 -0
  75. package/dist/table.js +3243 -0
  76. package/dist/table.js.map +1 -0
  77. package/dist/types.cjs +4 -0
  78. package/dist/types.cjs.map +1 -0
  79. package/dist/types.d.cts +22 -0
  80. package/dist/types.d.ts +22 -0
  81. package/dist/types.js +3 -0
  82. package/dist/types.js.map +1 -0
  83. package/dist/utils/partition-key-template.cjs +19 -0
  84. package/dist/utils/partition-key-template.cjs.map +1 -0
  85. package/dist/utils/partition-key-template.d.cts +32 -0
  86. package/dist/utils/partition-key-template.d.ts +32 -0
  87. package/dist/utils/partition-key-template.js +17 -0
  88. package/dist/utils/partition-key-template.js.map +1 -0
  89. package/dist/utils/sort-key-template.cjs +19 -0
  90. package/dist/utils/sort-key-template.cjs.map +1 -0
  91. package/dist/utils/sort-key-template.d.cts +35 -0
  92. package/dist/utils/sort-key-template.d.ts +35 -0
  93. package/dist/utils/sort-key-template.js +17 -0
  94. package/dist/utils/sort-key-template.js.map +1 -0
  95. package/package.json +97 -22
  96. package/dist/index.d.ts +0 -2923
package/README.md CHANGED
@@ -6,45 +6,77 @@
6
6
  <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
7
 
8
8
  ```ts
9
- // Type-safe DynamoDB operations made simple
10
- await table
9
+ // Type-safe dinosaur tracking operations made simple
10
+ await dinoTable
11
11
  .update<Dinosaur>({
12
12
  pk: 'SPECIES#trex',
13
13
  sk: 'PROFILE#001'
14
14
  })
15
- .set('diet', 'Carnivore')
16
- .add('sightings', 1)
17
- .condition(op => op.eq('status', 'ACTIVE'))
15
+ .set('diet', 'Carnivore') // Update dietary classification
16
+ .add('sightings', 1) // Increment sighting counter
17
+ .condition(op => op.eq('status', 'ACTIVE')) // Only if dinosaur is active
18
18
  .execute();
19
19
  ```
20
20
 
21
- ## 🌟 Why dyno-table?
21
+ > This README provides a concise overview of dyno-table's features with dinosaur-themed examples. For detailed documentation on specific features, please refer to the individual markdown files in the `docs/` directory.
22
+
23
+ ## 🌟 Why dyno-table for your Dinosaur Data?
22
24
 
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
25
+ - **🦕 Dinosaur-sized data made manageable** - Clean abstraction layer for complex DynamoDB patterns
26
+ - **🛡️ Extinction-proof type safety** - Full TypeScript support with strict type checking
27
+ - **⚡ Velociraptor-fast API** - Chainable builder pattern for complex operations
28
+ - **🔒 T-Rex-proof transactional safety** - ACID-compliant operations with easy-to-use transactions
29
+ - **📈 Jurassic-scale performance** - Automatic batch chunking and pagination handling
28
30
 
29
31
  ## 📑 Table of Contents
30
32
 
31
- - [Installation](#-installation)
32
- - [Quick Start](#-quick-start)
33
- - [Query](#-type-safe-query-building)
34
- - [Update](#update-operations)
35
- - [Condition Operators](#condition-operators)
36
- - [Multiple Operations](#multiple-operations)
37
- - [Type Safety Features](#-type-safety-features)
38
- - [Nested Object Support](#nested-object-support)
39
- - [Type-Safe Conditions](#type-safe-conditions)
40
- - [Batch Operations](#-batch-operations)
41
- - [Batch Get](#batch-get)
42
- - [Batch Write](#batch-write)
43
- - [Transaction Operations](#-transaction-operations)
44
- - [Transaction Builder](#transaction-builder)
45
- - [Transaction Options](#transaction-options)
46
- - [Error Handling](#-error-handling)
47
- - [API Reference](#-api-reference)
33
+ - [🦖 dyno-table ](#-dyno-table--)
34
+ - [🌟 Why dyno-table for your Dinosaur Data?](#-why-dyno-table-for-your-dinosaur-data)
35
+ - [📑 Table of Contents](#-table-of-contents)
36
+ - [📦 Installation](#-installation)
37
+ - [🚀 Quick Start](#-quick-start)
38
+ - [1. Configure Your Jurassic Table](#1-configure-your-jurassic-table)
39
+ - [2. Perform Type-Safe Dinosaur Operations](#2-perform-type-safe-dinosaur-operations)
40
+ - [🏗️ Entity Pattern](#️-entity-pattern-with-standard-schema-validators)
41
+ - [Defining Entities](#defining-entities)
42
+ - [Entity Features](#entity-features)
43
+ - [1. Schema Validation](#1-schema-validation)
44
+ - [2. CRUD Operations](#2-crud-operations)
45
+ - [3. Custom Queries](#3-custom-queries)
46
+ - [4. Indexes for Efficient Querying](#4-indexes-for-efficient-querying)
47
+ - [5. Lifecycle Hooks](#5-lifecycle-hooks)
48
+ - [Complete Entity Example](#complete-entity-example)
49
+ - [🧩 Advanced Features](#-advanced-features)
50
+ - [Transactional Operations](#transactional-operations)
51
+ - [Batch Processing](#batch-processing)
52
+ - [Pagination Made Simple](#pagination-made-simple)
53
+ - [🛡️ Type-Safe Query Building](#️-type-safe-query-building)
54
+ - [Comparison Operators](#comparison-operators)
55
+ - [Logical Operators](#logical-operators)
56
+ - [Query Operations](#query-operations)
57
+ - [Put Operations](#put-operations)
58
+ - [Update Operations](#update-operations)
59
+ - [Condition Operators](#condition-operators)
60
+ - [Multiple Operations](#multiple-operations)
61
+ - [🔄 Type Safety Features](#-type-safety-features)
62
+ - [Nested Object Support](#nested-object-support)
63
+ - [Type-Safe Conditions](#type-safe-conditions)
64
+ - [🔄 Batch Operations](#-batch-operations)
65
+ - [Batch Get](#batch-get)
66
+ - [Batch Write](#batch-write)
67
+ - [🔒 Transaction Operations](#-transaction-operations)
68
+ - [Transaction Builder](#transaction-builder)
69
+ - [Transaction Options](#transaction-options)
70
+ - [🚨 Error Handling](#-error-handling)
71
+ - [📚 API Reference](#-api-reference)
72
+ - [Condition Operators](#condition-operators-1)
73
+ - [Comparison Operators](#comparison-operators-1)
74
+ - [Attribute Operators](#attribute-operators)
75
+ - [Logical Operators](#logical-operators-1)
76
+ - [Key Condition Operators](#key-condition-operators)
77
+ - [🔮 Future Roadmap](#-future-roadmap)
78
+ - [🤝 Contributing](#-contributing)
79
+ - [🦔 Running Examples](#-running-examples)
48
80
 
49
81
  ## 📦 Installation
50
82
 
@@ -60,25 +92,26 @@ npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
60
92
 
61
93
  ## 🚀 Quick Start
62
94
 
63
- ### 1. Configure Your Table
95
+ ### 1. Configure Your Jurassic Table
64
96
 
65
97
  ```ts
66
98
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
67
99
  import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
68
- import { Table } from "dyno-table";
100
+ import { Table } from "dyno-table/table";
69
101
 
70
- // Configure AWS SDK clients
102
+ // Configure AWS SDK clients - your gateway to the prehistoric database
71
103
  const client = new DynamoDBClient({ region: "us-west-2" });
72
104
  const docClient = DynamoDBDocument.from(client);
73
105
 
74
- // Initialize table with single-table design schema
106
+ // Initialize table with single-table design schema - your dinosaur park database
75
107
  const dinoTable = new Table({
76
108
  client: docClient,
77
- tableName: "DinosaurPark",
109
+ tableName: "JurassicPark", // Your central dinosaur tracking system
78
110
  indexes: {
79
- partitionKey: "pk",
80
- sortKey: "sk",
111
+ partitionKey: "pk", // Primary partition key for fast dinosaur lookups
112
+ sortKey: "sk", // Sort key for organizing dinosaur data
81
113
  gsis: {
114
+ // Global Secondary Index for querying dinosaurs by species
82
115
  speciesId: {
83
116
  partitionKey: "gsi1pk",
84
117
  sortKey: "gsi1sk",
@@ -88,141 +121,538 @@ const dinoTable = new Table({
88
121
  });
89
122
  ```
90
123
 
91
- ### 2. Perform Type-Safe Operations
124
+ ### 2. Perform Type-Safe Dinosaur Operations
92
125
 
93
- **🦖 Creating a new dinosaur**
126
+ **🦖 Creating a new dinosaur specimen**
94
127
  ```ts
128
+ // Add a new T-Rex to your collection with complete type safety
95
129
  const rex = await dinoTable
96
130
  .create<Dinosaur>({
97
- pk: "SPECIES#trex",
98
- sk: "PROFILE#trex",
99
- speciesId: "trex",
100
- name: "Tyrannosaurus Rex",
101
- diet: "carnivore",
102
- length: 12.3,
103
- discoveryYear: 1902
131
+ pk: "SPECIES#trex", // Partition key identifies the species
132
+ sk: "PROFILE#trex", // Sort key for the specific profile
133
+ speciesId: "trex", // For GSI queries
134
+ name: "Tyrannosaurus Rex", // Display name
135
+ diet: "carnivore", // Dietary classification
136
+ length: 12.3, // Size in meters
137
+ discoveryYear: 1902 // When first discovered
104
138
  })
105
139
  .execute();
106
140
  ```
107
141
 
108
- **🔍 Query with conditions**
142
+ **🔍 Query for specific dinosaurs with conditions**
109
143
  ```ts
144
+ // Find large carnivorous dinosaurs in the T-Rex species
110
145
  const largeDinos = await dinoTable
111
146
  .query<Dinosaur>({
112
- pk: "SPECIES#trex",
113
- sk: (op) => op.beginsWith("PROFILE#")
147
+ pk: "SPECIES#trex", // Target the T-Rex species
148
+ sk: (op) => op.beginsWith("PROFILE#") // Look in profile records
114
149
  })
115
150
  .filter((op) => op.and(
116
- op.gte("length", 10),
117
- op.eq("diet", "carnivore")
151
+ op.gte("length", 10), // Only dinosaurs longer than 10 meters
152
+ op.eq("diet", "carnivore") // Must be carnivores
118
153
  ))
119
- .limit(10)
154
+ .limit(10) // Limit to 10 results
120
155
  .execute();
121
156
  ```
122
157
 
123
- **🔄 Complex update operation**
158
+ **🔄 Update dinosaur classification**
124
159
  ```ts
160
+ // Update a dinosaur's diet classification based on new research
125
161
  await dinoTable
126
162
  .update<Dinosaur>({
127
- pk: "SPECIES#trex",
128
- sk: "PROFILE#trex"
163
+ pk: "SPECIES#trex", // Target the T-Rex species
164
+ sk: "PROFILE#trex" // Specific profile to update
129
165
  })
130
- .set("diet", "omnivore")
131
- .add("discoveryYear", 1)
132
- .remove("outdatedField")
133
- .condition((op) => op.attributeExists("discoverySite"))
166
+ .set("diet", "omnivore") // New diet classification based on fossil evidence
167
+ .add("discoveryYear", 1) // Adjust discovery year with new findings
168
+ .remove("outdatedField") // Remove deprecated information
169
+ .condition((op) => op.attributeExists("discoverySite")) // Only if discovery site is documented
134
170
  .execute();
135
171
  ```
136
172
 
173
+ ## 🏗️ Entity Pattern with Standard Schema validators
174
+
175
+ The entity pattern provides a structured, type-safe way to work with DynamoDB items.
176
+ It combines schema validation, key management, and repository operations into a cohesive abstraction.
177
+
178
+ ✨ This library supports all standard schema validation libraries, including **zod**, **arktype**, and **valibot**,
179
+ allowing you to choose your preferred validation tool!
180
+
181
+ ### Defining Entities
182
+
183
+ Entities are defined using the `defineEntity` function, which takes a configuration object that includes a schema, primary key definition, and optional indexes and queries.
184
+
185
+ ```ts
186
+ import { z } from "zod";
187
+ import { defineEntity, createIndex } from "dyno-table/entity";
188
+
189
+ // Define your schema using Zod
190
+ const dinosaurSchema = z.object({
191
+ id: z.string(),
192
+ species: z.string(),
193
+ name: z.string(),
194
+ diet: z.enum(["carnivore", "herbivore", "omnivore"]),
195
+ dangerLevel: z.number().int().min(1).max(10),
196
+ height: z.number().positive(),
197
+ weight: z.number().positive(),
198
+ status: z.enum(["active", "inactive", "sick", "deceased"]),
199
+ createdAt: z.string().optional(),
200
+ updatedAt: z.string().optional(),
201
+ });
202
+
203
+ // Infer the type from the schema
204
+ type Dinosaur = z.infer<typeof dinosaurSchema>;
205
+
206
+ // Define key templates for Dinosaur entity
207
+ const dinosaurPK = partitionKey`ENTITY#DINOSAUR#DIET#${"diet"}`;
208
+ const dinosaurSK = sortKey`ID#${"id"}#SPECIES#${"species"}`;
209
+
210
+ // Create a primary index for Dinosaur entity
211
+ const primaryKey = createIndex()
212
+ .input(z.object({ id: z.string(), diet: z.string(), species: z.string() }))
213
+ .partitionKey(({ diet }) => dinosaurPK({ diet }))
214
+ .sortKey(({ id, species }) => dinosaurSK({ species, id }));
215
+
216
+ // Define the entity
217
+ const DinosaurEntity = defineEntity({
218
+ name: "Dinosaur",
219
+ schema: dinosaurSchema,
220
+ primaryKey,
221
+ });
222
+
223
+ // Create a repository
224
+ const dinosaurRepo = DinosaurEntity.createRepository(table);
225
+ ```
226
+
227
+ ### Entity Features
228
+
229
+ #### 1. Schema Validation
230
+
231
+ Entities use Zod schemas to validate data before operations:
232
+
233
+ ```ts
234
+ // Define a schema with Zod
235
+ const dinosaurSchema = z.object({
236
+ id: z.string(),
237
+ species: z.string(),
238
+ name: z.string(),
239
+ diet: z.enum(["carnivore", "herbivore", "omnivore"]),
240
+ dangerLevel: z.number().int().min(1).max(10),
241
+ height: z.number().positive(),
242
+ weight: z.number().positive(),
243
+ status: z.enum(["active", "inactive", "sick", "deceased"]),
244
+ tags: z.array(z.string()).optional(),
245
+ });
246
+
247
+ // Create an entity with the schema
248
+ const DinosaurEntity = defineEntity({
249
+ name: "Dinosaur",
250
+ schema: dinosaurSchema,
251
+ primaryKey: createIndex()
252
+ .input(z.object({ id: z.string(), diet: z.string(), species: z.string() }))
253
+ .partitionKey(({ diet }) => dinosaurPK({ diet }))
254
+ .sortKey(({ id, species }) => dinosaurSK({ species, id }))
255
+ });
256
+ ```
257
+
258
+ #### 2. CRUD Operations
259
+
260
+ Entities provide type-safe CRUD operations:
261
+
262
+ ```ts
263
+ // Create a new dinosaur
264
+ await dinosaurRepo.create({
265
+ id: "dino-001",
266
+ species: "Tyrannosaurus Rex",
267
+ name: "Rexy",
268
+ diet: "carnivore",
269
+ dangerLevel: 10,
270
+ height: 5.2,
271
+ weight: 7000,
272
+ status: "active",
273
+ }).execute();
274
+
275
+ // Get a dinosaur
276
+ const dino = await dinosaurRepo.get({
277
+ id: "dino-001",
278
+ diet: "carnivore",
279
+ species: "Tyrannosaurus Rex",
280
+ }).execute();
281
+
282
+ // Update a dinosaur
283
+ await dinosaurRepo.update(
284
+ { id: "dino-001", diet: "carnivore", species: "Tyrannosaurus Rex" },
285
+ { weight: 7200, status: "sick" }
286
+ ).execute();
287
+
288
+ // Delete a dinosaur
289
+ await dinosaurRepo.delete({
290
+ id: "dino-001",
291
+ diet: "carnivore",
292
+ species: "Tyrannosaurus Rex",
293
+ }).execute();
294
+ ```
295
+
296
+ #### 3. Custom Queries
297
+
298
+ Define custom queries with input validation:
299
+
300
+ ```ts
301
+ import { createQueries } from "dyno-table/entity";
302
+
303
+ const createQuery = createQueries<Dinosaur>();
304
+
305
+ const DinosaurEntity = defineEntity({
306
+ name: "Dinosaur",
307
+ schema: dinosaurSchema,
308
+ primaryKey,
309
+ queries: {
310
+ byDiet: createQuery
311
+ .input(
312
+ z.object({
313
+ diet: z.enum(["carnivore", "herbivore", "omnivore"]),
314
+ })
315
+ )
316
+ .query(({ input, entity }) => {
317
+ return entity
318
+ .scan()
319
+ .filter((op) => op.eq("diet", input.diet));
320
+ }),
321
+
322
+ bySpecies: createQuery
323
+ .input(
324
+ z.object({
325
+ species: z.string(),
326
+ })
327
+ )
328
+ .query(({ input, entity }) => {
329
+ return entity
330
+ .scan()
331
+ .filter((op) => op.eq("species", input.species));
332
+ }),
333
+ },
334
+ });
335
+
336
+ // Use the custom queries
337
+ const carnivores = await dinosaurRepo.query.byDiet({ diet: "carnivore" }).execute();
338
+ const trexes = await dinosaurRepo.query.bySpecies({ species: "Tyrannosaurus Rex" }).execute();
339
+ ```
340
+
341
+ #### 4. Indexes for Efficient Querying
342
+
343
+ Define indexes for efficient access patterns:
344
+
345
+ ```ts
346
+ import { createIndex } from "dyno-table/entity";
347
+
348
+ // Define GSI for querying by species
349
+ const speciesIndex = createIndex()
350
+ .input(dinosaurSchema)
351
+ .partitionKey(({ species }) => `SPECIES#${species}`)
352
+ .sortKey(({ id }) => `DINOSAUR#${id}`);
353
+
354
+ const DinosaurEntity = defineEntity({
355
+ name: "Dinosaur",
356
+ schema: dinosaurSchema,
357
+ primaryKey,
358
+ indexes: {
359
+ species: speciesIndex,
360
+ },
361
+ queries: {
362
+ bySpecies: createQuery
363
+ .input(
364
+ z.object({
365
+ species: z.string(),
366
+ })
367
+ )
368
+ .query(({ input, entity }) => {
369
+ return entity
370
+ .queryBuilder({
371
+ pk: `SPECIES#${input.species}`,
372
+ })
373
+ .useIndex("species");
374
+ }),
375
+ },
376
+ });
377
+ ```
378
+
379
+ #### 5. Lifecycle Hooks
380
+
381
+ Add hooks for pre/post processing:
382
+
383
+ ```ts
384
+ const dinosaurHooks = {
385
+ afterGet: async (data: Dinosaur | undefined) => {
386
+ if (data) {
387
+ return {
388
+ ...data,
389
+ displayName: `${data.name} (${data.species})`,
390
+ threatLevel: data.dangerLevel > 7 ? "HIGH" : "MODERATE",
391
+ };
392
+ }
393
+ return data;
394
+ },
395
+ };
396
+
397
+ const DinosaurEntity = defineEntity({
398
+ name: "Dinosaur",
399
+ schema: dinosaurSchema,
400
+ primaryKey,
401
+ hooks: dinosaurHooks,
402
+ });
403
+ ```
404
+
405
+ ### Complete Entity Example
406
+
407
+ Here's a complete example using Zod schemas directly:
408
+
409
+ ```ts
410
+ import { z } from "zod";
411
+ import { defineEntity, createQueries, createIndex } from "dyno-table/entity";
412
+ import { Table } from "dyno-table/table";
413
+
414
+ // Define the schema with Zod
415
+ const dinosaurSchema = z.object({
416
+ id: z.string(),
417
+ species: z.string(),
418
+ name: z.string(),
419
+ enclosureId: z.string(),
420
+ diet: z.enum(["carnivore", "herbivore", "omnivore"]),
421
+ dangerLevel: z.number().int().min(1).max(10),
422
+ height: z.number().positive(),
423
+ weight: z.number().positive(),
424
+ status: z.enum(["active", "inactive", "sick", "deceased"]),
425
+ trackingChipId: z.string().optional(),
426
+ lastFed: z.string().optional(),
427
+ createdAt: z.string().optional(),
428
+ updatedAt: z.string().optional(),
429
+ });
430
+
431
+ // Infer the type from the schema
432
+ type Dinosaur = z.infer<typeof dinosaurSchema>;
433
+
434
+ // Define key templates
435
+ const dinosaurPK = (id: string) => `DINOSAUR#${id}`;
436
+ const dinosaurSK = (status: string) => `STATUS#${status}`;
437
+
438
+ // Create a primary index
439
+ const primaryKey = createIndex()
440
+ .input(dinosaurSchema)
441
+ .partitionKey(({ id }) => dinosaurPK(id))
442
+ .sortKey(({ status }) => dinosaurSK(status));
443
+
444
+ // Create a GSI for querying by species
445
+ const speciesIndex = createIndex()
446
+ .input(dinosaurSchema)
447
+ .partitionKey(({ species }) => `SPECIES#${species}`)
448
+ .sortKey(({ id }) => `DINOSAUR#${id}`);
449
+
450
+ // Create a GSI for querying by enclosure
451
+ const enclosureIndex = createIndex()
452
+ .input(dinosaurSchema)
453
+ .partitionKey(({ enclosureId }) => `ENCLOSURE#${enclosureId}`)
454
+ .sortKey(({ id }) => `DINOSAUR#${id}`);
455
+
456
+ // Create query builders
457
+ const createQuery = createQueries<Dinosaur>();
458
+
459
+ // Define the entity
460
+ const DinosaurEntity = defineEntity({
461
+ name: "Dinosaur",
462
+ schema: dinosaurSchema,
463
+ primaryKey,
464
+ indexes: {
465
+ species: speciesIndex,
466
+ enclosure: enclosureIndex,
467
+ },
468
+ queries: {
469
+ bySpecies: createQuery
470
+ .input(
471
+ z.object({
472
+ species: z.string(),
473
+ })
474
+ )
475
+ .query(({ input, entity }) => {
476
+ return entity
477
+ .queryBuilder({
478
+ pk: `SPECIES#${input.species}`,
479
+ })
480
+ .useIndex("species");
481
+ }),
482
+
483
+ byEnclosure: createQuery
484
+ .input(
485
+ z.object({
486
+ enclosureId: z.string(),
487
+ })
488
+ )
489
+ .query(({ input, entity }) => {
490
+ return entity
491
+ .queryBuilder({
492
+ pk: `ENCLOSURE#${input.enclosureId}`,
493
+ })
494
+ .useIndex("enclosure");
495
+ }),
496
+
497
+ dangerousInEnclosure: createQuery
498
+ .input(
499
+ z.object({
500
+ enclosureId: z.string(),
501
+ minDangerLevel: z.number().int().min(1).max(10),
502
+ })
503
+ )
504
+ .query(({ input, entity }) => {
505
+ return entity
506
+ .queryBuilder({
507
+ pk: `ENCLOSURE#${input.enclosureId}`,
508
+ })
509
+ .useIndex("enclosure")
510
+ .filter((op) => op.gte("dangerLevel", input.minDangerLevel));
511
+ }),
512
+ },
513
+ });
514
+
515
+ // Create a repository
516
+ const dinosaurRepo = DinosaurEntity.createRepository(table);
517
+
518
+ // Use the repository
519
+ async function main() {
520
+ // Create a dinosaur
521
+ await dinosaurRepo
522
+ .create({
523
+ id: "dino-001",
524
+ species: "Tyrannosaurus Rex",
525
+ name: "Rexy",
526
+ enclosureId: "enc-001",
527
+ diet: "carnivore",
528
+ dangerLevel: 10,
529
+ height: 5.2,
530
+ weight: 7000,
531
+ status: "active",
532
+ trackingChipId: "TRX-001",
533
+ })
534
+ .execute();
535
+
536
+ // Query dinosaurs by species
537
+ const trexes = await dinosaurRepo.query.bySpecies({
538
+ species: "Tyrannosaurus Rex"
539
+ }).execute();
540
+
541
+ // Query dangerous dinosaurs in an enclosure
542
+ const dangerousDinos = await dinosaurRepo.query.dangerousInEnclosure({
543
+ enclosureId: "enc-001",
544
+ minDangerLevel: 8,
545
+ }).execute();
546
+ }
547
+ ```
548
+
549
+ **Key benefits:**
550
+ - 🚫 Prevents accidental cross-type data access
551
+ - 🔍 Automatically filters queries/scans to repository type
552
+ - 🛡️ Ensures consistent key structure across entities
553
+ - 📦 Encapsulates domain-specific query logic
554
+ - 🧪 Validates data with Zod schemas
555
+ - 🔄 Provides type inference from schemas
556
+
137
557
  ## 🧩 Advanced Features
138
558
 
139
559
  ### Transactional Operations
140
560
 
141
561
  **Safe dinosaur transfer between enclosures**
142
562
  ```ts
143
- // Start a transaction session for transferring a dinosaur
563
+ // Start a transaction session for transferring a T-Rex to a new enclosure
564
+ // Critical for safety: All operations must succeed or none will be applied
144
565
  await dinoTable.transaction(async (tx) => {
145
566
  // All operations are executed as a single transaction (up to 100 operations)
567
+ // This ensures the dinosaur transfer is atomic - preventing half-completed transfers
146
568
 
147
- // Check if destination enclosure is ready and compatible
569
+ // STEP 1: Check if destination enclosure is ready and compatible with the dinosaur
570
+ // We must verify the enclosure is prepared and suitable for a carnivore
148
571
  await dinoTable
149
572
  .conditionCheck({
150
- pk: "ENCLOSURE#B",
151
- sk: "STATUS"
573
+ pk: "ENCLOSURE#B", // Target enclosure B
574
+ sk: "STATUS" // Check the enclosure status record
152
575
  })
153
576
  .condition(op => op.and(
154
- op.eq("status", "READY"),
155
- op.eq("diet", "Carnivore") // Ensure enclosure matches dinosaur diet
577
+ op.eq("status", "READY"), // Enclosure must be in READY state
578
+ op.eq("diet", "Carnivore") // Must support carnivorous dinosaurs
156
579
  ))
157
580
  .withTransaction(tx);
158
581
 
159
- // Remove dinosaur from current enclosure
582
+ // STEP 2: Remove dinosaur from current enclosure
583
+ // Only proceed if the dinosaur is healthy enough for transfer
160
584
  await dinoTable
161
585
  .delete<Dinosaur>({
162
- pk: "ENCLOSURE#A",
163
- sk: "DINO#001"
586
+ pk: "ENCLOSURE#A", // Source enclosure A
587
+ sk: "DINO#001" // T-Rex with ID 001
164
588
  })
165
589
  .condition(op => op.and(
166
- op.eq("status", "HEALTHY"),
167
- op.gte("health", 80) // Only transfer healthy dinosaurs
590
+ op.eq("status", "HEALTHY"), // Dinosaur must be in HEALTHY state
591
+ op.gte("health", 80) // Health must be at least 80%
168
592
  ))
169
593
  .withTransaction(tx);
170
594
 
171
- // Add dinosaur to new enclosure
595
+ // STEP 3: Add dinosaur to new enclosure
596
+ // Create a fresh record in the destination enclosure
172
597
  await dinoTable
173
598
  .create<Dinosaur>({
174
- pk: "ENCLOSURE#B",
175
- sk: "DINO#001",
176
- name: "Rex",
177
- species: "Tyrannosaurus",
178
- diet: "Carnivore",
179
- status: "HEALTHY",
180
- health: 100,
181
- enclosureId: "B",
182
- lastFed: new Date().toISOString()
599
+ pk: "ENCLOSURE#B", // Destination enclosure B
600
+ sk: "DINO#001", // Same dinosaur ID for tracking
601
+ name: "Rex", // Dinosaur name
602
+ species: "Tyrannosaurus", // Species classification
603
+ diet: "Carnivore", // Dietary requirements
604
+ status: "HEALTHY", // Current health status
605
+ health: 100, // Reset health to 100% after transfer
606
+ enclosureId: "B", // Update enclosure reference
607
+ lastFed: new Date().toISOString() // Reset feeding clock
183
608
  })
184
609
  .withTransaction(tx);
185
610
 
186
- // Update enclosure occupancy tracking
611
+ // STEP 4: Update enclosure occupancy tracking
612
+ // Keep accurate count of dinosaurs in each enclosure
187
613
  await dinoTable
188
614
  .update<Dinosaur>({
189
- pk: "ENCLOSURE#B",
190
- sk: "OCCUPANCY"
615
+ pk: "ENCLOSURE#B", // Target enclosure B
616
+ sk: "OCCUPANCY" // Occupancy tracking record
191
617
  })
192
- .add("currentOccupants", 1)
193
- .set("lastUpdated", new Date().toISOString())
618
+ .add("currentOccupants", 1) // Increment occupant count
619
+ .set("lastUpdated", new Date().toISOString()) // Update timestamp
194
620
  .withTransaction(tx);
195
621
  });
196
622
 
197
- // Transaction with feeding and health monitoring
623
+ // Transaction for dinosaur feeding and health monitoring
624
+ // Ensures feeding status and schedule are updated atomically
198
625
  await dinoTable.transaction(
199
626
  async (tx) => {
200
- // Update dinosaur health and feeding status
627
+ // STEP 1: Update Stegosaurus health and feeding status
628
+ // Record that the dinosaur has been fed and update its health metrics
201
629
  await dinoTable
202
630
  .update<Dinosaur>({
203
- pk: "ENCLOSURE#D",
204
- sk: "DINO#003"
631
+ pk: "ENCLOSURE#D", // Herbivore enclosure D
632
+ sk: "DINO#003" // Stegosaurus with ID 003
205
633
  })
206
634
  .set({
207
- status: "HEALTHY",
208
- lastFed: new Date().toISOString(),
209
- health: 100
635
+ status: "HEALTHY", // Update health status
636
+ lastFed: new Date().toISOString(), // Record feeding time
637
+ health: 100 // Reset health to 100%
210
638
  })
211
- .deleteElementsFromSet("tags", ["needs_feeding"])
639
+ .deleteElementsFromSet("tags", ["needs_feeding"]) // Remove feeding alert tag
212
640
  .withTransaction(tx);
213
641
 
214
- // Update enclosure feeding schedule
642
+ // STEP 2: Update enclosure feeding schedule
643
+ // Schedule next feeding time for tomorrow
215
644
  await dinoTable
216
645
  .update<Dinosaur>({
217
- pk: "ENCLOSURE#D",
218
- sk: "SCHEDULE"
646
+ pk: "ENCLOSURE#D", // Same herbivore enclosure
647
+ sk: "SCHEDULE" // Feeding schedule record
219
648
  })
220
- .set("nextFeedingTime", new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString())
649
+ .set("nextFeedingTime", new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()) // 24 hours from now
221
650
  .withTransaction(tx);
222
651
  },
223
652
  {
224
- clientRequestToken: "feeding-session-001",
225
- returnConsumedCapacity: "TOTAL"
653
+ // Transaction options for tracking and idempotency
654
+ clientRequestToken: "feeding-session-001", // Prevents duplicate feeding operations
655
+ returnConsumedCapacity: "TOTAL" // Track capacity usage for park operations
226
656
  }
227
657
  );
228
658
  ```
@@ -239,35 +669,42 @@ await dinoTable.transaction(
239
669
 
240
670
  **Efficient dinosaur park management with bulk operations**
241
671
  ```ts
242
- // Batch health check for multiple dinosaurs
672
+ // SCENARIO 1: Morning health check for multiple dinosaurs across enclosures
673
+ // Retrieve health status for multiple dinosaurs in a single operation
243
674
  const healthCheckKeys = [
244
- { pk: "ENCLOSURE#A", sk: "DINO#001" }, // T-Rex
245
- { pk: "ENCLOSURE#B", sk: "DINO#002" }, // Velociraptor
246
- { pk: "ENCLOSURE#C", sk: "DINO#003" } // Stegosaurus
675
+ { pk: "ENCLOSURE#A", sk: "DINO#001" }, // T-Rex in Paddock A
676
+ { pk: "ENCLOSURE#B", sk: "DINO#002" }, // Velociraptor in Paddock B
677
+ { pk: "ENCLOSURE#C", sk: "DINO#003" } // Stegosaurus in Paddock C
247
678
  ];
248
679
 
680
+ // Perform batch get operation to retrieve all dinosaurs at once
681
+ // This is much more efficient than individual gets
249
682
  const { items: dinosaurs, unprocessedKeys } = await dinoTable.batchGet<Dinosaur>(healthCheckKeys);
250
683
  console.log(`Health check completed for ${dinosaurs.length} dinosaurs`);
684
+
685
+ // Process health check results and identify any dinosaurs needing attention
251
686
  dinosaurs.forEach(dino => {
252
687
  if (dino.health < 80) {
253
688
  console.log(`Health alert for ${dino.name} in Enclosure ${dino.enclosureId}`);
689
+ // In a real application, you might trigger alerts or schedule veterinary visits
254
690
  }
255
691
  });
256
692
 
257
- // Batch update feeding schedule for herbivore group
693
+ // SCENARIO 2: Adding new herbivores to the park after quarantine
694
+ // Prepare data for multiple new herbivores joining the collection
258
695
  const newHerbivores = [
259
696
  {
260
697
  pk: "ENCLOSURE#D", sk: "DINO#004",
261
- name: "Triceratops Alpha",
698
+ name: "Triceratops Alpha", // Three-horned herbivore
262
699
  species: "Triceratops",
263
700
  diet: "Herbivore",
264
701
  status: "HEALTHY",
265
- health: 95,
266
- lastFed: new Date().toISOString()
702
+ health: 95, // Excellent health after quarantine
703
+ lastFed: new Date().toISOString() // Just fed before joining main enclosure
267
704
  },
268
705
  {
269
706
  pk: "ENCLOSURE#D", sk: "DINO#005",
270
- name: "Brachy",
707
+ name: "Brachy", // Long-necked herbivore
271
708
  species: "Brachiosaurus",
272
709
  diet: "Herbivore",
273
710
  status: "HEALTHY",
@@ -276,91 +713,117 @@ const newHerbivores = [
276
713
  }
277
714
  ];
278
715
 
279
- // Add new herbivores to enclosure
716
+ // Add all new herbivores to the enclosure in a single batch operation
717
+ // More efficient than individual writes and ensures consistent state
280
718
  await dinoTable.batchWrite(
281
719
  newHerbivores.map(dino => ({
282
- type: "put",
283
- item: dino
720
+ type: "put", // Create or replace operation
721
+ item: dino // Full dinosaur record
284
722
  }))
285
723
  );
286
724
 
287
- // Mixed operations: relocate dinosaurs and update enclosure status
725
+ // SCENARIO 3: Releasing a dinosaur from quarantine to general population
726
+ // Multiple related operations performed as a batch
288
727
  await dinoTable.batchWrite([
289
- // Remove dinosaur from quarantine
290
- { type: "delete", key: { pk: "ENCLOSURE#QUARANTINE", sk: "DINO#006" } },
291
- // Add recovered dinosaur to main enclosure
728
+ // Step 1: Remove dinosaur from quarantine enclosure
729
+ {
730
+ type: "delete",
731
+ key: { pk: "ENCLOSURE#QUARANTINE", sk: "DINO#006" }
732
+ },
733
+
734
+ // Step 2: Add recovered dinosaur to main raptor enclosure
292
735
  {
293
736
  type: "put",
294
737
  item: {
295
738
  pk: "ENCLOSURE#E", sk: "DINO#006",
296
- name: "Raptor Beta",
739
+ name: "Raptor Beta", // Juvenile Velociraptor
297
740
  species: "Velociraptor",
298
741
  diet: "Carnivore",
299
- status: "HEALTHY",
742
+ status: "HEALTHY", // Now healthy after treatment
300
743
  health: 100,
301
744
  lastFed: new Date().toISOString()
302
745
  }
303
746
  },
304
- // Clear quarantine status
305
- { type: "delete", key: { pk: "ENCLOSURE#QUARANTINE", sk: "STATUS#DINO#006" } }
747
+
748
+ // Step 3: Clear quarantine status record
749
+ {
750
+ type: "delete",
751
+ key: { pk: "ENCLOSURE#QUARANTINE", sk: "STATUS#DINO#006" }
752
+ }
306
753
  ]);
307
754
 
308
- // Handle large-scale park operations
309
- // (25 items per batch write, 100 items per batch get)
755
+ // SCENARIO 4: Daily park-wide health monitoring
756
+ // Handle large-scale operations across all dinosaurs
757
+ // The library automatically handles chunking for large batches:
758
+ // - 25 items per batch write
759
+ // - 100 items per batch get
310
760
  const dailyHealthUpdates = generateDinosaurHealthUpdates(); // Hundreds of updates
311
- await dinoTable.batchWrite(dailyHealthUpdates); // Automatically chunked
761
+ await dinoTable.batchWrite(dailyHealthUpdates); // Automatically chunked into multiple requests
312
762
  ```
313
763
 
314
764
  ### Pagination Made Simple
315
765
 
316
- **Efficient dinosaur record browsing**
766
+ **Efficient dinosaur record browsing for park management**
317
767
  ```ts
318
- // Create a paginator for viewing herbivores by health status
768
+ // SCENARIO 1: Herbivore health monitoring with pagination
769
+ // Create a paginator for viewing healthy herbivores in manageable chunks
770
+ // Perfect for veterinary staff doing routine health checks
319
771
  const healthyHerbivores = dinoTable
320
772
  .query<Dinosaur>({
321
- pk: "DIET#herbivore",
322
- sk: op => op.beginsWith("STATUS#HEALTHY")
773
+ pk: "DIET#herbivore", // Target all herbivorous dinosaurs
774
+ sk: op => op.beginsWith("STATUS#HEALTHY") // Only those with HEALTHY status
323
775
  })
324
776
  .filter((op) => op.and(
325
- op.gte("health", 90),
326
- op.attributeExists("lastFed")
777
+ op.gte("health", 90), // Only those with excellent health (90%+)
778
+ op.attributeExists("lastFed") // Must have feeding records
327
779
  ))
328
- .paginate(5); // View 5 dinosaurs at a time
780
+ .paginate(5); // Process in small batches of 5 dinosaurs
329
781
 
330
- // Monitor all enclosures page by page
782
+ // Iterate through all pages of results - useful for processing large datasets
783
+ // without loading everything into memory at once
784
+ console.log("🦕 Beginning herbivore health inspection rounds...");
331
785
  while (healthyHerbivores.hasNextPage()) {
786
+ // Get the next page of dinosaurs
332
787
  const page = await healthyHerbivores.getNextPage();
333
788
  console.log(`Checking herbivores page ${page.page}, found ${page.items.length} dinosaurs`);
789
+
790
+ // Process each dinosaur in the current page
334
791
  page.items.forEach(dino => {
335
792
  console.log(`${dino.name}: Health ${dino.health}%, Last fed: ${dino.lastFed}`);
793
+ // In a real app, you might update health records or schedule next checkup
336
794
  });
337
795
  }
338
796
 
339
- // Get all carnivores for daily feeding schedule
797
+ // SCENARIO 2: Preparing carnivore feeding schedule
798
+ // Get all carnivores at once for daily feeding planning
799
+ // This approach loads all matching items into memory
340
800
  const carnivoreSchedule = await dinoTable
341
801
  .query<Dinosaur>({
342
- pk: "DIET#carnivore",
343
- sk: op => op.beginsWith("ENCLOSURE#")
802
+ pk: "DIET#carnivore", // Target all carnivorous dinosaurs
803
+ sk: op => op.beginsWith("ENCLOSURE#") // Organized by enclosure
344
804
  })
345
- .filter(op => op.attributeExists("lastFed"))
346
- .paginate(10)
347
- .getAllPages();
805
+ .filter(op => op.attributeExists("lastFed")) // Only those with feeding records
806
+ .paginate(10) // Process in pages of 10
807
+ .getAllPages(); // But collect all results at once
348
808
 
349
809
  console.log(`Scheduling feeding for ${carnivoreSchedule.length} carnivores`);
810
+ // Now we can sort and organize feeding times based on species, size, etc.
350
811
 
351
- // Limited view for visitor information kiosk
812
+ // SCENARIO 3: Visitor information kiosk with limited display
813
+ // Create a paginated view for the public-facing dinosaur information kiosk
352
814
  const visitorKiosk = dinoTable
353
815
  .query<Dinosaur>({
354
- pk: "VISITOR_VIEW",
355
- sk: op => op.beginsWith("SPECIES#")
816
+ pk: "VISITOR_VIEW", // Special partition for visitor-facing data
817
+ sk: op => op.beginsWith("SPECIES#") // Organized by species
356
818
  })
357
- .filter(op => op.eq("status", "ON_DISPLAY"))
358
- .limit(12) // Show max 12 dinosaurs per view
359
- .paginate(4); // Display 4 at a time
819
+ .filter(op => op.eq("status", "ON_DISPLAY")) // Only show dinosaurs currently on display
820
+ .limit(12) // Show maximum 12 dinosaurs total
821
+ .paginate(4); // Display 4 at a time for easy viewing
360
822
 
361
- // Get first page for kiosk display
823
+ // Get first page for initial kiosk display
362
824
  const firstPage = await visitorKiosk.getNextPage();
363
- console.log(`Now showing: ${firstPage.items.map(d => d.name).join(", ")}`);
825
+ console.log(`🦖 Now showing: ${firstPage.items.map(d => d.name).join(", ")}`);
826
+ // Visitors can press "Next" to see more dinosaurs in the collection
364
827
  ```
365
828
 
366
829
  ## 🛡️ Type-Safe Query Building
@@ -426,6 +889,51 @@ const limited = await table
426
889
  .execute();
427
890
  ```
428
891
 
892
+ ### Put Operations
893
+
894
+ | Operation | Method Example | Description |
895
+ |---------------------|---------------------------------------------------------------------|------------------------------------------------------------------------|
896
+ | **Create New Item** | `.create<Dinosaur>({ pk: "SPECIES#trex", sk: "PROFILE#001", ... })` | Creates a new item with a condition to ensure it doesn't already exist |
897
+ | **Put Item** | `.put<Dinosaur>({ pk: "SPECIES#trex", sk: "PROFILE#001", ... })` | Creates or replaces an item |
898
+ | **With Condition** | `.put(item).condition(op => op.attributeNotExists("pk"))` | Adds a condition that must be satisfied |
899
+
900
+ #### Return Values
901
+
902
+ Control what data is returned from put operations:
903
+
904
+ | Option | Description | Example |
905
+ |----------------|--------------------------------------------------------------------------------------------------------------------|---------------------------------------------------|
906
+ | **NONE** | Default. No return value. | `.put(item).returnValues("NONE").execute()` |
907
+ | **ALL_OLD** | Returns the item's previous state if it existed. (Does not consume any RCU and returns strongly consistent values) | `.put(item).returnValues("ALL_OLD").execute()` |
908
+ | **CONSISTENT** | Performs a consistent GET operation after the put to retrieve the item's new state. (Does consume RCU) | `.put(item).returnValues("CONSISTENT").execute()` |
909
+
910
+ ```ts
911
+ // Create with no return value (default)
912
+ await table.put<Dinosaur>({
913
+ pk: "SPECIES#trex",
914
+ sk: "PROFILE#001",
915
+ name: "Tyrannosaurus Rex",
916
+ diet: "carnivore"
917
+ }).execute();
918
+
919
+ // Create and return the newly created item
920
+ const newDino = await table.put<Dinosaur>({
921
+ pk: "SPECIES#trex",
922
+ sk: "PROFILE#002",
923
+ name: "Tyrannosaurus Rex",
924
+ diet: "carnivore"
925
+ }).returnValues("CONSISTENT").execute();
926
+
927
+ // Update with condition and get previous values
928
+ const oldDino = await table.put<Dinosaur>({
929
+ pk: "SPECIES#trex",
930
+ sk: "PROFILE#001",
931
+ name: "Tyrannosaurus Rex",
932
+ diet: "omnivore", // Updated diet
933
+ discoveryYear: 1905
934
+ }).returnValues("ALL_OLD").execute();
935
+ ```
936
+
429
937
  ### Update Operations
430
938
 
431
939
  | Operation | Method Example | Generated Expression |
@@ -603,24 +1111,6 @@ const result = await table.transaction(
603
1111
  );
604
1112
  ```
605
1113
 
606
- ## 🏗️ Entity Pattern Best Practices (Coming Soon TM)
607
-
608
- The entity implementation provides automatic type isolation:
609
-
610
- ```ts
611
- // All operations are automatically scoped to DINOSAUR type
612
- const dinosaur = await dinoEntity.get("SPECIES#trex", "PROFILE#trex");
613
- // Returns Dinosaur | undefined
614
-
615
- // Cross-type operations are prevented at compile time
616
- dinoEntity.create({ /* invalid shape */ }); // TypeScript error
617
- ```
618
-
619
- **Key benefits:**
620
- - 🚫 Prevents accidental cross-type data access
621
- - 🔍 Automatically filters queries/scans to repository type
622
- - 🛡️ Ensures consistent key structure across entities
623
- - 📦 Encapsulates domain-specific query logic
624
1114
 
625
1115
  ## 🚨 Error Handling
626
1116
 
@@ -782,3 +1272,34 @@ pnpm test
782
1272
  # Build the project
783
1273
  pnpm build
784
1274
  ```
1275
+
1276
+ ## 🦔 Running Examples
1277
+
1278
+ There's a few pre-configured example scripts in the `examples` directory.
1279
+
1280
+ First you'll need to install the dependencies:
1281
+
1282
+ ```bash
1283
+ pnpm install
1284
+ ```
1285
+ Then setup the test table in local DynamoDB by running the following command:
1286
+
1287
+ ```bash
1288
+ pnpm run ddb:start
1289
+ pnpm run local:setup
1290
+ ```
1291
+
1292
+ To run the examples, you can use the following command:
1293
+
1294
+ ```bash
1295
+ npx tsx examples/[EXAMPLE_NAME].ts
1296
+ ```
1297
+
1298
+ To view the test table GUI in action: [DynamoDB Admin](http://localhost:8001/)
1299
+
1300
+ <br />
1301
+ To teardown the test table when you're done, run the following command:
1302
+
1303
+ ```bash
1304
+ pnpm run local:teardown
1305
+ ```