@karmaniverous/entity-manager 4.2.1 → 4.3.0-1

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.&lt;ShardedQueryResult&gt;</code> - Sharded query result.
366
366
 
367
- | Param | Type | Default | Description |
368
- | --- | --- | --- | --- |
369
- | options | <code>object</code> | | Query options. |
370
- | options.entityToken | <code>string</code> | | Entity token. |
371
- | [options.keyToken] | <code>string</code> | <code>&quot;entityPK&quot;</code> | Key token. |
372
- | [options.item] | <code>object</code> | <code>{}</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. |
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>&#x27;all&#x27;</code> | Max number of pages to query. |
376
377
 
377
378
  <a name="module_entity-manager.EntityManager+rehydrateIndex"></a>
378
379
 
@@ -9,6 +9,8 @@ var _map2 = _interopRequireDefault(require("lodash/map"));
9
9
  var _size2 = _interopRequireDefault(require("lodash/size"));
10
10
  var _isEmpty2 = _interopRequireDefault(require("lodash/isEmpty"));
11
11
  var _fromPairs2 = _interopRequireDefault(require("lodash/fromPairs"));
12
+ var _parseInt2 = _interopRequireDefault(require("lodash/parseInt"));
13
+ var _isString2 = _interopRequireDefault(require("lodash/isString"));
12
14
  var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
13
15
  var _isInteger2 = _interopRequireDefault(require("lodash/isInteger"));
14
16
  var _isUndefined2 = _interopRequireDefault(require("lodash/isUndefined"));
@@ -25,20 +27,18 @@ var _cloneDeep2 = _interopRequireDefault(require("lodash/cloneDeep"));
25
27
  var _inspectParametersDeclaration = require("inspect-parameters-declaration");
26
28
  var _PrivateEntityManager = require("./PrivateEntityManager.js");
27
29
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
28
- function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
29
- function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
30
- function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
31
- function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
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();
30
+ /**
31
+ * @module entity-manager
32
+ */
33
+
36
34
  /**
37
35
  * Manage DynamoDb entities.
38
36
  *
39
37
  * @class
40
38
  */
41
39
  class EntityManager {
40
+ #entityManager;
41
+
42
42
  /**
43
43
  * Create an EntityManager instance.
44
44
  *
@@ -54,14 +54,10 @@ class EntityManager {
54
54
  config,
55
55
  logger
56
56
  } = _ref;
57
- _classPrivateFieldInitSpec(this, _entityManager, {
58
- writable: true,
59
- value: void 0
60
- });
61
- _classPrivateFieldSet(this, _entityManager, new _PrivateEntityManager.PrivateEntityManager({
57
+ this.#entityManager = new _PrivateEntityManager.PrivateEntityManager({
62
58
  config,
63
59
  logger
64
- }));
60
+ });
65
61
  }
66
62
 
67
63
  /**
@@ -69,17 +65,17 @@ class EntityManager {
69
65
  *
70
66
  * @param {string} entityToken - Entity token.
71
67
  * @param {object} item - Entity item.
72
- * @param {boolean} [overwrite=false] - Overwrite existing properties.
68
+ * @param {boolean} [overwrite] - Overwrite existing properties.
73
69
  * @returns {object} Decorated entity item.
74
70
  * @throws {Error} If entityToken is invalid.
75
71
  * @throws {Error} If item is invalid.
76
72
  */
77
73
  addKeys(entityToken, item) {
78
74
  let overwrite = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
79
- _classPrivateFieldGet(this, _entityManager).logger.debug(`adding sharded index keys to ${entityToken}${overwrite ? ' with overwrite' : ''}...`, item);
75
+ this.#entityManager.logger.debug(`adding sharded index keys to ${entityToken}${overwrite ? ' with overwrite' : ''}...`, item);
80
76
 
81
77
  // Validate item.
82
- _classPrivateFieldGet(this, _entityManager).validateItem(item);
78
+ this.#entityManager.validateItem(item);
83
79
 
84
80
  // Clone item.
85
81
  const newItem = (0, _cloneDeep2.default)(item);
@@ -87,10 +83,10 @@ class EntityManager {
87
83
  // Get entity config.
88
84
  const {
89
85
  shardKeyToken
90
- } = _classPrivateFieldGet(this, _entityManager);
86
+ } = this.#entityManager;
91
87
  const {
92
88
  keys
93
- } = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
89
+ } = this.#entityManager.getEntityConfig(entityToken);
94
90
 
95
91
  // Add shardKey.
96
92
  if (overwrite || (0, _isNil2.default)(newItem[shardKeyToken])) {
@@ -104,7 +100,7 @@ class EntityManager {
104
100
  } = _ref2;
105
101
  if (overwrite || (0, _isNil2.default)(newItem[key])) newItem[key] = encode(newItem);
106
102
  });
