@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.&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
 
@@ -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
- 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();
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
- _classPrivateFieldInitSpec(this, _entityManager, {
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=false] - Overwrite existing properties.
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
- _classPrivateFieldGet(this, _entityManager).logger.debug(`adding sharded index keys to ${entityToken}${overwrite ? ' with overwrite' : ''}...`, item);
73
+ this.#entityManager.logger.debug(`adding sharded index keys to ${entityToken}${overwrite ? ' with overwrite' : ''}...`, item);
80
74
 
81
75
  // Validate item.
82
- _classPrivateFieldGet(this, _entityManager).validateItem(item);
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
- } = _classPrivateFieldGet(this, _entityManager);
84
+ } = this.#entityManager;
91
85
  const {
92
86
  keys
93
- } = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
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
- _classPrivateFieldGet(this, _entityManager).logger.debug('done', newItem);
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
- } = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
118
+ } = this.#entityManager.getEntityConfig(entityToken);
125
119
  const entityKey = sharding.entityKey(item);
126
120
  const timestamp = sharding.timestamp(item);
127
- return _classPrivateFieldGet(this, _entityManager).getShardKey(entityToken, entityKey, timestamp);
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
- _classPrivateFieldGet(this, _entityManager).validateItem(index);
146
- const indexComponents = (0, _isArray2.default)(indexToken) ? indexToken : _classPrivateFieldGet(this, _entityManager).getIndexComponents(entityToken, 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
- } = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
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
- _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);
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
- [_classPrivateFieldGet(this, _entityManager).shardKeyToken]: shardKey
197
+ [this.#entityManager.shardKeyToken]: shardKey
204
198
  })));
205
- _classPrivateFieldGet(this, _entityManager).logger.debug('done', result);
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=entityPK] - Key token.
237
- * @param {object} [options.item={}] - Entity item sufficiently populated to generate property keyToken.
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
- _classPrivateFieldGet(this, _entityManager).validateKeyToken(entityToken, keyToken);
254
- _classPrivateFieldGet(this, _entityManager).validateItem(item);
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 an integer');
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
- // 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
- }, {
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 : _classPrivateFieldGet(this, _entityManager).getIndexComponents(entityToken, 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 indexProperies = (0, _zipObject2.default)(indexKeys, value.split(delimiter));
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(indexProperies)
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
- _classPrivateFieldGet(this, _entityManager).logger.debug(`removing sharded index keys from ${entityToken}...`, item);
365
+ this.#entityManager.logger.debug(`removing sharded index keys from ${entityToken}...`, item);
356
366
 
357
367
  // Validate item.
358
- _classPrivateFieldGet(this, _entityManager).validateItem(item);
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
- } = _classPrivateFieldGet(this, _entityManager);
376
+ } = this.#entityManager;
367
377
  const {
368
378
  keys
369
- } = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
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
- _classPrivateFieldGet(this, _entityManager).logger.debug('done', newItem);
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
- _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 (
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
- // 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 ((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 indexProperies = _.zipObject(indexKeys, value.split(delimiter));
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(indexProperies) };
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=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.0",
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.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
  }