betterddb 0.4.0 → 0.4.1
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 +33 -98
- package/lib/betterddb.js +12 -72
- package/lib/builders/create-builder.d.ts +7 -6
- package/lib/builders/delete-builder.d.ts +3 -2
- package/lib/builders/get-builder.d.ts +8 -7
- package/lib/builders/query-builder.d.ts +8 -7
- package/lib/builders/scan-builder.d.ts +8 -7
- package/lib/builders/update-builder.d.ts +8 -7
- package/package.json +1 -1
- package/src/betterddb.ts +73 -159
- package/src/builders/create-builder.ts +9 -8
- package/src/builders/delete-builder.ts +3 -2
- package/src/builders/get-builder.ts +9 -8
- package/src/builders/query-builder.ts +9 -8
- package/src/builders/scan-builder.ts +9 -8
- package/src/builders/update-builder.ts +14 -13
- package/test/create.test.ts +22 -8
- package/test/delete.test.ts +1 -1
- package/test/get.test.ts +1 -1
- package/test/query.test.ts +1 -1
- package/test/scan.test.ts +1 -1
- package/test/update.test.ts +1 -1
package/lib/betterddb.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
2
|
import { DynamoDB } from 'aws-sdk';
|
|
3
3
|
import { QueryBuilder } from './builders/query-builder';
|
|
4
4
|
import { ScanBuilder } from './builders/scan-builder';
|
|
@@ -6,124 +6,59 @@ import { UpdateBuilder } from './builders/update-builder';
|
|
|
6
6
|
import { CreateBuilder } from './builders/create-builder';
|
|
7
7
|
import { GetBuilder } from './builders/get-builder';
|
|
8
8
|
import { DeleteBuilder } from './builders/delete-builder';
|
|
9
|
+
export type SchemaType<S extends z.ZodType<any>> = z.infer<S>;
|
|
9
10
|
export type PrimaryKeyValue = string | number;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* or an object containing a build function that computes the value.
|
|
13
|
-
* (In this design, the attribute name is provided separately.)
|
|
14
|
-
*/
|
|
15
|
-
export type KeyDefinition<T> = keyof T | {
|
|
16
|
-
build: (rawKey: Partial<T>) => string;
|
|
11
|
+
export type KeyDefinition<S extends z.ZodType<any>> = keyof SchemaType<S> | {
|
|
12
|
+
build: (rawKey: Partial<SchemaType<S>>) => string;
|
|
17
13
|
};
|
|
18
|
-
|
|
19
|
-
* Configuration for a primary (partition) key.
|
|
20
|
-
*/
|
|
21
|
-
export interface PrimaryKeyConfig<T> {
|
|
22
|
-
/** The attribute name for the primary key in DynamoDB */
|
|
14
|
+
export interface PrimaryKeyConfig<S extends z.ZodType<any>> {
|
|
23
15
|
name: string;
|
|
24
|
-
|
|
25
|
-
* if an object, the build function is used.
|
|
26
|
-
*/
|
|
27
|
-
definition: KeyDefinition<T>;
|
|
16
|
+
definition: KeyDefinition<S>;
|
|
28
17
|
}
|
|
29
|
-
|
|
30
|
-
* Configuration for a sort key.
|
|
31
|
-
*/
|
|
32
|
-
export interface SortKeyConfig<T> {
|
|
33
|
-
/** The attribute name for the sort key in DynamoDB */
|
|
18
|
+
export interface SortKeyConfig<S extends z.ZodType<any>> {
|
|
34
19
|
name: string;
|
|
35
|
-
|
|
36
|
-
definition: KeyDefinition<T>;
|
|
20
|
+
definition: KeyDefinition<S>;
|
|
37
21
|
}
|
|
38
|
-
|
|
39
|
-
* Configuration for a Global Secondary Index (GSI).
|
|
40
|
-
*/
|
|
41
|
-
export interface GSIConfig<T> {
|
|
42
|
-
/** The name of the GSI in DynamoDB */
|
|
22
|
+
export interface GSIConfig<S extends z.ZodType<any>> {
|
|
43
23
|
name: string;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/** The sort key configuration for the GSI, if any */
|
|
47
|
-
sort?: SortKeyConfig<T>;
|
|
24
|
+
primary: PrimaryKeyConfig<S>;
|
|
25
|
+
sort?: SortKeyConfig<S>;
|
|
48
26
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
export interface KeysConfig<T> {
|
|
53
|
-
primary: PrimaryKeyConfig<T>;
|
|
54
|
-
sort?: SortKeyConfig<T>;
|
|
27
|
+
export interface KeysConfig<S extends z.ZodType<any>> {
|
|
28
|
+
primary: PrimaryKeyConfig<S>;
|
|
29
|
+
sort?: SortKeyConfig<S>;
|
|
55
30
|
gsis?: {
|
|
56
|
-
[gsiName: string]: GSIConfig<
|
|
31
|
+
[gsiName: string]: GSIConfig<S>;
|
|
57
32
|
};
|
|
58
33
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*/
|
|
62
|
-
export interface BetterDDBOptions<T> {
|
|
63
|
-
schema: ZodSchema<T>;
|
|
34
|
+
export interface BetterDDBOptions<S extends z.ZodType<any>> {
|
|
35
|
+
schema: S;
|
|
64
36
|
tableName: string;
|
|
65
37
|
entityName: string;
|
|
66
|
-
keys: KeysConfig<
|
|
38
|
+
keys: KeysConfig<S>;
|
|
67
39
|
client: DynamoDB.DocumentClient;
|
|
68
|
-
/**
|
|
69
|
-
* If true, automatically inject timestamp fields:
|
|
70
|
-
* - On create, sets both `createdAt` and `updatedAt`
|
|
71
|
-
* - On update, sets `updatedAt`
|
|
72
|
-
*
|
|
73
|
-
* (T should include these fields if enabled.)
|
|
74
|
-
*/
|
|
75
40
|
autoTimestamps?: boolean;
|
|
76
41
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*/
|
|
80
|
-
export declare class BetterDDB<T> {
|
|
81
|
-
protected schema: ZodSchema<T>;
|
|
42
|
+
export declare class BetterDDB<S extends z.ZodType<any>> {
|
|
43
|
+
protected schema: S;
|
|
82
44
|
protected tableName: string;
|
|
83
45
|
protected entityName: string;
|
|
84
46
|
protected client: DynamoDB.DocumentClient;
|
|
85
|
-
protected keys: KeysConfig<
|
|
47
|
+
protected keys: KeysConfig<S>;
|
|
86
48
|
protected autoTimestamps: boolean;
|
|
87
|
-
constructor(options: BetterDDBOptions<
|
|
88
|
-
getKeys(): KeysConfig<
|
|
49
|
+
constructor(options: BetterDDBOptions<S>);
|
|
50
|
+
getKeys(): KeysConfig<S>;
|
|
89
51
|
getTableName(): string;
|
|
90
52
|
getClient(): DynamoDB.DocumentClient;
|
|
91
|
-
getSchema():
|
|
53
|
+
getSchema(): S;
|
|
92
54
|
getAutoTimestamps(): boolean;
|
|
93
|
-
protected getKeyValue(def: KeyDefinition<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Create an item:
|
|
104
|
-
* - Computes primary key and index attributes,
|
|
105
|
-
* - Optionally injects timestamps,
|
|
106
|
-
* - Validates the item and writes it to DynamoDB.
|
|
107
|
-
*/
|
|
108
|
-
create(item: T): CreateBuilder<T>;
|
|
109
|
-
/**
|
|
110
|
-
* Get an item by its primary key.
|
|
111
|
-
*/
|
|
112
|
-
get(rawKey: Partial<T>): GetBuilder<T>;
|
|
113
|
-
/**
|
|
114
|
-
* Update an item.
|
|
115
|
-
*/
|
|
116
|
-
update(key: Partial<T>, expectedVersion?: number): UpdateBuilder<T>;
|
|
117
|
-
/**
|
|
118
|
-
* Delete an item.
|
|
119
|
-
*/
|
|
120
|
-
delete(rawKey: Partial<T>): DeleteBuilder<T>;
|
|
121
|
-
/**
|
|
122
|
-
* Query items.
|
|
123
|
-
*/
|
|
124
|
-
query(key: Partial<T>): QueryBuilder<T>;
|
|
125
|
-
/**
|
|
126
|
-
* Scan for items.
|
|
127
|
-
*/
|
|
128
|
-
scan(): ScanBuilder<T>;
|
|
55
|
+
protected getKeyValue(def: KeyDefinition<S>, rawKey: Partial<SchemaType<S>>): string;
|
|
56
|
+
buildKey(rawKey: Partial<SchemaType<S>>): Record<string, any>;
|
|
57
|
+
buildIndexes(rawItem: Partial<SchemaType<S>>): Record<string, any>;
|
|
58
|
+
create(item: SchemaType<S>): CreateBuilder<SchemaType<S>>;
|
|
59
|
+
get(rawKey: Partial<SchemaType<S>>): GetBuilder<SchemaType<S>>;
|
|
60
|
+
update(key: Partial<SchemaType<S>>, expectedVersion?: number): UpdateBuilder<SchemaType<S>>;
|
|
61
|
+
delete(rawKey: Partial<SchemaType<S>>): DeleteBuilder<SchemaType<S>>;
|
|
62
|
+
query(key: Partial<SchemaType<S>>): QueryBuilder<SchemaType<S>>;
|
|
63
|
+
scan(): ScanBuilder<SchemaType<S>>;
|
|
129
64
|
}
|
package/lib/betterddb.js
CHANGED
|
@@ -7,18 +7,15 @@ 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
|
-
/**
|
|
11
|
-
* BetterDDB is a definition-based DynamoDB wrapper library.
|
|
12
|
-
*/
|
|
13
10
|
class BetterDDB {
|
|
14
11
|
constructor(options) {
|
|
15
12
|
var _a;
|
|
16
13
|
this.schema = options.schema;
|
|
17
14
|
this.tableName = options.tableName;
|
|
18
15
|
this.entityName = options.entityName.toUpperCase();
|
|
19
|
-
this.keys = options.keys;
|
|
20
16
|
this.client = options.client;
|
|
21
17
|
this.autoTimestamps = (_a = options.autoTimestamps) !== null && _a !== void 0 ? _a : false;
|
|
18
|
+
this.keys = options.keys;
|
|
22
19
|
}
|
|
23
20
|
getKeys() {
|
|
24
21
|
return this.keys;
|
|
@@ -35,106 +32,49 @@ class BetterDDB {
|
|
|
35
32
|
getAutoTimestamps() {
|
|
36
33
|
return this.autoTimestamps;
|
|
37
34
|
}
|
|
38
|
-
// Helper: Retrieve the key value from a KeyDefinition.
|
|
39
35
|
getKeyValue(def, rawKey) {
|
|
40
36
|
if (typeof def === 'string' || typeof def === 'number' || typeof def === 'symbol') {
|
|
41
37
|
return String(rawKey[def]);
|
|
42
38
|
}
|
|
43
|
-
|
|
44
|
-
return def.build(rawKey);
|
|
45
|
-
}
|
|
39
|
+
return def.build(rawKey);
|
|
46
40
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Build the primary key from a raw key object.
|
|
49
|
-
*/
|
|
50
41
|
buildKey(rawKey) {
|
|
51
42
|
const keyObj = {};
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
typeof pkConfig.definition === 'number' ||
|
|
57
|
-
typeof pkConfig.definition === 'symbol')
|
|
58
|
-
? String(rawKey[pkConfig.definition])
|
|
59
|
-
: pkConfig.definition.build(rawKey);
|
|
60
|
-
// For sort key, if defined:
|
|
61
|
-
if (this.keys.sort) {
|
|
62
|
-
const skConfig = this.keys.sort;
|
|
63
|
-
keyObj[skConfig.name] =
|
|
64
|
-
(typeof skConfig.definition === 'string' ||
|
|
65
|
-
typeof skConfig.definition === 'number' ||
|
|
66
|
-
typeof skConfig.definition === 'symbol')
|
|
67
|
-
? String(rawKey[skConfig.definition])
|
|
68
|
-
: skConfig.definition.build(rawKey);
|
|
43
|
+
const { primary, sort } = this.keys;
|
|
44
|
+
keyObj[primary.name] = this.getKeyValue(primary.definition, rawKey);
|
|
45
|
+
if (sort) {
|
|
46
|
+
keyObj[sort.name] = this.getKeyValue(sort.definition, rawKey);
|
|
69
47
|
}
|
|
70
48
|
return keyObj;
|
|
71
49
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Build index attributes for each defined GSI.
|
|
74
|
-
*/
|
|
75
50
|
buildIndexes(rawItem) {
|
|
76
51
|
const indexAttributes = {};
|
|
77
52
|
if (this.keys.gsis) {
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
(typeof primaryConfig.definition === 'string' ||
|
|
84
|
-
typeof primaryConfig.definition === 'number' ||
|
|
85
|
-
typeof primaryConfig.definition === 'symbol')
|
|
86
|
-
? String(rawItem[primaryConfig.definition])
|
|
87
|
-
: primaryConfig.definition.build(rawItem);
|
|
88
|
-
// Compute sort index attribute if provided.
|
|
89
|
-
if (gsiConfig.sort) {
|
|
90
|
-
const sortConfig = gsiConfig.sort;
|
|
91
|
-
indexAttributes[sortConfig.name] =
|
|
92
|
-
(typeof sortConfig.definition === 'string' ||
|
|
93
|
-
typeof sortConfig.definition === 'number' ||
|
|
94
|
-
typeof sortConfig.definition === 'symbol')
|
|
95
|
-
? String(rawItem[sortConfig.definition])
|
|
96
|
-
: sortConfig.definition.build(rawItem);
|
|
53
|
+
Object.entries(this.keys.gsis).forEach(([_, gsiConfig]) => {
|
|
54
|
+
const { primary, sort } = gsiConfig;
|
|
55
|
+
indexAttributes[primary.name] = this.getKeyValue(primary.definition, rawItem);
|
|
56
|
+
if (sort) {
|
|
57
|
+
indexAttributes[sort.name] = this.getKeyValue(sort.definition, rawItem);
|
|
97
58
|
}
|
|
98
|
-
}
|
|
59
|
+
});
|
|
99
60
|
}
|
|
100
61
|
return indexAttributes;
|
|
101
62
|
}
|
|
102
|
-
/**
|
|
103
|
-
* Create an item:
|
|
104
|
-
* - Computes primary key and index attributes,
|
|
105
|
-
* - Optionally injects timestamps,
|
|
106
|
-
* - Validates the item and writes it to DynamoDB.
|
|
107
|
-
*/
|
|
108
63
|
create(item) {
|
|
109
64
|
return new create_builder_1.CreateBuilder(this, item);
|
|
110
65
|
}
|
|
111
|
-
/**
|
|
112
|
-
* Get an item by its primary key.
|
|
113
|
-
*/
|
|
114
66
|
get(rawKey) {
|
|
115
67
|
return new get_builder_1.GetBuilder(this, rawKey);
|
|
116
68
|
}
|
|
117
|
-
/**
|
|
118
|
-
* Update an item.
|
|
119
|
-
*/
|
|
120
69
|
update(key, expectedVersion) {
|
|
121
70
|
return new update_builder_1.UpdateBuilder(this, key, expectedVersion);
|
|
122
71
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Delete an item.
|
|
125
|
-
*/
|
|
126
72
|
delete(rawKey) {
|
|
127
73
|
return new delete_builder_1.DeleteBuilder(this, rawKey);
|
|
128
74
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Query items.
|
|
131
|
-
*/
|
|
132
75
|
query(key) {
|
|
133
76
|
return new query_builder_1.QueryBuilder(this, key);
|
|
134
77
|
}
|
|
135
|
-
/**
|
|
136
|
-
* Scan for items.
|
|
137
|
-
*/
|
|
138
78
|
scan() {
|
|
139
79
|
return new scan_builder_1.ScanBuilder(this);
|
|
140
80
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { DynamoDB } from 'aws-sdk';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
|
-
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export declare class CreateBuilder<S extends z.ZodType<any>> {
|
|
4
5
|
private parent;
|
|
5
6
|
private item;
|
|
6
7
|
private extraTransactItems;
|
|
7
|
-
constructor(parent: BetterDDB<
|
|
8
|
-
execute(): Promise<
|
|
8
|
+
constructor(parent: BetterDDB<S>, item: S);
|
|
9
|
+
execute(): Promise<S>;
|
|
9
10
|
transactWrite(ops: DynamoDB.DocumentClient.TransactWriteItemList | DynamoDB.DocumentClient.TransactWriteItem): this;
|
|
10
11
|
toTransactPut(): DynamoDB.DocumentClient.TransactWriteItem;
|
|
11
|
-
then<TResult1 =
|
|
12
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<
|
|
13
|
-
finally(onfinally?: (() => void) | null): Promise<
|
|
12
|
+
then<TResult1 = S, TResult2 = never>(onfulfilled?: ((value: S) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
13
|
+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<S | TResult>;
|
|
14
|
+
finally(onfinally?: (() => void) | null): Promise<S>;
|
|
14
15
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { DynamoDB } from 'aws-sdk';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
|
-
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export declare class DeleteBuilder<S extends z.ZodType<any>> {
|
|
4
5
|
private parent;
|
|
5
6
|
private key;
|
|
6
7
|
private condition?;
|
|
7
8
|
private extraTransactItems;
|
|
8
|
-
constructor(parent: BetterDDB<
|
|
9
|
+
constructor(parent: BetterDDB<S>, key: Partial<S>);
|
|
9
10
|
/**
|
|
10
11
|
* Specify a condition expression for the delete operation.
|
|
11
12
|
*/
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { DynamoDB } from 'aws-sdk';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
|
-
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export declare class GetBuilder<S extends z.ZodType<any>> {
|
|
4
5
|
private parent;
|
|
5
6
|
private key;
|
|
6
7
|
private projectionExpression?;
|
|
7
8
|
private expressionAttributeNames;
|
|
8
9
|
private extraTransactItems;
|
|
9
|
-
constructor(parent: BetterDDB<
|
|
10
|
+
constructor(parent: BetterDDB<S>, key: Partial<S>);
|
|
10
11
|
/**
|
|
11
12
|
* Specify a projection by providing an array of attribute names.
|
|
12
13
|
*/
|
|
13
|
-
withProjection(attributes: (keyof
|
|
14
|
-
execute(): Promise<
|
|
14
|
+
withProjection(attributes: (keyof S)[]): this;
|
|
15
|
+
execute(): Promise<S | null>;
|
|
15
16
|
transactGet(ops: DynamoDB.DocumentClient.TransactGetItemList | DynamoDB.DocumentClient.TransactGetItem): this;
|
|
16
17
|
toTransactGet(): DynamoDB.DocumentClient.TransactGetItem;
|
|
17
|
-
then<TResult1 =
|
|
18
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<
|
|
19
|
-
finally(onfinally?: (() => void) | null): Promise<
|
|
18
|
+
then<TResult1 = S | null, TResult2 = never>(onfulfilled?: ((value: S | null) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
19
|
+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<S | null | TResult>;
|
|
20
|
+
finally(onfinally?: (() => void) | null): Promise<S | null>;
|
|
20
21
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BetterDDB } from '../betterddb';
|
|
2
|
-
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare class QueryBuilder<S extends z.ZodType<any>> {
|
|
3
4
|
private parent;
|
|
4
5
|
private key;
|
|
5
6
|
private filters;
|
|
@@ -10,18 +11,18 @@ export declare class QueryBuilder<T> {
|
|
|
10
11
|
private limit?;
|
|
11
12
|
private lastKey?;
|
|
12
13
|
private ascending;
|
|
13
|
-
constructor(parent: BetterDDB<
|
|
14
|
+
constructor(parent: BetterDDB<S>, key: Partial<S>);
|
|
14
15
|
usingIndex(indexName: string): this;
|
|
15
16
|
sortAscending(): this;
|
|
16
17
|
sortDescending(): this;
|
|
17
|
-
where(attribute: keyof
|
|
18
|
+
where(attribute: keyof S, operator: 'eq' | 'begins_with' | 'between', values: any | [any, any]): this;
|
|
18
19
|
limitResults(limit: number): this;
|
|
19
20
|
startFrom(lastKey: Record<string, any>): this;
|
|
20
21
|
/**
|
|
21
22
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
22
23
|
*/
|
|
23
|
-
execute(): Promise<
|
|
24
|
-
then<TResult1 =
|
|
25
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<
|
|
26
|
-
finally(onfinally?: (() => void) | null): Promise<
|
|
24
|
+
execute(): Promise<S[]>;
|
|
25
|
+
then<TResult1 = S[], TResult2 = never>(onfulfilled?: ((value: S[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
26
|
+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<S[] | TResult>;
|
|
27
|
+
finally(onfinally?: (() => void) | null): Promise<S[]>;
|
|
27
28
|
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { BetterDDB } from '../betterddb';
|
|
2
|
-
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare class ScanBuilder<S extends z.ZodType<any>> {
|
|
3
4
|
private parent;
|
|
4
5
|
private filters;
|
|
5
6
|
private expressionAttributeNames;
|
|
6
7
|
private expressionAttributeValues;
|
|
7
8
|
private limit?;
|
|
8
9
|
private lastKey?;
|
|
9
|
-
constructor(parent: BetterDDB<
|
|
10
|
-
where(attribute: keyof
|
|
10
|
+
constructor(parent: BetterDDB<S>);
|
|
11
|
+
where(attribute: keyof S, operator: 'eq' | 'begins_with' | 'between', values: any | [any, any]): this;
|
|
11
12
|
limitResults(limit: number): this;
|
|
12
13
|
startFrom(lastKey: Record<string, any>): this;
|
|
13
14
|
/**
|
|
14
15
|
* Executes the scan and returns a Promise that resolves with an array of items.
|
|
15
16
|
*/
|
|
16
|
-
execute(): Promise<
|
|
17
|
-
then<TResult1 =
|
|
18
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<
|
|
19
|
-
finally(onfinally?: (() => void) | null): Promise<
|
|
17
|
+
execute(): Promise<S[]>;
|
|
18
|
+
then<TResult1 = S[], TResult2 = never>(onfulfilled?: ((value: S[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
19
|
+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<S[] | TResult>;
|
|
20
|
+
finally(onfinally?: (() => void) | null): Promise<S[]>;
|
|
20
21
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { DynamoDB } from 'aws-sdk';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
|
-
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export declare class UpdateBuilder<S extends z.ZodType<any>> {
|
|
4
5
|
private parent;
|
|
5
6
|
private key;
|
|
6
7
|
private actions;
|
|
7
8
|
private condition?;
|
|
8
9
|
private expectedVersion?;
|
|
9
10
|
private extraTransactItems;
|
|
10
|
-
constructor(parent: BetterDDB<
|
|
11
|
-
set(attrs: Partial<
|
|
12
|
-
remove(attrs: (keyof
|
|
13
|
-
add(attrs: Partial<Record<keyof
|
|
14
|
-
delete(attrs: Partial<Record<keyof
|
|
11
|
+
constructor(parent: BetterDDB<S>, key: Partial<S>, expectedVersion?: number);
|
|
12
|
+
set(attrs: Partial<S>): this;
|
|
13
|
+
remove(attrs: (keyof S)[]): this;
|
|
14
|
+
add(attrs: Partial<Record<keyof S, number | Set<any>>>): this;
|
|
15
|
+
delete(attrs: Partial<Record<keyof S, Set<any>>>): this;
|
|
15
16
|
/**
|
|
16
17
|
* Adds a condition expression to the update.
|
|
17
18
|
*/
|
|
@@ -31,5 +32,5 @@ export declare class UpdateBuilder<T> {
|
|
|
31
32
|
/**
|
|
32
33
|
* Commits the update immediately by calling the parent's update method.
|
|
33
34
|
*/
|
|
34
|
-
execute(): Promise<
|
|
35
|
+
execute(): Promise<S>;
|
|
35
36
|
}
|
package/package.json
CHANGED
package/src/betterddb.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
2
|
import { DynamoDB } from 'aws-sdk';
|
|
3
3
|
import { QueryBuilder } from './builders/query-builder';
|
|
4
4
|
import { ScanBuilder } from './builders/scan-builder';
|
|
@@ -7,117 +7,79 @@ import { CreateBuilder } from './builders/create-builder';
|
|
|
7
7
|
import { GetBuilder } from './builders/get-builder';
|
|
8
8
|
import { DeleteBuilder } from './builders/delete-builder';
|
|
9
9
|
|
|
10
|
+
export type SchemaType<S extends z.ZodType<any>> = z.infer<S>;
|
|
11
|
+
|
|
10
12
|
export type PrimaryKeyValue = string | number;
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* or an object containing a build function that computes the value.
|
|
15
|
-
* (In this design, the attribute name is provided separately.)
|
|
16
|
-
*/
|
|
17
|
-
export type KeyDefinition<T> =
|
|
18
|
-
| keyof T
|
|
14
|
+
export type KeyDefinition<S extends z.ZodType<any>> =
|
|
15
|
+
| keyof SchemaType<S>
|
|
19
16
|
| {
|
|
20
|
-
build: (rawKey: Partial<
|
|
17
|
+
build: (rawKey: Partial<SchemaType<S>>) => string;
|
|
21
18
|
};
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
* Configuration for a primary (partition) key.
|
|
25
|
-
*/
|
|
26
|
-
export interface PrimaryKeyConfig<T> {
|
|
27
|
-
/** The attribute name for the primary key in DynamoDB */
|
|
20
|
+
export interface PrimaryKeyConfig<S extends z.ZodType<any>> {
|
|
28
21
|
name: string;
|
|
29
|
-
|
|
30
|
-
* if an object, the build function is used.
|
|
31
|
-
*/
|
|
32
|
-
definition: KeyDefinition<T>;
|
|
22
|
+
definition: KeyDefinition<S>;
|
|
33
23
|
}
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
* Configuration for a sort key.
|
|
37
|
-
*/
|
|
38
|
-
export interface SortKeyConfig<T> {
|
|
39
|
-
/** The attribute name for the sort key in DynamoDB */
|
|
25
|
+
export interface SortKeyConfig<S extends z.ZodType<any>> {
|
|
40
26
|
name: string;
|
|
41
|
-
|
|
42
|
-
definition: KeyDefinition<T>;
|
|
27
|
+
definition: KeyDefinition<S>;
|
|
43
28
|
}
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
* Configuration for a Global Secondary Index (GSI).
|
|
47
|
-
*/
|
|
48
|
-
export interface GSIConfig<T> {
|
|
49
|
-
/** The name of the GSI in DynamoDB */
|
|
30
|
+
export interface GSIConfig<S extends z.ZodType<any>> {
|
|
50
31
|
name: string;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
/** The sort key configuration for the GSI, if any */
|
|
54
|
-
sort?: SortKeyConfig<T>;
|
|
32
|
+
primary: PrimaryKeyConfig<S>;
|
|
33
|
+
sort?: SortKeyConfig<S>;
|
|
55
34
|
}
|
|
56
35
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export interface KeysConfig<T> {
|
|
61
|
-
primary: PrimaryKeyConfig<T>;
|
|
62
|
-
sort?: SortKeyConfig<T>;
|
|
36
|
+
export interface KeysConfig<S extends z.ZodType<any>> {
|
|
37
|
+
primary: PrimaryKeyConfig<S>;
|
|
38
|
+
sort?: SortKeyConfig<S>;
|
|
63
39
|
gsis?: {
|
|
64
|
-
[gsiName: string]: GSIConfig<
|
|
40
|
+
[gsiName: string]: GSIConfig<S>;
|
|
65
41
|
};
|
|
66
42
|
}
|
|
67
43
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*/
|
|
71
|
-
export interface BetterDDBOptions<T> {
|
|
72
|
-
schema: ZodSchema<T>;
|
|
44
|
+
export interface BetterDDBOptions<S extends z.ZodType<any>> {
|
|
45
|
+
schema: S;
|
|
73
46
|
tableName: string;
|
|
74
47
|
entityName: string;
|
|
75
|
-
keys: KeysConfig<
|
|
48
|
+
keys: KeysConfig<S>;
|
|
76
49
|
client: DynamoDB.DocumentClient;
|
|
77
|
-
/**
|
|
78
|
-
* If true, automatically inject timestamp fields:
|
|
79
|
-
* - On create, sets both `createdAt` and `updatedAt`
|
|
80
|
-
* - On update, sets `updatedAt`
|
|
81
|
-
*
|
|
82
|
-
* (T should include these fields if enabled.)
|
|
83
|
-
*/
|
|
84
50
|
autoTimestamps?: boolean;
|
|
85
51
|
}
|
|
86
52
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*/
|
|
90
|
-
export class BetterDDB<T> {
|
|
91
|
-
protected schema: ZodSchema<T>;
|
|
53
|
+
export class BetterDDB<S extends z.ZodType<any>> {
|
|
54
|
+
protected schema: S;
|
|
92
55
|
protected tableName: string;
|
|
93
56
|
protected entityName: string;
|
|
94
57
|
protected client: DynamoDB.DocumentClient;
|
|
95
|
-
protected keys: KeysConfig<
|
|
58
|
+
protected keys: KeysConfig<S>;
|
|
96
59
|
protected autoTimestamps: boolean;
|
|
97
60
|
|
|
98
|
-
constructor(options: BetterDDBOptions<
|
|
61
|
+
constructor(options: BetterDDBOptions<S>) {
|
|
99
62
|
this.schema = options.schema;
|
|
100
63
|
this.tableName = options.tableName;
|
|
101
64
|
this.entityName = options.entityName.toUpperCase();
|
|
102
|
-
this.keys = options.keys;
|
|
103
65
|
this.client = options.client;
|
|
104
66
|
this.autoTimestamps = options.autoTimestamps ?? false;
|
|
67
|
+
this.keys = options.keys;
|
|
105
68
|
}
|
|
106
69
|
|
|
107
|
-
public getKeys(): KeysConfig<
|
|
70
|
+
public getKeys(): KeysConfig<S> {
|
|
108
71
|
return this.keys;
|
|
109
72
|
}
|
|
110
|
-
|
|
73
|
+
|
|
111
74
|
public getTableName(): string {
|
|
112
75
|
return this.tableName;
|
|
113
76
|
}
|
|
114
|
-
|
|
77
|
+
|
|
115
78
|
public getClient(): DynamoDB.DocumentClient {
|
|
116
79
|
return this.client;
|
|
117
80
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
public getSchema(): ZodSchema<T> {
|
|
81
|
+
|
|
82
|
+
public getSchema(): S {
|
|
121
83
|
return this.schema;
|
|
122
84
|
}
|
|
123
85
|
|
|
@@ -125,118 +87,70 @@ export class BetterDDB<T> {
|
|
|
125
87
|
return this.autoTimestamps;
|
|
126
88
|
}
|
|
127
89
|
|
|
128
|
-
|
|
129
|
-
|
|
90
|
+
protected getKeyValue(
|
|
91
|
+
def: KeyDefinition<S>,
|
|
92
|
+
rawKey: Partial<SchemaType<S>>
|
|
93
|
+
): string {
|
|
130
94
|
if (typeof def === 'string' || typeof def === 'number' || typeof def === 'symbol') {
|
|
131
|
-
return String(rawKey[def]);
|
|
132
|
-
} else {
|
|
133
|
-
return def.build(rawKey);
|
|
95
|
+
return String(rawKey[def as keyof typeof rawKey]);
|
|
134
96
|
}
|
|
97
|
+
return def.build(rawKey);
|
|
135
98
|
}
|
|
136
99
|
|
|
137
|
-
|
|
138
|
-
* Build the primary key from a raw key object.
|
|
139
|
-
*/
|
|
140
|
-
public buildKey(rawKey: Partial<T>): Record<string, any> {
|
|
100
|
+
public buildKey(rawKey: Partial<SchemaType<S>>): Record<string, any> {
|
|
141
101
|
const keyObj: Record<string, any> = {};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
typeof pkConfig.definition === 'symbol')
|
|
149
|
-
? String((rawKey as any)[pkConfig.definition])
|
|
150
|
-
: pkConfig.definition.build(rawKey);
|
|
151
|
-
|
|
152
|
-
// For sort key, if defined:
|
|
153
|
-
if (this.keys.sort) {
|
|
154
|
-
const skConfig = this.keys.sort;
|
|
155
|
-
keyObj[skConfig.name] =
|
|
156
|
-
(typeof skConfig.definition === 'string' ||
|
|
157
|
-
typeof skConfig.definition === 'number' ||
|
|
158
|
-
typeof skConfig.definition === 'symbol')
|
|
159
|
-
? String((rawKey as any)[skConfig.definition])
|
|
160
|
-
: skConfig.definition.build(rawKey);
|
|
102
|
+
const { primary, sort } = this.keys;
|
|
103
|
+
|
|
104
|
+
keyObj[primary.name] = this.getKeyValue(primary.definition, rawKey);
|
|
105
|
+
|
|
106
|
+
if (sort) {
|
|
107
|
+
keyObj[sort.name] = this.getKeyValue(sort.definition, rawKey);
|
|
161
108
|
}
|
|
109
|
+
|
|
162
110
|
return keyObj;
|
|
163
111
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
* Build index attributes for each defined GSI.
|
|
167
|
-
*/
|
|
168
|
-
public buildIndexes(rawItem: Partial<T>): Record<string, any> {
|
|
112
|
+
|
|
113
|
+
public buildIndexes(rawItem: Partial<SchemaType<S>>): Record<string, any> {
|
|
169
114
|
const indexAttributes: Record<string, any> = {};
|
|
115
|
+
|
|
170
116
|
if (this.keys.gsis) {
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
(typeof primaryConfig.definition === 'string' ||
|
|
178
|
-
typeof primaryConfig.definition === 'number' ||
|
|
179
|
-
typeof primaryConfig.definition === 'symbol')
|
|
180
|
-
? String((rawItem as any)[primaryConfig.definition])
|
|
181
|
-
: primaryConfig.definition.build(rawItem);
|
|
182
|
-
|
|
183
|
-
// Compute sort index attribute if provided.
|
|
184
|
-
if (gsiConfig.sort) {
|
|
185
|
-
const sortConfig = gsiConfig.sort;
|
|
186
|
-
indexAttributes[sortConfig.name] =
|
|
187
|
-
(typeof sortConfig.definition === 'string' ||
|
|
188
|
-
typeof sortConfig.definition === 'number' ||
|
|
189
|
-
typeof sortConfig.definition === 'symbol')
|
|
190
|
-
? String((rawItem as any)[sortConfig.definition])
|
|
191
|
-
: sortConfig.definition.build(rawItem);
|
|
117
|
+
Object.entries(this.keys.gsis).forEach(([_, gsiConfig]) => {
|
|
118
|
+
const { primary, sort } = gsiConfig;
|
|
119
|
+
indexAttributes[primary.name] = this.getKeyValue(primary.definition, rawItem);
|
|
120
|
+
|
|
121
|
+
if (sort) {
|
|
122
|
+
indexAttributes[sort.name] = this.getKeyValue(sort.definition, rawItem);
|
|
192
123
|
}
|
|
193
|
-
}
|
|
124
|
+
});
|
|
194
125
|
}
|
|
126
|
+
|
|
195
127
|
return indexAttributes;
|
|
196
128
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
* - Computes primary key and index attributes,
|
|
201
|
-
* - Optionally injects timestamps,
|
|
202
|
-
* - Validates the item and writes it to DynamoDB.
|
|
203
|
-
*/
|
|
204
|
-
public create(item: T): CreateBuilder<T> {
|
|
205
|
-
return new CreateBuilder<T>(this, item);
|
|
129
|
+
|
|
130
|
+
public create(item: SchemaType<S>): CreateBuilder<SchemaType<S>> {
|
|
131
|
+
return new CreateBuilder<SchemaType<S>>(this, item);
|
|
206
132
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
*/
|
|
211
|
-
public get(rawKey: Partial<T>): GetBuilder<T> {
|
|
212
|
-
return new GetBuilder<T>(this, rawKey);
|
|
133
|
+
|
|
134
|
+
public get(rawKey: Partial<SchemaType<S>>): GetBuilder<SchemaType<S>> {
|
|
135
|
+
return new GetBuilder<SchemaType<S>>(this, rawKey);
|
|
213
136
|
}
|
|
214
137
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return new UpdateBuilder<
|
|
138
|
+
public update(
|
|
139
|
+
key: Partial<SchemaType<S>>,
|
|
140
|
+
expectedVersion?: number
|
|
141
|
+
): UpdateBuilder<SchemaType<S>> {
|
|
142
|
+
return new UpdateBuilder<SchemaType<S>>(this, key, expectedVersion);
|
|
220
143
|
}
|
|
221
144
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
*/
|
|
225
|
-
public delete(rawKey: Partial<T>): DeleteBuilder<T> {
|
|
226
|
-
return new DeleteBuilder<T>(this, rawKey);
|
|
145
|
+
public delete(rawKey: Partial<SchemaType<S>>): DeleteBuilder<SchemaType<S>> {
|
|
146
|
+
return new DeleteBuilder<SchemaType<S>>(this, rawKey);
|
|
227
147
|
}
|
|
228
148
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
*/
|
|
232
|
-
public query(key: Partial<T>): QueryBuilder<T> {
|
|
233
|
-
return new QueryBuilder<T>(this, key);
|
|
149
|
+
public query(key: Partial<SchemaType<S>>): QueryBuilder<SchemaType<S>> {
|
|
150
|
+
return new QueryBuilder<SchemaType<S>>(this, key);
|
|
234
151
|
}
|
|
235
152
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
*/
|
|
239
|
-
public scan(): ScanBuilder<T> {
|
|
240
|
-
return new ScanBuilder<T>(this);
|
|
153
|
+
public scan(): ScanBuilder<SchemaType<S>> {
|
|
154
|
+
return new ScanBuilder<SchemaType<S>>(this);
|
|
241
155
|
}
|
|
242
|
-
}
|
|
156
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { DynamoDB } from 'aws-sdk';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
4
|
-
export class CreateBuilder<
|
|
5
|
+
export class CreateBuilder<S extends z.ZodType<any>> {
|
|
5
6
|
private extraTransactItems: DynamoDB.DocumentClient.TransactWriteItemList = [];
|
|
6
7
|
|
|
7
|
-
constructor(private parent: BetterDDB<
|
|
8
|
+
constructor(private parent: BetterDDB<S>, private item: S) {}
|
|
8
9
|
|
|
9
|
-
public async execute(): Promise<
|
|
10
|
+
public async execute(): Promise<S> {
|
|
10
11
|
if (this.extraTransactItems.length > 0) {
|
|
11
12
|
// Build our update transaction item.
|
|
12
13
|
const myTransactItem = this.toTransactPut();
|
|
@@ -25,7 +26,7 @@ export class CreateBuilder<T> {
|
|
|
25
26
|
let item = this.item;
|
|
26
27
|
if (this.parent.getAutoTimestamps()) {
|
|
27
28
|
const now = new Date().toISOString();
|
|
28
|
-
item = { ...item, createdAt: now, updatedAt: now } as
|
|
29
|
+
item = { ...item, createdAt: now, updatedAt: now } as S;
|
|
29
30
|
}
|
|
30
31
|
// Validate the item using the schema.
|
|
31
32
|
const validated = this.parent.getSchema().parse(item);
|
|
@@ -65,18 +66,18 @@ export class CreateBuilder<T> {
|
|
|
65
66
|
return { Put: putItem };
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
public then<TResult1 =
|
|
69
|
-
onfulfilled?: ((value:
|
|
69
|
+
public then<TResult1 = S, TResult2 = never>(
|
|
70
|
+
onfulfilled?: ((value: S) => TResult1 | PromiseLike<TResult1>) | null,
|
|
70
71
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
71
72
|
): Promise<TResult1 | TResult2> {
|
|
72
73
|
return this.execute().then(onfulfilled, onrejected);
|
|
73
74
|
}
|
|
74
75
|
public catch<TResult = never>(
|
|
75
76
|
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
76
|
-
): Promise<
|
|
77
|
+
): Promise<S | TResult> {
|
|
77
78
|
return this.execute().catch(onrejected);
|
|
78
79
|
}
|
|
79
|
-
public finally(onfinally?: (() => void) | null): Promise<
|
|
80
|
+
public finally(onfinally?: (() => void) | null): Promise<S> {
|
|
80
81
|
return this.execute().finally(onfinally);
|
|
81
82
|
}
|
|
82
83
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { DynamoDB } from 'aws-sdk';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
4
|
-
export class DeleteBuilder<
|
|
5
|
+
export class DeleteBuilder<S extends z.ZodType<any>> {
|
|
5
6
|
private condition?: { expression: string; attributeValues: Record<string, any> };
|
|
6
7
|
private extraTransactItems: DynamoDB.DocumentClient.TransactWriteItemList = [];
|
|
7
|
-
constructor(private parent: BetterDDB<
|
|
8
|
+
constructor(private parent: BetterDDB<S>, private key: Partial<S>) {}
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Specify a condition expression for the delete operation.
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { DynamoDB } from 'aws-sdk';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
4
|
-
export class GetBuilder<
|
|
5
|
+
export class GetBuilder<S extends z.ZodType<any>> {
|
|
5
6
|
private projectionExpression?: string;
|
|
6
7
|
private expressionAttributeNames: Record<string, string> = {};
|
|
7
8
|
private extraTransactItems: DynamoDB.DocumentClient.TransactGetItemList = [];
|
|
8
|
-
constructor(private parent: BetterDDB<
|
|
9
|
+
constructor(private parent: BetterDDB<S>, private key: Partial<S>) {}
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Specify a projection by providing an array of attribute names.
|
|
12
13
|
*/
|
|
13
|
-
public withProjection(attributes: (keyof
|
|
14
|
+
public withProjection(attributes: (keyof S)[]): this {
|
|
14
15
|
this.projectionExpression = attributes.map(attr => `#${String(attr)}`).join(', ');
|
|
15
16
|
for (const attr of attributes) {
|
|
16
17
|
this.expressionAttributeNames[`#${String(attr)}`] = String(attr);
|
|
@@ -18,7 +19,7 @@ export class GetBuilder<T> {
|
|
|
18
19
|
return this;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
public async execute(): Promise<
|
|
22
|
+
public async execute(): Promise<S | null> {
|
|
22
23
|
if (this.extraTransactItems.length > 0) {
|
|
23
24
|
// Build our update transaction item.
|
|
24
25
|
const myTransactItem = this.toTransactGet();
|
|
@@ -66,18 +67,18 @@ export class GetBuilder<T> {
|
|
|
66
67
|
return { Get: getItem };
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
public then<TResult1 =
|
|
70
|
-
onfulfilled?: ((value:
|
|
70
|
+
public then<TResult1 = S | null, TResult2 = never>(
|
|
71
|
+
onfulfilled?: ((value: S | null) => TResult1 | PromiseLike<TResult1>) | null,
|
|
71
72
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
72
73
|
): Promise<TResult1 | TResult2> {
|
|
73
74
|
return this.execute().then(onfulfilled, onrejected);
|
|
74
75
|
}
|
|
75
76
|
public catch<TResult = never>(
|
|
76
77
|
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
77
|
-
): Promise<
|
|
78
|
+
): Promise<S | null | TResult> {
|
|
78
79
|
return this.execute().catch(onrejected);
|
|
79
80
|
}
|
|
80
|
-
public finally(onfinally?: (() => void) | null): Promise<
|
|
81
|
+
public finally(onfinally?: (() => void) | null): Promise<S | null> {
|
|
81
82
|
return this.execute().finally(onfinally);
|
|
82
83
|
}
|
|
83
84
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { DynamoDB } from 'aws-sdk';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
4
|
-
export class QueryBuilder<
|
|
5
|
+
export class QueryBuilder<S extends z.ZodType<any>> {
|
|
5
6
|
private filters: string[] = [];
|
|
6
7
|
private expressionAttributeNames: Record<string, string> = {};
|
|
7
8
|
private expressionAttributeValues: Record<string, any> = {};
|
|
@@ -11,7 +12,7 @@ export class QueryBuilder<T> {
|
|
|
11
12
|
private lastKey?: Record<string, any>;
|
|
12
13
|
private ascending: boolean = true;
|
|
13
14
|
|
|
14
|
-
constructor(private parent: BetterDDB<
|
|
15
|
+
constructor(private parent: BetterDDB<S>, private key: Partial<S>) {}
|
|
15
16
|
|
|
16
17
|
public usingIndex(indexName: string): this {
|
|
17
18
|
this.indexName = indexName;
|
|
@@ -29,7 +30,7 @@ export class QueryBuilder<T> {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
public where(
|
|
32
|
-
attribute: keyof
|
|
33
|
+
attribute: keyof S,
|
|
33
34
|
operator: 'eq' | 'begins_with' | 'between',
|
|
34
35
|
values: any | [any, any]
|
|
35
36
|
): this {
|
|
@@ -73,7 +74,7 @@ export class QueryBuilder<T> {
|
|
|
73
74
|
/**
|
|
74
75
|
* Executes the query and returns a Promise that resolves with an array of items.
|
|
75
76
|
*/
|
|
76
|
-
public async execute(): Promise<
|
|
77
|
+
public async execute(): Promise<S[]> {
|
|
77
78
|
// Build a simple key condition for the partition key.
|
|
78
79
|
const keys = this.parent.getKeys();
|
|
79
80
|
const pkName = keys.primary.name;
|
|
@@ -110,18 +111,18 @@ export class QueryBuilder<T> {
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
// Thenable implementation.
|
|
113
|
-
public then<TResult1 =
|
|
114
|
-
onfulfilled?: ((value:
|
|
114
|
+
public then<TResult1 = S[], TResult2 = never>(
|
|
115
|
+
onfulfilled?: ((value: S[]) => TResult1 | PromiseLike<TResult1>) | null,
|
|
115
116
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
116
117
|
): Promise<TResult1 | TResult2> {
|
|
117
118
|
return this.execute().then(onfulfilled, onrejected);
|
|
118
119
|
}
|
|
119
120
|
public catch<TResult = never>(
|
|
120
121
|
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
121
|
-
): Promise<
|
|
122
|
+
): Promise<S[] | TResult> {
|
|
122
123
|
return this.execute().catch(onrejected);
|
|
123
124
|
}
|
|
124
|
-
public finally(onfinally?: (() => void) | null): Promise<
|
|
125
|
+
public finally(onfinally?: (() => void) | null): Promise<S[]> {
|
|
125
126
|
return this.execute().finally(onfinally);
|
|
126
127
|
}
|
|
127
128
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { DynamoDB } from 'aws-sdk';
|
|
2
2
|
import { BetterDDB } from '../betterddb';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
4
|
-
export class ScanBuilder<
|
|
5
|
+
export class ScanBuilder<S extends z.ZodType<any>> {
|
|
5
6
|
private filters: string[] = [];
|
|
6
7
|
private expressionAttributeNames: Record<string, string> = {};
|
|
7
8
|
private expressionAttributeValues: Record<string, any> = {};
|
|
8
9
|
private limit?: number;
|
|
9
10
|
private lastKey?: Record<string, any>;
|
|
10
11
|
|
|
11
|
-
constructor(private parent: BetterDDB<
|
|
12
|
+
constructor(private parent: BetterDDB<S>) {}
|
|
12
13
|
|
|
13
14
|
public where(
|
|
14
|
-
attribute: keyof
|
|
15
|
+
attribute: keyof S,
|
|
15
16
|
operator: 'eq' | 'begins_with' | 'between',
|
|
16
17
|
values: any | [any, any]
|
|
17
18
|
): this {
|
|
@@ -55,7 +56,7 @@ export class ScanBuilder<T> {
|
|
|
55
56
|
/**
|
|
56
57
|
* Executes the scan and returns a Promise that resolves with an array of items.
|
|
57
58
|
*/
|
|
58
|
-
public async execute(): Promise<
|
|
59
|
+
public async execute(): Promise<S[]> {
|
|
59
60
|
const params: DynamoDB.DocumentClient.ScanInput = {
|
|
60
61
|
TableName: this.parent.getTableName(),
|
|
61
62
|
ExpressionAttributeNames: this.expressionAttributeNames,
|
|
@@ -73,18 +74,18 @@ export class ScanBuilder<T> {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
// Thenable implementation.
|
|
76
|
-
public then<TResult1 =
|
|
77
|
-
onfulfilled?: ((value:
|
|
77
|
+
public then<TResult1 = S[], TResult2 = never>(
|
|
78
|
+
onfulfilled?: ((value: S[]) => TResult1 | PromiseLike<TResult1>) | null,
|
|
78
79
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
79
80
|
): Promise<TResult1 | TResult2> {
|
|
80
81
|
return this.execute().then(onfulfilled, onrejected);
|
|
81
82
|
}
|
|
82
83
|
public catch<TResult = never>(
|
|
83
84
|
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
84
|
-
): Promise<
|
|
85
|
+
): Promise<S[] | TResult> {
|
|
85
86
|
return this.execute().catch(onrejected);
|
|
86
87
|
}
|
|
87
|
-
public finally(onfinally?: (() => void) | null): Promise<
|
|
88
|
+
public finally(onfinally?: (() => void) | null): Promise<S[]> {
|
|
88
89
|
return this.execute().finally(onfinally);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
@@ -1,43 +1,44 @@
|
|
|
1
1
|
|
|
2
2
|
import { DynamoDB } from 'aws-sdk';
|
|
3
3
|
import { BetterDDB } from '../betterddb';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
|
|
5
|
-
interface UpdateActions<
|
|
6
|
-
set?: Partial<
|
|
7
|
-
remove?: (keyof
|
|
8
|
-
add?: Partial<Record<keyof
|
|
9
|
-
delete?: Partial<Record<keyof
|
|
6
|
+
interface UpdateActions<S extends z.ZodType<any>> {
|
|
7
|
+
set?: Partial<S>;
|
|
8
|
+
remove?: (keyof S)[];
|
|
9
|
+
add?: Partial<Record<keyof S, number | Set<any>>>;
|
|
10
|
+
delete?: Partial<Record<keyof S, Set<any>>>;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export class UpdateBuilder<
|
|
13
|
-
private actions: UpdateActions<
|
|
13
|
+
export class UpdateBuilder<S extends z.ZodType<any>> {
|
|
14
|
+
private actions: UpdateActions<S> = {};
|
|
14
15
|
private condition?: { expression: string; attributeValues: Record<string, any> };
|
|
15
16
|
private expectedVersion?: number;
|
|
16
17
|
// When using transaction mode, we store extra transaction items.
|
|
17
18
|
private extraTransactItems: DynamoDB.DocumentClient.TransactWriteItemList = [];
|
|
18
19
|
|
|
19
20
|
// Reference to the parent BetterDDB instance and key.
|
|
20
|
-
constructor(private parent: BetterDDB<
|
|
21
|
+
constructor(private parent: BetterDDB<S>, private key: Partial<S>, expectedVersion?: number) {
|
|
21
22
|
this.expectedVersion = expectedVersion;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
// Chainable methods:
|
|
25
|
-
public set(attrs: Partial<
|
|
26
|
+
public set(attrs: Partial<S>): this {
|
|
26
27
|
this.actions.set = { ...this.actions.set, ...attrs };
|
|
27
28
|
return this;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
public remove(attrs: (keyof
|
|
31
|
+
public remove(attrs: (keyof S)[]): this {
|
|
31
32
|
this.actions.remove = [...(this.actions.remove || []), ...attrs];
|
|
32
33
|
return this;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
public add(attrs: Partial<Record<keyof
|
|
36
|
+
public add(attrs: Partial<Record<keyof S, number | Set<any>>>): this {
|
|
36
37
|
this.actions.add = { ...this.actions.add, ...attrs };
|
|
37
38
|
return this;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
public delete(attrs: Partial<Record<keyof
|
|
41
|
+
public delete(attrs: Partial<Record<keyof S, Set<any>>>): this {
|
|
41
42
|
this.actions.delete = { ...this.actions.delete, ...attrs };
|
|
42
43
|
return this;
|
|
43
44
|
}
|
|
@@ -191,7 +192,7 @@ export class UpdateBuilder<T> {
|
|
|
191
192
|
/**
|
|
192
193
|
* Commits the update immediately by calling the parent's update method.
|
|
193
194
|
*/
|
|
194
|
-
public async execute(): Promise<
|
|
195
|
+
public async execute(): Promise<S> {
|
|
195
196
|
if (this.extraTransactItems.length > 0) {
|
|
196
197
|
// Build our update transaction item.
|
|
197
198
|
const myTransactItem = this.toTransactUpdate();
|
package/test/create.test.ts
CHANGED
|
@@ -24,15 +24,16 @@ const UserSchema = z.object({
|
|
|
24
24
|
email: z.string().email(),
|
|
25
25
|
createdAt: z.string(),
|
|
26
26
|
updatedAt: z.string(),
|
|
27
|
-
})
|
|
27
|
+
});
|
|
28
28
|
|
|
29
29
|
const userDdb = new BetterDDB({
|
|
30
30
|
schema: UserSchema,
|
|
31
31
|
tableName: TEST_TABLE,
|
|
32
32
|
entityName: ENTITY_NAME,
|
|
33
33
|
keys: {
|
|
34
|
-
primary: { name:
|
|
35
|
-
sort: { name:
|
|
34
|
+
primary: { name: "pk", definition: { build: (raw) => `USER#${raw.id}` } },
|
|
35
|
+
sort: { name: "sk", definition: { build: (raw) => `EMAIL#${raw.email}` } },
|
|
36
|
+
gsis: { gsi1: { name: 'gsi1', primary: { name: "gsi1pk", definition: { build: (raw) => "NAME" } }, sort: { name: "gsi1sk", definition: { build: (raw) => `NAME#${raw.name}` } } } },
|
|
36
37
|
},
|
|
37
38
|
client,
|
|
38
39
|
autoTimestamps: true,
|
|
@@ -49,11 +50,24 @@ afterAll(async () => {
|
|
|
49
50
|
describe('BetterDDB - Create Operation', () => {
|
|
50
51
|
it('should insert an item using CreateBuilder', async () => {
|
|
51
52
|
const user = { id: 'user-123', name: 'John Doe', email: 'john@example.com' };
|
|
53
|
+
|
|
52
54
|
await userDdb.create(user as any).execute();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
expect(
|
|
57
|
-
expect(
|
|
55
|
+
|
|
56
|
+
const result = await client.get({ TableName: TEST_TABLE, Key: { id: 'user-123', email: 'john@example.com' } }).promise();
|
|
57
|
+
|
|
58
|
+
expect(result).not.toBeNull();
|
|
59
|
+
expect(result.Item).not.toBeNull();
|
|
60
|
+
expect(result.Item?.pk).toBe('USER#user-123');
|
|
61
|
+
expect(result.Item?.sk).toBe('EMAIL#john@example.com');
|
|
62
|
+
expect(result.Item?.gsi1pk).toBe('NAME');
|
|
63
|
+
expect(result.Item?.gsi1sk).toBe('NAME#John Doe');
|
|
64
|
+
expect(result.Item?.id).toBe('user-123');
|
|
65
|
+
expect(result.Item?.createdAt).not.toBeNull();
|
|
66
|
+
expect(result.Item?.updatedAt).not.toBeNull();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should fails to validate and not insert an item', async () => {
|
|
70
|
+
const user = { id: 'user-123', email: 'john@example.com' };
|
|
71
|
+
await expect(userDdb.create(user as any).execute()).rejects.toThrow();
|
|
58
72
|
});
|
|
59
73
|
});
|
package/test/delete.test.ts
CHANGED
package/test/get.test.ts
CHANGED
package/test/query.test.ts
CHANGED
package/test/scan.test.ts
CHANGED