@dif.sh/sdk 0.2.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 +133 -0
- package/dist/bucket.d.ts +25 -0
- package/dist/bucket.d.ts.map +1 -0
- package/dist/bucket.js +68 -0
- package/dist/bucket.js.map +1 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +32 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +20 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +70 -0
- package/dist/core.js.map +1 -0
- package/dist/exposure.d.ts +7 -0
- package/dist/exposure.d.ts.map +1 -0
- package/dist/exposure.js +34 -0
- package/dist/exposure.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +56 -0
- package/dist/server.js.map +1 -0
- package/dist/sha256.d.ts +9 -0
- package/dist/sha256.d.ts.map +1 -0
- package/dist/sha256.js +124 -0
- package/dist/sha256.js.map +1 -0
- package/dist/sinks/amplitude.d.ts +11 -0
- package/dist/sinks/amplitude.d.ts.map +1 -0
- package/dist/sinks/amplitude.js +13 -0
- package/dist/sinks/amplitude.js.map +1 -0
- package/dist/sinks/index.d.ts +5 -0
- package/dist/sinks/index.d.ts.map +1 -0
- package/dist/sinks/index.js +7 -0
- package/dist/sinks/index.js.map +1 -0
- package/dist/sinks/mixpanel.d.ts +11 -0
- package/dist/sinks/mixpanel.d.ts.map +1 -0
- package/dist/sinks/mixpanel.js +13 -0
- package/dist/sinks/mixpanel.js.map +1 -0
- package/dist/sinks/segment.d.ts +11 -0
- package/dist/sinks/segment.d.ts.map +1 -0
- package/dist/sinks/segment.js +13 -0
- package/dist/sinks/segment.js.map +1 -0
- package/dist/sinks/webhook.d.ts +7 -0
- package/dist/sinks/webhook.d.ts.map +1 -0
- package/dist/sinks/webhook.js +24 -0
- package/dist/sinks/webhook.js.map +1 -0
- package/dist/track.d.ts +3 -0
- package/dist/track.d.ts.map +1 -0
- package/dist/track.js +61 -0
- package/dist/track.js.map +1 -0
- package/dist/types.d.ts +121 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# @dif.sh/sdk
|
|
2
|
+
|
|
3
|
+
Client SDK for [dif.sh](https://dif.sh). Handles two things:
|
|
4
|
+
|
|
5
|
+
1. **Experiment assignment** — picks the variant for each user and fires a
|
|
6
|
+
deterministic exposure event.
|
|
7
|
+
2. **Metric tracking** — fires conversion / outcome events to dif.sh Cloud
|
|
8
|
+
so the analysis layer can compute lift.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @dif.sh/sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Initialize once, at app boot
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { dif } from "@dif.sh/sdk";
|
|
20
|
+
|
|
21
|
+
dif.init({
|
|
22
|
+
project: "acme-shop",
|
|
23
|
+
publishableKey: "dif_pk_live_…", // browser-safe write key
|
|
24
|
+
userId: () => currentUser?.id ?? null,
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Get a publishable key from your project's Settings → Keys tab in dif.sh
|
|
29
|
+
Cloud. Publishable keys are safe to embed in browser bundles; they can only
|
|
30
|
+
write to `/v1/track` and `/v1/exposure` and are scoped by origin allowlist.
|
|
31
|
+
|
|
32
|
+
## Experiment assignment
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const cta = dif("checkout-cta-v2", {
|
|
36
|
+
control: () => "Place order",
|
|
37
|
+
variant_a: () => "Get it today",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// at the render site
|
|
41
|
+
<button>{cta()}</button>;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
You normally don't write the `dif(...)` call by hand — `dif build` emits a
|
|
45
|
+
generated module with one typed export per active experiment. Import the
|
|
46
|
+
named export and call it:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { checkoutCta } from "../.dif/generated/client";
|
|
50
|
+
<button>{checkoutCta()}</button>;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Variant resolution is deterministic, sticky per user, and byte-compatible
|
|
54
|
+
with `dif-core` (Rust). One exposure event fires per `(experiment, user)`
|
|
55
|
+
per session.
|
|
56
|
+
|
|
57
|
+
## Metric tracking
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// Simple conversion
|
|
61
|
+
dif.track("completed_checkout");
|
|
62
|
+
|
|
63
|
+
// Revenue with value
|
|
64
|
+
dif.track("revenue", { value: 49, currency: "USD" });
|
|
65
|
+
|
|
66
|
+
// With overrides
|
|
67
|
+
dif.track("article_read", {
|
|
68
|
+
userId: "u_42", // override the configured resolver
|
|
69
|
+
props: { article_id: "a_91" }, // arbitrary extras
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Calls are fire-and-forget: one HTTP POST per event using `fetch` with
|
|
74
|
+
`keepalive: true`. The call never throws — bad analytics must not crash a
|
|
75
|
+
render. When `publishableKey` isn't configured, the call logs to
|
|
76
|
+
`console.debug` and drops.
|
|
77
|
+
|
|
78
|
+
## Server-side
|
|
79
|
+
|
|
80
|
+
For server-side tracking (route handlers, server actions, background jobs)
|
|
81
|
+
import `DifServer` from the `/server` subpath. It takes a **secret** token
|
|
82
|
+
(`dif_<env>_…`), not a publishable key:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { DifServer } from "@dif.sh/sdk/server";
|
|
86
|
+
|
|
87
|
+
const dif = new DifServer({ apiKey: process.env.DIF_KEY });
|
|
88
|
+
|
|
89
|
+
await dif.track({
|
|
90
|
+
metric: "completed_checkout",
|
|
91
|
+
userId: user.id,
|
|
92
|
+
value: 49,
|
|
93
|
+
currency: "USD",
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Never put a secret token in a browser bundle. They authenticate as the
|
|
98
|
+
project (read-only on most routes, write to ingest), and leaked secrets
|
|
99
|
+
must be rotated immediately via the Keys settings.
|
|
100
|
+
|
|
101
|
+
## React
|
|
102
|
+
|
|
103
|
+
The `@dif.sh/react` package provides a `<DifProvider>` and a `useDif()`
|
|
104
|
+
hook so React apps can call `track` from anywhere in the tree:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { DifProvider, useDif } from "@dif.sh/react";
|
|
108
|
+
|
|
109
|
+
<DifProvider config={{ project: "acme-shop", publishableKey: "dif_pk_live_…" }}>
|
|
110
|
+
<App />
|
|
111
|
+
</DifProvider>;
|
|
112
|
+
|
|
113
|
+
// inside a component
|
|
114
|
+
const { track } = useDif();
|
|
115
|
+
useEffect(() => track("completed_checkout"), []);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## What this package does, what it doesn't
|
|
119
|
+
|
|
120
|
+
**Does:**
|
|
121
|
+
- Variant lookup against the generated experiment spec.
|
|
122
|
+
- Deterministic SHA-256 bucketing — byte-compatible with `dif-core` (Rust).
|
|
123
|
+
- Audience predicate evaluation + exclusion-group resolution.
|
|
124
|
+
- One exposure event per `(experiment, user)` per session, to a configurable sink.
|
|
125
|
+
- Metric tracking (`dif.track`) to dif.sh Cloud, browser + server.
|
|
126
|
+
|
|
127
|
+
**Does not (in v0):**
|
|
128
|
+
- Batch events. Each call is one HTTP POST.
|
|
129
|
+
- Retry on failure. Browser drops; server warns.
|
|
130
|
+
- Buffer offline. If the request fails, the event is gone.
|
|
131
|
+
- Compute lift, p-values, or any analytics. That's the cloud's job.
|
|
132
|
+
|
|
133
|
+
See [../../PLAN.md](../../PLAN.md) for the architectural rationale.
|
package/dist/bucket.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Bucket namespace — must match `dif_core::BUCKET_NAMESPACE`. */
|
|
2
|
+
export declare const BUCKET_NAMESPACE = "dif.sh/v1";
|
|
3
|
+
/**
|
|
4
|
+
* Compute the deterministic 16-byte salt for an experiment id, returned as
|
|
5
|
+
* a 32-character lowercase hex string. Mirrors `salt_for` in Rust.
|
|
6
|
+
*
|
|
7
|
+
* The runtime SDK does **not** call this — the generated `client.ts`
|
|
8
|
+
* embeds the precomputed salt. This export exists for the fixture test
|
|
9
|
+
* and for tooling that wants to verify the embed.
|
|
10
|
+
*/
|
|
11
|
+
export declare function saltFor(experimentId: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Compute the bucket (0..9999) for a user under the given salt.
|
|
14
|
+
*
|
|
15
|
+
* @param saltHex 32-character lowercase hex. Embedded in the generated file.
|
|
16
|
+
* @param userId user id string; UTF-8 encoded before hashing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function bucket(saltHex: string, userId: string): number;
|
|
19
|
+
/**
|
|
20
|
+
* Pick the variant corresponding to a bucket given the experiment's declared
|
|
21
|
+
* variants and weights. Returns `null` if weights don't sum to 100 (which
|
|
22
|
+
* `dif validate` should have caught at build time).
|
|
23
|
+
*/
|
|
24
|
+
export declare function selectVariant(variants: readonly string[], weights: Record<string, number>, bucketValue: number): string | null;
|
|
25
|
+
//# sourceMappingURL=bucket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket.d.ts","sourceRoot":"","sources":["../src/bucket.ts"],"names":[],"mappings":"AAcA,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAK5C;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAQpD;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAY9D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CAQf"}
|
package/dist/bucket.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// TS port of `dif_core::bucket`. Must produce byte-identical buckets for
|
|
2
|
+
// every entry in `crates/dif-core/tests/fixtures/bucket_tests.json` — the
|
|
3
|
+
// fixture test in `bucket.test.ts` enforces that contract.
|
|
4
|
+
//
|
|
5
|
+
// Algorithm:
|
|
6
|
+
// 1. SHA-256 of (salt || user_id) — salt is 16 raw bytes, user_id UTF-8
|
|
7
|
+
// 2. Take the first 2 bytes as a big-endian u16
|
|
8
|
+
// 3. Modulo 10_000 → bucket in [0, 10_000)
|
|
9
|
+
//
|
|
10
|
+
// Variant selection walks variants in declared order, accumulating
|
|
11
|
+
// `weight * 100`; first variant whose cumulative crosses the bucket wins.
|
|
12
|
+
import { sha256, bytesToHex, hexToBytes } from "./sha256.js";
|
|
13
|
+
/** Bucket namespace — must match `dif_core::BUCKET_NAMESPACE`. */
|
|
14
|
+
export const BUCKET_NAMESPACE = "dif.sh/v1";
|
|
15
|
+
/** UTF-8 encoder. Reused so we don't allocate one per call. */
|
|
16
|
+
const TE = new TextEncoder();
|
|
17
|
+
/**
|
|
18
|
+
* Compute the deterministic 16-byte salt for an experiment id, returned as
|
|
19
|
+
* a 32-character lowercase hex string. Mirrors `salt_for` in Rust.
|
|
20
|
+
*
|
|
21
|
+
* The runtime SDK does **not** call this — the generated `client.ts`
|
|
22
|
+
* embeds the precomputed salt. This export exists for the fixture test
|
|
23
|
+
* and for tooling that wants to verify the embed.
|
|
24
|
+
*/
|
|
25
|
+
export function saltFor(experimentId) {
|
|
26
|
+
const ns = TE.encode(BUCKET_NAMESPACE);
|
|
27
|
+
const id = TE.encode(experimentId);
|
|
28
|
+
const buf = new Uint8Array(ns.length + id.length);
|
|
29
|
+
buf.set(ns);
|
|
30
|
+
buf.set(id, ns.length);
|
|
31
|
+
const digest = sha256(buf);
|
|
32
|
+
return bytesToHex(digest.subarray(0, 16));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Compute the bucket (0..9999) for a user under the given salt.
|
|
36
|
+
*
|
|
37
|
+
* @param saltHex 32-character lowercase hex. Embedded in the generated file.
|
|
38
|
+
* @param userId user id string; UTF-8 encoded before hashing.
|
|
39
|
+
*/
|
|
40
|
+
export function bucket(saltHex, userId) {
|
|
41
|
+
const salt = hexToBytes(saltHex);
|
|
42
|
+
if (salt.length !== 16) {
|
|
43
|
+
throw new Error(`expected 16-byte salt, got ${salt.length}`);
|
|
44
|
+
}
|
|
45
|
+
const user = TE.encode(userId);
|
|
46
|
+
const buf = new Uint8Array(salt.length + user.length);
|
|
47
|
+
buf.set(salt);
|
|
48
|
+
buf.set(user, salt.length);
|
|
49
|
+
const digest = sha256(buf);
|
|
50
|
+
const pair = ((digest[0] << 8) | digest[1]) >>> 0;
|
|
51
|
+
return pair % 10_000;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Pick the variant corresponding to a bucket given the experiment's declared
|
|
55
|
+
* variants and weights. Returns `null` if weights don't sum to 100 (which
|
|
56
|
+
* `dif validate` should have caught at build time).
|
|
57
|
+
*/
|
|
58
|
+
export function selectVariant(variants, weights, bucketValue) {
|
|
59
|
+
let cumulative = 0;
|
|
60
|
+
for (const variant of variants) {
|
|
61
|
+
const w = weights[variant] ?? 0;
|
|
62
|
+
cumulative += w * 100;
|
|
63
|
+
if (bucketValue < cumulative)
|
|
64
|
+
return variant;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=bucket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket.js","sourceRoot":"","sources":["../src/bucket.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,0EAA0E;AAC1E,2DAA2D;AAC3D,EAAE;AACF,aAAa;AACb,kFAAkF;AAClF,kDAAkD;AAClD,6CAA6C;AAC7C,EAAE;AACF,mEAAmE;AACnE,0EAA0E;AAE1E,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE7D,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAE5C,+DAA+D;AAC/D,MAAM,EAAE,GAAG,IAAI,WAAW,EAAE,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,YAAoB;IAC1C,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACvC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACZ,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IACvB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,OAAe,EAAE,MAAc;IACpD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,IAAI,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,IAAI,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IACtD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACd,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO,IAAI,GAAG,MAAM,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,QAA2B,EAC3B,OAA+B,EAC/B,WAAmB;IAEnB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChC,UAAU,IAAI,CAAC,GAAG,GAAG,CAAC;QACtB,IAAI,WAAW,GAAG,UAAU;YAAE,OAAO,OAAO,CAAC;IAC/C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AttributeBag, DifConfig, DifInitConfig, Sink } from "./types.js";
|
|
2
|
+
export type { DifInitConfig };
|
|
3
|
+
export interface ResolvedState {
|
|
4
|
+
project: string | null;
|
|
5
|
+
publishableKey: string | null;
|
|
6
|
+
apiUrl: string;
|
|
7
|
+
userId: () => string | null;
|
|
8
|
+
attributes: () => AttributeBag;
|
|
9
|
+
sinks: Sink[];
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function setState(cfg: DifInitConfig | DifConfig): void;
|
|
13
|
+
export declare function getState(): ResolvedState | null;
|
|
14
|
+
/** Test-only. */
|
|
15
|
+
export declare function resetState(): void;
|
|
16
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAE/E,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,YAAY,CAAC;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAMD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI,CAa7D;AAED,wBAAgB,QAAQ,IAAI,aAAa,GAAG,IAAI,CAE/C;AAED,iBAAiB;AACjB,wBAAgB,UAAU,IAAI,IAAI,CAEjC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Module-singleton runtime config for @dif.sh/sdk.
|
|
2
|
+
//
|
|
3
|
+
// `dif.init(cfg)` calls setState(cfg); `dif()` and `dif.track()` read it via
|
|
4
|
+
// getState(). One config per bundle; calling init again overwrites. This is
|
|
5
|
+
// intentional — there is only one "current user" per app instance.
|
|
6
|
+
let state = null;
|
|
7
|
+
const DEFAULT_API_URL = "https://api.dif.sh";
|
|
8
|
+
export function setState(cfg) {
|
|
9
|
+
const merged = cfg;
|
|
10
|
+
const sinkVal = merged.sink;
|
|
11
|
+
const sinks = sinkVal === undefined ? [] : Array.isArray(sinkVal) ? sinkVal : [sinkVal];
|
|
12
|
+
state = {
|
|
13
|
+
project: merged.project ?? null,
|
|
14
|
+
publishableKey: merged.publishableKey ?? null,
|
|
15
|
+
apiUrl: stripTrailing(merged.apiUrl ?? DEFAULT_API_URL),
|
|
16
|
+
userId: merged.userId ?? (() => null),
|
|
17
|
+
attributes: merged.attributes ?? (() => ({})),
|
|
18
|
+
sinks,
|
|
19
|
+
enabled: merged.enabled !== false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function getState() {
|
|
23
|
+
return state;
|
|
24
|
+
}
|
|
25
|
+
/** Test-only. */
|
|
26
|
+
export function resetState() {
|
|
27
|
+
state = null;
|
|
28
|
+
}
|
|
29
|
+
function stripTrailing(url) {
|
|
30
|
+
return url.replace(/\/+$/, "");
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,mEAAmE;AAgBnE,IAAI,KAAK,GAAyB,IAAI,CAAC;AAEvC,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAE7C,MAAM,UAAU,QAAQ,CAAC,GAA8B;IACrD,MAAM,MAAM,GAAG,GAAoB,CAAC;IACpC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC;IAC5B,MAAM,KAAK,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACxF,KAAK,GAAG;QACN,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;QAC/B,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,IAAI;QAC7C,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,IAAI,eAAe,CAAC;QACvD,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;QACrC,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7C,KAAK;QACL,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,KAAK;KAClC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iBAAiB;AACjB,MAAM,UAAU,UAAU;IACxB,KAAK,GAAG,IAAI,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC"}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ExperimentSpec } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Register an experiment spec into the runtime registry. Called by the
|
|
4
|
+
* generated `.dif/generated/client.ts` file; customer code should not call
|
|
5
|
+
* this directly. The name is stable — the codegen emits this exact identifier.
|
|
6
|
+
*/
|
|
7
|
+
export declare function __register(spec: ExperimentSpec): void;
|
|
8
|
+
/** Test-only: clear the registry + state. Not exported from the package index. */
|
|
9
|
+
export declare function __resetRegistry(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Customer API. Looks up the experiment spec, resolves the user's assignment,
|
|
12
|
+
* fires an exposure event, and returns a thunk that yields the matching
|
|
13
|
+
* branch's value.
|
|
14
|
+
*
|
|
15
|
+
* Unknown experiment ids fall back to the first declared branch. `dif build`
|
|
16
|
+
* catches these as orphan refs at compile time; the runtime fallback exists
|
|
17
|
+
* so a missed cleanup doesn't crash production.
|
|
18
|
+
*/
|
|
19
|
+
export declare function difCall<V extends string, R>(id: string, branches: Record<V, () => R>): () => R;
|
|
20
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAQjD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAErD;AAED,kFAAkF;AAClF,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EACzC,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAC3B,MAAM,CAAC,CAKT"}
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Experiment-assignment core. Lifted from the old @dif.sh/sdk index.ts
|
|
2
|
+
// substantively unchanged — the only swap is reading config via getState()
|
|
3
|
+
// instead of a module-local `currentConfig` variable.
|
|
4
|
+
import { bucket, selectVariant } from "./bucket.js";
|
|
5
|
+
import { fireExposure } from "./exposure.js";
|
|
6
|
+
import { getState } from "./config.js";
|
|
7
|
+
/** Experiment specs the generated file has registered. */
|
|
8
|
+
const registry = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Register an experiment spec into the runtime registry. Called by the
|
|
11
|
+
* generated `.dif/generated/client.ts` file; customer code should not call
|
|
12
|
+
* this directly. The name is stable — the codegen emits this exact identifier.
|
|
13
|
+
*/
|
|
14
|
+
export function __register(spec) {
|
|
15
|
+
registry.set(spec.id, spec);
|
|
16
|
+
}
|
|
17
|
+
/** Test-only: clear the registry + state. Not exported from the package index. */
|
|
18
|
+
export function __resetRegistry() {
|
|
19
|
+
registry.clear();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Customer API. Looks up the experiment spec, resolves the user's assignment,
|
|
23
|
+
* fires an exposure event, and returns a thunk that yields the matching
|
|
24
|
+
* branch's value.
|
|
25
|
+
*
|
|
26
|
+
* Unknown experiment ids fall back to the first declared branch. `dif build`
|
|
27
|
+
* catches these as orphan refs at compile time; the runtime fallback exists
|
|
28
|
+
* so a missed cleanup doesn't crash production.
|
|
29
|
+
*/
|
|
30
|
+
export function difCall(id, branches) {
|
|
31
|
+
return () => {
|
|
32
|
+
const variant = resolve(id, branches);
|
|
33
|
+
return branches[variant]();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Pick the variant for one call. The full resolution flow:
|
|
38
|
+
*
|
|
39
|
+
* 1. Unknown id → fall back to the first declared branch.
|
|
40
|
+
* 2. Disabled / no user / config missing → control variant, no exposure.
|
|
41
|
+
* 3. Audience predicate misses → control variant, no exposure.
|
|
42
|
+
* 4. Otherwise: bucket, pick by cumulative weight, fire one exposure event.
|
|
43
|
+
*/
|
|
44
|
+
function resolve(id, branches) {
|
|
45
|
+
const spec = registry.get(id);
|
|
46
|
+
const variantKeys = Object.keys(branches);
|
|
47
|
+
if (variantKeys.length === 0) {
|
|
48
|
+
throw new Error(`dif("${id}"): branches map is empty`);
|
|
49
|
+
}
|
|
50
|
+
if (!spec) {
|
|
51
|
+
return variantKeys[0];
|
|
52
|
+
}
|
|
53
|
+
const state = getState();
|
|
54
|
+
if (!state || state.enabled === false) {
|
|
55
|
+
return spec.variants[0];
|
|
56
|
+
}
|
|
57
|
+
const userId = state.userId();
|
|
58
|
+
if (userId === null) {
|
|
59
|
+
return spec.variants[0];
|
|
60
|
+
}
|
|
61
|
+
const attrs = state.attributes();
|
|
62
|
+
if (!spec.audience(attrs)) {
|
|
63
|
+
return spec.variants[0];
|
|
64
|
+
}
|
|
65
|
+
const b = bucket(spec.salt, userId);
|
|
66
|
+
const picked = selectVariant(spec.variants, spec.weights, b) ?? spec.variants[0];
|
|
67
|
+
fireExposure(spec, picked, userId, b, state.sinks);
|
|
68
|
+
return picked;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,2EAA2E;AAC3E,sDAAsD;AAGtD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,0DAA0D;AAC1D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;AAEnD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAoB;IAC7C,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,eAAe;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CACrB,EAAU,EACV,QAA4B;IAE5B,OAAO,GAAG,EAAE;QACV,MAAM,OAAO,GAAG,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QACtC,OAAO,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;IAC7B,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,OAAO,CAAsB,EAAU,EAAE,QAA4B;IAC5E,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAQ,CAAC;IACjD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,WAAW,CAAC,CAAC,CAAM,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAM,CAAC;IAC/B,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;IAC9B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAM,CAAC;IAC/B,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC;IACjC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAM,CAAC;IAC/B,CAAC;IAED,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACjF,YAAY,CAAC,IAAI,EAAE,MAAO,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO,MAAW,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ExperimentSpec, Sink } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fire one exposure event across every configured sink. Idempotent per
|
|
4
|
+
* (experiment, user) for the lifetime of the module.
|
|
5
|
+
*/
|
|
6
|
+
export declare function fireExposure(spec: ExperimentSpec, variant: string, userId: string, bucket: number, sinks: Sink[]): void;
|
|
7
|
+
//# sourceMappingURL=exposure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exposure.d.ts","sourceRoot":"","sources":["../src/exposure.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAiB,IAAI,EAAE,MAAM,YAAY,CAAC;AAOtE;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,IAAI,EAAE,GACZ,IAAI,CAwBN"}
|
package/dist/exposure.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Exposure event firing — render-time, deduped per session per (experiment, user).
|
|
2
|
+
const SOURCE = "@dif.sh/sdk@0.2.0";
|
|
3
|
+
/** Per-session dedupe set. Cleared on page nav (the module is per-page). */
|
|
4
|
+
const fired = new Set();
|
|
5
|
+
/**
|
|
6
|
+
* Fire one exposure event across every configured sink. Idempotent per
|
|
7
|
+
* (experiment, user) for the lifetime of the module.
|
|
8
|
+
*/
|
|
9
|
+
export function fireExposure(spec, variant, userId, bucket, sinks) {
|
|
10
|
+
const dedupeKey = `${spec.id}::${userId}`;
|
|
11
|
+
if (fired.has(dedupeKey))
|
|
12
|
+
return;
|
|
13
|
+
fired.add(dedupeKey);
|
|
14
|
+
const event = {
|
|
15
|
+
event: "dif.exposure",
|
|
16
|
+
experiment: spec.id,
|
|
17
|
+
variant,
|
|
18
|
+
user_id: userId,
|
|
19
|
+
surface: spec.surface,
|
|
20
|
+
bucket,
|
|
21
|
+
fired_at: Date.now(),
|
|
22
|
+
source: SOURCE,
|
|
23
|
+
};
|
|
24
|
+
for (const sink of sinks) {
|
|
25
|
+
try {
|
|
26
|
+
sink.emit(event);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Sinks must never throw. If one does, swallow — bad analytics must
|
|
30
|
+
// not crash a render.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=exposure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exposure.js","sourceRoot":"","sources":["../src/exposure.ts"],"names":[],"mappings":"AAAA,mFAAmF;AAInF,MAAM,MAAM,GAAG,mBAAmB,CAAC;AAEnC,4EAA4E;AAC5E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;AAEhC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAoB,EACpB,OAAe,EACf,MAAc,EACd,MAAc,EACd,KAAa;IAEb,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IAC1C,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO;IACjC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAErB,MAAM,KAAK,GAAkB;QAC3B,KAAK,EAAE,cAAc;QACrB,UAAU,EAAE,IAAI,CAAC,EAAE;QACnB,OAAO;QACP,OAAO,EAAE,MAAM;QACf,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM;QACN,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE;QACpB,MAAM,EAAE,MAAM;KACf,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;YACpE,sBAAsB;QACxB,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { __register } from "./core.js";
|
|
2
|
+
import { type DifInitConfig } from "./config.js";
|
|
3
|
+
import type { DifConfig, TrackProps } from "./types.js";
|
|
4
|
+
/** Public type of the `dif` export. Function call signature + method bag. */
|
|
5
|
+
export interface DifFn {
|
|
6
|
+
<V extends string, R>(id: string, branches: Record<V, () => R>): () => R;
|
|
7
|
+
/** Initialize the SDK. Call once at app boot. */
|
|
8
|
+
init(config: DifInitConfig): void;
|
|
9
|
+
/** Fire a metric event. */
|
|
10
|
+
track(metric: string, opts?: TrackProps): void;
|
|
11
|
+
/**
|
|
12
|
+
* Legacy alias for {@link DifFn.init}. Accepts the older config shape.
|
|
13
|
+
* @deprecated Use `dif.init(...)`.
|
|
14
|
+
*/
|
|
15
|
+
configure(config: DifConfig | DifInitConfig): void;
|
|
16
|
+
}
|
|
17
|
+
declare function configure(config: DifConfig | DifInitConfig): void;
|
|
18
|
+
export declare const dif: DifFn;
|
|
19
|
+
export { configure };
|
|
20
|
+
export { __register };
|
|
21
|
+
export declare function __reset(): void;
|
|
22
|
+
export type { AttrValue, AttributeBag, AudienceFn, DifConfig, DifInitConfig, ExperimentSpec, ExposureEvent, MetricEvent, Sink, TrackProps, UserIdFn, } from "./types.js";
|
|
23
|
+
export { webhookSink } from "./sinks/webhook.js";
|
|
24
|
+
export { segmentSink } from "./sinks/segment.js";
|
|
25
|
+
export { amplitudeSink } from "./sinks/amplitude.js";
|
|
26
|
+
export { mixpanelSink } from "./sinks/mixpanel.js";
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAW,UAAU,EAAmB,MAAM,WAAW,CAAC;AACjE,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExD,6EAA6E;AAC7E,MAAM,WAAW,KAAK;IACpB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IACzE,iDAAiD;IACjD,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAClC,2BAA2B;IAC3B,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/C;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,IAAI,CAAC;CACpD;AAMD,iBAAS,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,IAAI,CAE1D;AAED,eAAO,MAAM,GAAG,EAAE,KAIhB,CAAC;AAIH,OAAO,EAAE,SAAS,EAAE,CAAC;AAGrB,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,wBAAgB,OAAO,IAAI,IAAI,CAG9B;AAGD,YAAY,EACV,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,EACX,IAAI,EACJ,UAAU,EACV,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// @dif.sh/sdk — browser entrypoint.
|
|
2
|
+
//
|
|
3
|
+
// `dif` is a callable object: it works as a function for experiment assignment
|
|
4
|
+
// AND carries methods for init / track. This lets the cloud-page snippets
|
|
5
|
+
// (`dif.init`, `dif.track`) coexist with existing call sites (`dif("id", branches)`).
|
|
6
|
+
import { difCall, __register, __resetRegistry } from "./core.js";
|
|
7
|
+
import { setState, resetState } from "./config.js";
|
|
8
|
+
import { track } from "./track.js";
|
|
9
|
+
function init(config) {
|
|
10
|
+
setState(config);
|
|
11
|
+
}
|
|
12
|
+
function configure(config) {
|
|
13
|
+
setState(config);
|
|
14
|
+
}
|
|
15
|
+
export const dif = Object.assign(difCall, {
|
|
16
|
+
init,
|
|
17
|
+
track,
|
|
18
|
+
configure,
|
|
19
|
+
});
|
|
20
|
+
// Legacy named export so customer code that imported `configure` directly
|
|
21
|
+
// still compiles after the rename. Same body as `dif.configure`.
|
|
22
|
+
export { configure };
|
|
23
|
+
// Internal hooks (used by the generated client.ts + tests).
|
|
24
|
+
export { __register };
|
|
25
|
+
export function __reset() {
|
|
26
|
+
__resetRegistry();
|
|
27
|
+
resetState();
|
|
28
|
+
}
|
|
29
|
+
export { webhookSink } from "./sinks/webhook.js";
|
|
30
|
+
export { segmentSink } from "./sinks/segment.js";
|
|
31
|
+
export { amplitudeSink } from "./sinks/amplitude.js";
|
|
32
|
+
export { mixpanelSink } from "./sinks/mixpanel.js";
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,EAAE;AACF,+EAA+E;AAC/E,0EAA0E;AAC1E,sFAAsF;AAEtF,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAsB,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAiBnC,SAAS,IAAI,CAAC,MAAqB;IACjC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACnB,CAAC;AAED,SAAS,SAAS,CAAC,MAAiC;IAClD,QAAQ,CAAC,MAAM,CAAC,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAU,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE;IAC/C,IAAI;IACJ,KAAK;IACL,SAAS;CACV,CAAC,CAAC;AAEH,0EAA0E;AAC1E,iEAAiE;AACjE,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,4DAA4D;AAC5D,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,MAAM,UAAU,OAAO;IACrB,eAAe,EAAE,CAAC;IAClB,UAAU,EAAE,CAAC;AACf,CAAC;AAgBD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface DifServerConfig {
|
|
2
|
+
/** Secret bearer token: dif_<env>_<prefix>_<secret>. */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Optional project slug stamp; not used by the cloud but useful in logs. */
|
|
5
|
+
project?: string;
|
|
6
|
+
/** Cloud base URL. Defaults to https://api.dif.sh. */
|
|
7
|
+
apiUrl?: string;
|
|
8
|
+
/** Overrides the SDK's "source" stamp on emitted events. */
|
|
9
|
+
source?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface TrackInput {
|
|
12
|
+
metric: string;
|
|
13
|
+
userId: string;
|
|
14
|
+
value?: number;
|
|
15
|
+
currency?: string;
|
|
16
|
+
unit?: string;
|
|
17
|
+
firedAt?: number;
|
|
18
|
+
idempotencyKey?: string;
|
|
19
|
+
props?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export declare class DifServer {
|
|
22
|
+
private readonly apiKey;
|
|
23
|
+
private readonly apiUrl;
|
|
24
|
+
private readonly source;
|
|
25
|
+
constructor(cfg: DifServerConfig);
|
|
26
|
+
track(input: TrackInput): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,GAAG,EAAE,eAAe;IAO1B,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CA+B9C"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Server entrypoint — `@dif.sh/sdk/server`.
|
|
2
|
+
//
|
|
3
|
+
// import { DifServer } from "@dif.sh/sdk/server";
|
|
4
|
+
// const dif = new DifServer({ apiKey: process.env.DIF_KEY });
|
|
5
|
+
// await dif.track({ metric: "completed_checkout", userId: user.id });
|
|
6
|
+
//
|
|
7
|
+
// Fire-and-forget HTTP POST to /v1/track. No batching, no retries in v0 — one
|
|
8
|
+
// call per event. The bearer is a secret token (dif_<env>_…), never a
|
|
9
|
+
// publishable key.
|
|
10
|
+
const DEFAULT_API_URL = "https://api.dif.sh";
|
|
11
|
+
const DEFAULT_SOURCE = "@dif.sh/sdk@0.2.0";
|
|
12
|
+
export class DifServer {
|
|
13
|
+
apiKey;
|
|
14
|
+
apiUrl;
|
|
15
|
+
source;
|
|
16
|
+
constructor(cfg) {
|
|
17
|
+
if (!cfg.apiKey)
|
|
18
|
+
throw new Error("DifServer: apiKey is required");
|
|
19
|
+
this.apiKey = cfg.apiKey;
|
|
20
|
+
this.apiUrl = (cfg.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
21
|
+
this.source = cfg.source ?? DEFAULT_SOURCE;
|
|
22
|
+
}
|
|
23
|
+
async track(input) {
|
|
24
|
+
const url = `${this.apiUrl}/v1/track`;
|
|
25
|
+
const payload = {
|
|
26
|
+
metric: input.metric,
|
|
27
|
+
user_id: input.userId,
|
|
28
|
+
value: input.value,
|
|
29
|
+
currency: input.currency,
|
|
30
|
+
unit: input.unit,
|
|
31
|
+
fired_at: input.firedAt ?? Date.now(),
|
|
32
|
+
idempotency_key: input.idempotencyKey,
|
|
33
|
+
source: this.source,
|
|
34
|
+
props: input.props,
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(url, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: {
|
|
40
|
+
"content-type": "application/json",
|
|
41
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify(payload),
|
|
44
|
+
});
|
|
45
|
+
if (!res.ok && typeof console !== "undefined") {
|
|
46
|
+
console.warn(`[dif] track ${input.metric} → ${res.status}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (typeof console !== "undefined") {
|
|
51
|
+
console.warn(`[dif] track ${input.metric} failed`, err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,EAAE;AACF,oDAAoD;AACpD,gEAAgE;AAChE,wEAAwE;AACxE,EAAE;AACF,8EAA8E;AAC9E,sEAAsE;AACtE,mBAAmB;AAEnB,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAC7C,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAwB3C,MAAM,OAAO,SAAS;IACH,MAAM,CAAS;IACf,MAAM,CAAS;IACf,MAAM,CAAS;IAEhC,YAAY,GAAoB;QAC9B,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAClE,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,cAAc,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAiB;QAC3B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,WAAW,CAAC;QACtC,MAAM,OAAO,GAAG;YACd,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO,EAAE,KAAK,CAAC,MAAM;YACrB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,QAAQ,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE;YACrC,eAAe,EAAE,KAAK,CAAC,cAAc;YACrC,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC;QACF,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;iBACvC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;aAC9B,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE,CAAC;gBAC9C,OAAO,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE,CAAC;gBACnC,OAAO,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,MAAM,SAAS,EAAE,GAAG,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
package/dist/sha256.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hash `data` and return the 32-byte digest. Pure, sync, no dependencies.
|
|
3
|
+
*/
|
|
4
|
+
export declare function sha256(data: Uint8Array): Uint8Array;
|
|
5
|
+
/** Hex-encode a byte array. Lowercase, no separators. */
|
|
6
|
+
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
7
|
+
/** Parse a lowercase-hex string into a byte array. Throws on bad input. */
|
|
8
|
+
export declare function hexToBytes(hex: string): Uint8Array;
|
|
9
|
+
//# sourceMappingURL=sha256.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sha256.d.ts","sourceRoot":"","sources":["../src/sha256.ts"],"names":[],"mappings":"AAoCA;;GAEG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CA8EnD;AAED,yDAAyD;AACzD,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAMpD;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAalD"}
|
package/dist/sha256.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Minimal sync SHA-256.
|
|
2
|
+
//
|
|
3
|
+
// Why inline this instead of Web Crypto or Node's `crypto`?
|
|
4
|
+
// - `crypto.subtle.digest` is async, which would force `dif()` to be async
|
|
5
|
+
// too. `dif()` is called at render time; making it async would mean every
|
|
6
|
+
// call site is async-aware. Non-starter.
|
|
7
|
+
// - Node's `crypto` works in Node only. The SDK runs in browsers as well.
|
|
8
|
+
//
|
|
9
|
+
// So we ship our own. The algorithm is RFC 6234 / FIPS 180-4, ~120 lines,
|
|
10
|
+
// passes the standard test vectors. The TS port matches `sha2::Sha256` in
|
|
11
|
+
// the Rust crate byte-for-byte; the bucketing fixture catches any drift.
|
|
12
|
+
const K = new Uint32Array([
|
|
13
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
|
14
|
+
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
|
15
|
+
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
|
16
|
+
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
17
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
|
18
|
+
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
19
|
+
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
|
20
|
+
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
21
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
|
22
|
+
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
|
23
|
+
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
24
|
+
]);
|
|
25
|
+
const INITIAL_H = new Uint32Array([
|
|
26
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c,
|
|
27
|
+
0x1f83d9ab, 0x5be0cd19,
|
|
28
|
+
]);
|
|
29
|
+
/** Right-rotate a 32-bit unsigned integer. */
|
|
30
|
+
function rotr(x, n) {
|
|
31
|
+
return ((x >>> n) | (x << (32 - n))) >>> 0;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Hash `data` and return the 32-byte digest. Pure, sync, no dependencies.
|
|
35
|
+
*/
|
|
36
|
+
export function sha256(data) {
|
|
37
|
+
const len = data.length;
|
|
38
|
+
const bitLen = BigInt(len) * 8n;
|
|
39
|
+
// Padding: append 0x80, then zeros, then 8-byte big-endian length so the
|
|
40
|
+
// total is a multiple of 64 bytes.
|
|
41
|
+
const padLen = -(len + 9) & 63;
|
|
42
|
+
const totalLen = len + 1 + padLen + 8;
|
|
43
|
+
const padded = new Uint8Array(totalLen);
|
|
44
|
+
padded.set(data);
|
|
45
|
+
padded[len] = 0x80;
|
|
46
|
+
const padView = new DataView(padded.buffer);
|
|
47
|
+
padView.setBigUint64(totalLen - 8, bitLen, /* littleEndian */ false);
|
|
48
|
+
const h = new Uint32Array(INITIAL_H);
|
|
49
|
+
const w = new Uint32Array(64);
|
|
50
|
+
for (let blockStart = 0; blockStart < totalLen; blockStart += 64) {
|
|
51
|
+
// Message schedule.
|
|
52
|
+
for (let t = 0; t < 16; t++) {
|
|
53
|
+
const off = blockStart + t * 4;
|
|
54
|
+
w[t] =
|
|
55
|
+
((padded[off] << 24) |
|
|
56
|
+
(padded[off + 1] << 16) |
|
|
57
|
+
(padded[off + 2] << 8) |
|
|
58
|
+
padded[off + 3]) >>>
|
|
59
|
+
0;
|
|
60
|
+
}
|
|
61
|
+
for (let t = 16; t < 64; t++) {
|
|
62
|
+
const wm15 = w[t - 15];
|
|
63
|
+
const wm2 = w[t - 2];
|
|
64
|
+
const s0 = rotr(wm15, 7) ^ rotr(wm15, 18) ^ (wm15 >>> 3);
|
|
65
|
+
const s1 = rotr(wm2, 17) ^ rotr(wm2, 19) ^ (wm2 >>> 10);
|
|
66
|
+
w[t] = (w[t - 16] + s0 + w[t - 7] + s1) >>> 0;
|
|
67
|
+
}
|
|
68
|
+
let a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
|
|
69
|
+
for (let t = 0; t < 64; t++) {
|
|
70
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
71
|
+
const ch = (e & f) ^ (~e & g);
|
|
72
|
+
const t1 = (hh + S1 + ch + K[t] + w[t]) >>> 0;
|
|
73
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
74
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
75
|
+
const t2 = (S0 + maj) >>> 0;
|
|
76
|
+
hh = g;
|
|
77
|
+
g = f;
|
|
78
|
+
f = e;
|
|
79
|
+
e = (d + t1) >>> 0;
|
|
80
|
+
d = c;
|
|
81
|
+
c = b;
|
|
82
|
+
b = a;
|
|
83
|
+
a = (t1 + t2) >>> 0;
|
|
84
|
+
}
|
|
85
|
+
h[0] = (h[0] + a) >>> 0;
|
|
86
|
+
h[1] = (h[1] + b) >>> 0;
|
|
87
|
+
h[2] = (h[2] + c) >>> 0;
|
|
88
|
+
h[3] = (h[3] + d) >>> 0;
|
|
89
|
+
h[4] = (h[4] + e) >>> 0;
|
|
90
|
+
h[5] = (h[5] + f) >>> 0;
|
|
91
|
+
h[6] = (h[6] + g) >>> 0;
|
|
92
|
+
h[7] = (h[7] + hh) >>> 0;
|
|
93
|
+
}
|
|
94
|
+
const out = new Uint8Array(32);
|
|
95
|
+
const outView = new DataView(out.buffer);
|
|
96
|
+
for (let i = 0; i < 8; i++) {
|
|
97
|
+
outView.setUint32(i * 4, h[i], /* littleEndian */ false);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/** Hex-encode a byte array. Lowercase, no separators. */
|
|
102
|
+
export function bytesToHex(bytes) {
|
|
103
|
+
let out = "";
|
|
104
|
+
for (const b of bytes) {
|
|
105
|
+
out += b.toString(16).padStart(2, "0");
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
/** Parse a lowercase-hex string into a byte array. Throws on bad input. */
|
|
110
|
+
export function hexToBytes(hex) {
|
|
111
|
+
if (hex.length % 2 !== 0) {
|
|
112
|
+
throw new Error(`hex string has odd length: ${hex.length}`);
|
|
113
|
+
}
|
|
114
|
+
const out = new Uint8Array(hex.length / 2);
|
|
115
|
+
for (let i = 0; i < out.length; i++) {
|
|
116
|
+
const byte = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
117
|
+
if (Number.isNaN(byte)) {
|
|
118
|
+
throw new Error(`invalid hex byte at offset ${i * 2}`);
|
|
119
|
+
}
|
|
120
|
+
out[i] = byte;
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=sha256.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sha256.js","sourceRoot":"","sources":["../src/sha256.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Sink } from "../types.js";
|
|
2
|
+
/** Minimal Amplitude surface. */
|
|
3
|
+
interface AmplitudeLike {
|
|
4
|
+
track(event: string, properties?: Record<string, unknown>): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Forward events to an Amplitude-shaped client.
|
|
8
|
+
*/
|
|
9
|
+
export declare function amplitudeSink(amplitude: AmplitudeLike): Sink;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=amplitude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amplitude.d.ts","sourceRoot":"","sources":["../../src/sinks/amplitude.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,IAAI,EAAE,MAAM,aAAa,CAAC;AAEvD,iCAAiC;AACjC,UAAU,aAAa;IACrB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAClE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAO5D"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Amplitude sink.
|
|
2
|
+
/**
|
|
3
|
+
* Forward events to an Amplitude-shaped client.
|
|
4
|
+
*/
|
|
5
|
+
export function amplitudeSink(amplitude) {
|
|
6
|
+
return {
|
|
7
|
+
kind: "amplitude",
|
|
8
|
+
emit(event) {
|
|
9
|
+
amplitude.track(event.event, { ...event });
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=amplitude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amplitude.js","sourceRoot":"","sources":["../../src/sinks/amplitude.ts"],"names":[],"mappings":"AAAA,kBAAkB;AASlB;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,SAAwB;IACpD,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,IAAI,CAAC,KAAoB;YACvB,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAC7C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sinks/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Public re-exports of bundled sinks. Customers can also implement their own
|
|
2
|
+
// — anything matching the `Sink` interface.
|
|
3
|
+
export { webhookSink } from "./webhook.js";
|
|
4
|
+
export { segmentSink } from "./segment.js";
|
|
5
|
+
export { amplitudeSink } from "./amplitude.js";
|
|
6
|
+
export { mixpanelSink } from "./mixpanel.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sinks/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,4CAA4C;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Sink } from "../types.js";
|
|
2
|
+
/** Minimal Mixpanel surface. */
|
|
3
|
+
interface MixpanelLike {
|
|
4
|
+
track(event: string, properties?: Record<string, unknown>): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Forward events to a Mixpanel-shaped client.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mixpanelSink(mixpanel: MixpanelLike): Sink;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=mixpanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixpanel.d.ts","sourceRoot":"","sources":["../../src/sinks/mixpanel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,IAAI,EAAE,MAAM,aAAa,CAAC;AAEvD,gCAAgC;AAChC,UAAU,YAAY;IACpB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAClE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAOzD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Mixpanel sink.
|
|
2
|
+
/**
|
|
3
|
+
* Forward events to a Mixpanel-shaped client.
|
|
4
|
+
*/
|
|
5
|
+
export function mixpanelSink(mixpanel) {
|
|
6
|
+
return {
|
|
7
|
+
kind: "mixpanel",
|
|
8
|
+
emit(event) {
|
|
9
|
+
mixpanel.track(event.event, { ...event });
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=mixpanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixpanel.js","sourceRoot":"","sources":["../../src/sinks/mixpanel.ts"],"names":[],"mappings":"AAAA,iBAAiB;AASjB;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAAsB;IACjD,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,IAAI,CAAC,KAAoB;YACvB,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAC5C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Sink } from "../types.js";
|
|
2
|
+
/** Minimal Segment surface we depend on. */
|
|
3
|
+
interface SegmentLike {
|
|
4
|
+
track(event: string, properties: Record<string, unknown>): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Forward events to a Segment-shaped client (e.g. `window.analytics`).
|
|
8
|
+
*/
|
|
9
|
+
export declare function segmentSink(analytics: SegmentLike): Sink;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=segment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../../src/sinks/segment.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,IAAI,EAAE,MAAM,aAAa,CAAC;AAEvD,4CAA4C;AAC5C,UAAU,WAAW;IACnB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACjE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAOxD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Segment sink — `analytics.track("dif.exposure", { ... })`.
|
|
2
|
+
/**
|
|
3
|
+
* Forward events to a Segment-shaped client (e.g. `window.analytics`).
|
|
4
|
+
*/
|
|
5
|
+
export function segmentSink(analytics) {
|
|
6
|
+
return {
|
|
7
|
+
kind: "segment",
|
|
8
|
+
emit(event) {
|
|
9
|
+
analytics.track(event.event, { ...event });
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=segment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment.js","sourceRoot":"","sources":["../../src/sinks/segment.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAS7D;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,SAAsB;IAChD,OAAO;QACL,IAAI,EAAE,SAAS;QACf,IAAI,CAAC,KAAoB;YACvB,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAC7C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Sink } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* POST every event as JSON to `url`. Fire-and-forget; uses `navigator.sendBeacon`
|
|
4
|
+
* when available so the request survives page nav.
|
|
5
|
+
*/
|
|
6
|
+
export declare function webhookSink(url: string): Sink;
|
|
7
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/sinks/webhook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,IAAI,EAAE,MAAM,aAAa,CAAC;AAEvD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAiB7C"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// POSTs the raw event JSON to a URL. The lowest-common-denominator sink.
|
|
2
|
+
/**
|
|
3
|
+
* POST every event as JSON to `url`. Fire-and-forget; uses `navigator.sendBeacon`
|
|
4
|
+
* when available so the request survives page nav.
|
|
5
|
+
*/
|
|
6
|
+
export function webhookSink(url) {
|
|
7
|
+
return {
|
|
8
|
+
kind: "webhook",
|
|
9
|
+
emit(event) {
|
|
10
|
+
const body = JSON.stringify(event);
|
|
11
|
+
if (typeof navigator !== "undefined" && "sendBeacon" in navigator) {
|
|
12
|
+
navigator.sendBeacon(url, body);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
void fetch(url, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "content-type": "application/json" },
|
|
18
|
+
body,
|
|
19
|
+
keepalive: true,
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/sinks/webhook.ts"],"names":[],"mappings":"AAAA,yEAAyE;AAIzE;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO;QACL,IAAI,EAAE,SAAS;QACf,IAAI,CAAC,KAAoB;YACvB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACnC,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,YAAY,IAAI,SAAS,EAAE,CAAC;gBAClE,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAChC,OAAO;YACT,CAAC;YACD,KAAK,KAAK,CAAC,GAAG,EAAE;gBACd,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI;gBACJ,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/track.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../src/track.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,UAAe,GAAG,IAAI,CA6CjE"}
|
package/dist/track.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Browser metric tracking. Mirrors the cloud-page snippet:
|
|
2
|
+
//
|
|
3
|
+
// dif.track("completed_checkout");
|
|
4
|
+
// dif.track("revenue", { value: 49, currency: "USD" });
|
|
5
|
+
//
|
|
6
|
+
// Implementation notes:
|
|
7
|
+
// - `navigator.sendBeacon` cannot set custom headers, and we need to send
|
|
8
|
+
// `Authorization: Bearer <publishableKey>`. So we always use
|
|
9
|
+
// `fetch` with `keepalive: true`. First call per page may trigger a CORS
|
|
10
|
+
// preflight; subsequent calls reuse the cached preflight up to 24h.
|
|
11
|
+
// - The call is fire-and-forget. We never throw at the customer's call site —
|
|
12
|
+
// analytics must not crash a render.
|
|
13
|
+
// - Without a configured publishableKey, we log to console.debug and drop.
|
|
14
|
+
// This matches the sample app's pre-SDK behavior and keeps dev ergonomic.
|
|
15
|
+
import { getState } from "./config.js";
|
|
16
|
+
const SOURCE = "@dif.sh/sdk@0.2.0";
|
|
17
|
+
export function track(metric, opts = {}) {
|
|
18
|
+
const state = getState();
|
|
19
|
+
if (!state || state.enabled === false)
|
|
20
|
+
return;
|
|
21
|
+
const userId = opts.userId ?? state.userId();
|
|
22
|
+
if (!userId) {
|
|
23
|
+
// Can't attribute; drop. The cloud expects a user_id on every event.
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!state.publishableKey) {
|
|
27
|
+
if (typeof console !== "undefined") {
|
|
28
|
+
console.debug("[dif] track (no publishableKey, dropped)", metric, opts);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const url = `${state.apiUrl}/v1/track`;
|
|
33
|
+
const body = JSON.stringify({
|
|
34
|
+
metric,
|
|
35
|
+
user_id: userId,
|
|
36
|
+
value: opts.value,
|
|
37
|
+
currency: opts.currency,
|
|
38
|
+
unit: opts.unit,
|
|
39
|
+
fired_at: opts.firedAt ?? Date.now(),
|
|
40
|
+
idempotency_key: opts.idempotencyKey,
|
|
41
|
+
source: opts.source ?? SOURCE,
|
|
42
|
+
props: opts.props,
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
void fetch(url, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"content-type": "application/json",
|
|
49
|
+
authorization: `Bearer ${state.publishableKey}`,
|
|
50
|
+
},
|
|
51
|
+
body,
|
|
52
|
+
keepalive: true,
|
|
53
|
+
}).catch(() => {
|
|
54
|
+
// Swallow — analytics must never throw at the call site.
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Synchronous throws (e.g. fetch undefined in unusual envs) are also swallowed.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../src/track.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,qCAAqC;AACrC,0DAA0D;AAC1D,EAAE;AACF,wBAAwB;AACxB,2EAA2E;AAC3E,gEAAgE;AAChE,4EAA4E;AAC5E,uEAAuE;AACvE,+EAA+E;AAC/E,wCAAwC;AACxC,4EAA4E;AAC5E,6EAA6E;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,MAAM,MAAM,GAAG,mBAAmB,CAAC;AAEnC,MAAM,UAAU,KAAK,CAAC,MAAc,EAAE,OAAmB,EAAE;IACzD,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO;IAE9C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,qEAAqE;QACrE,OAAO;IACT,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAC1B,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE,CAAC;YACnC,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,MAAM,WAAW,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,MAAM;QACN,OAAO,EAAE,MAAM;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE;QACpC,eAAe,EAAE,IAAI,CAAC,cAAc;QACpC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,MAAM;QAC7B,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,KAAK,KAAK,CAAC,GAAG,EAAE;YACd,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,KAAK,CAAC,cAAc,EAAE;aAChD;YACD,IAAI;YACJ,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACZ,yDAAyD;QAC3D,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,gFAAgF;IAClF,CAAC;AACH,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** A user-id resolver. Returns null when no user is identified. */
|
|
2
|
+
export type UserIdFn = () => string | null;
|
|
3
|
+
/** One audience attribute, scalar. */
|
|
4
|
+
export type AttrValue = string | number | boolean | null;
|
|
5
|
+
/** The bag of attributes the SDK consults when evaluating audience predicates. */
|
|
6
|
+
export type AttributeBag = Record<string, AttrValue>;
|
|
7
|
+
/** Audience predicate function — generated by `dif build`. */
|
|
8
|
+
export type AudienceFn = (attrs: Record<string, unknown>) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Legacy configuration shape accepted by `dif.configure(...)` (the alias kept
|
|
11
|
+
* for backwards compatibility). New code should pass {@link DifInitConfig} to
|
|
12
|
+
* `dif.init(...)` instead.
|
|
13
|
+
*
|
|
14
|
+
* @deprecated Use {@link DifInitConfig} via `dif.init()`.
|
|
15
|
+
*/
|
|
16
|
+
export interface DifConfig {
|
|
17
|
+
/** How to look up the current user id at call time. */
|
|
18
|
+
userId: UserIdFn;
|
|
19
|
+
/** How to read audience attributes. Defaults to an empty bag. */
|
|
20
|
+
attributes?: () => AttributeBag;
|
|
21
|
+
/** One or more exposure sinks. */
|
|
22
|
+
sink: Sink | Sink[];
|
|
23
|
+
/** Global kill switch. Defaults to true. */
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Configuration passed to `dif.init(...)`. Superset of {@link DifConfig}. */
|
|
27
|
+
export interface DifInitConfig {
|
|
28
|
+
/** Cloud project slug. Required when posting events to dif.sh Cloud. */
|
|
29
|
+
project?: string;
|
|
30
|
+
/** Browser-side write key (dif_pk_…). Safe to embed in bundles. */
|
|
31
|
+
publishableKey?: string;
|
|
32
|
+
/** Cloud base URL. Defaults to https://api.dif.sh. */
|
|
33
|
+
apiUrl?: string;
|
|
34
|
+
/** How to look up the current user id at call time. */
|
|
35
|
+
userId?: UserIdFn;
|
|
36
|
+
/** How to read audience attributes. Defaults to an empty bag. */
|
|
37
|
+
attributes?: () => AttributeBag;
|
|
38
|
+
/** One or more exposure sinks. */
|
|
39
|
+
sink?: Sink | Sink[];
|
|
40
|
+
/** Global kill switch. Defaults to true. */
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Anything that can receive an exposure event. */
|
|
44
|
+
export interface Sink {
|
|
45
|
+
/** Sink identifier — for logs / dedupe. */
|
|
46
|
+
readonly kind: string;
|
|
47
|
+
/** Send one event. Must not throw — it should swallow + log internally. */
|
|
48
|
+
emit(event: ExposureEvent): void;
|
|
49
|
+
}
|
|
50
|
+
/** Exposure event shape emitted to the configured sinks. */
|
|
51
|
+
export interface ExposureEvent {
|
|
52
|
+
/** Always `"dif.exposure"`. */
|
|
53
|
+
event: "dif.exposure";
|
|
54
|
+
/** Experiment id. */
|
|
55
|
+
experiment: string;
|
|
56
|
+
/** Variant id chosen for this user. */
|
|
57
|
+
variant: string;
|
|
58
|
+
/** User id at the time of firing. */
|
|
59
|
+
user_id: string;
|
|
60
|
+
/** Surface the experiment runs on. */
|
|
61
|
+
surface: string;
|
|
62
|
+
/** Resolved bucket 0..9999. Surfaced for debugging. */
|
|
63
|
+
bucket: number;
|
|
64
|
+
/** Unix milliseconds. */
|
|
65
|
+
fired_at: number;
|
|
66
|
+
/** SDK version, e.g. `"@dif.sh/sdk@0.2.0"`. */
|
|
67
|
+
source: string;
|
|
68
|
+
}
|
|
69
|
+
/** Optional properties on a `dif.track(metric, props)` call. */
|
|
70
|
+
export interface TrackProps {
|
|
71
|
+
/** Numeric value carried by the metric (revenue cents, duration ms, etc.). */
|
|
72
|
+
value?: number;
|
|
73
|
+
/** ISO currency code (`USD`, `EUR`, …) — pairs with `value` for revenue. */
|
|
74
|
+
currency?: string;
|
|
75
|
+
/** Unit hint for non-currency values (e.g. `ms`, `count`). */
|
|
76
|
+
unit?: string;
|
|
77
|
+
/** Override the configured userId resolver for this call. */
|
|
78
|
+
userId?: string;
|
|
79
|
+
/** Override the event timestamp (defaults to now). */
|
|
80
|
+
firedAt?: number;
|
|
81
|
+
/** Caller-supplied idempotency key for safe retries. */
|
|
82
|
+
idempotencyKey?: string;
|
|
83
|
+
/** Override the SDK's "source" stamp. */
|
|
84
|
+
source?: string;
|
|
85
|
+
/** Arbitrary additional properties forwarded to the cloud as `props`. */
|
|
86
|
+
props?: Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
/** Metric event shape sent to the cloud's `/v1/track` endpoint. */
|
|
89
|
+
export interface MetricEvent {
|
|
90
|
+
metric: string;
|
|
91
|
+
user_id: string;
|
|
92
|
+
value?: number;
|
|
93
|
+
currency?: string;
|
|
94
|
+
unit?: string;
|
|
95
|
+
fired_at: number;
|
|
96
|
+
source: string;
|
|
97
|
+
idempotency_key?: string;
|
|
98
|
+
props?: Record<string, unknown>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The shape `dif build` emits into `.dif/generated/client.ts`. The generated
|
|
102
|
+
* file calls {@link __register} once per active experiment with one of these.
|
|
103
|
+
* Customers should never construct these by hand.
|
|
104
|
+
*/
|
|
105
|
+
export interface ExperimentSpec {
|
|
106
|
+
/** Experiment id. Matches the `.md` filename stem. */
|
|
107
|
+
id: string;
|
|
108
|
+
/** Surface this experiment runs on. */
|
|
109
|
+
surface: string;
|
|
110
|
+
/** Variants in declared order. */
|
|
111
|
+
variants: readonly string[];
|
|
112
|
+
/** Hex salt computed by `dif-core` from the experiment id. */
|
|
113
|
+
salt: string;
|
|
114
|
+
/** Per-variant weight, 0..100. Sum is 100. */
|
|
115
|
+
weights: Record<string, number>;
|
|
116
|
+
/** Exclusion-group key, or null. */
|
|
117
|
+
exclusionGroup: string | null;
|
|
118
|
+
/** Compiled audience predicate. */
|
|
119
|
+
audience: AudienceFn;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,2EAA2E;AAC3E,kBAAkB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dif.sh/sdk",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Client SDK for dif.sh — experiment assignment + metric tracking.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./server": {
|
|
15
|
+
"types": "./dist/server.d.ts",
|
|
16
|
+
"import": "./dist/server.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.json",
|
|
26
|
+
"test": "node --test --import tsx src/*.test.ts",
|
|
27
|
+
"lint": "tsc --noEmit",
|
|
28
|
+
"prepublishOnly": "npm run lint && npm test && npm run build"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20.6"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/dif-sh/dif.git",
|
|
39
|
+
"directory": "cli/packages/sdk"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"experimentation",
|
|
43
|
+
"ab-testing",
|
|
44
|
+
"feature-flags",
|
|
45
|
+
"analytics",
|
|
46
|
+
"metrics",
|
|
47
|
+
"dif.sh"
|
|
48
|
+
],
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"typescript": "^5.4.0",
|
|
51
|
+
"tsx": "^4.7.0"
|
|
52
|
+
}
|
|
53
|
+
}
|