@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/index.js CHANGED
@@ -68,7 +68,366 @@ function fromConvexError(error) {
68
68
  });
69
69
  }
70
70
 
71
+ // ../observability/src/try-catch.ts
72
+ async function tryCatch(promise) {
73
+ try {
74
+ return [null, await promise];
75
+ } catch (e) {
76
+ return [e instanceof Error ? e : new Error(String(e)), null];
77
+ }
78
+ }
79
+
80
+ // ../observability/src/debug-log.ts
81
+ function isDevelopmentBuild() {
82
+ if (typeof process === "undefined") {
83
+ return false;
84
+ }
85
+ return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
86
+ }
87
+ function debugLog(line) {
88
+ if (!isDevelopmentBuild()) return;
89
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
90
+ console.info(line);
91
+ return;
92
+ }
93
+ if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
94
+ process.stderr.write(`${line}
95
+ `);
96
+ }
97
+ }
98
+ function formatDebugValue(value) {
99
+ if (value === void 0 || value === "") return "";
100
+ if (typeof value === "string") return value;
101
+ try {
102
+ return JSON.stringify(value);
103
+ } catch {
104
+ return String(value);
105
+ }
106
+ }
107
+ function track(...args) {
108
+ const [name, props] = args;
109
+ debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
110
+ }
111
+ function formatDebugValue2(value) {
112
+ if (value === void 0 || value === "") return "";
113
+ if (typeof value === "string") return value;
114
+ try {
115
+ return JSON.stringify(value);
116
+ } catch {
117
+ return String(value);
118
+ }
119
+ }
120
+ function identify(userId, traits) {
121
+ debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
122
+ }
123
+
124
+ // ../platform-kernel/src/ids.ts
125
+ function makePrefixedIdConstructor(prefix, fieldName) {
126
+ const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
127
+ return (raw) => {
128
+ if (typeof raw !== "string" || !re.test(raw)) {
129
+ throw new Error(
130
+ `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
131
+ );
132
+ }
133
+ return raw;
134
+ };
135
+ }
136
+ var toAccountId = makePrefixedIdConstructor(
137
+ "acct",
138
+ "accountId"
139
+ );
140
+ var toOrganizationId = makePrefixedIdConstructor("org", "organizationId");
141
+ var toMemberId = makePrefixedIdConstructor(
142
+ "mem",
143
+ "memberId"
144
+ );
145
+ var toSafeId = makePrefixedIdConstructor(
146
+ "safe",
147
+ "safeId"
148
+ );
149
+ var toTreasuryId = makePrefixedIdConstructor(
150
+ "try",
151
+ "treasuryId"
152
+ );
153
+ var toApiKeyId = makePrefixedIdConstructor(
154
+ "ak",
155
+ "apiKeyId"
156
+ );
157
+ var toOperationId = makePrefixedIdConstructor(
158
+ "op",
159
+ "operationId"
160
+ );
161
+ var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
162
+ var toKycProfileId = makePrefixedIdConstructor(
163
+ "kyc",
164
+ "kycProfileId"
165
+ );
166
+ var toKybProfileId = makePrefixedIdConstructor(
167
+ "kyb",
168
+ "kybProfileId"
169
+ );
170
+ var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
171
+ var toPaymentId = makePrefixedIdConstructor(
172
+ "pay",
173
+ "paymentId"
174
+ );
175
+ var toWithdrawalId = makePrefixedIdConstructor(
176
+ "wd",
177
+ "withdrawalId"
178
+ );
179
+ var toWebhookEndpointId = makePrefixedIdConstructor("we", "webhookEndpointId");
180
+ var toWebhookEventId = makePrefixedIdConstructor("evt", "webhookEventId");
181
+ var toSubAccountId = makePrefixedIdConstructor(
182
+ "sub",
183
+ "subAccountId"
184
+ );
185
+ var toVirtualAccountId = makePrefixedIdConstructor("va", "virtualAccountId");
186
+ var toVirtualCardId = makePrefixedIdConstructor(
187
+ "vc",
188
+ "virtualCardId"
189
+ );
190
+ var toTransferId = makePrefixedIdConstructor(
191
+ "txfr",
192
+ "transferId"
193
+ );
194
+ var toDocumentId = makePrefixedIdConstructor(
195
+ "doc",
196
+ "documentId"
197
+ );
198
+ var toBalanceLedgerEntryId = makePrefixedIdConstructor("bal", "balanceLedgerEntryId");
199
+
200
+ // ../platform-kernel/src/value-objects.ts
201
+ var PHONE_NUMBER_RE = /^\+[1-9]\d{1,14}$/;
202
+ function toPhoneNumber(raw) {
203
+ if (typeof raw !== "string" || !PHONE_NUMBER_RE.test(raw)) {
204
+ throw new Error(
205
+ `Invalid phoneNumber: expected E.164 string matching ^\\+[1-9]\\d{1,14}$, got ${String(raw)}`
206
+ );
207
+ }
208
+ return raw;
209
+ }
210
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
211
+ function toEmail(raw) {
212
+ if (typeof raw !== "string") {
213
+ throw new Error(
214
+ `Invalid email: expected string, got ${String(raw)}`
215
+ );
216
+ }
217
+ const lowered = raw.trim().toLowerCase();
218
+ if (!EMAIL_RE.test(lowered)) {
219
+ throw new Error(
220
+ `Invalid email: expected local@domain.tld, got ${String(raw)}`
221
+ );
222
+ }
223
+ return lowered;
224
+ }
225
+ var USERNAME_RE = /^[a-z][a-z0-9_-]{2,29}$/;
226
+ function toUsername(raw) {
227
+ if (typeof raw !== "string") {
228
+ throw new Error(
229
+ `Invalid username: expected string, got ${String(raw)}`
230
+ );
231
+ }
232
+ const lowered = raw.toLowerCase();
233
+ if (!USERNAME_RE.test(lowered)) {
234
+ throw new Error(
235
+ `Invalid username: expected 3-30 chars, letter-first, [a-z0-9_-], got ${String(raw)}`
236
+ );
237
+ }
238
+ return lowered;
239
+ }
240
+
241
+ // src/core/external-accounts.ts
242
+ function brandExternalAccount(raw) {
243
+ return {
244
+ ...raw,
245
+ id: toExternalAccountId(raw.id),
246
+ operation: {
247
+ id: toOperationId(raw.operation.id),
248
+ status: raw.operation.status,
249
+ correlationId: toCorrelationId(raw.operation.correlationId)
250
+ }
251
+ };
252
+ }
253
+ function createExternalAccountsClient(config = {}) {
254
+ return {
255
+ retrieve: async (externalAccountId) => {
256
+ if (!config.data) {
257
+ return stub(
258
+ "externalAccounts.retrieve"
259
+ );
260
+ }
261
+ const [err, raw] = await tryCatch(
262
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
263
+ externalAccountId
264
+ })
265
+ );
266
+ if (err) {
267
+ return [
268
+ fromConvexError(err),
269
+ null
270
+ ];
271
+ }
272
+ if (!raw) {
273
+ return [
274
+ new CapxulError({
275
+ code: "NOT_FOUND",
276
+ message: `external_account ${externalAccountId} not found`
277
+ }),
278
+ null
279
+ ];
280
+ }
281
+ return [null, brandExternalAccount(raw)];
282
+ },
283
+ remove: async (externalAccountId) => {
284
+ if (!config.data) {
285
+ return stub("externalAccounts.remove");
286
+ }
287
+ const [err] = await tryCatch(
288
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
289
+ externalAccountId
290
+ })
291
+ );
292
+ if (err) {
293
+ return [
294
+ fromConvexError(err),
295
+ null
296
+ ];
297
+ }
298
+ return [null, void 0];
299
+ }
300
+ };
301
+ }
302
+
71
303
  // src/core/accounts.ts
304
+ function createAccountExternalAccountsClient(config) {
305
+ return {
306
+ create: async (input) => {
307
+ if (!config.data) {
308
+ return stub(
309
+ "accounts.externalAccounts.create"
310
+ );
311
+ }
312
+ const [err, raw] = await tryCatch(
313
+ config.data.mutation(
314
+ api.externalAccounts.mutations.createPersonal,
315
+ {
316
+ kind: input.kind,
317
+ label: input.label,
318
+ address: input.address,
319
+ iban: input.iban,
320
+ bic: input.bic,
321
+ accountHolder: input.accountHolder,
322
+ network: input.network,
323
+ panToken: input.panToken,
324
+ last4: input.last4
325
+ }
326
+ )
327
+ );
328
+ if (err) {
329
+ return [
330
+ fromConvexError(err),
331
+ null
332
+ ];
333
+ }
334
+ if (!raw) {
335
+ return [
336
+ new CapxulError({
337
+ code: "NOT_FOUND",
338
+ message: "external_account creation returned no resource"
339
+ }),
340
+ null
341
+ ];
342
+ }
343
+ return [
344
+ null,
345
+ brandExternalAccount(
346
+ raw
347
+ )
348
+ ];
349
+ },
350
+ list: async (input) => {
351
+ if (!config.data) {
352
+ return stub(
353
+ "accounts.externalAccounts.list"
354
+ );
355
+ }
356
+ const [err, result] = await tryCatch(
357
+ config.data.query(api.externalAccounts.queries.listPersonal, {
358
+ limit: input.limit,
359
+ cursor: input.cursor
360
+ })
361
+ );
362
+ if (err) {
363
+ return [fromConvexError(err), null];
364
+ }
365
+ const branded = result.data.map(
366
+ (row) => brandExternalAccount(
367
+ row
368
+ )
369
+ );
370
+ return [
371
+ null,
372
+ {
373
+ object: "list",
374
+ data: branded,
375
+ page: result.page
376
+ }
377
+ ];
378
+ },
379
+ retrieve: async (externalAccountId) => {
380
+ if (!config.data) {
381
+ return stub(
382
+ "accounts.externalAccounts.retrieve"
383
+ );
384
+ }
385
+ const [err, raw] = await tryCatch(
386
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
387
+ externalAccountId
388
+ })
389
+ );
390
+ if (err) {
391
+ return [
392
+ fromConvexError(err),
393
+ null
394
+ ];
395
+ }
396
+ if (!raw) {
397
+ return [
398
+ new CapxulError({
399
+ code: "NOT_FOUND",
400
+ message: `external_account ${externalAccountId} not found`
401
+ }),
402
+ null
403
+ ];
404
+ }
405
+ return [
406
+ null,
407
+ brandExternalAccount(
408
+ raw
409
+ )
410
+ ];
411
+ },
412
+ remove: async (externalAccountId) => {
413
+ if (!config.data) {
414
+ return stub("accounts.externalAccounts.remove");
415
+ }
416
+ const [err] = await tryCatch(
417
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
418
+ externalAccountId
419
+ })
420
+ );
421
+ if (err) {
422
+ return [
423
+ fromConvexError(err),
424
+ null
425
+ ];
426
+ }
427
+ return [null, void 0];
428
+ }
429
+ };
430
+ }
72
431
  function createAccountsClient(config = {}) {
73
432
  return {
74
433
  retrieve: async (accountId) => {
@@ -223,18 +582,7 @@ function createAccountsClient(config = {}) {
223
582
  create: async () => stub("accounts.kycProfiles.create"),
224
583
  retrieve: async () => stub("accounts.kycProfiles.retrieve")
225
584
  },
226
- externalAccounts: {
227
- create: async () => stub(
228
- "accounts.externalAccounts.create"
229
- ),
230
- list: async () => stub(
231
- "accounts.externalAccounts.list"
232
- ),
233
- retrieve: async () => stub(
234
- "accounts.externalAccounts.retrieve"
235
- ),
236
- remove: async () => stub("accounts.externalAccounts.remove")
237
- },
585
+ externalAccounts: createAccountExternalAccountsClient(config),
238
586
  subAccounts: {
239
587
  create: async () => stub("accounts.subAccounts.create"),
240
588
  list: async () => stub("accounts.subAccounts.list"),
@@ -329,8 +677,35 @@ var Errors = {
329
677
  "Idempotency key was already used for a different request",
330
678
  { details }
331
679
  ),
332
- emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
333
- internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`)
680
+ emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
681
+ details
682
+ }),
683
+ rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
684
+ details: { ...details }
685
+ }),
686
+ internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
687
+ /**
688
+ * Verification gate. Surfaced when a request hits a verification
689
+ * boundary the actor cannot cross under their current state. Two
690
+ * variants share this code:
691
+ *
692
+ * - Rail gate (Withdrawals v1 W2, #465): the resolved
693
+ * `external_account.kind` routes to a withdrawal rail (e.g.
694
+ * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
695
+ * `details.rail` + `details.currentKind`.
696
+ * - KYC tier gate (legacy / future): the actor's KYC tier is below
697
+ * the required tier. Carries `details.requiredTier`.
698
+ *
699
+ * Code is shared because both expose the same UX shape ("you cannot
700
+ * proceed until verification advances"); the `details.*` keys
701
+ * differentiate the route.
702
+ */
703
+ verificationRequired: (details) => {
704
+ const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
705
+ return new CapxulError2("VERIFICATION_REQUIRED", message, {
706
+ details: { ...details }
707
+ });
708
+ }
334
709
  };
