@kheopskit/react 3.0.1 → 5.0.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 ADDED
@@ -0,0 +1,103 @@
1
+ # @kheopskit/react
2
+
3
+ React bindings for [Kheopskit](https://github.com/kheopskit/kheopskit) — list
4
+ wallets and accounts across Polkadot, Ethereum and Solana, with injected wallets
5
+ and WalletConnect (Reown AppKit).
6
+
7
+ The framework-agnostic core lives in
8
+ [`@kheopskit/core`](https://www.npmjs.com/package/@kheopskit/core).
9
+ Full docs and the interactive playground:
10
+ https://github.com/kheopskit/kheopskit
11
+
12
+ > **Upgrading from v3?** v4 moves platforms to plugins and makes platform SDKs
13
+ > (and WalletConnect) optional peer dependencies. See
14
+ > [MIGRATING_TO_V4.md](../core/MIGRATING_TO_V4.md).
15
+
16
+ ## Install
17
+
18
+ `rxjs` is always required; every platform SDK (and WalletConnect) is an optional
19
+ peer dependency — install only what you use:
20
+
21
+ ```bash
22
+ pnpm add @kheopskit/core @kheopskit/react rxjs
23
+
24
+ pnpm add polkadot-api # Polkadot
25
+ pnpm add viem mipd # Ethereum
26
+ pnpm add @solana/kit @wallet-standard/app @wallet-standard/base # Solana
27
+ pnpm add @reown/appkit # WalletConnect (optional)
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ Recommended: bind your platform tuple once with `createKheopskit` and get a
33
+ provider plus hooks already typed to those platforms — no generic to repeat.
34
+
35
+ ```ts
36
+ // kheopskit.ts
37
+ import { createKheopskit } from "@kheopskit/react";
38
+ import { polkadot } from "@kheopskit/core/polkadot";
39
+ import { ethereum } from "@kheopskit/core/ethereum";
40
+ import { solana } from "@kheopskit/core/solana";
41
+
42
+ export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({
43
+ platforms: [polkadot(), ethereum(), solana()],
44
+ autoReconnect: true,
45
+ });
46
+ ```
47
+
48
+ ```tsx
49
+ // app.tsx
50
+ import { KheopskitProvider } from "./kheopskit";
51
+
52
+ export const App = ({ children }: { children: React.ReactNode }) => (
53
+ <KheopskitProvider>{children}</KheopskitProvider>
54
+ );
55
+ ```
56
+
57
+ ```tsx
58
+ // anywhere — accounts/wallets are platform-precise (account.signer / account.client typed)
59
+ import { useWallets, useAccounts } from "./kheopskit";
60
+
61
+ const { wallets, isHydrating } = useWallets();
62
+ const accounts = useAccounts();
63
+ ```
64
+
65
+ ### Without the factory
66
+
67
+ You can also use the `KheopskitProvider` component directly and recover precise
68
+ types with a type argument (React context can't be generic):
69
+
70
+ ```tsx
71
+ import { KheopskitProvider, useWallets } from "@kheopskit/react";
72
+
73
+ const platforms = [polkadot(), ethereum(), solana()] as const;
74
+
75
+ <KheopskitProvider config={{ platforms }}>…</KheopskitProvider>;
76
+
77
+ const { accounts } = useWallets<typeof platforms>();
78
+ ```
79
+
80
+ ## SSR
81
+
82
+ Pass the request cookie header as `ssrCookies` to hydrate wallet state on the
83
+ server without a UI flash:
84
+
85
+ ```tsx
86
+ // Next.js App Router
87
+ const cookieStore = await cookies();
88
+ const ssrCookies = cookieStore
89
+ .getAll()
90
+ .map((c) => `${c.name}=${c.value}`)
91
+ .join("; ");
92
+
93
+ <KheopskitProvider ssrCookies={ssrCookies}>{children}</KheopskitProvider>;
94
+ ```
95
+
96
+ > While `state.isHydrating` is `true`, wallets/accounts are cached placeholders
97
+ > carrying only the base fields — SDK fields (`signer`, `client`,
98
+ > provider/extension handles) are absent until hydration completes. Guard access
99
+ > behind `!isHydrating`.
100
+
101
+ ## License
102
+
103
+ ISC
package/dist/index.d.mts CHANGED
@@ -1,8 +1,47 @@
1
1
  import * as _kheopskit_core from '@kheopskit/core';
2
- import { KheopskitConfig } from '@kheopskit/core';
2
+ import { KheopskitPlatform, KheopskitConfig, KheopskitState } from '@kheopskit/core';
3
3
  import { FC, PropsWithChildren } from 'react';
4
4
 
5
+ type CreateKheopskitConfig<P extends readonly KheopskitPlatform[]> = Omit<Partial<KheopskitConfig<P>>, "platforms"> & {
6
+ /** Platform plugins, e.g. `[polkadot(), solana()]`. Required. */
7
+ platforms: P;
8
+ };
9
+ /**
10
+ * Binds a platform tuple once and returns a `KheopskitProvider` plus hooks
11
+ * (`useWallets`, `useAccounts`) already typed to those platforms — so you don't
12
+ * repeat `useWallets<typeof platforms>()` in every component.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // kheopskit.ts
17
+ * export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({
18
+ * platforms: [polkadot(), ethereum(), solana()],
19
+ * });
20
+ *
21
+ * // anywhere
22
+ * const { accounts } = useWallets(); // accounts are platform-precise, no generic
23
+ * ```
24
+ */
25
+ declare const createKheopskit: <const P extends readonly [KheopskitPlatform, ...KheopskitPlatform[]]>(config: CreateKheopskitConfig<P>) => {
26
+ KheopskitProvider: FC<PropsWithChildren<{
27
+ ssrCookies?: string;
28
+ }>>;
29
+ /** Current state, typed to the bound platform tuple. */
30
+ useWallets: () => _kheopskit_core.KheopskitState<P>;
31
+ /** Current accounts, typed to the bound platform tuple. */
32
+ useAccounts: () => _kheopskit_core.AccountOf<P[number]>[];
33
+ };
34
+
5
35
  type KheopskitProviderProps = PropsWithChildren & {
36
+ /**
37
+ * Kheopskit configuration.
38
+ *
39
+ * @remarks
40
+ * Must be a **referentially stable** value — define it once (module scope, a
41
+ * `useMemo`, or via {@link createKheopskit}) and pass the same reference. A
42
+ * new object literal on every render (`config={{ platforms: [...] }}` inline)
43
+ * recreates the underlying store and re-subscribes each render.
44
+ */
6
45
  config?: Partial<KheopskitConfig>;
7
46
  /**
8
47
  * Cookie string for SSR hydration.
@@ -25,6 +64,22 @@ type KheopskitProviderProps = PropsWithChildren & {
25
64
  };
26
65
  declare const KheopskitProvider: FC<KheopskitProviderProps>;
27
66
 
28
- declare const useWallets: () => _kheopskit_core.KheopskitState;
67
+ /**
68
+ * Convenience hook returning just the accounts from kheopskit state. Pass the
69
+ * platform tuple as a type argument to recover SDK-precise account types —
70
+ * `useAccounts<typeof platforms>()` — or use the pre-typed hook from
71
+ * {@link createKheopskit}.
72
+ */
73
+ declare const useAccounts: <P extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[]>() => _kheopskit_core.AccountOf<P[number]>[];
74
+
75
+ /**
76
+ * Returns the current kheopskit state (wallets, accounts, config, isHydrating).
77
+ *
78
+ * Pass the platform tuple as a type argument to recover SDK-precise account and
79
+ * wallet types — `useWallets<typeof platforms>()`. React contexts can't be
80
+ * generic, so without the argument the state is typed with the base
81
+ * (SDK-free) wallet/account shapes.
82
+ */
83
+ declare const useWallets: <P extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[]>() => KheopskitState<P>;
29
84
 
30
- export { KheopskitProvider, type KheopskitProviderProps, useWallets };
85
+ export { type CreateKheopskitConfig, KheopskitProvider, type KheopskitProviderProps, createKheopskit, useAccounts, useWallets };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,47 @@
1
1
  import * as _kheopskit_core from '@kheopskit/core';
2
- import { KheopskitConfig } from '@kheopskit/core';
2
+ import { KheopskitPlatform, KheopskitConfig, KheopskitState } from '@kheopskit/core';
3
3
  import { FC, PropsWithChildren } from 'react';
4
4
 
5
+ type CreateKheopskitConfig<P extends readonly KheopskitPlatform[]> = Omit<Partial<KheopskitConfig<P>>, "platforms"> & {
6
+ /** Platform plugins, e.g. `[polkadot(), solana()]`. Required. */
7
+ platforms: P;
8
+ };
9
+ /**
10
+ * Binds a platform tuple once and returns a `KheopskitProvider` plus hooks
11
+ * (`useWallets`, `useAccounts`) already typed to those platforms — so you don't
12
+ * repeat `useWallets<typeof platforms>()` in every component.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // kheopskit.ts
17
+ * export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({
18
+ * platforms: [polkadot(), ethereum(), solana()],
19
+ * });
20
+ *
21
+ * // anywhere
22
+ * const { accounts } = useWallets(); // accounts are platform-precise, no generic
23
+ * ```
24
+ */
25
+ declare const createKheopskit: <const P extends readonly [KheopskitPlatform, ...KheopskitPlatform[]]>(config: CreateKheopskitConfig<P>) => {
26
+ KheopskitProvider: FC<PropsWithChildren<{
27
+ ssrCookies?: string;
28
+ }>>;
29
+ /** Current state, typed to the bound platform tuple. */
30
+ useWallets: () => _kheopskit_core.KheopskitState<P>;
31
+ /** Current accounts, typed to the bound platform tuple. */
32
+ useAccounts: () => _kheopskit_core.AccountOf<P[number]>[];
33
+ };
34
+
5
35
  type KheopskitProviderProps = PropsWithChildren & {
36
+ /**
37
+ * Kheopskit configuration.
38
+ *
39
+ * @remarks
40
+ * Must be a **referentially stable** value — define it once (module scope, a
41
+ * `useMemo`, or via {@link createKheopskit}) and pass the same reference. A
42
+ * new object literal on every render (`config={{ platforms: [...] }}` inline)
43
+ * recreates the underlying store and re-subscribes each render.
44
+ */
6
45
  config?: Partial<KheopskitConfig>;
7
46
  /**
8
47
  * Cookie string for SSR hydration.
@@ -25,6 +64,22 @@ type KheopskitProviderProps = PropsWithChildren & {
25
64
  };
26
65
  declare const KheopskitProvider: FC<KheopskitProviderProps>;
27
66
 
28
- declare const useWallets: () => _kheopskit_core.KheopskitState;
67
+ /**
68
+ * Convenience hook returning just the accounts from kheopskit state. Pass the
69
+ * platform tuple as a type argument to recover SDK-precise account types —
70
+ * `useAccounts<typeof platforms>()` — or use the pre-typed hook from
71
+ * {@link createKheopskit}.
72
+ */
73
+ declare const useAccounts: <P extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[]>() => _kheopskit_core.AccountOf<P[number]>[];
74
+
75
+ /**
76
+ * Returns the current kheopskit state (wallets, accounts, config, isHydrating).
77
+ *
78
+ * Pass the platform tuple as a type argument to recover SDK-precise account and
79
+ * wallet types — `useWallets<typeof platforms>()`. React contexts can't be
80
+ * generic, so without the argument the state is typed with the base
81
+ * (SDK-free) wallet/account shapes.
82
+ */
83
+ declare const useWallets: <P extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[]>() => KheopskitState<P>;
29
84
 
30
- export { KheopskitProvider, type KheopskitProviderProps, useWallets };
85
+ export { type CreateKheopskitConfig, KheopskitProvider, type KheopskitProviderProps, createKheopskit, useAccounts, useWallets };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -21,12 +22,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
22
  var index_exports = {};
22
23
  __export(index_exports, {
23
24
  KheopskitProvider: () => KheopskitProvider,
25
+ createKheopskit: () => createKheopskit,
26
+ useAccounts: () => useAccounts,
24
27
  useWallets: () => useWallets
25
28
  });
26
29
  module.exports = __toCommonJS(index_exports);
27
30
 
28
31
  // src/KheopskitProvider.tsx
29
32
  var import_core = require("@kheopskit/core");
33
+ var import_internal = require("@kheopskit/core/internal");
30
34
  var import_react2 = require("react");
31
35
 
32
36
  // src/context.ts
@@ -49,13 +53,12 @@ var createStore = (observable$, initialValue, serverValue) => {
49
53
  });
50
54
  }
51
55
  };
52
- ensureSubscription();
53
56
  const getSnapshot = () => latestValue ?? initialValue;
54
57
  const getServerSnapshot = () => serverValue ?? initialValue;
55
58
  const subscribe = (callback) => {
56
59
  subscriberCount++;
57
- listeners.add(callback);
58
60
  ensureSubscription();
61
+ listeners.add(callback);
59
62
  callback(getSnapshot());
60
63
  return () => {
61
64
  subscriberCount--;
@@ -84,6 +87,17 @@ var KheopskitProvider = ({
84
87
  ssrCookies
85
88
  }) => {
86
89
  const resolvedConfig = (0, import_react2.useMemo)(() => (0, import_core.resolveConfig)(config), [config]);
90
+ const lastConfigRef = (0, import_react2.useRef)(config);
91
+ const hasMountedRef = (0, import_react2.useRef)(false);
92
+ (0, import_react2.useEffect)(() => {
93
+ if (hasMountedRef.current && lastConfigRef.current !== config && (typeof process === "undefined" || process.env?.NODE_ENV !== "production")) {
94
+ console.warn(
95
+ "[kheopskit] KheopskitProvider received a new `config` reference; this recreates the store and re-subscribes on every render. Pass a referentially stable config (module scope, useMemo, or createKheopskit())."
96
+ );
97
+ }
98
+ hasMountedRef.current = true;
99
+ lastConfigRef.current = config;
100
+ }, [config]);
87
101
  const kheopskitStore = (0, import_react2.useMemo)(
88
102
  () => (0, import_core.createKheopskitStore)({
89
103
  ssrCookies,
@@ -102,32 +116,32 @@ var KheopskitProvider = ({
102
116
  }
103
117
  const cached = kheopskitStore.getCachedState();
104
118
  return {
105
- wallets: cached.wallets.map(import_core.hydrateWallet),
106
- accounts: cached.accounts.map(import_core.hydrateAccount).filter(
107
- (account) => account.platform !== "polkadot" || resolvedConfig.polkadotAccountTypes.includes(account.type)
108
- ),
119
+ wallets: cached.wallets.map(import_internal.hydrateWallet).sort(import_internal.sortWallets),
120
+ accounts: cached.accounts.filter(
121
+ (account) => (0, import_internal.acceptsCachedAccount)(account, resolvedConfig.platforms)
122
+ ).map(import_internal.hydrateAccount).sort(import_internal.sortAccounts),
109
123
  config: resolvedConfig,
110
124
  isHydrating: true
111
125
  };
112
126
  }, [ssrCookies, kheopskitStore, resolvedConfig]);
113
127
  const initialValue = (0, import_react2.useMemo)(() => {
114
- const enrichedWallets = serverValue.wallets.map((w) => {
115
- if (!w.icon) {
116
- const cachedIcon = (0, import_core.getCachedIcon)(w.id);
117
- if (cachedIcon) {
118
- return { ...w, icon: cachedIcon };
119
- }
120
- }
121
- return w;
122
- });
128
+ const cached = kheopskitStore.getCachedState();
123
129
  return {
124
- ...serverValue,
125
- wallets: enrichedWallets
130
+ wallets: cached.wallets.map(import_internal.hydrateWallet).map((wallet) => {
131
+ if (wallet.icon) return wallet;
132
+ const cachedIcon = (0, import_internal.getCachedIcon)(wallet.id);
133
+ return cachedIcon ? { ...wallet, icon: cachedIcon } : wallet;
134
+ }).sort(import_internal.sortWallets),
135
+ accounts: cached.accounts.filter(
136
+ (account) => (0, import_internal.acceptsCachedAccount)(account, resolvedConfig.platforms)
137
+ ).map(import_internal.hydrateAccount).sort(import_internal.sortAccounts),
138
+ config: resolvedConfig,
139
+ isHydrating: true
126
140
  };
127
- }, [serverValue]);
141
+ }, [kheopskitStore, resolvedConfig]);
128
142
  const store = (0, import_react2.useMemo)(
129
143
  () => createStore(
130
- (0, import_core.getKheopskit$)(config, ssrCookies, kheopskitStore),
144
+ (0, import_core.getKheopskit$)(config, { ssrCookies, store: kheopskitStore }),
131
145
  initialValue,
132
146
  serverValue
133
147
  ),
@@ -153,9 +167,31 @@ var useWallets = () => {
153
167
  throw new Error("useWallets can't be used without a KheopskitProvider");
154
168
  return ctx.state;
155
169
  };
170
+
171
+ // src/createKheopskit.tsx
172
+ var import_jsx_runtime2 = require("react/jsx-runtime");
173
+ var createKheopskit = (config) => {
174
+ const Provider = ({
175
+ children,
176
+ ssrCookies
177
+ }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(KheopskitProvider, { config, ssrCookies, children });
178
+ Provider.displayName = "KheopskitProvider";
179
+ return {
180
+ KheopskitProvider: Provider,
181
+ /** Current state, typed to the bound platform tuple. */
182
+ useWallets: () => useWallets(),
183
+ /** Current accounts, typed to the bound platform tuple. */
184
+ useAccounts: () => useWallets().accounts
185
+ };
186
+ };
187
+
188
+ // src/useAccounts.ts
189
+ var useAccounts = () => useWallets().accounts;
156
190
  // Annotate the CommonJS export names for ESM import in node:
157
191
  0 && (module.exports = {
158
192
  KheopskitProvider,
193
+ createKheopskit,
194
+ useAccounts,
159
195
  useWallets
160
196
  });
161
197
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/KheopskitProvider.tsx","../src/context.ts","../src/createStore.ts","../src/useWallets.ts"],"sourcesContent":["export * from \"./KheopskitProvider\";\nexport * from \"./useWallets\";\n","import {\n\tcreateKheopskitStore,\n\tgetCachedIcon,\n\tgetKheopskit$,\n\thydrateAccount,\n\thydrateWallet,\n\ttype KheopskitConfig,\n\ttype KheopskitState,\n\tresolveConfig,\n} from \"@kheopskit/core\";\nimport {\n\ttype FC,\n\ttype PropsWithChildren,\n\tuseEffect,\n\tuseMemo,\n\tuseSyncExternalStore,\n} from \"react\";\nimport { KheopskitContext } from \"./context\";\nimport { createStore } from \"./createStore\";\n\nexport type KheopskitProviderProps = PropsWithChildren & {\n\tconfig?: Partial<KheopskitConfig>;\n\t/**\n\t * Cookie string for SSR hydration.\n\t * Pass the request cookie header (e.g., from Next.js headers or TanStack Start)\n\t * to hydrate wallet state on the server.\n\t *\n\t * @remarks\n\t * This value should be stable per render to avoid unnecessary store recreation.\n\t * Compute it once in your server component or layout and pass it down.\n\t *\n\t * @example\n\t * ```tsx\n\t * // Next.js App Router\n\t * const cookieStore = await cookies();\n\t * const ssrCookies = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');\n\t * return <Providers ssrCookies={ssrCookies}>{children}</Providers>\n\t * ```\n\t */\n\tssrCookies?: string;\n};\n\nexport const KheopskitProvider: FC<KheopskitProviderProps> = ({\n\tchildren,\n\tconfig,\n\tssrCookies,\n}) => {\n\tconst resolvedConfig = useMemo(() => resolveConfig(config), [config]);\n\n\t// Create a single store for both reading cached state and powering the observable\n\tconst kheopskitStore = useMemo(\n\t\t() =>\n\t\t\tcreateKheopskitStore({\n\t\t\t\tssrCookies,\n\t\t\t\tstorageKey: resolvedConfig.storageKey,\n\t\t\t}),\n\t\t[ssrCookies, resolvedConfig.storageKey],\n\t);\n\n\t// Read cached state from the store for SSR hydration\n\t// This produces wallets WITHOUT localStorage icons (Ethereum wallets have no icon)\n\t// because localStorage isn't available on server\n\tconst serverValue = useMemo<KheopskitState>(() => {\n\t\tif (ssrCookies === undefined) {\n\t\t\treturn {\n\t\t\t\twallets: [],\n\t\t\t\taccounts: [],\n\t\t\t\tconfig: resolvedConfig,\n\t\t\t\tisHydrating: true,\n\t\t\t};\n\t\t}\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets.map(hydrateWallet),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.filter(\n\t\t\t\t\t(account) =>\n\t\t\t\t\t\taccount.platform !== \"polkadot\" ||\n\t\t\t\t\t\tresolvedConfig.polkadotAccountTypes.includes(account.type),\n\t\t\t\t),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [ssrCookies, kheopskitStore, resolvedConfig]);\n\n\t// Initial value for client includes localStorage icons\n\t// This is what we WANT the client to render, not what server rendered\n\tconst initialValue = useMemo<KheopskitState>(() => {\n\t\t// On client, enrich wallets with localStorage icons\n\t\t// getCachedIcon returns empty on server (no localStorage), so this is safe\n\t\tconst enrichedWallets = serverValue.wallets.map((w) => {\n\t\t\tif (!w.icon) {\n\t\t\t\tconst cachedIcon = getCachedIcon(w.id);\n\t\t\t\tif (cachedIcon) {\n\t\t\t\t\treturn { ...w, icon: cachedIcon };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn w;\n\t\t});\n\t\treturn {\n\t\t\t...serverValue,\n\t\t\twallets: enrichedWallets,\n\t\t};\n\t}, [serverValue]);\n\n\tconst store = useMemo(\n\t\t() =>\n\t\t\tcreateStore(\n\t\t\t\tgetKheopskit$(config, ssrCookies, kheopskitStore),\n\t\t\t\tinitialValue,\n\t\t\t\tserverValue,\n\t\t\t),\n\t\t[config, ssrCookies, kheopskitStore, initialValue, serverValue],\n\t);\n\n\t// Cleanup store subscriptions when store changes or component unmounts\n\tuseEffect(() => {\n\t\treturn () => store.destroy();\n\t}, [store]);\n\n\tconst state = useSyncExternalStore(\n\t\tstore.subscribe,\n\t\tstore.getSnapshot,\n\t\tstore.getServerSnapshot,\n\t);\n\n\tconst value = useMemo(() => ({ state }), [state]);\n\n\treturn (\n\t\t<KheopskitContext.Provider value={value}>\n\t\t\t{children}\n\t\t</KheopskitContext.Provider>\n\t);\n};\n","import type { KheopskitState } from \"@kheopskit/core\";\nimport { createContext } from \"react\";\n\nexport const KheopskitContext = createContext<{\n\tstate: KheopskitState;\n} | null>(null);\n","import type { Observable, Subscription } from \"rxjs\";\n\nexport const createStore = <T>(\n\tobservable$: Observable<T>,\n\tinitialValue: T,\n\tserverValue?: T,\n) => {\n\t// Use null as sentinel to indicate we haven't received first emission yet\n\tlet latestValue: T | null = null;\n\tlet subscription: Subscription | null = null;\n\tlet subscriberCount = 0;\n\tconst listeners = new Set<(value: T) => void>();\n\n\tconst ensureSubscription = () => {\n\t\tif (!subscription || subscription.closed) {\n\t\t\tsubscription = observable$.subscribe((value) => {\n\t\t\t\tlatestValue = value;\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\tlistener(value);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\t// Start subscription immediately\n\tensureSubscription();\n\n\t// If observable emitted synchronously, use that value\n\t// Otherwise fall back to initialValue\n\tconst getSnapshot = () => latestValue ?? initialValue;\n\n\t/**\n\t * Returns the server-side snapshot for SSR hydration.\n\t * This prevents hydration mismatches by providing a consistent\n\t * value during server rendering. Must return the same value as\n\t * what the server rendered.\n\t */\n\tconst getServerSnapshot = () => serverValue ?? initialValue;\n\n\tconst subscribe = (callback: (value: T) => void) => {\n\t\tsubscriberCount++;\n\t\tlisteners.add(callback);\n\t\t// Ensure observable subscription is active when someone subscribes\n\t\tensureSubscription();\n\n\t\t// Immediately emit current value (BehaviorSubject semantics)\n\t\tcallback(getSnapshot());\n\n\t\treturn () => {\n\t\t\tsubscriberCount--;\n\t\t\tlisteners.delete(callback);\n\t\t\t// Don't close the observable subscription on unsubscribe\n\t\t\t// Let destroy() handle that when the store is truly being disposed\n\t\t};\n\t};\n\n\tconst destroy = () => {\n\t\t// Only unsubscribe if no one is listening\n\t\t// React StrictMode may call destroy and then immediately resubscribe\n\t\tif (subscriberCount === 0 && subscription) {\n\t\t\tsubscription.unsubscribe();\n\t\t\tsubscription = null;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetSnapshot,\n\t\tgetServerSnapshot,\n\t\tsubscribe,\n\t\tdestroy,\n\t};\n};\n","import { useContext } from \"react\";\nimport { KheopskitContext } from \"./context\";\n\nexport const useWallets = () => {\n\tconst ctx = useContext(KheopskitContext);\n\n\t// useEffect(() => {\n\t// console.debug(\n\t// \"useWallets wallets:%s accounts:%s\",\n\t// ctx?.state.wallets.length ?? 0,\n\t// ctx?.state.accounts.length ?? 0,\n\t// );\n\t// }, [ctx?.state]);\n\n\tif (!ctx)\n\t\tthrow new Error(\"useWallets can't be used without a KheopskitProvider\");\n\n\treturn ctx.state;\n};\n"],"mappings":"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","names":["import_react","import_react"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/KheopskitProvider.tsx","../src/context.ts","../src/createStore.ts","../src/useWallets.ts","../src/createKheopskit.tsx","../src/useAccounts.ts"],"sourcesContent":["export * from \"./createKheopskit\";\nexport * from \"./KheopskitProvider\";\nexport * from \"./useAccounts\";\nexport * from \"./useWallets\";\n","import {\n\tcreateKheopskitStore,\n\tgetKheopskit$,\n\ttype KheopskitConfig,\n\ttype KheopskitState,\n\tresolveConfig,\n} from \"@kheopskit/core\";\nimport {\n\tacceptsCachedAccount,\n\tgetCachedIcon,\n\thydrateAccount,\n\thydrateWallet,\n\tsortAccounts,\n\tsortWallets,\n} from \"@kheopskit/core/internal\";\nimport {\n\ttype FC,\n\ttype PropsWithChildren,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseSyncExternalStore,\n} from \"react\";\nimport { KheopskitContext } from \"./context\";\nimport { createStore } from \"./createStore\";\n\nexport type KheopskitProviderProps = PropsWithChildren & {\n\t/**\n\t * Kheopskit configuration.\n\t *\n\t * @remarks\n\t * Must be a **referentially stable** value — define it once (module scope, a\n\t * `useMemo`, or via {@link createKheopskit}) and pass the same reference. A\n\t * new object literal on every render (`config={{ platforms: [...] }}` inline)\n\t * recreates the underlying store and re-subscribes each render.\n\t */\n\tconfig?: Partial<KheopskitConfig>;\n\t/**\n\t * Cookie string for SSR hydration.\n\t * Pass the request cookie header (e.g., from Next.js headers or TanStack Start)\n\t * to hydrate wallet state on the server.\n\t *\n\t * @remarks\n\t * This value should be stable per render to avoid unnecessary store recreation.\n\t * Compute it once in your server component or layout and pass it down.\n\t *\n\t * @example\n\t * ```tsx\n\t * // Next.js App Router\n\t * const cookieStore = await cookies();\n\t * const ssrCookies = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');\n\t * return <Providers ssrCookies={ssrCookies}>{children}</Providers>\n\t * ```\n\t */\n\tssrCookies?: string;\n};\n\nexport const KheopskitProvider: FC<KheopskitProviderProps> = ({\n\tchildren,\n\tconfig,\n\tssrCookies,\n}) => {\n\tconst resolvedConfig = useMemo(() => resolveConfig(config), [config]);\n\n\t// Dev-only: warn if the `config` prop reference changes between renders. A new\n\t// object each render recreates the store and re-subscribes the whole pipeline\n\t// (re-hydrating, dropping live connection state) on every render. `config` must\n\t// be referentially stable — define it at module scope, memoize it, or use\n\t// createKheopskit() (which passes a stable reference for you).\n\tconst lastConfigRef = useRef(config);\n\tconst hasMountedRef = useRef(false);\n\tuseEffect(() => {\n\t\tif (\n\t\t\thasMountedRef.current &&\n\t\t\tlastConfigRef.current !== config &&\n\t\t\t(typeof process === \"undefined\" || process.env?.NODE_ENV !== \"production\")\n\t\t) {\n\t\t\tconsole.warn(\n\t\t\t\t\"[kheopskit] KheopskitProvider received a new `config` reference; this \" +\n\t\t\t\t\t\"recreates the store and re-subscribes on every render. Pass a \" +\n\t\t\t\t\t\"referentially stable config (module scope, useMemo, or createKheopskit()).\",\n\t\t\t);\n\t\t}\n\t\thasMountedRef.current = true;\n\t\tlastConfigRef.current = config;\n\t}, [config]);\n\n\t// Create a single store for both reading cached state and powering the observable\n\tconst kheopskitStore = useMemo(\n\t\t() =>\n\t\t\tcreateKheopskitStore({\n\t\t\t\tssrCookies,\n\t\t\t\tstorageKey: resolvedConfig.storageKey,\n\t\t\t}),\n\t\t[ssrCookies, resolvedConfig.storageKey],\n\t);\n\n\t// Read cached state from the store for SSR hydration\n\t// This produces wallets WITHOUT localStorage icons (Ethereum wallets have no icon)\n\t// because localStorage isn't available on server\n\tconst serverValue = useMemo<KheopskitState>(() => {\n\t\tif (ssrCookies === undefined) {\n\t\t\treturn {\n\t\t\t\twallets: [],\n\t\t\t\taccounts: [],\n\t\t\t\tconfig: resolvedConfig,\n\t\t\t\tisHydrating: true,\n\t\t\t};\n\t\t}\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets.map(hydrateWallet).sort(sortWallets),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.filter((account) =>\n\t\t\t\t\tacceptsCachedAccount(account, resolvedConfig.platforms),\n\t\t\t\t)\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.sort(sortAccounts),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [ssrCookies, kheopskitStore, resolvedConfig]);\n\n\t// Client-only initial snapshot, read straight from the client cache so a hard\n\t// reload paints the cached wallet/account list on the very first frame instead\n\t// of flashing empty until the live observable produces its first emission —\n\t// which can be asynchronous (e.g. WalletConnect's AppKit is loaded via dynamic\n\t// import, so the underlying combineLatest can't emit synchronously).\n\t//\n\t// We can't derive this from `serverValue`: without SSR cookies that stays empty\n\t// (to keep the server/client hydration markup identical), so the SPA case would\n\t// otherwise render nothing. This snapshot is only ever read on the client via\n\t// getSnapshot, so reading the cache here is safe — and getCachedIcon returns \"\"\n\t// on the server, making the icon enrichment a no-op there.\n\tconst initialValue = useMemo<KheopskitState>(() => {\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets\n\t\t\t\t.map(hydrateWallet)\n\t\t\t\t.map((wallet) => {\n\t\t\t\t\tif (wallet.icon) return wallet;\n\t\t\t\t\tconst cachedIcon = getCachedIcon(wallet.id);\n\t\t\t\t\treturn cachedIcon ? { ...wallet, icon: cachedIcon } : wallet;\n\t\t\t\t})\n\t\t\t\t.sort(sortWallets),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.filter((account) =>\n\t\t\t\t\tacceptsCachedAccount(account, resolvedConfig.platforms),\n\t\t\t\t)\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.sort(sortAccounts),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [kheopskitStore, resolvedConfig]);\n\n\tconst store = useMemo(\n\t\t() =>\n\t\t\tcreateStore(\n\t\t\t\tgetKheopskit$(config, { ssrCookies, store: kheopskitStore }),\n\t\t\t\tinitialValue,\n\t\t\t\tserverValue,\n\t\t\t),\n\t\t[config, ssrCookies, kheopskitStore, initialValue, serverValue],\n\t);\n\n\t// Cleanup store subscriptions when store changes or component unmounts\n\tuseEffect(() => {\n\t\treturn () => store.destroy();\n\t}, [store]);\n\n\tconst state = useSyncExternalStore(\n\t\tstore.subscribe,\n\t\tstore.getSnapshot,\n\t\tstore.getServerSnapshot,\n\t);\n\n\tconst value = useMemo(() => ({ state }), [state]);\n\n\treturn (\n\t\t<KheopskitContext.Provider value={value}>\n\t\t\t{children}\n\t\t</KheopskitContext.Provider>\n\t);\n};\n","import type { KheopskitState } from \"@kheopskit/core\";\nimport { createContext } from \"react\";\n\nexport const KheopskitContext = createContext<{\n\tstate: KheopskitState;\n} | null>(null);\n","import type { Observable, Subscription } from \"rxjs\";\n\nexport const createStore = <T>(\n\tobservable$: Observable<T>,\n\tinitialValue: T,\n\tserverValue?: T,\n) => {\n\t// Use null as sentinel to indicate we haven't received first emission yet\n\tlet latestValue: T | null = null;\n\tlet subscription: Subscription | null = null;\n\tlet subscriberCount = 0;\n\tconst listeners = new Set<(value: T) => void>();\n\n\tconst ensureSubscription = () => {\n\t\tif (!subscription || subscription.closed) {\n\t\t\tsubscription = observable$.subscribe((value) => {\n\t\t\t\tlatestValue = value;\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\tlistener(value);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\t// The subscription is created lazily by subscribe() (which React calls from a\n\t// committed passive effect), never during render. React may render a tree and\n\t// throw it away without committing — StrictMode double-invokes the initial\n\t// render, and concurrent/Suspense can discard renders — so subscribing eagerly\n\t// here (createStore runs inside the Provider's render-phase useMemo) would leak\n\t// the discarded store's observable subscription. Until the first emission,\n\t// getSnapshot falls back to initialValue.\n\tconst getSnapshot = () => latestValue ?? initialValue;\n\n\t/**\n\t * Returns the server-side snapshot for SSR hydration.\n\t * This prevents hydration mismatches by providing a consistent\n\t * value during server rendering. Must return the same value as\n\t * what the server rendered.\n\t */\n\tconst getServerSnapshot = () => serverValue ?? initialValue;\n\n\tconst subscribe = (callback: (value: T) => void) => {\n\t\tsubscriberCount++;\n\t\t// Subscribe to the observable BEFORE registering this listener, so a\n\t\t// synchronous replay (BehaviorSubject / shareReplay) updates latestValue\n\t\t// without double-invoking this callback — the current value is emitted once,\n\t\t// explicitly, just below.\n\t\tensureSubscription();\n\t\tlisteners.add(callback);\n\n\t\t// Immediately emit current value (BehaviorSubject semantics)\n\t\tcallback(getSnapshot());\n\n\t\treturn () => {\n\t\t\tsubscriberCount--;\n\t\t\tlisteners.delete(callback);\n\t\t\t// Don't close the observable subscription on unsubscribe\n\t\t\t// Let destroy() handle that when the store is truly being disposed\n\t\t};\n\t};\n\n\tconst destroy = () => {\n\t\t// Only unsubscribe if no one is listening\n\t\t// React StrictMode may call destroy and then immediately resubscribe\n\t\tif (subscriberCount === 0 && subscription) {\n\t\t\tsubscription.unsubscribe();\n\t\t\tsubscription = null;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetSnapshot,\n\t\tgetServerSnapshot,\n\t\tsubscribe,\n\t\tdestroy,\n\t};\n};\n","import type { KheopskitPlatform, KheopskitState } from \"@kheopskit/core\";\nimport { useContext } from \"react\";\nimport { KheopskitContext } from \"./context\";\n\n/**\n * Returns the current kheopskit state (wallets, accounts, config, isHydrating).\n *\n * Pass the platform tuple as a type argument to recover SDK-precise account and\n * wallet types — `useWallets<typeof platforms>()`. React contexts can't be\n * generic, so without the argument the state is typed with the base\n * (SDK-free) wallet/account shapes.\n */\nexport const useWallets = <\n\tP extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[],\n>(): KheopskitState<P> => {\n\tconst ctx = useContext(KheopskitContext);\n\n\tif (!ctx)\n\t\tthrow new Error(\"useWallets can't be used without a KheopskitProvider\");\n\n\treturn ctx.state as unknown as KheopskitState<P>;\n};\n","import type { KheopskitConfig, KheopskitPlatform } from \"@kheopskit/core\";\nimport type { FC, PropsWithChildren } from \"react\";\nimport { KheopskitProvider } from \"./KheopskitProvider\";\nimport { useWallets } from \"./useWallets\";\n\nexport type CreateKheopskitConfig<P extends readonly KheopskitPlatform[]> =\n\tOmit<Partial<KheopskitConfig<P>>, \"platforms\"> & {\n\t\t/** Platform plugins, e.g. `[polkadot(), solana()]`. Required. */\n\t\tplatforms: P;\n\t};\n\n/**\n * Binds a platform tuple once and returns a `KheopskitProvider` plus hooks\n * (`useWallets`, `useAccounts`) already typed to those platforms — so you don't\n * repeat `useWallets<typeof platforms>()` in every component.\n *\n * @example\n * ```tsx\n * // kheopskit.ts\n * export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({\n * platforms: [polkadot(), ethereum(), solana()],\n * });\n *\n * // anywhere\n * const { accounts } = useWallets(); // accounts are platform-precise, no generic\n * ```\n */\nexport const createKheopskit = <\n\tconst P extends readonly [KheopskitPlatform, ...KheopskitPlatform[]],\n>(\n\tconfig: CreateKheopskitConfig<P>,\n) => {\n\tconst Provider: FC<PropsWithChildren<{ ssrCookies?: string }>> = ({\n\t\tchildren,\n\t\tssrCookies,\n\t}) => (\n\t\t<KheopskitProvider config={config} ssrCookies={ssrCookies}>\n\t\t\t{children}\n\t\t</KheopskitProvider>\n\t);\n\tProvider.displayName = \"KheopskitProvider\";\n\n\treturn {\n\t\tKheopskitProvider: Provider,\n\t\t/** Current state, typed to the bound platform tuple. */\n\t\tuseWallets: () => useWallets<P>(),\n\t\t/** Current accounts, typed to the bound platform tuple. */\n\t\tuseAccounts: () => useWallets<P>().accounts,\n\t};\n};\n","import type { KheopskitPlatform } from \"@kheopskit/core\";\nimport { useWallets } from \"./useWallets\";\n\n/**\n * Convenience hook returning just the accounts from kheopskit state. Pass the\n * platform tuple as a type argument to recover SDK-precise account types —\n * `useAccounts<typeof platforms>()` — or use the pre-typed hook from\n * {@link createKheopskit}.\n */\nexport const useAccounts = <\n\tP extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[],\n>() => useWallets<P>().accounts;\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime"]}
package/dist/index.mjs CHANGED
@@ -1,15 +1,23 @@
1
+ "use client";
2
+
1
3
  // src/KheopskitProvider.tsx
2
4
  import {
3
5
  createKheopskitStore,
4
- getCachedIcon,
5
6
  getKheopskit$,
6
- hydrateAccount,
7
- hydrateWallet,
8
7
  resolveConfig
9
8
  } from "@kheopskit/core";
9
+ import {
10
+ acceptsCachedAccount,
11
+ getCachedIcon,
12
+ hydrateAccount,
13
+ hydrateWallet,
14
+ sortAccounts,
15
+ sortWallets
16
+ } from "@kheopskit/core/internal";
10
17
  import {
11
18
  useEffect,
12
19
  useMemo,
20
+ useRef,
13
21
  useSyncExternalStore
14
22
  } from "react";
15
23
 
@@ -33,13 +41,12 @@ var createStore = (observable$, initialValue, serverValue) => {
33
41
  });
34
42
  }
35
43
  };
36
- ensureSubscription();
37
44
  const getSnapshot = () => latestValue ?? initialValue;
38
45
  const getServerSnapshot = () => serverValue ?? initialValue;
39
46
  const subscribe = (callback) => {
40
47
  subscriberCount++;
41
- listeners.add(callback);
42
48
  ensureSubscription();
49
+ listeners.add(callback);
43
50
  callback(getSnapshot());
44
51
  return () => {
45
52
  subscriberCount--;
@@ -68,6 +75,17 @@ var KheopskitProvider = ({
68
75
  ssrCookies
69
76
  }) => {
70
77
  const resolvedConfig = useMemo(() => resolveConfig(config), [config]);
78
+ const lastConfigRef = useRef(config);
79
+ const hasMountedRef = useRef(false);
80
+ useEffect(() => {
81
+ if (hasMountedRef.current && lastConfigRef.current !== config && (typeof process === "undefined" || process.env?.NODE_ENV !== "production")) {
82
+ console.warn(
83
+ "[kheopskit] KheopskitProvider received a new `config` reference; this recreates the store and re-subscribes on every render. Pass a referentially stable config (module scope, useMemo, or createKheopskit())."
84
+ );
85
+ }
86
+ hasMountedRef.current = true;
87
+ lastConfigRef.current = config;
88
+ }, [config]);
71
89
  const kheopskitStore = useMemo(
72
90
  () => createKheopskitStore({
73
91
  ssrCookies,
@@ -86,32 +104,32 @@ var KheopskitProvider = ({
86
104
  }
87
105
  const cached = kheopskitStore.getCachedState();
88
106
  return {
89
- wallets: cached.wallets.map(hydrateWallet),
90
- accounts: cached.accounts.map(hydrateAccount).filter(
91
- (account) => account.platform !== "polkadot" || resolvedConfig.polkadotAccountTypes.includes(account.type)
92
- ),
107
+ wallets: cached.wallets.map(hydrateWallet).sort(sortWallets),
108
+ accounts: cached.accounts.filter(
109
+ (account) => acceptsCachedAccount(account, resolvedConfig.platforms)
110
+ ).map(hydrateAccount).sort(sortAccounts),
93
111
  config: resolvedConfig,
94
112
  isHydrating: true
95
113
  };
96
114
  }, [ssrCookies, kheopskitStore, resolvedConfig]);
97
115
  const initialValue = useMemo(() => {
98
- const enrichedWallets = serverValue.wallets.map((w) => {
99
- if (!w.icon) {
100
- const cachedIcon = getCachedIcon(w.id);
101
- if (cachedIcon) {
102
- return { ...w, icon: cachedIcon };
103
- }
104
- }
105
- return w;
106
- });
116
+ const cached = kheopskitStore.getCachedState();
107
117
  return {
108
- ...serverValue,
109
- wallets: enrichedWallets
118
+ wallets: cached.wallets.map(hydrateWallet).map((wallet) => {
119
+ if (wallet.icon) return wallet;
120
+ const cachedIcon = getCachedIcon(wallet.id);
121
+ return cachedIcon ? { ...wallet, icon: cachedIcon } : wallet;
122
+ }).sort(sortWallets),
123
+ accounts: cached.accounts.filter(
124
+ (account) => acceptsCachedAccount(account, resolvedConfig.platforms)
125
+ ).map(hydrateAccount).sort(sortAccounts),
126
+ config: resolvedConfig,
127
+ isHydrating: true
110
128
  };
111
- }, [serverValue]);
129
+ }, [kheopskitStore, resolvedConfig]);
112
130
  const store = useMemo(
113
131
  () => createStore(
114
- getKheopskit$(config, ssrCookies, kheopskitStore),
132
+ getKheopskit$(config, { ssrCookies, store: kheopskitStore }),
115
133
  initialValue,
116
134
  serverValue
117
135
  ),
@@ -137,8 +155,30 @@ var useWallets = () => {
137
155
  throw new Error("useWallets can't be used without a KheopskitProvider");
138
156
  return ctx.state;
139
157
  };
158
+
159
+ // src/createKheopskit.tsx
160
+ import { jsx as jsx2 } from "react/jsx-runtime";
161
+ var createKheopskit = (config) => {
162
+ const Provider = ({
163
+ children,
164
+ ssrCookies
165
+ }) => /* @__PURE__ */ jsx2(KheopskitProvider, { config, ssrCookies, children });
166
+ Provider.displayName = "KheopskitProvider";
167
+ return {
168
+ KheopskitProvider: Provider,
169
+ /** Current state, typed to the bound platform tuple. */
170
+ useWallets: () => useWallets(),
171
+ /** Current accounts, typed to the bound platform tuple. */
172
+ useAccounts: () => useWallets().accounts
173
+ };
174
+ };
175
+
176
+ // src/useAccounts.ts
177
+ var useAccounts = () => useWallets().accounts;
140
178
  export {
141
179
  KheopskitProvider,
180
+ createKheopskit,
181
+ useAccounts,
142
182
  useWallets
143
183
  };
144
184
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/KheopskitProvider.tsx","../src/context.ts","../src/createStore.ts","../src/useWallets.ts"],"sourcesContent":["import {\n\tcreateKheopskitStore,\n\tgetCachedIcon,\n\tgetKheopskit$,\n\thydrateAccount,\n\thydrateWallet,\n\ttype KheopskitConfig,\n\ttype KheopskitState,\n\tresolveConfig,\n} from \"@kheopskit/core\";\nimport {\n\ttype FC,\n\ttype PropsWithChildren,\n\tuseEffect,\n\tuseMemo,\n\tuseSyncExternalStore,\n} from \"react\";\nimport { KheopskitContext } from \"./context\";\nimport { createStore } from \"./createStore\";\n\nexport type KheopskitProviderProps = PropsWithChildren & {\n\tconfig?: Partial<KheopskitConfig>;\n\t/**\n\t * Cookie string for SSR hydration.\n\t * Pass the request cookie header (e.g., from Next.js headers or TanStack Start)\n\t * to hydrate wallet state on the server.\n\t *\n\t * @remarks\n\t * This value should be stable per render to avoid unnecessary store recreation.\n\t * Compute it once in your server component or layout and pass it down.\n\t *\n\t * @example\n\t * ```tsx\n\t * // Next.js App Router\n\t * const cookieStore = await cookies();\n\t * const ssrCookies = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');\n\t * return <Providers ssrCookies={ssrCookies}>{children}</Providers>\n\t * ```\n\t */\n\tssrCookies?: string;\n};\n\nexport const KheopskitProvider: FC<KheopskitProviderProps> = ({\n\tchildren,\n\tconfig,\n\tssrCookies,\n}) => {\n\tconst resolvedConfig = useMemo(() => resolveConfig(config), [config]);\n\n\t// Create a single store for both reading cached state and powering the observable\n\tconst kheopskitStore = useMemo(\n\t\t() =>\n\t\t\tcreateKheopskitStore({\n\t\t\t\tssrCookies,\n\t\t\t\tstorageKey: resolvedConfig.storageKey,\n\t\t\t}),\n\t\t[ssrCookies, resolvedConfig.storageKey],\n\t);\n\n\t// Read cached state from the store for SSR hydration\n\t// This produces wallets WITHOUT localStorage icons (Ethereum wallets have no icon)\n\t// because localStorage isn't available on server\n\tconst serverValue = useMemo<KheopskitState>(() => {\n\t\tif (ssrCookies === undefined) {\n\t\t\treturn {\n\t\t\t\twallets: [],\n\t\t\t\taccounts: [],\n\t\t\t\tconfig: resolvedConfig,\n\t\t\t\tisHydrating: true,\n\t\t\t};\n\t\t}\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets.map(hydrateWallet),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.filter(\n\t\t\t\t\t(account) =>\n\t\t\t\t\t\taccount.platform !== \"polkadot\" ||\n\t\t\t\t\t\tresolvedConfig.polkadotAccountTypes.includes(account.type),\n\t\t\t\t),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [ssrCookies, kheopskitStore, resolvedConfig]);\n\n\t// Initial value for client includes localStorage icons\n\t// This is what we WANT the client to render, not what server rendered\n\tconst initialValue = useMemo<KheopskitState>(() => {\n\t\t// On client, enrich wallets with localStorage icons\n\t\t// getCachedIcon returns empty on server (no localStorage), so this is safe\n\t\tconst enrichedWallets = serverValue.wallets.map((w) => {\n\t\t\tif (!w.icon) {\n\t\t\t\tconst cachedIcon = getCachedIcon(w.id);\n\t\t\t\tif (cachedIcon) {\n\t\t\t\t\treturn { ...w, icon: cachedIcon };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn w;\n\t\t});\n\t\treturn {\n\t\t\t...serverValue,\n\t\t\twallets: enrichedWallets,\n\t\t};\n\t}, [serverValue]);\n\n\tconst store = useMemo(\n\t\t() =>\n\t\t\tcreateStore(\n\t\t\t\tgetKheopskit$(config, ssrCookies, kheopskitStore),\n\t\t\t\tinitialValue,\n\t\t\t\tserverValue,\n\t\t\t),\n\t\t[config, ssrCookies, kheopskitStore, initialValue, serverValue],\n\t);\n\n\t// Cleanup store subscriptions when store changes or component unmounts\n\tuseEffect(() => {\n\t\treturn () => store.destroy();\n\t}, [store]);\n\n\tconst state = useSyncExternalStore(\n\t\tstore.subscribe,\n\t\tstore.getSnapshot,\n\t\tstore.getServerSnapshot,\n\t);\n\n\tconst value = useMemo(() => ({ state }), [state]);\n\n\treturn (\n\t\t<KheopskitContext.Provider value={value}>\n\t\t\t{children}\n\t\t</KheopskitContext.Provider>\n\t);\n};\n","import type { KheopskitState } from \"@kheopskit/core\";\nimport { createContext } from \"react\";\n\nexport const KheopskitContext = createContext<{\n\tstate: KheopskitState;\n} | null>(null);\n","import type { Observable, Subscription } from \"rxjs\";\n\nexport const createStore = <T>(\n\tobservable$: Observable<T>,\n\tinitialValue: T,\n\tserverValue?: T,\n) => {\n\t// Use null as sentinel to indicate we haven't received first emission yet\n\tlet latestValue: T | null = null;\n\tlet subscription: Subscription | null = null;\n\tlet subscriberCount = 0;\n\tconst listeners = new Set<(value: T) => void>();\n\n\tconst ensureSubscription = () => {\n\t\tif (!subscription || subscription.closed) {\n\t\t\tsubscription = observable$.subscribe((value) => {\n\t\t\t\tlatestValue = value;\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\tlistener(value);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\t// Start subscription immediately\n\tensureSubscription();\n\n\t// If observable emitted synchronously, use that value\n\t// Otherwise fall back to initialValue\n\tconst getSnapshot = () => latestValue ?? initialValue;\n\n\t/**\n\t * Returns the server-side snapshot for SSR hydration.\n\t * This prevents hydration mismatches by providing a consistent\n\t * value during server rendering. Must return the same value as\n\t * what the server rendered.\n\t */\n\tconst getServerSnapshot = () => serverValue ?? initialValue;\n\n\tconst subscribe = (callback: (value: T) => void) => {\n\t\tsubscriberCount++;\n\t\tlisteners.add(callback);\n\t\t// Ensure observable subscription is active when someone subscribes\n\t\tensureSubscription();\n\n\t\t// Immediately emit current value (BehaviorSubject semantics)\n\t\tcallback(getSnapshot());\n\n\t\treturn () => {\n\t\t\tsubscriberCount--;\n\t\t\tlisteners.delete(callback);\n\t\t\t// Don't close the observable subscription on unsubscribe\n\t\t\t// Let destroy() handle that when the store is truly being disposed\n\t\t};\n\t};\n\n\tconst destroy = () => {\n\t\t// Only unsubscribe if no one is listening\n\t\t// React StrictMode may call destroy and then immediately resubscribe\n\t\tif (subscriberCount === 0 && subscription) {\n\t\t\tsubscription.unsubscribe();\n\t\t\tsubscription = null;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetSnapshot,\n\t\tgetServerSnapshot,\n\t\tsubscribe,\n\t\tdestroy,\n\t};\n};\n","import { useContext } from \"react\";\nimport { KheopskitContext } from \"./context\";\n\nexport const useWallets = () => {\n\tconst ctx = useContext(KheopskitContext);\n\n\t// useEffect(() => {\n\t// console.debug(\n\t// \"useWallets wallets:%s accounts:%s\",\n\t// ctx?.state.wallets.length ?? 0,\n\t// ctx?.state.accounts.length ?? 0,\n\t// );\n\t// }, [ctx?.state]);\n\n\tif (!ctx)\n\t\tthrow new Error(\"useWallets can't be used without a KheopskitProvider\");\n\n\treturn ctx.state;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/KheopskitProvider.tsx","../src/context.ts","../src/createStore.ts","../src/useWallets.ts","../src/createKheopskit.tsx","../src/useAccounts.ts"],"sourcesContent":["import {\n\tcreateKheopskitStore,\n\tgetKheopskit$,\n\ttype KheopskitConfig,\n\ttype KheopskitState,\n\tresolveConfig,\n} from \"@kheopskit/core\";\nimport {\n\tacceptsCachedAccount,\n\tgetCachedIcon,\n\thydrateAccount,\n\thydrateWallet,\n\tsortAccounts,\n\tsortWallets,\n} from \"@kheopskit/core/internal\";\nimport {\n\ttype FC,\n\ttype PropsWithChildren,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseSyncExternalStore,\n} from \"react\";\nimport { KheopskitContext } from \"./context\";\nimport { createStore } from \"./createStore\";\n\nexport type KheopskitProviderProps = PropsWithChildren & {\n\t/**\n\t * Kheopskit configuration.\n\t *\n\t * @remarks\n\t * Must be a **referentially stable** value — define it once (module scope, a\n\t * `useMemo`, or via {@link createKheopskit}) and pass the same reference. A\n\t * new object literal on every render (`config={{ platforms: [...] }}` inline)\n\t * recreates the underlying store and re-subscribes each render.\n\t */\n\tconfig?: Partial<KheopskitConfig>;\n\t/**\n\t * Cookie string for SSR hydration.\n\t * Pass the request cookie header (e.g., from Next.js headers or TanStack Start)\n\t * to hydrate wallet state on the server.\n\t *\n\t * @remarks\n\t * This value should be stable per render to avoid unnecessary store recreation.\n\t * Compute it once in your server component or layout and pass it down.\n\t *\n\t * @example\n\t * ```tsx\n\t * // Next.js App Router\n\t * const cookieStore = await cookies();\n\t * const ssrCookies = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');\n\t * return <Providers ssrCookies={ssrCookies}>{children}</Providers>\n\t * ```\n\t */\n\tssrCookies?: string;\n};\n\nexport const KheopskitProvider: FC<KheopskitProviderProps> = ({\n\tchildren,\n\tconfig,\n\tssrCookies,\n}) => {\n\tconst resolvedConfig = useMemo(() => resolveConfig(config), [config]);\n\n\t// Dev-only: warn if the `config` prop reference changes between renders. A new\n\t// object each render recreates the store and re-subscribes the whole pipeline\n\t// (re-hydrating, dropping live connection state) on every render. `config` must\n\t// be referentially stable — define it at module scope, memoize it, or use\n\t// createKheopskit() (which passes a stable reference for you).\n\tconst lastConfigRef = useRef(config);\n\tconst hasMountedRef = useRef(false);\n\tuseEffect(() => {\n\t\tif (\n\t\t\thasMountedRef.current &&\n\t\t\tlastConfigRef.current !== config &&\n\t\t\t(typeof process === \"undefined\" || process.env?.NODE_ENV !== \"production\")\n\t\t) {\n\t\t\tconsole.warn(\n\t\t\t\t\"[kheopskit] KheopskitProvider received a new `config` reference; this \" +\n\t\t\t\t\t\"recreates the store and re-subscribes on every render. Pass a \" +\n\t\t\t\t\t\"referentially stable config (module scope, useMemo, or createKheopskit()).\",\n\t\t\t);\n\t\t}\n\t\thasMountedRef.current = true;\n\t\tlastConfigRef.current = config;\n\t}, [config]);\n\n\t// Create a single store for both reading cached state and powering the observable\n\tconst kheopskitStore = useMemo(\n\t\t() =>\n\t\t\tcreateKheopskitStore({\n\t\t\t\tssrCookies,\n\t\t\t\tstorageKey: resolvedConfig.storageKey,\n\t\t\t}),\n\t\t[ssrCookies, resolvedConfig.storageKey],\n\t);\n\n\t// Read cached state from the store for SSR hydration\n\t// This produces wallets WITHOUT localStorage icons (Ethereum wallets have no icon)\n\t// because localStorage isn't available on server\n\tconst serverValue = useMemo<KheopskitState>(() => {\n\t\tif (ssrCookies === undefined) {\n\t\t\treturn {\n\t\t\t\twallets: [],\n\t\t\t\taccounts: [],\n\t\t\t\tconfig: resolvedConfig,\n\t\t\t\tisHydrating: true,\n\t\t\t};\n\t\t}\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets.map(hydrateWallet).sort(sortWallets),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.filter((account) =>\n\t\t\t\t\tacceptsCachedAccount(account, resolvedConfig.platforms),\n\t\t\t\t)\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.sort(sortAccounts),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [ssrCookies, kheopskitStore, resolvedConfig]);\n\n\t// Client-only initial snapshot, read straight from the client cache so a hard\n\t// reload paints the cached wallet/account list on the very first frame instead\n\t// of flashing empty until the live observable produces its first emission —\n\t// which can be asynchronous (e.g. WalletConnect's AppKit is loaded via dynamic\n\t// import, so the underlying combineLatest can't emit synchronously).\n\t//\n\t// We can't derive this from `serverValue`: without SSR cookies that stays empty\n\t// (to keep the server/client hydration markup identical), so the SPA case would\n\t// otherwise render nothing. This snapshot is only ever read on the client via\n\t// getSnapshot, so reading the cache here is safe — and getCachedIcon returns \"\"\n\t// on the server, making the icon enrichment a no-op there.\n\tconst initialValue = useMemo<KheopskitState>(() => {\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets\n\t\t\t\t.map(hydrateWallet)\n\t\t\t\t.map((wallet) => {\n\t\t\t\t\tif (wallet.icon) return wallet;\n\t\t\t\t\tconst cachedIcon = getCachedIcon(wallet.id);\n\t\t\t\t\treturn cachedIcon ? { ...wallet, icon: cachedIcon } : wallet;\n\t\t\t\t})\n\t\t\t\t.sort(sortWallets),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.filter((account) =>\n\t\t\t\t\tacceptsCachedAccount(account, resolvedConfig.platforms),\n\t\t\t\t)\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.sort(sortAccounts),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [kheopskitStore, resolvedConfig]);\n\n\tconst store = useMemo(\n\t\t() =>\n\t\t\tcreateStore(\n\t\t\t\tgetKheopskit$(config, { ssrCookies, store: kheopskitStore }),\n\t\t\t\tinitialValue,\n\t\t\t\tserverValue,\n\t\t\t),\n\t\t[config, ssrCookies, kheopskitStore, initialValue, serverValue],\n\t);\n\n\t// Cleanup store subscriptions when store changes or component unmounts\n\tuseEffect(() => {\n\t\treturn () => store.destroy();\n\t}, [store]);\n\n\tconst state = useSyncExternalStore(\n\t\tstore.subscribe,\n\t\tstore.getSnapshot,\n\t\tstore.getServerSnapshot,\n\t);\n\n\tconst value = useMemo(() => ({ state }), [state]);\n\n\treturn (\n\t\t<KheopskitContext.Provider value={value}>\n\t\t\t{children}\n\t\t</KheopskitContext.Provider>\n\t);\n};\n","import type { KheopskitState } from \"@kheopskit/core\";\nimport { createContext } from \"react\";\n\nexport const KheopskitContext = createContext<{\n\tstate: KheopskitState;\n} | null>(null);\n","import type { Observable, Subscription } from \"rxjs\";\n\nexport const createStore = <T>(\n\tobservable$: Observable<T>,\n\tinitialValue: T,\n\tserverValue?: T,\n) => {\n\t// Use null as sentinel to indicate we haven't received first emission yet\n\tlet latestValue: T | null = null;\n\tlet subscription: Subscription | null = null;\n\tlet subscriberCount = 0;\n\tconst listeners = new Set<(value: T) => void>();\n\n\tconst ensureSubscription = () => {\n\t\tif (!subscription || subscription.closed) {\n\t\t\tsubscription = observable$.subscribe((value) => {\n\t\t\t\tlatestValue = value;\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\tlistener(value);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\t// The subscription is created lazily by subscribe() (which React calls from a\n\t// committed passive effect), never during render. React may render a tree and\n\t// throw it away without committing — StrictMode double-invokes the initial\n\t// render, and concurrent/Suspense can discard renders — so subscribing eagerly\n\t// here (createStore runs inside the Provider's render-phase useMemo) would leak\n\t// the discarded store's observable subscription. Until the first emission,\n\t// getSnapshot falls back to initialValue.\n\tconst getSnapshot = () => latestValue ?? initialValue;\n\n\t/**\n\t * Returns the server-side snapshot for SSR hydration.\n\t * This prevents hydration mismatches by providing a consistent\n\t * value during server rendering. Must return the same value as\n\t * what the server rendered.\n\t */\n\tconst getServerSnapshot = () => serverValue ?? initialValue;\n\n\tconst subscribe = (callback: (value: T) => void) => {\n\t\tsubscriberCount++;\n\t\t// Subscribe to the observable BEFORE registering this listener, so a\n\t\t// synchronous replay (BehaviorSubject / shareReplay) updates latestValue\n\t\t// without double-invoking this callback — the current value is emitted once,\n\t\t// explicitly, just below.\n\t\tensureSubscription();\n\t\tlisteners.add(callback);\n\n\t\t// Immediately emit current value (BehaviorSubject semantics)\n\t\tcallback(getSnapshot());\n\n\t\treturn () => {\n\t\t\tsubscriberCount--;\n\t\t\tlisteners.delete(callback);\n\t\t\t// Don't close the observable subscription on unsubscribe\n\t\t\t// Let destroy() handle that when the store is truly being disposed\n\t\t};\n\t};\n\n\tconst destroy = () => {\n\t\t// Only unsubscribe if no one is listening\n\t\t// React StrictMode may call destroy and then immediately resubscribe\n\t\tif (subscriberCount === 0 && subscription) {\n\t\t\tsubscription.unsubscribe();\n\t\t\tsubscription = null;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetSnapshot,\n\t\tgetServerSnapshot,\n\t\tsubscribe,\n\t\tdestroy,\n\t};\n};\n","import type { KheopskitPlatform, KheopskitState } from \"@kheopskit/core\";\nimport { useContext } from \"react\";\nimport { KheopskitContext } from \"./context\";\n\n/**\n * Returns the current kheopskit state (wallets, accounts, config, isHydrating).\n *\n * Pass the platform tuple as a type argument to recover SDK-precise account and\n * wallet types — `useWallets<typeof platforms>()`. React contexts can't be\n * generic, so without the argument the state is typed with the base\n * (SDK-free) wallet/account shapes.\n */\nexport const useWallets = <\n\tP extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[],\n>(): KheopskitState<P> => {\n\tconst ctx = useContext(KheopskitContext);\n\n\tif (!ctx)\n\t\tthrow new Error(\"useWallets can't be used without a KheopskitProvider\");\n\n\treturn ctx.state as unknown as KheopskitState<P>;\n};\n","import type { KheopskitConfig, KheopskitPlatform } from \"@kheopskit/core\";\nimport type { FC, PropsWithChildren } from \"react\";\nimport { KheopskitProvider } from \"./KheopskitProvider\";\nimport { useWallets } from \"./useWallets\";\n\nexport type CreateKheopskitConfig<P extends readonly KheopskitPlatform[]> =\n\tOmit<Partial<KheopskitConfig<P>>, \"platforms\"> & {\n\t\t/** Platform plugins, e.g. `[polkadot(), solana()]`. Required. */\n\t\tplatforms: P;\n\t};\n\n/**\n * Binds a platform tuple once and returns a `KheopskitProvider` plus hooks\n * (`useWallets`, `useAccounts`) already typed to those platforms — so you don't\n * repeat `useWallets<typeof platforms>()` in every component.\n *\n * @example\n * ```tsx\n * // kheopskit.ts\n * export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({\n * platforms: [polkadot(), ethereum(), solana()],\n * });\n *\n * // anywhere\n * const { accounts } = useWallets(); // accounts are platform-precise, no generic\n * ```\n */\nexport const createKheopskit = <\n\tconst P extends readonly [KheopskitPlatform, ...KheopskitPlatform[]],\n>(\n\tconfig: CreateKheopskitConfig<P>,\n) => {\n\tconst Provider: FC<PropsWithChildren<{ ssrCookies?: string }>> = ({\n\t\tchildren,\n\t\tssrCookies,\n\t}) => (\n\t\t<KheopskitProvider config={config} ssrCookies={ssrCookies}>\n\t\t\t{children}\n\t\t</KheopskitProvider>\n\t);\n\tProvider.displayName = \"KheopskitProvider\";\n\n\treturn {\n\t\tKheopskitProvider: Provider,\n\t\t/** Current state, typed to the bound platform tuple. */\n\t\tuseWallets: () => useWallets<P>(),\n\t\t/** Current accounts, typed to the bound platform tuple. */\n\t\tuseAccounts: () => useWallets<P>().accounts,\n\t};\n};\n","import type { KheopskitPlatform } from \"@kheopskit/core\";\nimport { useWallets } from \"./useWallets\";\n\n/**\n * Convenience hook returning just the accounts from kheopskit state. Pass the\n * platform tuple as a type argument to recover SDK-precise account types —\n * `useAccounts<typeof platforms>()` — or use the pre-typed hook from\n * {@link createKheopskit}.\n */\nexport const useAccounts = <\n\tP extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[],\n>() => useWallets<P>().accounts;\n"],"mappings":"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","names":["jsx"]}
package/package.json CHANGED
@@ -1,20 +1,28 @@
1
1
  {
2
2
  "name": "@kheopskit/react",
3
- "version": "3.0.1",
3
+ "version": "5.0.0",
4
4
  "description": "",
5
5
  "private": false,
6
+ "sideEffects": false,
6
7
  "files": [
7
- "dist"
8
+ "dist",
9
+ "README.md"
8
10
  ],
9
11
  "main": "./dist/index.js",
10
12
  "module": "./dist/index.mjs",
11
13
  "types": "./dist/index.d.ts",
12
14
  "exports": {
13
15
  ".": {
14
- "types": "./dist/index.d.ts",
15
- "import": "./dist/index.mjs",
16
- "require": "./dist/index.js"
17
- }
16
+ "import": {
17
+ "types": "./dist/index.d.mts",
18
+ "default": "./dist/index.mjs"
19
+ },
20
+ "require": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "./package.json": "./package.json"
18
26
  },
19
27
  "publishConfig": {
20
28
  "access": "public"
@@ -27,7 +35,7 @@
27
35
  },
28
36
  "license": "ISC",
29
37
  "peerDependencies": {
30
- "@kheopskit/core": "^1.0.1",
38
+ "@kheopskit/core": "^5.0.0",
31
39
  "react": ">=18.0.0",
32
40
  "react-dom": ">=18.0.0",
33
41
  "rxjs": ">=7.0.0"
@@ -38,7 +46,7 @@
38
46
  "react": "^19.2.7",
39
47
  "react-dom": "^19.2.7",
40
48
  "rxjs": "^7.8.2",
41
- "@kheopskit/core": "1.0.1"
49
+ "@kheopskit/core": "5.0.0"
42
50
  },
43
51
  "tsup": {
44
52
  "entry": [
@@ -53,10 +61,13 @@
53
61
  "esm",
54
62
  "cjs"
55
63
  ],
56
- "target": "es2020"
64
+ "target": "es2020",
65
+ "banner": {
66
+ "js": "\"use client\";"
67
+ }
57
68
  },
58
69
  "scripts": {
59
- "test": "echo \"Error: no test specified\" && exit 1",
70
+ "test": "vitest run --root=../.. packages/react",
60
71
  "dev": "tsup --watch",
61
72
  "build": "tsup",
62
73
  "clean": "rm -rf ./dist && rm -rf ./node_modules",