@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 +26 -8
- package/package.json +1 -1
- package/src/config/hash.js +6 -2
- package/src/index.d.ts +4 -0
- package/src/metrics/HyperLogLog.js +8 -10
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' })
|
|
83
|
-
|
|
84
|
-
core.
|
|
85
|
-
|
|
86
|
-
core.
|
|
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] })
|
|
112
|
-
|
|
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')
|
|
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
package/src/config/hash.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import xxhash from 'xxhash-wasm';
|
|
2
2
|
|
|
3
|
-
const {
|
|
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
|
|
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 {
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 =
|
|
86
|
-
const index = (digest
|
|
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;
|