@karmaniverous/entity-manager 5.0.8 → 5.0.10
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/.github/FUNDING.yml +3 -0
- package/README.md +1 -2
- package/dist/cjs/BaseEntityClient/BaseEntityClient.js +28 -0
- package/dist/cjs/BaseQueryBuilder/BaseQueryBuilder.js +62 -0
- package/dist/cjs/EntityManager/EntityManager.js +131 -0
- package/dist/cjs/EntityManager/ParsedConfig.js +385 -0
- package/dist/cjs/EntityManager/addKeys.js +60 -0
- package/dist/cjs/EntityManager/createEntityManager.js +46 -0
- package/dist/cjs/EntityManager/decodeElement.js +46 -0
- package/dist/cjs/EntityManager/decodeGeneratedProperty.js +53 -0
- package/dist/cjs/EntityManager/dehydrateIndexItem.js +68 -0
- package/dist/cjs/EntityManager/dehydratePageKeyMap.js +93 -0
- package/dist/cjs/EntityManager/encodeElement.js +40 -0
- package/dist/cjs/EntityManager/encodeGeneratedProperty.js +55 -0
- package/dist/cjs/EntityManager/findIndexToken.js +10 -0
- package/dist/cjs/EntityManager/getHashKeySpace.js +89 -0
- package/dist/cjs/EntityManager/getIndexComponents.js +23 -0
- package/dist/cjs/EntityManager/getPrimaryKey.js +76 -0
- package/dist/cjs/EntityManager/getShardBump.js +24 -0
- package/dist/cjs/EntityManager/query.js +130 -0
- package/dist/cjs/EntityManager/rehydrateIndexItem.js +65 -0
- package/dist/cjs/EntityManager/rehydratePageKeyMap.js +103 -0
- package/dist/cjs/EntityManager/removeKeys.js +51 -0
- package/dist/cjs/EntityManager/unwrapIndex.js +59 -0
- package/dist/cjs/EntityManager/updateItemHashKey.js +79 -0
- package/dist/cjs/EntityManager/updateItemRangeKey.js +65 -0
- package/dist/cjs/EntityManager/validateEntityToken.js +17 -0
- package/dist/cjs/EntityManager/validateGeneratedProperty.js +23 -0
- package/dist/cjs/EntityManager/validateIndexToken.js +16 -0
- package/dist/cjs/EntityManager/validateTranscodedProperty.js +16 -0
- package/dist/cjs/index.js +15 -0
- package/dist/default/lib/EntityManager/EntityManager.js +46 -69
- package/dist/default/lib/EntityManager/PrivateEntityManager.js +44 -51
- package/dist/index.d.ts +1163 -0
- package/dist/mjs/BaseEntityClient/BaseEntityClient.js +26 -0
- package/dist/mjs/BaseQueryBuilder/BaseQueryBuilder.js +60 -0
- package/dist/mjs/EntityManager/EntityManager.js +129 -0
- package/dist/mjs/EntityManager/ParsedConfig.js +383 -0
- package/dist/mjs/EntityManager/addKeys.js +58 -0
- package/dist/mjs/EntityManager/createEntityManager.js +44 -0
- package/dist/mjs/EntityManager/decodeElement.js +44 -0
- package/dist/mjs/EntityManager/decodeGeneratedProperty.js +51 -0
- package/dist/mjs/EntityManager/dehydrateIndexItem.js +66 -0
- package/dist/mjs/EntityManager/dehydratePageKeyMap.js +91 -0
- package/dist/mjs/EntityManager/encodeElement.js +38 -0
- package/dist/mjs/EntityManager/encodeGeneratedProperty.js +53 -0
- package/dist/mjs/EntityManager/findIndexToken.js +8 -0
- package/dist/mjs/EntityManager/getHashKeySpace.js +87 -0
- package/dist/mjs/EntityManager/getIndexComponents.js +21 -0
- package/dist/mjs/EntityManager/getPrimaryKey.js +74 -0
- package/dist/mjs/EntityManager/getShardBump.js +22 -0
- package/dist/mjs/EntityManager/query.js +128 -0
- package/dist/mjs/EntityManager/rehydrateIndexItem.js +63 -0
- package/dist/mjs/EntityManager/rehydratePageKeyMap.js +101 -0
- package/dist/mjs/EntityManager/removeKeys.js +49 -0
- package/dist/mjs/EntityManager/unwrapIndex.js +57 -0
- package/dist/mjs/EntityManager/updateItemHashKey.js +77 -0
- package/dist/mjs/EntityManager/updateItemRangeKey.js +63 -0
- package/dist/mjs/EntityManager/validateEntityToken.js +15 -0
- package/dist/mjs/EntityManager/validateGeneratedProperty.js +21 -0
- package/dist/mjs/EntityManager/validateIndexToken.js +14 -0
- package/dist/mjs/EntityManager/validateTranscodedProperty.js +14 -0
- package/dist/mjs/index.js +5 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/hierarchy.js +1 -0
- package/docs/assets/highlight.css +99 -0
- package/docs/assets/icons.js +18 -0
- package/docs/assets/icons.svg +1 -0
- package/docs/assets/main.js +60 -0
- package/docs/assets/navigation.js +1 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1648 -0
- package/docs/classes/index.BaseEntityClient.html +124 -0
- package/docs/classes/index.BaseQueryBuilder.html +229 -0
- package/docs/classes/index.EntityManager.html +510 -0
- package/docs/documents/CHANGELOG.html +1432 -0
- package/docs/documents/guides_stan-assistant-guide.html +431 -0
- package/docs/functions/index.createEntityManager.html +60 -0
- package/docs/hierarchy.html +31 -0
- package/docs/index.html +212 -0
- package/docs/interfaces/index.BaseConfigMap.html +113 -0
- package/docs/interfaces/index.BaseEntityClientOptions.html +82 -0
- package/docs/interfaces/index.BaseQueryBuilderOptions.html +92 -0
- package/docs/interfaces/index.CapturedConfigMapFrom.html +126 -0
- package/docs/interfaces/index.ConfigInput.html +189 -0
- package/docs/interfaces/index.ParsedConfig.html +144 -0
- package/docs/interfaces/index.ParsedEntityConfig.html +91 -0
- package/docs/interfaces/index.ParsedGeneratedPropertiesConfig.html +67 -0
- package/docs/interfaces/index.ParsedIndexConfig.html +79 -0
- package/docs/interfaces/index.ParsedTranscoder.html +71 -0
- package/docs/interfaces/index.QueryOptions.html +180 -0
- package/docs/interfaces/index.QueryResult.html +91 -0
- package/docs/interfaces/index.ShardBump.html +79 -0
- package/docs/interfaces/index.ShardQueryResult.html +93 -0
- package/docs/modules/index.html +47 -0
- package/docs/modules.html +38 -0
- package/docs/sitemap.xml +263 -0
- package/docs/types/index.BaseKeyTokens.html +39 -0
- package/docs/types/index.Config.html +75 -0
- package/docs/types/index.ConfigMap.html +41 -0
- package/docs/types/index.ConfigOfClient.html +45 -0
- package/docs/types/index.EntitiesFromSchema.html +39 -0
- package/docs/types/index.EntityClientItemByToken.html +45 -0
- package/docs/types/index.EntityClientRecordByToken.html +43 -0
- package/docs/types/index.EntityItem.html +41 -0
- package/docs/types/index.EntityItemPartial.html +44 -0
- package/docs/types/index.EntityKey.html +40 -0
- package/docs/types/index.EntityOfToken.html +39 -0
- package/docs/types/index.EntityRecord.html +39 -0
- package/docs/types/index.EntityRecordPartial.html +40 -0
- package/docs/types/index.EntityToken.html +40 -0
- package/docs/types/index.FallbackIndexTokenSet.html +40 -0
- package/docs/types/index.HasIndexFor.html +43 -0
- package/docs/types/index.HashKeyFrom.html +38 -0
- package/docs/types/index.IndexComponentTokens.html +45 -0
- package/docs/types/index.IndexHashKeyOf.html +42 -0
- package/docs/types/index.IndexRangeKeyOf.html +41 -0
- package/docs/types/index.IndexTokensFrom.html +41 -0
- package/docs/types/index.IndexTokensOf.html +40 -0
- package/docs/types/index.KeysFrom.html +38 -0
- package/docs/types/index.PageKey.html +40 -0
- package/docs/types/index.PageKeyByIndex.html +46 -0
- package/docs/types/index.PresentIndexTokenSet.html +45 -0
- package/docs/types/index.Projected.html +39 -0
- package/docs/types/index.QueryBuilderQueryOptions.html +45 -0
- package/docs/types/index.QueryOptionsByCC.html +49 -0
- package/docs/types/index.QueryOptionsByCF.html +47 -0
- package/docs/types/index.RangeKeyFrom.html +38 -0
- package/docs/types/index.ShardQueryFunction.html +63 -0
- package/docs/types/index.ShardQueryMap.html +56 -0
- package/docs/types/index.ShardQueryMapByCC.html +49 -0
- package/docs/types/index.ShardQueryMapByCF.html +47 -0
- package/docs/types/index.ShardedKeysFrom.html +38 -0
- package/docs/types/index.StorageItem.html +42 -0
- package/docs/types/index.StorageRecord.html +40 -0
- package/docs/types/index.TranscodedPropertiesFrom.html +38 -0
- package/docs/types/index.UnshardedKeysFrom.html +38 -0
- package/docs/types/index.ValidateConfigMap.html +40 -0
- package/lib/EntityManager/PrivateEntityManager.js +4 -0
- package/package.json +6 -6
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var EntityManager = require('./EntityManager.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Values-first factory that captures literal tokens and index names directly
|
|
7
|
+
* from the provided config value. Runtime config parsing/validation is
|
|
8
|
+
* unchanged (performed in the EntityManager constructor).
|
|
9
|
+
*
|
|
10
|
+
* @typeParam CC - Captured config input (values-first). Prefer `as const` and
|
|
11
|
+
* `satisfies` at call sites to preserve literal keys.
|
|
12
|
+
* @typeParam EM - EntityMap for the manager. Defaults to a minimal derived map
|
|
13
|
+
* from `CC.entitiesSchema` when present; otherwise falls back to EntityMap.
|
|
14
|
+
*
|
|
15
|
+
* @returns An {@link EntityManager | `EntityManager`} instance whose type
|
|
16
|
+
* captures CF from the single values-first config literal ({@link ConfigInput | `ConfigInput`})
|
|
17
|
+
* as the second generic parameter (phantom; type-only).
|
|
18
|
+
*/
|
|
19
|
+
function createEntityManager(config, logger = console) {
|
|
20
|
+
// Cast to the existing Config<C> shape for runtime parsing; Zod validation
|
|
21
|
+
// remains authoritative at construction time.
|
|
22
|
+
// Optional dev guardrail: cross-check entitiesSchema keys vs config.entities keys.
|
|
23
|
+
try {
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
25
|
+
if (config && 'entitiesSchema' in config && config.entitiesSchema) {
|
|
26
|
+
const schemaKeys = Object.keys(config
|
|
27
|
+
.entitiesSchema ?? {});
|
|
28
|
+
const entitiesKeys = Object.keys(config
|
|
29
|
+
.entities ?? {});
|
|
30
|
+
const missingInEntities = schemaKeys.filter((k) => !entitiesKeys.includes(k));
|
|
31
|
+
const missingInSchema = entitiesKeys.filter((k) => !schemaKeys.includes(k));
|
|
32
|
+
if (missingInEntities.length || missingInSchema.length) {
|
|
33
|
+
logger.debug('entitiesSchema keys mismatch with config.entities', {
|
|
34
|
+
missingInEntities,
|
|
35
|
+
missingInSchema,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Best-effort warning only; never block construction.
|
|
42
|
+
}
|
|
43
|
+
return new EntityManager.EntityManager(config, logger);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exports.createEntityManager = createEntityManager;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var validateTranscodedProperty = require('./validateTranscodedProperty.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Decode an {@link EntityItem | `EntityItem`} generated property element or ungenerated index component using the associated {@link Transcodes | Transcodes} `encode` function.
|
|
7
|
+
*
|
|
8
|
+
* Returns all `undefined` values.
|
|
9
|
+
*
|
|
10
|
+
* If `element` is the {@link Config.hashKey | `hashKey`} or {@link Config.rangeKey | `rangeKey`}, returns the value as-is.
|
|
11
|
+
*
|
|
12
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
13
|
+
* @param element - The {@link Entity | `Entity`} generated property element or ungenerated index component to encode.
|
|
14
|
+
* @param value - Encoded entity element.
|
|
15
|
+
*
|
|
16
|
+
* @returns Decoded value.
|
|
17
|
+
*
|
|
18
|
+
* @throws `Error` if `entityToken` is invalid.
|
|
19
|
+
*/
|
|
20
|
+
function decodeElement(entityManager, element, value) {
|
|
21
|
+
try {
|
|
22
|
+
// Validate params.
|
|
23
|
+
validateTranscodedProperty.validateTranscodedProperty(entityManager, element);
|
|
24
|
+
if (!value)
|
|
25
|
+
return;
|
|
26
|
+
const { propertyTranscodes, transcodes } = entityManager.config;
|
|
27
|
+
const decodeFn = transcodes[propertyTranscodes[element]].decode;
|
|
28
|
+
const decoded = decodeFn(value);
|
|
29
|
+
entityManager.logger.debug('decoded entity element', {
|
|
30
|
+
element,
|
|
31
|
+
value,
|
|
32
|
+
decoded,
|
|
33
|
+
});
|
|
34
|
+
return decoded;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof Error)
|
|
38
|
+
entityManager.logger.error(error.message, {
|
|
39
|
+
element,
|
|
40
|
+
value,
|
|
41
|
+
});
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exports.decodeElement = decodeElement;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var radash = require('radash');
|
|
4
|
+
var decodeElement = require('./decodeElement.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Decode a generated property value. Returns an {@link EntityItem | `EntityItem`}.
|
|
8
|
+
*
|
|
9
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
10
|
+
* @param entityToken - {@link Config.entities | `entityManager.config.entities`} key.
|
|
11
|
+
* @param encoded - Encoded generated property value.
|
|
12
|
+
*
|
|
13
|
+
* @returns {@link EntityItem | `EntityItem`} object with updated properties decoded from `encoded`.
|
|
14
|
+
*
|
|
15
|
+
* @throws `Error` if `entityToken` is invalid.
|
|
16
|
+
*/
|
|
17
|
+
function decodeGeneratedProperty(entityManager, entityToken, encoded) {
|
|
18
|
+
try {
|
|
19
|
+
const { generatedKeyDelimiter, generatedValueDelimiter, hashKey, shardKeyDelimiter, } = entityManager.config;
|
|
20
|
+
// Handle degenerate case.
|
|
21
|
+
if (!encoded)
|
|
22
|
+
return {};
|
|
23
|
+
// Split encoded into keys.
|
|
24
|
+
const keys = encoded.split(generatedKeyDelimiter);
|
|
25
|
+
// Initiate result with hashKey if sharded.
|
|
26
|
+
const decoded = keys[0].includes(shardKeyDelimiter)
|
|
27
|
+
? { [hashKey]: keys.shift() }
|
|
28
|
+
: {};
|
|
29
|
+
// Split keys into values & validate.
|
|
30
|
+
const values = keys.map((key) => {
|
|
31
|
+
const pair = key.split(generatedValueDelimiter);
|
|
32
|
+
if (pair.length !== 2)
|
|
33
|
+
throw new Error(`invalid generated property value '${key}'`);
|
|
34
|
+
return pair;
|
|
35
|
+
});
|
|
36
|
+
// Assign decoded properties.
|
|
37
|
+
Object.assign(decoded, radash.objectify(values, ([key]) => key, ([key, value]) => decodeElement.decodeElement(entityManager, key, value)));
|
|
38
|
+
entityManager.logger.debug('decoded generated property', {
|
|
39
|
+
encoded,
|
|
40
|
+
decoded,
|
|
41
|
+
});
|
|
42
|
+
// entityToken used for typing only (ET-narrowed result).
|
|
43
|
+
void entityToken;
|
|
44
|
+
return decoded;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof Error)
|
|
48
|
+
entityManager.logger.error(error.message, { encoded });
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
exports.decodeGeneratedProperty = decodeGeneratedProperty;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var encodeElement = require('./encodeElement.js');
|
|
4
|
+
var unwrapIndex = require('./unwrapIndex.js');
|
|
5
|
+
var validateEntityToken = require('./validateEntityToken.js');
|
|
6
|
+
var validateIndexToken = require('./validateIndexToken.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Condense an {@link EntityItem | `EntityItem`} into a delimited string representing the deduped, sorted, ungenerated component elements of an {@link Config.indexes | index}, leaving out those of the index hash key.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* Reverses {@link EntityManager.rehydrateIndexItem | `rehydrateIndexItem`}.
|
|
13
|
+
*
|
|
14
|
+
* To create the output value, entityManager method:
|
|
15
|
+
*
|
|
16
|
+
* * Unwraps `index` components into deduped, sorted, ungenerated elements.
|
|
17
|
+
* * Joins `item` element values with {@link Config.generatedKeyDelimiter | `entityManager.config.generatedKeyDelimiter`}.
|
|
18
|
+
*
|
|
19
|
+
* `item` must be populated with all required index component elements!
|
|
20
|
+
*
|
|
21
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
22
|
+
* @param entityToken - {@link Config.entities | `entityManager.config.entities`} key.
|
|
23
|
+
* @param indexToken - {@link ConfigEntity.indexes | `entityManager.config.indexes`} key.
|
|
24
|
+
* @param item - {@link EntityItem | `EntityItem`} object.
|
|
25
|
+
*
|
|
26
|
+
* @returns Dehydrated index value.
|
|
27
|
+
*
|
|
28
|
+
* @throws `Error` if `indexToken` is invalid.
|
|
29
|
+
*/
|
|
30
|
+
function dehydrateIndexItem(entityManager, entityToken, indexToken, item) {
|
|
31
|
+
try {
|
|
32
|
+
// Validate params.
|
|
33
|
+
validateEntityToken.validateEntityToken(entityManager, entityToken);
|
|
34
|
+
validateIndexToken.validateIndexToken(entityManager, indexToken);
|
|
35
|
+
// Handle degenerate case.
|
|
36
|
+
if (!item)
|
|
37
|
+
return '';
|
|
38
|
+
// Unwrap index elements.
|
|
39
|
+
const { hashKey } = entityManager.config.indexes[indexToken];
|
|
40
|
+
const elements = unwrapIndex.unwrapIndex(entityManager, entityToken, indexToken, [
|
|
41
|
+
hashKey,
|
|
42
|
+
]);
|
|
43
|
+
// Join index element values.
|
|
44
|
+
const { generatedKeyDelimiter } = entityManager.config;
|
|
45
|
+
const dehydrated = elements
|
|
46
|
+
.map((element) => encodeElement.encodeElement(entityManager, element, item))
|
|
47
|
+
.join(generatedKeyDelimiter);
|
|
48
|
+
entityManager.logger.debug('dehydrated index', {
|
|
49
|
+
item,
|
|
50
|
+
entityToken,
|
|
51
|
+
indexToken,
|
|
52
|
+
elements,
|
|
53
|
+
dehydrated,
|
|
54
|
+
});
|
|
55
|
+
return dehydrated;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof Error)
|
|
59
|
+
entityManager.logger.error(error.message, {
|
|
60
|
+
item,
|
|
61
|
+
entityToken,
|
|
62
|
+
indexToken,
|
|
63
|
+
});
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
exports.dehydrateIndexItem = dehydrateIndexItem;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var decodeGeneratedProperty = require('./decodeGeneratedProperty.js');
|
|
4
|
+
var dehydrateIndexItem = require('./dehydrateIndexItem.js');
|
|
5
|
+
var validateEntityToken = require('./validateEntityToken.js');
|
|
6
|
+
var validateIndexToken = require('./validateIndexToken.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Dehydrate a {@link PageKeyMap | `PageKeyMap`} object into an array of dehydrated page keys.
|
|
10
|
+
*
|
|
11
|
+
* Reverses {@link EntityManager.rehydratePageKeyMap | `rehydratePageKeyMap`}.
|
|
12
|
+
*
|
|
13
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
14
|
+
* @param entityToken - {@link Config.entities | `entityManager.config.entities`} key.
|
|
15
|
+
* @param pageKeyMap - {@link PageKeyMap | `PageKeyMap`} object to dehydrate.
|
|
16
|
+
*
|
|
17
|
+
* @returns Array of dehydrated page keys.
|
|
18
|
+
*
|
|
19
|
+
* @throws `Error` if `entityToken` is invalid.
|
|
20
|
+
* @throws `Error` if any `pageKeyMap` key is an invalid indexToken is invalid {@link ConfigEntity.indexes | `entityManager.config.entities.<entityToken>.indexes`} key.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* In the returned array, an empty string member indicates the corresponding page key is `undefined`.
|
|
24
|
+
*
|
|
25
|
+
* An empty returned array indicates all page keys are `undefined`.
|
|
26
|
+
*/
|
|
27
|
+
function dehydratePageKeyMap(entityManager, entityToken, pageKeyMap) {
|
|
28
|
+
try {
|
|
29
|
+
// Validate params.
|
|
30
|
+
validateEntityToken.validateEntityToken(entityManager, entityToken);
|
|
31
|
+
// Shortcut empty pageKeyMap.
|
|
32
|
+
if (!Object.keys(pageKeyMap).length) {
|
|
33
|
+
const dehydrated = [];
|
|
34
|
+
entityManager.logger.debug('dehydrated empty page key map', {
|
|
35
|
+
entityToken,
|
|
36
|
+
pageKeyMap,
|
|
37
|
+
dehydrated,
|
|
38
|
+
});
|
|
39
|
+
return dehydrated;
|
|
40
|
+
}
|
|
41
|
+
// Extract, sort & validate indexs.
|
|
42
|
+
const indexes = Object.keys(pageKeyMap).sort();
|
|
43
|
+
indexes.map((indexToken) => {
|
|
44
|
+
validateIndexToken.validateIndexToken(entityManager, indexToken);
|
|
45
|
+
});
|
|
46
|
+
// Extract & sort hash keys.
|
|
47
|
+
const hashKeys = Object.keys(pageKeyMap[indexes[0]]);
|
|
48
|
+
// Dehydrate page keys.
|
|
49
|
+
let dehydrated = [];
|
|
50
|
+
for (const index of indexes) {
|
|
51
|
+
for (const hashKey of hashKeys) {
|
|
52
|
+
// Undefined pageKey.
|
|
53
|
+
if (!pageKeyMap[index][hashKey]) {
|
|
54
|
+
dehydrated.push('');
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// Compose item from page key
|
|
58
|
+
const pk = pageKeyMap[index][hashKey];
|
|
59
|
+
const item = Object.entries(pk).reduce((item, [property, value]) => {
|
|
60
|
+
if (property === entityManager.config.rangeKey ||
|
|
61
|
+
property in entityManager.config.generatedProperties.sharded ||
|
|
62
|
+
property in entityManager.config.generatedProperties.unsharded)
|
|
63
|
+
Object.assign(item, decodeGeneratedProperty.decodeGeneratedProperty(entityManager, entityToken, value));
|
|
64
|
+
else
|
|
65
|
+
Object.assign(item, { [property]: value });
|
|
66
|
+
return item;
|
|
67
|
+
},
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter
|
|
69
|
+
{});
|
|
70
|
+
// Dehydrate index from item.
|
|
71
|
+
dehydrated.push(dehydrateIndexItem.dehydrateIndexItem(entityManager, entityToken, index, item));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Replace with empty array if all pageKeys are empty strings.
|
|
75
|
+
if (dehydrated.every((pageKey) => pageKey === ''))
|
|
76
|
+
dehydrated = [];
|
|
77
|
+
entityManager.logger.debug('dehydrated page key map', {
|
|
78
|
+
entityToken,
|
|
79
|
+
pageKeyMap,
|
|
80
|
+
indexes,
|
|
81
|
+
hashKeys,
|
|
82
|
+
dehydrated,
|
|
83
|
+
});
|
|
84
|
+
return dehydrated;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof Error)
|
|
88
|
+
entityManager.logger.error(error.message, { entityToken, pageKeyMap });
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
exports.dehydratePageKeyMap = dehydratePageKeyMap;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encode an {@link EntityItem | `EntityItem`} generated property element or ungenerated index component using the associated {@link Transcodes | Transcodes} `encode` function.
|
|
5
|
+
*
|
|
6
|
+
* Returns all `undefined` values.
|
|
7
|
+
*
|
|
8
|
+
* If `element` is the {@link Config.hashKey | `hashKey`} or {@link Config.rangeKey | `rangeKey`}, returns the value as-is.
|
|
9
|
+
*
|
|
10
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
11
|
+
* @param element - The {@link Entity | `Entity`} generated property element or ungenerated index component to encode.
|
|
12
|
+
* @param item - {@link EntityItem | `EntityItem`} object.
|
|
13
|
+
*
|
|
14
|
+
* @returns Encoded value.
|
|
15
|
+
*
|
|
16
|
+
* @throws `Error` if `entityToken` is invalid.
|
|
17
|
+
*/
|
|
18
|
+
function encodeElement(entityManager, element, item) {
|
|
19
|
+
try {
|
|
20
|
+
const { hashKey, rangeKey, propertyTranscodes, transcodes } = entityManager.config;
|
|
21
|
+
const value = item[element];
|
|
22
|
+
if (value === undefined || [hashKey, rangeKey].includes(element))
|
|
23
|
+
return value;
|
|
24
|
+
const encodeFn = transcodes[propertyTranscodes[element]].encode;
|
|
25
|
+
const encoded = encodeFn(item[element]) || undefined;
|
|
26
|
+
entityManager.logger.debug('encoded entity element', {
|
|
27
|
+
element,
|
|
28
|
+
item,
|
|
29
|
+
encoded,
|
|
30
|
+
});
|
|
31
|
+
return encoded;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof Error)
|
|
35
|
+
entityManager.logger.error(error.message, { element, item });
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exports.encodeElement = encodeElement;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var entityTools = require('@karmaniverous/entity-tools');
|
|
4
|
+
var validateGeneratedProperty = require('./validateGeneratedProperty.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Encode a generated property value. Returns a string or undefined if atomicity requirement of sharded properties not met.
|
|
8
|
+
*
|
|
9
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
10
|
+
* @param property - {@link Config.generatedProperties | Generated property} key.
|
|
11
|
+
* @param item - {@link StorageItem | `StorageItem`} object.
|
|
12
|
+
*
|
|
13
|
+
* @returns Encoded generated property value.
|
|
14
|
+
*
|
|
15
|
+
* @throws `Error` if `property` is not a {@link Config.generatedProperties | generated property}.
|
|
16
|
+
*/
|
|
17
|
+
function encodeGeneratedProperty(entityManager, property, item) {
|
|
18
|
+
try {
|
|
19
|
+
// Validate params.
|
|
20
|
+
validateGeneratedProperty.validateGeneratedProperty(entityManager, property);
|
|
21
|
+
const sharded = property in entityManager.config.generatedProperties.sharded;
|
|
22
|
+
const elements = entityManager.config.generatedProperties[sharded ? 'sharded' : 'unsharded'][property];
|
|
23
|
+
// Map elements to [element, value] pairs.
|
|
24
|
+
const elementMap = elements.map((element) => [
|
|
25
|
+
element,
|
|
26
|
+
item[element],
|
|
27
|
+
]);
|
|
28
|
+
// Return undefined if sharded & atomicity requirement fails.
|
|
29
|
+
if (sharded && elementMap.some(([, value]) => entityTools.isNil(value)))
|
|
30
|
+
return;
|
|
31
|
+
// Encode property value.
|
|
32
|
+
const encoded = [
|
|
33
|
+
...(sharded
|
|
34
|
+
? [item[entityManager.config.hashKey]]
|
|
35
|
+
: []),
|
|
36
|
+
...elementMap.map(([element, value]) => [element, `${value ?? ''}`].join(entityManager.config.generatedValueDelimiter)),
|
|
37
|
+
].join(entityManager.config.generatedKeyDelimiter);
|
|
38
|
+
entityManager.logger.debug('encoded generated property', {
|
|
39
|
+
property,
|
|
40
|
+
item,
|
|
41
|
+
encoded,
|
|
42
|
+
});
|
|
43
|
+
return encoded;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof Error)
|
|
47
|
+
entityManager.logger.error(error.message, {
|
|
48
|
+
property,
|
|
49
|
+
item,
|
|
50
|
+
});
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exports.encodeGeneratedProperty = encodeGeneratedProperty;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function findIndexToken(entityManager, hashKeyToken, rangeKeyToken, suppressError) {
|
|
4
|
+
const indexToken = (Object.entries(entityManager.config.indexes).find(([, index]) => index.hashKey === hashKeyToken && index.rangeKey === rangeKeyToken)?.[0] ?? undefined);
|
|
5
|
+
if (!indexToken && !suppressError)
|
|
6
|
+
throw new Error(`No index token found for hashKey '${hashKeyToken}' & rangeKey '${rangeKeyToken}'.`);
|
|
7
|
+
return indexToken;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
exports.findIndexToken = findIndexToken;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var radash = require('radash');
|
|
4
|
+
var stringHash = require('string-hash');
|
|
5
|
+
var encodeGeneratedProperty = require('./encodeGeneratedProperty.js');
|
|
6
|
+
var validateGeneratedProperty = require('./validateGeneratedProperty.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Return an array of {@link ConfigKeys.hashKey | `entityManager.config.hashKey`} property values covering the shard space bounded by `timestampFrom` & `timestampTo`.
|
|
10
|
+
*
|
|
11
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
12
|
+
* @param entityToken - {@link Config.entities | `entityManager.config.entities`} key.
|
|
13
|
+
* @param hashKeyToken - {@link ParsedConfig | `entityManager.config.hashKey`} or {@link ParsedConfig | `entityManager.config.generatedProperties.sharded`} key.
|
|
14
|
+
* @param item - {@link EntityItem | `EntityItem`} object. Must include all properties required to generate the hash key space.
|
|
15
|
+
* @param timestampFrom - Lower timestanp limit. Defaults to `0`.
|
|
16
|
+
* @param timestampTo - Upper timestamp limit. Defaults to `Date.now()`.
|
|
17
|
+
*
|
|
18
|
+
* @returns Array of {@link ConfigKeys.hashKey | `entityManager.config.hashKey`} property values covering the indicated shard space.
|
|
19
|
+
*
|
|
20
|
+
* @throws `Error` if `entityToken` is invalid.
|
|
21
|
+
*/
|
|
22
|
+
function getHashKeySpace(entityManager, entityToken, hashKeyToken, item, timestampFrom = 0, timestampTo = Date.now()) {
|
|
23
|
+
try {
|
|
24
|
+
// Validate hashKeyToken is either the global hash key or a sharded generated property.
|
|
25
|
+
if (hashKeyToken !== entityManager.config.hashKey)
|
|
26
|
+
validateGeneratedProperty.validateGeneratedProperty(entityManager, hashKeyToken, true);
|
|
27
|
+
const { shardBumps } = entityManager.config.entities[entityToken];
|
|
28
|
+
// Detect presence of the entity's unique property on the item.
|
|
29
|
+
const uniqueProp = entityManager.config.entities[entityToken].uniqueProperty;
|
|
30
|
+
const uniqueValue = item[uniqueProp];
|
|
31
|
+
const hashKeySpace = shardBumps
|
|
32
|
+
// Filter shard bumps by timestamp range.
|
|
33
|
+
.filter((bump, i) => (i === shardBumps.length - 1 ||
|
|
34
|
+
shardBumps[i + 1].timestamp > timestampFrom) &&
|
|
35
|
+
bump.timestamp <= timestampTo)
|
|
36
|
+
// Generate shard key space.
|
|
37
|
+
.flatMap(({ charBits, chars }) => {
|
|
38
|
+
const radix = 2 ** charBits;
|
|
39
|
+
// If the item's unique property is present, deterministically
|
|
40
|
+
// compute exactly one shard suffix for this bump. Otherwise,
|
|
41
|
+
// enumerate the full shard space for the bump.
|
|
42
|
+
if (chars) {
|
|
43
|
+
if (uniqueValue) {
|
|
44
|
+
const space = radix ** chars;
|
|
45
|
+
const mod = stringHash(uniqueValue) % space;
|
|
46
|
+
return mod.toString(radix).padStart(chars, '0');
|
|
47
|
+
}
|
|
48
|
+
return [...radash.range(0, radix ** chars - 1)].map((char) => char.toString(radix).padStart(chars, '0'));
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
})
|
|
52
|
+
// Map shard keys to hash keys.
|
|
53
|
+
.map((shardKey) => {
|
|
54
|
+
// Calculate record hash key.
|
|
55
|
+
let hashKey = `${String(entityToken)}${entityManager.config.shardKeyDelimiter}${shardKey}`;
|
|
56
|
+
// If hash key space basis is a different property, encode it.
|
|
57
|
+
if (hashKeyToken !== entityManager.config.hashKey)
|
|
58
|
+
hashKey = encodeGeneratedProperty.encodeGeneratedProperty(entityManager, hashKeyToken, {
|
|
59
|
+
...item,
|
|
60
|
+
[entityManager.config.hashKey]: hashKey,
|
|
61
|
+
});
|
|
62
|
+
if (!hashKey)
|
|
63
|
+
throw new Error('item does not support hash key space');
|
|
64
|
+
return hashKey;
|
|
65
|
+
});
|
|
66
|
+
entityManager.logger.debug('generated hash key space', {
|
|
67
|
+
entityToken,
|
|
68
|
+
hashKeyToken,
|
|
69
|
+
item,
|
|
70
|
+
timestampFrom,
|
|
71
|
+
timestampTo,
|
|
72
|
+
hashKeySpace,
|
|
73
|
+
});
|
|
74
|
+
return hashKeySpace;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof Error)
|
|
78
|
+
entityManager.logger.error(error.message, {
|
|
79
|
+
entityToken,
|
|
80
|
+
hashKeyToken,
|
|
81
|
+
item,
|
|
82
|
+
timestampFrom,
|
|
83
|
+
timestampTo,
|
|
84
|
+
});
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exports.getHashKeySpace = getHashKeySpace;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var radash = require('radash');
|
|
4
|
+
var validateIndexToken = require('./validateIndexToken.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get the index components of an entity index. Adds the hash and range keys to the index components.
|
|
8
|
+
*
|
|
9
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
10
|
+
* @param indexToken - {@link Config.indexes | `entityManager.config.indexes`} key.
|
|
11
|
+
*
|
|
12
|
+
* @returns Array of index components.
|
|
13
|
+
*
|
|
14
|
+
* @throws `Error` if `indexToken` is invalid.
|
|
15
|
+
*/
|
|
16
|
+
function getIndexComponents(entityManager, indexToken) {
|
|
17
|
+
validateIndexToken.validateIndexToken(entityManager, indexToken);
|
|
18
|
+
const { hashKey, rangeKey, indexes } = entityManager.config;
|
|
19
|
+
const { hashKey: indexHashKey, rangeKey: indexRangeKey } = indexes[indexToken];
|
|
20
|
+
return radash.unique([hashKey, rangeKey, indexHashKey, indexRangeKey]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
exports.getIndexComponents = getIndexComponents;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var getHashKeySpace = require('./getHashKeySpace.js');
|
|
4
|
+
var updateItemHashKey = require('./updateItemHashKey.js');
|
|
5
|
+
var updateItemRangeKey = require('./updateItemRangeKey.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert an {@link EntityItem | `EntityItem`} into one or more {@link EntityKey | `EntityKey`} values.
|
|
9
|
+
*
|
|
10
|
+
* Behavior:
|
|
11
|
+
* - Always returns an array of keys.
|
|
12
|
+
* - If `overwrite` is false and the item already has both hash and range keys, returns exactly that pair.
|
|
13
|
+
* - Otherwise, computes the range key. Then:
|
|
14
|
+
* - If the timestampProperty is present, computes exactly one hash key and returns a single key.
|
|
15
|
+
* - If the timestampProperty is missing, enumerates the hash-key space across all shard bumps
|
|
16
|
+
* (with uniqueProperty present → one suffix per bump) and returns one key per bump.
|
|
17
|
+
*
|
|
18
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
19
|
+
* @param entityToken - {@link Config | `Config`} `entities` key.
|
|
20
|
+
* @param item - {@link EntityItem | `EntityItem`} object.
|
|
21
|
+
* @param overwrite - Overwrite existing properties (default `false`).
|
|
22
|
+
*
|
|
23
|
+
* @returns Array of {@link EntityKey | `EntityKey`} values derived from `item`.
|
|
24
|
+
*
|
|
25
|
+
* @throws `Error` if `entityToken` is invalid.
|
|
26
|
+
*/
|
|
27
|
+
function getPrimaryKey(entityManager, entityToken, item, overwrite = false) {
|
|
28
|
+
const { hashKey, rangeKey } = entityManager.config;
|
|
29
|
+
// If both keys are present and we're not overwriting, return the exact pair.
|
|
30
|
+
const rec = item;
|
|
31
|
+
const hk = hashKey;
|
|
32
|
+
const rk = rangeKey;
|
|
33
|
+
if (!overwrite && rec[hk] && rec[rk]) {
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
[hashKey]: rec[hk],
|
|
37
|
+
[rangeKey]: rec[rk],
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
// Compute/refresh the range key (throws if uniqueProperty missing).
|
|
42
|
+
const withRangeKey = updateItemRangeKey.updateItemRangeKey(entityManager, entityToken, item, true);
|
|
43
|
+
// If timestamp present, compute exactly one hash key and return single pair.
|
|
44
|
+
const tsProp = entityManager.config.entities[entityToken].timestampProperty;
|
|
45
|
+
if (withRangeKey[tsProp] !== undefined) {
|
|
46
|
+
// Note: use StorageItem here
|
|
47
|
+
const withHashKey = updateItemHashKey.updateItemHashKey(entityManager, entityToken, withRangeKey, true);
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
[hashKey]: withHashKey[hashKey],
|
|
51
|
+
[rangeKey]: withHashKey[rangeKey],
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
// No timestamp: enumerate hash-key space across all shard bumps (0..Infinity).
|
|
56
|
+
const hashKeys = getHashKeySpace.getHashKeySpace(entityManager, entityToken, hashKey, withRangeKey, 0, Infinity);
|
|
57
|
+
// Map to keys and de-duplicate.
|
|
58
|
+
const rangeKeyValue = withRangeKey[rangeKey];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
const keys = hashKeys
|
|
61
|
+
.map((hk) => {
|
|
62
|
+
const key = {
|
|
63
|
+
[hashKey]: hk,
|
|
64
|
+
[rangeKey]: rangeKeyValue,
|
|
65
|
+
};
|
|
66
|
+
const sig = `${hk}|${rangeKeyValue}`;
|
|
67
|
+
if (seen.has(sig))
|
|
68
|
+
return undefined;
|
|
69
|
+
seen.add(sig);
|
|
70
|
+
return key;
|
|
71
|
+
})
|
|
72
|
+
.filter((k) => !!k);
|
|
73
|
+
return keys;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
exports.getPrimaryKey = getPrimaryKey;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var validateEntityToken = require('./validateEntityToken.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get first entity shard bump before timestamp.
|
|
7
|
+
*
|
|
8
|
+
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
9
|
+
* @param entityToken - {@link Config.entities | `this.config.entities`} key.
|
|
10
|
+
* @param timestamp - Timestamp in milliseconds.
|
|
11
|
+
*
|
|
12
|
+
* @returns {@link ShardBump | `ShardBump`} object.
|
|
13
|
+
*
|
|
14
|
+
* @throws `Error` if `entityToken` is invalid.
|
|
15
|
+
*/
|
|
16
|
+
function getShardBump(entityManager, entityToken, timestamp) {
|
|
17
|
+
// Validate params.
|
|
18
|
+
validateEntityToken.validateEntityToken(entityManager, entityToken);
|
|
19
|
+
return [...entityManager.config.entities[entityToken].shardBumps]
|
|
20
|
+
.reverse()
|
|
21
|
+
.find((bump) => bump.timestamp <= timestamp);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exports.getShardBump = getShardBump;
|