dyno-table 0.1.8 → 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 (82) hide show
  1. package/README.md +570 -147
  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 +1 -1
  5. package/dist/builders/condition-check-builder.cjs.map +1 -1
  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 +1 -1
  9. package/dist/builders/condition-check-builder.js.map +1 -1
  10. package/dist/builders/delete-builder.cjs +0 -17
  11. package/dist/builders/delete-builder.cjs.map +1 -1
  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 +0 -17
  15. package/dist/builders/delete-builder.js.map +1 -1
  16. package/dist/builders/paginator.cjs.map +1 -1
  17. package/dist/builders/paginator.d.cts +179 -0
  18. package/dist/builders/paginator.d.ts +179 -0
  19. package/dist/builders/paginator.js.map +1 -1
  20. package/dist/builders/put-builder.cjs +8 -0
  21. package/dist/builders/put-builder.cjs.map +1 -1
  22. package/dist/builders/put-builder.d.cts +274 -0
  23. package/dist/builders/put-builder.d.ts +274 -0
  24. package/dist/builders/put-builder.js +8 -0
  25. package/dist/builders/put-builder.js.map +1 -1
  26. package/dist/builders/query-builder.cjs.map +1 -1
  27. package/dist/builders/query-builder.d.cts +6 -0
  28. package/dist/builders/query-builder.d.ts +6 -0
  29. package/dist/builders/query-builder.js.map +1 -1
  30. package/dist/builders/transaction-builder.cjs +40 -22
  31. package/dist/builders/transaction-builder.cjs.map +1 -1
  32. package/dist/builders/transaction-builder.d.cts +511 -0
  33. package/dist/builders/transaction-builder.d.ts +511 -0
  34. package/dist/builders/transaction-builder.js +40 -22
  35. package/dist/builders/transaction-builder.js.map +1 -1
  36. package/dist/builders/update-builder.cjs +3 -38
  37. package/dist/builders/update-builder.cjs.map +1 -1
  38. package/dist/builders/update-builder.d.cts +365 -0
  39. package/dist/builders/update-builder.d.ts +365 -0
  40. package/dist/builders/update-builder.js +3 -38
  41. package/dist/builders/update-builder.js.map +1 -1
  42. package/dist/conditions--ld9a78i.d.ts +331 -0
  43. package/dist/conditions-ChhQWd6z.d.cts +331 -0
  44. package/dist/conditions.cjs.map +1 -1
  45. package/dist/conditions.d.cts +3 -0
  46. package/dist/conditions.d.ts +3 -0
  47. package/dist/conditions.js.map +1 -1
  48. package/dist/entity.cjs +156 -97
  49. package/dist/entity.cjs.map +1 -1
  50. package/dist/entity.d.cts +149 -0
  51. package/dist/entity.d.ts +149 -0
  52. package/dist/entity.js +156 -97
  53. package/dist/entity.js.map +1 -1
  54. package/dist/query-builder-Csror9Iu.d.ts +507 -0
  55. package/dist/query-builder-D2FM9rsu.d.cts +507 -0
  56. package/dist/standard-schema.d.cts +57 -0
  57. package/dist/standard-schema.d.ts +57 -0
  58. package/dist/table-BEhBPy2G.d.cts +364 -0
  59. package/dist/table-BW3cmUqr.d.ts +364 -0
  60. package/dist/table.cjs +82 -102
  61. package/dist/table.cjs.map +1 -1
  62. package/dist/table.d.cts +12 -0
  63. package/dist/table.d.ts +12 -0
  64. package/dist/table.js +82 -102
  65. package/dist/table.js.map +1 -1
  66. package/dist/types.d.cts +22 -0
  67. package/dist/types.d.ts +22 -0
  68. package/dist/utils/{key-template.cjs → partition-key-template.cjs} +3 -3
  69. package/dist/utils/partition-key-template.cjs.map +1 -0
  70. package/dist/utils/partition-key-template.d.cts +32 -0
  71. package/dist/utils/partition-key-template.d.ts +32 -0
  72. package/dist/utils/{key-template.js → partition-key-template.js} +3 -3
  73. package/dist/utils/partition-key-template.js.map +1 -0
  74. package/dist/utils/sort-key-template.d.cts +35 -0
  75. package/dist/utils/sort-key-template.d.ts +35 -0
  76. package/package.json +86 -9
  77. package/dist/index.cjs +0 -3333
  78. package/dist/index.d.cts +0 -2971
  79. package/dist/index.d.ts +0 -2971
  80. package/dist/index.js +0 -3284
  81. package/dist/utils/key-template.cjs.map +0 -1
  82. package/dist/utils/key-template.js.map +0 -1