335
710
 
336
711
  // ../config/src/safe.ts
@@ -380,13 +755,13 @@ function createLifecycle(initial) {
380
755
  }
381
756
  function makeBuildTimeUrlsTransport(config) {
382
757
  if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
383
- throw Errors.invalidInput(
758
+ throw invalidConfigError(
384
759
  "authBaseUrl",
385
760
  "build-time-urls transport requires a non-empty authBaseUrl."
386
761
  );
387
762
  }
388
763
  if (!config.convexUrl || config.convexUrl.trim().length === 0) {
389
- throw Errors.invalidInput(
764
+ throw invalidConfigError(
390
765
  "convexUrl",
391
766
  "build-time-urls transport requires a non-empty convexUrl."
392
767
  );
@@ -400,6 +775,7 @@ function makeBuildTimeUrlsTransport(config) {
400
775
  return {
401
776
  authBaseUrl,
402
777
  convexUrl,
778
+ ensureRuntime: async () => runtime,
403
779
  fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
404
780
  getState: lifecycle.getState,
405
781
  subscribe: lifecycle.subscribe,
@@ -415,14 +791,15 @@ function makeBuildTimeUrlsTransport(config) {
415
791
  };
416
792
  }
417
793
  function makePublishableKeyTransport(config) {
418
- if (!config.publishableKey || config.publishableKey.trim().length === 0) {
419
- throw Errors.invalidInput(
794
+ const publishableKey = config.publishableKey?.trim();
795
+ if (!publishableKey) {
796
+ throw invalidConfigError(
420
797
  "publishableKey",
421
798
  "publishable-key transport requires a non-empty publishableKey."
422
799
  );
423
800
  }
424
801
  const fetchImpl = config.fetchImpl ?? globalThis.fetch;
425
- const bootstrapUrl = stripTrailingSlash(
802
+ const bootstrapUrl = normalizeBootstrapUrl(
426
803
  config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
427
804
  );
428
805
  let authBaseUrl = "";
@@ -437,23 +814,20 @@ function makePublishableKeyTransport(config) {
437
814
  const response = await fetchImpl(bootstrapUrl, {
438
815
  method: "POST",
439
816
  headers: { "content-type": "application/json" },
440
- body: JSON.stringify({ publishableKey: config.publishableKey })
817
+ body: JSON.stringify({ publishableKey })
441
818
  });
442
819
  if (!response.ok) {
443
- throw Errors.invalidInput(
444
- "publishableKey",
445
- `${bootstrapUrl} failed with HTTP ${response.status}.`
446
- );
820
+ throw await bootstrapResponseError(response, bootstrapUrl);
447
821
  }
448
- const body = await response.json();
822
+ const body = await readBootstrapSuccessBody(response);
449
823
  if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
450
- throw Errors.invalidInput(
824
+ throw bootstrapContractError(
451
825
  "authBaseUrl",
452
826
  "/v1/client/bootstrap returned no authBaseUrl."
453
827
  );
454
828
  }
455
829
  if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
456
- throw Errors.invalidInput(
830
+ throw bootstrapContractError(
457
831
  "convexUrl",
458
832
  "/v1/client/bootstrap returned no convexUrl."
459
833
  );
@@ -465,16 +839,13 @@ function makePublishableKeyTransport(config) {
465
839
  return runtime;
466
840
  })();
467
841
  bootstrapPromise = attempt.catch((err) => {
842
+ const error = normalizeBootstrapThrownError(err);
468
843
  bootstrapPromise = null;
469
844
  lifecycle.setState({
470
845
  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
- })
846
+ error
476
847
  });
477
- throw err;
848
+ throw error;
478
849
  });
479
850
  return await bootstrapPromise;
480
851
  }
@@ -485,6 +856,7 @@ function makePublishableKeyTransport(config) {
485
856
  get convexUrl() {
486
857
  return convexUrl;
487
858
  },
859
+ ensureRuntime: ensureBootstrap,
488
860
  fetch: async (path, init) => {
489
861
  const resolved = await ensureBootstrap();
490
862
  return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
@@ -495,7 +867,7 @@ function makePublishableKeyTransport(config) {
495
867
  markAuthenticated: ({ dataClient: nextDataClient }) => {
496
868
  const current = lifecycle.getState();
497
869
  if (current.status !== "ready" && current.status !== "authenticated") {
498
- throw Errors.internalError(
870
+ throw internalTransportError(
499
871
  `markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
500
872
  );
501
873
  }
@@ -517,16 +889,166 @@ function makePublishableKeyTransport(config) {
517
889
  function stripTrailingSlash(url) {
518
890
  return url.replace(/\/+$/, "");
519
891
  }
892
+ function normalizeBootstrapUrl(url) {
893
+ const normalized = stripTrailingSlash(url.trim());
894
+ if (!isAbsoluteHttpUrl(normalized)) {
895
+ throw invalidConfigError(
896
+ "bootstrapUrl",
897
+ "publishable-key transport requires an absolute http(s) bootstrapUrl."
898
+ );
899
+ }
900
+ return normalized;
901
+ }
902
+ function isAbsoluteHttpUrl(url) {
903
+ try {
904
+ const parsed = new URL(url);
905
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
906
+ } catch {
907
+ return false;
908
+ }
909
+ }
520
910
  function resolveUrl(authBaseUrl, path) {
521
911
  if (path.startsWith("http://") || path.startsWith("https://")) {
522
912
  return path;
523
913
  }
524
914
  return `${authBaseUrl}${path}`;
525
915
  }
526
- function assertNever(value) {
527
- throw Errors.internalError(
528
- `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
529
- );
916
+ function assertNever(value) {
917
+ throw internalTransportError(
918
+ `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
919
+ );
920
+ }
921
+ function invalidConfigError(field, reason) {
922
+ return new CapxulError({
923
+ code: "INVALID_INPUT",
924
+ message: `Invalid ${field}: ${reason}`,
925
+ details: { source: "sdk-config", field, reason }
926
+ });
927
+ }
928
+ function bootstrapContractError(field, message) {
929
+ return new CapxulError({
930
+ code: "INVALID_INPUT",
931
+ message,
932
+ details: {
933
+ source: "backend-bootstrap",
934
+ phase: "publishable-key-bootstrap",
935
+ field,
936
+ reason: message
937
+ }
938
+ });
939
+ }
940
+ function internalTransportError(reason) {
941
+ return new CapxulError({
942
+ code: "INTERNAL_ERROR",
943
+ message: `Internal error: ${reason}`,
944
+ details: { source: "sdk-transport", reason }
945
+ });
946
+ }
947
+ async function bootstrapResponseError(response, bootstrapUrl) {
948
+ const envelope = await readBootstrapErrorEnvelope(response);
949
+ const wireCode = readNonEmptyString(envelope?.error?.code);
950
+ const normalized = normalizeBootstrapErrorCode(wireCode);
951
+ const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
952
+ const backendDetails = readRecord(envelope?.error?.details);
953
+ return new CapxulError({
954
+ code: normalized.code,
955
+ message,
956
+ details: {
957
+ ...backendDetails,
958
+ source: "backend-bootstrap",
959
+ phase: "publishable-key-bootstrap",
960
+ httpStatus: response.status,
961
+ ...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
962
+ },
963
+ operationId: readNonEmptyString(envelope?.error?.operationId),
964
+ correlationId: readNonEmptyString(envelope?.error?.correlationId),
965
+ retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
966
+ });
967
+ }
968
+ async function readBootstrapErrorEnvelope(response) {
969
+ try {
970
+ const parsed = await response.json();
971
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
972
+ } catch {
973
+ return null;
974
+ }
975
+ }
976
+ async function readBootstrapSuccessBody(response) {
977
+ try {
978
+ const parsed = await response.json();
979
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
980
+ } catch {
981
+ throw bootstrapContractError(
982
+ "body",
983
+ "/v1/client/bootstrap returned invalid JSON."
984
+ );
985
+ }
986
+ }
987
+ function normalizeBootstrapThrownError(error) {
988
+ if (error instanceof CapxulError) return error;
989
+ return new CapxulError({
990
+ code: "NETWORK_ERROR",
991
+ message: "Publishable-key bootstrap network failure.",
992
+ cause: error,
993
+ details: {
994
+ source: "bootstrap-network",
995
+ phase: "publishable-key-bootstrap"
996
+ }
997
+ });
998
+ }
999
+ function normalizeBootstrapErrorCode(wireCode) {
1000
+ if (wireCode === "INTERNAL_SERVER_ERROR") {
1001
+ return { code: "INTERNAL_ERROR", wireCode };
1002
+ }
1003
+ if (wireCode && isCapxulErrorCode(wireCode)) {
1004
+ return { code: wireCode };
1005
+ }
1006
+ return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
1007
+ }
1008
+ var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
1009
+ "NOT_AUTHENTICATED",
1010
+ "EMAIL_DELIVERY_FAILED",
1011
+ "PROFILE_NOT_FOUND",
1012
+ "SMART_ACCOUNT_MISSING",
1013
+ "PLAYER_NOT_FOUND",
1014
+ "ACCOUNT_NOT_FOUND",
1015
+ "PROVIDER_ERROR",
1016
+ "INVALID_INPUT",
1017
+ "ENV_MISSING",
1018
+ "NOT_IMPLEMENTED",
1019
+ "VERIFICATION_REQUIRED",
1020
+ "INSUFFICIENT_BALANCE",
1021
+ "INVALID_RECIPIENT",
1022
+ "TRANSACTION_FAILED",
1023
+ "RATE_LIMITED",
1024
+ "NETWORK_ERROR",
1025
+ "UNKNOWN",
1026
+ "PERMISSION_DENIED",
1027
+ "API_KEY_INVALID",
1028
+ "API_KEY_EXPIRED",
1029
+ "IDEMPOTENCY_CONFLICT",
1030
+ "NOT_FOUND",
1031
+ "OPERATION_CANCELED",
1032
+ "OPERATION_TIMEOUT",
1033
+ "ACTION_REQUIRED",
1034
+ "KYC_REQUIRED",
1035
+ "POLICY_DENIED",
1036
+ "SAFE_NOT_READY",
1037
+ "PROVIDER_UNAVAILABLE",
1038
+ "PROVIDER_REJECTED",
1039
+ "RECONCILIATION_FAILED",
1040
+ "INTERNAL_ERROR",
1041
+ "QUOTE_EXPIRED",
1042
+ "QUOTE_NOT_FOUND"
1043
+ ]);
1044
+ function isCapxulErrorCode(value) {
1045
+ return CAPXUL_ERROR_CODES.has(value);
1046
+ }
1047
+ function readRecord(value) {
1048
+ return typeof value === "object" && value !== null ? value : null;
1049
+ }
1050
+ function readNonEmptyString(value) {
1051
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
530
1052
  }
531
1053
 
532
1054
  // src/core/auth.ts
@@ -582,13 +1104,16 @@ function createAuthClient(config = {}) {
582
1104
  email: signIn.user.email,
583
1105
  token: signIn.token,
584
1106
  convexJwt,
585
- expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString()
1107
+ expiresAt: new Date(
1108
+ Date.now() + 30 * 24 * 60 * 60 * 1e3
1109
+ ).toISOString()
586
1110
  };
587
1111
  sessionStore.set(session);
588
1112
  if (config.auth?.createDataClient) {
589
1113
  try {
590
1114
  dataClient = await config.auth.createDataClient(session);
591
1115
  mutableConfig(config).data = dataClient;
1116
+ transport.markAuthenticated({ dataClient });
592
1117
  } catch (cause) {
593
1118
  return [
594
1119
  new CapxulError({
@@ -607,6 +1132,8 @@ function createAuthClient(config = {}) {
607
1132
  sessionStore.clear();
608
1133
  dataClient = null;
609
1134
  mutableConfig(config).data = void 0;
1135
+ const transport = getTransport();
1136
+ transport?.clearAuth();
610
1137
  return [null, void 0];
611
1138
  },
612
1139
  serviceTokenMint: async () => stub("auth.serviceTokenMint"),
@@ -660,16 +1187,19 @@ async function postBetterAuth(transport, path, body, code, signal) {
660
1187
  body: JSON.stringify(body),
661
1188
  signal
662
1189
  });
1190
+ const text = await response.text();
663
1191
  if (!response.ok) {
1192
+ const parsedError = parseBetterAuthError(text);
664
1193
  return [
665
1194
  new CapxulError({
666
- code,
667
- message: `BetterAuth ${path} failed with HTTP ${response.status}.`
1195
+ code: parsedError.code ?? code,
1196
+ message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
1197
+ details: parsedError.details,
1198
+ retryable: parsedError.retryable
668
1199
  }),
669
1200
  null
670
1201
  ];
671
1202
  }
672
- const text = await response.text();
673
1203
  return [null, text ? JSON.parse(text) : void 0];
674
1204
  } catch (cause) {
675
1205
  return [
@@ -682,6 +1212,36 @@ async function postBetterAuth(transport, path, body, code, signal) {
682
1212
  ];
683
1213
  }
684
1214
  }
1215
+ function parseBetterAuthError(text) {
1216
+ if (!text.trim()) {
1217
+ return {};
1218
+ }
1219
+ try {
1220
+ const body = JSON.parse(text);
1221
+ if (!body || typeof body !== "object") {
1222
+ return {};
1223
+ }
1224
+ const record = body;
1225
+ const nested = record.error && typeof record.error === "object" ? record.error : record;
1226
+ const code = typeof nested.code === "string" ? nested.code : void 0;
1227
+ const message = typeof nested.message === "string" ? nested.message : void 0;
1228
+ const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
1229
+ const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
1230
+ const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
1231
+ return {
1232
+ code: isCapxulErrorCode2(code) ? code : void 0,
1233
+ message,
1234
+ details,
1235
+ correlationId,
1236
+ retryable
1237
+ };
1238
+ } catch {
1239
+ return {};
1240
+ }
1241
+ }
1242
+ function isCapxulErrorCode2(code) {
1243
+ 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";
1244
+ }
685
1245
  async function exchangeConvexToken(transport, config, token, signal) {
686
1246
  const path = config.auth?.convexTokenUrl ?? "/convex/token";
687
1247
  try {
@@ -742,14 +1302,6 @@ function createOrgDocumentsClient() {
742
1302
  };
743
1303
  }
744
1304
 
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
1305
  // src/core/me.ts
754
1306
  function createMeClient(config = {}) {
755
1307
  return {
@@ -1197,67 +1749,68 @@ function createOrgTransfersClient() {
1197
1749
  cancel: async () => stub("organizations.transfers.cancel")
1198
1750
  };
1199
1751
  }
1200
- var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
1201
1752
  function createWithdrawalsClient(config = {}) {
1202
1753
  return {
1203
1754
  create: async (input) => {
1204
1755
  if (!config.data) {
1205
1756
  return stub("withdrawals.create");
1206
1757
  }
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
- {}
1758
+ const [createErr, createdRaw] = await tryCatch(
1759
+ config.data.mutation(api.withdrawals.mutations.create, {
1760
+ amount: input.amount,
1761
+ destination: {
1762
+ externalAccountId: input.destination.externalAccountId
1763
+ },
1764
+ source: input.source,
1765
+ reference: input.reference,
1766
+ idempotencyKey: input.idempotencyKey
1767
+ })
1768
+ );
1769
+ if (createErr) {
1770
+ return [mapCreateError2(fromConvexError(createErr)), null];
1771
+ }
1772
+ const created = createdRaw;
1773
+ if (!created) {
1774
+ return [
1775
+ new CapxulError({
1776
+ code: "NETWORK_ERROR",
1777
+ message: "withdrawals.create returned no withdrawal resource"
1778
+ }),
1779
+ null
1780
+ ];
1781
+ }
1782
+ if (created.status !== "processing" || created.operation.status !== "processing") {
1783
+ return [null, created];
1784
+ }
1785
+ if (!config.signer || !config.signing) {
1786
+ return [null, created];
1787
+ }
1788
+ const [signerErr, currentSigner] = await tryCatch(
1789
+ config.data.query(api.safe.queries.getMySignerAddress, {})
1790
+ );
1791
+ if (signerErr) {
1792
+ return await handleSubmissionFailure(
1793
+ { data: config.data },
1794
+ created.id,
1795
+ mapCreateError2(fromConvexError(signerErr))
1251
1796
  );
1252
- if (!currentSigner?.address) {
1253
- throw new CapxulError({
1797
+ }
1798
+ if (!currentSigner?.address) {
1799
+ return await handleSubmissionFailure(
1800
+ { data: config.data },
1801
+ created.id,
1802
+ new CapxulError({
1254
1803
  code: "PERMISSION_DENIED",
1255
1804
  message: "No signer is registered for the authenticated account.",
1256
1805
  details: { withdrawalId: created.id }
1257
- });
1258
- }
1259
- if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
1260
- throw new CapxulError({
1806
+ })
1807
+ );
1808
+ }
1809
+ if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
1810
+ return await handleSubmissionFailure(
1811
+ { data: config.data },
1812
+ created.id,
1813
+ new CapxulError({
1261
1814
  code: "PERMISSION_DENIED",
1262
1815
  message: "Configured signer does not match the authenticated account signer.",
1263
1816
  details: {
@@ -1265,170 +1818,228 @@ function createWithdrawalsClient(config = {}) {
1265
1818
  expectedSignerAddress: currentSigner.address,
1266
1819
  actualSignerAddress: config.signer.address
1267
1820
  }
1268
- });
1269
- }
1270
- const submission = await config.data.query(
1271
- api.withdrawals.queries.prepareSubmission,
1272
- { withdrawalId: created.id }
1821
+ })
1273
1822
  );
1274
- if (!submission?.externalAccountId) {
1275
- throw new CapxulError({
1823
+ }
1824
+ const [prepErr, submission] = await tryCatch(
1825
+ config.data.query(api.withdrawals.queries.prepareSubmission, {
1826
+ withdrawalId: created.id
1827
+ })
1828
+ );
1829
+ if (prepErr) {
1830
+ return await handleSubmissionFailure(
1831
+ { data: config.data },
1832
+ created.id,
1833
+ mapCreateError2(fromConvexError(prepErr))
1834
+ );
1835
+ }
1836
+ const destinationAddress = submission?.destinationAddress;
1837
+ if (!submission || !destinationAddress) {
1838
+ return await handleSubmissionFailure(
1839
+ { data: config.data },
1840
+ created.id,
1841
+ new CapxulError({
1276
1842
  code: "NETWORK_ERROR",
1277
1843
  message: "withdrawals.prepareSubmission returned no destination.",
1278
1844
  details: { withdrawalId: created.id }
1279
- });
1280
- }
1281
- const transfer = await transferAsOwner(
1845
+ })
1846
+ );
1847
+ }
1848
+ const [transferErr, transferOk] = await tryCatch(
1849
+ transferAsOwner(
1282
1850
  {
1283
1851
  signer: config.signer,
1284
1852
  signing: config.signing
1285
1853
  },
1286
1854
  {
1287
1855
  tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
1288
- recipientAddress: submission.externalAccountId,
1856
+ recipientAddress: destinationAddress,
1289
1857
  amount: toTokenUnits(submission.amount.value, 6)
1290
1858
  }
1859
+ )
1860
+ );
1861
+ if (transferErr) {
1862
+ return await handleSubmissionFailure(
1863
+ { data: config.data },
1864
+ created.id,
1865
+ mapCreateError2(fromConvexError(transferErr))
1291
1866
  );
1292
- if (!transfer.success) {
1293
- throw new CapxulError({
1867
+ }
1868
+ if (!transferOk.success) {
1869
+ return await handleSubmissionFailure(
1870
+ { data: config.data },
1871
+ created.id,
1872
+ new CapxulError({
1294
1873
  code: "NETWORK_ERROR",
1295
1874
  message: "Bundler submission did not succeed.",
1296
1875
  details: {
1297
1876
  withdrawalId: created.id,
1298
- txHash: transfer.txHash,
1299
- userOpHash: transfer.userOpHash
1877
+ txHash: transferOk.txHash,
1878
+ userOpHash: transferOk.userOpHash
1300
1879
  }
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
- }
1880
+ })
1314
1881
  );
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
1882
  }
1883
+ const [recordErr] = await tryCatch(
1884
+ config.data.mutation(api.withdrawals.mutations.recordSubmitted, {
1885
+ withdrawalId: created.id,
1886
+ txHash: transferOk.txHash,
1887
+ userOpHash: transferOk.userOpHash
1888
+ })
1889
+ );
1890
+ if (recordErr) {
1891
+ return [
1892
+ new CapxulError({
1893
+ code: "NETWORK_ERROR",
1894
+ message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1895
+ cause: recordErr,
1896
+ details: {
1897
+ withdrawalId: created.id,
1898
+ txHash: transferOk.txHash,
1899
+ userOpHash: transferOk.userOpHash
1900
+ }
1901
+ }),
1902
+ null
1903
+ ];
1904
+ }
1905
+ return [null, created];
1338
1906
  },
1339
1907
  retrieve: async (withdrawalId) => {
1340
1908
  if (!config.data) {
1341
1909
  return stub("withdrawals.retrieve");
1342
1910
  }
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) {
1911
+ const [err, raw] = await tryCatch(
1912
+ config.data.query(api.withdrawals.queries.retrieve, { withdrawalId })
1913
+ );
1914
+ if (err) {
1915
+ return [fromConvexError(err), null];
1916
+ }
1917
+ const withdrawal = raw;
1918
+ if (!withdrawal) {
1359
1919
  return [
1360
- fromConvexError(cause),
1920
+ new CapxulError({
1921
+ code: "NOT_FOUND",
1922
+ message: `withdrawal ${withdrawalId} not found`
1923
+ }),
1361
1924
  null
1362
1925
  ];
1363
1926
  }
1927
+ return [null, withdrawal];
1364
1928
  },
1365
1929
  list: async (input) => {
1366
1930
  if (!config.data) {
1367
1931
  return stub("withdrawals.list");
1368
1932
  }
1369
- try {
1370
- const result = await config.data.query(
1371
- api.withdrawals.queries.list,
1372
- {
1373
- limit: input?.limit,
1374
- cursor: input?.cursor
1375
- }
1933
+ const [err, raw] = await tryCatch(
1934
+ config.data.query(api.withdrawals.queries.list, {
1935
+ limit: input?.limit,
1936
+ cursor: input?.cursor
1937
+ })
1938
+ );
1939
+ if (err) {
1940
+ return [fromConvexError(err), null];
1941
+ }
1942
+ return [null, raw];
1943
+ },
1944
+ recordCompleted: async (input) => {
1945
+ if (!config.data) {
1946
+ return stub(
1947
+ "withdrawals.recordCompleted"
1376
1948
  );
1377
- return [null, result];
1378
- } catch (cause) {
1949
+ }
1950
+ const [err] = await tryCatch(
1951
+ config.data.mutation(api.withdrawals.mutations.recordCompleted, {
1952
+ withdrawalId: input.withdrawalId,
1953
+ txHash: input.txHash
1954
+ })
1955
+ );
1956
+ if (err) {
1379
1957
  return [
1380
- fromConvexError(cause),
1958
+ mapRecordCompletedError(fromConvexError(err)),
1381
1959
  null
1382
1960
  ];
1383
1961
  }
1962
+ return [null, null];
1384
1963
  }
1385
1964
  };
1386
1965
  }
1387
1966
  function createOrgWithdrawalsClient(config = {}) {
1388
1967
  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
- ),
1968
+ /**
1969
+ * Org-scope create (Withdrawals v1 W2, #465).
1970
+ *
1971
+ * D6 returns the `processing` row only. No `transferAsOwner`
1972
+ * tail, no `recordSubmitted` call. Org Safe + Zodiac submission
1973
+ * orchestration ships in W3+.
1974
+ */
1975
+ create: async (input) => {
1976
+ if (!config.data) {
1977
+ return stub(
1978
+ "organizations.withdrawals.create"
1979
+ );
1980
+ }
1981
+ const [err, raw] = await tryCatch(
1982
+ config.data.mutation(api.withdrawals.mutations.createOrg, {
1983
+ organizationId: input.organizationId,
1984
+ amount: input.amount,
1985
+ destination: {
1986
+ externalAccountId: input.destination.externalAccountId
1987
+ },
1988
+ source: input.source,
1989
+ reference: input.reference,
1990
+ idempotencyKey: input.idempotencyKey
1991
+ })
1992
+ );
1993
+ if (err) {
1994
+ return [mapCreateError2(fromConvexError(err)), null];
1995
+ }
1996
+ const created = raw;
1997
+ if (!created) {
1998
+ return [
1999
+ new CapxulError({
2000
+ code: "NETWORK_ERROR",
2001
+ message: "organizations.withdrawals.create returned no withdrawal resource"
2002
+ }),
2003
+ null
2004
+ ];
2005
+ }
2006
+ return [null, created];
2007
+ },
1395
2008
  retrieve: async (input) => {
1396
2009
  if (!config.data) {
1397
2010
  return stub(
1398
2011
  "organizations.withdrawals.retrieve"
1399
2012
  );
1400
2013
  }
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) {
2014
+ const [err, raw] = await tryCatch(
2015
+ config.data.query(api.withdrawals.queries.retrieve, {
2016
+ withdrawalId: input.withdrawalId
2017
+ })
2018
+ );
2019
+ if (err) {
2020
+ return [fromConvexError(err), null];
2021
+ }
2022
+ const withdrawal = raw;
2023
+ if (!withdrawal) {
1427
2024
  return [
1428
- fromConvexError(cause),
2025
+ new CapxulError({
2026
+ code: "NOT_FOUND",
2027
+ message: `withdrawal ${input.withdrawalId} not found`
2028
+ }),
2029
+ null
2030
+ ];
2031
+ }
2032
+ const ownerCheck = withdrawal.owner;
2033
+ if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
2034
+ return [
2035
+ new CapxulError({
2036
+ code: "NOT_FOUND",
2037
+ message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
2038
+ }),
1429
2039
  null
1430
2040
  ];
1431
2041
  }
2042
+ return [null, withdrawal];
1432
2043
  },
1433
2044
  list: async (input) => {
1434
2045
  if (!config.data) {
@@ -1436,46 +2047,67 @@ function createOrgWithdrawalsClient(config = {}) {
1436
2047
  "organizations.withdrawals.list"
1437
2048
  );
1438
2049
  }
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
- ];
2050
+ const [err, raw] = await tryCatch(
2051
+ config.data.query(api.withdrawals.queries.listOrg, {
2052
+ organizationId: input.organizationId,
2053
+ limit: input.limit,
2054
+ cursor: input.cursor
2055
+ })
2056
+ );
2057
+ if (err) {
2058
+ return [fromConvexError(err), null];
1454
2059
  }
2060
+ return [null, raw];
1455
2061
  }
1456
2062
  };
1457
2063
  }
2064
+ async function handleSubmissionFailure(config, withdrawalId, error) {
2065
+ await bestEffortMarkFailed2(config, withdrawalId, error);
2066
+ return [error, null];
2067
+ }
1458
2068
  async function bestEffortMarkFailed2(config, withdrawalId, error) {
1459
- try {
1460
- await config.data.mutation(api.withdrawals.mutations.markFailed, {
2069
+ await tryCatch(
2070
+ config.data.mutation(api.withdrawals.mutations.markFailed, {
1461
2071
  withdrawalId,
1462
2072
  errorCode: error.code,
1463
2073
  errorMessage: error.message
1464
- });
1465
- } catch {
2074
+ })
2075
+ );
2076
+ }
2077
+ function mapCreateError2(error) {
2078
+ switch (error.code) {
2079
+ case "NOT_AUTHENTICATED":
2080
+ case "PERMISSION_DENIED":
2081
+ case "INVALID_INPUT":
2082
+ case "INSUFFICIENT_BALANCE":
2083
+ case "IDEMPOTENCY_CONFLICT":
2084
+ case "KYC_REQUIRED":
2085
+ case "POLICY_DENIED":
2086
+ case "RATE_LIMITED":
2087
+ case "NETWORK_ERROR":
2088
+ case "NOT_FOUND":
2089
+ case "VERIFICATION_REQUIRED":
2090
+ return error;
2091
+ default:
2092
+ return new CapxulError({
2093
+ code: "NETWORK_ERROR",
2094
+ message: error.message,
2095
+ cause: error,
2096
+ details: error.details,
2097
+ operationId: error.operationId,
2098
+ correlationId: error.correlationId,
2099
+ retryable: error.retryable
2100
+ });
1466
2101
  }
1467
2102
  }
1468
- function mapCreateError2(error) {
2103
+ function mapRecordCompletedError(error) {
1469
2104
  switch (error.code) {
1470
2105
  case "NOT_AUTHENTICATED":
1471
2106
  case "PERMISSION_DENIED":
1472
2107
  case "INVALID_INPUT":
1473
- case "INSUFFICIENT_BALANCE":
1474
- case "IDEMPOTENCY_CONFLICT":
1475
- case "KYC_REQUIRED":
1476
- case "POLICY_DENIED":
1477
- case "RATE_LIMITED":
2108
+ case "NOT_FOUND":
1478
2109
  case "NETWORK_ERROR":
2110
+ case "INTERNAL_ERROR":
1479
2111
  return error;
1480
2112
  default:
1481
2113
  return new CapxulError({
@@ -1511,6 +2143,136 @@ function createWebhookEventsClient() {
1511
2143
  }
1512
2144
 
1513
2145
  // src/core/organizations.ts
2146
+ function createOrgExternalAccountsClient(config) {
2147
+ return {
2148
+ create: async (input) => {
2149
+ if (!config.data) {
2150
+ return stub(
2151
+ "organizations.externalAccounts.create"
2152
+ );
2153
+ }
2154
+ const [err, raw] = await tryCatch(
2155
+ config.data.mutation(api.externalAccounts.mutations.createOrg, {
2156
+ organizationId: input.organizationId,
2157
+ kind: input.kind,
2158
+ label: input.label,
2159
+ address: input.address,
2160
+ iban: input.iban,
2161
+ bic: input.bic,
2162
+ accountHolder: input.accountHolder,
2163
+ network: input.network,
2164
+ panToken: input.panToken,
2165
+ last4: input.last4
2166
+ })
2167
+ );
2168
+ if (err) {
2169
+ return [
2170
+ fromConvexError(err),
2171
+ null
2172
+ ];
2173
+ }
2174
+ if (!raw) {
2175
+ return [
2176
+ new CapxulError({
2177
+ code: "NOT_FOUND",
2178
+ message: "external_account creation returned no resource"
2179
+ }),
2180
+ null
2181
+ ];
2182
+ }
2183
+ return [
2184
+ null,
2185
+ brandExternalAccount(
2186
+ raw
2187
+ )
2188
+ ];
2189
+ },
2190
+ list: async (input) => {
2191
+ if (!config.data) {
2192
+ return stub(
2193
+ "organizations.externalAccounts.list"
2194
+ );
2195
+ }
2196
+ const [err, result] = await tryCatch(
2197
+ config.data.query(api.externalAccounts.queries.listOrg, {
2198
+ organizationId: input.organizationId,
2199
+ limit: input.limit,
2200
+ cursor: input.cursor
2201
+ })
2202
+ );
2203
+ if (err) {
2204
+ return [fromConvexError(err), null];
2205
+ }
2206
+ const branded = result.data.map(
2207
+ (row) => brandExternalAccount(
2208
+ row
2209
+ )
2210
+ );
2211
+ return [
2212
+ null,
2213
+ {
2214
+ object: "list",
2215
+ data: branded,
2216
+ page: result.page
2217
+ }
2218
+ ];
2219
+ },
2220
+ retrieve: async (input) => {
2221
+ if (!config.data) {
2222
+ return stub(
2223
+ "organizations.externalAccounts.retrieve"
2224
+ );
2225
+ }
2226
+ const [err, raw] = await tryCatch(
2227
+ config.data.query(api.externalAccounts.queries.retrieveOrg, {
2228
+ organizationId: input.organizationId,
2229
+ externalAccountId: input.externalAccountId
2230
+ })
2231
+ );
2232
+ if (err) {
2233
+ return [
2234
+ fromConvexError(err),
2235
+ null
2236
+ ];
2237
+ }
2238
+ if (!raw) {
2239
+ return [
2240
+ new CapxulError({
2241
+ code: "NOT_FOUND",
2242
+ message: `external_account ${input.externalAccountId} not found`
2243
+ }),
2244
+ null
2245
+ ];
2246
+ }
2247
+ return [
2248
+ null,
2249
+ brandExternalAccount(
2250
+ raw
2251
+ )
2252
+ ];
2253
+ },
2254
+ remove: async (input) => {
2255
+ if (!config.data) {
2256
+ return stub(
2257
+ "organizations.externalAccounts.remove"
2258
+ );
2259
+ }
2260
+ const [err] = await tryCatch(
2261
+ config.data.mutation(api.externalAccounts.mutations.removeOrg, {
2262
+ organizationId: input.organizationId,
2263
+ externalAccountId: input.externalAccountId
2264
+ })
2265
+ );
2266
+ if (err) {
2267
+ return [
2268
+ fromConvexError(err),
2269
+ null
2270
+ ];
2271
+ }
2272
+ return [null, void 0];
2273
+ }
2274
+ };
2275
+ }
1514
2276
  function createOrganizationsClient(config = {}) {
1515
2277
  return {
1516
2278
  create: async () => stub("organizations.create"),
@@ -1566,18 +2328,7 @@ function createOrganizationsClient(config = {}) {
1566
2328
  retrieve: async () => stub("organizations.subAccounts.retrieve"),
1567
2329
  remove: async () => stub("organizations.subAccounts.remove")
1568
2330
  },
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
- },
2331
+ externalAccounts: createOrgExternalAccountsClient(config),
1581
2332
  balanceLedger: {
1582
2333
  list: async () => stub(
1583
2334
  "organizations.balanceLedger.list"
@@ -1714,59 +2465,6 @@ function createVirtualCardsClient() {
1714
2465
  cancel: async () => stub("virtualCards.cancel")
1715
2466
  };
1716
2467
  }
1717
-
1718
- // ../observability/src/try-catch.ts
1719
- async function tryCatch(promise) {
1720
- try {
1721
- return [null, await promise];
1722
- } catch (e) {
1723
- return [e instanceof Error ? e : new Error(String(e)), null];
1724
- }
1725
- }
1726
-
1727
- // ../observability/src/debug-log.ts
1728
- function isDevelopmentBuild() {
1729
- if (typeof process === "undefined") {
1730
- return false;
1731
- }
1732
- return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
1733
- }
1734
- function debugLog(line) {
1735
- if (!isDevelopmentBuild()) return;
1736
- if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
1737
- console.info(line);
1738
- return;
1739
- }
1740
- if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
1741
- process.stderr.write(`${line}
1742
- `);
1743
- }
1744
- }
1745
- function formatDebugValue(value) {
1746
- if (value === void 0 || value === "") return "";
1747
- if (typeof value === "string") return value;
1748
- try {
1749
- return JSON.stringify(value);
1750
- } catch {
1751
- return String(value);
1752
- }
1753
- }
1754
- function track(...args) {
1755
- const [name, props] = args;
1756
- debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
1757
- }
1758
- function formatDebugValue2(value) {
1759
- if (value === void 0 || value === "") return "";
1760
- if (typeof value === "string") return value;
1761
- try {
1762
- return JSON.stringify(value);
1763
- } catch {
1764
- return String(value);
1765
- }
1766
- }
1767
- function identify(userId, traits) {
1768
- debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
1769
- }
1770
2468
  function createAuthFlowMachine(client) {
1771
2469
  return setup({
1772
2470
  types: {},
@@ -2042,122 +2740,6 @@ function emailDomain(email) {
2042
2740
  const domain = email.split("@")[1]?.trim().toLowerCase();
2043
2741
  return domain || "unknown";
2044
2742
  }
2045
-
2046
- // ../platform-kernel/src/ids.ts
2047
- function makePrefixedIdConstructor(prefix, fieldName) {
2048
- const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
2049
- return (raw) => {
2050
- if (typeof raw !== "string" || !re.test(raw)) {
2051
- throw new Error(
2052
- `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
2053
- );
2054
- }
2055
- return raw;
2056
- };
2057
- }
2058
- var toAccountId = makePrefixedIdConstructor(
2059
- "acct",
2060
- "accountId"
2061
- );
2062
- var toOrganizationId = makePrefixedIdConstructor("org", "organizationId");
2063
- var toMemberId = makePrefixedIdConstructor(
2064
- "mem",
2065
- "memberId"
2066
- );
2067
- var toSafeId = makePrefixedIdConstructor(
2068
- "safe",
2069
- "safeId"
2070
- );
2071
- var toTreasuryId = makePrefixedIdConstructor(
2072
- "try",
2073
- "treasuryId"
2074
- );
2075
- var toApiKeyId = makePrefixedIdConstructor(
2076
- "ak",
2077
- "apiKeyId"
2078
- );
2079
- var toOperationId = makePrefixedIdConstructor(
2080
- "op",
2081
- "operationId"
2082
- );
2083
- var toKycProfileId = makePrefixedIdConstructor(
2084
- "kyc",
2085
- "kycProfileId"
2086
- );
2087
- var toKybProfileId = makePrefixedIdConstructor(
2088
- "kyb",
2089
- "kybProfileId"
2090
- );
2091
- var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
2092
- var toPaymentId = makePrefixedIdConstructor(
2093
- "pay",
2094
- "paymentId"
2095
- );
2096
- var toWithdrawalId = makePrefixedIdConstructor(
2097
- "wd",
2098
- "withdrawalId"
2099
- );
2100
- var toWebhookEndpointId = makePrefixedIdConstructor("we", "webhookEndpointId");
2101
- var toWebhookEventId = makePrefixedIdConstructor("evt", "webhookEventId");
2102
- var toSubAccountId = makePrefixedIdConstructor(
2103
- "sub",
2104
- "subAccountId"
2105
- );
2106
- var toVirtualAccountId = makePrefixedIdConstructor("va", "virtualAccountId");
2107
- var toVirtualCardId = makePrefixedIdConstructor(
2108
- "vc",
2109
- "virtualCardId"
2110
- );
2111
- var toTransferId = makePrefixedIdConstructor(
2112
- "txfr",
2113
- "transferId"
2114
- );
2115
- var toDocumentId = makePrefixedIdConstructor(
2116
- "doc",
2117
- "documentId"
2118
- );
2119
- var toBalanceLedgerEntryId = makePrefixedIdConstructor("bal", "balanceLedgerEntryId");
2120
-
2121
- // ../platform-kernel/src/value-objects.ts
2122
- var PHONE_NUMBER_RE = /^\+[1-9]\d{1,14}$/;
2123
- function toPhoneNumber(raw) {
2124
- if (typeof raw !== "string" || !PHONE_NUMBER_RE.test(raw)) {
2125
- throw new Error(
2126
- `Invalid phoneNumber: expected E.164 string matching ^\\+[1-9]\\d{1,14}$, got ${String(raw)}`
2127
- );
2128
- }
2129
- return raw;
2130
- }
2131
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2132
- function toEmail(raw) {
2133
- if (typeof raw !== "string") {
2134
- throw new Error(
2135
- `Invalid email: expected string, got ${String(raw)}`
2136
- );
2137
- }
2138
- const lowered = raw.trim().toLowerCase();
2139
- if (!EMAIL_RE.test(lowered)) {
2140
- throw new Error(
2141
- `Invalid email: expected local@domain.tld, got ${String(raw)}`
2142
- );
2143
- }
2144
- return lowered;
2145
- }
2146
- var USERNAME_RE = /^[a-z][a-z0-9_-]{2,29}$/;
2147
- function toUsername(raw) {
2148
- if (typeof raw !== "string") {
2149
- throw new Error(
2150
- `Invalid username: expected string, got ${String(raw)}`
2151
- );
2152
- }
2153
- const lowered = raw.toLowerCase();
2154
- if (!USERNAME_RE.test(lowered)) {
2155
- throw new Error(
2156
- `Invalid username: expected 3-30 chars, letter-first, [a-z0-9_-], got ${String(raw)}`
2157
- );
2158
- }
2159
- return lowered;
2160
- }
2161
2743
  function createProvisioningMachine(client) {
2162
2744
  return setup({
2163
2745
  types: {},
@@ -2550,7 +3132,7 @@ function createCapxulClient(config = {}) {
2550
3132
  subAccounts: createSubAccountsClient(),
2551
3133
  virtualAccounts: createVirtualAccountsClient(),
2552
3134
  virtualCards: createVirtualCardsClient(),
2553
- externalAccounts: createExternalAccountsClient(),
3135
+ externalAccounts: createExternalAccountsClient(config),
2554
3136
  operations: createOperationsClient(config),
2555
3137
  webhookEndpoints: createWebhookEndpointsClient(),
2556
3138
  webhookEvents: createWebhookEventsClient(),