@capxul/sdk-react 0.1.0-alpha.3 → 0.1.0-alpha.6

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
@@ -1,14 +1,54 @@
1
1
  "use client";
2
- import { createContext, useContext, useSyncExternalStore, useMemo, useState, useEffect } from 'react';
3
- import { createCapxulClient, makeHttpTransport, CapxulError as CapxulError$1 } from '@capxul/sdk';
2
+ import { createContext, useContext, useSyncExternalStore, useMemo, useEffect, useState } from 'react';
3
+ import { makeHttpTransport, createCapxulClient, CapxulError as CapxulError$1 } from '@capxul/sdk';
4
4
  import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
5
5
  import { jsx } from 'react/jsx-runtime';
6
+ import { ConvexReactClient } from 'convex/react';
6
7
  import { CapxulError as CapxulError$2 } from '@capxul/sdk/errors';
7
8
  import { useActor } from '@xstate/react';
8
9
  import { getAddress } from 'viem';
9
10
  import { privateKeyToAccount } from 'viem/accounts';
10
11
 
11
12
  // src/provider.tsx
13
+ var CapxulClientContext = createContext(null);
14
+ function CapxulClientProvider({
15
+ client,
16
+ children
17
+ }) {
18
+ return /* @__PURE__ */ jsx(CapxulClientContext.Provider, { value: client, children });
19
+ }
20
+ function useCapxul() {
21
+ const client = useContext(CapxulClientContext);
22
+ if (!client) {
23
+ throw new Error(
24
+ "useCapxul() was called outside a <CapxulProvider>. Wrap your app in <CapxulProvider config={...}> before rendering hooks from @capxul/sdk-react."
25
+ );
26
+ }
27
+ return client;
28
+ }
29
+ var CapxulTransportContext = createContext(null);
30
+ function CapxulTransportProvider({
31
+ transport,
32
+ children
33
+ }) {
34
+ return /* @__PURE__ */ jsx(CapxulTransportContext.Provider, { value: transport, children });
35
+ }
36
+ function useCapxulStatus() {
37
+ const transport = useContext(CapxulTransportContext);
38
+ return useSyncExternalStore(
39
+ (listener) => {
40
+ if (!transport) return () => {
41
+ };
42
+ return transport.subscribe(listener);
43
+ },
44
+ () => transport?.getState() ?? FALLBACK_READY,
45
+ () => transport?.getState() ?? FALLBACK_READY
46
+ );
47
+ }
48
+ var FALLBACK_READY = Object.freeze({
49
+ status: "ready",
50
+ runtime: { authBaseUrl: "", convexUrl: "" }
51
+ });
12
52
 
13
53
  // ../config/src/errors.ts
