@run402/sdk 1.53.0 → 1.54.0

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.
Files changed (81) hide show
  1. package/README.md +77 -18
  2. package/core-dist/keystore.d.ts +1 -0
  3. package/core-dist/keystore.js +65 -21
  4. package/dist/errors.d.ts +91 -0
  5. package/dist/errors.d.ts.map +1 -1
  6. package/dist/errors.js +162 -6
  7. package/dist/errors.js.map +1 -1
  8. package/dist/index.d.ts +4 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +15 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/namespaces/admin.d.ts +4 -1
  13. package/dist/namespaces/admin.d.ts.map +1 -1
  14. package/dist/namespaces/admin.js +1 -1
  15. package/dist/namespaces/admin.js.map +1 -1
  16. package/dist/namespaces/apps.d.ts +27 -30
  17. package/dist/namespaces/apps.d.ts.map +1 -1
  18. package/dist/namespaces/apps.js.map +1 -1
  19. package/dist/namespaces/auth.d.ts.map +1 -1
  20. package/dist/namespaces/auth.js +10 -1
  21. package/dist/namespaces/auth.js.map +1 -1
  22. package/dist/namespaces/billing.d.ts +15 -6
  23. package/dist/namespaces/billing.d.ts.map +1 -1
  24. package/dist/namespaces/billing.js.map +1 -1
  25. package/dist/namespaces/contracts.d.ts +67 -15
  26. package/dist/namespaces/contracts.d.ts.map +1 -1
  27. package/dist/namespaces/contracts.js +32 -8
  28. package/dist/namespaces/contracts.js.map +1 -1
  29. package/dist/namespaces/deploy.d.ts +5 -4
  30. package/dist/namespaces/deploy.d.ts.map +1 -1
  31. package/dist/namespaces/deploy.js +13 -7
  32. package/dist/namespaces/deploy.js.map +1 -1
  33. package/dist/namespaces/domains.d.ts +5 -1
  34. package/dist/namespaces/domains.d.ts.map +1 -1
  35. package/dist/namespaces/domains.js +1 -1
  36. package/dist/namespaces/domains.js.map +1 -1
  37. package/dist/namespaces/email.d.ts +21 -13
  38. package/dist/namespaces/email.d.ts.map +1 -1
  39. package/dist/namespaces/email.js +5 -4
  40. package/dist/namespaces/email.js.map +1 -1
  41. package/dist/namespaces/functions.d.ts +2 -2
  42. package/dist/namespaces/functions.d.ts.map +1 -1
  43. package/dist/namespaces/functions.js +1 -1
  44. package/dist/namespaces/functions.js.map +1 -1
  45. package/dist/namespaces/functions.types.d.ts +4 -0
  46. package/dist/namespaces/functions.types.d.ts.map +1 -1
  47. package/dist/namespaces/projects.d.ts.map +1 -1
  48. package/dist/namespaces/projects.js +3 -5
  49. package/dist/namespaces/projects.js.map +1 -1
  50. package/dist/namespaces/projects.types.d.ts +9 -1
  51. package/dist/namespaces/projects.types.d.ts.map +1 -1
  52. package/dist/namespaces/secrets.d.ts +5 -1
  53. package/dist/namespaces/secrets.d.ts.map +1 -1
  54. package/dist/namespaces/secrets.js +1 -1
  55. package/dist/namespaces/secrets.js.map +1 -1
  56. package/dist/namespaces/sender-domain.d.ts +4 -1
  57. package/dist/namespaces/sender-domain.d.ts.map +1 -1
  58. package/dist/namespaces/sender-domain.js +1 -1
  59. package/dist/namespaces/sender-domain.js.map +1 -1
  60. package/dist/namespaces/service.d.ts +11 -31
  61. package/dist/namespaces/service.d.ts.map +1 -1
  62. package/dist/namespaces/service.js.map +1 -1
  63. package/dist/namespaces/subdomains.d.ts +12 -2
  64. package/dist/namespaces/subdomains.d.ts.map +1 -1
  65. package/dist/namespaces/subdomains.js +23 -18
  66. package/dist/namespaces/subdomains.js.map +1 -1
  67. package/dist/namespaces/tier.d.ts +9 -1
  68. package/dist/namespaces/tier.d.ts.map +1 -1
  69. package/dist/namespaces/tier.js.map +1 -1
  70. package/dist/node/index.d.ts +2 -2
  71. package/dist/node/index.d.ts.map +1 -1
  72. package/dist/node/index.js +1 -1
  73. package/dist/node/index.js.map +1 -1
  74. package/dist/retry.d.ts +50 -0
  75. package/dist/retry.d.ts.map +1 -0
  76. package/dist/retry.js +61 -0
  77. package/dist/retry.js.map +1 -0
  78. package/dist/scoped.d.ts +13 -13
  79. package/dist/scoped.d.ts.map +1 -1
  80. package/dist/scoped.js.map +1 -1
  81. package/package.json +1 -1
