@shipeasy/sdk 2.0.1 → 2.0.2

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.
@@ -118,6 +118,15 @@ declare function getShipeasyClient(): FlagsClientBrowser | null;
118
118
  * Not part of the documented surface; production code should never call this.
119
119
  */
120
120
  declare function _resetShipeasyForTests(): void;
121
+ interface BootstrapPayload {
122
+ flags: Record<string, boolean>;
123
+ configs: Record<string, unknown>;
124
+ experiments: Record<string, {
125
+ inExperiment: boolean;
126
+ group: string;
127
+ params: Record<string, unknown>;
128
+ }>;
129
+ }
121
130
  /**
122
131
  * Universal flags facade. Methods return safe defaults when the singleton
123
132
  * hasn't been configured yet (false / undefined / `notIn` experiment), so
@@ -127,8 +136,11 @@ declare const flags: {
127
136
  configure(opts: FlagsClientBrowserOptions): void;
128
137
  identify(user: User): Promise<void>;
129
138
  /**
130
- * Read a feature gate. Returns false until FlagsBoundary mounts (SSR-safe).
131
- * After mount, URL overrides (?se_ks_*) apply even without a configured client.
139
+ * Read a feature gate.
140
+ * Priority: URL override → server bootstrap (window.__SE_BOOTSTRAP) CDN-fetched (post-mount) false.
141
+ * The _mountedAndReady gate still applies for the CDN path to prevent hydration
142
+ * mismatches on force-static pages; bootstrap data is safe to read immediately
143
+ * because the server rendered with the same values.
132
144
  */
133
145
  get(name: string): boolean;
134
146
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
@@ -190,4 +202,4 @@ declare const i18n: {
190
202
  onUpdate(cb: () => void): () => void;
191
203
  };
192
204
 
193
- export { type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, version };
205
+ export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, version };
@@ -118,6 +118,15 @@ declare function getShipeasyClient(): FlagsClientBrowser | null;
118
118
  * Not part of the documented surface; production code should never call this.
119
119
  */
120
120
  declare function _resetShipeasyForTests(): void;
121
+ interface BootstrapPayload {
122
+ flags: Record<string, boolean>;
123
+ configs: Record<string, unknown>;
124
+ experiments: Record<string, {
125
+ inExperiment: boolean;
126
+ group: string;
127
+ params: Record<string, unknown>;
128
+ }>;
129
+ }
121
130
  /**
122
131
  * Universal flags facade. Methods return safe defaults when the singleton
123
132
  * hasn't been configured yet (false / undefined / `notIn` experiment), so
@@ -127,8 +136,11 @@ declare const flags: {
127
136
  configure(opts: FlagsClientBrowserOptions): void;
128
137
  identify(user: User): Promise<void>;
129
138
  /**
130
- * Read a feature gate. Returns false until FlagsBoundary mounts (SSR-safe).
131
- * After mount, URL overrides (?se_ks_*) apply even without a configured client.
139
+ * Read a feature gate.
140
+ * Priority: URL override → server bootstrap (window.__SE_BOOTSTRAP) CDN-fetched (post-mount) false.
141
+ * The _mountedAndReady gate still applies for the CDN path to prevent hydration
142
+ * mismatches on force-static pages; bootstrap data is safe to read immediately
143
+ * because the server rendered with the same values.
132
144
  */
133
145
  get(name: string): boolean;
134
146
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
@@ -190,4 +202,4 @@ declare const i18n: {
190
202
  onUpdate(cb: () => void): () => void;
191
203
  };
192
204
 
193
- export { type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, version };
205
+ export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, version };
@@ -659,6 +659,10 @@ function _resetShipeasyForTests() {
659
659
  _client?.destroy();
660
660
  _client = null;
661
661
  }
662
+ function getBootstrap() {
663
+ if (typeof window === "undefined") return null;
664
+ return window.__SE_BOOTSTRAP ?? null;
665
+ }
662
666
  var _mountedAndReady = false;
