@karmaniverous/entity-manager 2.0.0 → 2.1.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
|
@@ -220,8 +220,11 @@ This change can be accomplished with no breaking changes to existing implementat
|
|
|
220
220
|
* [.EntityManager](#module_entity-manager.EntityManager)
|
|
221
221
|
* [new exports.EntityManager(options)](#new_module_entity-manager.EntityManager_new)
|
|
222
222
|
* [.addKeys(entityToken, item, [overwrite])](#module_entity-manager.EntityManager+addKeys) ⇒ <code>object</code>
|
|
223
|
+
* [.calcShardKey(entityToken, item)](#module_entity-manager.EntityManager+calcShardKey) ⇒ <code>string</code>
|
|
224
|
+
* [.dehydrateIndex(entityToken, indexToken, index, [delimiter])](#module_entity-manager.EntityManager+dehydrateIndex) ⇒ <code>string</code>
|
|
223
225
|
* [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.<string></code>
|
|
224
226
|
* [.query(entityToken, keyToken, item, shardQuery, [options])](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.<ShardedQueryResult></code>
|
|
227
|
+
* [.rehydrateIndex(entityToken, indexToken, value, [delimiter])](#module_entity-manager.EntityManager+rehydrateIndex) ⇒ <code>object</code>
|
|
225
228
|
* [.removeKeys(entityToken, item)](#module_entity-manager.EntityManager+removeKeys) ⇒ <code>object</code>
|
|
226
229
|
* _inner_
|
|
227
230
|
* [~ShardQueryResult](#module_entity-manager..ShardQueryResult) : <code>object</code>
|
|
@@ -238,8 +241,11 @@ Manage DynamoDb entities.
|
|
|
238
241
|
* [.EntityManager](#module_entity-manager.EntityManager)
|
|
239
242
|
* [new exports.EntityManager(options)](#new_module_entity-manager.EntityManager_new)
|
|
240
243
|
* [.addKeys(entityToken, item, [overwrite])](#module_entity-manager.EntityManager+addKeys) ⇒ <code>object</code>
|
|
244
|
+
* [.calcShardKey(entityToken, item)](#module_entity-manager.EntityManager+calcShardKey) ⇒ <code>string</code>
|
|
245
|
+
* [.dehydrateIndex(entityToken, indexToken, index, [delimiter])](#module_entity-manager.EntityManager+dehydrateIndex) ⇒ <code>string</code>
|
|
241
246
|
* [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.<string></code>
|
|
242
247
|
* [.query(entityToken, keyToken, item, shardQuery, [options])](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.<ShardedQueryResult></code>
|
|
248
|
+
* [.rehydrateIndex(entityToken, indexToken, value, [delimiter])](#module_entity-manager.EntityManager+rehydrateIndex) ⇒ <code>object</code>
|
|
243
249
|
* [.removeKeys(entityToken, item)](#module_entity-manager.EntityManager+removeKeys) ⇒ <code>object</code>
|
|
244
250
|
|
|
245
251
|
<a name="new_module_entity-manager.EntityManager_new"></a>
|
|
@@ -279,6 +285,40 @@ Add sharded keys to an entity item. Does not mutate original item.
|
|
|
279
285
|
| item | <code>object</code> | | Entity item. |
|
|
280
286
|
| [overwrite] | <code>boolean</code> | <code>false</code> | Overwrite existing properties. |
|
|
281
287
|
|
|
288
|
+
<a name="module_entity-manager.EntityManager+calcShardKey"></a>
|
|
289
|
+
|
|
290
|
+
#### entityManager.calcShardKey(entityToken, item) ⇒ <code>string</code>
|
|
291
|
+
Calculated the shard key for an entity item.
|
|
292
|
+
|
|
293
|
+
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
294
|
+
**Returns**: <code>string</code> - Shard key.
|
|
295
|
+
|
|
296
|
+
| Param | Type | Description |
|
|
297
|
+
| --- | --- | --- |
|
|
298
|
+
| entityToken | <code>string</code> | Entity token. |
|
|
299
|
+
| item | <code>object</code> | Entity item. |
|
|
300
|
+
|
|
301
|
+
<a name="module_entity-manager.EntityManager+dehydrateIndex"></a>
|
|
302
|
+
|
|
303
|
+
#### entityManager.dehydrateIndex(entityToken, indexToken, index, [delimiter]) ⇒ <code>string</code>
|
|
304
|
+
Condense an index object into a delimited string.
|
|
305
|
+
|
|
306
|
+
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
307
|
+
**Returns**: <code>string</code> - Dehydrated index.
|
|
308
|
+
**Throws**:
|
|
309
|
+
|
|
310
|
+
- <code>Error</code> If entityToken is invalid.
|
|
311
|
+
- <code>Error</code> If indexToken is invalid.
|
|
312
|
+
- <code>Error</code> If item is invalid.
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
| Param | Type | Default | Description |
|
|
316
|
+
| --- | --- | --- | --- |
|
|
317
|
+
| entityToken | <code>string</code> | | Entity token. |
|
|
318
|
+
| indexToken | <code>string</code> | | Index token. |
|
|
319
|
+
| index | <code>object</code> | | Index object. |
|
|
320
|
+
| [delimiter] | <code>string</code> | <code>"~"</code> | Delimiter. |
|
|
321
|
+
|
|
282
322
|
<a name="module_entity-manager.EntityManager+getKeySpace"></a>
|
|
283
323
|
|
|
284
324
|
#### entityManager.getKeySpace(entityToken, keyToken, item, timestamp) ⇒ <code>Array.<string></code>
|
|
@@ -319,6 +359,26 @@ Query an entity across shards.
|
|
|
319
359
|
| [options.limit] | <code>number</code> | Request limit. |
|
|
320
360
|
| [options.pageKeys] | <code>object</code> | Map of shard page keys. |
|
|
321
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>"~"</code> | Delimiter. |
|
|
381
|
+
|
|
322
382
|
<a name="module_entity-manager.EntityManager+removeKeys"></a>
|
|
323
383
|
|
|
324
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,11 +12,15 @@ 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 _entries2 = _interopRequireDefault(require("lodash/entries"));
|
|
17
|
+
var _sortBy2 = _interopRequireDefault(require("lodash/sortBy"));
|
|
18
|
+
var _sortedUniqBy2 = _interopRequireDefault(require("lodash/sortedUniqBy"));
|
|
19
|
+
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
|
|
16
20
|
var _forEach2 = _interopRequireDefault(require("lodash/forEach"));
|
|
17
21
|
var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
|
|
18
22
|
var _cloneDeep2 = _interopRequireDefault(require("lodash/cloneDeep"));
|
|
23
|
+
var _inspectParametersDeclaration = require("inspect-parameters-declaration");
|
|
19
24
|
var _PrivateEntityManager = require("./PrivateEntityManager.js");
|
|
20
25
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
21
26
|
function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
|
|
@@ -82,20 +87,20 @@ class EntityManager {
|
|
|
82
87
|
shardKeyToken
|
|
83
88
|
} = _classPrivateFieldGet(this, _entityManager);
|
|
84
89
|
const {
|
|
85
|
-
keys
|
|
86
|
-
sharding
|
|
90
|
+
keys
|
|
87
91
|
} = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
|
|
88
92
|
|
|
89
93
|
// Add shardKey.
|
|
90
94
|
if (overwrite || (0, _isNil2.default)(newItem[shardKeyToken])) {
|
|
91
|
-
|
|
92
|
-
const timestamp = sharding.timestamp(newItem);
|
|
93
|
-
newItem[shardKeyToken] = _classPrivateFieldGet(this, _entityManager).getShardKey(entityToken, entityKey, timestamp);
|
|
95
|
+
newItem[shardKeyToken] = this.calcShardKey(entityToken, newItem);
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
// Add keys.
|
|
97
|
-
(0, _forEach2.default)(keys, (
|
|
98
|
-
|
|
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);
|
|
99
104
|
});
|
|
100
105
|
_classPrivateFieldGet(this, _entityManager).logger.debug('done', newItem);
|
|
101
106
|
|
|
@@ -104,6 +109,61 @@ class EntityManager {
|
|
|
104
109
|
return newItem;
|
|
105
110
|
}
|
|
106
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Calculated the shard key for an entity item.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} entityToken - Entity token.
|
|
116
|
+
* @param {object} item - Entity item.
|
|
117
|
+
* @returns {string} Shard key.
|
|
118
|
+
*/
|
|
119
|
+
calcShardKey(entityToken, item) {
|
|
120
|
+
const {
|
|
121
|
+
sharding
|
|
122
|
+
} = _classPrivateFieldGet(this, _entityManager).getEntityConfig(entityToken);
|
|
123
|
+
const entityKey = sharding.entityKey(item);
|
|
124
|
+
const timestamp = sharding.timestamp(item);
|
|
125
|
+
return _classPrivateFieldGet(this, _entityManager).getShardKey(entityToken, entityKey, timestamp);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Condense an index object into a delimited string.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} entityToken - Entity token.
|
|
132
|
+
* @param {string} indexToken - Index token.
|
|
133
|
+
* @param {object} index - Index object.
|
|
134
|
+
* @param {string} [delimiter] - Delimiter.
|
|
135
|
+
* @returns {string} Dehydrated index.
|
|
136
|
+
* @throws {Error} If entityToken is invalid.
|
|
137
|
+
* @throws {Error} If indexToken is invalid.
|
|
138
|
+
* @throws {Error} If item is invalid.
|
|
139
|
+
*/
|
|
140
|
+
dehydrateIndex(entityToken, indexToken, index) {
|
|
141
|
+
let delimiter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '~';
|
|
142
|
+
// Validate item.
|
|
143
|
+
_classPrivateFieldGet(this, _entityManager).validateItem(index);
|
|
144
|
+
const indexComponents = _classPrivateFieldGet(this, _entityManager).getIndexComponents(entityToken, indexToken);
|
|
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])
|
|
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);
|
|
165
|
+
}
|
|
166
|
+
|
|
107
167
|
/**
|
|
108
168
|
* Return an array of sharded keys valid for a given entity token & timestamp.
|
|
109
169
|
*
|
|
@@ -121,7 +181,7 @@ class EntityManager {
|
|
|
121
181
|
let timestamp = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Date.now();
|
|
122
182
|
_classPrivateFieldGet(this, _entityManager).logger.debug(`getting shard key space for ${entityToken} on key '${keyToken}' at timestamp ${timestamp}...`, item);
|
|
123
183
|
const shardKeySpace = _classPrivateFieldGet(this, _entityManager).getShardKeySpace(entityToken, timestamp);
|
|
124
|
-
const result = (0, _sortedUniq2.default)(shardKeySpace.map(shardKey => _classPrivateFieldGet(this, _entityManager).
|
|
184
|
+
const result = (0, _sortedUniq2.default)(shardKeySpace.map(shardKey => _classPrivateFieldGet(this, _entityManager).getKey(entityToken, keyToken).encode({
|
|
125
185
|
...item,
|
|
126
186
|
[_classPrivateFieldGet(this, _entityManager).shardKeyToken]: shardKey
|
|
127
187
|
})));
|
|
@@ -192,11 +252,11 @@ class EntityManager {
|
|
|
192
252
|
const shardQueryResults = await Promise.all((0, _map2.default)(pageKeys, (pageKey, shardedKey) => new Promise(resolve => shardQuery(shardedKey, {
|
|
193
253
|
pageKey,
|
|
194
254
|
limit: shardLimit
|
|
195
|
-
}).then(
|
|
255
|
+
}).then(_ref6 => {
|
|
196
256
|
let {
|
|
197
257
|
items,
|
|
198
258
|
pageKey
|
|
199
|
-
} =
|
|
259
|
+
} = _ref6;
|
|
200
260
|
return resolve({
|
|
201
261
|
shardedKey,
|
|
202
262
|
items,
|
|
@@ -205,12 +265,12 @@ class EntityManager {
|
|
|
205
265
|
}))));
|
|
206
266
|
|
|
207
267
|
// Reduce shardQueryResults into a single result.
|
|
208
|
-
const result = shardQueryResults.reduce((shardedQueryResult,
|
|
268
|
+
const result = shardQueryResults.reduce((shardedQueryResult, _ref7) => {
|
|
209
269
|
let {
|
|
210
270
|
shardedKey,
|
|
211
271
|
items,
|
|
212
272
|
pageKey
|
|
213
|
-
} =
|
|
273
|
+
} = _ref7;
|
|
214
274
|
return {
|
|
215
275
|
items: [...shardedQueryResult.items, ...items],
|
|
216
276
|
pageKeys: {
|
|
@@ -227,6 +287,39 @@ class EntityManager {
|
|
|
227
287
|
return result;
|
|
228
288
|
}
|
|
229
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
|
+
|
|
230
323
|
/**
|
|
231
324
|
* Remove sharded keys from an entity item. Does not mutate original item.
|
|
232
325
|
*
|
|
@@ -12,8 +12,12 @@ 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"));
|
|
19
|
+
var _keys2 = _interopRequireDefault(require("lodash/keys"));
|
|
20
|
+
var _difference2 = _interopRequireDefault(require("lodash/difference"));
|
|
17
21
|
var _some2 = _interopRequireDefault(require("lodash/some"));
|
|
18
22
|
var _toPairs2 = _interopRequireDefault(require("lodash/toPairs"));
|
|
19
23
|
var _sortBy2 = _interopRequireDefault(require("lodash/sortBy"));
|
|
@@ -43,6 +47,16 @@ const configSchema = {
|
|
|
43
47
|
'^\\w+$': {
|
|
44
48
|
type: 'object',
|
|
45
49
|
properties: {
|
|
50
|
+
indexes: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
additionalProperties: {
|
|
53
|
+
type: 'array',
|
|
54
|
+
items: {
|
|
55
|
+
type: 'string'
|
|
56
|
+
},
|
|
57
|
+
minItems: 1
|
|
58
|
+
}
|
|
59
|
+
},
|
|
46
60
|
keys: {
|
|
47
61
|
type: 'object'
|
|
48
62
|
},
|
|
@@ -90,6 +104,15 @@ const configSchema = {
|
|
|
90
104
|
},
|
|
91
105
|
additionalProperties: false
|
|
92
106
|
};
|
|
107
|
+
const keySchema = {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
encode: {},
|
|
111
|
+
decode: {}
|
|
112
|
+
},
|
|
113
|
+
additionalProperties: false,
|
|
114
|
+
required: ['encode']
|
|
115
|
+
};
|
|
93
116
|
|
|
94
117
|
/**
|
|
95
118
|
* Private EntityManager implementation.
|
|
@@ -160,6 +183,7 @@ class PrivateEntityManager {
|
|
|
160
183
|
const conformedConfig = {
|
|
161
184
|
entities: (0, _mapValues2.default)(entities, _ref => {
|
|
162
185
|
let {
|
|
186
|
+
indexes = {},
|
|
163
187
|
keys = {},
|
|
164
188
|
sharding: {
|
|
165
189
|
bumps = {},
|
|
@@ -170,6 +194,7 @@ class PrivateEntityManager {
|
|
|
170
194
|
} = {}
|
|
171
195
|
} = _ref;
|
|
172
196
|
return {
|
|
197
|
+
indexes,
|
|
173
198
|
keys,
|
|
174
199
|
sharding: {
|
|
175
200
|
bumps: (0, _fromPairs2.default)((0, _sortBy2.default)((0, _toPairs2.default)(bumps), 0)),
|
|
@@ -183,9 +208,10 @@ class PrivateEntityManager {
|
|
|
183
208
|
shardKeyToken
|
|
184
209
|
};
|
|
185
210
|
|
|
186
|
-
//
|
|
211
|
+
// Conform & validate entity properties.
|
|
187
212
|
(0, _some2.default)(conformedConfig.entities, (_ref2, entityToken) => {
|
|
188
213
|
let {
|
|
214
|
+
indexes,
|
|
189
215
|
keys,
|
|
190
216
|
sharding: {
|
|
191
217
|
bumps,
|
|
@@ -195,15 +221,38 @@ class PrivateEntityManager {
|
|
|
195
221
|
timestamp
|
|
196
222
|
}
|
|
197
223
|
} = _ref2;
|
|
198
|
-
// Validate entity
|
|
199
|
-
(0, _some2.default)(
|
|
200
|
-
|
|
201
|
-
|
|
224
|
+
// Validate entity index components are entity keys.
|
|
225
|
+
(0, _some2.default)(indexes, (index, indexToken) => {
|
|
226
|
+
const invalidIndexComponents = (0, _difference2.default)(index, (0, _keys2.default)(keys));
|
|
227
|
+
if (invalidIndexComponents.length) {
|
|
228
|
+
const message = `${entityToken} index '${indexToken}' components '${invalidIndexComponents}' are not entity keys.`;
|
|
202
229
|
this.logger.error(message);
|
|
203
230
|
throw new Error(message);
|
|
204
231
|
} else return false;
|
|
205
232
|
});
|
|
206
233
|
|
|
234
|
+
// Conform & validate entity keys.
|
|
235
|
+
(0, _forEach2.default)(keys, (value, key) => {
|
|
236
|
+
keys[key] = (0, _isFunction2.default)(value) ? {
|
|
237
|
+
encode: value
|
|
238
|
+
} : value;
|
|
239
|
+
|
|
240
|
+
// Validate key against schema.
|
|
241
|
+
const validatorResult = (0, _jsonschema.validate)(keys[key], keySchema);
|
|
242
|
+
if (!validatorResult.valid) {
|
|
243
|
+
validatorResult.errors.forEach(error => this.logger.error(error.message));
|
|
244
|
+
throw new Error(validatorResult.errors);
|
|
245
|
+
}
|
|
246
|
+
const {
|
|
247
|
+
decode
|
|
248
|
+
} = keys[key];
|
|
249
|
+
if (!(0, _isUndefined2.default)(decode) && !(0, _isFunction2.default)(decode)) {
|
|
250
|
+
const message = `entity ${entityKey} key '${key}' decode must be a function or undefined.`;
|
|
251
|
+
this.logger.error(message);
|
|
252
|
+
throw new Error(message);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
207
256
|
// Validate sharding bump values increase monotonically with keys.
|
|
208
257
|
(0, _some2.default)((0, _toPairs2.default)(bumps), (_ref3, i, c) => {
|
|
209
258
|
let [bump, value] = _ref3;
|
|
@@ -284,7 +333,18 @@ class PrivateEntityManager {
|
|
|
284
333
|
this.validateEntityToken(entityToken);
|
|
285
334
|
return this.config.entities[entityToken];
|
|
286
335
|
}
|
|
287
|
-
|
|
336
|
+
getIndexComponents(entityToken, indexToken) {
|
|
337
|
+
const {
|
|
338
|
+
indexes
|
|
339
|
+
} = this.getEntityConfig(entityToken);
|
|
340
|
+
if (!(0, _has2.default)(indexes, indexToken)) {
|
|
341
|
+
const message = `Index '${indexToken}' does not exist for entity '${entityToken}'.`;
|
|
342
|
+
this.logger.error(message);
|
|
343
|
+
throw new Error(message);
|
|
344
|
+
}
|
|
345
|
+
return indexes[indexToken];
|
|
346
|
+
}
|
|
347
|
+
getKey(entityToken, keyToken) {
|
|
288
348
|
const {
|
|
289
349
|
keys
|
|
290
350
|
} = this.getEntityConfig(entityToken);
|
|
@@ -380,30 +440,30 @@ class PrivateEntityManager {
|
|
|
380
440
|
}
|
|
381
441
|
|
|
382
442
|
/**
|
|
383
|
-
* Tests whether
|
|
443
|
+
* Tests whether an item is valid.
|
|
384
444
|
*
|
|
385
|
-
* @param {string}
|
|
386
|
-
* @
|
|
387
|
-
* @returns {boolean} true if keyToken is valid.
|
|
445
|
+
* @param {string} item - Entity token.
|
|
446
|
+
* @returns {boolean} true if entityToken is valid.
|
|
388
447
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const message = `Invalid entity ${entityToken} keyToken: ${keyToken}`;
|
|
448
|
+
validateItem(item) {
|
|
449
|
+
if (!(0, _isPlainObject2.default)(item)) {
|
|
450
|
+
const message = `Invalid item: ${item}`;
|
|
393
451
|
this.logger.error(message);
|
|
394
452
|
throw new Error(message);
|
|
395
453
|
} else return true;
|
|
396
454
|
}
|
|
397
455
|
|
|
398
456
|
/**
|
|
399
|
-
* Tests whether
|
|
457
|
+
* Tests whether a key token is valid.
|
|
400
458
|
*
|
|
401
|
-
* @param {string}
|
|
402
|
-
* @
|
|
459
|
+
* @param {string} entityToken - Entity token.
|
|
460
|
+
* @param {string} keyToken - Key token.
|
|
461
|
+
* @returns {boolean} true if keyToken is valid.
|
|
403
462
|
*/
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
463
|
+
validateKeyToken(entityToken, keyToken) {
|
|
464
|
+
this.validateEntityToken(entityToken);
|
|
465
|
+
if (!this.config.entities[entityToken].keys[keyToken]) {
|
|
466
|
+
const message = `Invalid entity ${entityToken} keyToken: ${keyToken}`;
|
|
407
467
|
this.logger.error(message);
|
|
408
468
|
throw new Error(message);
|
|
409
469
|
} else return true;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @module entity-manager
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { getParametersNames } from 'inspect-parameters-declaration';
|
|
5
6
|
import _ from 'lodash';
|
|
6
7
|
|
|
7
8
|
import { PrivateEntityManager } from './PrivateEntityManager.js';
|
|
@@ -54,22 +55,16 @@ export class EntityManager {
|
|
|
54
55
|
|
|
55
56
|
// Get entity config.
|
|
56
57
|
const { shardKeyToken } = this.#entityManager;
|
|
57
|
-
const { keys
|
|
58
|
+
const { keys } = this.#entityManager.getEntityConfig(entityToken);
|
|
58
59
|
|
|
59
60
|
// Add shardKey.
|
|
60
61
|
if (overwrite || _.isNil(newItem[shardKeyToken])) {
|
|
61
|
-
|
|
62
|
-
const timestamp = sharding.timestamp(newItem);
|
|
63
|
-
newItem[shardKeyToken] = this.#entityManager.getShardKey(
|
|
64
|
-
entityToken,
|
|
65
|
-
entityKey,
|
|
66
|
-
timestamp
|
|
67
|
-
);
|
|
62
|
+
newItem[shardKeyToken] = this.calcShardKey(entityToken, newItem);
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
// Add keys.
|
|
71
|
-
_.forEach(keys, (
|
|
72
|
-
if (overwrite || _.isNil(newItem[key])) newItem[key] =
|
|
66
|
+
_.forEach(keys, ({ encode }, key) => {
|
|
67
|
+
if (overwrite || _.isNil(newItem[key])) newItem[key] = encode(newItem);
|
|
73
68
|
});
|
|
74
69
|
this.#entityManager.logger.debug('done', newItem);
|
|
75
70
|
|
|
@@ -79,6 +74,60 @@ export class EntityManager {
|
|
|
79
74
|
return newItem;
|
|
80
75
|
}
|
|
81
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Calculated the shard key for an entity item.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} entityToken - Entity token.
|
|
81
|
+
* @param {object} item - Entity item.
|
|
82
|
+
* @returns {string} Shard key.
|
|
83
|
+
*/
|
|
84
|
+
calcShardKey(entityToken, item) {
|
|
85
|
+
const { sharding } = this.#entityManager.getEntityConfig(entityToken);
|
|
86
|
+
const entityKey = sharding.entityKey(item);
|
|
87
|
+
const timestamp = sharding.timestamp(item);
|
|
88
|
+
|
|
89
|
+
return this.#entityManager.getShardKey(entityToken, entityKey, timestamp);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Condense an index object into a delimited string.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} entityToken - Entity token.
|
|
96
|
+
* @param {string} indexToken - Index token.
|
|
97
|
+
* @param {object} index - Index object.
|
|
98
|
+
* @param {string} [delimiter] - Delimiter.
|
|
99
|
+
* @returns {string} Dehydrated index.
|
|
100
|
+
* @throws {Error} If entityToken is invalid.
|
|
101
|
+
* @throws {Error} If indexToken is invalid.
|
|
102
|
+
* @throws {Error} If item is invalid.
|
|
103
|
+
*/
|
|
104
|
+
dehydrateIndex(entityToken, indexToken, index, delimiter = '~') {
|
|
105
|
+
// Validate item.
|
|
106
|
+
this.#entityManager.validateItem(index);
|
|
107
|
+
|
|
108
|
+
const indexComponents = this.#entityManager.getIndexComponents(
|
|
109
|
+
entityToken,
|
|
110
|
+
indexToken
|
|
111
|
+
);
|
|
112
|
+
|
|
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]) };
|
|
121
|
+
}, {});
|
|
122
|
+
|
|
123
|
+
return _.sortedUniqBy(
|
|
124
|
+
_.sortBy(_.entries(indexProperties), ([key]) => key),
|
|
125
|
+
([key]) => key
|
|
126
|
+
)
|
|
127
|
+
.map(([, value]) => value)
|
|
128
|
+
.join(delimiter);
|
|
129
|
+
}
|
|
130
|
+
|
|
82
131
|
/**
|
|
83
132
|
* Return an array of sharded keys valid for a given entity token & timestamp.
|
|
84
133
|
*
|
|
@@ -105,10 +154,7 @@ export class EntityManager {
|
|
|
105
154
|
|
|
106
155
|
const result = _.sortedUniq(
|
|
107
156
|
shardKeySpace.map((shardKey) =>
|
|
108
|
-
this.#entityManager.
|
|
109
|
-
entityToken,
|
|
110
|
-
keyToken
|
|
111
|
-
)({
|
|
157
|
+
this.#entityManager.getKey(entityToken, keyToken).encode({
|
|
112
158
|
...item,
|
|
113
159
|
[this.#entityManager.shardKeyToken]: shardKey,
|
|
114
160
|
})
|
|
@@ -215,6 +261,38 @@ export class EntityManager {
|
|
|
215
261
|
return result;
|
|
216
262
|
}
|
|
217
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
|
+
|
|
218
296
|
/**
|
|
219
297
|
* Remove sharded keys from an entity item. Does not mutate original item.
|
|
220
298
|
*
|
|
@@ -16,6 +16,16 @@ const configSchema = {
|
|
|
16
16
|
'^\\w+$': {
|
|
17
17
|
type: 'object',
|
|
18
18
|
properties: {
|
|
19
|
+
indexes: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
additionalProperties: {
|
|
22
|
+
type: 'array',
|
|
23
|
+
items: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
},
|
|
26
|
+
minItems: 1,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
19
29
|
keys: {
|
|
20
30
|
type: 'object',
|
|
21
31
|
},
|
|
@@ -57,6 +67,16 @@ const configSchema = {
|
|
|
57
67
|
additionalProperties: false,
|
|
58
68
|
};
|
|
59
69
|
|
|
70
|
+
const keySchema = {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
encode: {},
|
|
74
|
+
decode: {},
|
|
75
|
+
},
|
|
76
|
+
additionalProperties: false,
|
|
77
|
+
required: ['encode'],
|
|
78
|
+
};
|
|
79
|
+
|
|
60
80
|
/**
|
|
61
81
|
* Private EntityManager implementation.
|
|
62
82
|
*
|
|
@@ -117,6 +137,7 @@ export class PrivateEntityManager {
|
|
|
117
137
|
entities: _.mapValues(
|
|
118
138
|
entities,
|
|
119
139
|
({
|
|
140
|
+
indexes = {},
|
|
120
141
|
keys = {},
|
|
121
142
|
sharding: {
|
|
122
143
|
bumps = {},
|
|
@@ -126,6 +147,7 @@ export class PrivateEntityManager {
|
|
|
126
147
|
timestamp,
|
|
127
148
|
} = {},
|
|
128
149
|
}) => ({
|
|
150
|
+
indexes,
|
|
129
151
|
keys,
|
|
130
152
|
sharding: {
|
|
131
153
|
bumps: _.fromPairs(_.sortBy(_.toPairs(bumps), 0)),
|
|
@@ -139,25 +161,48 @@ export class PrivateEntityManager {
|
|
|
139
161
|
shardKeyToken,
|
|
140
162
|
};
|
|
141
163
|
|
|
142
|
-
//
|
|
164
|
+
// Conform & validate entity properties.
|
|
143
165
|
_.some(
|
|
144
166
|
conformedConfig.entities,
|
|
145
167
|
(
|
|
146
168
|
{
|
|
169
|
+
indexes,
|
|
147
170
|
keys,
|
|
148
171
|
sharding: { bumps, entityKey, nibbleBits, nibbles, timestamp },
|
|
149
172
|
},
|
|
150
173
|
entityToken
|
|
151
174
|
) => {
|
|
152
|
-
// Validate entity
|
|
153
|
-
_.some(
|
|
154
|
-
|
|
155
|
-
|
|
175
|
+
// Validate entity index components are entity keys.
|
|
176
|
+
_.some(indexes, (index, indexToken) => {
|
|
177
|
+
const invalidIndexComponents = _.difference(index, _.keys(keys));
|
|
178
|
+
if (invalidIndexComponents.length) {
|
|
179
|
+
const message = `${entityToken} index '${indexToken}' components '${invalidIndexComponents}' are not entity keys.`;
|
|
156
180
|
this.logger.error(message);
|
|
157
181
|
throw new Error(message);
|
|
158
182
|
} else return false;
|
|
159
183
|
});
|
|
160
184
|
|
|
185
|
+
// Conform & validate entity keys.
|
|
186
|
+
_.forEach(keys, (value, key) => {
|
|
187
|
+
keys[key] = _.isFunction(value) ? { encode: value } : value;
|
|
188
|
+
|
|
189
|
+
// Validate key against schema.
|
|
190
|
+
const validatorResult = validate(keys[key], keySchema);
|
|
191
|
+
if (!validatorResult.valid) {
|
|
192
|
+
validatorResult.errors.forEach((error) =>
|
|
193
|
+
this.logger.error(error.message)
|
|
194
|
+
);
|
|
195
|
+
throw new Error(validatorResult.errors);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const { decode } = keys[key];
|
|
199
|
+
if (!_.isUndefined(decode) && !_.isFunction(decode)) {
|
|
200
|
+
const message = `entity ${entityKey} key '${key}' decode must be a function or undefined.`;
|
|
201
|
+
this.logger.error(message);
|
|
202
|
+
throw new Error(message);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
161
206
|
// Validate sharding bump values increase monotonically with keys.
|
|
162
207
|
_.some(_.toPairs(bumps), ([bump, value], i, c) => {
|
|
163
208
|
const [lastBump, lastValue] = i ? c[i - 1] : [];
|
|
@@ -240,7 +285,19 @@ export class PrivateEntityManager {
|
|
|
240
285
|
return this.config.entities[entityToken];
|
|
241
286
|
}
|
|
242
287
|
|
|
243
|
-
|
|
288
|
+
getIndexComponents(entityToken, indexToken) {
|
|
289
|
+
const { indexes } = this.getEntityConfig(entityToken);
|
|
290
|
+
|
|
291
|
+
if (!_.has(indexes, indexToken)) {
|
|
292
|
+
const message = `Index '${indexToken}' does not exist for entity '${entityToken}'.`;
|
|
293
|
+
this.logger.error(message);
|
|
294
|
+
throw new Error(message);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return indexes[indexToken];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
getKey(entityToken, keyToken) {
|
|
244
301
|
const { keys } = this.getEntityConfig(entityToken);
|
|
245
302
|
|
|
246
303
|
if (!_.has(keys, keyToken)) {
|
|
@@ -348,6 +405,20 @@ export class PrivateEntityManager {
|
|
|
348
405
|
} else return true;
|
|
349
406
|
}
|
|
350
407
|
|
|
408
|
+
/**
|
|
409
|
+
* Tests whether an item is valid.
|
|
410
|
+
*
|
|
411
|
+
* @param {string} item - Entity token.
|
|
412
|
+
* @returns {boolean} true if entityToken is valid.
|
|
413
|
+
*/
|
|
414
|
+
validateItem(item) {
|
|
415
|
+
if (!_.isPlainObject(item)) {
|
|
416
|
+
const message = `Invalid item: ${item}`;
|
|
417
|
+
this.logger.error(message);
|
|
418
|
+
throw new Error(message);
|
|
419
|
+
} else return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
351
422
|
/**
|
|
352
423
|
* Tests whether a key token is valid.
|
|
353
424
|
*
|
|
@@ -365,20 +436,6 @@ export class PrivateEntityManager {
|
|
|
365
436
|
} else return true;
|
|
366
437
|
}
|
|
367
438
|
|
|
368
|
-
/**
|
|
369
|
-
* Tests whether an item is valid.
|
|
370
|
-
*
|
|
371
|
-
* @param {string} item - Entity token.
|
|
372
|
-
* @returns {boolean} true if entityToken is valid.
|
|
373
|
-
*/
|
|
374
|
-
validateItem(item) {
|
|
375
|
-
if (!_.isPlainObject(item)) {
|
|
376
|
-
const message = `Invalid item: ${item}`;
|
|
377
|
-
this.logger.error(message);
|
|
378
|
-
throw new Error(message);
|
|
379
|
-
} else return true;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
439
|
/**
|
|
383
440
|
* Tests whether a timestamp is valid.
|
|
384
441
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karmaniverous/entity-manager",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"license": "BSD-3-Clause",
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"inspect-parameters-declaration": "^0.1.0",
|
|
23
24
|
"jsonschema": "^1.4.1",
|
|
24
25
|
"lodash": "^4.17.21",
|
|
25
26
|
"string-hash": "^1.1.3"
|