betterddb 0.4.8 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/betterddb.d.ts +7 -6
- package/lib/betterddb.js +9 -6
- package/lib/builders/create-builder.js +4 -6
- package/lib/builders/query-builder.d.ts +7 -4
- package/lib/builders/query-builder.js +80 -31
- package/lib/builders/scan-builder.d.ts +4 -2
- package/lib/builders/scan-builder.js +13 -13
- package/lib/builders/update-builder.d.ts +1 -2
- package/lib/builders/update-builder.js +1 -24
- package/lib/operator.d.ts +2 -0
- package/lib/operator.js +31 -0
- package/lib/types/paginated-result.d.ts +4 -0
- package/lib/types/paginated-result.js +2 -0
- package/package.json +1 -1
- package/src/betterddb.ts +14 -10
- package/src/builders/create-builder.ts +5 -6
- package/src/builders/query-builder.ts +90 -35
- package/src/builders/scan-builder.ts +21 -15
- package/src/builders/update-builder.ts +1 -27
- package/src/operator.ts +43 -0
- package/src/types/paginated-result.ts +4 -0
- package/test/batch-get.test.ts +31 -13
- package/test/create.test.ts +30 -12
- package/test/delete.test.ts +29 -11
- package/test/get.test.ts +30 -11
- package/test/query.test.ts +118 -23
- package/test/scan.test.ts +64 -21
- package/test/update.test.ts +29 -12
- package/test/utils/table-setup.ts +2 -10
package/README.md
CHANGED
package/lib/betterddb.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export interface KeysConfig<T> {
|
|
|
63
63
|
export interface BetterDDBOptions<T> {
|
|
64
64
|
schema: z.ZodType<T, z.ZodTypeDef, any>;
|
|
65
65
|
tableName: string;
|
|
66
|
-
|
|
66
|
+
entityType: string;
|
|
67
67
|
keys: KeysConfig<T>;
|
|
68
68
|
client: DynamoDBDocumentClient;
|
|
69
69
|
/**
|
|
@@ -73,7 +73,7 @@ export interface BetterDDBOptions<T> {
|
|
|
73
73
|
*
|
|
74
74
|
* (T should include these fields if enabled.)
|
|
75
75
|
*/
|
|
76
|
-
|
|
76
|
+
timestamps?: boolean;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
79
|
* BetterDDB is a definition-based DynamoDB wrapper library.
|
|
@@ -81,16 +81,17 @@ export interface BetterDDBOptions<T> {
|
|
|
81
81
|
export declare class BetterDDB<T> {
|
|
82
82
|
protected schema: z.ZodType<T, z.ZodTypeDef, any>;
|
|
83
83
|
protected tableName: string;
|
|
84
|
-
protected
|
|
84
|
+
protected entityType: string;
|
|
85
85
|
protected client: DynamoDBDocumentClient;
|
|
86
86
|
protected keys: KeysConfig<T>;
|
|
87
|
-
protected
|
|
87
|
+
protected timestamps: boolean;
|
|
88
88
|
constructor(options: BetterDDBOptions<T>);
|
|
89
89
|
getKeys(): KeysConfig<T>;
|
|
90
90
|
getTableName(): string;
|
|
91
91
|
getClient(): DynamoDBDocumentClient;
|
|
92
92
|
getSchema(): z.ZodType<T, z.ZodTypeDef, any>;
|
|
93
|
-
|
|
93
|
+
getTimestamps(): boolean;
|
|
94
|
+
getEntityType(): string;
|
|
94
95
|
protected getKeyValue(def: KeyDefinition<T>, rawKey: Partial<T>): string;
|
|
95
96
|
/**
|
|
96
97
|
* Build the primary key from a raw key object.
|
|
@@ -118,7 +119,7 @@ export declare class BetterDDB<T> {
|
|
|
118
119
|
/**
|
|
119
120
|
* Update an item.
|
|
120
121
|
*/
|
|
121
|
-
update(key: Partial<T
|
|
122
|
+
update(key: Partial<T>): UpdateBuilder<T>;
|
|
122
123
|
/**
|
|
123
124
|
* Delete an item.
|
|
124
125
|
*/
|
package/lib/betterddb.js
CHANGED
|
@@ -16,10 +16,10 @@ class BetterDDB {
|
|
|
16
16
|
var _a;
|
|
17
17
|
this.schema = options.schema;
|
|
18
18
|
this.tableName = options.tableName;
|
|
19
|
-
this.
|
|
19
|
+
this.entityType = options.entityType.toUpperCase();
|
|
20
20
|
this.keys = options.keys;
|
|
21
21
|
this.client = options.client;
|
|
22
|
-
this.
|
|
22
|
+
this.timestamps = (_a = options.timestamps) !== null && _a !== void 0 ? _a : false;
|
|
23
23
|
}
|
|
24
24
|
getKeys() {
|
|
25
25
|
return this.keys;
|
|
@@ -33,8 +33,11 @@ class BetterDDB {
|
|
|
33
33
|
getSchema() {
|
|
34
34
|
return this.schema;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
return this.
|
|
36
|
+
getTimestamps() {
|
|
37
|
+
return this.timestamps;
|
|
38
|
+
}
|
|
39
|
+
getEntityType() {
|
|
40
|
+
return this.entityType;
|
|
38
41
|
}
|
|
39
42
|
// Helper: Retrieve the key value from a KeyDefinition.
|
|
40
43
|
getKeyValue(def, rawKey) {
|
|
@@ -124,8 +127,8 @@ class BetterDDB {
|
|
|
124
127
|
/**
|
|
125
128
|
* Update an item.
|
|
126
129
|
*/
|
|
127
|
-
update(key
|
|
128
|
-
return new update_builder_1.UpdateBuilder(this, key
|
|
130
|
+
update(key) {
|
|
131
|
+
return new update_builder_1.UpdateBuilder(this, key);
|
|
129
132
|
}
|
|
130
133
|
/**
|
|
131
134
|
* Delete an item.
|
|
@@ -9,6 +9,7 @@ class CreateBuilder {
|
|
|
9
9
|
this.extraTransactItems = [];
|
|
10
10
|
}
|
|
11
11
|
async execute() {
|
|
12
|
+
const validated = this.parent.getSchema().parse(this.item);
|
|
12
13
|
if (this.extraTransactItems.length > 0) {
|
|
13
14
|
// Build our update transaction item.
|
|
14
15
|
const myTransactItem = this.toTransactPut();
|
|
@@ -25,14 +26,11 @@ class CreateBuilder {
|
|
|
25
26
|
return result;
|
|
26
27
|
}
|
|
27
28
|
else {
|
|
28
|
-
let
|
|
29
|
-
if (this.parent.
|
|
29
|
+
let finalItem = { ...this.item, entityType: this.parent.getEntityType() };
|
|
30
|
+
if (this.parent.getTimestamps()) {
|
|
30
31
|
const now = new Date().toISOString();
|
|
31
|
-
|
|
32
|
+
finalItem = { ...finalItem, createdAt: now, updatedAt: now };
|
|
32
33
|
}
|
|
33
|
-
// Validate the item using the schema.
|
|
34
|
-
const validated = this.parent.getSchema().parse(item);
|
|
35
|
-
let finalItem = { ...validated };
|
|
36
34
|
// Compute and merge primary key.
|
|
37
35
|
const computedKeys = this.parent.buildKey(validated);
|
|
38
36
|
finalItem = { ...finalItem, ...computedKeys };
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { BetterDDB } from '../betterddb';
|
|
2
|
+
import { Operator } from '../operator';
|
|
3
|
+
import { PaginatedResult } from '../types/paginated-result';
|
|
2
4
|
export declare class QueryBuilder<T> {
|
|
3
5
|
private parent;
|
|
4
6
|
private key;
|
|
5
|
-
private
|
|
7
|
+
private keyConditions;
|
|
8
|
+
private filterConditions;
|
|
6
9
|
private expressionAttributeNames;
|
|
7
10
|
private expressionAttributeValues;
|
|
8
11
|
private index?;
|
|
9
|
-
private sortKeyCondition?;
|
|
10
12
|
private limit?;
|
|
11
13
|
private lastKey?;
|
|
12
14
|
private ascending;
|
|
@@ -14,11 +16,12 @@ export declare class QueryBuilder<T> {
|
|
|
14
16
|
usingIndex(indexName: string): this;
|
|
15
17
|
sortAscending(): this;
|
|
16
18
|
sortDescending(): this;
|
|
17
|
-
where(
|
|
19
|
+
where(operator: Operator, values: Partial<T> | [Partial<T>, Partial<T>]): this;
|
|
20
|
+
filter(attribute: keyof T, operator: Operator, values: any | [any, any]): this;
|
|
18
21
|
limitResults(limit: number): this;
|
|
19
22
|
startFrom(lastKey: Record<string, any>): this;
|
|
20
23
|
/**
|
|
21
24
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
22
25
|
*/
|
|
23
|
-
execute(): Promise<T
|
|
26
|
+
execute(): Promise<PaginatedResult<T>>;
|
|
24
27
|
}
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.QueryBuilder = void 0;
|
|
4
4
|
const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
|
|
5
|
+
const operator_1 = require("../operator");
|
|
5
6
|
class QueryBuilder {
|
|
6
7
|
constructor(parent, key) {
|
|
7
8
|
this.parent = parent;
|
|
8
9
|
this.key = key;
|
|
9
|
-
this.
|
|
10
|
+
this.keyConditions = [];
|
|
11
|
+
this.filterConditions = [];
|
|
10
12
|
this.expressionAttributeNames = {};
|
|
11
13
|
this.expressionAttributeValues = {};
|
|
12
14
|
this.ascending = true;
|
|
@@ -29,32 +31,78 @@ class QueryBuilder {
|
|
|
29
31
|
this.ascending = false;
|
|
30
32
|
return this;
|
|
31
33
|
}
|
|
32
|
-
where(
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.
|
|
39
|
-
|
|
34
|
+
where(operator, values) {
|
|
35
|
+
var _a, _b;
|
|
36
|
+
const keys = this.parent.getKeys();
|
|
37
|
+
// Determine the sort key name from either the index or the primary keys.
|
|
38
|
+
const sortKeyName = this.index ? (_a = this.index.sort) === null || _a === void 0 ? void 0 : _a.name : (_b = keys.sort) === null || _b === void 0 ? void 0 : _b.name;
|
|
39
|
+
if (!sortKeyName) {
|
|
40
|
+
throw new Error('Sort key is not defined for this table/index.');
|
|
41
|
+
}
|
|
42
|
+
const nameKey = '#sk';
|
|
43
|
+
this.expressionAttributeNames[nameKey] = sortKeyName;
|
|
44
|
+
// Enforce that a complex sort key requires an object input.
|
|
45
|
+
if (typeof values !== 'object' || values === null) {
|
|
46
|
+
throw new Error(`For complex sort keys, please provide an object with all necessary properties.`);
|
|
47
|
+
}
|
|
48
|
+
if (operator === 'between') {
|
|
49
|
+
if (!Array.isArray(values) || values.length !== 2) {
|
|
50
|
+
throw new Error(`For 'between' operator, values must be a tuple of two objects`);
|
|
51
|
+
}
|
|
52
|
+
const valueKeyStart = ':sk_start';
|
|
53
|
+
const valueKeyEnd = ':sk_end';
|
|
54
|
+
// Use the key definition's build function to build the key from the full object.
|
|
55
|
+
this.expressionAttributeValues[valueKeyStart] = this.index
|
|
56
|
+
? this.parent.buildIndexes(values[0])[sortKeyName]
|
|
57
|
+
: this.parent.buildKey(values[0])[sortKeyName];
|
|
58
|
+
this.expressionAttributeValues[valueKeyEnd] = this.index
|
|
59
|
+
? this.parent.buildIndexes(values[1])[sortKeyName]
|
|
60
|
+
: this.parent.buildKey(values[1])[sortKeyName];
|
|
61
|
+
this.keyConditions.push(`${nameKey} BETWEEN ${valueKeyStart} AND ${valueKeyEnd}`);
|
|
40
62
|
}
|
|
41
63
|
else if (operator === 'begins_with') {
|
|
42
|
-
const valueKey =
|
|
43
|
-
this.expressionAttributeValues[valueKey] =
|
|
44
|
-
|
|
64
|
+
const valueKey = ':sk_value';
|
|
65
|
+
this.expressionAttributeValues[valueKey] = this.index
|
|
66
|
+
? this.parent.buildIndexes(values)[sortKeyName]
|
|
67
|
+
: this.parent.buildKey(values)[sortKeyName];
|
|
68
|
+
this.keyConditions.push(`begins_with(${nameKey}, ${valueKey})`);
|
|
45
69
|
}
|
|
46
|
-
else
|
|
70
|
+
else {
|
|
71
|
+
// For eq, lt, lte, gt, gte:
|
|
72
|
+
const valueKey = ':sk_value';
|
|
73
|
+
this.expressionAttributeValues[valueKey] = this.index
|
|
74
|
+
? this.parent.buildIndexes(values)[sortKeyName]
|
|
75
|
+
: this.parent.buildKey(values)[sortKeyName];
|
|
76
|
+
const condition = (0, operator_1.getOperatorExpression)(operator, nameKey, valueKey);
|
|
77
|
+
this.keyConditions.push(condition);
|
|
78
|
+
}
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
filter(attribute, operator, values) {
|
|
82
|
+
const attrStr = String(attribute);
|
|
83
|
+
const randomString = Math.random().toString(36).substring(2, 15);
|
|
84
|
+
const placeholderName = `#attr_${attrStr}_${randomString}`;
|
|
85
|
+
this.expressionAttributeNames[placeholderName] = attrStr;
|
|
86
|
+
if (operator === 'between') {
|
|
47
87
|
if (!Array.isArray(values) || values.length !== 2) {
|
|
48
|
-
throw new Error(
|
|
88
|
+
throw new Error("For 'between' operator, values must be a tuple of two items");
|
|
49
89
|
}
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
this.expressionAttributeValues[
|
|
53
|
-
this.expressionAttributeValues[
|
|
54
|
-
this.
|
|
90
|
+
const placeholderValueStart = `:val_start_${attrStr}_${randomString}`;
|
|
91
|
+
const placeholderValueEnd = `:val_end_${attrStr}_${randomString}`;
|
|
92
|
+
this.expressionAttributeValues[placeholderValueStart] = values[0];
|
|
93
|
+
this.expressionAttributeValues[placeholderValueEnd] = values[1];
|
|
94
|
+
this.filterConditions.push(`${placeholderName} BETWEEN ${placeholderValueStart} AND ${placeholderValueEnd}`);
|
|
95
|
+
}
|
|
96
|
+
else if (operator === 'begins_with' || operator === 'contains') {
|
|
97
|
+
const placeholderValue = `:val_${attrStr}_${randomString}`;
|
|
98
|
+
this.expressionAttributeValues[placeholderValue] = values;
|
|
99
|
+
this.filterConditions.push(`${operator}(${placeholderName}, ${placeholderValue})`);
|
|
55
100
|
}
|
|
56
101
|
else {
|
|
57
|
-
|
|
102
|
+
const placeholderValue = `:val_${attrStr}_${randomString}`;
|
|
103
|
+
this.expressionAttributeValues[placeholderValue] = values;
|
|
104
|
+
const condition = (0, operator_1.getOperatorExpression)(operator, placeholderName, placeholderValue);
|
|
105
|
+
this.filterConditions.push(condition);
|
|
58
106
|
}
|
|
59
107
|
return this;
|
|
60
108
|
}
|
|
@@ -70,7 +118,7 @@ class QueryBuilder {
|
|
|
70
118
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
71
119
|
*/
|
|
72
120
|
async execute() {
|
|
73
|
-
var _a, _b;
|
|
121
|
+
var _a, _b, _c;
|
|
74
122
|
const keys = this.parent.getKeys();
|
|
75
123
|
let pkName = keys.primary.name;
|
|
76
124
|
let builtKey = this.parent.buildKey(this.key);
|
|
@@ -78,12 +126,12 @@ class QueryBuilder {
|
|
|
78
126
|
pkName = this.index.primary.name;
|
|
79
127
|
builtKey = this.parent.buildIndexes(this.key);
|
|
80
128
|
}
|
|
81
|
-
this.expressionAttributeNames['#pk']
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
129
|
+
if (!this.expressionAttributeNames['#pk']) {
|
|
130
|
+
this.expressionAttributeNames['#pk'] = pkName;
|
|
131
|
+
this.expressionAttributeValues[':pk_value'] = builtKey[pkName];
|
|
132
|
+
this.keyConditions.unshift(`#pk = :pk_value`);
|
|
85
133
|
}
|
|
86
|
-
this.
|
|
134
|
+
const keyConditionExpression = this.keyConditions.join(' AND ');
|
|
87
135
|
const params = {
|
|
88
136
|
TableName: this.parent.getTableName(),
|
|
89
137
|
KeyConditionExpression: keyConditionExpression,
|
|
@@ -92,13 +140,14 @@ class QueryBuilder {
|
|
|
92
140
|
ScanIndexForward: this.ascending,
|
|
93
141
|
Limit: this.limit,
|
|
94
142
|
ExclusiveStartKey: this.lastKey,
|
|
95
|
-
IndexName: (_b = (_a = this.index) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : undefined
|
|
143
|
+
IndexName: (_b = (_a = this.index) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : undefined,
|
|
96
144
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
145
|
+
this.filterConditions.push(`#entity = :entity_value`);
|
|
146
|
+
this.expressionAttributeNames['#entity'] = 'entityType';
|
|
147
|
+
this.expressionAttributeValues[':entity_value'] = this.parent.getEntityType();
|
|
148
|
+
params.FilterExpression = this.filterConditions.join(' AND ');
|
|
100
149
|
const result = await this.parent.getClient().send(new lib_dynamodb_1.QueryCommand(params));
|
|
101
|
-
return this.parent.getSchema().array().parse(result.Items);
|
|
150
|
+
return { items: this.parent.getSchema().array().parse(result.Items), lastKey: (_c = result.LastEvaluatedKey) !== null && _c !== void 0 ? _c : undefined };
|
|
102
151
|
}
|
|
103
152
|
}
|
|
104
153
|
exports.QueryBuilder = QueryBuilder;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { BetterDDB } from '../betterddb';
|
|
2
|
+
import { Operator } from '../operator';
|
|
3
|
+
import { PaginatedResult } from '../types/paginated-result';
|
|
2
4
|
export declare class ScanBuilder<T> {
|
|
3
5
|
private parent;
|
|
4
6
|
private filters;
|
|
@@ -7,11 +9,11 @@ export declare class ScanBuilder<T> {
|
|
|
7
9
|
private limit?;
|
|
8
10
|
private lastKey?;
|
|
9
11
|
constructor(parent: BetterDDB<T>);
|
|
10
|
-
where(attribute: keyof T, operator:
|
|
12
|
+
where(attribute: keyof T, operator: Operator, values: any | [any, any]): this;
|
|
11
13
|
limitResults(limit: number): this;
|
|
12
14
|
startFrom(lastKey: Record<string, any>): this;
|
|
13
15
|
/**
|
|
14
16
|
* Executes the scan and returns a Promise that resolves with an array of items.
|
|
15
17
|
*/
|
|
16
|
-
execute(): Promise<T
|
|
18
|
+
execute(): Promise<PaginatedResult<T>>;
|
|
17
19
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ScanBuilder = void 0;
|
|
4
4
|
const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
|
|
5
|
+
const operator_1 = require("../operator");
|
|
5
6
|
class ScanBuilder {
|
|
6
7
|
constructor(parent) {
|
|
7
8
|
this.parent = parent;
|
|
@@ -13,17 +14,7 @@ class ScanBuilder {
|
|
|
13
14
|
const attrStr = String(attribute);
|
|
14
15
|
const nameKey = `#attr_${attrStr}`;
|
|
15
16
|
this.expressionAttributeNames[nameKey] = attrStr;
|
|
16
|
-
if (operator === '
|
|
17
|
-
const valueKey = `:val_${attrStr}`;
|
|
18
|
-
this.expressionAttributeValues[valueKey] = values;
|
|
19
|
-
this.filters.push(`${nameKey} = ${valueKey}`);
|
|
20
|
-
}
|
|
21
|
-
else if (operator === 'begins_with') {
|
|
22
|
-
const valueKey = `:val_${attrStr}`;
|
|
23
|
-
this.expressionAttributeValues[valueKey] = values;
|
|
24
|
-
this.filters.push(`begins_with(${nameKey}, ${valueKey})`);
|
|
25
|
-
}
|
|
26
|
-
else if (operator === 'between') {
|
|
17
|
+
if (operator === 'between') {
|
|
27
18
|
if (!Array.isArray(values) || values.length !== 2) {
|
|
28
19
|
throw new Error(`For 'between' operator, values must be a tuple of two items`);
|
|
29
20
|
}
|
|
@@ -33,8 +24,16 @@ class ScanBuilder {
|
|
|
33
24
|
this.expressionAttributeValues[valueKeyEnd] = values[1];
|
|
34
25
|
this.filters.push(`${nameKey} BETWEEN ${valueKeyStart} AND ${valueKeyEnd}`);
|
|
35
26
|
}
|
|
27
|
+
else if (operator === 'begins_with' || operator === 'contains') {
|
|
28
|
+
const valueKey = `:val_${attrStr}`;
|
|
29
|
+
this.expressionAttributeValues[valueKey] = values;
|
|
30
|
+
this.filters.push(`${operator}(${nameKey}, ${valueKey})`);
|
|
31
|
+
}
|
|
36
32
|
else {
|
|
37
|
-
|
|
33
|
+
const valueKey = `:val_${attrStr}`;
|
|
34
|
+
this.expressionAttributeValues[valueKey] = values;
|
|
35
|
+
const condition = (0, operator_1.getOperatorExpression)(operator, nameKey, valueKey);
|
|
36
|
+
this.filters.push(condition);
|
|
38
37
|
}
|
|
39
38
|
return this;
|
|
40
39
|
}
|
|
@@ -50,6 +49,7 @@ class ScanBuilder {
|
|
|
50
49
|
* Executes the scan and returns a Promise that resolves with an array of items.
|
|
51
50
|
*/
|
|
52
51
|
async execute() {
|
|
52
|
+
var _a;
|
|
53
53
|
const params = {
|
|
54
54
|
TableName: this.parent.getTableName(),
|
|
55
55
|
ExpressionAttributeNames: this.expressionAttributeNames,
|
|
@@ -61,7 +61,7 @@ class ScanBuilder {
|
|
|
61
61
|
params.FilterExpression = this.filters.join(' AND ');
|
|
62
62
|
}
|
|
63
63
|
const result = await this.parent.getClient().send(new lib_dynamodb_1.ScanCommand(params));
|
|
64
|
-
return this.parent.getSchema().array().parse(result.Items);
|
|
64
|
+
return { items: this.parent.getSchema().array().parse(result.Items), lastKey: (_a = result.LastEvaluatedKey) !== null && _a !== void 0 ? _a : undefined };
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
exports.ScanBuilder = ScanBuilder;
|
|
@@ -5,9 +5,8 @@ export declare class UpdateBuilder<T> {
|
|
|
5
5
|
private key;
|
|
6
6
|
private actions;
|
|
7
7
|
private condition?;
|
|
8
|
-
private expectedVersion?;
|
|
9
8
|
private extraTransactItems;
|
|
10
|
-
constructor(parent: BetterDDB<T>, key: Partial<T
|
|
9
|
+
constructor(parent: BetterDDB<T>, key: Partial<T>);
|
|
11
10
|
set(attrs: Partial<T>): this;
|
|
12
11
|
remove(attrs: (keyof T)[]): this;
|
|
13
12
|
add(attrs: Partial<Record<keyof T, number | Set<any>>>): this;
|
|
@@ -4,13 +4,12 @@ exports.UpdateBuilder = void 0;
|
|
|
4
4
|
const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
|
|
5
5
|
class UpdateBuilder {
|
|
6
6
|
// Reference to the parent BetterDDB instance and key.
|
|
7
|
-
constructor(parent, key
|
|
7
|
+
constructor(parent, key) {
|
|
8
8
|
this.parent = parent;
|
|
9
9
|
this.key = key;
|
|
10
10
|
this.actions = {};
|
|
11
11
|
// When using transaction mode, we store extra transaction items.
|
|
12
12
|
this.extraTransactItems = [];
|
|
13
|
-
this.expectedVersion = expectedVersion;
|
|
14
13
|
}
|
|
15
14
|
// Chainable methods:
|
|
16
15
|
set(attrs) {
|
|
@@ -113,28 +112,6 @@ class UpdateBuilder {
|
|
|
113
112
|
clauses.push(`DELETE ${deleteParts.join(', ')}`);
|
|
114
113
|
}
|
|
115
114
|
}
|
|
116
|
-
// Incorporate expectedVersion if provided.
|
|
117
|
-
if (this.expectedVersion !== undefined) {
|
|
118
|
-
ExpressionAttributeNames['#version'] = 'version';
|
|
119
|
-
ExpressionAttributeValues[':expectedVersion'] = this.expectedVersion;
|
|
120
|
-
ExpressionAttributeValues[':newVersion'] = this.expectedVersion + 1;
|
|
121
|
-
// Append version update in SET clause.
|
|
122
|
-
const versionClause = '#version = :newVersion';
|
|
123
|
-
const setIndex = clauses.findIndex(clause => clause.startsWith('SET '));
|
|
124
|
-
if (setIndex >= 0) {
|
|
125
|
-
clauses[setIndex] += `, ${versionClause}`;
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
clauses.push(`SET ${versionClause}`);
|
|
129
|
-
}
|
|
130
|
-
// Ensure condition expression includes version check.
|
|
131
|
-
if (this.condition && this.condition.expression) {
|
|
132
|
-
this.condition.expression += ` AND #version = :expectedVersion`;
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
this.condition = { expression: '#version = :expectedVersion', attributeValues: {} };
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
115
|
// Merge any provided condition attribute values.
|
|
139
116
|
if (this.condition) {
|
|
140
117
|
Object.assign(ExpressionAttributeValues, this.condition.attributeValues);
|
package/lib/operator.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getOperatorExpression = void 0;
|
|
4
|
+
function getOperatorExpression(operator, nameKey, valueKey, secondValueKey) {
|
|
5
|
+
switch (operator) {
|
|
6
|
+
case '==':
|
|
7
|
+
return `${nameKey} = ${valueKey}`;
|
|
8
|
+
case '!=':
|
|
9
|
+
return `${nameKey} <> ${valueKey}`;
|
|
10
|
+
case '<':
|
|
11
|
+
return `${nameKey} < ${valueKey}`;
|
|
12
|
+
case '<=':
|
|
13
|
+
return `${nameKey} <= ${valueKey}`;
|
|
14
|
+
case '>':
|
|
15
|
+
return `${nameKey} > ${valueKey}`;
|
|
16
|
+
case '>=':
|
|
17
|
+
return `${nameKey} >= ${valueKey}`;
|
|
18
|
+
case 'begins_with':
|
|
19
|
+
return `begins_with(${nameKey}, ${valueKey})`;
|
|
20
|
+
case 'between':
|
|
21
|
+
if (!secondValueKey) {
|
|
22
|
+
throw new Error("The 'between' operator requires two value keys");
|
|
23
|
+
}
|
|
24
|
+
return `${nameKey} BETWEEN ${valueKey} AND ${secondValueKey}`;
|
|
25
|
+
case 'contains':
|
|
26
|
+
return `contains(${nameKey}, ${valueKey})`;
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Unsupported operator: ${operator}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.getOperatorExpression = getOperatorExpression;
|
package/package.json
CHANGED
package/src/betterddb.ts
CHANGED
|
@@ -72,7 +72,7 @@ export interface KeysConfig<T> {
|
|
|
72
72
|
export interface BetterDDBOptions<T> {
|
|
73
73
|
schema: z.ZodType<T, z.ZodTypeDef, any>;
|
|
74
74
|
tableName: string;
|
|
75
|
-
|
|
75
|
+
entityType: string;
|
|
76
76
|
keys: KeysConfig<T>;
|
|
77
77
|
client: DynamoDBDocumentClient;
|
|
78
78
|
/**
|
|
@@ -82,7 +82,7 @@ export interface BetterDDBOptions<T> {
|
|
|
82
82
|
*
|
|
83
83
|
* (T should include these fields if enabled.)
|
|
84
84
|
*/
|
|
85
|
-
|
|
85
|
+
timestamps?: boolean;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
@@ -91,18 +91,18 @@ export interface BetterDDBOptions<T> {
|
|
|
91
91
|
export class BetterDDB<T> {
|
|
92
92
|
protected schema: z.ZodType<T, z.ZodTypeDef, any>;
|
|
93
93
|
protected tableName: string;
|
|
94
|
-
protected
|
|
94
|
+
protected entityType: string;
|
|
95
95
|
protected client: DynamoDBDocumentClient;
|
|
96
96
|
protected keys: KeysConfig<T>;
|
|
97
|
-
protected
|
|
97
|
+
protected timestamps: boolean;
|
|
98
98
|
|
|
99
99
|
constructor(options: BetterDDBOptions<T>) {
|
|
100
100
|
this.schema = options.schema;
|
|
101
101
|
this.tableName = options.tableName;
|
|
102
|
-
this.
|
|
102
|
+
this.entityType = options.entityType.toUpperCase();
|
|
103
103
|
this.keys = options.keys;
|
|
104
104
|
this.client = options.client;
|
|
105
|
-
this.
|
|
105
|
+
this.timestamps = options.timestamps ?? false;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
public getKeys(): KeysConfig<T> {
|
|
@@ -122,8 +122,12 @@ export class BetterDDB<T> {
|
|
|
122
122
|
return this.schema;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
public
|
|
126
|
-
return this.
|
|
125
|
+
public getTimestamps(): boolean {
|
|
126
|
+
return this.timestamps;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public getEntityType(): string {
|
|
130
|
+
return this.entityType;
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
// Helper: Retrieve the key value from a KeyDefinition.
|
|
@@ -223,8 +227,8 @@ export class BetterDDB<T> {
|
|
|
223
227
|
/**
|
|
224
228
|
* Update an item.
|
|
225
229
|
*/
|
|
226
|
-
public update(key: Partial<T
|
|
227
|
-
return new UpdateBuilder<T>(this, key
|
|
230
|
+
public update(key: Partial<T>): UpdateBuilder<T> {
|
|
231
|
+
return new UpdateBuilder<T>(this, key);
|
|
228
232
|
}
|
|
229
233
|
|
|
230
234
|
/**
|
|
@@ -9,6 +9,7 @@ export class CreateBuilder<T> {
|
|
|
9
9
|
constructor(private parent: BetterDDB<T>, private item: T) {}
|
|
10
10
|
|
|
11
11
|
public async execute(): Promise<T> {
|
|
12
|
+
const validated = this.parent.getSchema().parse(this.item);
|
|
12
13
|
if (this.extraTransactItems.length > 0) {
|
|
13
14
|
// Build our update transaction item.
|
|
14
15
|
const myTransactItem = this.toTransactPut();
|
|
@@ -24,14 +25,12 @@ export class CreateBuilder<T> {
|
|
|
24
25
|
}
|
|
25
26
|
return result;
|
|
26
27
|
} else {
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
let finalItem: T = { ...this.item , entityType: this.parent.getEntityType() };
|
|
30
|
+
if (this.parent.getTimestamps()) {
|
|
29
31
|
const now = new Date().toISOString();
|
|
30
|
-
|
|
32
|
+
finalItem = { ...finalItem, createdAt: now, updatedAt: now } as T;
|
|
31
33
|
}
|
|
32
|
-
// Validate the item using the schema.
|
|
33
|
-
const validated = this.parent.getSchema().parse(item);
|
|
34
|
-
let finalItem = { ...validated };
|
|
35
34
|
|
|
36
35
|
// Compute and merge primary key.
|
|
37
36
|
const computedKeys = this.parent.buildKey(validated as Partial<T>);
|