@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.
- package/README.md +90 -0
- package/package.json +70 -0
- package/src/apis/actions/index.ts +113 -0
- package/src/apis/actions/schemas.ts +59 -0
- package/src/apis/actions/types.ts +77 -0
- package/src/apis/certificates/index.ts +326 -0
- package/src/apis/certificates/schemas.ts +140 -0
- package/src/apis/certificates/types.ts +176 -0
- package/src/apis/common/schemas.ts +19 -0
- package/src/apis/dns/index.ts +961 -0
- package/src/apis/dns/schemas.ts +437 -0
- package/src/apis/dns/types.ts +397 -0
- package/src/apis/firewalls/index.ts +469 -0
- package/src/apis/firewalls/schemas.ts +274 -0
- package/src/apis/firewalls/types.ts +205 -0
- package/src/apis/floating-ips/index.ts +466 -0
- package/src/apis/floating-ips/schemas.ts +203 -0
- package/src/apis/floating-ips/types.ts +207 -0
- package/src/apis/images/index.ts +195 -0
- package/src/apis/images/schemas.ts +113 -0
- package/src/apis/images/types.ts +124 -0
- package/src/apis/isos/index.ts +91 -0
- package/src/apis/isos/schemas.ts +43 -0
- package/src/apis/isos/types.ts +60 -0
- package/src/apis/load-balancers/index.ts +892 -0
- package/src/apis/load-balancers/schemas.ts +561 -0
- package/src/apis/load-balancers/types.ts +361 -0
- package/src/apis/locations/index.ts +176 -0
- package/src/apis/locations/schemas.ts +83 -0
- package/src/apis/locations/types.ts +113 -0
- package/src/apis/networks/index.ts +544 -0
- package/src/apis/networks/schemas.ts +279 -0
- package/src/apis/networks/types.ts +243 -0
- package/src/apis/placement-groups/index.ts +212 -0
- package/src/apis/placement-groups/schemas.ts +90 -0
- package/src/apis/placement-groups/types.ts +99 -0
- package/src/apis/pricing/index.ts +42 -0
- package/src/apis/pricing/schemas.ts +93 -0
- package/src/apis/pricing/types.ts +71 -0
- package/src/apis/primary-ips/index.ts +467 -0
- package/src/apis/primary-ips/schemas.ts +221 -0
- package/src/apis/primary-ips/types.ts +221 -0
- package/src/apis/server-types/index.ts +93 -0
- package/src/apis/server-types/schemas.ts +29 -0
- package/src/apis/server-types/types.ts +43 -0
- package/src/apis/servers/index.ts +378 -0
- package/src/apis/servers/schemas.ts +771 -0
- package/src/apis/servers/types.ts +538 -0
- package/src/apis/ssh-keys/index.ts +204 -0
- package/src/apis/ssh-keys/schemas.ts +84 -0
- package/src/apis/ssh-keys/types.ts +106 -0
- package/src/apis/volumes/index.ts +452 -0
- package/src/apis/volumes/schemas.ts +195 -0
- package/src/apis/volumes/types.ts +197 -0
- package/src/auth/index.ts +26 -0
- package/src/base/index.ts +10 -0
- package/src/client/index.ts +388 -0
- package/src/config/index.ts +34 -0
- package/src/errors/index.ts +38 -0
- package/src/index.ts +799 -0
- package/src/types/index.ts +37 -0
- 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
|
+
}
|