dyno-table 0.2.0-0 → 1.0.0-alpha.1
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.
- package/README.md +182 -577
- package/dist/{table.cjs → index.cjs} +175 -87
- package/dist/index.d.cts +2971 -0
- package/dist/index.d.ts +2971 -0
- package/dist/{table.js → index.js} +127 -86
- package/package.json +10 -77
- package/dist/builder-types-C_PDZhnP.d.ts +0 -118
- package/dist/builder-types-DtwbqMeF.d.cts +0 -118
- package/dist/builders/condition-check-builder.cjs +0 -394
- package/dist/builders/condition-check-builder.cjs.map +0 -1
- package/dist/builders/condition-check-builder.d.cts +0 -157
- package/dist/builders/condition-check-builder.d.ts +0 -157
- package/dist/builders/condition-check-builder.js +0 -392
- package/dist/builders/condition-check-builder.js.map +0 -1
- package/dist/builders/delete-builder.cjs +0 -405
- package/dist/builders/delete-builder.cjs.map +0 -1
- package/dist/builders/delete-builder.d.cts +0 -166
- package/dist/builders/delete-builder.d.ts +0 -166
- package/dist/builders/delete-builder.js +0 -403
- package/dist/builders/delete-builder.js.map +0 -1
- package/dist/builders/paginator.cjs +0 -199
- package/dist/builders/paginator.cjs.map +0 -1
- package/dist/builders/paginator.d.cts +0 -179
- package/dist/builders/paginator.d.ts +0 -179
- package/dist/builders/paginator.js +0 -197
- package/dist/builders/paginator.js.map +0 -1
- package/dist/builders/put-builder.cjs +0 -476
- package/dist/builders/put-builder.cjs.map +0 -1
- package/dist/builders/put-builder.d.cts +0 -274
- package/dist/builders/put-builder.d.ts +0 -274
- package/dist/builders/put-builder.js +0 -474
- package/dist/builders/put-builder.js.map +0 -1
- package/dist/builders/query-builder.cjs +0 -674
- package/dist/builders/query-builder.cjs.map +0 -1
- package/dist/builders/query-builder.d.cts +0 -6
- package/dist/builders/query-builder.d.ts +0 -6
- package/dist/builders/query-builder.js +0 -672
- package/dist/builders/query-builder.js.map +0 -1
- package/dist/builders/transaction-builder.cjs +0 -894
- package/dist/builders/transaction-builder.cjs.map +0 -1
- package/dist/builders/transaction-builder.d.cts +0 -511
- package/dist/builders/transaction-builder.d.ts +0 -511
- package/dist/builders/transaction-builder.js +0 -892
- package/dist/builders/transaction-builder.js.map +0 -1
- package/dist/builders/update-builder.cjs +0 -627
- package/dist/builders/update-builder.cjs.map +0 -1
- package/dist/builders/update-builder.d.cts +0 -365
- package/dist/builders/update-builder.d.ts +0 -365
- package/dist/builders/update-builder.js +0 -625
- package/dist/builders/update-builder.js.map +0 -1
- package/dist/conditions--ld9a78i.d.ts +0 -331
- package/dist/conditions-ChhQWd6z.d.cts +0 -331
- package/dist/conditions.cjs +0 -59
- package/dist/conditions.cjs.map +0 -1
- package/dist/conditions.d.cts +0 -3
- package/dist/conditions.d.ts +0 -3
- package/dist/conditions.js +0 -43
- package/dist/conditions.js.map +0 -1
- package/dist/entity.cjs +0 -228
- package/dist/entity.cjs.map +0 -1
- package/dist/entity.d.cts +0 -149
- package/dist/entity.d.ts +0 -149
- package/dist/entity.js +0 -224
- package/dist/entity.js.map +0 -1
- package/dist/query-builder-Csror9Iu.d.ts +0 -507
- package/dist/query-builder-D2FM9rsu.d.cts +0 -507
- package/dist/standard-schema.cjs +0 -4
- package/dist/standard-schema.cjs.map +0 -1
- package/dist/standard-schema.d.cts +0 -57
- package/dist/standard-schema.d.ts +0 -57
- package/dist/standard-schema.js +0 -3
- package/dist/standard-schema.js.map +0 -1
- package/dist/table-BEhBPy2G.d.cts +0 -364
- package/dist/table-BW3cmUqr.d.ts +0 -364
- package/dist/table.cjs.map +0 -1
- package/dist/table.d.cts +0 -12
- package/dist/table.d.ts +0 -12
- package/dist/table.js.map +0 -1
- package/dist/types.cjs +0 -4
- package/dist/types.cjs.map +0 -1
- package/dist/types.d.cts +0 -22
- package/dist/types.d.ts +0 -22
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/utils/partition-key-template.cjs +0 -19
- package/dist/utils/partition-key-template.cjs.map +0 -1
- package/dist/utils/partition-key-template.d.cts +0 -32
- package/dist/utils/partition-key-template.d.ts +0 -32
- package/dist/utils/partition-key-template.js +0 -17
- package/dist/utils/partition-key-template.js.map +0 -1
- package/dist/utils/sort-key-template.cjs +0 -19
- package/dist/utils/sort-key-template.cjs.map +0 -1
- package/dist/utils/sort-key-template.d.cts +0 -35
- package/dist/utils/sort-key-template.d.ts +0 -35
- package/dist/utils/sort-key-template.js +0 -17
- package/dist/utils/sort-key-template.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,46 +6,35 @@
|
|
|
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
|
|
10
|
-
await
|
|
9
|
+
// Type-safe DynamoDB operations made simple
|
|
10
|
+
await table
|
|
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')
|
|
16
|
+
.add('sightings', 1)
|
|
17
|
+
.condition(op => op.eq('status', 'ACTIVE'))
|
|
18
18
|
.execute();
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## 🌟 Why dyno-table for your Dinosaur Data?
|
|
21
|
+
## 🌟 Why dyno-table?
|
|
24
22
|
|
|
25
|
-
-
|
|
26
|
-
- **🛡️
|
|
27
|
-
- **⚡
|
|
28
|
-
- **🔒
|
|
29
|
-
- **📈
|
|
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
|
|
30
28
|
|
|
31
29
|
## 📑 Table of Contents
|
|
32
30
|
|
|
33
31
|
- [🦖 dyno-table ](#-dyno-table--)
|
|
34
|
-
- [🌟 Why dyno-table
|
|
32
|
+
- [🌟 Why dyno-table?](#-why-dyno-table)
|
|
35
33
|
- [📑 Table of Contents](#-table-of-contents)
|
|
36
34
|
- [📦 Installation](#-installation)
|
|
37
35
|
- [🚀 Quick Start](#-quick-start)
|
|
38
|
-
- [1. Configure Your
|
|
39
|
-
- [2. Perform Type-Safe
|
|
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)
|
|
36
|
+
- [1. Configure Your Table](#1-configure-your-table)
|
|
37
|
+
- [2. Perform Type-Safe Operations](#2-perform-type-safe-operations)
|
|
49
38
|
- [🧩 Advanced Features](#-advanced-features)
|
|
50
39
|
- [Transactional Operations](#transactional-operations)
|
|
51
40
|
- [Batch Processing](#batch-processing)
|
|
@@ -67,6 +56,7 @@ await dinoTable
|
|
|
67
56
|
- [🔒 Transaction Operations](#-transaction-operations)
|
|
68
57
|
- [Transaction Builder](#transaction-builder)
|
|
69
58
|
- [Transaction Options](#transaction-options)
|
|
59
|
+
- [🏗️ Entity Pattern Best Practices (Coming Soon TM)](#️-entity-pattern-best-practices-coming-soon-tm)
|
|
70
60
|
- [🚨 Error Handling](#-error-handling)
|
|
71
61
|
- [📚 API Reference](#-api-reference)
|
|
72
62
|
- [Condition Operators](#condition-operators-1)
|
|
@@ -92,26 +82,25 @@ npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
|
92
82
|
|
|
93
83
|
## 🚀 Quick Start
|
|
94
84
|
|
|
95
|
-
### 1. Configure Your
|
|
85
|
+
### 1. Configure Your Table
|
|
96
86
|
|
|
97
87
|
```ts
|
|
98
88
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
99
89
|
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
|
|
100
|
-
import { Table } from "dyno-table
|
|
90
|
+
import { Table } from "dyno-table";
|
|
101
91
|
|
|
102
|
-
// Configure AWS SDK clients
|
|
92
|
+
// Configure AWS SDK clients
|
|
103
93
|
const client = new DynamoDBClient({ region: "us-west-2" });
|
|
104
94
|
const docClient = DynamoDBDocument.from(client);
|
|
105
95
|
|
|
106
|
-
// Initialize table with single-table design schema
|
|
96
|
+
// Initialize table with single-table design schema
|
|
107
97
|
const dinoTable = new Table({
|
|
108
98
|
client: docClient,
|
|
109
|
-
tableName: "
|
|
99
|
+
tableName: "DinosaurPark",
|
|
110
100
|
indexes: {
|
|
111
|
-
partitionKey: "pk",
|
|
112
|
-
sortKey: "sk",
|
|
101
|
+
partitionKey: "pk",
|
|
102
|
+
sortKey: "sk",
|
|
113
103
|
gsis: {
|
|
114
|
-
// Global Secondary Index for querying dinosaurs by species
|
|
115
104
|
speciesId: {
|
|
116
105
|
partitionKey: "gsi1pk",
|
|
117
106
|
sortKey: "gsi1sk",
|
|
@@ -121,538 +110,141 @@ const dinoTable = new Table({
|
|
|
121
110
|
});
|
|
122
111
|
```
|
|
123
112
|
|
|
124
|
-
### 2. Perform Type-Safe
|
|
113
|
+
### 2. Perform Type-Safe Operations
|
|
125
114
|
|
|
126
|
-
**🦖 Creating a new dinosaur
|
|
115
|
+
**🦖 Creating a new dinosaur**
|
|
127
116
|
```ts
|
|
128
|
-
// Add a new T-Rex to your collection with complete type safety
|
|
129
117
|
const rex = await dinoTable
|
|
130
118
|
.create<Dinosaur>({
|
|
131
|
-
pk: "SPECIES#trex",
|
|
132
|
-
sk: "PROFILE#trex",
|
|
133
|
-
speciesId: "trex",
|
|
134
|
-
name: "Tyrannosaurus Rex",
|
|
135
|
-
diet: "carnivore",
|
|
136
|
-
length: 12.3,
|
|
137
|
-
discoveryYear: 1902
|
|
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
|
|
138
126
|
})
|
|
139
127
|
.execute();
|
|
140
128
|
```
|
|
141
129
|
|
|
142
|
-
**🔍 Query
|
|
130
|
+
**🔍 Query with conditions**
|
|
143
131
|
```ts
|
|
144
|
-
// Find large carnivorous dinosaurs in the T-Rex species
|
|
145
132
|
const largeDinos = await dinoTable
|
|
146
133
|
.query<Dinosaur>({
|
|
147
|
-
pk: "SPECIES#trex",
|
|
148
|
-
sk: (op) => op.beginsWith("PROFILE#")
|
|
134
|
+
pk: "SPECIES#trex",
|
|
135
|
+
sk: (op) => op.beginsWith("PROFILE#")
|
|
149
136
|
})
|
|
150
137
|
.filter((op) => op.and(
|
|
151
|
-
op.gte("length", 10),
|
|
152
|
-
op.eq("diet", "carnivore")
|
|
138
|
+
op.gte("length", 10),
|
|
139
|
+
op.eq("diet", "carnivore")
|
|
153
140
|
))
|
|
154
|
-
.limit(10)
|
|
141
|
+
.limit(10)
|
|
155
142
|
.execute();
|
|
156
143
|
```
|
|
157
144
|
|
|
158
|
-
**🔄
|
|
145
|
+
**🔄 Complex update operation**
|
|
159
146
|
```ts
|
|
160
|
-
// Update a dinosaur's diet classification based on new research
|
|
161
147
|
await dinoTable
|
|
162
148
|
.update<Dinosaur>({
|
|
163
|
-
pk: "SPECIES#trex",
|
|
164
|
-
sk: "PROFILE#trex"
|
|
149
|
+
pk: "SPECIES#trex",
|
|
150
|
+
sk: "PROFILE#trex"
|
|
165
151
|
})
|
|
166
|
-
.set("diet", "omnivore")
|
|
167
|
-
.add("discoveryYear", 1)
|
|
168
|
-
.remove("outdatedField")
|
|
169
|
-
.condition((op) => op.attributeExists("discoverySite"))
|
|
152
|
+
.set("diet", "omnivore")
|
|
153
|
+
.add("discoveryYear", 1)
|
|
154
|
+
.remove("outdatedField")
|
|
155
|
+
.condition((op) => op.attributeExists("discoverySite"))
|
|
170
156
|
.execute();
|
|
171
157
|
```
|
|
172
158
|
|
|
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
|
-
|
|
557
159
|
## 🧩 Advanced Features
|
|
558
160
|
|
|
559
161
|
### Transactional Operations
|
|
560
162
|
|
|
561
163
|
**Safe dinosaur transfer between enclosures**
|
|
562
164
|
```ts
|
|
563
|
-
// Start a transaction session for transferring a
|
|
564
|
-
// Critical for safety: All operations must succeed or none will be applied
|
|
165
|
+
// Start a transaction session for transferring a dinosaur
|
|
565
166
|
await dinoTable.transaction(async (tx) => {
|
|
566
167
|
// All operations are executed as a single transaction (up to 100 operations)
|
|
567
|
-
// This ensures the dinosaur transfer is atomic - preventing half-completed transfers
|
|
568
168
|
|
|
569
|
-
//
|
|
570
|
-
// We must verify the enclosure is prepared and suitable for a carnivore
|
|
169
|
+
// Check if destination enclosure is ready and compatible
|
|
571
170
|
await dinoTable
|
|
572
171
|
.conditionCheck({
|
|
573
|
-
pk: "ENCLOSURE#B",
|
|
574
|
-
sk: "STATUS"
|
|
172
|
+
pk: "ENCLOSURE#B",
|
|
173
|
+
sk: "STATUS"
|
|
575
174
|
})
|
|
576
175
|
.condition(op => op.and(
|
|
577
|
-
op.eq("status", "READY"),
|
|
578
|
-
op.eq("diet", "Carnivore") //
|
|
176
|
+
op.eq("status", "READY"),
|
|
177
|
+
op.eq("diet", "Carnivore") // Ensure enclosure matches dinosaur diet
|
|
579
178
|
))
|
|
580
179
|
.withTransaction(tx);
|
|
581
180
|
|
|
582
|
-
//
|
|
583
|
-
// Only proceed if the dinosaur is healthy enough for transfer
|
|
181
|
+
// Remove dinosaur from current enclosure
|
|
584
182
|
await dinoTable
|
|
585
183
|
.delete<Dinosaur>({
|
|
586
|
-
pk: "ENCLOSURE#A",
|
|
587
|
-
sk: "DINO#001"
|
|
184
|
+
pk: "ENCLOSURE#A",
|
|
185
|
+
sk: "DINO#001"
|
|
588
186
|
})
|
|
589
187
|
.condition(op => op.and(
|
|
590
|
-
op.eq("status", "HEALTHY"),
|
|
591
|
-
op.gte("health", 80)
|
|
188
|
+
op.eq("status", "HEALTHY"),
|
|
189
|
+
op.gte("health", 80) // Only transfer healthy dinosaurs
|
|
592
190
|
))
|
|
593
191
|
.withTransaction(tx);
|
|
594
192
|
|
|
595
|
-
//
|
|
596
|
-
// Create a fresh record in the destination enclosure
|
|
193
|
+
// Add dinosaur to new enclosure
|
|
597
194
|
await dinoTable
|
|
598
195
|
.create<Dinosaur>({
|
|
599
|
-
pk: "ENCLOSURE#B",
|
|
600
|
-
sk: "DINO#001",
|
|
601
|
-
name: "Rex",
|
|
602
|
-
species: "Tyrannosaurus",
|
|
603
|
-
diet: "Carnivore",
|
|
604
|
-
status: "HEALTHY",
|
|
605
|
-
health: 100,
|
|
606
|
-
enclosureId: "B",
|
|
607
|
-
lastFed: new Date().toISOString()
|
|
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()
|
|
608
205
|
})
|
|
609
206
|
.withTransaction(tx);
|
|
610
207
|
|
|
611
|
-
//
|
|
612
|
-
// Keep accurate count of dinosaurs in each enclosure
|
|
208
|
+
// Update enclosure occupancy tracking
|
|
613
209
|
await dinoTable
|
|
614
210
|
.update<Dinosaur>({
|
|
615
|
-
pk: "ENCLOSURE#B",
|
|
616
|
-
sk: "OCCUPANCY"
|
|
211
|
+
pk: "ENCLOSURE#B",
|
|
212
|
+
sk: "OCCUPANCY"
|
|
617
213
|
})
|
|
618
|
-
.add("currentOccupants", 1)
|
|
619
|
-
.set("lastUpdated", new Date().toISOString())
|
|
214
|
+
.add("currentOccupants", 1)
|
|
215
|
+
.set("lastUpdated", new Date().toISOString())
|
|
620
216
|
.withTransaction(tx);
|
|
621
217
|
});
|
|
622
218
|
|
|
623
|
-
// Transaction
|
|
624
|
-
// Ensures feeding status and schedule are updated atomically
|
|
219
|
+
// Transaction with feeding and health monitoring
|
|
625
220
|
await dinoTable.transaction(
|
|
626
221
|
async (tx) => {
|
|
627
|
-
//
|
|
628
|
-
// Record that the dinosaur has been fed and update its health metrics
|
|
222
|
+
// Update dinosaur health and feeding status
|
|
629
223
|
await dinoTable
|
|
630
224
|
.update<Dinosaur>({
|
|
631
|
-
pk: "ENCLOSURE#D",
|
|
632
|
-
sk: "DINO#003"
|
|
225
|
+
pk: "ENCLOSURE#D",
|
|
226
|
+
sk: "DINO#003"
|
|
633
227
|
})
|
|
634
228
|
.set({
|
|
635
|
-
status: "HEALTHY",
|
|
636
|
-
lastFed: new Date().toISOString(),
|
|
637
|
-
health: 100
|
|
229
|
+
status: "HEALTHY",
|
|
230
|
+
lastFed: new Date().toISOString(),
|
|
231
|
+
health: 100
|
|
638
232
|
})
|
|
639
|
-
.deleteElementsFromSet("tags", ["needs_feeding"])
|
|
233
|
+
.deleteElementsFromSet("tags", ["needs_feeding"])
|
|
640
234
|
.withTransaction(tx);
|
|
641
235
|
|
|
642
|
-
//
|
|
643
|
-
// Schedule next feeding time for tomorrow
|
|
236
|
+
// Update enclosure feeding schedule
|
|
644
237
|
await dinoTable
|
|
645
238
|
.update<Dinosaur>({
|
|
646
|
-
pk: "ENCLOSURE#D",
|
|
647
|
-
sk: "SCHEDULE"
|
|
239
|
+
pk: "ENCLOSURE#D",
|
|
240
|
+
sk: "SCHEDULE"
|
|
648
241
|
})
|
|
649
|
-
.set("nextFeedingTime", new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString())
|
|
242
|
+
.set("nextFeedingTime", new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString())
|
|
650
243
|
.withTransaction(tx);
|
|
651
244
|
},
|
|
652
245
|
{
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
returnConsumedCapacity: "TOTAL" // Track capacity usage for park operations
|
|
246
|
+
clientRequestToken: "feeding-session-001",
|
|
247
|
+
returnConsumedCapacity: "TOTAL"
|
|
656
248
|
}
|
|
657
249
|
);
|
|
658
250
|
```
|
|
@@ -669,42 +261,35 @@ await dinoTable.transaction(
|
|
|
669
261
|
|
|
670
262
|
**Efficient dinosaur park management with bulk operations**
|
|
671
263
|
```ts
|
|
672
|
-
//
|
|
673
|
-
// Retrieve health status for multiple dinosaurs in a single operation
|
|
264
|
+
// Batch health check for multiple dinosaurs
|
|
674
265
|
const healthCheckKeys = [
|
|
675
|
-
{ pk: "ENCLOSURE#A", sk: "DINO#001" }, // T-Rex
|
|
676
|
-
{ pk: "ENCLOSURE#B", sk: "DINO#002" }, // Velociraptor
|
|
677
|
-
{ pk: "ENCLOSURE#C", sk: "DINO#003" } // Stegosaurus
|
|
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
|
|
678
269
|
];
|
|
679
270
|
|
|
680
|
-
// Perform batch get operation to retrieve all dinosaurs at once
|
|
681
|
-
// This is much more efficient than individual gets
|
|
682
271
|
const { items: dinosaurs, unprocessedKeys } = await dinoTable.batchGet<Dinosaur>(healthCheckKeys);
|
|
683
272
|
console.log(`Health check completed for ${dinosaurs.length} dinosaurs`);
|
|
684
|
-
|
|
685
|
-
// Process health check results and identify any dinosaurs needing attention
|
|
686
273
|
dinosaurs.forEach(dino => {
|
|
687
274
|
if (dino.health < 80) {
|
|
688
275
|
console.log(`Health alert for ${dino.name} in Enclosure ${dino.enclosureId}`);
|
|
689
|
-
// In a real application, you might trigger alerts or schedule veterinary visits
|
|
690
276
|
}
|
|
691
277
|
});
|
|
692
278
|
|
|
693
|
-
//
|
|
694
|
-
// Prepare data for multiple new herbivores joining the collection
|
|
279
|
+
// Batch update feeding schedule for herbivore group
|
|
695
280
|
const newHerbivores = [
|
|
696
281
|
{
|
|
697
282
|
pk: "ENCLOSURE#D", sk: "DINO#004",
|
|
698
|
-
name: "Triceratops Alpha",
|
|
283
|
+
name: "Triceratops Alpha",
|
|
699
284
|
species: "Triceratops",
|
|
700
285
|
diet: "Herbivore",
|
|
701
286
|
status: "HEALTHY",
|
|
702
|
-
health: 95,
|
|
703
|
-
lastFed: new Date().toISOString()
|
|
287
|
+
health: 95,
|
|
288
|
+
lastFed: new Date().toISOString()
|
|
704
289
|
},
|
|
705
290
|
{
|
|
706
291
|
pk: "ENCLOSURE#D", sk: "DINO#005",
|
|
707
|
-
name: "Brachy",
|
|
292
|
+
name: "Brachy",
|
|
708
293
|
species: "Brachiosaurus",
|
|
709
294
|
diet: "Herbivore",
|
|
710
295
|
status: "HEALTHY",
|
|
@@ -713,117 +298,91 @@ const newHerbivores = [
|
|
|
713
298
|
}
|
|
714
299
|
];
|
|
715
300
|
|
|
716
|
-
// Add
|
|
717
|
-
// More efficient than individual writes and ensures consistent state
|
|
301
|
+
// Add new herbivores to enclosure
|
|
718
302
|
await dinoTable.batchWrite(
|
|
719
303
|
newHerbivores.map(dino => ({
|
|
720
|
-
type: "put",
|
|
721
|
-
item: dino
|
|
304
|
+
type: "put",
|
|
305
|
+
item: dino
|
|
722
306
|
}))
|
|
723
307
|
);
|
|
724
308
|
|
|
725
|
-
//
|
|
726
|
-
// Multiple related operations performed as a batch
|
|
309
|
+
// Mixed operations: relocate dinosaurs and update enclosure status
|
|
727
310
|
await dinoTable.batchWrite([
|
|
728
|
-
//
|
|
729
|
-
{
|
|
730
|
-
|
|
731
|
-
key: { pk: "ENCLOSURE#QUARANTINE", sk: "DINO#006" }
|
|
732
|
-
},
|
|
733
|
-
|
|
734
|
-
// Step 2: Add recovered dinosaur to main raptor enclosure
|
|
311
|
+
// Remove dinosaur from quarantine
|
|
312
|
+
{ type: "delete", key: { pk: "ENCLOSURE#QUARANTINE", sk: "DINO#006" } },
|
|
313
|
+
// Add recovered dinosaur to main enclosure
|
|
735
314
|
{
|
|
736
315
|
type: "put",
|
|
737
316
|
item: {
|
|
738
317
|
pk: "ENCLOSURE#E", sk: "DINO#006",
|
|
739
|
-
name: "Raptor Beta",
|
|
318
|
+
name: "Raptor Beta",
|
|
740
319
|
species: "Velociraptor",
|
|
741
320
|
diet: "Carnivore",
|
|
742
|
-
status: "HEALTHY",
|
|
321
|
+
status: "HEALTHY",
|
|
743
322
|
health: 100,
|
|
744
323
|
lastFed: new Date().toISOString()
|
|
745
324
|
}
|
|
746
325
|
},
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
{
|
|
750
|
-
type: "delete",
|
|
751
|
-
key: { pk: "ENCLOSURE#QUARANTINE", sk: "STATUS#DINO#006" }
|
|
752
|
-
}
|
|
326
|
+
// Clear quarantine status
|
|
327
|
+
{ type: "delete", key: { pk: "ENCLOSURE#QUARANTINE", sk: "STATUS#DINO#006" } }
|
|
753
328
|
]);
|
|
754
329
|
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
// The library automatically handles chunking for large batches:
|
|
758
|
-
// - 25 items per batch write
|
|
759
|
-
// - 100 items per batch get
|
|
330
|
+
// Handle large-scale park operations
|
|
331
|
+
// (25 items per batch write, 100 items per batch get)
|
|
760
332
|
const dailyHealthUpdates = generateDinosaurHealthUpdates(); // Hundreds of updates
|
|
761
|
-
await dinoTable.batchWrite(dailyHealthUpdates); // Automatically chunked
|
|
333
|
+
await dinoTable.batchWrite(dailyHealthUpdates); // Automatically chunked
|
|
762
334
|
```
|
|
763
335
|
|
|
764
336
|
### Pagination Made Simple
|
|
765
337
|
|
|
766
|
-
**Efficient dinosaur record browsing
|
|
338
|
+
**Efficient dinosaur record browsing**
|
|
767
339
|
```ts
|
|
768
|
-
//
|
|
769
|
-
// Create a paginator for viewing healthy herbivores in manageable chunks
|
|
770
|
-
// Perfect for veterinary staff doing routine health checks
|
|
340
|
+
// Create a paginator for viewing herbivores by health status
|
|
771
341
|
const healthyHerbivores = dinoTable
|
|
772
342
|
.query<Dinosaur>({
|
|
773
|
-
pk: "DIET#herbivore",
|
|
774
|
-
sk: op => op.beginsWith("STATUS#HEALTHY")
|
|
343
|
+
pk: "DIET#herbivore",
|
|
344
|
+
sk: op => op.beginsWith("STATUS#HEALTHY")
|
|
775
345
|
})
|
|
776
346
|
.filter((op) => op.and(
|
|
777
|
-
op.gte("health", 90),
|
|
778
|
-
op.attributeExists("lastFed")
|
|
347
|
+
op.gte("health", 90),
|
|
348
|
+
op.attributeExists("lastFed")
|
|
779
349
|
))
|
|
780
|
-
.paginate(5);
|
|
350
|
+
.paginate(5); // View 5 dinosaurs at a time
|
|
781
351
|
|
|
782
|
-
//
|
|
783
|
-
// without loading everything into memory at once
|
|
784
|
-
console.log("🦕 Beginning herbivore health inspection rounds...");
|
|
352
|
+
// Monitor all enclosures page by page
|
|
785
353
|
while (healthyHerbivores.hasNextPage()) {
|
|
786
|
-
// Get the next page of dinosaurs
|
|
787
354
|
const page = await healthyHerbivores.getNextPage();
|
|
788
355
|
console.log(`Checking herbivores page ${page.page}, found ${page.items.length} dinosaurs`);
|
|
789
|
-
|
|
790
|
-
// Process each dinosaur in the current page
|
|
791
356
|
page.items.forEach(dino => {
|
|
792
357
|
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
|
|
794
358
|
});
|
|
795
359
|
}
|
|
796
360
|
|
|
797
|
-
//
|
|
798
|
-
// Get all carnivores at once for daily feeding planning
|
|
799
|
-
// This approach loads all matching items into memory
|
|
361
|
+
// Get all carnivores for daily feeding schedule
|
|
800
362
|
const carnivoreSchedule = await dinoTable
|
|
801
363
|
.query<Dinosaur>({
|
|
802
|
-
pk: "DIET#carnivore",
|
|
803
|
-
sk: op => op.beginsWith("ENCLOSURE#")
|
|
364
|
+
pk: "DIET#carnivore",
|
|
365
|
+
sk: op => op.beginsWith("ENCLOSURE#")
|
|
804
366
|
})
|
|
805
|
-
.filter(op => op.attributeExists("lastFed"))
|
|
806
|
-
.paginate(10)
|
|
807
|
-
.getAllPages();
|
|
367
|
+
.filter(op => op.attributeExists("lastFed"))
|
|
368
|
+
.paginate(10)
|
|
369
|
+
.getAllPages();
|
|
808
370
|
|
|
809
371
|
console.log(`Scheduling feeding for ${carnivoreSchedule.length} carnivores`);
|
|
810
|
-
// Now we can sort and organize feeding times based on species, size, etc.
|
|
811
372
|
|
|
812
|
-
//
|
|
813
|
-
// Create a paginated view for the public-facing dinosaur information kiosk
|
|
373
|
+
// Limited view for visitor information kiosk
|
|
814
374
|
const visitorKiosk = dinoTable
|
|
815
375
|
.query<Dinosaur>({
|
|
816
|
-
pk: "VISITOR_VIEW",
|
|
817
|
-
sk: op => op.beginsWith("SPECIES#")
|
|
376
|
+
pk: "VISITOR_VIEW",
|
|
377
|
+
sk: op => op.beginsWith("SPECIES#")
|
|
818
378
|
})
|
|
819
|
-
.filter(op => op.eq("status", "ON_DISPLAY"))
|
|
820
|
-
.limit(12)
|
|
821
|
-
.paginate(4);
|
|
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
|
|
822
382
|
|
|
823
|
-
// Get first page for
|
|
383
|
+
// Get first page for kiosk display
|
|
824
384
|
const firstPage = await visitorKiosk.getNextPage();
|
|
825
|
-
console.log(
|
|
826
|
-
// Visitors can press "Next" to see more dinosaurs in the collection
|
|
385
|
+
console.log(`Now showing: ${firstPage.items.map(d => d.name).join(", ")}`);
|
|
827
386
|
```
|
|
828
387
|
|
|
829
388
|
## 🛡️ Type-Safe Query Building
|
|
@@ -1111,6 +670,24 @@ const result = await table.transaction(
|
|
|
1111
670
|
);
|
|
1112
671
|
```
|
|
1113
672
|
|
|
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
|
|
1114
691
|
|
|
1115
692
|
## 🚨 Error Handling
|
|
1116
693
|
|
|
@@ -1273,6 +850,41 @@ pnpm test
|
|
|
1273
850
|
pnpm build
|
|
1274
851
|
```
|
|
1275
852
|
|
|
853
|
+
## 📦 Release Process
|
|
854
|
+
|
|
855
|
+
This project uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated versioning and package publishing. The configuration is maintained in the `.releaserc.json` file. Releases are automatically triggered by commits to specific branches:
|
|
856
|
+
|
|
857
|
+
- **Main Channel**: Stable releases from the `main` branch
|
|
858
|
+
- **Alpha Channel**: Pre-releases from the `alpha` branch
|
|
859
|
+
|
|
860
|
+
### Commit Message Format
|
|
861
|
+
|
|
862
|
+
We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages, which determines the release type:
|
|
863
|
+
|
|
864
|
+
- `fix: ...` - Patch release (bug fixes)
|
|
865
|
+
- `feat: ...` - Minor release (new features)
|
|
866
|
+
- `feat!: ...` or `fix!: ...` or any commit with `BREAKING CHANGE:` in the footer - Major release
|
|
867
|
+
|
|
868
|
+
### Release Workflow
|
|
869
|
+
|
|
870
|
+
1. For regular features and fixes:
|
|
871
|
+
- Create a PR against the `main` branch
|
|
872
|
+
- Once merged, a new release will be automatically published
|
|
873
|
+
|
|
874
|
+
2. For experimental features:
|
|
875
|
+
- Create a PR against the `alpha` branch
|
|
876
|
+
- Once merged, a new alpha release will be published with an alpha tag
|
|
877
|
+
|
|
878
|
+
### Installing Specific Channels
|
|
879
|
+
|
|
880
|
+
```bash
|
|
881
|
+
# Install the latest stable version
|
|
882
|
+
npm install dyno-table
|
|
883
|
+
|
|
884
|
+
# Install the latest alpha version
|
|
885
|
+
npm install dyno-table@alpha
|
|
886
|
+
```
|
|
887
|
+
|
|
1276
888
|
## 🦔 Running Examples
|
|
1277
889
|
|
|
1278
890
|
There's a few pre-configured example scripts in the `examples` directory.
|
|
@@ -1296,10 +908,3 @@ npx tsx examples/[EXAMPLE_NAME].ts
|
|
|
1296
908
|
```
|
|
1297
909
|
|
|
1298
910
|
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
|
-
```
|