dyno-table 0.0.1
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.d.mts +323 -0
- package/dist/index.d.ts +323 -0
- package/dist/index.js +1083 -0
- package/dist/index.mjs +1051 -0
- package/package.json +51 -0
- package/readme.md +132 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1083 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BaseRepository: () => BaseRepository,
|
|
24
|
+
ConditionalCheckFailedError: () => ConditionalCheckFailedError,
|
|
25
|
+
DynamoError: () => DynamoError,
|
|
26
|
+
ExponentialBackoffStrategy: () => ExponentialBackoffStrategy,
|
|
27
|
+
ResourceNotFoundError: () => ResourceNotFoundError,
|
|
28
|
+
Table: () => Table
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/builders/expression-builder.ts
|
|
33
|
+
var ExpressionBuilder = class {
|
|
34
|
+
nameCount = 0;
|
|
35
|
+
valueCount = 0;
|
|
36
|
+
generateAlias(type, prefix = type === "name" ? "n" : "v") {
|
|
37
|
+
const count = type === "name" ? this.nameCount++ : this.valueCount++;
|
|
38
|
+
const symbol = type === "name" ? "#" : ":";
|
|
39
|
+
return `${symbol}${prefix}${count}`;
|
|
40
|
+
}
|
|
41
|
+
reset() {
|
|
42
|
+
this.nameCount = 0;
|
|
43
|
+
this.valueCount = 0;
|
|
44
|
+
}
|
|
45
|
+
createAttributePath(path) {
|
|
46
|
+
const parts = path.split(".");
|
|
47
|
+
const aliases = parts.map(() => this.generateAlias("name"));
|
|
48
|
+
return {
|
|
49
|
+
path: aliases.join("."),
|
|
50
|
+
names: Object.fromEntries(parts.map((part, i) => [aliases[i], part]))
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
addValue(attributes, value, prefix) {
|
|
54
|
+
const alias = this.generateAlias("value", prefix);
|
|
55
|
+
attributes.values[alias] = value;
|
|
56
|
+
return alias;
|
|
57
|
+
}
|
|
58
|
+
buildComparison(path, operator, value, attributes, prefix) {
|
|
59
|
+
const simpleOperators = ["=", "<>", "<", "<=", ">", ">="];
|
|
60
|
+
if (simpleOperators.includes(operator)) {
|
|
61
|
+
const valueAlias = this.addValue(attributes, value, prefix);
|
|
62
|
+
return `${path} ${operator} ${valueAlias}`;
|
|
63
|
+
}
|
|
64
|
+
switch (operator) {
|
|
65
|
+
case "attribute_exists":
|
|
66
|
+
case "attribute_not_exists":
|
|
67
|
+
return `${operator}(${path})`;
|
|
68
|
+
case "begins_with":
|
|
69
|
+
case "contains":
|
|
70
|
+
case "attribute_type":
|
|
71
|
+
return `${operator}(${path}, ${this.addValue(attributes, value, prefix)})`;
|
|
72
|
+
case "not_contains":
|
|
73
|
+
return `NOT contains(${path}, ${this.addValue(attributes, value, prefix)})`;
|
|
74
|
+
case "size": {
|
|
75
|
+
const { compare, value: sizeValue } = value;
|
|
76
|
+
return `size(${path}) ${compare} ${this.addValue(attributes, sizeValue, prefix)}`;
|
|
77
|
+
}
|
|
78
|
+
case "BETWEEN": {
|
|
79
|
+
const valueAlias = this.addValue(attributes, value, prefix);
|
|
80
|
+
return `${path} BETWEEN ${valueAlias}[0] AND ${valueAlias}[1]`;
|
|
81
|
+
}
|
|
82
|
+
case "IN":
|
|
83
|
+
return `${path} IN (${this.addValue(attributes, value, prefix)})`;
|
|
84
|
+
default:
|
|
85
|
+
throw new Error(`Unsupported operator: ${operator}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
createExpression(conditions) {
|
|
89
|
+
this.reset();
|
|
90
|
+
const attributes = { names: {}, values: {} };
|
|
91
|
+
const expressions = conditions.map(({ field, operator, value }) => {
|
|
92
|
+
const { path, names } = this.createAttributePath(field);
|
|
93
|
+
Object.assign(attributes.names, names);
|
|
94
|
+
return this.buildComparison(path, operator, value, attributes);
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
expression: expressions.length ? expressions.join(" AND ") : void 0,
|
|
98
|
+
attributes: this.formatAttributes(attributes)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
formatAttributes({
|
|
102
|
+
names,
|
|
103
|
+
values
|
|
104
|
+
}) {
|
|
105
|
+
return {
|
|
106
|
+
...Object.keys(names).length && { names },
|
|
107
|
+
...Object.keys(values).length && { values }
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
buildKeyCondition(key, indexConfig) {
|
|
111
|
+
this.reset();
|
|
112
|
+
const attributes = { names: {}, values: {} };
|
|
113
|
+
const conditions = [];
|
|
114
|
+
const pkName = this.generateAlias("name", "pk");
|
|
115
|
+
attributes.names[pkName] = indexConfig.pkName;
|
|
116
|
+
conditions.push(`${pkName} = ${this.addValue(attributes, key.pk, "pk")}`);
|
|
117
|
+
if (key.sk && indexConfig.skName) {
|
|
118
|
+
const skName = this.generateAlias("name", "sk");
|
|
119
|
+
attributes.names[skName] = indexConfig.skName;
|
|
120
|
+
if (typeof key.sk === "string") {
|
|
121
|
+
conditions.push(
|
|
122
|
+
`${skName} = ${this.addValue(attributes, key.sk, "sk")}`
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
conditions.push(
|
|
126
|
+
this.buildComparison(
|
|
127
|
+
skName,
|
|
128
|
+
key.sk.operator,
|
|
129
|
+
key.sk.value,
|
|
130
|
+
attributes
|
|
131
|
+
)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
expression: conditions.join(" AND "),
|
|
137
|
+
attributes: this.formatAttributes(attributes)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
buildUpdateExpression(updates) {
|
|
141
|
+
this.reset();
|
|
142
|
+
const attributes = { names: {}, values: {} };
|
|
143
|
+
const operations = { sets: [], removes: [] };
|
|
144
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
145
|
+
if (key === "") {
|
|
146
|
+
throw new Error("Empty key provided");
|
|
147
|
+
}
|
|
148
|
+
const nameAlias = this.generateAlias("name", "u");
|
|
149
|
+
attributes.names[nameAlias] = key;
|
|
150
|
+
if (value == null) {
|
|
151
|
+
operations.removes.push(nameAlias);
|
|
152
|
+
} else {
|
|
153
|
+
const valueAlias = this.addValue(attributes, value, "u");
|
|
154
|
+
operations.sets.push(`${nameAlias} = ${valueAlias}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const expression = [
|
|
158
|
+
operations.sets.length && `SET ${operations.sets.join(", ")}`,
|
|
159
|
+
operations.removes.length && `REMOVE ${operations.removes.join(", ")}`
|
|
160
|
+
].filter(Boolean).join(" ");
|
|
161
|
+
return {
|
|
162
|
+
expression,
|
|
163
|
+
attributes: this.formatAttributes(attributes)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/builders/operation-builder.ts
|
|
169
|
+
var OperationBuilder = class {
|
|
170
|
+
constructor(expressionBuilder) {
|
|
171
|
+
this.expressionBuilder = expressionBuilder;
|
|
172
|
+
}
|
|
173
|
+
conditions = [];
|
|
174
|
+
where(field, operator, value) {
|
|
175
|
+
this.conditions.push({ field, operator, value });
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
whereExists(field) {
|
|
179
|
+
this.conditions.push({ field, operator: "attribute_exists" });
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
whereNotExists(field) {
|
|
183
|
+
this.conditions.push({ field, operator: "attribute_not_exists" });
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
whereEquals(field, value) {
|
|
187
|
+
return this.where(field, "=", value);
|
|
188
|
+
}
|
|
189
|
+
whereBetween(field, start, end) {
|
|
190
|
+
return this.where(field, "BETWEEN", [start, end]);
|
|
191
|
+
}
|
|
192
|
+
whereIn(field, values) {
|
|
193
|
+
return this.where(field, "IN", values);
|
|
194
|
+
}
|
|
195
|
+
buildConditionExpression() {
|
|
196
|
+
return this.expressionBuilder.createExpression(this.conditions);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/builders/put-builder.ts
|
|
201
|
+
var PutBuilder = class extends OperationBuilder {
|
|
202
|
+
constructor(item, expressionBuilder, onBuild) {
|
|
203
|
+
super(expressionBuilder);
|
|
204
|
+
this.item = item;
|
|
205
|
+
this.onBuild = onBuild;
|
|
206
|
+
}
|
|
207
|
+
build() {
|
|
208
|
+
const { expression, attributes } = this.buildConditionExpression();
|
|
209
|
+
return {
|
|
210
|
+
type: "put",
|
|
211
|
+
item: this.item,
|
|
212
|
+
condition: expression ? {
|
|
213
|
+
expression,
|
|
214
|
+
names: attributes.names,
|
|
215
|
+
values: attributes.values
|
|
216
|
+
} : void 0
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
async execute() {
|
|
220
|
+
return this.onBuild(this.build());
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/builders/query-builder.ts
|
|
225
|
+
var QueryBuilder = class extends OperationBuilder {
|
|
226
|
+
constructor(key, indexConfig, expressionBuilder, onBuild) {
|
|
227
|
+
super(expressionBuilder);
|
|
228
|
+
this.key = key;
|
|
229
|
+
this.indexConfig = indexConfig;
|
|
230
|
+
this.onBuild = onBuild;
|
|
231
|
+
}
|
|
232
|
+
limitValue;
|
|
233
|
+
indexNameValue;
|
|
234
|
+
limit(value) {
|
|
235
|
+
this.limitValue = value;
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
useIndex(indexName) {
|
|
239
|
+
this.indexNameValue = indexName;
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
build() {
|
|
243
|
+
const filter = this.buildConditionExpression();
|
|
244
|
+
const keyCondition = this.expressionBuilder.buildKeyCondition(
|
|
245
|
+
this.key,
|
|
246
|
+
this.indexConfig
|
|
247
|
+
);
|
|
248
|
+
return {
|
|
249
|
+
type: "query",
|
|
250
|
+
keyCondition: {
|
|
251
|
+
expression: keyCondition.expression,
|
|
252
|
+
names: keyCondition.attributes.names,
|
|
253
|
+
values: keyCondition.attributes.values
|
|
254
|
+
},
|
|
255
|
+
filter: filter.expression ? {
|
|
256
|
+
expression: filter.expression,
|
|
257
|
+
names: filter.attributes.names,
|
|
258
|
+
values: filter.attributes.values
|
|
259
|
+
} : void 0,
|
|
260
|
+
limit: this.limitValue,
|
|
261
|
+
indexName: this.indexNameValue
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async execute() {
|
|
265
|
+
return this.onBuild(this.build());
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// src/builders/update-builder.ts
|
|
270
|
+
var UpdateBuilder = class extends OperationBuilder {
|
|
271
|
+
constructor(key, expressionBuilder, onBuild) {
|
|
272
|
+
super(expressionBuilder);
|
|
273
|
+
this.key = key;
|
|
274
|
+
this.onBuild = onBuild;
|
|
275
|
+
}
|
|
276
|
+
updates = {};
|
|
277
|
+
set(field, value) {
|
|
278
|
+
this.updates[field] = value;
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
setMany(attribtues) {
|
|
282
|
+
this.updates = { ...this.updates, ...attribtues };
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
remove(...fields) {
|
|
286
|
+
for (const field of fields) {
|
|
287
|
+
this.updates[field] = null;
|
|
288
|
+
}
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
increment(field, by = 1) {
|
|
292
|
+
this.updates[field] = { $add: by };
|
|
293
|
+
return this;
|
|
294
|
+
}
|
|
295
|
+
build() {
|
|
296
|
+
const condition = this.buildConditionExpression();
|
|
297
|
+
const update = this.expressionBuilder.buildUpdateExpression(this.updates);
|
|
298
|
+
return {
|
|
299
|
+
type: "update",
|
|
300
|
+
key: this.key,
|
|
301
|
+
update: {
|
|
302
|
+
expression: update.expression,
|
|
303
|
+
names: update.attributes.names,
|
|
304
|
+
values: update.attributes.values
|
|
305
|
+
},
|
|
306
|
+
condition: condition.expression ? {
|
|
307
|
+
expression: condition.expression,
|
|
308
|
+
names: condition.attributes.names,
|
|
309
|
+
values: condition.attributes.values
|
|
310
|
+
} : void 0
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
async execute() {
|
|
314
|
+
return this.onBuild(this.build());
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// src/errors/dynamo-error.ts
|
|
319
|
+
var DynamoError = class _DynamoError extends Error {
|
|
320
|
+
constructor(message, originalError, context) {
|
|
321
|
+
super(message);
|
|
322
|
+
this.originalError = originalError;
|
|
323
|
+
this.context = context;
|
|
324
|
+
this.name = "DynamoError";
|
|
325
|
+
if (Error.captureStackTrace) {
|
|
326
|
+
Error.captureStackTrace(this, _DynamoError);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var ConditionalCheckFailedError = class extends DynamoError {
|
|
331
|
+
constructor(message, originalError, context) {
|
|
332
|
+
super(message, originalError, context);
|
|
333
|
+
this.name = "ConditionalCheckFailedError";
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
var ResourceNotFoundError = class extends DynamoError {
|
|
337
|
+
constructor(message, originalError, context) {
|
|
338
|
+
super(message, originalError, context);
|
|
339
|
+
this.name = "ResourceNotFoundError";
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// src/errors/error-handler.ts
|
|
344
|
+
function translateExpression(expression, attributes) {
|
|
345
|
+
if (!expression || !attributes) return expression || "";
|
|
346
|
+
let translated = expression;
|
|
347
|
+
if (attributes.names) {
|
|
348
|
+
for (const [alias, name] of Object.entries(attributes.names)) {
|
|
349
|
+
translated = translated.replace(new RegExp(alias, "g"), name);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (attributes.values) {
|
|
353
|
+
for (const [alias, value] of Object.entries(attributes.values)) {
|
|
354
|
+
translated = translated.replace(
|
|
355
|
+
new RegExp(alias, "g"),
|
|
356
|
+
typeof value === "string" ? `"${value}"` : String(value)
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return translated;
|
|
361
|
+
}
|
|
362
|
+
function buildErrorMessage(context, error) {
|
|
363
|
+
const parts = [`DynamoDB ${context.operation} operation failed`];
|
|
364
|
+
if (context.tableName) {
|
|
365
|
+
parts.push(`
|
|
366
|
+
Table: ${context.tableName}`);
|
|
367
|
+
}
|
|
368
|
+
if (context.key) {
|
|
369
|
+
parts.push(`
|
|
370
|
+
Key: ${JSON.stringify(context.key, null, 2)}`);
|
|
371
|
+
}
|
|
372
|
+
if (context.expression) {
|
|
373
|
+
const { condition, update, filter, keyCondition } = context.expression;
|
|
374
|
+
if (condition) {
|
|
375
|
+
parts.push(
|
|
376
|
+
`
|
|
377
|
+
Condition: ${translateExpression(condition, context.attributes)}`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
if (update) {
|
|
381
|
+
parts.push(
|
|
382
|
+
`
|
|
383
|
+
Update: ${translateExpression(update, context.attributes)}`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
if (filter) {
|
|
387
|
+
parts.push(
|
|
388
|
+
`
|
|
389
|
+
Filter: ${translateExpression(filter, context.attributes)}`
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (keyCondition) {
|
|
393
|
+
parts.push(
|
|
394
|
+
`
|
|
395
|
+
Key Condition: ${translateExpression(keyCondition, context.attributes)}`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
parts.push(`
|
|
400
|
+
Original Error: ${error.message}`);
|
|
401
|
+
return parts.join("");
|
|
402
|
+
}
|
|
403
|
+
function handleDynamoError(error, context) {
|
|
404
|
+
if (!(error instanceof Error)) {
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
const errorMessage = buildErrorMessage(context, error);
|
|
408
|
+
switch (error.name) {
|
|
409
|
+
case "ConditionalCheckFailedException":
|
|
410
|
+
throw new ConditionalCheckFailedError(errorMessage, error, context);
|
|
411
|
+
case "ResourceNotFoundException":
|
|
412
|
+
throw new ResourceNotFoundError(errorMessage, error, context);
|
|
413
|
+
default:
|
|
414
|
+
throw new DynamoError(errorMessage, error, context);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/retry/retry-strategy.ts
|
|
419
|
+
var RETRYABLE_ERRORS = /* @__PURE__ */ new Set([
|
|
420
|
+
"ProvisionedThroughputExceededException",
|
|
421
|
+
"ThrottlingException",
|
|
422
|
+
"RequestLimitExceeded",
|
|
423
|
+
"InternalServerError",
|
|
424
|
+
"ServiceUnavailable"
|
|
425
|
+
]);
|
|
426
|
+
var isRetryableError = (error) => {
|
|
427
|
+
if (!error || typeof error !== "object") return false;
|
|
428
|
+
return "name" in error && RETRYABLE_ERRORS.has(error.name);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// src/retry/exponential-backoff-strategy.ts
|
|
432
|
+
var ExponentialBackoffStrategy = class {
|
|
433
|
+
constructor(maxAttempts = 3, baseDelay = 100, maxDelay = 5e3, jitter = true) {
|
|
434
|
+
this.maxAttempts = maxAttempts;
|
|
435
|
+
this.baseDelay = baseDelay;
|
|
436
|
+
this.maxDelay = maxDelay;
|
|
437
|
+
this.jitter = jitter;
|
|
438
|
+
}
|
|
439
|
+
shouldRetry(error, attempt) {
|
|
440
|
+
return attempt < this.maxAttempts && isRetryableError(error);
|
|
441
|
+
}
|
|
442
|
+
getDelay(attempt) {
|
|
443
|
+
const delay = Math.min(this.baseDelay * attempt ** 2, this.maxDelay);
|
|
444
|
+
if (!this.jitter) return delay;
|
|
445
|
+
return delay * (0.5 + Math.random());
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// src/dynamo/dynamo-converter.ts
|
|
450
|
+
var DynamoConverter = class {
|
|
451
|
+
constructor(tableName) {
|
|
452
|
+
this.tableName = tableName;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Converts our expression format to DynamoDB expression format
|
|
456
|
+
*/
|
|
457
|
+
convertExpression(expr) {
|
|
458
|
+
if (!expr) return {};
|
|
459
|
+
return {
|
|
460
|
+
...expr.expression && { Expression: expr.expression },
|
|
461
|
+
...expr.names && { ExpressionAttributeNames: expr.names },
|
|
462
|
+
...expr.values && { ExpressionAttributeValues: expr.values }
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Convert our format to DynamoDB put command input
|
|
467
|
+
*/
|
|
468
|
+
toPutCommand(options) {
|
|
469
|
+
return {
|
|
470
|
+
TableName: this.tableName,
|
|
471
|
+
Item: options.item,
|
|
472
|
+
...options.condition && {
|
|
473
|
+
ConditionExpression: options.condition.expression,
|
|
474
|
+
ExpressionAttributeNames: options.condition.names,
|
|
475
|
+
ExpressionAttributeValues: options.condition.values
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Convert our format to DynamoDB get command input
|
|
481
|
+
*/
|
|
482
|
+
toGetCommand(options) {
|
|
483
|
+
return {
|
|
484
|
+
TableName: this.tableName,
|
|
485
|
+
Key: options.key,
|
|
486
|
+
...options.indexName && { IndexName: options.indexName }
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Convert our format to DynamoDB update command input
|
|
491
|
+
*/
|
|
492
|
+
toUpdateCommand(options) {
|
|
493
|
+
return {
|
|
494
|
+
TableName: this.tableName,
|
|
495
|
+
Key: options.key,
|
|
496
|
+
UpdateExpression: options.update.expression,
|
|
497
|
+
ExpressionAttributeNames: {
|
|
498
|
+
...options.update.names,
|
|
499
|
+
...options.condition?.names
|
|
500
|
+
},
|
|
501
|
+
ExpressionAttributeValues: {
|
|
502
|
+
...options.update.values,
|
|
503
|
+
...options.condition?.values
|
|
504
|
+
},
|
|
505
|
+
...options.condition && {
|
|
506
|
+
ConditionExpression: options.condition.expression
|
|
507
|
+
},
|
|
508
|
+
...options.returnValues && {
|
|
509
|
+
ReturnValues: options.returnValues
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Convert our format to DynamoDB delete command input
|
|
515
|
+
*/
|
|
516
|
+
toDeleteCommand(options) {
|
|
517
|
+
return {
|
|
518
|
+
TableName: this.tableName,
|
|
519
|
+
Key: options.key,
|
|
520
|
+
...options.condition && {
|
|
521
|
+
ConditionExpression: options.condition.expression,
|
|
522
|
+
ExpressionAttributeNames: options.condition.names,
|
|
523
|
+
ExpressionAttributeValues: options.condition.values
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Convert our format to DynamoDB query command input
|
|
529
|
+
*/
|
|
530
|
+
toQueryCommand(options) {
|
|
531
|
+
return {
|
|
532
|
+
TableName: this.tableName,
|
|
533
|
+
...options.keyCondition && {
|
|
534
|
+
KeyConditionExpression: options.keyCondition.expression,
|
|
535
|
+
ExpressionAttributeNames: {
|
|
536
|
+
...options.keyCondition.names,
|
|
537
|
+
...options.filter?.names
|
|
538
|
+
},
|
|
539
|
+
ExpressionAttributeValues: {
|
|
540
|
+
...options.keyCondition.values,
|
|
541
|
+
...options.filter?.values
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
...options.filter && {
|
|
545
|
+
FilterExpression: options.filter.expression
|
|
546
|
+
},
|
|
547
|
+
IndexName: options.indexName,
|
|
548
|
+
Limit: options.limit,
|
|
549
|
+
ExclusiveStartKey: options.pageKey,
|
|
550
|
+
ConsistentRead: options.consistentRead
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Convert our format to DynamoDB scan command input
|
|
555
|
+
*/
|
|
556
|
+
toScanCommand(options) {
|
|
557
|
+
return {
|
|
558
|
+
TableName: this.tableName,
|
|
559
|
+
...options.filter && {
|
|
560
|
+
FilterExpression: options.filter.expression,
|
|
561
|
+
ExpressionAttributeNames: options.filter.names,
|
|
562
|
+
ExpressionAttributeValues: options.filter.values
|
|
563
|
+
},
|
|
564
|
+
IndexName: options.indexName,
|
|
565
|
+
Limit: options.limit,
|
|
566
|
+
ExclusiveStartKey: options.pageKey
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Convert our format to DynamoDB batch write command input
|
|
571
|
+
*/
|
|
572
|
+
toBatchWriteCommand(items) {
|
|
573
|
+
const requests = items.map((item) => {
|
|
574
|
+
if (item.put) {
|
|
575
|
+
return {
|
|
576
|
+
PutRequest: {
|
|
577
|
+
Item: item.put
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
if (item.delete) {
|
|
582
|
+
return {
|
|
583
|
+
DeleteRequest: {
|
|
584
|
+
Key: item.delete
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
throw new Error("Invalid batch write item");
|
|
589
|
+
});
|
|
590
|
+
return {
|
|
591
|
+
RequestItems: {
|
|
592
|
+
[this.tableName]: requests
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Convert our format to DynamoDB transact write command input
|
|
598
|
+
*/
|
|
599
|
+
toTransactWriteCommand(items) {
|
|
600
|
+
return {
|
|
601
|
+
TransactItems: items.map((item) => {
|
|
602
|
+
if (item.put) {
|
|
603
|
+
return {
|
|
604
|
+
Put: {
|
|
605
|
+
TableName: this.tableName,
|
|
606
|
+
Item: item.put.item,
|
|
607
|
+
...item.put.condition && {
|
|
608
|
+
ConditionExpression: item.put.condition.expression,
|
|
609
|
+
ExpressionAttributeNames: item.put.condition.names,
|
|
610
|
+
ExpressionAttributeValues: item.put.condition.values
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
if (item.delete) {
|
|
616
|
+
return {
|
|
617
|
+
Delete: {
|
|
618
|
+
TableName: this.tableName,
|
|
619
|
+
Key: item.delete.key,
|
|
620
|
+
...item.delete.condition && {
|
|
621
|
+
ConditionExpression: item.delete.condition.expression,
|
|
622
|
+
ExpressionAttributeNames: item.delete.condition.names,
|
|
623
|
+
ExpressionAttributeValues: item.delete.condition.values
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
if (item.update) {
|
|
629
|
+
return {
|
|
630
|
+
Update: {
|
|
631
|
+
TableName: this.tableName,
|
|
632
|
+
Key: item.update.key,
|
|
633
|
+
UpdateExpression: item.update.update.expression,
|
|
634
|
+
...item.update.condition && {
|
|
635
|
+
ConditionExpression: item.update.condition.expression,
|
|
636
|
+
ExpressionAttributeNames: {
|
|
637
|
+
...item.update.update.names,
|
|
638
|
+
...item.update.condition.names
|
|
639
|
+
},
|
|
640
|
+
ExpressionAttributeValues: {
|
|
641
|
+
...item.update.update.values,
|
|
642
|
+
...item.update.condition.values
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
throw new Error("Invalid transaction item");
|
|
649
|
+
})
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Convert DynamoDB batch write response to our format
|
|
654
|
+
*/
|
|
655
|
+
fromBatchWriteResponse(response) {
|
|
656
|
+
return response.map((item) => {
|
|
657
|
+
if ("PutRequest" in item) {
|
|
658
|
+
return {
|
|
659
|
+
put: item.PutRequest.Item
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if ("DeleteRequest" in item) {
|
|
663
|
+
return {
|
|
664
|
+
delete: item.DeleteRequest.Key
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
throw new Error("Invalid batch write response item");
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// src/dynamo/dynamo-service.ts
|
|
673
|
+
var BATCH_WRITE_LIMIT = 25;
|
|
674
|
+
var TRANSACTION_LIMIT = 100;
|
|
675
|
+
var DynamoService = class {
|
|
676
|
+
constructor(client, tableName) {
|
|
677
|
+
this.client = client;
|
|
678
|
+
this.tableName = tableName;
|
|
679
|
+
this.converter = new DynamoConverter(tableName);
|
|
680
|
+
}
|
|
681
|
+
converter;
|
|
682
|
+
async put(options) {
|
|
683
|
+
try {
|
|
684
|
+
const params = this.converter.toPutCommand(options);
|
|
685
|
+
return await this.withRetry(() => this.client.put(params));
|
|
686
|
+
} catch (error) {
|
|
687
|
+
handleDynamoError(error, {
|
|
688
|
+
operation: "PUT",
|
|
689
|
+
tableName: this.tableName,
|
|
690
|
+
key: options.item,
|
|
691
|
+
expression: {
|
|
692
|
+
condition: options.condition?.expression
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
async update(options) {
|
|
698
|
+
try {
|
|
699
|
+
const params = this.converter.toUpdateCommand(options);
|
|
700
|
+
return await this.withRetry(() => this.client.update(params));
|
|
701
|
+
} catch (error) {
|
|
702
|
+
handleDynamoError(error, {
|
|
703
|
+
operation: "UPDATE",
|
|
704
|
+
tableName: this.tableName,
|
|
705
|
+
key: options.key,
|
|
706
|
+
expression: {
|
|
707
|
+
update: options.update.expression,
|
|
708
|
+
condition: options.condition?.expression
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
async delete(options) {
|
|
714
|
+
const params = this.converter.toDeleteCommand(options);
|
|
715
|
+
try {
|
|
716
|
+
return await this.withRetry(() => this.client.delete(params));
|
|
717
|
+
} catch (error) {
|
|
718
|
+
handleDynamoError(error, {
|
|
719
|
+
operation: "DELETE",
|
|
720
|
+
tableName: this.tableName,
|
|
721
|
+
key: options.key,
|
|
722
|
+
expression: {
|
|
723
|
+
condition: params.ConditionExpression
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
async get(key, options) {
|
|
729
|
+
try {
|
|
730
|
+
const params = this.converter.toGetCommand({ key, ...options });
|
|
731
|
+
return await this.withRetry(() => this.client.get(params));
|
|
732
|
+
} catch (error) {
|
|
733
|
+
handleDynamoError(error, {
|
|
734
|
+
operation: "GET",
|
|
735
|
+
tableName: this.tableName,
|
|
736
|
+
key
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async query(options) {
|
|
741
|
+
try {
|
|
742
|
+
if (options.autoPaginate) {
|
|
743
|
+
return await this.executeWithAutoPagination(options);
|
|
744
|
+
}
|
|
745
|
+
const params = this.converter.toQueryCommand(options);
|
|
746
|
+
return await this.withRetry(() => this.client.query(params));
|
|
747
|
+
} catch (error) {
|
|
748
|
+
handleDynamoError(error, {
|
|
749
|
+
operation: "QUERY",
|
|
750
|
+
tableName: this.tableName,
|
|
751
|
+
expression: {
|
|
752
|
+
keyCondition: options.keyCondition?.expression,
|
|
753
|
+
filter: options.filter?.expression
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
async scan(options) {
|
|
759
|
+
try {
|
|
760
|
+
const params = this.converter.toScanCommand(options);
|
|
761
|
+
return await this.withRetry(() => this.client.scan(params));
|
|
762
|
+
} catch (error) {
|
|
763
|
+
handleDynamoError(error, {
|
|
764
|
+
operation: "SCAN",
|
|
765
|
+
tableName: this.tableName,
|
|
766
|
+
expression: {
|
|
767
|
+
filter: options.filter?.expression
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async batchWrite(items) {
|
|
773
|
+
try {
|
|
774
|
+
const chunks = this.chunkArray(items, BATCH_WRITE_LIMIT);
|
|
775
|
+
return await Promise.all(
|
|
776
|
+
chunks.map((chunk) => this.processBatchWrite(chunk))
|
|
777
|
+
);
|
|
778
|
+
} catch (error) {
|
|
779
|
+
handleDynamoError(error, {
|
|
780
|
+
operation: "BATCH_WRITE",
|
|
781
|
+
tableName: this.tableName
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async transactWrite(items) {
|
|
786
|
+
if (items.length > TRANSACTION_LIMIT) {
|
|
787
|
+
throw new Error(
|
|
788
|
+
`Transaction limit exceeded. Maximum is ${TRANSACTION_LIMIT} items, got ${items.length}`
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
const params = this.converter.toTransactWriteCommand(items);
|
|
793
|
+
return await this.withRetry(() => this.client.transactWrite(params));
|
|
794
|
+
} catch (error) {
|
|
795
|
+
handleDynamoError(error, {
|
|
796
|
+
operation: "TRANSACT_WRITE",
|
|
797
|
+
tableName: this.tableName
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
async executeWithAutoPagination(options) {
|
|
802
|
+
const allItems = [];
|
|
803
|
+
let lastEvaluatedKey;
|
|
804
|
+
do {
|
|
805
|
+
const result = await this.query({
|
|
806
|
+
...options,
|
|
807
|
+
pageKey: lastEvaluatedKey,
|
|
808
|
+
autoPaginate: false
|
|
809
|
+
});
|
|
810
|
+
if (result.Items) {
|
|
811
|
+
allItems.push(...result.Items);
|
|
812
|
+
}
|
|
813
|
+
lastEvaluatedKey = result.LastEvaluatedKey;
|
|
814
|
+
} while (lastEvaluatedKey);
|
|
815
|
+
return {
|
|
816
|
+
Items: allItems,
|
|
817
|
+
Count: allItems.length,
|
|
818
|
+
ScannedCount: allItems.length,
|
|
819
|
+
LastEvaluatedKey: void 0
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
async processBatchWrite(items) {
|
|
823
|
+
const processUnprocessedItems = async (unprocessedItems2) => {
|
|
824
|
+
const params2 = this.converter.toBatchWriteCommand(unprocessedItems2);
|
|
825
|
+
const result = await this.client.batchWrite(params2);
|
|
826
|
+
if (result.UnprocessedItems?.[this.tableName]?.length) {
|
|
827
|
+
const remainingItems = this.converter.fromBatchWriteResponse(
|
|
828
|
+
result.UnprocessedItems[this.tableName]
|
|
829
|
+
);
|
|
830
|
+
throw {
|
|
831
|
+
name: "UnprocessedItemsError",
|
|
832
|
+
unprocessedItems: remainingItems
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
return result;
|
|
836
|
+
};
|
|
837
|
+
const params = this.converter.toBatchWriteCommand(items);
|
|
838
|
+
const initialResult = await this.client.batchWrite(params);
|
|
839
|
+
if (!initialResult.UnprocessedItems?.[this.tableName]?.length) {
|
|
840
|
+
return initialResult;
|
|
841
|
+
}
|
|
842
|
+
const unprocessedItems = this.converter.fromBatchWriteResponse(
|
|
843
|
+
initialResult.UnprocessedItems[this.tableName]
|
|
844
|
+
);
|
|
845
|
+
return this.withRetry(() => processUnprocessedItems(unprocessedItems));
|
|
846
|
+
}
|
|
847
|
+
async withRetry(operation, strategy = new ExponentialBackoffStrategy()) {
|
|
848
|
+
let attempt = 0;
|
|
849
|
+
while (true) {
|
|
850
|
+
try {
|
|
851
|
+
return await operation();
|
|
852
|
+
} catch (error) {
|
|
853
|
+
if (!strategy.shouldRetry(error, attempt)) {
|
|
854
|
+
throw error;
|
|
855
|
+
}
|
|
856
|
+
await new Promise(
|
|
857
|
+
(resolve) => setTimeout(resolve, strategy.getDelay(attempt))
|
|
858
|
+
);
|
|
859
|
+
attempt++;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
chunkArray(array, size) {
|
|
864
|
+
return Array.from(
|
|
865
|
+
{ length: Math.ceil(array.length / size) },
|
|
866
|
+
(_, index) => array.slice(index * size, (index + 1) * size)
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// src/table.ts
|
|
872
|
+
var Table = class {
|
|
873
|
+
dynamoService;
|
|
874
|
+
expressionBuilder;
|
|
875
|
+
indexes;
|
|
876
|
+
constructor({
|
|
877
|
+
client,
|
|
878
|
+
tableName,
|
|
879
|
+
tableIndexes,
|
|
880
|
+
expressionBuilder
|
|
881
|
+
}) {
|
|
882
|
+
this.dynamoService = new DynamoService(client, tableName);
|
|
883
|
+
this.expressionBuilder = expressionBuilder ?? new ExpressionBuilder();
|
|
884
|
+
this.indexes = tableIndexes;
|
|
885
|
+
}
|
|
886
|
+
getIndexConfig(indexName) {
|
|
887
|
+
if (!indexName) {
|
|
888
|
+
return this.indexes.primary;
|
|
889
|
+
}
|
|
890
|
+
if (this.indexes[indexName]) {
|
|
891
|
+
return this.indexes[indexName];
|
|
892
|
+
}
|
|
893
|
+
throw new Error(`Index ${indexName} does not exist`);
|
|
894
|
+
}
|
|
895
|
+
put(item) {
|
|
896
|
+
return new PutBuilder(
|
|
897
|
+
item,
|
|
898
|
+
this.expressionBuilder,
|
|
899
|
+
(operation) => this.executeOperation(operation)
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
update(key, data) {
|
|
903
|
+
const builder = new UpdateBuilder(
|
|
904
|
+
key,
|
|
905
|
+
this.expressionBuilder,
|
|
906
|
+
(operation) => this.executeOperation(operation)
|
|
907
|
+
);
|
|
908
|
+
if (data) {
|
|
909
|
+
builder.setMany(data);
|
|
910
|
+
}
|
|
911
|
+
return builder;
|
|
912
|
+
}
|
|
913
|
+
query(key) {
|
|
914
|
+
return new QueryBuilder(
|
|
915
|
+
key,
|
|
916
|
+
this.getIndexConfig(),
|
|
917
|
+
this.expressionBuilder,
|
|
918
|
+
(operation) => this.executeOperation(operation)
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
async get(key, options) {
|
|
922
|
+
const indexConfig = this.getIndexConfig(options?.indexName);
|
|
923
|
+
const keyObject = this.buildKeyFromIndex(key, indexConfig);
|
|
924
|
+
const result = await this.dynamoService.get(keyObject, options);
|
|
925
|
+
return result.Item;
|
|
926
|
+
}
|
|
927
|
+
async delete(key) {
|
|
928
|
+
const operation = {
|
|
929
|
+
type: "delete",
|
|
930
|
+
key
|
|
931
|
+
};
|
|
932
|
+
return this.executeOperation(operation);
|
|
933
|
+
}
|
|
934
|
+
async scan(filters, options) {
|
|
935
|
+
let filter = void 0;
|
|
936
|
+
if (filters?.length) {
|
|
937
|
+
const filterResult = this.expressionBuilder.createExpression(filters);
|
|
938
|
+
filter = {
|
|
939
|
+
expression: filterResult.expression,
|
|
940
|
+
names: filterResult.attributes.names,
|
|
941
|
+
values: filterResult.attributes.values
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
return this.dynamoService.scan({
|
|
945
|
+
filter,
|
|
946
|
+
limit: options?.limit,
|
|
947
|
+
pageKey: options?.pageKey,
|
|
948
|
+
indexName: options?.indexName
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
async batchWrite(operations) {
|
|
952
|
+
const batchOperation = {
|
|
953
|
+
type: "batchWrite",
|
|
954
|
+
operations: operations.map((op) => {
|
|
955
|
+
if (op.type === "put") {
|
|
956
|
+
return { put: op.item };
|
|
957
|
+
}
|
|
958
|
+
return { delete: op.key };
|
|
959
|
+
})
|
|
960
|
+
};
|
|
961
|
+
return this.executeOperation(batchOperation);
|
|
962
|
+
}
|
|
963
|
+
async transactWrite(operations) {
|
|
964
|
+
const transactOperation = {
|
|
965
|
+
type: "transactWrite",
|
|
966
|
+
operations
|
|
967
|
+
};
|
|
968
|
+
return this.executeOperation(transactOperation);
|
|
969
|
+
}
|
|
970
|
+
async executeOperation(operation) {
|
|
971
|
+
switch (operation.type) {
|
|
972
|
+
case "put":
|
|
973
|
+
return this.dynamoService.put({
|
|
974
|
+
item: operation.item,
|
|
975
|
+
condition: operation.condition
|
|
976
|
+
});
|
|
977
|
+
case "update":
|
|
978
|
+
return this.dynamoService.update({
|
|
979
|
+
key: operation.key,
|
|
980
|
+
update: operation.update,
|
|
981
|
+
condition: operation.condition,
|
|
982
|
+
returnValues: "ALL_NEW"
|
|
983
|
+
});
|
|
984
|
+
case "query":
|
|
985
|
+
return this.dynamoService.query({
|
|
986
|
+
keyCondition: operation.keyCondition,
|
|
987
|
+
filter: operation.filter,
|
|
988
|
+
limit: operation.limit,
|
|
989
|
+
indexName: operation.indexName
|
|
990
|
+
});
|
|
991
|
+
case "delete":
|
|
992
|
+
return this.dynamoService.delete({
|
|
993
|
+
key: operation.key
|
|
994
|
+
});
|
|
995
|
+
case "batchWrite":
|
|
996
|
+
return this.dynamoService.batchWrite(operation.operations);
|
|
997
|
+
case "transactWrite":
|
|
998
|
+
return this.dynamoService.transactWrite(operation.operations);
|
|
999
|
+
default:
|
|
1000
|
+
throw new Error("Unknown operation type");
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
buildKeyFromIndex(key, indexConfig) {
|
|
1004
|
+
this.validateKey(key, indexConfig);
|
|
1005
|
+
const keyObject = {
|
|
1006
|
+
[indexConfig.pkName]: key.pk
|
|
1007
|
+
};
|
|
1008
|
+
if (indexConfig.skName && key.sk) {
|
|
1009
|
+
keyObject[indexConfig.skName] = key.sk;
|
|
1010
|
+
}
|
|
1011
|
+
return keyObject;
|
|
1012
|
+
}
|
|
1013
|
+
validateKey(key, indexConfig) {
|
|
1014
|
+
if (!key.pk) {
|
|
1015
|
+
throw new Error("Partition key is required");
|
|
1016
|
+
}
|
|
1017
|
+
if (key.sk && !indexConfig.skName) {
|
|
1018
|
+
throw new Error("Sort key provided but index does not support sort keys");
|
|
1019
|
+
}
|
|
1020
|
+
if (!key.sk && indexConfig.skName) {
|
|
1021
|
+
throw new Error("Index requires a sort key but none was provided");
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
// src/repository/base-repository.ts
|
|
1027
|
+
var BaseRepository = class {
|
|
1028
|
+
constructor(table, schema) {
|
|
1029
|
+
this.table = table;
|
|
1030
|
+
this.schema = schema;
|
|
1031
|
+
}
|
|
1032
|
+
beforeInsert(data) {
|
|
1033
|
+
return data;
|
|
1034
|
+
}
|
|
1035
|
+
beforeUpdate(data) {
|
|
1036
|
+
return data;
|
|
1037
|
+
}
|
|
1038
|
+
async create(data) {
|
|
1039
|
+
const parsed = this.schema.parse(data);
|
|
1040
|
+
const key = this.createPrimaryKey(parsed);
|
|
1041
|
+
const item = {
|
|
1042
|
+
...parsed,
|
|
1043
|
+
...key
|
|
1044
|
+
};
|
|
1045
|
+
const indexConfig = this.table.getIndexConfig();
|
|
1046
|
+
await this.table.put(item).whereNotExists(indexConfig.pkName).execute();
|
|
1047
|
+
return parsed;
|
|
1048
|
+
}
|
|
1049
|
+
async update(key, updates) {
|
|
1050
|
+
const parsed = this.schema.parse(updates);
|
|
1051
|
+
const result = await this.table.update(key).setMany(parsed).execute();
|
|
1052
|
+
return result.Attributes ? this.schema.parse(result.Attributes) : null;
|
|
1053
|
+
}
|
|
1054
|
+
async delete(key) {
|
|
1055
|
+
await this.table.delete(key);
|
|
1056
|
+
}
|
|
1057
|
+
async findOne(key) {
|
|
1058
|
+
const item = await this.table.query(key).where(this.getTypeAttributeName(), "=", this.getType()).execute();
|
|
1059
|
+
if (!item) {
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
return this.schema.parse(item);
|
|
1063
|
+
}
|
|
1064
|
+
async findOrFail(key) {
|
|
1065
|
+
const result = await this.findOne(key);
|
|
1066
|
+
if (!result) {
|
|
1067
|
+
throw new Error("Item not found");
|
|
1068
|
+
}
|
|
1069
|
+
return this.schema.parse(result);
|
|
1070
|
+
}
|
|
1071
|
+
query(key) {
|
|
1072
|
+
return this.table.query(key).where(this.getTypeAttributeName(), "=", this.getType());
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1076
|
+
0 && (module.exports = {
|
|
1077
|
+
BaseRepository,
|
|
1078
|
+
ConditionalCheckFailedError,
|
|
1079
|
+
DynamoError,
|
|
1080
|
+
ExponentialBackoffStrategy,
|
|
1081
|
+
ResourceNotFoundError,
|
|
1082
|
+
Table
|
|
1083
|
+
});
|