@jaypie/mcp 0.2.3 → 0.2.5
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/dist/index.js +1 -1
- package/package.json +1 -1
- package/prompts/Jaypie_CDK_Constructs_and_Patterns.md +51 -0
- package/prompts/Jaypie_DynamoDB_Package.md +662 -0
- package/prompts/Jaypie_Express_Package.md +91 -0
- package/prompts/Jaypie_Init_Lambda_Package.md +134 -1
- package/prompts/Jaypie_Llm_Calls.md +77 -0
- package/prompts/Jaypie_Llm_Tools.md +26 -3
- package/prompts/Jaypie_Vocabulary_Commander.md +411 -0
- package/prompts/Jaypie_Vocabulary_LLM.md +312 -0
- package/prompts/Jaypie_Vocabulary_Lambda.md +310 -0
- package/prompts/Jaypie_Vocabulary_MCP.md +296 -0
- package/prompts/Jaypie_Vocabulary_Package.md +141 -183
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Complete guide to @jaypie/dynamodb single-table design utilities including GSI patterns, key builders, entity operations, and query functions
|
|
3
|
+
globs: packages/dynamodb/**
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Jaypie DynamoDB Package
|
|
7
|
+
|
|
8
|
+
Jaypie provides DynamoDB single-table design utilities through `@jaypie/dynamodb`. The package implements a five-GSI pattern for hierarchical data with named access patterns (not gsi1, gsi2, etc.).
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @jaypie/dynamodb
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Core Concepts
|
|
17
|
+
|
|
18
|
+
### Single-Table Design
|
|
19
|
+
|
|
20
|
+
All entities share a single DynamoDB table with:
|
|
21
|
+
- **Primary Key**: `model` (partition) + `id` (sort)
|
|
22
|
+
- **Five GSIs**: All use `sequence` as sort key for chronological ordering
|
|
23
|
+
|
|
24
|
+
### Organizational Unit (OU)
|
|
25
|
+
|
|
26
|
+
The `ou` field creates hierarchical relationships:
|
|
27
|
+
- Root entities: `ou = "@"` (APEX constant)
|
|
28
|
+
- Child entities: `ou = "{parent.model}#{parent.id}"`
|
|
29
|
+
|
|
30
|
+
### GSI Pattern
|
|
31
|
+
|
|
32
|
+
| GSI Name | Partition Key | Use Case |
|
|
33
|
+
|----------|---------------|----------|
|
|
34
|
+
| `indexOu` | `{ou}#{model}` | List all entities under a parent |
|
|
35
|
+
| `indexAlias` | `{ou}#{model}#{alias}` | Human-friendly slug lookup |
|
|
36
|
+
| `indexClass` | `{ou}#{model}#{class}` | Category filtering |
|
|
37
|
+
| `indexType` | `{ou}#{model}#{type}` | Type filtering |
|
|
38
|
+
| `indexXid` | `{ou}#{model}#{xid}` | External system ID lookup |
|
|
39
|
+
|
|
40
|
+
## Client Initialization
|
|
41
|
+
|
|
42
|
+
Initialize once at application startup:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { initClient } from "@jaypie/dynamodb";
|
|
46
|
+
|
|
47
|
+
initClient({
|
|
48
|
+
tableName: process.env.DYNAMODB_TABLE_NAME,
|
|
49
|
+
region: process.env.AWS_REGION, // Optional, defaults to us-east-1
|
|
50
|
+
endpoint: process.env.DYNAMODB_ENDPOINT, // Optional, for local dev
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For local development with DynamoDB Local:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
initClient({
|
|
58
|
+
tableName: "local-table",
|
|
59
|
+
endpoint: "http://127.0.0.1:8100",
|
|
60
|
+
// Credentials auto-detected for localhost endpoints
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## FabricEntity Interface
|
|
65
|
+
|
|
66
|
+
All entities must implement `FabricEntity`:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import type { FabricEntity } from "@jaypie/dynamodb";
|
|
70
|
+
|
|
71
|
+
interface MyRecord extends FabricEntity {
|
|
72
|
+
// Primary Key (required)
|
|
73
|
+
model: string; // e.g., "record"
|
|
74
|
+
id: string; // UUID
|
|
75
|
+
|
|
76
|
+
// Required fields
|
|
77
|
+
name: string;
|
|
78
|
+
ou: string; // APEX or hierarchical
|
|
79
|
+
sequence: number; // Date.now()
|
|
80
|
+
|
|
81
|
+
// Timestamps (ISO 8601)
|
|
82
|
+
createdAt: string;
|
|
83
|
+
updatedAt: string;
|
|
84
|
+
archivedAt?: string; // Set by archiveEntity
|
|
85
|
+
deletedAt?: string; // Set by deleteEntity
|
|
86
|
+
|
|
87
|
+
// Optional - trigger GSI population
|
|
88
|
+
alias?: string; // Human-friendly slug
|
|
89
|
+
class?: string; // Category
|
|
90
|
+
type?: string; // Type classification
|
|
91
|
+
xid?: string; // External ID
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Entity Operations
|
|
96
|
+
|
|
97
|
+
### Creating Entities
|
|
98
|
+
|
|
99
|
+
Use `putEntity` to create or replace entities. GSI keys are auto-populated:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { APEX, putEntity } from "@jaypie/dynamodb";
|
|
103
|
+
|
|
104
|
+
const now = new Date().toISOString();
|
|
105
|
+
|
|
106
|
+
const record = await putEntity({
|
|
107
|
+
entity: {
|
|
108
|
+
model: "record",
|
|
109
|
+
id: crypto.randomUUID(),
|
|
110
|
+
name: "Daily Log",
|
|
111
|
+
ou: APEX,
|
|
112
|
+
sequence: Date.now(),
|
|
113
|
+
alias: "2026-01-07", // Optional
|
|
114
|
+
class: "memory", // Optional
|
|
115
|
+
createdAt: now,
|
|
116
|
+
updatedAt: now,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Result includes auto-populated indexes:
|
|
121
|
+
// indexOu: "@#record"
|
|
122
|
+
// indexAlias: "@#record#2026-01-07"
|
|
123
|
+
// indexClass: "@#record#memory"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Getting Entities
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { getEntity } from "@jaypie/dynamodb";
|
|
130
|
+
|
|
131
|
+
const record = await getEntity({ id: "123", model: "record" });
|
|
132
|
+
// Returns entity or null
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Updating Entities
|
|
136
|
+
|
|
137
|
+
Use `updateEntity` to update. Automatically sets `updatedAt` and re-indexes:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { updateEntity } from "@jaypie/dynamodb";
|
|
141
|
+
|
|
142
|
+
const updated = await updateEntity({
|
|
143
|
+
entity: {
|
|
144
|
+
...existingRecord,
|
|
145
|
+
name: "Updated Name",
|
|
146
|
+
alias: "new-alias",
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
// updatedAt is set automatically
|
|
150
|
+
// indexAlias is re-calculated
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Soft Delete
|
|
154
|
+
|
|
155
|
+
Use `deleteEntity` for soft delete (sets `deletedAt`):
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { deleteEntity } from "@jaypie/dynamodb";
|
|
159
|
+
|
|
160
|
+
await deleteEntity({ id: "123", model: "record" });
|
|
161
|
+
// Sets deletedAt and updatedAt timestamps
|
|
162
|
+
// Entity excluded from queries by default
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Archive
|
|
166
|
+
|
|
167
|
+
Use `archiveEntity` for archiving (sets `archivedAt`):
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { archiveEntity } from "@jaypie/dynamodb";
|
|
171
|
+
|
|
172
|
+
await archiveEntity({ id: "123", model: "record" });
|
|
173
|
+
// Sets archivedAt and updatedAt timestamps
|
|
174
|
+
// Entity excluded from queries by default
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Hard Delete
|
|
178
|
+
|
|
179
|
+
Use `destroyEntity` to permanently remove:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { destroyEntity } from "@jaypie/dynamodb";
|
|
183
|
+
|
|
184
|
+
await destroyEntity({ id: "123", model: "record" });
|
|
185
|
+
// Permanently removes from table
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Hierarchical Entities
|
|
189
|
+
|
|
190
|
+
Use `calculateOu` to derive OU from parent:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { calculateOu, putEntity, queryByOu } from "@jaypie/dynamodb";
|
|
194
|
+
|
|
195
|
+
// Parent reference
|
|
196
|
+
const chat = { model: "chat", id: "abc-123" };
|
|
197
|
+
|
|
198
|
+
// Calculate child OU
|
|
199
|
+
const messageOu = calculateOu(chat); // "chat#abc-123"
|
|
200
|
+
|
|
201
|
+
// Create child entity
|
|
202
|
+
const message = await putEntity({
|
|
203
|
+
entity: {
|
|
204
|
+
model: "message",
|
|
205
|
+
id: crypto.randomUUID(),
|
|
206
|
+
name: "First message",
|
|
207
|
+
ou: messageOu,
|
|
208
|
+
sequence: Date.now(),
|
|
209
|
+
createdAt: now,
|
|
210
|
+
updatedAt: now,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
// indexOu: "chat#abc-123#message"
|
|
214
|
+
|
|
215
|
+
// Query all messages in chat
|
|
216
|
+
const { items } = await queryByOu({ model: "message", ou: messageOu });
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Query Functions
|
|
220
|
+
|
|
221
|
+
All queries use object parameters and filter soft-deleted and archived records by default.
|
|
222
|
+
|
|
223
|
+
### queryByOu - List by Parent
|
|
224
|
+
|
|
225
|
+
List all entities of a model under a parent:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { APEX, queryByOu } from "@jaypie/dynamodb";
|
|
229
|
+
|
|
230
|
+
// Root-level records
|
|
231
|
+
const { items, lastEvaluatedKey } = await queryByOu({
|
|
232
|
+
model: "record",
|
|
233
|
+
ou: APEX,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Messages under a chat
|
|
237
|
+
const { items: messages } = await queryByOu({
|
|
238
|
+
model: "message",
|
|
239
|
+
ou: "chat#abc-123",
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### queryByAlias - Human-Friendly Lookup
|
|
244
|
+
|
|
245
|
+
Single entity lookup by slug:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { APEX, queryByAlias } from "@jaypie/dynamodb";
|
|
249
|
+
|
|
250
|
+
const record = await queryByAlias({
|
|
251
|
+
alias: "2026-01-07",
|
|
252
|
+
model: "record",
|
|
253
|
+
ou: APEX,
|
|
254
|
+
});
|
|
255
|
+
// Returns entity or null
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### queryByClass / queryByType - Category Filtering
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { APEX, queryByClass, queryByType } from "@jaypie/dynamodb";
|
|
262
|
+
|
|
263
|
+
// All memory records
|
|
264
|
+
const { items } = await queryByClass({
|
|
265
|
+
model: "record",
|
|
266
|
+
ou: APEX,
|
|
267
|
+
recordClass: "memory",
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// All note-type records
|
|
271
|
+
const { items: notes } = await queryByType({
|
|
272
|
+
model: "record",
|
|
273
|
+
ou: APEX,
|
|
274
|
+
type: "note",
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### queryByXid - External ID Lookup
|
|
279
|
+
|
|
280
|
+
Single entity lookup by external system ID:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { APEX, queryByXid } from "@jaypie/dynamodb";
|
|
284
|
+
|
|
285
|
+
const record = await queryByXid({
|
|
286
|
+
model: "record",
|
|
287
|
+
ou: APEX,
|
|
288
|
+
xid: "ext-12345",
|
|
289
|
+
});
|
|
290
|
+
// Returns entity or null
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Query Options
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import type { BaseQueryOptions } from "@jaypie/dynamodb";
|
|
297
|
+
|
|
298
|
+
const result = await queryByOu({
|
|
299
|
+
model: "record",
|
|
300
|
+
ou: APEX,
|
|
301
|
+
// BaseQueryOptions:
|
|
302
|
+
limit: 25, // Max items to return
|
|
303
|
+
ascending: true, // Oldest first (default: false = newest first)
|
|
304
|
+
includeDeleted: true, // Include soft-deleted records
|
|
305
|
+
includeArchived: true, // Include archived records
|
|
306
|
+
startKey: lastEvaluatedKey, // Pagination cursor
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Pagination
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { APEX, queryByOu } from "@jaypie/dynamodb";
|
|
314
|
+
|
|
315
|
+
let startKey: Record<string, unknown> | undefined;
|
|
316
|
+
const allItems: FabricEntity[] = [];
|
|
317
|
+
|
|
318
|
+
do {
|
|
319
|
+
const { items, lastEvaluatedKey } = await queryByOu({
|
|
320
|
+
model: "record",
|
|
321
|
+
ou: APEX,
|
|
322
|
+
limit: 100,
|
|
323
|
+
startKey,
|
|
324
|
+
});
|
|
325
|
+
allItems.push(...items);
|
|
326
|
+
startKey = lastEvaluatedKey;
|
|
327
|
+
} while (startKey);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Key Builder Functions
|
|
331
|
+
|
|
332
|
+
Use `indexEntity` to auto-populate GSI keys on an entity:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import { indexEntity } from "@jaypie/dynamodb";
|
|
336
|
+
|
|
337
|
+
const indexed = indexEntity({
|
|
338
|
+
model: "record",
|
|
339
|
+
id: "123",
|
|
340
|
+
ou: "@",
|
|
341
|
+
alias: "my-alias",
|
|
342
|
+
// ...
|
|
343
|
+
});
|
|
344
|
+
// indexOu: "@#record"
|
|
345
|
+
// indexAlias: "@#record#my-alias"
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Use individual key builders for manual key construction:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import {
|
|
352
|
+
buildIndexOu,
|
|
353
|
+
buildIndexAlias,
|
|
354
|
+
buildIndexClass,
|
|
355
|
+
buildIndexType,
|
|
356
|
+
buildIndexXid,
|
|
357
|
+
} from "@jaypie/dynamodb";
|
|
358
|
+
|
|
359
|
+
buildIndexOu("@", "record"); // "@#record"
|
|
360
|
+
buildIndexAlias("@", "record", "my-alias"); // "@#record#my-alias"
|
|
361
|
+
buildIndexClass("@", "record", "memory"); // "@#record#memory"
|
|
362
|
+
buildIndexType("@", "record", "note"); // "@#record#note"
|
|
363
|
+
buildIndexXid("@", "record", "ext-123"); // "@#record#ext-123"
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Constants
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import {
|
|
370
|
+
APEX, // "@" - Root-level marker
|
|
371
|
+
SEPARATOR, // "#" - Composite key separator
|
|
372
|
+
INDEX_OU, // "indexOu"
|
|
373
|
+
INDEX_ALIAS, // "indexAlias"
|
|
374
|
+
INDEX_CLASS, // "indexClass"
|
|
375
|
+
INDEX_TYPE, // "indexType"
|
|
376
|
+
INDEX_XID, // "indexXid"
|
|
377
|
+
} from "@jaypie/dynamodb";
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Table Schema (CloudFormation/CDK Reference)
|
|
381
|
+
|
|
382
|
+
```yaml
|
|
383
|
+
AttributeDefinitions:
|
|
384
|
+
- AttributeName: model
|
|
385
|
+
AttributeType: S
|
|
386
|
+
- AttributeName: id
|
|
387
|
+
AttributeType: S
|
|
388
|
+
- AttributeName: indexOu
|
|
389
|
+
AttributeType: S
|
|
390
|
+
- AttributeName: indexAlias
|
|
391
|
+
AttributeType: S
|
|
392
|
+
- AttributeName: indexClass
|
|
393
|
+
AttributeType: S
|
|
394
|
+
- AttributeName: indexType
|
|
395
|
+
AttributeType: S
|
|
396
|
+
- AttributeName: indexXid
|
|
397
|
+
AttributeType: S
|
|
398
|
+
- AttributeName: sequence
|
|
399
|
+
AttributeType: N
|
|
400
|
+
|
|
401
|
+
KeySchema:
|
|
402
|
+
- AttributeName: model
|
|
403
|
+
KeyType: HASH
|
|
404
|
+
- AttributeName: id
|
|
405
|
+
KeyType: RANGE
|
|
406
|
+
|
|
407
|
+
GlobalSecondaryIndexes:
|
|
408
|
+
- IndexName: indexOu
|
|
409
|
+
KeySchema:
|
|
410
|
+
- AttributeName: indexOu
|
|
411
|
+
KeyType: HASH
|
|
412
|
+
- AttributeName: sequence
|
|
413
|
+
KeyType: RANGE
|
|
414
|
+
Projection:
|
|
415
|
+
ProjectionType: ALL
|
|
416
|
+
# Repeat for indexAlias, indexClass, indexType, indexXid
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Error Handling
|
|
420
|
+
|
|
421
|
+
Functions throw `ConfigurationError` if client is not initialized:
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
import { getDocClient } from "@jaypie/dynamodb";
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
const client = getDocClient();
|
|
428
|
+
} catch (error) {
|
|
429
|
+
// ConfigurationError: DynamoDB client not initialized. Call initClient() first.
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## Testing
|
|
434
|
+
|
|
435
|
+
Mock implementations in `@jaypie/testkit`:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
import { vi } from "vitest";
|
|
439
|
+
|
|
440
|
+
vi.mock("@jaypie/dynamodb", async () => {
|
|
441
|
+
const testkit = await import("@jaypie/testkit/mock");
|
|
442
|
+
return testkit;
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Key builders and indexEntity work correctly (delegate to real implementations)
|
|
446
|
+
// Query functions return empty results by default
|
|
447
|
+
// Entity operations return sensible defaults
|
|
448
|
+
|
|
449
|
+
// Customize mock behavior:
|
|
450
|
+
import { queryByOu, getEntity, putEntity } from "@jaypie/testkit/mock";
|
|
451
|
+
|
|
452
|
+
queryByOu.mockResolvedValue({
|
|
453
|
+
items: [{ id: "123", name: "Test" }],
|
|
454
|
+
lastEvaluatedKey: undefined,
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
getEntity.mockResolvedValue({ id: "123", model: "record", name: "Test" });
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Best Practices
|
|
461
|
+
|
|
462
|
+
### Use putEntity, Not Direct Writes
|
|
463
|
+
|
|
464
|
+
Always use `putEntity` or `updateEntity` to ensure GSI keys are auto-populated:
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
// CORRECT - uses putEntity which calls indexEntity internally
|
|
468
|
+
const entity = await putEntity({
|
|
469
|
+
entity: {
|
|
470
|
+
model: "record",
|
|
471
|
+
alias: "my-alias",
|
|
472
|
+
// ...
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// WRONG - bypasses index population
|
|
477
|
+
const entity = {
|
|
478
|
+
model: "record",
|
|
479
|
+
alias: "my-alias",
|
|
480
|
+
indexAlias: "@#record#my-alias", // Don't manually set index keys
|
|
481
|
+
};
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Use indexEntity for Raw Entities
|
|
485
|
+
|
|
486
|
+
If you need to prepare an entity before a batch write:
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
import { indexEntity } from "@jaypie/dynamodb";
|
|
490
|
+
|
|
491
|
+
// Use indexEntity to prepare entities for batch operations
|
|
492
|
+
const indexed = indexEntity(myEntity);
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Use Meaningful Model Names
|
|
496
|
+
|
|
497
|
+
Model names are part of every key. Use short, descriptive names:
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
// GOOD
|
|
501
|
+
{ model: "record" }
|
|
502
|
+
{ model: "message" }
|
|
503
|
+
{ model: "chat" }
|
|
504
|
+
|
|
505
|
+
// AVOID
|
|
506
|
+
{ model: "DailyLogRecord" }
|
|
507
|
+
{ model: "ChatMessage_v2" }
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Sequence for Ordering
|
|
511
|
+
|
|
512
|
+
Always set `sequence: Date.now()` for chronological ordering in GSI queries:
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
const entity = await putEntity({
|
|
516
|
+
entity: {
|
|
517
|
+
// ...
|
|
518
|
+
sequence: Date.now(), // Required for proper ordering
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Soft Delete and Archive Patterns
|
|
524
|
+
|
|
525
|
+
Use `deleteEntity` for logical deletion and `archiveEntity` for archival:
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
// Soft delete - user action, can be recovered
|
|
529
|
+
await deleteEntity({ id: "123", model: "record" });
|
|
530
|
+
|
|
531
|
+
// Archive - system action, long-term storage
|
|
532
|
+
await archiveEntity({ id: "123", model: "record" });
|
|
533
|
+
|
|
534
|
+
// Queries exclude both by default
|
|
535
|
+
const { items } = await queryByOu({ model: "record", ou: APEX });
|
|
536
|
+
|
|
537
|
+
// Include if needed
|
|
538
|
+
const { items: all } = await queryByOu({
|
|
539
|
+
model: "record",
|
|
540
|
+
ou: APEX,
|
|
541
|
+
includeDeleted: true,
|
|
542
|
+
includeArchived: true,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Permanent deletion (use sparingly)
|
|
546
|
+
await destroyEntity({ id: "123", model: "record" });
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## MCP Integration
|
|
550
|
+
|
|
551
|
+
The package provides MCP (Model Context Protocol) tools via `@jaypie/dynamodb/mcp` subpath export.
|
|
552
|
+
|
|
553
|
+
### Installation
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
npm install @jaypie/dynamodb @modelcontextprotocol/sdk
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Environment Variables
|
|
560
|
+
|
|
561
|
+
| Variable | Required | Default | Description |
|
|
562
|
+
|----------|----------|---------|-------------|
|
|
563
|
+
| `DYNAMODB_TABLE_NAME` | Yes | - | Table name for operations |
|
|
564
|
+
| `DYNAMODB_ENDPOINT` | No | - | Local endpoint (e.g., `http://127.0.0.1:8000`) |
|
|
565
|
+
| `AWS_REGION` | No | `us-east-1` | AWS region |
|
|
566
|
+
| `PROJECT_NAME` | No | `jaypie` | Container name prefix for docker-compose |
|
|
567
|
+
|
|
568
|
+
### Register MCP Tools
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
572
|
+
import { registerDynamoDbTools } from "@jaypie/dynamodb/mcp";
|
|
573
|
+
|
|
574
|
+
const server = new McpServer({ name: "my-server", version: "1.0.0" });
|
|
575
|
+
|
|
576
|
+
const { tools } = registerDynamoDbTools({ server });
|
|
577
|
+
// tools: ["dynamodb_get", "dynamodb_put", "dynamodb_update", ...]
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### Available MCP Tools
|
|
581
|
+
|
|
582
|
+
#### Entity Operations
|
|
583
|
+
| Tool | Description |
|
|
584
|
+
|------|-------------|
|
|
585
|
+
| `dynamodb_get` | Get entity by id and model |
|
|
586
|
+
| `dynamodb_put` | Create or replace an entity |
|
|
587
|
+
| `dynamodb_update` | Update entity fields |
|
|
588
|
+
| `dynamodb_delete` | Soft delete (sets deletedAt) |
|
|
589
|
+
| `dynamodb_archive` | Archive entity (sets archivedAt) |
|
|
590
|
+
| `dynamodb_destroy` | Hard delete (permanent) |
|
|
591
|
+
|
|
592
|
+
#### Query Operations
|
|
593
|
+
| Tool | Description |
|
|
594
|
+
|------|-------------|
|
|
595
|
+
| `dynamodb_query_ou` | Query by organizational unit |
|
|
596
|
+
| `dynamodb_query_alias` | Query by human-friendly alias |
|
|
597
|
+
| `dynamodb_query_class` | Query by category classification |
|
|
598
|
+
| `dynamodb_query_type` | Query by type classification |
|
|
599
|
+
| `dynamodb_query_xid` | Query by external ID |
|
|
600
|
+
|
|
601
|
+
#### Admin Operations (Enabled by Default)
|
|
602
|
+
| Tool | Description |
|
|
603
|
+
|------|-------------|
|
|
604
|
+
| `dynamodb_status` | Check DynamoDB connection status |
|
|
605
|
+
| `dynamodb_create_table` | Create table with Jaypie GSI schema |
|
|
606
|
+
| `dynamodb_generate_docker_compose` | Generate docker-compose.yml for local dev |
|
|
607
|
+
|
|
608
|
+
### Disable Admin Tools
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
const { tools } = registerDynamoDbTools({
|
|
612
|
+
server,
|
|
613
|
+
includeAdmin: false, // Exclude admin tools
|
|
614
|
+
});
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Auto-Initialization
|
|
618
|
+
|
|
619
|
+
MCP tools auto-initialize the DynamoDB client from environment variables. Manual `initClient()` is not required when using MCP tools.
|
|
620
|
+
|
|
621
|
+
### Local Development with MCP
|
|
622
|
+
|
|
623
|
+
Generate docker-compose and create table:
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
// Use dynamodb_generate_docker_compose tool to get:
|
|
627
|
+
// - docker-compose.yml content
|
|
628
|
+
// - Environment variables (.env format)
|
|
629
|
+
|
|
630
|
+
// Start local DynamoDB
|
|
631
|
+
// docker compose up -d
|
|
632
|
+
|
|
633
|
+
// Use dynamodb_create_table tool to create table with full GSI schema
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### Example MCP Tool Usage
|
|
637
|
+
|
|
638
|
+
```json
|
|
639
|
+
// dynamodb_put
|
|
640
|
+
{
|
|
641
|
+
"id": "abc-123",
|
|
642
|
+
"model": "record",
|
|
643
|
+
"name": "My Record",
|
|
644
|
+
"ou": "@",
|
|
645
|
+
"alias": "my-record",
|
|
646
|
+
"class": "memory"
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// dynamodb_query_ou
|
|
650
|
+
{
|
|
651
|
+
"model": "record",
|
|
652
|
+
"ou": "@",
|
|
653
|
+
"limit": 10
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// dynamodb_update
|
|
657
|
+
{
|
|
658
|
+
"id": "abc-123",
|
|
659
|
+
"model": "record",
|
|
660
|
+
"name": "Updated Name"
|
|
661
|
+
}
|
|
662
|
+
```
|