dynoquery 0.1.18 → 0.1.20

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
@@ -182,6 +182,39 @@ mouse.price = 50;
182
182
  await mouse.save();
183
183
  ```
184
184
 
185
+ ### 4. Batch Operations
186
+
187
+ DynoQuery provides `batchWrite` and `batchRead` for processing multiple items across partitions.
188
+
189
+ ```typescript
190
+ // 1. Batch Write (Create/Replace multiple items)
191
+ const user1 = db.User('john@example.com', 'METADATA');
192
+ user1.name = 'John Doe';
193
+
194
+ const user2 = db.User('jane@example.com', 'METADATA');
195
+ user2.name = 'Jane Doe';
196
+
197
+ await db.batchWrite([user1, user2]);
198
+
199
+ // 2. Batch Read (Fetch multiple items or index queries)
200
+ const userDraft = db.User('john@example.com', 'METADATA');
201
+ const categoryQuery = db.findByCategory('ELECTRONICS', 'p123');
202
+
203
+ const results = await db.batchRead([userDraft, categoryQuery]);
204
+
205
+ results.forEach(item => {
206
+ console.log(item.__model); // Automatically mapped to models
207
+ if (item.__model === 'User') {
208
+ item.status = 'active';
209
+ item.save(); // Second-level methods are available
210
+ }
211
+ });
212
+
213
+ // 3. Batch Delete
214
+ const userToDelete = db.User('john@example.com').draftDelete('METADATA');
215
+ await db.batchWrite([userToDelete]);
216
+ ```
217
+
185
218
  ## Optional Configuration Parameters
186
219
 
187
220
  | Parameter | Type | Default | Description |
@@ -235,6 +268,8 @@ if (token) {
235
268
  - `delete(params)`: Low-level DeleteCommand wrapper.
236
269
  - `query(params)`: Low-level QueryCommand wrapper.
237
270
  - `scan(params)`: Low-level ScanCommand wrapper.
271
+ - `batchWrite(items)`: Batch persists multiple `Item` objects.
272
+ - `batchRead(items)`: Batch fetches multiple `Item` or `IndexQuery` objects.
238
273
  - `[ModelName](id, skValue?)`: Returns a `Partition` instance for the given ID. If `skValue` is provided, returns an `Item` object directly.
239
274
  - `findBy[IndexName](id, skValue?)`: Returns an `IndexQuery` instance.
240
275
 
@@ -245,6 +280,7 @@ if (token) {
245
280
  - `update(skValue, data)`: Partial update of an item.
246
281
  - `delete(skValue)`: Deletes an item.
247
282
  - `draft(skValue, data?)`: Returns an `Item` object initialized with `data` (optional).
283
+ - `draftDelete(skValue)`: Returns an `Item` object marked for deletion (for use with `batchWrite`).
248
284
  - `deleteAll()`: Deletes all items in the partition.
249
285
  - `getLastEvaluatedKey()`: Returns the pagination token from the last `getAll()`.
250
286
 
@@ -33,5 +33,4 @@ export declare class IndexQuery {
33
33
  getSkName(): string;
34
34
  getSkValue(): string | undefined;
35
35
  getLastEvaluatedKey(): any;
36
- private mapItemToModelItem;
37
36
  }
@@ -10,8 +10,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.IndexQuery = void 0;
13
- const partition_1 = require("./partition");
14
- const partition_2 = require("./partition");
15
13
  class IndexQuery {
16
14
  constructor(db, config) {
17
15
  this.lastEvaluatedKey = null;
@@ -55,7 +53,7 @@ class IndexQuery {
55
53
  ExclusiveStartKey: options === null || options === void 0 ? void 0 : options.exclusiveStartKey,
56
54
  });
57
55
  const items = (response.Items || []);
58
- const mappedItems = items.map(item => this.mapItemToModelItem(item));
56
+ const mappedItems = items.map(item => this.db.mapItemToModelItem(item));
59
57
  this.lastEvaluatedKey = response.LastEvaluatedKey || null;
60
58
  return mappedItems;
61
59
  });
@@ -81,37 +79,5 @@ class IndexQuery {
81
79
  getLastEvaluatedKey() {
82
80
  return this.lastEvaluatedKey;
83
81
  }
84
- mapItemToModelItem(item) {
85
- const pkName = this.db.getPkName();
86
- const pkValue = item[pkName];
87
- if (!pkValue)
88
- return item;
89
- const registeredModels = this.db.getRegisteredModels();
90
- const globalPrefix = this.db.getPkPrefix();
91
- for (const [name, def] of Object.entries(registeredModels)) {
92
- const fullPrefix = globalPrefix + def.pkPrefix;
93
- if (pkValue.startsWith(fullPrefix)) {
94
- // Find the ID by removing the prefix
95
- const id = pkValue.substring(fullPrefix.length);
96
- // Return a Partition instance and attach the data to its cache.
97
- const partition = new partition_1.Partition(this.db, { pkPrefix: fullPrefix }, id);
98
- // Pre-fill the cache if we have the SK
99
- const skName = this.db.getSkName();
100
- const skValue = item[skName];
101
- if (skValue) {
102
- partition["cache"][skValue] = item;
103
- }
104
- // Add a property __model to the item if it matches.
105
- item.__model = name;
106
- // Also provide a way to get the partition instance from the item
107
- item.getPartition = () => partition;
108
- if (skValue) {
109
- return new partition_2.Item(partition, skValue, item);
110
- }
111
- return item;
112
- }
113
- }
114
- return item;
115
- }
116
82
  }
117
83
  exports.IndexQuery = IndexQuery;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { PutCommandInput, GetCommandInput, UpdateCommandInput, DeleteCommandInput, QueryCommandInput, ScanCommandInput } from "@aws-sdk/lib-dynamodb";
2
+ import { Item } from "./partition";
3
+ import { IndexQuery } from "./index-query";
2
4
  export interface DynoQueryConfig {
3
5
  tableName?: string;
4
6
  pkName?: string;
@@ -55,6 +57,18 @@ export declare class DynoQuery {
55
57
  * Scan the table or index for items.
56
58
  */
57
59
  scan(params: ScanCommandInput): Promise<import("@aws-sdk/lib-dynamodb").ScanCommandOutput>;
60
+ /**
61
+ * Batch write items to the table.
62
+ */
63
+ batchWrite(items: Item[]): Promise<Item[]>;
64
+ /**
65
+ * Batch get items from the table.
66
+ */
67
+ batchRead(items: (Item | IndexQuery)[]): Promise<any[]>;
68
+ /**
69
+ * Maps a raw DynamoDB item to a Model Item if it matches a registered model.
70
+ */
71
+ mapItemToModelItem(item: any): any;
58
72
  getTableName(): string | undefined;
59
73
  getPkPrefix(): string;
60
74
  getPkName(): string;
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ exports.Item = exports.DynoQuery = 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
+ const index_query_1 = require("./index-query");
41
42
  class DynoQuery {
42
43
  constructor(config = {}) {
43
44
  this.registeredModels = {};
@@ -153,6 +154,149 @@ class DynoQuery {
153
154
  return yield this.docClient.send(command);
154
155
  });
155
156
  }
157
+ /**
158
+ * Batch write items to the table.
159
+ */
160
+ batchWrite(items) {
161
+ return __awaiter(this, void 0, void 0, function* () {
162
+ const tableGroups = {};
163
+ items.forEach((item) => {
164
+ const partition = item.getPartition();
165
+ const tableName = partition.getTableName();
166
+ const skValue = item.getSkValue();
167
+ if (!tableGroups[tableName]) {
168
+ tableGroups[tableName] = [];
169
+ }
170
+ if (item.toBeDeleted()) {
171
+ tableGroups[tableName].push({
172
+ DeleteRequest: {
173
+ Key: {
174
+ [this.pkName]: partition.getPkValue(),
175
+ [this.skName]: skValue,
176
+ },
177
+ },
178
+ });
179
+ }
180
+ 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
+ }
189
+ // Ensure PK and SK are in the data
190
+ dataToSave[this.pkName] = partition.getPkValue();
191
+ dataToSave[this.skName] = skValue;
192
+ tableGroups[tableName].push({
193
+ PutRequest: {
194
+ Item: dataToSave,
195
+ },
196
+ });
197
+ }
198
+ });
199
+ const results = [];
200
+ const tableNames = Object.keys(tableGroups);
201
+ for (const tableName of tableNames) {
202
+ const requests = tableGroups[tableName];
203
+ // Chunk requests into 25
204
+ for (let i = 0; i < requests.length; i += 25) {
205
+ const chunk = requests.slice(i, i + 25);
206
+ yield this.docClient.send(new lib_dynamodb_1.BatchWriteCommand({
207
+ RequestItems: {
208
+ [tableName]: chunk,
209
+ },
210
+ }));
211
+ }
212
+ }
213
+ return items;
214
+ });
215
+ }
216
+ /**
217
+ * Batch get items from the table.
218
+ */
219
+ batchRead(items) {
220
+ return __awaiter(this, void 0, void 0, function* () {
221
+ const tableGroups = {};
222
+ const requestMap = {};
223
+ items.forEach((item) => {
224
+ let tableName;
225
+ let pkValue;
226
+ let skValue;
227
+ if (item instanceof index_query_1.IndexQuery) {
228
+ tableName = item.tableName;
229
+ pkValue = item.getPkValue();
230
+ skValue = item.getSkValue() || "";
231
+ }
232
+ else {
233
+ const partition = item.getPartition();
234
+ tableName = partition.getTableName();
235
+ pkValue = partition.getPkValue();
236
+ skValue = item.getSkValue();
237
+ }
238
+ if (!tableGroups[tableName]) {
239
+ tableGroups[tableName] = [];
240
+ }
241
+ const key = {
242
+ [this.pkName]: pkValue,
243
+ [this.skName]: skValue,
244
+ };
245
+ tableGroups[tableName].push(key);
246
+ const requestKey = `${tableName}|${pkValue}|${skValue}`;
247
+ requestMap[requestKey] = item;
248
+ });
249
+ const allItems = [];
250
+ const tableNames = Object.keys(tableGroups);
251
+ for (const tableName of tableNames) {
252
+ const keys = tableGroups[tableName];
253
+ for (let i = 0; i < keys.length; i += 100) {
254
+ const chunk = keys.slice(i, i + 100);
255
+ const response = yield this.docClient.send(new lib_dynamodb_1.BatchGetCommand({
256
+ RequestItems: {
257
+ [tableName]: {
258
+ Keys: chunk,
259
+ },
260
+ },
261
+ }));
262
+ if (response.Responses && response.Responses[tableName]) {
263
+ const items = response.Responses[tableName];
264
+ items.forEach((item) => {
265
+ const mappedItem = this.mapItemToModelItem(item);
266
+ allItems.push(mappedItem);
267
+ });
268
+ }
269
+ }
270
+ }
271
+ return allItems;
272
+ });
273
+ }
274
+ /**
275
+ * Maps a raw DynamoDB item to a Model Item if it matches a registered model.
276
+ */
277
+ mapItemToModelItem(item) {
278
+ const pkValue = item[this.pkName];
279
+ if (!pkValue)
280
+ return item;
281
+ for (const [name, def] of Object.entries(this.registeredModels)) {
282
+ const fullPrefix = this.globalPkPrefix + def.pkPrefix;
283
+ if (pkValue.startsWith(fullPrefix)) {
284
+ const id = pkValue.substring(fullPrefix.length);
285
+ const partition = new partition_1.Partition(this, { pkPrefix: fullPrefix }, id);
286
+ const skValue = item[this.skName];
287
+ if (skValue) {
288
+ partition["cache"][skValue] = item;
289
+ }
290
+ // Add metadata to the item
291
+ item.__model = name;
292
+ item.getPartition = () => partition;
293
+ if (skValue) {
294
+ return new partition_1.Item(partition, skValue, item);
295
+ }
296
+ }
297
+ }
298
+ return item;
299
+ }
156
300
  getTableName() {
157
301
  return this.defaultTableName;
158
302
  }
@@ -8,6 +8,9 @@ export interface PartitionConfig {
8
8
  export declare class Item {
9
9
  [key: string]: any;
10
10
  private _indices;
11
+ private _partition;
12
+ private _skValue;
13
+ private _toBeDeleted;
11
14
  constructor(partition: Partition, skValue: string, data: any);
12
15
  }
13
16
  export declare class Partition {
@@ -53,10 +56,16 @@ export declare class Partition {
53
56
  get<T = any>(skValue: string): Promise<T | null>;
54
57
  /**
55
58
  * Pre-draft an item for creation. Returns an Item object.
56
- * @param sk The sort key value
59
+ * @param skValue The sort key value
57
60
  * @param data Initial data for the row
58
61
  */
59
62
  draft<T = any>(skValue: string, data?: any): T;
63
+ /**
64
+ * Pre-draft an item for deletion. Returns an Item object marked for deletion.
65
+ * @param skValue The sort key value
66
+ */
67
+ draftDelete<T = any>(skValue: string): T;
68
+ getTableName(): string;
60
69
  getPkValue(): string;
61
70
  getLastEvaluatedKey(): any;
62
71
  /**
package/dist/partition.js CHANGED
@@ -13,6 +13,9 @@ exports.Partition = exports.Item = void 0;
13
13
  class Item {
14
14
  constructor(partition, skValue, data) {
15
15
  this._indices = [];
16
+ this._partition = partition;
17
+ this._skValue = skValue;
18
+ this._toBeDeleted = !!(data === null || data === void 0 ? void 0 : data._toBeDeleted);
16
19
  Object.assign(this, data);
17
20
  const self = this;
18
21
  return new Proxy(this, {
@@ -21,7 +24,9 @@ class Item {
21
24
  return () => {
22
25
  const dataToSave = {};
23
26
  for (const key in target) {
24
- if (Object.prototype.hasOwnProperty.call(target, key) && key !== "_indices" && typeof target[key] !== 'function') {
27
+ if (Object.prototype.hasOwnProperty.call(target, key) &&
28
+ !["_indices", "_partition", "_skValue", "_toBeDeleted"].includes(key) &&
29
+ typeof target[key] !== "function") {
25
30
  dataToSave[key] = target[key];
26
31
  }
27
32
  }
@@ -51,6 +56,15 @@ class Item {
51
56
  return receiver;
52
57
  };
53
58
  }
59
+ if (prop === "getPartition") {
60
+ return () => partition;
61
+ }
62
+ if (prop === "getSkValue") {
63
+ return () => skValue;
64
+ }
65
+ if (prop === "toBeDeleted") {
66
+ return () => self._toBeDeleted;
67
+ }
54
68
  return Reflect.get(target, prop, receiver);
55
69
  },
56
70
  });
@@ -209,12 +223,22 @@ class Partition {
209
223
  }
210
224
  /**
211
225
  * Pre-draft an item for creation. Returns an Item object.
212
- * @param sk The sort key value
226
+ * @param skValue The sort key value
213
227
  * @param data Initial data for the row
214
228
  */
215
229
  draft(skValue, data = {}) {
216
230
  return new Item(this, skValue, data);
217
231
  }
232
+ /**
233
+ * Pre-draft an item for deletion. Returns an Item object marked for deletion.
234
+ * @param skValue The sort key value
235
+ */
236
+ draftDelete(skValue) {
237
+ return new Item(this, skValue, { _toBeDeleted: true });
238
+ }
239
+ getTableName() {
240
+ return this.tableName || "";
241
+ }
218
242
  getPkValue() {
219
243
  return this.pkValue;
220
244
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynoquery",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/devspikejs/dynoquery.git"