dynoquery 0.1.15 → 0.1.17
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 +86 -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 +9 -3
- package/dist/partition.d.ts +17 -4
- package/dist/partition.js +89 -27
- 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,41 @@ 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. You can also use a shorthand by passing a second argument to your model function (as shown in the examples below).
|
|
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
|
+
// 6. Shorthand access (returns an Item object directly)
|
|
98
|
+
const johnMeta = db.User('john@example.com', 'METADATA');
|
|
99
|
+
await johnMeta.update({ name: 'John Doe' });
|
|
100
|
+
```
|
|
101
|
+
|
|
67
102
|
### 2. Global Secondary Indexes (findBy)
|
|
68
103
|
|
|
69
104
|
Use `findBy` to query your GSIs easily.
|
|
@@ -87,10 +122,14 @@ async function indexExample() {
|
|
|
87
122
|
// Get all items in this category
|
|
88
123
|
const items = await electronics.getAll();
|
|
89
124
|
|
|
90
|
-
// If results match a registered
|
|
91
|
-
items.forEach(item => {
|
|
125
|
+
// If results match a registered item prefix, they are automatically mapped
|
|
126
|
+
items.forEach(async item => {
|
|
92
127
|
if (item.__model === 'Product') {
|
|
93
128
|
const productPartition = item.getPartition(); // Returns a Partition instance
|
|
129
|
+
|
|
130
|
+
// Now you can also edit and save directly if it matches a item
|
|
131
|
+
item.price = 45;
|
|
132
|
+
await item.save();
|
|
94
133
|
}
|
|
95
134
|
});
|
|
96
135
|
}
|
|
@@ -98,8 +137,9 @@ async function indexExample() {
|
|
|
98
137
|
|
|
99
138
|
### 3. Creating Items with GSI Support
|
|
100
139
|
|
|
101
|
-
You can pass index queries directly to `create()` to automatically populate GSI attributes.
|
|
140
|
+
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
141
|
|
|
142
|
+
#### Using Partition.create() and Partition.update()
|
|
103
143
|
```typescript
|
|
104
144
|
const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
|
|
105
145
|
|
|
@@ -108,6 +148,38 @@ await db.Product('p123').create('INFO', {
|
|
|
108
148
|
name: 'Gaming Mouse',
|
|
109
149
|
price: 50
|
|
110
150
|
}, [electronics]);
|
|
151
|
+
|
|
152
|
+
// Partial update with GSI support
|
|
153
|
+
await db.Product('p123').update('INFO', { price: 45 }, [electronics]);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Using Item.create() and Item.update()
|
|
157
|
+
```typescript
|
|
158
|
+
const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
|
|
159
|
+
const mouse = db.Product('p123', 'INFO');
|
|
160
|
+
|
|
161
|
+
// Pass data and indices directly to create()
|
|
162
|
+
await mouse.create({
|
|
163
|
+
name: 'Gaming Mouse',
|
|
164
|
+
price: 50
|
|
165
|
+
}, [electronics]);
|
|
166
|
+
|
|
167
|
+
// Or use update() directly on the item for partial updates
|
|
168
|
+
await mouse.update({ price: 40 }, [electronics]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### Using setIndex() for persistence
|
|
172
|
+
You can also use `setIndex()` to attach indices to an item so they are used automatically whenever you call `save()`.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
|
|
176
|
+
const mouse = db.Product('p123', 'INFO');
|
|
177
|
+
|
|
178
|
+
mouse.setIndex(electronics);
|
|
179
|
+
mouse.price = 50;
|
|
180
|
+
|
|
181
|
+
// save() will now automatically include GSI attributes from the attached index
|
|
182
|
+
await mouse.save();
|
|
111
183
|
```
|
|
112
184
|
|
|
113
185
|
## Optional Configuration Parameters
|
|
@@ -163,13 +235,16 @@ if (token) {
|
|
|
163
235
|
- `delete(params)`: Low-level DeleteCommand wrapper.
|
|
164
236
|
- `query(params)`: Low-level QueryCommand wrapper.
|
|
165
237
|
- `scan(params)`: Low-level ScanCommand wrapper.
|
|
238
|
+
- `[ModelName](id, skValue?)`: Returns a `Partition` instance for the given ID. If `skValue` is provided, returns an `Item` object directly.
|
|
239
|
+
- `findBy[IndexName](id, skValue?)`: Returns an `IndexQuery` instance.
|
|
166
240
|
|
|
167
241
|
### Partition
|
|
168
|
-
- `get(sk)`: Fetches data for a specific
|
|
242
|
+
- `get(sk)`: Fetches data for a specific Sort Key value (returns a Promise).
|
|
169
243
|
- `getAll(options?)`: Fetches items in the partition. Options: `{ limit, exclusiveStartKey }`.
|
|
170
244
|
- `create(sk, data, indices?)`: Creates an item. `indices` is an array of `IndexQuery` for GSI population.
|
|
171
245
|
- `update(sk, data)`: Partial update of an item.
|
|
172
246
|
- `delete(sk)`: Deletes an item.
|
|
247
|
+
- `draft(sk, data?)`: Returns an `Item` object initialized with `data` (optional).
|
|
173
248
|
- `deleteAll()`: Deletes all items in the partition.
|
|
174
249
|
- `getLastEvaluatedKey()`: Returns the pagination token from the last `getAll()`.
|
|
175
250
|
|
|
@@ -178,6 +253,12 @@ if (token) {
|
|
|
178
253
|
- `getAll(options?)`: Query index. Options: `{ limit, scanIndexForward, exclusiveStartKey, skValue }`.
|
|
179
254
|
- `getLastEvaluatedKey()`: Returns the pagination token from the last `getAll()`.
|
|
180
255
|
|
|
256
|
+
### Item (returned by Partition.get or draft)
|
|
257
|
+
- `create(data?, indices?)`: Persists the item as a new record with the provided data. Supports GSI indices.
|
|
258
|
+
- `update(data, indices?)`: Partial update of the item. Supports GSI indices.
|
|
259
|
+
- `save()`: Persists the current state of the item (alias for update of all properties). Uses indices attached via `setIndex()`.
|
|
260
|
+
- `setIndex(indices)`: Attaches one or more `IndexQuery` objects to the item for use with `save()` or `create()`.
|
|
261
|
+
|
|
181
262
|
## License
|
|
182
263
|
|
|
183
264
|
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");
|
|
@@ -55,8 +55,12 @@ class DynoQuery {
|
|
|
55
55
|
if (models) {
|
|
56
56
|
this.registeredModels = models;
|
|
57
57
|
Object.entries(models).forEach(([name, def]) => {
|
|
58
|
-
this[name] = (id) => {
|
|
59
|
-
|
|
58
|
+
this[name] = (id, sk) => {
|
|
59
|
+
const partition = new partition_1.Partition(this, { pkPrefix: this.globalPkPrefix + def.pkPrefix }, id);
|
|
60
|
+
if (sk) {
|
|
61
|
+
return partition.draft(sk);
|
|
62
|
+
}
|
|
63
|
+
return partition;
|
|
60
64
|
};
|
|
61
65
|
});
|
|
62
66
|
}
|
|
@@ -167,4 +171,6 @@ class DynoQuery {
|
|
|
167
171
|
}
|
|
168
172
|
exports.DynoQuery = DynoQuery;
|
|
169
173
|
__exportStar(require("./partition"), exports);
|
|
174
|
+
var partition_2 = require("./partition");
|
|
175
|
+
Object.defineProperty(exports, "Item", { enumerable: true, get: function () { return partition_2.Item; } });
|
|
170
176
|
__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;
|
|
@@ -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
|
|
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,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.
|
|
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
|
|
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
|
-
|
|
137
|
-
|
|
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
|
}
|