@capxul/sdk-react 0.1.0-alpha.0 → 0.1.0-alpha.10

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.cjs CHANGED
@@ -5,12 +5,52 @@ var react = require('react');
5
5
  var sdk = require('@capxul/sdk');
6
6
  var reactQuery = require('@tanstack/react-query');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
+ var react$2 = require('convex/react');
8
9
  var errors = require('@capxul/sdk/errors');
9
10
  var react$1 = require('@xstate/react');
10
11
  var viem = require('viem');
11
12
  var accounts = require('viem/accounts');
12
13
 
13
14
  // src/provider.tsx
15
+ var CapxulClientContext = react.createContext(null);
16
+ function CapxulClientProvider({
17
+ client,
18
+ children
19
+ }) {
20
+ return /* @__PURE__ */ jsxRuntime.jsx(CapxulClientContext.Provider, { value: client, children });
21
+ }
22
+ function useCapxul() {
23
+ const client = react.useContext(CapxulClientContext);
24
+ if (!client) {
25
+ throw new Error(
26
+ "useCapxul() was called outside a <CapxulProvider>. Wrap your app in <CapxulProvider config={...}> before rendering hooks from @capxul/sdk-react."
27
+ );
28
+ }
29
+ return client;
30
+ }
31
+ var CapxulTransportContext = react.createContext(null);
32
+ function CapxulTransportProvider({
33
+ transport,
34
+ children
35
+ }) {
36
+ return /* @__PURE__ */ jsxRuntime.jsx(CapxulTransportContext.Provider, { value: transport, children });
37
+ }
38
+ function useCapxulStatus() {
39
+ const transport = react.useContext(CapxulTransportContext);
40
+ return react.useSyncExternalStore(
41
+ (listener) => {
42
+ if (!transport) return () => {
43
+ };
44
+ return transport.subscribe(listener);
45
+ },
46
+ () => transport?.getState() ?? FALLBACK_READY,
47
+ () => transport?.getState() ?? FALLBACK_READY
48
+ );
49
+ }
50
+ var FALLBACK_READY = Object.freeze({
51
+ status: "ready",
52
+ runtime: { authBaseUrl: "", convexUrl: "" }
53
+ });
14
54
 
15
55
  // ../config/src/errors.ts
16
56
  var CapxulError = class extends Error {
@@ -70,8 +110,35 @@ var Errors = {
70
110
  "Idempotency key was already used for a different request",
71
111
  { details }
72
112
  ),
73
- emailDeliveryFailed: (detail) => new CapxulError("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
74
- internalError: (reason) => new CapxulError("INTERNAL_ERROR", `Internal error: ${reason}`)
113
+ emailDeliveryFailed: (detail, details) => new CapxulError("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
114
+ details
115
+ }),
116
+ rateLimited: (details) => new CapxulError("RATE_LIMITED", "Request was rate limited", {
117
+ details: { ...details }
118
+ }),
119
+ internalError: (reason) => new CapxulError("INTERNAL_ERROR", `Internal error: ${reason}`),
120
+ /**
121
+ * Verification gate. Surfaced when a request hits a verification
122
+ * boundary the actor cannot cross under their current state. Two
123
+ * variants share this code:
124
+ *
125
+ * - Rail gate (Withdrawals v1 W2, #465): the resolved
126
+ * `external_account.kind` routes to a withdrawal rail (e.g.
127
+ * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
128
+ * `details.rail` + `details.currentKind`.
129
+ * - KYC tier gate (legacy / future): the actor's KYC tier is below
130
+ * the required tier. Carries `details.requiredTier`.
131
+ *
132
+ * Code is shared because both expose the same UX shape ("you cannot
133
+ * proceed until verification advances"); the `details.*` keys
134
+ * differentiate the route.
135
+ */
136
+ verificationRequired: (details) => {
137
+ const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
138
+ return new CapxulError("VERIFICATION_REQUIRED", message, {
139
+ details: { ...details }
140
+ });
141
+ }
75
142
  };
