betterddb 0.2.0 → 0.3.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/lib/betterddb.d.ts +34 -41
- package/lib/betterddb.js +47 -363
- package/lib/builders/create-builder.d.ts +14 -0
- package/lib/builders/create-builder.js +74 -0
- package/lib/builders/delete-builder.d.ts +19 -0
- package/lib/builders/delete-builder.js +80 -0
- package/lib/builders/get-builder.d.ts +20 -0
- package/lib/builders/get-builder.js +79 -0
- package/lib/builders/query-builder.d.ts +27 -0
- package/lib/builders/query-builder.js +106 -0
- package/lib/builders/scan-builder.d.ts +20 -0
- package/lib/builders/scan-builder.js +76 -0
- package/lib/builders/update-builder.d.ts +35 -0
- package/lib/builders/update-builder.js +205 -0
- package/package.json +1 -1
- package/src/betterddb.ts +61 -422
- package/src/builders/create-builder.ts +82 -0
- package/src/builders/delete-builder.ts +84 -0
- package/src/builders/get-builder.ts +83 -0
- package/src/builders/query-builder.ts +127 -0
- package/src/builders/scan-builder.ts +90 -0
- package/src/builders/update-builder.ts +230 -0
- package/test/placeholder.test.ts +7 -6
package/lib/betterddb.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { ZodSchema } from 'zod';
|
|
2
2
|
import { DynamoDB } from 'aws-sdk';
|
|
3
|
+
import { QueryBuilder } from './builders/query-builder';
|
|
4
|
+
import { ScanBuilder } from './builders/scan-builder';
|
|
5
|
+
import { UpdateBuilder } from './builders/update-builder';
|
|
6
|
+
import { CreateBuilder } from './builders/create-builder';
|
|
7
|
+
import { GetBuilder } from './builders/get-builder';
|
|
8
|
+
import { DeleteBuilder } from './builders/delete-builder';
|
|
3
9
|
export type PrimaryKeyValue = string | number;
|
|
4
10
|
/**
|
|
5
11
|
* A key definition can be either a simple key (a property name)
|
|
@@ -56,6 +62,7 @@ export interface KeysConfig<T> {
|
|
|
56
62
|
export interface BetterDDBOptions<T> {
|
|
57
63
|
schema: ZodSchema<T>;
|
|
58
64
|
tableName: string;
|
|
65
|
+
entityName: string;
|
|
59
66
|
keys: KeysConfig<T>;
|
|
60
67
|
client: DynamoDB.DocumentClient;
|
|
61
68
|
/**
|
|
@@ -73,64 +80,50 @@ export interface BetterDDBOptions<T> {
|
|
|
73
80
|
export declare class BetterDDB<T> {
|
|
74
81
|
protected schema: ZodSchema<T>;
|
|
75
82
|
protected tableName: string;
|
|
83
|
+
protected entityName: string;
|
|
76
84
|
protected client: DynamoDB.DocumentClient;
|
|
77
85
|
protected keys: KeysConfig<T>;
|
|
78
86
|
protected autoTimestamps: boolean;
|
|
79
87
|
constructor(options: BetterDDBOptions<T>);
|
|
88
|
+
getKeys(): KeysConfig<T>;
|
|
89
|
+
getTableName(): string;
|
|
90
|
+
getClient(): DynamoDB.DocumentClient;
|
|
91
|
+
getSchema(): ZodSchema<T>;
|
|
92
|
+
getAutoTimestamps(): boolean;
|
|
80
93
|
protected getKeyValue(def: KeyDefinition<T>, rawKey: Partial<T>): string;
|
|
81
94
|
/**
|
|
82
95
|
* Build the primary key from a raw key object.
|
|
83
96
|
*/
|
|
84
|
-
|
|
97
|
+
buildKey(rawKey: Partial<T>): Record<string, any>;
|
|
85
98
|
/**
|
|
86
99
|
* Build index attributes for each defined GSI.
|
|
87
100
|
*/
|
|
88
|
-
|
|
101
|
+
buildIndexes(rawItem: Partial<T>): Record<string, any>;
|
|
89
102
|
/**
|
|
90
103
|
* Create an item:
|
|
91
104
|
* - Computes primary key and index attributes,
|
|
92
105
|
* - Optionally injects timestamps,
|
|
93
106
|
* - Validates the item and writes it to DynamoDB.
|
|
94
107
|
*/
|
|
95
|
-
create(item: T):
|
|
96
|
-
get(rawKey: Partial<T>): Promise<T | null>;
|
|
97
|
-
update(rawKey: Partial<T>, update: Partial<T>, options?: {
|
|
98
|
-
expectedVersion?: number;
|
|
99
|
-
}): Promise<T>;
|
|
100
|
-
delete(rawKey: Partial<T>): Promise<void>;
|
|
101
|
-
queryByGsi(gsiName: string, key: Partial<T>, sortKeyCondition?: {
|
|
102
|
-
operator: 'eq' | 'begins_with' | 'between';
|
|
103
|
-
values: any | [any, any];
|
|
104
|
-
}): Promise<T[]>;
|
|
108
|
+
create(item: T): CreateBuilder<T>;
|
|
105
109
|
/**
|
|
106
|
-
*
|
|
110
|
+
* Get an item by its primary key.
|
|
107
111
|
*/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
transactGet(getItems: {
|
|
126
|
-
TableName: string;
|
|
127
|
-
Key: any;
|
|
128
|
-
}[]): Promise<T[]>;
|
|
129
|
-
batchWrite(ops: {
|
|
130
|
-
puts?: T[];
|
|
131
|
-
deletes?: Partial<T>[];
|
|
132
|
-
}): Promise<void>;
|
|
133
|
-
private batchWriteChunk;
|
|
134
|
-
private retryBatchWrite;
|
|
135
|
-
batchGet(rawKeys: Partial<T>[]): Promise<T[]>;
|
|
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>;
|
|
136
129
|
}
|
package/lib/betterddb.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BetterDDB = void 0;
|
|
4
|
+
const query_builder_1 = require("./builders/query-builder");
|
|
5
|
+
const scan_builder_1 = require("./builders/scan-builder");
|
|
6
|
+
const update_builder_1 = require("./builders/update-builder");
|
|
7
|
+
const create_builder_1 = require("./builders/create-builder");
|
|
8
|
+
const get_builder_1 = require("./builders/get-builder");
|
|
9
|
+
const delete_builder_1 = require("./builders/delete-builder");
|
|
4
10
|
/**
|
|
5
11
|
* BetterDDB is a definition-based DynamoDB wrapper library.
|
|
6
12
|
*/
|
|
@@ -9,10 +15,26 @@ class BetterDDB {
|
|
|
9
15
|
var _a;
|
|
10
16
|
this.schema = options.schema;
|
|
11
17
|
this.tableName = options.tableName;
|
|
18
|
+
this.entityName = options.entityName.toUpperCase();
|
|
12
19
|
this.keys = options.keys;
|
|
13
20
|
this.client = options.client;
|
|
14
21
|
this.autoTimestamps = (_a = options.autoTimestamps) !== null && _a !== void 0 ? _a : false;
|
|
15
22
|
}
|
|
23
|
+
getKeys() {
|
|
24
|
+
return this.keys;
|
|
25
|
+
}
|
|
26
|
+
getTableName() {
|
|
27
|
+
return this.tableName;
|
|
28
|
+
}
|
|
29
|
+
getClient() {
|
|
30
|
+
return this.client;
|
|
31
|
+
}
|
|
32
|
+
getSchema() {
|
|
33
|
+
return this.schema;
|
|
34
|
+
}
|
|
35
|
+
getAutoTimestamps() {
|
|
36
|
+
return this.autoTimestamps;
|
|
37
|
+
}
|
|
16
38
|
// Helper: Retrieve the key value from a KeyDefinition.
|
|
17
39
|
getKeyValue(def, rawKey) {
|
|
18
40
|
if (typeof def === 'string' || typeof def === 'number' || typeof def === 'symbol') {
|
|
@@ -83,376 +105,38 @@ class BetterDDB {
|
|
|
83
105
|
* - Optionally injects timestamps,
|
|
84
106
|
* - Validates the item and writes it to DynamoDB.
|
|
85
107
|
*/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const now = new Date().toISOString();
|
|
89
|
-
item = { ...item, createdAt: now, updatedAt: now };
|
|
90
|
-
}
|
|
91
|
-
const validated = this.schema.parse(item);
|
|
92
|
-
let finalItem = { ...validated };
|
|
93
|
-
// Compute and merge primary key.
|
|
94
|
-
const computedKeys = this.buildKey(validated);
|
|
95
|
-
finalItem = { ...finalItem, ...computedKeys };
|
|
96
|
-
// Compute and merge index attributes.
|
|
97
|
-
const indexAttributes = this.buildIndexes(validated);
|
|
98
|
-
finalItem = { ...finalItem, ...indexAttributes };
|
|
99
|
-
try {
|
|
100
|
-
await this.client.put({ TableName: this.tableName, Item: finalItem }).promise();
|
|
101
|
-
return validated;
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
console.error('Error during create operation:', error);
|
|
105
|
-
throw error;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
async get(rawKey) {
|
|
109
|
-
const Key = this.buildKey(rawKey);
|
|
110
|
-
try {
|
|
111
|
-
const result = await this.client.get({ TableName: this.tableName, Key }).promise();
|
|
112
|
-
if (!result.Item)
|
|
113
|
-
return null;
|
|
114
|
-
return this.schema.parse(result.Item);
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
console.error('Error during get operation:', error);
|
|
118
|
-
throw error;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
async update(rawKey, update, options) {
|
|
122
|
-
const ExpressionAttributeNames = {};
|
|
123
|
-
const ExpressionAttributeValues = {};
|
|
124
|
-
const UpdateExpressionParts = [];
|
|
125
|
-
const ConditionExpressionParts = [];
|
|
126
|
-
// Exclude key fields from update.
|
|
127
|
-
const keyFieldNames = [
|
|
128
|
-
this.keys.primary.name,
|
|
129
|
-
this.keys.sort ? this.keys.sort.name : undefined
|
|
130
|
-
].filter(Boolean);
|
|
131
|
-
for (const [attr, value] of Object.entries(update)) {
|
|
132
|
-
if (keyFieldNames.includes(attr))
|
|
133
|
-
continue;
|
|
134
|
-
const attributeKey = `#${attr}`;
|
|
135
|
-
const valueKey = `:${attr}`;
|
|
136
|
-
ExpressionAttributeNames[attributeKey] = attr;
|
|
137
|
-
ExpressionAttributeValues[valueKey] = value;
|
|
138
|
-
UpdateExpressionParts.push(`${attributeKey} = ${valueKey}`);
|
|
139
|
-
}
|
|
140
|
-
if (this.autoTimestamps) {
|
|
141
|
-
const now = new Date().toISOString();
|
|
142
|
-
ExpressionAttributeNames['#updatedAt'] = 'updatedAt';
|
|
143
|
-
ExpressionAttributeValues[':updatedAt'] = now;
|
|
144
|
-
UpdateExpressionParts.push('#updatedAt = :updatedAt');
|
|
145
|
-
}
|
|
146
|
-
if ((options === null || options === void 0 ? void 0 : options.expectedVersion) !== undefined) {
|
|
147
|
-
ExpressionAttributeNames['#version'] = 'version';
|
|
148
|
-
ExpressionAttributeValues[':expectedVersion'] = options.expectedVersion;
|
|
149
|
-
ExpressionAttributeValues[':newVersion'] = options.expectedVersion + 1;
|
|
150
|
-
UpdateExpressionParts.push('#version = :newVersion');
|
|
151
|
-
ConditionExpressionParts.push('#version = :expectedVersion');
|
|
152
|
-
}
|
|
153
|
-
if (UpdateExpressionParts.length === 0) {
|
|
154
|
-
throw new Error('No attributes provided to update');
|
|
155
|
-
}
|
|
156
|
-
const UpdateExpression = 'SET ' + UpdateExpressionParts.join(', ');
|
|
157
|
-
const params = {
|
|
158
|
-
TableName: this.tableName,
|
|
159
|
-
Key: this.buildKey(rawKey),
|
|
160
|
-
UpdateExpression,
|
|
161
|
-
ExpressionAttributeNames,
|
|
162
|
-
ExpressionAttributeValues,
|
|
163
|
-
ReturnValues: 'ALL_NEW'
|
|
164
|
-
};
|
|
165
|
-
if (ConditionExpressionParts.length > 0) {
|
|
166
|
-
params.ConditionExpression = ConditionExpressionParts.join(' AND ');
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
const result = await this.client.update(params).promise();
|
|
170
|
-
if (!result.Attributes) {
|
|
171
|
-
throw new Error('No attributes returned after update');
|
|
172
|
-
}
|
|
173
|
-
return this.schema.parse(result.Attributes);
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
console.error('Error during update operation:', error);
|
|
177
|
-
throw error;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
async delete(rawKey) {
|
|
181
|
-
const Key = this.buildKey(rawKey);
|
|
182
|
-
try {
|
|
183
|
-
await this.client.delete({ TableName: this.tableName, Key }).promise();
|
|
184
|
-
}
|
|
185
|
-
catch (error) {
|
|
186
|
-
console.error('Error during delete operation:', error);
|
|
187
|
-
throw error;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
async queryByGsi(gsiName, key, sortKeyCondition) {
|
|
191
|
-
if (!this.keys.gsis || !this.keys.gsis[gsiName]) {
|
|
192
|
-
throw new Error(`GSI "${gsiName}" is not configured`);
|
|
193
|
-
}
|
|
194
|
-
const indexConfig = this.keys.gsis[gsiName];
|
|
195
|
-
const ExpressionAttributeNames = {
|
|
196
|
-
[`#${indexConfig.primary.name}`]: indexConfig.primary.name
|
|
197
|
-
};
|
|
198
|
-
const ExpressionAttributeValues = {
|
|
199
|
-
[`:${indexConfig.primary.name}`]: (typeof indexConfig.primary.definition === 'string' ||
|
|
200
|
-
typeof indexConfig.primary.definition === 'number' ||
|
|
201
|
-
typeof indexConfig.primary.definition === 'symbol')
|
|
202
|
-
? String(key[indexConfig.primary.definition])
|
|
203
|
-
: indexConfig.primary.definition.build(key)
|
|
204
|
-
};
|
|
205
|
-
let KeyConditionExpression = `#${indexConfig.primary.name} = :${indexConfig.primary.name}`;
|
|
206
|
-
if (indexConfig.sort && sortKeyCondition) {
|
|
207
|
-
const skFieldName = indexConfig.sort.name;
|
|
208
|
-
ExpressionAttributeNames['#gsiSk'] = skFieldName;
|
|
209
|
-
switch (sortKeyCondition.operator) {
|
|
210
|
-
case 'eq':
|
|
211
|
-
ExpressionAttributeValues[':gsiSk'] = sortKeyCondition.values;
|
|
212
|
-
KeyConditionExpression += ' AND #gsiSk = :gsiSk';
|
|
213
|
-
break;
|
|
214
|
-
case 'begins_with':
|
|
215
|
-
ExpressionAttributeValues[':gsiSk'] = sortKeyCondition.values;
|
|
216
|
-
KeyConditionExpression += ' AND begins_with(#gsiSk, :gsiSk)';
|
|
217
|
-
break;
|
|
218
|
-
case 'between':
|
|
219
|
-
if (!Array.isArray(sortKeyCondition.values) || sortKeyCondition.values.length !== 2) {
|
|
220
|
-
throw new Error("For 'between' operator, values must be a tuple of two items");
|
|
221
|
-
}
|
|
222
|
-
ExpressionAttributeValues[':gsiSkStart'] = sortKeyCondition.values[0];
|
|
223
|
-
ExpressionAttributeValues[':gsiSkEnd'] = sortKeyCondition.values[1];
|
|
224
|
-
KeyConditionExpression += ' AND #gsiSk BETWEEN :gsiSkStart AND :gsiSkEnd';
|
|
225
|
-
break;
|
|
226
|
-
default:
|
|
227
|
-
throw new Error(`Unsupported sort key operator: ${sortKeyCondition.operator}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
try {
|
|
231
|
-
const result = await this.client
|
|
232
|
-
.query({
|
|
233
|
-
TableName: this.tableName,
|
|
234
|
-
IndexName: gsiName,
|
|
235
|
-
KeyConditionExpression,
|
|
236
|
-
ExpressionAttributeNames,
|
|
237
|
-
ExpressionAttributeValues
|
|
238
|
-
})
|
|
239
|
-
.promise();
|
|
240
|
-
return (result.Items || []).map(item => this.schema.parse(item));
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
console.error('Error during queryByGsi operation:', error);
|
|
244
|
-
throw error;
|
|
245
|
-
}
|
|
108
|
+
create(item) {
|
|
109
|
+
return new create_builder_1.CreateBuilder(this, item);
|
|
246
110
|
}
|
|
247
111
|
/**
|
|
248
|
-
*
|
|
112
|
+
* Get an item by its primary key.
|
|
249
113
|
*/
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const pkValue = (typeof this.keys.primary.definition === 'string' ||
|
|
253
|
-
typeof this.keys.primary.definition === 'number' ||
|
|
254
|
-
typeof this.keys.primary.definition === 'symbol')
|
|
255
|
-
? String(rawKey[this.keys.primary.definition])
|
|
256
|
-
: this.keys.primary.definition.build(rawKey);
|
|
257
|
-
const ExpressionAttributeNames = {
|
|
258
|
-
[`#${pkAttrName}`]: pkAttrName
|
|
259
|
-
};
|
|
260
|
-
const ExpressionAttributeValues = {
|
|
261
|
-
[`:${pkAttrName}`]: pkValue
|
|
262
|
-
};
|
|
263
|
-
let KeyConditionExpression = `#${pkAttrName} = :${pkAttrName}`;
|
|
264
|
-
if (this.keys.sort && sortKeyCondition) {
|
|
265
|
-
const skAttrName = this.keys.sort.name;
|
|
266
|
-
ExpressionAttributeNames[`#${skAttrName}`] = skAttrName;
|
|
267
|
-
switch (sortKeyCondition.operator) {
|
|
268
|
-
case 'eq':
|
|
269
|
-
ExpressionAttributeValues[':skValue'] = sortKeyCondition.values;
|
|
270
|
-
KeyConditionExpression += ` AND #${skAttrName} = :skValue`;
|
|
271
|
-
break;
|
|
272
|
-
case 'begins_with':
|
|
273
|
-
ExpressionAttributeValues[':skValue'] = sortKeyCondition.values;
|
|
274
|
-
KeyConditionExpression += ` AND begins_with(#${skAttrName}, :skValue)`;
|
|
275
|
-
break;
|
|
276
|
-
case 'between':
|
|
277
|
-
if (!Array.isArray(sortKeyCondition.values) || sortKeyCondition.values.length !== 2) {
|
|
278
|
-
throw new Error("For 'between' operator, values must be a tuple of two items");
|
|
279
|
-
}
|
|
280
|
-
ExpressionAttributeValues[':skStart'] = sortKeyCondition.values[0];
|
|
281
|
-
ExpressionAttributeValues[':skEnd'] = sortKeyCondition.values[1];
|
|
282
|
-
KeyConditionExpression += ` AND #${skAttrName} BETWEEN :skStart AND :skEnd`;
|
|
283
|
-
break;
|
|
284
|
-
default:
|
|
285
|
-
throw new Error(`Unsupported sort key operator: ${sortKeyCondition.operator}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
const queryParams = {
|
|
289
|
-
TableName: this.tableName,
|
|
290
|
-
KeyConditionExpression,
|
|
291
|
-
ExpressionAttributeNames,
|
|
292
|
-
ExpressionAttributeValues
|
|
293
|
-
};
|
|
294
|
-
if (options === null || options === void 0 ? void 0 : options.limit) {
|
|
295
|
-
queryParams.Limit = options.limit;
|
|
296
|
-
}
|
|
297
|
-
if (options === null || options === void 0 ? void 0 : options.lastKey) {
|
|
298
|
-
queryParams.ExclusiveStartKey = options.lastKey;
|
|
299
|
-
}
|
|
300
|
-
const result = await this.client.query(queryParams).promise();
|
|
301
|
-
const items = (result.Items || []).map(item => this.schema.parse(item));
|
|
302
|
-
return { items, lastKey: result.LastEvaluatedKey };
|
|
303
|
-
}
|
|
304
|
-
// ───── Transaction Helpers ─────────────────────────────
|
|
305
|
-
buildTransactPut(item) {
|
|
306
|
-
const computedKeys = this.buildKey(item);
|
|
307
|
-
const indexAttributes = this.buildIndexes(item);
|
|
308
|
-
const finalItem = { ...item, ...computedKeys, ...indexAttributes };
|
|
309
|
-
const validated = this.schema.parse(finalItem);
|
|
310
|
-
return {
|
|
311
|
-
Put: {
|
|
312
|
-
TableName: this.tableName,
|
|
313
|
-
Item: validated
|
|
314
|
-
}
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
buildTransactUpdate(rawKey, update, options) {
|
|
318
|
-
const ExpressionAttributeNames = {};
|
|
319
|
-
const ExpressionAttributeValues = {};
|
|
320
|
-
const UpdateExpressionParts = [];
|
|
321
|
-
const ConditionExpressionParts = [];
|
|
322
|
-
const keyFieldNames = [
|
|
323
|
-
this.keys.primary.name,
|
|
324
|
-
this.keys.sort ? this.keys.sort.name : undefined
|
|
325
|
-
].filter(Boolean);
|
|
326
|
-
for (const [attr, value] of Object.entries(update)) {
|
|
327
|
-
if (keyFieldNames.includes(attr))
|
|
328
|
-
continue;
|
|
329
|
-
const attributeKey = `#${attr}`;
|
|
330
|
-
const valueKey = `:${attr}`;
|
|
331
|
-
ExpressionAttributeNames[attributeKey] = attr;
|
|
332
|
-
ExpressionAttributeValues[valueKey] = value;
|
|
333
|
-
UpdateExpressionParts.push(`${attributeKey} = ${valueKey}`);
|
|
334
|
-
}
|
|
335
|
-
if (this.autoTimestamps) {
|
|
336
|
-
const now = new Date().toISOString();
|
|
337
|
-
ExpressionAttributeNames['#updatedAt'] = 'updatedAt';
|
|
338
|
-
ExpressionAttributeValues[':updatedAt'] = now;
|
|
339
|
-
UpdateExpressionParts.push('#updatedAt = :updatedAt');
|
|
340
|
-
}
|
|
341
|
-
if ((options === null || options === void 0 ? void 0 : options.expectedVersion) !== undefined) {
|
|
342
|
-
ExpressionAttributeNames['#version'] = 'version';
|
|
343
|
-
ExpressionAttributeValues[':expectedVersion'] = options.expectedVersion;
|
|
344
|
-
ExpressionAttributeValues[':newVersion'] = options.expectedVersion + 1;
|
|
345
|
-
UpdateExpressionParts.push('#version = :newVersion');
|
|
346
|
-
ConditionExpressionParts.push('#version = :expectedVersion');
|
|
347
|
-
}
|
|
348
|
-
if (UpdateExpressionParts.length === 0) {
|
|
349
|
-
throw new Error('No attributes provided to update in transactUpdate');
|
|
350
|
-
}
|
|
351
|
-
const UpdateExpression = 'SET ' + UpdateExpressionParts.join(', ');
|
|
352
|
-
const updateItem = {
|
|
353
|
-
TableName: this.tableName,
|
|
354
|
-
Key: this.buildKey(rawKey),
|
|
355
|
-
UpdateExpression,
|
|
356
|
-
ExpressionAttributeNames,
|
|
357
|
-
ExpressionAttributeValues
|
|
358
|
-
};
|
|
359
|
-
if (ConditionExpressionParts.length > 0) {
|
|
360
|
-
updateItem.ConditionExpression = ConditionExpressionParts.join(' AND ');
|
|
361
|
-
}
|
|
362
|
-
return { Update: updateItem };
|
|
363
|
-
}
|
|
364
|
-
buildTransactDelete(rawKey) {
|
|
365
|
-
return {
|
|
366
|
-
Delete: {
|
|
367
|
-
TableName: this.tableName,
|
|
368
|
-
Key: this.buildKey(rawKey)
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
async transactWrite(operations) {
|
|
373
|
-
try {
|
|
374
|
-
await this.client.transactWrite({ TransactItems: operations }).promise();
|
|
375
|
-
}
|
|
376
|
-
catch (error) {
|
|
377
|
-
console.error('Error during transactWrite operation:', error);
|
|
378
|
-
throw error;
|
|
379
|
-
}
|
|
114
|
+
get(rawKey) {
|
|
115
|
+
return new get_builder_1.GetBuilder(this, rawKey);
|
|
380
116
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
try {
|
|
387
|
-
const response = await this.client
|
|
388
|
-
.transactGet({
|
|
389
|
-
TransactItems: getItems.map(item => ({ Get: item }))
|
|
390
|
-
})
|
|
391
|
-
.promise();
|
|
392
|
-
return (response.Responses || [])
|
|
393
|
-
.filter(r => r.Item)
|
|
394
|
-
.map(r => this.schema.parse(r.Item));
|
|
395
|
-
}
|
|
396
|
-
catch (error) {
|
|
397
|
-
console.error('Error during transactGet operation:', error);
|
|
398
|
-
throw error;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
// ───── Batch Write Support ─────────────────────────────
|
|
402
|
-
async batchWrite(ops) {
|
|
403
|
-
const putRequests = (ops.puts || []).map(item => {
|
|
404
|
-
const computedKeys = this.buildKey(item);
|
|
405
|
-
const indexAttributes = this.buildIndexes(item);
|
|
406
|
-
const finalItem = { ...item, ...computedKeys, ...indexAttributes };
|
|
407
|
-
const validated = this.schema.parse(finalItem);
|
|
408
|
-
return { PutRequest: { Item: validated } };
|
|
409
|
-
});
|
|
410
|
-
const deleteRequests = (ops.deletes || []).map(rawKey => {
|
|
411
|
-
const key = this.buildKey(rawKey);
|
|
412
|
-
return { DeleteRequest: { Key: key } };
|
|
413
|
-
});
|
|
414
|
-
const allRequests = [...putRequests, ...deleteRequests];
|
|
415
|
-
for (let i = 0; i < allRequests.length; i += 25) {
|
|
416
|
-
const chunk = allRequests.slice(i, i + 25);
|
|
417
|
-
let unprocessed = await this.batchWriteChunk(chunk);
|
|
418
|
-
while (unprocessed && Object.keys(unprocessed).length > 0) {
|
|
419
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
420
|
-
unprocessed = await this.retryBatchWrite(unprocessed);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
117
|
+
/**
|
|
118
|
+
* Update an item.
|
|
119
|
+
*/
|
|
120
|
+
update(key, expectedVersion) {
|
|
121
|
+
return new update_builder_1.UpdateBuilder(this, key, expectedVersion);
|
|
423
122
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
};
|
|
430
|
-
const result = await this.client.batchWrite(params).promise();
|
|
431
|
-
return result.UnprocessedItems;
|
|
123
|
+
/**
|
|
124
|
+
* Delete an item.
|
|
125
|
+
*/
|
|
126
|
+
delete(rawKey) {
|
|
127
|
+
return new delete_builder_1.DeleteBuilder(this, rawKey);
|
|
432
128
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Query items.
|
|
131
|
+
*/
|
|
132
|
+
query(key) {
|
|
133
|
+
return new query_builder_1.QueryBuilder(this, key);
|
|
437
134
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const chunk = keys.slice(i, i + 100);
|
|
444
|
-
const params = {
|
|
445
|
-
RequestItems: {
|
|
446
|
-
[this.tableName]: {
|
|
447
|
-
Keys: chunk
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
const result = await this.client.batchGet(params).promise();
|
|
452
|
-
const items = result.Responses ? result.Responses[this.tableName] : [];
|
|
453
|
-
results.push(...items.map(item => this.schema.parse(item)));
|
|
454
|
-
}
|
|
455
|
-
return results;
|
|
135
|
+
/**
|
|
136
|
+
* Scan for items.
|
|
137
|
+
*/
|
|
138
|
+
scan() {
|
|
139
|
+
return new scan_builder_1.ScanBuilder(this);
|
|
456
140
|
}
|
|
457
141
|
}
|
|
458
142
|
exports.BetterDDB = BetterDDB;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DynamoDB } from 'aws-sdk';
|
|
2
|
+
import { BetterDDB } from '../betterddb';
|
|
3
|
+
export declare class CreateBuilder<T> {
|
|
4
|
+
private parent;
|
|
5
|
+
private item;
|
|
6
|
+
private extraTransactItems;
|
|
7
|
+
constructor(parent: BetterDDB<T>, item: T);
|
|
8
|
+
execute(): Promise<T>;
|
|
9
|
+
transactWrite(ops: DynamoDB.DocumentClient.TransactWriteItemList | DynamoDB.DocumentClient.TransactWriteItem): this;
|
|
10
|
+
toTransactPut(): DynamoDB.DocumentClient.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
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CreateBuilder = void 0;
|
|
4
|
+
class CreateBuilder {
|
|
5
|
+
constructor(parent, item) {
|
|
6
|
+
this.parent = parent;
|
|
7
|
+
this.item = item;
|
|
8
|
+
this.extraTransactItems = [];
|
|
9
|
+
}
|
|
10
|
+
async execute() {
|
|
11
|
+
if (this.extraTransactItems.length > 0) {
|
|
12
|
+
// Build our update transaction item.
|
|
13
|
+
const myTransactItem = this.toTransactPut();
|
|
14
|
+
// Combine with extra transaction items.
|
|
15
|
+
const allItems = [...this.extraTransactItems, myTransactItem];
|
|
16
|
+
await this.parent.getClient().transactWrite({
|
|
17
|
+
TransactItems: allItems
|
|
18
|
+
}).promise();
|
|
19
|
+
// After transaction, retrieve the updated item.
|
|
20
|
+
const result = await this.parent.get(this.item).execute();
|
|
21
|
+
if (result === null) {
|
|
22
|
+
throw new Error('Item not found after transaction create');
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
let item = this.item;
|
|
28
|
+
if (this.parent.getAutoTimestamps()) {
|
|
29
|
+
const now = new Date().toISOString();
|
|
30
|
+
item = { ...item, createdAt: now, updatedAt: now };
|
|
31
|
+
}
|
|
32
|
+
// Validate the item using the schema.
|
|
33
|
+
const validated = this.parent.getSchema().parse(item);
|
|
34
|
+
let finalItem = { ...validated };
|
|
35
|
+
// Compute and merge primary key.
|
|
36
|
+
const computedKeys = this.parent.buildKey(validated);
|
|
37
|
+
finalItem = { ...finalItem, ...computedKeys };
|
|
38
|
+
// Compute and merge index attributes.
|
|
39
|
+
const indexAttributes = this.parent.buildIndexes(validated);
|
|
40
|
+
finalItem = { ...finalItem, ...indexAttributes };
|
|
41
|
+
await this.parent.getClient().put({
|
|
42
|
+
TableName: this.parent.getTableName(),
|
|
43
|
+
Item: finalItem
|
|
44
|
+
}).promise();
|
|
45
|
+
return validated;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
transactWrite(ops) {
|
|
49
|
+
if (Array.isArray(ops)) {
|
|
50
|
+
this.extraTransactItems.push(...ops);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.extraTransactItems.push(ops);
|
|
54
|
+
}
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
toTransactPut() {
|
|
58
|
+
const putItem = {
|
|
59
|
+
TableName: this.parent.getTableName(),
|
|
60
|
+
Item: this.item,
|
|
61
|
+
};
|
|
62
|
+
return { Put: putItem };
|
|
63
|
+
}
|
|
64
|
+
then(onfulfilled, onrejected) {
|
|
65
|
+
return this.execute().then(onfulfilled, onrejected);
|
|
66
|
+
}
|
|
67
|
+
catch(onrejected) {
|
|
68
|
+
return this.execute().catch(onrejected);
|
|
69
|
+
}
|
|
70
|
+
finally(onfinally) {
|
|
71
|
+
return this.execute().finally(onfinally);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.CreateBuilder = CreateBuilder;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DynamoDB } from 'aws-sdk';
|
|
2
|
+
import { BetterDDB } from '../betterddb';
|
|
3
|
+
export declare class DeleteBuilder<T> {
|
|
4
|
+
private parent;
|
|
5
|
+
private key;
|
|
6
|
+
private condition?;
|
|
7
|
+
private extraTransactItems;
|
|
8
|
+
constructor(parent: BetterDDB<T>, key: Partial<T>);
|
|
9
|
+
/**
|
|
10
|
+
* Specify a condition expression for the delete operation.
|
|
11
|
+
*/
|
|
12
|
+
withCondition(expression: string, attributeValues: Record<string, any>): this;
|
|
13
|
+
execute(): Promise<void>;
|
|
14
|
+
transactWrite(ops: DynamoDB.DocumentClient.TransactWriteItemList | DynamoDB.DocumentClient.TransactWriteItem): this;
|
|
15
|
+
toTransactDelete(): DynamoDB.DocumentClient.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
|
+
}
|