@fluxtrace/node 3.9.0 → 3.10.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 +138 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @fluxtrace/node
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fluxtrace/node)
|
|
4
|
+
|
|
5
|
+
Server-side tracking, identification, and feature flags for FluxTrace.
|
|
6
|
+
Requires Node.js 20 or later. ESM only.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @fluxtrace/node
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- Package: <https://www.npmjs.com/package/@fluxtrace/node>
|
|
15
|
+
- Zero runtime dependencies. TypeScript types included.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { FluxtraceClient } from "@fluxtrace/node";
|
|
21
|
+
|
|
22
|
+
const client = new FluxtraceClient({
|
|
23
|
+
trackingId: "YOUR_TRACKING_ID",
|
|
24
|
+
endpoint: "https://collector.example.com/api/collect",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await client.identify("user-123", { plan: "pro" });
|
|
28
|
+
await client.track("checkout_completed", { amount: 4999 });
|
|
29
|
+
|
|
30
|
+
if (await client.isFeatureEnabled("new-checkout")) {
|
|
31
|
+
// ...
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## API
|
|
36
|
+
|
|
37
|
+
### `new FluxtraceClient(options)`
|
|
38
|
+
|
|
39
|
+
| Option | Required | Default | Description |
|
|
40
|
+
| ------------ | -------- | -------------- | -------------------------------------------------------- |
|
|
41
|
+
| `trackingId` | yes | — | Project tracking id sent as `projectId` on collect |
|
|
42
|
+
| `endpoint` | no | `/api/collect` | Absolute collect URL (the relative default can't be called from Node — always set this server-side) |
|
|
43
|
+
| `apiHost` | no | — | Web origin for flag lookups. When set, flags use `subjectId`; when unset they stay on the collector origin with the deprecated `userId` alias |
|
|
44
|
+
| `fetch` | no | global `fetch` | Override for tests |
|
|
45
|
+
|
|
46
|
+
### `track(name, props?)`
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
await client.track("checkout_completed", {
|
|
50
|
+
amount: 4999,
|
|
51
|
+
currency: "USD",
|
|
52
|
+
coupon: null, // null/undefined values are allowed
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Throws when `name` is missing. Payloads are validated against the
|
|
57
|
+
FluxTrace collect schema client-side before sending.
|
|
58
|
+
|
|
59
|
+
### `identify(userId, traits?)`
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
await client.identify("user-123", { plan: "pro", role: "admin" });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Sends an identify call and remembers `userId` on the instance, so later
|
|
66
|
+
`track` calls are attributed to that user.
|
|
67
|
+
|
|
68
|
+
### `getFlags(userId?)`
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// Flags for the identified user…
|
|
72
|
+
const mine = await client.getFlags();
|
|
73
|
+
// …or for someone else
|
|
74
|
+
const theirs = await client.getFlags("user-456");
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Returns the evaluated flag map (`Record<string, boolean>`). Throws
|
|
78
|
+
`CollectError` on HTTP failure. Flag fetching never blocks `track`.
|
|
79
|
+
|
|
80
|
+
### `isFeatureEnabled(key, userId?)`
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
if (await client.isFeatureEnabled("new-checkout", "user-456")) {
|
|
84
|
+
// serve the new experience
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Returns `true` only when the flag is explicitly `true`.
|
|
89
|
+
|
|
90
|
+
## Reliability
|
|
91
|
+
|
|
92
|
+
- Up to 3 delivery attempts per call with linear backoff.
|
|
93
|
+
- HTTP `429` responses are retried; other non-2xx responses throw
|
|
94
|
+
`CollectError` with the response `status`.
|
|
95
|
+
- Network failures are retried; anything else is thrown immediately.
|
|
96
|
+
|
|
97
|
+
## Errors
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { CollectError, FluxtraceClient } from "@fluxtrace/node";
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await client.track("purchase", { amount: 100 });
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (err instanceof CollectError && err.status === 429) {
|
|
106
|
+
// rate limited — already retried 3× inside the client
|
|
107
|
+
}
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`CollectError extends Error` carries a numeric `status` (`400` for invalid
|
|
113
|
+
payloads, the HTTP status otherwise).
|
|
114
|
+
|
|
115
|
+
## Testing
|
|
116
|
+
|
|
117
|
+
Inject a stub `fetch` — no network needed:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const calls: unknown[] = [];
|
|
121
|
+
const client = new FluxtraceClient({
|
|
122
|
+
trackingId: "test-project",
|
|
123
|
+
endpoint: "https://collector.test/api/collect",
|
|
124
|
+
fetch: (async (url: unknown, init?: { body?: string }) => {
|
|
125
|
+
calls.push(JSON.parse(init!.body!));
|
|
126
|
+
return new Response(null, { status: 204 });
|
|
127
|
+
}) as typeof fetch,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await client.track("signup");
|
|
131
|
+
console.log(calls.length); // 1
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Changelog
|
|
135
|
+
|
|
136
|
+
### 3.9.0
|
|
137
|
+
|
|
138
|
+
- Initial public release.
|