dynoquery 0.1.20 → 0.1.22

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 = {};
@@ -181,7 +184,14 @@ class DynoQuery {
181
184
  const dataToSave = {};
182
185
  for (const key in item) {
183
186
  if (Object.prototype.hasOwnProperty.call(item, key) &&
184
- !["_indices", "_partition", "_skValue", "_toBeDeleted"].includes(key) &&
187
+ ![
188
+ "_indices",
189
+ "_partition",
190
+ "_skValue",
191
+ "_toBeDeleted",
192
+ "_filterBuilder",
193
+ "_conditionBuilder",
194
+ ].includes(key) &&
185
195
  typeof item[key] !== "function") {
186
196
  dataToSave[key] = item[key];
187
197
  }
@@ -271,6 +281,130 @@ class DynoQuery {
271
281
  return allItems;
272
282
  });
273
283
  }
284
+ /**
285
+ * Transact write items to the table.
286
+ */
287
+ transactWrite(items) {
288
+ return __awaiter(this, void 0, void 0, function* () {
289
+ // Chunk items into 100
290
+ for (let i = 0; i < items.length; i += 100) {
291
+ const chunk = items.slice(i, i + 100);
292
+ const transactItems = chunk.map((item) => {
293
+ const partition = item.getPartition();
294
+ const tableName = partition.getTableName();
295
+ const skValue = item.getSkValue();
296
+ const pkValue = partition.getPkValue();
297
+ const conditionBuilder = item.getConditionBuilder();
298
+ if (item.toBeDeleted()) {
299
+ const deleteItem = {
300
+ Key: {
301
+ [this.pkName]: pkValue,
302
+ [this.skName]: skValue,
303
+ },
304
+ TableName: tableName,
305
+ };
306
+ if (conditionBuilder) {
307
+ const { expression, attributeNames, attributeValues } = conditionBuilder.build();
308
+ deleteItem.ConditionExpression = expression;
309
+ deleteItem.ExpressionAttributeNames = attributeNames;
310
+ deleteItem.ExpressionAttributeValues = attributeValues;
311
+ }
312
+ return {
313
+ Delete: deleteItem,
314
+ };
315
+ }
316
+ else {
317
+ const dataToSave = {};
318
+ for (const key in item) {
319
+ if (Object.prototype.hasOwnProperty.call(item, key) &&
320
+ ![
321
+ "_indices",
322
+ "_partition",
323
+ "_skValue",
324
+ "_toBeDeleted",
325
+ "_filterBuilder",
326
+ "_conditionBuilder",
327
+ ].includes(key) &&
328
+ typeof item[key] !== "function") {
329
+ dataToSave[key] = item[key];
330
+ }
331
+ }
332
+ // Ensure PK and SK are in the data
333
+ dataToSave[this.pkName] = pkValue;
334
+ dataToSave[this.skName] = skValue;
335
+ const putItem = {
336
+ Item: dataToSave,
337
+ TableName: tableName,
338
+ };
339
+ if (conditionBuilder) {
340
+ const { expression, attributeNames, attributeValues } = conditionBuilder.build();
341
+ putItem.ConditionExpression = expression;
342
+ putItem.ExpressionAttributeNames = attributeNames;
343
+ putItem.ExpressionAttributeValues = attributeValues;
344
+ }
345
+ return {
346
+ Put: putItem,
347
+ };
348
+ }
349
+ });
350
+ yield this.docClient.send(new lib_dynamodb_1.TransactWriteCommand({
351
+ TransactItems: transactItems,
352
+ }));
353
+ }
354
+ return items;
355
+ });
356
+ }
357
+ /**
358
+ * Transact get items from the table.
359
+ */
360
+ transactRead(items) {
361
+ return __awaiter(this, void 0, void 0, function* () {
362
+ const allItems = [];
363
+ // Chunk items into 100
364
+ for (let i = 0; i < items.length; i += 100) {
365
+ const chunk = items.slice(i, i + 100);
366
+ const transactItems = chunk.map((item) => {
367
+ let tableName;
368
+ let pkValue;
369
+ let skValue;
370
+ if (item instanceof index_query_1.IndexQuery) {
371
+ tableName = item.tableName;
372
+ pkValue = item.getPkValue();
373
+ skValue = item.getSkValue() || "";
374
+ }
375
+ else {
376
+ const partition = item.getPartition();
377
+ tableName = partition.getTableName();
378
+ pkValue = partition.getPkValue();
379
+ skValue = item.getSkValue();
380
+ }
381
+ return {
382
+ Get: {
383
+ TableName: tableName,
384
+ Key: {
385
+ [this.pkName]: pkValue,
386
+ [this.skName]: skValue,
387
+ },
388
+ },
389
+ };
390
+ });
391
+ const response = yield this.docClient.send(new lib_dynamodb_1.TransactGetCommand({
392
+ TransactItems: transactItems,
393
+ }));
394
+ if (response.Responses) {
395
+ response.Responses.forEach((res) => {
396
+ if (res.Item) {
397
+ allItems.push(this.mapItemToModelItem(res.Item));
398
+ }
399
+ else {
400
+ allItems.push(null);
401
+ }
402
+ });
403
+ }
404
+ }
405
+ return allItems;
406
+ });
407
+ }
274
408
  /**
275
409
  * Maps a raw DynamoDB item to a Model Item if it matches a registered model.
276
410
  */
