@capxul/sdk 2.5.5 → 2.6.1

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.
@@ -1,4 +1,135 @@
1
1
  import { Context, Data, Effect, Layer } from "effect";
2
+ //#region ../errors/src/chain-cause.ts
3
+ /**
4
+ * Why an operation failed — the one flat vocabulary. The first four are the
5
+ * auth/provider diagnoses that predate this file. The rest name the chain
6
+ * path. `FailureMode` is derived from this list so a runtime membership check
7
+ * (the SDK's `$exception` projection, the observability schema) and the type
8
+ * can never drift apart.
9
+ */
10
+ const FAILURE_MODES = [
11
+ "auth-origin-mismatch",
12
+ "stale-openfort-cache",
13
+ "app-env-allowlist",
14
+ "no-secure-context",
15
+ "rate-limited",
16
+ "timeout",
17
+ "upstream-down",
18
+ "revert",
19
+ "insufficient-balance",
20
+ "insufficient-allowance",
21
+ "rejected",
22
+ "unauthorized",
23
+ "unknown"
24
+ ];
25
+ function isFailureMode(value) {
26
+ return typeof value === "string" && FAILURE_MODES.includes(value);
27
+ }
28
+ /** Who answered. `alto` is the bundler; `gateway` is the AA gateway's own logic. */
29
+ const CHAIN_UPSTREAMS = [
30
+ "alchemy",
31
+ "infura",
32
+ "public",
33
+ "alto",
34
+ "gateway"
35
+ ];
36
+ function isChainUpstream(value) {
37
+ return typeof value === "string" && CHAIN_UPSTREAMS.includes(value);
38
+ }
39
+ const PROVIDER_MESSAGE_MAX = 300;
40
+ const METHOD_MAX = 64;
41
+ const REVERT_NAME_MAX = 64;
42
+ const REVERT_ARG_KEY_MAX = 32;
43
+ const REVERT_ARG_VALUE_MAX = 128;
44
+ const REVERT_ARGS_MAX = 8;
45
+ const SELECTOR_RE = /^0x(?:[0-9a-fA-F]{8})?$/u;
46
+ const MASKS = [
47
+ [/\bBearer\s+[^\s,;]+/giu, "Bearer [REDACTED]"],
48
+ [/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]"],
49
+ [/\b0x[a-fA-F0-9]{40,}\b/gu, "[REDACTED]"],
50
+ [/(^|[^a-fA-F0-9])[a-fA-F0-9]{64}(?=$|[^a-fA-F0-9])/gu, "$1[REDACTED]"],
51
+ [/(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/giu, "[REDACTED]"],
52
+ [/(https?:\/\/[^\s"']+?\/v[0-9]\/)[A-Za-z0-9_-]{16,}/giu, "$1[REDACTED]"],
53
+ [/\b(api[ _-]?key|access[ _-]?token|token|secret|password|passphrase|private[ _-]?key|authorization|cookie|signature|credential|otp)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;&]+)/giu, "$1=[REDACTED]"]
54
+ ];
55
+ /** Mask credential-shaped content, then cap the length. Total on any string. */
56
+ function boundedEvidence(value, max) {
57
+ let text = value;
58
+ for (const [pattern, replacement] of MASKS) text = text.replace(pattern, replacement);
59
+ return text.length <= max ? text : text.slice(0, max);
60
+ }
61
+ function isRecord(value) {
62
+ return typeof value === "object" && value !== null && !Array.isArray(value);
63
+ }
64
+ function decodeRevert(value) {
65
+ if (!isRecord(value)) return null;
66
+ if (value.name !== null && typeof value.name !== "string") return null;
67
+ if (typeof value.selector !== "string" || !SELECTOR_RE.test(value.selector)) return null;
68
+ if (!isRecord(value.args)) return null;
69
+ const args = {};
70
+ for (const [key, arg] of Object.entries(value.args).slice(0, REVERT_ARGS_MAX)) {
71
+ if (typeof arg !== "string") return null;
72
+ args[boundedEvidence(key, REVERT_ARG_KEY_MAX)] = boundedEvidence(arg, REVERT_ARG_VALUE_MAX);
73
+ }
74
+ return {
75
+ name: value.name === null ? null : boundedEvidence(value.name, REVERT_NAME_MAX),
76
+ selector: value.selector,
77
+ args
78
+ };
79
+ }
80
+ /**
81
+ * Decode a `ChainCause` from untrusted wire data (gateway JSON-RPC `error.data`,
82
+ * a Convex error payload). Every string is masked and bounded; an unrecognized
83
+ * shape yields null, because a consumer must never invent a cause.
84
+ */
85
+ function decodeChainCause(value) {
86
+ if (!isRecord(value)) return null;
87
+ if (!isFailureMode(value.mode) || !isChainUpstream(value.upstream)) return null;
88
+ if (typeof value.method !== "string" || typeof value.retryable !== "boolean") return null;
89
+ const cause = {
90
+ mode: value.mode,
91
+ upstream: value.upstream,
92
+ method: boundedEvidence(value.method, METHOD_MAX),
93
+ retryable: value.retryable
94
+ };
95
+ if (typeof value.httpStatus === "number" && Number.isInteger(value.httpStatus)) cause.httpStatus = value.httpStatus;
96
+ if (typeof value.rpcCode === "number" && Number.isInteger(value.rpcCode)) cause.rpcCode = value.rpcCode;
97
+ const revert = decodeRevert(value.revert);
98
+ if (revert !== null) cause.revert = revert;
99
+ if (typeof value.providerMessage === "string") cause.providerMessage = boundedEvidence(value.providerMessage, PROVIDER_MESSAGE_MAX);
100
+ return cause;
101
+ }
102
+ /**
103
+ * The cause as flat PostHog properties. Same keys from every producer, so one
104
+ * filter (`failure_mode = 'rate-limited'`) finds the gateway's, the backend's,
105
+ * and the SDK's rows alike.
106
+ */
107
+ function chainCauseProperties(chain) {
108
+ return {
109
+ failure_mode: chain.mode,
110
+ chain_upstream: chain.upstream,
111
+ chain_method: chain.method,
112
+ chain_retryable: chain.retryable,
113
+ ...chain.httpStatus === void 0 ? {} : { chain_http_status: chain.httpStatus },
114
+ ...chain.rpcCode === void 0 ? {} : { chain_rpc_code: chain.rpcCode },
115
+ ...chain.revert === void 0 ? {} : {
116
+ chain_revert_name: chain.revert.name ?? "unknown",
117
+ chain_revert_selector: chain.revert.selector,
118
+ chain_revert_args: chain.revert.args
119
+ },
120
+ ...chain.providerMessage === void 0 ? {} : { chain_provider_message: chain.providerMessage }
121
+ };
122
+ }
123
+ /**
124
+ * The error-tracking fingerprint for a failure: the code, the one-word cause,
125
+ * and the operation. PostHog groups `$exception` rows by this value, so one
126
+ * issue is one cause at one operation instead of every provider failure in
127
+ * one bucket (money-path audit 2026-09-02, row P1).
128
+ */
129
+ function failureFingerprint(code, mode, operation) {
130
+ return `${code}:${mode ?? "none"}:${operation}`;
131
+ }
132
+ //#endregion
2
133
  //#region ../errors/src/errors.ts
3
134
  const CAPXUL_ERROR_CODES = [
4
135
  "NOT_AUTHENTICATED",
@@ -60,6 +191,10 @@ var CapxulError = class extends Error {
60
191
  details;
61
192
  correlationId;
62
193
  layer;
194
+ /** The one-word cause (`FailureMode`). `chain.mode` when a chain cause is set. */
195
+ mode;
196
+ /** The structured chain-path cause. Never rewritten by a later layer. */
197
+ chain;
63
198
  constructor(code, message, options = {}) {
64
199
  super(message, "cause" in options ? { cause: options.cause } : void 0);
65
200
  this.name = "CapxulError";
@@ -67,6 +202,10 @@ var CapxulError = class extends Error {
67
202
  if (options.details !== void 0) this.details = options.details;
68
203
  if (options.correlationId !== void 0) this.correlationId = options.correlationId;
69
204
  if (options.layer !== void 0) this.layer = options.layer;
205
+ const legacy = options.details?.failure_mode;
206
+ const mode = options.chain?.mode ?? (isFailureMode(options.mode) ? options.mode : void 0) ?? (isFailureMode(legacy) ? legacy : void 0);
207
+ if (mode !== void 0) this.mode = mode;
208
+ if (options.chain !== void 0) this.chain = options.chain;
70
209
  }
71
210
  };
72
211
  function isCapxulError(value) {
@@ -76,7 +215,9 @@ function deserializeCapxulError(serialized) {
76
215
  return new CapxulError(serialized.code, serialized.message, compactErrorOptions({
77
216
  details: serialized.details,
78
217
  correlationId: serialized.correlationId,
79
- layer: serialized.layer
218
+ layer: serialized.layer,
219
+ mode: serialized.mode,
220
+ chain: serialized.chain === void 0 ? void 0 : decodeChainCause(serialized.chain) ?? void 0
80
221
  }));
81
222
  }
82
223
  function compactErrorOptions(options) {
@@ -85,6 +226,8 @@ function compactErrorOptions(options) {
85
226
  if (options.details !== void 0) result.details = options.details;
86
227
  if (options.correlationId !== void 0) result.correlationId = options.correlationId;
87
228
  if (options.layer !== void 0) result.layer = options.layer;
229
+ if (options.mode !== void 0) result.mode = options.mode;
230
+ if (options.chain !== void 0) result.chain = options.chain;
88
231
  return result;
89
232
  }
90
233
  const Errors = {
@@ -100,11 +243,14 @@ const Errors = {
100
243
  provider,
101
244
  operation
102
245
  };
103
- if (opts?.failure_mode) details.failure_mode = opts.failure_mode;
246
+ const mode = opts?.chain?.mode ?? opts?.failure_mode;
247
+ if (mode !== void 0) details.failure_mode = mode;
104
248
  if (opts?.httpStatus !== void 0) details.httpStatus = opts.httpStatus;
105
249
  return new CapxulError("PROVIDER_ERROR", `Provider error: ${provider} ${operation}`, {
106
250
  cause,
107
- details
251
+ details,
252
+ ...mode === void 0 ? {} : { mode },
253
+ ...opts?.chain === void 0 ? {} : { chain: opts.chain }
108
254
  });
109
255
  },
110
256
  capabilityUnavailable: (provider, operation) => new CapxulError("CAPABILITY_UNAVAILABLE", "Provider capability is unavailable", { details: {
@@ -209,12 +355,18 @@ function isCapxulCode(value) {
209
355
  }
210
356
  function reconstruct(serialized) {
211
357
  if (!isCapxulCode(serialized.code)) return null;
212
- return deserializeCapxulError({
358
+ const payload = {
213
359
  code: serialized.code,
214
360
  message: typeof serialized.message === "string" ? serialized.message : String(serialized.code),
215
361
  ...typeof serialized.details === "object" && serialized.details !== null && !Array.isArray(serialized.details) ? { details: serialized.details } : {},
216
362
  ...typeof serialized.correlationId === "string" ? { correlationId: serialized.correlationId } : {},
217
- ...typeof serialized.layer === "string" ? { layer: serialized.layer } : {}
363
+ ...typeof serialized.layer === "string" ? { layer: serialized.layer } : {},
364
+ ...isFailureMode(serialized.mode) ? { mode: serialized.mode } : {}
365
+ };
366
+ const chain = decodeChainCause(serialized.chain);
367
+ return deserializeCapxulError(chain === null ? payload : {
368
+ ...payload,
369
+ chain
218
370
  });
219
371
  }
220
372
  function decodeConvexError(err) {
@@ -496,4 +648,4 @@ Layer.effect(AuthCachePortTag, Effect.sync(() => new InMemoryAuthCacheAdapter())
496
648
  cause
497
649
  }))));
498
650
  //#endregion
499
- export { toPartyId as A, Errors as B, toDurationMs as C, toJwtToken as D, toEpochSeconds as E, toSessionToken as F, decodeConvexError as I, CAPXUL_ERROR_CODES as L, toPayrollRunId as M, toPublishableKey as N, toKycTier as O, toRoleKey as P, CapxulError as R, toCurrencyCode as S, toEpochMs as T, isCapxulError as V, toAppId as _, AuthCacheError as a, toChainId as b, BYTES32_RE as c, WEI_RE as d, ZERO_BYTES32 as f, toAllowedOrigin as g, toAddress as h, parseCachedJwt as i, toPayrollGroupId as j, toOrgId as k, EVM_ADDRESS_RE as l, toAccountId as m, BrowserAuthCacheAdapter as n, AuthCachePortTag as o, currencySymbolFor as p, parseAuthSession as r, APP_ID_RE as s, InMemoryAuthCacheAdapter as t, SUPPORTED_CURRENCY_CODES as u, toAuthUserId as v, toEmail as w, toCountryCode as x, toBudgetId as y, EXPECTED_OPERATION_OUTCOMES as z };
651
+ export { toPartyId as A, Errors as B, toDurationMs as C, toJwtToken as D, toEpochSeconds as E, toSessionToken as F, failureFingerprint as G, CHAIN_UPSTREAMS as H, decodeConvexError as I, isFailureMode as K, CAPXUL_ERROR_CODES as L, toPayrollRunId as M, toPublishableKey as N, toKycTier as O, toRoleKey as P, CapxulError as R, toCurrencyCode as S, toEpochMs as T, FAILURE_MODES as U, isCapxulError as V, chainCauseProperties as W, toAppId as _, AuthCacheError as a, toChainId as b, BYTES32_RE as c, WEI_RE as d, ZERO_BYTES32 as f, toAllowedOrigin as g, toAddress as h, parseCachedJwt as i, toPayrollGroupId as j, toOrgId as k, EVM_ADDRESS_RE as l, toAccountId as m, BrowserAuthCacheAdapter as n, AuthCachePortTag as o, currencySymbolFor as p, parseAuthSession as r, APP_ID_RE as s, InMemoryAuthCacheAdapter as t, SUPPORTED_CURRENCY_CODES as u, toAuthUserId as v, toEmail as w, toCountryCode as x, toBudgetId as y, EXPECTED_OPERATION_OUTCOMES as z };