package/README.md CHANGED
@@ -156,25 +156,69 @@ const resumed = await r.deploy.resume(operationId);
156
156
 
157
157
  ### Errors
158
158
 
159
- All failures throw subclasses of `Run402Error`:
159
+ All failures throw subclasses of `Run402Error`. Every subclass carries a stable
160
+ `kind` discriminator string and an `isRun402Error` brand:
161
+
162
+ | Class | `kind` | When | Notable fields |
163
+ |---|---|---|---|
164
+ | `PaymentRequired` | `"payment_required"` | HTTP 402 | x402 payment requirements in `body` |
165
+ | `ProjectNotFound` | `"project_not_found"` | Project ID not in the credential provider | `projectId` |
166
+ | `Unauthorized` | `"unauthorized"` | HTTP 401 / 403 | — |
167
+ | `ApiError` | `"api_error"` | Other non-2xx responses | `status`, `body` |
168
+ | `NetworkError` | `"network_error"` | Fetch rejected with no HTTP response | `cause` |
169
+ | `LocalError` | `"local_error"` | Local-host issues (filesystem, signing) | `cause` |
170
+ | `Run402DeployError` | `"deploy_error"` | Structured envelope from the deploy state machine (v1.34+) | `code`, `phase`, `operationId`, `safeToRetry`, `mutationState`, `nextActions` |
171
+
172
+ **Branch with type guards, not `instanceof`.** `instanceof X` is an identity
173
+ check on the class object — it fails silently when the consumer's runtime
174
+ holds a different copy of the SDK (duplicate npm installs, bundler chunk
175
+ splits, ESM/CJS interop, V8-isolate realms). The exported guards
176
+ (`isPaymentRequired`, `isDeployError`, …) check `isRun402Error` + `kind`,
177
+ which is identity-free and survives all of those scenarios. `instanceof`
178
+ continues to work for back-compat in the simple single-copy case.
160
179
 
161
- | Class | When | Notable fields |
162
- |---|---|---|
163
- | `PaymentRequired` | HTTP 402 | x402 payment requirements |
164
- | `ProjectNotFound` | Project ID not in the credential provider | — |
165
- | `Unauthorized` | HTTP 401 / 403 | — |
166
- | `ApiError` | Other non-2xx responses | `status`, `body` |
167
- | `NetworkError` | Fetch rejected with no HTTP response | — |
168
- | `LocalError` | Local-host issues (filesystem, signing) | — |
169
- | `Run402DeployError` | Structured envelope from the deploy state machine (v1.34+) | `code`, `phase`, `operationId`, `safeToRetry`, `mutationState`, `nextActions` |
180
+ ```ts
181
+ import {
182
+ run402,
183
+ isPaymentRequired,
184
+ isDeployError,
185
+ type ReleaseSpec,
186
+ } from "@run402/sdk/node";
170
187
 
171
- Branch on the structured fields, not English `message` text:
188
+ declare const spec: ReleaseSpec;
189
+ const r = run402();
190
+
191
+ try {
192
+ await r.deploy.apply(spec);
193
+ } catch (e) {
194
+ if (isPaymentRequired(e)) {
195
+ // e is narrowed to PaymentRequired
196
+ // present payment requirements to the user — read e.body, e.context, etc.
197
+ } else if (isDeployError(e) && e.safeToRetry) {
198
+ // e is narrowed to Run402DeployError; it's safe to retry with the same idempotency key
199
+ } else throw e;
200
+ }
201
+ ```
202
+
203
+ `Run402Error.toJSON()` returns a canonical envelope, so `JSON.stringify(e)`
204
+ produces a populated structured object instead of the empty `"{}"` plain
205
+ `Error` produces. Use this for telemetry, MCP tool results, CLI JSON output,
206
+ and any inter-process boundary where the error needs to survive serialization.
207
+
208
+ #### Retry idempotent operations with `withRetry`
209
+
210
+ `withRetry(fn, opts?)` wraps any async call with exponential backoff. It uses
211
+ `isRetryableRun402Error` (the canonical "should I retry this?" policy: 408 /
212
+ 425 / 429 / 5xx / `NetworkError` / gateway-flagged `retryable` or
213
+ `safeToRetry`) by default. Pair it with the SDK method's own
214
+ `idempotencyKey` so retried mutations dedup server-side:
172
215
 
