@neon/sdk 1.3.0 → 1.4.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.
@@ -1,6 +1,5 @@
1
- # Apache License 2.0
2
1
 
3
- Apache License
2
+ Apache License
4
3
  Version 2.0, January 2004
5
4
  http://www.apache.org/licenses/
6
5
 
@@ -175,4 +174,29 @@
175
174
  incurred by, or claims asserted against, such Contributor by reason
176
175
  of your accepting any such warranty or additional liability.
177
176
 
178
- END OF TERMS AND CONDITIONS
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md CHANGED
@@ -83,7 +83,7 @@ The `error` channel carries a typed hierarchy (all `Error` subclasses with a `ki
83
83
  | `NeonRateLimitError` | `"rate_limit"` | (429, after retries) |
84
84
  | `NeonOperationError` | `"operation"` | `operationId`, `status` — an awaited operation failed |
85
85
  | `NeonTimeoutError` | `"timeout"` | readiness/wait deadline exceeded |
86
- | `NeonNetworkError` | `"network"` | transport failure (no response) |
86
+ | `NeonNetworkError` | `"network"` | `reason` — transport failure (no response) |
87
87
  | `NeonError` | `"client"` | SDK-side errors (e.g. ambiguous connection-string selection) |
88
88
 
89
89
  ```ts
@@ -91,6 +91,27 @@ const { error } = await neon.branches.get(pid, "nope");
91
91
  if (error?.kind === "not_found") { /* … */ }
92
92
  ```
93
93
 
94
+ Branch on `kind` rather than `name` or `message`. `name` is a stable string literal on every
95
+ class, so it survives bundling, but `message` is not a contract.
96
+
97
+ `NeonNetworkError.reason` carries the most specific reason the platform gave — an `errno`
98
+ code such as `ECONNRESET` when one is available, otherwise the innermost non-empty message.
99
+ It is also interpolated into `message`, so transport faults are distinguishable in logs and
100
+ error trackers instead of collapsing onto one string:
101
+
102
+ ```ts
103
+ const { error } = await neon.projects.get(id);
104
+ if (error?.kind === "network") {
105
+ error.reason; // "ECONNRESET"
106
+ error.message; // 'Network error: no response received from the Neon API (ECONNRESET).'
107
+ }
108
+ ```
109
+
110
+ Validating ids and other path parameters before passing them in is the caller's
111
+ responsibility. An empty path parameter builds a URL with an empty segment, which the Neon
112
+ API answers with a redirect rather than a `400`; that can surface as a `"network"` error
113
+ rather than anything that names the argument.
114
+
94
115
  ## Pagination
95
116
 
96
117
  Cursor-paginated `list()` methods return a lazy `Paginated<T>`:
@@ -6,7 +6,14 @@
6
6
  * it.
7
7
  */
8
8
  type NeonErrorKind = "api" | "not_found" | "auth" | "rate_limit" | "operation" | "timeout" | "network" | "client";
9
- /** Base class for every error the ergonomic layer produces. */
9
+ /**
10
+ * Base class for every error the ergonomic layer produces.
11
+ *
12
+ * Every subclass assigns `this.name` as a string literal rather than reading it from the
13
+ * constructor. Bundlers rename classes, so deriving the name at runtime leaves consumers
14
+ * of a minified build with errors called `s` and `r` — unreadable in logs and impossible
15
+ * to group on in an error tracker.
16
+ */
10
17
  declare class NeonError extends Error {
11
18
  readonly kind: NeonErrorKind;
12
19
  constructor(message: string, kind: NeonErrorKind, options?: {
@@ -63,10 +70,26 @@ declare class NeonTimeoutError extends NeonError {
63
70
  }
64
71
  /** A transport-level failure (DNS, connection, abort) — no HTTP response received. */
65
72
  declare class NeonNetworkError extends NeonError {
73
+ /**
74
+ * The most specific reason the platform gave for the failure — an `errno` code such as
75
+ * `ECONNRESET` when one is available, otherwise the innermost non-empty message. Read
76
+ * this instead of matching on {@link message}.
77
+ */
78
+ readonly reason: string;
66
79
  constructor(message: string, options?: {
67
80
  cause?: unknown;
81
+ reason?: string;
68
82
  });
69
83
  }
84
+ /**
85
+ * Walk a transport failure's `cause` chain for the most specific description available.
86
+ *
87
+ * `fetch` reports every transport fault as `TypeError: fetch failed` and puts the real
88
+ * reason underneath, sometimes several levels down and sometimes with an empty message and
89
+ * only an `errno` code. Without this, a DNS failure, a reset connection and a redirect the
90
+ * client refused to follow all produce the same sentence.
91
+ */
92
+ declare function describeTransportFailure(error: unknown): string;
70
93
  /**
71
94
  * Build the right {@link NeonError} subclass from a raw client result. `error` is the
72
95
  * decoded error body (Neon `GeneralError`); `response` is present unless the failure was
@@ -74,5 +97,5 @@ declare class NeonNetworkError extends NeonError {
74
97
  */
75
98
  declare function toNeonError(error: unknown, response: Response | undefined): NeonError;
76
99
  //#endregion
77
- export { NeonApiError, NeonAuthError, NeonError, NeonErrorKind, NeonNetworkError, NeonNotFoundError, NeonOperationError, NeonRateLimitError, NeonTimeoutError, toNeonError };
100
+ export { NeonApiError, NeonAuthError, NeonError, NeonErrorKind, NeonNetworkError, NeonNotFoundError, NeonOperationError, NeonRateLimitError, NeonTimeoutError, describeTransportFailure, toNeonError };
78
101
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","names":[],"sources":["../../src/neon/errors.ts"],"mappings":";;AAOA;AAWA;AAAuB;AACP;AAIR;AALuB,KAXnB,aAAA,GAWmB,KAAA,GAAA,WAAA,GAAA,MAAA,GAAA,YAAA,GAAA,WAAA,GAAA,SAAA,GAAA,SAAA,GAAA,QAAA;AAAK;AAevB,cAfA,SAAA,SAAkB,KAAA,CAeL;EAAA,SAAA,IAAA,EAdV,aAcU;EAQL,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,IAAA,EAlBb,aAkBa,EAAA,OAWP,CAXO,EAAA;IAOX,KAAA,CAAA,EAAA,OAAA;EAII,CAAA;AAnBoB;AAAS;AAiC9B,cAjCA,YAAA,SAAqB,SAAA,CAiCH;EAAA;EAGM,SAAA,MAAA,EAAA,MAAA;EAA7B;EAH+B,SAAA,IAAA,CAAA,EAAA,MAAA;EAAY;EAUtC,SAAA,SAAc,CAAA,EAAA,MAAA;EAAA;EAGU,SAAA,QAAA,CAAA,EAtChB,QAsCgB;EAA7B;EAH2B,SAAA,IAAA,EAAA,OAAA;EAAY,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,IAAA,EAAA;IAUlC,IAAA,CAAA,EAtCH,aAsCsB;IAAA,MAAA,EAAA,MAAA;IAGK,IAAA,CAAA,EAAA,MAAA;IAA7B,SAAA,CAAA,EAAA,MAAA;IAHgC,QAAA,CAAA,EAlC1B,QAkC0B;IAAY,IAAA,CAAA,EAAA,OAAA;EAUvC,CAAA;AAiBb;AAOA;AA6BgB,cAnFH,iBAAA,SAA0B,YAAA,CAmFZ;EAAA,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,IAAA,EAhFnB,qBAgFmB,CAAA,OAhFU,YAgFV,CAAA,CAAA,CAAA,CAAA;AAEhB;AACR;AAAS,cA5EC,aAAA,SAAsB,YAAA,CA4EvB;qCAzEJ,6BAA6B;;;cAOxB,kBAAA,SAA2B,YAAA;qCAGhC,6BAA6B;;;cAOxB,kBAAA,SAA2B,SAAS;;;;;;;;;;;cAiBpC,gBAAA,SAAyB,SAAS;;;;cAOlC,gBAAA,SAAyB,SAAS;;;;;;;;;;iBA6B/B,WAAA,2BAEL,uBACR"}
1
+ {"version":3,"file":"errors.d.ts","names":[],"sources":["../../src/neon/errors.ts"],"mappings":";;AAUA;AAkBA;AAAuB;AACP;AAIR;AALuB,KAlBnB,aAAA,GAkBmB,KAAA,GAAA,WAAA,GAAA,MAAA,GAAA,YAAA,GAAA,WAAA,GAAA,SAAA,GAAA,SAAA,GAAA,QAAA;AAAK;AAepC;AAA0B;AAQL;AAOX;AAII;AAnBoB;AAAS;AAkC9B,cAjDA,SAAA,SAAkB,KAAA,CAiDA;EAAA,SAAA,IAAA,EAhDf,aAgDe;EAGM,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,IAAA,EA/C7B,aA+C6B,EAAA,OAHE,CAGF,EAAA;IAA7B,KAAA,CAAA,EAAA,OAAA;EAH+B,CAAA;AAAY;AAWnD;AAA2B,cA7Cd,YAAA,SAAqB,SAAA,CA6CP;EAGU;EAA7B,SAAA,MAAA,EAAA,MAAA;EAH2B;EAAY,SAAA,IAAA,CAAA,EAAA,MAAA;EAWlC;EAAmB,SAAA,SAAA,CAAA,EAAA,MAAA;EAGK;EAA7B,SAAA,QAAA,CAAA,EAnDa,QAmDb;EAHgC;EAAY,SAAA,IAAA,EAAA,OAAA;EAWvC,WAAA,CAAA,OAAA,EAAmB,MAAA,EAAA,IAAA,EAAQ;IAkB3B,IAAA,CAAA,EAtEH,aAsEoB;IAQjB,MAAA,EAAA,MAAA;IA4CG,IAAA,CAAA,EAAA,MAAA;IAsBA,SAAA,CAAW,EAAA,MAAA;IAAA,QAAA,CAAA,EA5Ib,QA4Ia;IAEhB,IAAA,CAAA,EAAA,OAAA;EACR,CAAA;AAAS;;cAhIC,iBAAA,SAA0B,YAAA;qCAG/B,6BAA6B;;;cAQxB,aAAA,SAAsB,YAAA;qCAG3B,6BAA6B;;;cAQxB,kBAAA,SAA2B,YAAA;qCAGhC,6BAA6B;;;cAQxB,kBAAA,SAA2B,SAAS;;;;;;;;;;;cAkBpC,gBAAA,SAAyB,SAAS;;;;cAQlC,gBAAA,SAAyB,SAAS;;;;;;;;;;;;;;;;;;;;iBA4C/B,wBAAA;;;;;;iBAsBA,WAAA,2BAEL,uBACR"}
@@ -1,10 +1,25 @@
1
1
  //#region src/neon/errors.ts
2
- /** Base class for every error the ergonomic layer produces. */
2
+ /**
3
+ * Typed error hierarchy surfaced on the `error` channel of every ergonomic call (and
4
+ * thrown when `throwOnError` is set). All are `Error` subclasses with a `kind`
5
+ * discriminant, so the same value works whether you read it from `{ error }` or `catch`
6
+ * it.
7
+ */
8
+ /** Used when a transport failure carries neither an `errno` code nor any message. */
9
+ const UNKNOWN_TRANSPORT_REASON = "cause unavailable";
10
+ /**
11
+ * Base class for every error the ergonomic layer produces.
12
+ *
13
+ * Every subclass assigns `this.name` as a string literal rather than reading it from the
14
+ * constructor. Bundlers rename classes, so deriving the name at runtime leaves consumers
15
+ * of a minified build with errors called `s` and `r` — unreadable in logs and impossible
16
+ * to group on in an error tracker.
17
+ */
3
18
  var NeonError = class extends Error {
4
19
  kind;
5
20
  constructor(message, kind, options) {
6
21
  super(message, options);
7
- this.name = new.target.name;
22
+ this.name = "NeonError";
8
23
  this.kind = kind;
9
24
  }
10
25
  };
@@ -22,6 +37,7 @@ var NeonApiError = class extends NeonError {
22
37
  body;
23
38
  constructor(message, init) {
24
39
  super(message, init.kind ?? "api");
40
+ this.name = "NeonApiError";
25
41
  this.status = init.status;
26
42
  this.code = init.code;
27
43
  this.requestId = init.requestId;
@@ -36,6 +52,7 @@ var NeonNotFoundError = class extends NeonApiError {
36
52
  ...init,
37
53
  kind: "not_found"
38
54
  });
55
+ this.name = "NeonNotFoundError";
39
56
  }
40
57
  };
41
58
  /** 401/403 — the API key is missing, invalid, or lacks permission. */
@@ -45,6 +62,7 @@ var NeonAuthError = class extends NeonApiError {
45
62
  ...init,
46
63
  kind: "auth"
47
64
  });
65
+ this.name = "NeonAuthError";
48
66
  }
49
67
  };
50
68
  /** 429 — rate limited (after retries, if enabled, were exhausted). */
@@ -54,6 +72,7 @@ var NeonRateLimitError = class extends NeonApiError {
54
72
  ...init,
55
73
  kind: "rate_limit"
56
74
  });
75
+ this.name = "NeonRateLimitError";
57
76
  }
58
77
  };
59
78
  /** An awaited Neon operation ended in a non-success terminal state. */
@@ -64,6 +83,7 @@ var NeonOperationError = class extends NeonError {
64
83
  status;
65
84
  constructor(message, init) {
66
85
  super(message, "operation");
86
+ this.name = "NeonOperationError";
67
87
  this.operationId = init.operationId;
68
88
  this.status = init.status;
69
89
  }
@@ -72,12 +92,21 @@ var NeonOperationError = class extends NeonError {
72
92
  var NeonTimeoutError = class extends NeonError {
73
93
  constructor(message) {
74
94
  super(message, "timeout");
95
+ this.name = "NeonTimeoutError";
75
96
  }
76
97
  };
77
98
  /** A transport-level failure (DNS, connection, abort) — no HTTP response received. */
78
99
  var NeonNetworkError = class extends NeonError {
100
+ /**
101
+ * The most specific reason the platform gave for the failure — an `errno` code such as
102
+ * `ECONNRESET` when one is available, otherwise the innermost non-empty message. Read
103
+ * this instead of matching on {@link message}.
104
+ */
105
+ reason;
79
106
  constructor(message, options) {
80
- super(message, "network", options);
107
+ super(message, "network", { cause: options?.cause });
108
+ this.name = "NeonNetworkError";
109
+ this.reason = options?.reason ?? UNKNOWN_TRANSPORT_REASON;
81
110
  }
82
111
  };
83
112
  function readApiErrorBody(body) {
@@ -89,12 +118,38 @@ function readApiErrorBody(body) {
89
118
  return out;
90
119
  }
91
120
  /**
121
+ * Walk a transport failure's `cause` chain for the most specific description available.
122
+ *
123
+ * `fetch` reports every transport fault as `TypeError: fetch failed` and puts the real
124
+ * reason underneath, sometimes several levels down and sometimes with an empty message and
125
+ * only an `errno` code. Without this, a DNS failure, a reset connection and a redirect the
126
+ * client refused to follow all produce the same sentence.
127
+ */
128
+ function describeTransportFailure(error) {
129
+ const seen = /* @__PURE__ */ new Set();
130
+ let current = error;
131
+ let deepestMessage;
132
+ while (current instanceof Error && !seen.has(current)) {
133
+ seen.add(current);
134
+ if ("code" in current && typeof current.code === "string") return current.code;
135
+ if (current.message) deepestMessage = current.message;
136
+ current = current.cause;
137
+ }
138
+ return deepestMessage ?? UNKNOWN_TRANSPORT_REASON;
139
+ }
140
+ /**
92
141
  * Build the right {@link NeonError} subclass from a raw client result. `error` is the
93
142
  * decoded error body (Neon `GeneralError`); `response` is present unless the failure was
94
143
  * transport-level.
95
144
  */
96
145
  function toNeonError(error, response) {
97
- if (!response) return new NeonNetworkError("Network error: no response received from the Neon API.", { cause: error });
146
+ if (!response) {
147
+ const reason = describeTransportFailure(error);
148
+ return new NeonNetworkError(`Network error: no response received from the Neon API (${reason}).`, {
149
+ cause: error,
150
+ reason
151
+ });
152
+ }
98
153
  const parsed = readApiErrorBody(error);
99
154
  const status = response.status;
100
155
  const message = parsed.message ?? `Neon API request failed with status ${status}.`;
@@ -111,6 +166,6 @@ function toNeonError(error, response) {
111
166
  return new NeonApiError(message, init);
112
167
  }
113
168
  //#endregion
114
- export { NeonApiError, NeonAuthError, NeonError, NeonNetworkError, NeonNotFoundError, NeonOperationError, NeonRateLimitError, NeonTimeoutError, toNeonError };
169
+ export { NeonApiError, NeonAuthError, NeonError, NeonNetworkError, NeonNotFoundError, NeonOperationError, NeonRateLimitError, NeonTimeoutError, describeTransportFailure, toNeonError };
115
170
 
116
171
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../src/neon/errors.ts"],"sourcesContent":["/**\n * Typed error hierarchy surfaced on the `error` channel of every ergonomic call (and\n * thrown when `throwOnError` is set). All are `Error` subclasses with a `kind`\n * discriminant, so the same value works whether you read it from `{ error }` or `catch`\n * it.\n */\n\nexport type NeonErrorKind =\n\t| \"api\"\n\t| \"not_found\"\n\t| \"auth\"\n\t| \"rate_limit\"\n\t| \"operation\"\n\t| \"timeout\"\n\t| \"network\"\n\t| \"client\";\n\n/** Base class for every error the ergonomic layer produces. */\nexport class NeonError extends Error {\n\treadonly kind: NeonErrorKind;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tkind: NeonErrorKind,\n\t\toptions?: { cause?: unknown },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.name = new.target.name;\n\t\tthis.kind = kind;\n\t}\n}\n\n/** A non-2xx HTTP response from the Neon API. */\nexport class NeonApiError extends NeonError {\n\t/** HTTP status code. */\n\treadonly status: number;\n\t/** Machine-readable Neon error code (`GeneralError.code`), when present. */\n\treadonly code?: string;\n\t/** Neon request id (`X-Request-Id` / `GeneralError.request_id`), when present. */\n\treadonly requestId?: string;\n\t/** The raw response, when one was received. */\n\treadonly response?: Response;\n\t/** The parsed error body, as returned by the API. */\n\treadonly body: unknown;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: {\n\t\t\tkind?: NeonErrorKind;\n\t\t\tstatus: number;\n\t\t\tcode?: string;\n\t\t\trequestId?: string;\n\t\t\tresponse?: Response;\n\t\t\tbody?: unknown;\n\t\t},\n\t) {\n\t\tsuper(message, init.kind ?? \"api\");\n\t\tthis.status = init.status;\n\t\tthis.code = init.code;\n\t\tthis.requestId = init.requestId;\n\t\tthis.response = init.response;\n\t\tthis.body = init.body;\n\t}\n}\n\n/** 404 — the resource does not exist. */\nexport class NeonNotFoundError extends NeonApiError {\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: ConstructorParameters<typeof NeonApiError>[1],\n\t) {\n\t\tsuper(message, { ...init, kind: \"not_found\" });\n\t}\n}\n\n/** 401/403 — the API key is missing, invalid, or lacks permission. */\nexport class NeonAuthError extends NeonApiError {\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: ConstructorParameters<typeof NeonApiError>[1],\n\t) {\n\t\tsuper(message, { ...init, kind: \"auth\" });\n\t}\n}\n\n/** 429 — rate limited (after retries, if enabled, were exhausted). */\nexport class NeonRateLimitError extends NeonApiError {\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: ConstructorParameters<typeof NeonApiError>[1],\n\t) {\n\t\tsuper(message, { ...init, kind: \"rate_limit\" });\n\t}\n}\n\n/** An awaited Neon operation ended in a non-success terminal state. */\nexport class NeonOperationError extends NeonError {\n\t/** The id of the operation that failed. */\n\treadonly operationId: string;\n\t/** The terminal status reported by the API (`failed` / `error` / `cancelled`). */\n\treadonly status: string;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: { operationId: string; status: string },\n\t) {\n\t\tsuper(message, \"operation\");\n\t\tthis.operationId = init.operationId;\n\t\tthis.status = init.status;\n\t}\n}\n\n/** Waiting for operations to finish exceeded the configured timeout. */\nexport class NeonTimeoutError extends NeonError {\n\tconstructor(message: string) {\n\t\tsuper(message, \"timeout\");\n\t}\n}\n\n/** A transport-level failure (DNS, connection, abort) — no HTTP response received. */\nexport class NeonNetworkError extends NeonError {\n\tconstructor(message: string, options?: { cause?: unknown }) {\n\t\tsuper(message, \"network\", options);\n\t}\n}\n\ninterface ApiErrorBody {\n\tmessage?: string;\n\tcode?: string;\n\trequest_id?: string;\n}\n\nfunction readApiErrorBody(body: unknown): ApiErrorBody {\n\tif (typeof body !== \"object\" || body === null) return {};\n\tconst out: ApiErrorBody = {};\n\tif (\"message\" in body && typeof body.message === \"string\")\n\t\tout.message = body.message;\n\tif (\"code\" in body && typeof body.code === \"string\") out.code = body.code;\n\tif (\"request_id\" in body && typeof body.request_id === \"string\") {\n\t\tout.request_id = body.request_id;\n\t}\n\treturn out;\n}\n\n/**\n * Build the right {@link NeonError} subclass from a raw client result. `error` is the\n * decoded error body (Neon `GeneralError`); `response` is present unless the failure was\n * transport-level.\n */\nexport function toNeonError(\n\terror: unknown,\n\tresponse: Response | undefined,\n): NeonError {\n\tif (!response) {\n\t\treturn new NeonNetworkError(\n\t\t\t\"Network error: no response received from the Neon API.\",\n\t\t\t{ cause: error },\n\t\t);\n\t}\n\n\tconst parsed = readApiErrorBody(error);\n\tconst status = response.status;\n\tconst message =\n\t\tparsed.message ?? `Neon API request failed with status ${status}.`;\n\tconst init = {\n\t\tstatus,\n\t\tcode: parsed.code,\n\t\trequestId:\n\t\t\tparsed.request_id ??\n\t\t\tresponse.headers.get(\"x-request-id\") ??\n\t\t\tundefined,\n\t\tresponse,\n\t\tbody: error,\n\t};\n\n\tif (status === 404) return new NeonNotFoundError(message, init);\n\tif (status === 401 || status === 403)\n\t\treturn new NeonAuthError(message, init);\n\tif (status === 429) return new NeonRateLimitError(message, init);\n\treturn new NeonApiError(message, init);\n}\n"],"mappings":";;AAkBA,IAAa,YAAb,cAA+B,MAAM;CACpC;CAEA,YACC,SACA,MACA,SACC;EACD,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO,IAAI,OAAO;EACvB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,eAAb,cAAkC,UAAU;;CAE3C;;CAEA;;CAEA;;CAEA;;CAEA;CAEA,YACC,SACA,MAQC;EACD,MAAM,SAAS,KAAK,QAAQ,KAAK;EACjC,KAAK,SAAS,KAAK;EACnB,KAAK,OAAO,KAAK;EACjB,KAAK,YAAY,KAAK;EACtB,KAAK,WAAW,KAAK;EACrB,KAAK,OAAO,KAAK;CAClB;AACD;;AAGA,IAAa,oBAAb,cAAuC,aAAa;CACnD,YACC,SACA,MACC;EACD,MAAM,SAAS;GAAE,GAAG;GAAM,MAAM;EAAY,CAAC;CAC9C;AACD;;AAGA,IAAa,gBAAb,cAAmC,aAAa;CAC/C,YACC,SACA,MACC;EACD,MAAM,SAAS;GAAE,GAAG;GAAM,MAAM;EAAO,CAAC;CACzC;AACD;;AAGA,IAAa,qBAAb,cAAwC,aAAa;CACpD,YACC,SACA,MACC;EACD,MAAM,SAAS;GAAE,GAAG;GAAM,MAAM;EAAa,CAAC;CAC/C;AACD;;AAGA,IAAa,qBAAb,cAAwC,UAAU;;CAEjD;;CAEA;CAEA,YACC,SACA,MACC;EACD,MAAM,SAAS,WAAW;EAC1B,KAAK,cAAc,KAAK;EACxB,KAAK,SAAS,KAAK;CACpB;AACD;;AAGA,IAAa,mBAAb,cAAsC,UAAU;CAC/C,YAAY,SAAiB;EAC5B,MAAM,SAAS,SAAS;CACzB;AACD;;AAGA,IAAa,mBAAb,cAAsC,UAAU;CAC/C,YAAY,SAAiB,SAA+B;EAC3D,MAAM,SAAS,WAAW,OAAO;CAClC;AACD;AAQA,SAAS,iBAAiB,MAA6B;CACtD,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM,OAAO,CAAC;CACvD,MAAM,MAAoB,CAAC;CAC3B,IAAI,aAAa,QAAQ,OAAO,KAAK,YAAY,UAChD,IAAI,UAAU,KAAK;CACpB,IAAI,UAAU,QAAQ,OAAO,KAAK,SAAS,UAAU,IAAI,OAAO,KAAK;CACrE,IAAI,gBAAgB,QAAQ,OAAO,KAAK,eAAe,UACtD,IAAI,aAAa,KAAK;CAEvB,OAAO;AACR;;;;;;AAOA,SAAgB,YACf,OACA,UACY;CACZ,IAAI,CAAC,UACJ,OAAO,IAAI,iBACV,0DACA,EAAE,OAAO,MAAM,CAChB;CAGD,MAAM,SAAS,iBAAiB,KAAK;CACrC,MAAM,SAAS,SAAS;CACxB,MAAM,UACL,OAAO,WAAW,uCAAuC,OAAO;CACjE,MAAM,OAAO;EACZ;EACA,MAAM,OAAO;EACb,WACC,OAAO,cACP,SAAS,QAAQ,IAAI,cAAc,KACnC,KAAA;EACD;EACA,MAAM;CACP;CAEA,IAAI,WAAW,KAAK,OAAO,IAAI,kBAAkB,SAAS,IAAI;CAC9D,IAAI,WAAW,OAAO,WAAW,KAChC,OAAO,IAAI,cAAc,SAAS,IAAI;CACvC,IAAI,WAAW,KAAK,OAAO,IAAI,mBAAmB,SAAS,IAAI;CAC/D,OAAO,IAAI,aAAa,SAAS,IAAI;AACtC"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../src/neon/errors.ts"],"sourcesContent":["/**\n * Typed error hierarchy surfaced on the `error` channel of every ergonomic call (and\n * thrown when `throwOnError` is set). All are `Error` subclasses with a `kind`\n * discriminant, so the same value works whether you read it from `{ error }` or `catch`\n * it.\n */\n\n/** Used when a transport failure carries neither an `errno` code nor any message. */\nconst UNKNOWN_TRANSPORT_REASON = \"cause unavailable\";\n\nexport type NeonErrorKind =\n\t| \"api\"\n\t| \"not_found\"\n\t| \"auth\"\n\t| \"rate_limit\"\n\t| \"operation\"\n\t| \"timeout\"\n\t| \"network\"\n\t| \"client\";\n\n/**\n * Base class for every error the ergonomic layer produces.\n *\n * Every subclass assigns `this.name` as a string literal rather than reading it from the\n * constructor. Bundlers rename classes, so deriving the name at runtime leaves consumers\n * of a minified build with errors called `s` and `r` — unreadable in logs and impossible\n * to group on in an error tracker.\n */\nexport class NeonError extends Error {\n\treadonly kind: NeonErrorKind;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tkind: NeonErrorKind,\n\t\toptions?: { cause?: unknown },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.name = \"NeonError\";\n\t\tthis.kind = kind;\n\t}\n}\n\n/** A non-2xx HTTP response from the Neon API. */\nexport class NeonApiError extends NeonError {\n\t/** HTTP status code. */\n\treadonly status: number;\n\t/** Machine-readable Neon error code (`GeneralError.code`), when present. */\n\treadonly code?: string;\n\t/** Neon request id (`X-Request-Id` / `GeneralError.request_id`), when present. */\n\treadonly requestId?: string;\n\t/** The raw response, when one was received. */\n\treadonly response?: Response;\n\t/** The parsed error body, as returned by the API. */\n\treadonly body: unknown;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: {\n\t\t\tkind?: NeonErrorKind;\n\t\t\tstatus: number;\n\t\t\tcode?: string;\n\t\t\trequestId?: string;\n\t\t\tresponse?: Response;\n\t\t\tbody?: unknown;\n\t\t},\n\t) {\n\t\tsuper(message, init.kind ?? \"api\");\n\t\tthis.name = \"NeonApiError\";\n\t\tthis.status = init.status;\n\t\tthis.code = init.code;\n\t\tthis.requestId = init.requestId;\n\t\tthis.response = init.response;\n\t\tthis.body = init.body;\n\t}\n}\n\n/** 404 — the resource does not exist. */\nexport class NeonNotFoundError extends NeonApiError {\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: ConstructorParameters<typeof NeonApiError>[1],\n\t) {\n\t\tsuper(message, { ...init, kind: \"not_found\" });\n\t\tthis.name = \"NeonNotFoundError\";\n\t}\n}\n\n/** 401/403 — the API key is missing, invalid, or lacks permission. */\nexport class NeonAuthError extends NeonApiError {\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: ConstructorParameters<typeof NeonApiError>[1],\n\t) {\n\t\tsuper(message, { ...init, kind: \"auth\" });\n\t\tthis.name = \"NeonAuthError\";\n\t}\n}\n\n/** 429 — rate limited (after retries, if enabled, were exhausted). */\nexport class NeonRateLimitError extends NeonApiError {\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: ConstructorParameters<typeof NeonApiError>[1],\n\t) {\n\t\tsuper(message, { ...init, kind: \"rate_limit\" });\n\t\tthis.name = \"NeonRateLimitError\";\n\t}\n}\n\n/** An awaited Neon operation ended in a non-success terminal state. */\nexport class NeonOperationError extends NeonError {\n\t/** The id of the operation that failed. */\n\treadonly operationId: string;\n\t/** The terminal status reported by the API (`failed` / `error` / `cancelled`). */\n\treadonly status: string;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tinit: { operationId: string; status: string },\n\t) {\n\t\tsuper(message, \"operation\");\n\t\tthis.name = \"NeonOperationError\";\n\t\tthis.operationId = init.operationId;\n\t\tthis.status = init.status;\n\t}\n}\n\n/** Waiting for operations to finish exceeded the configured timeout. */\nexport class NeonTimeoutError extends NeonError {\n\tconstructor(message: string) {\n\t\tsuper(message, \"timeout\");\n\t\tthis.name = \"NeonTimeoutError\";\n\t}\n}\n\n/** A transport-level failure (DNS, connection, abort) — no HTTP response received. */\nexport class NeonNetworkError extends NeonError {\n\t/**\n\t * The most specific reason the platform gave for the failure — an `errno` code such as\n\t * `ECONNRESET` when one is available, otherwise the innermost non-empty message. Read\n\t * this instead of matching on {@link message}.\n\t */\n\treadonly reason: string;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions?: { cause?: unknown; reason?: string },\n\t) {\n\t\tsuper(message, \"network\", { cause: options?.cause });\n\t\tthis.name = \"NeonNetworkError\";\n\t\tthis.reason = options?.reason ?? UNKNOWN_TRANSPORT_REASON;\n\t}\n}\n\ninterface ApiErrorBody {\n\tmessage?: string;\n\tcode?: string;\n\trequest_id?: string;\n}\n\nfunction readApiErrorBody(body: unknown): ApiErrorBody {\n\tif (typeof body !== \"object\" || body === null) return {};\n\tconst out: ApiErrorBody = {};\n\tif (\"message\" in body && typeof body.message === \"string\")\n\t\tout.message = body.message;\n\tif (\"code\" in body && typeof body.code === \"string\") out.code = body.code;\n\tif (\"request_id\" in body && typeof body.request_id === \"string\") {\n\t\tout.request_id = body.request_id;\n\t}\n\treturn out;\n}\n\n/**\n * Walk a transport failure's `cause` chain for the most specific description available.\n *\n * `fetch` reports every transport fault as `TypeError: fetch failed` and puts the real\n * reason underneath, sometimes several levels down and sometimes with an empty message and\n * only an `errno` code. Without this, a DNS failure, a reset connection and a redirect the\n * client refused to follow all produce the same sentence.\n */\nexport function describeTransportFailure(error: unknown): string {\n\tconst seen = new Set<unknown>();\n\tlet current: unknown = error;\n\tlet deepestMessage: string | undefined;\n\n\twhile (current instanceof Error && !seen.has(current)) {\n\t\tseen.add(current);\n\t\tif (\"code\" in current && typeof current.code === \"string\") {\n\t\t\treturn current.code;\n\t\t}\n\t\tif (current.message) deepestMessage = current.message;\n\t\tcurrent = current.cause;\n\t}\n\n\treturn deepestMessage ?? UNKNOWN_TRANSPORT_REASON;\n}\n\n/**\n * Build the right {@link NeonError} subclass from a raw client result. `error` is the\n * decoded error body (Neon `GeneralError`); `response` is present unless the failure was\n * transport-level.\n */\nexport function toNeonError(\n\terror: unknown,\n\tresponse: Response | undefined,\n): NeonError {\n\tif (!response) {\n\t\tconst reason = describeTransportFailure(error);\n\t\treturn new NeonNetworkError(\n\t\t\t`Network error: no response received from the Neon API (${reason}).`,\n\t\t\t{ cause: error, reason },\n\t\t);\n\t}\n\n\tconst parsed = readApiErrorBody(error);\n\tconst status = response.status;\n\tconst message =\n\t\tparsed.message ?? `Neon API request failed with status ${status}.`;\n\tconst init = {\n\t\tstatus,\n\t\tcode: parsed.code,\n\t\trequestId:\n\t\t\tparsed.request_id ??\n\t\t\tresponse.headers.get(\"x-request-id\") ??\n\t\t\tundefined,\n\t\tresponse,\n\t\tbody: error,\n\t};\n\n\tif (status === 404) return new NeonNotFoundError(message, init);\n\tif (status === 401 || status === 403)\n\t\treturn new NeonAuthError(message, init);\n\tif (status === 429) return new NeonRateLimitError(message, init);\n\treturn new NeonApiError(message, init);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neon/sdk",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "The official TypeScript SDK for the Neon API, generated from Neon's OpenAPI specification. A modern, fetch-based replacement for @neondatabase/api-client.",
5
5
  "keywords": [
6
6
  "neon",
@@ -52,7 +52,8 @@
52
52
  "console-fail-test": "0.5.0",
53
53
  "tsdown": "^0.14.1",
54
54
  "typescript": "^5.9.0",
55
- "vitest": "^3.0.9"
55
+ "vitest": "^3.0.9",
56
+ "@neon/e2e-harness": "0.0.0"
56
57
  },
57
58
  "engines": {
58
59
  "node": ">=20.19.0"