@karmaniverous/entity-manager 6.14.1 → 6.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/cjs/EntityManager/EntityManager.js +1 -1
- package/dist/cjs/EntityManager/getHashKeySpace.js +16 -3
- package/dist/cjs/EntityManager/getPrimaryKey.js +51 -7
- package/dist/index.d.ts +5 -19
- package/dist/mjs/EntityManager/EntityManager.js +1 -1
- package/dist/mjs/EntityManager/getHashKeySpace.js +16 -3
- package/dist/mjs/EntityManager/getPrimaryKey.js +51 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -192,8 +192,17 @@ const user = {
|
|
|
192
192
|
// Add hashKey, rangeKey, and generated properties
|
|
193
193
|
const record = manager.addKeys('user', user); // returns EntityRecord<...>
|
|
194
194
|
|
|
195
|
-
// Get
|
|
196
|
-
|
|
195
|
+
// Get one or more primary keys for an item
|
|
196
|
+
// - If the timestampProperty is present, you'll usually get exactly one key.
|
|
197
|
+
// - If the timestampProperty is missing but uniqueProperty is present,
|
|
198
|
+
// you'll get one key per shard bump (deterministic suffix per bump).
|
|
199
|
+
const keys = manager.getPrimaryKey('user', user); // EntityKey[]
|
|
200
|
+
|
|
201
|
+
// Example: reading by unique id when timestamp is unknown
|
|
202
|
+
// (keys may include multiple candidates — one per bump):
|
|
203
|
+
// const keys = manager.getPrimaryKey('user', { userId: 'u123' });
|
|
204
|
+
// const { Items } = await entityClient.getItems(keys);
|
|
205
|
+
// const found = Items[0];
|
|
197
206
|
|
|
198
207
|
// Remove generated keys from a stored record
|
|
199
208
|
const pruned = manager.removeKeys('user', record);
|
|
@@ -70,7 +70,7 @@ class EntityManager {
|
|
|
70
70
|
}
|
|
71
71
|
getPrimaryKey(entityToken, i, overwrite = false) {
|
|
72
72
|
if (Array.isArray(i)) {
|
|
73
|
-
return i.
|
|
73
|
+
return i.flatMap((item) => getPrimaryKey.getPrimaryKey(this, entityToken, item, overwrite));
|
|
74
74
|
}
|
|
75
75
|
return getPrimaryKey.getPrimaryKey(this, entityToken, i, overwrite);
|
|
76
76
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var radash = require('radash');
|
|
4
|
+
var stringHash = require('string-hash');
|
|
4
5
|
var encodeGeneratedProperty = require('./encodeGeneratedProperty.js');
|
|
5
6
|
var validateGeneratedProperty = require('./validateGeneratedProperty.js');
|
|
6
7
|
|
|
@@ -24,6 +25,9 @@ function getHashKeySpace(entityManager, entityToken, hashKeyToken, item, timesta
|
|
|
24
25
|
if (hashKeyToken !== entityManager.config.hashKey)
|
|
25
26
|
validateGeneratedProperty.validateGeneratedProperty(entityManager, hashKeyToken, true);
|
|
26
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];
|
|
27
31
|
const hashKeySpace = shardBumps
|
|
28
32
|
// Filter shard bumps by timestamp range.
|
|
29
33
|
.filter((bump, i) => (i === shardBumps.length - 1 ||
|
|
@@ -32,9 +36,18 @@ function getHashKeySpace(entityManager, entityToken, hashKeyToken, item, timesta
|
|
|
32
36
|
// Generate shard key space.
|
|
33
37
|
.flatMap(({ charBits, chars }) => {
|
|
34
38
|
const radix = 2 ** charBits;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 '';
|
|
38
51
|
})
|
|
39
52
|
// Map shard keys to hash keys.
|
|
40
53
|
.map((shardKey) => {
|
|
@@ -1,25 +1,69 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var getHashKeySpace = require('./getHashKeySpace.js');
|
|
4
|
+
var updateItemHashKey = require('./updateItemHashKey.js');
|
|
5
|
+
var updateItemRangeKey = require('./updateItemRangeKey.js');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* Convert an {@link EntityItem | `EntityItem`} into
|
|
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.
|
|
8
17
|
*
|
|
9
18
|
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
10
19
|
* @param entityToken - {@link Config | `Config`} `entities` key.
|
|
11
20
|
* @param item - {@link EntityItem | `EntityItem`} object.
|
|
12
21
|
* @param overwrite - Overwrite existing properties (default `false`).
|
|
13
22
|
*
|
|
14
|
-
* @returns {@link EntityKey | `EntityKey`}
|
|
23
|
+
* @returns Array of {@link EntityKey | `EntityKey`} values derived from `item`.
|
|
15
24
|
*
|
|
16
25
|
* @throws `Error` if `entityToken` is invalid.
|
|
17
26
|
*/
|
|
18
27
|
function getPrimaryKey(entityManager, entityToken, item, overwrite = false) {
|
|
19
28
|
const { hashKey, rangeKey } = entityManager.config;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
// If both keys are present and we're not overwriting, return the exact pair.
|
|
30
|
+
if (!overwrite && item[hashKey] && item[rangeKey]) {
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
[hashKey]: item[hashKey],
|
|
34
|
+
[rangeKey]: item[rangeKey],
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
// Compute/refresh the range key (throws if uniqueProperty missing).
|
|
39
|
+
const withRangeKey = updateItemRangeKey.updateItemRangeKey(entityManager, entityToken, item, true);
|
|
40
|
+
// If timestamp present, compute exactly one hash key and return single pair.
|
|
41
|
+
const tsProp = entityManager.config.entities[entityToken].timestampProperty;
|
|
42
|
+
if (withRangeKey[tsProp] !== undefined) {
|
|
43
|
+
const withHashKey = updateItemHashKey.updateItemHashKey(entityManager, entityToken, withRangeKey, true);
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
[hashKey]: withHashKey[hashKey],
|
|
47
|
+
[rangeKey]: withHashKey[rangeKey],
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
// No timestamp: enumerate hash-key space across all shard bumps (0..Infinity).
|
|
52
|
+
const hashKeys = getHashKeySpace.getHashKeySpace(entityManager, entityToken, hashKey, withRangeKey, 0, Infinity);
|
|
53
|
+
// Map to keys and de-duplicate.
|
|
54
|
+
const rk = withRangeKey[rangeKey];
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
const keys = hashKeys
|
|
57
|
+
.map((hk) => {
|
|
58
|
+
const key = { [hashKey]: hk, [rangeKey]: rk };
|
|
59
|
+
const sig = `${hk}|${rk}`;
|
|
60
|
+
if (seen.has(sig))
|
|
61
|
+
return undefined;
|
|
62
|
+
seen.add(sig);
|
|
63
|
+
return key;
|
|
64
|
+
})
|
|
65
|
+
.filter((k) => !!k);
|
|
66
|
+
return keys;
|
|
23
67
|
}
|
|
24
68
|
|
|
25
69
|
exports.getPrimaryKey = getPrimaryKey;
|
package/dist/index.d.ts
CHANGED
|
@@ -431,32 +431,18 @@ declare class EntityManager<C extends BaseConfigMap> {
|
|
|
431
431
|
*/
|
|
432
432
|
addKeys(entityToken: EntityToken<C>, item: EntityItem<C>[], overwrite?: boolean): EntityRecord<C>[];
|
|
433
433
|
/**
|
|
434
|
-
* Convert
|
|
434
|
+
* Convert one or more {@link EntityItem | `EntityItem`} objects into an array of {@link EntityKey | `EntityKey`} values.
|
|
435
435
|
*
|
|
436
436
|
* @param entityToken - {@link Config | `Config`} `entities` key.
|
|
437
|
-
* @param item - {@link EntityItem | `EntityItem`} object.
|
|
437
|
+
* @param item - {@link EntityItem | `EntityItem`} object, or array of them.
|
|
438
438
|
* @param overwrite - Overwrite existing properties (default `false`).
|
|
439
439
|
*
|
|
440
|
-
* @returns {@link EntityKey | `EntityKey`}
|
|
440
|
+
* @returns An array of {@link EntityKey | `EntityKey`} values. For a single input item, returns 0..N keys (usually 1).
|
|
441
|
+
* For an array input, returns a single flattened array of keys across all inputs.
|
|
441
442
|
*
|
|
442
443
|
* @throws `Error` if `entityToken` is invalid.
|
|
443
|
-
*
|
|
444
|
-
* @overload
|
|
445
|
-
*/
|
|
446
|
-
getPrimaryKey(entityToken: EntityToken<C>, item: EntityItem<C>, overwrite?: boolean): EntityKey<C>;
|
|
447
|
-
/**
|
|
448
|
-
* Convert an array of {@link EntityItem | `EntityItem`} objects into {@link EntityKey | `EntityKey`} objects.
|
|
449
|
-
*
|
|
450
|
-
* @param entityToken - {@link Config | `Config`} `entities` key.
|
|
451
|
-
* @param items - Array of {@link EntityItem | `EntityItem`} objects.
|
|
452
|
-
* @param overwrite - Overwrite existing properties (default `false`).
|
|
453
|
-
*
|
|
454
|
-
* @returns An array of {@link EntityKey | `EntityKey`} objects extracted from shallow clone of each `item` with updated properties.
|
|
455
|
-
*
|
|
456
|
-
* @throws `Error` if `entityToken` is invalid.
|
|
457
|
-
*
|
|
458
|
-
* @overload
|
|
459
444
|
*/
|
|
445
|
+
getPrimaryKey(entityToken: EntityToken<C>, item: EntityItem<C>, overwrite?: boolean): EntityKey<C>[];
|
|
460
446
|
getPrimaryKey(entityToken: EntityToken<C>, items: EntityItem<C>[], overwrite?: boolean): EntityKey<C>[];
|
|
461
447
|
/**
|
|
462
448
|
* Strips generated properties, hash key, and range key from an {@link EntityRecord | `EntityRecord`} object.
|
|
@@ -68,7 +68,7 @@ class EntityManager {
|
|
|
68
68
|
}
|
|
69
69
|
getPrimaryKey(entityToken, i, overwrite = false) {
|
|
70
70
|
if (Array.isArray(i)) {
|
|
71
|
-
return i.
|
|
71
|
+
return i.flatMap((item) => getPrimaryKey(this, entityToken, item, overwrite));
|
|
72
72
|
}
|
|
73
73
|
return getPrimaryKey(this, entityToken, i, overwrite);
|
|
74
74
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { range } from 'radash';
|
|
2
|
+
import stringHash from 'string-hash';
|
|
2
3
|
import { encodeGeneratedProperty } from './encodeGeneratedProperty.js';
|
|
3
4
|
import { validateGeneratedProperty } from './validateGeneratedProperty.js';
|
|
4
5
|
|
|
@@ -22,6 +23,9 @@ function getHashKeySpace(entityManager, entityToken, hashKeyToken, item, timesta
|
|
|
22
23
|
if (hashKeyToken !== entityManager.config.hashKey)
|
|
23
24
|
validateGeneratedProperty(entityManager, hashKeyToken, true);
|
|
24
25
|
const { shardBumps } = entityManager.config.entities[entityToken];
|
|
26
|
+
// Detect presence of the entity's unique property on the item.
|
|
27
|
+
const uniqueProp = entityManager.config.entities[entityToken].uniqueProperty;
|
|
28
|
+
const uniqueValue = item[uniqueProp];
|
|
25
29
|
const hashKeySpace = shardBumps
|
|
26
30
|
// Filter shard bumps by timestamp range.
|
|
27
31
|
.filter((bump, i) => (i === shardBumps.length - 1 ||
|
|
@@ -30,9 +34,18 @@ function getHashKeySpace(entityManager, entityToken, hashKeyToken, item, timesta
|
|
|
30
34
|
// Generate shard key space.
|
|
31
35
|
.flatMap(({ charBits, chars }) => {
|
|
32
36
|
const radix = 2 ** charBits;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
// If the item's unique property is present, deterministically
|
|
38
|
+
// compute exactly one shard suffix for this bump. Otherwise,
|
|
39
|
+
// enumerate the full shard space for the bump.
|
|
40
|
+
if (chars) {
|
|
41
|
+
if (uniqueValue) {
|
|
42
|
+
const space = radix ** chars;
|
|
43
|
+
const mod = stringHash(uniqueValue) % space;
|
|
44
|
+
return mod.toString(radix).padStart(chars, '0');
|
|
45
|
+
}
|
|
46
|
+
return [...range(0, radix ** chars - 1)].map((char) => char.toString(radix).padStart(chars, '0'));
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
36
49
|
})
|
|
37
50
|
// Map shard keys to hash keys.
|
|
38
51
|
.map((shardKey) => {
|
|
@@ -1,23 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { getHashKeySpace } from './getHashKeySpace.js';
|
|
2
|
+
import { updateItemHashKey } from './updateItemHashKey.js';
|
|
3
|
+
import { updateItemRangeKey } from './updateItemRangeKey.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Convert an {@link EntityItem | `EntityItem`} into
|
|
6
|
+
* Convert an {@link EntityItem | `EntityItem`} into one or more {@link EntityKey | `EntityKey`} values.
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
* - Always returns an array of keys.
|
|
10
|
+
* - If `overwrite` is false and the item already has both hash and range keys, returns exactly that pair.
|
|
11
|
+
* - Otherwise, computes the range key. Then:
|
|
12
|
+
* - If the timestampProperty is present, computes exactly one hash key and returns a single key.
|
|
13
|
+
* - If the timestampProperty is missing, enumerates the hash-key space across all shard bumps
|
|
14
|
+
* (with uniqueProperty present → one suffix per bump) and returns one key per bump.
|
|
6
15
|
*
|
|
7
16
|
* @param entityManager - {@link EntityManager | `EntityManager`} instance.
|
|
8
17
|
* @param entityToken - {@link Config | `Config`} `entities` key.
|
|
9
18
|
* @param item - {@link EntityItem | `EntityItem`} object.
|
|
10
19
|
* @param overwrite - Overwrite existing properties (default `false`).
|
|
11
20
|
*
|
|
12
|
-
* @returns {@link EntityKey | `EntityKey`}
|
|
21
|
+
* @returns Array of {@link EntityKey | `EntityKey`} values derived from `item`.
|
|
13
22
|
*
|
|
14
23
|
* @throws `Error` if `entityToken` is invalid.
|
|
15
24
|
*/
|
|
16
25
|
function getPrimaryKey(entityManager, entityToken, item, overwrite = false) {
|
|
17
26
|
const { hashKey, rangeKey } = entityManager.config;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
// If both keys are present and we're not overwriting, return the exact pair.
|
|
28
|
+
if (!overwrite && item[hashKey] && item[rangeKey]) {
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
[hashKey]: item[hashKey],
|
|
32
|
+
[rangeKey]: item[rangeKey],
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
// Compute/refresh the range key (throws if uniqueProperty missing).
|
|
37
|
+
const withRangeKey = updateItemRangeKey(entityManager, entityToken, item, true);
|
|
38
|
+
// If timestamp present, compute exactly one hash key and return single pair.
|
|
39
|
+
const tsProp = entityManager.config.entities[entityToken].timestampProperty;
|
|
40
|
+
if (withRangeKey[tsProp] !== undefined) {
|
|
41
|
+
const withHashKey = updateItemHashKey(entityManager, entityToken, withRangeKey, true);
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
[hashKey]: withHashKey[hashKey],
|
|
45
|
+
[rangeKey]: withHashKey[rangeKey],
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
// No timestamp: enumerate hash-key space across all shard bumps (0..Infinity).
|
|
50
|
+
const hashKeys = getHashKeySpace(entityManager, entityToken, hashKey, withRangeKey, 0, Infinity);
|
|
51
|
+
// Map to keys and de-duplicate.
|
|
52
|
+
const rk = withRangeKey[rangeKey];
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
const keys = hashKeys
|
|
55
|
+
.map((hk) => {
|
|
56
|
+
const key = { [hashKey]: hk, [rangeKey]: rk };
|
|
57
|
+
const sig = `${hk}|${rk}`;
|
|
58
|
+
if (seen.has(sig))
|
|
59
|
+
return undefined;
|
|
60
|
+
seen.add(sig);
|
|
61
|
+
return key;
|
|
62
|
+
})
|
|
63
|
+
.filter((k) => !!k);
|
|
64
|
+
return keys;
|
|
21
65
|
}
|
|
22
66
|
|
|
23
67
|
export { getPrimaryKey };
|
package/package.json
CHANGED