107
- _classPrivateFieldGet(this, _entityManager).logger.debug('done', newItem);
103
+ this.#entityManager.logger.debug('done', newItem);
108
104
 
109
105
  // Remove undefined shardKey.
110
106
  if ((0, _isNil2.default)(newItem[shardKeyToken])) delete newItem[shardKeyToken];
@@ -121,10 +117,10 @@ class EntityManager {
121
117
  calcShardKey(entityToken, item) {
122
118
  const {
123
119
  sharding
124
- } = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
120
+ } = this.#entityManager.getEntityConfig(entityToken);
125
121
  const entityKey = sharding.entityKey(item);
126
122
  const timestamp = sharding.timestamp(item);
127
- return _classPrivateFieldGet(this, _entityManager).getShardKey(entityToken, entityKey, timestamp);
123
+ return this.#entityManager.getShardKey(entityToken, entityKey, timestamp);
128
124
  }
129
125
 
130
126
  /**
@@ -142,8 +138,8 @@ class EntityManager {
142
138
  dehydrateIndex(entityToken, indexToken, index) {
143
139
  let delimiter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '~';
144
140
  // Validate item.
145
- _classPrivateFieldGet(this, _entityManager).validateItem(index);
146
- const indexComponents = (0, _isArray2.default)(indexToken) ? indexToken : _classPrivateFieldGet(this, _entityManager).getIndexComponents(entityToken, indexToken);
141
+ this.#entityManager.validateItem(index);
142
+ const indexComponents = (0, _isArray2.default)(indexToken) ? indexToken : this.#entityManager.getIndexComponents(entityToken, indexToken);
147
143
  const indexProperties = indexComponents.reduce((properties, component) => {
148
144
  const {
149
145
  decode
@@ -176,7 +172,7 @@ class EntityManager {
176
172
  getKey(entityToken, keyToken) {
177
173
  const {
178
174
  keys
179
- } = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
175
+ } = this.#entityManager.getEntityConfig(entityToken);
180
176
  if (!(0, _has2.default)(keys, keyToken)) throw new Error(`Key '${keyToken}' does not exist for entity '${entityToken}'.`);
181
177
  return keys[keyToken];
182
178
  }
@@ -196,13 +192,13 @@ class EntityManager {
196
192
  */
