@capxul/sdk 0.1.0-alpha.4 → 0.1.0-alpha.8

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/client.cjs CHANGED
@@ -69,7 +69,268 @@ function fromConvexError(error) {
69
69
  });
70
70
  }
71
71
 
72
+ // ../observability/src/try-catch.ts
73
+ async function tryCatch(promise) {
74
+ try {
75
+ return [null, await promise];
76
+ } catch (e) {
77
+ return [e instanceof Error ? e : new Error(String(e)), null];
78
+ }
79
+ }
80
+
81
+ // ../observability/src/debug-log.ts
82
+ function isDevelopmentBuild() {
83
+ if (typeof process === "undefined") {
84
+ return false;
85
+ }
86
+ return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
87
+ }
88
+ function debugLog(line) {
89
+ if (!isDevelopmentBuild()) return;
90
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
91
+ console.info(line);
92
+ return;
93
+ }
94
+ if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
95
+ process.stderr.write(`${line}
96
+ `);
97
+ }
98
+ }
99
+ function formatDebugValue(value) {
100
+ if (value === void 0 || value === "") return "";
101
+ if (typeof value === "string") return value;
102
+ try {
103
+ return JSON.stringify(value);
104
+ } catch {
105
+ return String(value);
106
+ }
107
+ }
108
+ function track(...args) {
109
+ const [name, props] = args;
110
+ debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
111
+ }
112
+ function formatDebugValue2(value) {
113
+ if (value === void 0 || value === "") return "";
114
+ if (typeof value === "string") return value;
115
+ try {
116
+ return JSON.stringify(value);
117
+ } catch {
118
+ return String(value);
119
+ }
120
+ }
121
+ function identify(userId, traits) {
122
+ debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
123
+ }
124
+
125
+ // ../platform-kernel/src/ids.ts
126
+ function makePrefixedIdConstructor(prefix, fieldName) {
127
+ const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
128
+ return (raw) => {
129
+ if (typeof raw !== "string" || !re.test(raw)) {
130
+ throw new Error(
131
+ `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
132
+ );
133
+ }
134
+ return raw;
135
+ };
136
+ }
137
+ var toOperationId = makePrefixedIdConstructor(
138
+ "op",
139
+ "operationId"
140
+ );
141
+ var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
142
+ var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
143
+
144
+ // src/core/external-accounts.ts
145
+ function brandExternalAccount(raw) {
146
+ return {
147
+ ...raw,
148
+ id: toExternalAccountId(raw.id),
149
+ operation: {
150
+ id: toOperationId(raw.operation.id),
151
+ status: raw.operation.status,
152
+ correlationId: toCorrelationId(raw.operation.correlationId)
153
+ }
154
+ };
155
+ }
156
+ function createExternalAccountsClient(config = {}) {
157
+ return {
158
+ retrieve: async (externalAccountId) => {
159
+ if (!config.data) {
160
+ return stub(
161
+ "externalAccounts.retrieve"
162
+ );
163
+ }
164
+ const [err, raw] = await tryCatch(
165
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
166
+ externalAccountId
167
+ })
168
+ );
169
+ if (err) {
170
+ return [
171
+ fromConvexError(err),
172
+ null
173
+ ];
174
+ }
175
+ if (!raw) {
176
+ return [
177
+ new CapxulError({
178
+ code: "NOT_FOUND",
179
+ message: `external_account ${externalAccountId} not found`
180
+ }),
181
+ null
182
+ ];
183
+ }
184
+ return [null, brandExternalAccount(raw)];
185
+ },
186
+ remove: async (externalAccountId) => {
187
+ if (!config.data) {
188
+ return stub("externalAccounts.remove");
189
+ }
190
+ const [err] = await tryCatch(
191
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
192
+ externalAccountId
193
+ })
194
+ );
195
+ if (err) {
196
+ return [
197
+ fromConvexError(err),
198
+ null
199
+ ];
200
+ }
201
+ return [null, void 0];
202
+ }
203
+ };
204
+ }
205
+
72
206
  // src/core/accounts.ts
207
+ function createAccountExternalAccountsClient(config) {
208
+ return {
209
+ create: async (input) => {
210
+ if (!config.data) {
211
+ return stub(
212
+ "accounts.externalAccounts.create"
213
+ );
214
+ }
215
+ const [err, raw] = await tryCatch(
216
+ config.data.mutation(
217
+ api.externalAccounts.mutations.createPersonal,
218
+ {
219
+ kind: input.kind,
220
+ label: input.label,
221
+ address: input.address,
222
+ iban: input.iban,
223
+ bic: input.bic,
224
+ accountHolder: input.accountHolder,
225
+ network: input.network,
226
+ panToken: input.panToken,
227
+ last4: input.last4
228
+ }
229
+ )
230
+ );
231
+ if (err) {
232
+ return [
233
+ fromConvexError(err),
234
+ null
235
+ ];
236
+ }
237
+ if (!raw) {
238
+ return [
239
+ new CapxulError({
240
+ code: "NOT_FOUND",
241
+ message: "external_account creation returned no resource"
242
+ }),
243
+ null
244
+ ];
245
+ }
246
+ return [
247
+ null,
248
+ brandExternalAccount(
249
+ raw
250
+ )
251
+ ];
252
+ },
253
+ list: async (input) => {
254
+ if (!config.data) {
255
+ return stub(
256
+ "accounts.externalAccounts.list"
257
+ );
258
+ }
259
+ const [err, result] = await tryCatch(
260
+ config.data.query(api.externalAccounts.queries.listPersonal, {
261
+ limit: input.limit,
262
+ cursor: input.cursor
263
+ })
264
+ );
265
+ if (err) {
266
+ return [fromConvexError(err), null];
267
+ }
268
+ const branded = result.data.map(
269
+ (row) => brandExternalAccount(
270
+ row
271
+ )
272
+ );
273
+ return [
274
+ null,
275
+ {
276
+ object: "list",
277
+ data: branded,
278
+ page: result.page
279
+ }
280
+ ];
281
+ },
282
+ retrieve: async (externalAccountId) => {
283
+ if (!config.data) {
284
+ return stub(
285
+ "accounts.externalAccounts.retrieve"
286
+ );
287
+ }
288
+ const [err, raw] = await tryCatch(
289
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
290
+ externalAccountId
291
+ })
292
+ );
293
+ if (err) {
294
+ return [
295
+ fromConvexError(err),
296
+ null
297
+ ];
298
+ }
299
+ if (!raw) {
300
+ return [
301
+ new CapxulError({
302
+ code: "NOT_FOUND",
303
+ message: `external_account ${externalAccountId} not found`
304
+ }),
305
+ null
306
+ ];
307
+ }
308
+ return [
309
+ null,
310
+ brandExternalAccount(
311
+ raw
312
+ )
313
+ ];
314
+ },
315
+ remove: async (externalAccountId) => {
316
+ if (!config.data) {
317
+ return stub("accounts.externalAccounts.remove");
318
+ }
319
+ const [err] = await tryCatch(
320
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
321
+ externalAccountId
322
+ })
323
+ );
324
+ if (err) {
325
+ return [
326
+ fromConvexError(err),
327
+ null
328
+ ];
329
+ }
330
+ return [null, void 0];
331
+ }
332
+ };
333
+ }
73
334
  function createAccountsClient(config = {}) {
74
335
  return {
75
336
  retrieve: async (accountId) => {
@@ -224,18 +485,7 @@ function createAccountsClient(config = {}) {
224
485
  create: async () => stub("accounts.kycProfiles.create"),
225
486
  retrieve: async () => stub("accounts.kycProfiles.retrieve")
226
487
  },
227
- externalAccounts: {
228
- create: async () => stub(
229
- "accounts.externalAccounts.create"
230
- ),
231
- list: async () => stub(
232
- "accounts.externalAccounts.list"
233
- ),
234
- retrieve: async () => stub(
235
- "accounts.externalAccounts.retrieve"
236
- ),
237
- remove: async () => stub("accounts.externalAccounts.remove")
238
- },
488
+ externalAccounts: createAccountExternalAccountsClient(config),
239
489
  subAccounts: {
240
490
  create: async () => stub("accounts.subAccounts.create"),
241
491
  list: async () => stub("accounts.subAccounts.list"),
@@ -329,8 +579,35 @@ var Errors = {
329
579
  "Idempotency key was already used for a different request",
330
580
  { details }
331
581
  ),
332
- emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
333
- internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`)
582
+ emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
583
+ details
584
+ }),
585
+ rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
586
+ details: { ...details }
587
+ }),
588
+ internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
589
+ /**
590
+ * Verification gate. Surfaced when a request hits a verification
591
+ * boundary the actor cannot cross under their current state. Two
592
+ * variants share this code:
593
+ *
594
+ * - Rail gate (Withdrawals v1 W2, #465): the resolved
595
+ * `external_account.kind` routes to a withdrawal rail (e.g.
596
+ * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
597
+ * `details.rail` + `details.currentKind`.
598
+ * - KYC tier gate (legacy / future): the actor's KYC tier is below
599
+ * the required tier. Carries `details.requiredTier`.
600
+ *
601
+ * Code is shared because both expose the same UX shape ("you cannot
602
+ * proceed until verification advances"); the `details.*` keys
603
+ * differentiate the route.
604
+ */
605
+ verificationRequired: (details) => {
606
+ const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
607
+ return new CapxulError2("VERIFICATION_REQUIRED", message, {
608
+ details: { ...details }
609
+ });
610
+ }
334
611
  };
335
612
 
336
613
  // ../config/src/safe.ts
@@ -380,13 +657,13 @@ function createLifecycle(initial) {
380
657
  }
381
658
  function makeBuildTimeUrlsTransport(config) {
382
659
  if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
383
- throw Errors.invalidInput(
660
+ throw invalidConfigError(
384
661
  "authBaseUrl",
385
662
  "build-time-urls transport requires a non-empty authBaseUrl."
386
663
  );
387
664
  }
388
665
  if (!config.convexUrl || config.convexUrl.trim().length === 0) {
389
- throw Errors.invalidInput(
666
+ throw invalidConfigError(
390
667
  "convexUrl",
391
668
  "build-time-urls transport requires a non-empty convexUrl."
392
669
  );
@@ -400,6 +677,7 @@ function makeBuildTimeUrlsTransport(config) {
400
677
  return {
401
678
  authBaseUrl,
402
679
  convexUrl,
680
+ ensureRuntime: async () => runtime,
403
681
  fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
404
682
  getState: lifecycle.getState,
405
683
  subscribe: lifecycle.subscribe,
@@ -415,14 +693,15 @@ function makeBuildTimeUrlsTransport(config) {
415
693
  };
416
694
  }
417
695
  function makePublishableKeyTransport(config) {
418
- if (!config.publishableKey || config.publishableKey.trim().length === 0) {
419
- throw Errors.invalidInput(
696
+ const publishableKey = config.publishableKey?.trim();
697
+ if (!publishableKey) {
698
+ throw invalidConfigError(
420
699
  "publishableKey",
421
700
  "publishable-key transport requires a non-empty publishableKey."
422
701
  );
423
702
  }
424
703
  const fetchImpl = config.fetchImpl ?? globalThis.fetch;
425
- const bootstrapUrl = stripTrailingSlash(
704
+ const bootstrapUrl = normalizeBootstrapUrl(
426
705
  config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
427
706
  );
428
707
  let authBaseUrl = "";
@@ -437,23 +716,20 @@ function makePublishableKeyTransport(config) {
437
716
  const response = await fetchImpl(bootstrapUrl, {
438
717
  method: "POST",
439
718
  headers: { "content-type": "application/json" },
440
- body: JSON.stringify({ publishableKey: config.publishableKey })
719
+ body: JSON.stringify({ publishableKey })
441
720
  });
442
721
  if (!response.ok) {
443
- throw Errors.invalidInput(
444
- "publishableKey",
445
- `${bootstrapUrl} failed with HTTP ${response.status}.`
446
- );
722
+ throw await bootstrapResponseError(response, bootstrapUrl);
447
723
  }
448
- const body = await response.json();
724
+ const body = await readBootstrapSuccessBody(response);
449
725
  if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
450
- throw Errors.invalidInput(
726
+ throw bootstrapContractError(
451
727
  "authBaseUrl",
452
728
  "/v1/client/bootstrap returned no authBaseUrl."
453
729
  );
454
730
  }
455
731
  if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
456
- throw Errors.invalidInput(
732
+ throw bootstrapContractError(
457
733
  "convexUrl",
458
734
  "/v1/client/bootstrap returned no convexUrl."
459
735
  );
@@ -465,16 +741,13 @@ function makePublishableKeyTransport(config) {
465
741
  return runtime;
466
742
  })();
467
743
  bootstrapPromise = attempt.catch((err) => {
744
+ const error = normalizeBootstrapThrownError(err);
468
745
  bootstrapPromise = null;
469
746
  lifecycle.setState({
470
747
  status: "error",
471
- error: err instanceof CapxulError ? err : new CapxulError({
472
- code: "UNKNOWN",
473
- message: "Bootstrap failed without a typed CapxulError.",
474
- cause: err
475
- })
748
+ error
476
749
  });
477
- throw err;
750
+ throw error;
478
751
  });
479
752
  return await bootstrapPromise;
480
753
  }
@@ -485,6 +758,7 @@ function makePublishableKeyTransport(config) {
485
758
  get convexUrl() {
486
759
  return convexUrl;
487
760
  },
761
+ ensureRuntime: ensureBootstrap,
488
762
  fetch: async (path, init) => {
489
763
  const resolved = await ensureBootstrap();
490
764
  return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
@@ -495,7 +769,7 @@ function makePublishableKeyTransport(config) {
495
769
  markAuthenticated: ({ dataClient: nextDataClient }) => {
496
770
  const current = lifecycle.getState();
497
771
  if (current.status !== "ready" && current.status !== "authenticated") {
498
- throw Errors.internalError(
772
+ throw internalTransportError(
499
773
  `markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
500
774
  );
501
775
  }
@@ -517,6 +791,24 @@ function makePublishableKeyTransport(config) {
517
791
  function stripTrailingSlash(url) {
518
792
  return url.replace(/\/+$/, "");
519
793
  }
794
+ function normalizeBootstrapUrl(url) {
795
+ const normalized = stripTrailingSlash(url.trim());
796
+ if (!isAbsoluteHttpUrl(normalized)) {
797
+ throw invalidConfigError(
798
+ "bootstrapUrl",
799
+ "publishable-key transport requires an absolute http(s) bootstrapUrl."
800
+ );
801
+ }
802
+ return normalized;
803
+ }
804
+ function isAbsoluteHttpUrl(url) {
805
+ try {
806
+ const parsed = new URL(url);
807
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
808
+ } catch {
809
+ return false;
810
+ }
811
+ }
520
812
  function resolveUrl(authBaseUrl, path) {
521
813
  if (path.startsWith("http://") || path.startsWith("https://")) {
522
814
  return path;
@@ -524,10 +816,142 @@ function resolveUrl(authBaseUrl, path) {
524
816
  return `${authBaseUrl}${path}`;
525
817
  }
526
818
  function assertNever(value) {
527
- throw Errors.internalError(
819
+ throw internalTransportError(
528
820
  `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
529
821
  );
530
822
  }
823
+ function invalidConfigError(field, reason) {
824
+ return new CapxulError({
825
+ code: "INVALID_INPUT",
826
+ message: `Invalid ${field}: ${reason}`,
827
+ details: { source: "sdk-config", field, reason }
828
+ });
829
+ }
830
+ function bootstrapContractError(field, message) {
831
+ return new CapxulError({
832
+ code: "INVALID_INPUT",
833
+ message,
834
+ details: {
835
+ source: "backend-bootstrap",
836
+ phase: "publishable-key-bootstrap",
837
+ field,
838
+ reason: message
839
+ }
840
+ });
841
+ }
842
+ function internalTransportError(reason) {
843
+ return new CapxulError({
844
+ code: "INTERNAL_ERROR",
845
+ message: `Internal error: ${reason}`,
846
+ details: { source: "sdk-transport", reason }
847
+ });
848
+ }
849
+ async function bootstrapResponseError(response, bootstrapUrl) {
850
+ const envelope = await readBootstrapErrorEnvelope(response);
851
+ const wireCode = readNonEmptyString(envelope?.error?.code);
852
+ const normalized = normalizeBootstrapErrorCode(wireCode);
853
+ const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
854
+ const backendDetails = readRecord(envelope?.error?.details);
855
+ return new CapxulError({
856
+ code: normalized.code,
857
+ message,
858
+ details: {
859
+ ...backendDetails,
860
+ source: "backend-bootstrap",
861
+ phase: "publishable-key-bootstrap",
862
+ httpStatus: response.status,
863
+ ...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
864
+ },
865
+ operationId: readNonEmptyString(envelope?.error?.operationId),
866
+ correlationId: readNonEmptyString(envelope?.error?.correlationId),
867
+ retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
868
+ });
869
+ }
870
+ async function readBootstrapErrorEnvelope(response) {
871
+ try {
872
+ const parsed = await response.json();
873
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
874
+ } catch {
875
+ return null;
876
+ }
877
+ }
878
+ async function readBootstrapSuccessBody(response) {
879
+ try {
880
+ const parsed = await response.json();
881
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
882
+ } catch {
883
+ throw bootstrapContractError(
884
+ "body",
885
+ "/v1/client/bootstrap returned invalid JSON."
886
+ );
887
+ }
888
+ }
889
+ function normalizeBootstrapThrownError(error) {
890
+ if (error instanceof CapxulError) return error;
891
+ return new CapxulError({
892
+ code: "NETWORK_ERROR",
893
+ message: "Publishable-key bootstrap network failure.",
894
+ cause: error,
895
+ details: {
896
+ source: "bootstrap-network",
897
+ phase: "publishable-key-bootstrap"
898
+ }
899
+ });
900
+ }
901
+ function normalizeBootstrapErrorCode(wireCode) {
902
+ if (wireCode === "INTERNAL_SERVER_ERROR") {
903
+ return { code: "INTERNAL_ERROR", wireCode };
904
+ }
905
+ if (wireCode && isCapxulErrorCode(wireCode)) {
906
+ return { code: wireCode };
907
+ }
908
+ return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
909
+ }
910
+ var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
911
+ "NOT_AUTHENTICATED",
912
+ "EMAIL_DELIVERY_FAILED",
913
+ "PROFILE_NOT_FOUND",
914
+ "SMART_ACCOUNT_MISSING",
915
+ "PLAYER_NOT_FOUND",
916
+ "ACCOUNT_NOT_FOUND",
917
+ "PROVIDER_ERROR",
918
+ "INVALID_INPUT",
919
+ "ENV_MISSING",
920
+ "NOT_IMPLEMENTED",
921
+ "VERIFICATION_REQUIRED",
922
+ "INSUFFICIENT_BALANCE",
923
+ "INVALID_RECIPIENT",
924
+ "TRANSACTION_FAILED",
925
+ "RATE_LIMITED",
926
+ "NETWORK_ERROR",
927
+ "UNKNOWN",
928
+ "PERMISSION_DENIED",
929
+ "API_KEY_INVALID",
930
+ "API_KEY_EXPIRED",
931
+ "IDEMPOTENCY_CONFLICT",
932
+ "NOT_FOUND",
933
+ "OPERATION_CANCELED",
934
+ "OPERATION_TIMEOUT",
935
+ "ACTION_REQUIRED",
936
+ "KYC_REQUIRED",
937
+ "POLICY_DENIED",
938
+ "SAFE_NOT_READY",
939
+ "PROVIDER_UNAVAILABLE",
940
+ "PROVIDER_REJECTED",
941
+ "RECONCILIATION_FAILED",
942
+ "INTERNAL_ERROR",
943
+ "QUOTE_EXPIRED",
944
+ "QUOTE_NOT_FOUND"
945
+ ]);
946
+ function isCapxulErrorCode(value) {
947
+ return CAPXUL_ERROR_CODES.has(value);
948
+ }
949
+ function readRecord(value) {
950
+ return typeof value === "object" && value !== null ? value : null;
951
+ }
952
+ function readNonEmptyString(value) {
953
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
954
+ }
531
955
 
532
956
  // src/core/auth.ts
533
957
  function createAuthClient(config = {}) {
@@ -582,13 +1006,16 @@ function createAuthClient(config = {}) {
582
1006
  email: signIn.user.email,
583
1007
  token: signIn.token,
584
1008
  convexJwt,
585
- expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString()
1009
+ expiresAt: new Date(
1010
+ Date.now() + 30 * 24 * 60 * 60 * 1e3
1011
+ ).toISOString()
586
1012
  };
587
1013
  sessionStore.set(session);
588
1014
  if (config.auth?.createDataClient) {
589
1015
  try {
590
1016
  dataClient = await config.auth.createDataClient(session);
591
1017
  mutableConfig(config).data = dataClient;
1018
+ transport.markAuthenticated({ dataClient });
592
1019
  } catch (cause) {
593
1020
  return [
594
1021
  new CapxulError({
@@ -607,6 +1034,8 @@ function createAuthClient(config = {}) {
607
1034
  sessionStore.clear();
608
1035
  dataClient = null;
609
1036
  mutableConfig(config).data = void 0;
1037
+ const transport = getTransport();
1038
+ transport?.clearAuth();
610
1039
  return [null, void 0];
611
1040
  },
612
1041
  serviceTokenMint: async () => stub("auth.serviceTokenMint"),
@@ -660,16 +1089,19 @@ async function postBetterAuth(transport, path, body, code, signal) {
660
1089
  body: JSON.stringify(body),
661
1090
  signal
662
1091
  });
1092
+ const text = await response.text();
663
1093
  if (!response.ok) {
1094
+ const parsedError = parseBetterAuthError(text);
664
1095
  return [
665
1096
  new CapxulError({
666
- code,
667
- message: `BetterAuth ${path} failed with HTTP ${response.status}.`
1097
+ code: parsedError.code ?? code,
1098
+ message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
1099
+ details: parsedError.details,
1100
+ retryable: parsedError.retryable
668
1101
  }),
669
1102
  null
670
1103
  ];
671
1104
  }
672
- const text = await response.text();
673
1105
  return [null, text ? JSON.parse(text) : void 0];
674
1106
  } catch (cause) {
675
1107
  return [
@@ -682,6 +1114,36 @@ async function postBetterAuth(transport, path, body, code, signal) {
682
1114
  ];
683
1115
  }
684
1116
  }
1117
+ function parseBetterAuthError(text) {
1118
+ if (!text.trim()) {
1119
+ return {};
1120
+ }
1121
+ try {
1122
+ const body = JSON.parse(text);
1123
+ if (!body || typeof body !== "object") {
1124
+ return {};
1125
+ }
1126
+ const record = body;
1127
+ const nested = record.error && typeof record.error === "object" ? record.error : record;
1128
+ const code = typeof nested.code === "string" ? nested.code : void 0;
1129
+ const message = typeof nested.message === "string" ? nested.message : void 0;
1130
+ const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
1131
+ const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
1132
+ const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
1133
+ return {
1134
+ code: isCapxulErrorCode2(code) ? code : void 0,
1135
+ message,
1136
+ details,
1137
+ correlationId,
1138
+ retryable
1139
+ };
1140
+ } catch {
1141
+ return {};
1142
+ }
1143
+ }
1144
+ function isCapxulErrorCode2(code) {
1145
+ return code === "NOT_AUTHENTICATED" || code === "EMAIL_DELIVERY_FAILED" || code === "INVALID_INPUT" || code === "RATE_LIMITED" || code === "NETWORK_ERROR" || code === "API_KEY_INVALID" || code === "API_KEY_EXPIRED";
1146
+ }
685
1147
  async function exchangeConvexToken(transport, config, token, signal) {
686
1148
  const path = config.auth?.convexTokenUrl ?? "/convex/token";
687
1149
  try {
@@ -742,14 +1204,6 @@ function createOrgDocumentsClient() {
742
1204
  };
743
1205
  }
744
1206
 
745
- // src/core/external-accounts.ts
746
- function createExternalAccountsClient() {
747
- return {
748
- retrieve: async () => stub("externalAccounts.retrieve"),
749
- remove: async () => stub("externalAccounts.remove")
750
- };
751
- }
752
-
753
1207
  // src/core/me.ts
754
1208
  function createMeClient(config = {}) {
755
1209
  return {
@@ -1197,67 +1651,68 @@ function createOrgTransfersClient() {
1197
1651
  cancel: async () => stub("organizations.transfers.cancel")
1198
1652
  };
1199
1653
  }
1200
- var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
1201
1654
  function createWithdrawalsClient(config = {}) {
1202
1655
  return {
1203
1656
  create: async (input) => {
1204
1657
  if (!config.data) {
1205
1658
  return stub("withdrawals.create");
1206
1659
  }
1207
- let created = null;
1208
- let submitted = null;
1209
- try {
1210
- created = await config.data.mutation(
1211
- api.withdrawals.mutations.create,
1212
- {
1213
- amount: input.amount,
1214
- destination: {
1215
- externalAccountId: input.destination.externalAccountId,
1216
- kind: input.destination.kind
1217
- },
1218
- source: input.source,
1219
- reference: input.reference,
1220
- idempotencyKey: input.idempotencyKey
1221
- }
1222
- );
1223
- if (!created) {
1224
- return [
1225
- new CapxulError({
1226
- code: "NETWORK_ERROR",
1227
- message: "withdrawals.create returned no withdrawal resource"
1228
- }),
1229
- null
1230
- ];
1231
- }
1232
- if (created.status !== "processing" || created.operation.status !== "processing") {
1233
- return [null, created];
1234
- }
1235
- if (input.destination.kind !== "evm") {
1236
- return [null, created];
1237
- }
1238
- if (!config.signer || !config.signing) {
1239
- return [null, created];
1240
- }
1241
- if (!EVM_ADDRESS_RE.test(input.destination.externalAccountId)) {
1242
- throw new CapxulError({
1243
- code: "INVALID_INPUT",
1244
- message: "destination.externalAccountId must be a 0x-prefixed EVM address while external_accounts resolution is pending (slice 1).",
1245
- details: { field: "destination.externalAccountId" }
1246
- });
1247
- }
1248
- const currentSigner = await config.data.query(
1249
- api.safe.queries.getMySignerAddress,
1250
- {}
1660
+ const [createErr, createdRaw] = await tryCatch(
1661
+ config.data.mutation(api.withdrawals.mutations.create, {
1662
+ amount: input.amount,
1663
+ destination: {
1664
+ externalAccountId: input.destination.externalAccountId
1665
+ },
1666
+ source: input.source,
1667
+ reference: input.reference,
1668
+ idempotencyKey: input.idempotencyKey
1669
+ })
1670
+ );
1671
+ if (createErr) {
1672
+ return [mapCreateError2(fromConvexError(createErr)), null];
1673
+ }
1674
+ const created = createdRaw;
1675
+ if (!created) {
1676
+ return [
1677
+ new CapxulError({
1678
+ code: "NETWORK_ERROR",
1679
+ message: "withdrawals.create returned no withdrawal resource"
1680
+ }),
1681
+ null
1682
+ ];
1683
+ }
1684
+ if (created.status !== "processing" || created.operation.status !== "processing") {
1685
+ return [null, created];
1686
+ }
1687
+ if (!config.signer || !config.signing) {
1688
+ return [null, created];
1689
+ }
1690
+ const [signerErr, currentSigner] = await tryCatch(
1691
+ config.data.query(api.safe.queries.getMySignerAddress, {})
1692
+ );
1693
+ if (signerErr) {
1694
+ return await handleSubmissionFailure(
1695
+ { data: config.data },
1696
+ created.id,
1697
+ mapCreateError2(fromConvexError(signerErr))
1251
1698
  );
1252
- if (!currentSigner?.address) {
1253
- throw new CapxulError({
1699
+ }
1700
+ if (!currentSigner?.address) {
1701
+ return await handleSubmissionFailure(
1702
+ { data: config.data },
1703
+ created.id,
1704
+ new CapxulError({
1254
1705
  code: "PERMISSION_DENIED",
1255
1706
  message: "No signer is registered for the authenticated account.",
1256
1707
  details: { withdrawalId: created.id }
1257
- });
1258
- }
1259
- if (viem.getAddress(currentSigner.address) !== viem.getAddress(config.signer.address)) {
1260
- throw new CapxulError({
1708
+ })
1709
+ );
1710
+ }
1711
+ if (viem.getAddress(currentSigner.address) !== viem.getAddress(config.signer.address)) {
1712
+ return await handleSubmissionFailure(
1713
+ { data: config.data },
1714
+ created.id,
1715
+ new CapxulError({
1261
1716
  code: "PERMISSION_DENIED",
1262
1717
  message: "Configured signer does not match the authenticated account signer.",
1263
1718
  details: {
@@ -1265,170 +1720,228 @@ function createWithdrawalsClient(config = {}) {
1265
1720
  expectedSignerAddress: currentSigner.address,
1266
1721
  actualSignerAddress: config.signer.address
1267
1722
  }
1268
- });
1269
- }
1270
- const submission = await config.data.query(
1271
- api.withdrawals.queries.prepareSubmission,
1272
- { withdrawalId: created.id }
1723
+ })
1273
1724
  );
1274
- if (!submission?.externalAccountId) {
1275
- throw new CapxulError({
1725
+ }
1726
+ const [prepErr, submission] = await tryCatch(
1727
+ config.data.query(api.withdrawals.queries.prepareSubmission, {
1728
+ withdrawalId: created.id
1729
+ })
1730
+ );
1731
+ if (prepErr) {
1732
+ return await handleSubmissionFailure(
1733
+ { data: config.data },
1734
+ created.id,
1735
+ mapCreateError2(fromConvexError(prepErr))
1736
+ );
1737
+ }
1738
+ const destinationAddress = submission?.destinationAddress;
1739
+ if (!submission || !destinationAddress) {
1740
+ return await handleSubmissionFailure(
1741
+ { data: config.data },
1742
+ created.id,
1743
+ new CapxulError({
1276
1744
  code: "NETWORK_ERROR",
1277
1745
  message: "withdrawals.prepareSubmission returned no destination.",
1278
1746
  details: { withdrawalId: created.id }
1279
- });
1280
- }
1281
- const transfer = await transferAsOwner(
1747
+ })
1748
+ );
1749
+ }
1750
+ const [transferErr, transferOk] = await tryCatch(
1751
+ transferAsOwner(
1282
1752
  {
1283
1753
  signer: config.signer,
1284
1754
  signing: config.signing
1285
1755
  },
1286
1756
  {
1287
1757
  tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
1288
- recipientAddress: submission.externalAccountId,
1758
+ recipientAddress: destinationAddress,
1289
1759
  amount: toTokenUnits(submission.amount.value, 6)
1290
1760
  }
1761
+ )
1762
+ );
1763
+ if (transferErr) {
1764
+ return await handleSubmissionFailure(
1765
+ { data: config.data },
1766
+ created.id,
1767
+ mapCreateError2(fromConvexError(transferErr))
1291
1768
  );
1292
- if (!transfer.success) {
1293
- throw new CapxulError({
1769
+ }
1770
+ if (!transferOk.success) {
1771
+ return await handleSubmissionFailure(
1772
+ { data: config.data },
1773
+ created.id,
1774
+ new CapxulError({
1294
1775
  code: "NETWORK_ERROR",
1295
1776
  message: "Bundler submission did not succeed.",
1296
1777
  details: {
1297
1778
  withdrawalId: created.id,
1298
- txHash: transfer.txHash,
1299
- userOpHash: transfer.userOpHash
1779
+ txHash: transferOk.txHash,
1780
+ userOpHash: transferOk.userOpHash
1300
1781
  }
1301
- });
1302
- }
1303
- submitted = {
1304
- txHash: transfer.txHash,
1305
- userOpHash: transfer.userOpHash
1306
- };
1307
- await config.data.mutation(
1308
- api.withdrawals.mutations.recordSubmitted,
1309
- {
1310
- withdrawalId: created.id,
1311
- txHash: transfer.txHash,
1312
- userOpHash: transfer.userOpHash
1313
- }
1782
+ })
1314
1783
  );
1315
- return [null, created];
1316
- } catch (cause) {
1317
- const error = mapCreateError2(fromConvexError(cause));
1318
- if (created?.id && created.status === "processing" && !submitted) {
1319
- await bestEffortMarkFailed2({ data: config.data }, created.id, error);
1320
- }
1321
- if (submitted && created?.id) {
1322
- return [
1323
- new CapxulError({
1324
- code: "NETWORK_ERROR",
1325
- message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1326
- cause,
1327
- details: {
1328
- withdrawalId: created.id,
1329
- txHash: submitted.txHash,
1330
- userOpHash: submitted.userOpHash
1331
- }
1332
- }),
1333
- null
1334
- ];
1335
- }
1336
- return [error, null];
1337
1784
  }
1785
+ const [recordErr] = await tryCatch(
1786
+ config.data.mutation(api.withdrawals.mutations.recordSubmitted, {
1787
+ withdrawalId: created.id,
1788
+ txHash: transferOk.txHash,
1789
+ userOpHash: transferOk.userOpHash
1790
+ })
1791
+ );
1792
+ if (recordErr) {
1793
+ return [
1794
+ new CapxulError({
1795
+ code: "NETWORK_ERROR",
1796
+ message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1797
+ cause: recordErr,
1798
+ details: {
1799
+ withdrawalId: created.id,
1800
+ txHash: transferOk.txHash,
1801
+ userOpHash: transferOk.userOpHash
1802
+ }
1803
+ }),
1804
+ null
1805
+ ];
1806
+ }
1807
+ return [null, created];
1338
1808
  },
1339
1809
  retrieve: async (withdrawalId) => {
1340
1810
  if (!config.data) {
1341
1811
  return stub("withdrawals.retrieve");
1342
1812
  }
1343
- try {
1344
- const withdrawal = await config.data.query(
1345
- api.withdrawals.queries.retrieve,
1346
- { withdrawalId }
1347
- );
1348
- if (!withdrawal) {
1349
- return [
1350
- new CapxulError({
1351
- code: "NOT_FOUND",
1352
- message: `withdrawal ${withdrawalId} not found`
1353
- }),
1354
- null
1355
- ];
1356
- }
1357
- return [null, withdrawal];
1358
- } catch (cause) {
1813
+ const [err, raw] = await tryCatch(
1814
+ config.data.query(api.withdrawals.queries.retrieve, { withdrawalId })
1815
+ );
1816
+ if (err) {
1817
+ return [fromConvexError(err), null];
1818
+ }
1819
+ const withdrawal = raw;
1820
+ if (!withdrawal) {
1359
1821
  return [
1360
- fromConvexError(cause),
1822
+ new CapxulError({
1823
+ code: "NOT_FOUND",
1824
+ message: `withdrawal ${withdrawalId} not found`
1825
+ }),
1361
1826
  null
1362
1827
  ];
1363
1828
  }
1829
+ return [null, withdrawal];
1364
1830
  },
1365
1831
  list: async (input) => {
1366
1832
  if (!config.data) {
1367
1833
  return stub("withdrawals.list");
1368
1834
  }
1369
- try {
1370
- const result = await config.data.query(
1371
- api.withdrawals.queries.list,
1372
- {
1373
- limit: input?.limit,
1374
- cursor: input?.cursor
1375
- }
1835
+ const [err, raw] = await tryCatch(
1836
+ config.data.query(api.withdrawals.queries.list, {
1837
+ limit: input?.limit,
1838
+ cursor: input?.cursor
1839
+ })
1840
+ );
1841
+ if (err) {
1842
+ return [fromConvexError(err), null];
1843
+ }
1844
+ return [null, raw];
1845
+ },
1846
+ recordCompleted: async (input) => {
1847
+ if (!config.data) {
1848
+ return stub(
1849
+ "withdrawals.recordCompleted"
1376
1850
  );
1377
- return [null, result];
1378
- } catch (cause) {
1851
+ }
1852
+ const [err] = await tryCatch(
1853
+ config.data.mutation(api.withdrawals.mutations.recordCompleted, {
1854
+ withdrawalId: input.withdrawalId,
1855
+ txHash: input.txHash
1856
+ })
1857
+ );
1858
+ if (err) {
1379
1859
  return [
1380
- fromConvexError(cause),
1860
+ mapRecordCompletedError(fromConvexError(err)),
1381
1861
  null
1382
1862
  ];
1383
1863
  }
1864
+ return [null, null];
1384
1865
  }
1385
1866
  };
1386
1867
  }
1387
1868
  function createOrgWithdrawalsClient(config = {}) {
1388
1869
  return {
1389
- // Slice 1 ships personal-scope only end-to-end; org-scope create
1390
- // remains stubbed pending org-scoped backend mutation. List + retrieve
1391
- // are wired through the org-aware query.
1392
- create: async () => stub(
1393
- "organizations.withdrawals.create"
1394
- ),
1870
+ /**
1871
+ * Org-scope create (Withdrawals v1 W2, #465).
1872
+ *
1873
+ * D6 returns the `processing` row only. No `transferAsOwner`
1874
+ * tail, no `recordSubmitted` call. Org Safe + Zodiac submission
1875
+ * orchestration ships in W3+.
1876
+ */
1877
+ create: async (input) => {
1878
+ if (!config.data) {
1879
+ return stub(
1880
+ "organizations.withdrawals.create"
1881
+ );
1882
+ }
1883
+ const [err, raw] = await tryCatch(
1884
+ config.data.mutation(api.withdrawals.mutations.createOrg, {
1885
+ organizationId: input.organizationId,
1886
+ amount: input.amount,
1887
+ destination: {
1888
+ externalAccountId: input.destination.externalAccountId
1889
+ },
1890
+ source: input.source,
1891
+ reference: input.reference,
1892
+ idempotencyKey: input.idempotencyKey
1893
+ })
1894
+ );
1895
+ if (err) {
1896
+ return [mapCreateError2(fromConvexError(err)), null];
1897
+ }
1898
+ const created = raw;
1899
+ if (!created) {
1900
+ return [
1901
+ new CapxulError({
1902
+ code: "NETWORK_ERROR",
1903
+ message: "organizations.withdrawals.create returned no withdrawal resource"
1904
+ }),
1905
+ null
1906
+ ];
1907
+ }
1908
+ return [null, created];
1909
+ },
1395
1910
  retrieve: async (input) => {
1396
1911
  if (!config.data) {
1397
1912
  return stub(
1398
1913
  "organizations.withdrawals.retrieve"
1399
1914
  );
1400
1915
  }
1401
- try {
1402
- const withdrawal = await config.data.query(
1403
- api.withdrawals.queries.retrieve,
1404
- { withdrawalId: input.withdrawalId }
1405
- );
1406
- if (!withdrawal) {
1407
- return [
1408
- new CapxulError({
1409
- code: "NOT_FOUND",
1410
- message: `withdrawal ${input.withdrawalId} not found`
1411
- }),
1412
- null
1413
- ];
1414
- }
1415
- const ownerCheck = withdrawal.owner;
1416
- if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
1417
- return [
1418
- new CapxulError({
1419
- code: "NOT_FOUND",
1420
- message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
1421
- }),
1422
- null
1423
- ];
1424
- }
1425
- return [null, withdrawal];
1426
- } catch (cause) {
1916
+ const [err, raw] = await tryCatch(
1917
+ config.data.query(api.withdrawals.queries.retrieve, {
1918
+ withdrawalId: input.withdrawalId
1919
+ })
1920
+ );
1921
+ if (err) {
1922
+ return [fromConvexError(err), null];
1923
+ }
1924
+ const withdrawal = raw;
1925
+ if (!withdrawal) {
1427
1926
  return [
1428
- fromConvexError(cause),
1927
+ new CapxulError({
1928
+ code: "NOT_FOUND",
1929
+ message: `withdrawal ${input.withdrawalId} not found`
1930
+ }),
1931
+ null
1932
+ ];
1933
+ }
1934
+ const ownerCheck = withdrawal.owner;
1935
+ if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
1936
+ return [
1937
+ new CapxulError({
1938
+ code: "NOT_FOUND",
1939
+ message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
1940
+ }),
1429
1941
  null
1430
1942
  ];
1431
1943
  }
1944
+ return [null, withdrawal];
1432
1945
  },
1433
1946
  list: async (input) => {
1434
1947
  if (!config.data) {
@@ -1436,34 +1949,32 @@ function createOrgWithdrawalsClient(config = {}) {
1436
1949
  "organizations.withdrawals.list"
1437
1950
  );
1438
1951
  }
1439
- try {
1440
- const result = await config.data.query(
1441
- api.withdrawals.queries.listOrg,
1442
- {
1443
- organizationId: input.organizationId,
1444
- limit: input.limit,
1445
- cursor: input.cursor
1446
- }
1447
- );
1448
- return [null, result];
1449
- } catch (cause) {
1450
- return [
1451
- fromConvexError(cause),
1452
- null
1453
- ];
1952
+ const [err, raw] = await tryCatch(
1953
+ config.data.query(api.withdrawals.queries.listOrg, {
1954
+ organizationId: input.organizationId,
1955
+ limit: input.limit,
1956
+ cursor: input.cursor
1957
+ })
1958
+ );
1959
+ if (err) {
1960
+ return [fromConvexError(err), null];
1454
1961
  }
1962
+ return [null, raw];
1455
1963
  }
1456
1964
  };
1457
1965
  }
1966
+ async function handleSubmissionFailure(config, withdrawalId, error) {
1967
+ await bestEffortMarkFailed2(config, withdrawalId, error);
1968
+ return [error, null];
1969
+ }
1458
1970
  async function bestEffortMarkFailed2(config, withdrawalId, error) {
1459
- try {
1460
- await config.data.mutation(api.withdrawals.mutations.markFailed, {
1971
+ await tryCatch(
1972
+ config.data.mutation(api.withdrawals.mutations.markFailed, {
1461
1973
  withdrawalId,
1462
1974
  errorCode: error.code,
1463
1975
  errorMessage: error.message
1464
- });
1465
- } catch {
1466
- }
1976
+ })
1977
+ );
1467
1978
  }
1468
1979
  function mapCreateError2(error) {
1469
1980
  switch (error.code) {
@@ -1476,6 +1987,29 @@ function mapCreateError2(error) {
1476
1987
  case "POLICY_DENIED":
1477
1988
  case "RATE_LIMITED":
1478
1989
  case "NETWORK_ERROR":
1990
+ case "NOT_FOUND":
1991
+ case "VERIFICATION_REQUIRED":
1992
+ return error;
1993
+ default:
1994
+ return new CapxulError({
1995
+ code: "NETWORK_ERROR",
1996
+ message: error.message,
1997
+ cause: error,
1998
+ details: error.details,
1999
+ operationId: error.operationId,
2000
+ correlationId: error.correlationId,
2001
+ retryable: error.retryable
2002
+ });
2003
+ }
2004
+ }
2005
+ function mapRecordCompletedError(error) {
2006
+ switch (error.code) {
2007
+ case "NOT_AUTHENTICATED":
2008
+ case "PERMISSION_DENIED":
2009
+ case "INVALID_INPUT":
2010
+ case "NOT_FOUND":
2011
+ case "NETWORK_ERROR":
2012
+ case "INTERNAL_ERROR":
1479
2013
  return error;
1480
2014
  default:
1481
2015
  return new CapxulError({
@@ -1511,6 +2045,136 @@ function createWebhookEventsClient() {
1511
2045
  }
1512
2046
 
1513
2047
  // src/core/organizations.ts
2048
+ function createOrgExternalAccountsClient(config) {
2049
+ return {
2050
+ create: async (input) => {
2051
+ if (!config.data) {
2052
+ return stub(
2053
+ "organizations.externalAccounts.create"
2054
+ );
2055
+ }
2056
+ const [err, raw] = await tryCatch(
2057
+ config.data.mutation(api.externalAccounts.mutations.createOrg, {
2058
+ organizationId: input.organizationId,
2059
+ kind: input.kind,
2060
+ label: input.label,
2061
+ address: input.address,
2062
+ iban: input.iban,
2063
+ bic: input.bic,
2064
+ accountHolder: input.accountHolder,
2065
+ network: input.network,
2066
+ panToken: input.panToken,
2067
+ last4: input.last4
2068
+ })
2069
+ );
2070
+ if (err) {
2071
+ return [
2072
+ fromConvexError(err),
2073
+ null
2074
+ ];
2075
+ }
2076
+ if (!raw) {
2077
+ return [
2078
+ new CapxulError({
2079
+ code: "NOT_FOUND",
2080
+ message: "external_account creation returned no resource"
2081
+ }),
2082
+ null
2083
+ ];
2084
+ }
2085
+ return [
2086
+ null,
2087
+ brandExternalAccount(
2088
+ raw
2089
+ )
2090
+ ];
2091
+ },
2092
+ list: async (input) => {
2093
+ if (!config.data) {
2094
+ return stub(
2095
+ "organizations.externalAccounts.list"
2096
+ );
2097
+ }
2098
+ const [err, result] = await tryCatch(
2099
+ config.data.query(api.externalAccounts.queries.listOrg, {
2100
+ organizationId: input.organizationId,
2101
+ limit: input.limit,
2102
+ cursor: input.cursor
2103
+ })
2104
+ );
2105
+ if (err) {
2106
+ return [fromConvexError(err), null];
2107
+ }
2108
+ const branded = result.data.map(
2109
+ (row) => brandExternalAccount(
2110
+ row
2111
+ )
2112
+ );
2113
+ return [
2114
+ null,
2115
+ {
2116
+ object: "list",
2117
+ data: branded,
2118
+ page: result.page
2119
+ }
2120
+ ];
2121
+ },
2122
+ retrieve: async (input) => {
2123
+ if (!config.data) {
2124
+ return stub(
2125
+ "organizations.externalAccounts.retrieve"
2126
+ );
2127
+ }
2128
+ const [err, raw] = await tryCatch(
2129
+ config.data.query(api.externalAccounts.queries.retrieveOrg, {
2130
+ organizationId: input.organizationId,
2131
+ externalAccountId: input.externalAccountId
2132
+ })
2133
+ );
2134
+ if (err) {
2135
+ return [
2136
+ fromConvexError(err),
2137
+ null
2138
+ ];
2139
+ }
2140
+ if (!raw) {
2141
+ return [
2142
+ new CapxulError({
2143
+ code: "NOT_FOUND",
2144
+ message: `external_account ${input.externalAccountId} not found`
2145
+ }),
2146
+ null
2147
+ ];
2148
+ }
2149
+ return [
2150
+ null,
2151
+ brandExternalAccount(
2152
+ raw
2153
+ )
2154
+ ];
2155
+ },
2156
+ remove: async (input) => {
2157
+ if (!config.data) {
2158
+ return stub(
2159
+ "organizations.externalAccounts.remove"
2160
+ );
2161
+ }
2162
+ const [err] = await tryCatch(
2163
+ config.data.mutation(api.externalAccounts.mutations.removeOrg, {
2164
+ organizationId: input.organizationId,
2165
+ externalAccountId: input.externalAccountId
2166
+ })
2167
+ );
2168
+ if (err) {
2169
+ return [
2170
+ fromConvexError(err),
2171
+ null
2172
+ ];
2173
+ }
2174
+ return [null, void 0];
2175
+ }
2176
+ };
2177
+ }
1514
2178
  function createOrganizationsClient(config = {}) {
1515
2179
  return {
1516
2180
  create: async () => stub("organizations.create"),
@@ -1566,18 +2230,7 @@ function createOrganizationsClient(config = {}) {
1566
2230
  retrieve: async () => stub("organizations.subAccounts.retrieve"),
1567
2231
  remove: async () => stub("organizations.subAccounts.remove")
1568
2232
  },
1569
- externalAccounts: {
1570
- create: async () => stub(
1571
- "organizations.externalAccounts.create"
1572
- ),
1573
- list: async () => stub(
1574
- "organizations.externalAccounts.list"
1575
- ),
1576
- retrieve: async () => stub(
1577
- "organizations.externalAccounts.retrieve"
1578
- ),
1579
- remove: async () => stub("organizations.externalAccounts.remove")
1580
- },
2233
+ externalAccounts: createOrgExternalAccountsClient(config),
1581
2234
  balanceLedger: {
1582
2235
  list: async () => stub(
1583
2236
  "organizations.balanceLedger.list"
@@ -1714,50 +2367,6 @@ function createVirtualCardsClient() {
1714
2367
  cancel: async () => stub("virtualCards.cancel")
1715
2368
  };
1716
2369
  }
1717
-
1718
- // ../observability/src/debug-log.ts
1719
- function isDevelopmentBuild() {
1720
- if (typeof process === "undefined") {
1721
- return false;
1722
- }
1723
- return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
1724
- }
1725
- function debugLog(line) {
1726
- if (!isDevelopmentBuild()) return;
1727
- if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
1728
- console.info(line);
1729
- return;
1730
- }
1731
- if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
1732
- process.stderr.write(`${line}
1733
- `);
1734
- }
1735
- }
1736
- function formatDebugValue(value) {
1737
- if (value === void 0 || value === "") return "";
1738
- if (typeof value === "string") return value;
1739
- try {
1740
- return JSON.stringify(value);
1741
- } catch {
1742
- return String(value);
1743
- }
1744
- }
1745
- function track(...args) {
1746
- const [name, props] = args;
1747
- debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
1748
- }
1749
- function formatDebugValue2(value) {
1750
- if (value === void 0 || value === "") return "";
1751
- if (typeof value === "string") return value;
1752
- try {
1753
- return JSON.stringify(value);
1754
- } catch {
1755
- return String(value);
1756
- }
1757
- }
1758
- function identify(userId, traits) {
1759
- debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
1760
- }
1761
2370
  function createAuthFlowMachine(client) {
1762
2371
  return xstate.setup({
1763
2372
  types: {},
@@ -2033,23 +2642,6 @@ function emailDomain(email) {
2033
2642
  const domain = email.split("@")[1]?.trim().toLowerCase();
2034
2643
  return domain || "unknown";
2035
2644
  }
2036
-
2037
- // ../platform-kernel/src/ids.ts
2038
- function makePrefixedIdConstructor(prefix, fieldName) {
2039
- const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
2040
- return (raw) => {
2041
- if (typeof raw !== "string" || !re.test(raw)) {
2042
- throw new Error(
2043
- `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
2044
- );
2045
- }
2046
- return raw;
2047
- };
2048
- }
2049
- var toOperationId = makePrefixedIdConstructor(
2050
- "op",
2051
- "operationId"
2052
- );
2053
2645
  function createProvisioningMachine(client) {
2054
2646
  return xstate.setup({
2055
2647
  types: {},
@@ -2442,7 +3034,7 @@ function createCapxulClient(config = {}) {
2442
3034
  subAccounts: createSubAccountsClient(),
2443
3035
  virtualAccounts: createVirtualAccountsClient(),
2444
3036
  virtualCards: createVirtualCardsClient(),
2445
- externalAccounts: createExternalAccountsClient(),
3037
+ externalAccounts: createExternalAccountsClient(config),
2446
3038
  operations: createOperationsClient(config),
2447
3039
  webhookEndpoints: createWebhookEndpointsClient(),
2448
3040
  webhookEvents: createWebhookEventsClient(),