@wardx/core 0.7.0 → 0.9.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
@@ -79,11 +79,16 @@ const settings = {
79
79
 
80
80
  const core = new WardxCore(settings);
81
81
 
82
- core.counter('match.completed', { mode: 'ranked' }).inc();
83
- core.counter('coins.awarded').add(25);
84
- core.gauge('players.online').set(12921);
85
- core.histogram('request.duration').observe(42);
86
- core.distinct('shot.traffic.hids', { result: 'violating' }).add(hid);
82
+ const completed = core.counter('match.completed', { mode: 'ranked' });
83
+ completed.inc();
84
+ const coinsAwarded = core.counter('coins.awarded');
85
+ coinsAwarded.add(25);
86
+ const playersOnline = core.gauge('players.online');
87
+ playersOnline.set(12921);
88
+ const requestDuration = core.histogram('request.duration');
89
+ requestDuration.observe(42);
90
+ const shotTrafficHids = core.distinct('shot.traffic.hids', { result: 'violating' });
91
+ shotTrafficHids.add(hid);
87
92
 
88
93
  const endTimer = core.timer('matchmaking.duration');
89
94
  endTimer({ result: 'success' });
@@ -108,8 +113,10 @@ const frames = core.takePendingFrames();
108
113
  `histogram(name).observe(value)` records a finite value into buckets. You can set buckets:
109
114
 
110
115
  ```js
111
- core.histogram('request.duration', { buckets: [10, 25, 50, 100] }).observe(42);
112
- core.histogram('coins.award_size').observe(80, { grantId: 'g-80' });
116
+ const requestDuration = core.histogram('request.duration', { buckets: [10, 25, 50, 100] });
117
+ requestDuration.observe(42);
118
+ const coinsAwardSize = core.histogram('coins.award_size');
119
+ coinsAwardSize.observe(80, { grantId: 'g-80' });
113
120
  ```
114
121
 
115
122
  `observe(value, attrs)` keeps `attrs` only when `value` is the window max. The frame stores that pair as `exemplar`. Attrs use the same key and value limits as dimensions. Do not change the buckets of an existing series. The engine throws an error.
@@ -250,7 +257,8 @@ If `bucket >= allocation`, `assignVariant` returns `null`.
250
257
  ```js
251
258
  import { FrameBuilder, PROTOCOL_VERSION, SDK_NAME, PLATFORM } from '@wardx/core';
252
259
 
253
- core.counter('match.completed').inc();
260
+ const matchCompleted = core.counter('match.completed');
261
+ matchCompleted.inc();
254
262
  const batch = core.snapshotIfDirty();
255
263
  if (batch) {
256
264
  const frames = core.takePendingFrames();
@@ -311,3 +319,13 @@ server persists UTC cohorts and exact received-user D1/D7/D30 returns; the core
311
319
  does not infer activity or keep a durable client identity/outbox. Use the same
312
320
  user ID and privacy salt across sessions and clients. See the
313
321
  [Node retention contract](../node/README.md#user-retention-d1--d7--d30).
322
+
323
+ ## Reuse metric handles
324
+
325
+ Bind counters, gauges, histograms, and distinct handles once per stable name and
326
+ dimension set. Keep them in the owning module or component and measure through
327
+ the handles in callbacks. This avoids repeated dimension validation and registry
328
+ lookup. Normal snapshots reset values while preserving handles; recreate bindings
329
+ when replacing the core. Timer stop functions belong to individual operations.
330
+ See the [Node pattern](../node/README.md#recommended-bind-once-measure-through-handles)
331
+ and [C# field pattern](../../clients/csharp/README.md#recommended-keep-handles-as-fields).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wardx/core",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Runtime-agnostic Wardx engine for metrics, events, logs, Remote Config, and experiments.",
5
5
  "keywords": [
6
6
  "wardx",
@@ -1,6 +1,6 @@
1
1
  import xxhash from 'xxhash-wasm';
2
2
 
3
- const { h64ToString } = await xxhash();
3
+ const { h64 } = await xxhash();
4
4
 
5
5
  const encoder = new TextEncoder();
6
6
 
@@ -26,6 +26,10 @@ export function assignmentHash(experimentId, subjectId, salt) {
26
26
  return fnv1a32(`${experimentId}:${subjectId}:${salt}`);
27
27
  }
28
28
 
29
+ export function subjectHash64(projectSalt, subjectId) {
30
+ return h64(`${projectSalt}\0${subjectId}`);
31
+ }
32
+
29
33
  export function subjectHash(projectSalt, subjectId) {
30
- return h64ToString(`${projectSalt}\0${subjectId}`);
34
+ return subjectHash64(projectSalt, subjectId).toString(16).padStart(16, '0');
31
35
  }
package/src/index.d.ts CHANGED
@@ -67,6 +67,8 @@ export interface SdkDefaults {
67
67
  }
68
68
 
69
69
  export interface CreateWardxOptions extends Partial<SdkDefaults> {
70
+ attributes?: Record<string, string | number | boolean>;
71
+ enabled?: boolean;
70
72
  endpoint: string;
71
73
  projectKey: string;
72
74
  project: string;
@@ -89,6 +91,8 @@ export interface CoreSettings extends SdkDefaults {
89
91
  }
90
92
 
91
93
  export interface ResolvedSettings extends SdkDefaults {
94
+ attributes?: Record<string, string | number | boolean>;
95
+ enabled?: boolean;
92
96
  endpoint: string;
93
97
  projectKey: string;
94
98
  project: string;
@@ -1,8 +1,9 @@
1
- import { subjectHash } from '../config/hash.js';
1
+ import { subjectHash64 } from '../config/hash.js';
2
2
 
3
3
  export const HLL_PRECISION = 9;
4
4
  export const HLL_REGISTER_COUNT = 1 << HLL_PRECISION;
5
5
  export const HLL_MAX_RANK = 64 - HLL_PRECISION + 1;
6
+ const INDEX_SHIFT = BigInt(64 - HLL_PRECISION);
6
7
 
7
8
  function assertRegisters(registers) {
8
9
  if (!(registers instanceof Uint8Array) || registers.length !== HLL_REGISTER_COUNT) {
@@ -13,13 +14,10 @@ function assertRegisters(registers) {
13
14
  }
14
15
  }
15
16
 
16
- function rankAfterIndex(digest) {
17
- let rank = 1;
18
- for (let bit = HLL_PRECISION; bit < 64; bit++) {
19
- if ((digest[bit >> 3] & (1 << (7 - (bit & 7)))) !== 0) return rank;
20
- rank += 1;
21
- }
22
- return rank;
17
+ export function rankAfterIndex(digest) {
18
+ const remainingHigh = Number(digest >> 32n) << HLL_PRECISION;
19
+ if (remainingHigh !== 0) return Math.clz32(remainingHigh) + 1;
20
+ return 32 - HLL_PRECISION + Math.clz32(Number(digest & 0xffffffffn)) + 1;
23
21
  }
24
22
 
25
23
  export function encodeHllRegisters(registers) {
@@ -82,8 +80,8 @@ export class HyperLogLog {
82
80
  if (typeof identifier !== 'string' || identifier.length === 0) {
83
81
  throw new Error('distinct.add requires a non-empty string');
84
82
  }
85
- const digest = Buffer.from(subjectHash(this.privacySalt, identifier), 'hex');
86
- const index = (digest[0] << 1) | (digest[1] >> 7);
83
+ const digest = subjectHash64(this.privacySalt, identifier);
84
+ const index = Number(digest >> INDEX_SHIFT);
87
85
  const rank = rankAfterIndex(digest);
88
86
  if (rank > this.registers[index]) this.registers[index] = rank;
89
87
  this.dirty = true;