@lifi/composer-sdk 0.4.1 → 0.5.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.
Files changed (108) hide show
  1. package/README.md +6 -3
  2. package/dist/authoring/FlowBuilderCore.cjs +1 -5
  3. package/dist/authoring/FlowBuilderCore.cjs.map +1 -1
  4. package/dist/authoring/FlowBuilderCore.d.cts +9 -7
  5. package/dist/authoring/FlowBuilderCore.d.ts +9 -7
  6. package/dist/authoring/FlowBuilderCore.js +2 -6
  7. package/dist/authoring/FlowBuilderCore.js.map +1 -1
  8. package/dist/authoring/handles.cjs +5 -2
  9. package/dist/authoring/handles.cjs.map +1 -1
  10. package/dist/authoring/handles.d.cts +25 -6
  11. package/dist/authoring/handles.d.ts +25 -6
  12. package/dist/authoring/handles.js +3 -1
  13. package/dist/authoring/handles.js.map +1 -1
  14. package/dist/authoring/raw.cjs +3 -0
  15. package/dist/authoring/raw.cjs.map +1 -1
  16. package/dist/authoring/raw.d.cts +33 -3
  17. package/dist/authoring/raw.d.ts +33 -3
  18. package/dist/authoring/raw.js +2 -0
  19. package/dist/authoring/raw.js.map +1 -1
  20. package/dist/authoring/signatureArgs.cjs.map +1 -1
  21. package/dist/authoring/signatureArgs.d.cts +16 -12
  22. package/dist/authoring/signatureArgs.d.ts +16 -12
  23. package/dist/authoring/signatureArgs.js.map +1 -1
  24. package/dist/client.cjs +112 -55
  25. package/dist/client.cjs.map +1 -1
  26. package/dist/client.d.cts +42 -5
  27. package/dist/client.d.ts +42 -5
  28. package/dist/client.js +116 -55
  29. package/dist/client.js.map +1 -1
  30. package/dist/discovery.cjs.map +1 -1
  31. package/dist/discovery.d.cts +18 -2
  32. package/dist/discovery.d.ts +18 -2
  33. package/dist/errors.cjs +42 -3
  34. package/dist/errors.cjs.map +1 -1
  35. package/dist/errors.d.cts +67 -4
  36. package/dist/errors.d.ts +67 -4
  37. package/dist/errors.js +29 -2
  38. package/dist/errors.js.map +1 -1
  39. package/dist/generated/config.generated.cjs.map +1 -1
  40. package/dist/generated/config.generated.d.cts +21 -1
  41. package/dist/generated/config.generated.d.ts +21 -1
  42. package/dist/generated/operations.generated.cjs +6 -0
  43. package/dist/generated/operations.generated.cjs.map +1 -1
  44. package/dist/generated/operations.generated.d.cts +18 -2
  45. package/dist/generated/operations.generated.d.ts +18 -2
  46. package/dist/generated/operations.generated.js +6 -0
  47. package/dist/generated/operations.generated.js.map +1 -1
  48. package/dist/index.cjs +4 -0
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.d.cts +7 -7
  51. package/dist/index.d.ts +7 -7
  52. package/dist/index.js +8 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/{raw-IxCF_bSS.d.ts → raw-BvRghN3r.d.ts} +3 -2
  55. package/dist/{raw-Dj_2rBSQ.d.cts → raw-s1rer8X3.d.cts} +3 -2
  56. package/dist/raw.cjs +2 -0
  57. package/dist/raw.cjs.map +1 -1
  58. package/dist/raw.d.cts +1 -1
  59. package/dist/raw.d.ts +1 -1
  60. package/dist/raw.js +2 -1
  61. package/dist/raw.js.map +1 -1
  62. package/dist/responseSchemas.cjs +3 -0
  63. package/dist/responseSchemas.cjs.map +1 -1
  64. package/dist/responseSchemas.d.cts +3 -0
  65. package/dist/responseSchemas.d.ts +3 -0
  66. package/dist/responseSchemas.js +3 -0
  67. package/dist/responseSchemas.js.map +1 -1
  68. package/dist/run/inputs.cjs +14 -15
  69. package/dist/run/inputs.cjs.map +1 -1
  70. package/dist/run/inputs.d.cts +8 -0
  71. package/dist/run/inputs.d.ts +8 -0
  72. package/dist/run/inputs.js +14 -15
  73. package/dist/run/inputs.js.map +1 -1
  74. package/dist/run/simulate.cjs +5 -0
  75. package/dist/run/simulate.cjs.map +1 -1
  76. package/dist/run/simulate.d.cts +5 -2
  77. package/dist/run/simulate.d.ts +5 -2
  78. package/dist/run/simulate.js +5 -0
  79. package/dist/run/simulate.js.map +1 -1
  80. package/dist/sdk.cjs +2 -0
  81. package/dist/sdk.cjs.map +1 -1
  82. package/dist/sdk.d.cts +28 -1
  83. package/dist/sdk.d.ts +28 -1
  84. package/dist/sdk.js +2 -0
  85. package/dist/sdk.js.map +1 -1
  86. package/dist/types.cjs.map +1 -1
  87. package/dist/types.d.cts +18 -2
  88. package/dist/types.d.ts +18 -2
  89. package/package.json +3 -10
  90. package/src/authoring/FlowBuilderCore.ts +13 -15
  91. package/src/authoring/handles.ts +41 -5
  92. package/src/authoring/raw.ts +36 -2
  93. package/src/authoring/signatureArgs.ts +17 -13
  94. package/src/client.ts +222 -62
  95. package/src/discovery.ts +18 -1
  96. package/src/errors.ts +116 -3
  97. package/src/examples/flySwap.ts +167 -0
  98. package/src/examples/run.ts +35 -13
  99. package/src/examples/supportedChains.ts +122 -0
  100. package/src/generated/config.generated.ts +24 -0
  101. package/src/generated/operations.generated.ts +26 -0
  102. package/src/index.ts +18 -2
  103. package/src/raw.ts +1 -1
  104. package/src/responseSchemas.ts +6 -0
  105. package/src/run/inputs.ts +30 -11
  106. package/src/run/simulate.ts +14 -2
  107. package/src/sdk.ts +30 -0
  108. package/src/types.ts +20 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/discovery.ts"],"sourcesContent":["/**\n * Types for the Compose discovery API (`GET /compose/zap-packs`).\n *\n * These are SDK-facing presentation types — not part of the core compile\n * contract in compose-spec. The backend response is the source of truth;\n * the SDK provides typed access.\n *\n * The `type` field on edges is a plain `string` rather than a strict union\n * so the SDK remains forward-compatible when the backend introduces new\n * edge types. Known values at time of writing:\n * `\"enter-position\"`, `\"exit-position\"`, `\"wrap\"`, `\"unwrap\"`, `\"mint\"`, `\"burn\"`.\n */\n\n/**\n * A single routing edge representing a supported token-to-token path\n * through a DeFi protocol.\n */\nexport interface ZapPackEdge {\n /** Edge type (e.g. `\"enter-position\"`, `\"exit-position\"`, `\"wrap\"`). */\n readonly type: string;\n /** Input token. */\n readonly in: { readonly address: string; readonly chainId: number };\n /** Output token. */\n readonly out: { readonly address: string; readonly chainId: number };\n /** Minimum input amount accepted (stringified integer), if constrained. */\n readonly inAmountMin?: string;\n /** Maximum input amount accepted (stringified integer), if constrained. */\n readonly inAmountMax?: string;\n /**\n * Recipient binding obligation. `\"required\"` means the zap must bind its\n * `recipient` input. The output is externally owned, terminal, and unswept.\n * The loose string type preserves forward compatibility with new values.\n */\n readonly recipient?: string;\n /**\n * Output availability. `\"future\"` means the output becomes available only\n * after the initiating transaction settles; route via `lifi.zapAsync` with\n * a bound recipient. Absent means the output is available synchronously.\n * The loose string type preserves forward compatibility with new values.\n */\n readonly availability?: string;\n}\n\n/**\n * All routing edges for a single protocol.\n */\nexport interface ZapPackOverview {\n /** Protocol identifier (e.g. `\"aave\"`, `\"morpho\"`). */\n readonly protocol: string;\n /** Available routing edges for this protocol. */\n readonly edges: readonly ZapPackEdge[];\n}\n\n/**\n * Options for {@link ComposeClient.getZapPacks}.\n */\nexport interface GetZapPacksOptions {\n /**\n * Filter to specific protocols. Pass a single string or an array.\n * Only edges belonging to the listed protocols are returned.\n * Omit to fetch all available protocols.\n */\n readonly protocols?: string | readonly string[];\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../src/discovery.ts"],"sourcesContent":["/**\n * Types for the Compose discovery API (`GET /compose/zap-packs`, `GET /chains`).\n *\n * These are SDK-facing presentation types — not part of the core compile\n * contract in compose-spec. The backend response is the source of truth;\n * the SDK provides typed access.\n *\n * The `type` field on edges is a plain `string` rather than a strict union\n * so the SDK remains forward-compatible when the backend introduces new\n * edge types. Known values at time of writing:\n * `\"enter-position\"`, `\"exit-position\"`, `\"wrap\"`, `\"unwrap\"`, `\"mint\"`, `\"burn\"`.\n */\n\n/**\n * A chain the Compose deployment supports.\n */\nexport interface ChainSummary {\n /** EVM chain ID. The stable identifier — key on this. */\n readonly chainId: number;\n /**\n * Human-readable label, suitable for display in a chain selector.\n *\n * Maintained by the deployment rather than derived from canonical chain\n * branding, so it can differ from a chain's marketed name and can change\n * without notice. Never treat it as an identifier, and never match on it.\n * A chain the deployment has no configured label for reports `Chain <id>`.\n */\n readonly name: string;\n}\n\n/**\n * A single routing edge representing a supported token-to-token path\n * through a DeFi protocol.\n */\nexport interface ZapPackEdge {\n /** Edge type (e.g. `\"enter-position\"`, `\"exit-position\"`, `\"wrap\"`). */\n readonly type: string;\n /** Input token. */\n readonly in: { readonly address: string; readonly chainId: number };\n /** Output token. */\n readonly out: { readonly address: string; readonly chainId: number };\n /** Minimum input amount accepted (stringified integer), if constrained. */\n readonly inAmountMin?: string;\n /** Maximum input amount accepted (stringified integer), if constrained. */\n readonly inAmountMax?: string;\n /**\n * Recipient binding obligation. `\"required\"` means the zap must bind its\n * `recipient` input. The output is externally owned, terminal, and unswept.\n * The loose string type preserves forward compatibility with new values.\n */\n readonly recipient?: string;\n /**\n * Output availability. `\"future\"` means the output becomes available only\n * after the initiating transaction settles; route via `lifi.zapAsync` with\n * a bound recipient. Absent means the output is available synchronously.\n * The loose string type preserves forward compatibility with new values.\n */\n readonly availability?: string;\n}\n\n/**\n * All routing edges for a single protocol.\n */\nexport interface ZapPackOverview {\n /** Protocol identifier (e.g. `\"aave\"`, `\"morpho\"`). */\n readonly protocol: string;\n /** Available routing edges for this protocol. */\n readonly edges: readonly ZapPackEdge[];\n}\n\n/**\n * Options for {@link ComposeClient.getZapPacks}.\n */\nexport interface GetZapPacksOptions {\n /**\n * Filter to specific protocols. Pass a single string or an array.\n * Only edges belonging to the listed protocols are returned.\n * Omit to fetch all available protocols.\n */\n readonly protocols?: string | readonly string[];\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Types for the Compose discovery API (`GET /compose/zap-packs`).
2
+ * Types for the Compose discovery API (`GET /compose/zap-packs`, `GET /chains`).
3
3
  *
4
4
  * These are SDK-facing presentation types — not part of the core compile
5
5
  * contract in compose-spec. The backend response is the source of truth;
@@ -10,6 +10,22 @@
10
10
  * edge types. Known values at time of writing:
11
11
  * `"enter-position"`, `"exit-position"`, `"wrap"`, `"unwrap"`, `"mint"`, `"burn"`.
12
12
  */
13
+ /**
14
+ * A chain the Compose deployment supports.
15
+ */
16
+ interface ChainSummary {
17
+ /** EVM chain ID. The stable identifier — key on this. */
18
+ readonly chainId: number;
19
+ /**
20
+ * Human-readable label, suitable for display in a chain selector.
21
+ *
22
+ * Maintained by the deployment rather than derived from canonical chain
23
+ * branding, so it can differ from a chain's marketed name and can change
24
+ * without notice. Never treat it as an identifier, and never match on it.
25
+ * A chain the deployment has no configured label for reports `Chain <id>`.
26
+ */
27
+ readonly name: string;
28
+ }
13
29
  /**
14
30
  * A single routing edge representing a supported token-to-token path
15
31
  * through a DeFi protocol.
@@ -66,4 +82,4 @@ interface GetZapPacksOptions {
66
82
  readonly protocols?: string | readonly string[];
67
83
  }
68
84
 
69
- export type { GetZapPacksOptions, ZapPackEdge, ZapPackOverview };
85
+ export type { ChainSummary, GetZapPacksOptions, ZapPackEdge, ZapPackOverview };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Types for the Compose discovery API (`GET /compose/zap-packs`).
2
+ * Types for the Compose discovery API (`GET /compose/zap-packs`, `GET /chains`).
3
3
  *
4
4
  * These are SDK-facing presentation types — not part of the core compile
5
5
  * contract in compose-spec. The backend response is the source of truth;
@@ -10,6 +10,22 @@
10
10
  * edge types. Known values at time of writing:
11
11
  * `"enter-position"`, `"exit-position"`, `"wrap"`, `"unwrap"`, `"mint"`, `"burn"`.
12
12
  */
13
+ /**
14
+ * A chain the Compose deployment supports.
15
+ */
16
+ interface ChainSummary {
17
+ /** EVM chain ID. The stable identifier — key on this. */
18
+ readonly chainId: number;
19
+ /**
20
+ * Human-readable label, suitable for display in a chain selector.
21
+ *
22
+ * Maintained by the deployment rather than derived from canonical chain
23
+ * branding, so it can differ from a chain's marketed name and can change
24
+ * without notice. Never treat it as an identifier, and never match on it.
25
+ * A chain the deployment has no configured label for reports `Chain <id>`.
26
+ */
27
+ readonly name: string;
28
+ }
13
29
  /**
14
30
  * A single routing edge representing a supported token-to-token path
15
31
  * through a DeFi protocol.
@@ -66,4 +82,4 @@ interface GetZapPacksOptions {
66
82
  readonly protocols?: string | readonly string[];
67
83
  }
68
84
 
69
- export type { GetZapPacksOptions, ZapPackEdge, ZapPackOverview };
85
+ export type { ChainSummary, GetZapPacksOptions, ZapPackEdge, ZapPackOverview };
package/dist/errors.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,15 +17,26 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var errors_exports = {};
20
30
  __export(errors_exports, {
21
31
  ComposeError: () => ComposeError,
22
32
  errorFromHttpResponse: () => errorFromHttpResponse,
23
33
  isComposeError: () => isComposeError,
24
- isComposePreparationError: () => isComposePreparationError
34
+ isComposePreparationError: () => isComposePreparationError,
35
+ isComposeSdkOutdatedError: () => isComposeSdkOutdatedError,
36
+ isComposeServerOutdatedError: () => isComposeServerOutdatedError
25
37
  });
26
38
  module.exports = __toCommonJS(errors_exports);
39
+ var import_zod = __toESM(require("zod"), 1);
27
40
  var import_responseSchemas = require("./responseSchemas.js");
28
41
  class ComposeError extends Error {
29
42
  name = "ComposeError";
@@ -54,6 +67,10 @@ class ComposeError extends Error {
54
67
  * `preparation_error` errors alongside {@link ComposeError.failedOps}.
55
68
  */
56
69
  succeededOps;
70
+ /** Version details attached only to `sdk_outdated` errors. */
71
+ sdkOutdated;
72
+ /** Version details attached only to `server_outdated` errors. */
73
+ serverOutdated;
57
74
  constructor(code, message, options) {
58
75
  super(message, { cause: options?.cause });
59
76
  this.code = code;
@@ -64,10 +81,23 @@ class ComposeError extends Error {
64
81
  this.details = options?.details;
65
82
  this.failedOps = options?.failedOps;
66
83
  this.succeededOps = options?.succeededOps;
84
+ this.sdkOutdated = options?.sdkOutdated;
85
+ this.serverOutdated = options?.serverOutdated;
67
86
  }
68
87
  }
69
88
  const isComposeError = (e) => e instanceof ComposeError || e instanceof Error && e.name === "ComposeError" && "code" in e;
70
89
  const isComposePreparationError = (e) => isComposeError(e) && e.kind === "preparation_error" && Array.isArray(e.failedOps) && Array.isArray(e.succeededOps);
90
+ const sdkOutdatedSchema = import_zod.default.object({
91
+ sdkVersion: import_zod.default.string(),
92
+ serverVersion: import_zod.default.string(),
93
+ minimumSdkVersion: import_zod.default.string()
94
+ });
95
+ const isComposeSdkOutdatedError = (e) => isComposeError(e) && e.kind === "sdk_outdated" && sdkOutdatedSchema.safeParse(e.sdkOutdated).success;
96
+ const serverOutdatedSchema = import_zod.default.object({
97
+ sdkVersion: import_zod.default.string(),
98
+ serverVersion: import_zod.default.string()
99
+ });
100
+ const isComposeServerOutdatedError = (e) => isComposeError(e) && e.kind === "server_outdated" && serverOutdatedSchema.safeParse(e.serverOutdated).success;
71
101
  const STATUS_TO_CODE = /* @__PURE__ */ new Map([
72
102
  [400, "VALIDATION_ERROR"],
73
103
  [401, "UNAUTHENTICATED"],
@@ -85,9 +115,14 @@ const tryParseErrorBody = (body) => {
85
115
  }
86
116
  return (0, import_responseSchemas.parseServerErrorBody)(json);
87
117
  };
118
+ const readVersionSlots = (serverError) => ({
119
+ sdkOutdated: serverError?.kind === "sdk_outdated" ? sdkOutdatedSchema.safeParse(serverError).data : void 0,
120
+ serverOutdated: serverError?.kind === "server_outdated" ? serverOutdatedSchema.safeParse(serverError).data : void 0
121
+ });
88
122
  const errorFromHttpResponse = (status, body, url) => {
89
123
  const parsed = tryParseErrorBody(body);
90
124
  const serverError = parsed?.error;
125
+ const { sdkOutdated, serverOutdated } = readVersionSlots(serverError);
91
126
  return new ComposeError(
92
127
  STATUS_TO_CODE.get(status) ?? (status >= 500 ? "SERVER_ERROR" : "UNKNOWN_ERROR"),
93
128
  (serverError?.message ?? body) || `HTTP ${status}`,
@@ -103,7 +138,9 @@ const errorFromHttpResponse = (status, body, url) => {
103
138
  ...op,
104
139
  kind: op.kind
105
140
  })),
106
- succeededOps: serverError?.succeededOps
141
+ succeededOps: serverError?.succeededOps,
142
+ sdkOutdated,
143
+ serverOutdated
107
144
  }
108
145
  );
109
146
  };
