@shipeasy/sdk 2.3.0 → 2.5.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.
@@ -27,6 +27,12 @@ interface EvalResponse {
27
27
  flags: Record<string, boolean>;
28
28
  configs: Record<string, unknown>;
29
29
  experiments: Record<string, EvalExpResult>;
30
+ /**
31
+ * Killswitch state, flattened by the server. A boolean means the killswitch
32
+ * is whole-killed; an object means it's not whole-killed and carries per-
33
+ * switch booleans.
34
+ */
35
+ killswitches?: Record<string, boolean | Record<string, boolean>>;
30
36
  }
31
37
  interface AutoCollectGroups {
32
38
  vitals: boolean;
@@ -83,6 +89,12 @@ declare class FlagsClientBrowser {
83
89
  */
84
90
  installBridge(): ShipeasySdkBridge | null;
85
91
  track(eventName: string, props?: Record<string, unknown>): void;
92
+ /**
93
+ * Read a killswitch from the server's evaluated state. Without `switchKey`,
94
+ * returns true when the killswitch is whole-killed. With `switchKey`, returns
95
+ * the per-switch state. Returns false for unknown killswitches / switches.
96
+ */
97
+ getKillswitch(name: string, switchKey?: string): boolean;
86
98
  flush(): Promise<void>;
87
99
  destroy(): void;
88
100
  }
@@ -180,6 +192,12 @@ interface BootstrapPayload {
180
192
  group: string;
181
193
  params: Record<string, unknown>;
182
194
  }>;
195
+ /**
196
+ * Killswitch state, flattened by the server. A value of `boolean` means the
197
+ * killswitch is killed as a whole (no per-switch detail); a `Record` means
198
+ * the killswitch is not whole-killed and the map carries per-switch state.
199
+ */
200
+ killswitches?: Record<string, boolean | Record<string, boolean>>;
183
201
  /** Set by getBootstrapHtml() for auto-init. Not part of evaluate() output. */
184
202
  apiKey?: string;
185
203
  apiUrl?: string;
@@ -205,6 +223,16 @@ declare const flags: {
205
223
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
206
224
  getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
207
225
  track(eventName: string, props?: Record<string, unknown>): void;
226
+ /**
227
+ * Read a killswitch. Without `switchKey`, returns true when the killswitch is
228
+ * killed as a whole. With `switchKey`, returns true when that specific switch
229
+ * is on. Unknown killswitches / switches return false.
230
+ *
231
+ * Priority: bootstrap → CDN evalResult (post-mount) → false. Matches the
232
+ * pattern used by `flags.get` / `flags.getConfig` so SSR-hydrated values are
233
+ * available synchronously on first render.
234
+ */
235
+ ks(name: string, switchKey?: string): boolean;
208
236
  flush(): Promise<void>;
209
237
  /**
210
238
  * Called by FlagsBoundary after React hydration to unlock flag reads.
@@ -27,6 +27,12 @@ interface EvalResponse {
27
27
  flags: Record<string, boolean>;
28
28
  configs: Record<string, unknown>;
29
29
  experiments: Record<string, EvalExpResult>;
30
+ /**
31
+ * Killswitch state, flattened by the server. A boolean means the killswitch
32
+ * is whole-killed; an object means it's not whole-killed and carries per-
33
+ * switch booleans.
34
+ */
35
+ killswitches?: Record<string, boolean | Record<string, boolean>>;
30
36
  }
31
37
  interface AutoCollectGroups {
32
38
  vitals: boolean;
@@ -83,6 +89,12 @@ declare class FlagsClientBrowser {
83
89
  */
84
90
  installBridge(): ShipeasySdkBridge | null;
85
91
  track(eventName: string, props?: Record<string, unknown>): void;
92
+ /**
93
+ * Read a killswitch from the server's evaluated state. Without `switchKey`,
94
+ * returns true when the killswitch is whole-killed. With `switchKey`, returns
95
+ * the per-switch state. Returns false for unknown killswitches / switches.
96
+ */
97
+ getKillswitch(name: string, switchKey?: string): boolean;
86
98
  flush(): Promise<void>;
87
99
  destroy(): void;
88
100
  }
@@ -180,6 +192,12 @@ interface BootstrapPayload {
180
192
  group: string;
181
193
  params: Record<string, unknown>;
182
194
  }>;
195
+ /**
196
+ * Killswitch state, flattened by the server. A value of `boolean` means the
197
+ * killswitch is killed as a whole (no per-switch detail); a `Record` means
198
+ * the killswitch is not whole-killed and the map carries per-switch state.
199
+ */
200
+ killswitches?: Record<string, boolean | Record<string, boolean>>;
183
201
  /** Set by getBootstrapHtml() for auto-init. Not part of evaluate() output. */
184
202
  apiKey?: string;
185
203
  apiUrl?: string;
@@ -205,6 +223,16 @@ declare const flags: {
205
223
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
206
224
  getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
207
225
  track(eventName: string, props?: Record<string, unknown>): void;
226
+ /**
227
+ * Read a killswitch. Without `switchKey`, returns true when the killswitch is
228
+ * killed as a whole. With `switchKey`, returns true when that specific switch
229
+ * is on. Unknown killswitches / switches return false.
230
+ *
231
+ * Priority: bootstrap → CDN evalResult (post-mount) → false. Matches the
232
+ * pattern used by `flags.get` / `flags.getConfig` so SSR-hydrated values are
233
+ * available synchronously on first render.
234
+ */
235
+ ks(name: string, switchKey?: string): boolean;
208
236
  flush(): Promise<void>;
209
237
  /**
210
238
  * Called by FlagsBoundary after React hydration to unlock flag reads.
@@ -306,6 +306,23 @@ function installAutoGuardrails(buffer, userId, anonId, groups) {
306
306
  } catch {
307
307
  }
308
308
  };
309
+ if (groups.engagement) {
310
+ try {
311
+ buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
312
+ } catch {
313
+ }
314
+ let lastEmit = Date.now();
315
+ const SESSION_GAP_MS = 30 * 60 * 1e3;
316
+ document.addEventListener("visibilitychange", () => {
317
+ if (document.visibilityState !== "visible") return;
318
+ if (Date.now() - lastEmit < SESSION_GAP_MS) return;
319
+ try {
320
+ buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
321
+ lastEmit = Date.now();
322
+ } catch {
323
+ }
324
+ });
325
+ }
309
326
  const needHide = groups.vitals || groups.engagement;
310
327
  if (needHide) {
311
328
  if (document.readyState === "complete") {
@@ -558,6 +575,19 @@ var FlagsClientBrowser = class {
558
575
  track(eventName, props) {
559
576
  this.buffer.pushMetric(eventName, this.userId, this.anonId, props);
560
577
  }
578
+ /**
579
+ * Read a killswitch from the server's evaluated state. Without `switchKey`,
580
+ * returns true when the killswitch is whole-killed. With `switchKey`, returns
581
+ * the per-switch state. Returns false for unknown killswitches / switches.
582
+ */
583
+ getKillswitch(name, switchKey) {
584
+ if (this.evalResult === null) return false;
585
+ const ks = this.evalResult.killswitches?.[name];
586
+ if (ks === void 0) return false;
587
+ if (typeof ks === "boolean") return switchKey === void 0 ? ks : false;
588
+ if (switchKey === void 0) return false;
589
+ return ks[switchKey] === true;
590
+ }
561
591
  async flush() {
562
592
  await this.buffer.flushAsync();
563
593
  }
@@ -777,6 +807,26 @@ var flags = {
777
807
  track(eventName, props) {
778
808
  _client?.track(eventName, props);
779
809
  },
810
+ /**
811
+ * Read a killswitch. Without `switchKey`, returns true when the killswitch is
812
+ * killed as a whole. With `switchKey`, returns true when that specific switch
813
+ * is on. Unknown killswitches / switches return false.
814
+ *
815
+ * Priority: bootstrap → CDN evalResult (post-mount) → false. Matches the
816
+ * pattern used by `flags.get` / `flags.getConfig` so SSR-hydrated values are
817
+ * available synchronously on first render.
818
+ */
819
+ ks(name, switchKey) {
820
+ const bs = getBootstrap();
821
+ if (bs !== null && bs.killswitches && name in bs.killswitches) {
822
+ const ks = bs.killswitches[name];
823
+ if (typeof ks === "boolean") return switchKey === void 0 ? ks : false;
824
+ if (switchKey === void 0) return false;
825
+ return ks[switchKey] === true;
826
+ }
827
+ if (!_mountedAndReady) return false;
828
+ return _client?.getKillswitch(name, switchKey) ?? false;
829
+ },
780
830
  flush() {
781
831
  return _client?.flush() ?? Promise.resolve();
782
832
  },
@@ -263,6 +263,23 @@ function installAutoGuardrails(buffer, userId, anonId, groups) {
263
263
  } catch {
264
264
  }
265
265
  };
266
+ if (groups.engagement) {
267
+ try {
268
+ buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
269
+ } catch {
270
+ }
271
+ let lastEmit = Date.now();
272
+ const SESSION_GAP_MS = 30 * 60 * 1e3;
273
+ document.addEventListener("visibilitychange", () => {
274
+ if (document.visibilityState !== "visible") return;
275
+ if (Date.now() - lastEmit < SESSION_GAP_MS) return;
276
+ try {
277
+ buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
278
+ lastEmit = Date.now();
279
+ } catch {
280
+ }
281
+ });
282
+ }
266
283
  const needHide = groups.vitals || groups.engagement;
267
284
  if (needHide) {
268
285
  if (document.readyState === "complete") {
@@ -515,6 +532,19 @@ var FlagsClientBrowser = class {
515
532
  track(eventName, props) {
516
533
  this.buffer.pushMetric(eventName, this.userId, this.anonId, props);
517
534
  }
535
+ /**
536
+ * Read a killswitch from the server's evaluated state. Without `switchKey`,
537
+ * returns true when the killswitch is whole-killed. With `switchKey`, returns
538
+ * the per-switch state. Returns false for unknown killswitches / switches.
539
+ */
540
+ getKillswitch(name, switchKey) {
541
+ if (this.evalResult === null) return false;
542
+ const ks = this.evalResult.killswitches?.[name];
543
+ if (ks === void 0) return false;
544
+ if (typeof ks === "boolean") return switchKey === void 0 ? ks : false;
545
+ if (switchKey === void 0) return false;
546
+ return ks[switchKey] === true;
547
+ }
518
548
  async flush() {
519
549
  await this.buffer.flushAsync();
520
550
  }
@@ -734,6 +764,26 @@ var flags = {
734
764
  track(eventName, props) {
735
765
  _client?.track(eventName, props);
736
766
  },
767
+ /**
768
+ * Read a killswitch. Without `switchKey`, returns true when the killswitch is
769
+ * killed as a whole. With `switchKey`, returns true when that specific switch
770
+ * is on. Unknown killswitches / switches return false.
771
+ *
772
+ * Priority: bootstrap → CDN evalResult (post-mount) → false. Matches the
773
+ * pattern used by `flags.get` / `flags.getConfig` so SSR-hydrated values are
774
+ * available synchronously on first render.
775
+ */
776
+ ks(name, switchKey) {
777
+ const bs = getBootstrap();
778
+ if (bs !== null && bs.killswitches && name in bs.killswitches) {
779
+ const ks = bs.killswitches[name];
780
+ if (typeof ks === "boolean") return switchKey === void 0 ? ks : false;
781
+ if (switchKey === void 0) return false;
782
+ return ks[switchKey] === true;
783
+ }
784
+ if (!_mountedAndReady) return false;
785
+ return _client?.getKillswitch(name, switchKey) ?? false;
786
+ },
737
787
  flush() {
738
788
  return _client?.flush() ?? Promise.resolve();
739
789
  },
@@ -9,10 +9,36 @@ interface ExperimentResult<P> {
9
9
  group: string;
10
10
  params: P;
11
11
  }
12
+ interface GateRule {
13
+ attr: string;
14
+ op: "eq" | "neq" | "in" | "not_in" | "gt" | "gte" | "lt" | "lte" | "contains" | "regex";
15
+ value: unknown;
16
+ }
17
+ interface Gate {
18
+ rules: GateRule[];
19
+ rolloutPct: number;
20
+ salt: string;
21
+ enabled: 0 | 1 | boolean;
22
+ killswitch?: 0 | 1 | boolean;
23
+ }
24
+ interface Killswitch {
25
+ killed: 0 | 1 | boolean;
26
+ switches?: Record<string, 0 | 1 | boolean>;
27
+ }
28
+ interface FlagsBlob {
29
+ version: string;
30
+ plan: string;
31
+ gates: Record<string, Gate>;
32
+ configs: Record<string, {
33
+ value: unknown;
34
+ }>;
35
+ killswitches: Record<string, Killswitch>;
36
+ }
12
37
  interface BootstrapPayload {
13
38
  flags: Record<string, boolean>;
14
39
  configs: Record<string, unknown>;
15
40
  experiments: Record<string, ExperimentResult<Record<string, unknown>>>;
41
+ killswitches: Record<string, boolean | Record<string, boolean>>;
16
42
  }
17
43
  type FlagsClientEnv = "dev" | "staging" | "prod";
18
44
  interface FlagsClientOptions {
@@ -20,6 +46,11 @@ interface FlagsClientOptions {
20
46
  baseUrl?: string;
21
47
  /** Which published env to read values from. Defaults to "prod". */
22
48
  env?: FlagsClientEnv;
49
+ /**
50
+ * Preload the flags blob synchronously without a network fetch. Primarily
51
+ * for tests; production callers should rely on init()/initOnce().
52
+ */
53
+ initialBlob?: FlagsBlob;
23
54
  }
24
55
  declare class FlagsClient {
25
56
  private readonly apiKey;
@@ -54,6 +85,7 @@ declare class FlagsClient {
54
85
  * synchronously without waiting for identify() to resolve.
55
86
  */
56
87
  evaluate(user: User, rawUrl?: string): BootstrapPayload;
88
+ getKillswitch(name: string, switchKey?: string): boolean;
57
89
  }
58
90
  interface I18nForRequest {
59
91
  strings: Record<string, string>;
@@ -165,6 +197,12 @@ declare const flags: {
165
197
  get(name: string, user: User): boolean;
166
198
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
167
199
  getExperiment<P extends Record<string, unknown>>(name: string, user: User, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
200
+ /**
201
+ * Read a killswitch. Without `switchKey`, returns true when the whole
202
+ * killswitch is killed. With `switchKey`, returns true when that specific
203
+ * switch is on. Unknown killswitches / switches return false.
204
+ */
205
+ ks(name: string, switchKey?: string): boolean;
168
206
  track(userId: string, eventName: string, props?: Record<string, unknown>): void;
169
207
  /**
170
208
  * Evaluate all flags / configs / experiments for a user against the locally
@@ -9,10 +9,36 @@ interface ExperimentResult<P> {
9
9
  group: string;
10
10
  params: P;
11
11
  }
12
+ interface GateRule {
13
+ attr: string;
14
+ op: "eq" | "neq" | "in" | "not_in" | "gt" | "gte" | "lt" | "lte" | "contains" | "regex";
15
+ value: unknown;
16
+ }
17
+ interface Gate {
18
+ rules: GateRule[];
19
+ rolloutPct: number;
20
+ salt: string;
21
+ enabled: 0 | 1 | boolean;
22
+ killswitch?: 0 | 1 | boolean;
23
+ }
24
+ interface Killswitch {
25
+ killed: 0 | 1 | boolean;
26
+ switches?: Record<string, 0 | 1 | boolean>;
27
+ }
28
+ interface FlagsBlob {
29
+ version: string;
30
+ plan: string;
31
+ gates: Record<string, Gate>;
32
+ configs: Record<string, {
33
+ value: unknown;
34
+ }>;
35
+ killswitches: Record<string, Killswitch>;
36
+ }
12
37
  interface BootstrapPayload {
13
38
  flags: Record<string, boolean>;
14
39
  configs: Record<string, unknown>;
15
40
  experiments: Record<string, ExperimentResult<Record<string, unknown>>>;
41
+ killswitches: Record<string, boolean | Record<string, boolean>>;
16
42
  }
17
43
  type FlagsClientEnv = "dev" | "staging" | "prod";
18
44
  interface FlagsClientOptions {
@@ -20,6 +46,11 @@ interface FlagsClientOptions {
20
46
  baseUrl?: string;
21
47
  /** Which published env to read values from. Defaults to "prod". */
22
48
  env?: FlagsClientEnv;
49
+ /**
50
+ * Preload the flags blob synchronously without a network fetch. Primarily
51
+ * for tests; production callers should rely on init()/initOnce().
52
+ */
53
+ initialBlob?: FlagsBlob;
23
54
  }
24
55
  declare class FlagsClient {
25
56
  private readonly apiKey;
@@ -54,6 +85,7 @@ declare class FlagsClient {
54
85
  * synchronously without waiting for identify() to resolve.
55
86
  */
56
87
  evaluate(user: User, rawUrl?: string): BootstrapPayload;
88
+ getKillswitch(name: string, switchKey?: string): boolean;
57
89
  }
58
90
  interface I18nForRequest {
59
91
  strings: Record<string, string>;
@@ -165,6 +197,12 @@ declare const flags: {
165
197
  get(name: string, user: User): boolean;
166
198
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
167
199
  getExperiment<P extends Record<string, unknown>>(name: string, user: User, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
200
+ /**
201
+ * Read a killswitch. Without `switchKey`, returns true when the whole
202
+ * killswitch is killed. With `switchKey`, returns true when that specific
203
+ * switch is on. Unknown killswitches / switches return false.
204
+ */
205
+ ks(name: string, switchKey?: string): boolean;
168
206
  track(userId: string, eventName: string, props?: Record<string, unknown>): void;
169
207
  /**
170
208
  * Evaluate all flags / configs / experiments for a user against the locally
@@ -203,6 +203,10 @@ var FlagsClient = class {
203
203
  this.apiKey = opts.apiKey;
204
204
  this.baseUrl = (opts.baseUrl ?? "https://cdn.shipeasy.ai").replace(/\/$/, "");
205
205
  this.env = opts.env ?? "prod";
206
+ if (opts.initialBlob) {
207
+ this.flagsBlob = opts.initialBlob;
208
+ this.initialized = true;
209
+ }
206
210
  }
207
211
  async init() {
208
212
  await this.fetchAll();
@@ -343,6 +347,7 @@ var FlagsClient = class {
343
347
  const flags2 = {};
344
348
  const configs = {};
345
349
  const experiments = {};
350
+ const killswitches = {};
346
351
  for (const [name, gate] of Object.entries(this.flagsBlob?.gates ?? {})) {
347
352
  flags2[name] = evalGateInternal(gate, user);
348
353
  }
@@ -352,6 +357,15 @@ var FlagsClient = class {
352
357
  for (const [name] of Object.entries(this.expsBlob?.experiments ?? {})) {
353
358
  experiments[name] = this.getExperiment(name, user, {});
354
359
  }
360
+ for (const [name, ks] of Object.entries(this.flagsBlob?.killswitches ?? {})) {
361
+ if (ks.switches && Object.keys(ks.switches).length > 0) {
362
+ const out = {};
363
+ for (const [k, v] of Object.entries(ks.switches)) out[k] = isEnabled(v);
364
+ killswitches[name] = out;
365
+ } else {
366
+ killswitches[name] = isEnabled(ks.killed);
367
+ }
368
+ }
355
369
  if (rawUrl) {
356
370
  const ov = parseOverrides(rawUrl);
357
371
  Object.assign(flags2, ov.gates);
@@ -360,7 +374,13 @@ var FlagsClient = class {
360
374
  experiments[name] = { inExperiment: true, group, params: {} };
361
375
  }
362
376
  }
363
- return { flags: flags2, configs, experiments };
377
+ return { flags: flags2, configs, experiments, killswitches };
378
+ }
379
+ getKillswitch(name, switchKey) {
380
+ const ks = this.flagsBlob?.killswitches?.[name];
381
+ if (!ks) return false;
382
+ if (switchKey === void 0) return isEnabled(ks.killed);
383
+ return isEnabled(ks.switches?.[switchKey]);
364
384
  }
365
385
  };
366
386
  var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
@@ -589,6 +609,14 @@ var flags = {
589
609
  params: defaultParams
590
610
  };
591
611
  },
612
+ /**
613
+ * Read a killswitch. Without `switchKey`, returns true when the whole
614
+ * killswitch is killed. With `switchKey`, returns true when that specific
615
+ * switch is on. Unknown killswitches / switches return false.
616
+ */
617
+ ks(name, switchKey) {
618
+ return _server?.getKillswitch(name, switchKey) ?? false;
619
+ },
592
620
  track(userId, eventName, props) {
593
621
  _server?.track(userId, eventName, props);
594
622
  },
@@ -598,7 +626,12 @@ var flags = {
598
626
  * overrides. Returns an empty payload when the blob hasn't been fetched yet.
599
627
  */
600
628
  evaluate(user, rawUrl) {
601
- return _server?.evaluate(user, rawUrl) ?? { flags: {}, configs: {}, experiments: {} };
629
+ return _server?.evaluate(user, rawUrl) ?? {
630
+ flags: {},
631
+ configs: {},
632
+ experiments: {},
633
+ killswitches: {}
634
+ };
602
635
  }
603
636
  };
604
637
  // Annotate the CommonJS export names for ESM import in node:
@@ -160,6 +160,10 @@ var FlagsClient = class {
160
160
  this.apiKey = opts.apiKey;
161
161
  this.baseUrl = (opts.baseUrl ?? "https://cdn.shipeasy.ai").replace(/\/$/, "");
162
162
  this.env = opts.env ?? "prod";
163
+ if (opts.initialBlob) {
164
+ this.flagsBlob = opts.initialBlob;
165
+ this.initialized = true;
166
+ }
163
167
  }
164
168
  async init() {
165
169
  await this.fetchAll();
@@ -300,6 +304,7 @@ var FlagsClient = class {
300
304
  const flags2 = {};
301
305
  const configs = {};
302
306
  const experiments = {};
307
+ const killswitches = {};
303
308
  for (const [name, gate] of Object.entries(this.flagsBlob?.gates ?? {})) {
304
309
  flags2[name] = evalGateInternal(gate, user);
305
310
  }
@@ -309,6 +314,15 @@ var FlagsClient = class {
309
314
  for (const [name] of Object.entries(this.expsBlob?.experiments ?? {})) {
310
315
  experiments[name] = this.getExperiment(name, user, {});
311
316
  }
317
+ for (const [name, ks] of Object.entries(this.flagsBlob?.killswitches ?? {})) {
318
+ if (ks.switches && Object.keys(ks.switches).length > 0) {
319
+ const out = {};
320
+ for (const [k, v] of Object.entries(ks.switches)) out[k] = isEnabled(v);
321
+ killswitches[name] = out;
322
+ } else {
323
+ killswitches[name] = isEnabled(ks.killed);
324
+ }
325
+ }
312
326
  if (rawUrl) {
313
327
  const ov = parseOverrides(rawUrl);
314
328
  Object.assign(flags2, ov.gates);
@@ -317,7 +331,13 @@ var FlagsClient = class {
317
331
  experiments[name] = { inExperiment: true, group, params: {} };
318
332
  }
319
333
  }
320
- return { flags: flags2, configs, experiments };
334
+ return { flags: flags2, configs, experiments, killswitches };
335
+ }
336
+ getKillswitch(name, switchKey) {
337
+ const ks = this.flagsBlob?.killswitches?.[name];
338
+ if (!ks) return false;
339
+ if (switchKey === void 0) return isEnabled(ks.killed);
340
+ return isEnabled(ks.switches?.[switchKey]);
321
341
  }
322
342
  };
323
343
  var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
@@ -546,6 +566,14 @@ var flags = {
546
566
  params: defaultParams
547
567
  };
548
568
  },
569
+ /**
570
+ * Read a killswitch. Without `switchKey`, returns true when the whole
571
+ * killswitch is killed. With `switchKey`, returns true when that specific
572
+ * switch is on. Unknown killswitches / switches return false.
573
+ */
574
+ ks(name, switchKey) {
575
+ return _server?.getKillswitch(name, switchKey) ?? false;
576
+ },
549
577
  track(userId, eventName, props) {
550
578
  _server?.track(userId, eventName, props);
551
579
  },
@@ -555,7 +583,12 @@ var flags = {
555
583
  * overrides. Returns an empty payload when the blob hasn't been fetched yet.
556
584
  */
557
585
  evaluate(user, rawUrl) {
558
- return _server?.evaluate(user, rawUrl) ?? { flags: {}, configs: {}, experiments: {} };
586
+ return _server?.evaluate(user, rawUrl) ?? {
587
+ flags: {},
588
+ configs: {},
589
+ experiments: {},
590
+ killswitches: {}
591
+ };
559
592
  }
560
593
  };
561
594
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/sdk",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
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",