@shipeasy/sdk 5.3.0 → 6.0.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 +106 -27
- package/dist/client/index.d.mts +90 -18
- package/dist/client/index.d.ts +90 -18
- package/dist/client/index.js +100 -11
- package/dist/client/index.mjs +96 -10
- package/dist/openfeature-server/index.d.mts +19 -19
- package/dist/openfeature-server/index.d.ts +19 -19
- package/dist/openfeature-web/index.d.mts +12 -12
- package/dist/openfeature-web/index.d.ts +12 -12
- package/dist/server/index.d.mts +134 -45
- package/dist/server/index.d.ts +134 -45
- package/dist/server/index.js +113 -45
- package/dist/server/index.mjs +107 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,42 +17,121 @@ pnpm add @shipeasy/sdk
|
|
|
17
17
|
For React projects use [`@shipeasy/sdk-react`](https://github.com/shipeasy-ai/sdk-react)
|
|
18
18
|
which wraps this package with a `<ShipeasyProvider>` and hooks.
|
|
19
19
|
|
|
20
|
-
## Quickstart —
|
|
20
|
+
## Quickstart — `configure()` once, then `new Client(user)`
|
|
21
|
+
|
|
22
|
+
The ergonomic front door: **configure once** with your key and an optional
|
|
23
|
+
`attributes` transform from *your own user object* to Shipeasy targeting
|
|
24
|
+
attributes, then evaluate per user with `new Client(user)`. The bound
|
|
25
|
+
`Client` takes **no user argument** on its methods — the user is bound at
|
|
26
|
+
construction.
|
|
27
|
+
|
|
28
|
+
### Browser
|
|
21
29
|
|
|
22
30
|
```ts
|
|
23
|
-
import {
|
|
31
|
+
import { configure, Client } from "@shipeasy/sdk/client";
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
// Once, at app startup:
|
|
34
|
+
configure({
|
|
35
|
+
clientKey: process.env.NEXT_PUBLIC_SHIPEASY_CLIENT_KEY!,
|
|
36
|
+
// Optional — omit when you already pass a plain attribute object.
|
|
37
|
+
attributes: (u: MyUser) => ({ user_id: u.id, plan: u.plan }),
|
|
27
38
|
});
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
// Per visitor:
|
|
41
|
+
const flags = new Client(currentUser);
|
|
42
|
+
await flags.ready(); // optional — await the first /sdk/evaluate round-trip
|
|
30
43
|
|
|
31
|
-
if (
|
|
44
|
+
if (flags.getFlag("new_checkout")) {
|
|
32
45
|
// ship it
|
|
33
46
|
}
|
|
34
47
|
|
|
35
|
-
const cfg =
|
|
36
|
-
const { params } =
|
|
37
|
-
|
|
48
|
+
const cfg = flags.getConfig<{ max_uploads: number }>("upload_limits");
|
|
49
|
+
const { params } = flags.getExperiment("hero_cta", { primary_label: "Sign up" });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The browser is single-user: `new Client(user)` runs the transform and
|
|
53
|
+
`identify()`s the result under the hood (merging browser context — `locale`,
|
|
54
|
+
`timezone`, `path`, `referrer`, `screen_*`, `user_agent` — and a persisted
|
|
55
|
+
`anonymous_id`). `getFlag` reflects the latest identify; `await client.ready()`
|
|
56
|
+
when you need the first evaluation to have resolved.
|
|
57
|
+
|
|
58
|
+
### Server (Node, Cloudflare Worker, Deno)
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { configure, Client } from "@shipeasy/sdk/server";
|
|
62
|
+
|
|
63
|
+
// Once, at app boot:
|
|
64
|
+
configure({
|
|
65
|
+
apiKey: process.env.SHIPEASY_SERVER_KEY!,
|
|
66
|
+
attributes: (u: MyUser) => ({ user_id: u.id, plan: u.plan, country: u.geo.country }),
|
|
38
67
|
});
|
|
68
|
+
|
|
69
|
+
// Per request:
|
|
70
|
+
const flags = new Client(req.user);
|
|
71
|
+
if (flags.getFlag("new_checkout")) { /* ... */ }
|
|
72
|
+
const cfg = flags.getConfig("plan_limits");
|
|
39
73
|
```
|
|
40
74
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
holdouts can hash anonymous visitors out of the box.
|
|
75
|
+
When you don't pass `attributes`, the transform is the **identity** function —
|
|
76
|
+
the user object you pass is used verbatim, so it should already be the
|
|
77
|
+
attribute bag (`{ user_id, anonymous_id, ...targeting }`).
|
|
45
78
|
|
|
46
|
-
|
|
79
|
+
### Low-level: the `Engine` directly
|
|
80
|
+
|
|
81
|
+
`Client` is a cheap, user-bound handle over a single shared `Engine` (the
|
|
82
|
+
heavyweight class that owns the key, HTTP, the blob cache, and the poll timer —
|
|
83
|
+
**renamed from `FlagsClient` / `FlagsClientBrowser` in 6.0.0**). You can still
|
|
84
|
+
construct and drive an `Engine` yourself:
|
|
47
85
|
|
|
48
86
|
```ts
|
|
49
|
-
import {
|
|
87
|
+
import { Engine } from "@shipeasy/sdk/server";
|
|
50
88
|
|
|
51
|
-
const
|
|
89
|
+
const engine = new Engine({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
|
|
90
|
+
await engine.initOnce();
|
|
91
|
+
const on = engine.getFlag("new_checkout", { user_id: "u-1", plan: "pro" });
|
|
92
|
+
```
|
|
52
93
|
|
|
53
|
-
|
|
94
|
+
## SSR bootstrap (flags on first paint)
|
|
95
|
+
|
|
96
|
+
Server-render the evaluated flags / configs / experiments into the page so the
|
|
97
|
+
browser SDK reads them **synchronously on first paint** — no flash, no extra
|
|
98
|
+
round-trip. The `shipeasy()` server handle emits two declarative `<script>`
|
|
99
|
+
tags. **No SDK key is embedded** in the bootstrap tag (the server key never
|
|
100
|
+
reaches the browser).
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// app/layout.tsx — Next.js root layout (React Server Component)
|
|
104
|
+
import { shipeasy } from "@shipeasy/sdk/server";
|
|
105
|
+
|
|
106
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
107
|
+
const se = await shipeasy({ serverKey: process.env.SHIPEASY_SERVER_KEY ?? "" });
|
|
108
|
+
const boot = se.getBootstrapData({
|
|
109
|
+
// public client key — lets the i18n loader revalidate strings at runtime
|
|
110
|
+
clientKey: process.env.NEXT_PUBLIC_SHIPEASY_CLIENT_KEY,
|
|
111
|
+
});
|
|
112
|
+
return (
|
|
113
|
+
<html>
|
|
114
|
+
<body>
|
|
115
|
+
{/* Render REAL <script> elements — scripts set via
|
|
116
|
+
dangerouslySetInnerHTML do NOT execute. */}
|
|
117
|
+
<script src={boot.bootstrap.src} {...boot.bootstrap.attrs} />
|
|
118
|
+
{boot.i18nLoader && <script src={boot.i18nLoader.src} {...boot.i18nLoader.attrs} />}
|
|
119
|
+
{children}
|
|
120
|
+
</body>
|
|
121
|
+
</html>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
54
124
|
```
|
|
55
125
|
|
|
126
|
+
`bootstrap.src` is `https://cdn.shipeasy.ai/sdk/bootstrap.js` — a static loader
|
|
127
|
+
that reads its own `data-*` attributes, hydrates `window.__SE_BOOTSTRAP`, and
|
|
128
|
+
persists the `__se_anon_id` cookie so the browser buckets **identically** to the
|
|
129
|
+
server. The i18n loader tag carries the SSR strings (`data-strings`) for a
|
|
130
|
+
no-flash first paint plus the public client key for runtime revalidation.
|
|
131
|
+
|
|
132
|
+
For non-React SSR (Express, raw templates), `se.getBootstrapTags()` returns the
|
|
133
|
+
same two tags as an HTML string you can drop straight into the served markup.
|
|
134
|
+
|
|
56
135
|
## Error tracking — `see()`
|
|
57
136
|
|
|
58
137
|
`see` (shipeasy error) is the structured error reporter — opes-style: every
|
|
@@ -140,9 +219,9 @@ so your tests never touch the network.
|
|
|
140
219
|
|
|
141
220
|
```ts
|
|
142
221
|
// Server (Node / Cloudflare Worker / Deno)
|
|
143
|
-
import {
|
|
222
|
+
import { Engine } from "@shipeasy/sdk/server";
|
|
144
223
|
|
|
145
|
-
const client =
|
|
224
|
+
const client = Engine.forTesting();
|
|
146
225
|
|
|
147
226
|
client.overrideFlag("new_checkout", true);
|
|
148
227
|
client.overrideConfig("upload_limits", { max_uploads: 50 });
|
|
@@ -159,9 +238,9 @@ client.clearOverrides(); // reset every override back to default
|
|
|
159
238
|
|
|
160
239
|
```ts
|
|
161
240
|
// Browser (vanilla JS — no React required)
|
|
162
|
-
import {
|
|
241
|
+
import { Engine } from "@shipeasy/sdk/client";
|
|
163
242
|
|
|
164
|
-
const client =
|
|
243
|
+
const client = Engine.forTesting();
|
|
165
244
|
|
|
166
245
|
client.overrideFlag("new_checkout", true);
|
|
167
246
|
client.overrideConfig("upload_limits", { max_uploads: 50 });
|
|
@@ -242,13 +321,13 @@ enabled/killed state into a boolean, so `OFF` folds into `DEFAULT` there.
|
|
|
242
321
|
|
|
243
322
|
## Change listeners
|
|
244
323
|
|
|
245
|
-
The server `
|
|
324
|
+
The server `Engine` fires registered listeners after a **background poll
|
|
246
325
|
returns new data** (HTTP 200, not 304) — handy for invalidating a cache or
|
|
247
326
|
re-rendering when a flag flips. Returns an unsubscribe function. Never fires in
|
|
248
327
|
test/offline mode (no polling happens):
|
|
249
328
|
|
|
250
329
|
```ts
|
|
251
|
-
const client = new
|
|
330
|
+
const client = new Engine({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
|
|
252
331
|
await client.init();
|
|
253
332
|
const unsubscribe = client.onChange(() => {
|
|
254
333
|
console.log("flag rules changed — re-evaluating");
|
|
@@ -257,7 +336,7 @@ const unsubscribe = client.onChange(() => {
|
|
|
257
336
|
unsubscribe();
|
|
258
337
|
```
|
|
259
338
|
|
|
260
|
-
The browser `
|
|
339
|
+
The browser `Engine` already exposes the equivalent `subscribe()`
|
|
261
340
|
(fires after each `identify()` / override change).
|
|
262
341
|
|
|
263
342
|
## Offline snapshot
|
|
@@ -276,11 +355,11 @@ two SDK wire bodies:
|
|
|
276
355
|
```
|
|
277
356
|
|
|
278
357
|
```ts
|
|
279
|
-
import {
|
|
358
|
+
import { Engine } from "@shipeasy/sdk/server";
|
|
280
359
|
|
|
281
|
-
const client =
|
|
360
|
+
const client = Engine.fromFile("./snapshot.json");
|
|
282
361
|
// or, if you already hold the parsed object:
|
|
283
|
-
const client =
|
|
362
|
+
const client = Engine.fromSnapshot({ flags, experiments });
|
|
284
363
|
|
|
285
364
|
client.getFlag("new_checkout", { user_id: "u1" });
|
|
286
365
|
```
|
package/dist/client/index.d.mts
CHANGED
|
@@ -202,8 +202,8 @@ declare function sameOrigin(rawUrl: string): boolean;
|
|
|
202
202
|
* pass through unchanged (correlation is optional, never breaks the fetch).
|
|
203
203
|
*/
|
|
204
204
|
declare function injectCorrelationHeader(args: Parameters<typeof fetch>, corr: string): Parameters<typeof fetch>;
|
|
205
|
-
type
|
|
206
|
-
interface
|
|
205
|
+
type EngineEnv = "dev" | "staging" | "prod";
|
|
206
|
+
interface EngineOptions {
|
|
207
207
|
sdkKey: string;
|
|
208
208
|
baseUrl?: string;
|
|
209
209
|
autoGuardrails?: boolean;
|
|
@@ -222,7 +222,7 @@ interface FlagsClientBrowserOptions {
|
|
|
222
222
|
*/
|
|
223
223
|
autoCollectAlways?: boolean;
|
|
224
224
|
/** Which published env to read values from. Defaults to "prod". */
|
|
225
|
-
env?:
|
|
225
|
+
env?: EngineEnv;
|
|
226
226
|
/**
|
|
227
227
|
* Per-evaluation usage telemetry. ON by default — each getFlag/getConfig/
|
|
228
228
|
* getExperiment/getKillswitch call fires one fire-and-forget sendBeacon so
|
|
@@ -259,11 +259,11 @@ interface FlagsClientBrowserOptions {
|
|
|
259
259
|
* Test mode — no network at all. identify()/init are no-ops (never call
|
|
260
260
|
* /sdk/evaluate), track() is a no-op, telemetry is forced off, and the client
|
|
261
261
|
* starts "ready" with an empty eval result. Prefer the
|
|
262
|
-
* {@link
|
|
262
|
+
* {@link Engine.forTesting} factory over passing this directly.
|
|
263
263
|
*/
|
|
264
264
|
testMode?: boolean;
|
|
265
265
|
}
|
|
266
|
-
declare class
|
|
266
|
+
declare class Engine {
|
|
267
267
|
private readonly sdkKey;
|
|
268
268
|
private readonly baseUrl;
|
|
269
269
|
private readonly autoGuardrails;
|
|
@@ -288,7 +288,7 @@ declare class FlagsClientBrowser {
|
|
|
288
288
|
private readonly configOverrides;
|
|
289
289
|
private readonly experimentOverrides;
|
|
290
290
|
private onOverrideChange;
|
|
291
|
-
constructor(opts:
|
|
291
|
+
constructor(opts: EngineOptions);
|
|
292
292
|
/**
|
|
293
293
|
* Build a no-network, immediately-usable browser client for tests
|
|
294
294
|
* (Statsig-style). identify() is a no-op (never calls /sdk/evaluate), track()
|
|
@@ -297,12 +297,12 @@ declare class FlagsClientBrowser {
|
|
|
297
297
|
* key required.
|
|
298
298
|
*
|
|
299
299
|
* ```ts
|
|
300
|
-
* const client =
|
|
300
|
+
* const client = Engine.forTesting();
|
|
301
301
|
* client.overrideFlag("new_checkout", true);
|
|
302
302
|
* client.getFlag("new_checkout"); // true
|
|
303
303
|
* ```
|
|
304
304
|
*/
|
|
305
|
-
static forTesting(opts?: Partial<
|
|
305
|
+
static forTesting(opts?: Partial<EngineOptions>): Engine;
|
|
306
306
|
identify(user: User): Promise<void>;
|
|
307
307
|
/**
|
|
308
308
|
* Report a structured error into the errors primitive. Flushes immediately
|
|
@@ -411,7 +411,7 @@ interface AttachDevtoolsOptions {
|
|
|
411
411
|
* each `identify()`/override change. Returns an unsubscribe function for
|
|
412
412
|
* cleanup (e.g. React effect teardown).
|
|
413
413
|
*/
|
|
414
|
-
declare function attachDevtools(client:
|
|
414
|
+
declare function attachDevtools(client: Engine, opts?: AttachDevtoolsOptions): () => void;
|
|
415
415
|
/** Configure the singleton. Idempotent — re-calling with the same opts is a no-op. */
|
|
416
416
|
interface ShipeasyClientConfig {
|
|
417
417
|
/**
|
|
@@ -484,13 +484,13 @@ interface ShipeasyClientConfig {
|
|
|
484
484
|
* Attribute names usable for targeting but never persisted in analytics
|
|
485
485
|
* (LD/Statsig `privateAttributes`). Sent to the edge for evaluation, never
|
|
486
486
|
* stored, and stripped from `flags.track(props)`. See
|
|
487
|
-
* {@link
|
|
487
|
+
* {@link EngineOptions.privateAttributes}.
|
|
488
488
|
*/
|
|
489
489
|
privateAttributes?: string[];
|
|
490
490
|
/**
|
|
491
491
|
* Sticky bucketing (doc 20 §2). ON by default — locks each enrolled unit to
|
|
492
492
|
* its first-assigned variant via the `__se_sticky` cookie. Pass `false` to
|
|
493
|
-
* opt out. See {@link
|
|
493
|
+
* opt out. See {@link EngineOptions.stickyBucketing}.
|
|
494
494
|
*/
|
|
495
495
|
stickyBucketing?: boolean;
|
|
496
496
|
}
|
|
@@ -505,9 +505,9 @@ interface ShipeasyClientConfig {
|
|
|
505
505
|
* A later flags.identify({ user_id }) overrides this in place; anonId stays stable.
|
|
506
506
|
*/
|
|
507
507
|
declare function shipeasy(opts: ShipeasyClientConfig): () => void;
|
|
508
|
-
declare function configureShipeasy(opts:
|
|
508
|
+
declare function configureShipeasy(opts: EngineOptions): Engine;
|
|
509
509
|
/** Returns the configured singleton, or null if configureShipeasy() hasn't run yet. */
|
|
510
|
-
declare function getShipeasyClient():
|
|
510
|
+
declare function getShipeasyClient(): Engine | null;
|
|
511
511
|
/**
|
|
512
512
|
* Test helper — drop the singleton so the next configureShipeasy() builds fresh.
|
|
513
513
|
* Not part of the documented surface; production code should never call this.
|
|
@@ -530,8 +530,8 @@ interface BootstrapPayload {
|
|
|
530
530
|
/** i18n profile the server rendered with, so the client loader matches. No key is embedded. */
|
|
531
531
|
i18nProfile?: string;
|
|
532
532
|
apiUrl?: string;
|
|
533
|
-
/**
|
|
534
|
-
|
|
533
|
+
/** Stable anonymous bucketing id the server evaluated against (cross-SDK contract). */
|
|
534
|
+
anonId?: string;
|
|
535
535
|
}
|
|
536
536
|
/**
|
|
537
537
|
* Universal flags facade. Methods return safe defaults when the singleton
|
|
@@ -539,7 +539,7 @@ interface BootstrapPayload {
|
|
|
539
539
|
* importing this in a module that loads before app boot is harmless.
|
|
540
540
|
*/
|
|
541
541
|
declare const flags: {
|
|
542
|
-
configure(opts:
|
|
542
|
+
configure(opts: EngineOptions): void;
|
|
543
543
|
identify(user: User): Promise<void>;
|
|
544
544
|
/**
|
|
545
545
|
* Read a feature gate.
|
|
@@ -554,7 +554,7 @@ declare const flags: {
|
|
|
554
554
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
555
555
|
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decodeOrOpts?: ((raw: unknown) => P) | GetExperimentOptions<P>, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
|
|
556
556
|
/** Manually log an exposure for an enrolled experiment. See
|
|
557
|
-
* {@link
|
|
557
|
+
* {@link Engine.logExposure}. No-op before configure(). */
|
|
558
558
|
logExposure(name: string): void;
|
|
559
559
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
560
560
|
/**
|
|
@@ -583,6 +583,78 @@ declare const flags: {
|
|
|
583
583
|
/** True once identify() has completed and flags are available. */
|
|
584
584
|
readonly ready: boolean;
|
|
585
585
|
};
|
|
586
|
+
/** Transform YOUR application's user object into Shipeasy targeting attributes. */
|
|
587
|
+
type AttributesFn<U = unknown> = (user: U) => User;
|
|
588
|
+
interface ConfigureOptions<U = unknown> extends Omit<ShipeasyClientConfig, "clientKey"> {
|
|
589
|
+
/** Public client key — the single key the browser side accepts (NEXT_PUBLIC_SHIPEASY_CLIENT_KEY). */
|
|
590
|
+
clientKey: string;
|
|
591
|
+
/**
|
|
592
|
+
* Map your own user object into the attribute bag every flag/experiment
|
|
593
|
+
* evaluation sees. Runs once per `new Client(user)`. Omit when you already
|
|
594
|
+
* pass a plain attribute object (identity transform — the object is used
|
|
595
|
+
* verbatim, so it should carry `user_id` + any targeting attrs).
|
|
596
|
+
*/
|
|
597
|
+
attributes?: AttributesFn<U>;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Configure the SDK once at app boot, then evaluate per user with
|
|
601
|
+
* `new Client(user)`. Builds the process-wide {@link Engine} (the
|
|
602
|
+
* /sdk/evaluate-backed browser client) and registers the `attributes`
|
|
603
|
+
* transform. The first call wins; later calls reuse the existing engine
|
|
604
|
+
* (mirrors {@link shipeasy}).
|
|
605
|
+
*
|
|
606
|
+
* ```ts
|
|
607
|
+
* import { configure, Client } from "@shipeasy/sdk/client";
|
|
608
|
+
*
|
|
609
|
+
* configure({
|
|
610
|
+
* clientKey: process.env.NEXT_PUBLIC_SHIPEASY_CLIENT_KEY!,
|
|
611
|
+
* attributes: (u: MyUser) => ({ user_id: u.id, plan: u.plan }),
|
|
612
|
+
* });
|
|
613
|
+
*
|
|
614
|
+
* const flags = new Client(currentUser);
|
|
615
|
+
* await flags.ready();
|
|
616
|
+
* if (flags.getFlag("new_checkout")) { ... }
|
|
617
|
+
* ```
|
|
618
|
+
*
|
|
619
|
+
* Returns a cleanup function (same as {@link shipeasy}) that removes the
|
|
620
|
+
* devtools listeners — call it on teardown.
|
|
621
|
+
*/
|
|
622
|
+
declare function configure<U = unknown>(opts: ConfigureOptions<U>): () => void;
|
|
623
|
+
/** Test seam: reset the registered attribute transform. */
|
|
624
|
+
declare function _resetConfigureForTests(): void;
|
|
625
|
+
/**
|
|
626
|
+
* A user-bound evaluation handle for the browser. Construct one for the current
|
|
627
|
+
* visitor — it's cheap (it delegates to the single {@link Engine} built by
|
|
628
|
+
* {@link configure}); it does NOT open its own connection. The configured
|
|
629
|
+
* `attributes` transform runs once here and the result is `identify()`-ed into
|
|
630
|
+
* the engine (fire-and-forget, since identify is async).
|
|
631
|
+
*
|
|
632
|
+
* Because the browser is single-user, all bound handles share the engine's
|
|
633
|
+
* latest eval result. Use {@link Client.ready} to await the first evaluation.
|
|
634
|
+
*
|
|
635
|
+
* ```ts
|
|
636
|
+
* const flags = new Client(currentUser);
|
|
637
|
+
* await flags.ready();
|
|
638
|
+
* flags.getFlag("new_checkout"); // no user arg — bound at construction
|
|
639
|
+
* flags.getExperiment("price_test", { price: 9 });
|
|
640
|
+
* ```
|
|
641
|
+
*/
|
|
642
|
+
declare class Client<U = unknown> {
|
|
643
|
+
private readonly engine;
|
|
644
|
+
/** The resolved attribute bag this handle evaluates against. */
|
|
645
|
+
readonly attributes: User;
|
|
646
|
+
private readonly _identify;
|
|
647
|
+
constructor(user: U);
|
|
648
|
+
/** Resolves once the engine's identify() for this user has completed. */
|
|
649
|
+
ready(): Promise<void>;
|
|
650
|
+
getFlag(name: string, defaultValue?: boolean): boolean;
|
|
651
|
+
getFlagDetail(name: string): FlagDetail;
|
|
652
|
+
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
653
|
+
getConfig<T = unknown>(name: string, opts: GetConfigOptions<T>): T;
|
|
654
|
+
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
|
|
655
|
+
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
656
|
+
getKillswitch(name: string, switchKey?: string): boolean;
|
|
657
|
+
}
|
|
586
658
|
interface SeeApi {
|
|
587
659
|
/**
|
|
588
660
|
* Report a handled problem and its product consequence:
|
|
@@ -674,4 +746,4 @@ interface I18nFacade {
|
|
|
674
746
|
}
|
|
675
747
|
declare const i18n: I18nFacade;
|
|
676
748
|
|
|
677
|
-
export { type AutoCollectGroups, type BootstrapPayload, type
|
|
749
|
+
export { type AttributesFn, type AutoCollectGroups, type BootstrapPayload, Client, type ConfigureOptions, type Consequence, Engine, type EngineEnv, type EngineOptions, type ExperimentResult, FLAG_REASONS, type FlagDetail, type FlagReason, type GetConfigOptions, type GetExperimentOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetConfigureForTests, _resetShipeasyForTests, attachDevtools, configure, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -202,8 +202,8 @@ declare function sameOrigin(rawUrl: string): boolean;
|
|
|
202
202
|
* pass through unchanged (correlation is optional, never breaks the fetch).
|
|
203
203
|
*/
|
|
204
204
|
declare function injectCorrelationHeader(args: Parameters<typeof fetch>, corr: string): Parameters<typeof fetch>;
|
|
205
|
-
type
|
|
206
|
-
interface
|
|
205
|
+
type EngineEnv = "dev" | "staging" | "prod";
|
|
206
|
+
interface EngineOptions {
|
|
207
207
|
sdkKey: string;
|
|
208
208
|
baseUrl?: string;
|
|
209
209
|
autoGuardrails?: boolean;
|
|
@@ -222,7 +222,7 @@ interface FlagsClientBrowserOptions {
|
|
|
222
222
|
*/
|
|
223
223
|
autoCollectAlways?: boolean;
|
|
224
224
|
/** Which published env to read values from. Defaults to "prod". */
|
|
225
|
-
env?:
|
|
225
|
+
env?: EngineEnv;
|
|
226
226
|
/**
|
|
227
227
|
* Per-evaluation usage telemetry. ON by default — each getFlag/getConfig/
|
|
228
228
|
* getExperiment/getKillswitch call fires one fire-and-forget sendBeacon so
|
|
@@ -259,11 +259,11 @@ interface FlagsClientBrowserOptions {
|
|
|
259
259
|
* Test mode — no network at all. identify()/init are no-ops (never call
|
|
260
260
|
* /sdk/evaluate), track() is a no-op, telemetry is forced off, and the client
|
|
261
261
|
* starts "ready" with an empty eval result. Prefer the
|
|
262
|
-
* {@link
|
|
262
|
+
* {@link Engine.forTesting} factory over passing this directly.
|
|
263
263
|
*/
|
|
264
264
|
testMode?: boolean;
|
|
265
265
|
}
|
|
266
|
-
declare class
|
|
266
|
+
declare class Engine {
|
|
267
267
|
private readonly sdkKey;
|
|
268
268
|
private readonly baseUrl;
|
|
269
269
|
private readonly autoGuardrails;
|
|
@@ -288,7 +288,7 @@ declare class FlagsClientBrowser {
|
|
|
288
288
|
private readonly configOverrides;
|
|
289
289
|
private readonly experimentOverrides;
|
|
290
290
|
private onOverrideChange;
|
|
291
|
-
constructor(opts:
|
|
291
|
+
constructor(opts: EngineOptions);
|
|
292
292
|
/**
|
|
293
293
|
* Build a no-network, immediately-usable browser client for tests
|
|
294
294
|
* (Statsig-style). identify() is a no-op (never calls /sdk/evaluate), track()
|
|
@@ -297,12 +297,12 @@ declare class FlagsClientBrowser {
|
|
|
297
297
|
* key required.
|
|
298
298
|
*
|
|
299
299
|
* ```ts
|
|
300
|
-
* const client =
|
|
300
|
+
* const client = Engine.forTesting();
|
|
301
301
|
* client.overrideFlag("new_checkout", true);
|
|
302
302
|
* client.getFlag("new_checkout"); // true
|
|
303
303
|
* ```
|
|
304
304
|
*/
|
|
305
|
-
static forTesting(opts?: Partial<
|
|
305
|
+
static forTesting(opts?: Partial<EngineOptions>): Engine;
|
|
306
306
|
identify(user: User): Promise<void>;
|
|
307
307
|
/**
|
|
308
308
|
* Report a structured error into the errors primitive. Flushes immediately
|
|
@@ -411,7 +411,7 @@ interface AttachDevtoolsOptions {
|
|
|
411
411
|
* each `identify()`/override change. Returns an unsubscribe function for
|
|
412
412
|
* cleanup (e.g. React effect teardown).
|
|
413
413
|
*/
|
|
414
|
-
declare function attachDevtools(client:
|
|
414
|
+
declare function attachDevtools(client: Engine, opts?: AttachDevtoolsOptions): () => void;
|
|
415
415
|
/** Configure the singleton. Idempotent — re-calling with the same opts is a no-op. */
|
|
416
416
|
interface ShipeasyClientConfig {
|
|
417
417
|
/**
|
|
@@ -484,13 +484,13 @@ interface ShipeasyClientConfig {
|
|
|
484
484
|
* Attribute names usable for targeting but never persisted in analytics
|
|
485
485
|
* (LD/Statsig `privateAttributes`). Sent to the edge for evaluation, never
|
|
486
486
|
* stored, and stripped from `flags.track(props)`. See
|
|
487
|
-
* {@link
|
|
487
|
+
* {@link EngineOptions.privateAttributes}.
|
|
488
488
|
*/
|
|
489
489
|
privateAttributes?: string[];
|
|
490
490
|
/**
|
|
491
491
|
* Sticky bucketing (doc 20 §2). ON by default — locks each enrolled unit to
|
|
492
492
|
* its first-assigned variant via the `__se_sticky` cookie. Pass `false` to
|
|
493
|
-
* opt out. See {@link
|
|
493
|
+
* opt out. See {@link EngineOptions.stickyBucketing}.
|
|
494
494
|
*/
|
|
495
495
|
stickyBucketing?: boolean;
|
|
496
496
|
}
|
|
@@ -505,9 +505,9 @@ interface ShipeasyClientConfig {
|
|
|
505
505
|
* A later flags.identify({ user_id }) overrides this in place; anonId stays stable.
|
|
506
506
|
*/
|
|
507
507
|
declare function shipeasy(opts: ShipeasyClientConfig): () => void;
|
|
508
|
-
declare function configureShipeasy(opts:
|
|
508
|
+
declare function configureShipeasy(opts: EngineOptions): Engine;
|
|
509
509
|
/** Returns the configured singleton, or null if configureShipeasy() hasn't run yet. */
|
|
510
|
-
declare function getShipeasyClient():
|
|
510
|
+
declare function getShipeasyClient(): Engine | null;
|
|
511
511
|
/**
|
|
512
512
|
* Test helper — drop the singleton so the next configureShipeasy() builds fresh.
|
|
513
513
|
* Not part of the documented surface; production code should never call this.
|
|
@@ -530,8 +530,8 @@ interface BootstrapPayload {
|
|
|
530
530
|
/** i18n profile the server rendered with, so the client loader matches. No key is embedded. */
|
|
531
531
|
i18nProfile?: string;
|
|
532
532
|
apiUrl?: string;
|
|
533
|
-
/**
|
|
534
|
-
|
|
533
|
+
/** Stable anonymous bucketing id the server evaluated against (cross-SDK contract). */
|
|
534
|
+
anonId?: string;
|
|
535
535
|
}
|
|
536
536
|
/**
|
|
537
537
|
* Universal flags facade. Methods return safe defaults when the singleton
|
|
@@ -539,7 +539,7 @@ interface BootstrapPayload {
|
|
|
539
539
|
* importing this in a module that loads before app boot is harmless.
|
|
540
540
|
*/
|
|
541
541
|
declare const flags: {
|
|
542
|
-
configure(opts:
|
|
542
|
+
configure(opts: EngineOptions): void;
|
|
543
543
|
identify(user: User): Promise<void>;
|
|
544
544
|
/**
|
|
545
545
|
* Read a feature gate.
|
|
@@ -554,7 +554,7 @@ declare const flags: {
|
|
|
554
554
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
555
555
|
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decodeOrOpts?: ((raw: unknown) => P) | GetExperimentOptions<P>, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
|
|
556
556
|
/** Manually log an exposure for an enrolled experiment. See
|
|
557
|
-
* {@link
|
|
557
|
+
* {@link Engine.logExposure}. No-op before configure(). */
|
|
558
558
|
logExposure(name: string): void;
|
|
559
559
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
560
560
|
/**
|
|
@@ -583,6 +583,78 @@ declare const flags: {
|
|
|
583
583
|
/** True once identify() has completed and flags are available. */
|
|
584
584
|
readonly ready: boolean;
|
|
585
585
|
};
|
|
586
|
+
/** Transform YOUR application's user object into Shipeasy targeting attributes. */
|
|
587
|
+
type AttributesFn<U = unknown> = (user: U) => User;
|
|
588
|
+
interface ConfigureOptions<U = unknown> extends Omit<ShipeasyClientConfig, "clientKey"> {
|
|
589
|
+
/** Public client key — the single key the browser side accepts (NEXT_PUBLIC_SHIPEASY_CLIENT_KEY). */
|
|
590
|
+
clientKey: string;
|
|
591
|
+
/**
|
|
592
|
+
* Map your own user object into the attribute bag every flag/experiment
|
|
593
|
+
* evaluation sees. Runs once per `new Client(user)`. Omit when you already
|
|
594
|
+
* pass a plain attribute object (identity transform — the object is used
|
|
595
|
+
* verbatim, so it should carry `user_id` + any targeting attrs).
|
|
596
|
+
*/
|
|
597
|
+
attributes?: AttributesFn<U>;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Configure the SDK once at app boot, then evaluate per user with
|
|
601
|
+
* `new Client(user)`. Builds the process-wide {@link Engine} (the
|
|
602
|
+
* /sdk/evaluate-backed browser client) and registers the `attributes`
|
|
603
|
+
* transform. The first call wins; later calls reuse the existing engine
|
|
604
|
+
* (mirrors {@link shipeasy}).
|
|
605
|
+
*
|
|
606
|
+
* ```ts
|
|
607
|
+
* import { configure, Client } from "@shipeasy/sdk/client";
|
|
608
|
+
*
|
|
609
|
+
* configure({
|
|
610
|
+
* clientKey: process.env.NEXT_PUBLIC_SHIPEASY_CLIENT_KEY!,
|
|
611
|
+
* attributes: (u: MyUser) => ({ user_id: u.id, plan: u.plan }),
|
|
612
|
+
* });
|
|
613
|
+
*
|
|
614
|
+
* const flags = new Client(currentUser);
|
|
615
|
+
* await flags.ready();
|
|
616
|
+
* if (flags.getFlag("new_checkout")) { ... }
|
|
617
|
+
* ```
|
|
618
|
+
*
|
|
619
|
+
* Returns a cleanup function (same as {@link shipeasy}) that removes the
|
|
620
|
+
* devtools listeners — call it on teardown.
|
|
621
|
+
*/
|
|
622
|
+
declare function configure<U = unknown>(opts: ConfigureOptions<U>): () => void;
|
|
623
|
+
/** Test seam: reset the registered attribute transform. */
|
|
624
|
+
declare function _resetConfigureForTests(): void;
|
|
625
|
+
/**
|
|
626
|
+
* A user-bound evaluation handle for the browser. Construct one for the current
|
|
627
|
+
* visitor — it's cheap (it delegates to the single {@link Engine} built by
|
|
628
|
+
* {@link configure}); it does NOT open its own connection. The configured
|
|
629
|
+
* `attributes` transform runs once here and the result is `identify()`-ed into
|
|
630
|
+
* the engine (fire-and-forget, since identify is async).
|
|
631
|
+
*
|
|
632
|
+
* Because the browser is single-user, all bound handles share the engine's
|
|
633
|
+
* latest eval result. Use {@link Client.ready} to await the first evaluation.
|
|
634
|
+
*
|
|
635
|
+
* ```ts
|
|
636
|
+
* const flags = new Client(currentUser);
|
|
637
|
+
* await flags.ready();
|
|
638
|
+
* flags.getFlag("new_checkout"); // no user arg — bound at construction
|
|
639
|
+
* flags.getExperiment("price_test", { price: 9 });
|
|
640
|
+
* ```
|
|
641
|
+
*/
|
|
642
|
+
declare class Client<U = unknown> {
|
|
643
|
+
private readonly engine;
|
|
644
|
+
/** The resolved attribute bag this handle evaluates against. */
|
|
645
|
+
readonly attributes: User;
|
|
646
|
+
private readonly _identify;
|
|
647
|
+
constructor(user: U);
|
|
648
|
+
/** Resolves once the engine's identify() for this user has completed. */
|
|
649
|
+
ready(): Promise<void>;
|
|
650
|
+
getFlag(name: string, defaultValue?: boolean): boolean;
|
|
651
|
+
getFlagDetail(name: string): FlagDetail;
|
|
652
|
+
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
653
|
+
getConfig<T = unknown>(name: string, opts: GetConfigOptions<T>): T;
|
|
654
|
+
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
|
|
655
|
+
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
656
|
+
getKillswitch(name: string, switchKey?: string): boolean;
|
|
657
|
+
}
|
|
586
658
|
interface SeeApi {
|
|
587
659
|
/**
|
|
588
660
|
* Report a handled problem and its product consequence:
|
|
@@ -674,4 +746,4 @@ interface I18nFacade {
|
|
|
674
746
|
}
|
|
675
747
|
declare const i18n: I18nFacade;
|
|
676
748
|
|
|
677
|
-
export { type AutoCollectGroups, type BootstrapPayload, type
|
|
749
|
+
export { type AttributesFn, type AutoCollectGroups, type BootstrapPayload, Client, type ConfigureOptions, type Consequence, Engine, type EngineEnv, type EngineOptions, type ExperimentResult, FLAG_REASONS, type FlagDetail, type FlagReason, type GetConfigOptions, type GetExperimentOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetConfigureForTests, _resetShipeasyForTests, attachDevtools, configure, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
|