@@ -112,6 +149,8 @@ const errorFromHttpResponse = (status, body, url) => {
112
149
  ComposeError,
113
150
  errorFromHttpResponse,
114
151
  isComposeError,
115
- isComposePreparationError
152
+ isComposePreparationError,
153
+ isComposeSdkOutdatedError,
154
+ isComposeServerOutdatedError
116
155
  });
117
156
  //# sourceMappingURL=errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type {\n ComposeErrorKind,\n FailedPreparedOp,\n GenericComposeErrorKind,\n SimulationRevert,\n} from '@lifi/compose-spec';\n\nimport {\n parseServerErrorBody,\n type ServerErrorBody,\n} from './responseSchemas.js';\n\n/**\n * Machine-readable error codes returned by the SDK.\n *\n * - `NETWORK_ERROR` — The HTTP request failed (DNS, timeout, connection refused).\n * - `VALIDATION_ERROR` — The server rejected the request (HTTP 400/422).\n * - `UNAUTHENTICATED` — The request lacks valid authentication credentials (HTTP 401).\n * - `FORBIDDEN` — The server understood the request but refuses to authorise it (HTTP 403).\n * - `SERVER_ERROR` — The server returned a 5xx status.\n * - `RATE_LIMITED` — The server returned HTTP 429.\n * - `NOT_FOUND` — The requested resource does not exist (HTTP 404).\n * - `UNKNOWN_ERROR` — An unexpected error that doesn't fit other categories.\n */\nexport type ComposeErrorCode =\n | 'NETWORK_ERROR'\n | 'VALIDATION_ERROR'\n | 'UNAUTHENTICATED'\n | 'FORBIDDEN'\n | 'SERVER_ERROR'\n | 'RATE_LIMITED'\n | 'NOT_FOUND'\n | 'UNKNOWN_ERROR';\n\n/**\n * Error class for all failures originating from the Compose SDK or API.\n *\n * Includes structured metadata beyond the error message to support\n * programmatic error handling.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposeError(e) && e.code === 'VALIDATION_ERROR') {\n * console.error('Invalid request:', e.message, e.path);\n * }\n * }\n * ```\n */\nexport class ComposeError extends Error {\n override readonly name = 'ComposeError';\n /** Machine-readable error category. */\n readonly code: ComposeErrorCode;\n /** HTTP status code, when the error originated from an HTTP response. */\n readonly status?: number;\n /** The request URL that produced the error. */\n readonly url?: string;\n /** Server-provided error kind for finer-grained classification. */\n readonly kind?: ComposeErrorKind;\n /** JSON-pointer path to the field that caused a validation error. */\n readonly path?: string;\n /**\n * Simulation revert diagnostics attached to `simulation_revert` errors.\n * Contains the raw error bytes and decoded error candidates when the\n * backend can parse the revert reason.\n */\n readonly details?: SimulationRevert;\n /**\n * The prepared ops that failed, attached to `preparation_error` errors.\n * Each entry carries the `callId` of the failing node so callers can drop\n * the unroutable legs and resubmit a smaller flow.\n */\n readonly failedOps?: readonly FailedPreparedOp[];\n /**\n * The `callId`s of the prepared ops that succeeded, attached to\n * `preparation_error` errors alongside {@link ComposeError.failedOps}.\n */\n readonly succeededOps?: readonly string[];\n\n constructor(\n code: ComposeErrorCode,\n message: string,\n options?: {\n status?: number;\n url?: string;\n cause?: unknown;\n kind?: ComposeErrorKind;\n path?: string;\n details?: SimulationRevert;\n failedOps?: readonly FailedPreparedOp[];\n succeededOps?: readonly string[];\n },\n ) {\n super(message, { cause: options?.cause });\n this.code = code;\n this.status = options?.status;\n this.url = options?.url;\n this.kind = options?.kind;\n this.path = options?.path;\n this.details = options?.details;\n this.failedOps = options?.failedOps;\n this.succeededOps = options?.succeededOps;\n }\n}\n\n/**\n * The per-op preparation diagnostics carried by `preparation_error` responses,\n * with both arrays known to be present. Produced by narrowing with\n * {@link isComposePreparationError}.\n */\nexport interface ComposePreparationOps {\n readonly failedOps: readonly FailedPreparedOp[];\n readonly succeededOps: readonly string[];\n}\n\n/**\n * Type guard that narrows an unknown error to {@link ComposeError}.\n * @param e - The value to check.\n * @returns `true` if `e` is an instance of `ComposeError`.\n */\nexport const isComposeError = (e: unknown): e is ComposeError =>\n e instanceof ComposeError ||\n (e instanceof Error && e.name === 'ComposeError' && 'code' in e);\n\n/**\n * Type guard for `preparation_error` failures (HTTP 422): a mixed basket where\n * some prepared ops failed and others succeeded. Narrows both\n * {@link ComposeError.failedOps} and {@link ComposeError.succeededOps} to\n * present, so callers can read them without a cast or a truthiness check.\n *\n * @param e - The value to check.\n * @returns `true` if `e` is a `ComposeError` carrying per-op preparation diagnostics.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposePreparationError(e)) {\n * console.error('Failed ops:', e.failedOps);\n * console.error('Succeeded ops:', e.succeededOps);\n * }\n * }\n * ```\n */\nexport const isComposePreparationError = (\n e: unknown,\n): e is ComposeError & ComposePreparationOps =>\n isComposeError(e) &&\n e.kind === 'preparation_error' &&\n Array.isArray(e.failedOps) &&\n Array.isArray(e.succeededOps);\n\nconst STATUS_TO_CODE: ReadonlyMap<number, ComposeErrorCode> = new Map<\n number,\n ComposeErrorCode\n>([\n [400, 'VALIDATION_ERROR'],\n [401, 'UNAUTHENTICATED'],\n [403, 'FORBIDDEN'],\n [404, 'NOT_FOUND'],\n [422, 'VALIDATION_ERROR'],\n [429, 'RATE_LIMITED'],\n]);\n\nconst tryParseErrorBody = (body: string): ServerErrorBody | null => {\n let json: unknown;\n try {\n json = JSON.parse(body);\n } catch {\n return null;\n }\n return parseServerErrorBody(json);\n};\n\n/**\n * Constructs a {@link ComposeError} from an HTTP error response, extracting\n * structured error details from the response body when available.\n *\n * @param status - The HTTP status code.\n * @param body - The raw response body text.\n * @param url - The request URL that produced the error.\n * @returns A `ComposeError` with the appropriate error code and metadata.\n */\nexport const errorFromHttpResponse = (\n status: number,\n body: string,\n url: string,\n): ComposeError => {\n const parsed = tryParseErrorBody(body);\n const serverError = parsed?.error;\n\n return new ComposeError(\n STATUS_TO_CODE.get(status) ??\n (status >= 500 ? 'SERVER_ERROR' : 'UNKNOWN_ERROR'),\n (serverError?.message ?? body) || `HTTP ${status}`,\n {\n status,\n url,\n kind: serverError?.kind as ComposeErrorKind | undefined,\n path: serverError?.path,\n details: serverError?.details,\n // The wire schema types `kind` as a bare string; the server only ever\n // reports generic kinds for individual ops.\n failedOps: serverError?.failedOps?.map((op) => ({\n ...op,\n kind: op.kind as GenericComposeErrorKind,\n })),\n succeededOps: serverError?.succeededOps,\n },\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,6BAGO;AAyCA,MAAM,qBAAqB,MAAM;AAAA,EACpB,OAAO;AAAA;AAAA,EAEhB;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,EAET,YACE,MACA,SACA,SAUA;AACA,UAAM,SAAS,EAAE,OAAO,SAAS,MAAM,CAAC;AACxC,SAAK,OAAO;AACZ,SAAK,SAAS,SAAS;AACvB,SAAK,MAAM,SAAS;AACpB,SAAK,OAAO,SAAS;AACrB,SAAK,OAAO,SAAS;AACrB,SAAK,UAAU,SAAS;AACxB,SAAK,YAAY,SAAS;AAC1B,SAAK,eAAe,SAAS;AAAA,EAC/B;AACF;AAiBO,MAAM,iBAAiB,CAAC,MAC7B,aAAa,gBACZ,aAAa,SAAS,EAAE,SAAS,kBAAkB,UAAU;AAuBzD,MAAM,4BAA4B,CACvC,MAEA,eAAe,CAAC,KAChB,EAAE,SAAS,uBACX,MAAM,QAAQ,EAAE,SAAS,KACzB,MAAM,QAAQ,EAAE,YAAY;AAE9B,MAAM,iBAAwD,oBAAI,IAGhE;AAAA,EACA,CAAC,KAAK,kBAAkB;AAAA,EACxB,CAAC,KAAK,iBAAiB;AAAA,EACvB,CAAC,KAAK,WAAW;AAAA,EACjB,CAAC,KAAK,WAAW;AAAA,EACjB,CAAC,KAAK,kBAAkB;AAAA,EACxB,CAAC,KAAK,cAAc;AACtB,CAAC;AAED,MAAM,oBAAoB,CAAC,SAAyC;AAClE,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAO,6CAAqB,IAAI;AAClC;AAWO,MAAM,wBAAwB,CACnC,QACA,MACA,QACiB;AACjB,QAAM,SAAS,kBAAkB,IAAI;AACrC,QAAM,cAAc,QAAQ;AAE5B,SAAO,IAAI;AAAA,IACT,eAAe,IAAI,MAAM,MACtB,UAAU,MAAM,iBAAiB;AAAA,KACnC,aAAa,WAAW,SAAS,QAAQ,MAAM;AAAA,IAChD;AAAA,MACE;AAAA,MACA;AAAA,MACA,MAAM,aAAa;AAAA,MACnB,MAAM,aAAa;AAAA,MACnB,SAAS,aAAa;AAAA;AAAA;AAAA,MAGtB,WAAW,aAAa,WAAW,IAAI,CAAC,QAAQ;AAAA,QAC9C,GAAG;AAAA,QACH,MAAM,GAAG;AAAA,MACX,EAAE;AAAA,MACF,cAAc,aAAa;AAAA,IAC7B;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type {\n ComposeErrorKind,\n ComposeRouteErrorKind,\n FailedPreparedOp,\n GenericComposeErrorKind,\n SimulationRevert,\n} from '@lifi/compose-spec';\nimport z from 'zod';\n\nimport {\n parseServerErrorBody,\n type ServerErrorBody,\n} from './responseSchemas.js';\n\n/**\n * Machine-readable error codes returned by the SDK.\n *\n * - `NETWORK_ERROR` — The HTTP request failed (DNS, timeout, connection refused).\n * - `VALIDATION_ERROR` — The server rejected the request (HTTP 400/422).\n * - `UNAUTHENTICATED` — The request lacks valid authentication credentials (HTTP 401).\n * - `FORBIDDEN` — The server understood the request but refuses to authorise it (HTTP 403).\n * - `SERVER_ERROR` — The server returned a 5xx status.\n * - `RATE_LIMITED` — The server returned HTTP 429.\n * - `NOT_FOUND` — The requested resource does not exist (HTTP 404).\n * - `UNKNOWN_ERROR` — An unexpected error that doesn't fit other categories.\n */\nexport type ComposeErrorCode =\n | 'NETWORK_ERROR'\n | 'VALIDATION_ERROR'\n | 'UNAUTHENTICATED'\n | 'FORBIDDEN'\n | 'SERVER_ERROR'\n | 'RATE_LIMITED'\n | 'NOT_FOUND'\n | 'UNKNOWN_ERROR';\n\n/** Version details returned when the backend rejects an outdated SDK. */\nexport interface ComposeSdkOutdated {\n readonly sdkVersion: string;\n readonly serverVersion: string;\n readonly minimumSdkVersion: string;\n}\n\n/**\n * Version details attached when the SDK is ahead of the backend it reached.\n * Raised by the backend's version gate, or by the SDK itself when the\n * `x-lifi-composer-version` response header names an older contract than the\n * one this SDK was built against.\n */\nexport interface ComposeServerOutdated {\n readonly sdkVersion: string;\n readonly serverVersion: string;\n}\n\n/**\n * Error class for all failures originating from the Compose SDK or API.\n *\n * Includes structured metadata beyond the error message to support\n * programmatic error handling.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposeError(e) && e.code === 'VALIDATION_ERROR') {\n * console.error('Invalid request:', e.message, e.path);\n * }\n * }\n * ```\n */\nexport class ComposeError extends Error {\n override readonly name = 'ComposeError';\n /** Machine-readable error category. */\n readonly code: ComposeErrorCode;\n /** HTTP status code, when the error originated from an HTTP response. */\n readonly status?: number;\n /** The request URL that produced the error. */\n readonly url?: string;\n /** Server-provided error kind for finer-grained classification. */\n readonly kind?: ComposeErrorKind | ComposeRouteErrorKind;\n /** JSON-pointer path to the field that caused a validation error. */\n readonly path?: string;\n /**\n * Simulation revert diagnostics attached to `simulation_revert` errors.\n * Contains the raw error bytes and decoded error candidates when the\n * backend can parse the revert reason.\n */\n readonly details?: SimulationRevert;\n /**\n * The prepared ops that failed, attached to `preparation_error` errors.\n * Each entry carries the `callId` of the failing node so callers can drop\n * the unroutable legs and resubmit a smaller flow.\n */\n readonly failedOps?: readonly FailedPreparedOp[];\n /**\n * The `callId`s of the prepared ops that succeeded, attached to\n * `preparation_error` errors alongside {@link ComposeError.failedOps}.\n */\n readonly succeededOps?: readonly string[];\n /** Version details attached only to `sdk_outdated` errors. */\n readonly sdkOutdated?: ComposeSdkOutdated;\n /** Version details attached only to `server_outdated` errors. */\n readonly serverOutdated?: ComposeServerOutdated;\n\n constructor(\n code: ComposeErrorCode,\n message: string,\n options?: {\n status?: number;\n url?: string;\n cause?: unknown;\n kind?: ComposeErrorKind | ComposeRouteErrorKind;\n path?: string;\n details?: SimulationRevert;\n failedOps?: readonly FailedPreparedOp[];\n succeededOps?: readonly string[];\n sdkOutdated?: ComposeSdkOutdated;\n serverOutdated?: ComposeServerOutdated;\n },\n ) {\n super(message, { cause: options?.cause });\n this.code = code;\n this.status = options?.status;\n this.url = options?.url;\n this.kind = options?.kind;\n this.path = options?.path;\n this.details = options?.details;\n this.failedOps = options?.failedOps;\n this.succeededOps = options?.succeededOps;\n this.sdkOutdated = options?.sdkOutdated;\n this.serverOutdated = options?.serverOutdated;\n }\n}\n\n/**\n * The per-op preparation diagnostics carried by `preparation_error` responses,\n * with both arrays known to be present. Produced by narrowing with\n * {@link isComposePreparationError}.\n */\nexport interface ComposePreparationOps {\n readonly failedOps: readonly FailedPreparedOp[];\n readonly succeededOps: readonly string[];\n}\n\n/**\n * Type guard that narrows an unknown error to {@link ComposeError}.\n * @param e - The value to check.\n * @returns `true` if `e` is an instance of `ComposeError`.\n */\nexport const isComposeError = (e: unknown): e is ComposeError =>\n e instanceof ComposeError ||\n (e instanceof Error && e.name === 'ComposeError' && 'code' in e);\n\n/**\n * Type guard for `preparation_error` failures (HTTP 422): a mixed basket where\n * some prepared ops failed and others succeeded. Narrows both\n * {@link ComposeError.failedOps} and {@link ComposeError.succeededOps} to\n * present, so callers can read them without a cast or a truthiness check.\n *\n * @param e - The value to check.\n * @returns `true` if `e` is a `ComposeError` carrying per-op preparation diagnostics.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposePreparationError(e)) {\n * console.error('Failed ops:', e.failedOps);\n * console.error('Succeeded ops:', e.succeededOps);\n * }\n * }\n * ```\n */\nexport const isComposePreparationError = (\n e: unknown,\n): e is ComposeError & ComposePreparationOps =>\n isComposeError(e) &&\n e.kind === 'preparation_error' &&\n Array.isArray(e.failedOps) &&\n Array.isArray(e.succeededOps);\n\n// `isComposeError` accepts a structurally-similar error by name, so an error\n// raised by a *second* copy of this package in the dependency tree reaches the\n// guard below without its `sdkOutdated` field being type-guaranteed. Parse it.\nconst sdkOutdatedSchema = z.object({\n sdkVersion: z.string(),\n serverVersion: z.string(),\n minimumSdkVersion: z.string(),\n});\n\n/**\n * Narrows an outdated-SDK rejection to its required version details.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposeSdkOutdatedError(e)) {\n * console.error(`Upgrade to at least ${e.sdkOutdated.minimumSdkVersion}`);\n * return;\n * }\n * throw e;\n * }\n * ```\n */\nexport const isComposeSdkOutdatedError = (\n e: unknown,\n): e is ComposeError & { readonly sdkOutdated: ComposeSdkOutdated } =>\n isComposeError(e) &&\n e.kind === 'sdk_outdated' &&\n sdkOutdatedSchema.safeParse(e.sdkOutdated).success;\n\nconst serverOutdatedSchema = z.object({\n sdkVersion: z.string(),\n serverVersion: z.string(),\n});\n\n/**\n * Narrows a server-behind-SDK rejection to its required version details.\n *\n * This is the deploy-window error: the SDK on npm is ahead of the backend that\n * answered. Pin the SDK to the server's `major.minor`, or wait for the rollout.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposeServerOutdatedError(e)) {\n * console.error(`Server serves ${e.serverOutdated.serverVersion}`);\n * return;\n * }\n * throw e;\n * }\n * ```\n */\nexport const isComposeServerOutdatedError = (\n e: unknown,\n): e is ComposeError & { readonly serverOutdated: ComposeServerOutdated } =>\n isComposeError(e) &&\n e.kind === 'server_outdated' &&\n serverOutdatedSchema.safeParse(e.serverOutdated).success;\n\nconst STATUS_TO_CODE: ReadonlyMap<number, ComposeErrorCode> = new Map<\n number,\n ComposeErrorCode\n>([\n [400, 'VALIDATION_ERROR'],\n [401, 'UNAUTHENTICATED'],\n [403, 'FORBIDDEN'],\n [404, 'NOT_FOUND'],\n [422, 'VALIDATION_ERROR'],\n [429, 'RATE_LIMITED'],\n]);\n\nconst tryParseErrorBody = (body: string): ServerErrorBody | null => {\n let json: unknown;\n try {\n json = JSON.parse(body);\n } catch {\n return null;\n }\n return parseServerErrorBody(json);\n};\n\n/**\n * Extracts the version triple an `sdk_outdated` or `server_outdated` envelope\n * carries. The slots reuse the same schemas the type guards narrow with. Zod\n * strips keys the schema does not declare, so the parsed value is exactly the\n * triple; a partial payload fails the parse and yields `undefined` rather than\n * a half-populated object.\n */\nconst readVersionSlots = (serverError: ServerErrorBody['error']) => ({\n sdkOutdated:\n serverError?.kind === 'sdk_outdated'\n ? sdkOutdatedSchema.safeParse(serverError).data\n : undefined,\n serverOutdated:\n serverError?.kind === 'server_outdated'\n ? serverOutdatedSchema.safeParse(serverError).data\n : undefined,\n});\n\n/**\n * Constructs a {@link ComposeError} from an HTTP error response, extracting\n * structured error details from the response body when available.\n *\n * @param status - The HTTP status code.\n * @param body - The raw response body text.\n * @param url - The request URL that produced the error.\n * @returns A `ComposeError` with the appropriate error code and metadata.\n */\nexport const errorFromHttpResponse = (\n status: number,\n body: string,\n url: string,\n): ComposeError => {\n const parsed = tryParseErrorBody(body);\n const serverError = parsed?.error;\n const { sdkOutdated, serverOutdated } = readVersionSlots(serverError);\n\n return new ComposeError(\n STATUS_TO_CODE.get(status) ??\n (status >= 500 ? 'SERVER_ERROR' : 'UNKNOWN_ERROR'),\n (serverError?.message ?? body) || `HTTP ${status}`,\n {\n status,\n url,\n kind: serverError?.kind as\n ComposeErrorKind | ComposeRouteErrorKind | undefined,\n path: serverError?.path,\n details: serverError?.details,\n // The wire schema types `kind` as a bare string; the server only ever\n // reports generic kinds for individual ops.\n failedOps: serverError?.failedOps?.map((op) => ({\n ...op,\n kind: op.kind as GenericComposeErrorKind,\n })),\n succeededOps: serverError?.succeededOps,\n sdkOutdated,\n serverOutdated,\n },\n );\n};\n"],"mappings":"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","names":["z"]}
