@karmaniverous/entity-manager 5.0.8 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +15 -439
  2. package/dist/cjs/_virtual/_commonjsHelpers.js +7 -0
  3. package/dist/cjs/_virtual/lz-string.js +5 -0
  4. package/dist/cjs/node_modules/@karmaniverous/entity-tools/dist/mjs/Nil.js +14 -0
  5. package/dist/cjs/node_modules/@karmaniverous/entity-tools/dist/mjs/defaultTranscodes.js +94 -0
  6. package/dist/cjs/node_modules/@karmaniverous/entity-tools/dist/mjs/sort.js +52 -0
  7. package/dist/cjs/node_modules/lz-string/libs/lz-string.js +517 -0
  8. package/dist/cjs/node_modules/radash/dist/esm/array.js +90 -0
  9. package/dist/cjs/node_modules/radash/dist/esm/async.js +62 -0
  10. package/dist/cjs/node_modules/radash/dist/esm/object.js +25 -0
  11. package/dist/cjs/node_modules/radash/dist/esm/typed.js +35 -0
  12. package/dist/cjs/node_modules/string-hash/index.js +23 -0
  13. package/dist/cjs/node_modules/tslib/tslib.es6.js +39 -0
  14. package/dist/cjs/node_modules/zod/lib/index.js +4342 -0
  15. package/dist/cjs/src/EntityManager.js +93 -0
  16. package/dist/cjs/src/ParsedConfig.js +227 -0
  17. package/dist/cjs/src/addKeys.js +47 -0
  18. package/dist/cjs/src/decodeEntityElement.js +37 -0
  19. package/dist/cjs/src/decodeGeneratedProperty.js +48 -0
  20. package/dist/cjs/src/dehydrateIndexItem.js +52 -0
  21. package/dist/cjs/src/dehydratePageKeyMap.js +75 -0
  22. package/dist/cjs/src/encodeEntityElement.js +36 -0
  23. package/dist/cjs/src/encodeGeneratedProperty.js +44 -0
  24. package/dist/cjs/src/getHashKeySpace.js +41 -0
  25. package/dist/cjs/src/getShardBump.js +24 -0
  26. package/dist/cjs/src/index.js +7 -0
  27. package/dist/cjs/src/query.js +104 -0
  28. package/dist/cjs/src/rehydrateIndexItem.js +48 -0
  29. package/dist/cjs/src/rehydratePageKeyMap.js +66 -0
  30. package/dist/cjs/src/removeKeys.js +33 -0
  31. package/dist/cjs/src/unwrapIndex.js +40 -0
  32. package/dist/cjs/src/updateItemHashKey.js +55 -0
  33. package/dist/cjs/src/updateItemRangeKey.js +45 -0
  34. package/dist/cjs/src/validateEntityGeneratedProperty.js +29 -0
  35. package/dist/cjs/src/validateEntityIndexToken.js +22 -0
  36. package/dist/cjs/src/validateEntityToken.js +17 -0
  37. package/dist/index.d.cts +826 -0
  38. package/dist/index.d.mts +826 -0
  39. package/dist/index.d.ts +826 -0
  40. package/dist/mjs/_virtual/_commonjsHelpers.js +5 -0
  41. package/dist/mjs/_virtual/lz-string.js +3 -0
  42. package/dist/mjs/node_modules/@karmaniverous/entity-tools/dist/mjs/Nil.js +12 -0
  43. package/dist/mjs/node_modules/@karmaniverous/entity-tools/dist/mjs/defaultTranscodes.js +92 -0
  44. package/dist/mjs/node_modules/@karmaniverous/entity-tools/dist/mjs/sort.js +50 -0
  45. package/dist/mjs/node_modules/lz-string/libs/lz-string.js +515 -0
  46. package/dist/mjs/node_modules/radash/dist/esm/array.js +80 -0
  47. package/dist/mjs/node_modules/radash/dist/esm/async.js +58 -0
  48. package/dist/mjs/node_modules/radash/dist/esm/object.js +22 -0
  49. package/dist/mjs/node_modules/radash/dist/esm/typed.js +28 -0
  50. package/dist/mjs/node_modules/string-hash/index.js +21 -0
  51. package/dist/mjs/node_modules/tslib/tslib.es6.js +36 -0
  52. package/dist/mjs/node_modules/zod/lib/index.js +4233 -0
  53. package/dist/mjs/src/EntityManager.js +91 -0
  54. package/dist/mjs/src/ParsedConfig.js +225 -0
  55. package/dist/mjs/src/addKeys.js +45 -0
  56. package/dist/mjs/src/decodeEntityElement.js +35 -0
  57. package/dist/mjs/src/decodeGeneratedProperty.js +46 -0
  58. package/dist/mjs/src/dehydrateIndexItem.js +50 -0
  59. package/dist/mjs/src/dehydratePageKeyMap.js +73 -0
  60. package/dist/mjs/src/encodeEntityElement.js +34 -0
  61. package/dist/mjs/src/encodeGeneratedProperty.js +42 -0
  62. package/dist/mjs/src/getHashKeySpace.js +39 -0
  63. package/dist/mjs/src/getShardBump.js +22 -0
  64. package/dist/mjs/src/index.js +1 -0
  65. package/dist/mjs/src/query.js +102 -0
  66. package/dist/mjs/src/rehydrateIndexItem.js +46 -0
  67. package/dist/mjs/src/rehydratePageKeyMap.js +64 -0
  68. package/dist/mjs/src/removeKeys.js +31 -0
  69. package/dist/mjs/src/unwrapIndex.js +38 -0
  70. package/dist/mjs/src/updateItemHashKey.js +53 -0
  71. package/dist/mjs/src/updateItemRangeKey.js +43 -0
  72. package/dist/mjs/src/validateEntityGeneratedProperty.js +27 -0
  73. package/dist/mjs/src/validateEntityIndexToken.js +20 -0
  74. package/dist/mjs/src/validateEntityToken.js +15 -0
  75. package/package.json +108 -59
  76. package/.env +0 -1
  77. package/dist/default/lib/EntityManager/EntityManager.js +0 -423
  78. package/dist/default/lib/EntityManager/PrivateEntityManager.js +0 -421
  79. package/dist/default/lib/index.js +0 -12
  80. package/dist/package.json +0 -3
  81. package/lib/EntityManager/EntityManager.js +0 -396
  82. package/lib/EntityManager/PrivateEntityManager.js +0 -436
  83. package/lib/index.js +0 -1
