dinah 0.11.0 → 0.11.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 +167 -47
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,16 +99,29 @@ const UserTable = new Table(
|
|
|
99
99
|
|
|
100
100
|
### Using the Repository Class
|
|
101
101
|
|
|
102
|
-
`
|
|
102
|
+
`Repo` is the recommended way to interact with DynamoDB. For a plain repo with no configuration, use `db.createRepo`:
|
|
103
103
|
|
|
104
104
|
```typescript
|
|
105
|
-
const userRepo = db.
|
|
105
|
+
const userRepo = db.makeRepo(UserTable);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For repos with defaults, transforms, or attribute rules, use `makeRepo` to define a class (see [Repository Configuration](#repository-configuration)):
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { makeRepo } from "dinah";
|
|
112
|
+
|
|
113
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
114
|
+
defaultPutData: () => ({ createdAt: Date.now() }),
|
|
115
|
+
defaultUpdateData: () => ({ updatedAt: Date.now() }),
|
|
116
|
+
}) {}
|
|
117
|
+
|
|
118
|
+
const userRepo = new UserRepo(db);
|
|
106
119
|
```
|
|
107
120
|
|
|
108
121
|
#### CRUD
|
|
109
122
|
|
|
110
123
|
```typescript
|
|
111
|
-
// Create (conditional put
|
|
124
|
+
// Create (conditional put — fails if item already exists)
|
|
112
125
|
const user = await userRepo.create({
|
|
113
126
|
userId: "u1",
|
|
114
127
|
email: "alice@example.com",
|
|
@@ -126,14 +139,47 @@ const partial = await userRepo.get({ userId: "u1" }, { projection: ["name", "ema
|
|
|
126
139
|
// getOrThrow (throws if item not found)
|
|
127
140
|
const aliceOrThrow = await userRepo.getOrThrow({ userId: "u1" });
|
|
128
141
|
|
|
129
|
-
//
|
|
142
|
+
// Put (upsert)
|
|
143
|
+
await userRepo.put({
|
|
144
|
+
userId: "u1",
|
|
145
|
+
email: "alice@example.com",
|
|
146
|
+
name: "Alice",
|
|
147
|
+
role: "admin",
|
|
148
|
+
createdAt: Date.now(),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Update (throws if item does not exist)
|
|
130
152
|
const updated = await userRepo.update(
|
|
131
153
|
{ userId: "u1" },
|
|
132
154
|
{ name: "Alice Smith", updatedAt: Date.now() },
|
|
133
155
|
);
|
|
134
156
|
|
|
135
|
-
// Delete
|
|
136
|
-
await userRepo.delete({ userId: "u1" });
|
|
157
|
+
// Delete (returns old item or undefined)
|
|
158
|
+
const deleted = await userRepo.delete({ userId: "u1" });
|
|
159
|
+
|
|
160
|
+
// deleteOrThrow (throws if item not found)
|
|
161
|
+
const item = await userRepo.deleteOrThrow({ userId: "u1" });
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Update Expressions
|
|
165
|
+
|
|
166
|
+
The update argument supports MongoDB-style operators:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
await userRepo.update(
|
|
170
|
+
{ userId: "u1" },
|
|
171
|
+
{
|
|
172
|
+
name: "Alice", // set
|
|
173
|
+
age: undefined, // remove
|
|
174
|
+
score: { $plus: 10 }, // increment
|
|
175
|
+
score: { $minus: 5 }, // decrement
|
|
176
|
+
score: { $ifNotExists: 0 }, // set only if missing
|
|
177
|
+
tags: { $append: "vip" }, // list_append to end
|
|
178
|
+
tags: { $prepend: "featured" }, // list_append to front
|
|
179
|
+
followers: { $setAdd: "user-99" }, // ADD to DynamoDB set
|
|
180
|
+
followers: { $setDel: "user-99" }, // DELETE from DynamoDB set
|
|
181
|
+
},
|
|
182
|
+
);
|
|
137
183
|
```
|
|
138
184
|
|
|
139
185
|
#### Querying
|
|
@@ -142,7 +188,10 @@ Queries use a MongoDB-like syntax with operators like `$gt`, `$between`, `$prefi
|
|
|
142
188
|
|
|
143
189
|
```typescript
|
|
144
190
|
// Query by partition key
|
|
145
|
-
const
|
|
191
|
+
const posts = await postRepo.query({ authorId: "u1" });
|
|
192
|
+
|
|
193
|
+
// Query with sort key condition
|
|
194
|
+
const recent = await postRepo.query({ authorId: "u1" }, { postId: { $gte: "2024-" } });
|
|
146
195
|
|
|
147
196
|
// Query a GSI
|
|
148
197
|
const adminsByDate = await userRepo.queryGsi("byRole", {
|
|
@@ -150,82 +199,153 @@ const adminsByDate = await userRepo.queryGsi("byRole", {
|
|
|
150
199
|
createdAt: { $gt: 1700000000000 },
|
|
151
200
|
});
|
|
152
201
|
|
|
202
|
+
// Paginated query (async generator, one page at a time)
|
|
203
|
+
for await (const page of postRepo.queryPaged({ authorId: "u1" }, { limit: 20 })) {
|
|
204
|
+
console.log(page);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Paginated GSI query
|
|
208
|
+
for await (const page of userRepo.queryGsiPaged("byRole", { role: "admin" })) {
|
|
209
|
+
console.log(page);
|
|
210
|
+
}
|
|
211
|
+
|
|
153
212
|
// Scan with filters
|
|
154
213
|
const recentUsers = await userRepo.scan({
|
|
155
214
|
filter: { createdAt: { $gte: 1700000000000 } },
|
|
156
215
|
});
|
|
157
216
|
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
217
|
+
// Scan a GSI
|
|
218
|
+
const allByStatus = await postRepo.scanGsi("byStatus");
|
|
219
|
+
|
|
220
|
+
// Check existence (uses query or scan, no data returned)
|
|
221
|
+
const hasAdmins = await userRepo.existsGsi("byRole", { query: { role: "admin" } });
|
|
222
|
+
const exists = await postRepo.exists({ query: { authorId: "u1" } });
|
|
162
223
|
```
|
|
163
224
|
|
|
225
|
+
Filter operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$between`, `$in`, `$nin`, `$prefix`, `$includes`, `$exists`, `$size`, `$type`.
|
|
226
|
+
|
|
164
227
|
#### Batch Operations
|
|
165
228
|
|
|
166
229
|
```typescript
|
|
167
230
|
// Batch get
|
|
168
|
-
const { items, unprocessed } = await userRepo.batchGet([
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
]);
|
|
231
|
+
const { items, unprocessed } = await userRepo.batchGet([{ userId: "u1" }, { userId: "u2" }]);
|
|
232
|
+
|
|
233
|
+
// Batch get (throws on missing or unprocessed)
|
|
234
|
+
const items = await userRepo.batchGetOrThrow([{ userId: "u1" }, { userId: "u2" }]);
|
|
173
235
|
|
|
174
|
-
// Batch write (puts and deletes)
|
|
236
|
+
// Batch write (puts and deletes mixed)
|
|
175
237
|
await userRepo.batchWrite([
|
|
238
|
+
{
|
|
239
|
+
type: "PUT",
|
|
240
|
+
item: { userId: "u3", email: "c@d.com", name: "Carol", role: "user", createdAt: Date.now() },
|
|
241
|
+
},
|
|
242
|
+
{ type: "DELETE", key: { userId: "u2" } },
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
// Batch update (same update applied to multiple keys via PartiQL)
|
|
246
|
+
await userRepo.batchUpdate([{ userId: "u1" }, { userId: "u3" }], { role: "admin" });
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Transactions
|
|
250
|
+
|
|
251
|
+
`trxWrite` accepts plain request objects and executes them as a single DynamoDB transaction:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
await userRepo.trxWrite(
|
|
176
255
|
{
|
|
177
256
|
type: "PUT",
|
|
178
257
|
item: {
|
|
179
|
-
userId: "
|
|
180
|
-
email: "
|
|
181
|
-
name: "
|
|
258
|
+
userId: "u5",
|
|
259
|
+
email: "eve@example.com",
|
|
260
|
+
name: "Eve",
|
|
182
261
|
role: "user",
|
|
183
262
|
createdAt: Date.now(),
|
|
184
263
|
},
|
|
185
264
|
},
|
|
186
|
-
{ type: "
|
|
187
|
-
|
|
265
|
+
{ type: "UPDATE", key: { userId: "u1" }, update: { role: "superadmin" } },
|
|
266
|
+
{ type: "DELETE", key: { userId: "u2" } },
|
|
267
|
+
{ type: "CONDITION", key: { userId: "u3" }, condition: { role: "admin" } },
|
|
268
|
+
);
|
|
188
269
|
```
|
|
189
270
|
|
|
190
|
-
|
|
271
|
+
Convenience methods operate on multiple keys/items atomically:
|
|
191
272
|
|
|
192
273
|
```typescript
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
274
|
+
// Transactional get
|
|
275
|
+
const [u1, u2] = await userRepo.trxGet([{ userId: "u1" }, { userId: "u2" }]);
|
|
276
|
+
const items = await userRepo.trxGetOrThrow([{ userId: "u1" }, { userId: "u2" }]);
|
|
277
|
+
|
|
278
|
+
// Transactional writes
|
|
279
|
+
await userRepo.trxPut([item1, item2]);
|
|
280
|
+
await userRepo.trxUpdate([{ userId: "u1" }, { userId: "u2" }], { role: "admin" });
|
|
281
|
+
await userRepo.trxDelete([{ userId: "u1" }, { userId: "u2" }]);
|
|
282
|
+
await userRepo.trxCreate([item1, item2]); // fails if any item already exists
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
To build cross-repo transactions, use the `*Request` methods to produce request objects and pass them to `db.trxWrite`:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
await db.trxWrite(
|
|
289
|
+
userRepo.trxPutRequest(userItem),
|
|
290
|
+
postRepo.trxDeleteRequest({ authorId: "u1", postId: "p1" }),
|
|
203
291
|
);
|
|
204
292
|
```
|
|
205
293
|
|
|
206
|
-
|
|
294
|
+
## Repository Configuration
|
|
207
295
|
|
|
208
|
-
|
|
296
|
+
`makeRepo` accepts a config object that controls defaults, transforms, and attribute rules. Extend the result to create a named repo class:
|
|
209
297
|
|
|
210
298
|
```typescript
|
|
211
|
-
|
|
299
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
300
|
+
defaultPutData: () => ({ createdAt: Date.now() }),
|
|
301
|
+
defaultUpdateData: () => ({ updatedAt: Date.now() }),
|
|
302
|
+
}) {}
|
|
303
|
+
```
|
|
212
304
|
|
|
213
|
-
|
|
214
|
-
readonly table = UserTable;
|
|
305
|
+
`defaultPutData` is merged under every `put` / `create` / `batchWrite` put / `trxPut` / `trxCreate`. `defaultUpdateData` is merged under every `update` / `batchUpdate` / `trxUpdate`. Caller-provided values always win.
|
|
215
306
|
|
|
216
|
-
|
|
217
|
-
return { createdAt: Date.now() };
|
|
218
|
-
}
|
|
307
|
+
### transformInput / transformOutput
|
|
219
308
|
|
|
220
|
-
|
|
221
|
-
return { updatedAt: Date.now() };
|
|
222
|
-
}
|
|
223
|
-
}
|
|
309
|
+
`transformInput` runs on every write (after defaults are merged) and receives a partial of the schema. `transformOutput` runs on every read and maps the stored shape to your desired return type:
|
|
224
310
|
|
|
225
|
-
|
|
311
|
+
```typescript
|
|
312
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
313
|
+
transformInput: (item) => ({
|
|
314
|
+
...item,
|
|
315
|
+
email: item.email?.toLowerCase(),
|
|
316
|
+
}),
|
|
317
|
+
transformOutput: (item): UserWithDisplayName => ({
|
|
318
|
+
...item,
|
|
319
|
+
displayName: `${item.name} <${item.email}>`,
|
|
320
|
+
}),
|
|
321
|
+
}) {}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Transforms are skipped when a `projection` option is provided, since only a subset of fields is available.
|
|
325
|
+
|
|
326
|
+
### derivedAttributes / immutableAttributes
|
|
327
|
+
|
|
328
|
+
`derivedAttributes` lists fields that are computed by `transformInput` and should never be written directly by the caller. They are stripped from put and update inputs:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
332
|
+
transformInput: (item) => ({
|
|
333
|
+
...item,
|
|
334
|
+
emailDomain: item.email ? item.email.split("@")[1] : undefined,
|
|
335
|
+
}),
|
|
336
|
+
derivedAttributes: ["emailDomain"],
|
|
337
|
+
}) {}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
`immutableAttributes` lists fields that may be set on create but must not be changed by updates:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
344
|
+
immutableAttributes: ["createdAt"],
|
|
345
|
+
}) {}
|
|
226
346
|
```
|
|
227
347
|
|
|
228
|
-
|
|
348
|
+
Both arrays are inferred as literal types — no `as const` needed.
|
|
229
349
|
|
|
230
350
|
## License
|
|
231
351
|
|
package/dist/index.cjs
CHANGED
|
@@ -620,8 +620,8 @@ var Db = class {
|
|
|
620
620
|
}
|
|
621
621
|
this.config = dbConfig;
|
|
622
622
|
}
|
|
623
|
-
|
|
624
|
-
return new Repo(this, table);
|
|
623
|
+
makeRepo(table, config) {
|
|
624
|
+
return new Repo(this, table, config);
|
|
625
625
|
}
|
|
626
626
|
async createTable(table) {
|
|
627
627
|
await this.client.send(new _aws_sdk_client_dynamodb.CreateTableCommand(require_util.extractTableDesc(table)));
|