package/dist/errors.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { ComposeErrorKind, SimulationRevert, FailedPreparedOp } from '@lifi/compose-spec';
1
+ import { ComposeErrorKind, ComposeRouteErrorKind, SimulationRevert, FailedPreparedOp } from '@lifi/compose-spec';
2
2
 
3
3
  /**
4
4
  * Machine-readable error codes returned by the SDK.
@@ -13,6 +13,22 @@ import { ComposeErrorKind, SimulationRevert, FailedPreparedOp } from '@lifi/comp
13
13
  * - `UNKNOWN_ERROR` — An unexpected error that doesn't fit other categories.
14
14
  */
15
15
  type ComposeErrorCode = 'NETWORK_ERROR' | 'VALIDATION_ERROR' | 'UNAUTHENTICATED' | 'FORBIDDEN' | 'SERVER_ERROR' | 'RATE_LIMITED' | 'NOT_FOUND' | 'UNKNOWN_ERROR';
16
+ /** Version details returned when the backend rejects an outdated SDK. */
17
+ interface ComposeSdkOutdated {
18
+ readonly sdkVersion: string;
19
+ readonly serverVersion: string;
20
+ readonly minimumSdkVersion: string;
21
+ }
22
+ /**
23
+ * Version details attached when the SDK is ahead of the backend it reached.
24
+ * Raised by the backend's version gate, or by the SDK itself when the
25
+ * `x-lifi-composer-version` response header names an older contract than the
26
+ * one this SDK was built against.
27
+ */
28
+ interface ComposeServerOutdated {
29
+ readonly sdkVersion: string;
30
+ readonly serverVersion: string;
31
+ }
16
32
  /**
17
33
  * Error class for all failures originating from the Compose SDK or API.
18
34
  *
@@ -39,7 +55,7 @@ declare class ComposeError extends Error {
39
55
  /** The request URL that produced the error. */