173
216
  ```ts
174
217
  import {
175
218
  run402,
176
- PaymentRequired,
177
- Run402DeployError,
219
+ withRetry,
220
+ isPaymentRequired,
221
+ isDeployError,
178
222
  type ReleaseSpec,
179
223
  } from "@run402/sdk/node";
180
224
 
@@ -182,16 +226,31 @@ declare const spec: ReleaseSpec;
182
226
  const r = run402();
183
227
 
184
228
  try {
185
- await r.deploy.apply(spec);
229
+ const release = await withRetry(
230
+ () => r.deploy.apply(spec, { idempotencyKey: "deploy-2026-05-01" }),
231
+ {
232
+ attempts: 3,
233
+ onRetry: (e, attempt, delayMs) =>
234
+ process.stderr.write(`retry ${attempt} in ${delayMs}ms\n`),
235
+ },
236
+ );
237
+ console.log(release.urls);
186
238
  } catch (e) {
187
- if (e instanceof PaymentRequired) {
188
- // present payment requirements to the user
189
- } else if (e instanceof Run402DeployError && e.safeToRetry) {
190
- // safe to retry same idempotency key
239
+ if (isPaymentRequired(e)) {
240
+ // ... present payment
241
+ } else if (isDeployError(e)) {
242
+ // log structured envelope for triage
243
+ process.stderr.write(JSON.stringify(e) + "\n");
191
244
  } else throw e;
192
245
  }
193
246
  ```
194
247
 
248
+ Defaults: 3 attempts (1 initial + 2 retries), 250 ms base delay, 5 s cap. Pass
249
+ a custom `retryIf` to override the default policy (e.g., retry on
250
+ `PaymentRequired` if your sandbox auto-funds). After exhausting attempts
251
+ `withRetry` throws the LAST error — your catch handler sees the original
252
+ structured envelope, not a wrapper.
253
+
195
254
  The SDK never calls `process.exit`. Each interface (MCP tools, CLI, your code) wraps with its own error behavior.
196
255
 
197
256
  ## Stability
@@ -7,6 +7,7 @@ export interface StoredProject {
7
7
  }
8
8
  export interface KeyStore {
9
9
  active_project_id?: string;
10
+ previous_active_project_id?: string;
10
11
  projects: Record<string, StoredProject>;
11
12
  }
