@wardx/core 0.1.1 → 0.1.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 +16 -8
- package/package.json +2 -2
- package/src/WardxCore.js +27 -8
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ import { WardxCore, assignVariant, loadSdkDefaults } from '@wardx/core';
|
|
|
27
27
|
- A measure call does not wait for a Promise.
|
|
28
28
|
- If a buffer is full, the engine discards data. The engine does not block the application.
|
|
29
29
|
- Counters in a frame are window deltas. Counters are not lifetime totals.
|
|
30
|
+
- `identify(subjectId)` sets the default subject for this instance. A per-call `{ subjectId }` overrides it.
|
|
30
31
|
|
|
31
32
|
## Settings
|
|
32
33
|
|
|
@@ -125,7 +126,7 @@ A dimension value must be a string, a number, or a boolean.
|
|
|
125
126
|
```js
|
|
126
127
|
core.event('purchase', { product: 'premium' });
|
|
127
128
|
core.log.info('match_started', { mode: 'ranked', players: 4 });
|
|
128
|
-
core.log.error('payment_failed', { code: 'timeout' });
|
|
129
|
+
core.log.error('payment_failed', { code: 'timeout', stack: 'PaymentError: timeout' });
|
|
129
130
|
```
|
|
130
131
|
|
|
131
132
|
Log levels: `debug`, `info`, `warn`, `error`.
|
|
@@ -138,7 +139,9 @@ Log levels: `debug`, `info`, `warn`, `error`.
|
|
|
138
139
|
|
|
139
140
|
Dropped items increment `wardx.internal.events_dropped` or `wardx.internal.logs_dropped`.
|
|
140
141
|
|
|
141
|
-
Use counters and histograms for rates and latency. Use events for rare product facts: a purchase, an experiment exposure or goal, a named screen. Use logs for failures. Do not put user ids on metric dimensions.
|
|
142
|
+
Use counters and histograms for rates and latency. Use events for rare product facts: a purchase, an experiment exposure or goal, a named screen. Use logs for failures. Put a clipped `stack` or a provider `code` on the log attrs so MCP `get_recent_logs` can show an agent where to look. The engine does not edit source. Do not put user ids on metric dimensions.
|
|
143
|
+
|
|
144
|
+
A volume funnel is one event name and one counter per step. The engine does not join events by subject. See `docs/ARCHITECTURE.md`.
|
|
142
145
|
|
|
143
146
|
## Use case 3: Get Remote Config and assign an experiment
|
|
144
147
|
|
|
@@ -146,6 +149,8 @@ Use counters and histograms for rates and latency. Use events for rare product f
|
|
|
146
149
|
|
|
147
150
|
**Objective:** Get a config value. If an experiment applies, get the variant value.
|
|
148
151
|
|
|
152
|
+
There is a default subject after `identify(subjectId)`. Pass `{ subjectId }` on a call to override it, or when one process serves many users. Use a stable account id, not `sessionId`. With no subject, `configGet` returns the Remote Config value and that call is not in the A/B test.
|
|
153
|
+
|
|
149
154
|
```js
|
|
150
155
|
core.applyConfig(13, {
|
|
151
156
|
values: {
|
|
@@ -168,23 +173,26 @@ core.applyConfig(13, {
|
|
|
168
173
|
});
|
|
169
174
|
|
|
170
175
|
const fallback = 1000;
|
|
171
|
-
const
|
|
172
|
-
core.
|
|
176
|
+
const shared = core.configGet('message.delayMs', fallback);
|
|
177
|
+
core.identify('user-1');
|
|
178
|
+
const delay = core.configGet('message.delayMs', fallback);
|
|
179
|
+
core.experimentGoal('message.sent', { value: 1 });
|
|
180
|
+
const other = core.configGet('message.delayMs', fallback, { subjectId: 'user-2' });
|
|
173
181
|
```
|
|
174
182
|
|
|
183
|
+
`shared` is always the snapshot value (`1000`). `delay` is `1000` or `400` for `user-1`. `other` is the variant for `user-2`. The same `subjectId`, experiment `id`, and `salt` always map to the same variant. You do not persist the group. Changing `salt` redistributes the population. `identify(null)` clears the default.
|
|
184
|
+
|
|
175
185
|
### Resolution order
|
|
176
186
|
|
|
177
187
|
1. If the key is not in the snapshot, return `fallback`.
|
|
178
|
-
2. If `
|
|
188
|
+
2. If there is no subject (`identify` unset and no `{ subjectId }`), return the Remote Config value.
|
|
179
189
|
3. If no enabled experiment contains the key, return the Remote Config value.
|
|
180
190
|
4. If the subject is not in the allocation, return the Remote Config value.
|
|
181
191
|
5. If the subject is in the allocation, return the variant value.
|
|
182
192
|
|
|
183
|
-
The assignment is deterministic. The same `experimentId`, `subjectId`, and `salt` always give the same variant.
|
|
184
|
-
|
|
185
193
|
The first resolve for a subject in a session emits event `experiment.exposure`. The payload contains a hashed subject. The payload does not contain the raw `subjectId`.
|
|
186
194
|
|
|
187
|
-
`experimentGoal` emits event `experiment.goal`.
|
|
195
|
+
`experimentGoal` emits event `experiment.goal`. The subject comes from `identify()` or from `{ subjectId }` on that call. You can supply `value`. Use milliseconds for a session-duration goal. The server stores that number as `goalSum` and `goalMean` per variant. Without a subject, the call throws. One experiment should have one quantitative goal name.
|
|
188
196
|
|
|
189
197
|
## Use case 4: Assign a variant without WardxCore
|
|
190
198
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wardx/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Runtime-agnostic Wardx engine for metrics, events, logs, Remote Config, and experiments.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wardx",
|
|
@@ -36,4 +36,4 @@
|
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
38
38
|
}
|
|
39
|
-
}
|
|
39
|
+
}
|
package/src/WardxCore.js
CHANGED
|
@@ -40,6 +40,7 @@ export class WardxCore {
|
|
|
40
40
|
this.seq = 0;
|
|
41
41
|
this.pendingFrames = [];
|
|
42
42
|
this.windowStart = Date.now();
|
|
43
|
+
this._subjectId = null;
|
|
43
44
|
this.log = {
|
|
44
45
|
debug: (message, attrs) => this._log('debug', message, attrs),
|
|
45
46
|
info: (message, attrs) => this._log('info', message, attrs),
|
|
@@ -96,30 +97,48 @@ export class WardxCore {
|
|
|
96
97
|
return wrapped;
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
identify(subjectId) {
|
|
101
|
+
if (subjectId === undefined || subjectId === null) {
|
|
102
|
+
this._subjectId = null;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (typeof subjectId !== 'string' || subjectId.length === 0) {
|
|
106
|
+
throw new Error('identify requires a non-empty subjectId');
|
|
107
|
+
}
|
|
108
|
+
this._subjectId = subjectId;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_subjectIdFrom(context) {
|
|
112
|
+
if (context && context.subjectId !== undefined && context.subjectId !== null) {
|
|
113
|
+
return context.subjectId;
|
|
114
|
+
}
|
|
115
|
+
return this._subjectId;
|
|
116
|
+
}
|
|
117
|
+
|
|
99
118
|
configGet(key, fallback, context) {
|
|
100
119
|
if (!this.configStore.has(key)) return fallback;
|
|
101
120
|
const remote = this.configStore.getRaw(key);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
121
|
+
const subjectId = this._subjectIdFrom(context);
|
|
122
|
+
if (subjectId === undefined || subjectId === null) return remote;
|
|
105
123
|
return this.experiments.resolve(
|
|
106
124
|
key,
|
|
107
125
|
remote,
|
|
108
|
-
|
|
126
|
+
subjectId,
|
|
109
127
|
this.configStore.experimentsByKey
|
|
110
128
|
);
|
|
111
129
|
}
|
|
112
130
|
|
|
113
131
|
experimentGoal(name, context) {
|
|
114
|
-
|
|
132
|
+
const subjectId = this._subjectIdFrom(context);
|
|
133
|
+
if (subjectId === undefined || subjectId === null) {
|
|
115
134
|
throw new Error('experiment.goal requires subjectId');
|
|
116
135
|
}
|
|
117
136
|
if (typeof name !== 'string' || name.length === 0) {
|
|
118
137
|
throw new Error('experiment.goal requires a metric name');
|
|
119
138
|
}
|
|
120
|
-
const subject = this.experiments.hashSubject(
|
|
139
|
+
const subject = this.experiments.hashSubject(subjectId);
|
|
121
140
|
const experiments = this.experiments.relevantExperiments(
|
|
122
|
-
|
|
141
|
+
subjectId,
|
|
123
142
|
this.configStore.experiments
|
|
124
143
|
);
|
|
125
144
|
const payload = {
|
|
@@ -127,7 +146,7 @@ export class WardxCore {
|
|
|
127
146
|
subject,
|
|
128
147
|
experiments
|
|
129
148
|
};
|
|
130
|
-
if (context.value !== undefined) payload.value = context.value;
|
|
149
|
+
if (context && context.value !== undefined) payload.value = context.value;
|
|
131
150
|
this.event('experiment.goal', payload);
|
|
132
151
|
}
|
|
133
152
|
|