1import * as Hetzner from "@distilled.cloud/hetzner";
2import * as Data from "effect/Data";
3import * as Effect from "effect/Effect";
4
5export type CatalogKind =
6 | "location"
7 | "serverType"
8 | "image"
9 | "iso"
10 | "loadBalancerType";
11
12export class CatalogNotFound extends Data.TaggedError(
13 "Hetzner.CatalogNotFound",
14)<{
15 kind: CatalogKind;
16 ref: string;
17}> {}
18
19const numericId = (ref: string | number): number | undefined =>
20 typeof ref === "number" ? ref : /^\d+$/.test(ref) ? Number(ref) : undefined;
21
22const notFound = (kind: CatalogKind, ref: string | number) =>
23 new CatalogNotFound({ kind, ref: String(ref) });
24
25/**
26 * Resolve a Hetzner Location by name (`nbg1`, `fsn1`, `hel1`, …) or
27 * numeric id.
28 */
29export const findLocation = (ref: string | number) =>
30 Effect.gen(function* () {
31 const id = numericId(ref);
32 if (id !== undefined) {
33 const { location } = yield* Hetzner.locations
34 .getLocation({ id })
35 .pipe(
36 Effect.catchTag("NotFound", () =>
37 Effect.fail(notFound("location", ref)),
38 ),
39 );
40 return location;
41 }
42 const { locations } = yield* Hetzner.locations.listLocations({
43 name: String(ref),
44 per_page: 50,
45 });
46 const found = locations.find((item) => item.name === ref);
47 if (found === undefined) {
48 return yield* notFound("location", ref);
49 }
50 return found;
51 });
52
53/**
54 * Resolve a Hetzner Server Type by name (`cx22`, `cpx11`, …) or numeric id.
55 */
56export const findServerType = (ref: string | number) =>
57 Effect.gen(function* () {
58 const id = numericId(ref);
59 if (id !== undefined) {
60 const { server_type } = yield* Hetzner.serverTypes
61 .getServerType({ id })
62 .pipe(
63 Effect.catchTag("NotFound", () =>
64 Effect.fail(notFound("serverType", ref)),
65 ),
66 );
67 return server_type;
68 }
69 const { server_types } = yield* Hetzner.serverTypes.listServerTypes({
70 name: String(ref),
71 per_page: 50,
72 });
73 const found = server_types.find((item) => item.name === ref);
74 if (found === undefined) {
75 return yield* notFound("serverType", ref);
76 }
77 return found;
78 });
79
80/**
81 * Resolve a Hetzner Image by name (`ubuntu-24.04`, …) or numeric id.
82 */
83export const findImage = (ref: string | number) =>
84 Effect.gen(function* () {
85 const id = numericId(ref);
86 if (id !== undefined) {
87 const { image } = yield* Hetzner.images
88 .getImage({ id })
89 .pipe(
90 Effect.catchTag("NotFound", () =>
91 Effect.fail(notFound("image", ref)),
92 ),
93 );
94 return image;
95 }
96 const { images } = yield* Hetzner.images.listImages({
97 name: String(ref),
98 include_deprecated: true,
99 per_page: 50,
100 });
101 const found = images.find((item) => item.name === ref);
102 if (found === undefined) {
103 return yield* notFound("image", ref);
104 }
105 return found;
106 });
107
108/**
109 * Resolve a Hetzner ISO by name or numeric id.
110 */
111export const findIso = (ref: string | number) =>
112 Effect.gen(function* () {
113 const id = numericId(ref);
114 if (id !== undefined) {
115 const { iso } = yield* Hetzner.isos
116 .getIso({ id })
117 .pipe(
118 Effect.catchTag("NotFound", () => Effect.fail(notFound("iso", ref))),
119 );
120 return iso;
121 }
122 const { isos } = yield* Hetzner.isos.listIsos({
123 name: String(ref),
124 per_page: 50,
125 });
126 const found = isos.find((item) => item.name === ref);
127 if (found === undefined) {
128 return yield* notFound("iso", ref);
129 }
130 return found;
131 });
132
133/**
134 * Resolve a Hetzner Load Balancer Type by name (`lb11`, `lb21`, …) or
135 * numeric id.
136 */
137export const findLoadBalancerType = (ref: string | number) =>
138 Effect.gen(function* () {
139 const id = numericId(ref);
140 if (id !== undefined) {
141 const { load_balancer_type } = yield* Hetzner.loadBalancerTypes
142 .getLoadBalancerType({ id })
143 .pipe(
144 Effect.catchTag("NotFound", () =>
145 Effect.fail(notFound("loadBalancerType", ref)),
146 ),
147 );
148 return load_balancer_type;
149 }
150 const { load_balancer_types } =
151 yield* Hetzner.loadBalancerTypes.listLoadBalancerTypes({
152 name: String(ref),
153 per_page: 50,
154 });
155 const found = load_balancer_types.find((item) => item.name === ref);
156 if (found === undefined) {
157 return yield* notFound("loadBalancerType", ref);
158 }
159 return found;
160 });