40
56
  readonly url?: string;
41
57
  /** Server-provided error kind for finer-grained classification. */
42
- readonly kind?: ComposeErrorKind;
58
+ readonly kind?: ComposeErrorKind | ComposeRouteErrorKind;
43
59
  /** JSON-pointer path to the field that caused a validation error. */
44
60
  readonly path?: string;
45
61
  /**
@@ -59,15 +75,21 @@ declare class ComposeError extends Error {
59
75
  * `preparation_error` errors alongside {@link ComposeError.failedOps}.
60
76
  */
61
77
  readonly succeededOps?: readonly string[];
78
+ /** Version details attached only to `sdk_outdated` errors. */
79
+ readonly sdkOutdated?: ComposeSdkOutdated;
80
+ /** Version details attached only to `server_outdated` errors. */
81
+ readonly serverOutdated?: ComposeServerOutdated;
62
82
  constructor(code: ComposeErrorCode, message: string, options?: {
63
83
  status?: number;
64
84
  url?: string;
65
85
  cause?: unknown;
66
- kind?: ComposeErrorKind;
86
+ kind?: ComposeErrorKind | ComposeRouteErrorKind;
67
87
  path?: string;
68
88
  details?: SimulationRevert;
69
89
  failedOps?: readonly FailedPreparedOp[];
70
90
  succeededOps?: readonly string[];
91
+ sdkOutdated?: ComposeSdkOutdated;
92
+ serverOutdated?: ComposeServerOutdated;
71
93
  });
