betterddb 0.6.0 → 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 +29 -17
- 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 +27 -18
- package/lib/operator.js +10 -10
- package/lib/{betterddb.d.ts → types/betterddb.d.ts} +13 -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 +46 -37
- 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 +53 -27
- package/src/index.ts +1 -1
- package/src/operator.ts +21 -21
- package/src/types/paginated-result.ts +1 -1
- package/test/update.test.ts +6 -0
- package/tsconfig.json +25 -11
- package/lib/index.d.ts +0 -1
- package/lib/operator.d.ts +0 -2
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { BetterDDB } from
|
|
2
|
-
import { BatchGetCommand } from
|
|
3
|
-
import { BatchGetItemInput } from
|
|
1
|
+
import { BetterDDB } from "../betterddb";
|
|
2
|
+
import { BatchGetCommand } from "@aws-sdk/lib-dynamodb";
|
|
3
|
+
import { BatchGetItemInput } from "@aws-sdk/client-dynamodb";
|
|
4
4
|
|
|
5
5
|
export class BatchGetBuilder<T> {
|
|
6
6
|
/**
|
|
7
7
|
* @param parent - The BetterDDB instance for the table.
|
|
8
8
|
* @param keys - An array of partial keys for the items you wish to retrieve.
|
|
9
9
|
*/
|
|
10
|
-
constructor(
|
|
10
|
+
constructor(
|
|
11
|
+
private parent: BetterDDB<T>,
|
|
12
|
+
private keys: Partial<T>[],
|
|
13
|
+
) {}
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Executes the batch get operation.
|
|
@@ -29,7 +32,7 @@ export class BatchGetBuilder<T> {
|
|
|
29
32
|
});
|
|
30
33
|
const tableName = this.parent.getTableName();
|
|
31
34
|
// Build an array of keys using the parent's key builder.
|
|
32
|
-
const keysArray = deduplicatedKeys.map(key => this.parent.buildKey(key));
|
|
35
|
+
const keysArray = deduplicatedKeys.map((key) => this.parent.buildKey(key));
|
|
33
36
|
|
|
34
37
|
// Construct the BatchGet parameters.
|
|
35
38
|
const params: BatchGetItemInput = {
|
|
@@ -40,7 +43,9 @@ export class BatchGetBuilder<T> {
|
|
|
40
43
|
},
|
|
41
44
|
};
|
|
42
45
|
|
|
43
|
-
const result = await this.parent
|
|
46
|
+
const result = await this.parent
|
|
47
|
+
.getClient()
|
|
48
|
+
.send(new BatchGetCommand(params));
|
|
44
49
|
const responses = result.Responses ? result.Responses[tableName] : [];
|
|
45
50
|
if (!responses) {
|
|
46
51
|
return [];
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
AttributeValue,
|
|
3
|
+
Put,
|
|
4
|
+
TransactWriteItem,
|
|
5
|
+
} from "@aws-sdk/client-dynamodb";
|
|
6
|
+
import { BetterDDB } from "../betterddb";
|
|
7
|
+
import { PutCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb";
|
|
5
8
|
|
|
6
9
|
export class CreateBuilder<T> {
|
|
7
10
|
private extraTransactItems: TransactWriteItem[] = [];
|
|
8
11
|
|
|
9
|
-
constructor(
|
|
12
|
+
constructor(
|
|
13
|
+
private parent: BetterDDB<T>,
|
|
14
|
+
private item: T,
|
|
15
|
+
) {}
|
|
10
16
|
|
|
11
17
|
public async execute(): Promise<T> {
|
|
12
18
|
const validated = this.parent.getSchema().parse(this.item);
|
|
@@ -15,35 +21,41 @@ export class CreateBuilder<T> {
|
|
|
15
21
|
const myTransactItem = this.toTransactPut();
|
|
16
22
|
// Combine with extra transaction items.
|
|
17
23
|
const allItems = [...this.extraTransactItems, myTransactItem];
|
|
18
|
-
await this.parent.getClient().send(
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
await this.parent.getClient().send(
|
|
25
|
+
new TransactWriteCommand({
|
|
26
|
+
TransactItems: allItems,
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
21
29
|
// After transaction, retrieve the updated item.
|
|
22
30
|
const result = await this.parent.get(this.item).execute();
|
|
23
31
|
if (result === null) {
|
|
24
|
-
throw new Error(
|
|
32
|
+
throw new Error("Item not found after transaction create");
|
|
25
33
|
}
|
|
26
34
|
return result;
|
|
27
35
|
} else {
|
|
36
|
+
let finalItem: T = {
|
|
37
|
+
...this.item,
|
|
38
|
+
entityType: this.parent.getEntityType(),
|
|
39
|
+
};
|
|
40
|
+
if (this.parent.getTimestamps()) {
|
|
41
|
+
const now = new Date().toISOString();
|
|
42
|
+
finalItem = { ...finalItem, createdAt: now, updatedAt: now } as T;
|
|
43
|
+
}
|
|
28
44
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
finalItem = { ...finalItem, createdAt: now, updatedAt: now } as T;
|
|
33
|
-
}
|
|
45
|
+
// Compute and merge primary key.
|
|
46
|
+
const computedKeys = this.parent.buildKey(validated as Partial<T>);
|
|
47
|
+
finalItem = { ...finalItem, ...computedKeys };
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
// Compute and merge index attributes.
|
|
50
|
+
const indexAttributes = this.parent.buildIndexes(validated as Partial<T>);
|
|
51
|
+
finalItem = { ...finalItem, ...indexAttributes };
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Item: finalItem as Record<string, AttributeValue>
|
|
46
|
-
}));
|
|
53
|
+
await this.parent.getClient().send(
|
|
54
|
+
new PutCommand({
|
|
55
|
+
TableName: this.parent.getTableName(),
|
|
56
|
+
Item: finalItem as Record<string, AttributeValue>,
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
47
59
|
|
|
48
60
|
return validated as T;
|
|
49
61
|
}
|
|
@@ -58,9 +70,12 @@ export class CreateBuilder<T> {
|
|
|
58
70
|
return this;
|
|
59
71
|
}
|
|
60
72
|
|
|
61
|
-
public toTransactPut(): TransactWriteItem{
|
|
73
|
+
public toTransactPut(): TransactWriteItem {
|
|
62
74
|
const validated = this.parent.getSchema().parse(this.item);
|
|
63
|
-
let finalItem: T = {
|
|
75
|
+
let finalItem: T = {
|
|
76
|
+
...this.item,
|
|
77
|
+
entityType: this.parent.getEntityType(),
|
|
78
|
+
};
|
|
64
79
|
if (this.parent.getTimestamps()) {
|
|
65
80
|
const now = new Date().toISOString();
|
|
66
81
|
finalItem = { ...finalItem, createdAt: now, updatedAt: now } as T;
|
|
@@ -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
|
}
|