dynoquery 0.1.20 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -215,6 +215,33 @@ const userToDelete = db.User('john@example.com').draftDelete('METADATA');
215
215
  await db.batchWrite([userToDelete]);
216
216
  ```
217
217
 
218
+ ### 5. Transaction Operations
219
+
220
+ DynoQuery supports `transactWrite` and `transactRead` for atomic operations across multiple items.
221
+
222
+ ```typescript
223
+ // 1. Transaction Write (All operations succeed or all fail)
224
+ const user1 = db.User('john@example.com', 'METADATA');
225
+ user1.name = 'John Doe';
226
+
227
+ const userToDelete = db.User('olduser@example.com').draftDelete('METADATA');
228
+
229
+ // You can even set conditions for items in a transaction
230
+ const criticalItem = db.User('admin@example.com', 'METADATA');
231
+ criticalItem.lastLogin = new Date().toISOString();
232
+ criticalItem.setCondition(attr('status').equals('ACTIVE'));
233
+
234
+ await db.transactWrite([user1, userToDelete, criticalItem]);
235
+
236
+ // 2. Transaction Read (Read multiple items atomically)
237
+ const userDraft = db.User('john@example.com', 'METADATA');
238
+ const items = await db.transactRead([userDraft]);
239
+
240
+ if (items[0]) {
241
+ console.log('Found user:', items[0].name);
242
+ }
243
+ ```
244
+
218
245
  ## Optional Configuration Parameters
219
246
 
220
247
  | Parameter | Type | Default | Description |
@@ -259,6 +286,55 @@ if (token) {
259
286
  }
260
287
  ```
261
288
 
289
+ ### Expression Builder (Filters & Conditions)
290
+
291
+ Use `ExpressionBuilder` to build complex filter and condition expressions in a type-safe way.
292
+
293
+ ```typescript
294
+ import { attr, ExpressionBuilder } from 'dynoquery';
295
+
296
+ // 1. Filtering in queries
297
+ const builder = attr('age').greaterThan(25).and(attr('status').equals('ACTIVE'));
298
+ const activeUsers = await db.User('some-id').getAll({ filterBuilder: builder });
299
+
300
+ // 2. Conditional Updates
301
+ const johnMeta = db.User('john@example.com', 'METADATA');
302
+ const condition = attr('version').equals(1);
303
+
304
+ johnMeta.setCondition(condition);
305
+ johnMeta.name = 'John New Name';
306
+ johnMeta.version = 2;
307
+
308
+ await johnMeta.save(); // Fails if version is not 1
309
+
310
+ // 3. Raw Condition Expressions
311
+ await db.User('john@example.com').update({ status: 'INACTIVE' }, [], {
312
+ ConditionExpression: '#v = :v',
313
+ ExpressionAttributeNames: { '#v': 'version' },
314
+ ExpressionAttributeValues: { ':v': 2 }
315
+ });
316
+
317
+ // Or using Item object
318
+ johnMeta.setCondition(attr('name').exists());
319
+ await johnMeta.save();
320
+
321
+ // 4. Supported Operators
322
+ // - attr('name').equals('val') / notEquals('val')
323
+ // - .lessThan(val) / lessThanOrEqual(val)
324
+ // - .greaterThan(val) / greaterThanOrEqual(val)
325
+ // - .between(start, end)
326
+ // - .in([val1, val2])
327
+ // - logical: .and(otherBuilder), .or(otherBuilder), ExpressionBuilder.not(builder)
328
+
329
+ // 4. Supported Functions
330
+ // - attr('field').exists()
331
+ // - attr('field').notExists()
332
+ // - attr('field').type('S')
333
+ // - attr('field').beginsWith('prefix')
334
+ // - attr('field').contains('value')
335
+ // - attr('field').size().greaterThan(5)
336
+ ```
337
+
262
338
  ## API Reference
263
339
 
264
340
  ### DynoQuery