72
94
  }
73
95
  /**
@@ -107,6 +129,47 @@ declare const isComposeError: (e: unknown) => e is ComposeError;
107
129
  * ```
108
130
  */
109
131
  declare const isComposePreparationError: (e: unknown) => e is ComposeError & ComposePreparationOps;
132
+ /**
133
+ * Narrows an outdated-SDK rejection to its required version details.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * try {
138
+ * await builder.compile(run);
139
+ * } catch (e) {
140
+ * if (isComposeSdkOutdatedError(e)) {
141
+ * console.error(`Upgrade to at least ${e.sdkOutdated.minimumSdkVersion}`);
142
+ * return;
143
+ * }
144
+ * throw e;
145
+ * }
146
+ * ```
147
+ */
148
+ declare const isComposeSdkOutdatedError: (e: unknown) => e is ComposeError & {
149
+ readonly sdkOutdated: ComposeSdkOutdated;
150
+ };
151
+ /**
152
+ * Narrows a server-behind-SDK rejection to its required version details.
153
+ *
154
+ * This is the deploy-window error: the SDK on npm is ahead of the backend that
155
+ * answered. Pin the SDK to the server's `major.minor`, or wait for the rollout.
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * try {
160
+ * await builder.compile(run);
161
+ * } catch (e) {
162
+ * if (isComposeServerOutdatedError(e)) {
163
+ * console.error(`Server serves ${e.serverOutdated.serverVersion}`);
164
+ * return;
165
+ * }
166
+ * throw e;
167
+ * }
168
+ * ```
169
+ */
170
+ declare const isComposeServerOutdatedError: (e: unknown) => e is ComposeError & {
171
+ readonly serverOutdated: ComposeServerOutdated;
172
+ };
110
173
  /**
111
174
  * Constructs a {@link ComposeError} from an HTTP error response, extracting
112
175
  * structured error details from the response body when available.
@@ -118,4 +181,4 @@ declare const isComposePreparationError: (e: unknown) => e is ComposeError & Com
118
181
  */
119
182
  declare const errorFromHttpResponse: (status: number, body: string, url: string) => ComposeError;
120
183
 
121
- export { ComposeError, type ComposeErrorCode, type ComposePreparationOps, errorFromHttpResponse, isComposeError, isComposePreparationError };
184
+ export { ComposeError, type ComposeErrorCode, type ComposePreparationOps, type ComposeSdkOutdated, type ComposeServerOutdated, errorFromHttpResponse, isComposeError, isComposePreparationError, isComposeSdkOutdatedError, isComposeServerOutdatedError };
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ComposeErrorKind, SimulationRevert, FailedPreparedOp } from '@lifi/compose-spec';
1
+ import { ComposeErrorKind, ComposeRouteErrorKind, SimulationRevert, FailedPreparedOp } from '@lifi/compose-spec';
2
2
 
3
3
  /**
4
4
  * Machine-readable error codes returned by the SDK.
@@ -13,6 +13,22 @@ import { ComposeErrorKind, SimulationRevert, FailedPreparedOp } from '@lifi/comp
13
13
  * - `UNKNOWN_ERROR` — An unexpected error that doesn't fit other categories.
14
14
  */
15
15
  type ComposeErrorCode = 'NETWORK_ERROR' | 'VALIDATION_ERROR' | 'UNAUTHENTICATED' | 'FORBIDDEN' | 'SERVER_ERROR' | 'RATE_LIMITED' | 'NOT_FOUND' | 'UNKNOWN_ERROR';
16
+ /** Version details returned when the backend rejects an outdated SDK. */
17
+ interface ComposeSdkOutdated {
18
+ readonly sdkVersion: string;
19
+ readonly serverVersion: string;
20
+ readonly minimumSdkVersion: string;
21
+ }
22
+ /**
23
+ * Version details attached when the SDK is ahead of the backend it reached.
24
+ * Raised by the backend's version gate, or by the SDK itself when the
25
+ * `x-lifi-composer-version` response header names an older contract than the
26
+ * one this SDK was built against.
27
+ */
28
+ interface ComposeServerOutdated {
29
+ readonly sdkVersion: string;
30
+ readonly serverVersion: string;
31
+ }
16
32
  /**
17
33
  * Error class for all failures originating from the Compose SDK or API.
18
34
  *
@@ -39,7 +55,7 @@ declare class ComposeError extends Error {
39
55
  /** The request URL that produced the error. */
