betterddb 0.6.1 → 0.6.4
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/.eslintrc.cjs +41 -0
- package/lib/betterddb.js +26 -18
- package/lib/builders/batch-get-builder.js +6 -2
- package/lib/builders/create-builder.js +14 -6
- package/lib/builders/delete-builder.js +8 -5
- package/lib/builders/get-builder.js +11 -6
- package/lib/builders/query-builder.js +38 -31
- package/lib/builders/scan-builder.js +17 -11
- package/lib/builders/update-builder.js +21 -16
- package/lib/operator.js +10 -10
- package/lib/{betterddb.d.ts → types/betterddb.d.ts} +10 -9
- package/lib/types/betterddb.d.ts.map +1 -0
- package/lib/{builders → types/builders}/batch-get-builder.d.ts +2 -1
- package/lib/types/builders/batch-get-builder.d.ts.map +1 -0
- package/lib/{builders → types/builders}/create-builder.d.ts +3 -2
- package/lib/types/builders/create-builder.d.ts.map +1 -0
- package/lib/{builders → types/builders}/delete-builder.d.ts +3 -2
- package/lib/types/builders/delete-builder.d.ts.map +1 -0
- package/lib/{builders → types/builders}/get-builder.d.ts +3 -2
- package/lib/types/builders/get-builder.d.ts.map +1 -0
- package/lib/{builders → types/builders}/query-builder.d.ts +4 -3
- package/lib/types/builders/query-builder.d.ts.map +1 -0
- package/lib/{builders → types/builders}/scan-builder.d.ts +4 -3
- package/lib/types/builders/scan-builder.d.ts.map +1 -0
- package/lib/{builders → types/builders}/update-builder.d.ts +3 -2
- package/lib/types/builders/update-builder.d.ts.map +1 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/operator.d.ts +3 -0
- package/lib/types/operator.d.ts.map +1 -0
- package/lib/types/{paginated-result.d.ts → types/paginated-result.d.ts} +1 -0
- package/lib/types/types/paginated-result.d.ts.map +1 -0
- package/package.json +15 -5
- package/prettier.config.js +6 -0
- package/src/betterddb.ts +39 -35
- package/src/builders/batch-get-builder.ts +11 -6
- package/src/builders/create-builder.ts +42 -27
- package/src/builders/delete-builder.ts +28 -17
- package/src/builders/get-builder.ts +26 -19
- package/src/builders/query-builder.ts +64 -44
- package/src/builders/scan-builder.ts +18 -14
- package/src/builders/update-builder.ts +49 -28
- package/src/index.ts +1 -1
- package/src/operator.ts +21 -21
- package/src/types/paginated-result.ts +1 -1
- package/tsconfig.json +25 -11
- package/lib/index.d.ts +0 -1
- package/lib/operator.d.ts +0 -2
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import { BetterDDB } from
|
|
2
|
-
import { TransactWriteItem, DeleteItemInput } from
|
|
3
|
-
import { TransactWriteCommand, DeleteCommand } from
|
|
1
|
+
import { BetterDDB } from "../betterddb";
|
|
2
|
+
import { TransactWriteItem, DeleteItemInput } from "@aws-sdk/client-dynamodb";
|
|
3
|
+
import { TransactWriteCommand, DeleteCommand } from "@aws-sdk/lib-dynamodb";
|
|
4
4
|
export class DeleteBuilder<T> {
|
|
5
|
-
private condition?: {
|
|
5
|
+
private condition?: {
|
|
6
|
+
expression: string;
|
|
7
|
+
attributeValues: Record<string, any>;
|
|
8
|
+
};
|
|
6
9
|
private extraTransactItems: TransactWriteItem[] = [];
|
|
7
|
-
constructor(
|
|
10
|
+
constructor(
|
|
11
|
+
private parent: BetterDDB<T>,
|
|
12
|
+
private key: Partial<T>,
|
|
13
|
+
) {}
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* Specify a condition expression for the delete operation.
|
|
11
17
|
*/
|
|
12
|
-
public withCondition(
|
|
18
|
+
public withCondition(
|
|
19
|
+
expression: string,
|
|
20
|
+
attributeValues: Record<string, any>,
|
|
21
|
+
): this {
|
|
13
22
|
if (this.condition) {
|
|
14
23
|
this.condition.expression += ` AND ${expression}`;
|
|
15
24
|
Object.assign(this.condition.attributeValues, attributeValues);
|
|
@@ -25,21 +34,23 @@ export class DeleteBuilder<T> {
|
|
|
25
34
|
const myTransactItem = this.toTransactDelete();
|
|
26
35
|
// Combine with extra transaction items.
|
|
27
36
|
const allItems = [...this.extraTransactItems, myTransactItem];
|
|
28
|
-
await this.parent.getClient().send(
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
await this.parent.getClient().send(
|
|
38
|
+
new TransactWriteCommand({
|
|
39
|
+
TransactItems: allItems,
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
31
42
|
// After transaction, retrieve the updated item.
|
|
32
43
|
const result = await this.parent.get(this.key).execute();
|
|
33
44
|
if (result === null) {
|
|
34
|
-
throw new Error(
|
|
45
|
+
throw new Error("Item not found after transaction delete");
|
|
35
46
|
}
|
|
36
47
|
} else {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
const params: DeleteItemInput = {
|
|
49
|
+
TableName: this.parent.getTableName(),
|
|
50
|
+
Key: this.parent.buildKey(this.key),
|
|
51
|
+
};
|
|
52
|
+
if (this.condition) {
|
|
53
|
+
params.ConditionExpression = this.condition.expression;
|
|
43
54
|
params.ExpressionAttributeValues = this.condition.attributeValues;
|
|
44
55
|
}
|
|
45
56
|
await this.parent.getClient().send(new DeleteCommand(params));
|
|
@@ -58,7 +69,7 @@ export class DeleteBuilder<T> {
|
|
|
58
69
|
public toTransactDelete(): TransactWriteItem {
|
|
59
70
|
const deleteItem: DeleteItemInput = {
|
|
60
71
|
TableName: this.parent.getTableName(),
|
|
61
|
-
Key: this.parent.buildKey(this.key)
|
|
72
|
+
Key: this.parent.buildKey(this.key),
|
|
62
73
|
};
|
|
63
74
|
if (this.condition) {
|
|
64
75
|
deleteItem.ConditionExpression = this.condition.expression;
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { BetterDDB } from
|
|
2
|
-
import { TransactGetCommand, GetCommand } from
|
|
3
|
-
import { GetItemInput, TransactGetItem } from
|
|
1
|
+
import { BetterDDB } from "../betterddb";
|
|
2
|
+
import { TransactGetCommand, GetCommand } from "@aws-sdk/lib-dynamodb";
|
|
3
|
+
import { GetItemInput, TransactGetItem } from "@aws-sdk/client-dynamodb";
|
|
4
4
|
export class GetBuilder<T> {
|
|
5
5
|
private projectionExpression?: string;
|
|
6
6
|
private expressionAttributeNames: Record<string, string> = {};
|
|
7
7
|
private extraTransactItems: TransactGetItem[] = [];
|
|
8
|
-
constructor(
|
|
8
|
+
constructor(
|
|
9
|
+
private parent: BetterDDB<T>,
|
|
10
|
+
private key: Partial<T>,
|
|
11
|
+
) {}
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Specify a projection by providing an array of attribute names.
|
|
12
15
|
*/
|
|
13
16
|
public withProjection(attributes: (keyof T)[]): this {
|
|
14
|
-
this.projectionExpression = attributes
|
|
17
|
+
this.projectionExpression = attributes
|
|
18
|
+
.map((attr) => `#${String(attr)}`)
|
|
19
|
+
.join(", ");
|
|
15
20
|
for (const attr of attributes) {
|
|
16
21
|
this.expressionAttributeNames[`#${String(attr)}`] = String(attr);
|
|
17
22
|
}
|
|
@@ -24,23 +29,25 @@ export class GetBuilder<T> {
|
|
|
24
29
|
const myTransactItem = this.toTransactGet();
|
|
25
30
|
// Combine with extra transaction items.
|
|
26
31
|
const allItems = [...this.extraTransactItems, myTransactItem];
|
|
27
|
-
await this.parent.getClient().send(
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
await this.parent.getClient().send(
|
|
33
|
+
new TransactGetCommand({
|
|
34
|
+
TransactItems: allItems,
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
30
37
|
// After transaction, retrieve the updated item.
|
|
31
38
|
const result = await this.parent.get(this.key).execute();
|
|
32
39
|
return result;
|
|
33
40
|
} else {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
const params: GetItemInput = {
|
|
42
|
+
TableName: this.parent.getTableName(),
|
|
43
|
+
Key: this.parent.buildKey(this.key),
|
|
44
|
+
};
|
|
45
|
+
if (this.projectionExpression) {
|
|
46
|
+
params.ProjectionExpression = this.projectionExpression;
|
|
47
|
+
params.ExpressionAttributeNames = this.expressionAttributeNames;
|
|
48
|
+
}
|
|
49
|
+
const result = await this.parent.getClient().send(new GetCommand(params));
|
|
50
|
+
if (!result.Item) return null;
|
|
44
51
|
return this.parent.getSchema().parse(result.Item) as T;
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -57,7 +64,7 @@ export class GetBuilder<T> {
|
|
|
57
64
|
public toTransactGet(): TransactGetItem {
|
|
58
65
|
const getItem: GetItemInput = {
|
|
59
66
|
TableName: this.parent.getTableName(),
|
|
60
|
-
Key: this.parent.buildKey(this.key)
|
|
67
|
+
Key: this.parent.buildKey(this.key),
|
|
61
68
|
};
|
|
62
69
|
if (this.projectionExpression) {
|
|
63
70
|
getItem.ProjectionExpression = this.projectionExpression;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { QueryCommand, QueryCommandInput } from
|
|
2
|
-
import { BetterDDB, GSIConfig } from
|
|
3
|
-
import { getOperatorExpression, Operator } from
|
|
4
|
-
import { PaginatedResult } from
|
|
1
|
+
import { QueryCommand, QueryCommandInput } from "@aws-sdk/lib-dynamodb";
|
|
2
|
+
import { BetterDDB, GSIConfig } from "../betterddb";
|
|
3
|
+
import { getOperatorExpression, Operator } from "../operator";
|
|
4
|
+
import { PaginatedResult } from "../types/paginated-result";
|
|
5
5
|
|
|
6
6
|
export class QueryBuilder<T> {
|
|
7
7
|
private keyConditions: string[] = [];
|
|
@@ -13,29 +13,32 @@ export class QueryBuilder<T> {
|
|
|
13
13
|
private lastKey?: Record<string, any>;
|
|
14
14
|
private ascending: boolean = true;
|
|
15
15
|
|
|
16
|
-
constructor(
|
|
16
|
+
constructor(
|
|
17
|
+
private parent: BetterDDB<T>,
|
|
18
|
+
private key: Partial<T>,
|
|
19
|
+
) {
|
|
17
20
|
const keys = this.parent.getKeys();
|
|
18
21
|
let pkName = keys.primary.name;
|
|
19
22
|
let builtKey = this.parent.buildKey(this.key) as Record<string, any>;
|
|
20
|
-
|
|
21
|
-
this.expressionAttributeNames[
|
|
22
|
-
this.expressionAttributeValues[
|
|
23
|
+
|
|
24
|
+
this.expressionAttributeNames["#pk"] = pkName;
|
|
25
|
+
this.expressionAttributeValues[":pk_value"] = builtKey[pkName];
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
public usingIndex(indexName: string): this {
|
|
26
29
|
if (!this.parent.getKeys().gsis) {
|
|
27
|
-
throw new Error(
|
|
30
|
+
throw new Error("No global secondary indexes defined for this table");
|
|
28
31
|
}
|
|
29
32
|
if (!(indexName in this.parent.getKeys().gsis!)) {
|
|
30
|
-
throw new Error(
|
|
33
|
+
throw new Error("index does not exist");
|
|
31
34
|
}
|
|
32
|
-
|
|
35
|
+
|
|
33
36
|
this.index = this.parent.getKeys().gsis![indexName];
|
|
34
37
|
|
|
35
|
-
const pkName = this.index
|
|
38
|
+
const pkName = this.index!.primary.name;
|
|
36
39
|
const builtKey = this.parent.buildIndexes(this.key);
|
|
37
|
-
this.expressionAttributeNames[
|
|
38
|
-
this.expressionAttributeValues[
|
|
40
|
+
this.expressionAttributeNames["#pk"] = pkName;
|
|
41
|
+
this.expressionAttributeValues[":pk_value"] = builtKey[pkName];
|
|
39
42
|
|
|
40
43
|
return this;
|
|
41
44
|
}
|
|
@@ -52,28 +55,32 @@ export class QueryBuilder<T> {
|
|
|
52
55
|
|
|
53
56
|
public where(
|
|
54
57
|
operator: Operator,
|
|
55
|
-
values: Partial<T> | [Partial<T>, Partial<T>]
|
|
58
|
+
values: Partial<T> | [Partial<T>, Partial<T>],
|
|
56
59
|
): this {
|
|
57
60
|
const keys = this.parent.getKeys();
|
|
58
61
|
// Determine the sort key name from either the index or the primary keys.
|
|
59
62
|
const sortKeyName = this.index ? this.index.sort?.name : keys.sort?.name;
|
|
60
63
|
if (!sortKeyName) {
|
|
61
|
-
throw new Error(
|
|
64
|
+
throw new Error("Sort key is not defined for this table/index.");
|
|
62
65
|
}
|
|
63
|
-
const nameKey =
|
|
66
|
+
const nameKey = "#sk";
|
|
64
67
|
this.expressionAttributeNames[nameKey] = sortKeyName;
|
|
65
|
-
|
|
68
|
+
|
|
66
69
|
// Enforce that a complex sort key requires an object input.
|
|
67
|
-
if (typeof values !==
|
|
68
|
-
throw new Error(
|
|
70
|
+
if (typeof values !== "object" || values === null) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`For complex sort keys, please provide an object with all necessary properties.`,
|
|
73
|
+
);
|
|
69
74
|
}
|
|
70
|
-
|
|
71
|
-
if (operator ===
|
|
75
|
+
|
|
76
|
+
if (operator === "between") {
|
|
72
77
|
if (!Array.isArray(values) || values.length !== 2) {
|
|
73
|
-
throw new Error(
|
|
78
|
+
throw new Error(
|
|
79
|
+
`For 'between' operator, values must be a tuple of two objects`,
|
|
80
|
+
);
|
|
74
81
|
}
|
|
75
|
-
const valueKeyStart =
|
|
76
|
-
const valueKeyEnd =
|
|
82
|
+
const valueKeyStart = ":sk_start";
|
|
83
|
+
const valueKeyEnd = ":sk_end";
|
|
77
84
|
// Use the key definition's build function to build the key from the full object.
|
|
78
85
|
this.expressionAttributeValues[valueKeyStart] = this.index
|
|
79
86
|
? this.parent.buildIndexes(values[0])[sortKeyName]
|
|
@@ -81,16 +88,18 @@ export class QueryBuilder<T> {
|
|
|
81
88
|
this.expressionAttributeValues[valueKeyEnd] = this.index
|
|
82
89
|
? this.parent.buildIndexes(values[1])[sortKeyName]
|
|
83
90
|
: this.parent.buildKey(values[1])[sortKeyName];
|
|
84
|
-
this.keyConditions.push(
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
this.keyConditions.push(
|
|
92
|
+
`${nameKey} BETWEEN ${valueKeyStart} AND ${valueKeyEnd}`,
|
|
93
|
+
);
|
|
94
|
+
} else if (operator === "begins_with") {
|
|
95
|
+
const valueKey = ":sk_value";
|
|
87
96
|
this.expressionAttributeValues[valueKey] = this.index
|
|
88
97
|
? this.parent.buildIndexes(values as Partial<T>)[sortKeyName]
|
|
89
98
|
: this.parent.buildKey(values as Partial<T>)[sortKeyName];
|
|
90
99
|
this.keyConditions.push(`begins_with(${nameKey}, ${valueKey})`);
|
|
91
100
|
} else {
|
|
92
101
|
// For eq, lt, lte, gt, gte:
|
|
93
|
-
const valueKey =
|
|
102
|
+
const valueKey = ":sk_value";
|
|
94
103
|
this.expressionAttributeValues[valueKey] = this.index
|
|
95
104
|
? this.parent.buildIndexes(values as Partial<T>)[sortKeyName]
|
|
96
105
|
: this.parent.buildKey(values as Partial<T>)[sortKeyName];
|
|
@@ -99,36 +108,43 @@ export class QueryBuilder<T> {
|
|
|
99
108
|
}
|
|
100
109
|
return this;
|
|
101
110
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
111
|
|
|
106
112
|
public filter(
|
|
107
113
|
attribute: keyof T,
|
|
108
114
|
operator: Operator,
|
|
109
|
-
values: any | [any, any]
|
|
115
|
+
values: any | [any, any],
|
|
110
116
|
): this {
|
|
111
117
|
const attrStr = String(attribute);
|
|
112
118
|
const randomString = Math.random().toString(36).substring(2, 15);
|
|
113
119
|
const placeholderName = `#attr_${attrStr}_${randomString}`;
|
|
114
120
|
this.expressionAttributeNames[placeholderName] = attrStr;
|
|
115
|
-
if (operator ===
|
|
121
|
+
if (operator === "between") {
|
|
116
122
|
if (!Array.isArray(values) || values.length !== 2) {
|
|
117
|
-
throw new Error(
|
|
123
|
+
throw new Error(
|
|
124
|
+
"For 'between' operator, values must be a tuple of two items",
|
|
125
|
+
);
|
|
118
126
|
}
|
|
119
127
|
const placeholderValueStart = `:val_start_${attrStr}_${randomString}`;
|
|
120
128
|
const placeholderValueEnd = `:val_end_${attrStr}_${randomString}`;
|
|
121
129
|
this.expressionAttributeValues[placeholderValueStart] = values[0];
|
|
122
130
|
this.expressionAttributeValues[placeholderValueEnd] = values[1];
|
|
123
|
-
this.filterConditions.push(
|
|
124
|
-
|
|
131
|
+
this.filterConditions.push(
|
|
132
|
+
`${placeholderName} BETWEEN ${placeholderValueStart} AND ${placeholderValueEnd}`,
|
|
133
|
+
);
|
|
134
|
+
} else if (operator === "begins_with" || operator === "contains") {
|
|
125
135
|
const placeholderValue = `:val_${attrStr}_${randomString}`;
|
|
126
136
|
this.expressionAttributeValues[placeholderValue] = values;
|
|
127
|
-
this.filterConditions.push(
|
|
137
|
+
this.filterConditions.push(
|
|
138
|
+
`${operator}(${placeholderName}, ${placeholderValue})`,
|
|
139
|
+
);
|
|
128
140
|
} else {
|
|
129
141
|
const placeholderValue = `:val_${attrStr}_${randomString}`;
|
|
130
142
|
this.expressionAttributeValues[placeholderValue] = values;
|
|
131
|
-
const condition = getOperatorExpression(
|
|
143
|
+
const condition = getOperatorExpression(
|
|
144
|
+
operator,
|
|
145
|
+
placeholderName,
|
|
146
|
+
placeholderValue,
|
|
147
|
+
);
|
|
132
148
|
this.filterConditions.push(condition);
|
|
133
149
|
}
|
|
134
150
|
|
|
@@ -150,7 +166,7 @@ export class QueryBuilder<T> {
|
|
|
150
166
|
*/
|
|
151
167
|
public async execute(): Promise<PaginatedResult<T>> {
|
|
152
168
|
this.keyConditions.unshift(`#pk = :pk_value`);
|
|
153
|
-
const keyConditionExpression = this.keyConditions.join(
|
|
169
|
+
const keyConditionExpression = this.keyConditions.join(" AND ");
|
|
154
170
|
|
|
155
171
|
const params: QueryCommandInput = {
|
|
156
172
|
TableName: this.parent.getTableName(),
|
|
@@ -165,12 +181,16 @@ export class QueryBuilder<T> {
|
|
|
165
181
|
|
|
166
182
|
if (this.parent.getEntityType()) {
|
|
167
183
|
this.filterConditions.push(`#entity = :entity_value`);
|
|
168
|
-
this.expressionAttributeNames[
|
|
169
|
-
this.expressionAttributeValues[
|
|
184
|
+
this.expressionAttributeNames["#entity"] = "entityType";
|
|
185
|
+
this.expressionAttributeValues[":entity_value"] =
|
|
186
|
+
this.parent.getEntityType();
|
|
170
187
|
}
|
|
171
|
-
params.FilterExpression = this.filterConditions.join(
|
|
188
|
+
params.FilterExpression = this.filterConditions.join(" AND ");
|
|
172
189
|
|
|
173
190
|
const result = await this.parent.getClient().send(new QueryCommand(params));
|
|
174
|
-
return {
|
|
191
|
+
return {
|
|
192
|
+
items: this.parent.getSchema().array().parse(result.Items) as T[],
|
|
193
|
+
lastKey: result.LastEvaluatedKey ?? undefined,
|
|
194
|
+
};
|
|
175
195
|
}
|
|
176
196
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ScanCommand, ScanCommandInput } from
|
|
2
|
-
import { BetterDDB } from
|
|
3
|
-
import { getOperatorExpression, Operator } from
|
|
4
|
-
import { PaginatedResult } from
|
|
1
|
+
import { ScanCommand, ScanCommandInput } from "@aws-sdk/lib-dynamodb";
|
|
2
|
+
import { BetterDDB } from "../betterddb";
|
|
3
|
+
import { getOperatorExpression, Operator } from "../operator";
|
|
4
|
+
import { PaginatedResult } from "../types/paginated-result";
|
|
5
5
|
|
|
6
6
|
export class ScanBuilder<T> {
|
|
7
7
|
private filters: string[] = [];
|
|
@@ -15,16 +15,16 @@ export class ScanBuilder<T> {
|
|
|
15
15
|
public where(
|
|
16
16
|
attribute: keyof T,
|
|
17
17
|
operator: Operator,
|
|
18
|
-
values: any | [any, any]
|
|
18
|
+
values: any | [any, any],
|
|
19
19
|
): this {
|
|
20
20
|
const attrStr = String(attribute);
|
|
21
21
|
const nameKey = `#attr_${attrStr}`;
|
|
22
22
|
this.expressionAttributeNames[nameKey] = attrStr;
|
|
23
23
|
|
|
24
|
-
if (operator ===
|
|
24
|
+
if (operator === "between") {
|
|
25
25
|
if (!Array.isArray(values) || values.length !== 2) {
|
|
26
26
|
throw new Error(
|
|
27
|
-
`For 'between' operator, values must be a tuple of two items
|
|
27
|
+
`For 'between' operator, values must be a tuple of two items`,
|
|
28
28
|
);
|
|
29
29
|
}
|
|
30
30
|
const valueKeyStart = `:val_start_${attrStr}`;
|
|
@@ -32,9 +32,9 @@ export class ScanBuilder<T> {
|
|
|
32
32
|
this.expressionAttributeValues[valueKeyStart] = values[0];
|
|
33
33
|
this.expressionAttributeValues[valueKeyEnd] = values[1];
|
|
34
34
|
this.filters.push(
|
|
35
|
-
`${nameKey} BETWEEN ${valueKeyStart} AND ${valueKeyEnd}
|
|
35
|
+
`${nameKey} BETWEEN ${valueKeyStart} AND ${valueKeyEnd}`,
|
|
36
36
|
);
|
|
37
|
-
} else if (operator ===
|
|
37
|
+
} else if (operator === "begins_with" || operator === "contains") {
|
|
38
38
|
const valueKey = `:val_${attrStr}`;
|
|
39
39
|
this.expressionAttributeValues[valueKey] = values;
|
|
40
40
|
this.filters.push(`${operator}(${nameKey}, ${valueKey})`);
|
|
@@ -66,18 +66,22 @@ export class ScanBuilder<T> {
|
|
|
66
66
|
ExpressionAttributeNames: this.expressionAttributeNames,
|
|
67
67
|
ExpressionAttributeValues: this.expressionAttributeValues,
|
|
68
68
|
Limit: this.limit,
|
|
69
|
-
ExclusiveStartKey: this.lastKey
|
|
69
|
+
ExclusiveStartKey: this.lastKey,
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
if (this.parent.getEntityType()) {
|
|
73
73
|
this.filters.push(`#entity = :entity_value`);
|
|
74
|
-
this.expressionAttributeNames[
|
|
75
|
-
this.expressionAttributeValues[
|
|
74
|
+
this.expressionAttributeNames["#entity"] = "entityType";
|
|
75
|
+
this.expressionAttributeValues[":entity_value"] =
|
|
76
|
+
this.parent.getEntityType();
|
|
76
77
|
}
|
|
77
|
-
params.FilterExpression = this.filters.join(
|
|
78
|
+
params.FilterExpression = this.filters.join(" AND ");
|
|
78
79
|
|
|
79
80
|
const result = await this.parent.getClient().send(new ScanCommand(params));
|
|
80
81
|
|
|
81
|
-
return {
|
|
82
|
+
return {
|
|
83
|
+
items: this.parent.getSchema().array().parse(result.Items) as T[],
|
|
84
|
+
lastKey: result.LastEvaluatedKey ?? undefined,
|
|
85
|
+
};
|
|
82
86
|
}
|
|
83
87
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { TransactWriteCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";
|
|
2
|
+
import { BetterDDB } from "../betterddb";
|
|
3
|
+
import {
|
|
4
|
+
TransactWriteItem,
|
|
5
|
+
Update,
|
|
6
|
+
UpdateItemInput,
|
|
7
|
+
} from "@aws-sdk/client-dynamodb";
|
|
8
|
+
import { z } from "zod";
|
|
7
9
|
interface UpdateActions<T> {
|
|
8
10
|
set?: Partial<T>;
|
|
9
11
|
remove?: (keyof T)[];
|
|
@@ -13,16 +15,24 @@ interface UpdateActions<T> {
|
|
|
13
15
|
|
|
14
16
|
export class UpdateBuilder<T> {
|
|
15
17
|
private actions: UpdateActions<T> = {};
|
|
16
|
-
private condition?: {
|
|
18
|
+
private condition?: {
|
|
19
|
+
expression: string;
|
|
20
|
+
attributeValues: Record<string, any>;
|
|
21
|
+
};
|
|
17
22
|
// When using transaction mode, we store extra transaction items.
|
|
18
23
|
private extraTransactItems: TransactWriteItem[] = [];
|
|
19
24
|
|
|
20
25
|
// Reference to the parent BetterDDB instance and key.
|
|
21
|
-
constructor(
|
|
26
|
+
constructor(
|
|
27
|
+
private parent: BetterDDB<T>,
|
|
28
|
+
private key: Partial<T>,
|
|
29
|
+
) {}
|
|
22
30
|
|
|
23
31
|
// Chainable methods:
|
|
24
32
|
public set(attrs: Partial<T>): this {
|
|
25
|
-
const partialSchema = (
|
|
33
|
+
const partialSchema = (
|
|
34
|
+
this.parent.getSchema() as unknown as z.ZodObject<any>
|
|
35
|
+
).partial();
|
|
26
36
|
const validated = partialSchema.parse(attrs);
|
|
27
37
|
this.actions.set = { ...this.actions.set, ...validated };
|
|
28
38
|
return this;
|
|
@@ -34,7 +44,9 @@ export class UpdateBuilder<T> {
|
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
public add(attrs: Partial<Record<keyof T, number | Set<any>>>): this {
|
|
37
|
-
const partialSchema = (
|
|
47
|
+
const partialSchema = (
|
|
48
|
+
this.parent.getSchema() as unknown as z.ZodObject<any>
|
|
49
|
+
).partial();
|
|
38
50
|
const validated = partialSchema.parse(attrs);
|
|
39
51
|
this.actions.add = { ...this.actions.add, ...validated };
|
|
40
52
|
return this;
|
|
@@ -48,7 +60,10 @@ export class UpdateBuilder<T> {
|
|
|
48
60
|
/**
|
|
49
61
|
* Adds a condition expression to the update.
|
|
50
62
|
*/
|
|
51
|
-
public setCondition(
|
|
63
|
+
public setCondition(
|
|
64
|
+
expression: string,
|
|
65
|
+
attributeValues: Record<string, any>,
|
|
66
|
+
): this {
|
|
52
67
|
if (this.condition) {
|
|
53
68
|
// Merge conditions with AND.
|
|
54
69
|
this.condition.expression += ` AND ${expression}`;
|
|
@@ -94,18 +109,18 @@ export class UpdateBuilder<T> {
|
|
|
94
109
|
setParts.push(`${nameKey} = ${valueKey}`);
|
|
95
110
|
}
|
|
96
111
|
if (setParts.length > 0) {
|
|
97
|
-
clauses.push(`SET ${setParts.join(
|
|
112
|
+
clauses.push(`SET ${setParts.join(", ")}`);
|
|
98
113
|
}
|
|
99
114
|
}
|
|
100
115
|
|
|
101
116
|
// Build REMOVE clause.
|
|
102
117
|
if (this.actions.remove && this.actions.remove.length > 0) {
|
|
103
|
-
const removeParts = this.actions.remove.map(attr => {
|
|
118
|
+
const removeParts = this.actions.remove.map((attr) => {
|
|
104
119
|
const nameKey = `#remove_${String(attr)}`;
|
|
105
120
|
ExpressionAttributeNames[nameKey] = String(attr);
|
|
106
121
|
return nameKey;
|
|
107
122
|
});
|
|
108
|
-
clauses.push(`REMOVE ${removeParts.join(
|
|
123
|
+
clauses.push(`REMOVE ${removeParts.join(", ")}`);
|
|
109
124
|
}
|
|
110
125
|
|
|
111
126
|
// Build ADD clause.
|
|
@@ -119,7 +134,7 @@ export class UpdateBuilder<T> {
|
|
|
119
134
|
addParts.push(`${nameKey} ${valueKey}`);
|
|
120
135
|
}
|
|
121
136
|
if (addParts.length > 0) {
|
|
122
|
-
clauses.push(`ADD ${addParts.join(
|
|
137
|
+
clauses.push(`ADD ${addParts.join(", ")}`);
|
|
123
138
|
}
|
|
124
139
|
}
|
|
125
140
|
|
|
@@ -134,7 +149,7 @@ export class UpdateBuilder<T> {
|
|
|
134
149
|
deleteParts.push(`${nameKey} ${valueKey}`);
|
|
135
150
|
}
|
|
136
151
|
if (deleteParts.length > 0) {
|
|
137
|
-
clauses.push(`DELETE ${deleteParts.join(
|
|
152
|
+
clauses.push(`DELETE ${deleteParts.join(", ")}`);
|
|
138
153
|
}
|
|
139
154
|
}
|
|
140
155
|
|
|
@@ -144,9 +159,9 @@ export class UpdateBuilder<T> {
|
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
return {
|
|
147
|
-
updateExpression: clauses.join(
|
|
162
|
+
updateExpression: clauses.join(" "),
|
|
148
163
|
attributeNames: ExpressionAttributeNames,
|
|
149
|
-
attributeValues: ExpressionAttributeValues
|
|
164
|
+
attributeValues: ExpressionAttributeValues,
|
|
150
165
|
};
|
|
151
166
|
}
|
|
152
167
|
|
|
@@ -154,13 +169,14 @@ export class UpdateBuilder<T> {
|
|
|
154
169
|
* Returns a transaction update item that can be included in a transactWrite call.
|
|
155
170
|
*/
|
|
156
171
|
public toTransactUpdate(): TransactWriteItem {
|
|
157
|
-
const { updateExpression, attributeNames, attributeValues } =
|
|
172
|
+
const { updateExpression, attributeNames, attributeValues } =
|
|
173
|
+
this.buildExpression();
|
|
158
174
|
const updateItem: Update = {
|
|
159
175
|
TableName: this.parent.getTableName(),
|
|
160
176
|
Key: this.parent.buildKey(this.key),
|
|
161
177
|
UpdateExpression: updateExpression,
|
|
162
178
|
ExpressionAttributeNames: attributeNames,
|
|
163
|
-
ExpressionAttributeValues: attributeValues
|
|
179
|
+
ExpressionAttributeValues: attributeValues,
|
|
164
180
|
};
|
|
165
181
|
if (this.condition && this.condition.expression) {
|
|
166
182
|
updateItem.ConditionExpression = this.condition.expression;
|
|
@@ -177,32 +193,37 @@ export class UpdateBuilder<T> {
|
|
|
177
193
|
const myTransactItem = this.toTransactUpdate();
|
|
178
194
|
// Combine with extra transaction items.
|
|
179
195
|
const allItems = [...this.extraTransactItems, myTransactItem];
|
|
180
|
-
await this.parent.getClient().send(
|
|
181
|
-
|
|
182
|
-
|
|
196
|
+
await this.parent.getClient().send(
|
|
197
|
+
new TransactWriteCommand({
|
|
198
|
+
TransactItems: allItems,
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
183
201
|
// After transaction, retrieve the updated item.
|
|
184
202
|
const result = await this.parent.get(this.key).execute();
|
|
185
203
|
if (result === null) {
|
|
186
|
-
throw new Error(
|
|
204
|
+
throw new Error("Item not found after transaction update");
|
|
187
205
|
}
|
|
188
206
|
return result;
|
|
189
207
|
} else {
|
|
190
208
|
// Normal update flow.
|
|
191
|
-
const { updateExpression, attributeNames, attributeValues } =
|
|
209
|
+
const { updateExpression, attributeNames, attributeValues } =
|
|
210
|
+
this.buildExpression();
|
|
192
211
|
const params: UpdateItemInput = {
|
|
193
212
|
TableName: this.parent.getTableName(),
|
|
194
213
|
Key: this.parent.buildKey(this.key),
|
|
195
214
|
UpdateExpression: updateExpression,
|
|
196
215
|
ExpressionAttributeNames: attributeNames,
|
|
197
216
|
ExpressionAttributeValues: attributeValues,
|
|
198
|
-
ReturnValues:
|
|
217
|
+
ReturnValues: "ALL_NEW",
|
|
199
218
|
};
|
|
200
219
|
if (this.condition && this.condition.expression) {
|
|
201
220
|
params.ConditionExpression = this.condition.expression;
|
|
202
221
|
}
|
|
203
|
-
const result = await this.parent
|
|
222
|
+
const result = await this.parent
|
|
223
|
+
.getClient()
|
|
224
|
+
.send(new UpdateCommand(params));
|
|
204
225
|
if (!result.Attributes) {
|
|
205
|
-
throw new Error(
|
|
226
|
+
throw new Error("No attributes returned after update");
|
|
206
227
|
}
|
|
207
228
|
return this.parent.getSchema().parse(result.Attributes) as T;
|
|
208
229
|
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./betterddb";
|