663
667
  var _standaloneListeners = /* @__PURE__ */ new Set();
664
668
  var _standaloneOverrideWired = false;
@@ -681,25 +685,44 @@ var flags = {
681
685
  return _client.identify(user);
682
686
  },
683
687
  /**
684
- * Read a feature gate. Returns false until FlagsBoundary mounts (SSR-safe).
685
- * After mount, URL overrides (?se_ks_*) apply even without a configured client.
688
+ * Read a feature gate.
689
+ * Priority: URL override → server bootstrap (window.__SE_BOOTSTRAP) CDN-fetched (post-mount) false.
690
+ * The _mountedAndReady gate still applies for the CDN path to prevent hydration
691
+ * mismatches on force-static pages; bootstrap data is safe to read immediately
692
+ * because the server rendered with the same values.
686
693
  */
687
694
  get(name) {
695
+ const ov = readGateOverride(name);
696
+ if (ov !== null) return ov;
697
+ const bs = getBootstrap();
698
+ if (bs !== null && name in bs.flags) return bs.flags[name];
688
699
  if (!_mountedAndReady) return false;
689
700
  if (_client) return _client.getFlag(name);
690
- return readGateOverride(name) ?? false;
701
+ return false;
691
702
  },
692
703
  getConfig(name, decode) {
693
- if (!_mountedAndReady) return void 0;
694
- if (_client) return _client.getConfig(name, decode);
695
704
  const ov = readConfigOverride(name);
696
- if (ov === void 0) return void 0;
697
- if (!decode) return ov;
698
- try {
699
- return decode(ov);
700
- } catch {
701
- return void 0;
705
+ if (ov !== void 0) {
706
+ if (!decode) return ov;
707
+ try {
708
+ return decode(ov);
709
+ } catch {
710
+ return void 0;
711
+ }
702
712
  }
713
+ const bs = getBootstrap();
714
+ if (bs !== null && name in bs.configs) {
715
+ const raw = bs.configs[name];
716
+ if (!decode) return raw;
717
+ try {
718
+ return decode(raw);
719
+ } catch {
720
+ return void 0;
721
+ }
722
+ }
723
+ if (!_mountedAndReady) return void 0;
724
+ if (_client) return _client.getConfig(name, decode);
725
+ return void 0;
703
726
  },
704
727
  getExperiment(name, defaultParams, decode, variants) {
705
728
  return _client?.getExperiment(name, defaultParams, decode, variants) ?? {
@@ -617,6 +617,10 @@ function _resetShipeasyForTests() {
617
617
  _client?.destroy();
618
618
  _client = null;
619
619
  }
620
+ function getBootstrap() {
621
+ if (typeof window === "undefined") return null;
622
+ return window.__SE_BOOTSTRAP ?? null;
623
+ }
620
624
  var _mountedAndReady = false;
621
625
  var _standaloneListeners = /* @__PURE__ */ new Set();
622
626
  var _standaloneOverrideWired = false;
@@ -639,25 +643,44 @@ var flags = {
639
643
  return _client.identify(user);
640
644
  },
641
645
  /**
642
- * Read a feature gate. Returns false until FlagsBoundary mounts (SSR-safe).
643
- * After mount, URL overrides (?se_ks_*) apply even without a configured client.
646
+ * Read a feature gate.
647
+ * Priority: URL override → server bootstrap (window.__SE_BOOTSTRAP) CDN-fetched (post-mount) false.
648
+ * The _mountedAndReady gate still applies for the CDN path to prevent hydration
649
+ * mismatches on force-static pages; bootstrap data is safe to read immediately
650
+ * because the server rendered with the same values.
644
651
  */
645
652
  get(name) {
653
+ const ov = readGateOverride(name);
654
+ if (ov !== null) return ov;
655
+ const bs = getBootstrap();
656
+ if (bs !== null && name in bs.flags) return bs.flags[name];
646
657
  if (!_mountedAndReady) return false;
647
658
  if (_client) return _client.getFlag(name);
648
- return readGateOverride(name) ?? false;
659
+ return false;
649
660
  },
650
661
  getConfig(name, decode) {
651
- if (!_mountedAndReady) return void 0;
652
- if (_client) return _client.getConfig(name, decode);
653
662
  const ov = readConfigOverride(name);
654
- if (ov === void 0) return void 0;
655
- if (!decode) return ov;
656
- try {
657
- return decode(ov);
658
- } catch {
659
- return void 0;
663
+ if (ov !== void 0) {
664
+ if (!decode) return ov;
665
+ try {
666
+ return decode(ov);
667
+ } catch {
668
+ return void 0;
669
+ }
660
670
  }
671
+ const bs = getBootstrap();
672
+ if (bs !== null && name in bs.configs) {
673
+ const raw = bs.configs[name];
674
+ if (!decode) return raw;
675
+ try {
676
+ return decode(raw);
677
+ } catch {
678
+ return void 0;
679
+ }
680
+ }
681
+ if (!_mountedAndReady) return void 0;
682
+ if (_client) return _client.getConfig(name, decode);
683
+ return void 0;
661
684
  },
662
685
  getExperiment(name, defaultParams, decode, variants) {
663
686
  return _client?.getExperiment(name, defaultParams, decode, variants) ?? {
@@ -9,6 +9,11 @@ interface ExperimentResult<P> {
9
9
  group: string;
10
10
  params: P;
11
11
  }
12
+ interface BootstrapPayload {
13
+ flags: Record<string, boolean>;
14
+ configs: Record<string, unknown>;
15
+ experiments: Record<string, ExperimentResult<Record<string, unknown>>>;
16
+ }
12
17
  type FlagsClientEnv = "dev" | "staging" | "prod";
13
18
  interface FlagsClientOptions {
14
19
  apiKey: string;
@@ -39,6 +44,16 @@ declare class FlagsClient {
39
44
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
40
45
  getExperiment<P extends Record<string, unknown>>(name: string, user: User, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
41
46
  track(userId: string, eventName: string, props?: Record<string, unknown>): void;
47
+ /**
48
+ * Evaluate all flags, configs, and experiments for a user against the locally
49
+ * cached blob (no network call). Applies ?se_ks_* / ?se_cf_* / ?se_exp_*
50
+ * overrides from the request URL when provided.
51
+ *
52
+ * Intended for SSR: call on the server, inject the result as
53
+ * `window.__SE_BOOTSTRAP` in the HTML, and the client SDK will read it
54
+ * synchronously without waiting for identify() to resolve.
55
+ */
56
+ evaluate(user: User, rawUrl?: string): BootstrapPayload;
42
57
  }
43
58
  interface LabelFile {
44
59
  v: number;
@@ -72,6 +87,12 @@ declare const flags: {
72
87
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
73
88
  getExperiment<P extends Record<string, unknown>>(name: string, user: User, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
74
89
  track(userId: string, eventName: string, props?: Record<string, unknown>): void;
90
+ /**
91
+ * Evaluate all flags / configs / experiments for a user against the locally
92
+ * cached blob. Pass the request URL to apply ?se_ks_* / ?se_cf_* / ?se_exp_*
93
+ * overrides. Returns an empty payload when the blob hasn't been fetched yet.
94
+ */
95
+ evaluate(user: User, rawUrl?: string): BootstrapPayload;
75
96
  };
76
97
 
77
- export { type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type LabelFile, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getShipeasyServerClient, version };
98
+ export { type BootstrapPayload, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type LabelFile, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getShipeasyServerClient, version };
@@ -9,6 +9,11 @@ interface ExperimentResult<P> {
9
9
  group: string;
10
10
  params: P;
11
11
  }
12
+ interface BootstrapPayload {
13
+ flags: Record<string, boolean>;
14
+ configs: Record<string, unknown>;
15
+ experiments: Record<string, ExperimentResult<Record<string, unknown>>>;
16
+ }
12
17
  type FlagsClientEnv = "dev" | "staging" | "prod";
13
18
  interface FlagsClientOptions {
14
19
  apiKey: string;
@@ -39,6 +44,16 @@ declare class FlagsClient {
39
44
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
40
45
  getExperiment<P extends Record<string, unknown>>(name: string, user: User, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
41
46
  track(userId: string, eventName: string, props?: Record<string, unknown>): void;
47
+ /**
48
+ * Evaluate all flags, configs, and experiments for a user against the locally
49
+ * cached blob (no network call). Applies ?se_ks_* / ?se_cf_* / ?se_exp_*
50
+ * overrides from the request URL when provided.
51
+ *
52
+ * Intended for SSR: call on the server, inject the result as
53
+ * `window.__SE_BOOTSTRAP` in the HTML, and the client SDK will read it
54
+ * synchronously without waiting for identify() to resolve.
55
+ */
56
+ evaluate(user: User, rawUrl?: string): BootstrapPayload;
42
57
  }
43
58
  interface LabelFile {
44
59
  v: number;
@@ -72,6 +87,12 @@ declare const flags: {
72
87
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
73
88
  getExperiment<P extends Record<string, unknown>>(name: string, user: User, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
74
89
  track(userId: string, eventName: string, props?: Record<string, unknown>): void;
90
+ /**
91
+ * Evaluate all flags / configs / experiments for a user against the locally
92
+ * cached blob. Pass the request URL to apply ?se_ks_* / ?se_cf_* / ?se_exp_*
93
+ * overrides. Returns an empty payload when the blob hasn't been fetched yet.
94
+ */
95
+ evaluate(user: User, rawUrl?: string): BootstrapPayload;
75
96
  };
76
97
 
77
- export { type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type LabelFile, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getShipeasyServerClient, version };
98
+ export { type BootstrapPayload, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type LabelFile, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getShipeasyServerClient, version };
@@ -129,6 +129,51 @@ function evalGateInternal(gate, user) {
129
129
  if (!uid) return false;
130
130
  return murmur3(`${gate.salt}:${uid}`) % 1e4 < gate.rolloutPct;
131
131
  }
132
+ var TRUE_RX = /^(true|on|1|yes)$/i;
133
+ var FALSE_RX = /^(false|off|0|no)$/i;
134
+ function parseOverrideBool(raw) {
135
+ if (TRUE_RX.test(raw)) return true;
136
+ if (FALSE_RX.test(raw)) return false;
137
+ return null;
138
+ }
139
+ function decodeOverrideConfigValue(raw) {
140
+ if (raw.startsWith("b64:")) {
141
+ try {
142
+ const json = atob(raw.slice(4).replace(/-/g, "+").replace(/_/g, "/"));
143
+ return JSON.parse(json);
144
+ } catch {
145
+ return raw;
146
+ }
147
+ }
148
+ try {
149
+ return JSON.parse(raw);
150
+ } catch {
151
+ return raw;
152
+ }
153
+ }
154
+ function parseOverrides(rawUrl) {
155
+ const gates = {};
156
+ const configs = {};
157
+ const experiments = {};
158
+ try {
159
+ const url = new URL(rawUrl, "http://localhost");
160
+ for (const [k, v] of url.searchParams) {
161
+ if (k.startsWith("se_ks_")) {
162
+ const b = parseOverrideBool(v);
163
+ if (b !== null) gates[k.slice(6)] = b;
164
+ } else if (k.startsWith("se_cf_")) {
165
+ configs[k.slice(6)] = decodeOverrideConfigValue(v);
166
+ } else if (k.startsWith("se_config_")) {
167
+ configs[k.slice(10)] = decodeOverrideConfigValue(v);
168
+ } else if (k.startsWith("se_exp_")) {
169
+ const name = k.slice(7);
170
+ if (v && v !== "default" && v !== "none") experiments[name] = v;
171
+ }
172
+ }
173
+ } catch {
174
+ }
175
+ return { gates, configs, experiments };
176
+ }
132
177
  var FlagsClient = class {
133
178
  apiKey;
134
179
  baseUrl;
@@ -271,6 +316,38 @@ var FlagsClient = class {
271
316
  body
272
317
  }).catch((err) => console.warn("[shipeasy] track failed:", String(err)));
273
318
  }
319
+ /**
320
+ * Evaluate all flags, configs, and experiments for a user against the locally
321
+ * cached blob (no network call). Applies ?se_ks_* / ?se_cf_* / ?se_exp_*
322
+ * overrides from the request URL when provided.
323
+ *
324
+ * Intended for SSR: call on the server, inject the result as
325
+ * `window.__SE_BOOTSTRAP` in the HTML, and the client SDK will read it
326
+ * synchronously without waiting for identify() to resolve.
327
+ */
328
+ evaluate(user, rawUrl) {
329
+ const flags2 = {};
330
+ const configs = {};
331
+ const experiments = {};
332
+ for (const [name, gate] of Object.entries(this.flagsBlob?.gates ?? {})) {
333
+ flags2[name] = evalGateInternal(gate, user);
334
+ }
335
+ for (const [name, entry] of Object.entries(this.flagsBlob?.configs ?? {})) {
336
+ configs[name] = entry.value;
337
+ }
338
+ for (const [name] of Object.entries(this.expsBlob?.experiments ?? {})) {
339
+ experiments[name] = this.getExperiment(name, user, {});
340
+ }
341
+ if (rawUrl) {
342
+ const ov = parseOverrides(rawUrl);
343
+ Object.assign(flags2, ov.gates);
344
+ Object.assign(configs, ov.configs);
345
+ for (const [name, group] of Object.entries(ov.experiments)) {
346
+ experiments[name] = { inExperiment: true, group, params: {} };
347
+ }
348
+ }
349
+ return { flags: flags2, configs, experiments };
350
+ }
274
351
  };
275
352
  var DEFAULT_I18N_CDN = "https://cdn.i18n.shipeasy.ai";
276
353
  async function fetchJson(url, timeoutMs = 2e3) {
@@ -351,6 +428,14 @@ var flags = {
351
428
  },
352
429
  track(userId, eventName, props) {
353
430
  _server?.track(userId, eventName, props);
431
+ },
432
+ /**
433
+ * Evaluate all flags / configs / experiments for a user against the locally
434
+ * cached blob. Pass the request URL to apply ?se_ks_* / ?se_cf_* / ?se_exp_*
435
+ * overrides. Returns an empty payload when the blob hasn't been fetched yet.
436
+ */
437
+ evaluate(user, rawUrl) {
438
+ return _server?.evaluate(user, rawUrl) ?? { flags: {}, configs: {}, experiments: {} };
354
439
  }
355
440
  };
356
441
  // Annotate the CommonJS export names for ESM import in node:
@@ -99,6 +99,51 @@ function evalGateInternal(gate, user) {
99
99
  if (!uid) return false;
100
100
  return murmur3(`${gate.salt}:${uid}`) % 1e4 < gate.rolloutPct;
101
101
  }
102
+ var TRUE_RX = /^(true|on|1|yes)$/i;
103
+ var FALSE_RX = /^(false|off|0|no)$/i;
104
+ function parseOverrideBool(raw) {
105
+ if (TRUE_RX.test(raw)) return true;
106
+ if (FALSE_RX.test(raw)) return false;
107
+ return null;
108
+ }
109
+ function decodeOverrideConfigValue(raw) {
110
+ if (raw.startsWith("b64:")) {
111
+ try {
112
+ const json = atob(raw.slice(4).replace(/-/g, "+").replace(/_/g, "/"));
113
+ return JSON.parse(json);
114
+ } catch {
115
+ return raw;
116
+ }
117
+ }
118
+ try {
119
+ return JSON.parse(raw);
120
+ } catch {
121
+ return raw;
122
+ }
123
+ }
124
+ function parseOverrides(rawUrl) {
125
+ const gates = {};
126
+ const configs = {};
127
+ const experiments = {};
128
+ try {
129
+ const url = new URL(rawUrl, "http://localhost");
130
+ for (const [k, v] of url.searchParams) {
131
+ if (k.startsWith("se_ks_")) {
132
+ const b = parseOverrideBool(v);
133
+ if (b !== null) gates[k.slice(6)] = b;
134
+ } else if (k.startsWith("se_cf_")) {
135
+ configs[k.slice(6)] = decodeOverrideConfigValue(v);
136
+ } else if (k.startsWith("se_config_")) {
137
+ configs[k.slice(10)] = decodeOverrideConfigValue(v);
138
+ } else if (k.startsWith("se_exp_")) {
139
+ const name = k.slice(7);
140
+ if (v && v !== "default" && v !== "none") experiments[name] = v;
141
+ }
142
+ }
143
+ } catch {
144
+ }
145
+ return { gates, configs, experiments };
146
+ }
102
147
  var FlagsClient = class {
103
148
  apiKey;
104
149
  baseUrl;
@@ -241,6 +286,38 @@ var FlagsClient = class {
241
286
  body
242
287
  }).catch((err) => console.warn("[shipeasy] track failed:", String(err)));
243
288
  }
289
+ /**
290
+ * Evaluate all flags, configs, and experiments for a user against the locally
291
+ * cached blob (no network call). Applies ?se_ks_* / ?se_cf_* / ?se_exp_*
292
+ * overrides from the request URL when provided.
293
+ *
294
+ * Intended for SSR: call on the server, inject the result as
295
+ * `window.__SE_BOOTSTRAP` in the HTML, and the client SDK will read it
296
+ * synchronously without waiting for identify() to resolve.
297
+ */
298
+ evaluate(user, rawUrl) {
299
+ const flags2 = {};
300
+ const configs = {};
301
+ const experiments = {};
302
+ for (const [name, gate] of Object.entries(this.flagsBlob?.gates ?? {})) {
303
+ flags2[name] = evalGateInternal(gate, user);
304
+ }
305
+ for (const [name, entry] of Object.entries(this.flagsBlob?.configs ?? {})) {
306
+ configs[name] = entry.value;
307
+ }
308
+ for (const [name] of Object.entries(this.expsBlob?.experiments ?? {})) {
309
+ experiments[name] = this.getExperiment(name, user, {});
310
+ }
311
+ if (rawUrl) {
312
+ const ov = parseOverrides(rawUrl);
313
+ Object.assign(flags2, ov.gates);
314
+ Object.assign(configs, ov.configs);
315
+ for (const [name, group] of Object.entries(ov.experiments)) {
316
+ experiments[name] = { inExperiment: true, group, params: {} };
317
+ }
318
+ }
319
+ return { flags: flags2, configs, experiments };
320
+ }
244
321
  };
245
322
  var DEFAULT_I18N_CDN = "https://cdn.i18n.shipeasy.ai";
246
323
  async function fetchJson(url, timeoutMs = 2e3) {
@@ -321,6 +398,14 @@ var flags = {
321
398
  },
322
399
  track(userId, eventName, props) {
323
400
  _server?.track(userId, eventName, props);
401
+ },
402
+ /**
403
+ * Evaluate all flags / configs / experiments for a user against the locally
404
+ * cached blob. Pass the request URL to apply ?se_ks_* / ?se_cf_* / ?se_exp_*
405
+ * overrides. Returns an empty payload when the blob hasn't been fetched yet.
406
+ */
407
+ evaluate(user, rawUrl) {
408
+ return _server?.evaluate(user, rawUrl) ?? { flags: {}, configs: {}, experiments: {} };
324
409
  }
325
410
  };
326
411
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/sdk",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Shipeasy SDK — feature gates, runtime configs, experiments, and metrics for the Shipeasy hosted service.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://shipeasy.ai",