@karmaniverous/entity-manager 1.0.0 → 2.1.0
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
|
@@ -220,8 +220,11 @@ This change can be accomplished with no breaking changes to existing implementat
|
|
|
220
220
|
* [.EntityManager](#module_entity-manager.EntityManager)
|
|
221
221
|
* [new exports.EntityManager(options)](#new_module_entity-manager.EntityManager_new)
|
|
222
222
|
* [.addKeys(entityToken, item, [overwrite])](#module_entity-manager.EntityManager+addKeys) ⇒ <code>object</code>
|
|
223
|
+
* [.calcShardKey(entityToken, item)](#module_entity-manager.EntityManager+calcShardKey) ⇒ <code>string</code>
|
|
224
|
+
* [.dehydrateIndex(entityToken, indexToken, item, [delimiter])](#module_entity-manager.EntityManager+dehydrateIndex) ⇒ <code>string</code>
|
|
225
|
+
* [.rehydrateIndex(entityToken, indexToken, value, [delimiter])](#module_entity-manager.EntityManager+rehydrateIndex) ⇒ <code>object</code>
|
|
223
226
|
* [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.<string></code>
|
|
224
|
-
* [.query(entityToken, keyToken, item, shardQuery, [
|
|
227
|
+
* [.query(entityToken, keyToken, item, shardQuery, [options])](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.<ShardedQueryResult></code>
|
|
225
228
|
* [.removeKeys(entityToken, item)](#module_entity-manager.EntityManager+removeKeys) ⇒ <code>object</code>
|
|
226
229
|
* _inner_
|
|
227
230
|
* [~ShardQueryResult](#module_entity-manager..ShardQueryResult) : <code>object</code>
|
|
@@ -238,8 +241,11 @@ Manage DynamoDb entities.
|
|
|
238
241
|
* [.EntityManager](#module_entity-manager.EntityManager)
|
|
239
242
|
* [new exports.EntityManager(options)](#new_module_entity-manager.EntityManager_new)
|
|
240
243
|
* [.addKeys(entityToken, item, [overwrite])](#module_entity-manager.EntityManager+addKeys) ⇒ <code>object</code>
|
|
244
|
+
* [.calcShardKey(entityToken, item)](#module_entity-manager.EntityManager+calcShardKey) ⇒ <code>string</code>
|
|
245
|
+
* [.dehydrateIndex(entityToken, indexToken, item, [delimiter])](#module_entity-manager.EntityManager+dehydrateIndex) ⇒ <code>string</code>
|
|
246
|
+
* [.rehydrateIndex(entityToken, indexToken, value, [delimiter])](#module_entity-manager.EntityManager+rehydrateIndex) ⇒ <code>object</code>
|
|
241
247
|
* [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.<string></code>
|
|
242
|
-
* [.query(entityToken, keyToken, item, shardQuery, [
|
|
248
|
+
* [.query(entityToken, keyToken, item, shardQuery, [options])](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.<ShardedQueryResult></code>
|
|
243
249
|
* [.removeKeys(entityToken, item)](#module_entity-manager.EntityManager+removeKeys) ⇒ <code>object</code>
|
|
244
250
|
|
|
245
251
|
<a name="new_module_entity-manager.EntityManager_new"></a>
|
|
@@ -279,6 +285,60 @@ Add sharded keys to an entity item. Does not mutate original item.
|
|
|
279
285
|
| item | <code>object</code> | | Entity item. |
|
|
280
286
|
| [overwrite] | <code>boolean</code> | <code>false</code> | Overwrite existing properties. |
|
|
281
287
|
|
|
288
|
+
<a name="module_entity-manager.EntityManager+calcShardKey"></a>
|
|
289
|
+
|
|
290
|
+
#### entityManager.calcShardKey(entityToken, item) ⇒ <code>string</code>
|
|
291
|
+
Calculated the shard key for an entity item.
|
|
292
|
+
|
|
293
|
+
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
294
|
+
**Returns**: <code>string</code> - Shard key.
|
|
295
|
+
|
|
296
|
+
| Param | Type | Description |
|
|
297
|
+
| --- | --- | --- |
|
|
298
|
+
| entityToken | <code>string</code> | Entity token. |
|
|
299
|
+
| item | <code>object</code> | Entity item. |
|
|
300
|
+
|
|
301
|
+
<a name="module_entity-manager.EntityManager+dehydrateIndex"></a>
|
|
302
|
+
|
|
303
|
+
#### entityManager.dehydrateIndex(entityToken, indexToken, item, [delimiter]) ⇒ <code>string</code>
|
|
304
|
+
Convert a item into a delimited string containing the properties of a named index.
|
|
305
|
+
|
|
306
|
+
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
307
|
+
**Returns**: <code>string</code> - Dehydrated index.
|
|
308
|
+
**Throws**:
|
|
309
|
+
|
|
310
|
+
- <code>Error</code> If entityToken is invalid.
|
|
311
|
+
- <code>Error</code> If indexToken is invalid.
|
|
312
|
+
- <code>Error</code> If item is invalid.
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
| Param | Type | Default | Description |
|
|
316
|
+
| --- | --- | --- | --- |
|
|
317
|
+
| entityToken | <code>string</code> | | Entity token. |
|
|
318
|
+
| indexToken | <code>string</code> | | Index token. |
|
|
319
|
+
| item | <code>object</code> | | Entity item. |
|
|
320
|
+
| [delimiter] | <code>string</code> | <code>"~"</code> | Delimiter. |
|
|
321
|
+
|
|
322
|
+
<a name="module_entity-manager.EntityManager+rehydrateIndex"></a>
|
|
323
|
+
|
|
324
|
+
#### entityManager.rehydrateIndex(entityToken, indexToken, value, [delimiter]) ⇒ <code>object</code>
|
|
325
|
+
Convert a delimited string into a named index key.
|
|
326
|
+
|
|
327
|
+
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
328
|
+
**Returns**: <code>object</code> - Rehydrated index key.
|
|
329
|
+
**Throws**:
|
|
330
|
+
|
|
331
|
+
- <code>Error</code> If entityToken is invalid.
|
|
332
|
+
- <code>Error</code> If indexToken is invalid.
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
| Param | Type | Default | Description |
|
|
336
|
+
| --- | --- | --- | --- |
|
|
337
|
+
| entityToken | <code>string</code> | | Entity token. |
|
|
338
|
+
| indexToken | <code>string</code> | | Index token. |
|
|
339
|
+
| value | <code>string</code> | | Dehydrated index value. |
|
|
340
|
+
| [delimiter] | <code>string</code> | <code>"~"</code> | Delimiter. |
|
|
341
|
+
|
|
282
342
|
<a name="module_entity-manager.EntityManager+getKeySpace"></a>
|
|
283
343
|
|
|
284
344
|
#### entityManager.getKeySpace(entityToken, keyToken, item, timestamp) ⇒ <code>Array.<string></code>
|
|
@@ -303,7 +363,7 @@ Return an array of sharded keys valid for a given entity token & timestamp.
|
|
|
303
363
|
|
|
304
364
|
<a name="module_entity-manager.EntityManager+query"></a>
|
|
305
365
|
|
|
306
|
-
#### entityManager.query(entityToken, keyToken, item, shardQuery, [
|
|
366
|
+
#### entityManager.query(entityToken, keyToken, item, shardQuery, [options]) ⇒ <code>Promise.<ShardedQueryResult></code>
|
|
307
367
|
Query an entity across shards.
|
|
308
368
|
|
|
309
369
|
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
@@ -315,7 +375,9 @@ Query an entity across shards.
|
|
|
315
375
|
| keyToken | <code>string</code> | Key token. |
|
|
316
376
|
| item | <code>object</code> | Entity item sufficiently populated to generate property keyToken. |
|
|
317
377
|
| shardQuery | <code>ShardQueryFunction</code> | Sharded query function. |
|
|
318
|
-
| [
|
|
378
|
+
| [options] | <code>object</code> | Options object. |
|
|
379
|
+
| [options.limit] | <code>number</code> | Request limit. |
|
|
380
|
+
| [options.pageKeys] | <code>object</code> | Map of shard page keys. |
|
|
319
381
|
|
|
320
382
|
<a name="module_entity-manager.EntityManager+removeKeys"></a>
|
|
321
383
|
|
|
@@ -357,7 +419,9 @@ Shard query function
|
|
|
357
419
|
| Param | Type | Description |
|
|
358
420
|
| --- | --- | --- |
|
|
359
421
|
| shardedKey | <code>string</code> | Sharded key. |
|
|
360
|
-
| [
|
|
422
|
+
| [options] | <code>object</code> | Options object. |
|
|
423
|
+
| [options.limit] | <code>number</code> | Request limit. |
|
|
424
|
+
| [options.pageKey] | <code>\*</code> | Page key. |
|
|
361
425
|
|
|
362
426
|
<a name="module_entity-manager..ShardedQueryResult"></a>
|
|
363
427
|
|
|
@@ -4,13 +4,25 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.EntityManager = void 0;
|
|
7
|
-
var _isUndefined2 = _interopRequireDefault(require("lodash/isUndefined"));
|
|
8
7
|
var _map2 = _interopRequireDefault(require("lodash/map"));
|
|
8
|
+
var _size2 = _interopRequireDefault(require("lodash/size"));
|
|
9
|
+
var _isEmpty2 = _interopRequireDefault(require("lodash/isEmpty"));
|
|
9
10
|
var _fromPairs2 = _interopRequireDefault(require("lodash/fromPairs"));
|
|
11
|
+
var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
|
|
12
|
+
var _isInteger2 = _interopRequireDefault(require("lodash/isInteger"));
|
|
13
|
+
var _isUndefined2 = _interopRequireDefault(require("lodash/isUndefined"));
|
|
14
|
+
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
|
|
10
15
|
var _sortedUniq2 = _interopRequireDefault(require("lodash/sortedUniq"));
|
|
16
|
+
var _zipObject2 = _interopRequireDefault(require("lodash/zipObject"));
|
|
17
|
+
var _flatten2 = _interopRequireDefault(require("lodash/flatten"));
|
|
18
|
+
var _uniq2 = _interopRequireDefault(require("lodash/uniq"));
|
|
19
|
+
var _sortBy2 = _interopRequireDefault(require("lodash/sortBy"));
|
|
20
|
+
var _pick2 = _interopRequireDefault(require("lodash/pick"));
|
|
21
|
+
var _values2 = _interopRequireDefault(require("lodash/values"));
|
|
11
22
|
var _forEach2 = _interopRequireDefault(require("lodash/forEach"));
|
|
12
23
|
var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
|
|
13
24
|
var _cloneDeep2 = _interopRequireDefault(require("lodash/cloneDeep"));
|
|
25
|
+
var _inspectParametersDeclaration = require("inspect-parameters-declaration");
|
|
14
26
|
var _PrivateEntityManager = require("./PrivateEntityManager.js");
|
|
15
27
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
16
28
|
function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
|
|
@@ -21,6 +33,7 @@ function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _
|
|
|
21
33
|
function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
|
|
22
34
|
function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
|
|
23
35
|
var _entityManager = /*#__PURE__*/new WeakMap();
|
|
36
|
+
var _getIndexProperties = /*#__PURE__*/new WeakMap();
|
|
24
37
|
/**
|
|
25
38
|
* Manage DynamoDb entities.
|
|
26
39
|
*
|
|
@@ -46,6 +59,10 @@ class EntityManager {
|
|
|
46
59
|
writable: true,
|
|
47
60
|
value: void 0
|
|
48
61
|
});
|
|
62
|
+
_classPrivateFieldInitSpec(this, _getIndexProperties, {
|
|
63
|
+
writable: true,
|
|
64
|
+
value: (entityToken, indexComponents) => (0, _sortBy2.default)((0, _uniq2.default)((0, _flatten2.default)(indexComponents.map(keyToken => (0, _inspectParametersDeclaration.getParametersNames)(_classPrivateFieldGet(this, _entityManager).getKeyGenerator(entityToken, keyToken))))))
|
|
65
|
+
});
|
|
49
66
|
_classPrivateFieldSet(this, _entityManager, new _PrivateEntityManager.PrivateEntityManager({
|
|
50
67
|
config,
|
|
51
68
|
logger
|
|
@@ -77,15 +94,12 @@ class EntityManager {
|
|
|
77
94
|
shardKeyToken
|
|
78
95
|
} = _classPrivateFieldGet(this, _entityManager);
|
|
79
96
|
const {
|
|
80
|
-
keys
|
|
81
|
-
sharding
|
|
97
|
+
keys
|
|
82
98
|
} = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
|
|
83
99
|
|
|
84
100
|
// Add shardKey.
|
|
85
101
|
if (overwrite || (0, _isNil2.default)(newItem[shardKeyToken])) {
|
|
86
|
-
|
|
87
|
-
const timestamp = sharding.timestamp(newItem);
|
|
88
|
-
newItem[shardKeyToken] = _classPrivateFieldGet(this, _entityManager).getShardKey(entityToken, entityKey, timestamp);
|
|
102
|
+
newItem[shardKeyToken] = this.calcShardKey(entityToken, newItem);
|
|
89
103
|
}
|
|
90
104
|
|
|
91
105
|
// Add keys.
|
|
@@ -99,6 +113,71 @@ class EntityManager {
|
|
|
99
113
|
return newItem;
|
|
100
114
|
}
|
|
101
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Calculated the shard key for an entity item.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} entityToken - Entity token.
|
|
120
|
+
* @param {object} item - Entity item.
|
|
121
|
+
* @returns {string} Shard key.
|
|
122
|
+
*/
|
|
123
|
+
calcShardKey(entityToken, item) {
|
|
124
|
+
const {
|
|
125
|
+
sharding
|
|
126
|
+
} = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
|
|
127
|
+
const entityKey = sharding.entityKey(item);
|
|
128
|
+
const timestamp = sharding.timestamp(item);
|
|
129
|
+
return _classPrivateFieldGet(this, _entityManager).getShardKey(entityToken, entityKey, timestamp);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Convert a item into a delimited string containing the properties of a named index.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} entityToken - Entity token.
|
|
136
|
+
* @param {string} indexToken - Index token.
|
|
137
|
+
* @param {object} item - Entity item.
|
|
138
|
+
* @param {string} [delimiter] - Delimiter.
|
|
139
|
+
* @returns {string} Dehydrated index.
|
|
140
|
+
* @throws {Error} If entityToken is invalid.
|
|
141
|
+
* @throws {Error} If indexToken is invalid.
|
|
142
|
+
* @throws {Error} If item is invalid.
|
|
143
|
+
*/
|
|
144
|
+
dehydrateIndex(entityToken, indexToken, item) {
|
|
145
|
+
let delimiter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '~';
|
|
146
|
+
// Validate item.
|
|
147
|
+
_classPrivateFieldGet(this, _entityManager).validateItem(item);
|
|
148
|
+
const indexComponents = _classPrivateFieldGet(this, _entityManager).getIndexComponents(entityToken, indexToken);
|
|
149
|
+
const indexProperties = _classPrivateFieldGet(this, _getIndexProperties).call(this, entityToken, indexComponents);
|
|
150
|
+
const {
|
|
151
|
+
shardKeyToken
|
|
152
|
+
} = _classPrivateFieldGet(this, _entityManager);
|
|
153
|
+
return (0, _values2.default)((0, _pick2.default)({
|
|
154
|
+
...item,
|
|
155
|
+
[shardKeyToken]: this.calcShardKey(entityToken, item)
|
|
156
|
+
}, indexProperties)).join(delimiter);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Convert a delimited string into a named index key.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} entityToken - Entity token.
|
|
162
|
+
* @param {string} indexToken - Index token.
|
|
163
|
+
* @param {string} value - Dehydrated index value.
|
|
164
|
+
* @param {string} [delimiter] - Delimiter.
|
|
165
|
+
* @returns {object} Rehydrated index key.
|
|
166
|
+
* @throws {Error} If entityToken is invalid.
|
|
167
|
+
* @throws {Error} If indexToken is invalid.
|
|
168
|
+
*/
|
|
169
|
+
rehydrateIndex(entityToken, indexToken) {
|
|
170
|
+
let value = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
|
171
|
+
let delimiter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '~';
|
|
172
|
+
const indexComponents = _classPrivateFieldGet(this, _entityManager).getIndexComponents(entityToken, indexToken);
|
|
173
|
+
const indexProperties = _classPrivateFieldGet(this, _getIndexProperties).call(this, entityToken, indexComponents);
|
|
174
|
+
const item = (0, _zipObject2.default)(indexProperties, value.split(delimiter));
|
|
175
|
+
return indexComponents.reduce((index, component) => ({
|
|
176
|
+
...index,
|
|
177
|
+
[component]: _classPrivateFieldGet(this, _entityManager).getKeyGenerator(entityToken, component)(item)
|
|
178
|
+
}), {});
|
|
179
|
+
}
|
|
180
|
+
|
|
102
181
|
/**
|
|
103
182
|
* Return an array of sharded keys valid for a given entity token & timestamp.
|
|
104
183
|
*
|
|
@@ -135,7 +214,9 @@ class EntityManager {
|
|
|
135
214
|
*
|
|
136
215
|
* @callback ShardQueryFunction
|
|
137
216
|
* @param {string} shardedKey - Sharded key.
|
|
138
|
-
* @param {
|
|
217
|
+
* @param {object} [options] - Options object.
|
|
218
|
+
* @param {number} [options.limit] - Request limit.
|
|
219
|
+
* @param {*} [options.pageKey] - Page key.
|
|
139
220
|
* @returns {Promise<ShardQueryResult>} Sharded query result.
|
|
140
221
|
*/
|
|
141
222
|
|
|
@@ -152,31 +233,50 @@ class EntityManager {
|
|
|
152
233
|
* @param {string} keyToken - Key token.
|
|
153
234
|
* @param {object} item - Entity item sufficiently populated to generate property keyToken.
|
|
154
235
|
* @param {ShardQueryFunction} shardQuery - Sharded query function.
|
|
155
|
-
* @param {object} [
|
|
236
|
+
* @param {object} [options] - Options object.
|
|
237
|
+
* @param {number} [options.limit] - Request limit.
|
|
238
|
+
* @param {object} [options.pageKeys] - Map of shard page keys.
|
|
156
239
|
* @returns {Promise<ShardedQueryResult>} Sharded query result.
|
|
157
240
|
*/
|
|
158
|
-
async query(entityToken, keyToken, item, shardQuery
|
|
241
|
+
async query(entityToken, keyToken, item, shardQuery) {
|
|
242
|
+
let {
|
|
243
|
+
limit,
|
|
244
|
+
pageKeys
|
|
245
|
+
} = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
|
|
246
|
+
// Validate params.
|
|
247
|
+
_classPrivateFieldGet(this, _entityManager).validateKeyToken(entityToken, keyToken);
|
|
248
|
+
_classPrivateFieldGet(this, _entityManager).validateItem(item);
|
|
249
|
+
if (!(0, _isFunction2.default)(shardQuery)) throw new Error('shardQuery must be a function');
|
|
250
|
+
if (!(0, _isUndefined2.default)(limit) && !(0, _isInteger2.default)(limit)) throw new Error('limit must be an integer');
|
|
251
|
+
if (!(0, _isUndefined2.default)(pageKeys) && !(0, _isPlainObject2.default)(pageKeys)) throw new Error('pageKeys must be an object');
|
|
252
|
+
|
|
159
253
|
// Generate default pageKeys if not provided
|
|
160
254
|
pageKeys ??= (0, _fromPairs2.default)(this.getKeySpace(entityToken, keyToken, item).map(shardedKey => [shardedKey, undefined]));
|
|
161
255
|
|
|
256
|
+
// Return empty result if no pageKeys.
|
|
257
|
+
if ((0, _isEmpty2.default)(pageKeys)) return {
|
|
258
|
+
items: [],
|
|
259
|
+
pageKeys: {}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Calculate shard limit.
|
|
263
|
+
const shardLimit = (0, _isUndefined2.default)(limit) ? undefined : Math.ceil(limit / (0, _size2.default)(pageKeys));
|
|
264
|
+
|
|
162
265
|
// Query every shard in pageKeys.
|
|
163
|
-
const shardQueryResults = await Promise.all((0, _map2.default)(pageKeys, (pageKey, shardedKey) => new Promise(resolve => shardQuery(shardedKey,
|
|
266
|
+
const shardQueryResults = await Promise.all((0, _map2.default)(pageKeys, (pageKey, shardedKey) => new Promise(resolve => shardQuery(shardedKey, {
|
|
267
|
+
pageKey,
|
|
268
|
+
limit: shardLimit
|
|
269
|
+
}).then(_ref2 => {
|
|
164
270
|
let {
|
|
165
271
|
items,
|
|
166
272
|
pageKey
|
|
167
273
|
} = _ref2;
|
|
168
|
-
console.log('shardQueryResult', {
|
|
169
|
-
shardedKey,
|
|
170
|
-
items,
|
|
171
|
-
pageKey
|
|
172
|
-
});
|
|
173
274
|
return resolve({
|
|
174
275
|
shardedKey,
|
|
175
276
|
items,
|
|
176
277
|
pageKey
|
|
177
278
|
});
|
|
178
279
|
}))));
|
|
179
|
-
console.log('shardQueryResults', shardQueryResults);
|
|
180
280
|
|
|
181
281
|
// Reduce shardQueryResults into a single result.
|
|
182
282
|
const result = shardQueryResults.reduce((shardedQueryResult, _ref3) => {
|
|
@@ -14,6 +14,8 @@ var _has2 = _interopRequireDefault(require("lodash/has"));
|
|
|
14
14
|
var _map2 = _interopRequireDefault(require("lodash/map"));
|
|
15
15
|
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
|
|
16
16
|
var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
|
|
17
|
+
var _keys2 = _interopRequireDefault(require("lodash/keys"));
|
|
18
|
+
var _difference2 = _interopRequireDefault(require("lodash/difference"));
|
|
17
19
|
var _some2 = _interopRequireDefault(require("lodash/some"));
|
|
18
20
|
var _toPairs2 = _interopRequireDefault(require("lodash/toPairs"));
|
|
19
21
|
var _sortBy2 = _interopRequireDefault(require("lodash/sortBy"));
|
|
@@ -43,6 +45,16 @@ const configSchema = {
|
|
|
43
45
|
'^\\w+$': {
|
|
44
46
|
type: 'object',
|
|
45
47
|
properties: {
|
|
48
|
+
indexes: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
additionalProperties: {
|
|
51
|
+
type: 'array',
|
|
52
|
+
items: {
|
|
53
|
+
type: 'string'
|
|
54
|
+
},
|
|
55
|
+
minItems: 1
|
|
56
|
+
}
|
|
57
|
+
},
|
|
46
58
|
keys: {
|
|
47
59
|
type: 'object'
|
|
48
60
|
},
|
|
@@ -160,6 +172,7 @@ class PrivateEntityManager {
|
|
|
160
172
|
const conformedConfig = {
|
|
161
173
|
entities: (0, _mapValues2.default)(entities, _ref => {
|
|
162
174
|
let {
|
|
175
|
+
indexes = {},
|
|
163
176
|
keys = {},
|
|
164
177
|
sharding: {
|
|
165
178
|
bumps = {},
|
|
@@ -170,6 +183,7 @@ class PrivateEntityManager {
|
|
|
170
183
|
} = {}
|
|
171
184
|
} = _ref;
|
|
172
185
|
return {
|
|
186
|
+
indexes,
|
|
173
187
|
keys,
|
|
174
188
|
sharding: {
|
|
175
189
|
bumps: (0, _fromPairs2.default)((0, _sortBy2.default)((0, _toPairs2.default)(bumps), 0)),
|
|
@@ -186,6 +200,7 @@ class PrivateEntityManager {
|
|
|
186
200
|
// Validate entity properties.
|
|
187
201
|
(0, _some2.default)(conformedConfig.entities, (_ref2, entityToken) => {
|
|
188
202
|
let {
|
|
203
|
+
indexes,
|
|
189
204
|
keys,
|
|
190
205
|
sharding: {
|
|
191
206
|
bumps,
|
|
@@ -195,6 +210,16 @@ class PrivateEntityManager {
|
|
|
195
210
|
timestamp
|
|
196
211
|
}
|
|
197
212
|
} = _ref2;
|
|
213
|
+
// Validate entity index components are entity keys.
|
|
214
|
+
(0, _some2.default)(indexes, (index, indexToken) => {
|
|
215
|
+
const invalidIndexComponents = (0, _difference2.default)(index, (0, _keys2.default)(keys));
|
|
216
|
+
if (invalidIndexComponents.length) {
|
|
217
|
+
const message = `${entityToken} index '${indexToken}' components '${invalidIndexComponents}' are not entity keys.`;
|
|
218
|
+
this.logger.error(message);
|
|
219
|
+
throw new Error(message);
|
|
220
|
+
} else return false;
|
|
221
|
+
});
|
|
222
|
+
|
|
198
223
|
// Validate entity keys are functions or undefined.
|
|
199
224
|
(0, _some2.default)(keys, (value, key) => {
|
|
200
225
|
if (!(0, _isNil2.default)(value) && !(0, _isFunction2.default)(value)) {
|
|
@@ -284,6 +309,17 @@ class PrivateEntityManager {
|
|
|
284
309
|
this.validateEntityToken(entityToken);
|
|
285
310
|
return this.config.entities[entityToken];
|
|
286
311
|
}
|
|
312
|
+
getIndexComponents(entityToken, indexToken) {
|
|
313
|
+
const {
|
|
314
|
+
indexes
|
|
315
|
+
} = this.getEntityConfig(entityToken);
|
|
316
|
+
if (!(0, _has2.default)(indexes, indexToken)) {
|
|
317
|
+
const message = `Index '${indexToken}' does not exist for entity '${entityToken}'.`;
|
|
318
|
+
this.logger.error(message);
|
|
319
|
+
throw new Error(message);
|
|
320
|
+
}
|
|
321
|
+
return indexes[indexToken];
|
|
322
|
+
}
|
|
287
323
|
getKeyGenerator(entityToken, keyToken) {
|
|
288
324
|
const {
|
|
289
325
|
keys
|
|
@@ -379,6 +415,22 @@ class PrivateEntityManager {
|
|
|
379
415
|
} else return true;
|
|
380
416
|
}
|
|
381
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Tests whether a key token is valid.
|
|
420
|
+
*
|
|
421
|
+
* @param {string} entityToken - Entity token.
|
|
422
|
+
* @param {string} keyToken - Key token.
|
|
423
|
+
* @returns {boolean} true if keyToken is valid.
|
|
424
|
+
*/
|
|
425
|
+
validateKeyToken(entityToken, keyToken) {
|
|
426
|
+
this.validateEntityToken(entityToken);
|
|
427
|
+
if (!this.config.entities[entityToken].keys[keyToken]) {
|
|
428
|
+
const message = `Invalid entity ${entityToken} keyToken: ${keyToken}`;
|
|
429
|
+
this.logger.error(message);
|
|
430
|
+
throw new Error(message);
|
|
431
|
+
} else return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
382
434
|
/**
|
|
383
435
|
* Tests whether an item is valid.
|
|
384
436
|
*
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @module entity-manager
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { getParametersNames } from 'inspect-parameters-declaration';
|
|
5
6
|
import _ from 'lodash';
|
|
6
7
|
|
|
7
8
|
import { PrivateEntityManager } from './PrivateEntityManager.js';
|
|
@@ -54,17 +55,11 @@ export class EntityManager {
|
|
|
54
55
|
|
|
55
56
|
// Get entity config.
|
|
56
57
|
const { shardKeyToken } = this.#entityManager;
|
|
57
|
-
const { keys
|
|
58
|
+
const { keys } = this.#entityManager.getEntityConfig(entityToken);
|
|
58
59
|
|
|
59
60
|
// Add shardKey.
|
|
60
61
|
if (overwrite || _.isNil(newItem[shardKeyToken])) {
|
|
61
|
-
|
|
62
|
-
const timestamp = sharding.timestamp(newItem);
|
|
63
|
-
newItem[shardKeyToken] = this.#entityManager.getShardKey(
|
|
64
|
-
entityToken,
|
|
65
|
-
entityKey,
|
|
66
|
-
timestamp
|
|
67
|
-
);
|
|
62
|
+
newItem[shardKeyToken] = this.calcShardKey(entityToken, newItem);
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
// Add keys.
|
|
@@ -79,6 +74,108 @@ export class EntityManager {
|
|
|
79
74
|
return newItem;
|
|
80
75
|
}
|
|
81
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Calculated the shard key for an entity item.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} entityToken - Entity token.
|
|
81
|
+
* @param {object} item - Entity item.
|
|
82
|
+
* @returns {string} Shard key.
|
|
83
|
+
*/
|
|
84
|
+
calcShardKey(entityToken, item) {
|
|
85
|
+
const { sharding } = this.#entityManager.getEntityConfig(entityToken);
|
|
86
|
+
const entityKey = sharding.entityKey(item);
|
|
87
|
+
const timestamp = sharding.timestamp(item);
|
|
88
|
+
|
|
89
|
+
return this.#entityManager.getShardKey(entityToken, entityKey, timestamp);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Convert a item into a delimited string containing the properties of a named index.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} entityToken - Entity token.
|
|
96
|
+
* @param {string} indexToken - Index token.
|
|
97
|
+
* @param {object} item - Entity item.
|
|
98
|
+
* @param {string} [delimiter] - Delimiter.
|
|
99
|
+
* @returns {string} Dehydrated index.
|
|
100
|
+
* @throws {Error} If entityToken is invalid.
|
|
101
|
+
* @throws {Error} If indexToken is invalid.
|
|
102
|
+
* @throws {Error} If item is invalid.
|
|
103
|
+
*/
|
|
104
|
+
dehydrateIndex(entityToken, indexToken, item, delimiter = '~') {
|
|
105
|
+
// Validate item.
|
|
106
|
+
this.#entityManager.validateItem(item);
|
|
107
|
+
|
|
108
|
+
const indexComponents = this.#entityManager.getIndexComponents(
|
|
109
|
+
entityToken,
|
|
110
|
+
indexToken
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const indexProperties = this.#getIndexProperties(
|
|
114
|
+
entityToken,
|
|
115
|
+
indexComponents
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const { shardKeyToken } = this.#entityManager;
|
|
119
|
+
return _.values(
|
|
120
|
+
_.pick(
|
|
121
|
+
{
|
|
122
|
+
...item,
|
|
123
|
+
[shardKeyToken]: this.calcShardKey(entityToken, item),
|
|
124
|
+
},
|
|
125
|
+
indexProperties
|
|
126
|
+
)
|
|
127
|
+
).join(delimiter);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#getIndexProperties = (entityToken, indexComponents) =>
|
|
131
|
+
_.sortBy(
|
|
132
|
+
_.uniq(
|
|
133
|
+
_.flatten(
|
|
134
|
+
indexComponents.map((keyToken) =>
|
|
135
|
+
getParametersNames(
|
|
136
|
+
this.#entityManager.getKeyGenerator(entityToken, keyToken)
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Convert a delimited string into a named index key.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} entityToken - Entity token.
|
|
147
|
+
* @param {string} indexToken - Index token.
|
|
148
|
+
* @param {string} value - Dehydrated index value.
|
|
149
|
+
* @param {string} [delimiter] - Delimiter.
|
|
150
|
+
* @returns {object} Rehydrated index key.
|
|
151
|
+
* @throws {Error} If entityToken is invalid.
|
|
152
|
+
* @throws {Error} If indexToken is invalid.
|
|
153
|
+
*/
|
|
154
|
+
rehydrateIndex(entityToken, indexToken, value = '', delimiter = '~') {
|
|
155
|
+
const indexComponents = this.#entityManager.getIndexComponents(
|
|
156
|
+
entityToken,
|
|
157
|
+
indexToken
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const indexProperties = this.#getIndexProperties(
|
|
161
|
+
entityToken,
|
|
162
|
+
indexComponents
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const item = _.zipObject(indexProperties, value.split(delimiter));
|
|
166
|
+
|
|
167
|
+
return indexComponents.reduce(
|
|
168
|
+
(index, component) => ({
|
|
169
|
+
...index,
|
|
170
|
+
[component]: this.#entityManager.getKeyGenerator(
|
|
171
|
+
entityToken,
|
|
172
|
+
component
|
|
173
|
+
)(item),
|
|
174
|
+
}),
|
|
175
|
+
{}
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
82
179
|
/**
|
|
83
180
|
* Return an array of sharded keys valid for a given entity token & timestamp.
|
|
84
181
|
*
|
|
@@ -130,7 +227,9 @@ export class EntityManager {
|
|
|
130
227
|
*
|
|
131
228
|
* @callback ShardQueryFunction
|
|
132
229
|
* @param {string} shardedKey - Sharded key.
|
|
133
|
-
* @param {
|
|
230
|
+
* @param {object} [options] - Options object.
|
|
231
|
+
* @param {number} [options.limit] - Request limit.
|
|
232
|
+
* @param {*} [options.pageKey] - Page key.
|
|
134
233
|
* @returns {Promise<ShardQueryResult>} Sharded query result.
|
|
135
234
|
*/
|
|
136
235
|
|
|
@@ -147,10 +246,28 @@ export class EntityManager {
|
|
|
147
246
|
* @param {string} keyToken - Key token.
|
|
148
247
|
* @param {object} item - Entity item sufficiently populated to generate property keyToken.
|
|
149
248
|
* @param {ShardQueryFunction} shardQuery - Sharded query function.
|
|
150
|
-
* @param {object} [
|
|
249
|
+
* @param {object} [options] - Options object.
|
|
250
|
+
* @param {number} [options.limit] - Request limit.
|
|
251
|
+
* @param {object} [options.pageKeys] - Map of shard page keys.
|
|
151
252
|
* @returns {Promise<ShardedQueryResult>} Sharded query result.
|
|
152
253
|
*/
|
|
153
|
-
async query(
|
|
254
|
+
async query(
|
|
255
|
+
entityToken,
|
|
256
|
+
keyToken,
|
|
257
|
+
item,
|
|
258
|
+
shardQuery,
|
|
259
|
+
{ limit, pageKeys } = {}
|
|
260
|
+
) {
|
|
261
|
+
// Validate params.
|
|
262
|
+
this.#entityManager.validateKeyToken(entityToken, keyToken);
|
|
263
|
+
this.#entityManager.validateItem(item);
|
|
264
|
+
if (!_.isFunction(shardQuery))
|
|
265
|
+
throw new Error('shardQuery must be a function');
|
|
266
|
+
if (!_.isUndefined(limit) && !_.isInteger(limit))
|
|
267
|
+
throw new Error('limit must be an integer');
|
|
268
|
+
if (!_.isUndefined(pageKeys) && !_.isPlainObject(pageKeys))
|
|
269
|
+
throw new Error('pageKeys must be an object');
|
|
270
|
+
|
|
154
271
|
// Generate default pageKeys if not provided
|
|
155
272
|
pageKeys ??= _.fromPairs(
|
|
156
273
|
this.getKeySpace(entityToken, keyToken, item).map((shardedKey) => [
|
|
@@ -159,22 +276,27 @@ export class EntityManager {
|
|
|
159
276
|
])
|
|
160
277
|
);
|
|
161
278
|
|
|
279
|
+
// Return empty result if no pageKeys.
|
|
280
|
+
if (_.isEmpty(pageKeys)) return { items: [], pageKeys: {} };
|
|
281
|
+
|
|
282
|
+
// Calculate shard limit.
|
|
283
|
+
const shardLimit = _.isUndefined(limit)
|
|
284
|
+
? undefined
|
|
285
|
+
: Math.ceil(limit / _.size(pageKeys));
|
|
286
|
+
|
|
162
287
|
// Query every shard in pageKeys.
|
|
163
288
|
const shardQueryResults = await Promise.all(
|
|
164
289
|
_.map(
|
|
165
290
|
pageKeys,
|
|
166
291
|
(pageKey, shardedKey) =>
|
|
167
292
|
new Promise((resolve) =>
|
|
168
|
-
shardQuery(shardedKey,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
})
|
|
293
|
+
shardQuery(shardedKey, { pageKey, limit: shardLimit }).then(
|
|
294
|
+
({ items, pageKey }) => resolve({ shardedKey, items, pageKey })
|
|
295
|
+
)
|
|
172
296
|
)
|
|
173
297
|
)
|
|
174
298
|
);
|
|
175
299
|
|
|
176
|
-
console.log('shardQueryResults', shardQueryResults);
|
|
177
|
-
|
|
178
300
|
// Reduce shardQueryResults into a single result.
|
|
179
301
|
const result = shardQueryResults.reduce(
|
|
180
302
|
(shardedQueryResult, { shardedKey, items, pageKey }) => ({
|
|
@@ -16,6 +16,16 @@ const configSchema = {
|
|
|
16
16
|
'^\\w+$': {
|
|
17
17
|
type: 'object',
|
|
18
18
|
properties: {
|
|
19
|
+
indexes: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
additionalProperties: {
|
|
22
|
+
type: 'array',
|
|
23
|
+
items: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
},
|
|
26
|
+
minItems: 1,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
19
29
|
keys: {
|
|
20
30
|
type: 'object',
|
|
21
31
|
},
|
|
@@ -117,6 +127,7 @@ export class PrivateEntityManager {
|
|
|
117
127
|
entities: _.mapValues(
|
|
118
128
|
entities,
|
|
119
129
|
({
|
|
130
|
+
indexes = {},
|
|
120
131
|
keys = {},
|
|
121
132
|
sharding: {
|
|
122
133
|
bumps = {},
|
|
@@ -126,6 +137,7 @@ export class PrivateEntityManager {
|
|
|
126
137
|
timestamp,
|
|
127
138
|
} = {},
|
|
128
139
|
}) => ({
|
|
140
|
+
indexes,
|
|
129
141
|
keys,
|
|
130
142
|
sharding: {
|
|
131
143
|
bumps: _.fromPairs(_.sortBy(_.toPairs(bumps), 0)),
|
|
@@ -144,11 +156,22 @@ export class PrivateEntityManager {
|
|
|
144
156
|
conformedConfig.entities,
|
|
145
157
|
(
|
|
146
158
|
{
|
|
159
|
+
indexes,
|
|
147
160
|
keys,
|
|
148
161
|
sharding: { bumps, entityKey, nibbleBits, nibbles, timestamp },
|
|
149
162
|
},
|
|
150
163
|
entityToken
|
|
151
164
|
) => {
|
|
165
|
+
// Validate entity index components are entity keys.
|
|
166
|
+
_.some(indexes, (index, indexToken) => {
|
|
167
|
+
const invalidIndexComponents = _.difference(index, _.keys(keys));
|
|
168
|
+
if (invalidIndexComponents.length) {
|
|
169
|
+
const message = `${entityToken} index '${indexToken}' components '${invalidIndexComponents}' are not entity keys.`;
|
|
170
|
+
this.logger.error(message);
|
|
171
|
+
throw new Error(message);
|
|
172
|
+
} else return false;
|
|
173
|
+
});
|
|
174
|
+
|
|
152
175
|
// Validate entity keys are functions or undefined.
|
|
153
176
|
_.some(keys, (value, key) => {
|
|
154
177
|
if (!_.isNil(value) && !_.isFunction(value)) {
|
|
@@ -240,6 +263,18 @@ export class PrivateEntityManager {
|
|
|
240
263
|
return this.config.entities[entityToken];
|
|
241
264
|
}
|
|
242
265
|
|
|
266
|
+
getIndexComponents(entityToken, indexToken) {
|
|
267
|
+
const { indexes } = this.getEntityConfig(entityToken);
|
|
268
|
+
|
|
269
|
+
if (!_.has(indexes, indexToken)) {
|
|
270
|
+
const message = `Index '${indexToken}' does not exist for entity '${entityToken}'.`;
|
|
271
|
+
this.logger.error(message);
|
|
272
|
+
throw new Error(message);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return indexes[indexToken];
|
|
276
|
+
}
|
|
277
|
+
|
|
243
278
|
getKeyGenerator(entityToken, keyToken) {
|
|
244
279
|
const { keys } = this.getEntityConfig(entityToken);
|
|
245
280
|
|
|
@@ -348,6 +383,23 @@ export class PrivateEntityManager {
|
|
|
348
383
|
} else return true;
|
|
349
384
|
}
|
|
350
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Tests whether a key token is valid.
|
|
388
|
+
*
|
|
389
|
+
* @param {string} entityToken - Entity token.
|
|
390
|
+
* @param {string} keyToken - Key token.
|
|
391
|
+
* @returns {boolean} true if keyToken is valid.
|
|
392
|
+
*/
|
|
393
|
+
validateKeyToken(entityToken, keyToken) {
|
|
394
|
+
this.validateEntityToken(entityToken);
|
|
395
|
+
|
|
396
|
+
if (!this.config.entities[entityToken].keys[keyToken]) {
|
|
397
|
+
const message = `Invalid entity ${entityToken} keyToken: ${keyToken}`;
|
|
398
|
+
this.logger.error(message);
|
|
399
|
+
throw new Error(message);
|
|
400
|
+
} else return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
351
403
|
/**
|
|
352
404
|
* Tests whether an item is valid.
|
|
353
405
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karmaniverous/entity-manager",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"license": "BSD-3-Clause",
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"inspect-parameters-declaration": "^0.1.0",
|
|
23
24
|
"jsonschema": "^1.4.1",
|
|
24
25
|
"lodash": "^4.17.21",
|
|
25
26
|
"string-hash": "^1.1.3"
|