@karmaniverous/entity-manager 6.14.0 → 6.14.2

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.
@@ -28,7 +28,7 @@ function encodeGeneratedProperty(entityManager, property, item) {
28
28
  ...(sharded
29
29
  ? [item[entityManager.config.hashKey]]
30
30
  : []),
31
- ...elementMap.map(([element, value]) => [element, (value ?? '').toString()].join(entityManager.config.generatedValueDelimiter)),
31
+ ...elementMap.map(([element, value]) => [element, `${value ?? ''}`].join(entityManager.config.generatedValueDelimiter)),
32
32
  ].join(entityManager.config.generatedKeyDelimiter);
33
33
  entityManager.logger.debug('encoded generated property', {
34
34
  property,
@@ -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) => {
@@ -45,7 +45,9 @@ function rehydratePageKeyMap(entityManager, entityToken, indexTokens, item, dehy
45
45
  if (hashKeys.length > 1)
46
46
  throw new Error('inconsistent hashKeys');
47
47
  const [hashKeyToken] = hashKeys;
48
- indexTokens.map((index) => validateIndexToken(entityManager, index));
48
+ indexTokens.map((index) => {
49
+ validateIndexToken(entityManager, index);
50
+ });
49
51
  // Shortcut empty dehydrated.
50
52
  if (dehydrated && !dehydrated.length)
51
53
  return [hashKeyToken, {}];
@@ -15,14 +15,16 @@ function removeKeys(entityManager, entityToken, item) {
15
15
  try {
16
16
  // Validate params.
17
17
  validateEntityToken(entityManager, entityToken);
18
- // Delete hash & range keys.
19
- const newItem = { ...item };
20
- delete newItem[entityManager.config.hashKey];
21
- delete newItem[entityManager.config.rangeKey];
22
- // Delete generated properties.
23
- const { sharded, unsharded } = entityManager.config.generatedProperties;
24
- for (const property in { ...sharded, ...unsharded })
25
- delete newItem[property];
18
+ // Build a set of keys to strip (hash, range, and all generated properties).
19
+ const { hashKey, rangeKey, generatedProperties: { sharded, unsharded }, } = entityManager.config;
20
+ const keysToStrip = new Set([
21
+ hashKey,
22
+ rangeKey,
23
+ ...Object.keys(sharded),
24
+ ...Object.keys(unsharded),
25
+ ]);
26
+ // Create a shallow copy of item omitting the keys above (no delete operator).
27
+ const newItem = Object.fromEntries(Object.entries(item).filter(([key]) => !keysToStrip.has(key)));
26
28
  entityManager.logger.debug('stripped entity item generated properties', {
27
29
  entityToken,
28
30
  item,
@@ -40,12 +40,15 @@ function updateItemHashKey(entityManager, entityToken, item, overwrite = false)
40
40
  if (chars) {
41
41
  // Radix is the numerical base of the shardKey.
42
42
  const radix = 2 ** charBits;
43
+ // Compute the full shard space for this bump. Use radix ** chars to ensure
44
+ // all placeholders are utilized (e.g., chars=2, charBits=2 => 16 combos).
45
+ const space = radix ** chars;
43
46
  // Get item unique property & validate.
44
47
  const uniqueId = item[entityManager.config.entities[entityToken]
45
48
  .uniqueProperty];
46
49
  if (isNil(uniqueId))
47
50
  throw new Error(`missing item unique property`);
48
- hashKey += (stringHash(uniqueId.toString()) % (chars * radix))
51
+ hashKey += (stringHash(uniqueId) % space)
49
52
  .toString(radix)
50
53
  .padStart(chars, '0');
51
54
  }
package/package.json CHANGED
@@ -5,58 +5,52 @@
5
5
  },
6
6
  "dependencies": {
7
7
  "@karmaniverous/batch-process": "^0.1.0",
8
- "@karmaniverous/entity-tools": "^0.6.8",
8
+ "@karmaniverous/entity-tools": "^0.6.9",
9
9
  "@karmaniverous/string-utilities": "^0.2.1",
10
10
  "lz-string": "^1.5.0",
11
- "radash": "^12.1.0",
11
+ "radash": "^12.1.1",
12
12
  "string-hash": "^1.1.3",
13
- "zod": "^3.23.8"
13
+ "zod": "^4.1.12"
14
14
  },
15
15
  "description": "Rational indexing & cross-shard querying at scale in your NoSQL database so you can focus on your application logic.",
16
16
  "devDependencies": {
17
- "@dotenvx/dotenvx": "^1.23.0",
18
- "@eslint/js": "^9.14.0",
19
- "@faker-js/faker": "^9.2.0",
20
- "@karmaniverous/mock-db": "^0.3.4",
21
- "@rollup/plugin-alias": "^5.1.1",
22
- "@rollup/plugin-commonjs": "^28.0.1",
17
+ "@dotenvx/dotenvx": "^1.51.1",
18
+ "@eslint/js": "^9.39.1",
19
+ "@faker-js/faker": "^10.1.0",
20
+ "@karmaniverous/mock-db": "^0.3.5",
21
+ "@rollup/plugin-alias": "^6.0.0",
22
+ "@rollup/plugin-commonjs": "^29.0.0",
23
23
  "@rollup/plugin-json": "^6.1.0",
24
- "@rollup/plugin-node-resolve": "^15.3.0",
24
+ "@rollup/plugin-node-resolve": "^16.0.3",
25
25
  "@rollup/plugin-strip": "^3.0.4",
26
- "@rollup/plugin-typescript": "^12.1.1",
27
- "@types/chai": "^5.0.1",
28
- "@types/eslint__js": "^8.42.3",
29
- "@types/eslint-config-prettier": "^6.11.3",
30
- "@types/eslint-plugin-mocha": "^10.4.0",
31
- "@types/mocha": "^10.0.9",
32
- "@types/node": "^22.9.0",
26
+ "@rollup/plugin-typescript": "^12.3.0",
27
+ "@types/node": "^24.10.0",
33
28
  "@types/string-hash": "^1.1.3",
34
29
  "auto-changelog": "^2.5.0",
35
- "chai": "^5.1.2",
36
- "eslint": "^9.14.0",
37
- "eslint-config-prettier": "^9.1.0",
38
- "eslint-plugin-mocha": "^10.5.0",
30
+ "cross-env": "^10.1.0",
31
+ "eslint": "^9.39.1",
32
+ "eslint-config-prettier": "^10.1.8",
33
+ "eslint-plugin-prettier": "^5.5.4",
39
34
  "eslint-plugin-simple-import-sort": "^12.1.1",
40
- "eslint-plugin-tsdoc": "^0.3.0",
41
- "jsdom-global": "^3.0.2",
42
- "knip": "^5.37.0",
43
- "lefthook": "^1.8.2",
44
- "mocha": "^10.8.2",
45
- "nyc": "^17.1.0",
46
- "prettier": "^3.3.3",
47
- "release-it": "^17.10.0",
48
- "rimraf": "^6.0.1",
49
- "rollup": "^4.26.0",
50
- "rollup-plugin-dts": "^6.1.1",
51
- "source-map-support": "^0.5.21",
52
- "ts-node": "^10.9.2",
35
+ "eslint-plugin-tsdoc": "^0.4.0",
36
+ "knip": "^5.67.1",
37
+ "lefthook": "^2.0.2",
38
+ "prettier": "^3.6.2",
39
+ "release-it": "^19.0.5",
40
+ "rimraf": "^6.1.0",
41
+ "rollup": "^4.52.5",
42
+ "rollup-plugin-dts": "^6.2.3",
43
+ "tsd": "^0.33.0",
53
44
  "tslib": "^2.8.1",
54
- "typedoc": "^0.26.11",
55
- "typedoc-plugin-mdn-links": "^3.3.7",
56
- "typedoc-plugin-replace-text": "^4.0.0",
57
- "typedoc-plugin-zod": "^1.2.1",
58
- "typescript": "^5.6.3",
59
- "typescript-eslint": "^8.14.0"
45
+ "typedoc": "^0.28.14",
46
+ "typedoc-plugin-mdn-links": "^5.0.10",
47
+ "typedoc-plugin-replace-text": "^4.2.0",
48
+ "typedoc-plugin-zod": "^1.4.3",
49
+ "@vitest/coverage-v8": "^4.0.6",
50
+ "@vitest/eslint-plugin": "^1.4.0",
51
+ "vitest": "^4.0.6",
52
+ "typescript": "^5.9.3",
53
+ "typescript-eslint": "^8.46.3"
60
54
  },
61
55
  "exports": {
62
56
  ".": {
@@ -122,15 +116,20 @@
122
116
  },
123
117
  "scripts": {
124
118
  "build": "rimraf dist && rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",
119
+ "changelog": "auto-changelog",
125
120
  "docs": "typedoc",
126
121
  "knip": "knip",
127
- "lint": "eslint src/** && prettier -c src",
128
- "lint:fix": "eslint --fix src/** && prettier --write src",
122
+ "lint": "eslint .",
123
+ "lint:fix": "npm run lint -- --fix .",
129
124
  "release": "dotenvx run -f .env.local -- release-it",
130
- "release:pre": "dotenvx run -f .env.local -- release-it --no-git.requireBranch --github.prerelease --preRelease",
131
- "test": "dotenvx run --quiet --log-level=LOG_LEVEL=debug -- nyc mocha"
125
+ "release:pre": "npm run release -- --no-git.requireBranch --github.prerelease --preRelease",
126
+ "test": "vitest --run --silent",
127
+ "typecheck": "tsc && tsd"
128
+ },
129
+ "tsd": {
130
+ "directory": "test/types"
132
131
  },
133
132
  "type": "module",
134
133
  "types": "dist/index.d.ts",
135
- "version": "6.14.0"
134
+ "version": "6.14.2"
136
135
  }