@karmaniverous/entity-manager 0.0.8
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/.env +1 -0
- package/README.md +298 -0
- package/dist/default/lib/EntityManager/EntityManager.js +115 -0
- package/dist/default/lib/EntityManager/PrivateEntityManager.js +408 -0
- package/dist/default/lib/index.js +12 -0
- package/dist/package.json +3 -0
- package/lib/EntityManager/EntityManager.js +113 -0
- package/lib/EntityManager/PrivateEntityManager.js +381 -0
- package/lib/index.js +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PrivateEntityManager = void 0;
|
|
7
|
+
var _isInteger2 = _interopRequireDefault(require("lodash/isInteger"));
|
|
8
|
+
var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
|
|
9
|
+
var _range2 = _interopRequireDefault(require("lodash/range"));
|
|
10
|
+
var _flatten2 = _interopRequireDefault(require("lodash/flatten"));
|
|
11
|
+
var _filter2 = _interopRequireDefault(require("lodash/filter"));
|
|
12
|
+
var _size2 = _interopRequireDefault(require("lodash/size"));
|
|
13
|
+
var _findLast2 = _interopRequireDefault(require("lodash/findLast"));
|
|
14
|
+
var _has2 = _interopRequireDefault(require("lodash/has"));
|
|
15
|
+
var _map2 = _interopRequireDefault(require("lodash/map"));
|
|
16
|
+
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
|
|
17
|
+
var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
|
|
18
|
+
var _some2 = _interopRequireDefault(require("lodash/some"));
|
|
19
|
+
var _toPairs2 = _interopRequireDefault(require("lodash/toPairs"));
|
|
20
|
+
var _sortBy2 = _interopRequireDefault(require("lodash/sortBy"));
|
|
21
|
+
var _fromPairs2 = _interopRequireDefault(require("lodash/fromPairs"));
|
|
22
|
+
var _mapValues2 = _interopRequireDefault(require("lodash/mapValues"));
|
|
23
|
+
var _jsonschema = require("jsonschema");
|
|
24
|
+
var _stringHash = _interopRequireDefault(require("string-hash"));
|
|
25
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
26
|
+
function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
|
|
27
|
+
function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
|
|
28
|
+
function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
|
|
29
|
+
function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
|
|
30
|
+
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
|
|
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); }
|
|
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; } }
|
|
33
|
+
/**
|
|
34
|
+
* EntityManager config validation schema.
|
|
35
|
+
*
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
const configSchema = {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
entities: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
patternProperties: {
|
|
44
|
+
'^\\w+$': {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
keys: {
|
|
48
|
+
type: 'object'
|
|
49
|
+
},
|
|
50
|
+
sharding: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
bumps: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
patternProperties: {
|
|
56
|
+
'^\\d+$': {
|
|
57
|
+
type: 'integer',
|
|
58
|
+
minimum: 0
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
additionalProperties: false
|
|
62
|
+
},
|
|
63
|
+
entityKey: {
|
|
64
|
+
type: 'function'
|
|
65
|
+
},
|
|
66
|
+
nibbleBits: {
|
|
67
|
+
type: 'integer',
|
|
68
|
+
minimum: 1,
|
|
69
|
+
maximum: 5
|
|
70
|
+
},
|
|
71
|
+
nibbles: {
|
|
72
|
+
type: 'integer',
|
|
73
|
+
minimum: 0
|
|
74
|
+
},
|
|
75
|
+
timestamp: {
|
|
76
|
+
type: 'function'
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
additionalProperties: false
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
additionalProperties: false
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
additionalProperties: false
|
|
86
|
+
},
|
|
87
|
+
shardKeyToken: {
|
|
88
|
+
type: 'string',
|
|
89
|
+
pattern: '^\\w+$'
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
additionalProperties: false
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Private EntityManager implementation.
|
|
97
|
+
*
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
var _config = /*#__PURE__*/new WeakMap();
|
|
101
|
+
var _logger = /*#__PURE__*/new WeakMap();
|
|
102
|
+
class PrivateEntityManager {
|
|
103
|
+
/**
|
|
104
|
+
* Create a PrivateEntityManager instance.
|
|
105
|
+
*
|
|
106
|
+
* @param {object} options - Options object.
|
|
107
|
+
* @param {object} [options.config] - EntityManager configuration object.
|
|
108
|
+
* @param {object} [options.logger] - Logger instance (defaults to console, must support error & debug methods).
|
|
109
|
+
* @throws {Error} If config is invalid.
|
|
110
|
+
* @throws {Error} If logger is invalid.
|
|
111
|
+
*/
|
|
112
|
+
constructor() {
|
|
113
|
+
let {
|
|
114
|
+
config = {},
|
|
115
|
+
logger = console
|
|
116
|
+
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
117
|
+
_classPrivateFieldInitSpec(this, _config, {
|
|
118
|
+
writable: true,
|
|
119
|
+
value: void 0
|
|
120
|
+
});
|
|
121
|
+
_classPrivateFieldInitSpec(this, _logger, {
|
|
122
|
+
writable: true,
|
|
123
|
+
value: void 0
|
|
124
|
+
});
|
|
125
|
+
// Validate logger.
|
|
126
|
+
if (!logger.error || !logger.debug) throw new Error('logger must implement error & debug methods.');
|
|
127
|
+
_classPrivateFieldSet(this, _logger, logger);
|
|
128
|
+
this.config = config;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get the current config.
|
|
133
|
+
*
|
|
134
|
+
* @returns {object} Current config.
|
|
135
|
+
*/
|
|
136
|
+
get config() {
|
|
137
|
+
return _classPrivateFieldGet(this, _config);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Set the current config.
|
|
142
|
+
*
|
|
143
|
+
* @param {object} value - Config object.
|
|
144
|
+
* @throws {Error} If config is invalid.
|
|
145
|
+
* @throws {Error} If entity bumps do not monotonically increase.
|
|
146
|
+
* @throws {Error} If entity nibbles are greater than minimum bump value.
|
|
147
|
+
*/
|
|
148
|
+
set config(value) {
|
|
149
|
+
// Validate config against schema.
|
|
150
|
+
const validatorResult = (0, _jsonschema.validate)(value, configSchema);
|
|
151
|
+
if (!validatorResult.valid) {
|
|
152
|
+
validatorResult.errors.forEach(error => this.logger.error(error.message));
|
|
153
|
+
throw new Error(validatorResult.errors);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Conform config.
|
|
157
|
+
const {
|
|
158
|
+
entities = {},
|
|
159
|
+
shardKeyToken = 'shardId'
|
|
160
|
+
} = value;
|
|
161
|
+
const conformedConfig = {
|
|
162
|
+
entities: (0, _mapValues2.default)(entities, _ref => {
|
|
163
|
+
let {
|
|
164
|
+
keys = {},
|
|
165
|
+
sharding: {
|
|
166
|
+
bumps = {},
|
|
167
|
+
entityKey,
|
|
168
|
+
nibbleBits = 1,
|
|
169
|
+
nibbles = 0,
|
|
170
|
+
timestamp
|
|
171
|
+
} = {}
|
|
172
|
+
} = _ref;
|
|
173
|
+
return {
|
|
174
|
+
keys,
|
|
175
|
+
sharding: {
|
|
176
|
+
bumps: (0, _fromPairs2.default)((0, _sortBy2.default)((0, _toPairs2.default)(bumps), 0)),
|
|
177
|
+
entityKey,
|
|
178
|
+
nibbleBits,
|
|
179
|
+
nibbles,
|
|
180
|
+
timestamp
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}),
|
|
184
|
+
shardKeyToken
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Validate entity properties.
|
|
188
|
+
(0, _some2.default)(conformedConfig.entities, (_ref2, entityToken) => {
|
|
189
|
+
let {
|
|
190
|
+
keys,
|
|
191
|
+
sharding: {
|
|
192
|
+
bumps,
|
|
193
|
+
entityKey,
|
|
194
|
+
nibbleBits,
|
|
195
|
+
nibbles,
|
|
196
|
+
timestamp
|
|
197
|
+
}
|
|
198
|
+
} = _ref2;
|
|
199
|
+
// Validate entity keys are functions or undefined.
|
|
200
|
+
(0, _some2.default)(keys, (value, key) => {
|
|
201
|
+
if (!(0, _isNil2.default)(value) && !(0, _isFunction2.default)(value)) {
|
|
202
|
+
const message = `${entityToken} key '${key}' must be a function or nil.`;
|
|
203
|
+
this.logger.error(message);
|
|
204
|
+
throw new Error(message);
|
|
205
|
+
} else return false;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Validate sharding bump values increase monotonically with keys.
|
|
209
|
+
(0, _some2.default)((0, _toPairs2.default)(bumps), (_ref3, i, c) => {
|
|
210
|
+
let [bump, value] = _ref3;
|
|
211
|
+
const [lastBump, lastValue] = i ? c[i - 1] : [];
|
|
212
|
+
if (value <= lastValue) {
|
|
213
|
+
const message = `${entityToken} sharding bumps do not monotonically increase from '${lastBump}: ${lastValue}' to '${bump}: ${value}'.)`;
|
|
214
|
+
this.logger.error(message);
|
|
215
|
+
throw new Error(message);
|
|
216
|
+
} else return false;
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Validate sharding entityKey is a function or undefined.
|
|
220
|
+
if (!(0, _isNil2.default)(entityKey) && !(0, _isFunction2.default)(entityKey)) {
|
|
221
|
+
const message = `${entityToken} sharding entityKey must be a function or nil`;
|
|
222
|
+
this.logger.error(message);
|
|
223
|
+
throw new Error(message);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Validate sharding nibbles do not exceed 32 bits.
|
|
227
|
+
if (nibbles * nibbleBits > 32) {
|
|
228
|
+
const message = `${entityToken} nibbles (${nibbles} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`;
|
|
229
|
+
this.logger.error(message);
|
|
230
|
+
throw new Error(message);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Validate sharding nibbles are less than first bump value.
|
|
234
|
+
const firstBumpValue = (0, _map2.default)(bumps)[0];
|
|
235
|
+
if (nibbles >= firstBumpValue) {
|
|
236
|
+
const message = `${entityToken} nibbles (${nibbles}) not less than minimum bump value (${firstBumpValue})`;
|
|
237
|
+
this.logger.error(message);
|
|
238
|
+
throw new Error(message);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Validate last bump value does not exceed 32 bits.
|
|
242
|
+
const lastBumpValue = (0, _map2.default)(bumps).slice(-1);
|
|
243
|
+
if (lastBumpValue * nibbleBits > 32) {
|
|
244
|
+
const message = `${entityToken} maximum bump value (${lastBumpValue} nibbles at ${nibbleBits} nibbleBits) exceed 32 bits`;
|
|
245
|
+
this.logger.error(message);
|
|
246
|
+
throw new Error(message);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Validate sharding timestamp is a function or undefined.
|
|
250
|
+
if (!(0, _isNil2.default)(timestamp) && !(0, _isFunction2.default)(timestamp)) {
|
|
251
|
+
const message = `${entityToken} sharding timestamp must be a function or nil.`;
|
|
252
|
+
this.logger.error(message);
|
|
253
|
+
throw new Error(message);
|
|
254
|
+
} else return false;
|
|
255
|
+
});
|
|
256
|
+
_classPrivateFieldSet(this, _config, conformedConfig);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get logger instance.
|
|
261
|
+
*
|
|
262
|
+
* @returns {object} Logger instance.
|
|
263
|
+
*/
|
|
264
|
+
get logger() {
|
|
265
|
+
return _classPrivateFieldGet(this, _logger);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get shard key token.
|
|
270
|
+
*
|
|
271
|
+
* @returns {string} Shard key token.
|
|
272
|
+
*/
|
|
273
|
+
get shardKeyToken() {
|
|
274
|
+
return _classPrivateFieldGet(this, _config).shardKeyToken;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get entity config.
|
|
279
|
+
*
|
|
280
|
+
* @param {string} entityToken - Entity token.
|
|
281
|
+
* @returns {object} Entity config.
|
|
282
|
+
* @throws {Error} If entityToken is invalid.
|
|
283
|
+
*/
|
|
284
|
+
getEntityConfig(entityToken) {
|
|
285
|
+
this.validateEntityToken(entityToken);
|
|
286
|
+
return this.config.entities[entityToken];
|
|
287
|
+
}
|
|
288
|
+
getKeyGenerator(entityToken, keyToken) {
|
|
289
|
+
const {
|
|
290
|
+
keys
|
|
291
|
+
} = this.getEntityConfig(entityToken);
|
|
292
|
+
if (!(0, _has2.default)(keys, keyToken)) {
|
|
293
|
+
const message = `Key '${keyToken}' does not exist for entity '${entityToken}'.`;
|
|
294
|
+
this.logger.error(message);
|
|
295
|
+
throw new Error(message);
|
|
296
|
+
}
|
|
297
|
+
return keys[keyToken];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get the number of nibbles & nibbleBits for a given entityToken at a given timestamp.
|
|
302
|
+
*
|
|
303
|
+
* @param {string} entityToken - Entity token.
|
|
304
|
+
* @param {number} [timestamp] - Timestamp in milliseconds (defaults to current time).
|
|
305
|
+
* @returns {{nibbleBits: number, nibbles: number}} Result object.
|
|
306
|
+
*/
|
|
307
|
+
getNibbles(entityToken) {
|
|
308
|
+
let timestamp = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Date.now();
|
|
309
|
+
const {
|
|
310
|
+
nibbleBits,
|
|
311
|
+
nibbles,
|
|
312
|
+
bumps
|
|
313
|
+
} = this.getEntityConfig(entityToken).sharding;
|
|
314
|
+
this.validateTimestamp(timestamp);
|
|
315
|
+
return {
|
|
316
|
+
nibbleBits,
|
|
317
|
+
nibbles: (0, _findLast2.default)(bumps, (value, key) => key <= timestamp) ?? nibbles
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Return a shard key for a given entity token, entity id & timestamp.
|
|
323
|
+
*
|
|
324
|
+
* @param {string} entityToken - Entity token.
|
|
325
|
+
* @param {string} entityKey - Entity id.
|
|
326
|
+
* @param {number} [timestamp] - Timestamp in milliseconds (defaults to current time).
|
|
327
|
+
* @returns {string} shard key.
|
|
328
|
+
*/
|
|
329
|
+
getShardKey(entityToken, entityKey) {
|
|
330
|
+
let timestamp = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Date.now();
|
|
331
|
+
// Get nibbles for entityToken at timestamp (validates entityToken)
|
|
332
|
+
const {
|
|
333
|
+
nibbleBits,
|
|
334
|
+
nibbles
|
|
335
|
+
} = this.getNibbles(entityToken, timestamp);
|
|
336
|
+
|
|
337
|
+
// Calculate shardKey.
|
|
338
|
+
const radix = 2 ** nibbleBits;
|
|
339
|
+
const shardKey = nibbles ? ((0, _stringHash.default)(entityKey) % (nibbles * radix)).toString(radix).padStart(nibbles, '0') : undefined;
|
|
340
|
+
if ((0, _isNil2.default)(shardKey)) this.logger.debug(`no shard key generated for ${entityToken} id '${entityKey}' at timestamp ${timestamp}.`);else this.logger.debug(`generated shard key '${shardKey}' for ${entityToken} id '${entityKey}' at timestamp ${timestamp}.`);
|
|
341
|
+
return shardKey;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Return an array of shard keys valid for a given entity token & timestamp.
|
|
346
|
+
*
|
|
347
|
+
* @param {string} entityToken - Entity token.
|
|
348
|
+
* @param {number} [timestamp] - Timestamp in milliseconds (defaults to current time).
|
|
349
|
+
* @returns {string[]} shard key space.
|
|
350
|
+
*/
|
|
351
|
+
getShardKeySpace(entityToken) {
|
|
352
|
+
let timestamp = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Date.now();
|
|
353
|
+
const {
|
|
354
|
+
nibbleBits,
|
|
355
|
+
nibbles,
|
|
356
|
+
bumps
|
|
357
|
+
} = this.getEntityConfig(entityToken).sharding;
|
|
358
|
+
if (!nibbles && !(0, _size2.default)(bumps)) return [];
|
|
359
|
+
this.validateTimestamp(timestamp);
|
|
360
|
+
const nibbleSpace = [nibbles, ...(0, _map2.default)((0, _filter2.default)(bumps, (value, key) => key <= timestamp))];
|
|
361
|
+
const radix = 2 ** nibbleBits;
|
|
362
|
+
const shardKeySpace = (0, _flatten2.default)((0, _map2.default)(nibbleSpace, nibbles => {
|
|
363
|
+
return nibbles ? (0, _range2.default)(0, radix ** nibbles).map(nibble => nibble.toString(radix).padStart(nibbles, '0')) : undefined;
|
|
364
|
+
}));
|
|
365
|
+
return shardKeySpace;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Tests whether an entityToken is valid.
|
|
370
|
+
*
|
|
371
|
+
* @param {string} entityToken - Entity token.
|
|
372
|
+
* @returns {boolean} true if entityToken is valid.
|
|
373
|
+
*/
|
|
374
|
+
validateEntityToken(entityToken) {
|
|
375
|
+
if (!this.config.entities[entityToken]) {
|
|
376
|
+
const message = `Invalid entityToken: ${entityToken}`;
|
|
377
|
+
this.logger.error(message);
|
|
378
|
+
throw new Error(message);
|
|
379
|
+
} else return true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Tests whether an item is valid.
|
|
384
|
+
*
|
|
385
|
+
* @param {string} item - Entity token.
|
|
386
|
+
* @returns {boolean} true if entityToken is valid.
|
|
387
|
+
*/
|
|
388
|
+
validateItem(item) {
|
|
389
|
+
if (!(0, _isPlainObject2.default)(item)) {
|
|
390
|
+
const message = `Invalid item: ${item}`;
|
|
391
|
+
this.logger.error(message);
|
|
392
|
+
throw new Error(message);
|
|
393
|
+
} else return true;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Tests whether a timestamp is valid.
|
|
398
|
+
*
|
|
399
|
+
* @param {number} timestamp - timestamp.
|
|
400
|
+
* @param {boolean} [future=false] - true if timestamp must be in the future.
|
|
401
|
+
* @returns {boolean} true if timestamp is valid.
|
|
402
|
+
*/
|
|
403
|
+
validateTimestamp(timestamp) {
|
|
404
|
+
let future = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
405
|
+
if (!(0, _isInteger2.default)(timestamp) || future && timestamp <= Date.now()) throw new Error(`invalid timestamp (must be an integer${future ? ' in the future' : ''})`);else return true;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
exports.PrivateEntityManager = PrivateEntityManager;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "EntityManager", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _EntityManager.EntityManager;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _EntityManager = require("./EntityManager/EntityManager.js");
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module entity-manager
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import _ from 'lodash';
|
|
6
|
+
|
|
7
|
+
import { PrivateEntityManager } from './PrivateEntityManager.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Manage DynamoDb entities.
|
|
11
|
+
*
|
|
12
|
+
* @class
|
|
13
|
+
*/
|
|
14
|
+
export class EntityManager {
|
|
15
|
+
#entityManager;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create an EntityManager instance.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} options - Options object.
|
|
21
|
+
* @param {object} [options.config] - EntityManager configuration object (see {@link https://github.com/karmaniverous/entity-manager#configuration README} for a breakdown).
|
|
22
|
+
* @param {object} [options.logger] - Logger instance (defaults to console, must support error & debug methods).
|
|
23
|
+
* @returns {EntityManager} EntityManager instance.
|
|
24
|
+
* @throws {Error} If config is invalid.
|
|
25
|
+
* @throws {Error} If logger is invalid.
|
|
26
|
+
*/
|
|
27
|
+
constructor({ config, logger }) {
|
|
28
|
+
this.#entityManager = new PrivateEntityManager({ config, logger });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Decorate an entity item with keys.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} entityToken - Entity token.
|
|
35
|
+
* @param {object} item - Entity item.
|
|
36
|
+
* @param {boolean} [overwrite=false] - Overwrite existing properties.
|
|
37
|
+
* @returns {object} Decorated entity item.
|
|
38
|
+
* @throws {Error} If entityToken is invalid.
|
|
39
|
+
* @throws {Error} If item is invalid.
|
|
40
|
+
*/
|
|
41
|
+
addKeys(entityToken, item, overwrite = false) {
|
|
42
|
+
this.#entityManager.logger.debug(
|
|
43
|
+
`adding sharded index keys to ${entityToken}${
|
|
44
|
+
overwrite ? ' with overwrite' : ''
|
|
45
|
+
}...`,
|
|
46
|
+
item
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Get entity config.
|
|
50
|
+
const { shardKeyToken } = this.#entityManager;
|
|
51
|
+
const { keys, sharding } = this.#entityManager.getEntityConfig(entityToken);
|
|
52
|
+
|
|
53
|
+
// Add shardKey.
|
|
54
|
+
this.#entityManager.validateItem(item);
|
|
55
|
+
if (overwrite || _.isNil(item[shardKeyToken])) {
|
|
56
|
+
const entityKey = sharding.entityKey(item);
|
|
57
|
+
const timestamp = sharding.timestamp(item);
|
|
58
|
+
item[shardKeyToken] = this.#entityManager.getShardKey(
|
|
59
|
+
entityToken,
|
|
60
|
+
entityKey,
|
|
61
|
+
timestamp
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add keys.
|
|
66
|
+
_.forEach(keys, (getValue, key) => {
|
|
67
|
+
if (overwrite || _.isNil(item[key])) item[key] = getValue(item);
|
|
68
|
+
});
|
|
69
|
+
this.#entityManager.logger.debug('done', item);
|
|
70
|
+
|
|
71
|
+
return item;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Return an array of sharded keys valid for a given entity token & timestamp.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} entityToken - Entity token.
|
|
78
|
+
* @param {object} item - Entity item.
|
|
79
|
+
* @param {string} keyToken - Key token.
|
|
80
|
+
* @param {number} timestamp - Timestamp.
|
|
81
|
+
* @returns {string[]} Array of keys.
|
|
82
|
+
* @throws {Error} If entityToken is invalid.
|
|
83
|
+
* @throws {Error} If item is invalid.
|
|
84
|
+
* @throws {Error} If keyToken is invalid.
|
|
85
|
+
* @throws {Error} If timestamp is invalid.
|
|
86
|
+
*/
|
|
87
|
+
getKeySpace(entityToken, item, keyToken, timestamp = Date.now()) {
|
|
88
|
+
this.#entityManager.logger.debug(
|
|
89
|
+
`getting shard key space for ${entityToken} on key '${keyToken}' at timestamp ${timestamp}...`,
|
|
90
|
+
item
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const shardKeySpace = this.#entityManager.getShardKeySpace(
|
|
94
|
+
entityToken,
|
|
95
|
+
timestamp
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const result = _.sortedUniq(
|
|
99
|
+
shardKeySpace.map((shardKey) =>
|
|
100
|
+
this.#entityManager.getKeyGenerator(
|
|
101
|
+
entityToken,
|
|
102
|
+
keyToken
|
|
103
|
+
)({
|
|
104
|
+
...item,
|
|
105
|
+
[this.#entityManager.shardKeyToken]: shardKey,
|
|
106
|
+
})
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
this.#entityManager.logger.debug('done', result);
|
|
110
|
+
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
}
|