@shipeasy/sdk 6.3.1 → 7.1.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 +1 -4
- package/dist/client/index.d.mts +166 -27
- package/dist/client/index.d.ts +166 -27
- package/dist/client/index.js +268 -132
- package/dist/client/index.mjs +268 -132
- package/dist/openfeature-server/index.d.mts +144 -14
- package/dist/openfeature-server/index.d.ts +144 -14
- package/dist/openfeature-server/index.js +65 -33
- package/dist/openfeature-server/index.mjs +65 -33
- package/dist/openfeature-web/index.d.mts +110 -30
- package/dist/openfeature-web/index.d.ts +110 -30
- package/dist/server/index.d.mts +181 -28
- package/dist/server/index.d.ts +181 -28
- package/dist/server/index.js +337 -159
- package/dist/server/index.mjs +337 -159
- package/docs/skill/SKILL.md +20 -10
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ Copy-paste snippets live under [`docs/snippets/`](https://github.com/shipeasy-ai
|
|
|
83
83
|
|
|
84
84
|
## Testing
|
|
85
85
|
|
|
86
|
-
For unit tests, swap the live `configure()` for **`configureForTesting()`** — a drop-in sibling with **no network, ever** (no SDK key required). It replaces the active configuration with a network-free engine, seeds the values your code should see, and is read through the ordinary `new Client(user)`. In this mode the rules never fetch, `track()`
|
|
86
|
+
For unit tests, swap the live `configure()` for **`configureForTesting()`** — a drop-in sibling with **no network, ever** (no SDK key required). It replaces the active configuration with a network-free engine, seeds the values your code should see, and is read through the ordinary `new Client(user)`. In this mode the rules never fetch, `track()` is a no-op, `assign()` logs no exposure, and telemetry is off — your tests never touch the network.
|
|
87
87
|
|
|
88
88
|
```ts
|
|
89
89
|
import { configureForTesting, Client, clearOverrides } from "@shipeasy/sdk/server"; // or /client
|
|
@@ -92,14 +92,11 @@ import { configureForTesting, Client, clearOverrides } from "@shipeasy/sdk/serve
|
|
|
92
92
|
configureForTesting({
|
|
93
93
|
flags: { new_checkout: true },
|
|
94
94
|
configs: { upload_limits: { max_uploads: 50 } },
|
|
95
|
-
experiments: { hero_cta: ["treatment", { primary_label: "Buy now" }] },
|
|
96
95
|
});
|
|
97
96
|
|
|
98
97
|
const flags = new Client({ user_id: "u_1" }); // construct once per callsite
|
|
99
98
|
flags.getFlag("new_checkout"); // true
|
|
100
99
|
flags.getConfig("upload_limits"); // { max_uploads: 50 }
|
|
101
|
-
flags.getExperiment("hero_cta", { primary_label: "Sign up" });
|
|
102
|
-
// → { inExperiment: true, group: "treatment", params: { primary_label: "Buy now" } }
|
|
103
100
|
|
|
104
101
|
clearOverrides(); // reset every seeded override back to the empty-blob default
|
|
105
102
|
```
|
package/dist/client/index.d.mts
CHANGED
|
@@ -169,11 +169,19 @@ interface EvalExpResult {
|
|
|
169
169
|
inExperiment: boolean;
|
|
170
170
|
group: string;
|
|
171
171
|
params: Record<string, unknown>;
|
|
172
|
+
/** The universe this experiment belongs to — lets `universe(name).assign()`
|
|
173
|
+
* find the enrolled experiment within a universe. */
|
|
174
|
+
universe?: string;
|
|
172
175
|
}
|
|
173
176
|
interface EvalResponse {
|
|
174
177
|
flags: Record<string, boolean>;
|
|
175
178
|
configs: Record<string, unknown>;
|
|
176
179
|
experiments: Record<string, EvalExpResult>;
|
|
180
|
+
/** Per-universe param defaults (name → default map) so `universe(name).get()`
|
|
181
|
+
* resolves to the universe default even when the unit is not enrolled. */
|
|
182
|
+
universes?: Record<string, {
|
|
183
|
+
defaults: Record<string, unknown>;
|
|
184
|
+
}>;
|
|
177
185
|
/**
|
|
178
186
|
* Killswitch state, flattened by the server. A boolean means the killswitch
|
|
179
187
|
* is whole-killed; an object means it's not whole-killed and carries per-
|
|
@@ -187,6 +195,23 @@ interface EvalResponse {
|
|
|
187
195
|
*/
|
|
188
196
|
sticky?: Record<string, StickyCookieEntry>;
|
|
189
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* The result of `universe(name).assign()` — the visitor's standing in a universe.
|
|
200
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in **at most one**
|
|
201
|
+
* experiment. Never throws: an un-enrolled visitor still resolves `get()` to the
|
|
202
|
+
* universe defaults (or your fallback). `assign()` auto-logs one exposure when
|
|
203
|
+
* enrolled (subject to `disableAutoExposure`).
|
|
204
|
+
*/
|
|
205
|
+
interface Assignment {
|
|
206
|
+
/** The experiment the visitor landed in, or `null` when not enrolled. */
|
|
207
|
+
readonly name: string | null;
|
|
208
|
+
/** The assigned variant/group name, or `null` when not enrolled. */
|
|
209
|
+
readonly group: string | null;
|
|
210
|
+
/** True iff the visitor is enrolled in an experiment in this universe. */
|
|
211
|
+
readonly enrolled: boolean;
|
|
212
|
+
/** Read a resolved param: variant override ?? universe default ?? `fallback`. */
|
|
213
|
+
get<T = unknown>(field: string, fallback?: T): T | undefined;
|
|
214
|
+
}
|
|
190
215
|
/** One persisted sticky assignment: group + 8-char salt prefix (reshuffle key). */
|
|
191
216
|
interface StickyCookieEntry {
|
|
192
217
|
g: string;
|
|
@@ -207,6 +232,33 @@ declare function sameOrigin(rawUrl: string): boolean;
|
|
|
207
232
|
*/
|
|
208
233
|
declare function injectCorrelationHeader(args: Parameters<typeof fetch>, corr: string): Parameters<typeof fetch>;
|
|
209
234
|
type EngineEnv = "dev" | "staging" | "prod";
|
|
235
|
+
/**
|
|
236
|
+
* A pluggable persistence backend for the anonymous id. Primarily for non-DOM
|
|
237
|
+
* runtimes (React Native / Expo) where there is no cookie or `localStorage`, so
|
|
238
|
+
* the anon id would otherwise regenerate every app launch and re-bucket the
|
|
239
|
+
* visitor. Point it at `@react-native-async-storage/async-storage` (or any
|
|
240
|
+
* store) and the SDK hydrates a stable id before the first `/sdk/evaluate`:
|
|
241
|
+
*
|
|
242
|
+
* ```ts
|
|
243
|
+
* import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
244
|
+
* configure({
|
|
245
|
+
* clientKey: "...",
|
|
246
|
+
* anonymousStore: {
|
|
247
|
+
* get: (k) => AsyncStorage.getItem(k),
|
|
248
|
+
* set: (k, v) => AsyncStorage.setItem(k, v),
|
|
249
|
+
* remove: (k) => AsyncStorage.removeItem(k),
|
|
250
|
+
* },
|
|
251
|
+
* });
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* Methods may be sync or async — the SDK awaits either. Any thrown error is
|
|
255
|
+
* swallowed (the in-memory id is used as a fallback).
|
|
256
|
+
*/
|
|
257
|
+
interface AnonymousStore {
|
|
258
|
+
get(key: string): string | null | Promise<string | null>;
|
|
259
|
+
set(key: string, value: string): void | Promise<void>;
|
|
260
|
+
remove(key: string): void | Promise<void>;
|
|
261
|
+
}
|
|
210
262
|
interface EngineOptions {
|
|
211
263
|
sdkKey: string;
|
|
212
264
|
baseUrl?: string;
|
|
@@ -228,10 +280,26 @@ interface EngineOptions {
|
|
|
228
280
|
/** Which published env to read values from. Defaults to "prod". */
|
|
229
281
|
env?: EngineEnv;
|
|
230
282
|
/**
|
|
231
|
-
*
|
|
283
|
+
* Master network switch — when `false`, the browser SDK makes NO outbound
|
|
284
|
+
* requests at all: identify()/init never call /sdk/evaluate (getters read code
|
|
285
|
+
* defaults / overrides), track() and exposure logging are no-ops, usage
|
|
286
|
+
* telemetry is off, and SDK-internal error self-monitoring is off.
|
|
287
|
+
*
|
|
288
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
289
|
+
* production, OFF everywhere else. In the browser there is usually no native
|
|
290
|
+
* `NODE_ENV`, so production is taken from the `env` option above (default
|
|
291
|
+
* `"prod"`) — meaning it defaults ON. Pass `false` (or set `env: "dev"`) to
|
|
292
|
+
* keep a local build quiet.
|
|
293
|
+
*/
|
|
294
|
+
isNetworkEnabled?: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Per-evaluation usage telemetry ("tracking") — each getFlag/getConfig/
|
|
232
297
|
* getExperiment/getKillswitch call fires one fire-and-forget sendBeacon so
|
|
233
|
-
* usage is counted by Cloudflare's native per-path analytics.
|
|
234
|
-
*
|
|
298
|
+
* usage is counted by Cloudflare's native per-path analytics.
|
|
299
|
+
*
|
|
300
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
301
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
302
|
+
* to force it on. Forced off whenever `isNetworkEnabled` is false.
|
|
235
303
|
*/
|
|
236
304
|
disableTelemetry?: boolean;
|
|
237
305
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -244,6 +312,14 @@ interface EngineOptions {
|
|
|
244
312
|
* `silent` < `error` < `warn` < `info` < `debug`. Defaults to `"warn"`.
|
|
245
313
|
*/
|
|
246
314
|
logLevel?: LogLevel;
|
|
315
|
+
/**
|
|
316
|
+
* Opt out of SDK-internal error self-monitoring. When one of the SDK's own
|
|
317
|
+
* last-resort guards catches an internal failure (an "on our end" bug), the
|
|
318
|
+
* SDK reports it to Shipeasy's own project so we can track SDK bugs across
|
|
319
|
+
* apps — never to your project. ON by default; forced off in test mode. Pass
|
|
320
|
+
* `true` to disable.
|
|
321
|
+
*/
|
|
322
|
+
disableInternalErrorReporting?: boolean;
|
|
247
323
|
/**
|
|
248
324
|
* Suppress automatic exposure logging in `getExperiment` (Statsig's
|
|
249
325
|
* `disableExposureLogging`). Default false — reading an enrolled variant
|
|
@@ -267,6 +343,14 @@ interface EngineOptions {
|
|
|
267
343
|
* lever. Pass `false` to opt out (pure deterministic eval).
|
|
268
344
|
*/
|
|
269
345
|
stickyBucketing?: boolean;
|
|
346
|
+
/**
|
|
347
|
+
* Pluggable persistence for the anonymous id (React Native / Expo, or any
|
|
348
|
+
* runtime without a cookie / `localStorage`). When set, the SDK hydrates the
|
|
349
|
+
* stored id before the first `/sdk/evaluate` so bucketing stays stable across
|
|
350
|
+
* app launches; on a fresh device it persists the freshly-minted id. See
|
|
351
|
+
* {@link AnonymousStore}.
|
|
352
|
+
*/
|
|
353
|
+
anonymousStore?: AnonymousStore;
|
|
270
354
|
/**
|
|
271
355
|
* Test mode — no network at all. identify()/init are no-ops (never call
|
|
272
356
|
* /sdk/evaluate), track() is a no-op, telemetry is forced off, and the client
|
|
@@ -287,6 +371,7 @@ declare class Engine {
|
|
|
287
371
|
private readonly env;
|
|
288
372
|
private evalResult;
|
|
289
373
|
private anonId;
|
|
374
|
+
private anonReady;
|
|
290
375
|
private userId;
|
|
291
376
|
private buffer;
|
|
292
377
|
private telemetry;
|
|
@@ -296,11 +381,20 @@ declare class Engine {
|
|
|
296
381
|
private overrideListenerInstalled;
|
|
297
382
|
private identifySeq;
|
|
298
383
|
private readonly testMode;
|
|
384
|
+
private readonly networkEnabled;
|
|
385
|
+
private readonly offline;
|
|
299
386
|
private readonly flagOverrides;
|
|
300
387
|
private readonly configOverrides;
|
|
301
388
|
private readonly experimentOverrides;
|
|
302
389
|
private onOverrideChange;
|
|
303
390
|
constructor(opts: EngineOptions);
|
|
391
|
+
/**
|
|
392
|
+
* Consult the caller-supplied {@link AnonymousStore}: adopt a persisted id so
|
|
393
|
+
* the visitor buckets identically across app launches, or persist the id just
|
|
394
|
+
* minted by getOrCreateAnonId() on a fresh device. Best-effort — any store
|
|
395
|
+
* failure leaves the in-memory id in place.
|
|
396
|
+
*/
|
|
397
|
+
private hydrateAnonFromStore;
|
|
304
398
|
/**
|
|
305
399
|
* Build a no-network, immediately-usable browser client for tests
|
|
306
400
|
* (Statsig-style). identify() is a no-op (never calls /sdk/evaluate), track()
|
|
@@ -354,16 +448,21 @@ declare class Engine {
|
|
|
354
448
|
getFlag(name: string, defaultValue?: boolean): boolean;
|
|
355
449
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
356
450
|
getConfig<T = unknown>(name: string, opts: GetConfigOptions<T>): T;
|
|
357
|
-
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
|
|
358
|
-
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, opts: GetExperimentOptions<P>): ExperimentResult<P>;
|
|
359
451
|
/**
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
* the
|
|
363
|
-
*
|
|
364
|
-
* `
|
|
365
|
-
|
|
366
|
-
|
|
452
|
+
* Assign the visitor within a universe: `universe("checkout").assign()`. A
|
|
453
|
+
* universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
454
|
+
* the returned {@link Assignment} exposes `.group` / `.get(field, fallback)`
|
|
455
|
+
* and auto-logs one exposure when enrolled (unless `disableAutoExposure` or
|
|
456
|
+
* `assign({ logExposure: false })`). An un-enrolled visitor still resolves
|
|
457
|
+
* `get()` to the universe defaults. This is the sole experiment read path —
|
|
458
|
+
* there is no `getExperiment` (ask a universe, not an experiment).
|
|
459
|
+
*/
|
|
460
|
+
universe(name: string): {
|
|
461
|
+
assign(opts?: {
|
|
462
|
+
logExposure?: boolean;
|
|
463
|
+
}): Assignment;
|
|
464
|
+
};
|
|
465
|
+
private assignUniverse;
|
|
367
466
|
/**
|
|
368
467
|
* Subscribe to state changes — fires after identify() completes and on
|
|
369
468
|
* `se:override:change` events from the devtools overlay. Returns an
|
|
@@ -436,6 +535,12 @@ interface ShipeasyClientConfig {
|
|
|
436
535
|
clientKey: string;
|
|
437
536
|
/** Override the ShipEasy CDN/edge base URL. Defaults to https://cdn.shipeasy.ai. */
|
|
438
537
|
baseUrl?: string;
|
|
538
|
+
/**
|
|
539
|
+
* Which published env to read values from — also the fallback signal for the
|
|
540
|
+
* environment-derived egress defaults (see {@link isNetworkEnabled}) when the
|
|
541
|
+
* browser exposes no native `NODE_ENV`. Defaults to `"prod"`.
|
|
542
|
+
*/
|
|
543
|
+
env?: EngineEnv;
|
|
439
544
|
/** Override the admin URL for the devtools overlay (dev use). */
|
|
440
545
|
adminUrl?: string;
|
|
441
546
|
/**
|
|
@@ -488,11 +593,27 @@ interface ShipeasyClientConfig {
|
|
|
488
593
|
*/
|
|
489
594
|
logLevel?: LogLevel;
|
|
490
595
|
/**
|
|
491
|
-
*
|
|
596
|
+
* Master network switch — `false` puts the browser SDK fully offline (no
|
|
597
|
+
* /sdk/evaluate, no /collect, no telemetry, no error self-monitoring).
|
|
598
|
+
* Defaults to environment-derived: ON in production, OFF everywhere else
|
|
599
|
+
* (in the browser, "production" comes from `env`, default `"prod"`). See
|
|
600
|
+
* {@link EngineOptions.isNetworkEnabled}.
|
|
601
|
+
*/
|
|
602
|
+
isNetworkEnabled?: boolean;
|
|
603
|
+
/**
|
|
604
|
+
* Disable per-evaluation usage telemetry ("tracking"). Defaults to
|
|
605
|
+
* environment-derived: ON in production, OFF everywhere else. Every
|
|
492
606
|
* flag/config/experiment/killswitch read fires one fire-and-forget beacon
|
|
493
|
-
* counted by Cloudflare's native per-path analytics. Pass `true` to
|
|
607
|
+
* counted by Cloudflare's native per-path analytics. Pass `true` to force off.
|
|
494
608
|
*/
|
|
495
609
|
disableTelemetry?: boolean;
|
|
610
|
+
/**
|
|
611
|
+
* Opt out of SDK-internal error self-monitoring. Internal SDK failures ("on
|
|
612
|
+
* our end") are reported to Shipeasy's own project (never yours) so we can
|
|
613
|
+
* track SDK bugs. ON by default. See
|
|
614
|
+
* {@link EngineOptions.disableInternalErrorReporting}.
|
|
615
|
+
*/
|
|
616
|
+
disableInternalErrorReporting?: boolean;
|
|
496
617
|
/**
|
|
497
618
|
* Suppress automatic exposure logging in `flags.getExperiment` (Statsig's
|
|
498
619
|
* `disableExposureLogging`). Default false. When true, call
|
|
@@ -512,6 +633,12 @@ interface ShipeasyClientConfig {
|
|
|
512
633
|
* opt out. See {@link EngineOptions.stickyBucketing}.
|
|
513
634
|
*/
|
|
514
635
|
stickyBucketing?: boolean;
|
|
636
|
+
/**
|
|
637
|
+
* Pluggable anon-id persistence for non-DOM runtimes (React Native / Expo).
|
|
638
|
+
* Back it with AsyncStorage so the anonymous id — and thus bucketing — stays
|
|
639
|
+
* stable across app launches. See {@link AnonymousStore}.
|
|
640
|
+
*/
|
|
641
|
+
anonymousStore?: AnonymousStore;
|
|
515
642
|
}
|
|
516
643
|
/**
|
|
517
644
|
* Initialise the ShipEasy client SDK and wire up lazy devtools.
|
|
@@ -571,10 +698,19 @@ declare const flags: {
|
|
|
571
698
|
/** Evaluate a gate and report why (value + reason). See {@link FlagDetail}. */
|
|
572
699
|
getDetail(name: string): FlagDetail;
|
|
573
700
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
*
|
|
577
|
-
|
|
701
|
+
/**
|
|
702
|
+
* Assign the visitor within a universe: `flags.universe("checkout").assign()`.
|
|
703
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
704
|
+
* the returned {@link Assignment} exposes `.group` / `.get(field, fallback)`
|
|
705
|
+
* and auto-logs one exposure when enrolled. Before configure() (or on error)
|
|
706
|
+
* returns a safe not-enrolled handle. Replaces the removed `getExperiment` —
|
|
707
|
+
* read experiments by universe, never by name.
|
|
708
|
+
*/
|
|
709
|
+
universe(name: string): {
|
|
710
|
+
assign(opts?: {
|
|
711
|
+
logExposure?: boolean;
|
|
712
|
+
}): Assignment;
|
|
713
|
+
};
|
|
578
714
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
579
715
|
/**
|
|
580
716
|
* Read a killswitch. Without `switchKey`, returns true when the killswitch is
|
|
@@ -670,7 +806,17 @@ declare class Client<U = unknown> {
|
|
|
670
806
|
getFlagDetail(name: string): FlagDetail;
|
|
671
807
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
672
808
|
getConfig<T = unknown>(name: string, opts: GetConfigOptions<T>): T;
|
|
673
|
-
|
|
809
|
+
/**
|
|
810
|
+
* Assign the visitor within a universe: `client.universe("checkout").assign()`.
|
|
811
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
812
|
+
* returns an {@link Assignment} (`.group` / `.get(field, fallback)`) and
|
|
813
|
+
* auto-logs one exposure when enrolled. Replaces the removed `getExperiment`.
|
|
814
|
+
*/
|
|
815
|
+
universe(name: string): {
|
|
816
|
+
assign(opts?: {
|
|
817
|
+
logExposure?: boolean;
|
|
818
|
+
}): Assignment;
|
|
819
|
+
};
|
|
674
820
|
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
675
821
|
getKillswitch(name: string, switchKey?: string): boolean;
|
|
676
822
|
/**
|
|
@@ -680,13 +826,6 @@ declare class Client<U = unknown> {
|
|
|
680
826
|
* test mode.
|
|
681
827
|
*/
|
|
682
828
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
683
|
-
/**
|
|
684
|
-
* Log an exposure for `name` at the treatment's render for the bound user.
|
|
685
|
-
* Delegates to {@link Engine.logExposure} (no-op when the visitor isn't
|
|
686
|
-
* enrolled). Pair with `getExperiment(name, …, { logExposure: false })` /
|
|
687
|
-
* `disableAutoExposure` to log exposure exactly when you render.
|
|
688
|
-
*/
|
|
689
|
-
logExposure(name: string): void;
|
|
690
829
|
}
|
|
691
830
|
interface SeeApi {
|
|
692
831
|
/**
|
|
@@ -779,4 +918,4 @@ interface I18nFacade {
|
|
|
779
918
|
}
|
|
780
919
|
declare const i18n: I18nFacade;
|
|
781
920
|
|
|
782
|
-
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, LOG_LEVELS, type LabelAttrs, type LogLevel, 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 };
|
|
921
|
+
export { type AnonymousStore, type Assignment, 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, LOG_LEVELS, type LabelAttrs, type LogLevel, 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
|
@@ -169,11 +169,19 @@ interface EvalExpResult {
|
|
|
169
169
|
inExperiment: boolean;
|
|
170
170
|
group: string;
|
|
171
171
|
params: Record<string, unknown>;
|
|
172
|
+
/** The universe this experiment belongs to — lets `universe(name).assign()`
|
|
173
|
+
* find the enrolled experiment within a universe. */
|
|
174
|
+
universe?: string;
|
|
172
175
|
}
|
|
173
176
|
interface EvalResponse {
|
|
174
177
|
flags: Record<string, boolean>;
|
|
175
178
|
configs: Record<string, unknown>;
|
|
176
179
|
experiments: Record<string, EvalExpResult>;
|
|
180
|
+
/** Per-universe param defaults (name → default map) so `universe(name).get()`
|
|
181
|
+
* resolves to the universe default even when the unit is not enrolled. */
|
|
182
|
+
universes?: Record<string, {
|
|
183
|
+
defaults: Record<string, unknown>;
|
|
184
|
+
}>;
|
|
177
185
|
/**
|
|
178
186
|
* Killswitch state, flattened by the server. A boolean means the killswitch
|
|
179
187
|
* is whole-killed; an object means it's not whole-killed and carries per-
|
|
@@ -187,6 +195,23 @@ interface EvalResponse {
|
|
|
187
195
|
*/
|
|
188
196
|
sticky?: Record<string, StickyCookieEntry>;
|
|
189
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* The result of `universe(name).assign()` — the visitor's standing in a universe.
|
|
200
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in **at most one**
|
|
201
|
+
* experiment. Never throws: an un-enrolled visitor still resolves `get()` to the
|
|
202
|
+
* universe defaults (or your fallback). `assign()` auto-logs one exposure when
|
|
203
|
+
* enrolled (subject to `disableAutoExposure`).
|
|
204
|
+
*/
|
|
205
|
+
interface Assignment {
|
|
206
|
+
/** The experiment the visitor landed in, or `null` when not enrolled. */
|
|
207
|
+
readonly name: string | null;
|
|
208
|
+
/** The assigned variant/group name, or `null` when not enrolled. */
|
|
209
|
+
readonly group: string | null;
|
|
210
|
+
/** True iff the visitor is enrolled in an experiment in this universe. */
|
|
211
|
+
readonly enrolled: boolean;
|
|
212
|
+
/** Read a resolved param: variant override ?? universe default ?? `fallback`. */
|
|
213
|
+
get<T = unknown>(field: string, fallback?: T): T | undefined;
|
|
214
|
+
}
|
|
190
215
|
/** One persisted sticky assignment: group + 8-char salt prefix (reshuffle key). */
|
|
191
216
|
interface StickyCookieEntry {
|
|
192
217
|
g: string;
|
|
@@ -207,6 +232,33 @@ declare function sameOrigin(rawUrl: string): boolean;
|
|
|
207
232
|
*/
|
|
208
233
|
declare function injectCorrelationHeader(args: Parameters<typeof fetch>, corr: string): Parameters<typeof fetch>;
|
|
209
234
|
type EngineEnv = "dev" | "staging" | "prod";
|
|
235
|
+
/**
|
|
236
|
+
* A pluggable persistence backend for the anonymous id. Primarily for non-DOM
|
|
237
|
+
* runtimes (React Native / Expo) where there is no cookie or `localStorage`, so
|
|
238
|
+
* the anon id would otherwise regenerate every app launch and re-bucket the
|
|
239
|
+
* visitor. Point it at `@react-native-async-storage/async-storage` (or any
|
|
240
|
+
* store) and the SDK hydrates a stable id before the first `/sdk/evaluate`:
|
|
241
|
+
*
|
|
242
|
+
* ```ts
|
|
243
|
+
* import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
244
|
+
* configure({
|
|
245
|
+
* clientKey: "...",
|
|
246
|
+
* anonymousStore: {
|
|
247
|
+
* get: (k) => AsyncStorage.getItem(k),
|
|
248
|
+
* set: (k, v) => AsyncStorage.setItem(k, v),
|
|
249
|
+
* remove: (k) => AsyncStorage.removeItem(k),
|
|
250
|
+
* },
|
|
251
|
+
* });
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* Methods may be sync or async — the SDK awaits either. Any thrown error is
|
|
255
|
+
* swallowed (the in-memory id is used as a fallback).
|
|
256
|
+
*/
|
|
257
|
+
interface AnonymousStore {
|
|
258
|
+
get(key: string): string | null | Promise<string | null>;
|
|
259
|
+
set(key: string, value: string): void | Promise<void>;
|
|
260
|
+
remove(key: string): void | Promise<void>;
|
|
261
|
+
}
|
|
210
262
|
interface EngineOptions {
|
|
211
263
|
sdkKey: string;
|
|
212
264
|
baseUrl?: string;
|
|
@@ -228,10 +280,26 @@ interface EngineOptions {
|
|
|
228
280
|
/** Which published env to read values from. Defaults to "prod". */
|
|
229
281
|
env?: EngineEnv;
|
|
230
282
|
/**
|
|
231
|
-
*
|
|
283
|
+
* Master network switch — when `false`, the browser SDK makes NO outbound
|
|
284
|
+
* requests at all: identify()/init never call /sdk/evaluate (getters read code
|
|
285
|
+
* defaults / overrides), track() and exposure logging are no-ops, usage
|
|
286
|
+
* telemetry is off, and SDK-internal error self-monitoring is off.
|
|
287
|
+
*
|
|
288
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
289
|
+
* production, OFF everywhere else. In the browser there is usually no native
|
|
290
|
+
* `NODE_ENV`, so production is taken from the `env` option above (default
|
|
291
|
+
* `"prod"`) — meaning it defaults ON. Pass `false` (or set `env: "dev"`) to
|
|
292
|
+
* keep a local build quiet.
|
|
293
|
+
*/
|
|
294
|
+
isNetworkEnabled?: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Per-evaluation usage telemetry ("tracking") — each getFlag/getConfig/
|
|
232
297
|
* getExperiment/getKillswitch call fires one fire-and-forget sendBeacon so
|
|
233
|
-
* usage is counted by Cloudflare's native per-path analytics.
|
|
234
|
-
*
|
|
298
|
+
* usage is counted by Cloudflare's native per-path analytics.
|
|
299
|
+
*
|
|
300
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
301
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
302
|
+
* to force it on. Forced off whenever `isNetworkEnabled` is false.
|
|
235
303
|
*/
|
|
236
304
|
disableTelemetry?: boolean;
|
|
237
305
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -244,6 +312,14 @@ interface EngineOptions {
|
|
|
244
312
|
* `silent` < `error` < `warn` < `info` < `debug`. Defaults to `"warn"`.
|
|
245
313
|
*/
|
|
246
314
|
logLevel?: LogLevel;
|
|
315
|
+
/**
|
|
316
|
+
* Opt out of SDK-internal error self-monitoring. When one of the SDK's own
|
|
317
|
+
* last-resort guards catches an internal failure (an "on our end" bug), the
|
|
318
|
+
* SDK reports it to Shipeasy's own project so we can track SDK bugs across
|
|
319
|
+
* apps — never to your project. ON by default; forced off in test mode. Pass
|
|
320
|
+
* `true` to disable.
|
|
321
|
+
*/
|
|
322
|
+
disableInternalErrorReporting?: boolean;
|
|
247
323
|
/**
|
|
248
324
|
* Suppress automatic exposure logging in `getExperiment` (Statsig's
|
|
249
325
|
* `disableExposureLogging`). Default false — reading an enrolled variant
|
|
@@ -267,6 +343,14 @@ interface EngineOptions {
|
|
|
267
343
|
* lever. Pass `false` to opt out (pure deterministic eval).
|
|
268
344
|
*/
|
|
269
345
|
stickyBucketing?: boolean;
|
|
346
|
+
/**
|
|
347
|
+
* Pluggable persistence for the anonymous id (React Native / Expo, or any
|
|
348
|
+
* runtime without a cookie / `localStorage`). When set, the SDK hydrates the
|
|
349
|
+
* stored id before the first `/sdk/evaluate` so bucketing stays stable across
|
|
350
|
+
* app launches; on a fresh device it persists the freshly-minted id. See
|
|
351
|
+
* {@link AnonymousStore}.
|
|
352
|
+
*/
|
|
353
|
+
anonymousStore?: AnonymousStore;
|
|
270
354
|
/**
|
|
271
355
|
* Test mode — no network at all. identify()/init are no-ops (never call
|
|
272
356
|
* /sdk/evaluate), track() is a no-op, telemetry is forced off, and the client
|
|
@@ -287,6 +371,7 @@ declare class Engine {
|
|
|
287
371
|
private readonly env;
|
|
288
372
|
private evalResult;
|
|
289
373
|
private anonId;
|
|
374
|
+
private anonReady;
|
|
290
375
|
private userId;
|
|
291
376
|
private buffer;
|
|
292
377
|
private telemetry;
|
|
@@ -296,11 +381,20 @@ declare class Engine {
|
|
|
296
381
|
private overrideListenerInstalled;
|
|
297
382
|
private identifySeq;
|
|
298
383
|
private readonly testMode;
|
|
384
|
+
private readonly networkEnabled;
|
|
385
|
+
private readonly offline;
|
|
299
386
|
private readonly flagOverrides;
|
|
300
387
|
private readonly configOverrides;
|
|
301
388
|
private readonly experimentOverrides;
|
|
302
389
|
private onOverrideChange;
|
|
303
390
|
constructor(opts: EngineOptions);
|
|
391
|
+
/**
|
|
392
|
+
* Consult the caller-supplied {@link AnonymousStore}: adopt a persisted id so
|
|
393
|
+
* the visitor buckets identically across app launches, or persist the id just
|
|
394
|
+
* minted by getOrCreateAnonId() on a fresh device. Best-effort — any store
|
|
395
|
+
* failure leaves the in-memory id in place.
|
|
396
|
+
*/
|
|
397
|
+
private hydrateAnonFromStore;
|
|
304
398
|
/**
|
|
305
399
|
* Build a no-network, immediately-usable browser client for tests
|
|
306
400
|
* (Statsig-style). identify() is a no-op (never calls /sdk/evaluate), track()
|
|
@@ -354,16 +448,21 @@ declare class Engine {
|
|
|
354
448
|
getFlag(name: string, defaultValue?: boolean): boolean;
|
|
355
449
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
356
450
|
getConfig<T = unknown>(name: string, opts: GetConfigOptions<T>): T;
|
|
357
|
-
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
|
|
358
|
-
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, opts: GetExperimentOptions<P>): ExperimentResult<P>;
|
|
359
451
|
/**
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
* the
|
|
363
|
-
*
|
|
364
|
-
* `
|
|
365
|
-
|
|
366
|
-
|
|
452
|
+
* Assign the visitor within a universe: `universe("checkout").assign()`. A
|
|
453
|
+
* universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
454
|
+
* the returned {@link Assignment} exposes `.group` / `.get(field, fallback)`
|
|
455
|
+
* and auto-logs one exposure when enrolled (unless `disableAutoExposure` or
|
|
456
|
+
* `assign({ logExposure: false })`). An un-enrolled visitor still resolves
|
|
457
|
+
* `get()` to the universe defaults. This is the sole experiment read path —
|
|
458
|
+
* there is no `getExperiment` (ask a universe, not an experiment).
|
|
459
|
+
*/
|
|
460
|
+
universe(name: string): {
|
|
461
|
+
assign(opts?: {
|
|
462
|
+
logExposure?: boolean;
|
|
463
|
+
}): Assignment;
|
|
464
|
+
};
|
|
465
|
+
private assignUniverse;
|
|
367
466
|
/**
|
|
368
467
|
* Subscribe to state changes — fires after identify() completes and on
|
|
369
468
|
* `se:override:change` events from the devtools overlay. Returns an
|
|
@@ -436,6 +535,12 @@ interface ShipeasyClientConfig {
|
|
|
436
535
|
clientKey: string;
|
|
437
536
|
/** Override the ShipEasy CDN/edge base URL. Defaults to https://cdn.shipeasy.ai. */
|
|
438
537
|
baseUrl?: string;
|
|
538
|
+
/**
|
|
539
|
+
* Which published env to read values from — also the fallback signal for the
|
|
540
|
+
* environment-derived egress defaults (see {@link isNetworkEnabled}) when the
|
|
541
|
+
* browser exposes no native `NODE_ENV`. Defaults to `"prod"`.
|
|
542
|
+
*/
|
|
543
|
+
env?: EngineEnv;
|
|
439
544
|
/** Override the admin URL for the devtools overlay (dev use). */
|
|
440
545
|
adminUrl?: string;
|
|
441
546
|
/**
|
|
@@ -488,11 +593,27 @@ interface ShipeasyClientConfig {
|
|
|
488
593
|
*/
|
|
489
594
|
logLevel?: LogLevel;
|
|
490
595
|
/**
|
|
491
|
-
*
|
|
596
|
+
* Master network switch — `false` puts the browser SDK fully offline (no
|
|
597
|
+
* /sdk/evaluate, no /collect, no telemetry, no error self-monitoring).
|
|
598
|
+
* Defaults to environment-derived: ON in production, OFF everywhere else
|
|
599
|
+
* (in the browser, "production" comes from `env`, default `"prod"`). See
|
|
600
|
+
* {@link EngineOptions.isNetworkEnabled}.
|
|
601
|
+
*/
|
|
602
|
+
isNetworkEnabled?: boolean;
|
|
603
|
+
/**
|
|
604
|
+
* Disable per-evaluation usage telemetry ("tracking"). Defaults to
|
|
605
|
+
* environment-derived: ON in production, OFF everywhere else. Every
|
|
492
606
|
* flag/config/experiment/killswitch read fires one fire-and-forget beacon
|
|
493
|
-
* counted by Cloudflare's native per-path analytics. Pass `true` to
|
|
607
|
+
* counted by Cloudflare's native per-path analytics. Pass `true` to force off.
|
|
494
608
|
*/
|
|
495
609
|
disableTelemetry?: boolean;
|
|
610
|
+
/**
|
|
611
|
+
* Opt out of SDK-internal error self-monitoring. Internal SDK failures ("on
|
|
612
|
+
* our end") are reported to Shipeasy's own project (never yours) so we can
|
|
613
|
+
* track SDK bugs. ON by default. See
|
|
614
|
+
* {@link EngineOptions.disableInternalErrorReporting}.
|
|
615
|
+
*/
|
|
616
|
+
disableInternalErrorReporting?: boolean;
|
|
496
617
|
/**
|
|
497
618
|
* Suppress automatic exposure logging in `flags.getExperiment` (Statsig's
|
|
498
619
|
* `disableExposureLogging`). Default false. When true, call
|
|
@@ -512,6 +633,12 @@ interface ShipeasyClientConfig {
|
|
|
512
633
|
* opt out. See {@link EngineOptions.stickyBucketing}.
|
|
513
634
|
*/
|
|
514
635
|
stickyBucketing?: boolean;
|
|
636
|
+
/**
|
|
637
|
+
* Pluggable anon-id persistence for non-DOM runtimes (React Native / Expo).
|
|
638
|
+
* Back it with AsyncStorage so the anonymous id — and thus bucketing — stays
|
|
639
|
+
* stable across app launches. See {@link AnonymousStore}.
|
|
640
|
+
*/
|
|
641
|
+
anonymousStore?: AnonymousStore;
|
|
515
642
|
}
|
|
516
643
|
/**
|
|
517
644
|
* Initialise the ShipEasy client SDK and wire up lazy devtools.
|
|
@@ -571,10 +698,19 @@ declare const flags: {
|
|
|
571
698
|
/** Evaluate a gate and report why (value + reason). See {@link FlagDetail}. */
|
|
572
699
|
getDetail(name: string): FlagDetail;
|
|
573
700
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
*
|
|
577
|
-
|
|
701
|
+
/**
|
|
702
|
+
* Assign the visitor within a universe: `flags.universe("checkout").assign()`.
|
|
703
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
704
|
+
* the returned {@link Assignment} exposes `.group` / `.get(field, fallback)`
|
|
705
|
+
* and auto-logs one exposure when enrolled. Before configure() (or on error)
|
|
706
|
+
* returns a safe not-enrolled handle. Replaces the removed `getExperiment` —
|
|
707
|
+
* read experiments by universe, never by name.
|
|
708
|
+
*/
|
|
709
|
+
universe(name: string): {
|
|
710
|
+
assign(opts?: {
|
|
711
|
+
logExposure?: boolean;
|
|
712
|
+
}): Assignment;
|
|
713
|
+
};
|
|
578
714
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
579
715
|
/**
|
|
580
716
|
* Read a killswitch. Without `switchKey`, returns true when the killswitch is
|
|
@@ -670,7 +806,17 @@ declare class Client<U = unknown> {
|
|
|
670
806
|
getFlagDetail(name: string): FlagDetail;
|
|
671
807
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
672
808
|
getConfig<T = unknown>(name: string, opts: GetConfigOptions<T>): T;
|
|
673
|
-
|
|
809
|
+
/**
|
|
810
|
+
* Assign the visitor within a universe: `client.universe("checkout").assign()`.
|
|
811
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
812
|
+
* returns an {@link Assignment} (`.group` / `.get(field, fallback)`) and
|
|
813
|
+
* auto-logs one exposure when enrolled. Replaces the removed `getExperiment`.
|
|
814
|
+
*/
|
|
815
|
+
universe(name: string): {
|
|
816
|
+
assign(opts?: {
|
|
817
|
+
logExposure?: boolean;
|
|
818
|
+
}): Assignment;
|
|
819
|
+
};
|
|
674
820
|
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
675
821
|
getKillswitch(name: string, switchKey?: string): boolean;
|
|
676
822
|
/**
|
|
@@ -680,13 +826,6 @@ declare class Client<U = unknown> {
|
|
|
680
826
|
* test mode.
|
|
681
827
|
*/
|
|
682
828
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
683
|
-
/**
|
|
684
|
-
* Log an exposure for `name` at the treatment's render for the bound user.
|
|
685
|
-
* Delegates to {@link Engine.logExposure} (no-op when the visitor isn't
|
|
686
|
-
* enrolled). Pair with `getExperiment(name, …, { logExposure: false })` /
|
|
687
|
-
* `disableAutoExposure` to log exposure exactly when you render.
|
|
688
|
-
*/
|
|
689
|
-
logExposure(name: string): void;
|
|
690
829
|
}
|
|
691
830
|
interface SeeApi {
|
|
692
831
|
/**
|
|
@@ -779,4 +918,4 @@ interface I18nFacade {
|
|
|
779
918
|
}
|
|
780
919
|
declare const i18n: I18nFacade;
|
|
781
920
|
|
|
782
|
-
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, LOG_LEVELS, type LabelAttrs, type LogLevel, 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 };
|
|
921
|
+
export { type AnonymousStore, type Assignment, 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, LOG_LEVELS, type LabelAttrs, type LogLevel, 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 };
|