@karmaniverous/entity-manager 4.2.0 → 4.3.0-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
|
@@ -364,15 +364,16 @@ Query an entity across shards.
|
|
|
364
364
|
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
365
365
|
**Returns**: <code>Promise.<ShardedQueryResult></code> - Sharded query result.
|
|
366
366
|
|
|
367
|
-
| Param | Type |
|
|
368
|
-
| --- | --- | --- |
|
|
369
|
-
| options | <code>object</code> |
|
|
370
|
-
| options.entityToken | <code>string</code> |
|
|
371
|
-
| [options.keyToken] | <code>string</code> |
|
|
372
|
-
| [options.item] | <code>object</code> |
|
|
373
|
-
| options.shardQuery | <code>ShardQueryFunction</code> |
|
|
374
|
-
| [options.limit] | <code>number</code> |
|
|
375
|
-
| [options.pageKeys] | <code>object</code> |
|
|
367
|
+
| Param | Type | Description |
|
|
368
|
+
| --- | --- | --- |
|
|
369
|
+
| options | <code>object</code> | Query options. |
|
|
370
|
+
| options.entityToken | <code>string</code> | Entity token. |
|
|
371
|
+
| [options.keyToken] | <code>string</code> | Key token. |
|
|
372
|
+
| [options.item] | <code>object</code> | Entity item sufficiently populated to generate property keyToken. |
|
|
373
|
+
| options.shardQuery | <code>ShardQueryFunction</code> | Sharded query function. |
|
|
374
|
+
| [options.limit] | <code>number</code> | Request limit. |
|
|
375
|
+
| [options.pageKeys] | <code>object</code> | Map of shard page keys. |
|
|
376
|
+
| [options.pages] | <code>number</code> \| <code>'all'</code> | Max number of pages to query. |
|
|
376
377
|
|
|
377
378
|
<a name="module_entity-manager.EntityManager+rehydrateIndex"></a>
|
|
378
379
|
|
|
@@ -25,20 +25,18 @@ var _cloneDeep2 = _interopRequireDefault(require("lodash/cloneDeep"));
|
|
|
25
25
|
var _inspectParametersDeclaration = require("inspect-parameters-declaration");
|
|
26
26
|
var _PrivateEntityManager = require("./PrivateEntityManager.js");
|
|
27
27
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
|
|
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); }
|
|
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; } }
|
|
35
|
-
var _entityManager = /*#__PURE__*/new WeakMap();
|
|
28
|
+
/**
|
|
29
|
+
* @module entity-manager
|
|
30
|
+
*/
|
|
31
|
+
|
|
36
32
|
/**
|
|
37
33
|
* Manage DynamoDb entities.
|
|
38
34
|
*
|
|
39
35
|
* @class
|
|
40
36
|
*/
|
|
41
37
|
class EntityManager {
|
|
38
|
+
#entityManager;
|
|
39
|
+
|
|
42
40
|
/**
|
|
43
41
|
* Create an EntityManager instance.
|
|
44
42
|
*
|
|
@@ -54,14 +52,10 @@ class EntityManager {
|
|
|
54
52
|
config,
|
|
55
53
|
logger
|
|
56
54
|
} = _ref;
|
|
57
|
-
|
|
58
|
-
writable: true,
|
|
59
|
-
value: void 0
|
|
60
|
-
});
|
|
61
|
-
_classPrivateFieldSet(this, _entityManager, new _PrivateEntityManager.PrivateEntityManager({
|
|
55
|
+
this.#entityManager = new _PrivateEntityManager.PrivateEntityManager({
|
|
62
56
|
config,
|
|
63
57
|
logger
|
|
64
|
-
})
|
|
58
|
+
});
|
|
65
59
|
}
|
|
66
60
|
|
|
67
61
|
/**
|
|
@@ -69,17 +63,17 @@ class EntityManager {
|
|
|
69
63
|
*
|
|
70
64
|
* @param {string} entityToken - Entity token.
|
|
71
65
|
* @param {object} item - Entity item.
|
|
72
|
-
* @param {boolean} [overwrite
|
|
66
|
+
* @param {boolean} [overwrite] - Overwrite existing properties.
|
|
73
67
|
* @returns {object} Decorated entity item.
|
|
74
68
|
* @throws {Error} If entityToken is invalid.
|
|
75
69
|
* @throws {Error} If item is invalid.
|
|
76
70
|
*/
|
|
77
71
|
addKeys(entityToken, item) {
|
|
78
72
|
let overwrite = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
79
|
-
|
|
73
|
+
this.#entityManager.logger.debug(`adding sharded index keys to ${entityToken}${overwrite ? ' with overwrite' : ''}...`, item);
|
|
80
74
|
|
|
81
75
|
// Validate item.
|
|
82
|
-
|
|
76
|
+
this.#entityManager.validateItem(item);
|
|
83
77
|
|
|
84
78
|
// Clone item.
|
|
85
79
|
const newItem = (0, _cloneDeep2.default)(item);
|
|
@@ -87,10 +81,10 @@ class EntityManager {
|
|
|
87
81
|
// Get entity config.
|
|
88
82
|
const {
|
|
89
83
|
shardKeyToken
|
|
90
|
-
} =
|
|
84
|
+
} = this.#entityManager;
|
|
91
85
|
const {
|
|
92
86
|
keys
|
|
93
|
-
} =
|
|
87
|
+
} = this.#entityManager.getEntityConfig(entityToken);
|
|
94
88
|
|
|
95
89
|
// Add shardKey.
|
|
96
90
|
if (overwrite || (0, _isNil2.default)(newItem[shardKeyToken])) {
|
|
@@ -104,7 +98,7 @@ class EntityManager {
|
|
|
104
98
|
} = _ref2;
|
|
105
99
|
if (overwrite || (0, _isNil2.default)(newItem[key])) newItem[key] = encode(newItem);
|
|
106
100
|
});
|
|
107
|
-
|
|
101
|
+
this.#entityManager.logger.debug('done', newItem);
|
|
108
102
|
|
|
109
103
|
// Remove undefined shardKey.
|
|
110
104
|
if ((0, _isNil2.default)(newItem[shardKeyToken])) delete newItem[shardKeyToken];
|
|
@@ -121,10 +115,10 @@ class EntityManager {
|
|
|
121
115
|
calcShardKey(entityToken, item) {
|
|
122
116
|
const {
|
|
123
117
|
sharding
|
|
124
|
-
} =
|
|
118
|
+
} = this.#entityManager.getEntityConfig(entityToken);
|
|
125
119
|
const entityKey = sharding.entityKey(item);
|
|
126
120
|
const timestamp = sharding.timestamp(item);
|
|
127
|
-
return
|
|
121
|
+
return this.#entityManager.getShardKey(entityToken, entityKey, timestamp);
|
|
128
122
|
}
|
|
129
123
|
|
|
130
124
|
/**
|
|
@@ -142,8 +136,8 @@ class EntityManager {
|
|
|
142
136
|
dehydrateIndex(entityToken, indexToken, index) {
|
|
143
137
|
let delimiter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '~';
|
|
144
138
|
// Validate item.
|
|
145
|
-
|
|
146
|
-
const indexComponents = (0, _isArray2.default)(indexToken) ? indexToken :
|
|
139
|
+
this.#entityManager.validateItem(index);
|
|
140
|
+
const indexComponents = (0, _isArray2.default)(indexToken) ? indexToken : this.#entityManager.getIndexComponents(entityToken, indexToken);
|
|
147
141
|
const indexProperties = indexComponents.reduce((properties, component) => {
|
|
148
142
|
const {
|
|
149
143
|
decode
|
|
@@ -176,7 +170,7 @@ class EntityManager {
|
|
|
176
170
|
getKey(entityToken, keyToken) {
|
|
177
171
|
const {
|
|
178
172
|
keys
|
|
179
|
-
} =
|
|
173
|
+
} = this.#entityManager.getEntityConfig(entityToken);
|
|
180
174
|
if (!(0, _has2.default)(keys, keyToken)) throw new Error(`Key '${keyToken}' does not exist for entity '${entityToken}'.`);
|
|
181
175
|
return keys[keyToken];
|
|
182
176
|
}
|
|
@@ -196,13 +190,13 @@ class EntityManager {
|
|
|
196
190
|
*/
|
|
197
191
|
getKeySpace(entityToken, keyToken, item) {
|
|
198
192
|
let timestamp = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Date.now();
|
|
199
|
-
|
|
200
|
-
const shardKeySpace =
|
|
193
|
+
this.#entityManager.logger.debug(`getting shard key space for ${entityToken} on key '${keyToken}' at timestamp ${timestamp}...`, item);
|
|
194
|
+
const shardKeySpace = this.#entityManager.getShardKeySpace(entityToken, timestamp);
|
|
201
195
|
const result = (0, _sortedUniq2.default)(shardKeySpace.map(shardKey => this.getKey(entityToken, keyToken).encode({
|
|
202
196
|
...item,
|
|
203
|
-
[
|
|
197
|
+
[this.#entityManager.shardKeyToken]: shardKey
|
|
204
198
|
})));
|
|
205
|
-
|
|
199
|
+
this.#entityManager.logger.debug('done', result);
|
|
206
200
|
return result;
|
|
207
201
|
}
|
|
208
202
|
|
|
@@ -233,11 +227,12 @@ class EntityManager {
|
|
|
233
227
|
*
|
|
234
228
|
* @param {object} options - Query options.
|
|
235
229
|
* @param {string} options.entityToken - Entity token.
|
|
236
|
-
* @param {string} [options.keyToken
|
|
237
|
-
* @param {object} [options.item
|
|
230
|
+
* @param {string} [options.keyToken] - Key token.
|
|
231
|
+
* @param {object} [options.item] - Entity item sufficiently populated to generate property keyToken.
|
|
238
232
|
* @param {ShardQueryFunction} options.shardQuery - Sharded query function.
|
|
239
233
|
* @param {number} [options.limit] - Request limit.
|
|
240
234
|
* @param {object} [options.pageKeys] - Map of shard page keys.
|
|
235
|
+
* @param {number|'all'} [options.pages] - Max number of pages to query.
|
|
241
236
|
* @returns {Promise<ShardedQueryResult>} Sharded query result.
|
|
242
237
|
*/
|
|
243
238
|
async query() {
|
|
@@ -247,14 +242,16 @@ class EntityManager {
|
|
|
247
242
|
item = {},
|
|
248
243
|
shardQuery,
|
|
249
244
|
limit,
|
|
250
|
-
pageKeys
|
|
245
|
+
pageKeys,
|
|
246
|
+
pages = 1
|
|
251
247
|
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
252
248
|
// Validate params.
|
|
253
|
-
|
|
254
|
-
|
|
249
|
+
this.#entityManager.validateKeyToken(entityToken, keyToken);
|
|
250
|
+
this.#entityManager.validateItem(item);
|
|
255
251
|
if (!(0, _isFunction2.default)(shardQuery)) throw new Error('shardQuery must be a function');
|
|
256
|
-
if (!(0, _isUndefined2.default)(limit) && !(0, _isInteger2.default)(limit)) throw new Error('limit must be
|
|
252
|
+
if (!(0, _isUndefined2.default)(limit) && !((0, _isInteger2.default)(limit) && limit >= 1)) throw new Error('limit must be a positive integer');
|
|
257
253
|
if (!(0, _isUndefined2.default)(pageKeys) && !(0, _isPlainObject2.default)(pageKeys)) throw new Error('pageKeys must be an object');
|
|
254
|
+
if (!(0, _isUndefined2.default)(pageKeys) && !((0, _isInteger2.default)(pages) && pages >= 1) && pages !== 'all') throw new Error("pages must be a positive integer or 'all'");
|
|
258
255
|
|
|
259
256
|
// Generate default pageKeys if not provided
|
|
260
257
|
pageKeys ??= (0, _fromPairs2.default)(this.getKeySpace(entityToken, keyToken, item).map(shardedKey => [shardedKey, undefined]));
|
|
@@ -268,44 +265,57 @@ class EntityManager {
|
|
|
268
265
|
// Calculate shard limit.
|
|
269
266
|
const shardLimit = (0, _isUndefined2.default)(limit) ? undefined : Math.ceil(limit / (0, _size2.default)(pageKeys));
|
|
270
267
|
|
|
271
|
-
//
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
count,
|
|
275
|
-
items,
|
|
276
|
-
pageKey
|
|
277
|
-
} = _ref6;
|
|
278
|
-
return resolve({
|
|
279
|
-
count,
|
|
280
|
-
items,
|
|
281
|
-
pageKey,
|
|
282
|
-
shardedKey
|
|
283
|
-
});
|
|
284
|
-
}))));
|
|
285
|
-
|
|
286
|
-
// Reduce shardQueryResults into a single result.
|
|
287
|
-
const result = shardQueryResults.reduce((shardedQueryResult, _ref7) => {
|
|
288
|
-
let {
|
|
289
|
-
count,
|
|
290
|
-
items,
|
|
291
|
-
pageKey,
|
|
292
|
-
shardedKey
|
|
293
|
-
} = _ref7;
|
|
294
|
-
return {
|
|
295
|
-
count: shardedQueryResult.count + count,
|
|
296
|
-
items: [...shardedQueryResult.items, ...items],
|
|
297
|
-
pageKeys: {
|
|
298
|
-
...shardedQueryResult.pageKeys,
|
|
299
|
-
...((0, _isUndefined2.default)(pageKey) ? {} : {
|
|
300
|
-
[shardedKey]: pageKey
|
|
301
|
-
})
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
}, {
|
|
268
|
+
// Iterate search over pages.
|
|
269
|
+
let page = 0;
|
|
270
|
+
const result = {
|
|
305
271
|
count: 0,
|
|
306
272
|
items: [],
|
|
307
|
-
pageKeys
|
|
308
|
-
}
|
|
273
|
+
pageKeys
|
|
274
|
+
};
|
|
275
|
+
do {
|
|
276
|
+
// Query every shard in pageKeys.
|
|
277
|
+
const shardQueryResults = await Promise.all((0, _map2.default)(result.pageKeys, (pageKey, shardedKey) => new Promise(resolve => shardQuery(shardedKey, pageKey, shardLimit).then(_ref6 => {
|
|
278
|
+
let {
|
|
279
|
+
count,
|
|
280
|
+
items,
|
|
281
|
+
pageKey
|
|
282
|
+
} = _ref6;
|
|
283
|
+
return resolve({
|
|
284
|
+
count,
|
|
285
|
+
items,
|
|
286
|
+
pageKey,
|
|
287
|
+
shardedKey
|
|
288
|
+
});
|
|
289
|
+
}))));
|
|
290
|
+
|
|
291
|
+
// Reduce shardQueryResults into a single result.
|
|
292
|
+
const pageResult = shardQueryResults.reduce((shardedQueryResult, _ref7) => {
|
|
293
|
+
let {
|
|
294
|
+
count,
|
|
295
|
+
items,
|
|
296
|
+
pageKey,
|
|
297
|
+
shardedKey
|
|
298
|
+
} = _ref7;
|
|
299
|
+
return {
|
|
300
|
+
count: shardedQueryResult.count + count,
|
|
301
|
+
items: [...shardedQueryResult.items, ...items],
|
|
302
|
+
pageKeys: {
|
|
303
|
+
...shardedQueryResult.pageKeys,
|
|
304
|
+
...((0, _isUndefined2.default)(pageKey) ? {} : {
|
|
305
|
+
[shardedKey]: pageKey
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}, {
|
|
310
|
+
count: 0,
|
|
311
|
+
items: [],
|
|
312
|
+
pageKeys: {}
|
|
313
|
+
});
|
|
314
|
+
result.count += pageResult.count;
|
|
315
|
+
result.items = [...result.items, ...pageResult.items];
|
|
316
|
+
result.pageKeys = pageResult.pageKeys;
|
|
317
|
+
page++;
|
|
318
|
+
} while ((pages === 'all' || page < pages) && !(0, _isEmpty2.default)(result.pageKeys));
|
|
309
319
|
return result;
|
|
310
320
|
}
|
|
311
321
|
|
|
@@ -323,21 +333,21 @@ class EntityManager {
|
|
|
323
333
|
rehydrateIndex(entityToken, indexToken) {
|
|
324
334
|
let value = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
|
325
335
|
let delimiter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '~';
|
|
326
|
-
const indexComponents = (0, _isArray2.default)(indexToken) ? indexToken :
|
|
336
|
+
const indexComponents = (0, _isArray2.default)(indexToken) ? indexToken : this.#entityManager.getIndexComponents(entityToken, indexToken);
|
|
327
337
|
const indexKeys = (0, _sortBy2.default)(indexComponents.reduce((keys, component) => {
|
|
328
338
|
const {
|
|
329
339
|
encode
|
|
330
340
|
} = this.getKey(entityToken, component);
|
|
331
341
|
return [...new Set([...keys, ...(0, _inspectParametersDeclaration.getParametersNames)(encode)]).values()];
|
|
332
342
|
}, []));
|
|
333
|
-
const
|
|
343
|
+
const indexProperties = (0, _zipObject2.default)(indexKeys, value.split(delimiter));
|
|
334
344
|
return indexComponents.reduce((index, component) => {
|
|
335
345
|
const {
|
|
336
346
|
encode
|
|
337
347
|
} = this.getKey(entityToken, component);
|
|
338
348
|
return {
|
|
339
349
|
...index,
|
|
340
|
-
[component]: encode(
|
|
350
|
+
[component]: encode(indexProperties)
|
|
341
351
|
};
|
|
342
352
|
}, {});
|
|
343
353
|
}
|
|
@@ -352,10 +362,10 @@ class EntityManager {
|
|
|
352
362
|
* @throws {Error} If item is invalid.
|
|
353
363
|
*/
|
|
354
364
|
removeKeys(entityToken, item) {
|
|
355
|
-
|
|
365
|
+
this.#entityManager.logger.debug(`removing sharded index keys from ${entityToken}...`, item);
|
|
356
366
|
|
|
357
367
|
// Validate item.
|
|
358
|
-
|
|
368
|
+
this.#entityManager.validateItem(item);
|
|
359
369
|
|
|
360
370
|
// Clone item.
|
|
361
371
|
const newItem = (0, _cloneDeep2.default)(item);
|
|
@@ -363,10 +373,10 @@ class EntityManager {
|
|
|
363
373
|
// Get entity config.
|
|
364
374
|
const {
|
|
365
375
|
shardKeyToken
|
|
366
|
-
} =
|
|
376
|
+
} = this.#entityManager;
|
|
367
377
|
const {
|
|
368
378
|
keys
|
|
369
|
-
} =
|
|
379
|
+
} = this.#entityManager.getEntityConfig(entityToken);
|
|
370
380
|
|
|
371
381
|
// Remove shardKey.
|
|
372
382
|
delete newItem[shardKeyToken];
|
|
@@ -375,7 +385,7 @@ class EntityManager {
|
|
|
375
385
|
(0, _forEach2.default)(keys, (value, key) => {
|
|
376
386
|
if (!value.retain) delete newItem[key];
|
|
377
387
|
});
|
|
378
|
-
|
|
388
|
+
this.#entityManager.logger.debug('done', newItem);
|
|
379
389
|
return newItem;
|
|
380
390
|
}
|
|
381
391
|
}
|
|
@@ -26,13 +26,6 @@ var _mapValues2 = _interopRequireDefault(require("lodash/mapValues"));
|
|
|
26
26
|
var _jsonschema = require("jsonschema");
|
|
27
27
|
var _stringHash = _interopRequireDefault(require("string-hash"));
|
|
28
28
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
29
|
-
function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
|
|
30
|
-
function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
|
|
31
|
-
function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
|
|
32
|
-
function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
|
|
33
|
-
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
|
|
34
|
-
function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
|
|
35
|
-
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; } }
|
|
36
29
|
/**
|
|
37
30
|
* EntityManager config validation schema.
|
|
38
31
|
*
|
|
@@ -122,9 +115,10 @@ const keySchema = {
|
|
|
122
115
|
*
|
|
123
116
|
* @private
|
|
124
117
|
*/
|
|
125
|
-
var _config = /*#__PURE__*/new WeakMap();
|
|
126
|
-
var _logger = /*#__PURE__*/new WeakMap();
|
|
127
118
|
class PrivateEntityManager {
|
|
119
|
+
#config;
|
|
120
|
+
#logger;
|
|
121
|
+
|
|
128
122
|
/**
|
|
129
123
|
* Create a PrivateEntityManager instance.
|
|
130
124
|
*
|
|
@@ -139,17 +133,9 @@ class PrivateEntityManager {
|
|
|
139
133
|
config = {},
|
|
140
134
|
logger = console
|
|
141
135
|
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
142
|
-
_classPrivateFieldInitSpec(this, _config, {
|
|
143
|
-
writable: true,
|
|
144
|
-
value: void 0
|
|
145
|
-
});
|
|
146
|
-
_classPrivateFieldInitSpec(this, _logger, {
|
|
147
|
-
writable: true,
|
|
148
|
-
value: void 0
|
|
149
|
-
});
|
|
150
136
|
// Validate logger.
|
|
151
137
|
if (!logger.error || !logger.debug) throw new Error('logger must implement error & debug methods.');
|
|
152
|
-
|
|
138
|
+
this.#logger = logger;
|
|
153
139
|
this.config = config;
|
|
154
140
|
}
|
|
155
141
|
|
|
@@ -159,7 +145,7 @@ class PrivateEntityManager {
|
|
|
159
145
|
* @returns {object} Current config.
|
|
160
146
|
*/
|
|
161
147
|
get config() {
|
|
162
|
-
return
|
|
148
|
+
return this.#config;
|
|
163
149
|
}
|
|
164
150
|
|
|
165
151
|
/**
|
|
@@ -266,7 +252,7 @@ class PrivateEntityManager {
|
|
|
266
252
|
// Validate sharding timestamp is a function or undefined.
|
|
267
253
|
if (!(0, _isNil2.default)(timestamp) && !(0, _isFunction2.default)(timestamp)) throw new Error(`${entityToken} sharding timestamp must be a function or nil.`);else return false;
|
|
268
254
|
});
|
|
269
|
-
|
|
255
|
+
this.#config = conformedConfig;
|
|
270
256
|
}
|
|
271
257
|
|
|
272
258
|
/**
|
|
@@ -275,7 +261,7 @@ class PrivateEntityManager {
|
|
|
275
261
|
* @returns {object} Logger instance.
|
|
276
262
|
*/
|
|
277
263
|
get logger() {
|
|
278
|
-
return
|
|
264
|
+
return this.#logger;
|
|
279
265
|
}
|
|
280
266
|
|
|
281
267
|
/**
|
|
@@ -284,7 +270,7 @@ class PrivateEntityManager {
|
|
|
284
270
|
* @returns {string} Shard key token.
|
|
285
271
|
*/
|
|
286
272
|
get shardKeyToken() {
|
|
287
|
-
return
|
|
273
|
+
return this.#config.shardKeyToken;
|
|
288
274
|
}
|
|
289
275
|
|
|
290
276
|
/**
|
|
@@ -411,7 +397,7 @@ class PrivateEntityManager {
|
|
|
411
397
|
* Tests whether a timestamp is valid.
|
|
412
398
|
*
|
|
413
399
|
* @param {number} timestamp - timestamp.
|
|
414
|
-
* @param {boolean} [future
|
|
400
|
+
* @param {boolean} [future] - true if timestamp must be in the future.
|
|
415
401
|
* @returns {boolean} true if timestamp is valid.
|
|
416
402
|
*/
|
|
417
403
|
validateTimestamp(timestamp) {
|
|
@@ -34,7 +34,7 @@ export class EntityManager {
|
|
|
34
34
|
*
|
|
35
35
|
* @param {string} entityToken - Entity token.
|
|
36
36
|
* @param {object} item - Entity item.
|
|
37
|
-
* @param {boolean} [overwrite
|
|
37
|
+
* @param {boolean} [overwrite] - Overwrite existing properties.
|
|
38
38
|
* @returns {object} Decorated entity item.
|
|
39
39
|
* @throws {Error} If entityToken is invalid.
|
|
40
40
|
* @throws {Error} If item is invalid.
|
|
@@ -212,11 +212,12 @@ export class EntityManager {
|
|
|
212
212
|
*
|
|
213
213
|
* @param {object} options - Query options.
|
|
214
214
|
* @param {string} options.entityToken - Entity token.
|
|
215
|
-
* @param {string} [options.keyToken
|
|
216
|
-
* @param {object} [options.item
|
|
215
|
+
* @param {string} [options.keyToken] - Key token.
|
|
216
|
+
* @param {object} [options.item] - Entity item sufficiently populated to generate property keyToken.
|
|
217
217
|
* @param {ShardQueryFunction} options.shardQuery - Sharded query function.
|
|
218
218
|
* @param {number} [options.limit] - Request limit.
|
|
219
219
|
* @param {object} [options.pageKeys] - Map of shard page keys.
|
|
220
|
+
* @param {number|'all'} [options.pages] - Max number of pages to query.
|
|
220
221
|
* @returns {Promise<ShardedQueryResult>} Sharded query result.
|
|
221
222
|
*/
|
|
222
223
|
async query({
|
|
@@ -226,16 +227,23 @@ export class EntityManager {
|
|
|
226
227
|
shardQuery,
|
|
227
228
|
limit,
|
|
228
229
|
pageKeys,
|
|
230
|
+
pages = 1,
|
|
229
231
|
} = {}) {
|
|
230
232
|
// Validate params.
|
|
231
233
|
this.#entityManager.validateKeyToken(entityToken, keyToken);
|
|
232
234
|
this.#entityManager.validateItem(item);
|
|
233
235
|
if (!_.isFunction(shardQuery))
|
|
234
236
|
throw new Error('shardQuery must be a function');
|
|
235
|
-
if (!_.isUndefined(limit) && !_.isInteger(limit))
|
|
236
|
-
throw new Error('limit must be
|
|
237
|
+
if (!_.isUndefined(limit) && !(_.isInteger(limit) && limit >= 1))
|
|
238
|
+
throw new Error('limit must be a positive integer');
|
|
237
239
|
if (!_.isUndefined(pageKeys) && !_.isPlainObject(pageKeys))
|
|
238
240
|
throw new Error('pageKeys must be an object');
|
|
241
|
+
if (
|
|
242
|
+
!_.isUndefined(pageKeys) &&
|
|
243
|
+
!(_.isInteger(pages) && pages >= 1) &&
|
|
244
|
+
pages !== 'all'
|
|
245
|
+
)
|
|
246
|
+
throw new Error("pages must be a positive integer or 'all'");
|
|
239
247
|
|
|
240
248
|
// Generate default pageKeys if not provided
|
|
241
249
|
pageKeys ??= _.fromPairs(
|
|
@@ -253,32 +261,43 @@ export class EntityManager {
|
|
|
253
261
|
? undefined
|
|
254
262
|
: Math.ceil(limit / _.size(pageKeys));
|
|
255
263
|
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
// Iterate search over pages.
|
|
265
|
+
let page = 0;
|
|
266
|
+
const result = { count: 0, items: [], pageKeys };
|
|
267
|
+
|
|
268
|
+
do {
|
|
269
|
+
// Query every shard in pageKeys.
|
|
270
|
+
const shardQueryResults = await Promise.all(
|
|
271
|
+
_.map(
|
|
272
|
+
result.pageKeys,
|
|
273
|
+
(pageKey, shardedKey) =>
|
|
274
|
+
new Promise((resolve) =>
|
|
275
|
+
shardQuery(shardedKey, pageKey, shardLimit).then(
|
|
276
|
+
({ count, items, pageKey }) =>
|
|
277
|
+
resolve({ count, items, pageKey, shardedKey })
|
|
278
|
+
)
|
|
265
279
|
)
|
|
266
|
-
|
|
267
|
-
)
|
|
268
|
-
);
|
|
280
|
+
)
|
|
281
|
+
);
|
|
269
282
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
283
|
+
// Reduce shardQueryResults into a single result.
|
|
284
|
+
const pageResult = shardQueryResults.reduce(
|
|
285
|
+
(shardedQueryResult, { count, items, pageKey, shardedKey }) => ({
|
|
286
|
+
count: shardedQueryResult.count + count,
|
|
287
|
+
items: [...shardedQueryResult.items, ...items],
|
|
288
|
+
pageKeys: {
|
|
289
|
+
...shardedQueryResult.pageKeys,
|
|
290
|
+
...(_.isUndefined(pageKey) ? {} : { [shardedKey]: pageKey }),
|
|
291
|
+
},
|
|
292
|
+
}),
|
|
293
|
+
{ count: 0, items: [], pageKeys: {} }
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
result.count += pageResult.count;
|
|
297
|
+
result.items = [...result.items, ...pageResult.items];
|
|
298
|
+
result.pageKeys = pageResult.pageKeys;
|
|
299
|
+
page++;
|
|
300
|
+
} while ((pages === 'all' || page < pages) && !_.isEmpty(result.pageKeys));
|
|
282
301
|
|
|
283
302
|
return result;
|
|
284
303
|
}
|
|
@@ -306,11 +325,11 @@ export class EntityManager {
|
|
|
306
325
|
}, [])
|
|
307
326
|
);
|
|
308
327
|
|
|
309
|
-
const
|
|
328
|
+
const indexProperties = _.zipObject(indexKeys, value.split(delimiter));
|
|
310
329
|
|
|
311
330
|
return indexComponents.reduce((index, component) => {
|
|
312
331
|
const { encode } = this.getKey(entityToken, component);
|
|
313
|
-
return { ...index, [component]: encode(
|
|
332
|
+
return { ...index, [component]: encode(indexProperties) };
|
|
314
333
|
}, {});
|
|
315
334
|
}
|
|
316
335
|
|
|
@@ -408,7 +408,7 @@ export class PrivateEntityManager {
|
|
|
408
408
|
* Tests whether a timestamp is valid.
|
|
409
409
|
*
|
|
410
410
|
* @param {number} timestamp - timestamp.
|
|
411
|
-
* @param {boolean} [future
|
|
411
|
+
* @param {boolean} [future] - true if timestamp must be in the future.
|
|
412
412
|
* @returns {boolean} true if timestamp is valid.
|
|
413
413
|
*/
|
|
414
414
|
validateTimestamp(timestamp, future = false) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karmaniverous/entity-manager",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0-0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -26,27 +26,25 @@
|
|
|
26
26
|
"string-hash": "^1.1.3"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@babel/cli": "^7.
|
|
30
|
-
"@babel/core": "^7.
|
|
31
|
-
"@babel/eslint-parser": "^7.
|
|
32
|
-
"@babel/plugin-syntax-import-assertions": "^7.
|
|
33
|
-
"@babel/preset-env": "^7.
|
|
34
|
-
"@babel/register": "^7.
|
|
35
|
-
"@karmaniverous/edge-logger": "^1.
|
|
36
|
-
"@karmaniverous/
|
|
37
|
-
"@
|
|
38
|
-
"@types/node": "^18.11.18",
|
|
29
|
+
"@babel/cli": "^7.22.5",
|
|
30
|
+
"@babel/core": "^7.22.5",
|
|
31
|
+
"@babel/eslint-parser": "^7.22.5",
|
|
32
|
+
"@babel/plugin-syntax-import-assertions": "^7.22.5",
|
|
33
|
+
"@babel/preset-env": "^7.22.5",
|
|
34
|
+
"@babel/register": "^7.22.5",
|
|
35
|
+
"@karmaniverous/edge-logger": "^1.2.1",
|
|
36
|
+
"@karmaniverous/tagged-templates": "^0.1.1",
|
|
37
|
+
"@types/node": "^20.3.1",
|
|
39
38
|
"babel-plugin-lodash": "^3.3.4",
|
|
40
39
|
"chai": "^4.3.7",
|
|
41
|
-
"concat-md": "^0.5.
|
|
42
|
-
"eslint": "^8.
|
|
43
|
-
"eslint-
|
|
44
|
-
"eslint-plugin-jsdoc": "^39.7.4",
|
|
40
|
+
"concat-md": "^0.5.1",
|
|
41
|
+
"eslint": "^8.43.0",
|
|
42
|
+
"eslint-plugin-jsdoc": "^46.2.6",
|
|
45
43
|
"eslint-plugin-mocha": "^10.1.0",
|
|
44
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
46
45
|
"jsdoc-to-markdown": "^8.0.0",
|
|
47
46
|
"mocha": "^10.2.0",
|
|
48
|
-
"
|
|
49
|
-
"release-it": "^15.6.0"
|
|
47
|
+
"release-it": "^15.11.0"
|
|
50
48
|
},
|
|
51
49
|
"exports": {
|
|
52
50
|
".": {
|
|
@@ -80,8 +78,8 @@
|
|
|
80
78
|
"test": "mocha",
|
|
81
79
|
"build": "babel lib -d dist/default/lib --delete-dir-on-start --config-file ./dist/default/.babelrc",
|
|
82
80
|
"doc": "jsdoc2md -c doc/jsdoc.config.json -f lib/**/*.* -t doc/api-template.hbs > doc/2-api.jsdoc2.md && concat-md doc --hide-anchor-links > README.md",
|
|
83
|
-
"
|
|
84
|
-
"release": "
|
|
81
|
+
"prerelease": "npm run lint && npm run test && npm run build && npm run doc",
|
|
82
|
+
"release": "release-it"
|
|
85
83
|
},
|
|
86
84
|
"type": "module"
|
|
87
85
|
}
|