@shipeasy/sdk 2.0.0 → 2.0.1

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.
@@ -126,12 +126,21 @@ declare function _resetShipeasyForTests(): void;
126
126
  declare const flags: {
127
127
  configure(opts: FlagsClientBrowserOptions): void;
128
128
  identify(user: User): Promise<void>;
129
- /** Read a feature gate. Returns false until identify() resolves. */
129
+ /**
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.
132
+ */
130
133
  get(name: string): boolean;
131
134
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
132
135
  getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
133
136
  track(eventName: string, props?: Record<string, unknown>): void;
134
137
  flush(): Promise<void>;
138
+ /**
139
+ * Called by FlagsBoundary after React hydration to unlock flag reads.
140
+ * Dispatches se:override:change so subscribers (FlagsBoundary) re-render
141
+ * once with real values — URL overrides and server-evaluated flags.
142
+ */
143
+ notifyMounted(): void;
135
144
  /** Subscribe for change notifications (identify/override). Used by framework adapters. */
136
145
  subscribe(listener: () => void): () => void;
137
146
  /** True once identify() has completed and flags are available. */
@@ -148,14 +157,14 @@ interface LabelAttrs {
148
157
  "data-label-desc"?: string;
149
158
  }
150
159
  declare function labelAttrs(key: string, variables?: Record<string, string | number>, desc?: string): LabelAttrs;
160
+ /**
161
+ * Universal i18n facade. Backed by the `window.i18n` global the loader
162
+ * script installs. Returns the key itself when the loader hasn't run
163
+ * (SSR, missing script tag, before profile fetch completes), so call
164
+ * sites never need to null-check.
165
+ */
151
166
  declare const i18n: {
152
- /**
153
- * Look up `key` in the active translation profile. When the profile
154
- * hasn't been fetched yet (SSR, CDN downtime, missing key), interpolate
155
- * `fallback` instead — `fallback` is the source-of-truth English copy
156
- * and is mandatory so the page never renders a raw key.
157
- */
158
- t(key: string, fallback: string, variables?: Record<string, string | number>): string;
167
+ t(key: string, variables?: Record<string, string | number>): string;
159
168
  /**
160
169
  * Translate a key and return a framework element (e.g. React <span>)
161
170
  * carrying `data-label` / `data-variables` attributes so the ShipEasy
@@ -126,12 +126,21 @@ declare function _resetShipeasyForTests(): void;
126
126
  declare const flags: {
127
127
  configure(opts: FlagsClientBrowserOptions): void;
128
128
  identify(user: User): Promise<void>;
129
- /** Read a feature gate. Returns false until identify() resolves. */
129
+ /**
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.
132
+ */
130
133
  get(name: string): boolean;
131
134
  getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
132
135
  getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
133
136
  track(eventName: string, props?: Record<string, unknown>): void;
134
137
  flush(): Promise<void>;
138
+ /**
139
+ * Called by FlagsBoundary after React hydration to unlock flag reads.
140
+ * Dispatches se:override:change so subscribers (FlagsBoundary) re-render
141
+ * once with real values — URL overrides and server-evaluated flags.
142
+ */
143
+ notifyMounted(): void;
135
144
  /** Subscribe for change notifications (identify/override). Used by framework adapters. */
136
145
  subscribe(listener: () => void): () => void;
137
146
  /** True once identify() has completed and flags are available. */
@@ -148,14 +157,14 @@ interface LabelAttrs {
148
157
  "data-label-desc"?: string;
149
158
  }
150
159
  declare function labelAttrs(key: string, variables?: Record<string, string | number>, desc?: string): LabelAttrs;
160
+ /**
161
+ * Universal i18n facade. Backed by the `window.i18n` global the loader
162
+ * script installs. Returns the key itself when the loader hasn't run
163
+ * (SSR, missing script tag, before profile fetch completes), so call
164
+ * sites never need to null-check.
165
+ */
151
166
  declare const i18n: {
152
- /**
153
- * Look up `key` in the active translation profile. When the profile
154
- * hasn't been fetched yet (SSR, CDN downtime, missing key), interpolate
155
- * `fallback` instead — `fallback` is the source-of-truth English copy
156
- * and is mandatory so the page never renders a raw key.
157
- */
158
- t(key: string, fallback: string, variables?: Record<string, string | number>): string;
167
+ t(key: string, variables?: Record<string, string | number>): string;
159
168
  /**
160
169
  * Translate a key and return a framework element (e.g. React <span>)
161
170
  * carrying `data-label` / `data-variables` attributes so the ShipEasy
@@ -454,15 +454,13 @@ var FlagsClientBrowser = class {
454
454
  this.evalResult = data;
455
455
  }
456
456
  getFlag(name) {
457
- if (this.evalResult === null) return false;
458
457
  const ov = readGateOverride(name);
459
458
  if (ov !== null) return ov;
460
- return this.evalResult.flags[name] ?? false;
459
+ return this.evalResult?.flags[name] ?? false;
461
460
  }
462
461
  getConfig(name, decode) {
463
- if (this.evalResult === null) return void 0;
464
462
  const ov = readConfigOverride(name);
465
- const raw = ov !== void 0 ? ov : this.evalResult.configs?.[name];
463
+ const raw = ov !== void 0 ? ov : this.evalResult?.configs?.[name];
466
464
  if (raw === void 0) return void 0;
467
465
  if (!decode) return raw;
468
466
  try {
@@ -661,6 +659,16 @@ function _resetShipeasyForTests() {
661
659
  _client?.destroy();
662
660
  _client = null;
663
661
  }
662
+ var _mountedAndReady = false;
663
+ var _standaloneListeners = /* @__PURE__ */ new Set();
664
+ var _standaloneOverrideWired = false;
665
+ function wireStandaloneOverride() {
666
+ if (_standaloneOverrideWired || typeof window === "undefined") return;
667
+ _standaloneOverrideWired = true;
668
+ window.addEventListener("se:override:change", () => {
669
+ for (const cb of _standaloneListeners) cb();
670
+ });
671
+ }
664
672
  var flags = {
665
673
  configure(opts) {
666
674
  configureShipeasy(opts);
@@ -672,12 +680,26 @@ var flags = {
672
680
  }
673
681
  return _client.identify(user);
674
682
  },
675
- /** Read a feature gate. Returns false until identify() resolves. */
683
+ /**
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.
686
+ */
676
687
  get(name) {
677
- return _client?.getFlag(name) ?? false;
688
+ if (!_mountedAndReady) return false;
689
+ if (_client) return _client.getFlag(name);
690
+ return readGateOverride(name) ?? false;
678
691
  },
679
692
  getConfig(name, decode) {
680
- return _client?.getConfig(name, decode);
693
+ if (!_mountedAndReady) return void 0;
694
+ if (_client) return _client.getConfig(name, decode);
695
+ 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;
702
+ }
681
703
  },
682
704
  getExperiment(name, defaultParams, decode, variants) {
683
705
  return _client?.getExperiment(name, defaultParams, decode, variants) ?? {
@@ -692,11 +714,24 @@ var flags = {
692
714
  flush() {
693
715
  return _client?.flush() ?? Promise.resolve();
694
716
  },
717
+ /**
718
+ * Called by FlagsBoundary after React hydration to unlock flag reads.
719
+ * Dispatches se:override:change so subscribers (FlagsBoundary) re-render
720
+ * once with real values — URL overrides and server-evaluated flags.
721
+ */
722
+ notifyMounted() {
723
+ if (_mountedAndReady) return;
724
+ _mountedAndReady = true;
725
+ if (typeof window !== "undefined") {
726
+ window.dispatchEvent(new CustomEvent("se:override:change"));
727
+ }
728
+ },
695
729
  /** Subscribe for change notifications (identify/override). Used by framework adapters. */
696
730
  subscribe(listener) {
697
- if (!_client) return () => {
698
- };
699
- return _client.subscribe(listener);
731
+ if (_client) return _client.subscribe(listener);
732
+ _standaloneListeners.add(listener);
733
+ wireStandaloneOverride();
734
+ return () => _standaloneListeners.delete(listener);
700
735
  },
701
736
  /** True once identify() has completed and flags are available. */
702
737
  get ready() {
@@ -717,27 +752,10 @@ function labelAttrs(key, variables, desc) {
717
752
  return attrs;
718
753
  }
719
754
  var _createElement = null;
720
- function interpolate(template, variables) {
721
- if (!variables) return template;
722
- let out = template;
723
- for (const name of Object.keys(variables)) {
724
- out = out.replace(new RegExp(`\\{\\{${name}\\}\\}`, "g"), String(variables[name]));
725
- }
726
- return out;
727
- }
728
755
  var i18n = {
729
- /**
730
- * Look up `key` in the active translation profile. When the profile
731
- * hasn't been fetched yet (SSR, CDN downtime, missing key), interpolate
732
- * `fallback` instead — `fallback` is the source-of-truth English copy
733
- * and is mandatory so the page never renders a raw key.
734
- */
735
- t(key, fallback, variables) {
736
- if (typeof window !== "undefined" && window.i18n) {
737
- const v = window.i18n.t(key, variables);
738
- if (v !== key) return v;
739
- }
740
- return interpolate(fallback, variables);
756
+ t(key, variables) {
757
+ if (typeof window !== "undefined" && window.i18n) return window.i18n.t(key, variables);
758
+ return key;
741
759
  },
742
760
  /**
743
761
  * Translate a key and return a framework element (e.g. React <span>)
@@ -752,7 +770,7 @@ var i18n = {
752
770
  * configured (e.g. server-side or in non-JSX contexts).
753
771
  */
754
772
  tEl(key, fallback, variables, desc) {
755
- const text = this.t(key, fallback, variables);
773
+ const text = this.t(key, variables) || fallback;
756
774
  if (!_createElement) return text;
757
775
  return _createElement("span", labelAttrs(key, variables, desc), text);
758
776
  },
@@ -412,15 +412,13 @@ var FlagsClientBrowser = class {
412
412
  this.evalResult = data;
413
413
  }
414
414
  getFlag(name) {
415
- if (this.evalResult === null) return false;
416
415
  const ov = readGateOverride(name);
417
416
  if (ov !== null) return ov;
418
- return this.evalResult.flags[name] ?? false;
417
+ return this.evalResult?.flags[name] ?? false;
419
418
  }
420
419
  getConfig(name, decode) {
421
- if (this.evalResult === null) return void 0;
422
420
  const ov = readConfigOverride(name);
423
- const raw = ov !== void 0 ? ov : this.evalResult.configs?.[name];
421
+ const raw = ov !== void 0 ? ov : this.evalResult?.configs?.[name];
424
422
  if (raw === void 0) return void 0;
425
423
  if (!decode) return raw;
426
424
  try {
@@ -619,6 +617,16 @@ function _resetShipeasyForTests() {
619
617
  _client?.destroy();
620
618
  _client = null;
621
619
  }
620
+ var _mountedAndReady = false;
621
+ var _standaloneListeners = /* @__PURE__ */ new Set();
622
+ var _standaloneOverrideWired = false;
623
+ function wireStandaloneOverride() {
624
+ if (_standaloneOverrideWired || typeof window === "undefined") return;
625
+ _standaloneOverrideWired = true;
626
+ window.addEventListener("se:override:change", () => {
627
+ for (const cb of _standaloneListeners) cb();
628
+ });
629
+ }
622
630
  var flags = {
623
631
  configure(opts) {
624
632
  configureShipeasy(opts);
@@ -630,12 +638,26 @@ var flags = {
630
638
  }
631
639
  return _client.identify(user);
632
640
  },
633
- /** Read a feature gate. Returns false until identify() resolves. */
641
+ /**
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.
644
+ */
634
645
  get(name) {
635
- return _client?.getFlag(name) ?? false;
646
+ if (!_mountedAndReady) return false;
647
+ if (_client) return _client.getFlag(name);
648
+ return readGateOverride(name) ?? false;
636
649
  },
637
650
  getConfig(name, decode) {
638
- return _client?.getConfig(name, decode);
651
+ if (!_mountedAndReady) return void 0;
652
+ if (_client) return _client.getConfig(name, decode);
653
+ 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;
660
+ }
639
661
  },
640
662
  getExperiment(name, defaultParams, decode, variants) {
641
663
  return _client?.getExperiment(name, defaultParams, decode, variants) ?? {
@@ -650,11 +672,24 @@ var flags = {
650
672
  flush() {
651
673
  return _client?.flush() ?? Promise.resolve();
652
674
  },
675
+ /**
676
+ * Called by FlagsBoundary after React hydration to unlock flag reads.
677
+ * Dispatches se:override:change so subscribers (FlagsBoundary) re-render
678
+ * once with real values — URL overrides and server-evaluated flags.
679
+ */
680
+ notifyMounted() {
681
+ if (_mountedAndReady) return;
682
+ _mountedAndReady = true;
683
+ if (typeof window !== "undefined") {
684
+ window.dispatchEvent(new CustomEvent("se:override:change"));
685
+ }
686
+ },
653
687
  /** Subscribe for change notifications (identify/override). Used by framework adapters. */
654
688
  subscribe(listener) {
655
- if (!_client) return () => {
656
- };
657
- return _client.subscribe(listener);
689
+ if (_client) return _client.subscribe(listener);
690
+ _standaloneListeners.add(listener);
691
+ wireStandaloneOverride();
692
+ return () => _standaloneListeners.delete(listener);
658
693
  },
659
694
  /** True once identify() has completed and flags are available. */
660
695
  get ready() {
@@ -675,27 +710,10 @@ function labelAttrs(key, variables, desc) {
675
710
  return attrs;
676
711
  }
677
712
  var _createElement = null;
678
- function interpolate(template, variables) {
679
- if (!variables) return template;
680
- let out = template;
681
- for (const name of Object.keys(variables)) {
682
- out = out.replace(new RegExp(`\\{\\{${name}\\}\\}`, "g"), String(variables[name]));
683
- }
684
- return out;
685
- }
686
713
  var i18n = {
687
- /**
688
- * Look up `key` in the active translation profile. When the profile
689
- * hasn't been fetched yet (SSR, CDN downtime, missing key), interpolate
690
- * `fallback` instead — `fallback` is the source-of-truth English copy
691
- * and is mandatory so the page never renders a raw key.
692
- */
693
- t(key, fallback, variables) {
694
- if (typeof window !== "undefined" && window.i18n) {
695
- const v = window.i18n.t(key, variables);
696
- if (v !== key) return v;
697
- }
698
- return interpolate(fallback, variables);
714
+ t(key, variables) {
715
+ if (typeof window !== "undefined" && window.i18n) return window.i18n.t(key, variables);
716
+ return key;
699
717
  },
700
718
  /**
701
719
  * Translate a key and return a framework element (e.g. React <span>)
@@ -710,7 +728,7 @@ var i18n = {
710
728
  * configured (e.g. server-side or in non-JSX contexts).
711
729
  */
712
730
  tEl(key, fallback, variables, desc) {
713
- const text = this.t(key, fallback, variables);
731
+ const text = this.t(key, variables) || fallback;
714
732
  if (!_createElement) return text;
715
733
  return _createElement("span", labelAttrs(key, variables, desc), text);
716
734
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/sdk",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
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",