@nilovonjs/hcloud-js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +90 -0
  2. package/package.json +70 -0
  3. package/src/apis/actions/index.ts +113 -0
  4. package/src/apis/actions/schemas.ts +59 -0
  5. package/src/apis/actions/types.ts +77 -0
  6. package/src/apis/certificates/index.ts +326 -0
  7. package/src/apis/certificates/schemas.ts +140 -0
  8. package/src/apis/certificates/types.ts +176 -0
  9. package/src/apis/common/schemas.ts +19 -0
  10. package/src/apis/dns/index.ts +961 -0
  11. package/src/apis/dns/schemas.ts +437 -0
  12. package/src/apis/dns/types.ts +397 -0
  13. package/src/apis/firewalls/index.ts +469 -0
  14. package/src/apis/firewalls/schemas.ts +274 -0
  15. package/src/apis/firewalls/types.ts +205 -0
  16. package/src/apis/floating-ips/index.ts +466 -0
  17. package/src/apis/floating-ips/schemas.ts +203 -0
  18. package/src/apis/floating-ips/types.ts +207 -0
  19. package/src/apis/images/index.ts +195 -0
  20. package/src/apis/images/schemas.ts +113 -0
  21. package/src/apis/images/types.ts +124 -0
  22. package/src/apis/isos/index.ts +91 -0
  23. package/src/apis/isos/schemas.ts +43 -0
  24. package/src/apis/isos/types.ts +60 -0
  25. package/src/apis/load-balancers/index.ts +892 -0
  26. package/src/apis/load-balancers/schemas.ts +561 -0
  27. package/src/apis/load-balancers/types.ts +361 -0
  28. package/src/apis/locations/index.ts +176 -0
  29. package/src/apis/locations/schemas.ts +83 -0
  30. package/src/apis/locations/types.ts +113 -0
  31. package/src/apis/networks/index.ts +544 -0
  32. package/src/apis/networks/schemas.ts +279 -0
  33. package/src/apis/networks/types.ts +243 -0
  34. package/src/apis/placement-groups/index.ts +212 -0
  35. package/src/apis/placement-groups/schemas.ts +90 -0
  36. package/src/apis/placement-groups/types.ts +99 -0
  37. package/src/apis/pricing/index.ts +42 -0
  38. package/src/apis/pricing/schemas.ts +93 -0
  39. package/src/apis/pricing/types.ts +71 -0
  40. package/src/apis/primary-ips/index.ts +467 -0
  41. package/src/apis/primary-ips/schemas.ts +221 -0
  42. package/src/apis/primary-ips/types.ts +221 -0
  43. package/src/apis/server-types/index.ts +93 -0
  44. package/src/apis/server-types/schemas.ts +29 -0
  45. package/src/apis/server-types/types.ts +43 -0
  46. package/src/apis/servers/index.ts +378 -0
  47. package/src/apis/servers/schemas.ts +771 -0
  48. package/src/apis/servers/types.ts +538 -0
  49. package/src/apis/ssh-keys/index.ts +204 -0
  50. package/src/apis/ssh-keys/schemas.ts +84 -0
  51. package/src/apis/ssh-keys/types.ts +106 -0
  52. package/src/apis/volumes/index.ts +452 -0
  53. package/src/apis/volumes/schemas.ts +195 -0
  54. package/src/apis/volumes/types.ts +197 -0
  55. package/src/auth/index.ts +26 -0
  56. package/src/base/index.ts +10 -0
  57. package/src/client/index.ts +388 -0
  58. package/src/config/index.ts +34 -0
  59. package/src/errors/index.ts +38 -0
  60. package/src/index.ts +799 -0
  61. package/src/types/index.ts +37 -0
  62. package/src/validation/index.ts +109 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Types for Hetzner Cloud Volumes API