@@ -1,396 +0,0 @@
1
- /**
2
- * @module entity-manager
3
- */
4
-
5
- import { getParametersNames } from 'inspect-parameters-declaration';
6
- import _ from 'lodash';
7
-
8
- import { PrivateEntityManager } from './PrivateEntityManager.js';
9
-
10
- /**
11
- * Manage DynamoDb entities.
12
- *
13
- * @class
14
- */
15
- export class EntityManager {
16
- #entityManager;
17
-
18
- /**
19
- * Create an EntityManager instance.
20
- *
21
- * @param {object} options - Options object.
22
- * @param {object} [options.config] - EntityManager configuration object (see {@link https://github.com/karmaniverous/entity-manager#configuration README} for a breakdown).
23
- * @param {object} [options.logger] - Logger instance (defaults to console, must support error & debug methods).
24
- * @returns {EntityManager} EntityManager instance.
25
- * @throws {Error} If config is invalid.
26
- * @throws {Error} If logger is invalid.
27
- */
28
- constructor({ config, logger }) {
29
- this.#entityManager = new PrivateEntityManager({ config, logger });
30
- }
31
-
32
- /**
33
- * Add sharded keys to an entity item. Does not mutate original item.
34
- *
35
- * @param {string} entityToken - Entity token.
36
- * @param {object} item - Entity item.
37
- * @param {boolean} [overwrite] - Overwrite existing properties.
38
- * @returns {object} Decorated entity item.
39
- * @throws {Error} If entityToken is invalid.
40
- * @throws {Error} If item is invalid.
41
- */
42
- addKeys(entityToken, item, overwrite = false) {
43
- this.#entityManager.logger.debug(
44
- `adding sharded index keys to ${entityToken}${
45
- overwrite ? ' with overwrite' : ''
46
- }...`,
47
- { item }
48
- );
49
-
50
- // Validate item.
51
- this.#entityManager.validateItem(item);
52
-
53
- // Clone item.
54
- const newItem = _.cloneDeep(item);
55
-
56
- // Get entity config.
57
- const { shardKeyToken } = this.#entityManager;
58
- const { keys } = this.#entityManager.getEntityConfig(entityToken);
59
-
60
- // Add shardKey.
61
- if (overwrite || _.isNil(newItem[shardKeyToken])) {
62
- newItem[shardKeyToken] = this.calcShardKey(entityToken, newItem);
63
- }
64
-
65
- // Add keys.
66
- _.forEach(keys, ({ encode }, key) => {
67
- if (overwrite || _.isNil(newItem[key])) newItem[key] = encode(newItem);
68
- });
69
- this.#entityManager.logger.debug('done', { newItem });
70
-
71
- // Remove undefined shardKey.
72
- if (_.isNil(newItem[shardKeyToken])) delete newItem[shardKeyToken];
73
-
74
- return newItem;
75
- }
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|string[]} indexToken - Index token or array of key tokens.
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 = _.isArray(indexToken)
109
- ? indexToken
110
- : this.#entityManager.getIndexComponents(entityToken, indexToken);
111
-
112
- const indexProperties = indexComponents.reduce((properties, component) => {
113
- const { decode } = this.getKey(entityToken, component);
114
- if (!_.isFunction(decode))
115
- throw new Error(
116
- `no decode function defined on ${entityToken} entity key '${component}'`
117
- );
118
-
119
- return {
120
- ...properties,
121
- ...(decode(index[component] ?? '') ?? {}),
122
- };
123
- }, {});
124
-
125
- return _.sortedUniqBy(
126
- _.sortBy(_.entries(indexProperties), ([key]) => key),
127
- ([key]) => key
128
- )
129
- .map(([, value]) => value)
130
- .join(delimiter);
131
- }
132
-
133
- /**
134
- * Return the config for a given entity key token.
135
- *
136
- * @param {string} entityToken - Entity token.
137
- * @param {string} keyToken - Key token.
138
- * @returns {object} Entity key config.
139
- */
140
- getKey(entityToken, keyToken) {
141
- const { keys } = this.#entityManager.getEntityConfig(entityToken);
142
-
143
- if (!_.has(keys, keyToken))
144
- throw new Error(
145
- `Key '${keyToken}' does not exist for entity '${entityToken}'.`
146
- );
147
-
148
- return keys[keyToken];
149
- }
150
-
151
- /**
152
- * Return an array of sharded keys valid for a given entity token & timestamp.
153
- *
154
- * @param {string} entityToken - Entity token.
155
- * @param {string} keyToken - Key token.
156
- * @param {object} item - Entity item sufficiently populated to generate property keyToken.
157
- * @param {number} timestamp - Timestamp.
158
- * @returns {string[]} Array of keys.
159
- * @throws {Error} If entityToken is invalid.
160
- * @throws {Error} If item is invalid.
161
- * @throws {Error} If keyToken is invalid.
162
- * @throws {Error} If timestamp is invalid.
163
- */
164
- getKeySpace(entityToken, keyToken, item, timestamp = Date.now()) {
165
- this.#entityManager.logger.debug(
166
- `getting shard key space for ${entityToken} on key '${keyToken}' at timestamp ${timestamp}...`,
167
- { item }
168
- );
169
-
170
- const shardKeySpace = this.#entityManager.getShardKeySpace(
171
- entityToken,
172
- timestamp
173
- );
174
-
175
- const result = _.sortedUniq(
176
- shardKeySpace.map((shardKey) =>
177
- this.getKey(entityToken, keyToken).encode({
178
- ...item,
179
- [this.#entityManager.shardKeyToken]: shardKey,
180
- })
181
- )
182
- );
183
- this.#entityManager.logger.debug('done', { result });
184
-
185
- return result;
186
- }
187
-
188
- /**
189
- * @typedef {object} ShardQueryResult
190
- * @property {any[]} items - Query result array.
191
- * @property {*} pageKey - Shard page key.
192
- */
193
-
194
- /**
195
- * Shard query function
196
- *
197
- * @callback ShardQueryFunction
198
- * @param {string} shardedKey - Sharded key.
199
- * @param {*} [pageKey] - Page key.
200
- * @param {number} [limit] - Request limit.
201
- * @returns {Promise<ShardQueryResult>} Sharded query result.
202
- */
203
-
204
- /**
205
- * @typedef {object} ShardedQueryResult
206
- * @property {any[]} items - Query result array.
207
- * @property {object} pageKeys - Shard page keys.
208
- */
209
-
210
- /**
211
- * Query an entity across shards.
212
- *
213
- * @param {object} options - Query options.
214
- * @param {string} options.entityToken - Entity token.
215
- * @param {string} [options.keyToken] - Key token.
216
- * @param {object} [options.item] - Entity item sufficiently populated to generate property keyToken.
217
- * @param {ShardQueryFunction} options.shardQuery - Sharded query function.
218
- * @param {number} [options.limit] - Request limit.
219
- * @param {object} [options.pageKeys] - Map of shard page keys.
220
- * @param {number} [options.pageSize] - Request page size.
221
- * @returns {Promise<ShardedQueryResult>} Sharded query result.
222
- */
223
- async query({
224
- entityToken,
225
- keyToken = 'entityPK',
226
- item = {},
227
- shardQuery,
228
- limit,
229
- pageKeys,
230
- pageSize,
231
- } = {}) {
232
- // Validate params.
233
- this.#entityManager.validateKeyToken(entityToken, keyToken);
234
- this.#entityManager.validateItem(item);
235
-
236
- if (!_.isFunction(shardQuery))
237
- throw new Error('shardQuery must be a function');
238
-
239
- if (
240
- !_.isUndefined(limit) &&
241
- !(limit === Infinity || (_.isInteger(limit) && limit >= 1))
242
- )
243
- throw new Error('limit must be a positive integer or Infinity.');
244
-
245
- if (!_.isUndefined(pageKeys) && !_.isPlainObject(pageKeys))
246
- throw new Error('pageKeys must be an object');
247
-
248
- if (!_.isUndefined(pageSize) && !(_.isInteger(pageSize) && pageSize >= 1))
249
- throw new Error('pageSize must be a positive integer');
250
-
251
- // Apply default limit.
252
- limit ??= this.#entityManager.getEntityConfig(entityToken).defaultLimit;
253
-
254
- // Apply default pageSize.
255
- pageSize ??=
256
- this.#entityManager.getEntityConfig(entityToken).defaultPageSize;
257
-
258
- // Generate default pageKeys if not provided
259
- pageKeys ??= _.fromPairs(
260
- this.getKeySpace(entityToken, keyToken, item).map((shardedKey) => [
261
- shardedKey,
262
- undefined,
263
- ])
264
- );
265
-
266
- this.#entityManager.logger.debug('EntityManager.query parsed params', {
267
- entityToken,
268
- keyToken,
269
- item,
270
- shardQuery,
271
- limit,
272
- pageKeys,
273
- pageSize,
274
- });
275
-
276
- // Return empty result if no pageKeys.
277
- if (_.isEmpty(pageKeys)) return { count: 0, items: [], pageKeys: {} };
278
-
279
- // Iterate search over pages.
280
- let page = 0;
281
- const result = { count: 0, items: [], pageKeys };
282
-
283
- do {
284
- // TODO: This loop will blow up as shards scale, since at a minimum it will return shardCount x pageSize
285
- // items, which may be >> limit. Probably the way to fix this is to limit the number of shards queried per
286
- // iteration in order to keep shardsQueried * pageSize > (limit - items.length) but only just.
287
-
288
- // Query every shard in pageKeys.
289
- const shardQueryResults = await Promise.all(
290
- _.map(
291
- result.pageKeys,
292
- (pageKey, shardedKey) =>
293
- new Promise((resolve) =>
294
- shardQuery(shardedKey, pageKey, pageSize).then(
295
- ({ count, items, pageKey }) =>
296
- resolve({ count, items, pageKey, shardedKey })
297
- )
298
- )
299
- )
300
- );
301
-
302
- // Reduce shardQueryResults into a single result.
303
- const pageResult = shardQueryResults.reduce(
304
- (shardedQueryResult, { count, items, pageKey, shardedKey }) => ({
305
- count: shardedQueryResult.count + count,
306
- items: [...shardedQueryResult.items, ...items],
307
- pageKeys: {
308
- ...shardedQueryResult.pageKeys,
309
- ...(_.isUndefined(pageKey) ? {} : { [shardedKey]: pageKey }),
310
- },
311
- }),
312
- { count: 0, items: [], pageKeys: {} }
313
- );
314
-
315
- this.#entityManager.logger.debug(
316
- `EntityManager.query page ${page} result`,
317
- { pageResult }
318
- );
319
-
320
- result.count += pageResult.count;
321
- result.items = [...result.items, ...pageResult.items];
322
- result.pageKeys = pageResult.pageKeys;
323
- page++;
324
- } while (result.items.length < limit && !_.isEmpty(result.pageKeys));
325
-
326
- return result;
327
- }
328
-
329
- /**
330
- * Convert a delimited string into a named index key.
331
- *
332
- * @param {string} entityToken - Entity token.
333
- * @param {string|string[]} indexToken - Index token or array of key tokens.
334
- * @param {string} value - Dehydrated index value.
335
- * @param {string} [delimiter] - Delimiter.
336
- * @returns {object} Rehydrated index key.
337
- * @throws {Error} If entityToken is invalid.
338
- * @throws {Error} If indexToken is invalid.
339
- */
340
- rehydrateIndex(entityToken, indexToken, value = '', delimiter = '~') {
341
- const indexComponents = _.isArray(indexToken)
342
- ? indexToken
343
- : this.#entityManager.getIndexComponents(entityToken, indexToken);
344
-
345
- const indexKeys = _.sortBy(
346
- indexComponents.reduce((keys, component) => {
347
- const { encode } = this.getKey(entityToken, component);
348
- return [...new Set([...keys, ...getParametersNames(encode)]).values()];
349
- }, [])
350
- );
351
-
352
- const indexProperties = _.zipObject(indexKeys, value.split(delimiter));
353
-
354
- return indexComponents.reduce((index, component) => {
355
- const { encode } = this.getKey(entityToken, component);
356
- return { ...index, [component]: encode(indexProperties) };
357
- }, {});
358
- }
359
-
360
- /**
361
- * Remove sharded keys from an entity item. Does not mutate original item.
362
- *
363
- * @param {string} entityToken - Entity token.
364
- * @param {object} item - Entity item.
365
- * @returns {object} Stripped entity item.
366
- * @throws {Error} If entityToken is invalid.
367
- * @throws {Error} If item is invalid.
368
- */
369
- removeKeys(entityToken, item) {
370
- this.#entityManager.logger.debug(
371
- `removing sharded index keys from ${entityToken}...`,
372
- { item }
373
- );
374
-
375
- // Validate item.
376
- this.#entityManager.validateItem(item);
377
-
378
- // Clone item.
379
- const newItem = _.cloneDeep(item);
380
-
381
- // Get entity config.
382
- const { shardKeyToken } = this.#entityManager;
383
- const { keys } = this.#entityManager.getEntityConfig(entityToken);
384
-
385
- // Remove shardKey.
386
- delete newItem[shardKeyToken];
387
-
388
- // Remove keys.
389
- _.forEach(keys, (value, key) => {
390
- if (!value.retain) delete newItem[key];
391
- });
392
- this.#entityManager.logger.debug('done', { newItem });
393
-
394
- return newItem;
395
- }
396
- }