40
56
  readonly url?: string;
41
57
  /** Server-provided error kind for finer-grained classification. */
42
- readonly kind?: ComposeErrorKind;
58
+ readonly kind?: ComposeErrorKind | ComposeRouteErrorKind;
43
59
  /** JSON-pointer path to the field that caused a validation error. */
44
60
  readonly path?: string;
45
61
  /**
@@ -59,15 +75,21 @@ declare class ComposeError extends Error {
59
75
  * `preparation_error` errors alongside {@link ComposeError.failedOps}.
60
76
  */
61
77
  readonly succeededOps?: readonly string[];
78
+ /** Version details attached only to `sdk_outdated` errors. */
79
+ readonly sdkOutdated?: ComposeSdkOutdated;
80
+ /** Version details attached only to `server_outdated` errors. */
81
+ readonly serverOutdated?: ComposeServerOutdated;
62
82
  constructor(code: ComposeErrorCode, message: string, options?: {
63
83
  status?: number;
64
84
  url?: string;
65
85
  cause?: unknown;
66
- kind?: ComposeErrorKind;
86
+ kind?: ComposeErrorKind | ComposeRouteErrorKind;
67
87
  path?: string;
68
88
  details?: SimulationRevert;
69
89
  failedOps?: readonly FailedPreparedOp[];
70
90
  succeededOps?: readonly string[];
91
+ sdkOutdated?: ComposeSdkOutdated;
92
+ serverOutdated?: ComposeServerOutdated;
71
93
  });
72
94
  }
73
95
  /**
@@ -107,6 +129,47 @@ declare const isComposeError: (e: unknown) => e is ComposeError;
107
129
  * ```
108
130
  */
109
131
  declare const isComposePreparationError: (e: unknown) => e is ComposeError & ComposePreparationOps;
132
+ /**
133
+ * Narrows an outdated-SDK rejection to its required version details.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * try {
138
+ * await builder.compile(run);
139
+ * } catch (e) {
140
+ * if (isComposeSdkOutdatedError(e)) {
141
+ * console.error(`Upgrade to at least ${e.sdkOutdated.minimumSdkVersion}`);
142
+ * return;
143
+ * }
144
+ * throw e;
145
+ * }
146
+ * ```
147
+ */
148
+ declare const isComposeSdkOutdatedError: (e: unknown) => e is ComposeError & {
149
+ readonly sdkOutdated: ComposeSdkOutdated;
150
+ };
151
+ /**
152
+ * Narrows a server-behind-SDK rejection to its required version details.
153
+ *
154
+ * This is the deploy-window error: the SDK on npm is ahead of the backend that
155
+ * answered. Pin the SDK to the server's `major.minor`, or wait for the rollout.
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * try {
160
+ * await builder.compile(run);
161
+ * } catch (e) {
162
+ * if (isComposeServerOutdatedError(e)) {
163
+ * console.error(`Server serves ${e.serverOutdated.serverVersion}`);
164
+ * return;
165
+ * }
166
+ * throw e;
167
+ * }
168
+ * ```
169
+ */
170
+ declare const isComposeServerOutdatedError: (e: unknown) => e is ComposeError & {
171
+ readonly serverOutdated: ComposeServerOutdated;
172
+ };
110
173
  /**
111
174
  * Constructs a {@link ComposeError} from an HTTP error response, extracting
112
175
  * structured error details from the response body when available.
@@ -118,4 +181,4 @@ declare const isComposePreparationError: (e: unknown) => e is ComposeError & Com
118
181
  */
119
182
  declare const errorFromHttpResponse: (status: number, body: string, url: string) => ComposeError;
120
183
 
121
- export { ComposeError, type ComposeErrorCode, type ComposePreparationOps, errorFromHttpResponse, isComposeError, isComposePreparationError };
184
+ export { ComposeError, type ComposeErrorCode, type ComposePreparationOps, type ComposeSdkOutdated, type ComposeServerOutdated, errorFromHttpResponse, isComposeError, isComposePreparationError, isComposeSdkOutdatedError, isComposeServerOutdatedError };
package/dist/errors.js CHANGED
@@ -1,3 +1,4 @@
1
+ import z from "zod";
1
2
  import {
2
3
  parseServerErrorBody
3
4
  } from "./responseSchemas.js";
@@ -30,6 +31,10 @@ class ComposeError extends Error {
30
31
  * `preparation_error` errors alongside {@link ComposeError.failedOps}.
31
32
  */
32
33
  succeededOps;
34
+ /** Version details attached only to `sdk_outdated` errors. */
35
+ sdkOutdated;
36
+ /** Version details attached only to `server_outdated` errors. */
37
+ serverOutdated;
33
38
  constructor(code, message, options) {
34
39
  super(message, { cause: options?.cause });
35
40
  this.code = code;
@@ -40,10 +45,23 @@ class ComposeError extends Error {
40
45
  this.details = options?.details;
41
46
  this.failedOps = options?.failedOps;
42
47
  this.succeededOps = options?.succeededOps;
48
+ this.sdkOutdated = options?.sdkOutdated;
49
+ this.serverOutdated = options?.serverOutdated;
43
50
  }
44
51
  }
45
52
  const isComposeError = (e) => e instanceof ComposeError || e instanceof Error && e.name === "ComposeError" && "code" in e;
46
53
  const isComposePreparationError = (e) => isComposeError(e) && e.kind === "preparation_error" && Array.isArray(e.failedOps) && Array.isArray(e.succeededOps);
54
+ const sdkOutdatedSchema = z.object({
55
+ sdkVersion: z.string(),
56
+ serverVersion: z.string(),
57
+ minimumSdkVersion: z.string()
58
+ });
59
+ const isComposeSdkOutdatedError = (e) => isComposeError(e) && e.kind === "sdk_outdated" && sdkOutdatedSchema.safeParse(e.sdkOutdated).success;
60
+ const serverOutdatedSchema = z.object({
61
+ sdkVersion: z.string(),
62
+ serverVersion: z.string()
63
+ });
64
+ const isComposeServerOutdatedError = (e) => isComposeError(e) && e.kind === "server_outdated" && serverOutdatedSchema.safeParse(e.serverOutdated).success;
47
65
  const STATUS_TO_CODE = /* @__PURE__ */ new Map([
48
66
  [400, "VALIDATION_ERROR"],
49
67
  [401, "UNAUTHENTICATED"],
@@ -61,9 +79,14 @@ const tryParseErrorBody = (body) => {
61
79
  }
62
80
  return parseServerErrorBody(json);
63
81
  };
82
+ const readVersionSlots = (serverError) => ({
83
+ sdkOutdated: serverError?.kind === "sdk_outdated" ? sdkOutdatedSchema.safeParse(serverError).data : void 0,
84
+ serverOutdated: serverError?.kind === "server_outdated" ? serverOutdatedSchema.safeParse(serverError).data : void 0
85
+ });
64
86
  const errorFromHttpResponse = (status, body, url) => {
65
87
  const parsed = tryParseErrorBody(body);
66
88
  const serverError = parsed?.error;
89
+ const { sdkOutdated, serverOutdated } = readVersionSlots(serverError);
67
90
  return new ComposeError(
68
91
  STATUS_TO_CODE.get(status) ?? (status >= 500 ? "SERVER_ERROR" : "UNKNOWN_ERROR"),
69
92
  (serverError?.message ?? body) || `HTTP ${status}`,
@@ -79,7 +102,9 @@ const errorFromHttpResponse = (status, body, url) => {
79
102
  ...op,
80
103
  kind: op.kind
81
104
  })),
82
- succeededOps: serverError?.succeededOps
105
+ succeededOps: serverError?.succeededOps,
106
+ sdkOutdated,
107
+ serverOutdated
83
108
  }
84
109
  );
85
110
  };
@@ -87,6 +112,8 @@ export {
87
112
  ComposeError,
88
113
  errorFromHttpResponse,
89
114
  isComposeError,
90
- isComposePreparationError
115
+ isComposePreparationError,
116
+ isComposeSdkOutdatedError,
117
+ isComposeServerOutdatedError
91
118
  };
