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.mjs ADDED
@@ -0,0 +1,1045 @@
1
+ import { ConditionalCheckFailedException, CreateTableCommand, DeleteTableCommand, DynamoDB, DynamoDBClient, ListTablesCommand, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
2
+ import * as Lib from "@aws-sdk/lib-dynamodb";
3
+ import sift from "sift";
4
+ import * as T from "typebox";
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 Repository = class {
186
+ table;
187
+ db;
188
+ constructor(table, db) {
189
+ this.table = table;
190
+ this.db = db;
191
+ }
192
+ get tableName() {
193
+ return `${this.db.config?.tableNamePrefix ?? ""}${this.table.def.name}`;
194
+ }
195
+ get def() {
196
+ return this.table.def;
197
+ }
198
+ extractKey(item) {
199
+ const { partitionKey, sortKey } = this.table.def;
200
+ if (sortKey) return {
201
+ [partitionKey]: item[partitionKey],
202
+ [sortKey]: item[sortKey]
203
+ };
204
+ return { [partitionKey]: item[partitionKey] };
205
+ }
206
+ async get(key, options) {
207
+ return this.db.get({
208
+ table: this.tableName,
209
+ key: this.extractKey(key),
210
+ ...options
211
+ });
212
+ }
213
+ async getOrThrow(key, options) {
214
+ return this.db.getOrThrow({
215
+ table: this.tableName,
216
+ key: this.extractKey(key),
217
+ ...options
218
+ });
219
+ }
220
+ async put(item, options) {
221
+ const itemWithDefaults = this.table.def.beforePut ? {
222
+ ...this.table.def.beforePut(item),
223
+ ...item
224
+ } : item;
225
+ return this.db.put({
226
+ table: this.tableName,
227
+ item: itemWithDefaults,
228
+ ...options
229
+ });
230
+ }
231
+ async update(key, update, options) {
232
+ const updateWithDefaults = this.table.def.beforeUpdate ? {
233
+ ...this.table.def.beforeUpdate(update),
234
+ ...update
235
+ } : update;
236
+ return this.db.update({
237
+ table: this.tableName,
238
+ key: this.extractKey(key),
239
+ update: updateWithDefaults,
240
+ ...options
241
+ });
242
+ }
243
+ async create(item, options) {
244
+ const itemWithDefaults = this.table.def.beforePut ? {
245
+ ...this.table.def.beforePut(item),
246
+ ...item
247
+ } : item;
248
+ return this.db.create({
249
+ table: this.tableName,
250
+ item: itemWithDefaults,
251
+ partitionKeyName: this.table.def.partitionKey,
252
+ ...options
253
+ });
254
+ }
255
+ async upsert(data) {
256
+ const { update, item, key, ...options } = data;
257
+ const updateWithDefaults = this.table.def.beforeUpdate ? {
258
+ ...this.table.def.beforeUpdate(update),
259
+ ...update
260
+ } : update;
261
+ const itemWithDefaults = this.table.def.beforePut ? {
262
+ ...this.table.def.beforePut(item),
263
+ ...item
264
+ } : item;
265
+ return this.db.upsert({
266
+ table: this.tableName,
267
+ key: this.extractKey(key),
268
+ update: updateWithDefaults,
269
+ item: itemWithDefaults,
270
+ ...options
271
+ });
272
+ }
273
+ async delete(key, options) {
274
+ return this.db.delete({
275
+ table: this.tableName,
276
+ key: this.extractKey(key),
277
+ ...options
278
+ });
279
+ }
280
+ async deleteOrThrow(key, options) {
281
+ return this.db.deleteOrThrow({
282
+ table: this.tableName,
283
+ key: this.extractKey(key),
284
+ ...options
285
+ });
286
+ }
287
+ async query(query, options) {
288
+ return this.db.query({
289
+ table: this.tableName,
290
+ query,
291
+ ...options
292
+ });
293
+ }
294
+ async *queryPaged(query, options) {
295
+ yield* this.db.queryPaged({
296
+ table: this.tableName,
297
+ query,
298
+ ...options
299
+ });
300
+ }
301
+ async queryGsi(gsi, query, options) {
302
+ return this.db.query({
303
+ table: this.tableName,
304
+ index: gsi,
305
+ query,
306
+ ...options
307
+ });
308
+ }
309
+ async *queryGsiPaged(gsi, query, options) {
310
+ yield* this.db.queryPaged({
311
+ table: this.tableName,
312
+ index: gsi,
313
+ query,
314
+ ...options
315
+ });
316
+ }
317
+ async scan(options) {
318
+ return this.db.scan({
319
+ table: this.tableName,
320
+ ...options
321
+ });
322
+ }
323
+ async *scanPaged(options) {
324
+ yield* this.db.scanPaged({
325
+ table: this.tableName,
326
+ ...options
327
+ });
328
+ }
329
+ async scanGsi(gsi, options) {
330
+ return this.db.scan({
331
+ table: this.tableName,
332
+ index: gsi,
333
+ ...options
334
+ });
335
+ }
336
+ async *scanGsiPaged(gsi, options) {
337
+ yield* this.db.scanPaged({
338
+ table: this.tableName,
339
+ index: gsi,
340
+ ...options
341
+ });
342
+ }
343
+ async exists(options) {
344
+ return this.db.exists({
345
+ table: this.tableName,
346
+ projection: [this.table.def.partitionKey],
347
+ ...options
348
+ });
349
+ }
350
+ async existsGsi(gsi, options) {
351
+ return this.db.exists({
352
+ table: this.tableName,
353
+ index: gsi,
354
+ projection: [this.table.def.partitionKey],
355
+ ...options
356
+ });
357
+ }
358
+ async batchGet(keys, options) {
359
+ const { items, unprocessed } = await this.db.batchGet({ [this.tableName]: {
360
+ keys: keys.map((key) => this.extractKey(key)),
361
+ ...options
362
+ } });
363
+ return {
364
+ items: items[this.tableName],
365
+ unprocessed: unprocessed?.[this.tableName]?.keys
366
+ };
367
+ }
368
+ async batchGetOrThrow(keys, options) {
369
+ return (await this.db.batchGetOrThrow({ [this.tableName]: {
370
+ keys: keys.map((key) => this.extractKey(key)),
371
+ ...options
372
+ } }))[this.tableName];
373
+ }
374
+ async batchWrite(requests) {
375
+ const { items, unprocessed } = await this.db.batchWrite({ [this.tableName]: requests.map((request) => {
376
+ if (request.type === "DELETE") return {
377
+ type: "DELETE",
378
+ key: this.extractKey(request.key)
379
+ };
380
+ else return {
381
+ type: "PUT",
382
+ item: this.table.def.beforePut ? {
383
+ ...this.table.def.beforePut(request.item),
384
+ ...request.item
385
+ } : request.item
386
+ };
387
+ }) });
388
+ return {
389
+ items: items[this.tableName],
390
+ unprocessed: unprocessed?.[this.tableName]
391
+ };
392
+ }
393
+ async trxGet(keys, options) {
394
+ return this.db.trxGet(...keys.map((key) => ({
395
+ table: this.tableName,
396
+ key: this.extractKey(key),
397
+ ...options
398
+ })));
399
+ }
400
+ async trxGetOrThrow(keys, options) {
401
+ return this.db.trxGetOrThrow(...keys.map((key) => ({
402
+ table: this.tableName,
403
+ key: this.extractKey(key),
404
+ ...options
405
+ })));
406
+ }
407
+ async trxWrite(...requests) {
408
+ await this.db.trxWrite(...requests.map((request) => {
409
+ switch (request.type) {
410
+ case "CONDITION": {
411
+ const { key, condition, ...options } = request;
412
+ return this.trxConditionRequest(key, condition, options);
413
+ }
414
+ case "DELETE": {
415
+ const { key, ...options } = request;
416
+ return this.trxDeleteRequest(key, options);
417
+ }
418
+ case "PUT": {
419
+ const { item, ...options } = request;
420
+ return this.trxPutRequest(item, options);
421
+ }
422
+ case "UPDATE": {
423
+ const { key, update, ...options } = request;
424
+ return this.trxUpdateRequest(key, update, options);
425
+ }
426
+ default: throw new Error("Unexpected request type.");
427
+ }
428
+ }));
429
+ }
430
+ async trxDelete(keys, options) {
431
+ return this.db.trxWrite(...keys.map((key) => this.trxDeleteRequest(key, options)));
432
+ }
433
+ async trxPut(items, options) {
434
+ return this.db.trxWrite(...items.map((item) => this.trxPutRequest(item, options)));
435
+ }
436
+ async trxUpdate(keys, update, options) {
437
+ return this.db.trxWrite(...keys.map((key) => this.trxUpdateRequest(key, update, options)));
438
+ }
439
+ async trxCreate(items, options) {
440
+ return this.db.trxWrite(...items.map((item) => this.trxCreateRequest(item, options)));
441
+ }
442
+ trxGetRequest(key, options) {
443
+ return {
444
+ table: this.tableName,
445
+ key: this.extractKey(key),
446
+ ...options
447
+ };
448
+ }
449
+ trxDeleteRequest(key, options) {
450
+ return {
451
+ table: this.tableName,
452
+ type: "DELETE",
453
+ key: this.extractKey(key),
454
+ ...options
455
+ };
456
+ }
457
+ trxConditionRequest(key, condition, options) {
458
+ return {
459
+ table: this.tableName,
460
+ type: "CONDITION",
461
+ key: this.extractKey(key),
462
+ condition,
463
+ ...options
464
+ };
465
+ }
466
+ trxPutRequest(item, options) {
467
+ const itemWithDefaults = this.table.def.beforePut ? {
468
+ ...this.table.def.beforePut(item),
469
+ ...item
470
+ } : item;
471
+ return {
472
+ table: this.tableName,
473
+ type: "PUT",
474
+ item: itemWithDefaults,
475
+ ...options
476
+ };
477
+ }
478
+ trxUpdateRequest(key, update, options) {
479
+ const updateWithDefaults = this.table.def.beforeUpdate ? {
480
+ ...this.table.def.beforeUpdate(update),
481
+ ...update
482
+ } : update;
483
+ return {
484
+ table: this.tableName,
485
+ type: "UPDATE",
486
+ key: this.extractKey(key),
487
+ update: updateWithDefaults,
488
+ ...options
489
+ };
490
+ }
491
+ trxCreateRequest(item, options) {
492
+ const { condition: otherCondition, ...otherOptions } = options ?? {};
493
+ const condition = { $and: [{ [this.table.def.partitionKey]: { $exists: false } }] };
494
+ if (otherCondition) condition.$and.push(otherCondition);
495
+ const itemWithDefaults = this.table.def.beforePut ? {
496
+ ...this.table.def.beforePut(item),
497
+ ...item
498
+ } : item;
499
+ return {
500
+ table: this.tableName,
501
+ type: "PUT",
502
+ item: itemWithDefaults,
503
+ condition,
504
+ ...otherOptions
505
+ };
506
+ }
507
+ };
508
+ //#endregion
509
+ //#region src/util.ts
510
+ const resolveAttrType = (schema, attrName) => {
511
+ if (T.IsString(schema) || T.IsLiteralString(schema)) return T.IsOptional(schema) ? ["S", void 0] : ["S"];
512
+ if (T.IsNumber(schema) || T.IsLiteralNumber(schema)) return T.IsOptional(schema) ? ["N", void 0] : ["N"];
513
+ if (T.IsObject(schema)) {
514
+ const attrSchema = schema.properties[attrName];
515
+ if (!attrSchema) return [void 0];
516
+ return resolveAttrType(attrSchema, attrName);
517
+ }
518
+ if (T.IsIntersect(schema)) return schema.allOf.flatMap((s) => resolveAttrType(s, attrName));
519
+ if (T.IsUnion(schema)) return schema.anyOf.flatMap((s) => resolveAttrType(s, attrName));
520
+ return [];
521
+ };
522
+ const getAttrType = (schema, attrName, allowedTypes = [
523
+ "S",
524
+ "N",
525
+ void 0
526
+ ]) => {
527
+ const resolvedType = resolveAttrType(schema, attrName);
528
+ if (resolvedType.includes("S") && resolvedType.includes("N")) throw new Error(`Attribute ${attrName} cannot be both a number and a string.`);
529
+ if (!allowedTypes.includes(void 0) && resolvedType.includes(void 0)) throw new Error(`Attribute ${attrName} cannot be optional.`);
530
+ return resolvedType.includes("S") ? "S" : "N";
531
+ };
532
+ const extractAttribute = (schema, attrName, allowedTypes) => {
533
+ return {
534
+ AttributeName: attrName,
535
+ AttributeType: getAttrType(schema, attrName, allowedTypes)
536
+ };
537
+ };
538
+ const extractTableKey = (schema, attrName, allowedTypes) => {
539
+ return {
540
+ name: attrName,
541
+ type: getAttrType(schema, attrName, allowedTypes)
542
+ };
543
+ };
544
+ const extractTableDesc = (schema, def) => {
545
+ const gsis = Object.entries(def.gsis ?? {}).map(([indexName, gsi]) => {
546
+ return {
547
+ indexName,
548
+ partitionKey: extractTableKey(schema, gsi.partitionKey),
549
+ sortKey: gsi.sortKey ? extractTableKey(schema, gsi.sortKey) : void 0,
550
+ projectionType: Array.isArray(gsi.projection) ? "INCLUDE" : gsi.projection ?? "ALL",
551
+ nonKeyAttributes: Array.isArray(gsi.projection) ? gsi.projection : void 0
552
+ };
553
+ });
554
+ return {
555
+ tableName: def.name,
556
+ billingMode: def.billingMode ?? "PAY_PER_REQUEST",
557
+ stream: def.stream,
558
+ partitionKey: extractTableKey(schema, def.partitionKey, ["N", "S"]),
559
+ sortKey: def.sortKey ? extractTableKey(schema, def.sortKey, ["N", "S"]) : void 0,
560
+ gsis: gsis.length ? gsis : void 0
561
+ };
562
+ };
563
+ const chunk = (arr, chunkSize) => {
564
+ if (chunkSize <= 0) return [];
565
+ const chunks = [];
566
+ for (let i = 0; i < arr.length; i += chunkSize) chunks.push(arr.slice(i, i + chunkSize));
567
+ return chunks;
568
+ };
569
+ const removeUndefined = (obj) => {
570
+ const stack = [obj];
571
+ while (stack.length) {
572
+ const currentObj = stack.pop();
573
+ if (currentObj !== void 0) Object.entries(currentObj).forEach(([k, v]) => {
574
+ if (v && v instanceof Object) stack.push(v);
575
+ else if (v === void 0) delete currentObj[k];
576
+ });
577
+ }
578
+ return obj;
579
+ };
580
+ //#endregion
581
+ //#region src/db.ts
582
+ var Db = class {
583
+ client;
584
+ config;
585
+ constructor(clientOrConfig, dbConfig) {
586
+ if (clientOrConfig instanceof DynamoDBClient || clientOrConfig instanceof DynamoDB) this.client = Lib.DynamoDBDocumentClient.from(new DynamoDBClient(clientOrConfig));
587
+ else {
588
+ const { endpoint, ...clientConfig } = clientOrConfig;
589
+ this.client = Lib.DynamoDBDocumentClient.from(new DynamoDBClient({
590
+ endpoint: endpoint || void 0,
591
+ ...clientConfig
592
+ }));
593
+ }
594
+ this.config = dbConfig;
595
+ }
596
+ createRepo(table) {
597
+ return new Repository(table, this);
598
+ }
599
+ async listTables(data) {
600
+ const tables = [];
601
+ let lastEvaluatedTableName;
602
+ do {
603
+ const output = await this.client.send(new ListTablesCommand({
604
+ Limit: data?.limit,
605
+ ExclusiveStartTableName: lastEvaluatedTableName
606
+ }));
607
+ tables.push(...output.TableNames ?? []);
608
+ lastEvaluatedTableName = output.LastEvaluatedTableName;
609
+ } while (lastEvaluatedTableName);
610
+ return tables;
611
+ }
612
+ async get(data) {
613
+ const exp = new ExpressionBuilder();
614
+ const input = new Lib.GetCommand({
615
+ TableName: data.table,
616
+ Key: data.key,
617
+ ConsistentRead: data.consistent,
618
+ ProjectionExpression: exp.projection(data.projection),
619
+ ExpressionAttributeNames: exp.attributeNames
620
+ });
621
+ const output = await this.client.send(input);
622
+ if (output.Item && data.condition) {
623
+ if (!sift(data.condition)(output.Item)) return;
624
+ }
625
+ return output.Item;
626
+ }
627
+ async getOrThrow(data) {
628
+ const item = await this.get(data);
629
+ if (!item) throw new Error(`Item not found in "${data.table}" table.`);
630
+ return item;
631
+ }
632
+ async put(data) {
633
+ const exp = new ExpressionBuilder();
634
+ const input = new Lib.PutCommand({
635
+ TableName: data.table,
636
+ Item: removeUndefined(data.item),
637
+ ReturnValues: data.return === "ALL_OLD" ? "ALL_OLD" : void 0,
638
+ ReturnValuesOnConditionCheckFailure: "ALL_OLD",
639
+ ConditionExpression: exp.condition(data.condition),
640
+ ExpressionAttributeNames: exp.attributeNames,
641
+ ExpressionAttributeValues: exp.attributeValues
642
+ });
643
+ const output = await this.client.send(input);
644
+ return data.return === "ALL_OLD" ? output.Attributes : data;
645
+ }
646
+ async update(data) {
647
+ const exp = new ExpressionBuilder();
648
+ const condition = { $and: Object.keys(data.key).map((field) => ({ [field]: { $exists: true } })) };
649
+ if (data.condition) condition.$and.push(data.condition);
650
+ const input = new Lib.UpdateCommand({
651
+ TableName: data.table,
652
+ Key: data.key,
653
+ ReturnValues: data.return ?? "ALL_NEW",
654
+ ReturnValuesOnConditionCheckFailure: "ALL_OLD",
655
+ UpdateExpression: exp.update(data.update),
656
+ ConditionExpression: exp.condition(condition),
657
+ ExpressionAttributeNames: exp.attributeNames,
658
+ ExpressionAttributeValues: exp.attributeValues
659
+ });
660
+ return (await this.client.send(input)).Attributes;
661
+ }
662
+ async create(data) {
663
+ const { condition: otherCondition, partitionKeyName, ...options } = data;
664
+ const condition = { $and: [{ [partitionKeyName]: { $exists: false } }] };
665
+ if (otherCondition) condition.$and.push(otherCondition);
666
+ return this.put({
667
+ ...options,
668
+ condition,
669
+ return: "ALL_NEW"
670
+ });
671
+ }
672
+ async upsert(data) {
673
+ const { key, item, update, ...options } = data;
674
+ try {
675
+ return await this.update({
676
+ ...options,
677
+ key,
678
+ update,
679
+ return: "ALL_NEW"
680
+ });
681
+ } catch (e) {
682
+ if (e instanceof ConditionalCheckFailedException && !e.Item) return this.create({
683
+ item,
684
+ partitionKeyName: Object.keys(key)[0] ?? "",
685
+ ...options
686
+ });
687
+ throw e;
688
+ }
689
+ }
690
+ async delete(data) {
691
+ const exp = new ExpressionBuilder();
692
+ const input = new Lib.DeleteCommand({
693
+ TableName: data.table,
694
+ Key: data.key,
695
+ ReturnValues: data.return ?? "ALL_OLD",
696
+ ConditionExpression: exp.condition(data.condition),
697
+ ReturnValuesOnConditionCheckFailure: "ALL_OLD",
698
+ ExpressionAttributeNames: exp.attributeNames,
699
+ ExpressionAttributeValues: exp.attributeValues
700
+ });
701
+ return (await this.client.send(input)).Attributes;
702
+ }
703
+ async deleteOrThrow(data) {
704
+ const item = await this.delete(data);
705
+ if (!item) throw new Error(`Item not found in "${data.table}" table.`);
706
+ return item;
707
+ }
708
+ async *queryPaged(data) {
709
+ const exp = new ExpressionBuilder();
710
+ let lastEvaluatedKey = data.startKey;
711
+ do {
712
+ const input = new Lib.QueryCommand({
713
+ TableName: data.table,
714
+ IndexName: data.index,
715
+ ExclusiveStartKey: lastEvaluatedKey,
716
+ Limit: data.limit,
717
+ ScanIndexForward: data.sort !== "DESC",
718
+ ConsistentRead: data.consistent,
719
+ KeyConditionExpression: exp.condition(data.query),
720
+ ProjectionExpression: exp.projection(data.projection),
721
+ FilterExpression: exp.condition(data.filter),
722
+ ExpressionAttributeNames: exp.attributeNames,
723
+ ExpressionAttributeValues: exp.attributeValues
724
+ });
725
+ const output = await this.client.send(input);
726
+ if (output.Items?.length) yield output.Items;
727
+ lastEvaluatedKey = output.LastEvaluatedKey;
728
+ } while (lastEvaluatedKey);
729
+ }
730
+ async query(data) {
731
+ const items = [];
732
+ for await (const page of this.queryPaged(data)) items.push(...page);
733
+ return items;
734
+ }
735
+ async *scanPaged(data) {
736
+ const exp = new ExpressionBuilder();
737
+ const totalSegments = data.parallel ?? 1;
738
+ const segments = [...Array(totalSegments).keys()];
739
+ const lastEvaluatedKeys = segments.map(() => data.startKey);
740
+ do {
741
+ const items = (await Promise.all(segments.map(async (segment) => {
742
+ if (lastEvaluatedKeys[segment] === null) return [];
743
+ const input = new Lib.ScanCommand({
744
+ ExclusiveStartKey: lastEvaluatedKeys[segment],
745
+ Segment: segment,
746
+ TotalSegments: totalSegments,
747
+ TableName: data.table,
748
+ IndexName: data.index,
749
+ Limit: data.limit,
750
+ ConsistentRead: data.consistent,
751
+ ProjectionExpression: exp.projection(data.projection),
752
+ FilterExpression: exp.condition(data.filter),
753
+ ExpressionAttributeNames: exp.attributeNames,
754
+ ExpressionAttributeValues: exp.attributeValues
755
+ });
756
+ const output = await this.client.send(input);
757
+ lastEvaluatedKeys[segment] = output.LastEvaluatedKey ?? null;
758
+ return output.Items ?? [];
759
+ }))).flat();
760
+ if (items.length) yield items;
761
+ } while (lastEvaluatedKeys.some((key) => key !== null));
762
+ }
763
+ async scan(data) {
764
+ const items = [];
765
+ for await (const page of this.scanPaged(data)) items.push(...page);
766
+ return items;
767
+ }
768
+ async exists(data) {
769
+ const { query, ...otherOptions } = data;
770
+ const sharedOptions = {
771
+ ...otherOptions,
772
+ limit: data.filter ? void 0 : 1
773
+ };
774
+ const pagedItems = query ? this.queryPaged({
775
+ query,
776
+ ...sharedOptions
777
+ }) : this.scanPaged(sharedOptions);
778
+ for await (const items of pagedItems) if (items.length) return true;
779
+ return false;
780
+ }
781
+ async batchGet(data) {
782
+ const result = { items: {} };
783
+ const tableData = Object.fromEntries(Object.entries(data).map(([table, request]) => {
784
+ const exp = new ExpressionBuilder();
785
+ const sifter = request?.condition ? sift(request?.condition) : void 0;
786
+ result.items[table] = [];
787
+ return [table, {
788
+ request: {
789
+ ConsistentRead: request?.consistent,
790
+ ProjectionExpression: exp.projection(request?.projection),
791
+ ExpressionAttributeNames: exp.attributeNames
792
+ },
793
+ keyNames: Object.keys(request.keys.at(0) ?? {}),
794
+ itemIndexMap: /* @__PURE__ */ new Map(),
795
+ sifter
796
+ }];
797
+ }));
798
+ const flattenedBatches = Object.entries(data).flatMap(([table, request]) => {
799
+ return request.keys.map((key, i) => {
800
+ const itemIndexKey = tableData[table]?.keyNames.map((keyName) => key[keyName]).join("|") ?? "";
801
+ tableData[table]?.itemIndexMap.set(itemIndexKey, i);
802
+ return [table, key];
803
+ });
804
+ });
805
+ let batchSize = 100;
806
+ let retryCount = 0;
807
+ while (flattenedBatches.length && retryCount < 5) {
808
+ const batch = flattenedBatches.splice(0, batchSize || 1);
809
+ const request = {};
810
+ for (const [table, key] of batch) {
811
+ if (!request[table]) request[table] = {
812
+ ...tableData[table]?.request,
813
+ Keys: []
814
+ };
815
+ request[table].Keys?.push(key);
816
+ }
817
+ const input = new Lib.BatchGetCommand({ RequestItems: request });
818
+ const output = await this.client.send(input);
819
+ for (const [table, items] of Object.entries(output.Responses ?? {})) {
820
+ if (!result.items[table]) continue;
821
+ for (const item of items) {
822
+ if (tableData[table]?.sifter && !tableData[table].sifter(item)) continue;
823
+ result.items[table].push(item);
824
+ }
825
+ }
826
+ let unprocessedKeyCount = 0;
827
+ for (const [table, request] of Object.entries(output.UnprocessedKeys ?? {})) {
828
+ const unprocessed = (request.Keys ?? []).map((key) => [table, key]);
829
+ flattenedBatches.push(...unprocessed);
830
+ unprocessedKeyCount += unprocessed.length;
831
+ }
832
+ if (!unprocessedKeyCount) {
833
+ retryCount = 0;
834
+ continue;
835
+ }
836
+ batchSize -= Math.floor(unprocessedKeyCount / 2);
837
+ retryCount++;
838
+ }
839
+ if (flattenedBatches.length) {
840
+ result.unprocessed = {};
841
+ for (const [table, key] of flattenedBatches) {
842
+ if (!result.unprocessed[table]) result.unprocessed[table] = {
843
+ ...tableData[table]?.request,
844
+ keys: []
845
+ };
846
+ result.unprocessed[table].keys.push(key);
847
+ }
848
+ }
849
+ for (const [table, items] of Object.entries(result.items)) {
850
+ const itemIndexMap = tableData[table]?.itemIndexMap;
851
+ const keyNames = tableData[table]?.keyNames;
852
+ if (!itemIndexMap || !keyNames) continue;
853
+ items.sort((item1, item2) => {
854
+ const item1IndexKey = keyNames.map((keyName) => item1[keyName]).join("|");
855
+ const item2IndexKey = keyNames.map((keyName) => item2[keyName]).join("|");
856
+ return (itemIndexMap.get(item1IndexKey) ?? 0) - (itemIndexMap.get(item2IndexKey) ?? 0);
857
+ });
858
+ }
859
+ return result;
860
+ }
861
+ async batchGetOrThrow(data) {
862
+ const { items, unprocessed } = await this.batchGet(data);
863
+ for (const table of Object.keys(data)) {
864
+ if (unprocessed?.[table]) throw new Error(`One or more batch get requests were not processed in "${table}" table.`);
865
+ if (items[table]?.length !== data[table]?.keys?.length) throw new Error(`One or more items were not found in "${table}" table.`);
866
+ }
867
+ return items;
868
+ }
869
+ async batchWrite(data) {
870
+ const result = { items: {} };
871
+ const flattenedBatches = Object.keys(data).flatMap((table) => {
872
+ return data[table]?.map((r) => {
873
+ if (r.type === "DELETE") return [table, { DeleteRequest: { Key: r.key } }];
874
+ else return [table, { PutRequest: { Item: r.item } }];
875
+ }) ?? [];
876
+ });
877
+ let batchSize = 25;
878
+ let retryCount = 0;
879
+ while (flattenedBatches.length && retryCount < 5) {
880
+ const batch = flattenedBatches.splice(0, batchSize || 1);
881
+ const request = {};
882
+ for (const [table, req] of batch) {
883
+ if (!request[table]) request[table] = [];
884
+ request[table].push(req);
885
+ }
886
+ const input = new Lib.BatchWriteCommand({ RequestItems: request });
887
+ const output = await this.client.send(input);
888
+ let unprocessedKeyCount = 0;
889
+ for (const [table, requests] of Object.entries(output.UnprocessedItems ?? {})) for (const request of requests) {
890
+ flattenedBatches.push([table, request]);
891
+ unprocessedKeyCount++;
892
+ }
893
+ if (!unprocessedKeyCount) {
894
+ retryCount = 0;
895
+ continue;
896
+ }
897
+ batchSize -= Math.floor(unprocessedKeyCount / 2);
898
+ retryCount++;
899
+ }
900
+ if (flattenedBatches.length) {
901
+ result.unprocessed = {};
902
+ for (const [table, request] of flattenedBatches) {
903
+ if (!result.unprocessed[table]) result.unprocessed[table] = [];
904
+ if ("DeleteRequest" in request && request.DeleteRequest?.Key) result.unprocessed[table].push({
905
+ type: "DELETE",
906
+ key: request.DeleteRequest.Key
907
+ });
908
+ else if ("PutRequest" in request && request.PutRequest?.Item) result.unprocessed[table].push({
909
+ type: "PUT",
910
+ item: request.PutRequest.Item
911
+ });
912
+ }
913
+ }
914
+ return result;
915
+ }
916
+ async trxGet(...requests) {
917
+ const trxItems = requests.map((request) => {
918
+ const exp = new ExpressionBuilder();
919
+ return { Get: {
920
+ Key: request.key,
921
+ TableName: request.table,
922
+ ProjectionExpression: exp.projection(request?.projection),
923
+ ExpressionAttributeNames: exp.attributeNames
924
+ } };
925
+ });
926
+ const input = new Lib.TransactGetCommand({ TransactItems: trxItems });
927
+ return (await this.client.send(input)).Responses?.map((response, i) => {
928
+ if (requests[i]?.condition && !sift(requests[i].condition)(response.Item)) return;
929
+ return response.Item;
930
+ }) ?? [];
931
+ }
932
+ async trxGetOrThrow(...requests) {
933
+ const items = await this.trxGet(...requests);
934
+ 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.`);
935
+ return items;
936
+ }
937
+ async trxWrite(...requests) {
938
+ const trxItems = requests.map((request) => {
939
+ const exp = new ExpressionBuilder();
940
+ if (request.type === "CONDITION" || request.type === "DELETE") return { [request.type === "CONDITION" ? "ConditionCheck" : "Delete"]: {
941
+ TableName: request.table,
942
+ Key: request.key,
943
+ ConditionExpression: exp.condition(request.condition),
944
+ ExpressionAttributeNames: exp.attributeNames,
945
+ ExpressionAttributeValues: exp.attributeValues
946
+ } };
947
+ if (request.type === "PUT") return { Put: {
948
+ TableName: request.table,
949
+ Item: removeUndefined(request.item),
950
+ ConditionExpression: exp.condition(request.condition),
951
+ ExpressionAttributeNames: exp.attributeNames,
952
+ ExpressionAttributeValues: exp.attributeValues
953
+ } };
954
+ return { Update: {
955
+ Key: request.key,
956
+ TableName: request.table,
957
+ UpdateExpression: exp.update(request.update),
958
+ ConditionExpression: exp.condition(request.condition),
959
+ ExpressionAttributeNames: exp.attributeNames,
960
+ ExpressionAttributeValues: exp.attributeValues
961
+ } };
962
+ });
963
+ const input = new Lib.TransactWriteCommand({ TransactItems: trxItems });
964
+ await this.client.send(input);
965
+ }
966
+ };
967
+ //#endregion
968
+ //#region src/table.ts
969
+ var Table = class {
970
+ schema;
971
+ def;
972
+ constructor(schema, def) {
973
+ this.schema = schema;
974
+ this.def = def;
975
+ }
976
+ async drop(client) {
977
+ await client.send(new DeleteTableCommand({ TableName: this.def.name }));
978
+ }
979
+ async createTable(client) {
980
+ const desc = extractTableDesc(this.schema, this.def);
981
+ const attributes = /* @__PURE__ */ new Map();
982
+ const setAttribute = (tableKey) => {
983
+ attributes.set(tableKey.name, {
984
+ AttributeName: tableKey.name,
985
+ AttributeType: tableKey.type
986
+ });
987
+ };
988
+ const keySchema = [{
989
+ AttributeName: desc.partitionKey.name,
990
+ KeyType: "HASH"
991
+ }];
992
+ setAttribute(desc.partitionKey);
993
+ if (desc.sortKey) {
994
+ keySchema.push({
995
+ AttributeName: desc.sortKey.name,
996
+ KeyType: "RANGE"
997
+ });
998
+ setAttribute(desc.sortKey);
999
+ }
1000
+ const gsis = desc.gsis?.map((gsi) => {
1001
+ const gsiKeySchema = [{
1002
+ AttributeName: gsi.partitionKey.name,
1003
+ KeyType: "HASH"
1004
+ }];
1005
+ setAttribute(gsi.partitionKey);
1006
+ if (gsi.sortKey) {
1007
+ gsiKeySchema.push({
1008
+ AttributeName: gsi.sortKey.name,
1009
+ KeyType: "RANGE"
1010
+ });
1011
+ setAttribute(gsi.sortKey);
1012
+ }
1013
+ return {
1014
+ IndexName: gsi.indexName,
1015
+ KeySchema: gsiKeySchema,
1016
+ Projection: {
1017
+ ProjectionType: gsi.projectionType,
1018
+ NonKeyAttributes: gsi.nonKeyAttributes
1019
+ }
1020
+ };
1021
+ });
1022
+ await client.send(new CreateTableCommand({
1023
+ TableName: desc.tableName,
1024
+ KeySchema: keySchema,
1025
+ AttributeDefinitions: [...attributes.values()],
1026
+ BillingMode: desc.billingMode,
1027
+ GlobalSecondaryIndexes: gsis,
1028
+ StreamSpecification: desc.stream ? {
1029
+ StreamEnabled: true,
1030
+ StreamViewType: desc.stream
1031
+ } : void 0
1032
+ }));
1033
+ if (desc.ttlAttribute) await client.send(new UpdateTimeToLiveCommand({
1034
+ TableName: desc.tableName,
1035
+ TimeToLiveSpecification: {
1036
+ Enabled: true,
1037
+ AttributeName: desc.ttlAttribute
1038
+ }
1039
+ }));
1040
+ }
1041
+ };
1042
+ //#endregion
1043
+ export { Db, Repository, Table, chunk, extractAttribute, extractTableDesc, extractTableKey, getAttrType, removeUndefined, resolveAttrType };
1044
+
1045
+ //# sourceMappingURL=index.mjs.map