12
13
  /**
@@ -1,7 +1,34 @@
1
- import { readFileSync, writeFileSync, mkdirSync, renameSync, chmodSync } from "node:fs";
1
+ import { readFileSync, writeFileSync, mkdirSync, renameSync, chmodSync, rmdirSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { getKeystorePath } from "./config.js";
5
+ function withFileLock(path, fn, { retries = 200, delayMs = 20 } = {}) {
6
+ const lockDir = path + ".lock";
7
+ mkdirSync(dirname(path), { recursive: true });
8
+ for (let i = 0; i < retries; i++) {
9
+ try {
10
+ mkdirSync(lockDir, { mode: 0o700 });
11
+ }
12
+ catch (e) {
13
+ const code = e.code;
14
+ if (code !== "EEXIST")
15
+ throw e;
16
+ const until = Date.now() + delayMs;
17
+ while (Date.now() < until) { /* spin */ }
18
+ continue;
19
+ }
20
+ try {
21
+ return fn();
22
+ }
23
+ finally {
24
+ try {
25
+ rmdirSync(lockDir);
26
+ }
27
+ catch { /* best-effort */ }
28
+ }
29
+ }
30
+ throw new Error(`Could not acquire keystore lock after ${retries} retries: ${lockDir}`);
31
+ }
5
32
  /**
6
33
  * Load the keystore from disk.
7
34
  * Auto-migrates legacy formats:
@@ -13,7 +40,6 @@ export function loadKeyStore(path) {
13
40
  try {
14
41
  const data = readFileSync(p, "utf-8");
15
42
  const parsed = JSON.parse(data);
16
- // Auto-migrate array format (CLI legacy) to object format
17
43
  if (Array.isArray(parsed)) {
18
44
  const projects = {};
19
45
  for (const item of parsed) {
@@ -29,7 +55,6 @@ export function loadKeyStore(path) {
29
55
  return { projects };
30
56
  }
31
57
  if (parsed && typeof parsed === "object" && parsed.projects) {
32
- // Strip legacy fields (tier, lease_expires_at, expires_at) from projects
33
58
  for (const proj of Object.values(parsed.projects)) {
34
59
  const rec = proj;
35
60
  delete rec.tier;
@@ -38,6 +63,7 @@ export function loadKeyStore(path) {
38
63
  }
39
64
  return {
40
65
  ...(parsed.active_project_id && { active_project_id: parsed.active_project_id }),
66
+ ...(parsed.previous_active_project_id && { previous_active_project_id: parsed.previous_active_project_id }),
41
67
  projects: parsed.projects,
42
68
  };
43
69
  }
@@ -62,27 +88,40 @@ export function getProject(projectId, path) {
62
88
  }
63
89
  export function saveProject(projectId, project, path) {
64
90
  const p = path ?? getKeystorePath();
65
- const store = loadKeyStore(p);
66
- store.projects[projectId] = project;
67
- saveKeyStore(store, p);
91
+ withFileLock(p, () => {
92
+ const store = loadKeyStore(p);
93
+ store.projects[projectId] = project;
94
+ saveKeyStore(store, p);
95
+ });
68
96
  }
69
97
  export function updateProject(projectId, update, path) {
70
98
  const p = path ?? getKeystorePath();
71
- const store = loadKeyStore(p);
72
- const existing = store.projects[projectId];
73
- if (existing) {
74
- store.projects[projectId] = { ...existing, ...update };
75
- saveKeyStore(store, p);
76
- }
99
+ withFileLock(p, () => {
100
+ const store = loadKeyStore(p);
101
+ const existing = store.projects[projectId];
102
+ if (existing) {
103
+ store.projects[projectId] = { ...existing, ...update };
104
+ saveKeyStore(store, p);
105
+ }
106
+ });
77
107
  }
78
108
  export function removeProject(projectId, path) {
79
109
  const p = path ?? getKeystorePath();
80
- const store = loadKeyStore(p);
81
- delete store.projects[projectId];
82
- if (store.active_project_id === projectId) {
83
- delete store.active_project_id;
84
- }
85
- saveKeyStore(store, p);
110
+ withFileLock(p, () => {
111
+ const store = loadKeyStore(p);
112
+ delete store.projects[projectId];
113
+ if (store.active_project_id === projectId) {
114
+ const fallback = store.previous_active_project_id;
115
+ if (fallback && fallback !== projectId && store.projects[fallback]) {
116
+ store.active_project_id = fallback;
117
+ }
118
+ else {
119
+ delete store.active_project_id;
120
+ }
121
+ delete store.previous_active_project_id;
122
+ }
123
+ saveKeyStore(store, p);
124
+ });
86
125
  }
87
126
  export function getActiveProjectId(path) {
88
127
  const store = loadKeyStore(path);
@@ -90,8 +129,13 @@ export function getActiveProjectId(path) {
90
129
  }
91
130
  export function setActiveProjectId(projectId, path) {
92
131
  const p = path ?? getKeystorePath();
93
- const store = loadKeyStore(p);
94
- store.active_project_id = projectId;
95
- saveKeyStore(store, p);
132
+ withFileLock(p, () => {
133
+ const store = loadKeyStore(p);
134
+ if (store.active_project_id && store.active_project_id !== projectId) {
135
+ store.previous_active_project_id = store.active_project_id;
136
+ }
137
+ store.active_project_id = projectId;
138
+ saveKeyStore(store, p);
139
+ });
96
140
  }
97
141
  //# sourceMappingURL=keystore.js.map
package/dist/errors.d.ts CHANGED
@@ -2,8 +2,34 @@
2
2
  * Error hierarchy for the Run402 SDK. Every failure throws a subclass of
3
3
  * {@link Run402Error}. Consumers (MCP handlers, CLI commands, user functions)
4
4
  * translate these into their native error shapes at the edge.
5
+ *
6
+ * Branch on {@link Run402Error.kind} (or the exported `is*` type guards) rather
7
+ * than `instanceof`. Discriminator-based checks survive duplicate SDK installs,
8
+ * bundler chunk splits, ESM/CJS interop, and V8-isolate realm boundaries —
9
+ * any setting where a class object's identity might differ from the consumer's
10
+ * own class object reference. `instanceof X` continues to work for callers
11
+ * holding a single SDK copy (back-compat); the guards are the recommended path.
12
+ */
13
+ /**
14
+ * Stable string discriminator on every {@link Run402Error} subclass. Use this
15
+ * (or the exported `is*` guards) to branch on errors safely across SDK copies
16
+ * and realms — value comparison, no class-identity dependency.
5
17
  */
