@wardx/core 0.5.0 → 0.7.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.
package/README.md CHANGED
@@ -117,7 +117,7 @@ core.histogram('coins.award_size').observe(80, { grantId: 'g-80' });
117
117
  `timer(name, dims)` starts a timer. The returned function records the duration in milliseconds into a histogram. You can add dimensions when you stop the timer.
118
118
 
119
119
  `distinct(name, dims).add(identifier)` updates a fixed HyperLogLog sketch. The
120
- engine hashes the identifier as `SHA-256(privacySalt || 0x00 || identifier)` and
120
+ engine hashes the identifier as `XXHash64(privacySalt || 0x00 || identifier)` and
121
121
  discards it immediately; the frame contains only 512 HLL registers (`p=9`,
122
122
  about 4.6% standard error). Keep `privacySalt` stable across workers and time
123
123
  windows so equal identifiers map to equal registers.
@@ -239,7 +239,7 @@ bucket = hash / 2^32
239
239
 
240
240
  If `bucket >= allocation`, `assignVariant` returns `null`.
241
241
 
242
- `subjectHash` returns 64 lowercase hex digits of `SHA-256(UTF8(privacySalt) || 0x00 || UTF8(subjectId))`.
242
+ `subjectHash` returns 16 lowercase hex digits of `XXHash64(UTF8(privacySalt) || 0x00 || UTF8(subjectId))`.
243
243
 
244
244
  ## Use case 5: Build a frame for a custom transport
245
245
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wardx/core",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Runtime-agnostic Wardx engine for metrics, events, logs, Remote Config, and experiments.",
5
5
  "keywords": [
6
6
  "wardx",
@@ -40,5 +40,8 @@
40
40
  ],
41
41
  "publishConfig": {
42
42
  "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "xxhash-wasm": "1.1.0"
43
46
  }
44
47
  }
@@ -1,4 +1,6 @@
1
- import { createHash } from 'node:crypto';
1
+ import xxhash from 'xxhash-wasm';
2
+
3
+ const { h64ToString } = await xxhash();
2
4
 
3
5
  const encoder = new TextEncoder();
4
6
 
@@ -25,9 +27,5 @@ export function assignmentHash(experimentId, subjectId, salt) {
25
27
  }
26
28
 
27
29
  export function subjectHash(projectSalt, subjectId) {
28
- return createHash('sha256')
29
- .update(projectSalt, 'utf8')
30
- .update('\0', 'utf8')
31
- .update(subjectId, 'utf8')
32
- .digest('hex');
30
+ return h64ToString(`${projectSalt}\0${subjectId}`);
33
31
  }
@@ -1,4 +1,4 @@
1
- import { createHash } from 'node:crypto';
1
+ import { subjectHash } from '../config/hash.js';
2
2
 
3
3
  export const HLL_PRECISION = 9;
4
4
  export const HLL_REGISTER_COUNT = 1 << HLL_PRECISION;
@@ -82,11 +82,7 @@ export class HyperLogLog {
82
82
  if (typeof identifier !== 'string' || identifier.length === 0) {
83
83
  throw new Error('distinct.add requires a non-empty string');
84
84
  }
85
- const digest = createHash('sha256')
86
- .update(this.privacySalt, 'utf8')
87
- .update('\0', 'utf8')
88
- .update(identifier, 'utf8')
89
- .digest();
85
+ const digest = Buffer.from(subjectHash(this.privacySalt, identifier), 'hex');
90
86
  const index = (digest[0] << 1) | (digest[1] >> 7);
91
87
  const rank = rankAfterIndex(digest);
92
88
  if (rank > this.registers[index]) this.registers[index] = rank;