dinah 0.1.3 → 0.3.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/dist/index.mjs ADDED
@@ -0,0 +1,914 @@
1
+ import { i as resolveAttrType, n as extractTableDesc, r as removeUndefined, t as chunk } from "./util-DDOj_NRD.mjs";
2
+ import { CreateTableCommand, DeleteTableCommand, DynamoDB, DynamoDBClient, ListTablesCommand } from "@aws-sdk/client-dynamodb";
3
+ import * as Lib from "@aws-sdk/lib-dynamodb";
4
+ import sift from "sift";
5
+ //#region src/expression-builder.ts
6
+ const attrTypes = [
7
+ "S",
8
+ "SS",
9
+ "N",
10
+ "NS",
11
+ "B",
12
+ "BS",
13
+ "BOOL",
14
+ "NULL",
15
+ "L",
16
+ "M"
17
+ ];
18
+ const comparatorOps = {
19
+ $eq: "=",
20
+ $ne: "<>",
21
+ $gt: ">",
22
+ $gte: ">=",
23
+ $lt: "<",
24
+ $lte: "<="
25
+ };
26
+ const isOperation = (val) => {
27
+ if (!val || typeof val !== "object" || Array.isArray(val)) return false;
28
+ return Object.keys(val).every((op) => op.startsWith("$"));
29
+ };
30
+ var ExpressionBuilder = class {
31
+ attrNames = /* @__PURE__ */ new Map();
32
+ attrValues = /* @__PURE__ */ new Map();
33
+ get attributeNames() {
34
+ return this.attrNames.size ? Object.fromEntries([...this.attrNames.entries()].map(([name, placeholder]) => [placeholder, name])) : void 0;
35
+ }
36
+ get attributeValues() {
37
+ return this.attrValues.size ? Object.fromEntries([...this.attrValues.entries()].map(([value, placeholder]) => [placeholder, value])) : void 0;
38
+ }
39
+ reset() {
40
+ this.attrNames.clear();
41
+ this.attrValues.clear();
42
+ }
43
+ getPathSub(path) {
44
+ const segments = path.split(".");
45
+ const placeholders = [];
46
+ for (const segment of segments) {
47
+ let placeholder = this.attrNames.get(segment);
48
+ if (!placeholder) {
49
+ placeholder = `#${this.attrNames.size}`;
50
+ this.attrNames.set(segment, placeholder);
51
+ }
52
+ placeholders.push(placeholder);
53
+ }
54
+ return placeholders.join(".");
55
+ }
56
+ getValueSub(value) {
57
+ let placeholder = this.attrValues.get(value);
58
+ if (!placeholder) {
59
+ placeholder = `:${this.attrValues.size}`;
60
+ this.attrValues.set(value, placeholder);
61
+ }
62
+ return placeholder;
63
+ }
64
+ getValueOrPathSub(pathOrValue) {
65
+ if (isOperation(pathOrValue)) {
66
+ if (pathOrValue.$path) return this.getPathSub(pathOrValue.$path);
67
+ throw new Error("Expected $path or operand.");
68
+ }
69
+ return this.getValueSub(pathOrValue);
70
+ }
71
+ projection(paths) {
72
+ return paths?.map((path) => this.getPathSub(path)).join(", ");
73
+ }
74
+ condition(expression) {
75
+ if (!expression) return void 0;
76
+ const expressions = [];
77
+ for (const [key, val] of Object.entries(expression)) if (key === "$and") {
78
+ if (!Array.isArray(val)) throw new Error("$and expects an array operand.");
79
+ expressions.push(`(${val.map((v) => this.condition(v)).join(" AND ")})`);
80
+ } else if (key === "$or" && Array.isArray(val)) {
81
+ if (!Array.isArray(val)) throw new Error("$or expects an array operand.");
82
+ expressions.push(`(${val.map((v) => this.condition(v)).join(" OR ")})`);
83
+ } else if (key === "$not") {
84
+ if (!val || typeof val !== "object") throw new Error("$not expects an object operand.");
85
+ expressions.push(`NOT ${this.condition(val)}`);
86
+ } else if (!key.startsWith("$")) expressions.push(this.resolveCondition(key, val));
87
+ else throw new Error(`Unexpected operator "${key}". Expected attribute path or compound operator.`);
88
+ return expressions.join(" AND ");
89
+ }
90
+ update(expression) {
91
+ if (expression === void 0) return void 0;
92
+ const setOperations = [];
93
+ const removeOperations = [];
94
+ const setAddOperations = [];
95
+ const setDelOperations = [];
96
+ for (const [path, valOrOperation] of Object.entries(expression)) {
97
+ const placeholder = this.getPathSub(path);
98
+ if (valOrOperation === void 0) removeOperations.push(placeholder);
99
+ else if (isOperation(valOrOperation)) if (valOrOperation.$remove === true) removeOperations.push(placeholder);
100
+ else if (valOrOperation.$setAdd !== void 0 || valOrOperation.$setDel !== void 0) {
101
+ const operations = valOrOperation.$setAdd ? setAddOperations : setDelOperations;
102
+ const operand = valOrOperation.$setAdd ?? valOrOperation.$setDel;
103
+ operations.push(`${placeholder} ${this.getValueSub(operand instanceof Set ? operand : new Set([operand].flat()))}`);
104
+ } else setOperations.push(`${placeholder} = ${this.resolveSetOperand(path, valOrOperation)}`);
105
+ else setOperations.push(`${placeholder} = ${this.getValueSub(valOrOperation)}`);
106
+ }
107
+ let updateExpression = "";
108
+ if (setOperations.length) updateExpression += `SET ${setOperations.join(", ")} `;
109
+ if (removeOperations.length) updateExpression += `REMOVE ${removeOperations.join(", ")} `;
110
+ if (setAddOperations.length) updateExpression += `ADD ${setAddOperations.join(", ")} `;
111
+ if (setDelOperations.length) updateExpression += `DELETE ${setDelOperations.join(", ")} `;
112
+ return updateExpression;
113
+ }
114
+ resolveCondition(path, exp) {
115
+ if (!exp || typeof exp !== "object" || !Object.keys(exp).at(0)?.startsWith("$")) return this.resolveCondition(path, { $eq: exp });
116
+ const placeholder = this.getPathSub(path);
117
+ const [operator, operand] = Object.entries(exp).at(0) ?? [""];
118
+ if (comparatorOps[operator]) {
119
+ if (isOperation(operand) && operand.$path) return `${placeholder} ${comparatorOps[operator]} ${this.getPathSub(operand.$path)}`;
120
+ if (![
121
+ "string",
122
+ "boolean",
123
+ "number"
124
+ ].includes(typeof operand)) throw new Error(`${operator} expects a primitive/scalar operand.`);
125
+ return `${placeholder} ${comparatorOps[operator]} ${this.getValueSub(operand)}`;
126
+ }
127
+ switch (operator) {
128
+ case "$in":
129
+ if (!Array.isArray(operand)) throw new Error("$in operator expects an array operand.");
130
+ return `${placeholder} IN (${operand.map((op) => this.getValueSub(op)).join(", ")})`;
131
+ case "$nin":
132
+ if (!Array.isArray(operand)) throw new Error("$nin operator expects an array operand.");
133
+ return `${placeholder} NOT IN (${operand.map((op) => this.getValueSub(op)).join(", ")})`;
134
+ case "$prefix":
135
+ if (typeof operand !== "string") throw new Error("$prefix operator expects a string operand.");
136
+ return `begins_with(${placeholder}, ${this.getValueSub(operand)})`;
137
+ case "$includes": return `contains(${placeholder}, ${this.getValueSub(operand)})`;
138
+ case "$between":
139
+ if (!Array.isArray(operand) || operand.length !== 2) throw new Error("$between operator expects an array operand of exactly two values.");
140
+ return `${placeholder} BETWEEN ${this.getValueSub(operand[0])} AND ${this.getValueSub(operand[1])}`;
141
+ case "$exists":
142
+ if (typeof operand !== "boolean") throw new Error("$exists operator expects a boolean operand.");
143
+ return operand ? `attribute_exists(${placeholder})` : `attribute_not_exists(${placeholder})`;
144
+ case "$type":
145
+ if (!attrTypes.includes(operand)) throw new Error(`$type operator expects operand to be one of ${attrTypes.join(",")}`);
146
+ return `attribute_type(${placeholder})`;
147
+ case "$size": {
148
+ const sizeExp = typeof operand === "number" ? { $eq: operand } : operand;
149
+ if (!sizeExp || typeof sizeExp !== "object" || !Object.keys(sizeExp).at(0)?.startsWith("$")) throw new Error("$size operator expects a number operand or a comparator object.");
150
+ const [sizeOperator, sizeOperand] = Object.entries(sizeExp).at(0) ?? [""];
151
+ if (!comparatorOps[sizeOperator]) throw new Error("$size operator expects a number operand or a comparator object.");
152
+ if (![
153
+ "string",
154
+ "boolean",
155
+ "number"
156
+ ].includes(typeof sizeOperand)) throw new Error(`${operator} expects a primitive/scalar operand.`);
157
+ return `size(${placeholder}) ${comparatorOps[sizeOperator]} ${this.getValueSub(sizeOperand)}`;
158
+ }
159
+ default: throw new Error(`Invalid operator "${operator}"`);
160
+ }
161
+ }
162
+ resolveSetOperand(path, operand) {
163
+ if (isOperation(operand)) {
164
+ if (operand.$ifNotExists) {
165
+ const params = Array.isArray(operand.$ifNotExists) ? operand.$ifNotExists : [path, operand.$ifNotExists];
166
+ return `if_not_exists(${this.getPathSub(params[0])}, ${this.resolveSetOperand(path, params[1])})`;
167
+ }
168
+ if (operand.$set) return this.resolveSetOperand(path, operand.$set);
169
+ if (operand.$plus !== void 0 || operand.$minus !== void 0) {
170
+ const mathOperator = operand.$plus ? "+" : "-";
171
+ const mathOperand = operand.$plus ?? operand.$minus;
172
+ return `${(Array.isArray(mathOperand) ? mathOperand : [{ $path: path }, mathOperand]).map((param) => this.resolveSetOperand(path, param)).join(` ${mathOperator} `)}`;
173
+ }
174
+ if (operand.$append !== void 0 || operand.$prepend !== void 0) {
175
+ const listOperand = operand.$append ?? operand.$prepend;
176
+ const resolvedListOperand = isOperation(listOperand) ? listOperand : [listOperand].flat();
177
+ return `list_append(${(operand.$append ? [{ $path: path }, resolvedListOperand] : [resolvedListOperand, { $path: path }]).map((param) => this.resolveSetOperand(path, param)).join(", ")})`;
178
+ }
179
+ }
180
+ return this.getValueOrPathSub(operand);
181
+ }
182
+ };
183
+ //#endregion
184
+ //#region src/repo.ts
185
+ var AbstractRepo = class {
186
+ db;
187
+ constructor(db) {
188
+ this.db = db;
189
+ }
190
+ get tableName() {
191
+ return `${this.db.config?.tableNamePrefix ?? ""}${this.table.def.name}`;
192
+ }
193
+ get defaultPutData() {
194
+ return {};
195
+ }
196
+ get defaultUpdateData() {
197
+ return {};
198
+ }
199
+ transformItem(item) {
200
+ return item;
201
+ }
202
+ extractKey(item) {
203
+ const { partitionKey, sortKey } = this.table.def;
204
+ if (sortKey) return {
205
+ [partitionKey]: item[partitionKey],
206
+ [sortKey]: item[sortKey]
207
+ };
208
+ return { [partitionKey]: item[partitionKey] };
209
+ }
210
+ async get(key, options) {
211
+ const item = await this.db.get({
212
+ table: this.tableName,
213
+ key: this.extractKey(key),
214
+ ...options
215
+ });
216
+ return item && this.applyTransformIfNeeded(item, options);
217
+ }
218
+ async getOrThrow(key, options) {
219
+ const item = await this.db.getOrThrow({
220
+ table: this.tableName,
221
+ key: this.extractKey(key),
222
+ ...options
223
+ });
224
+ return this.applyTransformIfNeeded(item, options);
225
+ }
226
+ async put(item, options) {
227
+ const itemWithDefaults = {
228
+ ...this.defaultPutData,
229
+ ...item
230
+ };
231
+ const result = await this.db.put({
232
+ table: this.tableName,
233
+ item: itemWithDefaults,
234
+ ...options
235
+ });
236
+ return this.applyTransformIfNeeded(result);
237
+ }
238
+ async update(key, update, options) {
239
+ const updateWithDefaults = {
240
+ ...this.defaultUpdateData,
241
+ ...update
242
+ };
243
+ const result = await this.db.update({
244
+ table: this.tableName,
245
+ key: this.extractKey(key),
246
+ update: updateWithDefaults,
247
+ ...options
248
+ });
249
+ return this.applyTransformIfNeeded(result);
250
+ }
251
+ async create(item, options) {
252
+ const { condition: otherCondition, ...otherOptions } = options ?? {};
253
+ const condition = { $and: [{ [this.table.def.partitionKey]: { $exists: false } }] };
254
+ if (otherCondition) condition.$and.push(otherCondition);
255
+ return this.put(item, {
256
+ condition,
257
+ ...otherOptions
258
+ });
259
+ }
260
+ async delete(key, options) {
261
+ const item = await this.db.delete({
262
+ table: this.tableName,
263
+ key: this.extractKey(key),
264
+ ...options
265
+ });
266
+ return item && this.applyTransformIfNeeded(item);
267
+ }
268
+ async deleteOrThrow(key, options) {
269
+ const item = await this.db.deleteOrThrow({
270
+ table: this.tableName,
271
+ key: this.extractKey(key),
272
+ ...options
273
+ });
274
+ return this.applyTransformIfNeeded(item);
275
+ }
276
+ async query(query, options) {
277
+ const items = await this.db.query({
278
+ table: this.tableName,
279
+ query,
280
+ ...options
281
+ });
282
+ return this.applyTransformsIfNeeded(items, options);
283
+ }
284
+ async *queryPaged(query, options) {
285
+ for await (const page of this.db.queryPaged({
286
+ table: this.tableName,
287
+ query,
288
+ ...options
289
+ })) yield this.applyTransformsIfNeeded(page, options);
290
+ }
291
+ async queryGsi(gsi, query, options) {
292
+ const items = await this.db.query({
293
+ table: this.tableName,
294
+ index: gsi,
295
+ query,
296
+ ...options
297
+ });
298
+ return this.applyTransformsIfNeeded(items, {
299
+ ...options,
300
+ gsi
301
+ });
302
+ }
303
+ async *queryGsiPaged(gsi, query, options) {
304
+ for await (const page of this.db.queryPaged({
305
+ table: this.tableName,
306
+ index: gsi,
307
+ query,
308
+ ...options
309
+ })) yield this.applyTransformsIfNeeded(page, {
310
+ ...options,
311
+ gsi
312
+ });
313
+ }
314
+ async scan(options) {
315
+ const items = await this.db.scan({
316
+ table: this.tableName,
317
+ ...options
318
+ });
319
+ return this.applyTransformsIfNeeded(items, options);
320
+ }
321
+ async *scanPaged(options) {
322
+ for await (const page of this.db.scanPaged({
323
+ table: this.tableName,
324
+ ...options
325
+ })) yield this.applyTransformsIfNeeded(page, options);
326
+ }
327
+ async scanGsi(gsi, options) {
328
+ const items = await this.db.scan({
329
+ table: this.tableName,
330
+ index: gsi,
331
+ ...options
332
+ });
333
+ return this.applyTransformsIfNeeded(items, {
334
+ ...options,
335
+ gsi
336
+ });
337
+ }
338
+ async *scanGsiPaged(gsi, options) {
339
+ for await (const page of this.db.scanPaged({
340
+ table: this.tableName,
341
+ index: gsi,
342
+ ...options
343
+ })) yield this.applyTransformsIfNeeded(page, {
344
+ ...options,
345
+ gsi
346
+ });
347
+ }
348
+ async exists(options) {
349
+ return this.db.exists({
350
+ table: this.tableName,
351
+ projection: [this.table.def.partitionKey],
352
+ ...options
353
+ });
354
+ }
355
+ async existsGsi(gsi, options) {
356
+ return this.db.exists({
357
+ table: this.tableName,
358
+ index: gsi,
359
+ projection: [this.table.def.partitionKey],
360
+ ...options
361
+ });
362
+ }
363
+ async batchGet(keys, options) {
364
+ const { items, unprocessed } = await this.db.batchGet({ [this.tableName]: {
365
+ keys: keys.map((key) => this.extractKey(key)),
366
+ ...options
367
+ } });
368
+ const tableItems = items[this.tableName];
369
+ return {
370
+ items: tableItems && this.applyTransformsIfNeeded(tableItems, options),
371
+ unprocessed: unprocessed?.[this.tableName]?.keys
372
+ };
373
+ }
374
+ async batchGetOrThrow(keys, options) {
375
+ const result = await this.db.batchGetOrThrow({ [this.tableName]: {
376
+ keys: keys.map((key) => this.extractKey(key)),
377
+ ...options
378
+ } });
379
+ return this.applyTransformsIfNeeded(result[this.tableName] ?? [], options);
380
+ }
381
+ async batchWrite(requests) {
382
+ const { items, unprocessed } = await this.db.batchWrite({ [this.tableName]: requests.map((request) => {
383
+ if (request.type === "DELETE") return {
384
+ type: "DELETE",
385
+ key: this.extractKey(request.key)
386
+ };
387
+ else return {
388
+ type: "PUT",
389
+ item: {
390
+ ...this.defaultPutData,
391
+ ...request.item
392
+ }
393
+ };
394
+ }) });
395
+ return {
396
+ items: items[this.tableName],
397
+ unprocessed: unprocessed?.[this.tableName]
398
+ };
399
+ }
400
+ async trxGet(keys, options) {
401
+ return (await this.db.trxGet(...keys.map((key) => ({
402
+ table: this.tableName,
403
+ key: this.extractKey(key),
404
+ ...options
405
+ })))).map((item) => item && this.applyTransformIfNeeded(item, options));
406
+ }
407
+ async trxGetOrThrow(keys, options) {
408
+ const items = await this.db.trxGetOrThrow(...keys.map((key) => ({
409
+ table: this.tableName,
410
+ key: this.extractKey(key),
411
+ ...options
412
+ })));
413
+ return this.applyTransformsIfNeeded(items, options);
414
+ }
415
+ async trxWrite(...requests) {
416
+ await this.db.trxWrite(...requests.map((request) => {
417
+ switch (request.type) {
418
+ case "CONDITION": {
419
+ const { key, condition, ...options } = request;
420
+ return this.trxConditionRequest(key, condition, options);
421
+ }
422
+ case "DELETE": {
423
+ const { key, ...options } = request;
424
+ return this.trxDeleteRequest(key, options);
425
+ }
426
+ case "PUT": {
427
+ const { item, ...options } = request;
428
+ return this.trxPutRequest(item, options);
429
+ }
430
+ case "UPDATE": {
431
+ const { key, update, ...options } = request;
432
+ return this.trxUpdateRequest(key, update, options);
433
+ }
434
+ default: throw new Error("Unexpected request type.");
435
+ }
436
+ }));
437
+ }
438
+ async trxDelete(keys, options) {
439
+ return this.db.trxWrite(...keys.map((key) => this.trxDeleteRequest(key, options)));
440
+ }
441
+ async trxPut(items, options) {
442
+ return this.db.trxWrite(...items.map((item) => this.trxPutRequest(item, options)));
443
+ }
444
+ async trxUpdate(keys, update, options) {
445
+ return this.db.trxWrite(...keys.map((key) => this.trxUpdateRequest(key, update, options)));
446
+ }
447
+ async trxCreate(items, options) {
448
+ return this.db.trxWrite(...items.map((item) => this.trxCreateRequest(item, options)));
449
+ }
450
+ trxGetRequest(key, options) {
451
+ return {
452
+ table: this.tableName,
453
+ key: this.extractKey(key),
454
+ ...options
455
+ };
456
+ }
457
+ trxDeleteRequest(key, options) {
458
+ return {
459
+ table: this.tableName,
460
+ type: "DELETE",
461
+ key: this.extractKey(key),
462
+ ...options
463
+ };
464
+ }
465
+ trxConditionRequest(key, condition, options) {
466
+ return {
467
+ table: this.tableName,
468
+ type: "CONDITION",
469
+ key: this.extractKey(key),
470
+ condition,
471
+ ...options
472
+ };
473
+ }
474
+ trxPutRequest(item, options) {
475
+ const itemWithDefaults = {
476
+ ...this.defaultPutData,
477
+ ...item
478
+ };
479
+ return {
480
+ table: this.tableName,
481
+ type: "PUT",
482
+ item: itemWithDefaults,
483
+ ...options
484
+ };
485
+ }
486
+ trxUpdateRequest(key, update, options) {
487
+ const updateWithDefaults = {
488
+ ...this.defaultUpdateData,
489
+ ...update
490
+ };
491
+ return {
492
+ table: this.tableName,
493
+ type: "UPDATE",
494
+ key: this.extractKey(key),
495
+ update: updateWithDefaults,
496
+ ...options
497
+ };
498
+ }
499
+ trxCreateRequest(item, options) {
500
+ const { condition: otherCondition, ...otherOptions } = options ?? {};
501
+ const condition = { $and: [{ [this.table.def.partitionKey]: { $exists: false } }] };
502
+ if (otherCondition) condition.$and.push(otherCondition);
503
+ const itemWithDefaults = {
504
+ ...this.defaultPutData,
505
+ ...item
506
+ };
507
+ return {
508
+ table: this.tableName,
509
+ type: "PUT",
510
+ item: itemWithDefaults,
511
+ condition,
512
+ ...otherOptions
513
+ };
514
+ }
515
+ applyTransformsIfNeeded(items, options) {
516
+ if (options?.projection?.length) return items;
517
+ if (options?.gsi) {
518
+ const gsiProj = this.table.def.gsis?.[options.gsi]?.projection;
519
+ if (gsiProj === "KEYS_ONLY" || Array.isArray(gsiProj)) return items;
520
+ }
521
+ return items.map((item) => this.transformItem(item));
522
+ }
523
+ applyTransformIfNeeded(item, options) {
524
+ const [transformedItem] = this.applyTransformsIfNeeded([item], options);
525
+ return transformedItem;
526
+ }
527
+ };
528
+ var Repo = class extends AbstractRepo {
529
+ table;
530
+ constructor(db, table) {
531
+ super(db);
532
+ this.table = table;
533
+ }
534
+ };
535
+ //#endregion
536
+ //#region src/db.ts
537
+ var Db = class {
538
+ client;
539
+ config;
540
+ constructor(clientOrConfig, dbConfig) {
541
+ if (clientOrConfig instanceof DynamoDBClient || clientOrConfig instanceof DynamoDB) this.client = Lib.DynamoDBDocumentClient.from(new DynamoDBClient(clientOrConfig));
542
+ else {
543
+ const { endpoint, ...clientConfig } = clientOrConfig;
544
+ this.client = Lib.DynamoDBDocumentClient.from(new DynamoDBClient({
545
+ endpoint: endpoint || void 0,
546
+ ...clientConfig
547
+ }));
548
+ }
549
+ this.config = dbConfig;
550
+ }
551
+ createRepo(table) {
552
+ return new Repo(this, table);
553
+ }
554
+ async createTable(table) {
555
+ await this.client.send(new CreateTableCommand(extractTableDesc(table)));
556
+ }
557
+ async deleteTable(tableName) {
558
+ await this.client.send(new DeleteTableCommand({ TableName: tableName }));
559
+ }
560
+ async listTables(data) {
561
+ const tables = [];
562
+ let lastEvaluatedTableName;
563
+ do {
564
+ const output = await this.client.send(new ListTablesCommand({
565
+ Limit: data?.limit,
566
+ ExclusiveStartTableName: lastEvaluatedTableName
567
+ }));
568
+ tables.push(...output.TableNames ?? []);
569
+ lastEvaluatedTableName = output.LastEvaluatedTableName;
570
+ } while (lastEvaluatedTableName);
571
+ return tables;
572
+ }
573
+ async get(data) {
574
+ const exp = new ExpressionBuilder();
575
+ const input = new Lib.GetCommand({
576
+ TableName: data.table,
577
+ Key: data.key,
578
+ ConsistentRead: data.consistent,
579
+ ProjectionExpression: exp.projection(data.projection),
580
+ ExpressionAttributeNames: exp.attributeNames
581
+ });
582
+ const output = await this.client.send(input);
583
+ if (output.Item && data.condition) {
584
+ if (!sift(data.condition)(output.Item)) return;
585
+ }
586
+ return output.Item;
587
+ }
588
+ async getOrThrow(data) {
589
+ const item = await this.get(data);
590
+ if (!item) throw new Error(`Item not found in "${data.table}" table.`);
591
+ return item;
592
+ }
593
+ async put(data) {
594
+ const exp = new ExpressionBuilder();
595
+ const item = removeUndefined(data.item);
596
+ const input = new Lib.PutCommand({
597
+ TableName: data.table,
598
+ Item: item,
599
+ ReturnValues: data.returnOld ? "ALL_OLD" : "NONE",
600
+ ReturnValuesOnConditionCheckFailure: "ALL_OLD",
601
+ ConditionExpression: exp.condition(data.condition),
602
+ ExpressionAttributeNames: exp.attributeNames,
603
+ ExpressionAttributeValues: exp.attributeValues
604
+ });
605
+ const output = await this.client.send(input);
606
+ return data.returnOld ? output.Attributes : item;
607
+ }
608
+ async update(data) {
609
+ const exp = new ExpressionBuilder();
610
+ const condition = { $and: Object.keys(data.key).map((field) => ({ [field]: { $exists: true } })) };
611
+ if (data.condition) condition.$and.push(data.condition);
612
+ const input = new Lib.UpdateCommand({
613
+ TableName: data.table,
614
+ Key: data.key,
615
+ ReturnValues: "ALL_NEW",
616
+ ReturnValuesOnConditionCheckFailure: "ALL_OLD",
617
+ UpdateExpression: exp.update(data.update),
618
+ ConditionExpression: exp.condition(condition),
619
+ ExpressionAttributeNames: exp.attributeNames,
620
+ ExpressionAttributeValues: exp.attributeValues
621
+ });
622
+ return (await this.client.send(input)).Attributes;
623
+ }
624
+ async delete(data) {
625
+ const exp = new ExpressionBuilder();
626
+ const input = new Lib.DeleteCommand({
627
+ TableName: data.table,
628
+ Key: data.key,
629
+ ReturnValues: "ALL_OLD",
630
+ ConditionExpression: exp.condition(data.condition),
631
+ ReturnValuesOnConditionCheckFailure: "ALL_OLD",
632
+ ExpressionAttributeNames: exp.attributeNames,
633
+ ExpressionAttributeValues: exp.attributeValues
634
+ });
635
+ return (await this.client.send(input)).Attributes;
636
+ }
637
+ async deleteOrThrow(data) {
638
+ const item = await this.delete(data);
639
+ if (!item) throw new Error(`Item not found in "${data.table}" table.`);
640
+ return item;
641
+ }
642
+ async *queryPaged(data) {
643
+ const exp = new ExpressionBuilder();
644
+ let lastEvaluatedKey = data.startKey;
645
+ do {
646
+ const input = new Lib.QueryCommand({
647
+ TableName: data.table,
648
+ IndexName: data.index,
649
+ ExclusiveStartKey: lastEvaluatedKey,
650
+ Limit: data.limit,
651
+ ScanIndexForward: data.sort !== "DESC",
652
+ ConsistentRead: data.consistent,
653
+ KeyConditionExpression: exp.condition(data.query),
654
+ ProjectionExpression: exp.projection(data.projection),
655
+ FilterExpression: exp.condition(data.filter),
656
+ ExpressionAttributeNames: exp.attributeNames,
657
+ ExpressionAttributeValues: exp.attributeValues
658
+ });
659
+ const output = await this.client.send(input);
660
+ if (output.Items?.length) yield output.Items;
661
+ lastEvaluatedKey = output.LastEvaluatedKey;
662
+ } while (lastEvaluatedKey);
663
+ }
664
+ async query(data) {
665
+ const items = [];
666
+ for await (const page of this.queryPaged(data)) items.push(...page);
667
+ return items;
668
+ }
669
+ async *scanPaged(data) {
670
+ const exp = new ExpressionBuilder();
671
+ const totalSegments = data.parallel ?? 1;
672
+ const segments = [...Array(totalSegments).keys()];
673
+ const lastEvaluatedKeys = segments.map(() => data.startKey);
674
+ do {
675
+ const items = (await Promise.all(segments.map(async (segment) => {
676
+ if (lastEvaluatedKeys[segment] === null) return [];
677
+ const input = new Lib.ScanCommand({
678
+ ExclusiveStartKey: lastEvaluatedKeys[segment],
679
+ Segment: segment,
680
+ TotalSegments: totalSegments,
681
+ TableName: data.table,
682
+ IndexName: data.index,
683
+ Limit: data.limit,
684
+ ConsistentRead: data.consistent,
685
+ ProjectionExpression: exp.projection(data.projection),
686
+ FilterExpression: exp.condition(data.filter),
687
+ ExpressionAttributeNames: exp.attributeNames,
688
+ ExpressionAttributeValues: exp.attributeValues
689
+ });
690
+ const output = await this.client.send(input);
691
+ lastEvaluatedKeys[segment] = output.LastEvaluatedKey ?? null;
692
+ return output.Items ?? [];
693
+ }))).flat();
694
+ if (items.length) yield items;
695
+ } while (lastEvaluatedKeys.some((key) => key !== null));
696
+ }
697
+ async scan(data) {
698
+ const items = [];
699
+ for await (const page of this.scanPaged(data)) items.push(...page);
700
+ return items;
701
+ }
702
+ async exists(data) {
703
+ const { query, ...otherOptions } = data;
704
+ const sharedOptions = {
705
+ ...otherOptions,
706
+ limit: data.filter ? void 0 : 1
707
+ };
708
+ const pagedItems = query ? this.queryPaged({
709
+ query,
710
+ ...sharedOptions
711
+ }) : this.scanPaged(sharedOptions);
712
+ for await (const items of pagedItems) if (items.length) return true;
713
+ return false;
714
+ }
715
+ async batchGet(data) {
716
+ const result = { items: {} };
717
+ const tableData = Object.fromEntries(Object.entries(data).map(([table, request]) => {
718
+ const exp = new ExpressionBuilder();
719
+ const sifter = request?.condition ? sift(request?.condition) : void 0;
720
+ result.items[table] = [];
721
+ return [table, {
722
+ request: {
723
+ ConsistentRead: request?.consistent,
724
+ ProjectionExpression: exp.projection(request?.projection),
725
+ ExpressionAttributeNames: exp.attributeNames
726
+ },
727
+ keyNames: Object.keys(request.keys.at(0) ?? {}),
728
+ itemIndexMap: /* @__PURE__ */ new Map(),
729
+ sifter
730
+ }];
731
+ }));
732
+ const flattenedBatches = Object.entries(data).flatMap(([table, request]) => {
733
+ return request.keys.map((key, i) => {
734
+ const itemIndexKey = tableData[table]?.keyNames.map((keyName) => key[keyName]).join("|") ?? "";
735
+ tableData[table]?.itemIndexMap.set(itemIndexKey, i);
736
+ return [table, key];
737
+ });
738
+ });
739
+ let batchSize = 100;
740
+ let retryCount = 0;
741
+ while (flattenedBatches.length && retryCount < 5) {
742
+ const batch = flattenedBatches.splice(0, batchSize || 1);
743
+ const request = {};
744
+ for (const [table, key] of batch) {
745
+ if (!request[table]) request[table] = {
746
+ ...tableData[table]?.request,
747
+ Keys: []
748
+ };
749
+ request[table].Keys?.push(key);
750
+ }
751
+ const input = new Lib.BatchGetCommand({ RequestItems: request });
752
+ const output = await this.client.send(input);
753
+ for (const [table, items] of Object.entries(output.Responses ?? {})) {
754
+ if (!result.items[table]) continue;
755
+ for (const item of items) {
756
+ if (tableData[table]?.sifter && !tableData[table].sifter(item)) continue;
757
+ result.items[table].push(item);
758
+ }
759
+ }
760
+ let unprocessedKeyCount = 0;
761
+ for (const [table, request] of Object.entries(output.UnprocessedKeys ?? {})) {
762
+ const unprocessed = (request.Keys ?? []).map((key) => [table, key]);
763
+ flattenedBatches.push(...unprocessed);
764
+ unprocessedKeyCount += unprocessed.length;
765
+ }
766
+ if (!unprocessedKeyCount) {
767
+ retryCount = 0;
768
+ continue;
769
+ }
770
+ batchSize -= Math.floor(unprocessedKeyCount / 2);
771
+ retryCount++;
772
+ }
773
+ if (flattenedBatches.length) {
774
+ result.unprocessed = {};
775
+ for (const [table, key] of flattenedBatches) {
776
+ if (!result.unprocessed[table]) result.unprocessed[table] = {
777
+ ...tableData[table]?.request,
778
+ keys: []
779
+ };
780
+ result.unprocessed[table].keys.push(key);
781
+ }
782
+ }
783
+ for (const [table, items] of Object.entries(result.items)) {
784
+ const itemIndexMap = tableData[table]?.itemIndexMap;
785
+ const keyNames = tableData[table]?.keyNames;
786
+ if (!itemIndexMap || !keyNames) continue;
787
+ items.sort((item1, item2) => {
788
+ const item1IndexKey = keyNames.map((keyName) => item1[keyName]).join("|");
789
+ const item2IndexKey = keyNames.map((keyName) => item2[keyName]).join("|");
790
+ return (itemIndexMap.get(item1IndexKey) ?? 0) - (itemIndexMap.get(item2IndexKey) ?? 0);
791
+ });
792
+ }
793
+ return result;
794
+ }
795
+ async batchGetOrThrow(data) {
796
+ const { items, unprocessed } = await this.batchGet(data);
797
+ for (const table of Object.keys(data)) {
798
+ if (unprocessed?.[table]) throw new Error(`One or more batch get requests were not processed in "${table}" table.`);
799
+ if (items[table]?.length !== data[table]?.keys?.length) throw new Error(`One or more items were not found in "${table}" table.`);
800
+ }
801
+ return items;
802
+ }
803
+ async batchWrite(data) {
804
+ const result = { items: {} };
805
+ const flattenedBatches = Object.keys(data).flatMap((table) => {
806
+ return data[table]?.map((r) => {
807
+ if (r.type === "DELETE") return [table, { DeleteRequest: { Key: r.key } }];
808
+ else return [table, { PutRequest: { Item: r.item } }];
809
+ }) ?? [];
810
+ });
811
+ let batchSize = 25;
812
+ let retryCount = 0;
813
+ while (flattenedBatches.length && retryCount < 5) {
814
+ const batch = flattenedBatches.splice(0, batchSize || 1);
815
+ const request = {};
816
+ for (const [table, req] of batch) {
817
+ if (!request[table]) request[table] = [];
818
+ request[table].push(req);
819
+ }
820
+ const input = new Lib.BatchWriteCommand({ RequestItems: request });
821
+ const output = await this.client.send(input);
822
+ let unprocessedKeyCount = 0;
823
+ for (const [table, requests] of Object.entries(output.UnprocessedItems ?? {})) for (const request of requests) {
824
+ flattenedBatches.push([table, request]);
825
+ unprocessedKeyCount++;
826
+ }
827
+ if (!unprocessedKeyCount) {
828
+ retryCount = 0;
829
+ continue;
830
+ }
831
+ batchSize -= Math.floor(unprocessedKeyCount / 2);
832
+ retryCount++;
833
+ }
834
+ if (flattenedBatches.length) {
835
+ result.unprocessed = {};
836
+ for (const [table, request] of flattenedBatches) {
837
+ if (!result.unprocessed[table]) result.unprocessed[table] = [];
838
+ if ("DeleteRequest" in request && request.DeleteRequest?.Key) result.unprocessed[table].push({
839
+ type: "DELETE",
840
+ key: request.DeleteRequest.Key
841
+ });
842
+ else if ("PutRequest" in request && request.PutRequest?.Item) result.unprocessed[table].push({
843
+ type: "PUT",
844
+ item: request.PutRequest.Item
845
+ });
846
+ }
847
+ }
848
+ return result;
849
+ }
850
+ async trxGet(...requests) {
851
+ const trxItems = requests.map((request) => {
852
+ const exp = new ExpressionBuilder();
853
+ return { Get: {
854
+ Key: request.key,
855
+ TableName: request.table,
856
+ ProjectionExpression: exp.projection(request?.projection),
857
+ ExpressionAttributeNames: exp.attributeNames
858
+ } };
859
+ });
860
+ const input = new Lib.TransactGetCommand({ TransactItems: trxItems });
861
+ return (await this.client.send(input)).Responses?.map((response, i) => {
862
+ if (requests[i]?.condition && !sift(requests[i].condition)(response.Item)) return;
863
+ return response.Item;
864
+ }) ?? [];
865
+ }
866
+ async trxGetOrThrow(...requests) {
867
+ const items = await this.trxGet(...requests);
868
+ for (let i = 0; i < items.length; i++) if (!items[i]) throw new Error(`One or more items were not found in "${requests[i]?.table}" table.`);
869
+ return items;
870
+ }
871
+ async trxWrite(...requests) {
872
+ const trxItems = requests.map((request) => {
873
+ const exp = new ExpressionBuilder();
874
+ if (request.type === "CONDITION" || request.type === "DELETE") return { [request.type === "CONDITION" ? "ConditionCheck" : "Delete"]: {
875
+ TableName: request.table,
876
+ Key: request.key,
877
+ ConditionExpression: exp.condition(request.condition),
878
+ ExpressionAttributeNames: exp.attributeNames,
879
+ ExpressionAttributeValues: exp.attributeValues
880
+ } };
881
+ if (request.type === "PUT") return { Put: {
882
+ TableName: request.table,
883
+ Item: removeUndefined(request.item),
884
+ ConditionExpression: exp.condition(request.condition),
885
+ ExpressionAttributeNames: exp.attributeNames,
886
+ ExpressionAttributeValues: exp.attributeValues
887
+ } };
888
+ return { Update: {
889
+ Key: request.key,
890
+ TableName: request.table,
891
+ UpdateExpression: exp.update(request.update),
892
+ ConditionExpression: exp.condition(request.condition),
893
+ ExpressionAttributeNames: exp.attributeNames,
894
+ ExpressionAttributeValues: exp.attributeValues
895
+ } };
896
+ });
897
+ const input = new Lib.TransactWriteCommand({ TransactItems: trxItems });
898
+ await this.client.send(input);
899
+ }
900
+ };
901
+ //#endregion
902
+ //#region src/table.ts
903
+ var Table = class {
904
+ schema;
905
+ def;
906
+ constructor(schema, def) {
907
+ this.schema = schema;
908
+ this.def = def;
909
+ }
910
+ };
911
+ //#endregion
912
+ export { AbstractRepo, Db, Repo, Table, chunk, extractTableDesc, removeUndefined, resolveAttrType };
913
+
914
+ //# sourceMappingURL=index.mjs.map