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