14
54
  var CapxulError = class extends Error {
@@ -69,7 +109,29 @@ var Errors = {
69
109
  { details }
70
110
  ),
71
111
  emailDeliveryFailed: (detail) => new CapxulError("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
72
- internalError: (reason) => new CapxulError("INTERNAL_ERROR", `Internal error: ${reason}`)
112
+ internalError: (reason) => new CapxulError("INTERNAL_ERROR", `Internal error: ${reason}`),
113
+ /**
114
+ * Verification gate. Surfaced when a request hits a verification
115
+ * boundary the actor cannot cross under their current state. Two
116
+ * variants share this code:
117
+ *
118
+ * - Rail gate (Withdrawals v1 W2, #465): the resolved
119
+ * `external_account.kind` routes to a withdrawal rail (e.g.
120
+ * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
121
+ * `details.rail` + `details.currentKind`.
122
+ * - KYC tier gate (legacy / future): the actor's KYC tier is below
123
+ * the required tier. Carries `details.requiredTier`.
124
+ *
125
+ * Code is shared because both expose the same UX shape ("you cannot
126
+ * proceed until verification advances"); the `details.*` keys
127
+ * differentiate the route.
128
+ */
129
+ verificationRequired: (details) => {
130
+ const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
131
+ return new CapxulError("VERIFICATION_REQUIRED", message, {
132
+ details: { ...details }
133
+ });
134
+ }
73
135
  };
74
136
 
75
137
  // ../config/src/org-roles.ts
@@ -81,56 +143,95 @@ function roleKeyFromLabel(label) {
81
143
  roleKeyFromLabel("OWNER");
82
144
  roleKeyFromLabel("FINANCE_MANAGER");
83
145
  roleKeyFromLabel("TEAM_LEAD");
84
- var CapxulClientContext = createContext(null);
85
- function CapxulClientProvider({
86
- client,
87
- children
88
- }) {
89
- return /* @__PURE__ */ jsx(CapxulClientContext.Provider, { value: client, children });
146
+
147
+ // src/config.ts
148
+ function createCapxulConfig(input) {
149
+ assertOnlyKnownKeys(input);
150
+ assertModeRequiredFields(input);
151
+ return Object.freeze({ ...input });
90
152
  }
91
- function useCapxul() {
92
- const client = useContext(CapxulClientContext);
93
- if (!client) {
94
- throw new Error(
95
- "useCapxul() was called outside a <CapxulProvider>. Wrap your app in <CapxulProvider config={...}> before rendering hooks from @capxul/sdk-react."
96
- );
97
- }
98
- return client;
153
+ var ALLOWED_BROWSER_CONFIG_KEYS = [
154
+ "mode",
155
+ "authBaseUrl",
156
+ "convexUrl",
157
+ "publishableKey",
158
+ "bootstrapUrl",
159
+ "fetchImpl"
160
+ ];
161
+ function assertOnlyKnownKeys(input) {
162
+ const candidate = input;
163
+ const allowed = new Set(ALLOWED_BROWSER_CONFIG_KEYS);
164
+ const unknown = Object.keys(candidate).filter((key) => !allowed.has(key));
165
+ if (unknown.length === 0) return;
166
+ throw Errors.invalidInput(
167
+ "config",
168
+ `Browser Capxul config contains unknown or secret/server-only fields: ${unknown.join(", ")}. Allowed keys: ${ALLOWED_BROWSER_CONFIG_KEYS.join(", ")}.`
169
+ );
99
170
  }
100
- var CapxulTransportContext = createContext(null);
101
- function CapxulTransportProvider({
102
- transport,
103
- children
104
- }) {
105
- return /* @__PURE__ */ jsx(CapxulTransportContext.Provider, { value: transport, children });
171
+ function assertModeRequiredFields(input) {
172
+ switch (input.mode) {
173
+ case "build-time-urls": {
174
+ if (!input.authBaseUrl || input.authBaseUrl.trim().length === 0) {
175
+ throw Errors.invalidInput("authBaseUrl", "non-empty string required.");
176
+ }
177
+ if (!input.convexUrl || input.convexUrl.trim().length === 0) {
178
+ throw Errors.invalidInput("convexUrl", "non-empty string required.");
179
+ }
180
+ return;
181
+ }
182
+ case "publishable-key": {
183
+ if (!input.publishableKey || input.publishableKey.trim().length === 0) {
184
+ throw Errors.invalidInput("publishableKey", "non-empty string required.");
185
+ }
186
+ return;
187
+ }
188
+ default: {
189
+ const value = input;
190
+ throw Errors.internalError(
191
+ `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
192
+ );
193
+ }
194
+ }
106
195
  }
107
- function useCapxulStatus() {
108
- const transport = useContext(CapxulTransportContext);
109
- return useSyncExternalStore(
110
- (listener) => {
111
- if (!transport) return () => {
112
- };
113
- return transport.subscribe(listener);
114
- },
115
- () => transport?.getState() ?? FALLBACK_READY,
116
- () => transport?.getState() ?? FALLBACK_READY
117
- );
196
+ function createReactDataClient(convexUrl, sessionStore) {
197
+ const client = new ConvexReactClient(convexUrl);
198
+ const refreshAuth = () => {
199
+ const session = sessionStore.get();
200
+ const jwt = session?.convexJwt;
201
+ if (jwt) {
202
+ client.setAuth(() => Promise.resolve(jwt));
203
+ } else {
204
+ client.clearAuth();
205
+ }
206
+ };
207
+ refreshAuth();
208
+ return {
209
+ query: (name, args) => client.query(name, args),
210
+ mutation: (name, args) => client.mutation(name, args),
211
+ action: (name, args) => client.action(name, args),
212
+ refreshAuth,
213
+ close: () => {
214
+ void client.close();
215
+ }
216
+ };
118
217
  }
119
- var FALLBACK_READY = Object.freeze({
120
- status: "ready",
121
- runtime: { authBaseUrl: "", convexUrl: "" }
122
- });
123
218
  function CapxulProvider({
124
219
  config,
125
- publishableKey,
126
- browserConfig,
220
+ sessionStore,
127
221
  queryClient,
128
222
  children
129
223
  }) {
224
+ const defaultSessionStore = useMemo(() => createMemorySessionStore(), []);
225
+ const effectiveSessionStore = sessionStore ?? defaultSessionStore;
130
226
  const wiring = useMemo(
131
- () => buildWiring({ config, publishableKey, browserConfig }),
132
- [config, publishableKey, browserConfig]
227
+ () => buildWiring(config, effectiveSessionStore),
228
+ [config, effectiveSessionStore]
133
229
  );
230
+ useEffect(() => {
231
+ return () => {
232
+ wiring.dataClient?.close();
233
+ };
234
+ }, [wiring]);
134
235
  const defaultClient = useMemo(
135
236
  () => new QueryClient({
136
237
  defaultOptions: { queries: { staleTime: 3e4 } }
@@ -138,51 +239,60 @@ function CapxulProvider({
138
239
  []
139
240
  );
140
241
  const effectiveClient = queryClient ?? defaultClient;
141
- const inner = /* @__PURE__ */ jsx(CapxulClientProvider, { client: wiring.client, children });
142
- return /* @__PURE__ */ jsx(QueryClientProvider, { client: effectiveClient, children: wiring.transport ? /* @__PURE__ */ jsx(CapxulTransportProvider, { transport: wiring.transport, children: inner }) : inner });
242
+ return /* @__PURE__ */ jsx(QueryClientProvider, { client: effectiveClient, children: /* @__PURE__ */ jsx(CapxulTransportProvider, { transport: wiring.transport, children: /* @__PURE__ */ jsx(CapxulClientProvider, { client: wiring.client, children }) }) });
143
243
  }
144
- function buildWiring({
145
- config,
146
- publishableKey,
147
- browserConfig
148
- }) {
149
- const sources = [
150
- config !== void 0,
151
- publishableKey !== void 0,
152
- browserConfig !== void 0
153
- ].filter(Boolean).length;
154
- if (sources === 0) {
155
- throw Errors.invalidInput(
156
- "CapxulProvider",
157
- "Pass exactly one of `config`, `publishableKey`, or `browserConfig`."
158
- );
159
- }
160
- if (sources > 1) {
161
- throw Errors.invalidInput(
162
- "CapxulProvider",
163
- "`config`, `publishableKey`, and `browserConfig` are mutually exclusive \u2014 pass exactly one."
164
- );
165
- }
166
- if (config !== void 0) {
167
- return {
168
- client: createCapxulClient(config),
169
- transport: null
170
- };
171
- }
172
- const browserCfg = browserConfig ?? {
173
- mode: "publishable-key",
174
- // The narrowing above (`sources === 0` rejected; `config` not
175
- // present) guarantees `publishableKey` is set on this branch.
176
- publishableKey
177
- };
178
- const transport = makeHttpTransport(browserCfg);
244
+ function buildWiring(config, sessionStore) {
245
+ const validated = createCapxulConfig(config);
246
+ const transport = makeHttpTransport(validated);
247
+ const dataClient = validated.mode === "build-time-urls" ? createReactDataClient(validated.convexUrl, sessionStore) : null;
179
248
  const sdkConfig = {
180
249
  _transport: transport,
181
- publishableKey: browserCfg.mode === "publishable-key" ? browserCfg.publishableKey : void 0
250
+ publishableKey: validated.mode === "publishable-key" ? validated.publishableKey : void 0,
251
+ data: dataClient ?? void 0,
252
+ auth: dataClient ? {
253
+ // The auth client builds the BetterAuth root URL from this
254
+ // value. Convex's `.cloud` URL is the wrong host (BetterAuth
255
+ // is mounted on the `.site` URL), but the build-time-urls
256
+ // transport already encodes the correct `authBaseUrl` via
257
+ // its discriminated union. We pass `convexUrl` here only so
258
+ // `core/auth.ts`'s `createTransportProvider` short-circuits
259
+ // to the externally-injected `_transport` cache slot.
260
+ baseUrl: transport.authBaseUrl,
261
+ sessionStore,
262
+ createDataClient: async (_session) => {
263
+ dataClient.refreshAuth();
264
+ return dataClient;
265
+ }
266
+ } : void 0
267
+ };
268
+ const client = createCapxulClient(sdkConfig);
269
+ if (dataClient) {
270
+ const originalSignOut = client.auth.signOut;
271
+ Object.assign(client.auth, {
272
+ signOut: async () => {
273
+ const result = await originalSignOut();
274
+ dataClient.refreshAuth();
275
+ sdkConfig.data = dataClient;
276
+ return result;
277
+ }
278
+ });
279
+ }
280
+ return {
281
+ client,
282
+ transport,
283
+ dataClient
182
284
  };
285
+ }
286
+ function createMemorySessionStore() {
287
+ let current = null;
183
288
  return {
184
- client: createCapxulClient(sdkConfig),
185
- transport
289
+ get: () => current,
290
+ set: (session) => {
291
+ current = session;
292
+ },
293
+ clear: () => {
294
+ current = null;
295
+ }
186
296
  };
187
297
  }
188
298
  function notImplementedQuery(hookName) {
@@ -272,8 +382,12 @@ function useMe() {
272
382
  staleTime: 3e4
273
383
  });
274
384
  }
275
- function useAccount(_accountId) {
276
- return notImplementedQuery("useAccount");
385
+ function useAccount(accountId) {
386
+ const capxul = useCapxul();
387
+ return useSdkQuery(
388
+ () => accountId !== void 0 ? capxul.accounts.retrieve(accountId) : capxul.me.get(),
389
+ [capxul, accountId]
390
+ );
277
391
  }
278
392
  function useOrganization(_organizationId) {
279
393
  return notImplementedQuery("useOrganization");
@@ -281,8 +395,12 @@ function useOrganization(_organizationId) {
281
395
  function useMember(_args) {
282
396
  return notImplementedQuery("useMember");
283
397
  }
284
- function useSafe(_safeId) {
285
- return notImplementedQuery("useSafe");
398
+ function useSafe(safeId) {
399
+ const capxul = useCapxul();
400
+ return useSdkQuery(
401
+ () => capxul.accounts.safes.retrieve(safeId),
402
+ [capxul, safeId]
403
+ );
286
404
  }
287
405
  function useTreasury(_organizationId) {
288
406
  return notImplementedQuery("useTreasury");
@@ -296,8 +414,15 @@ function useKycProfile(_accountId) {
296
414
  function useKybProfile(_organizationId) {
297
415
  return notImplementedQuery("useKybProfile");
298
416
  }
299
- function useExternalAccount(_args) {
300
- return notImplementedQuery("useExternalAccount");
417
+ function useExternalAccount(args) {
418
+ const capxul = useCapxul();
419
+ return useSdkQuery(
420
+ () => args.ownerKind === "account" ? capxul.externalAccounts.retrieve(args.externalAccountId) : capxul.organizations.externalAccounts.retrieve({
421
+ organizationId: args.ownerId,
422
+ externalAccountId: args.externalAccountId
423
+ }),
424
+ [capxul, args.ownerKind, args.ownerId, args.externalAccountId]
425
+ );
301
426
  }
302
427
  function useSubAccount(_subAccountId) {
303
428
  return notImplementedQuery("useSubAccount");
@@ -314,14 +439,25 @@ function usePayment(_paymentId) {
314
439
  function useTransfer(_transferId) {
315
440
  return notImplementedQuery("useTransfer");
316
441
  }
442
+ function useTokenTransfer(args) {
443
+ const capxul = useCapxul();
444
+ return useSdkQuery(
445
+ () => capxul.tokenTransfers.retrieve(args),
446
+ [capxul, args.txHash, args.logIndex, args.chainId]
447
+ );
448
+ }
317
449
  function useBalanceLedgerEntry(_args) {
318
450
  return notImplementedQuery("useBalanceLedgerEntry");
319
451
  }
320
452
  function useDocument(_documentId) {
321
453
  return notImplementedQuery("useDocument");
322
454
  }
323
- function useWithdrawal(_withdrawalId) {
324
- return notImplementedQuery("useWithdrawal");
455
+ function useWithdrawal(withdrawalId) {
456
+ const capxul = useCapxul();
457
+ return useSdkQuery(() => capxul.withdrawals.retrieve(withdrawalId), [
458
+ capxul,
459
+ withdrawalId
460
+ ]);
325
461
  }
326
462
  function useOperation(operationId) {
327
463
  const capxul = useCapxul();
@@ -344,8 +480,14 @@ function useOrganizations() {
344
480
  function useMembers(_organizationId) {
345
481
  return notImplementedQuery("useMembers");
346
482
  }
347
- function useExternalAccounts(_args) {
348
- return notImplementedQuery("useExternalAccounts");
483
+ function useExternalAccounts(args) {
484
+ const capxul = useCapxul();
485
+ return useSdkQuery(
486
+ () => args.ownerKind === "account" ? capxul.accounts.externalAccounts.list({ accountId: args.ownerId }) : capxul.organizations.externalAccounts.list({
487
+ organizationId: args.ownerId
488
+ }),
489
+ [capxul, args.ownerKind, args.ownerId]
490
+ );
349
491
  }
350
492
  function useSubAccounts(_args) {
351
493
  return notImplementedQuery("useSubAccounts");
@@ -368,6 +510,13 @@ function useTransfers(_filters) {
368
510
  function useOrgTransfers(_args) {
369
511
  return notImplementedQuery("useOrgTransfers");
370
512
  }
513
+ function useTokenTransfers(filters) {
514
+ const capxul = useCapxul();
515
+ return useSdkQuery(
516
+ () => capxul.tokenTransfers.list(filters),
517
+ [capxul, filters?.limit, filters?.cursor, filters?.direction]
518
+ );
519
+ }
371
520
  function useBalanceLedger(_args) {
372
521
  return notImplementedQuery("useBalanceLedger");
373
522
  }
@@ -377,11 +526,19 @@ function useDocuments(_filters) {
377
526
  function useOrgDocuments(_args) {
378
527
  return notImplementedQuery("useOrgDocuments");
379
528
  }
380
- function useWithdrawals(_filters) {
381
- return notImplementedQuery("useWithdrawals");
529
+ function useWithdrawals(filters) {
530
+ const capxul = useCapxul();
531
+ return useSdkQuery(
532
+ () => capxul.withdrawals.list(filters),
533
+ [capxul, filters?.limit, filters?.cursor]
534
+ );
382
535
  }
383
- function useOrgWithdrawals(_args) {
384
- return notImplementedQuery("useOrgWithdrawals");
536
+ function useOrgWithdrawals(args) {
537
+ const capxul = useCapxul();
538
+ return useSdkQuery(
539
+ () => capxul.organizations.withdrawals.list(args),
540
+ [capxul, args.organizationId, args.limit, args.cursor]
541
+ );
385
542
  }
386
543
  function useApiKeys(_organizationId) {
387
544
  return notImplementedQuery("useApiKeys");
@@ -407,56 +564,6 @@ function useProvisioningFlow() {
407
564
  const [snapshot, send] = useActor(machine);
408
565
  return { snapshot, send };
409
566
  }
410
-
411
- // src/config.ts
412
- function createCapxulConfig(input) {
413
- assertOnlyKnownKeys(input);
414
- assertModeRequiredFields(input);
415
- return Object.freeze({ ...input });
416
- }
417
- var ALLOWED_BROWSER_CONFIG_KEYS = [
418
- "mode",
419
- "authBaseUrl",
420
- "convexUrl",
421
- "publishableKey",
422
- "bootstrapUrl",
423
- "fetchImpl"
424
- ];
425
- function assertOnlyKnownKeys(input) {
426
- const candidate = input;
427
- const allowed = new Set(ALLOWED_BROWSER_CONFIG_KEYS);
428
- const unknown = Object.keys(candidate).filter((key) => !allowed.has(key));
429
- if (unknown.length === 0) return;
430
- throw Errors.invalidInput(
431
- "config",
432
- `Browser Capxul config contains unknown or secret/server-only fields: ${unknown.join(", ")}. Allowed keys: ${ALLOWED_BROWSER_CONFIG_KEYS.join(", ")}.`
433
- );
434
- }
435
- function assertModeRequiredFields(input) {
436
- switch (input.mode) {
437
- case "build-time-urls": {
438
- if (!input.authBaseUrl || input.authBaseUrl.trim().length === 0) {
439
- throw Errors.invalidInput("authBaseUrl", "non-empty string required.");
440
- }
441
- if (!input.convexUrl || input.convexUrl.trim().length === 0) {
442
- throw Errors.invalidInput("convexUrl", "non-empty string required.");
443
- }
444
- return;
445
- }
446
- case "publishable-key": {
447
- if (!input.publishableKey || input.publishableKey.trim().length === 0) {
448
- throw Errors.invalidInput("publishableKey", "non-empty string required.");
449
- }
450
- return;
451
- }
452
- default: {
453
- const value = input;
454
- throw Errors.internalError(
455
- `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
456
- );
457
- }
458
- }
459
- }
460
567
  var PRIVATE_KEY_PATTERN = /^0x[0-9a-fA-F]{64}$/;
461
568
  var EVM_ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
462
569
  function injectedConnector(options = {}) {
@@ -553,4 +660,4 @@ function validateAndNormalizeEvmAddress(field, raw) {
553
660
  }
554
661
  }
555
662
 
556
- export { CapxulClientProvider, CapxulProvider, CapxulTransportProvider, createCapxulConfig, injectedConnector, localPrivateKeyConnector, useAccount, useApiKey, useApiKeys, useAuthFlow, useBalanceLedger, useBalanceLedgerEntry, useCapxul, useCapxulStatus, useDocument, useDocuments, useExternalAccount, useExternalAccounts, useKybProfile, useKycProfile, useMe, useMember, useMembers, useOnboardingFlow, useOperation, useOrgDocuments, useOrgPayments, useOrgTransfers, useOrgWithdrawals, useOrganization, useOrganizations, usePayment, usePayments, useProvisioningFlow, useSafe, useSubAccount, useSubAccounts, useTransfer, useTransfers, useTreasury, useVirtualAccount, useVirtualAccounts, useVirtualCard, useVirtualCards, useWebhookEndpoint, useWebhookEndpoints, useWebhookEvent, useWithdrawal, useWithdrawals };
663
+ export { CapxulClientProvider, CapxulProvider, CapxulTransportProvider, createCapxulConfig, injectedConnector, localPrivateKeyConnector, useAccount, useApiKey, useApiKeys, useAuthFlow, useBalanceLedger, useBalanceLedgerEntry, useCapxul, useCapxulStatus, useDocument, useDocuments, useExternalAccount, useExternalAccounts, useKybProfile, useKycProfile, useMe, useMember, useMembers, useOnboardingFlow, useOperation, useOrgDocuments, useOrgPayments, useOrgTransfers, useOrgWithdrawals, useOrganization, useOrganizations, usePayment, usePayments, useProvisioningFlow, useSafe, useSubAccount, useSubAccounts, useTokenTransfer, useTokenTransfers, useTransfer, useTransfers, useTreasury, useVirtualAccount, useVirtualAccounts, useVirtualCard, useVirtualCards, useWebhookEndpoint, useWebhookEndpoints, useWebhookEvent, useWithdrawal, useWithdrawals };