@capxul/sdk-react 0.1.0-alpha.4 → 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/CHANGELOG.md +48 -0
- package/dist/index.cjs +121 -10
- package/dist/index.d.cts +54 -8
- package/dist/index.d.ts +54 -8
- package/dist/index.js +122 -11
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @capxul/sdk-react
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.6
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3af04a9: Public `<CapxulProvider>` now wires authenticated Convex reads end-to-end (#487, closes #484).
|
|
8
|
+
|
|
9
|
+
The `build-time-urls` arm of `BrowserCapxulConfig` now causes the provider to internally instantiate a `ConvexReactClient`, wrap it as a `CapxulDataClient`, and feed it as `config.data` into `createCapxulClient`. The existing `auth.createDataClient` callback is wired to the same singleton so a `verifyOtp` call refreshes the data client's auth header without churning the WebSocket. The provider also wraps `client.auth.signOut` so the data client survives sign-out — post-signout reads return typed backend `NOT_AUTHENTICATED` instead of the SDK's `NOT_IMPLEMENTED` stub (closes the #474 friction as a side-effect).
|
|
10
|
+
|
|
11
|
+
New optional `sessionStore?: AuthSessionStore` prop lets browser apps opt into `localStorage`-backed persistence and Node consumers (CLIs, e2e harnesses) opt into file-backed persistence. Defaults to in-memory.
|
|
12
|
+
|
|
13
|
+
This closes the architectural gap that made the React hook surface (`useMe`, `useAccount`, `useSafe`, ...) impossible to exercise end-to-end through the public provider — previously `config.data` was never populated, so every authenticated read short-circuited to `NOT_IMPLEMENTED`. The Ink reference CLI (`apps/reference-cli/`) is rebuilt on top of the public provider as proof: 17/17 agent-driver assertions pass against live alpha-3 Convex, including a dev-OTP authenticated round-trip that mints a real session via `AUTH_DEV_OTP=00000` and exercises three distinct hook end-states (`NOT_AUTHENTICATED`, `PROFILE_NOT_FOUND`, post-signout `NOT_AUTHENTICATED`).
|
|
14
|
+
|
|
15
|
+
- 57203a4: Withdrawals v1 W2 (#465) — public surface tightening + org-scope create
|
|
16
|
+
- `WithdrawalsCreateInput.destination` no longer accepts `kind`. The
|
|
17
|
+
backend now resolves the `external_account` row by FK and infers
|
|
18
|
+
the kind + rail server-side. Anything that doesn't route to
|
|
19
|
+
`chain_wallet` (or is chain_wallet but non-EVM in slice 1) returns
|
|
20
|
+
`VERIFICATION_REQUIRED` with `details.rail` + `details.currentKind`.
|
|
21
|
+
- `organizations.withdrawals.create` is now a real mutation (no
|
|
22
|
+
longer a `NOT_IMPLEMENTED` stub). Returns the `processing` row
|
|
23
|
+
only — Safe + Zodiac submission orchestration ships in W3+.
|
|
24
|
+
- `Errors.verificationRequired({ rail, currentKind })` factory
|
|
25
|
+
added; the `VERIFICATION_REQUIRED` code now broadens to cover
|
|
26
|
+
both KYC tier gates and unsupported withdrawal rails.
|
|
27
|
+
|
|
28
|
+
**Migration:** Remove `destination.kind` from any
|
|
29
|
+
`capxul.withdrawals.create({ destination: { kind, externalAccountId } })`
|
|
30
|
+
call sites. Pass only `externalAccountId`.
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [57203a4]
|
|
35
|
+
- @capxul/sdk@0.1.0-alpha.6
|
|
36
|
+
|
|
37
|
+
## 0.1.0-alpha.5
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- Widen the React peer dependency to `>=18.2.0 <20` so React 18-era Ink 5
|
|
42
|
+
CLI apps can install `@capxul/sdk-react` without forcing React 19 into
|
|
43
|
+
Ink's React reconciler. The provider and hooks only use React 18-compatible
|
|
44
|
+
APIs (`createContext`, `useContext`, `useMemo`, `useEffect`, `useState`, and
|
|
45
|
+
`useSyncExternalStore`), preserving React 19 compatibility while unblocking
|
|
46
|
+
React 18 hosts.
|
|
47
|
+
|
|
48
|
+
- Updated dependencies
|
|
49
|
+
- @capxul/sdk@0.1.0-alpha.5
|
|
50
|
+
|
|
3
51
|
## 0.1.0-alpha.4
|
|
4
52
|
|
|
5
53
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ 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');
|
|
@@ -110,7 +111,29 @@ var Errors = {
|
|
|
110
111
|
{ details }
|
|
111
112
|
),
|
|
112
113
|
emailDeliveryFailed: (detail) => new CapxulError("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
|
|
113
|
-
internalError: (reason) => new CapxulError("INTERNAL_ERROR", `Internal error: ${reason}`)
|
|
114
|
+
internalError: (reason) => new CapxulError("INTERNAL_ERROR", `Internal error: ${reason}`),
|
|
115
|
+
/**
|
|
116
|
+
* Verification gate. Surfaced when a request hits a verification
|
|
117
|
+
* boundary the actor cannot cross under their current state. Two
|
|
118
|
+
* variants share this code:
|
|
119
|
+
*
|
|
120
|
+
* - Rail gate (Withdrawals v1 W2, #465): the resolved
|
|
121
|
+
* `external_account.kind` routes to a withdrawal rail (e.g.
|
|
122
|
+
* `fiat_offramp`, `card_payout`) that is not yet supported. Carries
|
|
123
|
+
* `details.rail` + `details.currentKind`.
|
|
124
|
+
* - KYC tier gate (legacy / future): the actor's KYC tier is below
|
|
125
|
+
* the required tier. Carries `details.requiredTier`.
|
|
126
|
+
*
|
|
127
|
+
* Code is shared because both expose the same UX shape ("you cannot
|
|
128
|
+
* proceed until verification advances"); the `details.*` keys
|
|
129
|
+
* differentiate the route.
|
|
130
|
+
*/
|
|
131
|
+
verificationRequired: (details) => {
|
|
132
|
+
const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
|
|
133
|
+
return new CapxulError("VERIFICATION_REQUIRED", message, {
|
|
134
|
+
details: { ...details }
|
|
135
|
+
});
|
|
136
|
+
}
|
|
114
137
|
};
|
|
115
138
|
|
|
116
139
|
// ../config/src/org-roles.ts
|
|
@@ -172,12 +195,45 @@ function assertModeRequiredFields(input) {
|
|
|
172
195
|
}
|
|
173
196
|
}
|
|
174
197
|
}
|
|
198
|
+
function createReactDataClient(convexUrl, sessionStore) {
|
|
199
|
+
const client = new react$2.ConvexReactClient(convexUrl);
|
|
200
|
+
const refreshAuth = () => {
|
|
201
|
+
const session = sessionStore.get();
|
|
202
|
+
const jwt = session?.convexJwt;
|
|
203
|
+
if (jwt) {
|
|
204
|
+
client.setAuth(() => Promise.resolve(jwt));
|
|
205
|
+
} else {
|
|
206
|
+
client.clearAuth();
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
refreshAuth();
|
|
210
|
+
return {
|
|
211
|
+
query: (name, args) => client.query(name, args),
|
|
212
|
+
mutation: (name, args) => client.mutation(name, args),
|
|
213
|
+
action: (name, args) => client.action(name, args),
|
|
214
|
+
refreshAuth,
|
|
215
|
+
close: () => {
|
|
216
|
+
void client.close();
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
175
220
|
function CapxulProvider({
|
|
176
221
|
config,
|
|
222
|
+
sessionStore,
|
|
177
223
|
queryClient,
|
|
178
224
|
children
|
|
179
225
|
}) {
|
|
180
|
-
const
|
|
226
|
+
const defaultSessionStore = react.useMemo(() => createMemorySessionStore(), []);
|
|
227
|
+
const effectiveSessionStore = sessionStore ?? defaultSessionStore;
|
|
228
|
+
const wiring = react.useMemo(
|
|
229
|
+
() => buildWiring(config, effectiveSessionStore),
|
|
230
|
+
[config, effectiveSessionStore]
|
|
231
|
+
);
|
|
232
|
+
react.useEffect(() => {
|
|
233
|
+
return () => {
|
|
234
|
+
wiring.dataClient?.close();
|
|
235
|
+
};
|
|
236
|
+
}, [wiring]);
|
|
181
237
|
const defaultClient = react.useMemo(
|
|
182
238
|
() => new reactQuery.QueryClient({
|
|
183
239
|
defaultOptions: { queries: { staleTime: 3e4 } }
|
|
@@ -187,16 +243,58 @@ function CapxulProvider({
|
|
|
187
243
|
const effectiveClient = queryClient ?? defaultClient;
|
|
188
244
|
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 }) }) });
|
|
189
245
|
}
|
|
190
|
-
function buildWiring(config) {
|
|
246
|
+
function buildWiring(config, sessionStore) {
|
|
191
247
|
const validated = createCapxulConfig(config);
|
|
192
248
|
const transport = sdk.makeHttpTransport(validated);
|
|
249
|
+
const dataClient = validated.mode === "build-time-urls" ? createReactDataClient(validated.convexUrl, sessionStore) : null;
|
|
193
250
|
const sdkConfig = {
|
|
194
251
|
_transport: transport,
|
|
195
|
-
publishableKey: validated.mode === "publishable-key" ? validated.publishableKey : void 0
|
|
252
|
+
publishableKey: validated.mode === "publishable-key" ? validated.publishableKey : void 0,
|
|
253
|
+
data: dataClient ?? void 0,
|
|
254
|
+
auth: dataClient ? {
|
|
255
|
+
// The auth client builds the BetterAuth root URL from this
|
|
256
|
+
// value. Convex's `.cloud` URL is the wrong host (BetterAuth
|
|
257
|
+
// is mounted on the `.site` URL), but the build-time-urls
|
|
258
|
+
// transport already encodes the correct `authBaseUrl` via
|
|
259
|
+
// its discriminated union. We pass `convexUrl` here only so
|
|
260
|
+
// `core/auth.ts`'s `createTransportProvider` short-circuits
|
|
261
|
+
// to the externally-injected `_transport` cache slot.
|
|
262
|
+
baseUrl: transport.authBaseUrl,
|
|
263
|
+
sessionStore,
|
|
264
|
+
createDataClient: async (_session) => {
|
|
265
|
+
dataClient.refreshAuth();
|
|
266
|
+
return dataClient;
|
|
267
|
+
}
|
|
268
|
+
} : void 0
|
|
196
269
|
};
|
|
270
|
+
const client = sdk.createCapxulClient(sdkConfig);
|
|
271
|
+
if (dataClient) {
|
|
272
|
+
const originalSignOut = client.auth.signOut;
|
|
273
|
+
Object.assign(client.auth, {
|
|
274
|
+
signOut: async () => {
|
|
275
|
+
const result = await originalSignOut();
|
|
276
|
+
dataClient.refreshAuth();
|
|
277
|
+
sdkConfig.data = dataClient;
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
197
282
|
return {
|
|
198
|
-
client
|
|
199
|
-
transport
|
|
283
|
+
client,
|
|
284
|
+
transport,
|
|
285
|
+
dataClient
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function createMemorySessionStore() {
|
|
289
|
+
let current = null;
|
|
290
|
+
return {
|
|
291
|
+
get: () => current,
|
|
292
|
+
set: (session) => {
|
|
293
|
+
current = session;
|
|
294
|
+
},
|
|
295
|
+
clear: () => {
|
|
296
|
+
current = null;
|
|
297
|
+
}
|
|
200
298
|
};
|
|
201
299
|
}
|
|
202
300
|
function notImplementedQuery(hookName) {
|
|
@@ -318,8 +416,15 @@ function useKycProfile(_accountId) {
|
|
|
318
416
|
function useKybProfile(_organizationId) {
|
|
319
417
|
return notImplementedQuery("useKybProfile");
|
|
320
418
|
}
|
|
321
|
-
function useExternalAccount(
|
|
322
|
-
|
|
419
|
+
function useExternalAccount(args) {
|
|
420
|
+
const capxul = useCapxul();
|
|
421
|
+
return useSdkQuery(
|
|
422
|
+
() => args.ownerKind === "account" ? capxul.externalAccounts.retrieve(args.externalAccountId) : capxul.organizations.externalAccounts.retrieve({
|
|
423
|
+
organizationId: args.ownerId,
|
|
424
|
+
externalAccountId: args.externalAccountId
|
|
425
|
+
}),
|
|
426
|
+
[capxul, args.ownerKind, args.ownerId, args.externalAccountId]
|
|
427
|
+
);
|
|
323
428
|
}
|
|
324
429
|
function useSubAccount(_subAccountId) {
|
|
325
430
|
return notImplementedQuery("useSubAccount");
|
|
@@ -377,8 +482,14 @@ function useOrganizations() {
|
|
|
377
482
|
function useMembers(_organizationId) {
|
|
378
483
|
return notImplementedQuery("useMembers");
|
|
379
484
|
}
|
|
380
|
-
function useExternalAccounts(
|
|
381
|
-
|
|
485
|
+
function useExternalAccounts(args) {
|
|
486
|
+
const capxul = useCapxul();
|
|
487
|
+
return useSdkQuery(
|
|
488
|
+
() => args.ownerKind === "account" ? capxul.accounts.externalAccounts.list({ accountId: args.ownerId }) : capxul.organizations.externalAccounts.list({
|
|
489
|
+
organizationId: args.ownerId
|
|
490
|
+
}),
|
|
491
|
+
[capxul, args.ownerKind, args.ownerId]
|
|
492
|
+
);
|
|
382
493
|
}
|
|
383
494
|
function useSubAccounts(_args) {
|
|
384
495
|
return notImplementedQuery("useSubAccounts");
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { HttpTransport, TransportState, BrowserCapxulConfig, AccountId, OrganizationId, ApiKeyId, BalanceLedgerEntryId, ExternalAccountId, MemberId, Account, ApiKey, BalanceLedgerEntry, DocumentId, Document, ExternalAccount, KybProfile, KycProfile, CapxulError as CapxulError$1, Member, OperationId, Operation, Organization, PaymentId, Payment, SafeId, Safe, SubAccountId, SubAccount, TokenTransfer, TransferId, Transfer, Treasury, VirtualAccountId, VirtualAccount, VirtualCardId, VirtualCard, WebhookEndpointId, WebhookEndpoint, WebhookEventId, WebhookEvent, WithdrawalId, Withdrawal, List, TokenTransfersListInput, TokenTransfersListPage, LocalPrivateKeySignerProvider } from '@capxul/sdk';
|
|
2
|
+
import { HttpTransport, TransportState, BrowserCapxulConfig, AuthSessionStore, AccountId, OrganizationId, ApiKeyId, BalanceLedgerEntryId, ExternalAccountId, MemberId, Account, ApiKey, BalanceLedgerEntry, DocumentId, Document, ExternalAccount, KybProfile, KycProfile, CapxulError as CapxulError$1, Member, OperationId, Operation, Organization, PaymentId, Payment, SafeId, Safe, SubAccountId, SubAccount, TokenTransfer, TransferId, Transfer, Treasury, VirtualAccountId, VirtualAccount, VirtualCardId, VirtualCard, WebhookEndpointId, WebhookEndpoint, WebhookEventId, WebhookEvent, WithdrawalId, Withdrawal, List, TokenTransfersListInput, TokenTransfersListPage, LocalPrivateKeySignerProvider } from '@capxul/sdk';
|
|
3
3
|
export { AuthFlowContext, AuthFlowEvent, BrowserCapxulConfig, OnboardingFlowContext, OnboardingFlowEvent, ProvisioningFlowContext, ProvisioningFlowEvent } from '@capxul/sdk';
|
|
4
4
|
import { QueryClient, UseQueryResult } from '@tanstack/react-query';
|
|
5
5
|
import { CapxulClient } from '@capxul/sdk/client';
|
|
@@ -58,16 +58,30 @@ declare function useCapxulStatus(): TransportState;
|
|
|
58
58
|
* `CapxulProvider` — public React provider for `@capxul/sdk-react`.
|
|
59
59
|
*
|
|
60
60
|
* Single-input contract: accepts ONLY `{ config: BrowserCapxulConfig,
|
|
61
|
-
* queryClient?, children }`. The browser config is the
|
|
62
|
-
* discriminated union from `@capxul/sdk` — `apiKey`,
|
|
63
|
-
* and other server-only fields are rejected at
|
|
64
|
-
* runtime via `createCapxulConfig`'s allow-list
|
|
61
|
+
* sessionStore?, queryClient?, children }`. The browser config is the
|
|
62
|
+
* secret-safe discriminated union from `@capxul/sdk` — `apiKey`,
|
|
63
|
+
* `data`, `signer`, and other server-only fields are rejected at
|
|
64
|
+
* compile-time AND at runtime via `createCapxulConfig`'s allow-list
|
|
65
|
+
* validator.
|
|
65
66
|
*
|
|
66
67
|
* The provider builds an `HttpTransport` synchronously and injects it
|
|
67
68
|
* into `createCapxulClient` via the internal `_transport` slot. The
|
|
68
69
|
* transport is exposed through `CapxulTransportContext` so
|
|
69
70
|
* `useCapxulStatus()` can subscribe to its lifecycle state machine.
|
|
70
71
|
*
|
|
72
|
+
* The provider also builds a `ConvexReactClient`-backed data client
|
|
73
|
+
* for the `build-time-urls` arm and feeds it as `config.data` so the
|
|
74
|
+
* SDK domain methods (`me.get`, `accounts.retrieve`, ...) and the
|
|
75
|
+
* React hooks built on top can run authenticated reads against live
|
|
76
|
+
* Convex. The auth lifecycle threads through the optional
|
|
77
|
+
* `sessionStore` prop: `verifyOtp` writes a session to the store
|
|
78
|
+
* (which the SDK auth client persists), and the provider's data
|
|
79
|
+
* client re-reads the JWT on demand via `refreshAuth()`. Without a
|
|
80
|
+
* session store the provider falls back to an in-memory one, which
|
|
81
|
+
* is fine for browser apps that hold the page until next reload but
|
|
82
|
+
* inadequate for CLI / Node consumers that need cross-process
|
|
83
|
+
* persistence — those pass a file-backed store via the prop.
|
|
84
|
+
*
|
|
71
85
|
* Tests and the e2e harness need the server-augmented `CapxulConfig`
|
|
72
86
|
* shape (with `data`, `signer`, `signing`). Those callers use
|
|
73
87
|
* `CapxulTestProvider` from `@capxul/sdk-react/proof` instead.
|
|
@@ -88,6 +102,17 @@ type CapxulProviderProps = {
|
|
|
88
102
|
* via `createCapxulConfig`'s allow-list validator.
|
|
89
103
|
*/
|
|
90
104
|
readonly config: BrowserCapxulConfig;
|
|
105
|
+
/**
|
|
106
|
+
* Auth session persistence adapter. The SDK auth client writes
|
|
107
|
+
* sessions here on `verifyOtp` and clears them on `signOut`. The
|
|
108
|
+
* provider's data client re-reads the JWT from this store on every
|
|
109
|
+
* `refreshAuth` call so reads carry the right auth header.
|
|
110
|
+
*
|
|
111
|
+
* Defaults to an in-memory store scoped to this provider. Pass a
|
|
112
|
+
* file-backed (Node CLI) or `localStorage`-backed (browser) store
|
|
113
|
+
* to persist sessions across process or page lifetimes.
|
|
114
|
+
*/
|
|
115
|
+
readonly sessionStore?: AuthSessionStore;
|
|
91
116
|
/**
|
|
92
117
|
* Optional TanStack Query `QueryClient`. Pass your app's existing
|
|
93
118
|
* client to share the cache across the SDK hooks and the host
|
|
@@ -97,7 +122,7 @@ type CapxulProviderProps = {
|
|
|
97
122
|
readonly queryClient?: QueryClient;
|
|
98
123
|
readonly children: ReactNode;
|
|
99
124
|
};
|
|
100
|
-
declare function CapxulProvider({ config, queryClient, children, }: CapxulProviderProps): ReactNode;
|
|
125
|
+
declare function CapxulProvider({ config, sessionStore, queryClient, children, }: CapxulProviderProps): ReactNode;
|
|
101
126
|
|
|
102
127
|
/**
|
|
103
128
|
* `QueryResult<T>` — the canonical three-state return shape for every
|
|
@@ -232,7 +257,18 @@ declare function useTreasury(_organizationId: OrganizationId): QueryResult<Treas
|
|
|
232
257
|
declare function useApiKey(_args: UseApiKeyArgs): QueryResult<ApiKey>;
|
|
233
258
|
declare function useKycProfile(_accountId: AccountId): QueryResult<KycProfile>;
|
|
234
259
|
declare function useKybProfile(_organizationId: OrganizationId): QueryResult<KybProfile>;
|
|
235
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Withdrawals v1 W1 (#464) — wired through the SDK.
|
|
262
|
+
*
|
|
263
|
+
* Branches on `args.ownerKind` so `account` scope reads via the
|
|
264
|
+
* top-level `capxul.externalAccounts.retrieve(id)` (visibility-gated
|
|
265
|
+
* server-side by the caller's accountId), and `organization` scope
|
|
266
|
+
* reads via `capxul.organizations.externalAccounts.retrieve({ ... })`
|
|
267
|
+
* which adds an org-scope check for cross-org isolation. Both routes
|
|
268
|
+
* resolve to the same Convex query handler — the SDK ergonomics
|
|
269
|
+
* differ but the wire shape is identical.
|
|
270
|
+
*/
|
|
271
|
+
declare function useExternalAccount(args: UseExternalAccountArgs): QueryResult<ExternalAccount>;
|
|
236
272
|
declare function useSubAccount(_subAccountId: SubAccountId): QueryResult<SubAccount>;
|
|
237
273
|
declare function useVirtualAccount(_virtualAccountId: VirtualAccountId): QueryResult<VirtualAccount>;
|
|
238
274
|
declare function useVirtualCard(_virtualCardId: VirtualCardId): QueryResult<VirtualCard>;
|
|
@@ -344,7 +380,17 @@ type OrgDocumentsFilters = OrgScopedFilters & {
|
|
|
344
380
|
*/
|
|
345
381
|
declare function useOrganizations(): QueryResult<List<Organization>>;
|
|
346
382
|
declare function useMembers(_organizationId: OrganizationId): QueryResult<List<Member>>;
|
|
347
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Withdrawals v1 W1 (#464) — wired through the SDK.
|
|
385
|
+
*
|
|
386
|
+
* Personal scope reads through `capxul.accounts.externalAccounts.list({
|
|
387
|
+
* accountId })` (Pattern A nested namespace); org scope reads through
|
|
388
|
+
* `capxul.organizations.externalAccounts.list({ organizationId })`.
|
|
389
|
+
* Both resolve to the same Convex query handler — the SDK ergonomics
|
|
390
|
+
* differ. Backend filters out `revoked` rows but keeps
|
|
391
|
+
* `pending_verification` rows visible (D5).
|
|
392
|
+
*/
|
|
393
|
+
declare function useExternalAccounts(args: OwnerRef): QueryResult<List<ExternalAccount>>;
|
|
348
394
|
declare function useSubAccounts(_args: OwnerRef): QueryResult<List<SubAccount>>;
|
|
349
395
|
declare function useVirtualAccounts(_filters?: VirtualAccountsFilters): QueryResult<List<VirtualAccount>>;
|
|
350
396
|
declare function useVirtualCards(_filters?: VirtualCardsFilters): QueryResult<List<VirtualCard>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { HttpTransport, TransportState, BrowserCapxulConfig, AccountId, OrganizationId, ApiKeyId, BalanceLedgerEntryId, ExternalAccountId, MemberId, Account, ApiKey, BalanceLedgerEntry, DocumentId, Document, ExternalAccount, KybProfile, KycProfile, CapxulError as CapxulError$1, Member, OperationId, Operation, Organization, PaymentId, Payment, SafeId, Safe, SubAccountId, SubAccount, TokenTransfer, TransferId, Transfer, Treasury, VirtualAccountId, VirtualAccount, VirtualCardId, VirtualCard, WebhookEndpointId, WebhookEndpoint, WebhookEventId, WebhookEvent, WithdrawalId, Withdrawal, List, TokenTransfersListInput, TokenTransfersListPage, LocalPrivateKeySignerProvider } from '@capxul/sdk';
|
|
2
|
+
import { HttpTransport, TransportState, BrowserCapxulConfig, AuthSessionStore, AccountId, OrganizationId, ApiKeyId, BalanceLedgerEntryId, ExternalAccountId, MemberId, Account, ApiKey, BalanceLedgerEntry, DocumentId, Document, ExternalAccount, KybProfile, KycProfile, CapxulError as CapxulError$1, Member, OperationId, Operation, Organization, PaymentId, Payment, SafeId, Safe, SubAccountId, SubAccount, TokenTransfer, TransferId, Transfer, Treasury, VirtualAccountId, VirtualAccount, VirtualCardId, VirtualCard, WebhookEndpointId, WebhookEndpoint, WebhookEventId, WebhookEvent, WithdrawalId, Withdrawal, List, TokenTransfersListInput, TokenTransfersListPage, LocalPrivateKeySignerProvider } from '@capxul/sdk';
|
|
3
3
|
export { AuthFlowContext, AuthFlowEvent, BrowserCapxulConfig, OnboardingFlowContext, OnboardingFlowEvent, ProvisioningFlowContext, ProvisioningFlowEvent } from '@capxul/sdk';
|
|
4
4
|
import { QueryClient, UseQueryResult } from '@tanstack/react-query';
|
|
5
5
|
import { CapxulClient } from '@capxul/sdk/client';
|
|
@@ -58,16 +58,30 @@ declare function useCapxulStatus(): TransportState;
|
|
|
58
58
|
* `CapxulProvider` — public React provider for `@capxul/sdk-react`.
|
|
59
59
|
*
|
|
60
60
|
* Single-input contract: accepts ONLY `{ config: BrowserCapxulConfig,
|
|
61
|
-
* queryClient?, children }`. The browser config is the
|
|
62
|
-
* discriminated union from `@capxul/sdk` — `apiKey`,
|
|
63
|
-
* and other server-only fields are rejected at
|
|
64
|
-
* runtime via `createCapxulConfig`'s allow-list
|
|
61
|
+
* sessionStore?, queryClient?, children }`. The browser config is the
|
|
62
|
+
* secret-safe discriminated union from `@capxul/sdk` — `apiKey`,
|
|
63
|
+
* `data`, `signer`, and other server-only fields are rejected at
|
|
64
|
+
* compile-time AND at runtime via `createCapxulConfig`'s allow-list
|
|
65
|
+
* validator.
|
|
65
66
|
*
|
|
66
67
|
* The provider builds an `HttpTransport` synchronously and injects it
|
|
67
68
|
* into `createCapxulClient` via the internal `_transport` slot. The
|
|
68
69
|
* transport is exposed through `CapxulTransportContext` so
|
|
69
70
|
* `useCapxulStatus()` can subscribe to its lifecycle state machine.
|
|
70
71
|
*
|
|
72
|
+
* The provider also builds a `ConvexReactClient`-backed data client
|
|
73
|
+
* for the `build-time-urls` arm and feeds it as `config.data` so the
|
|
74
|
+
* SDK domain methods (`me.get`, `accounts.retrieve`, ...) and the
|
|
75
|
+
* React hooks built on top can run authenticated reads against live
|
|
76
|
+
* Convex. The auth lifecycle threads through the optional
|
|
77
|
+
* `sessionStore` prop: `verifyOtp` writes a session to the store
|
|
78
|
+
* (which the SDK auth client persists), and the provider's data
|
|
79
|
+
* client re-reads the JWT on demand via `refreshAuth()`. Without a
|
|
80
|
+
* session store the provider falls back to an in-memory one, which
|
|
81
|
+
* is fine for browser apps that hold the page until next reload but
|
|
82
|
+
* inadequate for CLI / Node consumers that need cross-process
|
|
83
|
+
* persistence — those pass a file-backed store via the prop.
|
|
84
|
+
*
|
|
71
85
|
* Tests and the e2e harness need the server-augmented `CapxulConfig`
|
|
72
86
|
* shape (with `data`, `signer`, `signing`). Those callers use
|
|
73
87
|
* `CapxulTestProvider` from `@capxul/sdk-react/proof` instead.
|
|
@@ -88,6 +102,17 @@ type CapxulProviderProps = {
|
|
|
88
102
|
* via `createCapxulConfig`'s allow-list validator.
|
|
89
103
|
*/
|
|
90
104
|
readonly config: BrowserCapxulConfig;
|
|
105
|
+
/**
|
|
106
|
+
* Auth session persistence adapter. The SDK auth client writes
|
|
107
|
+
* sessions here on `verifyOtp` and clears them on `signOut`. The
|
|
108
|
+
* provider's data client re-reads the JWT from this store on every
|
|
109
|
+
* `refreshAuth` call so reads carry the right auth header.
|
|
110
|
+
*
|
|
111
|
+
* Defaults to an in-memory store scoped to this provider. Pass a
|
|
112
|
+
* file-backed (Node CLI) or `localStorage`-backed (browser) store
|
|
113
|
+
* to persist sessions across process or page lifetimes.
|
|
114
|
+
*/
|
|
115
|
+
readonly sessionStore?: AuthSessionStore;
|
|
91
116
|
/**
|
|
92
117
|
* Optional TanStack Query `QueryClient`. Pass your app's existing
|
|
93
118
|
* client to share the cache across the SDK hooks and the host
|
|
@@ -97,7 +122,7 @@ type CapxulProviderProps = {
|
|
|
97
122
|
readonly queryClient?: QueryClient;
|
|
98
123
|
readonly children: ReactNode;
|
|
99
124
|
};
|
|
100
|
-
declare function CapxulProvider({ config, queryClient, children, }: CapxulProviderProps): ReactNode;
|
|
125
|
+
declare function CapxulProvider({ config, sessionStore, queryClient, children, }: CapxulProviderProps): ReactNode;
|
|
101
126
|
|
|
102
127
|
/**
|
|
103
128
|
* `QueryResult<T>` — the canonical three-state return shape for every
|
|
@@ -232,7 +257,18 @@ declare function useTreasury(_organizationId: OrganizationId): QueryResult<Treas
|
|
|
232
257
|
declare function useApiKey(_args: UseApiKeyArgs): QueryResult<ApiKey>;
|
|
233
258
|
declare function useKycProfile(_accountId: AccountId): QueryResult<KycProfile>;
|
|
234
259
|
declare function useKybProfile(_organizationId: OrganizationId): QueryResult<KybProfile>;
|
|
235
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Withdrawals v1 W1 (#464) — wired through the SDK.
|
|
262
|
+
*
|
|
263
|
+
* Branches on `args.ownerKind` so `account` scope reads via the
|
|
264
|
+
* top-level `capxul.externalAccounts.retrieve(id)` (visibility-gated
|
|
265
|
+
* server-side by the caller's accountId), and `organization` scope
|
|
266
|
+
* reads via `capxul.organizations.externalAccounts.retrieve({ ... })`
|
|
267
|
+
* which adds an org-scope check for cross-org isolation. Both routes
|
|
268
|
+
* resolve to the same Convex query handler — the SDK ergonomics
|
|
269
|
+
* differ but the wire shape is identical.
|
|
270
|
+
*/
|
|
271
|
+
declare function useExternalAccount(args: UseExternalAccountArgs): QueryResult<ExternalAccount>;
|
|
236
272
|
declare function useSubAccount(_subAccountId: SubAccountId): QueryResult<SubAccount>;
|
|
237
273
|
declare function useVirtualAccount(_virtualAccountId: VirtualAccountId): QueryResult<VirtualAccount>;
|
|
238
274
|
declare function useVirtualCard(_virtualCardId: VirtualCardId): QueryResult<VirtualCard>;
|
|
@@ -344,7 +380,17 @@ type OrgDocumentsFilters = OrgScopedFilters & {
|
|
|
344
380
|
*/
|
|
345
381
|
declare function useOrganizations(): QueryResult<List<Organization>>;
|
|
346
382
|
declare function useMembers(_organizationId: OrganizationId): QueryResult<List<Member>>;
|
|
347
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Withdrawals v1 W1 (#464) — wired through the SDK.
|
|
385
|
+
*
|
|
386
|
+
* Personal scope reads through `capxul.accounts.externalAccounts.list({
|
|
387
|
+
* accountId })` (Pattern A nested namespace); org scope reads through
|
|
388
|
+
* `capxul.organizations.externalAccounts.list({ organizationId })`.
|
|
389
|
+
* Both resolve to the same Convex query handler — the SDK ergonomics
|
|
390
|
+
* differ. Backend filters out `revoked` rows but keeps
|
|
391
|
+
* `pending_verification` rows visible (D5).
|
|
392
|
+
*/
|
|
393
|
+
declare function useExternalAccounts(args: OwnerRef): QueryResult<List<ExternalAccount>>;
|
|
348
394
|
declare function useSubAccounts(_args: OwnerRef): QueryResult<List<SubAccount>>;
|
|
349
395
|
declare function useVirtualAccounts(_filters?: VirtualAccountsFilters): QueryResult<List<VirtualAccount>>;
|
|
350
396
|
declare function useVirtualCards(_filters?: VirtualCardsFilters): QueryResult<List<VirtualCard>>;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { createContext, useContext, useSyncExternalStore, useMemo,
|
|
2
|
+
import { createContext, useContext, useSyncExternalStore, useMemo, useEffect, useState } from 'react';
|
|
3
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';
|
|
@@ -108,7 +109,29 @@ var Errors = {
|
|
|
108
109
|
{ details }
|
|
109
110
|
),
|
|
110
111
|
emailDeliveryFailed: (detail) => new CapxulError("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
|
|
111
|
-
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
|
+
}
|
|
112
135
|
};
|
|
113
136
|
|
|
114
137
|
// ../config/src/org-roles.ts
|
|
@@ -170,12 +193,45 @@ function assertModeRequiredFields(input) {
|
|
|
170
193
|
}
|
|
171
194
|
}
|
|
172
195
|
}
|
|
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
|
+
};
|
|
217
|
+
}
|
|
173
218
|
function CapxulProvider({
|
|
174
219
|
config,
|
|
220
|
+
sessionStore,
|
|
175
221
|
queryClient,
|
|
176
222
|
children
|
|
177
223
|
}) {
|
|
178
|
-
const
|
|
224
|
+
const defaultSessionStore = useMemo(() => createMemorySessionStore(), []);
|
|
225
|
+
const effectiveSessionStore = sessionStore ?? defaultSessionStore;
|
|
226
|
+
const wiring = useMemo(
|
|
227
|
+
() => buildWiring(config, effectiveSessionStore),
|
|
228
|
+
[config, effectiveSessionStore]
|
|
229
|
+
);
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
return () => {
|
|
232
|
+
wiring.dataClient?.close();
|
|
233
|
+
};
|
|
234
|
+
}, [wiring]);
|
|
179
235
|
const defaultClient = useMemo(
|
|
180
236
|
() => new QueryClient({
|
|
181
237
|
defaultOptions: { queries: { staleTime: 3e4 } }
|
|
@@ -185,16 +241,58 @@ function CapxulProvider({
|
|
|
185
241
|
const effectiveClient = queryClient ?? defaultClient;
|
|
186
242
|
return /* @__PURE__ */ jsx(QueryClientProvider, { client: effectiveClient, children: /* @__PURE__ */ jsx(CapxulTransportProvider, { transport: wiring.transport, children: /* @__PURE__ */ jsx(CapxulClientProvider, { client: wiring.client, children }) }) });
|
|
187
243
|
}
|
|
188
|
-
function buildWiring(config) {
|
|
244
|
+
function buildWiring(config, sessionStore) {
|
|
189
245
|
const validated = createCapxulConfig(config);
|
|
190
246
|
const transport = makeHttpTransport(validated);
|
|
247
|
+
const dataClient = validated.mode === "build-time-urls" ? createReactDataClient(validated.convexUrl, sessionStore) : null;
|
|
191
248
|
const sdkConfig = {
|
|
192
249
|
_transport: transport,
|
|
193
|
-
publishableKey: validated.mode === "publishable-key" ? validated.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
|
|
194
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
|
+
}
|
|
195
280
|
return {
|
|
196
|
-
client
|
|
197
|
-
transport
|
|
281
|
+
client,
|
|
282
|
+
transport,
|
|
283
|
+
dataClient
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function createMemorySessionStore() {
|
|
287
|
+
let current = null;
|
|
288
|
+
return {
|
|
289
|
+
get: () => current,
|
|
290
|
+
set: (session) => {
|
|
291
|
+
current = session;
|
|
292
|
+
},
|
|
293
|
+
clear: () => {
|
|
294
|
+
current = null;
|
|
295
|
+
}
|
|
198
296
|
};
|
|
199
297
|
}
|
|
200
298
|
function notImplementedQuery(hookName) {
|
|
@@ -316,8 +414,15 @@ function useKycProfile(_accountId) {
|
|
|
316
414
|
function useKybProfile(_organizationId) {
|
|
317
415
|
return notImplementedQuery("useKybProfile");
|
|
318
416
|
}
|
|
319
|
-
function useExternalAccount(
|
|
320
|
-
|
|
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
|
+
);
|
|
321
426
|
}
|
|
322
427
|
function useSubAccount(_subAccountId) {
|
|
323
428
|
return notImplementedQuery("useSubAccount");
|
|
@@ -375,8 +480,14 @@ function useOrganizations() {
|
|
|
375
480
|
function useMembers(_organizationId) {
|
|
376
481
|
return notImplementedQuery("useMembers");
|
|
377
482
|
}
|
|
378
|
-
function useExternalAccounts(
|
|
379
|
-
|
|
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
|
+
);
|
|
380
491
|
}
|
|
381
492
|
function useSubAccounts(_args) {
|
|
382
493
|
return notImplementedQuery("useSubAccounts");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capxul/sdk-react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.6",
|
|
4
4
|
"description": "React provider + hooks for the @capxul/sdk headless client.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -46,13 +46,13 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@capxul/sdk": "0.1.0-alpha.
|
|
49
|
+
"@capxul/sdk": "0.1.0-alpha.6"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@tanstack/react-query": "^5",
|
|
53
53
|
"@xstate/react": "^5",
|
|
54
54
|
"convex": ">=1.0.0",
|
|
55
|
-
"react": ">=
|
|
55
|
+
"react": ">=18.2.0 <20",
|
|
56
56
|
"viem": ">=2.0.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|