18
+ export type Run402ErrorKind = "payment_required" | "project_not_found" | "unauthorized" | "api_error" | "network_error" | "local_error" | "deploy_error";
6
19
  export declare abstract class Run402Error extends Error {
20
+ /**
21
+ * Structural brand. Always `true` on any {@link Run402Error} subclass
22
+ * instance, regardless of which SDK copy created it. The exported
23
+ * {@link isRun402Error} guard checks this field instead of `instanceof`,
24
+ * so cross-realm and cross-bundle errors still match.
25
+ */
26
+ readonly isRun402Error: true;
27
+ /**
28
+ * Stable string discriminator. Branch on `e.kind === "..."` (or the
29
+ * exported subclass guards) rather than `e instanceof X`. Equality on
30
+ * `kind` survives duplicate SDK copies and cross-realm errors.
31
+ */
32
+ abstract readonly kind: Run402ErrorKind;
7
33
  /** HTTP status, or null for local/network failures that produced no response. */
8
34
  readonly status: number | null;
9
35
  /** Parsed response body, or null when no body was received. */
@@ -27,28 +53,60 @@ export declare abstract class Run402Error extends Error {
27
53
  /** Advisory next actions from the gateway. Rendering them must not execute them. */
28
54
  readonly nextActions?: unknown[];
29
55
  constructor(message: string, status: number | null, body: unknown, context: string);
56
+ /**
57
+ * Canonical structured envelope for `JSON.stringify`. Without this, an
58
+ * `Error` instance serializes as `"{}"` (its built-in fields are
59
+ * non-enumerable), losing every structured detail an agent needs for
60
+ * triage. Subclasses with extra fields (e.g. {@link Run402DeployError})
61
+ * override and spread `super.toJSON()`.
62
+ */
63
+ toJSON(): Record<string, unknown>;
30
64
  }
31
65
  /** HTTP 402 — the gateway requires payment (lease expired, insufficient balance, or x402 quote). */
32
66
  export declare class PaymentRequired extends Run402Error {
67
+ static readonly DEFAULT_CODE = "PAYMENT_REQUIRED";
68
+ static readonly DEFAULT_CATEGORY = "payment_required";
69
+ static readonly DEFAULT_RETRYABLE = false;
70
+ readonly kind: "payment_required";
33
71
  }
34
72
  /** Project ID is not present in the credential provider (local miss) or the gateway returned 404. */
35
73
  export declare class ProjectNotFound extends Run402Error {
74
+ static readonly DEFAULT_CODE = "PROJECT_NOT_FOUND";
75
+ static readonly DEFAULT_CATEGORY = "not_found";
76
+ static readonly DEFAULT_RETRYABLE = false;
77
+ readonly kind: "project_not_found";
36
78
  readonly projectId: string;
37
79
  constructor(projectId: string, context: string, status?: number | null, body?: unknown);
38
80
  }
39
81
  /** HTTP 401 or 403 — authentication missing, invalid, or insufficient for the operation. */
40
82
  export declare class Unauthorized extends Run402Error {
83
+ static readonly DEFAULT_CODE = "UNAUTHORIZED";
84
+ static readonly DEFAULT_CATEGORY = "auth";
85
+ static readonly DEFAULT_RETRYABLE = false;
86
+ readonly kind: "unauthorized";
41
87
  }
42
88
  /** Any other non-2xx HTTP response from the gateway. */
43
89
  export declare class ApiError extends Run402Error {
90
+ static readonly DEFAULT_CODE = "API_ERROR";
91
+ static readonly DEFAULT_CATEGORY = "api";
92
+ static readonly DEFAULT_RETRYABLE = false;
93
+ readonly kind: "api_error";
44
94
  }
45
95
  /** The underlying `fetch` threw before producing a response (DNS, connection reset, offline). */
46
96
  export declare class NetworkError extends Run402Error {
97
+ static readonly DEFAULT_CODE = "NETWORK_ERROR";
98
+ static readonly DEFAULT_CATEGORY = "network";
99
+ static readonly DEFAULT_RETRYABLE = true;
100
+ readonly kind: "network_error";
47
101
  readonly cause: unknown;
48
102
  constructor(message: string, cause: unknown, context: string);
49
103
  }
50
104
  /** Local/filesystem error — input validation, missing path, unreadable dir. No HTTP involved. */
51
105
  export declare class LocalError extends Run402Error {
106
+ static readonly DEFAULT_CODE = "LOCAL_ERROR";
107
+ static readonly DEFAULT_CATEGORY = "local";
108
+ static readonly DEFAULT_RETRYABLE = false;
109
+ readonly kind: "local_error";
52
110
  readonly cause?: unknown;
53
111
  constructor(message: string, context: string, cause?: unknown);
54
112
  }
@@ -69,6 +127,7 @@ export interface Run402DeployErrorFix {
69
127
  [key: string]: unknown;
70
128
  }
71
129
  export declare class Run402DeployError extends Run402Error {
130
+ readonly kind: "deploy_error";
72
131
  readonly code: Run402DeployErrorCode;
73
132
  readonly phase: string | null;
74
133
  readonly resource: string | null;
@@ -92,5 +151,37 @@ export declare class Run402DeployError extends Run402Error {
92
151
  body?: unknown;
93
152
  context: string;
94
153
  });
154
+ toJSON(): Record<string, unknown>;
95
155
  }
