@karmaniverous/entity-manager 2.1.0 → 2.1.2

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
@@ -221,10 +221,10 @@ This change can be accomplished with no breaking changes to existing implementat
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
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>
224
+ * [.dehydrateIndex(entityToken, indexToken, index, [delimiter])](#module_entity-manager.EntityManager+dehydrateIndex) ⇒ <code>string</code>
226
225
  * [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.&lt;string&gt;</code>
227
226
  * [.query(entityToken, keyToken, item, shardQuery, [options])](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.&lt;ShardedQueryResult&gt;</code>
227
+ * [.rehydrateIndex(entityToken, indexToken, value, [delimiter])](#module_entity-manager.EntityManager+rehydrateIndex) ⇒ <code>object</code>
228
228
  * [.removeKeys(entityToken, item)](#module_entity-manager.EntityManager+removeKeys) ⇒ <code>object</code>
229
229
  * _inner_
230
230
  * [~ShardQueryResult](#module_entity-manager..ShardQueryResult) : <code>object</code>
@@ -242,10 +242,10 @@ Manage DynamoDb entities.
242
242
  * [new exports.EntityManager(options)](#new_module_entity-manager.EntityManager_new)
243
243
  * [.addKeys(entityToken, item, [overwrite])](#module_entity-manager.EntityManager+addKeys) ⇒ <code>object</code>
244
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>
245
+ * [.dehydrateIndex(entityToken, indexToken, index, [delimiter])](#module_entity-manager.EntityManager+dehydrateIndex) ⇒ <code>string</code>
247
246
  * [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.&lt;string&gt;</code>
248
247
  * [.query(entityToken, keyToken, item, shardQuery, [options])](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.&lt;ShardedQueryResult&gt;</code>
248
+ * [.rehydrateIndex(entityToken, indexToken, value, [delimiter])](#module_entity-manager.EntityManager+rehydrateIndex) ⇒ <code>object</code>
249
249
  * [.removeKeys(entityToken, item)](#module_entity-manager.EntityManager+removeKeys) ⇒ <code>object</code>
250
250
 
251
251
  <a name="new_module_entity-manager.EntityManager_new"></a>
@@ -300,8 +300,8 @@ Calculated the shard key for an entity item.
300
300
 
301
301
  <a name="module_entity-manager.EntityManager+dehydrateIndex"></a>
302
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.
303
+ #### entityManager.dehydrateIndex(entityToken, indexToken, index, [delimiter]) ⇒ <code>string</code>
304
+ Condense an index object into a delimited string.
305
305
 
306
306
  **Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
307
307
  **Returns**: <code>string</code> - Dehydrated index.
@@ -316,27 +316,7 @@ Convert a item into a delimited string containing the properties of a named inde
316
316
  | --- | --- | --- | --- |
317
317
  | entityToken | <code>string</code> | | Entity token. |
318
318
  | indexToken | <code>string</code> | | Index token. |
319
- | item | <code>object</code> | | Entity item. |
320
- | [delimiter] | <code>string</code> | <code>&quot;~&quot;</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. |
319
+ | index | <code>object</code> | | Index object. |
340
320
  | [delimiter] | <code>string</code> | <code>&quot;~&quot;</code> | Delimiter. |
341
321
 
342
322
  <a name="module_entity-manager.EntityManager+getKeySpace"></a>
@@ -379,6 +359,26 @@ Query an entity across shards.
379
359
  | [options.limit] | <code>number</code> | Request limit. |
380
360
  | [options.pageKeys] | <code>object</code> | Map of shard page keys. |
381
361
 
362
+ <a name="module_entity-manager.EntityManager+rehydrateIndex"></a>
363
+
364
+ #### entityManager.rehydrateIndex(entityToken, indexToken, value, [delimiter]) ⇒ <code>object</code>
365
+ Convert a delimited string into a named index key.
366
+
367
+ **Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
368
+ **Returns**: <code>object</code> - Rehydrated index key.
369
+ **Throws**:
370
+
371
+ - <code>Error</code> If entityToken is invalid.
372
+ - <code>Error</code> If indexToken is invalid.
373
+
374
+
375
+ | Param | Type | Default | Description |
376
+ | --- | --- | --- | --- |
377
+ | entityToken | <code>string</code> | | Entity token. |
378
+ | indexToken | <code>string</code> | | Index token. |
379
+ | value | <code>string</code> | | Dehydrated index value. |
380
+ | [delimiter] | <code>string</code> | <code>&quot;~&quot;</code> | Delimiter. |
381
+
382
382
  <a name="module_entity-manager.EntityManager+removeKeys"></a>
383
383
 
384
384
  #### entityManager.removeKeys(entityToken, item) ⇒ <code>object</code>
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.EntityManager = void 0;
7
+ var _zipObject2 = _interopRequireDefault(require("lodash/zipObject"));
7
8
  var _map2 = _interopRequireDefault(require("lodash/map"));
8
9
  var _size2 = _interopRequireDefault(require("lodash/size"));
9
10
  var _isEmpty2 = _interopRequireDefault(require("lodash/isEmpty"));
@@ -11,14 +12,11 @@ var _fromPairs2 = _interopRequireDefault(require("lodash/fromPairs"));
11
12
  var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
12
13
  var _isInteger2 = _interopRequireDefault(require("lodash/isInteger"));
13
14
  var _isUndefined2 = _interopRequireDefault(require("lodash/isUndefined"));
14
- var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
15
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"));
16
+ var _entries2 = _interopRequireDefault(require("lodash/entries"));
19
17
  var _sortBy2 = _interopRequireDefault(require("lodash/sortBy"));
20
- var _pick2 = _interopRequireDefault(require("lodash/pick"));
21
- var _values2 = _interopRequireDefault(require("lodash/values"));
18
+ var _sortedUniqBy2 = _interopRequireDefault(require("lodash/sortedUniqBy"));
19
+ var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
22
20
  var _forEach2 = _interopRequireDefault(require("lodash/forEach"));
23
21
  var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
24
22
  var _cloneDeep2 = _interopRequireDefault(require("lodash/cloneDeep"));
@@ -33,7 +31,6 @@ function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _
33
31
  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
32
  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
33
  var _entityManager = /*#__PURE__*/new WeakMap();
36
- var _getIndexProperties = /*#__PURE__*/new WeakMap();
37
34
  /**
38
35
  * Manage DynamoDb entities.
39
36
  *
@@ -59,10 +56,6 @@ class EntityManager {
59
56
  writable: true,
60
57
  value: void 0
61
58
  });
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
- });
66
59
  _classPrivateFieldSet(this, _entityManager, new _PrivateEntityManager.PrivateEntityManager({
67
60
  config,
68
61
  logger
@@ -103,8 +96,11 @@ class EntityManager {
103
96
  }
104
97
 
105
98
  // Add keys.
106
- (0, _forEach2.default)(keys, (getValue, key) => {
107
- if (overwrite || (0, _isNil2.default)(newItem[key])) newItem[key] = getValue(newItem);
99
+ (0, _forEach2.default)(keys, (_ref2, key) => {
100
+ let {
101
+ encode
102
+ } = _ref2;
103
+ if (overwrite || (0, _isNil2.default)(newItem[key])) newItem[key] = encode(newItem);
108
104
  });
109
105
  _classPrivateFieldGet(this, _entityManager).logger.debug('done', newItem);
110
106
 
@@ -130,52 +126,42 @@ class EntityManager {
130
126
  }
131
127
 
132
128
  /**
133
- * Convert a item into a delimited string containing the properties of a named index.
129
+ * Condense an index object into a delimited string.
134
130
  *
135
131
  * @param {string} entityToken - Entity token.
136
132
  * @param {string} indexToken - Index token.
137
- * @param {object} item - Entity item.
133
+ * @param {object} index - Index object.
138
134
  * @param {string} [delimiter] - Delimiter.
139
135
  * @returns {string} Dehydrated index.
140
136
  * @throws {Error} If entityToken is invalid.
141
137
  * @throws {Error} If indexToken is invalid.
142
138
  * @throws {Error} If item is invalid.
143
139
  */
144
- dehydrateIndex(entityToken, indexToken, item) {
140
+ dehydrateIndex(entityToken, indexToken, index) {
145
141
  let delimiter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '~';
146
142
  // Validate item.
147
- _classPrivateFieldGet(this, _entityManager).validateItem(item);
143
+ _classPrivateFieldGet(this, _entityManager).validateItem(index);
148
144
  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
- }), {});
145
+ const indexProperties = indexComponents.reduce((properties, component) => {
146
+ const {
147
+ decode
148
+ } = _classPrivateFieldGet(this, _entityManager).getKey(entityToken, component);
149
+ if (!(0, _isFunction2.default)(decode)) throw new Error(`no decode function defined on ${entityToken} entity key '${component}'`);
150
+ return {
151
+ ...properties,
152
+ ...(decode(index[component])?.groups ?? [])
153
+ };
154
+ }, {});
155
+ return (0, _sortedUniqBy2.default)((0, _sortBy2.default)((0, _entries2.default)(indexProperties), _ref3 => {
156
+ let [key] = _ref3;
157
+ return key;
158
+ }), _ref4 => {
159
+ let [key] = _ref4;
160
+ return key;
161
+ }).map(_ref5 => {
162
+ let [, value] = _ref5;
163
+ return value;
164
+ }).join(delimiter);
179
165
  }
180
166
 
181
167
  /**
@@ -195,7 +181,7 @@ class EntityManager {
195
181
  let timestamp = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Date.now();
196
182
  _classPrivateFieldGet(this, _entityManager).logger.debug(`getting shard key space for ${entityToken} on key '${keyToken}' at timestamp ${timestamp}...`, item);
197
183
  const shardKeySpace = _classPrivateFieldGet(this, _entityManager).getShardKeySpace(entityToken, timestamp);
198
- const result = (0, _sortedUniq2.default)(shardKeySpace.map(shardKey => _classPrivateFieldGet(this, _entityManager).getKeyGenerator(entityToken, keyToken)({
184
+ const result = (0, _sortedUniq2.default)(shardKeySpace.map(shardKey => _classPrivateFieldGet(this, _entityManager).getKey(entityToken, keyToken).encode({
199
185
  ...item,
200
186
  [_classPrivateFieldGet(this, _entityManager).shardKeyToken]: shardKey
201
187
  })));
@@ -266,11 +252,11 @@ class EntityManager {
266
252
  const shardQueryResults = await Promise.all((0, _map2.default)(pageKeys, (pageKey, shardedKey) => new Promise(resolve => shardQuery(shardedKey, {
267
253
  pageKey,
268
254
  limit: shardLimit
269
- }).then(_ref2 => {
255
+ }).then(_ref6 => {
270
256
  let {
271
257
  items,
272
258
  pageKey
273
- } = _ref2;
259
+ } = _ref6;
274
260
  return resolve({
275
261
  shardedKey,
276
262
  items,
@@ -279,12 +265,12 @@ class EntityManager {
279
265
  }))));
280
266
 
281
267
  // Reduce shardQueryResults into a single result.
282
- const result = shardQueryResults.reduce((shardedQueryResult, _ref3) => {
268
+ const result = shardQueryResults.reduce((shardedQueryResult, _ref7) => {
283
269
  let {
284
270
  shardedKey,
285
271
  items,
286
272
  pageKey
287
- } = _ref3;
273
+ } = _ref7;
288
274
  return {
289
275
  items: [...shardedQueryResult.items, ...items],
290
276
  pageKeys: {
@@ -301,6 +287,39 @@ class EntityManager {
301
287
  return result;
302
288
  }
303
289
 
290
+ /**
291
+ * Convert a delimited string into a named index key.
292
+ *
293
+ * @param {string} entityToken - Entity token.
294
+ * @param {string} indexToken - Index token.
295
+ * @param {string} value - Dehydrated index value.
296
+ * @param {string} [delimiter] - Delimiter.
297
+ * @returns {object} Rehydrated index key.
298
+ * @throws {Error} If entityToken is invalid.
299
+ * @throws {Error} If indexToken is invalid.
300
+ */
301
+ rehydrateIndex(entityToken, indexToken) {
302
+ let value = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
303
+ let delimiter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '~';
304
+ const indexComponents = _classPrivateFieldGet(this, _entityManager).getIndexComponents(entityToken, indexToken);
305
+ const indexKeys = (0, _sortBy2.default)(indexComponents.reduce((keys, component) => {
306
+ const {
307
+ encode
308
+ } = _classPrivateFieldGet(this, _entityManager).getKey(entityToken, component);
309
+ return [...new Set([...keys, ...(0, _inspectParametersDeclaration.getParametersNames)(encode)]).values()];
310
+ }, []));
311
+ const indexProperies = (0, _zipObject2.default)(indexKeys, value.split(delimiter));
312
+ return indexComponents.reduce((index, component) => {
313
+ const {
314
+ encode
315
+ } = _classPrivateFieldGet(this, _entityManager).getKey(entityToken, component);
316
+ return {
317
+ ...index,
318
+ [component]: encode(indexProperies)
319
+ };
320
+ }, {});
321
+ }
322
+
304
323
  /**
305
324
  * Remove sharded keys from an entity item. Does not mutate original item.
306
325
  *
@@ -12,8 +12,10 @@ var _filter2 = _interopRequireDefault(require("lodash/filter"));
12
12
  var _findLast2 = _interopRequireDefault(require("lodash/findLast"));
13
13
  var _has2 = _interopRequireDefault(require("lodash/has"));
14
14
  var _map2 = _interopRequireDefault(require("lodash/map"));
15
- var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
16
15
  var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
16
+ var _isUndefined2 = _interopRequireDefault(require("lodash/isUndefined"));
17
+ var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
18
+ var _forEach2 = _interopRequireDefault(require("lodash/forEach"));
17
19
  var _keys2 = _interopRequireDefault(require("lodash/keys"));
18
20
  var _difference2 = _interopRequireDefault(require("lodash/difference"));
19
21
  var _some2 = _interopRequireDefault(require("lodash/some"));
@@ -102,6 +104,15 @@ const configSchema = {
102
104
  },
103
105
  additionalProperties: false
104
106
  };
107
+ const keySchema = {
108
+ type: 'object',
109
+ properties: {
110
+ encode: {},
111
+ decode: {}
112
+ },
113
+ additionalProperties: false,
114
+ required: ['encode']
115
+ };
105
116
 
106
117
  /**
107
118
  * Private EntityManager implementation.
@@ -159,10 +170,7 @@ class PrivateEntityManager {
159
170
  set config(value) {
160
171
  // Validate config against schema.
161
172
  const validatorResult = (0, _jsonschema.validate)(value, configSchema);
162
- if (!validatorResult.valid) {
163
- validatorResult.errors.forEach(error => this.logger.error(error.message));
164
- throw new Error(validatorResult.errors);
165
- }
173
+ if (!validatorResult.valid) throw new Error(validatorResult.errors);
166
174
 
167
175
  // Conform config.
168
176
  const {
@@ -197,7 +205,7 @@ class PrivateEntityManager {
197
205
  shardKeyToken
198
206
  };
199
207
 
200
- // Validate entity properties.
208
+ // Conform & validate entity properties.
201
209
  (0, _some2.default)(conformedConfig.entities, (_ref2, entityToken) => {
202
210
  let {
203
211
  indexes,
@@ -213,69 +221,47 @@ class PrivateEntityManager {
213
221
  // Validate entity index components are entity keys.
214
222
  (0, _some2.default)(indexes, (index, indexToken) => {
215
223
  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;
224
+ if (invalidIndexComponents.length) throw new Error(`${entityToken} index '${indexToken}' components '${invalidIndexComponents}' are not entity keys.`);else return false;
221
225
  });
222
226
 
223
- // Validate entity keys are functions or undefined.
224
- (0, _some2.default)(keys, (value, key) => {
225
- if (!(0, _isNil2.default)(value) && !(0, _isFunction2.default)(value)) {
226
- const message = `${entityToken} key '${key}' must be a function or nil.`;
227
- this.logger.error(message);
228
- throw new Error(message);
229
- } else return false;
227
+ // Conform & validate entity keys.
228
+ (0, _forEach2.default)(keys, (value, key) => {
229
+ keys[key] = (0, _isFunction2.default)(value) ? {
230
+ encode: value
231
+ } : value;
232
+
233
+ // Validate key against schema.
234
+ const validatorResult = (0, _jsonschema.validate)(keys[key], keySchema);
235
+ if (!validatorResult.valid) throw new Error(validatorResult.errors);
236
+ const {
237
+ decode
238
+ } = keys[key];
239
+ if (!(0, _isUndefined2.default)(decode) && !(0, _isFunction2.default)(decode)) throw new Error(`entity ${entityKey} key '${key}' decode must be a function or undefined.`);
230
240
  });
231
241
 
232
242
  // Validate sharding bump values increase monotonically with keys.
233
243
  (0, _some2.default)((0, _toPairs2.default)(bumps), (_ref3, i, c) => {
234
244
  let [bump, value] = _ref3;
235
245
  const [lastBump, lastValue] = i ? c[i - 1] : [];
236
- if (value <= lastValue) {
237
- const message = `${entityToken} sharding bumps do not monotonically increase from '${lastBump}: ${lastValue}' to '${bump}: ${value}'.)`;
238
- this.logger.error(message);
239
- throw new Error(message);
240
- } else return false;
246
+ if (value <= lastValue) throw new Error(`${entityToken} sharding bumps do not monotonically increase from '${lastBump}: ${lastValue}' to '${bump}: ${value}'.)`);else return false;
241
247
  });
242
248
 
243
249
  // Validate sharding entityKey is a function or undefined.
244
- if (!(0, _isNil2.default)(entityKey) && !(0, _isFunction2.default)(entityKey)) {
245
- const message = `${entityToken} sharding entityKey must be a function or nil`;
246
- this.logger.error(message);
247
- throw new Error(message);
248
- }
250
+ if (!(0, _isNil2.default)(entityKey) && !(0, _isFunction2.default)(entityKey)) throw new Error(`${entityToken} sharding entityKey must be a function or nil`);
249
251
 
250
252
  // Validate sharding nibbles do not exceed 32 bits.
251
- if (nibbles * nibbleBits > 32) {
252
- const message = `${entityToken} nibbles (${nibbles} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`;
253
- this.logger.error(message);
254
- throw new Error(message);
255
- }
253
+ if (nibbles * nibbleBits > 32) throw new Error(`${entityToken} nibbles (${nibbles} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`);
256
254
 
257
255
  // Validate sharding nibbles are less than first bump value.
258
256
  const firstBumpValue = (0, _map2.default)(bumps)[0];
259
- if (nibbles >= firstBumpValue) {
260
- const message = `${entityToken} nibbles (${nibbles}) not less than minimum bump value (${firstBumpValue})`;
261
- this.logger.error(message);
262
- throw new Error(message);
263
- }
257
+ if (nibbles >= firstBumpValue) throw new Error(`${entityToken} nibbles (${nibbles}) not less than minimum bump value (${firstBumpValue})`);
264
258
 
265
259
  // Validate last bump value does not exceed 32 bits.
266
260
  const lastBumpValue = (0, _map2.default)(bumps).slice(-1);
267
- if (lastBumpValue * nibbleBits > 32) {
268
- const message = `${entityToken} maximum bump value (${lastBumpValue} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`;
269
- this.logger.error(message);
270
- throw new Error(message);
271
- }
261
+ if (lastBumpValue * nibbleBits > 32) throw new Error(`${entityToken} maximum bump value (${lastBumpValue} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`);
272
262
 
273
263
  // Validate sharding timestamp is a function or undefined.
274
- if (!(0, _isNil2.default)(timestamp) && !(0, _isFunction2.default)(timestamp)) {
275
- const message = `${entityToken} sharding timestamp must be a function or nil.`;
276
- this.logger.error(message);
277
- throw new Error(message);
278
- } else return false;
264
+ if (!(0, _isNil2.default)(timestamp) && !(0, _isFunction2.default)(timestamp)) throw new Error(`${entityToken} sharding timestamp must be a function or nil.`);else return false;
279
265
  });
280
266
  _classPrivateFieldSet(this, _config, conformedConfig);
281
267
  }
@@ -313,22 +299,14 @@ class PrivateEntityManager {
313
299
  const {
314
300
  indexes
315
301
  } = 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
- }
302
+ if (!(0, _has2.default)(indexes, indexToken)) throw new Error(`Index '${indexToken}' does not exist for entity '${entityToken}'.`);
321
303
  return indexes[indexToken];
322
304
  }
323
- getKeyGenerator(entityToken, keyToken) {
305
+ getKey(entityToken, keyToken) {
324
306
  const {
325
307
  keys
326
308
  } = this.getEntityConfig(entityToken);
327
- if (!(0, _has2.default)(keys, keyToken)) {
328
- const message = `Key '${keyToken}' does not exist for entity '${entityToken}'.`;
329
- this.logger.error(message);
330
- throw new Error(message);
331
- }
309
+ if (!(0, _has2.default)(keys, keyToken)) throw new Error(`Key '${keyToken}' does not exist for entity '${entityToken}'.`);
332
310
  return keys[keyToken];
333
311
  }
334
312
 
@@ -408,11 +386,17 @@ class PrivateEntityManager {
408
386
  * @returns {boolean} true if entityToken is valid.
409
387
  */
410
388
  validateEntityToken(entityToken) {
411
- if (!this.config.entities[entityToken]) {
412
- const message = `Invalid entityToken: ${entityToken}`;
413
- this.logger.error(message);
414
- throw new Error(message);
415
- } else return true;
389
+ if (!this.config.entities[entityToken]) throw new Error(`Invalid entityToken: ${entityToken}`);else return true;
390
+ }
391
+
392
+ /**
393
+ * Tests whether an item is valid.
394
+ *
395
+ * @param {string} item - Entity token.
396
+ * @returns {boolean} true if entityToken is valid.
397
+ */
398
+ validateItem(item) {
399
+ if (!(0, _isPlainObject2.default)(item)) throw new Error(`Invalid item: ${item}`);else return true;
416
400
  }
417
401
 
418
402
  /**
@@ -424,25 +408,7 @@ class PrivateEntityManager {
424
408
  */
425
409
  validateKeyToken(entityToken, keyToken) {
426
410
  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
-
434
- /**
435
- * Tests whether an item is valid.
436
- *
437
- * @param {string} item - Entity token.
438
- * @returns {boolean} true if entityToken is valid.
439
- */
440
- validateItem(item) {
441
- if (!(0, _isPlainObject2.default)(item)) {
442
- const message = `Invalid item: ${item}`;
443
- this.logger.error(message);
444
- throw new Error(message);
445
- } else return true;
411
+ if (!this.config.entities[entityToken].keys[keyToken]) throw new Error(`Invalid entity ${entityToken} keyToken: ${keyToken}`);else return true;
446
412
  }
447
413
 
448
414
  /**
@@ -63,8 +63,8 @@ export class EntityManager {
63
63
  }
64
64
 
65
65
  // Add keys.
66
- _.forEach(keys, (getValue, key) => {
67
- if (overwrite || _.isNil(newItem[key])) newItem[key] = getValue(newItem);
66
+ _.forEach(keys, ({ encode }, key) => {
67
+ if (overwrite || _.isNil(newItem[key])) newItem[key] = encode(newItem);
68
68
  });
69
69
  this.#entityManager.logger.debug('done', newItem);
70
70
 
@@ -90,90 +90,42 @@ export class EntityManager {
90
90
  }
91
91
 
92
92
  /**
93
- * Convert a item into a delimited string containing the properties of a named index.
93
+ * Condense an index object into a delimited string.
94
94
  *
95
95
  * @param {string} entityToken - Entity token.
96
96
  * @param {string} indexToken - Index token.
97
- * @param {object} item - Entity item.
97
+ * @param {object} index - Index object.
98
98
  * @param {string} [delimiter] - Delimiter.
99
99
  * @returns {string} Dehydrated index.
100
100
  * @throws {Error} If entityToken is invalid.
101
101
  * @throws {Error} If indexToken is invalid.
102
102
  * @throws {Error} If item is invalid.
103
103
  */
104
- dehydrateIndex(entityToken, indexToken, item, delimiter = '~') {
104
+ dehydrateIndex(entityToken, indexToken, index, delimiter = '~') {
105
105
  // Validate item.
106
- this.#entityManager.validateItem(item);
107
-
108
- const indexComponents = this.#entityManager.getIndexComponents(
109
- entityToken,
110
- indexToken
111
- );
106
+ this.#entityManager.validateItem(index);
112
107
 
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
108
  const indexComponents = this.#entityManager.getIndexComponents(
156
109
  entityToken,
157
110
  indexToken
158
111
  );
159
112
 
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
- );
113
+ const indexProperties = indexComponents.reduce((properties, component) => {
114
+ const { decode } = this.#entityManager.getKey(entityToken, component);
115
+ if (!_.isFunction(decode))
116
+ throw new Error(
117
+ `no decode function defined on ${entityToken} entity key '${component}'`
118
+ );
119
+
120
+ return { ...properties, ...(decode(index[component])?.groups ?? []) };
121
+ }, {});
122
+
123
+ return _.sortedUniqBy(
124
+ _.sortBy(_.entries(indexProperties), ([key]) => key),
125
+ ([key]) => key
126
+ )
127
+ .map(([, value]) => value)
128
+ .join(delimiter);
177
129
  }
178
130
 
179
131
  /**
@@ -202,10 +154,7 @@ export class EntityManager {
202
154
 
203
155
  const result = _.sortedUniq(
204
156
  shardKeySpace.map((shardKey) =>
205
- this.#entityManager.getKeyGenerator(
206
- entityToken,
207
- keyToken
208
- )({
157
+ this.#entityManager.getKey(entityToken, keyToken).encode({
209
158
  ...item,
210
159
  [this.#entityManager.shardKeyToken]: shardKey,
211
160
  })
@@ -312,6 +261,38 @@ export class EntityManager {
312
261
  return result;
313
262
  }
314
263
 
264
+ /**
265
+ * Convert a delimited string into a named index key.
266
+ *
267
+ * @param {string} entityToken - Entity token.
268
+ * @param {string} indexToken - Index token.
269
+ * @param {string} value - Dehydrated index value.
270
+ * @param {string} [delimiter] - Delimiter.
271
+ * @returns {object} Rehydrated index key.
272
+ * @throws {Error} If entityToken is invalid.
273
+ * @throws {Error} If indexToken is invalid.
274
+ */
275
+ rehydrateIndex(entityToken, indexToken, value = '', delimiter = '~') {
276
+ const indexComponents = this.#entityManager.getIndexComponents(
277
+ entityToken,
278
+ indexToken
279
+ );
280
+
281
+ const indexKeys = _.sortBy(
282
+ indexComponents.reduce((keys, component) => {
283
+ const { encode } = this.#entityManager.getKey(entityToken, component);
284
+ return [...new Set([...keys, ...getParametersNames(encode)]).values()];
285
+ }, [])
286
+ );
287
+
288
+ const indexProperies = _.zipObject(indexKeys, value.split(delimiter));
289
+
290
+ return indexComponents.reduce((index, component) => {
291
+ const { encode } = this.#entityManager.getKey(entityToken, component);
292
+ return { ...index, [component]: encode(indexProperies) };
293
+ }, {});
294
+ }
295
+
315
296
  /**
316
297
  * Remove sharded keys from an entity item. Does not mutate original item.
317
298
  *
@@ -67,6 +67,16 @@ const configSchema = {
67
67
  additionalProperties: false,
68
68
  };
69
69
 
70
+ const keySchema = {
71
+ type: 'object',
72
+ properties: {
73
+ encode: {},
74
+ decode: {},
75
+ },
76
+ additionalProperties: false,
77
+ required: ['encode'],
78
+ };
79
+
70
80
  /**
71
81
  * Private EntityManager implementation.
72
82
  *
@@ -114,12 +124,7 @@ export class PrivateEntityManager {
114
124
  set config(value) {
115
125
  // Validate config against schema.
116
126
  const validatorResult = validate(value, configSchema);
117
- if (!validatorResult.valid) {
118
- validatorResult.errors.forEach((error) =>
119
- this.logger.error(error.message)
120
- );
121
- throw new Error(validatorResult.errors);
122
- }
127
+ if (!validatorResult.valid) throw new Error(validatorResult.errors);
123
128
 
124
129
  // Conform config.
125
130
  const { entities = {}, shardKeyToken = 'shardId' } = value;
@@ -151,7 +156,7 @@ export class PrivateEntityManager {
151
156
  shardKeyToken,
152
157
  };
153
158
 
154
- // Validate entity properties.
159
+ // Conform & validate entity properties.
155
160
  _.some(
156
161
  conformedConfig.entities,
157
162
  (
@@ -165,68 +170,70 @@ export class PrivateEntityManager {
165
170
  // Validate entity index components are entity keys.
166
171
  _.some(indexes, (index, indexToken) => {
167
172
  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
+ if (invalidIndexComponents.length)
174
+ throw new Error(
175
+ `${entityToken} index '${indexToken}' components '${invalidIndexComponents}' are not entity keys.`
176
+ );
177
+ else return false;
173
178
  });
174
179
 
175
- // Validate entity keys are functions or undefined.
176
- _.some(keys, (value, key) => {
177
- if (!_.isNil(value) && !_.isFunction(value)) {
178
- const message = `${entityToken} key '${key}' must be a function or nil.`;
179
- this.logger.error(message);
180
- throw new Error(message);
181
- } else return false;
180
+ // Conform & validate entity keys.
181
+ _.forEach(keys, (value, key) => {
182
+ keys[key] = _.isFunction(value) ? { encode: value } : value;
183
+
184
+ // Validate key against schema.
185
+ const validatorResult = validate(keys[key], keySchema);
186
+ if (!validatorResult.valid) throw new Error(validatorResult.errors);
187
+
188
+ const { decode } = keys[key];
189
+ if (!_.isUndefined(decode) && !_.isFunction(decode))
190
+ throw new Error(
191
+ `entity ${entityKey} key '${key}' decode must be a function or undefined.`
192
+ );
182
193
  });
183
194
 
184
195
  // Validate sharding bump values increase monotonically with keys.
185
196
  _.some(_.toPairs(bumps), ([bump, value], i, c) => {
186
197
  const [lastBump, lastValue] = i ? c[i - 1] : [];
187
- if (value <= lastValue) {
188
- const message = `${entityToken} sharding bumps do not monotonically increase from '${lastBump}: ${lastValue}' to '${bump}: ${value}'.)`;
189
- this.logger.error(message);
190
- throw new Error(message);
191
- } else return false;
198
+ if (value <= lastValue)
199
+ throw new Error(
200
+ `${entityToken} sharding bumps do not monotonically increase from '${lastBump}: ${lastValue}' to '${bump}: ${value}'.)`
201
+ );
202
+ else return false;
192
203
  });
193
204
 
194
205
  // Validate sharding entityKey is a function or undefined.
195
- if (!_.isNil(entityKey) && !_.isFunction(entityKey)) {
196
- const message = `${entityToken} sharding entityKey must be a function or nil`;
197
- this.logger.error(message);
198
- throw new Error(message);
199
- }
206
+ if (!_.isNil(entityKey) && !_.isFunction(entityKey))
207
+ throw new Error(
208
+ `${entityToken} sharding entityKey must be a function or nil`
209
+ );
200
210
 
201
211
  // Validate sharding nibbles do not exceed 32 bits.
202
- if (nibbles * nibbleBits > 32) {
203
- const message = `${entityToken} nibbles (${nibbles} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`;
204
- this.logger.error(message);
205
- throw new Error(message);
206
- }
212
+ if (nibbles * nibbleBits > 32)
213
+ throw new Error(
214
+ `${entityToken} nibbles (${nibbles} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`
215
+ );
207
216
 
208
217
  // Validate sharding nibbles are less than first bump value.
209
218
  const firstBumpValue = _.map(bumps)[0];
210
- if (nibbles >= firstBumpValue) {
211
- const message = `${entityToken} nibbles (${nibbles}) not less than minimum bump value (${firstBumpValue})`;
212
- this.logger.error(message);
213
- throw new Error(message);
214
- }
219
+ if (nibbles >= firstBumpValue)
220
+ throw new Error(
221
+ `${entityToken} nibbles (${nibbles}) not less than minimum bump value (${firstBumpValue})`
222
+ );
215
223
 
216
224
  // Validate last bump value does not exceed 32 bits.
217
225
  const lastBumpValue = _.map(bumps).slice(-1);
218
- if (lastBumpValue * nibbleBits > 32) {
219
- const message = `${entityToken} maximum bump value (${lastBumpValue} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`;
220
- this.logger.error(message);
221
- throw new Error(message);
222
- }
226
+ if (lastBumpValue * nibbleBits > 32)
227
+ throw new Error(
228
+ `${entityToken} maximum bump value (${lastBumpValue} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`
229
+ );
223
230
 
224
231
  // Validate sharding timestamp is a function or undefined.
225
- if (!_.isNil(timestamp) && !_.isFunction(timestamp)) {
226
- const message = `${entityToken} sharding timestamp must be a function or nil.`;
227
- this.logger.error(message);
228
- throw new Error(message);
229
- } else return false;
232
+ if (!_.isNil(timestamp) && !_.isFunction(timestamp))
233
+ throw new Error(
234
+ `${entityToken} sharding timestamp must be a function or nil.`
235
+ );
236
+ else return false;
230
237
  }
231
238
  );
232
239
 
@@ -266,23 +273,21 @@ export class PrivateEntityManager {
266
273
  getIndexComponents(entityToken, indexToken) {
267
274
  const { indexes } = this.getEntityConfig(entityToken);
268
275
 
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
- }
276
+ if (!_.has(indexes, indexToken))
277
+ throw new Error(
278
+ `Index '${indexToken}' does not exist for entity '${entityToken}'.`
279
+ );
274
280
 
275
281
  return indexes[indexToken];
276
282
  }
277
283
 
278
- getKeyGenerator(entityToken, keyToken) {
284
+ getKey(entityToken, keyToken) {
279
285
  const { keys } = this.getEntityConfig(entityToken);
280
286
 
281
- if (!_.has(keys, keyToken)) {
282
- const message = `Key '${keyToken}' does not exist for entity '${entityToken}'.`;
283
- this.logger.error(message);
284
- throw new Error(message);
285
- }
287
+ if (!_.has(keys, keyToken))
288
+ throw new Error(
289
+ `Key '${keyToken}' does not exist for entity '${entityToken}'.`
290
+ );
286
291
 
287
292
  return keys[keyToken];
288
293
  }
@@ -376,11 +381,20 @@ export class PrivateEntityManager {
376
381
  * @returns {boolean} true if entityToken is valid.
377
382
  */
378
383
  validateEntityToken(entityToken) {
379
- if (!this.config.entities[entityToken]) {
380
- const message = `Invalid entityToken: ${entityToken}`;
381
- this.logger.error(message);
382
- throw new Error(message);
383
- } else return true;
384
+ if (!this.config.entities[entityToken])
385
+ throw new Error(`Invalid entityToken: ${entityToken}`);
386
+ else return true;
387
+ }
388
+
389
+ /**
390
+ * Tests whether an item is valid.
391
+ *
392
+ * @param {string} item - Entity token.
393
+ * @returns {boolean} true if entityToken is valid.
394
+ */
395
+ validateItem(item) {
396
+ if (!_.isPlainObject(item)) throw new Error(`Invalid item: ${item}`);
397
+ else return true;
384
398
  }
385
399
 
386
400
  /**
@@ -393,25 +407,9 @@ export class PrivateEntityManager {
393
407
  validateKeyToken(entityToken, keyToken) {
394
408
  this.validateEntityToken(entityToken);
395
409
 
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
-
403
- /**
404
- * Tests whether an item is valid.
405
- *
406
- * @param {string} item - Entity token.
407
- * @returns {boolean} true if entityToken is valid.
408
- */
409
- validateItem(item) {
410
- if (!_.isPlainObject(item)) {
411
- const message = `Invalid item: ${item}`;
412
- this.logger.error(message);
413
- throw new Error(message);
414
- } else return true;
410
+ if (!this.config.entities[entityToken].keys[keyToken])
411
+ throw new Error(`Invalid entity ${entityToken} keyToken: ${keyToken}`);
412
+ else return true;
415
413
  }
416
414
 
417
415
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/entity-manager",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },