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