156
+ /** True if `e` is any {@link Run402Error} subclass instance, regardless of which SDK copy created it. */
157
+ export declare function isRun402Error(e: unknown): e is Run402Error;
158
+ /** True if `e` is a {@link PaymentRequired}. Survives duplicate SDK copies and realms. */
159
+ export declare function isPaymentRequired(e: unknown): e is PaymentRequired;
160
+ /** True if `e` is a {@link ProjectNotFound}. */
161
+ export declare function isProjectNotFound(e: unknown): e is ProjectNotFound;
162
+ /** True if `e` is an {@link Unauthorized}. */
163
+ export declare function isUnauthorized(e: unknown): e is Unauthorized;
164
+ /** True if `e` is an {@link ApiError}. */
165
+ export declare function isApiError(e: unknown): e is ApiError;
166
+ /** True if `e` is a {@link NetworkError}. */
167
+ export declare function isNetworkError(e: unknown): e is NetworkError;
168
+ /** True if `e` is a {@link LocalError}. */
169
+ export declare function isLocalError(e: unknown): e is LocalError;
170
+ /** True if `e` is a {@link Run402DeployError}. */
171
+ export declare function isDeployError(e: unknown): e is Run402DeployError;
172
+ /**
173
+ * Canonical "should I retry this?" policy. Returns true when `e` is a
174
+ * {@link Run402Error} AND any of:
175
+ * - `e.retryable === true` (gateway flagged it)
176
+ * - `e.safeToRetry === true` (gateway flagged it)
177
+ * - `e.kind === "network_error"` (fetch never produced a response)
178
+ * - `e.status` is 408 (Request Timeout), 425 (Too Early), or 429 (Too Many
179
+ * Requests)
180
+ * - `e.status` is a 5xx server error
181
+ *
182
+ * Returns false for non-Run402 errors so it can be safely called with
183
+ * `unknown` from a catch block. Used as the default `retryIf` in
184
+ * {@link withRetry}.
185
+ */
186
+ export declare function isRetryableRun402Error(e: unknown): boolean;
96
187
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/errors.js CHANGED
@@ -2,8 +2,22 @@
2
2
  * Error hierarchy for the Run402 SDK. Every failure throws a subclass of
3
3
  * {@link Run402Error}. Consumers (MCP handlers, CLI commands, user functions)
4
4
  * translate these into their native error shapes at the edge.
5
+ *
6
+ * Branch on {@link Run402Error.kind} (or the exported `is*` type guards) rather
7
+ * than `instanceof`. Discriminator-based checks survive duplicate SDK installs,
8
+ * bundler chunk splits, ESM/CJS interop, and V8-isolate realm boundaries —
9
+ * any setting where a class object's identity might differ from the consumer's
10
+ * own class object reference. `instanceof X` continues to work for callers
11
+ * holding a single SDK copy (back-compat); the guards are the recommended path.
5
12
  */
