betterddb 0.4.9 → 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 +2 -1
- package/lib/builders/query-builder.js +6 -6
- package/lib/builders/scan-builder.d.ts +2 -1
- package/lib/builders/scan-builder.js +2 -1
- package/lib/builders/update-builder.d.ts +1 -2
- package/lib/builders/update-builder.js +1 -24
- 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 +9 -6
- package/src/builders/scan-builder.ts +5 -2
- package/src/builders/update-builder.ts +1 -27
- package/src/types/paginated-result.ts +4 -0
- package/test/batch-get.test.ts +3 -5
- package/test/create.test.ts +3 -5
- package/test/delete.test.ts +3 -5
- package/test/get.test.ts +3 -5
- package/test/query.test.ts +16 -18
- package/test/scan.test.ts +9 -11
- package/test/update.test.ts +3 -6
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,5 +1,6 @@
|
|
|
1
1
|
import { BetterDDB } from '../betterddb';
|
|
2
2
|
import { Operator } from '../operator';
|
|
3
|
+
import { PaginatedResult } from '../types/paginated-result';
|
|
3
4
|
export declare class QueryBuilder<T> {
|
|
4
5
|
private parent;
|
|
5
6
|
private key;
|
|
@@ -22,5 +23,5 @@ export declare class QueryBuilder<T> {
|
|
|
22
23
|
/**
|
|
23
24
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
24
25
|
*/
|
|
25
|
-
execute(): Promise<T
|
|
26
|
+
execute(): Promise<PaginatedResult<T>>;
|
|
26
27
|
}
|
|
@@ -118,7 +118,7 @@ class QueryBuilder {
|
|
|
118
118
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
119
119
|
*/
|
|
120
120
|
async execute() {
|
|
121
|
-
var _a, _b;
|
|
121
|
+
var _a, _b, _c;
|
|
122
122
|
const keys = this.parent.getKeys();
|
|
123
123
|
let pkName = keys.primary.name;
|
|
124
124
|
let builtKey = this.parent.buildKey(this.key);
|
|
@@ -142,12 +142,12 @@ class QueryBuilder {
|
|
|
142
142
|
ExclusiveStartKey: this.lastKey,
|
|
143
143
|
IndexName: (_b = (_a = this.index) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : undefined,
|
|
144
144
|
};
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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 ');
|
|
149
149
|
const result = await this.parent.getClient().send(new lib_dynamodb_1.QueryCommand(params));
|
|
150
|
-
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 };
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
exports.QueryBuilder = QueryBuilder;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BetterDDB } from '../betterddb';
|
|
2
2
|
import { Operator } from '../operator';
|
|
3
|
+
import { PaginatedResult } from '../types/paginated-result';
|
|
3
4
|
export declare class ScanBuilder<T> {
|
|
4
5
|
private parent;
|
|
5
6
|
private filters;
|
|
@@ -14,5 +15,5 @@ export declare class ScanBuilder<T> {
|
|
|
14
15
|
/**
|
|
15
16
|
* Executes the scan and returns a Promise that resolves with an array of items.
|
|
16
17
|
*/
|
|
17
|
-
execute(): Promise<T
|
|
18
|
+
execute(): Promise<PaginatedResult<T>>;
|
|
18
19
|
}
|
|
@@ -49,6 +49,7 @@ class ScanBuilder {
|
|
|
49
49
|
* Executes the scan and returns a Promise that resolves with an array of items.
|
|
50
50
|
*/
|
|
51
51
|
async execute() {
|
|
52
|
+
var _a;
|
|
52
53
|
const params = {
|
|
53
54
|
TableName: this.parent.getTableName(),
|
|
54
55
|
ExpressionAttributeNames: this.expressionAttributeNames,
|
|
@@ -60,7 +61,7 @@ class ScanBuilder {
|
|
|
60
61
|
params.FilterExpression = this.filters.join(' AND ');
|
|
61
62
|
}
|
|
62
63
|
const result = await this.parent.getClient().send(new lib_dynamodb_1.ScanCommand(params));
|
|
63
|
-
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 };
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
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/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>);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { QueryCommand, QueryCommandInput } from '@aws-sdk/lib-dynamodb';
|
|
2
2
|
import { BetterDDB, GSIConfig } from '../betterddb';
|
|
3
3
|
import { getOperatorExpression, Operator } from '../operator';
|
|
4
|
+
import { PaginatedResult } from '../types/paginated-result';
|
|
5
|
+
|
|
4
6
|
export class QueryBuilder<T> {
|
|
5
7
|
private keyConditions: string[] = [];
|
|
6
8
|
private filterConditions: string[] = [];
|
|
@@ -133,7 +135,7 @@ export class QueryBuilder<T> {
|
|
|
133
135
|
/**
|
|
134
136
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
135
137
|
*/
|
|
136
|
-
public async execute(): Promise<T
|
|
138
|
+
public async execute(): Promise<PaginatedResult<T>> {
|
|
137
139
|
const keys = this.parent.getKeys();
|
|
138
140
|
let pkName = keys.primary.name;
|
|
139
141
|
let builtKey = this.parent.buildKey(this.key) as Record<string, any>;
|
|
@@ -160,11 +162,12 @@ export class QueryBuilder<T> {
|
|
|
160
162
|
IndexName: this.index?.name ?? undefined,
|
|
161
163
|
};
|
|
162
164
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
this.filterConditions.push(`#entity = :entity_value`);
|
|
166
|
+
this.expressionAttributeNames['#entity'] = 'entityType';
|
|
167
|
+
this.expressionAttributeValues[':entity_value'] = this.parent.getEntityType();
|
|
168
|
+
params.FilterExpression = this.filterConditions.join(' AND ');
|
|
169
|
+
|
|
167
170
|
const result = await this.parent.getClient().send(new QueryCommand(params));
|
|
168
|
-
return this.parent.getSchema().array().parse(result.Items) as T[];
|
|
171
|
+
return {items: this.parent.getSchema().array().parse(result.Items) as T[], lastKey: result.LastEvaluatedKey ?? undefined};
|
|
169
172
|
}
|
|
170
173
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ScanCommand, ScanCommandInput } from '@aws-sdk/lib-dynamodb';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
3
|
import { getOperatorExpression, Operator } from '../operator';
|
|
4
|
+
import { PaginatedResult } from '../types/paginated-result';
|
|
5
|
+
|
|
4
6
|
export class ScanBuilder<T> {
|
|
5
7
|
private filters: string[] = [];
|
|
6
8
|
private expressionAttributeNames: Record<string, string> = {};
|
|
@@ -58,7 +60,7 @@ export class ScanBuilder<T> {
|
|
|
58
60
|
/**
|
|
59
61
|
* Executes the scan and returns a Promise that resolves with an array of items.
|
|
60
62
|
*/
|
|
61
|
-
public async execute(): Promise<T
|
|
63
|
+
public async execute(): Promise<PaginatedResult<T>> {
|
|
62
64
|
const params: ScanCommandInput = {
|
|
63
65
|
TableName: this.parent.getTableName(),
|
|
64
66
|
ExpressionAttributeNames: this.expressionAttributeNames,
|
|
@@ -72,6 +74,7 @@ export class ScanBuilder<T> {
|
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
const result = await this.parent.getClient().send(new ScanCommand(params));
|
|
75
|
-
|
|
77
|
+
|
|
78
|
+
return {items: this.parent.getSchema().array().parse(result.Items) as T[], lastKey: result.LastEvaluatedKey ?? undefined};
|
|
76
79
|
}
|
|
77
80
|
}
|
|
@@ -13,14 +13,11 @@ interface UpdateActions<T> {
|
|
|
13
13
|
export class UpdateBuilder<T> {
|
|
14
14
|
private actions: UpdateActions<T> = {};
|
|
15
15
|
private condition?: { expression: string; attributeValues: Record<string, any> };
|
|
16
|
-
private expectedVersion?: number;
|
|
17
16
|
// When using transaction mode, we store extra transaction items.
|
|
18
17
|
private extraTransactItems: TransactWriteItem[] = [];
|
|
19
18
|
|
|
20
19
|
// Reference to the parent BetterDDB instance and key.
|
|
21
|
-
constructor(private parent: BetterDDB<T>, private key: Partial<T
|
|
22
|
-
this.expectedVersion = expectedVersion;
|
|
23
|
-
}
|
|
20
|
+
constructor(private parent: BetterDDB<T>, private key: Partial<T>) {}
|
|
24
21
|
|
|
25
22
|
// Chainable methods:
|
|
26
23
|
public set(attrs: Partial<T>): this {
|
|
@@ -136,29 +133,6 @@ export class UpdateBuilder<T> {
|
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
135
|
|
|
139
|
-
// Incorporate expectedVersion if provided.
|
|
140
|
-
if (this.expectedVersion !== undefined) {
|
|
141
|
-
ExpressionAttributeNames['#version'] = 'version';
|
|
142
|
-
ExpressionAttributeValues[':expectedVersion'] = this.expectedVersion;
|
|
143
|
-
ExpressionAttributeValues[':newVersion'] = this.expectedVersion + 1;
|
|
144
|
-
|
|
145
|
-
// Append version update in SET clause.
|
|
146
|
-
const versionClause = '#version = :newVersion';
|
|
147
|
-
const setIndex = clauses.findIndex(clause => clause.startsWith('SET '));
|
|
148
|
-
if (setIndex >= 0) {
|
|
149
|
-
clauses[setIndex] += `, ${versionClause}`;
|
|
150
|
-
} else {
|
|
151
|
-
clauses.push(`SET ${versionClause}`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Ensure condition expression includes version check.
|
|
155
|
-
if (this.condition && this.condition.expression) {
|
|
156
|
-
this.condition.expression += ` AND #version = :expectedVersion`;
|
|
157
|
-
} else {
|
|
158
|
-
this.condition = { expression: '#version = :expectedVersion', attributeValues: {} };
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
136
|
// Merge any provided condition attribute values.
|
|
163
137
|
if (this.condition) {
|
|
164
138
|
Object.assign(ExpressionAttributeValues, this.condition.attributeValues);
|
package/test/batch-get.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { KeySchemaElement, AttributeDefinition } from '@aws-sdk/client-dynamodb'
|
|
|
7
7
|
const TEST_TABLE = "batch-get-test-table";
|
|
8
8
|
const ENDPOINT = 'http://localhost:4566';
|
|
9
9
|
const REGION = 'us-east-1';
|
|
10
|
-
const
|
|
10
|
+
const ENTITY_TYPE = 'USER';
|
|
11
11
|
const PRIMARY_KEY = 'pk';
|
|
12
12
|
const PRIMARY_KEY_TYPE = 'S';
|
|
13
13
|
const SORT_KEY = 'sk';
|
|
@@ -44,8 +44,6 @@ const UserSchema = z.object({
|
|
|
44
44
|
id: z.string(),
|
|
45
45
|
name: z.string(),
|
|
46
46
|
email: z.string().email(),
|
|
47
|
-
createdAt: z.string(),
|
|
48
|
-
updatedAt: z.string(),
|
|
49
47
|
});
|
|
50
48
|
|
|
51
49
|
type User = z.infer<typeof UserSchema>;
|
|
@@ -53,13 +51,13 @@ type User = z.infer<typeof UserSchema>;
|
|
|
53
51
|
const userDdb = new BetterDDB({
|
|
54
52
|
schema: UserSchema,
|
|
55
53
|
tableName: TEST_TABLE,
|
|
56
|
-
|
|
54
|
+
entityType: ENTITY_TYPE,
|
|
57
55
|
keys: {
|
|
58
56
|
primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id! } },
|
|
59
57
|
sort: { name: SORT_KEY, definition: { build: (raw) => raw.email! } },
|
|
60
58
|
},
|
|
61
59
|
client,
|
|
62
|
-
|
|
60
|
+
timestamps: true,
|
|
63
61
|
});
|
|
64
62
|
|
|
65
63
|
beforeAll(async () => {
|
package/test/create.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { KeySchemaElement, AttributeDefinition } from '@aws-sdk/client-dynamodb'
|
|
|
8
8
|
const TEST_TABLE = "create-test-table";
|
|
9
9
|
const ENDPOINT = 'http://localhost:4566';
|
|
10
10
|
const REGION = 'us-east-1';
|
|
11
|
-
const
|
|
11
|
+
const ENTITY_TYPE = 'USER';
|
|
12
12
|
const PRIMARY_KEY = 'pk';
|
|
13
13
|
const PRIMARY_KEY_TYPE = 'S';
|
|
14
14
|
const SORT_KEY = 'sk';
|
|
@@ -44,8 +44,6 @@ const UserSchema = z.object({
|
|
|
44
44
|
id: z.string(),
|
|
45
45
|
name: z.string(),
|
|
46
46
|
email: z.string().email(),
|
|
47
|
-
createdAt: z.string(),
|
|
48
|
-
updatedAt: z.string(),
|
|
49
47
|
});
|
|
50
48
|
|
|
51
49
|
type User = z.infer<typeof UserSchema>;
|
|
@@ -53,14 +51,14 @@ type User = z.infer<typeof UserSchema>;
|
|
|
53
51
|
const userDdb = new BetterDDB({
|
|
54
52
|
schema: UserSchema,
|
|
55
53
|
tableName: TEST_TABLE,
|
|
56
|
-
|
|
54
|
+
entityType: ENTITY_TYPE,
|
|
57
55
|
keys: {
|
|
58
56
|
primary: { name: "pk", definition: { build: (raw) => `USER#${raw.id}` } },
|
|
59
57
|
sort: { name: "sk", definition: { build: (raw) => `EMAIL#${raw.email}` } },
|
|
60
58
|
gsis: { gsi1: { name: 'gsi1', primary: { name: "gsi1pk", definition: { build: (raw) => "NAME" } }, sort: { name: "gsi1sk", definition: { build: (raw) => `NAME#${raw.name}` } } } },
|
|
61
59
|
},
|
|
62
60
|
client,
|
|
63
|
-
|
|
61
|
+
timestamps: true,
|
|
64
62
|
});
|
|
65
63
|
|
|
66
64
|
beforeAll(async () => {
|
package/test/delete.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { KeySchemaElement, AttributeDefinition, GlobalSecondaryIndex } from '@aw
|
|
|
8
8
|
const TEST_TABLE = "delete-test-table";
|
|
9
9
|
const ENDPOINT = 'http://localhost:4566';
|
|
10
10
|
const REGION = 'us-east-1';
|
|
11
|
-
const
|
|
11
|
+
const ENTITY_TYPE = 'USER';
|
|
12
12
|
const PRIMARY_KEY = 'pk';
|
|
13
13
|
const PRIMARY_KEY_TYPE = 'S';
|
|
14
14
|
const SORT_KEY = 'sk';
|
|
@@ -44,8 +44,6 @@ const UserSchema = z.object({
|
|
|
44
44
|
id: z.string(),
|
|
45
45
|
name: z.string(),
|
|
46
46
|
email: z.string().email(),
|
|
47
|
-
createdAt: z.string(),
|
|
48
|
-
updatedAt: z.string(),
|
|
49
47
|
});
|
|
50
48
|
|
|
51
49
|
type User = z.infer<typeof UserSchema>;
|
|
@@ -53,13 +51,13 @@ type User = z.infer<typeof UserSchema>;
|
|
|
53
51
|
const userDdb = new BetterDDB({
|
|
54
52
|
schema: UserSchema,
|
|
55
53
|
tableName: TEST_TABLE,
|
|
56
|
-
|
|
54
|
+
entityType: ENTITY_TYPE,
|
|
57
55
|
keys: {
|
|
58
56
|
primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id! } },
|
|
59
57
|
sort: { name: SORT_KEY, definition: { build: (raw) => raw.email! } },
|
|
60
58
|
},
|
|
61
59
|
client,
|
|
62
|
-
|
|
60
|
+
timestamps: true,
|
|
63
61
|
});
|
|
64
62
|
|
|
65
63
|
beforeAll(async () => {
|
package/test/get.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { KeySchemaElement, AttributeDefinition } from '@aws-sdk/client-dynamodb'
|
|
|
8
8
|
const TEST_TABLE = "get-test-table";
|
|
9
9
|
const ENDPOINT = 'http://localhost:4566';
|
|
10
10
|
const REGION = 'us-east-1';
|
|
11
|
-
const
|
|
11
|
+
const ENTITY_TYPE = 'USER';
|
|
12
12
|
const PRIMARY_KEY = 'pk';
|
|
13
13
|
const PRIMARY_KEY_TYPE = 'S';
|
|
14
14
|
const SORT_KEY = 'sk';
|
|
@@ -46,8 +46,6 @@ const UserSchema = z.object({
|
|
|
46
46
|
id: z.string(),
|
|
47
47
|
name: z.string(),
|
|
48
48
|
email: z.string().email(),
|
|
49
|
-
createdAt: z.string(),
|
|
50
|
-
updatedAt: z.string(),
|
|
51
49
|
});
|
|
52
50
|
|
|
53
51
|
type User = z.infer<typeof UserSchema>;
|
|
@@ -55,13 +53,13 @@ type User = z.infer<typeof UserSchema>;
|
|
|
55
53
|
const userDdb = new BetterDDB({
|
|
56
54
|
schema: UserSchema,
|
|
57
55
|
tableName: TEST_TABLE,
|
|
58
|
-
|
|
56
|
+
entityType: ENTITY_TYPE,
|
|
59
57
|
keys: {
|
|
60
58
|
primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id! } },
|
|
61
59
|
sort: { name: SORT_KEY, definition: { build: (raw) => raw.email! } },
|
|
62
60
|
},
|
|
63
61
|
client,
|
|
64
|
-
|
|
62
|
+
timestamps: true,
|
|
65
63
|
});
|
|
66
64
|
|
|
67
65
|
beforeAll(async () => {
|
package/test/query.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { createTestTable, deleteTestTable } from './utils/table-setup';
|
|
|
7
7
|
const TEST_TABLE = "query-test-table";
|
|
8
8
|
const ENDPOINT = 'http://localhost:4566';
|
|
9
9
|
const REGION = 'us-east-1';
|
|
10
|
-
const
|
|
10
|
+
const ENTITY_TYPE = 'USER';
|
|
11
11
|
const PRIMARY_KEY = 'pk';
|
|
12
12
|
const PRIMARY_KEY_TYPE = 'S';
|
|
13
13
|
const SORT_KEY = 'sk';
|
|
@@ -45,8 +45,6 @@ const UserSchema = z.object({
|
|
|
45
45
|
id: z.string(),
|
|
46
46
|
name: z.string(),
|
|
47
47
|
email: z.string().email(),
|
|
48
|
-
createdAt: z.string(),
|
|
49
|
-
updatedAt: z.string(),
|
|
50
48
|
});
|
|
51
49
|
|
|
52
50
|
type User = z.infer<typeof UserSchema>;
|
|
@@ -54,7 +52,7 @@ type User = z.infer<typeof UserSchema>;
|
|
|
54
52
|
const userDdb = new BetterDDB({
|
|
55
53
|
schema: UserSchema,
|
|
56
54
|
tableName: TEST_TABLE,
|
|
57
|
-
|
|
55
|
+
entityType: ENTITY_TYPE,
|
|
58
56
|
keys: {
|
|
59
57
|
primary: {
|
|
60
58
|
name: PRIMARY_KEY,
|
|
@@ -79,7 +77,7 @@ const userDdb = new BetterDDB({
|
|
|
79
77
|
}
|
|
80
78
|
},
|
|
81
79
|
client,
|
|
82
|
-
|
|
80
|
+
timestamps: true,
|
|
83
81
|
});
|
|
84
82
|
|
|
85
83
|
beforeAll(async () => {
|
|
@@ -103,8 +101,8 @@ describe('BetterDDB - Query Operation', () => {
|
|
|
103
101
|
.filter('name', 'begins_with', 'Alice')
|
|
104
102
|
.limitResults(5)
|
|
105
103
|
.execute();
|
|
106
|
-
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
107
|
-
results.forEach(result => {
|
|
104
|
+
expect(results.items.length).toBeGreaterThanOrEqual(1);
|
|
105
|
+
results.items.forEach(result => {
|
|
108
106
|
expect(result.name).toMatch(/^Alice/);
|
|
109
107
|
});
|
|
110
108
|
});
|
|
@@ -114,8 +112,8 @@ describe('BetterDDB - Query Operation', () => {
|
|
|
114
112
|
.usingIndex('EmailIndex')
|
|
115
113
|
.limitResults(1)
|
|
116
114
|
.execute();
|
|
117
|
-
expect(results.length).toEqual(1);
|
|
118
|
-
results.forEach(result => {
|
|
115
|
+
expect(results.items.length).toEqual(1);
|
|
116
|
+
results.items.forEach(result => {
|
|
119
117
|
expect(result.email).toEqual('alice@example.com');
|
|
120
118
|
});
|
|
121
119
|
});
|
|
@@ -125,8 +123,8 @@ describe('BetterDDB - Query Operation', () => {
|
|
|
125
123
|
const results = await userDdb.query({ id: 'user-1' })
|
|
126
124
|
.where('begins_with', { email: 'alice' })
|
|
127
125
|
.execute();
|
|
128
|
-
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
129
|
-
results.forEach(result => {
|
|
126
|
+
expect(results.items.length).toBeGreaterThanOrEqual(1);
|
|
127
|
+
results.items.forEach(result => {
|
|
130
128
|
expect(result.email).toMatch(/^alice/i);
|
|
131
129
|
});
|
|
132
130
|
});
|
|
@@ -135,7 +133,7 @@ describe('BetterDDB - Query Operation', () => {
|
|
|
135
133
|
const results = await userDdb.query({ id: 'user-1' })
|
|
136
134
|
.where('begins_with', { email: 'bob' })
|
|
137
135
|
.execute();
|
|
138
|
-
expect(results.length).toEqual(0);
|
|
136
|
+
expect(results.items.length).toEqual(0);
|
|
139
137
|
});
|
|
140
138
|
|
|
141
139
|
it('should query items using QueryBuilder with index and additional filter', async () => {
|
|
@@ -143,8 +141,8 @@ describe('BetterDDB - Query Operation', () => {
|
|
|
143
141
|
.usingIndex('EmailIndex')
|
|
144
142
|
.filter('name', 'begins_with', 'Alice')
|
|
145
143
|
.execute();
|
|
146
|
-
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
147
|
-
results.forEach(result => {
|
|
144
|
+
expect(results.items.length).toBeGreaterThanOrEqual(1);
|
|
145
|
+
results.items.forEach(result => {
|
|
148
146
|
expect(result.email).toEqual('alice@example.com');
|
|
149
147
|
expect(result.name).toMatch(/^Alice/);
|
|
150
148
|
});
|
|
@@ -160,8 +158,8 @@ describe('BetterDDB - Query Operation', () => {
|
|
|
160
158
|
{ email: 'alice@example.com' } // Upper bound -> built to "USER#alice@example.com"
|
|
161
159
|
])
|
|
162
160
|
.execute();
|
|
163
|
-
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
164
|
-
results.forEach(result => {
|
|
161
|
+
expect(results.items.length).toBeGreaterThanOrEqual(1);
|
|
162
|
+
results.items.forEach(result => {
|
|
165
163
|
// The built sort key for user-1 is "USER#alice@example.com"
|
|
166
164
|
expect(result.email).toMatch(/alice@example\.com/i);
|
|
167
165
|
});
|
|
@@ -175,8 +173,8 @@ describe('BetterDDB - Query Operation', () => {
|
|
|
175
173
|
.filter('name', 'begins_with', 'Alice')
|
|
176
174
|
.filter('name', 'contains', 'B')
|
|
177
175
|
.execute();
|
|
178
|
-
expect(results.length).toEqual(1);
|
|
179
|
-
results.forEach(result => {
|
|
176
|
+
expect(results.items.length).toEqual(1);
|
|
177
|
+
results.items.forEach(result => {
|
|
180
178
|
expect(result.name).toMatch(/^Alice/);
|
|
181
179
|
expect(result.name).toContain('B');
|
|
182
180
|
});
|
package/test/scan.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { KeySchemaElement, AttributeDefinition } from '@aws-sdk/client-dynamodb'
|
|
|
7
7
|
const TEST_TABLE = "scan-test-table";
|
|
8
8
|
const ENDPOINT = 'http://localhost:4566';
|
|
9
9
|
const REGION = 'us-east-1';
|
|
10
|
-
const
|
|
10
|
+
const ENTITY_TYPE = 'USER';
|
|
11
11
|
const PRIMARY_KEY = 'pk';
|
|
12
12
|
const PRIMARY_KEY_TYPE = 'S';
|
|
13
13
|
const SORT_KEY = 'sk';
|
|
@@ -44,8 +44,6 @@ const UserSchema = z.object({
|
|
|
44
44
|
id: z.string(),
|
|
45
45
|
name: z.string(),
|
|
46
46
|
email: z.string().email(),
|
|
47
|
-
createdAt: z.string(),
|
|
48
|
-
updatedAt: z.string(),
|
|
49
47
|
});
|
|
50
48
|
|
|
51
49
|
type User = z.infer<typeof UserSchema>;
|
|
@@ -53,13 +51,13 @@ type User = z.infer<typeof UserSchema>;
|
|
|
53
51
|
const userDdb = new BetterDDB({
|
|
54
52
|
schema: UserSchema,
|
|
55
53
|
tableName: TEST_TABLE,
|
|
56
|
-
|
|
54
|
+
entityType: ENTITY_TYPE,
|
|
57
55
|
keys: {
|
|
58
56
|
primary: { name: PRIMARY_KEY, definition: { build: (raw) => `USER#${raw.id}` } },
|
|
59
57
|
sort: { name: SORT_KEY, definition: { build: (raw) => `EMAIL#${raw.email}` } },
|
|
60
58
|
},
|
|
61
59
|
client,
|
|
62
|
-
|
|
60
|
+
timestamps: true,
|
|
63
61
|
});
|
|
64
62
|
|
|
65
63
|
beforeAll(async () => {
|
|
@@ -82,8 +80,8 @@ describe('BetterDDB - Scan Operation', () => {
|
|
|
82
80
|
const results = await userDdb.scan()
|
|
83
81
|
.where('email', 'begins_with', 'a')
|
|
84
82
|
.limitResults(10).execute();
|
|
85
|
-
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
86
|
-
results.forEach(result => {
|
|
83
|
+
expect(results.items.length).toBeGreaterThanOrEqual(1);
|
|
84
|
+
results.items.forEach(result => {
|
|
87
85
|
expect(result.email).toMatch(/^alice/i);
|
|
88
86
|
});
|
|
89
87
|
});
|
|
@@ -94,8 +92,8 @@ describe('BetterDDB - Scan Operation', () => {
|
|
|
94
92
|
.where('name', 'contains', 'Alice')
|
|
95
93
|
.limitResults(10)
|
|
96
94
|
.execute();
|
|
97
|
-
expect(results.length).toBeGreaterThan(0);
|
|
98
|
-
results.forEach(result => {
|
|
95
|
+
expect(results.items.length).toBeGreaterThan(0);
|
|
96
|
+
results.items.forEach(result => {
|
|
99
97
|
expect(result.name).toContain('Alice');
|
|
100
98
|
});
|
|
101
99
|
});
|
|
@@ -106,8 +104,8 @@ describe('BetterDDB - Scan Operation', () => {
|
|
|
106
104
|
const results = await userDdb.scan()
|
|
107
105
|
.where('email', 'between', ['a', 'c'])
|
|
108
106
|
.execute();
|
|
109
|
-
expect(results.length).toBeGreaterThan(0);
|
|
110
|
-
results.forEach(result => {
|
|
107
|
+
expect(results.items.length).toBeGreaterThan(0);
|
|
108
|
+
results.items.forEach(result => {
|
|
111
109
|
// A simple lexicographical check
|
|
112
110
|
expect(result.email >= 'a' && result.email <= 'c').toBeTruthy();
|
|
113
111
|
});
|
package/test/update.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { KeySchemaElement, AttributeDefinition } from '@aws-sdk/client-dynamodb'
|
|
|
7
7
|
const TEST_TABLE = "update-test-table";
|
|
8
8
|
const ENDPOINT = 'http://localhost:4566';
|
|
9
9
|
const REGION = 'us-east-1';
|
|
10
|
-
const
|
|
10
|
+
const ENTITY_TYPE = 'USER';
|
|
11
11
|
const PRIMARY_KEY = 'pk';
|
|
12
12
|
const PRIMARY_KEY_TYPE = 'S';
|
|
13
13
|
const SORT_KEY = 'sk';
|
|
@@ -44,9 +44,6 @@ const UserSchema = z.object({
|
|
|
44
44
|
id: z.string(),
|
|
45
45
|
name: z.string(),
|
|
46
46
|
email: z.string().email(),
|
|
47
|
-
createdAt: z.string(),
|
|
48
|
-
updatedAt: z.string(),
|
|
49
|
-
version: z.number().optional(),
|
|
50
47
|
});
|
|
51
48
|
|
|
52
49
|
type User = z.infer<typeof UserSchema>;
|
|
@@ -54,13 +51,13 @@ type User = z.infer<typeof UserSchema>;
|
|
|
54
51
|
const userDdb = new BetterDDB({
|
|
55
52
|
schema: UserSchema,
|
|
56
53
|
tableName: TEST_TABLE,
|
|
57
|
-
|
|
54
|
+
entityType: ENTITY_TYPE,
|
|
58
55
|
keys: {
|
|
59
56
|
primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id! } },
|
|
60
57
|
sort: { name: SORT_KEY, definition: { build: (raw) => raw.email! } },
|
|
61
58
|
},
|
|
62
59
|
client,
|
|
63
|
-
|
|
60
|
+
timestamps: true,
|
|
64
61
|
});
|
|
65
62
|
|
|
66
63
|
beforeAll(async () => {
|