dynoquery 0.1.15 → 0.1.16

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
@@ -12,7 +12,7 @@ npm install dynoquery
12
12
 
13
13
  - Basic CRUD operations (create, get, update, delete)
14
14
  - Optimized for **Single-Table Design** (Partitions and GSIs)
15
- - Automatic result mapping to models
15
+ - Automatic result mapping to partition models
16
16
  - Built-in caching for partition instances
17
17
  - TypeScript support
18
18
 
@@ -64,6 +64,38 @@ async function userExample() {
64
64
  }
65
65
  ```
66
66
 
67
+ #### Second-Level Objects (Single-Row Operations)
68
+
69
+ For a more flexible way to work with individual rows, you can use `draft()` and `get()` to obtain an `Item` object, then call `create()`, `update()`, or `save()` on it directly.
70
+
71
+ ```typescript
72
+ const john = db.User('john@example.com');
73
+
74
+ // 1. Create a new row
75
+ const johnMeta = john.draft('METADATA');
76
+ johnMeta.name = 'John Doe';
77
+ johnMeta.email = 'johndoe@johnmail.com';
78
+ await johnMeta.save();
79
+
80
+ // 2. Create with properties in arguments
81
+ const johnStat = john.draft('STAT');
82
+ await johnStat.create({ views: 100 });
83
+
84
+ // 3. Get and update an existing row
85
+ const johnBio = await john.get('BIO');
86
+ johnBio.birthYear = 1986;
87
+ await johnBio.save();
88
+
89
+ // 4. Partial update without fetching
90
+ const johnFriend1 = john.draft('FRIEND#1');
91
+ await johnFriend1.update({ Name: 'Alice', rank: 1 });
92
+
93
+ // 5. Set properties during initialization
94
+ const johnPref = john.draft('PREF', { theme: 'dark' });
95
+ await johnPref.save();
96
+
97
+ ````
98
+
67
99
  ### 2. Global Secondary Indexes (findBy)
68
100
 
69
101
  Use `findBy` to query your GSIs easily.
