dynoquery 0.1.14 → 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 +81 -5
- package/dist/index-query.d.ts +1 -1
- package/dist/index-query.js +9 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/partition.d.ts +18 -5
- package/dist/partition.js +89 -26
- package/package.json +1 -1
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
|
|
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
|
|
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
|
package/dist/index-query.d.ts
CHANGED
package/dist/index-query.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
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);
|
package/dist/partition.d.ts
CHANGED
|
@@ -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;
|
|
@@ -29,21 +34,29 @@ export declare class Partition {
|
|
|
29
34
|
/**
|
|
30
35
|
* Create an item in this partition.
|
|
31
36
|
*/
|
|
32
|
-
create<T = any>(sk: string, data: T, indices?: IndexQuery[]): Promise<
|
|
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
|
|
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
|
|
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,16 +145,42 @@ class Partition {
|
|
|
98
145
|
Item: item,
|
|
99
146
|
});
|
|
100
147
|
this.cache[sk] = 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;
|
|
101
174
|
});
|
|
102
175
|
}
|
|
103
176
|
/**
|
|
104
177
|
* Update an existing item in this partition.
|
|
105
178
|
*/
|
|
106
|
-
update(sk, data) {
|
|
179
|
+
update(sk, data, indices) {
|
|
107
180
|
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
-
const current = (yield this.
|
|
181
|
+
const current = (yield this._getRaw(sk)) || {};
|
|
109
182
|
const updated = Object.assign(Object.assign({}, current), data);
|
|
110
|
-
yield this.create(sk, updated);
|
|
183
|
+
return yield this.create(sk, updated, indices);
|
|
111
184
|
});
|
|
112
185
|
}
|
|
113
186
|
/**
|
|
@@ -126,32 +199,22 @@ class Partition {
|
|
|
126
199
|
});
|
|
127
200
|
}
|
|
128
201
|
/**
|
|
129
|
-
* Get data for a specific SK
|
|
130
|
-
* If the partition is loaded, it returns from cache.
|
|
131
|
-
* Otherwise, it fetches the data immediately.
|
|
202
|
+
* Get data for a specific SK and return it wrapped in a Item object.
|
|
132
203
|
*/
|
|
133
204
|
get(sk) {
|
|
134
205
|
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
if (this.isLoaded) {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
const response = yield this.db.get({
|
|
142
|
-
TableName: this.tableName,
|
|
143
|
-
Key: {
|
|
144
|
-
[this.pkName]: this.pkValue,
|
|
145
|
-
[this.skName]: sk,
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
const data = response.Item || null;
|
|
149
|
-
if (data) {
|
|
150
|
-
this.cache[sk] = data;
|
|
151
|
-
}
|
|
152
|
-
return data;
|
|
206
|
+
const data = yield this._getRaw(sk);
|
|
207
|
+
return data ? new Item(this, sk, data) : null;
|
|
153
208
|
});
|
|
154
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
|
+
}
|
|
155
218
|
getPkValue() {
|
|
156
219
|
return this.pkValue;
|
|
157
220
|
}
|