6
13
  export class Run402Error extends Error {
14
+ /**
15
+ * Structural brand. Always `true` on any {@link Run402Error} subclass
16
+ * instance, regardless of which SDK copy created it. The exported
17
+ * {@link isRun402Error} guard checks this field instead of `instanceof`,
18
+ * so cross-realm and cross-bundle errors still match.
19
+ */
20
+ isRun402Error = true;
7
21
  /** HTTP status, or null for local/network failures that produced no response. */
8
22
  status;
9
23
  /** Parsed response body, or null when no body was received. */
@@ -33,12 +47,22 @@ export class Run402Error extends Error {
33
47
  this.body = body;
34
48
  this.context = context;
35
49
  const envelope = canonicalEnvelope(body);
36
- if (typeof envelope?.code === "string")
37
- this.code = envelope.code;
38
- if (typeof envelope?.category === "string")
39
- this.category = envelope.category;
40
- if (typeof envelope?.retryable === "boolean")
41
- this.retryable = envelope.retryable;
50
+ const ctor = this.constructor;
51
+ const envelopeCode = typeof envelope?.code === "string" ? envelope.code : undefined;
52
+ const envelopeCategory = typeof envelope?.category === "string" ? envelope.category : undefined;
53
+ const envelopeRetryable = typeof envelope?.retryable === "boolean" ? envelope.retryable : undefined;
54
+ if (envelopeCode !== undefined)
55
+ this.code = envelopeCode;
56
+ else if (ctor.DEFAULT_CODE !== undefined)
57
+ this.code = ctor.DEFAULT_CODE;
58
+ if (envelopeCategory !== undefined)
59
+ this.category = envelopeCategory;
60
+ else if (ctor.DEFAULT_CATEGORY !== undefined)
61
+ this.category = ctor.DEFAULT_CATEGORY;
62
+ if (envelopeRetryable !== undefined)
63
+ this.retryable = envelopeRetryable;
64
+ else if (ctor.DEFAULT_RETRYABLE !== undefined)
65
+ this.retryable = ctor.DEFAULT_RETRYABLE;
42
66
  if (typeof envelope?.safe_to_retry === "boolean")
43
67
  this.safeToRetry = envelope.safe_to_retry;
44
68
  if (typeof envelope?.mutation_state === "string")
@@ -51,6 +75,31 @@ export class Run402Error extends Error {
51
75
  if (Array.isArray(envelope?.next_actions))
52
76
  this.nextActions = envelope.next_actions;
53
77
  }
78
+ /**
79
+ * Canonical structured envelope for `JSON.stringify`. Without this, an
80
+ * `Error` instance serializes as `"{}"` (its built-in fields are
81
+ * non-enumerable), losing every structured detail an agent needs for
82
+ * triage. Subclasses with extra fields (e.g. {@link Run402DeployError})
83
+ * override and spread `super.toJSON()`.
84
+ */
85
+ toJSON() {
86
+ return {
87
+ name: this.name,
88
+ kind: this.kind,
89
+ message: this.message,
90
+ status: this.status,
91
+ code: this.code,
92
+ category: this.category,
93
+ retryable: this.retryable,
94
+ safeToRetry: this.safeToRetry,
95
+ mutationState: this.mutationState,
96
+ traceId: this.traceId,
97
+ context: this.context,
98
+ details: this.details,
99
+ nextActions: this.nextActions,
100
+ body: this.body,
101
+ };
102
+ }
54
103
  }
55
104
  function canonicalEnvelope(body) {
56
105
  return body && typeof body === "object" && !Array.isArray(body)
@@ -59,9 +108,17 @@ function canonicalEnvelope(body) {
59
108
  }
60
109
  /** HTTP 402 — the gateway requires payment (lease expired, insufficient balance, or x402 quote). */
61
110
  export class PaymentRequired extends Run402Error {
111
+ static DEFAULT_CODE = "PAYMENT_REQUIRED";
112
+ static DEFAULT_CATEGORY = "payment_required";
113
+ static DEFAULT_RETRYABLE = false;
114
+ kind = "payment_required";
62
115
  }
63
116
  /** Project ID is not present in the credential provider (local miss) or the gateway returned 404. */
64
117
  export class ProjectNotFound extends Run402Error {
118
+ static DEFAULT_CODE = "PROJECT_NOT_FOUND";
119
+ static DEFAULT_CATEGORY = "not_found";
120
+ static DEFAULT_RETRYABLE = false;
121
+ kind = "project_not_found";
65
122
  projectId;
66
123
  constructor(projectId, context, status = null, body = null) {
67
124
  super(`Project ${projectId} not found`, status, body, context);
@@ -70,12 +127,24 @@ export class ProjectNotFound extends Run402Error {
70
127
  }
71
128
  /** HTTP 401 or 403 — authentication missing, invalid, or insufficient for the operation. */
72
129
  export class Unauthorized extends Run402Error {
130
+ static DEFAULT_CODE = "UNAUTHORIZED";
131
+ static DEFAULT_CATEGORY = "auth";
132
+ static DEFAULT_RETRYABLE = false;
133
+ kind = "unauthorized";
73
134
  }
74
135
  /** Any other non-2xx HTTP response from the gateway. */
75
136
  export class ApiError extends Run402Error {
137
+ static DEFAULT_CODE = "API_ERROR";
138
+ static DEFAULT_CATEGORY = "api";
139
+ static DEFAULT_RETRYABLE = false;
140
+ kind = "api_error";
76
141
  }
77
142
  /** The underlying `fetch` threw before producing a response (DNS, connection reset, offline). */
78
143
  export class NetworkError extends Run402Error {
144
+ static DEFAULT_CODE = "NETWORK_ERROR";
145
+ static DEFAULT_CATEGORY = "network";
146
+ static DEFAULT_RETRYABLE = true;
147
+ kind = "network_error";
79
148
  cause;
80
149
  constructor(message, cause, context) {
81
150
  super(message, null, null, context);
@@ -84,6 +153,10 @@ export class NetworkError extends Run402Error {
84
153
  }
85
154
  /** Local/filesystem error — input validation, missing path, unreadable dir. No HTTP involved. */
86
155
  export class LocalError extends Run402Error {
156
+ static DEFAULT_CODE = "LOCAL_ERROR";
157
+ static DEFAULT_CATEGORY = "local";
158
+ static DEFAULT_RETRYABLE = false;
159
+ kind = "local_error";
87
160
  cause;
88
161
  constructor(message, context, cause) {
89
162
  super(message, null, null, context);
@@ -92,6 +165,7 @@ export class LocalError extends Run402Error {
92
165
  }
93
166
  }
94
167
  export class Run402DeployError extends Run402Error {
168
+ kind = "deploy_error";
95
169
  code;
96
170
  phase;
97
171
  resource;
@@ -113,5 +187,87 @@ export class Run402DeployError extends Run402Error {
113
187
  this.logs = init.logs ?? null;
114
188
  this.rolledBack = init.rolledBack ?? false;
115
189
  }
190
+ toJSON() {
191
+ return {
192
+ ...super.toJSON(),
193
+ phase: this.phase,
194
+ resource: this.resource,
195
+ operationId: this.operationId,
196
+ planId: this.planId,
197
+ fix: this.fix,
198
+ logs: this.logs,
199
+ rolledBack: this.rolledBack,
200
+ retryable: this.retryable,
201
+ };
202
+ }
203
+ }
204
+ // ─── Type guards ─────────────────────────────────────────────────────────────
205
+ //
206
+ // Identity-free guards. Each one checks the structural brand and (for subclass
207
+ // guards) the `kind` discriminator. Use these instead of `instanceof X` so the
208
+ // check survives duplicate SDK installs, bundler chunk splits, ESM/CJS interop,
209
+ // and V8-isolate realm boundaries — anywhere the consumer's class object
210
+ // reference might differ from the throw site's.
211
+ /** True if `e` is any {@link Run402Error} subclass instance, regardless of which SDK copy created it. */
212
+ export function isRun402Error(e) {
213
+ return Boolean(e &&
214
+ typeof e === "object" &&
215
+ e.isRun402Error === true);
216
+ }
217
+ /** True if `e` is a {@link PaymentRequired}. Survives duplicate SDK copies and realms. */
218
+ export function isPaymentRequired(e) {
219
+ return isRun402Error(e) && e.kind === "payment_required";
220
+ }
221
+ /** True if `e` is a {@link ProjectNotFound}. */
222
+ export function isProjectNotFound(e) {
223
+ return isRun402Error(e) && e.kind === "project_not_found";
224
+ }
225
+ /** True if `e` is an {@link Unauthorized}. */
226
+ export function isUnauthorized(e) {
227
+ return isRun402Error(e) && e.kind === "unauthorized";
228
+ }
229
+ /** True if `e` is an {@link ApiError}. */
230
+ export function isApiError(e) {
231
+ return isRun402Error(e) && e.kind === "api_error";
232
+ }
233
+ /** True if `e` is a {@link NetworkError}. */
234
+ export function isNetworkError(e) {
235
+ return isRun402Error(e) && e.kind === "network_error";
236
+ }
237
+ /** True if `e` is a {@link LocalError}. */
238
+ export function isLocalError(e) {
239
+ return isRun402Error(e) && e.kind === "local_error";
240
+ }
241
+ /** True if `e` is a {@link Run402DeployError}. */
242
+ export function isDeployError(e) {
243
+ return isRun402Error(e) && e.kind === "deploy_error";
244
+ }
245
+ /**
246
+ * Canonical "should I retry this?" policy. Returns true when `e` is a
247
+ * {@link Run402Error} AND any of:
248
+ * - `e.retryable === true` (gateway flagged it)
249
+ * - `e.safeToRetry === true` (gateway flagged it)
250
+ * - `e.kind === "network_error"` (fetch never produced a response)
251
+ * - `e.status` is 408 (Request Timeout), 425 (Too Early), or 429 (Too Many
252
+ * Requests)
253
+ * - `e.status` is a 5xx server error
254
+ *
255
+ * Returns false for non-Run402 errors so it can be safely called with
256
+ * `unknown` from a catch block. Used as the default `retryIf` in
257
+ * {@link withRetry}.
258
+ */
259
+ export function isRetryableRun402Error(e) {
260
+ if (!isRun402Error(e))
261
+ return false;
262
+ if (e.retryable === true || e.safeToRetry === true)
263
+ return true;
264
+ if (e.kind === "network_error")
265
+ return true;
266
+ const s = e.status;
267
+ if (s === 408 || s === 425 || s === 429)
268
+ return true;
269
+ if (typeof s === "number" && s >= 500)
270
+ return true;
271
+ return false;
116
272
  }
117
273
  //# sourceMappingURL=errors.js.map