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