@@ -270,15 +346,17 @@ if (token) {
270
346
  - `scan(params)`: Low-level ScanCommand wrapper.
271
347
  - `batchWrite(items)`: Batch persists multiple `Item` objects.
272
348
  - `batchRead(items)`: Batch fetches multiple `Item` or `IndexQuery` objects.
349
+ - `transactWrite(items)`: Performs atomic write operations (Put/Delete) for up to 100 items.
350
+ - `transactRead(items)`: Performs atomic read operations (Get) for up to 100 items.
273
351
  - `[ModelName](id, skValue?)`: Returns a `Partition` instance for the given ID. If `skValue` is provided, returns an `Item` object directly.
274
352
  - `findBy[IndexName](id, skValue?)`: Returns an `IndexQuery` instance.
275
353
 
276
354
  ### Partition
277
355
  - `get(skValue)`: Fetches data for a specific Sort Key value (returns a Promise).
278
- - `getAll(options?)`: Fetches items in the partition. Options: `{ limit, exclusiveStartKey }`.
279
- - `create(skValue, data, indices?)`: Creates an item. `indices` is an array of `IndexQuery` for GSI population.
280
- - `update(skValue, data)`: Partial update of an item.
281
- - `delete(skValue)`: Deletes an item.
356
+ - `getAll(options?)`: Fetches items in the partition. Options: `{ limit, exclusiveStartKey, filterBuilder, FilterExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
357
+ - `create(skValue, data, indices?, options?)`: Creates an item. `options`: `{ conditionBuilder, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
358
+ - `update(skValue, data, indices?, options?)`: Partial update of an item. `options`: `{ conditionBuilder, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
359
+ - `delete(skValue, options?)`: Deletes an item. `options`: `{ conditionBuilder, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
282
360
  - `draft(skValue, data?)`: Returns an `Item` object initialized with `data` (optional).
283
361
  - `draftDelete(skValue)`: Returns an `Item` object marked for deletion (for use with `batchWrite`).
284
362
  - `deleteAll()`: Deletes all items in the partition.
@@ -286,14 +364,16 @@ if (token) {
286
364
 
287
365
  ### IndexQuery
288
366
  - `get(skValue?)`: Get a single item from the index.
289
- - `getAll(options?)`: Query index. Options: `{ limit, scanIndexForward, exclusiveStartKey, skValue }`.
367
+ - `getAll(options?)`: Query index. Options: `{ limit, scanIndexForward, exclusiveStartKey, skValue, filterBuilder, FilterExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
290
368
  - `getLastEvaluatedKey()`: Returns the pagination token from the last `getAll()`.
291
369
 
292
370
  ### Item (returned by Partition.get or draft)
293
- - `create(data?, indices?)`: Persists the item as a new record with the provided data. Supports GSI indices.
294
- - `update(data, indices?)`: Partial update of the item. Supports GSI indices.
295
- - `save()`: Persists the current state of the item (alias for update of all properties). Uses indices attached via `setIndex()`.
296
- - `setIndex(indices)`: Attaches one or more `IndexQuery` objects to the item for use with `save()` or `create()`.
371
+ - `create(data?, indices?)`: Persists the item as a new record with the provided data. Supports GSI indices and internal `conditionBuilder`.
372
+ - `update(data, indices?)`: Partial update of the item. Supports GSI indices and internal `conditionBuilder`.
373
+ - `save()`: Persists the current state of the item. Uses indices attached via `setIndex()` and internal `conditionBuilder`.
374
+ - `setIndex(indices)`: Attaches one or more `IndexQuery` objects to the item.
375
+ - `setFilter(builder)`: Sets a filter expression builder for the item.
376
+ - `setCondition(builder)`: Sets a condition for the item using an `ExpressionBuilder`.
297
377
 
298
378
  ## License
299
379
 
@@ -0,0 +1,41 @@
1
+ export type Operand = string | number | ExpressionBuilder;
2
+ export declare class ExpressionBuilder {
3
+ private parts;
4
+ private attributeNames;
5
+ private attributeValues;
6
+ private valueCounter;
7
+ private nameCounter;
8
+ constructor();
9
+ static field(name: string): ExpressionBuilder;
10
+ static value(val: any): ExpressionBuilder;
11
+ where(field: string, operator: string, value: any): ExpressionBuilder;
12
+ equals(value: any): ExpressionBuilder;
13
+ notEquals(value: any): ExpressionBuilder;
14
+ lessThan(value: any): ExpressionBuilder;
15
+ lessThanOrEqual(value: any): ExpressionBuilder;
16
+ greaterThan(value: any): ExpressionBuilder;
17
+ greaterThanOrEqual(value: any): ExpressionBuilder;
18
+ between(start: any, end: any): ExpressionBuilder;
19
+ in(values: any[]): ExpressionBuilder;
20
+ exists(): ExpressionBuilder;
21
+ notExists(): ExpressionBuilder;
22
+ type(type: string): ExpressionBuilder;
23
+ attributeExists(path: string): ExpressionBuilder;
24
+ attributeNotExists(path: string): ExpressionBuilder;
25
+ attributeType(path: string, type: string): ExpressionBuilder;
26
+ beginsWith(pathOrSubstr: string, substr?: string): ExpressionBuilder;
27
+ contains(pathOrValue: string, value?: any): ExpressionBuilder;
28
+ size(path?: string): ExpressionBuilder;
29
+ and(other: ExpressionBuilder): ExpressionBuilder;
30
+ or(other: ExpressionBuilder): ExpressionBuilder;
31
+ static not(condition: ExpressionBuilder): ExpressionBuilder;
32
+ build(attributeNames?: Record<string, string>, attributeValues?: Record<string, any>, counters?: {
33
+ name: number;
34
+ value: number;
35
+ }): {
36
+ expression: string;
37
+ attributeNames: Record<string, string>;
38
+ attributeValues: Record<string, any>;
39
+ };
40
+ }
41
+ export declare function attr(name: string): ExpressionBuilder;
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExpressionBuilder = void 0;
4
+ exports.attr = attr;
5
+ class ExpressionBuilder {
6
+ constructor() {
7
+ this.parts = [];
8
+ this.attributeNames = {};
9
+ this.attributeValues = {};
10
+ this.valueCounter = 0;
11
+ this.nameCounter = 0;
12
+ }
13
+ static field(name) {
14
+ const builder = new ExpressionBuilder();
15
+ builder.parts.push({ type: 'field', name });
16
+ return builder;
17
+ }
18
+ static value(val) {
19
+ const builder = new ExpressionBuilder();
20
+ builder.parts.push({ type: 'value', value: val });
21
+ return builder;
22
+ }
23
+ where(field, operator, value) {
24
+ this.parts.push({ type: 'condition', field, operator, value });
25
+ return this;
26
+ }
27
+ equals(value) {
28
+ this.parts.push({ type: 'operator', operator: '=', value });
29
+ return this;
30
+ }
31
+ notEquals(value) {
32
+ this.parts.push({ type: 'operator', operator: '<>', value });
33
+ return this;
34
+ }
35
+ lessThan(value) {
36
+ this.parts.push({ type: 'operator', operator: '<', value });
37
+ return this;
38
+ }
39
+ lessThanOrEqual(value) {
40
+ this.parts.push({ type: 'operator', operator: '<=', value });
41
+ return this;
42
+ }
43
+ greaterThan(value) {
44
+ this.parts.push({ type: 'operator', operator: '>', value });
45
+ return this;
46
+ }
47
+ greaterThanOrEqual(value) {
48
+ this.parts.push({ type: 'operator', operator: '>=', value });
49
+ return this;
50
+ }
51
+ between(start, end) {
52
+ this.parts.push({ type: 'between', start, end });
53
+ return this;
54
+ }
55
+ in(values) {
56
+ this.parts.push({ type: 'in', values });
57
+ return this;
58
+ }
59
+ exists() {
60
+ const last = this.parts.pop();
61
+ if (last && last.type === 'field') {
62
+ this.parts.push({ type: 'function', name: 'attribute_exists', args: [last] });
63
+ }
64
+ return this;
65
+ }
66
+ notExists() {
67
+ const last = this.parts.pop();
68
+ if (last && last.type === 'field') {
69
+ this.parts.push({ type: 'function', name: 'attribute_not_exists', args: [last] });
70
+ }
71
+ return this;
72
+ }
73
+ type(type) {
74
+ const last = this.parts.pop();
75
+ if (last && last.type === 'field') {
76
+ this.parts.push({ type: 'function', name: 'attribute_type', args: [last, { type: 'value', value: type }] });
77
+ }
78
+ return this;
79
+ }
80
+ attributeExists(path) {
81
+ this.parts.push({ type: 'function', name: 'attribute_exists', args: [{ type: 'field', name: path }] });
82
+ return this;
83
+ }
84
+ attributeNotExists(path) {
85
+ this.parts.push({ type: 'function', name: 'attribute_not_exists', args: [{ type: 'field', name: path }] });
86
+ return this;
87
+ }
88
+ attributeType(path, type) {
89
+ this.parts.push({ type: 'function', name: 'attribute_type', args: [{ type: 'field', name: path }, { type: 'value', value: type }] });
90
+ return this;
91
+ }
92
+ beginsWith(pathOrSubstr, substr) {
93
+ if (substr !== undefined) {
94
+ // Legacy static-like call: builder.beginsWith('field', 'prefix')
95
+ this.parts.push({ type: 'function', name: 'begins_with', args: [{ type: 'field', name: pathOrSubstr }, { type: 'value', value: substr }] });
96
+ }
97
+ else {
98
+ // Fluent call: attr('field').beginsWith('prefix')
99
+ const last = this.parts.pop();
100
+ if (last && last.type === 'field') {
101
+ this.parts.push({ type: 'function', name: 'begins_with', args: [last, { type: 'value', value: pathOrSubstr }] });
102
+ }
103
+ }
104
+ return this;
105
+ }
106
+ contains(pathOrValue, value) {
107
+ if (value !== undefined) {
108
+ // Legacy static-like call: builder.contains('field', 'value')
109
+ this.parts.push({ type: 'function', name: 'contains', args: [{ type: 'field', name: pathOrValue }, { type: 'value', value }] });
110
+ }
111
+ else {
112
+ // Fluent call: attr('field').contains('value')
113
+ const last = this.parts.pop();
114
+ if (last && last.type === 'field') {
115
+ this.parts.push({ type: 'function', name: 'contains', args: [last, { type: 'value', value: pathOrValue }] });
116
+ }
117
+ }
118
+ return this;
119
+ }
120
+ size(path) {
121
+ if (path) {
122
+ // Legacy: builder.size('field')
123
+ const builder = new ExpressionBuilder();
124
+ builder.parts.push({ type: 'function', name: 'size', args: [{ type: 'field', name: path }] });
125
+ return builder;
126
+ }
127
+ else {
128
+ // Fluent: attr('field').size()
129
+ const last = this.parts.pop();
130
+ if (last && last.type === 'field') {
131
+ this.parts.push({ type: 'function', name: 'size', args: [last] });
132
+ }
133
+ return this;
134
+ }
135
+ }
136
+ and(other) {
137
+ const newBuilder = new ExpressionBuilder();
138
+ newBuilder.parts.push({ type: 'group', builder: this });
139
+ newBuilder.parts.push({ type: 'logical', operator: 'AND' });
140
+ newBuilder.parts.push({ type: 'group', builder: other });
141
+ return newBuilder;
142
+ }
143
+ or(other) {
144
+ const newBuilder = new ExpressionBuilder();
145
+ newBuilder.parts.push({ type: 'group', builder: this });
146
+ newBuilder.parts.push({ type: 'logical', operator: 'OR' });
147
+ newBuilder.parts.push({ type: 'group', builder: other });
148
+ return newBuilder;
149
+ }
150
+ static not(condition) {
151
+ const builder = new ExpressionBuilder();
152
+ builder.parts.push({ type: 'logical', operator: 'NOT' });
153
+ builder.parts.push({ type: 'group', builder: condition });
154
+ return builder;
155
+ }
156
+ build(attributeNames = {}, attributeValues = {}, counters = { name: 0, value: 0 }) {
157
+ let expressionParts = [];
158
+ for (const part of this.parts) {
159
+ switch (part.type) {
160
+ case 'field': {
161
+ const namePlaceholder = `#n${counters.name++}`;
162
+ attributeNames[namePlaceholder] = part.name;
163
+ expressionParts.push(namePlaceholder);
164
+ break;
165
+ }
166
+ case 'value': {
167
+ const valuePlaceholder = `:v${counters.value++}`;
168
+ attributeValues[valuePlaceholder] = part.value;
169
+ expressionParts.push(valuePlaceholder);
170
+ break;
171
+ }
172
+ case 'condition': {
173
+ const namePlaceholder = `#n${counters.name++}`;
174
+ attributeNames[namePlaceholder] = part.field;
175
+ const valuePlaceholder = `:v${counters.value++}`;
176
+ attributeValues[valuePlaceholder] = part.value;
177
+ expressionParts.push(`${namePlaceholder} ${part.operator} ${valuePlaceholder}`);
178
+ break;
179
+ }
180
+ case 'operator': {
181
+ const valuePlaceholder = `:v${counters.value++}`;
182
+ attributeValues[valuePlaceholder] = part.value;
183
+ const last = expressionParts.pop();
184
+ expressionParts.push(`${last} ${part.operator} ${valuePlaceholder}`);
185
+ break;
186
+ }
187
+ case 'between': {
188
+ const startPlaceholder = `:v${counters.value++}`;
189
+ attributeValues[startPlaceholder] = part.start;
190
+ const endPlaceholder = `:v${counters.value++}`;
191
+ attributeValues[endPlaceholder] = part.end;
192
+ const last = expressionParts.pop();
193
+ expressionParts.push(`${last} BETWEEN ${startPlaceholder} AND ${endPlaceholder}`);
194
+ break;
195
+ }
196
+ case 'in': {
197
+ const placeholders = part.values.map((v) => {
198
+ const vp = `:v${counters.value++}`;
199
+ attributeValues[vp] = v;
200
+ return vp;
201
+ });
202
+ const last = expressionParts.pop();
203
+ expressionParts.push(`${last} IN (${placeholders.join(', ')})`);
204
+ break;
205
+ }
206
+ case 'function': {
207
+ const args = part.args.map((arg) => {
208
+ if (arg.type === 'field') {
209
+ const np = `#n${counters.name++}`;
210
+ attributeNames[np] = arg.name;
211
+ return np;
212
+ }
213
+ else if (arg.type === 'value') {
214
+ const vp = `:v${counters.value++}`;
215
+ attributeValues[vp] = arg.value;
216
+ return vp;
217
+ }
218
+ else if (arg instanceof ExpressionBuilder) {
219
+ const sub = arg.build(attributeNames, attributeValues, counters);
220
+ return sub.expression;
221
+ }
222
+ return arg;
223
+ });
224
+ expressionParts.push(`${part.name}(${args.join(', ')})`);
225
+ break;
226
+ }
227
+ case 'logical': {
228
+ expressionParts.push(part.operator);
229
+ break;
230
+ }
231
+ case 'group': {
232
+ const sub = part.builder.build(attributeNames, attributeValues, counters);
233
+ expressionParts.push(`(${sub.expression})`);
234
+ break;
235
+ }
236
+ }
237
+ }
238
+ return {
239
+ expression: expressionParts.join(' '),
240
+ attributeNames,
241
+ attributeValues
242
+ };
243
+ }
244
+ }
245
+ exports.ExpressionBuilder = ExpressionBuilder;
246
+ function attr(name) {
247
+ return ExpressionBuilder.field(name);
248
+ }
@@ -1,4 +1,5 @@
1
1
  import { DynoQuery } from "./index";
