@ebowwa/hetzner 0.1.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 (46) hide show
  1. package/actions.js +802 -0
  2. package/actions.ts +1053 -0
  3. package/auth.js +35 -0
  4. package/auth.ts +37 -0
  5. package/bootstrap/FIREWALL.md +326 -0
  6. package/bootstrap/KERNEL-HARDENING.md +258 -0
  7. package/bootstrap/SECURITY-INTEGRATION.md +281 -0
  8. package/bootstrap/TESTING.md +301 -0
  9. package/bootstrap/cloud-init.js +279 -0
  10. package/bootstrap/cloud-init.ts +394 -0
  11. package/bootstrap/firewall.js +279 -0
  12. package/bootstrap/firewall.ts +342 -0
  13. package/bootstrap/genesis.js +406 -0
  14. package/bootstrap/genesis.ts +518 -0
  15. package/bootstrap/index.js +35 -0
  16. package/bootstrap/index.ts +71 -0
  17. package/bootstrap/kernel-hardening.js +266 -0
  18. package/bootstrap/kernel-hardening.test.ts +230 -0
  19. package/bootstrap/kernel-hardening.ts +272 -0
  20. package/bootstrap/security-audit.js +118 -0
  21. package/bootstrap/security-audit.ts +124 -0
  22. package/bootstrap/ssh-hardening.js +182 -0
  23. package/bootstrap/ssh-hardening.ts +192 -0
  24. package/client.js +137 -0
  25. package/client.ts +177 -0
  26. package/config.js +5 -0
  27. package/config.ts +5 -0
  28. package/errors.js +270 -0
  29. package/errors.ts +371 -0
  30. package/index.js +28 -0
  31. package/index.ts +55 -0
  32. package/package.json +56 -0
  33. package/pricing.js +284 -0
  34. package/pricing.ts +422 -0
  35. package/schemas.js +660 -0
  36. package/schemas.ts +765 -0
  37. package/server-status.ts +81 -0
  38. package/servers.js +424 -0
  39. package/servers.ts +568 -0
  40. package/ssh-keys.js +90 -0
  41. package/ssh-keys.ts +122 -0
  42. package/ssh-setup.ts +218 -0
  43. package/types.js +96 -0
  44. package/types.ts +389 -0
  45. package/volumes.js +172 -0
  46. package/volumes.ts +229 -0