92
119
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type {\n ComposeErrorKind,\n FailedPreparedOp,\n GenericComposeErrorKind,\n SimulationRevert,\n} from '@lifi/compose-spec';\n\nimport {\n parseServerErrorBody,\n type ServerErrorBody,\n} from './responseSchemas.js';\n\n/**\n * Machine-readable error codes returned by the SDK.\n *\n * - `NETWORK_ERROR` — The HTTP request failed (DNS, timeout, connection refused).\n * - `VALIDATION_ERROR` — The server rejected the request (HTTP 400/422).\n * - `UNAUTHENTICATED` — The request lacks valid authentication credentials (HTTP 401).\n * - `FORBIDDEN` — The server understood the request but refuses to authorise it (HTTP 403).\n * - `SERVER_ERROR` — The server returned a 5xx status.\n * - `RATE_LIMITED` — The server returned HTTP 429.\n * - `NOT_FOUND` — The requested resource does not exist (HTTP 404).\n * - `UNKNOWN_ERROR` — An unexpected error that doesn't fit other categories.\n */\nexport type ComposeErrorCode =\n | 'NETWORK_ERROR'\n | 'VALIDATION_ERROR'\n | 'UNAUTHENTICATED'\n | 'FORBIDDEN'\n | 'SERVER_ERROR'\n | 'RATE_LIMITED'\n | 'NOT_FOUND'\n | 'UNKNOWN_ERROR';\n\n/**\n * Error class for all failures originating from the Compose SDK or API.\n *\n * Includes structured metadata beyond the error message to support\n * programmatic error handling.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposeError(e) && e.code === 'VALIDATION_ERROR') {\n * console.error('Invalid request:', e.message, e.path);\n * }\n * }\n * ```\n */\nexport class ComposeError extends Error {\n override readonly name = 'ComposeError';\n /** Machine-readable error category. */\n readonly code: ComposeErrorCode;\n /** HTTP status code, when the error originated from an HTTP response. */\n readonly status?: number;\n /** The request URL that produced the error. */\n readonly url?: string;\n /** Server-provided error kind for finer-grained classification. */\n readonly kind?: ComposeErrorKind;\n /** JSON-pointer path to the field that caused a validation error. */\n readonly path?: string;\n /**\n * Simulation revert diagnostics attached to `simulation_revert` errors.\n * Contains the raw error bytes and decoded error candidates when the\n * backend can parse the revert reason.\n */\n readonly details?: SimulationRevert;\n /**\n * The prepared ops that failed, attached to `preparation_error` errors.\n * Each entry carries the `callId` of the failing node so callers can drop\n * the unroutable legs and resubmit a smaller flow.\n */\n readonly failedOps?: readonly FailedPreparedOp[];\n /**\n * The `callId`s of the prepared ops that succeeded, attached to\n * `preparation_error` errors alongside {@link ComposeError.failedOps}.\n */\n readonly succeededOps?: readonly string[];\n\n constructor(\n code: ComposeErrorCode,\n message: string,\n options?: {\n status?: number;\n url?: string;\n cause?: unknown;\n kind?: ComposeErrorKind;\n path?: string;\n details?: SimulationRevert;\n failedOps?: readonly FailedPreparedOp[];\n succeededOps?: readonly string[];\n },\n ) {\n super(message, { cause: options?.cause });\n this.code = code;\n this.status = options?.status;\n this.url = options?.url;\n this.kind = options?.kind;\n this.path = options?.path;\n this.details = options?.details;\n this.failedOps = options?.failedOps;\n this.succeededOps = options?.succeededOps;\n }\n}\n\n/**\n * The per-op preparation diagnostics carried by `preparation_error` responses,\n * with both arrays known to be present. Produced by narrowing with\n * {@link isComposePreparationError}.\n */\nexport interface ComposePreparationOps {\n readonly failedOps: readonly FailedPreparedOp[];\n readonly succeededOps: readonly string[];\n}\n\n/**\n * Type guard that narrows an unknown error to {@link ComposeError}.\n * @param e - The value to check.\n * @returns `true` if `e` is an instance of `ComposeError`.\n */\nexport const isComposeError = (e: unknown): e is ComposeError =>\n e instanceof ComposeError ||\n (e instanceof Error && e.name === 'ComposeError' && 'code' in e);\n\n/**\n * Type guard for `preparation_error` failures (HTTP 422): a mixed basket where\n * some prepared ops failed and others succeeded. Narrows both\n * {@link ComposeError.failedOps} and {@link ComposeError.succeededOps} to\n * present, so callers can read them without a cast or a truthiness check.\n *\n * @param e - The value to check.\n * @returns `true` if `e` is a `ComposeError` carrying per-op preparation diagnostics.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposePreparationError(e)) {\n * console.error('Failed ops:', e.failedOps);\n * console.error('Succeeded ops:', e.succeededOps);\n * }\n * }\n * ```\n */\nexport const isComposePreparationError = (\n e: unknown,\n): e is ComposeError & ComposePreparationOps =>\n isComposeError(e) &&\n e.kind === 'preparation_error' &&\n Array.isArray(e.failedOps) &&\n Array.isArray(e.succeededOps);\n\nconst STATUS_TO_CODE: ReadonlyMap<number, ComposeErrorCode> = new Map<\n number,\n ComposeErrorCode\n>([\n [400, 'VALIDATION_ERROR'],\n [401, 'UNAUTHENTICATED'],\n [403, 'FORBIDDEN'],\n [404, 'NOT_FOUND'],\n [422, 'VALIDATION_ERROR'],\n [429, 'RATE_LIMITED'],\n]);\n\nconst tryParseErrorBody = (body: string): ServerErrorBody | null => {\n let json: unknown;\n try {\n json = JSON.parse(body);\n } catch {\n return null;\n }\n return parseServerErrorBody(json);\n};\n\n/**\n * Constructs a {@link ComposeError} from an HTTP error response, extracting\n * structured error details from the response body when available.\n *\n * @param status - The HTTP status code.\n * @param body - The raw response body text.\n * @param url - The request URL that produced the error.\n * @returns A `ComposeError` with the appropriate error code and metadata.\n */\nexport const errorFromHttpResponse = (\n status: number,\n body: string,\n url: string,\n): ComposeError => {\n const parsed = tryParseErrorBody(body);\n const serverError = parsed?.error;\n\n return new ComposeError(\n STATUS_TO_CODE.get(status) ??\n (status >= 500 ? 'SERVER_ERROR' : 'UNKNOWN_ERROR'),\n (serverError?.message ?? body) || `HTTP ${status}`,\n {\n status,\n url,\n kind: serverError?.kind as ComposeErrorKind | undefined,\n path: serverError?.path,\n details: serverError?.details,\n // The wire schema types `kind` as a bare string; the server only ever\n // reports generic kinds for individual ops.\n failedOps: serverError?.failedOps?.map((op) => ({\n ...op,\n kind: op.kind as GenericComposeErrorKind,\n })),\n succeededOps: serverError?.succeededOps,\n },\n );\n};\n"],"mappings":"AAOA;AAAA,EACE;AAAA,OAEK;AAyCA,MAAM,qBAAqB,MAAM;AAAA,EACpB,OAAO;AAAA;AAAA,EAEhB;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,EAET,YACE,MACA,SACA,SAUA;AACA,UAAM,SAAS,EAAE,OAAO,SAAS,MAAM,CAAC;AACxC,SAAK,OAAO;AACZ,SAAK,SAAS,SAAS;AACvB,SAAK,MAAM,SAAS;AACpB,SAAK,OAAO,SAAS;AACrB,SAAK,OAAO,SAAS;AACrB,SAAK,UAAU,SAAS;AACxB,SAAK,YAAY,SAAS;AAC1B,SAAK,eAAe,SAAS;AAAA,EAC/B;AACF;AAiBO,MAAM,iBAAiB,CAAC,MAC7B,aAAa,gBACZ,aAAa,SAAS,EAAE,SAAS,kBAAkB,UAAU;AAuBzD,MAAM,4BAA4B,CACvC,MAEA,eAAe,CAAC,KAChB,EAAE,SAAS,uBACX,MAAM,QAAQ,EAAE,SAAS,KACzB,MAAM,QAAQ,EAAE,YAAY;AAE9B,MAAM,iBAAwD,oBAAI,IAGhE;AAAA,EACA,CAAC,KAAK,kBAAkB;AAAA,EACxB,CAAC,KAAK,iBAAiB;AAAA,EACvB,CAAC,KAAK,WAAW;AAAA,EACjB,CAAC,KAAK,WAAW;AAAA,EACjB,CAAC,KAAK,kBAAkB;AAAA,EACxB,CAAC,KAAK,cAAc;AACtB,CAAC;AAED,MAAM,oBAAoB,CAAC,SAAyC;AAClE,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,qBAAqB,IAAI;AAClC;AAWO,MAAM,wBAAwB,CACnC,QACA,MACA,QACiB;AACjB,QAAM,SAAS,kBAAkB,IAAI;AACrC,QAAM,cAAc,QAAQ;AAE5B,SAAO,IAAI;AAAA,IACT,eAAe,IAAI,MAAM,MACtB,UAAU,MAAM,iBAAiB;AAAA,KACnC,aAAa,WAAW,SAAS,QAAQ,MAAM;AAAA,IAChD;AAAA,MACE;AAAA,MACA;AAAA,MACA,MAAM,aAAa;AAAA,MACnB,MAAM,aAAa;AAAA,MACnB,SAAS,aAAa;AAAA;AAAA;AAAA,MAGtB,WAAW,aAAa,WAAW,IAAI,CAAC,QAAQ;AAAA,QAC9C,GAAG;AAAA,QACH,MAAM,GAAG;AAAA,MACX,EAAE;AAAA,MACF,cAAc,aAAa;AAAA,IAC7B;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type {\n ComposeErrorKind,\n ComposeRouteErrorKind,\n FailedPreparedOp,\n GenericComposeErrorKind,\n SimulationRevert,\n} from '@lifi/compose-spec';\nimport z from 'zod';\n\nimport {\n parseServerErrorBody,\n type ServerErrorBody,\n} from './responseSchemas.js';\n\n/**\n * Machine-readable error codes returned by the SDK.\n *\n * - `NETWORK_ERROR` — The HTTP request failed (DNS, timeout, connection refused).\n * - `VALIDATION_ERROR` — The server rejected the request (HTTP 400/422).\n * - `UNAUTHENTICATED` — The request lacks valid authentication credentials (HTTP 401).\n * - `FORBIDDEN` — The server understood the request but refuses to authorise it (HTTP 403).\n * - `SERVER_ERROR` — The server returned a 5xx status.\n * - `RATE_LIMITED` — The server returned HTTP 429.\n * - `NOT_FOUND` — The requested resource does not exist (HTTP 404).\n * - `UNKNOWN_ERROR` — An unexpected error that doesn't fit other categories.\n */\nexport type ComposeErrorCode =\n | 'NETWORK_ERROR'\n | 'VALIDATION_ERROR'\n | 'UNAUTHENTICATED'\n | 'FORBIDDEN'\n | 'SERVER_ERROR'\n | 'RATE_LIMITED'\n | 'NOT_FOUND'\n | 'UNKNOWN_ERROR';\n\n/** Version details returned when the backend rejects an outdated SDK. */\nexport interface ComposeSdkOutdated {\n readonly sdkVersion: string;\n readonly serverVersion: string;\n readonly minimumSdkVersion: string;\n}\n\n/**\n * Version details attached when the SDK is ahead of the backend it reached.\n * Raised by the backend's version gate, or by the SDK itself when the\n * `x-lifi-composer-version` response header names an older contract than the\n * one this SDK was built against.\n */\nexport interface ComposeServerOutdated {\n readonly sdkVersion: string;\n readonly serverVersion: string;\n}\n\n/**\n * Error class for all failures originating from the Compose SDK or API.\n *\n * Includes structured metadata beyond the error message to support\n * programmatic error handling.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposeError(e) && e.code === 'VALIDATION_ERROR') {\n * console.error('Invalid request:', e.message, e.path);\n * }\n * }\n * ```\n */\nexport class ComposeError extends Error {\n override readonly name = 'ComposeError';\n /** Machine-readable error category. */\n readonly code: ComposeErrorCode;\n /** HTTP status code, when the error originated from an HTTP response. */\n readonly status?: number;\n /** The request URL that produced the error. */\n readonly url?: string;\n /** Server-provided error kind for finer-grained classification. */\n readonly kind?: ComposeErrorKind | ComposeRouteErrorKind;\n /** JSON-pointer path to the field that caused a validation error. */\n readonly path?: string;\n /**\n * Simulation revert diagnostics attached to `simulation_revert` errors.\n * Contains the raw error bytes and decoded error candidates when the\n * backend can parse the revert reason.\n */\n readonly details?: SimulationRevert;\n /**\n * The prepared ops that failed, attached to `preparation_error` errors.\n * Each entry carries the `callId` of the failing node so callers can drop\n * the unroutable legs and resubmit a smaller flow.\n */\n readonly failedOps?: readonly FailedPreparedOp[];\n /**\n * The `callId`s of the prepared ops that succeeded, attached to\n * `preparation_error` errors alongside {@link ComposeError.failedOps}.\n */\n readonly succeededOps?: readonly string[];\n /** Version details attached only to `sdk_outdated` errors. */\n readonly sdkOutdated?: ComposeSdkOutdated;\n /** Version details attached only to `server_outdated` errors. */\n readonly serverOutdated?: ComposeServerOutdated;\n\n constructor(\n code: ComposeErrorCode,\n message: string,\n options?: {\n status?: number;\n url?: string;\n cause?: unknown;\n kind?: ComposeErrorKind | ComposeRouteErrorKind;\n path?: string;\n details?: SimulationRevert;\n failedOps?: readonly FailedPreparedOp[];\n succeededOps?: readonly string[];\n sdkOutdated?: ComposeSdkOutdated;\n serverOutdated?: ComposeServerOutdated;\n },\n ) {\n super(message, { cause: options?.cause });\n this.code = code;\n this.status = options?.status;\n this.url = options?.url;\n this.kind = options?.kind;\n this.path = options?.path;\n this.details = options?.details;\n this.failedOps = options?.failedOps;\n this.succeededOps = options?.succeededOps;\n this.sdkOutdated = options?.sdkOutdated;\n this.serverOutdated = options?.serverOutdated;\n }\n}\n\n/**\n * The per-op preparation diagnostics carried by `preparation_error` responses,\n * with both arrays known to be present. Produced by narrowing with\n * {@link isComposePreparationError}.\n */\nexport interface ComposePreparationOps {\n readonly failedOps: readonly FailedPreparedOp[];\n readonly succeededOps: readonly string[];\n}\n\n/**\n * Type guard that narrows an unknown error to {@link ComposeError}.\n * @param e - The value to check.\n * @returns `true` if `e` is an instance of `ComposeError`.\n */\nexport const isComposeError = (e: unknown): e is ComposeError =>\n e instanceof ComposeError ||\n (e instanceof Error && e.name === 'ComposeError' && 'code' in e);\n\n/**\n * Type guard for `preparation_error` failures (HTTP 422): a mixed basket where\n * some prepared ops failed and others succeeded. Narrows both\n * {@link ComposeError.failedOps} and {@link ComposeError.succeededOps} to\n * present, so callers can read them without a cast or a truthiness check.\n *\n * @param e - The value to check.\n * @returns `true` if `e` is a `ComposeError` carrying per-op preparation diagnostics.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposePreparationError(e)) {\n * console.error('Failed ops:', e.failedOps);\n * console.error('Succeeded ops:', e.succeededOps);\n * }\n * }\n * ```\n */\nexport const isComposePreparationError = (\n e: unknown,\n): e is ComposeError & ComposePreparationOps =>\n isComposeError(e) &&\n e.kind === 'preparation_error' &&\n Array.isArray(e.failedOps) &&\n Array.isArray(e.succeededOps);\n\n// `isComposeError` accepts a structurally-similar error by name, so an error\n// raised by a *second* copy of this package in the dependency tree reaches the\n// guard below without its `sdkOutdated` field being type-guaranteed. Parse it.\nconst sdkOutdatedSchema = z.object({\n sdkVersion: z.string(),\n serverVersion: z.string(),\n minimumSdkVersion: z.string(),\n});\n\n/**\n * Narrows an outdated-SDK rejection to its required version details.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposeSdkOutdatedError(e)) {\n * console.error(`Upgrade to at least ${e.sdkOutdated.minimumSdkVersion}`);\n * return;\n * }\n * throw e;\n * }\n * ```\n */\nexport const isComposeSdkOutdatedError = (\n e: unknown,\n): e is ComposeError & { readonly sdkOutdated: ComposeSdkOutdated } =>\n isComposeError(e) &&\n e.kind === 'sdk_outdated' &&\n sdkOutdatedSchema.safeParse(e.sdkOutdated).success;\n\nconst serverOutdatedSchema = z.object({\n sdkVersion: z.string(),\n serverVersion: z.string(),\n});\n\n/**\n * Narrows a server-behind-SDK rejection to its required version details.\n *\n * This is the deploy-window error: the SDK on npm is ahead of the backend that\n * answered. Pin the SDK to the server's `major.minor`, or wait for the rollout.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposeServerOutdatedError(e)) {\n * console.error(`Server serves ${e.serverOutdated.serverVersion}`);\n * return;\n * }\n * throw e;\n * }\n * ```\n */\nexport const isComposeServerOutdatedError = (\n e: unknown,\n): e is ComposeError & { readonly serverOutdated: ComposeServerOutdated } =>\n isComposeError(e) &&\n e.kind === 'server_outdated' &&\n serverOutdatedSchema.safeParse(e.serverOutdated).success;\n\nconst STATUS_TO_CODE: ReadonlyMap<number, ComposeErrorCode> = new Map<\n number,\n ComposeErrorCode\n>([\n [400, 'VALIDATION_ERROR'],\n [401, 'UNAUTHENTICATED'],\n [403, 'FORBIDDEN'],\n [404, 'NOT_FOUND'],\n [422, 'VALIDATION_ERROR'],\n [429, 'RATE_LIMITED'],\n]);\n\nconst tryParseErrorBody = (body: string): ServerErrorBody | null => {\n let json: unknown;\n try {\n json = JSON.parse(body);\n } catch {\n return null;\n }\n return parseServerErrorBody(json);\n};\n\n/**\n * Extracts the version triple an `sdk_outdated` or `server_outdated` envelope\n * carries. The slots reuse the same schemas the type guards narrow with. Zod\n * strips keys the schema does not declare, so the parsed value is exactly the\n * triple; a partial payload fails the parse and yields `undefined` rather than\n * a half-populated object.\n */\nconst readVersionSlots = (serverError: ServerErrorBody['error']) => ({\n sdkOutdated:\n serverError?.kind === 'sdk_outdated'\n ? sdkOutdatedSchema.safeParse(serverError).data\n : undefined,\n serverOutdated:\n serverError?.kind === 'server_outdated'\n ? serverOutdatedSchema.safeParse(serverError).data\n : undefined,\n});\n\n/**\n * Constructs a {@link ComposeError} from an HTTP error response, extracting\n * structured error details from the response body when available.\n *\n * @param status - The HTTP status code.\n * @param body - The raw response body text.\n * @param url - The request URL that produced the error.\n * @returns A `ComposeError` with the appropriate error code and metadata.\n */\nexport const errorFromHttpResponse = (\n status: number,\n body: string,\n url: string,\n): ComposeError => {\n const parsed = tryParseErrorBody(body);\n const serverError = parsed?.error;\n const { sdkOutdated, serverOutdated } = readVersionSlots(serverError);\n\n return new ComposeError(\n STATUS_TO_CODE.get(status) ??\n (status >= 500 ? 'SERVER_ERROR' : 'UNKNOWN_ERROR'),\n (serverError?.message ?? body) || `HTTP ${status}`,\n {\n status,\n url,\n kind: serverError?.kind as\n ComposeErrorKind | ComposeRouteErrorKind | undefined,\n path: serverError?.path,\n details: serverError?.details,\n // The wire schema types `kind` as a bare string; the server only ever\n // reports generic kinds for individual ops.\n failedOps: serverError?.failedOps?.map((op) => ({\n ...op,\n kind: op.kind as GenericComposeErrorKind,\n })),\n succeededOps: serverError?.succeededOps,\n sdkOutdated,\n serverOutdated,\n },\n );\n};\n"],"mappings":"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","names":[]}