@@ -87,10 +119,14 @@ async function indexExample() {
87
119
  // Get all items in this category
88
120
  const items = await electronics.getAll();
89
121
 
90
- // If results match a registered model prefix, they are automatically mapped
91
- items.forEach(item => {
122
+ // If results match a registered item prefix, they are automatically mapped
123
+ items.forEach(async item => {
92
124
  if (item.__model === 'Product') {
93
125
  const productPartition = item.getPartition(); // Returns a Partition instance
126
+
127
+ // Now you can also edit and save directly if it matches a item
128
+ item.price = 45;
129
+ await item.save();
94
130
  }
95
131
  });
96
132
  }
@@ -98,8 +134,9 @@ async function indexExample() {
98
134
 
99
135
  ### 3. Creating Items with GSI Support
100
136
 
101
- You can pass index queries directly to `create()` to automatically populate GSI attributes.
137
+ You can pass index queries directly to `create()` or `update()` to automatically populate GSI attributes. This works on both the Partition level and the Item level.
102
138
 
139
+ #### Using Partition.create() and Partition.update()
103
140
  ```typescript
104
141
  const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
105
142
 
@@ -108,6 +145,38 @@ await db.Product('p123').create('INFO', {
108
145
  name: 'Gaming Mouse',
109
146
  price: 50
110
147
  }, [electronics]);
148
+
149
+ // Partial update with GSI support
150
+ await db.Product('p123').update('INFO', { price: 45 }, [electronics]);
151
+ ```
152
+
153
+ #### Using Item.create() and Item.update()
154
+ ```typescript
155
+ const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
156
+ const mouse = db.Product('p123').draft('INFO');
157
+
158
+ // Pass data and indices directly to create()
159
+ await mouse.create({
160
+ name: 'Gaming Mouse',
161
+ price: 50
162
+ }, [electronics]);
163
+
164
+ // Or use update() directly on the item for partial updates
165
+ await mouse.update({ price: 40 }, [electronics]);
166
+ ```
167
+
168
+ #### Using setIndex() for persistence
169
+ You can also use `setIndex()` to attach indices to an item so they are used automatically whenever you call `save()`.
170
+
171
+ ```typescript
172
+ const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
173
+ const mouse = db.Product('p123').draft('INFO');
174
+
175
+ mouse.setIndex(electronics);
176
+ mouse.price = 50;
177
+
178
+ // save() will now automatically include GSI attributes from the attached index
179
+ await mouse.save();
111
180
  ```
112
181
 
113
182
  ## Optional Configuration Parameters
@@ -165,11 +234,12 @@ if (token) {
165
234
  - `scan(params)`: Low-level ScanCommand wrapper.
166
235
 
167
236
  ### Partition
168
- - `get(sk)`: Fetches data for a specific SK (returns a Promise).
237
+ - `get(sk)`: Fetches data for a specific Sort Key value (returns a Promise).
169
238
  - `getAll(options?)`: Fetches items in the partition. Options: `{ limit, exclusiveStartKey }`.
170
239
  - `create(sk, data, indices?)`: Creates an item. `indices` is an array of `IndexQuery` for GSI population.
171
240
  - `update(sk, data)`: Partial update of an item.
172
241
  - `delete(sk)`: Deletes an item.
242
+ - `draft(sk, data?)`: Returns an `Item` object initialized with `data` (optional).
173
243
  - `deleteAll()`: Deletes all items in the partition.
174
244
  - `getLastEvaluatedKey()`: Returns the pagination token from the last `getAll()`.
175
245
 
@@ -178,6 +248,12 @@ if (token) {
178
248
  - `getAll(options?)`: Query index. Options: `{ limit, scanIndexForward, exclusiveStartKey, skValue }`.
179
249
  - `getLastEvaluatedKey()`: Returns the pagination token from the last `getAll()`.
180
250
 
251
+ ### Item (returned by Partition.get or draft)
252
+ - `create(data?, indices?)`: Persists the item as a new record with the provided data. Supports GSI indices.
253
+ - `update(data, indices?)`: Partial update of the item. Supports GSI indices.
254
+ - `save()`: Persists the current state of the item (alias for update of all properties). Uses indices attached via `setIndex()`.
255
+ - `setIndex(indices)`: Attaches one or more `IndexQuery` objects to the item for use with `save()` or `create()`.
256
+
181
257
  ## License
182
258
 
183
259
  MIT
@@ -33,5 +33,5 @@ export declare class IndexQuery {
33
33
  getSkName(): string;
34
34
  getSkValue(): string | undefined;
35
35
  getLastEvaluatedKey(): any;
36
- private mapItemToModel;
36
+ private mapItemToModelItem;
37
37
  }
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.IndexQuery = void 0;
13
13
  const partition_1 = require("./partition");
14
+ const partition_2 = require("./partition");
14
15
  class IndexQuery {
15
16
  constructor(db, config) {
16
17
  this.lastEvaluatedKey = null;
@@ -54,7 +55,7 @@ class IndexQuery {
54
55
  ExclusiveStartKey: options === null || options === void 0 ? void 0 : options.exclusiveStartKey,
55
56
  });
56
57
  const items = (response.Items || []);
57
- const mappedItems = items.map(item => this.mapItemToModel(item));
58
+ const mappedItems = items.map(item => this.mapItemToModelItem(item));
58
59
  this.lastEvaluatedKey = response.LastEvaluatedKey || null;
59
60
  return mappedItems;
60
61
  });
@@ -80,7 +81,7 @@ class IndexQuery {
80
81
  getLastEvaluatedKey() {
81
82
  return this.lastEvaluatedKey;
82
83
  }
83
- mapItemToModel(item) {
84
+ mapItemToModelItem(item) {
84
85
  const pkName = this.db.getPkName();
85
86
  const pkValue = item[pkName];
86
87
  if (!pkValue)
@@ -96,13 +97,17 @@ class IndexQuery {
96
97
  const partition = new partition_1.Partition(this.db, { pkPrefix: fullPrefix }, id);
97
98
  // Pre-fill the cache if we have the SK
98
99
  const skName = this.db.getSkName();
99
- if (item[skName]) {
100
- partition["cache"][item[skName]] = item;
100
+ const skValue = item[skName];
101
+ if (skValue) {
102
+ partition["cache"][skValue] = item;
101
103
  }
102
104
  // Add a property __model to the item if it matches.
103
105
  item.__model = name;
104
106
  // Also provide a way to get the partition instance from the item
105
107
  item.getPartition = () => partition;
108
+ if (skValue) {
109
+ return new partition_2.Item(partition, skValue, item);
110
+ }
106
111
  return item;
107
112
  }
108
113
  }
package/dist/index.d.ts CHANGED
@@ -64,4 +64,5 @@ export declare class DynoQuery {
64
64
  }>;
65
65
  }
66
66
  export * from "./partition";
67
+ export { Item } from "./partition";
67
68
  export * from "./index-query";
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ var __rest = (this && this.__rest) || function (s, e) {
34
34
  return t;
35
35
  };
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
- exports.DynoQuery = void 0;
37
+ 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");
@@ -167,4 +167,6 @@ class DynoQuery {
167
167
  }
168
168
  exports.DynoQuery = DynoQuery;
169
169
  __exportStar(require("./partition"), exports);
170
+ var partition_2 = require("./partition");
171
+ Object.defineProperty(exports, "Item", { enumerable: true, get: function () { return partition_2.Item; } });
170
172
  __exportStar(require("./index-query"), exports);
@@ -5,6 +5,11 @@ export interface PartitionConfig {
5
5
  pk?: string;
6
6
  pkPrefix?: string;
7
7
  }
8
+ export declare class Item {
9
+ [key: string]: any;
10
+ private _indices;
11
+ constructor(partition: Partition, skValue: string, data: any);
12
+ }
8
13
  export declare class Partition {
9
14
  protected db: DynoQuery;
10
15
  protected tableName?: string;
@@ -30,20 +35,28 @@ export declare class Partition {
30
35
  * Create an item in this partition.
31
36
  */
32
37
  create<T = any>(sk: string, data: T, indices?: IndexQuery[]): Promise<T>;
38
+ /**
39
+ * Internal method to get raw data for a specific SK.
40
+ */
41
+ private _getRaw;
33
42
  /**
34
43
  * Update an existing item in this partition.
35
44
  */
36
- update<T = any>(sk: string, data: Partial<T>): Promise<T>;
45
+ update<T = any>(sk: string, data: Partial<T>, indices?: IndexQuery[]): Promise<T>;
37
46
  /**
38
47
  * Delete an item by its SK within this partition.
39
48
  */
40
49
  delete(sk: string): Promise<void>;
41
50
  /**
42
- * Get data for a specific SK within this partition.
43
- * If the partition is loaded, it returns from cache.
44
- * Otherwise, it fetches the data immediately.
51
+ * Get data for a specific SK and return it wrapped in a Item object.
45
52
  */
46
53
  get<T = any>(sk: string): Promise<T | null>;
54
+ /**
55
+ * Pre-draft an item for creation. Returns an Item object.
56
+ * @param sk The sort key value
57
+ * @param data Initial data for the row
58
+ */
59
+ draft<T = any>(sk: string, data?: any): T;
47
60
  getPkValue(): string;
48
61
  getLastEvaluatedKey(): any;
49
62
  /**
package/dist/partition.js CHANGED
@@ -9,7 +9,54 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.Partition = void 0;
12
+ exports.Partition = exports.Item = void 0;
13
+ class Item {
14
+ constructor(partition, skValue, data) {
15
+ this._indices = [];
16
+ Object.assign(this, data);
17
+ const self = this;
18
+ return new Proxy(this, {
19
+ get(target, prop, receiver) {
20
+ if (prop === "save") {
21
+ return () => {
22
+ const dataToSave = {};
23
+ for (const key in target) {
24
+ if (Object.prototype.hasOwnProperty.call(target, key) && key !== "_indices" && typeof target[key] !== 'function') {
25
+ dataToSave[key] = target[key];
26
+ }
27
+ }
28
+ return partition.update(skValue, dataToSave, self._indices);
29
+ };
30
+ }
31
+ if (prop === "create") {
32
+ return (data, indices) => {
33
+ const dataToSave = data || {};
34
+ const finalIndices = indices || self._indices;
35
+ return partition.create(skValue, dataToSave, finalIndices);
36
+ };
37
+ }
38
+ if (prop === "update") {
39
+ return (data, indices) => {
40
+ return partition.update(skValue, data, indices);
41
+ };
42
+ }
43
+ if (prop === "setIndex") {
44
+ return (indexObj) => {
45
+ if (Array.isArray(indexObj)) {
46
+ self._indices.push(...indexObj);
47
+ }
48
+ else {
49
+ self._indices.push(indexObj);
50
+ }
51
+ return receiver;
52
+ };
53
+ }
54
+ return Reflect.get(target, prop, receiver);
55
+ },
56
+ });
57
+ }
58
+ }
59
+ exports.Item = Item;
13
60
  class Partition {
14
61
  constructor(db, config, id) {
15
62
  this.cache = {};
@@ -76,7 +123,7 @@ class Partition {
76
123
  this.isLoaded = true;
77
124
  }
78
125
  this.lastEvaluatedKey = response.LastEvaluatedKey || null;
79
- return items;
126
+ return items.map((item) => new Item(this, item[this.skName], item));
80
127
  });
81
128
  }
82
129
  /**
@@ -98,17 +145,42 @@ class Partition {
98
145
  Item: item,
99
146
  });
100
147
  this.cache[sk] = item;
101
- return item;
148
+ return new Item(this, sk, item);
149
+ });
150
+ }
151
+ /**
152
+ * Internal method to get raw data for a specific SK.
153
+ */
154
+ _getRaw(sk) {
155
+ return __awaiter(this, void 0, void 0, function* () {
156
+ if (this.cache[sk] !== undefined) {
157
+ return this.cache[sk] || null;
158
+ }
159
+ if (this.isLoaded) {
160
+ return null;
161
+ }
162
+ const response = yield this.db.get({
163
+ TableName: this.tableName,
164
+ Key: {
165
+ [this.pkName]: this.pkValue,
166
+ [this.skName]: sk,
167
+ },
168
+ });
169
+ const data = response.Item || null;
170
+ if (data) {
171
+ this.cache[sk] = data;
172
+ }
173
+ return data;
102
174
  });
103
175
  }
104
176
  /**
105
177
  * Update an existing item in this partition.
106
178
  */
107
- update(sk, data) {
179
+ update(sk, data, indices) {
108
180
  return __awaiter(this, void 0, void 0, function* () {
109
- const current = (yield this.get(sk)) || {};
181
+ const current = (yield this._getRaw(sk)) || {};
110
182
  const updated = Object.assign(Object.assign({}, current), data);
111
- return yield this.create(sk, updated);
183
+ return yield this.create(sk, updated, indices);
112
184
  });
113
185
  }
114
186
  /**
@@ -127,32 +199,22 @@ class Partition {
127
199
  });
128
200
  }
129
201
  /**
130
- * Get data for a specific SK within this partition.
131
- * If the partition is loaded, it returns from cache.
132
- * Otherwise, it fetches the data immediately.
202
+ * Get data for a specific SK and return it wrapped in a Item object.
133
203
  */
134
204
  get(sk) {
135
205
  return __awaiter(this, void 0, void 0, function* () {
136
- if (this.cache[sk] !== undefined) {
137
- return this.cache[sk] || null;
138
- }
139
- if (this.isLoaded) {
140
- return null;
141
- }
142
- const response = yield this.db.get({
143
- TableName: this.tableName,
144
- Key: {
145
- [this.pkName]: this.pkValue,
146
- [this.skName]: sk,
147
- },
148
- });
149
- const data = response.Item || null;
150
- if (data) {
151
- this.cache[sk] = data;
152
- }
153
- return data;
206
+ const data = yield this._getRaw(sk);
207
+ return data ? new Item(this, sk, data) : null;
154
208
  });
155
209
  }
210
+ /**
211
+ * Pre-draft an item for creation. Returns an Item object.
212
+ * @param sk The sort key value
213
+ * @param data Initial data for the row
214
+ */
215
+ draft(sk, data = {}) {
216
+ return new Item(this, sk, data);
217
+ }
156
218
  getPkValue() {
157
219
  return this.pkValue;
158
220
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynoquery",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/devspikejs/dynoquery.git"