betterddb 0.4.5 → 0.4.7
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/lib/betterddb.d.ts +8 -3
- package/lib/betterddb.js +7 -0
- package/lib/builders/batch-get-builder.d.ts +21 -0
- package/lib/builders/batch-get-builder.js +53 -0
- package/lib/builders/create-builder.d.ts +0 -3
- package/lib/builders/create-builder.js +0 -9
- package/lib/builders/delete-builder.d.ts +0 -3
- package/lib/builders/delete-builder.js +0 -9
- package/lib/builders/get-builder.d.ts +0 -3
- package/lib/builders/get-builder.js +0 -9
- package/lib/builders/query-builder.d.ts +1 -4
- package/lib/builders/query-builder.js +16 -19
- package/lib/builders/scan-builder.d.ts +0 -3
- package/lib/builders/scan-builder.js +0 -10
- package/package.json +2 -2
- package/src/betterddb.ts +11 -3
- package/src/builders/batch-get-builder.ts +56 -0
- package/src/builders/create-builder.ts +0 -15
- package/src/builders/delete-builder.ts +0 -15
- package/src/builders/get-builder.ts +0 -15
- package/src/builders/query-builder.ts +20 -29
- package/src/builders/scan-builder.ts +0 -16
- package/test/create.test.ts +1 -1
- package/test/delete.test.ts +1 -1
- package/test/get.test.ts +1 -1
- package/test/query.test.ts +14 -3
- package/test/scan.test.ts +2 -2
- package/test/update.test.ts +1 -1
- package/test/utils/table-setup.ts +9 -0
package/lib/betterddb.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { CreateBuilder } from './builders/create-builder';
|
|
|
6
6
|
import { GetBuilder } from './builders/get-builder';
|
|
7
7
|
import { DeleteBuilder } from './builders/delete-builder';
|
|
8
8
|
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
9
|
+
import { BatchGetBuilder } from './builders/batch-get-builder';
|
|
9
10
|
export type PrimaryKeyValue = string | number;
|
|
10
11
|
/**
|
|
11
12
|
* A key definition can be either a simple key (a property name)
|
|
@@ -60,7 +61,7 @@ export interface KeysConfig<T> {
|
|
|
60
61
|
* Options for initializing BetterDDB.
|
|
61
62
|
*/
|
|
62
63
|
export interface BetterDDBOptions<T> {
|
|
63
|
-
schema: z.ZodType<T, z.ZodTypeDef,
|
|
64
|
+
schema: z.ZodType<T, z.ZodTypeDef, any>;
|
|
64
65
|
tableName: string;
|
|
65
66
|
entityName: string;
|
|
66
67
|
keys: KeysConfig<T>;
|
|
@@ -78,7 +79,7 @@ export interface BetterDDBOptions<T> {
|
|
|
78
79
|
* BetterDDB is a definition-based DynamoDB wrapper library.
|
|
79
80
|
*/
|
|
80
81
|
export declare class BetterDDB<T> {
|
|
81
|
-
protected schema: z.ZodType<T, z.ZodTypeDef,
|
|
82
|
+
protected schema: z.ZodType<T, z.ZodTypeDef, any>;
|
|
82
83
|
protected tableName: string;
|
|
83
84
|
protected entityName: string;
|
|
84
85
|
protected client: DynamoDBDocumentClient;
|
|
@@ -88,7 +89,7 @@ export declare class BetterDDB<T> {
|
|
|
88
89
|
getKeys(): KeysConfig<T>;
|
|
89
90
|
getTableName(): string;
|
|
90
91
|
getClient(): DynamoDBDocumentClient;
|
|
91
|
-
getSchema(): z.ZodType<T, z.ZodTypeDef,
|
|
92
|
+
getSchema(): z.ZodType<T, z.ZodTypeDef, any>;
|
|
92
93
|
getAutoTimestamps(): boolean;
|
|
93
94
|
protected getKeyValue(def: KeyDefinition<T>, rawKey: Partial<T>): string;
|
|
94
95
|
/**
|
|
@@ -110,6 +111,10 @@ export declare class BetterDDB<T> {
|
|
|
110
111
|
* Get an item by its primary key.
|
|
111
112
|
*/
|
|
112
113
|
get(rawKey: Partial<T>): GetBuilder<T>;
|
|
114
|
+
/**
|
|
115
|
+
* Get multiple items by their primary keys.
|
|
116
|
+
*/
|
|
117
|
+
batchGet(rawKeys: Partial<T>[]): BatchGetBuilder<T>;
|
|
113
118
|
/**
|
|
114
119
|
* Update an item.
|
|
115
120
|
*/
|
package/lib/betterddb.js
CHANGED
|
@@ -7,6 +7,7 @@ const update_builder_1 = require("./builders/update-builder");
|
|
|
7
7
|
const create_builder_1 = require("./builders/create-builder");
|
|
8
8
|
const get_builder_1 = require("./builders/get-builder");
|
|
9
9
|
const delete_builder_1 = require("./builders/delete-builder");
|
|
10
|
+
const batch_get_builder_1 = require("./builders/batch-get-builder");
|
|
10
11
|
/**
|
|
11
12
|
* BetterDDB is a definition-based DynamoDB wrapper library.
|
|
12
13
|
*/
|
|
@@ -114,6 +115,12 @@ class BetterDDB {
|
|
|
114
115
|
get(rawKey) {
|
|
115
116
|
return new get_builder_1.GetBuilder(this, rawKey);
|
|
116
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Get multiple items by their primary keys.
|
|
120
|
+
*/
|
|
121
|
+
batchGet(rawKeys) {
|
|
122
|
+
return new batch_get_builder_1.BatchGetBuilder(this, rawKeys);
|
|
123
|
+
}
|
|
117
124
|
/**
|
|
118
125
|
* Update an item.
|
|
119
126
|
*/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BetterDDB } from '../betterddb';
|
|
2
|
+
export declare class BatchGetBuilder<T> {
|
|
3
|
+
private parent;
|
|
4
|
+
private keys;
|
|
5
|
+
private projectionExpression?;
|
|
6
|
+
private expressionAttributeNames;
|
|
7
|
+
/**
|
|
8
|
+
* @param parent - The BetterDDB instance for the table.
|
|
9
|
+
* @param keys - An array of partial keys for the items you wish to retrieve.
|
|
10
|
+
*/
|
|
11
|
+
constructor(parent: BetterDDB<T>, keys: Partial<T>[]);
|
|
12
|
+
/**
|
|
13
|
+
* Specify a projection by providing an array of attribute names.
|
|
14
|
+
*/
|
|
15
|
+
withProjection(attributes: (keyof T)[]): this;
|
|
16
|
+
/**
|
|
17
|
+
* Executes the batch get operation.
|
|
18
|
+
* Returns an array of parsed items of type T.
|
|
19
|
+
*/
|
|
20
|
+
execute(): Promise<T[]>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BatchGetBuilder = void 0;
|
|
4
|
+
const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
|
|
5
|
+
class BatchGetBuilder {
|
|
6
|
+
/**
|
|
7
|
+
* @param parent - The BetterDDB instance for the table.
|
|
8
|
+
* @param keys - An array of partial keys for the items you wish to retrieve.
|
|
9
|
+
*/
|
|
10
|
+
constructor(parent, keys) {
|
|
11
|
+
this.parent = parent;
|
|
12
|
+
this.keys = keys;
|
|
13
|
+
this.expressionAttributeNames = {};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Specify a projection by providing an array of attribute names.
|
|
17
|
+
*/
|
|
18
|
+
withProjection(attributes) {
|
|
19
|
+
this.projectionExpression = attributes.map(attr => `#${String(attr)}`).join(', ');
|
|
20
|
+
for (const attr of attributes) {
|
|
21
|
+
this.expressionAttributeNames[`#${String(attr)}`] = String(attr);
|
|
22
|
+
}
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Executes the batch get operation.
|
|
27
|
+
* Returns an array of parsed items of type T.
|
|
28
|
+
*/
|
|
29
|
+
async execute() {
|
|
30
|
+
const tableName = this.parent.getTableName();
|
|
31
|
+
// Build an array of keys using the parent's key builder.
|
|
32
|
+
const keysArray = this.keys.map(key => this.parent.buildKey(key));
|
|
33
|
+
// Construct the BatchGet parameters.
|
|
34
|
+
const params = {
|
|
35
|
+
RequestItems: {
|
|
36
|
+
[tableName]: {
|
|
37
|
+
Keys: keysArray,
|
|
38
|
+
...(this.projectionExpression && {
|
|
39
|
+
ProjectionExpression: this.projectionExpression,
|
|
40
|
+
ExpressionAttributeNames: this.expressionAttributeNames,
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
const result = await this.parent.getClient().send(new lib_dynamodb_1.BatchGetCommand(params));
|
|
46
|
+
const responses = result.Responses ? result.Responses[tableName] : [];
|
|
47
|
+
if (!responses) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
return this.parent.getSchema().array().parse(responses);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.BatchGetBuilder = BatchGetBuilder;
|
|
@@ -8,7 +8,4 @@ export declare class CreateBuilder<T> {
|
|
|
8
8
|
execute(): Promise<T>;
|
|
9
9
|
transactWrite(ops: TransactWriteItem[] | TransactWriteItem): this;
|
|
10
10
|
toTransactPut(): TransactWriteItem;
|
|
11
|
-
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
12
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<T | TResult>;
|
|
13
|
-
finally(onfinally?: (() => void) | null): Promise<T>;
|
|
14
11
|
}
|
|
@@ -62,14 +62,5 @@ class CreateBuilder {
|
|
|
62
62
|
};
|
|
63
63
|
return { Put: putItem };
|
|
64
64
|
}
|
|
65
|
-
then(onfulfilled, onrejected) {
|
|
66
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
67
|
-
}
|
|
68
|
-
catch(onrejected) {
|
|
69
|
-
return this.execute().catch(onrejected);
|
|
70
|
-
}
|
|
71
|
-
finally(onfinally) {
|
|
72
|
-
return this.execute().finally(onfinally);
|
|
73
|
-
}
|
|
74
65
|
}
|
|
75
66
|
exports.CreateBuilder = CreateBuilder;
|
|
@@ -13,7 +13,4 @@ export declare class DeleteBuilder<T> {
|
|
|
13
13
|
execute(): Promise<void>;
|
|
14
14
|
transactWrite(ops: TransactWriteItem[] | TransactWriteItem): this;
|
|
15
15
|
toTransactDelete(): TransactWriteItem;
|
|
16
|
-
then<TResult1 = void, TResult2 = never>(onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
17
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<void | TResult>;
|
|
18
|
-
finally(onfinally?: (() => void) | null): Promise<void>;
|
|
19
16
|
}
|
|
@@ -68,14 +68,5 @@ class DeleteBuilder {
|
|
|
68
68
|
}
|
|
69
69
|
return { Delete: deleteItem };
|
|
70
70
|
}
|
|
71
|
-
then(onfulfilled, onrejected) {
|
|
72
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
73
|
-
}
|
|
74
|
-
catch(onrejected) {
|
|
75
|
-
return this.execute().catch(onrejected);
|
|
76
|
-
}
|
|
77
|
-
finally(onfinally) {
|
|
78
|
-
return this.execute().finally(onfinally);
|
|
79
|
-
}
|
|
80
71
|
}
|
|
81
72
|
exports.DeleteBuilder = DeleteBuilder;
|
|
@@ -14,7 +14,4 @@ export declare class GetBuilder<T> {
|
|
|
14
14
|
execute(): Promise<T | null>;
|
|
15
15
|
transactGet(ops: TransactGetItem[] | TransactGetItem): this;
|
|
16
16
|
toTransactGet(): TransactGetItem;
|
|
17
|
-
then<TResult1 = T | null, TResult2 = never>(onfulfilled?: ((value: T | null) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
18
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<T | null | TResult>;
|
|
19
|
-
finally(onfinally?: (() => void) | null): Promise<T | null>;
|
|
20
17
|
}
|
|
@@ -67,14 +67,5 @@ class GetBuilder {
|
|
|
67
67
|
}
|
|
68
68
|
return { Get: getItem };
|
|
69
69
|
}
|
|
70
|
-
then(onfulfilled, onrejected) {
|
|
71
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
72
|
-
}
|
|
73
|
-
catch(onrejected) {
|
|
74
|
-
return this.execute().catch(onrejected);
|
|
75
|
-
}
|
|
76
|
-
finally(onfinally) {
|
|
77
|
-
return this.execute().finally(onfinally);
|
|
78
|
-
}
|
|
79
70
|
}
|
|
80
71
|
exports.GetBuilder = GetBuilder;
|
|
@@ -5,7 +5,7 @@ export declare class QueryBuilder<T> {
|
|
|
5
5
|
private filters;
|
|
6
6
|
private expressionAttributeNames;
|
|
7
7
|
private expressionAttributeValues;
|
|
8
|
-
private
|
|
8
|
+
private index?;
|
|
9
9
|
private sortKeyCondition?;
|
|
10
10
|
private limit?;
|
|
11
11
|
private lastKey?;
|
|
@@ -21,7 +21,4 @@ export declare class QueryBuilder<T> {
|
|
|
21
21
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
22
22
|
*/
|
|
23
23
|
execute(): Promise<T[]>;
|
|
24
|
-
then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
25
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<T[] | TResult>;
|
|
26
|
-
finally(onfinally?: (() => void) | null): Promise<T[]>;
|
|
27
24
|
}
|
|
@@ -12,7 +12,13 @@ class QueryBuilder {
|
|
|
12
12
|
this.ascending = true;
|
|
13
13
|
}
|
|
14
14
|
usingIndex(indexName) {
|
|
15
|
-
this.
|
|
15
|
+
if (!this.parent.getKeys().gsis) {
|
|
16
|
+
throw new Error('No global secondary indexes defined for this table');
|
|
17
|
+
}
|
|
18
|
+
if (!(indexName in this.parent.getKeys().gsis)) {
|
|
19
|
+
throw new Error('index does not exist');
|
|
20
|
+
}
|
|
21
|
+
this.index = this.parent.getKeys().gsis[indexName];
|
|
16
22
|
return this;
|
|
17
23
|
}
|
|
18
24
|
sortAscending() {
|
|
@@ -64,19 +70,20 @@ class QueryBuilder {
|
|
|
64
70
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
65
71
|
*/
|
|
66
72
|
async execute() {
|
|
67
|
-
|
|
73
|
+
var _a, _b;
|
|
68
74
|
const keys = this.parent.getKeys();
|
|
69
|
-
|
|
75
|
+
let pkName = keys.primary.name;
|
|
76
|
+
let builtKey = this.parent.buildKey(this.key);
|
|
77
|
+
if (this.index) {
|
|
78
|
+
pkName = this.index.primary.name;
|
|
79
|
+
builtKey = this.parent.buildIndexes(this.key);
|
|
80
|
+
}
|
|
70
81
|
this.expressionAttributeNames['#pk'] = pkName;
|
|
71
|
-
// Cast the built key to a record so that we can index by string.
|
|
72
|
-
const builtKey = this.parent.buildKey(this.key);
|
|
73
|
-
this.expressionAttributeValues[':pk_value'] = builtKey[pkName];
|
|
74
82
|
let keyConditionExpression = `#pk = :pk_value`;
|
|
75
|
-
// If a sortKeyCondition was set via another fluent method, append it.
|
|
76
83
|
if (this.sortKeyCondition) {
|
|
77
84
|
keyConditionExpression += ` AND ${this.sortKeyCondition}`;
|
|
78
85
|
}
|
|
79
|
-
|
|
86
|
+
this.expressionAttributeValues[':pk_value'] = builtKey[pkName];
|
|
80
87
|
const params = {
|
|
81
88
|
TableName: this.parent.getTableName(),
|
|
82
89
|
KeyConditionExpression: keyConditionExpression,
|
|
@@ -85,7 +92,7 @@ class QueryBuilder {
|
|
|
85
92
|
ScanIndexForward: this.ascending,
|
|
86
93
|
Limit: this.limit,
|
|
87
94
|
ExclusiveStartKey: this.lastKey,
|
|
88
|
-
IndexName: this.
|
|
95
|
+
IndexName: (_b = (_a = this.index) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : undefined
|
|
89
96
|
};
|
|
90
97
|
if (this.filters.length > 0) {
|
|
91
98
|
params.FilterExpression = this.filters.join(' AND ');
|
|
@@ -93,15 +100,5 @@ class QueryBuilder {
|
|
|
93
100
|
const result = await this.parent.getClient().send(new lib_dynamodb_1.QueryCommand(params));
|
|
94
101
|
return this.parent.getSchema().array().parse(result.Items);
|
|
95
102
|
}
|
|
96
|
-
// Thenable implementation.
|
|
97
|
-
then(onfulfilled, onrejected) {
|
|
98
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
99
|
-
}
|
|
100
|
-
catch(onrejected) {
|
|
101
|
-
return this.execute().catch(onrejected);
|
|
102
|
-
}
|
|
103
|
-
finally(onfinally) {
|
|
104
|
-
return this.execute().finally(onfinally);
|
|
105
|
-
}
|
|
106
103
|
}
|
|
107
104
|
exports.QueryBuilder = QueryBuilder;
|
|
@@ -14,7 +14,4 @@ export declare class ScanBuilder<T> {
|
|
|
14
14
|
* Executes the scan and returns a Promise that resolves with an array of items.
|
|
15
15
|
*/
|
|
16
16
|
execute(): Promise<T[]>;
|
|
17
|
-
then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
18
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<T[] | TResult>;
|
|
19
|
-
finally(onfinally?: (() => void) | null): Promise<T[]>;
|
|
20
17
|
}
|
|
@@ -63,15 +63,5 @@ class ScanBuilder {
|
|
|
63
63
|
const result = await this.parent.getClient().send(new lib_dynamodb_1.ScanCommand(params));
|
|
64
64
|
return this.parent.getSchema().array().parse(result.Items);
|
|
65
65
|
}
|
|
66
|
-
// Thenable implementation.
|
|
67
|
-
then(onfulfilled, onrejected) {
|
|
68
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
69
|
-
}
|
|
70
|
-
catch(onrejected) {
|
|
71
|
-
return this.execute().catch(onrejected);
|
|
72
|
-
}
|
|
73
|
-
finally(onfinally) {
|
|
74
|
-
return this.execute().finally(onfinally);
|
|
75
|
-
}
|
|
76
66
|
}
|
|
77
67
|
exports.ScanBuilder = ScanBuilder;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "betterddb",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "A definition-based DynamoDB wrapper library that provides a schema-driven and fully typesafe DAL.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@aws-sdk/lib-dynamodb": "^3.744.0",
|
|
32
32
|
"@aws-sdk/client-dynamodb": "^3.744.0",
|
|
33
|
-
"zod": "^3.
|
|
33
|
+
"zod": "^3.24.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/jest": "^29.5.14",
|
package/src/betterddb.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { CreateBuilder } from './builders/create-builder';
|
|
|
6
6
|
import { GetBuilder } from './builders/get-builder';
|
|
7
7
|
import { DeleteBuilder } from './builders/delete-builder';
|
|
8
8
|
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
9
|
+
import { BatchGetBuilder } from './builders/batch-get-builder';
|
|
9
10
|
|
|
10
11
|
export type PrimaryKeyValue = string | number;
|
|
11
12
|
|
|
@@ -69,7 +70,7 @@ export interface KeysConfig<T> {
|
|
|
69
70
|
* Options for initializing BetterDDB.
|
|
70
71
|
*/
|
|
71
72
|
export interface BetterDDBOptions<T> {
|
|
72
|
-
schema: z.ZodType<T, z.ZodTypeDef,
|
|
73
|
+
schema: z.ZodType<T, z.ZodTypeDef, any>;
|
|
73
74
|
tableName: string;
|
|
74
75
|
entityName: string;
|
|
75
76
|
keys: KeysConfig<T>;
|
|
@@ -88,7 +89,7 @@ export interface BetterDDBOptions<T> {
|
|
|
88
89
|
* BetterDDB is a definition-based DynamoDB wrapper library.
|
|
89
90
|
*/
|
|
90
91
|
export class BetterDDB<T> {
|
|
91
|
-
protected schema: z.ZodType<T, z.ZodTypeDef,
|
|
92
|
+
protected schema: z.ZodType<T, z.ZodTypeDef, any>;
|
|
92
93
|
protected tableName: string;
|
|
93
94
|
protected entityName: string;
|
|
94
95
|
protected client: DynamoDBDocumentClient;
|
|
@@ -117,7 +118,7 @@ export class BetterDDB<T> {
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
|
|
120
|
-
public getSchema(): z.ZodType<T, z.ZodTypeDef,
|
|
121
|
+
public getSchema(): z.ZodType<T, z.ZodTypeDef, any> {
|
|
121
122
|
return this.schema;
|
|
122
123
|
}
|
|
123
124
|
|
|
@@ -212,6 +213,13 @@ export class BetterDDB<T> {
|
|
|
212
213
|
return new GetBuilder<T>(this, rawKey);
|
|
213
214
|
}
|
|
214
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Get multiple items by their primary keys.
|
|
218
|
+
*/
|
|
219
|
+
public batchGet(rawKeys: Partial<T>[]): BatchGetBuilder<T> {
|
|
220
|
+
return new BatchGetBuilder<T>(this, rawKeys);
|
|
221
|
+
}
|
|
222
|
+
|
|
215
223
|
/**
|
|
216
224
|
* Update an item.
|
|
217
225
|
*/
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BetterDDB } from '../betterddb';
|
|
2
|
+
import { BatchGetCommand } from '@aws-sdk/lib-dynamodb';
|
|
3
|
+
import { BatchGetItemInput } from '@aws-sdk/client-dynamodb';
|
|
4
|
+
|
|
5
|
+
export class BatchGetBuilder<T> {
|
|
6
|
+
private projectionExpression?: string;
|
|
7
|
+
private expressionAttributeNames: Record<string, string> = {};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param parent - The BetterDDB instance for the table.
|
|
11
|
+
* @param keys - An array of partial keys for the items you wish to retrieve.
|
|
12
|
+
*/
|
|
13
|
+
constructor(private parent: BetterDDB<T>, private keys: Partial<T>[]) {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Specify a projection by providing an array of attribute names.
|
|
17
|
+
*/
|
|
18
|
+
public withProjection(attributes: (keyof T)[]): this {
|
|
19
|
+
this.projectionExpression = attributes.map(attr => `#${String(attr)}`).join(', ');
|
|
20
|
+
for (const attr of attributes) {
|
|
21
|
+
this.expressionAttributeNames[`#${String(attr)}`] = String(attr);
|
|
22
|
+
}
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Executes the batch get operation.
|
|
28
|
+
* Returns an array of parsed items of type T.
|
|
29
|
+
*/
|
|
30
|
+
public async execute(): Promise<T[]> {
|
|
31
|
+
const tableName = this.parent.getTableName();
|
|
32
|
+
// Build an array of keys using the parent's key builder.
|
|
33
|
+
const keysArray = this.keys.map(key => this.parent.buildKey(key));
|
|
34
|
+
|
|
35
|
+
// Construct the BatchGet parameters.
|
|
36
|
+
const params: BatchGetItemInput = {
|
|
37
|
+
RequestItems: {
|
|
38
|
+
[tableName]: {
|
|
39
|
+
Keys: keysArray,
|
|
40
|
+
...(this.projectionExpression && {
|
|
41
|
+
ProjectionExpression: this.projectionExpression,
|
|
42
|
+
ExpressionAttributeNames: this.expressionAttributeNames,
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const result = await this.parent.getClient().send(new BatchGetCommand(params));
|
|
49
|
+
const responses = result.Responses ? result.Responses[tableName] : [];
|
|
50
|
+
if (!responses) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.parent.getSchema().array().parse(responses) as T[];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -66,19 +66,4 @@ export class CreateBuilder<T> {
|
|
|
66
66
|
};
|
|
67
67
|
return { Put: putItem };
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
public then<TResult1 = T, TResult2 = never>(
|
|
71
|
-
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
72
|
-
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
73
|
-
): Promise<TResult1 | TResult2> {
|
|
74
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
75
|
-
}
|
|
76
|
-
public catch<TResult = never>(
|
|
77
|
-
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
78
|
-
): Promise<T | TResult> {
|
|
79
|
-
return this.execute().catch(onrejected);
|
|
80
|
-
}
|
|
81
|
-
public finally(onfinally?: (() => void) | null): Promise<T> {
|
|
82
|
-
return this.execute().finally(onfinally);
|
|
83
|
-
}
|
|
84
69
|
}
|
|
@@ -66,19 +66,4 @@ export class DeleteBuilder<T> {
|
|
|
66
66
|
}
|
|
67
67
|
return { Delete: deleteItem };
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
public then<TResult1 = void, TResult2 = never>(
|
|
71
|
-
onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null,
|
|
72
|
-
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
73
|
-
): Promise<TResult1 | TResult2> {
|
|
74
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
75
|
-
}
|
|
76
|
-
public catch<TResult = never>(
|
|
77
|
-
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
78
|
-
): Promise<void | TResult> {
|
|
79
|
-
return this.execute().catch(onrejected);
|
|
80
|
-
}
|
|
81
|
-
public finally(onfinally?: (() => void) | null): Promise<void> {
|
|
82
|
-
return this.execute().finally(onfinally);
|
|
83
|
-
}
|
|
84
69
|
}
|
|
@@ -65,19 +65,4 @@ export class GetBuilder<T> {
|
|
|
65
65
|
}
|
|
66
66
|
return { Get: getItem };
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
public then<TResult1 = T | null, TResult2 = never>(
|
|
70
|
-
onfulfilled?: ((value: T | null) => TResult1 | PromiseLike<TResult1>) | null,
|
|
71
|
-
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
72
|
-
): Promise<TResult1 | TResult2> {
|
|
73
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
74
|
-
}
|
|
75
|
-
public catch<TResult = never>(
|
|
76
|
-
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
77
|
-
): Promise<T | null | TResult> {
|
|
78
|
-
return this.execute().catch(onrejected);
|
|
79
|
-
}
|
|
80
|
-
public finally(onfinally?: (() => void) | null): Promise<T | null> {
|
|
81
|
-
return this.execute().finally(onfinally);
|
|
82
|
-
}
|
|
83
68
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { QueryCommand, QueryCommandInput } from '@aws-sdk/lib-dynamodb';
|
|
2
|
-
import { BetterDDB } from '../betterddb';
|
|
2
|
+
import { BetterDDB, GSIConfig } from '../betterddb';
|
|
3
3
|
|
|
4
4
|
export class QueryBuilder<T> {
|
|
5
5
|
private filters: string[] = [];
|
|
6
6
|
private expressionAttributeNames: Record<string, string> = {};
|
|
7
7
|
private expressionAttributeValues: Record<string, any> = {};
|
|
8
|
-
private
|
|
9
|
-
private sortKeyCondition?: string;
|
|
8
|
+
private index?: GSIConfig<T>;
|
|
9
|
+
private sortKeyCondition?: string;
|
|
10
10
|
private limit?: number;
|
|
11
11
|
private lastKey?: Record<string, any>;
|
|
12
12
|
private ascending: boolean = true;
|
|
@@ -14,7 +14,14 @@ export class QueryBuilder<T> {
|
|
|
14
14
|
constructor(private parent: BetterDDB<T>, private key: Partial<T>) {}
|
|
15
15
|
|
|
16
16
|
public usingIndex(indexName: string): this {
|
|
17
|
-
this.
|
|
17
|
+
if (!this.parent.getKeys().gsis) {
|
|
18
|
+
throw new Error('No global secondary indexes defined for this table');
|
|
19
|
+
}
|
|
20
|
+
if (!(indexName in this.parent.getKeys().gsis!)) {
|
|
21
|
+
throw new Error('index does not exist')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.index = this.parent.getKeys().gsis![indexName];
|
|
18
25
|
return this;
|
|
19
26
|
}
|
|
20
27
|
|
|
@@ -74,22 +81,22 @@ export class QueryBuilder<T> {
|
|
|
74
81
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
75
82
|
*/
|
|
76
83
|
public async execute(): Promise<T[]> {
|
|
77
|
-
// Build a simple key condition for the partition key.
|
|
78
84
|
const keys = this.parent.getKeys();
|
|
79
|
-
|
|
85
|
+
let pkName = keys.primary.name;
|
|
86
|
+
let builtKey = this.parent.buildKey(this.key) as Record<string, any>;
|
|
87
|
+
if (this.index) {
|
|
88
|
+
pkName = this.index.primary.name;
|
|
89
|
+
builtKey = this.parent.buildIndexes(this.key);
|
|
90
|
+
}
|
|
80
91
|
this.expressionAttributeNames['#pk'] = pkName;
|
|
81
92
|
|
|
82
|
-
// Cast the built key to a record so that we can index by string.
|
|
83
|
-
const builtKey = this.parent.buildKey(this.key) as Record<string, any>;
|
|
84
|
-
this.expressionAttributeValues[':pk_value'] = builtKey[pkName];
|
|
85
|
-
|
|
86
93
|
let keyConditionExpression = `#pk = :pk_value`;
|
|
87
|
-
// If a sortKeyCondition was set via another fluent method, append it.
|
|
88
94
|
if (this.sortKeyCondition) {
|
|
89
95
|
keyConditionExpression += ` AND ${this.sortKeyCondition}`;
|
|
90
96
|
}
|
|
91
97
|
|
|
92
|
-
|
|
98
|
+
this.expressionAttributeValues[':pk_value'] = builtKey[pkName];
|
|
99
|
+
|
|
93
100
|
const params: QueryCommandInput = {
|
|
94
101
|
TableName: this.parent.getTableName(),
|
|
95
102
|
KeyConditionExpression: keyConditionExpression,
|
|
@@ -98,7 +105,7 @@ export class QueryBuilder<T> {
|
|
|
98
105
|
ScanIndexForward: this.ascending,
|
|
99
106
|
Limit: this.limit,
|
|
100
107
|
ExclusiveStartKey: this.lastKey,
|
|
101
|
-
IndexName: this.
|
|
108
|
+
IndexName: this.index?.name ?? undefined
|
|
102
109
|
};
|
|
103
110
|
|
|
104
111
|
if (this.filters.length > 0) {
|
|
@@ -108,20 +115,4 @@ export class QueryBuilder<T> {
|
|
|
108
115
|
const result = await this.parent.getClient().send(new QueryCommand(params));
|
|
109
116
|
return this.parent.getSchema().array().parse(result.Items) as T[];
|
|
110
117
|
}
|
|
111
|
-
|
|
112
|
-
// Thenable implementation.
|
|
113
|
-
public then<TResult1 = T[], TResult2 = never>(
|
|
114
|
-
onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null,
|
|
115
|
-
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
116
|
-
): Promise<TResult1 | TResult2> {
|
|
117
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
118
|
-
}
|
|
119
|
-
public catch<TResult = never>(
|
|
120
|
-
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
121
|
-
): Promise<T[] | TResult> {
|
|
122
|
-
return this.execute().catch(onrejected);
|
|
123
|
-
}
|
|
124
|
-
public finally(onfinally?: (() => void) | null): Promise<T[]> {
|
|
125
|
-
return this.execute().finally(onfinally);
|
|
126
|
-
}
|
|
127
118
|
}
|
|
@@ -71,20 +71,4 @@ export class ScanBuilder<T> {
|
|
|
71
71
|
const result = await this.parent.getClient().send(new ScanCommand(params));
|
|
72
72
|
return this.parent.getSchema().array().parse(result.Items) as T[];
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
// Thenable implementation.
|
|
76
|
-
public then<TResult1 = T[], TResult2 = never>(
|
|
77
|
-
onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null,
|
|
78
|
-
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
79
|
-
): Promise<TResult1 | TResult2> {
|
|
80
|
-
return this.execute().then(onfulfilled, onrejected);
|
|
81
|
-
}
|
|
82
|
-
public catch<TResult = never>(
|
|
83
|
-
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
84
|
-
): Promise<T[] | TResult> {
|
|
85
|
-
return this.execute().catch(onrejected);
|
|
86
|
-
}
|
|
87
|
-
public finally(onfinally?: (() => void) | null): Promise<T[]> {
|
|
88
|
-
return this.execute().finally(onfinally);
|
|
89
|
-
}
|
|
90
74
|
}
|
package/test/create.test.ts
CHANGED
package/test/delete.test.ts
CHANGED
package/test/get.test.ts
CHANGED
package/test/query.test.ts
CHANGED
|
@@ -29,7 +29,7 @@ const UserSchema = z.object({
|
|
|
29
29
|
|
|
30
30
|
type User = z.infer<typeof UserSchema>;
|
|
31
31
|
|
|
32
|
-
const userDdb = new BetterDDB
|
|
32
|
+
const userDdb = new BetterDDB({
|
|
33
33
|
schema: UserSchema,
|
|
34
34
|
tableName: TEST_TABLE,
|
|
35
35
|
entityName: ENTITY_NAME,
|
|
@@ -48,7 +48,7 @@ const userDdb = new BetterDDB<User>({
|
|
|
48
48
|
|
|
49
49
|
beforeAll(async () => {
|
|
50
50
|
await createTestTable(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS);
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
const items = [
|
|
53
53
|
{ id: 'user-1', name: 'Alice', email: 'alice@example.com' },
|
|
54
54
|
{ id: 'user-2', name: 'Alice B', email: 'alice@example.com' },
|
|
@@ -67,10 +67,21 @@ describe('BetterDDB - Query Operation', () => {
|
|
|
67
67
|
it('should query items using QueryBuilder', async () => {
|
|
68
68
|
const results = await userDdb.query({ id: 'user-1' })
|
|
69
69
|
.where('name', 'begins_with', 'Alice')
|
|
70
|
-
.limitResults(5);
|
|
70
|
+
.limitResults(5).execute();
|
|
71
71
|
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
72
72
|
results.forEach(result => {
|
|
73
73
|
expect(result.name).toMatch(/^Alice/);
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
|
+
|
|
77
|
+
it('should query items using QueryBuilder with index', async () => {
|
|
78
|
+
const results = await userDdb.query({ email: 'alice@example.com' })
|
|
79
|
+
.usingIndex('EmailIndex')
|
|
80
|
+
.limitResults(1)
|
|
81
|
+
.execute();
|
|
82
|
+
expect(results.length).toEqual(1);
|
|
83
|
+
results.forEach(result => {
|
|
84
|
+
expect(result.email).toEqual('alice@example.com');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
76
87
|
});
|
package/test/scan.test.ts
CHANGED
|
@@ -30,7 +30,7 @@ const UserSchema = z.object({
|
|
|
30
30
|
|
|
31
31
|
type User = z.infer<typeof UserSchema>;
|
|
32
32
|
|
|
33
|
-
const userDdb = new BetterDDB
|
|
33
|
+
const userDdb = new BetterDDB({
|
|
34
34
|
schema: UserSchema,
|
|
35
35
|
tableName: TEST_TABLE,
|
|
36
36
|
entityName: ENTITY_NAME,
|
|
@@ -61,7 +61,7 @@ describe('BetterDDB - Scan Operation', () => {
|
|
|
61
61
|
it('should scan items using ScanBuilder', async () => {
|
|
62
62
|
const results = await userDdb.scan()
|
|
63
63
|
.where('email', 'begins_with', 'char')
|
|
64
|
-
.limitResults(10);
|
|
64
|
+
.limitResults(10).execute();
|
|
65
65
|
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
66
66
|
results.forEach(result => {
|
|
67
67
|
expect(result.email).toMatch(/^char/i);
|
package/test/update.test.ts
CHANGED
|
@@ -14,6 +14,15 @@ export const createTestTable = async (tableName: string, keySchema: CreateTableC
|
|
|
14
14
|
KeySchema: keySchema,
|
|
15
15
|
AttributeDefinitions: attributeDefinitions,
|
|
16
16
|
BillingMode: 'PAY_PER_REQUEST',
|
|
17
|
+
GlobalSecondaryIndexes: [
|
|
18
|
+
{
|
|
19
|
+
IndexName: 'EmailIndex',
|
|
20
|
+
KeySchema: [{ AttributeName: 'email', KeyType: 'HASH' }],
|
|
21
|
+
Projection: {
|
|
22
|
+
ProjectionType: 'ALL',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
],
|
|
17
26
|
});
|
|
18
27
|
} catch (error: any) {
|
|
19
28
|
if (error.code === 'ResourceInUseException') {
|