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