197
193
  getKeySpace(entityToken, keyToken, item) {
198
194
  let timestamp = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Date.now();
199
- _classPrivateFieldGet(this, _entityManager).logger.debug(`getting shard key space for ${entityToken} on key '${keyToken}' at timestamp ${timestamp}...`, item);
200
- const shardKeySpace = _classPrivateFieldGet(this, _entityManager).getShardKeySpace(entityToken, timestamp);
195
+ this.#entityManager.logger.debug(`getting shard key space for ${entityToken} on key '${keyToken}' at timestamp ${timestamp}...`, item);
196
+ const shardKeySpace = this.#entityManager.getShardKeySpace(entityToken, timestamp);
201
197
  const result = (0, _sortedUniq2.default)(shardKeySpace.map(shardKey => this.getKey(entityToken, keyToken).encode({
202
198
  ...item,
203
- [_classPrivateFieldGet(this, _entityManager).shardKeyToken]: shardKey
199
+ [this.#entityManager.shardKeyToken]: shardKey
204
200
  })));
205
- _classPrivateFieldGet(this, _entityManager).logger.debug('done', result);
201
+ this.#entityManager.logger.debug('done', result);
206
202
  return result;
207
203
  }
208
204
 
@@ -233,11 +229,12 @@ class EntityManager {
233
229
  *
234
230
  * @param {object} options - Query options.
235
231
  * @param {string} options.entityToken - Entity token.
236
- * @param {string} [options.keyToken=entityPK] - Key token.
237
- * @param {object} [options.item={}] - Entity item sufficiently populated to generate property keyToken.
232
+ * @param {string} [options.keyToken] - Key token.
233
+ * @param {object} [options.item] - Entity item sufficiently populated to generate property keyToken.
238
234
  * @param {ShardQueryFunction} options.shardQuery - Sharded query function.
239
235
  * @param {number} [options.limit] - Request limit.
240
236
  * @param {object} [options.pageKeys] - Map of shard page keys.
237
+ * @param {number|'all'} [options.pages] - Max number of pages to query.
241
238
  * @returns {Promise<ShardedQueryResult>} Sharded query result.
242
239
  */
243
240
  async query() {
@@ -247,14 +244,19 @@ class EntityManager {
247
244
  item = {},
248
245
  shardQuery,
249
246
  limit,
250
- pageKeys
247
+ pageKeys,
248
+ pages = 1
251
249
  } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
252
250
  // Validate params.
253
- _classPrivateFieldGet(this, _entityManager).validateKeyToken(entityToken, keyToken);
254
- _classPrivateFieldGet(this, _entityManager).validateItem(item);
251
+ this.#entityManager.validateKeyToken(entityToken, keyToken);
252
+ this.#entityManager.validateItem(item);
255
253
  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 an integer');
254
+ if (!(0, _isUndefined2.default)(limit) && !((0, _isInteger2.default)(limit) && limit >= 1)) throw new Error('limit must be a positive integer');
257
255
  if (!(0, _isUndefined2.default)(pageKeys) && !(0, _isPlainObject2.default)(pageKeys)) throw new Error('pageKeys must be an object');
256
+ if (!(0, _isUndefined2.default)(pages) && !/^(?:\d+|all)$/i.test(pages.toString())) throw new Error("pages must be a positive integer or 'all'");
257
+
258
+ // Parse pages.
259
+ if ((0, _isString2.default)(pages)) pages = pages.toLowerCase() === 'all' ? Infinity : (0, _parseInt2.default)(pages);
258
260
 
259
261
  // Generate default pageKeys if not provided
260
262
  pageKeys ??= (0, _fromPairs2.default)(this.getKeySpace(entityToken, keyToken, item).map(shardedKey => [shardedKey, undefined]));
@@ -268,44 +270,57 @@ class EntityManager {
268
270
  // Calculate shard limit.
269
271
  const shardLimit = (0, _isUndefined2.default)(limit) ? undefined : Math.ceil(limit / (0, _size2.default)(pageKeys));
270
272
 
271
- // Query every shard in pageKeys.
272
- const shardQueryResults = await Promise.all((0, _map2.default)(pageKeys, (pageKey, shardedKey) => new Promise(resolve => shardQuery(shardedKey, pageKey, shardLimit).then(_ref6 => {
273
- let {
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
- }, {
273
+ // Iterate search over pages.
274
+ let page = 0;
275
+ const result = {
305
276
  count: 0,
306
277
  items: [],
307
- pageKeys: {}
308
- });
278
+ pageKeys
279
+ };
280
+ do {
281
+ // Query every shard in pageKeys.
282
+ const shardQueryResults = await Promise.all((0, _map2.default)(result.pageKeys, (pageKey, shardedKey) => new Promise(resolve => shardQuery(shardedKey, pageKey, shardLimit).then(_ref6 => {
283
+ let {
284
+ count,
285
+ items,
286
+ pageKey
287
+ } = _ref6;
288
+ return resolve({
289
+ count,
290
+ items,
291
+ pageKey,
292
+ shardedKey
293
+ });
294
+ }))));
295
+
296
+ // Reduce shardQueryResults into a single result.
297
+ const pageResult = shardQueryResults.reduce((shardedQueryResult, _ref7) => {
298
+ let {
299
+ count,
300
+ items,
301
+ pageKey,
302
+ shardedKey
303
+ } = _ref7;
304
+ return {
305
+ count: shardedQueryResult.count + count,
306
+ items: [...shardedQueryResult.items, ...items],
307
+ pageKeys: {
308
+ ...shardedQueryResult.pageKeys,
309
+ ...((0, _isUndefined2.default)(pageKey) ? {} : {
310
+ [shardedKey]: pageKey
311
+ })
312
+ }
313
+ };
314
+ }, {
315
+ count: 0,
316
+ items: [],
317
+ pageKeys: {}
318
+ });
319
+ result.count += pageResult.count;
320
+ result.items = [...result.items, ...pageResult.items];
321
+ result.pageKeys = pageResult.pageKeys;
322
+ page++;
323
+ } while (page < pages && !(0, _isEmpty2.default)(result.pageKeys));
309
324
  return result;
310
325
  }
311
326
 
@@ -323,7 +338,7 @@ class EntityManager {
323
338
  rehydrateIndex(entityToken, indexToken) {
324
339
  let value = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
325
340
  let delimiter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '~';
326
- const indexComponents = (0, _isArray2.default)(indexToken) ? indexToken : _classPrivateFieldGet(this, _entityManager).getIndexComponents(entityToken, indexToken);
341
+ const indexComponents = (0, _isArray2.default)(indexToken) ? indexToken : this.#entityManager.getIndexComponents(entityToken, indexToken);
327
342
  const indexKeys = (0, _sortBy2.default)(indexComponents.reduce((keys, component) => {
328
343
  const {
329
344
  encode
@@ -352,10 +367,10 @@ class EntityManager {
352
367
  * @throws {Error} If item is invalid.
353
368
  */
354
369
  removeKeys(entityToken, item) {
355
- _classPrivateFieldGet(this, _entityManager).logger.debug(`removing sharded index keys from ${entityToken}...`, item);
370
+ this.#entityManager.logger.debug(`removing sharded index keys from ${entityToken}...`, item);
356
371
 
357
372
  // Validate item.
358
- _classPrivateFieldGet(this, _entityManager).validateItem(item);
373
+ this.#entityManager.validateItem(item);
359
374
 
360
375
  // Clone item.
361
376
  const newItem = (0, _cloneDeep2.default)(item);
@@ -363,10 +378,10 @@ class EntityManager {
363
378
  // Get entity config.
364
379
  const {
365
380
  shardKeyToken
366
- } = _classPrivateFieldGet(this, _entityManager);
381
+ } = this.#entityManager;
367
382
  const {
368
383
  keys
369
- } = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
384
+ } = this.#entityManager.getEntityConfig(entityToken);
370
385
 
371
386
  // Remove shardKey.
372
387
  delete newItem[shardKeyToken];
@@ -375,7 +390,7 @@ class EntityManager {
375
390
  (0, _forEach2.default)(keys, (value, key) => {
376
391
  if (!value.retain) delete newItem[key];
377
392
  });
378
- _classPrivateFieldGet(this, _entityManager).logger.debug('done', newItem);
393
+ this.#entityManager.logger.debug('done', newItem);
379
394
  return newItem;
380
395
  }
381
396
  }
@@ -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
- _classPrivateFieldSet(this, _logger, logger);
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 _classPrivateFieldGet(this, _config);
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
- _classPrivateFieldSet(this, _config, conformedConfig);
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 _classPrivateFieldGet(this, _logger);
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 _classPrivateFieldGet(this, _config).shardKeyToken;
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=false] - true if timestamp must be in the 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=false] - Overwrite existing properties.
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=entityPK] - Key token.
216
- * @param {object} [options.item={}] - Entity item sufficiently populated to generate property keyToken.
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 an integer');
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 (!_.isUndefined(pages) && !/^(?:\d+|all)$/i.test(pages.toString()))
242
+ throw new Error("pages must be a positive integer or 'all'");
243
+
244
+ // Parse pages.
245
+ if (_.isString(pages))
246
+ pages = pages.toLowerCase() === 'all' ? Infinity : _.parseInt(pages);
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
- // Query every shard in pageKeys.
257
- const shardQueryResults = await Promise.all(
258
- _.map(
259
- pageKeys,
260
- (pageKey, shardedKey) =>
261
- new Promise((resolve) =>
262
- shardQuery(shardedKey, pageKey, shardLimit).then(
263
- ({ count, items, pageKey }) =>
264
- resolve({ count, items, pageKey, shardedKey })
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
- // Reduce shardQueryResults into a single result.
271
- const result = shardQueryResults.reduce(
272
- (shardedQueryResult, { count, items, pageKey, shardedKey }) => ({
273
- count: shardedQueryResult.count + count,
274
- items: [...shardedQueryResult.items, ...items],
275
- pageKeys: {
276
- ...shardedQueryResult.pageKeys,
277
- ...(_.isUndefined(pageKey) ? {} : { [shardedKey]: pageKey }),
278
- },
279
- }),
280
- { count: 0, items: [], pageKeys: {} }
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 (page < pages && !_.isEmpty(result.pageKeys));
282
301
 
283
302
  return result;
284
303
  }
@@ -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=false] - true if timestamp must be in the 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.2.1",
3
+ "version": "4.3.0-1",
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.20.7",
30
- "@babel/core": "^7.20.7",
31
- "@babel/eslint-parser": "^7.19.1",
32
- "@babel/plugin-syntax-import-assertions": "^7.20.0",
33
- "@babel/preset-env": "^7.20.2",
34
- "@babel/register": "^7.18.9",
35
- "@karmaniverous/edge-logger": "^1.0.2",
36
- "@karmaniverous/get-dotenv": "^0.1.0",
37
- "@karmaniverous/tagged-templates": "^0.0.4",
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.0",
42
- "eslint": "^8.30.0",
43
- "eslint-config-standard": "^17.0.0",
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
- "prettier": "^2.8.1",
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
- "package": "npm run lint && npm run test && npm run build && npm run doc",
84
- "release": "npm run package && getdotenv -- release-it"
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
  }