@shipeasy/sdk 5.4.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 +66 -28
- package/dist/client/index.d.mts +88 -16
- package/dist/client/index.d.ts +88 -16
- package/dist/client/index.js +63 -8
- package/dist/client/index.mjs +59 -7
- 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 +80 -21
- package/dist/server/index.d.ts +80 -21
- package/dist/server/index.js +61 -11
- package/dist/server/index.mjs +57 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,40 +17,78 @@ 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
|
-
primary_label: "Sign up",
|
|
38
|
-
});
|
|
48
|
+
const cfg = flags.getConfig<{ max_uploads: number }>("upload_limits");
|
|
49
|
+
const { params } = flags.getExperiment("hero_cta", { primary_label: "Sign up" });
|
|
39
50
|
```
|
|
40
51
|
|
|
41
|
-
`
|
|
42
|
-
`
|
|
43
|
-
`
|
|
44
|
-
|
|
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.
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
### Server (Node, Cloudflare Worker, Deno)
|
|
47
59
|
|
|
48
60
|
```ts
|
|
49
|
-
import {
|
|
61
|
+
import { configure, Client } from "@shipeasy/sdk/server";
|
|
50
62
|
|
|
51
|
-
|
|
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 }),
|
|
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");
|
|
73
|
+
```
|
|
74
|
+
|
|
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 }`).
|
|
78
|
+
|
|
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:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { Engine } from "@shipeasy/sdk/server";
|
|
52
88
|
|
|
53
|
-
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" });
|
|
54
92
|
```
|
|
55
93
|
|
|
56
94
|
## SSR bootstrap (flags on first paint)
|
|
@@ -181,9 +219,9 @@ so your tests never touch the network.
|
|
|
181
219
|
|
|
182
220
|
```ts
|
|
183
221
|
// Server (Node / Cloudflare Worker / Deno)
|
|
184
|
-
import {
|
|
222
|
+
import { Engine } from "@shipeasy/sdk/server";
|
|
185
223
|
|
|
186
|
-
const client =
|
|
224
|
+
const client = Engine.forTesting();
|
|
187
225
|
|
|
188
226
|
client.overrideFlag("new_checkout", true);
|
|
189
227
|
client.overrideConfig("upload_limits", { max_uploads: 50 });
|
|
@@ -200,9 +238,9 @@ client.clearOverrides(); // reset every override back to default
|
|
|
200
238
|
|
|
201
239
|
```ts
|
|
202
240
|
// Browser (vanilla JS — no React required)
|
|
203
|
-
import {
|
|
241
|
+
import { Engine } from "@shipeasy/sdk/client";
|
|
204
242
|
|
|
205
|
-
const client =
|
|
243
|
+
const client = Engine.forTesting();
|
|
206
244
|
|
|
207
245
|
client.overrideFlag("new_checkout", true);
|
|
208
246
|
client.overrideConfig("upload_limits", { max_uploads: 50 });
|
|
@@ -283,13 +321,13 @@ enabled/killed state into a boolean, so `OFF` folds into `DEFAULT` there.
|
|
|
283
321
|
|
|
284
322
|
## Change listeners
|
|
285
323
|
|
|
286
|
-
The server `
|
|
324
|
+
The server `Engine` fires registered listeners after a **background poll
|
|
287
325
|
returns new data** (HTTP 200, not 304) — handy for invalidating a cache or
|
|
288
326
|
re-rendering when a flag flips. Returns an unsubscribe function. Never fires in
|
|
289
327
|
test/offline mode (no polling happens):
|
|
290
328
|
|
|
291
329
|
```ts
|
|
292
|
-
const client = new
|
|
330
|
+
const client = new Engine({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
|
|
293
331
|
await client.init();
|
|
294
332
|
const unsubscribe = client.onChange(() => {
|
|
295
333
|
console.log("flag rules changed — re-evaluating");
|
|
@@ -298,7 +336,7 @@ const unsubscribe = client.onChange(() => {
|
|
|
298
336
|
unsubscribe();
|
|
299
337
|
```
|
|
300
338
|
|
|
301
|
-
The browser `
|
|
339
|
+
The browser `Engine` already exposes the equivalent `subscribe()`
|
|
302
340
|
(fires after each `identify()` / override change).
|
|
303
341
|
|
|
304
342
|
## Offline snapshot
|
|
@@ -317,11 +355,11 @@ two SDK wire bodies:
|
|
|
317
355
|
```
|
|
318
356
|
|
|
319
357
|
```ts
|
|
320
|
-
import {
|
|
358
|
+
import { Engine } from "@shipeasy/sdk/server";
|
|
321
359
|
|
|
322
|
-
const client =
|
|
360
|
+
const client = Engine.fromFile("./snapshot.json");
|
|
323
361
|
// or, if you already hold the parsed object:
|
|
324
|
-
const client =
|
|
362
|
+
const client = Engine.fromSnapshot({ flags, experiments });
|
|
325
363
|
|
|
326
364
|
client.getFlag("new_checkout", { user_id: "u1" });
|
|
327
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.
|
|
@@ -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.
|
|
@@ -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 };
|