2
+ import { ExpressionBuilder } from "./expression-builder";
2
3
  export interface IndexQueryConfig {
3
4
  tableName?: string;
4
5
  indexName: string;
@@ -23,9 +24,10 @@ export declare class IndexQuery {
23
24
  scanIndexForward?: boolean;
24
25
  exclusiveStartKey?: any;
25
26
  skValue?: string;
26
- filterExpression?: string;
27
- expressionAttributeNames?: Record<string, string>;
28
- expressionAttributeValues?: Record<string, any>;
27
+ filterBuilder?: ExpressionBuilder;
28
+ FilterExpression?: string;
29
+ ExpressionAttributeNames?: Record<string, string>;
30
+ ExpressionAttributeValues?: Record<string, any>;
29
31
  }): Promise<T[]>;
30
32
  get<T = any>(skValue?: string): Promise<T | null>;
31
33
  getPkValue(): string;
@@ -34,18 +34,25 @@ class IndexQuery {
34
34
  return __awaiter(this, void 0, void 0, function* () {
35
35
  const finalSkValue = (options === null || options === void 0 ? void 0 : options.skValue) || this.skValue;
36
36
  let keyCondition = "#pk = :pk";
37
- const expressionAttributeNames = Object.assign({ "#pk": this.pkName }, options === null || options === void 0 ? void 0 : options.expressionAttributeNames);
38
- const expressionAttributeValues = Object.assign({ ":pk": this.pkValue }, options === null || options === void 0 ? void 0 : options.expressionAttributeValues);
37
+ let expressionAttributeNames = Object.assign({ "#pk": this.pkName }, options === null || options === void 0 ? void 0 : options.ExpressionAttributeNames);
38
+ let expressionAttributeValues = Object.assign({ ":pk": this.pkValue }, options === null || options === void 0 ? void 0 : options.ExpressionAttributeValues);
39
39
  if (finalSkValue) {
40
40
  keyCondition += " AND begins_with(#sk, :sk)";
41
41
  expressionAttributeNames["#sk"] = this.skName;
42
42
  expressionAttributeValues[":sk"] = finalSkValue;
43
43
  }
44
+ let filterExpression = options === null || options === void 0 ? void 0 : options.FilterExpression;
45
+ if (options === null || options === void 0 ? void 0 : options.filterBuilder) {
46
+ const { expression, attributeNames, attributeValues } = options.filterBuilder.build();
47
+ filterExpression = expression;
48
+ expressionAttributeNames = Object.assign(Object.assign({}, expressionAttributeNames), attributeNames);
49
+ expressionAttributeValues = Object.assign(Object.assign({}, expressionAttributeValues), attributeValues);
50
+ }
44
51
  const response = yield this.db.query({
45
52
  TableName: this.tableName,
46
53
  IndexName: this.indexName,
47
54
  KeyConditionExpression: keyCondition,
48
- FilterExpression: options === null || options === void 0 ? void 0 : options.filterExpression,
55
+ FilterExpression: filterExpression,
49
56
  ExpressionAttributeNames: expressionAttributeNames,
50
57
  ExpressionAttributeValues: expressionAttributeValues,
51
58
  Limit: options === null || options === void 0 ? void 0 : options.limit,
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { PutCommandInput, GetCommandInput, UpdateCommandInput, DeleteCommandInput, QueryCommandInput, ScanCommandInput } from "@aws-sdk/lib-dynamodb";
2
2
  import { Item } from "./partition";
3
3
  import { IndexQuery } from "./index-query";
4
+ import { ExpressionBuilder, attr } from "./expression-builder";
5
+ export { ExpressionBuilder, attr };
4
6
  export interface DynoQueryConfig {
5
7
  tableName?: string;
6
8
  pkName?: string;
@@ -65,6 +67,14 @@ export declare class DynoQuery {
65
67
  * Batch get items from the table.
66
68
  */
67
69
  batchRead(items: (Item | IndexQuery)[]): Promise<any[]>;
70
+ /**
71
+ * Transact write items to the table.
72
+ */
73
+ transactWrite(items: Item[]): Promise<Item[]>;
74
+ /**
75
+ * Transact get items from the table.
76
+ */
77
+ transactRead(items: (Item | IndexQuery)[]): Promise<any[]>;
68
78
  /**
69
79
  * Maps a raw DynamoDB item to a Model Item if it matches a registered model.
70
80
  */
package/dist/index.js CHANGED
@@ -34,11 +34,14 @@ var __rest = (this && this.__rest) || function (s, e) {
34
34
  return t;
35
35
  };
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
- exports.Item = exports.DynoQuery = void 0;
37
+ exports.Item = exports.DynoQuery = exports.attr = exports.ExpressionBuilder = void 0;
38
38
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
39
39
  const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
40
40
  const partition_1 = require("./partition");
41
41
  const index_query_1 = require("./index-query");
42
+ const expression_builder_1 = require("./expression-builder");
43
+ Object.defineProperty(exports, "ExpressionBuilder", { enumerable: true, get: function () { return expression_builder_1.ExpressionBuilder; } });
44
+ Object.defineProperty(exports, "attr", { enumerable: true, get: function () { return expression_builder_1.attr; } });
42
45
  class DynoQuery {
43
46
  constructor(config = {}) {
44
47
  this.registeredModels = {};
@@ -178,14 +181,7 @@ class DynoQuery {
178
181
  });
179
182
  }
180
183
  else {
181
- const dataToSave = {};
182
- for (const key in item) {
183
- if (Object.prototype.hasOwnProperty.call(item, key) &&
184
- !["_indices", "_partition", "_skValue", "_toBeDeleted"].includes(key) &&
185
- typeof item[key] !== "function") {
186
- dataToSave[key] = item[key];
187
- }
188
- }
184
+ const dataToSave = item.getData();
189
185
  // Ensure PK and SK are in the data
190
186
  dataToSave[this.pkName] = partition.getPkValue();
191
187
  dataToSave[this.skName] = skValue;
@@ -271,6 +267,116 @@ class DynoQuery {
271
267
  return allItems;
272
268
  });
273
269
  }
270
+ /**
271
+ * Transact write items to the table.
272
+ */
273
+ transactWrite(items) {
274
+ return __awaiter(this, void 0, void 0, function* () {
275
+ // Chunk items into 100
276
+ for (let i = 0; i < items.length; i += 100) {
277
+ const chunk = items.slice(i, i + 100);
278
+ const transactItems = chunk.map((item) => {
279
+ const partition = item.getPartition();
280
+ const tableName = partition.getTableName();
281
+ const skValue = item.getSkValue();
282
+ const pkValue = partition.getPkValue();
283
+ const conditionBuilder = item.getConditionBuilder();
284
+ if (item.toBeDeleted()) {
285
+ const deleteItem = {
286
+ Key: {
287
+ [this.pkName]: pkValue,
288
+ [this.skName]: skValue,
289
+ },
290
+ TableName: tableName,
291
+ };
292
+ if (conditionBuilder) {
293
+ const { expression, attributeNames, attributeValues } = conditionBuilder.build();
294
+ deleteItem.ConditionExpression = expression;
295
+ deleteItem.ExpressionAttributeNames = attributeNames;
296
+ deleteItem.ExpressionAttributeValues = attributeValues;
297
+ }
298
+ return {
299
+ Delete: deleteItem,
300
+ };
301
+ }
302
+ else {
303
+ const dataToSave = item.getData();
304
+ // Ensure PK and SK are in the data
305
+ dataToSave[this.pkName] = pkValue;
306
+ dataToSave[this.skName] = skValue;
307
+ const putItem = {
308
+ Item: dataToSave,
309
+ TableName: tableName,
310
+ };
311
+ if (conditionBuilder) {
312
+ const { expression, attributeNames, attributeValues } = conditionBuilder.build();
313
+ putItem.ConditionExpression = expression;
314
+ putItem.ExpressionAttributeNames = attributeNames;
315
+ putItem.ExpressionAttributeValues = attributeValues;
316
+ }
317
+ return {
318
+ Put: putItem,
319
+ };
320
+ }
321
+ });
322
+ yield this.docClient.send(new lib_dynamodb_1.TransactWriteCommand({
323
+ TransactItems: transactItems,
324
+ }));
325
+ }
326
+ return items;
327
+ });
328
+ }
329
+ /**
330
+ * Transact get items from the table.
331
+ */
332
+ transactRead(items) {
333
+ return __awaiter(this, void 0, void 0, function* () {
334
+ const allItems = [];
335
+ // Chunk items into 100
336
+ for (let i = 0; i < items.length; i += 100) {
337
+ const chunk = items.slice(i, i + 100);
338
+ const transactItems = chunk.map((item) => {
339
+ let tableName;
340
+ let pkValue;
341
+ let skValue;
342
+ if (item instanceof index_query_1.IndexQuery) {
343
+ tableName = item.tableName;
344
+ pkValue = item.getPkValue();
345
+ skValue = item.getSkValue() || "";
346
+ }
347
+ else {
348
+ const partition = item.getPartition();
349
+ tableName = partition.getTableName();
350
+ pkValue = partition.getPkValue();
351
+ skValue = item.getSkValue();
352
+ }
353
+ return {
354
+ Get: {
355
+ TableName: tableName,
356
+ Key: {
357
+ [this.pkName]: pkValue,
358
+ [this.skName]: skValue,
359
+ },
360
+ },
361
+ };
362
+ });
363
+ const response = yield this.docClient.send(new lib_dynamodb_1.TransactGetCommand({
364
+ TransactItems: transactItems,
365
+ }));
366
+ if (response.Responses) {
367
+ response.Responses.forEach((res) => {
368
+ if (res.Item) {
369
+ allItems.push(this.mapItemToModelItem(res.Item));
370
+ }
371
+ else {
372
+ allItems.push(null);
373
+ }
374
+ });
375
+ }
376
+ }
377
+ return allItems;
378
+ });
379
+ }
274
380
  /**
275
381
  * Maps a raw DynamoDB item to a Model Item if it matches a registered model.
276
382
  */
@@ -1,5 +1,6 @@
1
1
  import { DynoQuery } from "./index";
2
2
  import { IndexQuery } from "./index-query";
3
+ import { ExpressionBuilder } from "./expression-builder";
3
4
  export interface PartitionConfig {
4
5
  tableName?: string;
5
6
  pk?: string;
@@ -11,6 +12,8 @@ export declare class Item {
11
12
  private _partition;
12
13
  private _skValue;
13
14
  private _toBeDeleted;
15
+ private _filterBuilder?;
16
+ private _conditionBuilder?;
14
17
  constructor(partition: Partition, skValue: string, data: any);
15
18
  }
16
19
  export declare class Partition {
@@ -30,14 +33,20 @@ export declare class Partition {
30
33
  getAll<T = any>(options?: {
31
34
  limit?: number;
32
35
  exclusiveStartKey?: any;
33
- filterExpression?: string;
34
- expressionAttributeNames?: Record<string, string>;
35
- expressionAttributeValues?: Record<string, any>;
36
+ filterBuilder?: ExpressionBuilder;
37
+ FilterExpression?: string;
38
+ ExpressionAttributeNames?: Record<string, string>;
39
+ ExpressionAttributeValues?: Record<string, any>;
36
40
  }): Promise<T[]>;
37
41
  /**
38
42
  * Create an item in this partition.
39
43
  */
40
- create<T = any>(skValue: string, data: T, indices?: IndexQuery[]): Promise<T>;
44
+ create<T = any>(skValue: string, data: T, indices?: IndexQuery[], options?: {
45
+ conditionBuilder?: ExpressionBuilder;
46
+ ConditionExpression?: string;
47
+ ExpressionAttributeNames?: Record<string, string>;
48
+ ExpressionAttributeValues?: Record<string, any>;
49
+ }): Promise<T>;
41
50
  /**
42
51
  * Internal method to get raw data for a specific SK.
43
52
  */
@@ -45,11 +54,21 @@ export declare class Partition {
45
54
  /**
46
55
  * Update an existing item in this partition.
47
56
  */
48
- update<T = any>(skValue: string, data: Partial<T>, indices?: IndexQuery[]): Promise<T>;
57
+ update<T = any>(skValue: string, data: Partial<T>, indices?: IndexQuery[], options?: {
58
+ conditionBuilder?: ExpressionBuilder;
59
+ ConditionExpression?: string;
60
+ ExpressionAttributeNames?: Record<string, string>;
61
+ ExpressionAttributeValues?: Record<string, any>;
62
+ }): Promise<T>;
49
63
  /**
50
64
  * Delete an item by its SK within this partition.
51
65
  */
52
- delete(skValue: string): Promise<void>;
66
+ delete(skValue: string, options?: {
67
+ conditionBuilder?: ExpressionBuilder;
68
+ ConditionExpression?: string;
69
+ ExpressionAttributeNames?: Record<string, string>;
70
+ ExpressionAttributeValues?: Record<string, any>;
71
+ }): Promise<void>;
53
72
  /**
54
73
  * Get data for a specific SK and return it wrapped in a Item object.
55
74
  */
package/dist/partition.js CHANGED
@@ -20,31 +20,67 @@ class Item {
20
20
  const self = this;
21
21
  return new Proxy(this, {
22
22
  get(target, prop, receiver) {
23
- if (prop === "save") {
23
+ if (prop === "getData") {
24
24
  return () => {
25
- const dataToSave = {};
25
+ const data = {};
26
26
  for (const key in target) {
27
27
  if (Object.prototype.hasOwnProperty.call(target, key) &&
28
- !["_indices", "_partition", "_skValue", "_toBeDeleted"].includes(key) &&
28
+ ![
29
+ "_indices",
30
+ "_partition",
31
+ "_skValue",
32
+ "_toBeDeleted",
33
+ "_filterBuilder",
34
+ "_conditionBuilder",
35
+ ].includes(key) &&
29
36
  typeof target[key] !== "function") {
30
- dataToSave[key] = target[key];
37
+ data[key] = target[key];
31
38
  }
32
39
  }
33
- return partition.update(skValue, dataToSave, self._indices);
40
+ return data;
41
+ };
42
+ }
43
+ if (prop === "save") {
44
+ return () => {
45
+ return partition.update(skValue, receiver.getData(), self._indices, {
46
+ conditionBuilder: self._conditionBuilder,
47
+ });
34
48
  };
35
49
  }
36
50
  if (prop === "create") {
37
51
  return (data, indices) => {
38
52
  const dataToSave = data || {};
39
53
  const finalIndices = indices || self._indices;
40
- return partition.create(skValue, dataToSave, finalIndices);
54
+ return partition.create(skValue, dataToSave, finalIndices, {
55
+ conditionBuilder: self._conditionBuilder,
56
+ });
41
57
  };
42
58
  }
43
59
  if (prop === "update") {
44
60
  return (data, indices) => {
45
- return partition.update(skValue, data, indices);
61
+ return partition.update(skValue, data, indices, {
62
+ conditionBuilder: self._conditionBuilder,
63
+ });
64
+ };
65
+ }
66
+ if (prop === "setFilter") {
67
+ return (builder) => {
68
+ self._filterBuilder = builder;
69
+ return receiver;
70
+ };
71
+ }
72
+ if (prop === "setCondition") {
73
+ return (builder) => {
74
+ self._conditionBuilder = builder;
75
+ return receiver;
46
76
  };
47
77
  }
78
+ if (prop === "getFilterBuilder") {
79
+ return () => self._filterBuilder;
80
+ }
81
+ if (prop === "getConditionBuilder") {
82
+ return () => self._conditionBuilder;
83
+ }
48
84
  if (prop === "setIndex") {
49
85
  return (indexObj) => {
50
86
  if (Array.isArray(indexObj)) {
@@ -118,12 +154,21 @@ class Partition {
118
154
  */
119
155
  getAll(options) {
120
156
  return __awaiter(this, void 0, void 0, function* () {
157
+ let filterExpression = options === null || options === void 0 ? void 0 : options.FilterExpression;
158
+ let expressionAttributeNames = Object.assign({ "#pk": this.pkName }, options === null || options === void 0 ? void 0 : options.ExpressionAttributeNames);
159
+ let expressionAttributeValues = Object.assign({ ":pk": this.pkValue }, options === null || options === void 0 ? void 0 : options.ExpressionAttributeValues);
160
+ if (options === null || options === void 0 ? void 0 : options.filterBuilder) {
161
+ const { expression, attributeNames, attributeValues } = options.filterBuilder.build();
162
+ filterExpression = expression;
163
+ expressionAttributeNames = Object.assign(Object.assign({}, expressionAttributeNames), attributeNames);
164
+ expressionAttributeValues = Object.assign(Object.assign({}, expressionAttributeValues), attributeValues);
165
+ }
121
166
  const response = yield this.db.query({
122
167
  TableName: this.tableName,
123
168
  KeyConditionExpression: "#pk = :pk",
124
- FilterExpression: options === null || options === void 0 ? void 0 : options.filterExpression,
125
- ExpressionAttributeNames: Object.assign({ "#pk": this.pkName }, options === null || options === void 0 ? void 0 : options.expressionAttributeNames),
126
- ExpressionAttributeValues: Object.assign({ ":pk": this.pkValue }, options === null || options === void 0 ? void 0 : options.expressionAttributeValues),
169
+ FilterExpression: filterExpression,
170
+ ExpressionAttributeNames: expressionAttributeNames,
171
+ ExpressionAttributeValues: expressionAttributeValues,
127
172
  Limit: options === null || options === void 0 ? void 0 : options.limit,
128
173
  ExclusiveStartKey: options === null || options === void 0 ? void 0 : options.exclusiveStartKey,
129
174
  });
@@ -143,7 +188,7 @@ class Partition {
143
188
  /**
144
189
  * Create an item in this partition.
145
190
  */
146
- create(skValue, data, indices) {
191
+ create(skValue, data, indices, options) {
147
192
  return __awaiter(this, void 0, void 0, function* () {
148
193
  const item = Object.assign({ [this.pkName]: this.pkValue, [this.skName]: skValue }, data);
149
194
  if (indices) {
@@ -154,10 +199,22 @@ class Partition {
154
199
  }
155
200
  });
156
201
  }
157
- yield this.db.create({
202
+ const createParams = {
158
203
  TableName: this.tableName,
159
204
  Item: item,
160
- });
205
+ };
206
+ if (options === null || options === void 0 ? void 0 : options.conditionBuilder) {
207
+ const { expression, attributeNames, attributeValues } = options.conditionBuilder.build();
208
+ createParams.ConditionExpression = expression;
209
+ createParams.ExpressionAttributeNames = Object.assign(Object.assign({}, createParams.ExpressionAttributeNames), attributeNames);
210
+ createParams.ExpressionAttributeValues = Object.assign(Object.assign({}, createParams.ExpressionAttributeValues), attributeValues);
211
+ }
212
+ if (options === null || options === void 0 ? void 0 : options.ConditionExpression) {
213
+ createParams.ConditionExpression = options.ConditionExpression;
214
+ createParams.ExpressionAttributeNames = Object.assign(Object.assign({}, createParams.ExpressionAttributeNames), options.ExpressionAttributeNames);
215
+ createParams.ExpressionAttributeValues = Object.assign(Object.assign({}, createParams.ExpressionAttributeValues), options.ExpressionAttributeValues);
216
+ }
217
+ yield this.db.create(createParams);
161
218
  this.cache[skValue] = item;
162
219
  return new Item(this, skValue, item);
163
220
  });
@@ -190,25 +247,37 @@ class Partition {
190
247
  /**
191
248
  * Update an existing item in this partition.
192
249
  */
193
- update(skValue, data, indices) {
250
+ update(skValue, data, indices, options) {
194
251
  return __awaiter(this, void 0, void 0, function* () {
195
252
  const current = (yield this._getRaw(skValue)) || {};
196
253
  const updated = Object.assign(Object.assign({}, current), data);
197
- return yield this.create(skValue, updated, indices);
254
+ return yield this.create(skValue, updated, indices, options);
198
255
  });
199
256
  }
200
257
  /**
201
258
  * Delete an item by its SK within this partition.
202
259
  */
203
- delete(skValue) {
260
+ delete(skValue, options) {
204
261
  return __awaiter(this, void 0, void 0, function* () {
205
- yield this.db.delete({
262
+ const deleteParams = {
206
263
  TableName: this.tableName,
207
264
  Key: {
208
265
  [this.pkName]: this.pkValue,
209
266
  [this.skName]: skValue,
210
267
  },
211
- });
268
+ };
269
+ if (options === null || options === void 0 ? void 0 : options.conditionBuilder) {
270
+ const { expression, attributeNames, attributeValues } = options.conditionBuilder.build();
271
+ deleteParams.ConditionExpression = expression;
272
+ deleteParams.ExpressionAttributeNames = Object.assign(Object.assign({}, deleteParams.ExpressionAttributeNames), attributeNames);
273
+ deleteParams.ExpressionAttributeValues = Object.assign(Object.assign({}, deleteParams.ExpressionAttributeValues), attributeValues);
274
+ }
275
+ if (options === null || options === void 0 ? void 0 : options.ConditionExpression) {
276
+ deleteParams.ConditionExpression = options.ConditionExpression;
277
+ deleteParams.ExpressionAttributeNames = Object.assign(Object.assign({}, deleteParams.ExpressionAttributeNames), options.ExpressionAttributeNames);
278
+ deleteParams.ExpressionAttributeValues = Object.assign(Object.assign({}, deleteParams.ExpressionAttributeValues), options.ExpressionAttributeValues);
279
+ }
280
+ yield this.db.delete(deleteParams);
212
281
  delete this.cache[skValue];
213
282
  });
214
283
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynoquery",
3
- "version": "0.1.20",
3
+ "version": "0.1.23",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/devspikejs/dynoquery.git"