76
143
 
77
144
  // ../config/src/org-roles.ts
@@ -83,56 +150,137 @@ function roleKeyFromLabel(label) {
83
150
  roleKeyFromLabel("OWNER");
84
151
  roleKeyFromLabel("FINANCE_MANAGER");
85
152
  roleKeyFromLabel("TEAM_LEAD");
86
- var CapxulClientContext = react.createContext(null);
87
- function CapxulClientProvider({
88
- client,
89
- children
90
- }) {
91
- return /* @__PURE__ */ jsxRuntime.jsx(CapxulClientContext.Provider, { value: client, children });
153
+
154
+ // src/config.ts
155
+ function createCapxulConfig(input) {
156
+ assertOnlyKnownKeys(input);
157
+ assertModeRequiredFields(input);
158
+ return Object.freeze({ ...input });
92
159
  }
93
- function useCapxul() {
94
- const client = react.useContext(CapxulClientContext);
95
- if (!client) {
96
- throw new Error(
97
- "useCapxul() was called outside a <CapxulProvider>. Wrap your app in <CapxulProvider config={...}> before rendering hooks from @capxul/sdk-react."
98
- );
160
+ var ALLOWED_BROWSER_CONFIG_KEYS = [
161
+ "mode",
162
+ "authBaseUrl",
163
+ "convexUrl",
164
+ "publishableKey",
165
+ "bootstrapUrl",
166
+ "fetchImpl"
167
+ ];
168
+ function assertOnlyKnownKeys(input) {
169
+ const candidate = input;
170
+ const allowed = new Set(ALLOWED_BROWSER_CONFIG_KEYS);
171
+ const unknown = Object.keys(candidate).filter((key) => !allowed.has(key));
172
+ if (unknown.length === 0) return;
173
+ throw Errors.invalidInput(
174
+ "config",
175
+ `Browser Capxul config contains unknown or secret/server-only fields: ${unknown.join(", ")}. Allowed keys: ${ALLOWED_BROWSER_CONFIG_KEYS.join(", ")}.`
176
+ );
177
+ }
178
+ function assertModeRequiredFields(input) {
179
+ switch (input.mode) {
180
+ case "build-time-urls": {
181
+ if (!input.authBaseUrl || input.authBaseUrl.trim().length === 0) {
182
+ throw Errors.invalidInput("authBaseUrl", "non-empty string required.");
183
+ }
184
+ if (!input.convexUrl || input.convexUrl.trim().length === 0) {
185
+ throw Errors.invalidInput("convexUrl", "non-empty string required.");
186
+ }
187
+ return;
188
+ }
189
+ case "publishable-key": {
190
+ if (!input.publishableKey || input.publishableKey.trim().length === 0) {
191
+ throw Errors.invalidInput("publishableKey", "non-empty string required.");
192
+ }
193
+ return;
194
+ }
195
+ default: {
196
+ const value = input;
197
+ throw Errors.internalError(
198
+ `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
199
+ );
200
+ }
99
201
  }
100
- return client;
101
202
  }
102
- var CapxulTransportContext = react.createContext(null);
103
- function CapxulTransportProvider({
104
- transport,
105
- children
106
- }) {
107
- return /* @__PURE__ */ jsxRuntime.jsx(CapxulTransportContext.Provider, { value: transport, children });
203
+ function createReactDataClient(convexUrl, sessionStore) {
204
+ const client = new react$2.ConvexReactClient(convexUrl);
205
+ const refreshAuth = () => {
206
+ const session = sessionStore.get();
207
+ const jwt = session?.convexJwt;
208
+ if (jwt) {
209
+ client.setAuth(() => Promise.resolve(jwt));
210
+ } else {
211
+ client.clearAuth();
212
+ }
213
+ };
214
+ refreshAuth();
215
+ return {
216
+ query: (name, args) => client.query(name, args),
217
+ mutation: (name, args) => client.mutation(name, args),
218
+ action: (name, args) => client.action(name, args),
219
+ refreshAuth,
220
+ close: () => {
221
+ void client.close();
222
+ }
223
+ };
108
224
  }
109
- function useCapxulStatus() {
110
- const transport = react.useContext(CapxulTransportContext);
111
- return react.useSyncExternalStore(
112
- (listener) => {
113
- if (!transport) return () => {
114
- };
115
- return transport.subscribe(listener);
225
+ function createLazyReactDataClient(transport, sessionStore, createClient = createReactDataClient) {
226
+ let client = null;
227
+ let initializeClient = null;
228
+ let closed = false;
229
+ function closedError() {
230
+ return new Error("Capxul React data client is closed");
231
+ }
232
+ async function getClient() {
233
+ if (closed) throw closedError();
234
+ if (client) return client;
235
+ initializeClient ??= (async () => {
236
+ const runtime = await transport.ensureRuntime();
237
+ if (closed) throw closedError();
238
+ const nextClient = createClient(runtime.convexUrl, sessionStore);
239
+ if (closed) {
240
+ nextClient.close();
241
+ throw closedError();
242
+ }
243
+ client = nextClient;
244
+ return nextClient;
245
+ })().catch((error) => {
246
+ if (!closed) {
247
+ initializeClient = null;
248
+ }
249
+ throw error;
250
+ });
251
+ return initializeClient;
252
+ }
253
+ return {
254
+ query: async (name, args) => (await getClient()).query(name, args),
255
+ mutation: async (name, args) => (await getClient()).mutation(name, args),
256
+ action: async (name, args) => (await getClient()).action?.(name, args),
257
+ refreshAuth: () => {
258
+ client?.refreshAuth();
116
259
  },
117
- () => transport?.getState() ?? FALLBACK_READY,
118
- () => transport?.getState() ?? FALLBACK_READY
119
- );
260
+ close: () => {
261
+ closed = true;
262
+ client?.close();
263
+ client = null;
264
+ }
265
+ };
120
266
  }
121
- var FALLBACK_READY = Object.freeze({
122
- status: "ready",
123
- runtime: { authBaseUrl: "", convexUrl: "" }
124
- });
125
267
  function CapxulProvider({
126
268
  config,
127
- publishableKey,
128
- browserConfig,
269
+ sessionStore,
129
270
  queryClient,
130
271
  children
131
272
  }) {
273
+ const defaultSessionStore = react.useMemo(() => createMemorySessionStore(), []);
274
+ const effectiveSessionStore = sessionStore ?? defaultSessionStore;
132
275
  const wiring = react.useMemo(
133
- () => buildWiring({ config, publishableKey, browserConfig }),
134
- [config, publishableKey, browserConfig]
276
+ () => buildWiring(config, effectiveSessionStore),
277
+ [config, effectiveSessionStore]
135
278
  );
279
+ react.useEffect(() => {
280
+ return () => {
281
+ wiring.dataClient?.close();
282
+ };
283
+ }, [wiring]);
136
284
  const defaultClient = react.useMemo(
137
285
  () => new reactQuery.QueryClient({
138
286
  defaultOptions: { queries: { staleTime: 3e4 } }
@@ -140,51 +288,60 @@ function CapxulProvider({
140
288
  []
141
289
  );
142
290
  const effectiveClient = queryClient ?? defaultClient;
143
- const inner = /* @__PURE__ */ jsxRuntime.jsx(CapxulClientProvider, { client: wiring.client, children });
144
- return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: effectiveClient, children: wiring.transport ? /* @__PURE__ */ jsxRuntime.jsx(CapxulTransportProvider, { transport: wiring.transport, children: inner }) : inner });
291
+ return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: effectiveClient, children: /* @__PURE__ */ jsxRuntime.jsx(CapxulTransportProvider, { transport: wiring.transport, children: /* @__PURE__ */ jsxRuntime.jsx(CapxulClientProvider, { client: wiring.client, children }) }) });
145
292
  }
146
- function buildWiring({
147
- config,
148
- publishableKey,
149
- browserConfig
150
- }) {
151
- const sources = [
152
- config !== void 0,
153
- publishableKey !== void 0,
154
- browserConfig !== void 0
155
- ].filter(Boolean).length;
156
- if (sources === 0) {
157
- throw Errors.invalidInput(
158
- "CapxulProvider",
159
- "Pass exactly one of `config`, `publishableKey`, or `browserConfig`."
160
- );
161
- }
162
- if (sources > 1) {
163
- throw Errors.invalidInput(
164
- "CapxulProvider",
165
- "`config`, `publishableKey`, and `browserConfig` are mutually exclusive \u2014 pass exactly one."
166
- );
167
- }
168
- if (config !== void 0) {
169
- return {
170
- client: sdk.createCapxulClient(config),
171
- transport: null
172
- };
173
- }
174
- const browserCfg = browserConfig ?? {
175
- mode: "publishable-key",
176
- // The narrowing above (`sources === 0` rejected; `config` not
177
- // present) guarantees `publishableKey` is set on this branch.
178
- publishableKey
179
- };
180
- const transport = sdk.makeHttpTransport(browserCfg);
293
+ function buildWiring(config, sessionStore) {
294
+ const validated = createCapxulConfig(config);
295
+ const transport = sdk.makeHttpTransport(validated);
296
+ const dataClient = validated.mode === "build-time-urls" ? createReactDataClient(validated.convexUrl, sessionStore) : createLazyReactDataClient(transport, sessionStore);
181
297
  const sdkConfig = {
182
298
  _transport: transport,
183
- publishableKey: browserCfg.mode === "publishable-key" ? browserCfg.publishableKey : void 0
299
+ publishableKey: validated.mode === "publishable-key" ? validated.publishableKey : void 0,
300
+ data: dataClient ?? void 0,
301
+ auth: validated.mode === "build-time-urls" || validated.mode === "publishable-key" ? {
302
+ // The auth client builds the BetterAuth root URL from this
303
+ // value. Convex's `.cloud` URL is the wrong host (BetterAuth
304
+ // is mounted on the `.site` URL), but the build-time-urls
305
+ // transport already encodes the correct `authBaseUrl` via
306
+ // its discriminated union. We pass `convexUrl` here only so
307
+ // `core/auth.ts`'s `createTransportProvider` short-circuits
308
+ // to the externally-injected `_transport` cache slot.
309
+ baseUrl: transport.authBaseUrl,
310
+ sessionStore,
311
+ createDataClient: async (_session) => {
312
+ dataClient.refreshAuth();
313
+ return dataClient;
314
+ }
315
+ } : void 0
184
316
  };
317
+ const client = sdk.createCapxulClient(sdkConfig);
318
+ if (dataClient) {
319
+ const originalSignOut = client.auth.signOut;
320
+ Object.assign(client.auth, {
321
+ signOut: async () => {
322
+ const result = await originalSignOut();
323
+ dataClient.refreshAuth();
324
+ sdkConfig.data = dataClient;
325
+ return result;
326
+ }
327
+ });
328
+ }
185
329
  return {
186
- client: sdk.createCapxulClient(sdkConfig),
187
- transport
330
+ client,
331
+ transport,
332
+ dataClient
333
+ };
334
+ }
335
+ function createMemorySessionStore() {
336
+ let current = null;
337
+ return {
338
+ get: () => current,
339
+ set: (session) => {
340
+ current = session;
341
+ },
342
+ clear: () => {
343
+ current = null;
344
+ }
188
345
  };
189
346
  }
190
347
  function notImplementedQuery(hookName) {
@@ -274,8 +431,12 @@ function useMe() {
274
431
  staleTime: 3e4
275
432
  });
276
433
  }
277
- function useAccount(_accountId) {
278
- return notImplementedQuery("useAccount");
434
+ function useAccount(accountId) {
435
+ const capxul = useCapxul();
436
+ return useSdkQuery(
437
+ () => accountId !== void 0 ? capxul.accounts.retrieve(accountId) : capxul.me.get(),
438
+ [capxul, accountId]
439
+ );
279
440
  }
280
441
  function useOrganization(_organizationId) {
281
442
  return notImplementedQuery("useOrganization");
@@ -283,8 +444,12 @@ function useOrganization(_organizationId) {
283
444
  function useMember(_args) {
284
445
  return notImplementedQuery("useMember");
285
446
  }
286
- function useSafe(_safeId) {
287
- return notImplementedQuery("useSafe");
447
+ function useSafe(safeId) {
448
+ const capxul = useCapxul();
449
+ return useSdkQuery(
450
+ () => capxul.accounts.safes.retrieve(safeId),
451
+ [capxul, safeId]
452
+ );
288
453
  }
289
454
  function useTreasury(_organizationId) {
290
455
  return notImplementedQuery("useTreasury");
@@ -298,8 +463,15 @@ function useKycProfile(_accountId) {
298
463
  function useKybProfile(_organizationId) {
299
464
  return notImplementedQuery("useKybProfile");
300
465
  }
301
- function useExternalAccount(_args) {
302
- return notImplementedQuery("useExternalAccount");
466
+ function useExternalAccount(args) {
467
+ const capxul = useCapxul();
468
+ return useSdkQuery(
469
+ () => args.ownerKind === "account" ? capxul.externalAccounts.retrieve(args.externalAccountId) : capxul.organizations.externalAccounts.retrieve({
470
+ organizationId: args.ownerId,
471
+ externalAccountId: args.externalAccountId
472
+ }),
473
+ [capxul, args.ownerKind, args.ownerId, args.externalAccountId]
474
+ );
303
475
  }
304
476
  function useSubAccount(_subAccountId) {
305
477
  return notImplementedQuery("useSubAccount");
@@ -316,6 +488,13 @@ function usePayment(_paymentId) {
316
488
  function useTransfer(_transferId) {
317
489
  return notImplementedQuery("useTransfer");
318
490
  }
491
+ function useTokenTransfer(args) {
492
+ const capxul = useCapxul();
493
+ return useSdkQuery(
494
+ () => capxul.tokenTransfers.retrieve(args),
495
+ [capxul, args.txHash, args.logIndex, args.chainId]
496
+ );
497
+ }
319
498
  function useBalanceLedgerEntry(_args) {
320
499
  return notImplementedQuery("useBalanceLedgerEntry");
321
500
  }
@@ -350,8 +529,14 @@ function useOrganizations() {
350
529
  function useMembers(_organizationId) {
351
530
  return notImplementedQuery("useMembers");
352
531
  }
353
- function useExternalAccounts(_args) {
354
- return notImplementedQuery("useExternalAccounts");
532
+ function useExternalAccounts(args) {
533
+ const capxul = useCapxul();
534
+ return useSdkQuery(
535
+ () => args.ownerKind === "account" ? capxul.accounts.externalAccounts.list({ accountId: args.ownerId }) : capxul.organizations.externalAccounts.list({
536
+ organizationId: args.ownerId
537
+ }),
538
+ [capxul, args.ownerKind, args.ownerId]
539
+ );
355
540
  }
356
541
  function useSubAccounts(_args) {
357
542
  return notImplementedQuery("useSubAccounts");
@@ -374,6 +559,13 @@ function useTransfers(_filters) {
374
559
  function useOrgTransfers(_args) {
375
560
  return notImplementedQuery("useOrgTransfers");
376
561
  }
562
+ function useTokenTransfers(filters) {
563
+ const capxul = useCapxul();
564
+ return useSdkQuery(
565
+ () => capxul.tokenTransfers.list(filters),
566
+ [capxul, filters?.limit, filters?.cursor, filters?.direction]
567
+ );
568
+ }
377
569
  function useBalanceLedger(_args) {
378
570
  return notImplementedQuery("useBalanceLedger");
379
571
  }
@@ -409,6 +601,12 @@ function useAuthFlow() {
409
601
  const [snapshot, send] = react$1.useActor(machine);
410
602
  return { snapshot, send };
411
603
  }
604
+ function useAuthBootstrapFlow() {
605
+ const client = useCapxul();
606
+ const machine = react.useMemo(() => client.flows.authBootstrap(), [client]);
607
+ const [snapshot, send] = react$1.useActor(machine);
608
+ return { snapshot, send };
609
+ }
412
610
  function useOnboardingFlow() {
413
611
  const client = useCapxul();
414
612
  const machine = react.useMemo(() => client.flows.onboarding(), [client]);
@@ -421,56 +619,6 @@ function useProvisioningFlow() {
421
619
  const [snapshot, send] = react$1.useActor(machine);
422
620
  return { snapshot, send };
423
621
  }
424
-
425
- // src/config.ts
426
- function createCapxulConfig(input) {
427
- assertOnlyKnownKeys(input);
428
- assertModeRequiredFields(input);
429
- return Object.freeze({ ...input });
430
- }
431
- var ALLOWED_BROWSER_CONFIG_KEYS = [
432
- "mode",
433
- "authBaseUrl",
434
- "convexUrl",
435
- "publishableKey",
436
- "bootstrapUrl",
437
- "fetchImpl"
438
- ];
439
- function assertOnlyKnownKeys(input) {
440
- const candidate = input;
441
- const allowed = new Set(ALLOWED_BROWSER_CONFIG_KEYS);
442
- const unknown = Object.keys(candidate).filter((key) => !allowed.has(key));
443
- if (unknown.length === 0) return;
444
- throw Errors.invalidInput(
445
- "config",
446
- `Browser Capxul config contains unknown or secret/server-only fields: ${unknown.join(", ")}. Allowed keys: ${ALLOWED_BROWSER_CONFIG_KEYS.join(", ")}.`
447
- );
448
- }
449
- function assertModeRequiredFields(input) {
450
- switch (input.mode) {
451
- case "build-time-urls": {
452
- if (!input.authBaseUrl || input.authBaseUrl.trim().length === 0) {
453
- throw Errors.invalidInput("authBaseUrl", "non-empty string required.");
454
- }
455
- if (!input.convexUrl || input.convexUrl.trim().length === 0) {
456
- throw Errors.invalidInput("convexUrl", "non-empty string required.");
457
- }
458
- return;
459
- }
460
- case "publishable-key": {
461
- if (!input.publishableKey || input.publishableKey.trim().length === 0) {
462
- throw Errors.invalidInput("publishableKey", "non-empty string required.");
463
- }
464
- return;
465
- }
466
- default: {
467
- const value = input;
468
- throw Errors.internalError(
469
- `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
470
- );
471
- }
472
- }
473
- }
474
622
  var PRIVATE_KEY_PATTERN = /^0x[0-9a-fA-F]{64}$/;
475
623
  var EVM_ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
476
624
  function injectedConnector(options = {}) {
@@ -576,6 +724,7 @@ exports.localPrivateKeyConnector = localPrivateKeyConnector;
576
724
  exports.useAccount = useAccount;
577
725
  exports.useApiKey = useApiKey;
578
726
  exports.useApiKeys = useApiKeys;
727
+ exports.useAuthBootstrapFlow = useAuthBootstrapFlow;
579
728
  exports.useAuthFlow = useAuthFlow;
580
729
  exports.useBalanceLedger = useBalanceLedger;
581
730
  exports.useBalanceLedgerEntry = useBalanceLedgerEntry;
@@ -604,6 +753,8 @@ exports.useProvisioningFlow = useProvisioningFlow;
604
753
  exports.useSafe = useSafe;
605
754
  exports.useSubAccount = useSubAccount;
606
755
  exports.useSubAccounts = useSubAccounts;
756
+ exports.useTokenTransfer = useTokenTransfer;
757
+ exports.useTokenTransfers = useTokenTransfers;
607
758
  exports.useTransfer = useTransfer;
608
759
  exports.useTransfers = useTransfers;
609
760
  exports.useTreasury = useTreasury;