@@ -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
@@ -25,26 +25,50 @@ class Item {
25
25
  const dataToSave = {};
26
26
  for (const key in target) {
27
27
  if (Object.prototype.hasOwnProperty.call(target, key) &&
28
- !["_indices", "_partition", "_skValue", "_toBeDeleted"].includes(key) &&
28
+ !["_indices", "_partition", "_skValue", "_toBeDeleted", "_filterBuilder", "_conditionBuilder"].includes(key) &&
29
29
  typeof target[key] !== "function") {
30
30
  dataToSave[key] = target[key];
31
31
  }
32
32
  }
33
- return partition.update(skValue, dataToSave, self._indices);
33
+ return partition.update(skValue, dataToSave, self._indices, {
34
+ conditionBuilder: self._conditionBuilder,
35
+ });
34
36
  };
35
37
  }
36
38
  if (prop === "create") {
37
39
  return (data, indices) => {
38
40
  const dataToSave = data || {};
39
41
  const finalIndices = indices || self._indices;
40
- return partition.create(skValue, dataToSave, finalIndices);
42
+ return partition.create(skValue, dataToSave, finalIndices, {
43
+ conditionBuilder: self._conditionBuilder,
44
+ });
41
45
  };
42
46
  }
43
47
  if (prop === "update") {
44
48
  return (data, indices) => {
45
- return partition.update(skValue, data, indices);
49
+ return partition.update(skValue, data, indices, {
50
+ conditionBuilder: self._conditionBuilder,
51
+ });
46
52
  };
47
53
  }
