@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
@@ -0,0 +1,39 @@
1
+ import { validateEntityToken } from './validateEntityToken.js';
2
+ import { range } from '../node_modules/radash/dist/esm/array.js';
3
+
4
+ /**
5
+ * Return an array of {@link ConfigKeys.hashKey | `entityManager.config.hashKey`} property values covering the shard space bounded by `timestampFrom` & `timestampTo`.
6
+ *
7
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
8
+ * @param entityToken - {@link ConfigKeys.entities | `entityManager.config.entities`} key.
9
+ * @param timestampFrom - Lower timestanp limit. Defaults to `0`.
10
+ * @param timestampTo - Upper timestamp limit. Defaults to `Date.now()`.
11
+ *
12
+ * @returns Array of {@link ConfigKeys.hashKey | `entityManager.config.hashKey`} property values covering the indicated shard space.
13
+ *
14
+ * @throws `Error` if `entityToken` is invalid.
15
+ */
16
+ function getHashKeySpace(entityManager, entityToken, timestampFrom = 0, timestampTo = Date.now()) {
17
+ try {
18
+ // Validate params.
19
+ validateEntityToken(entityManager, entityToken);
20
+ const { shardBumps } = entityManager.config.entities[entityToken];
21
+ const hashKeySpace = shardBumps
22
+ .filter((bump, i) => (i === shardBumps.length - 1 ||
23
+ shardBumps[i + 1].timestamp > timestampFrom) &&
24
+ bump.timestamp <= timestampTo)
25
+ .flatMap(({ charBits, chars }) => {
26
+ const radix = 2 ** charBits;
27
+ return chars
28
+ ? [...range(0, radix ** chars - 1)].map((char) => char.toString(radix).padStart(chars, '0'))
29
+ : '';
30
+ })
31
+ .map((shardKey) => `${entityToken}${entityManager.config.shardKeyDelimiter}${shardKey}`);
32
+ return hashKeySpace;
33
+ }
34
+ catch (error) {
35
+ throw error;
36
+ }
37
+ }
38
+
39
+ export { getHashKeySpace };
@@ -0,0 +1,22 @@
1
+ import { validateEntityToken } from './validateEntityToken.js';
2
+
3
+ /**
4
+ * Get first entity shard bump before timestamp.
5
+ *
6
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
7
+ * @param entityToken - {@link ConfigKeys.entities | `this.config.entities`} key.
8
+ * @param timestamp - Timestamp in milliseconds.
9
+ *
10
+ * @returns {@link ShardBump | `ShardBump`} object.
11
+ *
12
+ * @throws `Error` if `entityToken` is invalid.
13
+ */
14
+ function getShardBump(entityManager, entityToken, timestamp) {
15
+ // Validate params.
16
+ validateEntityToken(entityManager, entityToken);
17
+ return [...entityManager.config.entities[entityToken].shardBumps]
18
+ .reverse()
19
+ .find((bump) => bump.timestamp <= timestamp);
20
+ }
21
+
22
+ export { getShardBump };
@@ -0,0 +1 @@
1
+ export { EntityManager } from './EntityManager.js';
@@ -0,0 +1,102 @@
1
+ import { sort } from '../node_modules/@karmaniverous/entity-tools/dist/mjs/sort.js';
2
+ import lzString from '../node_modules/lz-string/libs/lz-string.js';
3
+ import { dehydratePageKeyMap } from './dehydratePageKeyMap.js';
4
+ import { rehydratePageKeyMap } from './rehydratePageKeyMap.js';
5
+ import { validateEntityGeneratedProperty } from './validateEntityGeneratedProperty.js';
6
+ import { isInt } from '../node_modules/radash/dist/esm/typed.js';
7
+ import { parallel } from '../node_modules/radash/dist/esm/async.js';
8
+ import { unique } from '../node_modules/radash/dist/esm/array.js';
9
+
10
+ const { compressToEncodedURIComponent, decompressFromEncodedURIComponent } = lzString;
11
+ /**
12
+ * Query a database entity across shards in a provider-generic fashion.
13
+ *
14
+ * @remarks
15
+ * The provided {@link ShardQueryFunction | `ShardQueryFunction`} performs the actual query of individual data pages on individual shards. This function is presumed to express provider-specific query logic, including any necessary indexing or search constraints.
16
+ *
17
+ * Individual shard query results will be combined, deduped by {@link ConfigEntity.uniqueProperty} property value, and sorted by {@link QueryOptions.sortOrder | `sortOrder`}.
18
+ *
19
+ * In queries on sharded data, expect the leading and trailing edges of returned data pages to interleave somewhat with preceding & following pages.
20
+ *
21
+ * Unsharded query results should sort & page as expected.
22
+ *
23
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
24
+ * @param options - {@link QueryOptions | `QueryOptions`} object.
25
+ *
26
+ * @returns {@link QueryResult} object.
27
+ *
28
+ * @throws Error if {@link QueryOptions.pageKeyMap | `pageKeyMap`} keys do not match {@link QueryOptions.queryMap | `queryMap`} keys.
29
+ */
30
+ async function query(entityManager, { entityToken, hashKey, item, limit, pageKeyMap, pageSize, queryMap, sortOrder = [], timestampFrom = 0, timestampTo = Date.now(), throttle = entityManager.config.throttle, }) {
31
+ try {
32
+ // Get defaults.
33
+ const { defaultLimit, defaultPageSize } = entityManager.config.entities[entityToken];
34
+ limit ?? (limit = defaultLimit);
35
+ pageSize ?? (pageSize = defaultPageSize);
36
+ // Validate params.
37
+ validateEntityGeneratedProperty(entityManager, entityToken, hashKey, true);
38
+ if (!(limit === Infinity || (isInt(limit) && limit >= 1)))
39
+ throw new Error('limit must be a positive integer or Infinity.');
40
+ if (!(isInt(pageSize) && pageSize >= 1))
41
+ throw new Error('pageSize must be a positive integer');
42
+ // Rehydrate pageKeyMap.
43
+ const rehydratedPageKeyMap = rehydratePageKeyMap(entityManager, pageKeyMap
44
+ ? JSON.parse(decompressFromEncodedURIComponent(pageKeyMap))
45
+ : undefined, entityToken, Object.keys(queryMap), timestampFrom, timestampTo);
46
+ // Shortcut if pageKeyMap is empty.
47
+ if (!Object.keys(rehydratedPageKeyMap).length)
48
+ return {
49
+ count: 0,
50
+ items: [],
51
+ pageKeyMap: compressToEncodedURIComponent(JSON.stringify([])),
52
+ };
53
+ // Iterate search over pages.
54
+ let workingResult = {
55
+ items: [],
56
+ pageKeyMap: rehydratedPageKeyMap,
57
+ };
58
+ do {
59
+ // TODO: This loop will blow up as shards scale, since at a minimum it will return shardCount * pageSize
60
+ // items, which may be >> limit. Probably the way to fix entityManager is to limit the number of shards queried per
61
+ // iteration in order to keep shardsQueried * pageSize > (limit - items.length) but only just.
62
+ // TODO: Test for invalid characters (path delimiters) in index keys & shard key values.
63
+ // Query every shard on every index in pageKeyMap.
64
+ const shardQueryResults = await parallel(throttle, Object.entries(rehydratedPageKeyMap).flatMap(([index, indexPageKeys]) => Object.entries(indexPageKeys).map(([hashKey, pageKey]) => [
65
+ index,
66
+ hashKey,
67
+ pageKey,
68
+ ])), async ([index, hashKey, pageKey]) => ({
69
+ index,
70
+ queryResult: await queryMap[index](hashKey, pageKey, pageSize),
71
+ hashKey,
72
+ }));
73
+ // Reduce shardQueryResults & updateworkingRresult.
74
+ workingResult = shardQueryResults.reduce(({ items, pageKeyMap }, { index, queryResult, hashKey }) => {
75
+ Object.assign(rehydratedPageKeyMap[index], {
76
+ [hashKey]: queryResult.pageKey,
77
+ });
78
+ return {
79
+ items: [...items, ...queryResult.items],
80
+ pageKeyMap,
81
+ };
82
+ }, workingResult);
83
+ } while (
84
+ // Repeat while pages remain & limit is not reached.
85
+ Object.values(workingResult.pageKeyMap).some((indexPageKeys) => Object.values(indexPageKeys).some((pageKey) => pageKey !== undefined)) &&
86
+ workingResult.items.length < limit);
87
+ // Dedupe & sort working result.
88
+ workingResult.items = sort(unique(workingResult.items, (item) => item[entityManager.config.entities[entityToken]
89
+ .uniqueProperty].toString()), sortOrder);
90
+ const result = {
91
+ count: workingResult.items.length,
92
+ items: workingResult.items,
93
+ pageKeyMap: compressToEncodedURIComponent(JSON.stringify(dehydratePageKeyMap(entityManager, workingResult.pageKeyMap, entityToken))),
94
+ };
95
+ return result;
96
+ }
97
+ catch (error) {
98
+ throw error;
99
+ }
100
+ }
101
+
102
+ export { query };
@@ -0,0 +1,46 @@
1
+ import { decodeEntityElement } from './decodeEntityElement.js';
2
+ import { unwrapIndex } from './unwrapIndex.js';
3
+ import { validateEntityIndexToken } from './validateEntityIndexToken.js';
4
+ import { shake } from '../node_modules/radash/dist/esm/object.js';
5
+ import { zipToObject } from '../node_modules/radash/dist/esm/array.js';
6
+
7
+ /**
8
+ * Convert a delimited string into a partial {@link ItemMap | `ItemMap`} object representing the ungenerated component elements of a Config entity index.
9
+ *
10
+ * @remarks
11
+ * Reverses {@link EntityManager.dehydrateIndexItem | `dehydrateIndexItem`}.
12
+ *
13
+ * {@link EntityManager.dehydrateIndexItem | `dehydrateIndexItem`} alphebetically sorts unwrapped index elements during the dehydration process. This method assumes delimited element values are presented in the same order.
14
+ *
15
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
16
+ * @param dehydrated - Dehydrated index value.
17
+ * @param entityToken - {@link ConfigKeys.entities | `entityManager.config.entities`} key.
18
+ * @param indexToken - {@link ConfigEntity.indexes | `entityManager.config.entities.<entityToken>.indexes`} key.
19
+ * @param omit - Array of index components omitted from `dehydrated`.
20
+ *
21
+ * @returns Partial {@link ItemMap | `ItemMap`} object containing rehydrated index component elements.
22
+ *
23
+ * @throws `Error` if `entityToken` is invalid.
24
+ * @throws `Error` if `indexToken` is invalid.
25
+ */
26
+ function rehydrateIndexItem(entityManager, dehydrated, entityToken, indexToken, omit = []) {
27
+ try {
28
+ const { generatedKeyDelimiter } = entityManager.config;
29
+ // Validate params.
30
+ validateEntityIndexToken(entityManager, entityToken, indexToken);
31
+ // Unwrap index elements.
32
+ const elements = unwrapIndex(entityManager, entityToken, indexToken).filter((element) => !omit.includes(element));
33
+ // Split dehydrated value & validate.
34
+ const values = dehydrated.split(generatedKeyDelimiter);
35
+ if (elements.length !== values.length)
36
+ throw new Error('index rehydration key-value mismatch');
37
+ // Assign values to elements.
38
+ const rehydrated = shake(zipToObject(elements, values.map((value, i) => decodeEntityElement(entityManager, value, entityToken, elements[i]))));
39
+ return rehydrated;
40
+ }
41
+ catch (error) {
42
+ throw error;
43
+ }
44
+ }
45
+
46
+ export { rehydrateIndexItem };
@@ -0,0 +1,64 @@
1
+ import { encodeGeneratedProperty } from './encodeGeneratedProperty.js';
2
+ import { getHashKeySpace } from './getHashKeySpace.js';
3
+ import { rehydrateIndexItem } from './rehydrateIndexItem.js';
4
+ import { updateItemRangeKey } from './updateItemRangeKey.js';
5
+ import { validateEntityIndexToken } from './validateEntityIndexToken.js';
6
+ import { range, zipToObject, cluster } from '../node_modules/radash/dist/esm/array.js';
7
+ import { mapValues } from '../node_modules/radash/dist/esm/object.js';
8
+
9
+ /**
10
+ * Rehydrate an array of dehydrated page keys into a {@link PageKeyMap | `PageKeyMap`} object.
11
+ *
12
+ * Reverses the {@link EntityManager.dehydratePageKeyMap | `dehydratePageKeyMap`} method.
13
+ *
14
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
15
+ * @param dehydrated - Array of dehydrated page keys or undefined if new query.
16
+ * @param entityToken - {@link ConfigKeys.entities | `entityManager.config.entities`} key.
17
+ * @param indexTokens - Array of {@link ConfigEntity.indexes | `entityManager.config.entities.<entityToken>.indexes`} keys used as keys of the original {@link PageKeyMap | `PageKeyMap`}.
18
+ * @param timestampFrom - Lower timestanp limit used to generate the original {@link PageKeyMap | `PageKeyMap`}. Defaults to `0`.
19
+ * @param timestampTo - Upper timestamp limit used to generate the original {@link PageKeyMap | `PageKeyMap`}. Defaults to `Date.now()`.
20
+ *
21
+ * @returns Rehydrated {@link PageKeyMap | `PageKeyMap`} object.
22
+ *
23
+ * @throws `Error` if `entityToken` is invalid.
24
+ * @throws `Error` if `indexTokens` is empty.
25
+ * @throws `Error` if any `indexTokens` are invalid.
26
+ * @throws `Error` if `dehydrated` has invalid length.
27
+ */
28
+ function rehydratePageKeyMap(entityManager, dehydrated, entityToken, indexTokens, timestampFrom = 0, timestampTo = Date.now()) {
29
+ try {
30
+ // Validate params.
31
+ if (!indexTokens.length)
32
+ throw new Error('indexTokens empty');
33
+ indexTokens.map((index) => validateEntityIndexToken(entityManager, entityToken, index));
34
+ // Shortcut empty dehydrated.
35
+ if (dehydrated && !dehydrated.length)
36
+ return {};
37
+ // Get hash key space.
38
+ const hashKeySpace = getHashKeySpace(entityManager, entityToken, timestampFrom, timestampTo);
39
+ // Default dehydrated.
40
+ dehydrated ?? (dehydrated = [...range(1, hashKeySpace.length * indexTokens.length, '')]);
41
+ // Validate dehydrated length
42
+ if (dehydrated.length !== hashKeySpace.length * indexTokens.length)
43
+ throw new Error('dehydrated length mismatch');
44
+ // Rehydrate pageKeys.
45
+ const rehydrated = mapValues(zipToObject(indexTokens, cluster(dehydrated, hashKeySpace.length)), (dehydratedIndexPageKeyMaps, index) => zipToObject(hashKeySpace, (hashKey, i) => {
46
+ if (!dehydratedIndexPageKeyMaps[i])
47
+ return;
48
+ const item = {
49
+ [entityManager.config.hashKey]: hashKey,
50
+ ...rehydrateIndexItem(entityManager, dehydratedIndexPageKeyMaps[i], entityToken, index, [entityManager.config.hashKey]),
51
+ };
52
+ updateItemRangeKey(entityManager, item, entityToken);
53
+ return zipToObject(entityManager.config.entities[entityToken].indexes[index], (component) => entityManager.config.entities[entityToken].generated[component]
54
+ ? encodeGeneratedProperty(entityManager, item, entityToken, component)
55
+ : item[component]);
56
+ }));
57
+ return rehydrated;
58
+ }
59
+ catch (error) {
60
+ throw error;
61
+ }
62
+ }
63
+
64
+ export { rehydratePageKeyMap };
@@ -0,0 +1,31 @@
1
+ import { validateEntityToken } from './validateEntityToken.js';
2
+
3
+ /**
4
+ * Strips generated properties, hash key, and range key from an {@link ItemMap | `ItemMap`} object. Mutates `item`.
5
+ *
6
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
7
+ * @param item - {@link ItemMap | `ItemMap`} object.
8
+ * @param entityToken - {@link ConfigKeys.entities | `entityManager.config.entities`} key.
9
+ *
10
+ * @returns Mutated `item` without generated properties, hash key or range key.
11
+ *
12
+ * @throws `Error` if `entityToken` is invalid.
13
+ */
14
+ function removeKeys(entityManager, item, entityToken) {
15
+ try {
16
+ // Validate params.
17
+ validateEntityToken(entityManager, entityToken);
18
+ // Delete hash & range keys.
19
+ delete item[entityManager.config.hashKey];
20
+ delete item[entityManager.config.rangeKey];
21
+ // Delete generated properties.
22
+ for (const property in entityManager.config.entities[entityToken].generated)
23
+ delete item[property];
24
+ return item;
25
+ }
26
+ catch (error) {
27
+ throw error;
28
+ }
29
+ }
30
+
31
+ export { removeKeys };
@@ -0,0 +1,38 @@
1
+ import { validateEntityIndexToken } from './validateEntityIndexToken.js';
2
+ import { shake } from '../node_modules/radash/dist/esm/object.js';
3
+
4
+ /**
5
+ * Unwraps an {@link ConfigEntity.indexes | Entity index} into deduped, sorted, ungenerated index component elements.
6
+ *
7
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
8
+ * @param entityToken - {@link ConfigKeys.entities | `entityManager.config.entities`} key.
9
+ * @param indexToken - {@link ConfigEntity.indexes | `entityManager.config.entities.<entityToken>.indexes`} key.
10
+ *
11
+ * @returns Deduped, sorted array of ungenerated index component elements.
12
+ *
13
+ * @throws `Error` if `entityToken` is invalid.
14
+ * @throws `Error` if `indexToken` is invalid.
15
+ */
16
+ function unwrapIndex(entityManager, entityToken, indexToken) {
17
+ try {
18
+ // Validate params.
19
+ validateEntityIndexToken(entityManager, entityToken, indexToken);
20
+ const generated = entityManager.config.entities[entityToken].generated;
21
+ const generatedKeys = Object.keys(shake(generated));
22
+ return entityManager.config.entities[entityToken].indexes[indexToken]
23
+ .map((component) => component === entityManager.config.hashKey
24
+ ? entityManager.config.hashKey
25
+ : component === entityManager.config.rangeKey
26
+ ? entityManager.config.entities[entityToken].uniqueProperty
27
+ : generatedKeys.includes(component)
28
+ ? generated[component].elements
29
+ : component)
30
+ .flat()
31
+ .sort();
32
+ }
33
+ catch (error) {
34
+ throw error;
35
+ }
36
+ }
37
+
38
+ export { unwrapIndex };
@@ -0,0 +1,53 @@
1
+ import { isNil } from '../node_modules/@karmaniverous/entity-tools/dist/mjs/Nil.js';
2
+ import stringHash from '../node_modules/string-hash/index.js';
3
+ import { getShardBump } from './getShardBump.js';
4
+ import { validateEntityToken } from './validateEntityToken.js';
5
+
6
+ /**
7
+ * Update the hash key on a partial {@link ItemMap | `ItemMap`} object. Mutates `item`.
8
+ *
9
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
10
+ * @param item - Partial {@link ItemMap | `ItemMap`} object.
11
+ * @param entityToken - {@link ConfigKeys.entities | `this.config.entities`} key.
12
+ * @param overwrite - Overwrite existing {@link ConfigKeys.hashKey | `this.config.hashKey`} property value (default `false`).
13
+ *
14
+ * @returns Mutated `item` with updated hash key.
15
+ *
16
+ * @throws `Error` if `entityToken` is invalid.
17
+ */
18
+ function updateItemHashKey(entityManager, item, entityToken, overwrite = false) {
19
+ try {
20
+ // Validate params.
21
+ validateEntityToken(entityManager, entityToken);
22
+ // Return current item if hashKey exists and overwrite is false.
23
+ if (item[entityManager.config.hashKey] && !overwrite) {
24
+ return item;
25
+ }
26
+ // Get item timestamp property & validate.
27
+ const timestamp = item[entityManager.config.entities[entityToken].timestampProperty];
28
+ if (isNil(timestamp))
29
+ throw new Error(`missing item timestamp property`);
30
+ // Find first entity sharding bump before timestamp.
31
+ const { charBits, chars } = getShardBump(entityManager, entityToken, timestamp);
32
+ let hashKey = `${entityToken}${entityManager.config.shardKeyDelimiter}`;
33
+ if (chars) {
34
+ // Radix is the numerical base of the shardKey.
35
+ const radix = 2 ** charBits;
36
+ // Get item unique property & validate.
37
+ const uniqueId = item[entityManager.config.entities[entityToken]
38
+ .uniqueProperty];
39
+ if (isNil(uniqueId))
40
+ throw new Error(`missing item unique property`);
41
+ hashKey += (stringHash(uniqueId.toString()) % (chars * radix))
42
+ .toString(radix)
43
+ .padStart(chars, '0');
44
+ }
45
+ Object.assign(item, { [entityManager.config.hashKey]: hashKey });
46
+ return item;
47
+ }
48
+ catch (error) {
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ export { updateItemHashKey };
@@ -0,0 +1,43 @@
1
+ import { isNil } from '../node_modules/@karmaniverous/entity-tools/dist/mjs/Nil.js';
2
+ import { validateEntityToken } from './validateEntityToken.js';
3
+
4
+ /**
5
+ * Update the range key on a partial {@link ItemMap | `ItemMap`} object. Mutates `item`.
6
+ *
7
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
8
+ * @param item - Partial {@link ItemMap | `ItemMap`} object.
9
+ * @param entityToken - {@link ConfigKeys.entities | `this.config.entities`} key.
10
+ * @param overwrite - Overwrite existing {@link ConfigKeys.rangeKey | `this.config.rangeKey`} property value (default `false`).
11
+ *
12
+ * @returns Mutated `item` with updated range key.
13
+ *
14
+ * @throws `Error` if `entityToken` is invalid.
15
+ * @throws `Error` if `item` {@link ConfigEntity.uniqueProperty | `this.config.entities<entityToken>.uniqueProperty`} property value is missing.
16
+ */
17
+ function updateItemRangeKey(entityManager, item, entityToken, overwrite = false) {
18
+ try {
19
+ // Validate params.
20
+ validateEntityToken(entityManager, entityToken);
21
+ // Return current item if rangeKey exists and overwrite is false.
22
+ if (item[entityManager.config.rangeKey] && !overwrite) {
23
+ return item;
24
+ }
25
+ // Get item unique property & validate.
26
+ const uniqueProperty = item[entityManager.config.entities[entityToken].uniqueProperty];
27
+ if (isNil(uniqueProperty))
28
+ throw new Error(`missing item unique property`);
29
+ // Update range key.
30
+ Object.assign(item, {
31
+ [entityManager.config.rangeKey]: [
32
+ entityManager.config.entities[entityToken].uniqueProperty,
33
+ uniqueProperty,
34
+ ].join(entityManager.config.generatedValueDelimiter),
35
+ });
36
+ return item;
37
+ }
38
+ catch (error) {
39
+ throw error;
40
+ }
41
+ }
42
+
43
+ export { updateItemRangeKey };
@@ -0,0 +1,27 @@
1
+ import { validateEntityToken } from './validateEntityToken.js';
2
+
3
+ /**
4
+ * Validate that an entity generated property is defined in EntityManager
5
+ * config.
6
+ *
7
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
8
+ * @param entityToken - {@link ConfigKeys.entities | `this.config.entities`} key.
9
+ * @param property - {@link ConfigEntityGenerated | `this.config.entities.<entityToken>.generated`} key.
10
+ * @param sharded - Whether the generated property is sharded. `undefined` indicates no constraint.
11
+ *
12
+ * @throws `Error` if `entityToken` is invalid.
13
+ * @throws `Error` if `property` is invalid.
14
+ * @throws `Error` if `sharded` is specified & does not match `this.config.entities.<entityToken>.generated.<property>.sharded`.
15
+ */
16
+ function validateEntityGeneratedProperty(entityManager, entityToken, property, sharded) {
17
+ validateEntityToken(entityManager, entityToken);
18
+ const generated = entityManager.config.entities[entityToken].generated[property];
19
+ if (!generated && property !== entityManager.config.hashKey)
20
+ throw new Error('invalid entity generated property');
21
+ if (sharded !== undefined &&
22
+ ((generated && sharded !== generated.sharded) ||
23
+ (!sharded && property === entityManager.config.hashKey)))
24
+ throw new Error(`entity generated property ${sharded ? 'not ' : ''}sharded`);
25
+ }
26
+
27
+ export { validateEntityGeneratedProperty };
@@ -0,0 +1,20 @@
1
+ import { validateEntityToken } from './validateEntityToken.js';
2
+
3
+ /**
4
+ * Validate that an entity index is defined in EntityManager config.
5
+ *
6
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
7
+ * @param entityToken - {@link ConfigKeys.entities | `this.config.entities`} key.
8
+ * @param indexToken - {@link ConfigEntity.indexes | `this.config.entities.<entityToken>.indexes`} key.
9
+ *
10
+ * @throws `Error` if `entityToken` is invalid.
11
+ * @throws `Error` if `indexToken` is invalid.
12
+ */
13
+ function validateEntityIndexToken(entityManager, entityToken, indexToken) {
14
+ validateEntityToken(entityManager, entityToken);
15
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
16
+ if (!entityManager.config.entities[entityToken].indexes[indexToken])
17
+ throw new Error('invalid entity index token');
18
+ }
19
+
20
+ export { validateEntityIndexToken };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Validate that an entity is defined in the EntityManager config.
3
+ *
4
+ * @param entityManager - {@link EntityManager | `EntityManager`} instance.
5
+ * @param entityToken - `entityManager.config.entities` key.
6
+ *
7
+ * @throws `Error` if `entityToken` is invalid.
8
+ */
9
+ function validateEntityToken(entityManager, entityToken) {
10
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
11
+ if (!entityManager.config.entities[entityToken])
12
+ throw new Error('invalid entity token');
13
+ }
14
+
15
+ export { validateEntityToken };