@rixl/sdk 0.8.2 → 0.8.3

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/dist/index.js CHANGED
@@ -625,6 +625,117 @@ const createClient = (config = {}) => {
625
625
  //#region src/generated/client.gen.ts
626
626
  const client = createClient(createConfig({ baseUrl: "https://raw.githubusercontent.com" }));
627
627
 
628
+ //#endregion
629
+ //#region src/shared-runtime.ts
630
+ /**
631
+ * Cross-instance runtime state for the SDK.
632
+ *
633
+ * Every store and client in this package is module-level singleton state that
634
+ * `connect()` mutates. That only works while exactly one copy of `@rixl/sdk`
635
+ * is evaluated. Bundlers routinely break that assumption: Vite's dependency
636
+ * pre-bundling, for example, inlines a private copy of the SDK into any
637
+ * optimized dependency that imports it, so a consumer's `connect()` configures
638
+ * one copy while a library's requests read another — with a wrong baseUrl and,
639
+ * worse, no auth tokens.
640
+ *
641
+ * Anchoring the state to a `Symbol.for` key on `globalThis` makes every copy
642
+ * resolve the same objects, so duplication becomes harmless instead of a
643
+ * silent, hard-to-trace failure.
644
+ */
645
+ const REGISTRY_KEY = Symbol.for("@rixl/sdk.runtime");
646
+ /** Bumped only if the shape below changes incompatibly. */
647
+ const STATE_VERSION = 1;
648
+ function registry() {
649
+ const host = globalThis;
650
+ const existing = host[REGISTRY_KEY];
651
+ if (existing && existing.version === STATE_VERSION) return existing;
652
+ const created = {
653
+ version: STATE_VERSION,
654
+ copies: 0,
655
+ values: /* @__PURE__ */ new Map()
656
+ };
657
+ host[REGISTRY_KEY] = created;
658
+ return created;
659
+ }
660
+ /**
661
+ * Returns the one shared value for `name`, creating it on first use.
662
+ *
663
+ * `create` runs at most once per realm no matter how many copies of the SDK are
664
+ * loaded, so callers get object identity (a nanostore atom, a Set) rather than
665
+ * a per-copy clone.
666
+ */
667
+ function shared(name, create) {
668
+ const { values } = registry();
669
+ if (!values.has(name)) values.set(name, create());
670
+ return values.get(name);
671
+ }
672
+ const copies = registry().copies += 1;
673
+ if (copies > 1) console.warn(`[@rixl/sdk] ${copies} copies of this package were loaded in one page. Shared state is deduplicated, so this is not fatal, but it doubles bundle size and usually means a bundler inlined a private copy — with Vite, add "@rixl/sdk" to optimizeDeps.include and resolve.dedupe. Ensure libraries depending on @rixl/sdk declare it as a peerDependency.`);
674
+
675
+ //#endregion
676
+ //#region src/client-registry.ts
677
+ /**
678
+ * The baseUrl the code generator bakes in. It is derived from the origin of the
679
+ * OpenAPI input URL, not from a real server, so a client still holding it has
680
+ * simply never been configured. Captured from the client itself rather than
681
+ * hardcoded so it stays correct if the generator input ever moves.
682
+ */
683
+ const placeholderBaseUrl = shared("placeholderBaseUrl", () => client.getConfig().baseUrl);
684
+ const clients = shared("clients", () => /* @__PURE__ */ new Set());
685
+ /** Setup applied to every client copy, including ones registered later. */
686
+ const initializers = shared("clientInitializers", () => []);
687
+ /** Last baseUrl passed to {@link configureAllClients}, replayed onto late joiners. */
688
+ const appliedConfig = shared("appliedConfig", () => ({ baseUrl: void 0 }));
689
+ function isUnconfigured(target) {
690
+ const { baseUrl } = target.getConfig();
691
+ return !baseUrl || baseUrl === placeholderBaseUrl;
692
+ }
693
+ /**
694
+ * Fails a request that would otherwise be sent to the generator's placeholder
695
+ * host. Without this the request goes out looking legitimate and comes back a
696
+ * 404 from an unrelated origin, which reads like a backend fault instead of
697
+ * "the SDK was never initialised".
698
+ */
699
+ function installUnconfiguredGuard(target) {
700
+ target.interceptors.request.use((request) => {
701
+ if (isUnconfigured(target)) throw new Error(`[@rixl/sdk] Cannot send ${request.method} ${new URL(request.url).pathname}: no baseUrl is configured. Call connect({baseUrl}) before issuing requests.`);
702
+ return request;
703
+ });
704
+ }
705
+ /**
706
+ * Registers setup to run against every client copy — those already known and
707
+ * any that register afterwards. Interceptors added through here therefore reach
708
+ * a copy that loads from a lazy chunk after `connect()` has already run.
709
+ */
710
+ function addClientInitializer(initialize) {
711
+ initializers.push(initialize);
712
+ for (const target of clients) initialize(target);
713
+ }
714
+ /**
715
+ * Adds a client to the shared registry so configuration reaches it. Registering
716
+ * is idempotent, and a client that joins late is brought fully up to date.
717
+ */
718
+ function registerClient(target) {
719
+ if (clients.has(target)) return;
720
+ clients.add(target);
721
+ installUnconfiguredGuard(target);
722
+ for (const initialize of initializers) initialize(target);
723
+ if (appliedConfig.baseUrl !== void 0) target.setConfig({ baseUrl: appliedConfig.baseUrl });
724
+ }
725
+ /**
726
+ * Points every known client copy at `baseUrl`.
727
+ *
728
+ * Duplicate copies of this package each construct their own client object, and
729
+ * the generated request functions close over whichever one their copy owns.
730
+ * Configuring only the caller's copy is what leaves a bundled library issuing
731
+ * requests to the placeholder host.
732
+ */
733
+ function configureAllClients(baseUrl) {
734
+ appliedConfig.baseUrl = baseUrl;
735
+ for (const target of clients) target.setConfig({ baseUrl });
736
+ }
737
+ registerClient(client);
738
+
628
739
  //#endregion
629
740
  //#region src/generated/sdk.gen.ts
630
741
  /**
@@ -2472,11 +2583,14 @@ const parseUser = () => {
2472
2583
  return;
2473
2584
  }
2474
2585
  };
2475
- const user = atom(parseUser());
2476
- user.subscribe((value) => {
2477
- if (typeof localStorage === "undefined" || !localStorage || typeof localStorage.setItem !== "function") return;
2478
- if (value) localStorage.setItem(userPath, JSON.stringify(value));
2479
- else localStorage.removeItem(userPath);
2586
+ const user = shared("user", () => {
2587
+ const store = atom(parseUser());
2588
+ store.subscribe((value) => {
2589
+ if (typeof localStorage === "undefined" || !localStorage || typeof localStorage.setItem !== "function") return;
2590
+ if (value) localStorage.setItem(userPath, JSON.stringify(value));
2591
+ else localStorage.removeItem(userPath);
2592
+ });
2593
+ return store;
2480
2594
  });
2481
2595
 
2482
2596
  //#endregion
@@ -2530,13 +2644,13 @@ const isTokenExpired = (expireAt) => {
2530
2644
 
2531
2645
  //#endregion
2532
2646
  //#region src/auth/authStore.ts
2533
- const isLogged = atom(initVals["isLogged"] === "true" || detectProvider() !== void 0);
2534
- const accessToken = atom(initVals["accessToken"]);
2535
- const refreshToken = atom(initVals["refreshToken"]);
2536
- const expireAt = atom(Number(initVals["expireAt"]));
2537
- const authError = atom(null);
2538
- const requiresAction = atom(initVals["requiresAction"] || null);
2539
- const limitedAccessToken = atom(initVals["limitedAccessToken"] || null);
2647
+ const isLogged = shared("isLogged", () => atom(initVals["isLogged"] === "true" || detectProvider() !== void 0));
2648
+ const accessToken = shared("accessToken", () => atom(initVals["accessToken"]));
2649
+ const refreshToken = shared("refreshToken", () => atom(initVals["refreshToken"]));
2650
+ const expireAt = shared("expireAt", () => atom(Number(initVals["expireAt"])));
2651
+ const authError = shared("authError", () => atom(null));
2652
+ const requiresAction = shared("requiresAction", () => atom(initVals["requiresAction"] || null));
2653
+ const limitedAccessToken = shared("limitedAccessToken", () => atom(initVals["limitedAccessToken"] || null));
2540
2654
  let currentTokenPromise = null;
2541
2655
  const PROVIDER_URL_MAP = {
2542
2656
  google: googleAuthUrl,
@@ -2678,17 +2792,19 @@ var ApiError = class extends Error {
2678
2792
  /**
2679
2793
  * Global API base URL store
2680
2794
  */
2681
- const apiURL = atom("");
2795
+ const apiURL = shared("apiURL", () => atom(""));
2682
2796
 
2683
2797
  //#endregion
2684
2798
  //#region src/auth/api/sdk-client.ts
2685
2799
  function isWireErrorBody(error) {
2686
2800
  return typeof error === "object" && error !== null;
2687
2801
  }
2688
- let configured = false;
2689
- let tokenResolver = getToken;
2802
+ const state = shared("sdkClientState", () => ({
2803
+ configured: false,
2804
+ tokenResolver: getToken
2805
+ }));
2690
2806
  function setTokenResolver(resolver) {
2691
- tokenResolver = resolver;
2807
+ state.tokenResolver = resolver;
2692
2808
  }
2693
2809
  /**
2694
2810
  * Routes that do not require a Bearer token at the gateway edge. These
@@ -2800,26 +2916,28 @@ function isPublicRoute(method, pathname) {
2800
2916
  return publicRoutes.some(({ method: m, path, prefix }) => match === m && (prefix ? pathname.startsWith(path) : pathname === path));
2801
2917
  }
2802
2918
  function configureSdkClient() {
2803
- if (configured) return;
2804
- configured = true;
2805
- client.setConfig({ baseUrl: apiURL.get() });
2919
+ if (state.configured) return;
2920
+ state.configured = true;
2921
+ configureAllClients(apiURL.get());
2806
2922
  apiURL.subscribe((url) => {
2807
- client.setConfig({ baseUrl: url });
2923
+ configureAllClients(url);
2808
2924
  });
2809
- client.interceptors.request.use(async (request) => {
2810
- if (request.headers.has("Authorization")) return request;
2811
- const { pathname } = new URL(request.url);
2812
- if (isPublicRoute(request.method, pathname)) return request;
2813
- const token = await tokenResolver();
2814
- if (!token) throw new ApiError("No access token available for an authenticated request", HTTP_STATUS.UNAUTHORIZED, pathname);
2815
- request.headers.set("Authorization", `Bearer ${token}`);
2816
- return request;
2817
- });
2818
- client.interceptors.error.use((error, response, request) => {
2819
- if (error instanceof Error) return error;
2820
- const body = isWireErrorBody(error) ? error : void 0;
2821
- const status = response?.status ?? body?.code ?? 0;
2822
- return new ApiError(body?.error || body?.details || (typeof error === "string" ? error : "Request failed"), status, request ? new URL(request.url).pathname : "", error);
2925
+ addClientInitializer((client) => {
2926
+ client.interceptors.request.use(async (request) => {
2927
+ if (request.headers.has("Authorization")) return request;
2928
+ const { pathname } = new URL(request.url);
2929
+ if (isPublicRoute(request.method, pathname)) return request;
2930
+ const token = await state.tokenResolver();
2931
+ if (!token) throw new ApiError("No access token available for an authenticated request", HTTP_STATUS.UNAUTHORIZED, pathname);
2932
+ request.headers.set("Authorization", `Bearer ${token}`);
2933
+ return request;
2934
+ });
2935
+ client.interceptors.error.use((error, response, request) => {
2936
+ if (error instanceof Error) return error;
2937
+ const body = isWireErrorBody(error) ? error : void 0;
2938
+ const status = response?.status ?? body?.code ?? 0;
2939
+ return new ApiError(body?.error || body?.details || (typeof error === "string" ? error : "Request failed"), status, request ? new URL(request.url).pathname : "", error);
2940
+ });
2823
2941
  });
2824
2942
  }
2825
2943
 
@@ -3180,9 +3298,9 @@ const initSocials = async () => {
3180
3298
 
3181
3299
  //#endregion
3182
3300
  //#region src/platform/platformAuthStore.ts
3183
- const platformAccessToken = atom(void 0);
3184
- const platformRefreshToken = atom(void 0);
3185
- const platformExpireAt = atom(0);
3301
+ const platformAccessToken = shared("platformAccessToken", () => atom(void 0));
3302
+ const platformRefreshToken = shared("platformRefreshToken", () => atom(void 0));
3303
+ const platformExpireAt = shared("platformExpireAt", () => atom(0));
3186
3304
  let currentPlatformTokenPromise = null;
3187
3305
  const setPlatformTokens = (access, refresh, expiresIn) => {
3188
3306
  platformAccessToken.set(access);
@@ -3239,7 +3357,7 @@ const getPlatformToken = async () => {
3239
3357
  //#region src/connect.ts
3240
3358
  const connect = async (config) => {
3241
3359
  apiURL.set(config.baseUrl);
3242
- client.setConfig({ baseUrl: config.baseUrl });
3360
+ configureAllClients(config.baseUrl);
3243
3361
  if (config.apiKey) {
3244
3362
  await exchangeApiKey(config.apiKey);
3245
3363
  setTokenResolver(getPlatformToken);