3
+ * Types are inferred from Zod schemas
4
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes
5
+ */
6
+
7
+ // biome-ignore assist/source/organizeImports: we need to import the schemas first
8
+ import {
9
+ listVolumesResponseSchema,
10
+ createVolumeRequestSchema,
11
+ createVolumeResponseSchema,
12
+ getVolumeResponseSchema,
13
+ updateVolumeRequestSchema,
14
+ updateVolumeResponseSchema,
15
+ deleteVolumeResponseSchema,
16
+ listVolumeActionsResponseSchema,
17
+ getVolumeActionResponseSchema,
18
+ attachVolumeToServerRequestSchema,
19
+ attachVolumeToServerResponseSchema,
20
+ detachVolumeRequestSchema,
21
+ detachVolumeResponseSchema,
22
+ resizeVolumeRequestSchema,
23
+ resizeVolumeResponseSchema,
24
+ changeVolumeProtectionRequestSchema,
25
+ changeVolumeProtectionResponseSchema,
26
+ volumeSchema,
27
+ volumeStatusSchema,
28
+ volumeProtectionSchema,
29
+ } from "../../apis/volumes/schemas";
30
+ import type { z } from "zod";
31
+
32
+ /**
33
+ * Volume status
34
+ */
35
+ export type VolumeStatus = z.infer<typeof volumeStatusSchema>;
36
+
37
+ /**
38
+ * Volume protection
39
+ */
40
+ export type VolumeProtection = z.infer<typeof volumeProtectionSchema>;
41
+
42
+ /**
43
+ * Volume
44
+ */
45
+ export type Volume = z.infer<typeof volumeSchema>;
46
+
47
+ /**
48
+ * List Volumes query parameters
49
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-list-volumes
50
+ */
51
+ export interface ListVolumesParams {
52
+ /**
53
+ * Can be used to filter resources by their name. The response will only contain the resources matching the specified name.
54
+ */
55
+ name?: string;
56
+ /**
57
+ * Can be used multiple times. Choices: id, id:asc, id:desc, name, name:asc, name:desc, created, created:asc, created:desc
58
+ * @see https://docs.hetzner.cloud/reference/cloud#sorting
59
+ */
60
+ sort?: string | string[];
61
+ /**
62
+ * Can be used to filter resources by labels. The response will only contain resources matching the label selector.
63
+ */
64
+ label_selector?: string;
65
+ /**
66
+ * Page number to return. For more information, see [Pagination](https://docs.hetzner.cloud/reference/cloud#pagination).
67
+ */
68
+ page?: number;
69
+ /**
70
+ * Maximum number of entries returned per page. For more information, see [Pagination](https://docs.hetzner.cloud/reference/cloud#pagination).
71
+ */
72
+ per_page?: number;
73
+ }
74
+
75
+ /**
76
+ * List Volumes response
77
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-list-volumes
78
+ */
79
+ export type ListVolumesResponse = z.infer<typeof listVolumesResponseSchema>;
80
+
81
+ /**
82
+ * Create Volume parameters
83
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-create-a-volume
84
+ */
85
+ export type CreateVolumeParams = z.infer<typeof createVolumeRequestSchema>;
86
+
87
+ /**
88
+ * Create Volume response
89
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-create-a-volume
90
+ */
91
+ export type CreateVolumeResponse = z.infer<typeof createVolumeResponseSchema>;
92
+
93
+ /**
94
+ * Get Volume response
95
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-get-a-volume
96
+ */
97
+ export type GetVolumeResponse = z.infer<typeof getVolumeResponseSchema>;
98
+
99
+ /**
100
+ * Update Volume parameters
101
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-update-a-volume
102
+ */
103
+ export type UpdateVolumeParams = z.infer<typeof updateVolumeRequestSchema>;
104
+
105
+ /**
106
+ * Update Volume response
107
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-update-a-volume
108
+ */
109
+ export type UpdateVolumeResponse = z.infer<typeof updateVolumeResponseSchema>;
110
+
111
+ /**
112
+ * Delete Volume response
113
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-delete-a-volume
114
+ */
115
+ export type DeleteVolumeResponse = z.infer<typeof deleteVolumeResponseSchema>;
116
+
117
+ /**
118
+ * List Volume Actions query parameters
119
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-list-actions-for-a-volume
120
+ */
121
+ export interface ListVolumeActionsParams {
122
+ /**
123
+ * Can be used multiple times. Choices: id, id:asc, id:desc, command, command:asc, command:desc, status, status:asc, status:desc, progress, progress:asc, progress:desc, started, started:asc, started:desc, finished, finished:asc, finished:desc
124
+ * @see https://docs.hetzner.cloud/reference/cloud#sorting
125
+ */
126
+ sort?: string | string[];
127
+ /**
128
+ * Can be used to filter Actions by status. The response will only contain Actions matching the status.
129
+ */
130
+ status?: string | string[];
131
+ /**
132
+ * Page number to return. For more information, see [Pagination](https://docs.hetzner.cloud/reference/cloud#pagination).
133
+ */
134
+ page?: number;
135
+ /**
136
+ * Maximum number of entries returned per page. For more information, see [Pagination](https://docs.hetzner.cloud/reference/cloud#pagination).
137
+ */
138
+ per_page?: number;
139
+ }
140
+
141
+ /**
142
+ * List Volume Actions response
143
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-list-actions-for-a-volume
144
+ */
145
+ export type ListVolumeActionsResponse = z.infer<typeof listVolumeActionsResponseSchema>;
146
+
147
+ /**
148
+ * Get Volume Action response
149
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-get-an-action-for-a-volume
150
+ */
151
+ export type GetVolumeActionResponse = z.infer<typeof getVolumeActionResponseSchema>;
152
+
153
+ /**
154
+ * Attach Volume to Server parameters
155
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-attach-volume-to-a-server
156
+ */
157
+ export type AttachVolumeToServerParams = z.infer<typeof attachVolumeToServerRequestSchema>;
158
+
159
+ /**
160
+ * Attach Volume to Server response
161
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-attach-volume-to-a-server
162
+ */
163
+ export type AttachVolumeToServerResponse = z.infer<typeof attachVolumeToServerResponseSchema>;
164
+
165
+ /**
166
+ * Detach Volume response
167
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-detach-volume
168
+ */
169
+ export type DetachVolumeResponse = z.infer<typeof detachVolumeResponseSchema>;
170
+
171
+ /**
172
+ * Resize Volume parameters
173
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-resize-volume
174
+ */
175
+ export type ResizeVolumeParams = z.infer<typeof resizeVolumeRequestSchema>;
176
+
177
+ /**
178
+ * Resize Volume response
179
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-resize-volume
180
+ */
181
+ export type ResizeVolumeResponse = z.infer<typeof resizeVolumeResponseSchema>;
182
+
183
+ /**
184
+ * Change Volume Protection parameters
185
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-change-volume-protection
186
+ */
187
+ export type ChangeVolumeProtectionParams = z.infer<
188
+ typeof changeVolumeProtectionRequestSchema
189
+ >;
190
+
191
+ /**
192
+ * Change Volume Protection response
193
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes-change-volume-protection
194
+ */
195
+ export type ChangeVolumeProtectionResponse = z.infer<
196
+ typeof changeVolumeProtectionResponseSchema
197
+ >;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Authentication for Hetzner Cloud API
3
+ * @see https://docs.hetzner.cloud/reference/cloud#authentication
4
+ */
5
+
6
+ /**
7
+ * Creates the Authorization header with Bearer token
8
+ * for Hetzner Cloud API requests.
9
+ *
10
+ * The API uses Bearer token authentication. Every request must include
11
+ * the Authorization header with your API token.
12
+ *
13
+ * @param token - Hetzner Cloud API token
14
+ * @returns Authorization header object
15
+ * @throws {Error} If token is not provided
16
+ * @see https://docs.hetzner.cloud/reference/cloud#authentication
17
+ */
18
+ export function createAuthHeader(token: string): { Authorization: string } {
19
+ if (!token || token.trim().length === 0) {
20
+ throw new Error("API token is required for Hetzner Cloud API authentication");
21
+ }
22
+
23
+ return {
24
+ Authorization: `Bearer ${token}`,
25
+ };
26
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @deprecated Use exports from root level instead
3
+ * This file is kept for backward compatibility but will be removed in a future version.
4
+ */
5
+
6
+ export * from "../client/index";
7
+ export * from "../errors/index";
8
+ export * from "../types/index";
9
+ export * from "../config/index";
10
+ export * from "../auth/index";
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Hetzner Cloud API Client
3
+ * @see https://docs.hetzner.cloud/reference/cloud
4
+ */
5
+
6
+ import type { RequestOptions } from "../types/index";
7
+ import type { ClientOptions } from "../config/index";
8
+ import { HCLOUD_API_BASE_URL, DEFAULT_TIMEOUT_MS } from "../config/index";
9
+ import { createAuthHeader } from "../auth/index";
10
+ import { HCloudError } from "../errors/index";
11
+ import type { ApiErrorResponse } from "../types/index";
12
+ import { ServersClient } from "../apis/servers/index";
13
+ import { ImagesClient } from "../apis/images/index";
14
+ import { ActionsClient } from "../apis/actions/index";
15
+ import { CertificatesClient } from "../apis/certificates/index";
16
+ import { SSHKeysClient } from "../apis/ssh-keys/index";
17
+ import { LocationsClient } from "../apis/locations/index";
18
+ import { FirewallsClient } from "../apis/firewalls/index";
19
+ import { FloatingIPsClient } from "../apis/floating-ips/index";
20
+ import { ISOsClient } from "../apis/isos/index";
21
+ import { PlacementGroupsClient } from "../apis/placement-groups/index";
22
+ import { PrimaryIPsClient } from "../apis/primary-ips/index";
23
+ import { ServerTypesClient } from "../apis/server-types/index";
24
+ import { LoadBalancersClient } from "../apis/load-balancers/index";
25
+ import { NetworksClient } from "../apis/networks/index";
26
+ import { PricingClient } from "../apis/pricing/index";
27
+ import { VolumesClient } from "../apis/volumes/index";
28
+ import { DNSClient } from "../apis/dns/index";
29
+ import type { HeadersInit } from "bun";
30
+
31
+ /**
32
+ * Hetzner Cloud API Client
33
+ *
34
+ * This is the main client class for interacting with the Hetzner Cloud API.
35
+ * It handles authentication, request/response processing, and error handling.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * import { HCloudClient } from '@hcloud-js/library';
40
+ *
41
+ * const client = new HCloudClient({
42
+ * token: 'your-api-token'
43
+ * });
44
+ *
45
+ * const servers = await client.get('/servers');
46
+ * ```
47
+ */
48
+ export class HCloudClient {
49
+ private readonly token: string;
50
+ private readonly baseUrl: string;
51
+ private readonly timeout: number;
52
+
53
+ /**
54
+ * Servers API client
55
+ * @see https://docs.hetzner.cloud/reference/cloud#servers
56
+ */
57
+ public readonly servers: ServersClient;
58
+
59
+ /**
60
+ * Images API client
61
+ * @see https://docs.hetzner.cloud/reference/cloud#images
62
+ */
63
+ public readonly images: ImagesClient;
64
+
65
+ /**
66
+ * Actions API client
67
+ * @see https://docs.hetzner.cloud/reference/cloud#actions
68
+ */
69
+ public readonly actions: ActionsClient;
70
+
71
+ /**
72
+ * Certificates API client
73
+ * @see https://docs.hetzner.cloud/reference/cloud#certificates
74
+ */
75
+ public readonly certificates: CertificatesClient;
76
+
77
+ /**
78
+ * SSH Keys API client
79
+ * @see https://docs.hetzner.cloud/reference/cloud#ssh-keys
80
+ */
81
+ public readonly sshKeys: SSHKeysClient;
82
+
83
+ /**
84
+ * Locations API client
85
+ * @see https://docs.hetzner.cloud/reference/cloud#locations
86
+ */
87
+ public readonly locations: LocationsClient;
88
+
89
+ /**
90
+ * Firewalls API client
91
+ * @see https://docs.hetzner.cloud/reference/cloud#firewalls
92
+ */
93
+ public readonly firewalls: FirewallsClient;
94
+
95
+ /**
96
+ * Floating IPs API client
97
+ * @see https://docs.hetzner.cloud/reference/cloud#floating-ips
98
+ */
99
+ public readonly floatingIPs: FloatingIPsClient;
100
+
101
+ /**
102
+ * ISOs API client
103
+ * @see https://docs.hetzner.cloud/reference/cloud#isos
104
+ */
105
+ public readonly isos: ISOsClient;
106
+
107
+ /**
108
+ * Placement Groups API client
109
+ * @see https://docs.hetzner.cloud/reference/cloud#placement-groups
110
+ */
111
+ public readonly placementGroups: PlacementGroupsClient;
112
+
113
+ /**
114
+ * Primary IPs API client
115
+ * @see https://docs.hetzner.cloud/reference/cloud#primary-ips
116
+ */
117
+ public readonly primaryIPs: PrimaryIPsClient;
118
+
119
+ /**
120
+ * Server Types API client
121
+ * @see https://docs.hetzner.cloud/reference/cloud#server-types
122
+ */
123
+ public readonly serverTypes: ServerTypesClient;
124
+
125
+ /**
126
+ * Load Balancers API client
127
+ * @see https://docs.hetzner.cloud/reference/cloud#load-balancers
128
+ */
129
+ public readonly loadBalancers: LoadBalancersClient;
130
+
131
+ /**
132
+ * Networks API client
133
+ * @see https://docs.hetzner.cloud/reference/cloud#networks
134
+ */
135
+ public readonly networks: NetworksClient;
136
+
137
+ /**
138
+ * Pricing API client
139
+ * @see https://docs.hetzner.cloud/reference/cloud#pricing
140
+ */
141
+ public readonly pricing: PricingClient;
142
+
143
+ /**
144
+ * Volumes API client
145
+ * @see https://docs.hetzner.cloud/reference/cloud#volumes
146
+ */
147
+ public readonly volumes: VolumesClient;
148
+
149
+ /**
150
+ * DNS (Zones) API client
151
+ * @see https://docs.hetzner.cloud/reference/cloud#dns
152
+ */
153
+ public readonly dns: DNSClient;
154
+
155
+ /**
156
+ * Creates a new Hetzner Cloud API client
157
+ *
158
+ * @param options - Client configuration options
159
+ * @throws {HCloudError} If token is not provided
160
+ */
161
+ constructor(options: ClientOptions) {
162
+ if (!options.token || options.token.trim().length === 0) {
163
+ throw new HCloudError("API token is required", "INVALID_TOKEN");
164
+ }
165
+
166
+ this.token = options.token;
167
+ this.baseUrl = options.baseUrl ?? HCLOUD_API_BASE_URL;
168
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
169
+
170
+ // Initialize API clients
171
+ this.servers = new ServersClient(this);
172
+ this.images = new ImagesClient(this);
173
+ this.actions = new ActionsClient(this);
174
+ this.certificates = new CertificatesClient(this);
175
+ this.sshKeys = new SSHKeysClient(this);
176
+ this.locations = new LocationsClient(this);
177
+ this.firewalls = new FirewallsClient(this);
178
+ this.floatingIPs = new FloatingIPsClient(this);
179
+ this.isos = new ISOsClient(this);
180
+ this.placementGroups = new PlacementGroupsClient(this);
181
+ this.primaryIPs = new PrimaryIPsClient(this);
182
+ this.serverTypes = new ServerTypesClient(this);
183
+ this.loadBalancers = new LoadBalancersClient(this);
184
+ this.networks = new NetworksClient(this);
185
+ this.pricing = new PricingClient(this);
186
+ this.volumes = new VolumesClient(this);
187
+ this.dns = new DNSClient(this);
188
+ }
189
+
190
+ /**
191
+ * Make an HTTP request to the Hetzner Cloud API
192
+ *
193
+ * @param options - Request options
194
+ * @returns Promise resolving to the response data
195
+ * @throws {HCloudError} If the request fails
196
+ */
197
+ async request<T>(options: RequestOptions): Promise<T> {
198
+ const { method = "GET", path, body, params, headers = {} } = options;
199
+
200
+ // Build URL with query parameters
201
+ const baseUrlWithoutTrailingSlash = this.baseUrl.replace(/\/$/, "");
202
+ const pathWithoutLeadingSlash = path.replace(/^\//, "");
203
+ const url = new URL(`${baseUrlWithoutTrailingSlash}/${pathWithoutLeadingSlash}`);
204
+
205
+ if (params) {
206
+ Object.entries(params).forEach(([key, value]) => {
207
+ if (value !== undefined && value !== null) {
208
+ // Handle array values (e.g., sort, status) - append each value separately
209
+ if (Array.isArray(value)) {
210
+ value.forEach((item) => {
211
+ url.searchParams.append(key, String(item));
212
+ });
213
+ } else {
214
+ url.searchParams.append(key, String(value));
215
+ }
216
+ }
217
+ });
218
+ }
219
+
220
+ // Prepare request headers with authentication
221
+ const authHeader = createAuthHeader(this.token);
222
+ const requestHeaders: HeadersInit = {
223
+ ...authHeader,
224
+ "Content-Type": "application/json",
225
+ "User-Agent": "hcloud-ts",
226
+ ...headers,
227
+ };
228
+
229
+ // Prepare fetch options
230
+ const fetchOptions: RequestInit = {
231
+ method,
232
+ headers: requestHeaders,
233
+ signal: AbortSignal.timeout(this.timeout),
234
+ };
235
+
236
+ // Add body for non-GET requests
237
+ if (body !== undefined && method !== "GET") {
238
+ fetchOptions.body = JSON.stringify(body);
239
+ }
240
+
241
+ try {
242
+ const response = await fetch(url.toString(), fetchOptions);
243
+
244
+ // Handle error responses
245
+ if (!response.ok) {
246
+ await this.handleErrorResponse(response);
247
+ }
248
+
249
+ // Handle empty responses (e.g., 204 No Content)
250
+ if (response.status === 204 || response.headers.get("content-length") === "0") {
251
+ return undefined as T;
252
+ }
253
+
254
+ // Parse JSON response
255
+ return (await response.json()) as T;
256
+ } catch (error: unknown) {
257
+ // Re-throw HCloudError as-is
258
+ if (error instanceof HCloudError) {
259
+ throw error;
260
+ }
261
+
262
+ // Handle fetch errors (network, timeout, etc.)
263
+ this.handleFetchError(error);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Handle error responses from the API
269
+ */
270
+ private async handleErrorResponse(response: Response): Promise<never> {
271
+ let errorMessage = `HTTP ${response.status} ${response.statusText}`;
272
+ let errorCode: string | undefined;
273
+ let errorDetails: ApiErrorResponse["error"]["details"];
274
+
275
+ try {
276
+ const errorData = (await response.json()) as ApiErrorResponse;
277
+ if (errorData.error) {
278
+ errorMessage = errorData.error.message || errorMessage;
279
+ errorCode = errorData.error.code;
280
+ errorDetails = errorData.error.details;
281
+ }
282
+ } catch {
283
+ // If error response is not JSON, use default message
284
+ }
285
+
286
+ throw new HCloudError(errorMessage, errorCode, response.status, errorDetails);
287
+ }
288
+
289
+ /**
290
+ * Handle fetch errors (network, timeout, etc.)
291
+ */
292
+ private handleFetchError(error: unknown): never {
293
+ if (error instanceof Error) {
294
+ if (error.name === "AbortError" || error.message.includes("timeout")) {
295
+ throw new HCloudError(`Request timeout after ${this.timeout}ms`, "TIMEOUT", 0);
296
+ }
297
+ throw new HCloudError(`Request failed: ${error.message}`, "NETWORK_ERROR", 0);
298
+ }
299
+
300
+ throw new HCloudError("Unknown error occurred", "UNKNOWN_ERROR", 0);
301
+ }
302
+
303
+ /**
304
+ * GET request
305
+ *
306
+ * @param path - API endpoint path
307
+ * @param params - Query parameters
308
+ * @param headers - Additional headers
309
+ * @returns Promise resolving to the response data
310
+ */
311
+ async get<T>(
312
+ path: string,
313
+ params?: RequestOptions["params"],
314
+ headers?: RequestOptions["headers"],
315
+ ): Promise<T> {
316
+ return this.request<T>({ method: "GET", path, params, headers });
317
+ }
318
+
319
+ /**
320
+ * POST request
321
+ *
322
+ * @param path - API endpoint path
323
+ * @param body - Request body
324
+ * @param params - Query parameters
325
+ * @param headers - Additional headers
326
+ * @returns Promise resolving to the response data
327
+ */
328
+ async post<T>(
329
+ path: string,
330
+ body?: unknown,
331
+ params?: RequestOptions["params"],
332
+ headers?: RequestOptions["headers"],
333
+ ): Promise<T> {
334
+ return this.request<T>({ method: "POST", path, body, params, headers });
335
+ }
336
+
337
+ /**
338
+ * PUT request
339
+ *
340
+ * @param path - API endpoint path
341
+ * @param body - Request body
342
+ * @param params - Query parameters
343
+ * @param headers - Additional headers
344
+ * @returns Promise resolving to the response data
345
+ */
346
+ async put<T>(
347
+ path: string,
348
+ body?: unknown,
349
+ params?: RequestOptions["params"],
350
+ headers?: RequestOptions["headers"],
351
+ ): Promise<T> {
352
+ return this.request<T>({ method: "PUT", path, body, params, headers });
353
+ }
354
+
355
+ /**
356
+ * PATCH request
357
+ *
358
+ * @param path - API endpoint path
359
+ * @param body - Request body
360
+ * @param params - Query parameters
361
+ * @param headers - Additional headers
362
+ * @returns Promise resolving to the response data
363
+ */
364
+ async patch<T>(
365
+ path: string,
366
+ body?: unknown,
367
+ params?: RequestOptions["params"],
368
+ headers?: RequestOptions["headers"],
369
+ ): Promise<T> {
370
+ return this.request<T>({ method: "PATCH", path, body, params, headers });
371
+ }
372
+
373
+ /**
374
+ * DELETE request
375
+ *
376
+ * @param path - API endpoint path
377
+ * @param params - Query parameters
378
+ * @param headers - Additional headers
379
+ * @returns Promise resolving to the response data
380
+ */
381
+ async delete<T>(
382
+ path: string,
383
+ params?: RequestOptions["params"],
384
+ headers?: RequestOptions["headers"],
385
+ ): Promise<T> {
386
+ return this.request<T>({ method: "DELETE", path, params, headers });
387
+ }
388
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Configuration for Hetzner Cloud API Client
3
+ */
4
+
5
+ /**
6
+ * Default base URL for Hetzner Cloud API
7
+ * @see https://docs.hetzner.cloud/
8
+ */
9
+ export const HCLOUD_API_BASE_URL = "https://api.hetzner.cloud/v1";
10
+
11
+ /**
12
+ * Default request timeout in milliseconds
13
+ */
14
+ export const DEFAULT_TIMEOUT_MS = 30000;
15
+
16
+ /**
17
+ * Client configuration options
18
+ */
19
+ export interface ClientOptions {
20
+ /**
21
+ * Hetzner Cloud API token (Bearer token)
22
+ * Get your API token from: https://console.hetzner.cloud/
23
+ * @see https://docs.hetzner.cloud/reference/cloud#authentication
24
+ */
25
+ token: string;
26
+ /**
27
+ * Custom base URL (defaults to official Hetzner Cloud API)
28
+ */
29
+ baseUrl?: string;
30
+ /**
31
+ * Request timeout in milliseconds (defaults to 30000)
32
+ */
33
+ timeout?: number;
34
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Error handling for Hetzner Cloud API
3
+ * @see https://docs.hetzner.cloud/reference/cloud#errors
4
+ */
5
+
6
+ import type { ApiErrorResponse } from "../types/index";
7
+
8
+ /**
9
+ * Hetzner Cloud API Error
10
+ *
11
+ * All API errors follow a consistent format with a code and message.
12
+ * @see https://docs.hetzner.cloud/reference/cloud#errors
13
+ */
14
+ export class HCloudError extends Error {
15
+ constructor(
16
+ message: string,
17
+ public readonly code?: string,
18
+ public readonly statusCode?: number,
19
+ public readonly details?: ApiErrorResponse["error"]["details"],
20
+ ) {
21
+ super(message);
22
+ this.name = "HCloudError";
23
+ }
24
+
25
+ /**
26
+ * Check if error has field validation errors
27
+ */
28
+ hasFieldErrors(): boolean {
29
+ return Boolean(this.details?.fields && this.details.fields.length > 0);
30
+ }
31
+
32
+ /**
33
+ * Get field errors if available
34
+ */
35
+ getFieldErrors() {
36
+ return this.details?.fields ?? [];
37
+ }
38
+ }