54
+ if (prop === "setFilter") {
55
+ return (builder) => {
56
+ self._filterBuilder = builder;
57
+ return receiver;
58
+ };
59
+ }
60
+ if (prop === "setCondition") {
61
+ return (builder) => {
62
+ self._conditionBuilder = builder;
63
+ return receiver;
64
+ };
65
+ }
66
+ if (prop === "getFilterBuilder") {
67
+ return () => self._filterBuilder;
68
+ }
69
+ if (prop === "getConditionBuilder") {
70
+ return () => self._conditionBuilder;
71
+ }
48
72
  if (prop === "setIndex") {
49
73
  return (indexObj) => {
50
74
  if (Array.isArray(indexObj)) {
@@ -118,12 +142,21 @@ class Partition {
118
142
  */
119
143
  getAll(options) {
120
144
  return __awaiter(this, void 0, void 0, function* () {
145
+ let filterExpression = options === null || options === void 0 ? void 0 : options.FilterExpression;
146
+ let expressionAttributeNames = Object.assign({ "#pk": this.pkName }, options === null || options === void 0 ? void 0 : options.ExpressionAttributeNames);
147
+ let expressionAttributeValues = Object.assign({ ":pk": this.pkValue }, options === null || options === void 0 ? void 0 : options.ExpressionAttributeValues);
148
+ if (options === null || options === void 0 ? void 0 : options.filterBuilder) {
149
+ const { expression, attributeNames, attributeValues } = options.filterBuilder.build();
150
+ filterExpression = expression;
151
+ expressionAttributeNames = Object.assign(Object.assign({}, expressionAttributeNames), attributeNames);
152
+ expressionAttributeValues = Object.assign(Object.assign({}, expressionAttributeValues), attributeValues);
153
+ }
121
154
  const response = yield this.db.query({
122
155
  TableName: this.tableName,
123
156
  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),
157
+ FilterExpression: filterExpression,
158
+ ExpressionAttributeNames: expressionAttributeNames,
159
+ ExpressionAttributeValues: expressionAttributeValues,
127
160
  Limit: options === null || options === void 0 ? void 0 : options.limit,
128
161
  ExclusiveStartKey: options === null || options === void 0 ? void 0 : options.exclusiveStartKey,
129
162
  });
@@ -143,7 +176,7 @@ class Partition {
143
176
  /**
144
177
  * Create an item in this partition.
145
178
  */
146
- create(skValue, data, indices) {
179
+ create(skValue, data, indices, options) {
147
180
  return __awaiter(this, void 0, void 0, function* () {
148
181
  const item = Object.assign({ [this.pkName]: this.pkValue, [this.skName]: skValue }, data);
149
182
  if (indices) {
@@ -154,10 +187,22 @@ class Partition {
154
187
  }
155
188
  });
156
189
  }
157
- yield this.db.create({
190
+ const createParams = {
158
191
  TableName: this.tableName,
159
192
  Item: item,
160
- });
193
+ };
194
+ if (options === null || options === void 0 ? void 0 : options.conditionBuilder) {
195
+ const { expression, attributeNames, attributeValues } = options.conditionBuilder.build();
196
+ createParams.ConditionExpression = expression;
197
+ createParams.ExpressionAttributeNames = Object.assign(Object.assign({}, createParams.ExpressionAttributeNames), attributeNames);
198
+ createParams.ExpressionAttributeValues = Object.assign(Object.assign({}, createParams.ExpressionAttributeValues), attributeValues);
199
+ }
200
+ if (options === null || options === void 0 ? void 0 : options.ConditionExpression) {
201
+ createParams.ConditionExpression = options.ConditionExpression;
202
+ createParams.ExpressionAttributeNames = Object.assign(Object.assign({}, createParams.ExpressionAttributeNames), options.ExpressionAttributeNames);
203
+ createParams.ExpressionAttributeValues = Object.assign(Object.assign({}, createParams.ExpressionAttributeValues), options.ExpressionAttributeValues);
204
+ }
205
+ yield this.db.create(createParams);
161
206
  this.cache[skValue] = item;
162
207
  return new Item(this, skValue, item);
163
208
  });
@@ -190,25 +235,37 @@ class Partition {
190
235
  /**
191
236
  * Update an existing item in this partition.
192
237
  */
193
- update(skValue, data, indices) {
238
+ update(skValue, data, indices, options) {
194
239
  return __awaiter(this, void 0, void 0, function* () {
195
240
  const current = (yield this._getRaw(skValue)) || {};
196
241
  const updated = Object.assign(Object.assign({}, current), data);
197
- return yield this.create(skValue, updated, indices);
242
+ return yield this.create(skValue, updated, indices, options);
198
243
  });
199
244
  }
200
245
  /**
201
246
  * Delete an item by its SK within this partition.
202
247
  */
203
- delete(skValue) {
248
+ delete(skValue, options) {
204
249
  return __awaiter(this, void 0, void 0, function* () {
205
- yield this.db.delete({
250
+ const deleteParams = {
206
251
  TableName: this.tableName,
207
252
  Key: {
208
253
  [this.pkName]: this.pkValue,
209
254
  [this.skName]: skValue,
210
255
  },
211
- });
256
+ };
257
+ if (options === null || options === void 0 ? void 0 : options.conditionBuilder) {
258
+ const { expression, attributeNames, attributeValues } = options.conditionBuilder.build();
259
+ deleteParams.ConditionExpression = expression;
260
+ deleteParams.ExpressionAttributeNames = Object.assign(Object.assign({}, deleteParams.ExpressionAttributeNames), attributeNames);
261
+ deleteParams.ExpressionAttributeValues = Object.assign(Object.assign({}, deleteParams.ExpressionAttributeValues), attributeValues);
262
+ }
263
+ if (options === null || options === void 0 ? void 0 : options.ConditionExpression) {
264
+ deleteParams.ConditionExpression = options.ConditionExpression;
265
+ deleteParams.ExpressionAttributeNames = Object.assign(Object.assign({}, deleteParams.ExpressionAttributeNames), options.ExpressionAttributeNames);
266
+ deleteParams.ExpressionAttributeValues = Object.assign(Object.assign({}, deleteParams.ExpressionAttributeValues), options.ExpressionAttributeValues);
267
+ }
268
+ yield this.db.delete(deleteParams);
212
269
  delete this.cache[skValue];
213
270
  });
214
271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynoquery",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/devspikejs/dynoquery.git"