dinah 0.11.0 → 0.12.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.
- package/README.md +172 -47
- package/dist/index.cjs +99 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +35 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +99 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,16 +99,34 @@ 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. Use `db.makeRepo` to create a repo inline, with or without configuration:
|
|
103
103
|
|
|
104
104
|
```typescript
|
|
105
|
-
const userRepo = db.
|
|
105
|
+
const userRepo = db.makeRepo(UserTable);
|
|
106
|
+
|
|
107
|
+
const userRepo = db.makeRepo(UserTable, {
|
|
108
|
+
defaultPutData: () => ({ createdAt: Date.now() }),
|
|
109
|
+
defaultUpdateData: () => ({ updatedAt: Date.now() }),
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
To create a named, reusable repo class, use the standalone `makeRepo` (see [Repository Configuration](#repository-configuration)):
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { makeRepo } from "dinah";
|
|
117
|
+
|
|
118
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
119
|
+
defaultPutData: () => ({ createdAt: Date.now() }),
|
|
120
|
+
defaultUpdateData: () => ({ updatedAt: Date.now() }),
|
|
121
|
+
}) {}
|
|
122
|
+
|
|
123
|
+
const userRepo = new UserRepo(db);
|
|
106
124
|
```
|
|
107
125
|
|
|
108
126
|
#### CRUD
|
|
109
127
|
|
|
110
128
|
```typescript
|
|
111
|
-
// Create (conditional put
|
|
129
|
+
// Create (conditional put — fails if item already exists)
|
|
112
130
|
const user = await userRepo.create({
|
|
113
131
|
userId: "u1",
|
|
114
132
|
email: "alice@example.com",
|
|
@@ -126,14 +144,47 @@ const partial = await userRepo.get({ userId: "u1" }, { projection: ["name", "ema
|
|
|
126
144
|
// getOrThrow (throws if item not found)
|
|
127
145
|
const aliceOrThrow = await userRepo.getOrThrow({ userId: "u1" });
|
|
128
146
|
|
|
129
|
-
//
|
|
147
|
+
// Put (upsert)
|
|
148
|
+
await userRepo.put({
|
|
149
|
+
userId: "u1",
|
|
150
|
+
email: "alice@example.com",
|
|
151
|
+
name: "Alice",
|
|
152
|
+
role: "admin",
|
|
153
|
+
createdAt: Date.now(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Update (throws if item does not exist)
|
|
130
157
|
const updated = await userRepo.update(
|
|
131
158
|
{ userId: "u1" },
|
|
132
159
|
{ name: "Alice Smith", updatedAt: Date.now() },
|
|
133
160
|
);
|
|
134
161
|
|
|
135
|
-
// Delete
|
|
136
|
-
await userRepo.delete({ userId: "u1" });
|
|
162
|
+
// Delete (returns old item or undefined)
|
|
163
|
+
const deleted = await userRepo.delete({ userId: "u1" });
|
|
164
|
+
|
|
165
|
+
// deleteOrThrow (throws if item not found)
|
|
166
|
+
const item = await userRepo.deleteOrThrow({ userId: "u1" });
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Update Expressions
|
|
170
|
+
|
|
171
|
+
The update argument supports MongoDB-style operators:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
await userRepo.update(
|
|
175
|
+
{ userId: "u1" },
|
|
176
|
+
{
|
|
177
|
+
name: "Alice", // set
|
|
178
|
+
age: undefined, // remove
|
|
179
|
+
score: { $plus: 10 }, // increment
|
|
180
|
+
score: { $minus: 5 }, // decrement
|
|
181
|
+
score: { $ifNotExists: 0 }, // set only if missing
|
|
182
|
+
tags: { $append: "vip" }, // list_append to end
|
|
183
|
+
tags: { $prepend: "featured" }, // list_append to front
|
|
184
|
+
followers: { $setAdd: "user-99" }, // ADD to DynamoDB set
|
|
185
|
+
followers: { $setDel: "user-99" }, // DELETE from DynamoDB set
|
|
186
|
+
},
|
|
187
|
+
);
|
|
137
188
|
```
|
|
138
189
|
|
|
139
190
|
#### Querying
|
|
@@ -142,7 +193,10 @@ Queries use a MongoDB-like syntax with operators like `$gt`, `$between`, `$prefi
|
|
|
142
193
|
|
|
143
194
|
```typescript
|
|
144
195
|
// Query by partition key
|
|
145
|
-
const
|
|
196
|
+
const posts = await postRepo.query({ authorId: "u1" });
|
|
197
|
+
|
|
198
|
+
// Query with sort key condition
|
|
199
|
+
const recent = await postRepo.query({ authorId: "u1" }, { postId: { $gte: "2024-" } });
|
|
146
200
|
|
|
147
201
|
// Query a GSI
|
|
148
202
|
const adminsByDate = await userRepo.queryGsi("byRole", {
|
|
@@ -150,82 +204,153 @@ const adminsByDate = await userRepo.queryGsi("byRole", {
|
|
|
150
204
|
createdAt: { $gt: 1700000000000 },
|
|
151
205
|
});
|
|
152
206
|
|
|
207
|
+
// Paginated query (async generator, one page at a time)
|
|
208
|
+
for await (const page of postRepo.queryPaged({ authorId: "u1" }, { limit: 20 })) {
|
|
209
|
+
console.log(page);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Paginated GSI query
|
|
213
|
+
for await (const page of userRepo.queryGsiPaged("byRole", { role: "admin" })) {
|
|
214
|
+
console.log(page);
|
|
215
|
+
}
|
|
216
|
+
|
|
153
217
|
// Scan with filters
|
|
154
218
|
const recentUsers = await userRepo.scan({
|
|
155
219
|
filter: { createdAt: { $gte: 1700000000000 } },
|
|
156
220
|
});
|
|
157
221
|
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
222
|
+
// Scan a GSI
|
|
223
|
+
const allByStatus = await postRepo.scanGsi("byStatus");
|
|
224
|
+
|
|
225
|
+
// Check existence (uses query or scan, no data returned)
|
|
226
|
+
const hasAdmins = await userRepo.existsGsi("byRole", { query: { role: "admin" } });
|
|
227
|
+
const exists = await postRepo.exists({ query: { authorId: "u1" } });
|
|
162
228
|
```
|
|
163
229
|
|
|
230
|
+
Filter operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$between`, `$in`, `$nin`, `$prefix`, `$includes`, `$exists`, `$size`, `$type`.
|
|
231
|
+
|
|
164
232
|
#### Batch Operations
|
|
165
233
|
|
|
166
234
|
```typescript
|
|
167
235
|
// Batch get
|
|
168
|
-
const { items, unprocessed } = await userRepo.batchGet([
|
|
169
|
-
{ userId: "u1" },
|
|
170
|
-
{ userId: "u2" },
|
|
171
|
-
{ userId: "u3" },
|
|
172
|
-
]);
|
|
236
|
+
const { items, unprocessed } = await userRepo.batchGet([{ userId: "u1" }, { userId: "u2" }]);
|
|
173
237
|
|
|
174
|
-
// Batch
|
|
238
|
+
// Batch get (throws on missing or unprocessed)
|
|
239
|
+
const items = await userRepo.batchGetOrThrow([{ userId: "u1" }, { userId: "u2" }]);
|
|
240
|
+
|
|
241
|
+
// Batch write (puts and deletes mixed)
|
|
175
242
|
await userRepo.batchWrite([
|
|
243
|
+
{
|
|
244
|
+
type: "PUT",
|
|
245
|
+
item: { userId: "u3", email: "c@d.com", name: "Carol", role: "user", createdAt: Date.now() },
|
|
246
|
+
},
|
|
247
|
+
{ type: "DELETE", key: { userId: "u2" } },
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
// Batch update (same update applied to multiple keys via PartiQL)
|
|
251
|
+
await userRepo.batchUpdate([{ userId: "u1" }, { userId: "u3" }], { role: "admin" });
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### Transactions
|
|
255
|
+
|
|
256
|
+
`trxWrite` accepts plain request objects and executes them as a single DynamoDB transaction:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
await userRepo.trxWrite(
|
|
176
260
|
{
|
|
177
261
|
type: "PUT",
|
|
178
262
|
item: {
|
|
179
|
-
userId: "
|
|
180
|
-
email: "
|
|
181
|
-
name: "
|
|
263
|
+
userId: "u5",
|
|
264
|
+
email: "eve@example.com",
|
|
265
|
+
name: "Eve",
|
|
182
266
|
role: "user",
|
|
183
267
|
createdAt: Date.now(),
|
|
184
268
|
},
|
|
185
269
|
},
|
|
186
|
-
{ type: "
|
|
187
|
-
|
|
270
|
+
{ type: "UPDATE", key: { userId: "u1" }, update: { role: "superadmin" } },
|
|
271
|
+
{ type: "DELETE", key: { userId: "u2" } },
|
|
272
|
+
{ type: "CONDITION", key: { userId: "u3" }, condition: { role: "admin" } },
|
|
273
|
+
);
|
|
188
274
|
```
|
|
189
275
|
|
|
190
|
-
|
|
276
|
+
Convenience methods operate on multiple keys/items atomically:
|
|
191
277
|
|
|
192
278
|
```typescript
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
279
|
+
// Transactional get
|
|
280
|
+
const [u1, u2] = await userRepo.trxGet([{ userId: "u1" }, { userId: "u2" }]);
|
|
281
|
+
const items = await userRepo.trxGetOrThrow([{ userId: "u1" }, { userId: "u2" }]);
|
|
282
|
+
|
|
283
|
+
// Transactional writes
|
|
284
|
+
await userRepo.trxPut([item1, item2]);
|
|
285
|
+
await userRepo.trxUpdate([{ userId: "u1" }, { userId: "u2" }], { role: "admin" });
|
|
286
|
+
await userRepo.trxDelete([{ userId: "u1" }, { userId: "u2" }]);
|
|
287
|
+
await userRepo.trxCreate([item1, item2]); // fails if any item already exists
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
To build cross-repo transactions, use the `*Request` methods to produce request objects and pass them to `db.trxWrite`:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
await db.trxWrite(
|
|
294
|
+
userRepo.trxPutRequest(userItem),
|
|
295
|
+
postRepo.trxDeleteRequest({ authorId: "u1", postId: "p1" }),
|
|
203
296
|
);
|
|
204
297
|
```
|
|
205
298
|
|
|
206
|
-
|
|
299
|
+
## Repository Configuration
|
|
207
300
|
|
|
208
|
-
|
|
301
|
+
`makeRepo` accepts a config object that controls defaults, transforms, and attribute rules. Extend the result to create a named repo class:
|
|
209
302
|
|
|
210
303
|
```typescript
|
|
211
|
-
|
|
304
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
305
|
+
defaultPutData: () => ({ createdAt: Date.now() }),
|
|
306
|
+
defaultUpdateData: () => ({ updatedAt: Date.now() }),
|
|
307
|
+
}) {}
|
|
308
|
+
```
|
|
212
309
|
|
|
213
|
-
|
|
214
|
-
readonly table = UserTable;
|
|
310
|
+
`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
311
|
|
|
216
|
-
|
|
217
|
-
return { createdAt: Date.now() };
|
|
218
|
-
}
|
|
312
|
+
### transformInput / transformOutput
|
|
219
313
|
|
|
220
|
-
|
|
221
|
-
return { updatedAt: Date.now() };
|
|
222
|
-
}
|
|
223
|
-
}
|
|
314
|
+
`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
315
|
|
|
225
|
-
|
|
316
|
+
```typescript
|
|
317
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
318
|
+
transformInput: (item) => ({
|
|
319
|
+
...item,
|
|
320
|
+
email: item.email?.toLowerCase(),
|
|
321
|
+
}),
|
|
322
|
+
transformOutput: (item): UserWithDisplayName => ({
|
|
323
|
+
...item,
|
|
324
|
+
displayName: `${item.name} <${item.email}>`,
|
|
325
|
+
}),
|
|
326
|
+
}) {}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Transforms are skipped when a `projection` option is provided, since only a subset of fields is available.
|
|
330
|
+
|
|
331
|
+
### derivedAttributes / immutableAttributes
|
|
332
|
+
|
|
333
|
+
`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:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
337
|
+
transformInput: (item) => ({
|
|
338
|
+
...item,
|
|
339
|
+
emailDomain: item.email ? item.email.split("@")[1] : undefined,
|
|
340
|
+
}),
|
|
341
|
+
derivedAttributes: ["emailDomain"],
|
|
342
|
+
}) {}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
`immutableAttributes` lists fields that may be set on create but must not be changed by updates:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
class UserRepo extends makeRepo(UserTable, {
|
|
349
|
+
immutableAttributes: ["createdAt"],
|
|
350
|
+
}) {}
|
|
226
351
|
```
|
|
227
352
|
|
|
228
|
-
|
|
353
|
+
Both arrays are inferred as literal types — no `as const` needed.
|
|
229
354
|
|
|
230
355
|
## License
|
|
231
356
|
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,25 @@ const require_util = require("./util-D88GS6Gt.cjs");
|
|
|
3
3
|
let _aws_sdk_client_dynamodb = require("@aws-sdk/client-dynamodb");
|
|
4
4
|
let _aws_sdk_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
|
|
5
5
|
_aws_sdk_lib_dynamodb = require_util.__toESM(_aws_sdk_lib_dynamodb, 1);
|
|
6
|
+
//#region src/error.ts
|
|
7
|
+
function dinahErrorMessage(details) {
|
|
8
|
+
switch (details.type) {
|
|
9
|
+
case "NOT_FOUND": return `${details.resource ?? "Item"} not found: ${JSON.stringify(details.key)}`;
|
|
10
|
+
case "CONDITIONAL_CHECK_FAILED": return details.key ? `${details.resource ?? "Item"} condition check failed: ${JSON.stringify(details.key)}` : `${details.resource ?? "Item"} condition check failed`;
|
|
11
|
+
case "VALIDATION": return `Validation error: ${details.message}`;
|
|
12
|
+
case "TRANSACTION_CANCELED": return `Transaction canceled: ${details.reasons.map((r) => r.type).join(", ")}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var DinahError = class extends Error {
|
|
16
|
+
details;
|
|
17
|
+
constructor(details, options) {
|
|
18
|
+
super(dinahErrorMessage(details), options);
|
|
19
|
+
this.name = "DinahError";
|
|
20
|
+
this.details = details;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const isDinahError = (err) => err instanceof DinahError;
|
|
24
|
+
//#endregion
|
|
6
25
|
//#region src/expression-builder.ts
|
|
7
26
|
const attrTypes = [
|
|
8
27
|
"S",
|
|
@@ -225,6 +244,13 @@ var Repo = class {
|
|
|
225
244
|
get tableName() {
|
|
226
245
|
return `${this.db.config?.tableNamePrefix ?? ""}${this.table.def.name}`;
|
|
227
246
|
}
|
|
247
|
+
get resourceName() {
|
|
248
|
+
if (this.config.resourceName) return this.config.resourceName;
|
|
249
|
+
const clsName = this.constructor.name;
|
|
250
|
+
if (clsName && clsName !== "Repo") return clsName.replace(/Repo$/i, "") || clsName;
|
|
251
|
+
const t = this.table.def.name;
|
|
252
|
+
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
253
|
+
}
|
|
228
254
|
get defaultPutData() {
|
|
229
255
|
return this.config.defaultPutData?.() ?? {};
|
|
230
256
|
}
|
|
@@ -257,6 +283,7 @@ var Repo = class {
|
|
|
257
283
|
const item = await this.db.getOrThrow({
|
|
258
284
|
table: this.tableName,
|
|
259
285
|
key: this.extractKey(key),
|
|
286
|
+
resource: this.resourceName,
|
|
260
287
|
...options
|
|
261
288
|
});
|
|
262
289
|
return this.applyTransformIfNeeded(item, options);
|
|
@@ -266,6 +293,7 @@ var Repo = class {
|
|
|
266
293
|
const result = await this.db.put({
|
|
267
294
|
table: this.tableName,
|
|
268
295
|
item: itemWithDefaults,
|
|
296
|
+
resource: this.resourceName,
|
|
269
297
|
...options
|
|
270
298
|
});
|
|
271
299
|
return this.applyTransformIfNeeded(result);
|
|
@@ -278,6 +306,7 @@ var Repo = class {
|
|
|
278
306
|
const result = await this.db.update({
|
|
279
307
|
table: this.tableName,
|
|
280
308
|
key: this.extractKey(key),
|
|
309
|
+
resource: this.resourceName,
|
|
281
310
|
update: updateWithDefaults,
|
|
282
311
|
...options
|
|
283
312
|
});
|
|
@@ -296,6 +325,7 @@ var Repo = class {
|
|
|
296
325
|
const item = await this.db.delete({
|
|
297
326
|
table: this.tableName,
|
|
298
327
|
key: this.extractKey(key),
|
|
328
|
+
resource: this.resourceName,
|
|
299
329
|
...options
|
|
300
330
|
});
|
|
301
331
|
return item && this.applyTransformIfNeeded(item);
|
|
@@ -304,6 +334,7 @@ var Repo = class {
|
|
|
304
334
|
const item = await this.db.deleteOrThrow({
|
|
305
335
|
table: this.tableName,
|
|
306
336
|
key: this.extractKey(key),
|
|
337
|
+
resource: this.resourceName,
|
|
307
338
|
...options
|
|
308
339
|
});
|
|
309
340
|
return this.applyTransformIfNeeded(item);
|
|
@@ -409,6 +440,7 @@ var Repo = class {
|
|
|
409
440
|
async batchGetOrThrow(keys, options) {
|
|
410
441
|
const result = await this.db.batchGetOrThrow({ [this.tableName]: {
|
|
411
442
|
keys: keys.map((key) => this.extractKey(key)),
|
|
443
|
+
resource: this.resourceName,
|
|
412
444
|
...options
|
|
413
445
|
} });
|
|
414
446
|
return this.applyTransformsIfNeeded(result[this.tableName] ?? [], options);
|
|
@@ -450,6 +482,7 @@ var Repo = class {
|
|
|
450
482
|
const items = await this.db.trxGetOrThrow(...keys.map((key) => ({
|
|
451
483
|
table: this.tableName,
|
|
452
484
|
key: this.extractKey(key),
|
|
485
|
+
resource: this.resourceName,
|
|
453
486
|
...options
|
|
454
487
|
})));
|
|
455
488
|
return this.applyTransformsIfNeeded(items, options);
|
|
@@ -620,8 +653,8 @@ var Db = class {
|
|
|
620
653
|
}
|
|
621
654
|
this.config = dbConfig;
|
|
622
655
|
}
|
|
623
|
-
|
|
624
|
-
return new Repo(this, table);
|
|
656
|
+
makeRepo(table, config) {
|
|
657
|
+
return new Repo(this, table, config);
|
|
625
658
|
}
|
|
626
659
|
async createTable(table) {
|
|
627
660
|
await this.client.send(new _aws_sdk_client_dynamodb.CreateTableCommand(require_util.extractTableDesc(table)));
|
|
@@ -657,7 +690,11 @@ var Db = class {
|
|
|
657
690
|
}
|
|
658
691
|
async getOrThrow(data) {
|
|
659
692
|
const item = await this.get(data);
|
|
660
|
-
if (!item) throw new
|
|
693
|
+
if (!item) throw new DinahError({
|
|
694
|
+
type: "NOT_FOUND",
|
|
695
|
+
key: data.key,
|
|
696
|
+
resource: data.resource
|
|
697
|
+
});
|
|
661
698
|
return item;
|
|
662
699
|
}
|
|
663
700
|
async put(data) {
|
|
@@ -672,8 +709,16 @@ var Db = class {
|
|
|
672
709
|
ExpressionAttributeNames: exp.attributeNames,
|
|
673
710
|
ExpressionAttributeValues: exp.attributeValues
|
|
674
711
|
});
|
|
675
|
-
|
|
676
|
-
|
|
712
|
+
try {
|
|
713
|
+
const output = await this.client.send(input);
|
|
714
|
+
return data.returnOld ? output.Attributes : item;
|
|
715
|
+
} catch (err) {
|
|
716
|
+
if (err instanceof _aws_sdk_client_dynamodb.ConditionalCheckFailedException) throw new DinahError({
|
|
717
|
+
type: "CONDITIONAL_CHECK_FAILED",
|
|
718
|
+
resource: data.resource
|
|
719
|
+
}, { cause: err });
|
|
720
|
+
throw err;
|
|
721
|
+
}
|
|
677
722
|
}
|
|
678
723
|
async update(data) {
|
|
679
724
|
const exp = new ExpressionBuilder();
|
|
@@ -689,7 +734,16 @@ var Db = class {
|
|
|
689
734
|
ExpressionAttributeNames: exp.attributeNames,
|
|
690
735
|
ExpressionAttributeValues: exp.attributeValues
|
|
691
736
|
});
|
|
692
|
-
|
|
737
|
+
try {
|
|
738
|
+
return (await this.client.send(input)).Attributes;
|
|
739
|
+
} catch (err) {
|
|
740
|
+
if (err instanceof _aws_sdk_client_dynamodb.ConditionalCheckFailedException) throw new DinahError({
|
|
741
|
+
type: "CONDITIONAL_CHECK_FAILED",
|
|
742
|
+
key: data.key,
|
|
743
|
+
resource: data.resource
|
|
744
|
+
}, { cause: err });
|
|
745
|
+
throw err;
|
|
746
|
+
}
|
|
693
747
|
}
|
|
694
748
|
async delete(data) {
|
|
695
749
|
const exp = new ExpressionBuilder();
|
|
@@ -702,11 +756,24 @@ var Db = class {
|
|
|
702
756
|
ExpressionAttributeNames: exp.attributeNames,
|
|
703
757
|
ExpressionAttributeValues: exp.attributeValues
|
|
704
758
|
});
|
|
705
|
-
|
|
759
|
+
try {
|
|
760
|
+
return (await this.client.send(input)).Attributes;
|
|
761
|
+
} catch (err) {
|
|
762
|
+
if (err instanceof _aws_sdk_client_dynamodb.ConditionalCheckFailedException) throw new DinahError({
|
|
763
|
+
type: "CONDITIONAL_CHECK_FAILED",
|
|
764
|
+
key: data.key,
|
|
765
|
+
resource: data.resource
|
|
766
|
+
}, { cause: err });
|
|
767
|
+
throw err;
|
|
768
|
+
}
|
|
706
769
|
}
|
|
707
770
|
async deleteOrThrow(data) {
|
|
708
771
|
const item = await this.delete(data);
|
|
709
|
-
if (!item) throw new
|
|
772
|
+
if (!item) throw new DinahError({
|
|
773
|
+
type: "NOT_FOUND",
|
|
774
|
+
key: data.key,
|
|
775
|
+
resource: data.resource
|
|
776
|
+
});
|
|
710
777
|
return item;
|
|
711
778
|
}
|
|
712
779
|
async *queryPaged(data) {
|
|
@@ -863,7 +930,11 @@ var Db = class {
|
|
|
863
930
|
const { items, unprocessed } = await this.batchGet(data);
|
|
864
931
|
for (const table of Object.keys(data)) {
|
|
865
932
|
if (unprocessed?.[table]) throw new Error(`One or more batch get requests were not processed in "${table}" table.`);
|
|
866
|
-
if (items[table]?.length !== data[table]?.keys?.length) throw new
|
|
933
|
+
if (items[table]?.length !== data[table]?.keys?.length) throw new DinahError({
|
|
934
|
+
type: "NOT_FOUND",
|
|
935
|
+
key: {},
|
|
936
|
+
resource: data[table]?.resource
|
|
937
|
+
});
|
|
867
938
|
}
|
|
868
939
|
return items;
|
|
869
940
|
}
|
|
@@ -983,7 +1054,11 @@ var Db = class {
|
|
|
983
1054
|
}
|
|
984
1055
|
async trxGetOrThrow(...requests) {
|
|
985
1056
|
const items = await this.trxGet(...requests);
|
|
986
|
-
for (let i = 0; i < items.length; i++) if (!items[i]) throw new
|
|
1057
|
+
for (let i = 0; i < items.length; i++) if (!items[i]) throw new DinahError({
|
|
1058
|
+
type: "NOT_FOUND",
|
|
1059
|
+
key: requests[i]?.key ?? {},
|
|
1060
|
+
resource: requests[i]?.resource
|
|
1061
|
+
});
|
|
987
1062
|
return items;
|
|
988
1063
|
}
|
|
989
1064
|
async trxWrite(...requests) {
|
|
@@ -1013,7 +1088,18 @@ var Db = class {
|
|
|
1013
1088
|
} };
|
|
1014
1089
|
});
|
|
1015
1090
|
const input = new _aws_sdk_lib_dynamodb.TransactWriteCommand({ TransactItems: trxItems });
|
|
1016
|
-
|
|
1091
|
+
try {
|
|
1092
|
+
await this.client.send(input);
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
if (err instanceof _aws_sdk_client_dynamodb.TransactionCanceledException) throw new DinahError({
|
|
1095
|
+
type: "TRANSACTION_CANCELED",
|
|
1096
|
+
reasons: (err.CancellationReasons ?? []).map((r) => ({
|
|
1097
|
+
type: r.Code ?? "Unknown",
|
|
1098
|
+
message: r.Message
|
|
1099
|
+
}))
|
|
1100
|
+
}, { cause: err });
|
|
1101
|
+
throw err;
|
|
1102
|
+
}
|
|
1017
1103
|
}
|
|
1018
1104
|
};
|
|
1019
1105
|
//#endregion
|
|
@@ -1028,10 +1114,12 @@ var Table = class {
|
|
|
1028
1114
|
};
|
|
1029
1115
|
//#endregion
|
|
1030
1116
|
exports.Db = Db;
|
|
1117
|
+
exports.DinahError = DinahError;
|
|
1031
1118
|
exports.Repo = Repo;
|
|
1032
1119
|
exports.Table = Table;
|
|
1033
1120
|
exports.chunk = require_util.chunk;
|
|
1034
1121
|
exports.extractTableDesc = require_util.extractTableDesc;
|
|
1122
|
+
exports.isDinahError = isDinahError;
|
|
1035
1123
|
exports.makeRepo = makeRepo;
|
|
1036
1124
|
exports.removeUndefined = require_util.removeUndefined;
|
|
1037
1125
|
exports.resolveAttrType = require_util.resolveAttrType;
|