package/schemas.js ADDED
@@ -0,0 +1,660 @@
1
+ /**
2
+ * Hetzner Cloud API Zod validation schemas
3
+ *
4
+ * These schemas provide runtime validation for API responses
5
+ * and help ensure type safety throughout the application.
6
+ */
7
+ import { z } from "zod";
8
+ // Import status enums to use in schema validation
9
+ import { EnvironmentStatus, ActionStatus, VolumeStatus, } from "@ebowwa/codespaces-types/compile";
10
+ // ============================================================================
11
+ // Helper Validators
12
+ // ============================================================================
13
+ /**
14
+ * IPv4 address validator
15
+ */
16
+ const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
17
+ /**
18
+ * IPv6 address validator
19
+ * Hetzner returns IPv6 in CIDR notation which can vary in format
20
+ * Using a more lenient pattern since exact format varies
21
+ */
22
+ const ipv6Regex = /^[0-9a-fA-F:]+(?:\/\d{1,3})?$/;
23
+ /**
24
+ * IP address validator (IPv4 or IPv6)
25
+ */
26
+ const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
27
+ // ============================================================================
28
+ // Base Schemas
29
+ // ============================================================================
30
+ /**
31
+ * Hetzner API error response schema
32
+ */
33
+ export const HetznerErrorSchema = z.object({
34
+ code: z.string(),
35
+ message: z.string(),
36
+ details: z.any().optional(),
37
+ });
38
+ /**
39
+ * Pagination metadata schema
40
+ */
41
+ export const HetznerPaginationSchema = z.object({
42
+ page: z.number(),
43
+ per_page: z.number(),
44
+ previous_page: z.number().nullable(),
45
+ next_page: z.number().nullable(),
46
+ last_page: z.number(),
47
+ total_entries: z.number(),
48
+ });
49
+ /**
50
+ * Metadata wrapper schema
51
+ */
52
+ export const HetznerMetaSchema = z.object({
53
+ pagination: HetznerPaginationSchema.optional(),
54
+ });
55
+ // ============================================================================
56
+ // Action Schemas
57
+ // ============================================================================
58
+ /**
59
+ * Action resource schema
60
+ */
61
+ export const HetznerActionResourceSchema = z.object({
62
+ id: z.number(),
63
+ type: z.enum([
64
+ "server",
65
+ "volume",
66
+ "network",
67
+ "floating_ip",
68
+ "load_balancer",
69
+ "certificate",
70
+ "firewall",
71
+ "image",
72
+ ]),
73
+ });
74
+ /**
75
+ * Action error schema
76
+ */
77
+ export const HetznerActionErrorSchema = z.object({
78
+ code: z.string(),
79
+ message: z.string(),
80
+ });
81
+ /**
82
+ * Base action schema
83
+ */
84
+ export const HetznerActionSchema = z.object({
85
+ id: z.number(),
86
+ command: z.string(), // API returns string, not ActionCommand enum
87
+ status: z.nativeEnum(ActionStatus),
88
+ started: z.string(),
89
+ finished: z.string().nullable(),
90
+ progress: z.number().min(0).max(100),
91
+ resources: z.array(HetznerActionResourceSchema),
92
+ error: HetznerActionErrorSchema.nullable(),
93
+ });
94
+ /**
95
+ * Action response schema
96
+ */
97
+ export const HetznerActionResponseSchema = z.object({
98
+ action: HetznerActionSchema,
99
+ });
100
+ /**
101
+ * Actions list response schema
102
+ */
103
+ export const HetznerActionsResponseSchema = z.object({
104
+ actions: z.array(HetznerActionSchema),
105
+ meta: HetznerMetaSchema,
106
+ });
107
+ // ============================================================================
108
+ // Server Schemas
109
+ // ============================================================================
110
+ /**
111
+ * Server image schema
112
+ */
113
+ export const HetznerServerImageSchema = z.object({
114
+ id: z.number(),
115
+ name: z.string(),
116
+ description: z.string(),
117
+ type: z.enum(["snapshot", "backup", "system"]),
118
+ });
119
+ /**
120
+ * Server IPv4 schema
121
+ */
122
+ export const HetznerIPv4Schema = z.object({
123
+ ip: z.string().regex(ipv4Regex),
124
+ blocked: z.boolean(),
125
+ });
126
+ /**
127
+ * Server IPv6 schema
128
+ */
129
+ export const HetznerIPv6Schema = z.object({
130
+ ip: z.string().regex(ipv6Regex),
131
+ blocked: z.boolean(),
132
+ });
133
+ /**
134
+ * Server floating IP reference schema
135
+ */
136
+ export const HetznerFloatingIpRefSchema = z.object({
137
+ id: z.number(),
138
+ ip: z.string(),
139
+ });
140
+ /**
141
+ * Server firewall reference schema
142
+ */
143
+ export const HetznerFirewallRefSchema = z.object({
144
+ id: z.number(),
145
+ name: z.string(),
146
+ status: z.enum(["applied", "pending"]),
147
+ });
148
+ /**
149
+ * Server public network schema
150
+ */
151
+ export const HetznerPublicNetSchema = z.object({
152
+ ipv4: HetznerIPv4Schema,
153
+ ipv6: HetznerIPv6Schema.optional(),
154
+ floating_ips: z.array(HetznerFloatingIpRefSchema),
155
+ firewalls: z.array(HetznerFirewallRefSchema),
156
+ });
157
+ /**
158
+ * Server type schema
159
+ */
160
+ export const HetznerServerTypeSchema = z.object({
161
+ id: z.number(),
162
+ name: z.string(),
163
+ description: z.string(),
164
+ cores: z.number(),
165
+ memory: z.number(),
166
+ disk: z.number(),
167
+ });
168
+ /**
169
+ * Location schema
170
+ */
171
+ export const HetznerLocationSchema = z.object({
172
+ id: z.number(),
173
+ name: z.string(),
174
+ description: z.string(),
175
+ country: z.string(),
176
+ city: z.string(),
177
+ latitude: z.number(),
178
+ longitude: z.number(),
179
+ network_zone: z.string(),
180
+ });
181
+ /**
182
+ * Datacenter schema
183
+ */
184
+ export const HetznerDatacenterSchema = z.object({
185
+ id: z.number(),
186
+ name: z.string(),
187
+ description: z.string(),
188
+ location: HetznerLocationSchema,
189
+ // Hetzner API sometimes doesn't return this field
190
+ supported_server_types: z
191
+ .array(z.object({
192
+ id: z.number(),
193
+ name: z.string(),
194
+ }))
195
+ .optional()
196
+ .nullable(),
197
+ });
198
+ /**
199
+ * Server volume reference schema
200
+ */
201
+ export const HetznerVolumeRefSchema = z.object({
202
+ id: z.number(),
203
+ name: z.string(),
204
+ size: z.number().positive(),
205
+ linux_device: z.string(),
206
+ });
207
+ /**
208
+ * Server protection schema
209
+ */
210
+ export const HetznerServerProtectionSchema = z.object({
211
+ delete: z.boolean(),
212
+ rebuild: z.boolean(),
213
+ });
214
+ /**
215
+ * Full server schema
216
+ */
217
+ export const HetznerServerSchema = z.object({
218
+ id: z.number().positive(),
219
+ name: z.string().min(1),
220
+ status: z.nativeEnum(EnvironmentStatus),
221
+ image: HetznerServerImageSchema.nullable().optional(),
222
+ public_net: HetznerPublicNetSchema,
223
+ server_type: HetznerServerTypeSchema,
224
+ datacenter: HetznerDatacenterSchema,
225
+ labels: z.record(z.string(), z.any()),
226
+ created: z.string().datetime(),
227
+ protection: HetznerServerProtectionSchema,
228
+ volumes: z.array(HetznerVolumeRefSchema),
229
+ });
230
+ /**
231
+ * List servers response schema
232
+ */
233
+ export const HetznerListServersResponseSchema = z.object({
234
+ servers: z.array(HetznerServerSchema),
235
+ meta: HetznerMetaSchema,
236
+ });
237
+ /**
238
+ * Get server response schema
239
+ */
240
+ export const HetznerGetServerResponseSchema = z.object({
241
+ server: HetznerServerSchema,
242
+ });
243
+ /**
244
+ * Create server request options schema
245
+ */
246
+ export const HetznerCreateServerRequestSchema = z
247
+ .object({
248
+ name: z
249
+ .string()
250
+ .min(1)
251
+ .max(64)
252
+ .regex(/^[a-zA-Z0-9][a-zA-Z0-9-]*$/, "Name must start with letter/number and contain only letters, numbers, and hyphens"),
253
+ server_type: z.string().min(1),
254
+ image: z.string().min(1),
255
+ location: z.string().min(1).optional(),
256
+ datacenter: z.string().min(1).optional(),
257
+ ssh_keys: z.array(z.union([z.string(), z.number()])).optional(),
258
+ volumes: z.array(z.number().positive()).optional(),
259
+ labels: z.record(z.string(), z.any()).optional(),
260
+ start_after_create: z.boolean().optional(),
261
+ })
262
+ .refine((data) => !(data.location && data.datacenter), "Cannot specify both location and datacenter");
263
+ /**
264
+ * Create server response schema
265
+ */
266
+ export const HetznerCreateServerResponseSchema = z.object({
267
+ server: HetznerServerSchema,
268
+ action: HetznerActionSchema,
269
+ next_actions: z.array(HetznerActionSchema),
270
+ root_password: z.string().nullable(),
271
+ });
272
+ /**
273
+ * Update server request schema
274
+ */
275
+ export const HetznerUpdateServerRequestSchema = z.object({
276
+ name: z.string().min(1).max(64).optional(),
277
+ labels: z.record(z.string(), z.any()).optional(),
278
+ });
279
+ /**
280
+ * Update server response schema
281
+ */
282
+ export const HetznerUpdateServerResponseSchema = z.object({
283
+ server: HetznerServerSchema,
284
+ });
285
+ // ============================================================================
286
+ // Volume Schemas
287
+ // ============================================================================
288
+ /**
289
+ * Volume location schema
290
+ */
291
+ export const HetznerVolumeLocationSchema = z.object({
292
+ id: z.number(),
293
+ name: z.string(),
294
+ description: z.string(),
295
+ country: z.string(),
296
+ city: z.string(),
297
+ latitude: z.number(),
298
+ longitude: z.number(),
299
+ });
300
+ /**
301
+ * Volume protection schema
302
+ */
303
+ export const HetznerVolumeProtectionSchema = z.object({
304
+ delete: z.boolean(),
305
+ });
306
+ /**
307
+ * Volume schema
308
+ */
309
+ export const HetznerVolumeSchema = z.object({
310
+ id: z.number().positive(),
311
+ name: z.string().min(1),
312
+ status: z.nativeEnum(VolumeStatus),
313
+ server: z.number().positive().nullable(),
314
+ size: z.number().positive(),
315
+ linux_device: z.string().nullable(),
316
+ format: z.string().nullable(),
317
+ location: HetznerVolumeLocationSchema.nullable(),
318
+ labels: z.record(z.string(), z.any()),
319
+ created: z.string().datetime(),
320
+ protection: HetznerVolumeProtectionSchema,
321
+ });
322
+ /**
323
+ * List volumes response schema
324
+ */
325
+ export const HetznerListVolumesResponseSchema = z.object({
326
+ volumes: z.array(HetznerVolumeSchema),
327
+ meta: HetznerMetaSchema,
328
+ });
329
+ /**
330
+ * Get volume response schema
331
+ */
332
+ export const HetznerGetVolumeResponseSchema = z.object({
333
+ volume: HetznerVolumeSchema,
334
+ });
335
+ /**
336
+ * Create volume request schema
337
+ */
338
+ export const HetznerCreateVolumeRequestSchema = z
339
+ .object({
340
+ name: z.string().min(1).max(64),
341
+ size: z.number().positive().multipleOf(1), // GB
342
+ server: z.number().positive().optional(),
343
+ location: z.string().min(1).optional(),
344
+ automount: z.boolean().optional(),
345
+ format: z.string().optional(),
346
+ labels: z.record(z.string(), z.any()).optional(),
347
+ })
348
+ .refine((data) => {
349
+ // Ensure size is a multiple of GB (1, 2, 3, etc.)
350
+ return Number.isInteger(data.size);
351
+ }, "Volume size must be a whole number in GB");
352
+ /**
353
+ * Create volume response schema
354
+ */
355
+ export const HetznerCreateVolumeResponseSchema = z.object({
356
+ volume: HetznerVolumeSchema,
357
+ action: HetznerActionSchema,
358
+ next_actions: z.array(HetznerActionSchema),
359
+ });
360
+ // ============================================================================
361
+ // Network Schemas
362
+ // ============================================================================
363
+ /**
364
+ * Network subnet schema
365
+ */
366
+ export const HetznerSubnetSchema = z.object({
367
+ type: z.enum(["server", "cloud", "vswitch"]),
368
+ ip_range: z.string().regex(ipRegex),
369
+ network_zone: z.string(),
370
+ gateway: z.string().regex(ipRegex),
371
+ });
372
+ /**
373
+ * Network route schema
374
+ */
375
+ export const HetznerRouteSchema = z.object({
376
+ destination: z.string().regex(ipRegex),
377
+ gateway: z.string().regex(ipRegex),
378
+ });
379
+ /**
380
+ * Network protection schema
381
+ */
382
+ export const HetznerNetworkProtectionSchema = z.object({
383
+ delete: z.boolean(),
384
+ });
385
+ /**
386
+ * Network schema
387
+ */
388
+ export const HetznerNetworkSchema = z.object({
389
+ id: z.number().positive(),
390
+ name: z.string().min(1).max(64),
391
+ ip_range: z.string().regex(ipRegex),
392
+ subnets: z.array(HetznerSubnetSchema),
393
+ routes: z.array(HetznerRouteSchema),
394
+ servers: z.array(z.number().positive()),
395
+ protection: HetznerNetworkProtectionSchema,
396
+ labels: z.record(z.string(), z.any()),
397
+ created: z.string().datetime(),
398
+ });
399
+ /**
400
+ * List networks response schema
401
+ */
402
+ export const HetznerListNetworksResponseSchema = z.object({
403
+ networks: z.array(HetznerNetworkSchema),
404
+ meta: HetznerMetaSchema,
405
+ });
406
+ /**
407
+ * Get network response schema
408
+ */
409
+ export const HetznerGetNetworkResponseSchema = z.object({
410
+ network: HetznerNetworkSchema,
411
+ });
412
+ // ============================================================================
413
+ // SSH Key Schemas
414
+ // ============================================================================
415
+ /**
416
+ * SSH key schema
417
+ */
418
+ export const HetznerSSHKeySchema = z.object({
419
+ id: z.number().positive(),
420
+ name: z.string().min(1).max(64),
421
+ fingerprint: z.string(),
422
+ public_key: z.string(),
423
+ labels: z.record(z.string(), z.any()),
424
+ created: z.string().datetime(),
425
+ });
426
+ /**
427
+ * List SSH keys response schema
428
+ */
429
+ export const HetznerListSSHKeysResponseSchema = z.object({
430
+ ssh_keys: z.array(HetznerSSHKeySchema),
431
+ meta: HetznerMetaSchema,
432
+ });
433
+ /**
434
+ * Get SSH key response schema
435
+ */
436
+ export const HetznerGetSSHKeyResponseSchema = z.object({
437
+ ssh_key: HetznerSSHKeySchema,
438
+ });
439
+ /**
440
+ * Create SSH key request schema
441
+ */
442
+ export const HetznerCreateSSHKeyRequestSchema = z.object({
443
+ name: z
444
+ .string()
445
+ .min(1)
446
+ .max(64)
447
+ .regex(/^[a-zA-Z0-9][a-zA-Z0-9-]*$/, "Name must start with letter/number and contain only letters, numbers, and hyphens"),
448
+ public_key: z.string().min(1),
449
+ labels: z.record(z.string(), z.any()).optional(),
450
+ });
451
+ /**
452
+ * Create SSH key response schema
453
+ */
454
+ export const HetznerCreateSSHKeyResponseSchema = z.object({
455
+ ssh_key: HetznerSSHKeySchema,
456
+ });
457
+ // ============================================================================
458
+ // Floating IP Schemas
459
+ // ============================================================================
460
+ /**
461
+ * Floating IP schema
462
+ */
463
+ export const HetznerFloatingIpSchema = z.object({
464
+ id: z.number().positive(),
465
+ name: z.string().min(1).max(64),
466
+ description: z.string().optional(),
467
+ type: z.enum(["ipv4", "ipv6"]),
468
+ ip: z.string().regex(ipRegex),
469
+ server: z.number().positive().nullable(),
470
+ dns_ptr: z.array(z.object({
471
+ ip: z.string(),
472
+ dns_ptr: z.string(),
473
+ })),
474
+ home_location: HetznerLocationSchema,
475
+ blocked: z.boolean(),
476
+ protection: z.object({
477
+ delete: z.boolean(),
478
+ }),
479
+ labels: z.record(z.string(), z.any()),
480
+ created: z.string().datetime(),
481
+ });
482
+ /**
483
+ * List floating IPs response schema
484
+ */
485
+ export const HetznerListFloatingIpsResponseSchema = z.object({
486
+ floating_ips: z.array(HetznerFloatingIpSchema),
487
+ meta: HetznerMetaSchema,
488
+ });
489
+ // ============================================================================
490
+ // Firewall Schemas
491
+ // ============================================================================
492
+ /**
493
+ * Firewall rule schema
494
+ */
495
+ export const HetznerFirewallRuleSchema = z.object({
496
+ direction: z.enum(["in", "out"]),
497
+ source_ips: z.array(z.string().regex(ipRegex)).optional(),
498
+ destination_ips: z.array(z.string().regex(ipRegex)).optional(),
499
+ source_port: z.string().optional(),
500
+ destination_port: z.string().optional(),
501
+ protocol: z.enum(["tcp", "udp", "icmp", "esp", "gre"]),
502
+ });
503
+ /**
504
+ * Firewall resource schema
505
+ */
506
+ export const HetznerFirewallResourceSchema = z.object({
507
+ type: z.enum(["server", "label_selector"]),
508
+ server: z
509
+ .object({
510
+ id: z.number().positive(),
511
+ })
512
+ .optional(),
513
+ label_selector: z
514
+ .object({
515
+ selector: z.string(),
516
+ })
517
+ .optional(),
518
+ });
519
+ /**
520
+ * Firewall schema
521
+ */
522
+ export const HetznerFirewallSchema = z.object({
523
+ id: z.number().positive(),
524
+ name: z.string().min(1).max(64),
525
+ rules: z.array(HetznerFirewallRuleSchema),
526
+ apply_to: z.array(HetznerFirewallResourceSchema),
527
+ labels: z.record(z.string(), z.any()),
528
+ created: z.string().datetime(),
529
+ });
530
+ /**
531
+ * List firewalls response schema
532
+ */
533
+ export const HetznerListFirewallsResponseSchema = z.object({
534
+ firewalls: z.array(HetznerFirewallSchema),
535
+ meta: HetznerMetaSchema,
536
+ });
537
+ // ============================================================================
538
+ // ISO Schemas
539
+ // ============================================================================
540
+ /**
541
+ * ISO schema
542
+ */
543
+ export const HetznerIsoSchema = z.object({
544
+ id: z.number().positive(),
545
+ name: z.string(),
546
+ description: z.string(),
547
+ type: z.enum(["public", "private"]),
548
+ deprecated: z.date().nullable().optional(),
549
+ architecture: z.array(z.enum(["x86", "arm"])).optional(),
550
+ });
551
+ /**
552
+ * List ISOs response schema
553
+ */
554
+ export const HetznerListIsosResponseSchema = z.object({
555
+ isos: z.array(HetznerIsoSchema),
556
+ meta: HetznerMetaSchema,
557
+ });
558
+ // ============================================================================
559
+ // Location Schemas
560
+ // ============================================================================
561
+ /**
562
+ * List locations response schema
563
+ */
564
+ export const HetznerListLocationsResponseSchema = z.object({
565
+ locations: z.array(HetznerLocationSchema),
566
+ });
567
+ // ============================================================================
568
+ // Datacenter Schemas
569
+ // ============================================================================
570
+ /**
571
+ * List datacenters response schema
572
+ */
573
+ export const HetznerListDatacentersResponseSchema = z.object({
574
+ datacenters: z.array(HetznerDatacenterSchema),
575
+ });
576
+ // ============================================================================
577
+ // Server Type Schemas
578
+ // ============================================================================
579
+ /**
580
+ * Server type pricing schema
581
+ *
582
+ * Note: location can be null in some Hetzner API responses
583
+ */
584
+ export const HetznerServerTypePricingSchema = z.object({
585
+ location: z.string().nullable(),
586
+ price_hourly: z.object({
587
+ net: z.string(),
588
+ gross: z.string(),
589
+ }),
590
+ price_monthly: z.object({
591
+ net: z.string(),
592
+ gross: z.string(),
593
+ }),
594
+ });
595
+ /**
596
+ * Extended server type schema (for listing)
597
+ */
598
+ export const HetznerServerTypeExtendedSchema = HetznerServerTypeSchema.extend({
599
+ deprecated: z.boolean().optional(),
600
+ prices: z.array(HetznerServerTypePricingSchema),
601
+ storage_type: z.enum(["local", "network"]),
602
+ cpu_type: z.enum(["shared", "dedicated"]),
603
+ });
604
+ /**
605
+ * List server types response schema
606
+ */
607
+ export const HetznerListServerTypesResponseSchema = z.object({
608
+ server_types: z.array(HetznerServerTypeExtendedSchema),
609
+ });
610
+ // ============================================================================
611
+ // Certificate Schemas
612
+ // ============================================================================
613
+ /**
614
+ * Certificate schema
615
+ */
616
+ export const HetznerCertificateSchema = z.object({
617
+ id: z.number().positive(),
618
+ name: z.string().min(1).max(64),
619
+ labels: z.record(z.string(), z.any()),
620
+ certificate: z.string(),
621
+ not_valid_before: z.string().datetime(),
622
+ not_valid_after: z.string().datetime(),
623
+ domain_names: z.array(z.string().url()),
624
+ fingerprint: z.string(),
625
+ created: z.string().datetime(),
626
+ status: z.enum(["pending", "issued", "failed", "revoked"]),
627
+ failed: z.boolean().optional(),
628
+ type: z.enum(["uploaded", "managed"]),
629
+ usage: z
630
+ .array(z.enum(["dual_stack", "server", "load_balancer", "dns"]))
631
+ .optional(),
632
+ });
633
+ /**
634
+ * List certificates response schema
635
+ */
636
+ export const HetznerListCertificatesResponseSchema = z.object({
637
+ certificates: z.array(HetznerCertificateSchema),
638
+ meta: HetznerMetaSchema,
639
+ });
640
+ // ============================================================================
641
+ // Generic Response Wrappers
642
+ // ============================================================================
643
+ /**
644
+ * Generic paginated response schema
645
+ */
646
+ export function createPaginatedResponseSchema(itemSchema, itemName) {
647
+ return z.object({
648
+ [itemName]: z.array(itemSchema),
649
+ meta: HetznerMetaSchema,
650
+ });
651
+ }
652
+ /**
653
+ * Generic single item response schema
654
+ */
655
+ export function createItemResponseSchema(itemSchema, itemName) {
656
+ return z.object({
657
+ [itemName]: itemSchema,
658
+ });
659
+ }
660
+ //# sourceMappingURL=schemas.js.map