package/README.md CHANGED
@@ -6,35 +6,46 @@
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
33
  - [🦖 dyno-table ](#-dyno-table--)
32
- - [🌟 Why dyno-table?](#-why-dyno-table)
34
+ - [🌟 Why dyno-table for your Dinosaur Data?](#-why-dyno-table-for-your-dinosaur-data)
33
35
  - [📑 Table of Contents](#-table-of-contents)
34
36
  - [📦 Installation](#-installation)
35
37
  - [🚀 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
+ - [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)
38
49
  - [🧩 Advanced Features](#-advanced-features)
39
50
  - [Transactional Operations](#transactional-operations)
40
51
  - [Batch Processing](#batch-processing)
@@ -56,7 +67,6 @@ await table
56
67
  - [🔒 Transaction Operations](#-transaction-operations)
57
68
  - [Transaction Builder](#transaction-builder)
58
69
  - [Transaction Options](#transaction-options)
59
- - [🏗️ Entity Pattern Best Practices (Coming Soon TM)](#️-entity-pattern-best-practices-coming-soon-tm)
60
70
  - [🚨 Error Handling](#-error-handling)
61
71
  - [📚 API Reference](#-api-reference)
62
72
  - [Condition Operators](#condition-operators-1)
@@ -82,25 +92,26 @@ npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
82
92
 
83
93
  ## 🚀 Quick Start
84
94
 
85
- ### 1. Configure Your Table
95
+ ### 1. Configure Your Jurassic Table
86
96
 
87
97
  ```ts
88
98
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
89
99
  import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
90
- import { Table } from "dyno-table";
100
+ import { Table } from "dyno-table/table";
91
101
 
92
- // Configure AWS SDK clients
102
+ // Configure AWS SDK clients - your gateway to the prehistoric database
93
103
  const client = new DynamoDBClient({ region: "us-west-2" });
94
104
  const docClient = DynamoDBDocument.from(client);
95
105
 
96
- // Initialize table with single-table design schema
106
+ // Initialize table with single-table design schema - your dinosaur park database
97
107
  const dinoTable = new Table({
98
108
  client: docClient,
99
- tableName: "DinosaurPark",
109
+ tableName: "JurassicPark", // Your central dinosaur tracking system
100
110
  indexes: {
101
- partitionKey: "pk",
102
- sortKey: "sk",
111
+ partitionKey: "pk", // Primary partition key for fast dinosaur lookups
112
+ sortKey: "sk", // Sort key for organizing dinosaur data
103
113
  gsis: {
114
+ // Global Secondary Index for querying dinosaurs by species
104
115
  speciesId: {
105
116
  partitionKey: "gsi1pk",
106
117
  sortKey: "gsi1sk",
@@ -110,141 +121,538 @@ const dinoTable = new Table({
110
121
  });
111
122
  ```
112
123
 
113
- ### 2. Perform Type-Safe Operations
124
+ ### 2. Perform Type-Safe Dinosaur Operations
114
125
 
115
- **🦖 Creating a new dinosaur**
126
+ **🦖 Creating a new dinosaur specimen**
116
127
  ```ts
128
+ // Add a new T-Rex to your collection with complete type safety
117
129
  const rex = await dinoTable
118
130
  .create<Dinosaur>({
119
- pk: "SPECIES#trex",
120
- sk: "PROFILE#trex",
121
- speciesId: "trex",
122
- name: "Tyrannosaurus Rex",
123
- diet: "carnivore",
124
- length: 12.3,
125
- 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
126
138
  })
127
139
  .execute();
128
140
  ```
129
141
 
130
- **🔍 Query with conditions**
142
+ **🔍 Query for specific dinosaurs with conditions**
131
143
  ```ts
144
+ // Find large carnivorous dinosaurs in the T-Rex species
132
145
  const largeDinos = await dinoTable
133
146
  .query<Dinosaur>({
134
- pk: "SPECIES#trex",
135
- sk: (op) => op.beginsWith("PROFILE#")
147
+ pk: "SPECIES#trex", // Target the T-Rex species
148
+ sk: (op) => op.beginsWith("PROFILE#") // Look in profile records
136
149
  })
137
150
  .filter((op) => op.and(
138
- op.gte("length", 10),
139
- op.eq("diet", "carnivore")
151
+ op.gte("length", 10), // Only dinosaurs longer than 10 meters
152
+ op.eq("diet", "carnivore") // Must be carnivores
140
153
  ))
141
- .limit(10)
154
+ .limit(10) // Limit to 10 results
142
155
  .execute();
143
156
  ```
144
157
 
145
- **🔄 Complex update operation**
158
+ **🔄 Update dinosaur classification**
146
159
  ```ts
160
+ // Update a dinosaur's diet classification based on new research
147
161
  await dinoTable
148
162
  .update<Dinosaur>({
149
- pk: "SPECIES#trex",
150
- sk: "PROFILE#trex"
163
+ pk: "SPECIES#trex", // Target the T-Rex species
164
+ sk: "PROFILE#trex" // Specific profile to update
151
165
  })
152
- .set("diet", "omnivore")
153
- .add("discoveryYear", 1)
154
- .remove("outdatedField")
155
- .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
156
170
  .execute();
157
171
  ```
158
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
+
159
557
  ## 🧩 Advanced Features
160
558
 
161
559
  ### Transactional Operations
162
560
 
163
561
  **Safe dinosaur transfer between enclosures**
164
562
  ```ts
165
- // 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
166
565
  await dinoTable.transaction(async (tx) => {
167
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
168
568
 
169
- // 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
170
571
  await dinoTable
171
572
  .conditionCheck({
172
- pk: "ENCLOSURE#B",
173
- sk: "STATUS"
573
+ pk: "ENCLOSURE#B", // Target enclosure B
574
+ sk: "STATUS" // Check the enclosure status record
174
575
  })
175
576
  .condition(op => op.and(
176
- op.eq("status", "READY"),
177
- 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
178
579
  ))
179
580
  .withTransaction(tx);
180
581
 
181
- // Remove dinosaur from current enclosure
582
+ // STEP 2: Remove dinosaur from current enclosure
583
+ // Only proceed if the dinosaur is healthy enough for transfer
182
584
  await dinoTable
183
585
  .delete<Dinosaur>({
184
- pk: "ENCLOSURE#A",
185
- sk: "DINO#001"
586
+ pk: "ENCLOSURE#A", // Source enclosure A
587
+ sk: "DINO#001" // T-Rex with ID 001
186
588
  })
187
589
  .condition(op => op.and(
188
- op.eq("status", "HEALTHY"),
189
- 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%
190
592
  ))
191
593
  .withTransaction(tx);
192
594
 
193
- // Add dinosaur to new enclosure
595
+ // STEP 3: Add dinosaur to new enclosure
596
+ // Create a fresh record in the destination enclosure
194
597
  await dinoTable
195
598
  .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()
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
205
608
  })
206
609
  .withTransaction(tx);
207
610
 
208
- // Update enclosure occupancy tracking
611
+ // STEP 4: Update enclosure occupancy tracking
612
+ // Keep accurate count of dinosaurs in each enclosure
209
613
  await dinoTable
210
614
  .update<Dinosaur>({
211
- pk: "ENCLOSURE#B",
212
- sk: "OCCUPANCY"
615
+ pk: "ENCLOSURE#B", // Target enclosure B
616
+ sk: "OCCUPANCY" // Occupancy tracking record
213
617
  })
214
- .add("currentOccupants", 1)
215
- .set("lastUpdated", new Date().toISOString())
618
+ .add("currentOccupants", 1) // Increment occupant count
619
+ .set("lastUpdated", new Date().toISOString()) // Update timestamp
216
620
  .withTransaction(tx);
217
621
  });
218
622
 
219
- // Transaction with feeding and health monitoring
623
+ // Transaction for dinosaur feeding and health monitoring
624
+ // Ensures feeding status and schedule are updated atomically
220
625
  await dinoTable.transaction(
221
626
  async (tx) => {
222
- // 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
223
629
  await dinoTable
224
630
  .update<Dinosaur>({
225
- pk: "ENCLOSURE#D",
226
- sk: "DINO#003"
631
+ pk: "ENCLOSURE#D", // Herbivore enclosure D
632
+ sk: "DINO#003" // Stegosaurus with ID 003
227
633
  })
228
634
  .set({
229
- status: "HEALTHY",
230
- lastFed: new Date().toISOString(),
231
- health: 100
635
+ status: "HEALTHY", // Update health status
636
+ lastFed: new Date().toISOString(), // Record feeding time
637
+ health: 100 // Reset health to 100%
232
638
  })
233
- .deleteElementsFromSet("tags", ["needs_feeding"])
639
+ .deleteElementsFromSet("tags", ["needs_feeding"]) // Remove feeding alert tag
234
640
  .withTransaction(tx);
235
641
 
236
- // Update enclosure feeding schedule
642
+ // STEP 2: Update enclosure feeding schedule
643
+ // Schedule next feeding time for tomorrow
237
644
  await dinoTable
238
645
  .update<Dinosaur>({
239
- pk: "ENCLOSURE#D",
240
- sk: "SCHEDULE"
646
+ pk: "ENCLOSURE#D", // Same herbivore enclosure
647
+ sk: "SCHEDULE" // Feeding schedule record
241
648
  })
242
- .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
243
650
  .withTransaction(tx);
244
651
  },
245
652
  {
246
- clientRequestToken: "feeding-session-001",
247
- 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
248
656
  }
249
657
  );
250
658
  ```
@@ -261,35 +669,42 @@ await dinoTable.transaction(
261
669
 
262
670
  **Efficient dinosaur park management with bulk operations**
263
671
  ```ts
264
- // 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
265
674
  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
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
269
678
  ];
270
679
 
680
+ // Perform batch get operation to retrieve all dinosaurs at once
681
+ // This is much more efficient than individual gets
271
682
  const { items: dinosaurs, unprocessedKeys } = await dinoTable.batchGet<Dinosaur>(healthCheckKeys);
272
683
  console.log(`Health check completed for ${dinosaurs.length} dinosaurs`);
684
+
685
+ // Process health check results and identify any dinosaurs needing attention
273
686
  dinosaurs.forEach(dino => {
274
687
  if (dino.health < 80) {
275
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
276
690
  }
277
691
  });
278
692
 
279
- // 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
280
695
  const newHerbivores = [
281
696
  {
282
697
  pk: "ENCLOSURE#D", sk: "DINO#004",
283
- name: "Triceratops Alpha",
698
+ name: "Triceratops Alpha", // Three-horned herbivore
284
699
  species: "Triceratops",
285
700
  diet: "Herbivore",
286
701
  status: "HEALTHY",
287
- health: 95,
288
- lastFed: new Date().toISOString()
702
+ health: 95, // Excellent health after quarantine
703
+ lastFed: new Date().toISOString() // Just fed before joining main enclosure
289
704
  },
290
705
  {
291
706
  pk: "ENCLOSURE#D", sk: "DINO#005",
292
- name: "Brachy",
707
+ name: "Brachy", // Long-necked herbivore
293
708
  species: "Brachiosaurus",
294
709
  diet: "Herbivore",
295
710
  status: "HEALTHY",
@@ -298,91 +713,117 @@ const newHerbivores = [
298
713
  }
299
714
  ];
300
715
 
301
- // 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
302
718
  await dinoTable.batchWrite(
303
719
  newHerbivores.map(dino => ({
304
- type: "put",
305
- item: dino
720
+ type: "put", // Create or replace operation
721
+ item: dino // Full dinosaur record
306
722
  }))
307
723
  );
308
724
 
309
- // 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
310
727
  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
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
314
735
  {
315
736
  type: "put",
316
737
  item: {
317
738
  pk: "ENCLOSURE#E", sk: "DINO#006",
318
- name: "Raptor Beta",
739
+ name: "Raptor Beta", // Juvenile Velociraptor
319
740
  species: "Velociraptor",
320
741
  diet: "Carnivore",
321
- status: "HEALTHY",
742
+ status: "HEALTHY", // Now healthy after treatment
322
743
  health: 100,
323
744
  lastFed: new Date().toISOString()
324
745
  }
325
746
  },
326
- // Clear quarantine status
327
- { 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
+ }
328
753
  ]);
329
754
 
330
- // Handle large-scale park operations
331
- // (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
332
760
  const dailyHealthUpdates = generateDinosaurHealthUpdates(); // Hundreds of updates
333
- await dinoTable.batchWrite(dailyHealthUpdates); // Automatically chunked
761
+ await dinoTable.batchWrite(dailyHealthUpdates); // Automatically chunked into multiple requests
334
762
  ```
335
763
 
336
764
  ### Pagination Made Simple
337
765
 
338
- **Efficient dinosaur record browsing**
766
+ **Efficient dinosaur record browsing for park management**
339
767
  ```ts
340
- // 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
341
771
  const healthyHerbivores = dinoTable
342
772
  .query<Dinosaur>({
343
- pk: "DIET#herbivore",
344
- 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
345
775
  })
346
776
  .filter((op) => op.and(
347
- op.gte("health", 90),
348
- op.attributeExists("lastFed")
777
+ op.gte("health", 90), // Only those with excellent health (90%+)
778
+ op.attributeExists("lastFed") // Must have feeding records
349
779
  ))
350
- .paginate(5); // View 5 dinosaurs at a time
780
+ .paginate(5); // Process in small batches of 5 dinosaurs
351
781
 
352
- // 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...");
353
785
  while (healthyHerbivores.hasNextPage()) {
786
+ // Get the next page of dinosaurs
354
787
  const page = await healthyHerbivores.getNextPage();
355
788
  console.log(`Checking herbivores page ${page.page}, found ${page.items.length} dinosaurs`);
789
+
790
+ // Process each dinosaur in the current page
356
791
  page.items.forEach(dino => {
357
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
358
794
  });
359
795
  }
360
796
 
361
- // 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
362
800
  const carnivoreSchedule = await dinoTable
363
801
  .query<Dinosaur>({
364
- pk: "DIET#carnivore",
365
- sk: op => op.beginsWith("ENCLOSURE#")
802
+ pk: "DIET#carnivore", // Target all carnivorous dinosaurs
803
+ sk: op => op.beginsWith("ENCLOSURE#") // Organized by enclosure
366
804
  })
367
- .filter(op => op.attributeExists("lastFed"))
368
- .paginate(10)
369
- .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
370
808
 
371
809
  console.log(`Scheduling feeding for ${carnivoreSchedule.length} carnivores`);
810
+ // Now we can sort and organize feeding times based on species, size, etc.
372
811
 
373
- // 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
374
814
  const visitorKiosk = dinoTable
375
815
  .query<Dinosaur>({
376
- pk: "VISITOR_VIEW",
377
- sk: op => op.beginsWith("SPECIES#")
816
+ pk: "VISITOR_VIEW", // Special partition for visitor-facing data
817
+ sk: op => op.beginsWith("SPECIES#") // Organized by species
378
818
  })
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
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
382
822
 
383
- // Get first page for kiosk display
823
+ // Get first page for initial kiosk display
384
824
  const firstPage = await visitorKiosk.getNextPage();
385
- 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
386
827
  ```
387
828
 
388
829
  ## 🛡️ Type-Safe Query Building
@@ -670,24 +1111,6 @@ const result = await table.transaction(
670
1111
  );
671
1112
  ```
672
1113
 
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
1114
 
692
1115
  ## 🚨 Error Handling
693
1116