@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 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 just the primary key
196
- const keyOnly = manager.getPrimaryKey('user', user); // { hashKey, rangeKey }
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.map((item) => getPrimaryKey.getPrimaryKey(this, entityToken, item, overwrite));
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
- return chars
36
- ? [...radash.range(0, radix ** chars - 1)].map((char) => char.toString(radix).padStart(chars, '0'))
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 radash = require('radash');
4
- var addKeys = require('./addKeys.js');
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 an {@link EntityKey | `EntityKey`}.
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`} extracted from shallow clone of `item` with updated properties.
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
- return radash.pick(!overwrite && item[hashKey] && item[rangeKey]
21
- ? item
22
- : addKeys.addKeys(entityManager, entityToken, item, overwrite), [entityManager.config.hashKey, entityManager.config.rangeKey]);
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 an {@link EntityItem | `EntityItem`} into an {@link EntityKey | `EntityKey`}.
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`} extracted from shallow clone of `item` with updated properties.
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.map((item) => getPrimaryKey(this, entityToken, item, overwrite));
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
- return chars
34
- ? [...range(0, radix ** chars - 1)].map((char) => char.toString(radix).padStart(chars, '0'))
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 { pick } from 'radash';
2
- import { addKeys } from './addKeys.js';
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 an {@link EntityKey | `EntityKey`}.
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`} extracted from shallow clone of `item` with updated properties.
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
- return pick(!overwrite && item[hashKey] && item[rangeKey]
19
- ? item
20
- : addKeys(entityManager, entityToken, item, overwrite), [entityManager.config.hashKey, entityManager.config.rangeKey]);
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
@@ -131,5 +131,5 @@
131
131
  },
132
132
  "type": "module",
133
133
  "types": "dist/index.d.ts",
134
- "version": "6.14.1"
134
+ "version": "6.14.3"
135
135
  }