@highstate/common 0.14.2 → 0.16.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 (91) hide show
  1. package/dist/{chunk-WFWXDYUX.js → chunk-X5BK6JSN.js} +877 -194
  2. package/dist/chunk-X5BK6JSN.js.map +1 -0
  3. package/dist/highstate.manifest.json +12 -2
  4. package/dist/index.js +1 -1
  5. package/dist/units/databases/etcd-patch/index.js +20 -0
  6. package/dist/units/databases/etcd-patch/index.js.map +1 -0
  7. package/dist/units/databases/existing-etcd/index.js +14 -0
  8. package/dist/units/databases/existing-etcd/index.js.map +1 -0
  9. package/dist/units/databases/existing-mariadb/index.js +2 -2
  10. package/dist/units/databases/existing-mariadb/index.js.map +1 -1
  11. package/dist/units/databases/existing-mongodb/index.js +2 -2
  12. package/dist/units/databases/existing-mongodb/index.js.map +1 -1
  13. package/dist/units/databases/existing-postgresql/index.js +2 -2
  14. package/dist/units/databases/existing-postgresql/index.js.map +1 -1
  15. package/dist/units/databases/existing-redis/index.js +2 -2
  16. package/dist/units/databases/existing-redis/index.js.map +1 -1
  17. package/dist/units/databases/existing-s3/index.js +18 -0
  18. package/dist/units/databases/existing-s3/index.js.map +1 -0
  19. package/dist/units/databases/mariadb-patch/index.js +24 -0
  20. package/dist/units/databases/mariadb-patch/index.js.map +1 -0
  21. package/dist/units/databases/mongodb-patch/index.js +24 -0
  22. package/dist/units/databases/mongodb-patch/index.js.map +1 -0
  23. package/dist/units/databases/postgresql-patch/index.js +24 -0
  24. package/dist/units/databases/postgresql-patch/index.js.map +1 -0
  25. package/dist/units/databases/redis-patch/index.js +27 -0
  26. package/dist/units/databases/redis-patch/index.js.map +1 -0
  27. package/dist/units/databases/s3-patch/index.js +25 -0
  28. package/dist/units/databases/s3-patch/index.js.map +1 -0
  29. package/dist/units/dns/record-set/index.js +14 -20
  30. package/dist/units/dns/record-set/index.js.map +1 -1
  31. package/dist/units/existing-server/index.js +3 -4
  32. package/dist/units/existing-server/index.js.map +1 -1
  33. package/dist/units/network/address-space/index.js +20 -0
  34. package/dist/units/network/address-space/index.js.map +1 -0
  35. package/dist/units/network/endpoint-filter/index.js +15 -0
  36. package/dist/units/network/endpoint-filter/index.js.map +1 -0
  37. package/dist/units/network/l3-endpoint/index.js +2 -2
  38. package/dist/units/network/l3-endpoint/index.js.map +1 -1
  39. package/dist/units/network/l4-endpoint/index.js +2 -2
  40. package/dist/units/network/l4-endpoint/index.js.map +1 -1
  41. package/dist/units/network/l7-endpoint/index.js +12 -0
  42. package/dist/units/network/l7-endpoint/index.js.map +1 -0
  43. package/dist/units/script/index.js +1 -1
  44. package/dist/units/server-patch/index.js +9 -12
  45. package/dist/units/server-patch/index.js.map +1 -1
  46. package/dist/units/ssh/key-pair/index.js +1 -1
  47. package/package.json +64 -10
  48. package/src/shared/command.ts +1 -1
  49. package/src/shared/dns.ts +11 -93
  50. package/src/shared/files.ts +3 -3
  51. package/src/shared/impl-ref.ts +4 -0
  52. package/src/shared/index.ts +2 -0
  53. package/src/shared/network/address-space.spec.ts +114 -0
  54. package/src/shared/network/address-space.ts +364 -0
  55. package/src/shared/network/address.spec.ts +109 -0
  56. package/src/shared/network/address.ts +119 -0
  57. package/src/shared/network/endpoints.spec.ts +249 -0
  58. package/src/shared/network/endpoints.ts +608 -0
  59. package/src/shared/network/index.ts +4 -0
  60. package/src/shared/network/ip.ts +236 -0
  61. package/src/shared/network/subnet.spec.ts +62 -0
  62. package/src/shared/network/subnet.ts +137 -0
  63. package/src/shared/ssh.ts +1 -1
  64. package/src/shared/tls.ts +21 -5
  65. package/src/shared/utils.ts +93 -0
  66. package/src/units/databases/etcd-patch/index.ts +23 -0
  67. package/src/units/databases/existing-etcd/index.ts +11 -0
  68. package/src/units/databases/existing-mariadb/index.ts +1 -1
  69. package/src/units/databases/existing-mongodb/index.ts +1 -1
  70. package/src/units/databases/existing-postgresql/index.ts +1 -1
  71. package/src/units/databases/existing-redis/index.ts +1 -1
  72. package/src/units/databases/existing-s3/index.ts +1 -1
  73. package/src/units/databases/mariadb-patch/index.ts +27 -0
  74. package/src/units/databases/mongodb-patch/index.ts +27 -0
  75. package/src/units/databases/postgresql-patch/index.ts +27 -0
  76. package/src/units/databases/redis-patch/index.ts +32 -0
  77. package/src/units/databases/s3-patch/index.ts +28 -0
  78. package/src/units/dns/record-set/index.ts +15 -20
  79. package/src/units/existing-server/index.ts +3 -4
  80. package/src/units/network/address-space/index.ts +20 -0
  81. package/src/units/network/endpoint-filter/index.ts +5 -5
  82. package/src/units/network/l3-endpoint/index.ts +2 -2
  83. package/src/units/network/l4-endpoint/index.ts +2 -2
  84. package/src/units/network/l7-endpoint/index.ts +2 -2
  85. package/src/units/remote-file/index.ts +12 -5
  86. package/src/units/server-patch/index.ts +10 -13
  87. package/dist/chunk-WFWXDYUX.js.map +0 -1
  88. package/dist/units/server-dns/index.js +0 -26
  89. package/dist/units/server-dns/index.js.map +0 -1
  90. package/src/shared/network.ts +0 -413
  91. package/src/units/server-dns/index.ts +0 -26
@@ -1,11 +1,11 @@
1
+ import { z, check, getOrCreate, HighstateSignature, stripNullish } from '@highstate/contract';
2
+ import { network, metadataSchema } from '@highstate/library';
1
3
  import { ComponentResource, Resource, toPromise, getImportBaseUrl, output, interpolate, normalizeInputsAndMap, normalizeInputs, asset, fileFromString, secret } from '@highstate/pulumi';
2
- import { uniqueBy, flat, groupBy } from 'remeda';
4
+ import { pipe, filter, isNonNullish, map, uniqueBy, omit, flat } from 'remeda';
3
5
  import { homedir, tmpdir } from 'node:os';
4
6
  import { sha256 } from '@noble/hashes/sha2';
5
7
  import { local, remote } from '@pulumi/command';
6
8
  import { resolve } from 'import-meta-resolve';
7
- import { z, getOrCreate, HighstateSignature, stripNullish } from '@highstate/contract';
8
- import { network } from '@highstate/library';
9
9
  import { createHash } from 'node:crypto';
10
10
  import { createReadStream } from 'node:fs';
11
11
  import { mkdtemp, writeFile, cp, rm, stat, rename, mkdir } from 'node:fs/promises';
@@ -19,188 +19,629 @@ import { randomBytes, bytesToHex } from '@noble/hashes/utils.js';
19
19
  import { secureMask } from 'micro-key-producer/password.js';
20
20
  import getKeys, { PrivateExport } from 'micro-key-producer/ssh.js';
21
21
  import { randomBytes as randomBytes$1 } from 'micro-key-producer/utils.js';
22
+ import { compile } from 'filter-expression';
22
23
 
23
- // src/shared/network.ts
24
+ // src/shared/network/address.ts
25
+
26
+ // src/shared/network/ip.ts
27
+ function parseIp(value) {
28
+ if (value.includes(":")) {
29
+ return { type: "ipv6", value: parseIpv6(value) };
30
+ }
31
+ return { type: "ipv4", value: parseIpv4(value) };
32
+ }
33
+ function parseCidr(value) {
34
+ const [ipPart, prefixPart, ...rest] = value.split("/");
35
+ if (!ipPart || !prefixPart || rest.length > 0) {
36
+ throw new Error(`Invalid CIDR: "${value}"`);
37
+ }
38
+ const parsedIp = parseIp(ipPart.trim());
39
+ const prefix = parseInt(prefixPart.trim(), 10);
40
+ if (!Number.isFinite(prefix)) {
41
+ throw new Error(`Invalid CIDR prefix: "${value}"`);
42
+ }
43
+ const bits = parsedIp.type === "ipv4" ? 32 : 128;
44
+ if (prefix < 0 || prefix > bits) {
45
+ throw new Error(`Invalid CIDR prefix length: "${value}"`);
46
+ }
47
+ return { type: parsedIp.type, ip: parsedIp.value, prefixLength: prefix };
48
+ }
49
+ function subnetBaseFromCidr(parsed) {
50
+ const bits = parsed.type === "ipv4" ? 32 : 128;
51
+ if (parsed.prefixLength === 0) {
52
+ return 0n;
53
+ }
54
+ const mask = (1n << BigInt(parsed.prefixLength)) - 1n << BigInt(bits - parsed.prefixLength);
55
+ return parsed.ip & mask;
56
+ }
57
+ function cidrBlockSize(type, prefixLength) {
58
+ const bits = type === "ipv4" ? 32 : 128;
59
+ return 1n << BigInt(bits - prefixLength);
60
+ }
61
+ function ipToString(type, value) {
62
+ return type === "ipv4" ? ipv4ToString(value) : ipv6ToString(value);
63
+ }
64
+ function parseIpv4(value) {
65
+ const parts = value.trim().split(".");
66
+ if (parts.length !== 4) {
67
+ throw new Error(`Invalid IPv4 address: "${value}"`);
68
+ }
69
+ let result = 0n;
70
+ for (const part of parts) {
71
+ if (!part) {
72
+ throw new Error(`Invalid IPv4 address: "${value}"`);
73
+ }
74
+ const octet = parseInt(part, 10);
75
+ if (!Number.isInteger(octet) || octet < 0 || octet > 255) {
76
+ throw new Error(`Invalid IPv4 address: "${value}"`);
77
+ }
78
+ result = (result << 8n) + BigInt(octet);
79
+ }
80
+ return result;
81
+ }
82
+ function parseIpv6(value) {
83
+ const input = value.trim().toLowerCase();
84
+ if (!input) {
85
+ throw new Error(`Invalid IPv6 address: "${value}"`);
86
+ }
87
+ let leftParts = [];
88
+ let rightParts = [];
89
+ const doubleColonIndex = input.indexOf("::");
90
+ if (doubleColonIndex >= 0) {
91
+ const [left, right] = input.split("::");
92
+ leftParts = left ? left.split(":") : [];
93
+ rightParts = right ? right.split(":") : [];
94
+ } else {
95
+ leftParts = input.split(":");
96
+ rightParts = [];
97
+ }
98
+ const expandIpv4Tail = (parts2) => {
99
+ if (parts2.length === 0) return parts2;
100
+ const last = parts2.at(-1);
101
+ if (!last.includes(".")) {
102
+ return parts2;
103
+ }
104
+ const ipv4Value = parseIpv4(last);
105
+ const high = Number(ipv4Value >> 16n & 0xffffn);
106
+ const low = Number(ipv4Value & 0xffffn);
107
+ return [...parts2.slice(0, -1), high.toString(16), low.toString(16)];
108
+ };
109
+ leftParts = leftParts.filter((p) => p.length > 0);
110
+ rightParts = rightParts.filter((p) => p.length > 0);
111
+ leftParts = expandIpv4Tail(leftParts);
112
+ rightParts = expandIpv4Tail(rightParts);
113
+ const totalParts = leftParts.length + rightParts.length;
114
+ if (doubleColonIndex < 0 && totalParts !== 8) {
115
+ throw new Error(`Invalid IPv6 address: "${value}"`);
116
+ }
117
+ if (totalParts > 8) {
118
+ throw new Error(`Invalid IPv6 address: "${value}"`);
119
+ }
120
+ const missing = 8 - totalParts;
121
+ const parts = doubleColonIndex >= 0 ? [...leftParts, ...Array.from({ length: missing }, () => "0"), ...rightParts] : leftParts;
122
+ if (parts.length !== 8) {
123
+ throw new Error(`Invalid IPv6 address: "${value}"`);
124
+ }
125
+ let result = 0n;
126
+ for (const part of parts) {
127
+ if (!part) {
128
+ throw new Error(`Invalid IPv6 address: "${value}"`);
129
+ }
130
+ const hextet = parseInt(part, 16);
131
+ if (!Number.isInteger(hextet) || hextet < 0 || hextet > 65535) {
132
+ throw new Error(`Invalid IPv6 address: "${value}"`);
133
+ }
134
+ result = (result << 16n) + BigInt(hextet);
135
+ }
136
+ return result;
137
+ }
138
+ function ipv4ToString(value) {
139
+ const parts = [
140
+ Number(value >> 24n & 0xffn),
141
+ Number(value >> 16n & 0xffn),
142
+ Number(value >> 8n & 0xffn),
143
+ Number(value & 0xffn)
144
+ ];
145
+ return parts.join(".");
146
+ }
147
+ function ipv6ToString(value) {
148
+ const hextets = [];
149
+ for (let i = 0; i < 8; i++) {
150
+ const shift = BigInt((7 - i) * 16);
151
+ hextets.push(Number(value >> shift & 0xffffn));
152
+ }
153
+ let bestStart = -1;
154
+ let bestLength = 0;
155
+ let currentStart = -1;
156
+ let currentLength = 0;
157
+ for (let i = 0; i < hextets.length; i++) {
158
+ if (hextets[i] === 0) {
159
+ if (currentStart === -1) {
160
+ currentStart = i;
161
+ currentLength = 1;
162
+ } else {
163
+ currentLength++;
164
+ }
165
+ if (currentLength > bestLength) {
166
+ bestStart = currentStart;
167
+ bestLength = currentLength;
168
+ }
169
+ } else {
170
+ currentStart = -1;
171
+ currentLength = 0;
172
+ }
173
+ }
174
+ if (bestLength < 2) {
175
+ bestStart = -1;
176
+ bestLength = 0;
177
+ }
178
+ const parts = [];
179
+ for (let i = 0; i < hextets.length; i++) {
180
+ if (bestStart >= 0 && i >= bestStart && i < bestStart + bestLength) {
181
+ if (i === bestStart) {
182
+ parts.push("");
183
+ }
184
+ continue;
185
+ }
186
+ parts.push(hextets[i].toString(16));
187
+ }
188
+ let result = parts.join(":");
189
+ if (bestStart === 0) {
190
+ result = `:${result}`;
191
+ }
192
+ if (bestStart >= 0 && bestStart + bestLength === 8) {
193
+ result = `${result}:`;
194
+ }
195
+ if (result === "") {
196
+ return "::";
197
+ }
198
+ return result.replace(/:{3,}/g, "::");
199
+ }
200
+
201
+ // src/shared/network/address.ts
202
+ function parseAddress(address) {
203
+ if (check(network.addressEntity.schema, address)) {
204
+ return address;
205
+ }
206
+ const input = address.trim();
207
+ if (!input) {
208
+ throw new Error("Empty address string");
209
+ }
210
+ const parsed = input.includes("/") ? parseCidr(input) : parseCidrFromIp(input);
211
+ const canonicalAddress = ipToString(parsed.type, parsed.ip);
212
+ const subnetBase = subnetBaseFromCidr(parsed);
213
+ const subnetBaseAddress = ipToString(parsed.type, subnetBase);
214
+ const result = {
215
+ type: parsed.type,
216
+ value: canonicalAddress,
217
+ subnet: {
218
+ type: parsed.type,
219
+ baseAddress: subnetBaseAddress,
220
+ prefixLength: parsed.prefixLength
221
+ }
222
+ };
223
+ const validated = network.addressEntity.schema.safeParse(result);
224
+ if (!validated.success) {
225
+ throw new Error(`Invalid address "${input}": ${validated.error.message}`);
226
+ }
227
+ return validated.data;
228
+ }
229
+ function addressToCidr(address) {
230
+ return `${address.value}/${address.subnet.prefixLength}`;
231
+ }
232
+ function doesAddressBelongToSubnet(address, subnet) {
233
+ if (address.type !== subnet.type) {
234
+ return false;
235
+ }
236
+ const addressIp = parseIp(address.value);
237
+ const subnetBaseIp = parseIp(subnet.baseAddress);
238
+ if (addressIp.type !== subnet.type || subnetBaseIp.type !== subnet.type) {
239
+ return false;
240
+ }
241
+ const bits = subnet.type === "ipv4" ? 32 : 128;
242
+ if (subnet.prefixLength === 0) {
243
+ return true;
244
+ }
245
+ const mask = (1n << BigInt(subnet.prefixLength)) - 1n << BigInt(bits - subnet.prefixLength);
246
+ return (addressIp.value & mask) === (subnetBaseIp.value & mask);
247
+ }
248
+ function mergeAddresses(addresses) {
249
+ const mergedMap = /* @__PURE__ */ new Map();
250
+ for (const address of addresses) {
251
+ mergedMap.set(addressToCidr(address), address);
252
+ }
253
+ return Array.from(mergedMap.values());
254
+ }
255
+ function parseCidrFromIp(value) {
256
+ const parsed = parseIp(value);
257
+ const prefixLength = parsed.type === "ipv4" ? 32 : 128;
258
+ return {
259
+ type: parsed.type,
260
+ ip: parsed.value,
261
+ prefixLength
262
+ };
263
+ }
264
+ function parseSubnet(subnet) {
265
+ if (check(network.subnetEntity.schema, subnet)) {
266
+ return subnet;
267
+ }
268
+ if (check(network.addressEntity.schema, subnet)) {
269
+ const prefixLength = subnet.type === "ipv4" ? 32 : 128;
270
+ const result2 = {
271
+ type: subnet.type,
272
+ baseAddress: subnet.value,
273
+ prefixLength
274
+ };
275
+ const validated2 = network.subnetEntity.schema.safeParse(result2);
276
+ if (!validated2.success) {
277
+ throw new Error(
278
+ `Invalid subnet "${subnet.value}/${prefixLength}": ${validated2.error.message}`
279
+ );
280
+ }
281
+ return validated2.data;
282
+ }
283
+ const input = subnet.trim();
284
+ if (!input) {
285
+ throw new Error("Empty subnet string");
286
+ }
287
+ let parsed;
288
+ if (input.includes("/")) {
289
+ parsed = parseCidr(input);
290
+ } else {
291
+ const ip = parseIp(input);
292
+ const prefixLength = ip.type === "ipv4" ? 32 : 128;
293
+ parsed = { type: ip.type, ip: ip.value, prefixLength };
294
+ }
295
+ const subnetBase = subnetBaseFromCidr(parsed);
296
+ const baseAddress = ipToString(parsed.type, subnetBase);
297
+ const result = {
298
+ type: parsed.type,
299
+ baseAddress,
300
+ prefixLength: parsed.prefixLength
301
+ };
302
+ const validated = network.subnetEntity.schema.safeParse(result);
303
+ if (!validated.success) {
304
+ throw new Error(`Invalid subnet "${input}": ${validated.error.message}`);
305
+ }
306
+ return validated.data;
307
+ }
308
+ var privateIpV4Subnets = [
309
+ parseSubnet("10.0.0.0/8"),
310
+ parseSubnet("127.0.0.0/8"),
311
+ parseSubnet("172.16.0.0/12"),
312
+ parseSubnet("192.168.0.0/16")
313
+ ];
314
+ var privateIpV6Subnets = [
315
+ parseSubnet("fc00::/7"),
316
+ // IPv4-mapped private ranges.
317
+ parseSubnet("::ffff:10.0.0.0/104"),
318
+ parseSubnet("::ffff:127.0.0.0/104"),
319
+ parseSubnet("::ffff:172.16.0.0/108"),
320
+ parseSubnet("::ffff:192.168.0.0/112")
321
+ ];
322
+ var privateSubnets = [...privateIpV4Subnets, ...privateIpV6Subnets];
323
+ function isPrivateAddress(address) {
324
+ for (const subnet of privateSubnets) {
325
+ if (doesAddressBelongToSubnet(address, subnet)) {
326
+ return true;
327
+ }
328
+ }
329
+ return false;
330
+ }
331
+ async function parseSubnets(subnets, inputSubnets) {
332
+ const resolvedInputSubnets = await toPromise(inputSubnets ?? []);
333
+ return pipe(
334
+ [...subnets ?? [], ...resolvedInputSubnets],
335
+ filter(isNonNullish),
336
+ map((subnet) => parseSubnet(subnet)),
337
+ uniqueBy((subnet) => subnetToString(subnet))
338
+ );
339
+ }
340
+ function subnetToString(subnet) {
341
+ return `${subnet.baseAddress}/${subnet.prefixLength}`;
342
+ }
24
343
  function l3EndpointToString(l3Endpoint) {
25
344
  switch (l3Endpoint.type) {
26
345
  case "ipv4":
27
- return l3Endpoint.address;
346
+ return l3Endpoint.address.value;
28
347
  case "ipv6":
29
- return l3Endpoint.address;
348
+ return l3Endpoint.address.value;
30
349
  case "hostname":
31
350
  return l3Endpoint.hostname;
32
351
  }
33
352
  }
34
353
  function l4EndpointToString(l4Endpoint) {
35
- if (l4Endpoint.type === "ipv6") {
36
- return `[${l4Endpoint.address}]:${l4Endpoint.port}`;
37
- }
38
- return `${l3EndpointToString(l4Endpoint)}:${l4Endpoint.port}`;
354
+ const host = l3EndpointToString(l4Endpoint);
355
+ const wrappedHost = l4Endpoint.type === "ipv6" ? `[${host}]` : host;
356
+ return `${wrappedHost}:${l4Endpoint.port}`;
39
357
  }
40
- function l4EndpointWithProtocolToString(l4Endpoint) {
358
+ function l4EndpointToFullString(l4Endpoint) {
41
359
  const protocol = `${l4Endpoint.protocol}://`;
42
360
  return `${protocol}${l4EndpointToString(l4Endpoint)}`;
43
361
  }
44
362
  function l7EndpointToString(l7Endpoint) {
45
363
  const protocol = `${l7Endpoint.appProtocol}://`;
46
364
  let endpoint = l4EndpointToString(l7Endpoint);
47
- if (l7Endpoint.resource) {
48
- endpoint += `/${l7Endpoint.resource}`;
365
+ if (l7Endpoint.path) {
366
+ endpoint += `/${l7Endpoint.path}`;
49
367
  }
50
368
  return `${protocol}${endpoint}`;
51
369
  }
52
- function l34EndpointToString(l34Endpoint) {
53
- if (l34Endpoint.port) {
54
- return l4EndpointToString(l34Endpoint);
370
+ function endpointToString(endpoint) {
371
+ switch (endpoint.level) {
372
+ case 3:
373
+ return l3EndpointToString(endpoint);
374
+ case 4:
375
+ return l4EndpointToString(endpoint);
376
+ case 7:
377
+ return l7EndpointToString(endpoint);
55
378
  }
56
- return l3EndpointToString(l34Endpoint);
57
379
  }
58
- var L34_ENDPOINT_RE = /^(?:(?<protocol>[a-z]+):\/\/)?(?:(?:\[?(?<ipv6>[0-9A-Fa-f:]+)\]?)|(?<ipv4>(?:\d{1,3}\.){3}\d{1,3})|(?<hostname>[a-zA-Z0-9-*]+(?:\.[a-zA-Z0-9-*]+)*))(?::(?<port>\d{1,5}))?$/;
59
- var L7_ENDPOINT_RE = /^(?<appProtocol>[a-z]+):\/\/(?:(?:\[?(?<ipv6>[0-9A-Fa-f:]+)\]?)|(?<ipv4>(?:\d{1,3}\.){3}\d{1,3})|(?<hostname>[a-zA-Z0-9-*]+(?:\.[a-zA-Z0-9-*]+)*))(?::(?<port>\d{1,5}))?(?:\/(?<resource>.*))?$/;
60
- function parseL34Endpoint(l34Endpoint) {
61
- if (typeof l34Endpoint === "object") {
62
- return l34Endpoint;
63
- }
64
- const match = l34Endpoint.match(L34_ENDPOINT_RE);
65
- if (!match) {
66
- throw new Error(`Invalid L3/L4 endpoint: "${l34Endpoint}"`);
67
- }
68
- const { protocol, ipv6, ipv4, hostname, port } = match.groups;
69
- if (protocol && protocol !== "tcp" && protocol !== "udp") {
70
- throw new Error(`Invalid L4 endpoint protocol: "${protocol}"`);
71
- }
72
- let visibility = "public";
73
- if (ipv4 && IPV4_PRIVATE_REGEX.test(ipv4)) {
74
- visibility = "external";
75
- } else if (ipv6 && IPV6_PRIVATE_REGEX.test(ipv6)) {
76
- visibility = "external";
77
- }
78
- const fallbackProtocol = port ? "tcp" : void 0;
79
- return {
80
- type: ipv6 ? "ipv6" : ipv4 ? "ipv4" : "hostname",
81
- visibility,
82
- address: ipv6 || ipv4,
83
- hostname,
84
- port: port ? parseInt(port, 10) : void 0,
85
- protocol: protocol ? protocol : fallbackProtocol
86
- };
380
+ function checkEndpointLevel(endpoint, minLevel) {
381
+ return endpoint.level >= minLevel;
87
382
  }
88
- function parseL3Endpoint(l3Endpoint) {
89
- if (typeof l3Endpoint === "object") {
90
- return l3Endpoint;
91
- }
92
- const parsed = parseL34Endpoint(l3Endpoint);
93
- if (parsed.port) {
94
- throw new Error(`Port cannot be specified in L3 endpoint: "${l3Endpoint}"`);
383
+ function assertEndpointLevel(endpoint, minLevel) {
384
+ if (!checkEndpointLevel(endpoint, minLevel)) {
385
+ throw new Error(
386
+ `The endpoint "${endpointToString(endpoint)}" is L${endpoint.level}, but L${minLevel} is required`
387
+ );
95
388
  }
96
- return parsed;
97
389
  }
98
- function parseL4Endpoint(l4Endpoint) {
99
- if (typeof l4Endpoint === "object") {
100
- return l4Endpoint;
101
- }
102
- const parsed = parseL34Endpoint(l4Endpoint);
103
- if (!parsed.port) {
104
- throw new Error(`No port found in L4 endpoint: "${l4Endpoint}"`);
390
+ function parseEndpoint(endpoint, minLevel = 3) {
391
+ function validateEndpoint(value) {
392
+ const schema = value.level === 7 ? network.l7EndpointEntity.schema : value.level === 4 ? network.l4EndpointEntity.schema : network.l3EndpointEntity.schema;
393
+ const result = schema.safeParse(value);
394
+ if (!result.success) {
395
+ throw new Error(`Invalid endpoint "${endpointToString(value)}": ${result.error.message}`);
396
+ }
397
+ return value;
105
398
  }
106
- return parsed;
107
- }
108
- var IPV4_PRIVATE_REGEX = /^(?:10|127)(?:\.\d{1,3}){3}$|^(?:172\.1[6-9]|172\.2[0-9]|172\.3[0-1])(?:\.\d{1,3}){2}$|^(?:192\.168)(?:\.\d{1,3}){2}$/;
109
- var IPV6_PRIVATE_REGEX = /^(?:fc|fd)(?:[0-9a-f]{2}){0,2}::(?:[0-9a-f]{1,4}:){7}[0-9a-f]{1,4}$|^::(?:ffff:(?:10|127)(?:\.\d{1,3}){3}|(?:172\.1[6-9]|172\.2[0-9]|172\.3[0-1])(?:\.\d{1,3}){2}|(?:192\.168)(?:\.\d{1,3}){2})$/;
110
- async function requireInputL3Endpoint(rawEndpoint, inputEndpoint) {
111
- if (rawEndpoint) {
112
- return parseL3Endpoint(rawEndpoint);
399
+ function parseHostToL3(host) {
400
+ const trimmed = host.trim();
401
+ if (!trimmed) {
402
+ throw new Error("Empty endpoint host");
403
+ }
404
+ try {
405
+ const address = parseAddress(trimmed);
406
+ return {
407
+ type: address.type,
408
+ level: 3,
409
+ metadata: extractMetadata(address),
410
+ address,
411
+ subnet: address.subnet
412
+ };
413
+ } catch {
414
+ return {
415
+ type: "hostname",
416
+ level: 3,
417
+ hostname: trimmed,
418
+ metadata: {}
419
+ };
420
+ }
113
421
  }
114
- if (inputEndpoint) {
115
- return toPromise(inputEndpoint);
422
+ function splitHostPort(input) {
423
+ const trimmed = input.trim();
424
+ if (!trimmed) {
425
+ throw new Error("Empty endpoint");
426
+ }
427
+ if (trimmed.startsWith("[")) {
428
+ const closingIndex = trimmed.indexOf("]");
429
+ if (closingIndex === -1) {
430
+ throw new Error(`Invalid endpoint: "${input}"`);
431
+ }
432
+ const host2 = trimmed.slice(1, closingIndex);
433
+ const remainder = trimmed.slice(closingIndex + 1);
434
+ if (!remainder) {
435
+ return { host: host2 };
436
+ }
437
+ if (!remainder.startsWith(":")) {
438
+ throw new Error(`Invalid endpoint: "${input}"`);
439
+ }
440
+ const portString2 = remainder.slice(1);
441
+ if (!/^\d{1,5}$/.test(portString2)) {
442
+ throw new Error(`Invalid endpoint port: "${portString2}"`);
443
+ }
444
+ return { host: host2, port: parseInt(portString2, 10) };
445
+ }
446
+ const lastColonIndex = trimmed.lastIndexOf(":");
447
+ if (lastColonIndex === -1) {
448
+ return { host: trimmed };
449
+ }
450
+ if (trimmed.includes(":")) {
451
+ const firstColonIndex = trimmed.indexOf(":");
452
+ if (firstColonIndex !== lastColonIndex) {
453
+ return { host: trimmed };
454
+ }
455
+ }
456
+ const host = trimmed.slice(0, lastColonIndex);
457
+ const portString = trimmed.slice(lastColonIndex + 1);
458
+ if (!/^\d{1,5}$/.test(portString)) {
459
+ return { host: trimmed };
460
+ }
461
+ return { host, port: parseInt(portString, 10) };
462
+ }
463
+ if (check(network.l3EndpointEntity.schema, endpoint)) {
464
+ assertEndpointLevel(endpoint, minLevel);
465
+ return endpoint;
466
+ }
467
+ if (check(network.addressEntity.schema, endpoint)) {
468
+ const address = endpoint;
469
+ const built = {
470
+ type: address.type,
471
+ level: 3,
472
+ metadata: extractMetadata(address),
473
+ address,
474
+ subnet: address.subnet
475
+ };
476
+ const withDynamic2 = syncDynamic(built);
477
+ const validated2 = validateEndpoint(withDynamic2);
478
+ assertEndpointLevel(validated2, minLevel);
479
+ return validated2;
480
+ }
481
+ if (typeof endpoint !== "string") {
482
+ throw new Error("Invalid endpoint");
483
+ }
484
+ const endpointString = endpoint;
485
+ let builtEndpoint;
486
+ const schemeMatch = /^([a-z]+):\/\/(.*)$/.exec(endpointString);
487
+ if (schemeMatch) {
488
+ const appProtocol = schemeMatch[1];
489
+ const rest = schemeMatch[2];
490
+ const pathIndex = rest.indexOf("/");
491
+ const hostPortPart = pathIndex === -1 ? rest : rest.slice(0, pathIndex);
492
+ const path = pathIndex === -1 ? void 0 : rest.slice(pathIndex + 1);
493
+ const udpAppProtocols = ["dns", "dhcp"];
494
+ const { host, port } = splitHostPort(hostPortPart);
495
+ const l3Base = parseHostToL3(host);
496
+ const portNumber = port ?? 443;
497
+ const protocol = udpAppProtocols.includes(appProtocol) ? "udp" : "tcp";
498
+ builtEndpoint = l3Base.type === "hostname" ? {
499
+ ...l3Base,
500
+ level: 7,
501
+ port: portNumber,
502
+ protocol,
503
+ appProtocol,
504
+ path: path || void 0
505
+ } : {
506
+ ...l3Base,
507
+ level: 7,
508
+ port: portNumber,
509
+ protocol,
510
+ appProtocol,
511
+ path: path || void 0
512
+ };
513
+ } else {
514
+ const { host, port } = splitHostPort(endpointString);
515
+ const l3Base = parseHostToL3(host);
516
+ if (port !== void 0) {
517
+ builtEndpoint = l3Base.type === "hostname" ? {
518
+ ...l3Base,
519
+ level: 4,
520
+ port,
521
+ protocol: "tcp"
522
+ } : {
523
+ ...l3Base,
524
+ level: 4,
525
+ port,
526
+ protocol: "tcp"
527
+ };
528
+ } else {
529
+ builtEndpoint = l3Base;
530
+ }
116
531
  }
117
- throw new Error("No endpoint provided");
532
+ const withDynamic = syncDynamic(builtEndpoint);
533
+ const validated = validateEndpoint(withDynamic);
534
+ assertEndpointLevel(validated, minLevel);
535
+ return validated;
118
536
  }
119
- async function requireInputL4Endpoint(rawEndpoint, inputEndpoint) {
120
- if (rawEndpoint) {
121
- return parseL4Endpoint(rawEndpoint);
122
- }
123
- if (inputEndpoint) {
124
- return toPromise(inputEndpoint);
537
+ function parseL4Protocol(input) {
538
+ input = input.trim().toLowerCase();
539
+ if (input === "tcp" || input === "udp") {
540
+ return input;
125
541
  }
126
- throw new Error("No endpoint provided");
542
+ throw new Error(`Invalid L4 protocol: "${input}"`);
127
543
  }
128
544
  function l3EndpointToL4(l3Endpoint, port, protocol = "tcp") {
545
+ const parsed = parseEndpoint(l3Endpoint);
129
546
  return {
130
- ...parseL3Endpoint(l3Endpoint),
547
+ ...parsed,
548
+ level: 4,
131
549
  port,
132
550
  protocol
133
551
  };
134
552
  }
135
- function filterEndpoints(endpoints, filter, types) {
136
- if (filter?.length) {
137
- endpoints = endpoints.filter((endpoint) => filter.includes(endpoint.visibility));
138
- } else if (endpoints.some((endpoint) => endpoint.visibility === "public")) {
139
- endpoints = endpoints.filter((endpoint) => endpoint.visibility === "public");
140
- } else if (endpoints.some((endpoint) => endpoint.visibility === "external")) {
141
- endpoints = endpoints.filter((endpoint) => endpoint.visibility === "external");
142
- }
143
- if (types?.length) {
144
- endpoints = endpoints.filter((endpoint) => types.includes(endpoint.type));
145
- }
146
- return endpoints;
553
+ function l4EndpointToL7(l4Endpoint, appProtocol, path = "") {
554
+ const parsed = parseEndpoint(l4Endpoint, 4);
555
+ return {
556
+ ...parsed,
557
+ level: 7,
558
+ appProtocol,
559
+ path
560
+ };
147
561
  }
148
- function l3EndpointToCidr(l3Endpoint) {
149
- switch (l3Endpoint.type) {
562
+ function l3EndpointToCidr(endpoint) {
563
+ switch (endpoint.type) {
150
564
  case "ipv4":
151
- return `${l3Endpoint.address}/32`;
565
+ return `${endpoint.address.value}/32`;
152
566
  case "ipv6":
153
- return `${l3Endpoint.address}/128`;
567
+ return `${endpoint.address.value}/128`;
154
568
  case "hostname":
155
569
  throw new Error("Cannot convert hostname to CIDR");
156
570
  }
157
571
  }
158
- var udpAppProtocols = ["dns", "dhcp"];
159
- function parseL7Endpoint(l7Endpoint) {
160
- if (typeof l7Endpoint === "object") {
161
- return l7Endpoint;
572
+ function extractMetadata(address) {
573
+ if (!address) {
574
+ return {};
162
575
  }
163
- const match = l7Endpoint.match(L7_ENDPOINT_RE);
164
- if (!match) {
165
- throw new Error(`Invalid L7 endpoint: "${l7Endpoint}"`);
576
+ const metadata = {};
577
+ if (privateSubnets.some((subnet) => doesAddressBelongToSubnet(address, subnet))) {
578
+ metadata["iana.scope"] = "private";
579
+ } else {
580
+ metadata["iana.scope"] = "global";
166
581
  }
167
- const { appProtocol, ipv6, ipv4, hostname, port, resource } = match.groups;
168
- let visibility = "public";
169
- if (ipv4 && IPV4_PRIVATE_REGEX.test(ipv4)) {
170
- visibility = "external";
171
- } else if (ipv6 && IPV6_PRIVATE_REGEX.test(ipv6)) {
172
- visibility = "external";
582
+ return metadata;
583
+ }
584
+ async function parseEndpoints(endpoints, inputEndpoints, minLevel = 3) {
585
+ const resolvedInputEndpoints = await toPromise(inputEndpoints ?? []);
586
+ return pipe(
587
+ [...endpoints ?? [], ...resolvedInputEndpoints],
588
+ filter(isNonNullish),
589
+ map((endpoint) => parseEndpoint(endpoint, minLevel)),
590
+ uniqueBy(endpointToString)
591
+ );
592
+ }
593
+ function addEndpointMetadata(endpoint, newMetadata) {
594
+ const parsedMetadata = metadataSchema.safeParse(newMetadata);
595
+ if (!parsedMetadata.success) {
596
+ throw new Error(
597
+ `Invalid new metadata for endpoint "${endpointToString(endpoint)}": ${parsedMetadata.error.message}`
598
+ );
173
599
  }
600
+ return syncDynamic({
601
+ ...endpoint,
602
+ metadata: {
603
+ ...endpoint.metadata,
604
+ ...newMetadata
605
+ }
606
+ });
607
+ }
608
+ function mergeEndpoints(endpoints) {
609
+ const mergedMap = /* @__PURE__ */ new Map();
610
+ for (const endpoint of endpoints) {
611
+ const key = endpointToString(endpoint);
612
+ const existing = mergedMap.get(key);
613
+ if (existing) {
614
+ mergedMap.set(key, addEndpointMetadata(existing, endpoint.metadata ?? {}));
615
+ } else {
616
+ mergedMap.set(key, endpoint);
617
+ }
618
+ }
619
+ return Array.from(mergedMap.values());
620
+ }
621
+ function syncDynamic(endpoint) {
174
622
  return {
175
- type: ipv6 ? "ipv6" : ipv4 ? "ipv4" : "hostname",
176
- visibility,
177
- address: ipv6 || ipv4,
178
- hostname,
179
- // Default port for L7 endpoints (TODO: add more specific defaults for common protocols)
180
- port: port ? parseInt(port, 10) : 443,
181
- // L7 endpoints typically use TCP, but can also use UDP for specific protocols
182
- protocol: udpAppProtocols.includes(appProtocol) ? "udp" : "tcp",
183
- appProtocol,
184
- resource: resource || ""
623
+ ...endpoint,
624
+ dynamic: {
625
+ type: "static",
626
+ endpoint: omit(endpoint, ["dynamic"])
627
+ }
185
628
  };
186
629
  }
187
- async function updateEndpoints(currentEndpoints, endpoints, inputEndpoints, mode = "prepend") {
188
- const resolvedCurrentEndpoints = await toPromise(currentEndpoints);
189
- const newEndpoints = await parseEndpoints(endpoints, inputEndpoints);
190
- if (mode === "replace") {
191
- return newEndpoints;
630
+ function replaceEndpointBase(endpoint, base) {
631
+ if (base.type === "hostname") {
632
+ return syncDynamic({
633
+ ...omit(endpoint, ["type", "address", "subnet"]),
634
+ type: "hostname",
635
+ hostname: base.hostname
636
+ });
192
637
  }
193
- return uniqueBy(
194
- [...newEndpoints, ...resolvedCurrentEndpoints],
195
- l34EndpointToString
196
- );
197
- }
198
- async function parseEndpoints(endpoints, inputEndpoints) {
199
- const resolvedInputEndpoints = await toPromise(inputEndpoints);
200
- return uniqueBy(
201
- [...endpoints?.map(parseL34Endpoint) ?? [], ...resolvedInputEndpoints ?? []],
202
- l34EndpointToString
203
- );
638
+ return syncDynamic({
639
+ ...omit(endpoint, ["type", "hostname", "metadata"]),
640
+ type: base.type,
641
+ address: base.address,
642
+ subnet: base.subnet,
643
+ metadata: base.metadata ?? extractMetadata(base.address)
644
+ });
204
645
  }
205
646
  function getServerConnection(ssh) {
206
647
  return output(ssh).apply((ssh2) => ({
@@ -443,6 +884,9 @@ var ImplementationMediator = class {
443
884
  return output(this.call(implRef, input));
444
885
  }
445
886
  };
887
+ function areImplRefsEqual(a, b) {
888
+ return a.package === b.package && JSON.stringify(a.data) === JSON.stringify(b.data);
889
+ }
446
890
  var dnsRecordMediator = new ImplementationMediator(
447
891
  "dns-record",
448
892
  z.object({ name: z.string(), args: z.custom() }),
@@ -471,7 +915,7 @@ var DnsRecord = class extends ComponentResource {
471
915
  waitCommands;
472
916
  constructor(name, args, opts) {
473
917
  super("highstate:common:DnsRecord", name, args, opts);
474
- const l3Endpoint = output(args.value).apply((value) => parseL3Endpoint(value));
918
+ const l3Endpoint = output(args.value).apply((value) => parseEndpoint(value));
475
919
  const resolvedValue = l3Endpoint.apply(l3EndpointToString);
476
920
  const resolvedType = args.type ? output(args.type) : l3Endpoint.apply(getTypeByEndpoint);
477
921
  this.dnsRecord = output(args.provider).apply((provider) => {
@@ -547,7 +991,9 @@ var DnsRecordSet = class _DnsRecordSet extends ComponentResource {
547
991
  providers: args.providers,
548
992
  name: args.name ?? name
549
993
  }).apply(({ providers, name: name2 }) => {
550
- const matchedProviders2 = providers.filter((provider) => name2.endsWith(provider.domain));
994
+ const matchedProviders2 = providers.filter(
995
+ (provider) => provider.zones.some((zone) => name2.endsWith(zone))
996
+ );
551
997
  if (matchedProviders2.length === 0) {
552
998
  throw new Error(`No DNS provider matched the domain "${name2}"`);
553
999
  }
@@ -559,7 +1005,7 @@ var DnsRecordSet = class _DnsRecordSet extends ComponentResource {
559
1005
  providers: matchedProviders
560
1006
  }).apply(({ name: name2, providers }) => {
561
1007
  return providers.flatMap((provider) => {
562
- const l3Endpoint = parseL3Endpoint(value);
1008
+ const l3Endpoint = parseEndpoint(value);
563
1009
  return new DnsRecord(
564
1010
  `${name2}.${provider.id}.${l3EndpointToString(l3Endpoint)}`,
565
1011
  {
@@ -597,53 +1043,6 @@ var DnsRecordSet = class _DnsRecordSet extends ComponentResource {
597
1043
  );
598
1044
  }
599
1045
  };
600
- async function updateEndpointsWithFqdn(endpoints, fqdn, fqdnEndpointFilter, patchMode, dnsProviders, dnsSetName) {
601
- const resolvedEndpoints = await toPromise(endpoints);
602
- if (!fqdn) {
603
- return {
604
- endpoints: resolvedEndpoints,
605
- dnsRecordSet: void 0
606
- };
607
- }
608
- const filteredEndpoints = filterEndpoints(resolvedEndpoints, fqdnEndpointFilter);
609
- const dnsRecordSet = new DnsRecordSet(dnsSetName ?? fqdn, {
610
- name: fqdn,
611
- providers: dnsProviders,
612
- values: filteredEndpoints,
613
- waitAt: "local"
614
- });
615
- const portProtocolGroups = groupBy(
616
- filteredEndpoints,
617
- (endpoint) => endpoint.port ? `${endpoint.port}-${endpoint.protocol}` : ""
618
- );
619
- const newEndpoints = [];
620
- for (const group of Object.values(portProtocolGroups)) {
621
- newEndpoints.unshift({
622
- type: "hostname",
623
- hostname: fqdn,
624
- visibility: group[0].visibility,
625
- port: group[0].port,
626
- protocol: group[0].protocol
627
- });
628
- }
629
- await toPromise(
630
- dnsRecordSet.waitCommands.apply((waitCommands) => waitCommands.map((command) => command.stdout))
631
- );
632
- if (patchMode === "prepend") {
633
- return {
634
- endpoints: uniqueBy(
635
- //
636
- [...newEndpoints, ...resolvedEndpoints],
637
- (endpoint) => l34EndpointToString(endpoint)
638
- ),
639
- dnsRecordSet
640
- };
641
- }
642
- return {
643
- endpoints: newEndpoints,
644
- dnsRecordSet
645
- };
646
- }
647
1046
  var gatewayRouteMediator = new ImplementationMediator(
648
1047
  "gateway-route",
649
1048
  z.object({
@@ -701,18 +1100,29 @@ var TlsCertificate = class _TlsCertificate extends ComponentResource {
701
1100
  dnsNames: args.dnsNames
702
1101
  }).apply(async ({ issuers: issuers2, commonName, dnsNames }) => {
703
1102
  const matchedIssuer = issuers2.find((issuer) => {
704
- if (commonName && !commonName.endsWith(issuer.domain)) {
1103
+ if (commonName && !issuer.zones.some((zone) => commonName.endsWith(zone))) {
705
1104
  return false;
706
1105
  }
707
- if (dnsNames && !dnsNames.every((name2) => name2.endsWith(issuer.domain))) {
1106
+ if (dnsNames && !dnsNames.every((name2) => issuer.zones.some((zone) => name2.endsWith(zone)))) {
708
1107
  return false;
709
1108
  }
710
1109
  return true;
711
1110
  });
712
1111
  if (!matchedIssuer) {
713
- throw new Error(
714
- `No TLS issuer matched the common name "${commonName}" and DNS names "${dnsNames?.join(", ") ?? ""}"`
715
- );
1112
+ if (commonName && dnsNames && dnsNames.length > 0) {
1113
+ const dnsNameList = dnsNames.join(", ");
1114
+ throw new Error(
1115
+ `No TLS issuer matched the common name "${commonName}" and DNS names "${dnsNameList}"`
1116
+ );
1117
+ }
1118
+ if (commonName) {
1119
+ throw new Error(`No TLS issuer matched the common name "${commonName}"`);
1120
+ }
1121
+ if (dnsNames && dnsNames.length > 0) {
1122
+ const dnsNameList = dnsNames.join(", ");
1123
+ throw new Error(`No TLS issuer matched the DNS names "${dnsNameList}"`);
1124
+ }
1125
+ throw new Error("No TLS issuer provided");
716
1126
  }
717
1127
  return await tlsCertificateMediator.call(matchedIssuer.implRef, {
718
1128
  name,
@@ -1287,8 +1697,251 @@ async function fetchFileSize(endpoint) {
1287
1697
  return size;
1288
1698
  }
1289
1699
  function getNameByEndpoint(endpoint) {
1290
- const parsedEndpoint = parseL7Endpoint(endpoint);
1291
- return parsedEndpoint.resource ? basename(parsedEndpoint.resource) : "";
1700
+ const parsedEndpoint = parseEndpoint(endpoint, 7);
1701
+ return parsedEndpoint.path ? basename(parsedEndpoint.path) : "";
1702
+ }
1703
+ function createAddressSpace({
1704
+ included,
1705
+ excluded = []
1706
+ }) {
1707
+ const includedRanges = included.flatMap(resolveInputToRanges);
1708
+ const excludedRanges = excluded.flatMap(resolveInputToRanges);
1709
+ const normalized = normalizeRanges(includedRanges, excludedRanges);
1710
+ const subnets = rangesToCanonicalSubnets(normalized);
1711
+ return { subnets };
1712
+ }
1713
+ function resolveInputToRanges(input) {
1714
+ if (typeof input === "string") {
1715
+ return [rangeFromString(input)];
1716
+ }
1717
+ if (check(network.addressSpaceEntity.schema, input)) {
1718
+ return input.subnets.flatMap((subnet) => [rangeFromCidr(subnetToString(subnet))]);
1719
+ }
1720
+ if (check(network.subnetEntity.schema, input)) {
1721
+ return [rangeFromCidr(subnetToString(input))];
1722
+ }
1723
+ if (check(network.addressEntity.schema, input)) {
1724
+ return [rangeFromCidr(addressToCidr(input))];
1725
+ }
1726
+ if (check(network.l3EndpointEntity.schema, input)) {
1727
+ if (input.type === "hostname") {
1728
+ return [];
1729
+ }
1730
+ const cidr = input.cidr;
1731
+ if (typeof cidr === "string") {
1732
+ return [rangeFromCidr(cidr)];
1733
+ }
1734
+ const address = input.address;
1735
+ if (typeof address === "string") {
1736
+ const parsed = parseIp(address);
1737
+ const prefixLength = parsed.type === "ipv4" ? 32 : 128;
1738
+ return [
1739
+ rangeFromParsedCidr({ family: parsed.type, base: parsed.value, prefix: prefixLength })
1740
+ ];
1741
+ }
1742
+ if (check(network.addressEntity.schema, address)) {
1743
+ return [rangeFromCidr(addressToCidr(address))];
1744
+ }
1745
+ throw new Error("Invalid L3 endpoint: missing address information");
1746
+ }
1747
+ throw new Error("Unsupported address space input");
1748
+ }
1749
+ function rangeFromString(value) {
1750
+ const trimmed = value.trim();
1751
+ if (!trimmed) {
1752
+ throw new Error("Empty address string");
1753
+ }
1754
+ if (trimmed.includes("-")) {
1755
+ const [left, right, ...rest] = trimmed.split("-");
1756
+ if (!left || !right || rest.length > 0) {
1757
+ throw new Error(`Invalid range: "${value}"`);
1758
+ }
1759
+ const start = parseIp(left.trim());
1760
+ const end = parseIp(right.trim());
1761
+ if (start.type !== end.type) {
1762
+ throw new Error(`Range must not mix IPv4 and IPv6: "${value}"`);
1763
+ }
1764
+ const min = start.value <= end.value ? start.value : end.value;
1765
+ const max = start.value <= end.value ? end.value : start.value;
1766
+ return {
1767
+ family: start.type,
1768
+ start: min,
1769
+ endExclusive: max + 1n
1770
+ };
1771
+ }
1772
+ if (trimmed.includes("/")) {
1773
+ return rangeFromCidr(trimmed);
1774
+ }
1775
+ const parsed = parseIp(trimmed);
1776
+ return {
1777
+ family: parsed.type,
1778
+ start: parsed.value,
1779
+ endExclusive: parsed.value + 1n
1780
+ };
1781
+ }
1782
+ function rangeFromCidr(cidr) {
1783
+ const parsed = parseCidr(cidr);
1784
+ return rangeFromParsedCidr({
1785
+ family: parsed.type,
1786
+ base: subnetBaseFromCidr(parsed),
1787
+ prefix: parsed.prefixLength
1788
+ });
1789
+ }
1790
+ function rangeFromParsedCidr(parsed) {
1791
+ const size = cidrBlockSize(parsed.family, parsed.prefix);
1792
+ return {
1793
+ family: parsed.family,
1794
+ start: parsed.base,
1795
+ endExclusive: parsed.base + size
1796
+ };
1797
+ }
1798
+ function normalizeRanges(included, excluded) {
1799
+ const includedByFamily = splitByFamily(included);
1800
+ const excludedByFamily = splitByFamily(excluded);
1801
+ const normalizedV4 = subtractMergedRanges(
1802
+ mergeRanges(includedByFamily.v4),
1803
+ mergeRanges(excludedByFamily.v4)
1804
+ );
1805
+ const normalizedV6 = subtractMergedRanges(
1806
+ mergeRanges(includedByFamily.v6),
1807
+ mergeRanges(excludedByFamily.v6)
1808
+ );
1809
+ return [...normalizedV4, ...normalizedV6];
1810
+ }
1811
+ function splitByFamily(ranges) {
1812
+ const v4 = [];
1813
+ const v6 = [];
1814
+ for (const range of ranges) {
1815
+ if (range.start >= range.endExclusive) continue;
1816
+ if (range.family === "ipv4") {
1817
+ v4.push(range);
1818
+ } else {
1819
+ v6.push(range);
1820
+ }
1821
+ }
1822
+ return { v4, v6 };
1823
+ }
1824
+ function mergeRanges(ranges) {
1825
+ const sorted = [...ranges].sort((a, b) => {
1826
+ if (a.start === b.start) {
1827
+ return a.endExclusive < b.endExclusive ? -1 : a.endExclusive > b.endExclusive ? 1 : 0;
1828
+ }
1829
+ return a.start < b.start ? -1 : 1;
1830
+ });
1831
+ const merged = [];
1832
+ for (const current of sorted) {
1833
+ const last = merged.at(-1);
1834
+ if (!last) {
1835
+ merged.push({ ...current });
1836
+ continue;
1837
+ }
1838
+ if (current.start <= last.endExclusive) {
1839
+ if (current.endExclusive > last.endExclusive) {
1840
+ last.endExclusive = current.endExclusive;
1841
+ }
1842
+ continue;
1843
+ }
1844
+ merged.push({ ...current });
1845
+ }
1846
+ return merged;
1847
+ }
1848
+ function subtractMergedRanges(included, excluded) {
1849
+ if (included.length === 0) return [];
1850
+ if (excluded.length === 0) return included;
1851
+ const result = [];
1852
+ let j = 0;
1853
+ for (const incOriginal of included) {
1854
+ const inc = { ...incOriginal };
1855
+ while (j < excluded.length && excluded[j].endExclusive <= inc.start) {
1856
+ j++;
1857
+ }
1858
+ while (j < excluded.length) {
1859
+ const exc = excluded[j];
1860
+ if (exc.start >= inc.endExclusive) {
1861
+ break;
1862
+ }
1863
+ if (exc.start <= inc.start) {
1864
+ inc.start = inc.start < exc.endExclusive ? exc.endExclusive : inc.start;
1865
+ if (inc.start >= inc.endExclusive) {
1866
+ break;
1867
+ }
1868
+ if (exc.endExclusive <= inc.start) {
1869
+ j++;
1870
+ }
1871
+ continue;
1872
+ }
1873
+ result.push({
1874
+ family: inc.family,
1875
+ start: inc.start,
1876
+ endExclusive: exc.start
1877
+ });
1878
+ inc.start = exc.endExclusive;
1879
+ if (inc.start >= inc.endExclusive) {
1880
+ break;
1881
+ }
1882
+ }
1883
+ if (inc.start < inc.endExclusive) {
1884
+ result.push(inc);
1885
+ }
1886
+ }
1887
+ return result;
1888
+ }
1889
+ function rangesToCanonicalSubnets(ranges) {
1890
+ const subnets = [];
1891
+ for (const range of ranges) {
1892
+ for (const cidr of rangeToCidrs(range)) {
1893
+ const baseAddress = ipToString(cidr.family, cidr.base);
1894
+ subnets.push({
1895
+ type: cidr.family,
1896
+ baseAddress,
1897
+ prefixLength: cidr.prefix
1898
+ });
1899
+ }
1900
+ }
1901
+ return sortSubnetsCanonical(subnets);
1902
+ }
1903
+ function sortSubnetsCanonical(subnets) {
1904
+ return [...subnets].sort((a, b) => {
1905
+ const aFamily = a.type;
1906
+ const bFamily = b.type;
1907
+ if (aFamily !== bFamily) {
1908
+ return aFamily === "ipv4" ? -1 : 1;
1909
+ }
1910
+ const aBase = parseIp(a.baseAddress).value;
1911
+ const bBase = parseIp(b.baseAddress).value;
1912
+ if (aBase !== bBase) {
1913
+ return aBase < bBase ? -1 : 1;
1914
+ }
1915
+ return a.prefixLength - b.prefixLength;
1916
+ });
1917
+ }
1918
+ function rangeToCidrs(range) {
1919
+ const bits = range.family === "ipv4" ? 32 : 128;
1920
+ const result = [];
1921
+ let current = range.start;
1922
+ while (current < range.endExclusive) {
1923
+ const remaining = range.endExclusive - current;
1924
+ const maxAligned = current === 0n ? 1n << BigInt(bits) : current & -current;
1925
+ const maxByRemaining = highestPowerOfTwoAtMost(remaining);
1926
+ const blockSize = maxAligned < maxByRemaining ? maxAligned : maxByRemaining;
1927
+ const prefix = bits - bitLength(blockSize) + 1;
1928
+ result.push({
1929
+ family: range.family,
1930
+ base: current,
1931
+ prefix
1932
+ });
1933
+ current += blockSize;
1934
+ }
1935
+ return result;
1936
+ }
1937
+ function highestPowerOfTwoAtMost(value) {
1938
+ if (value <= 0n) {
1939
+ throw new Error("Value must be positive");
1940
+ }
1941
+ return 1n << BigInt(bitLength(value) - 1);
1942
+ }
1943
+ function bitLength(value) {
1944
+ return value.toString(2).length;
1292
1945
  }
1293
1946
  function generatePassword() {
1294
1947
  return secureMask.apply(randomBytes(32)).password;
@@ -1460,7 +2113,37 @@ async function createServerEntity({
1460
2113
  }
1461
2114
  });
1462
2115
  }
2116
+ function filterByExpression(items, expression, getContext = (item) => item) {
2117
+ const { evaluate } = compile(expression);
2118
+ return items.filter((item) => evaluate(getContext(item)));
2119
+ }
2120
+ function filterWithMetadataByExpression(items, expression, getContext = (item) => item) {
2121
+ return filterByExpression(
2122
+ items,
2123
+ expression,
2124
+ (item) => getContext({ ...item, metadata: item.metadata ? flattenMetadata(item.metadata) : void 0 })
2125
+ );
2126
+ }
2127
+ function flattenMetadata(metadata) {
2128
+ const result = {};
2129
+ for (const [key, value] of Object.entries(metadata)) {
2130
+ const path = key.split(".");
2131
+ let current = result;
2132
+ for (let i = 0; i < path.length; i++) {
2133
+ const segment = path[i];
2134
+ if (i === path.length - 1) {
2135
+ current[segment] = value;
2136
+ } else {
2137
+ if (!(segment in current)) {
2138
+ current[segment] = {};
2139
+ }
2140
+ current = current[segment];
2141
+ }
2142
+ }
2143
+ }
2144
+ return result;
2145
+ }
1463
2146
 
1464
- export { AccessPointRoute, Command, DnsRecord, DnsRecordSet, GatewayRoute, ImplementationMediator, MaterializedFile, MaterializedFolder, TlsCertificate, archiveFromFolder, assetFromFile, createServerBundle, createServerEntity, createSshHostKeyFile, createSshTerminal, dnsRecordMediator, fetchFileSize, filterEndpoints, gatewayRouteMediator, generateKey, generatePassword, generateSshPrivateKey, getNameByEndpoint, getServerConnection, l34EndpointToString, l3EndpointToCidr, l3EndpointToL4, l3EndpointToString, l4EndpointToString, l4EndpointWithProtocolToString, l7EndpointToString, parseEndpoints, parseL34Endpoint, parseL3Endpoint, parseL4Endpoint, parseL7Endpoint, requireInputL3Endpoint, requireInputL4Endpoint, sshPrivateKeyToKeyPair, tlsCertificateMediator, updateEndpoints, updateEndpointsWithFqdn };
1465
- //# sourceMappingURL=chunk-WFWXDYUX.js.map
1466
- //# sourceMappingURL=chunk-WFWXDYUX.js.map
2147
+ export { AccessPointRoute, Command, DnsRecord, DnsRecordSet, GatewayRoute, ImplementationMediator, MaterializedFile, MaterializedFolder, TlsCertificate, addEndpointMetadata, addressToCidr, archiveFromFolder, areImplRefsEqual, assertEndpointLevel, assetFromFile, checkEndpointLevel, createAddressSpace, createServerBundle, createServerEntity, createSshHostKeyFile, createSshTerminal, dnsRecordMediator, doesAddressBelongToSubnet, endpointToString, fetchFileSize, filterByExpression, filterWithMetadataByExpression, flattenMetadata, gatewayRouteMediator, generateKey, generatePassword, generateSshPrivateKey, getNameByEndpoint, getServerConnection, isPrivateAddress, l3EndpointToCidr, l3EndpointToL4, l3EndpointToString, l4EndpointToFullString, l4EndpointToL7, l4EndpointToString, l7EndpointToString, mergeAddresses, mergeEndpoints, parseAddress, parseEndpoint, parseEndpoints, parseL4Protocol, parseSubnet, parseSubnets, privateIpV4Subnets, privateIpV6Subnets, privateSubnets, replaceEndpointBase, sshPrivateKeyToKeyPair, subnetToString, tlsCertificateMediator };
2148
+ //# sourceMappingURL=chunk-X5BK6JSN.js.map
2149
+ //# sourceMappingURL=chunk-X5BK6JSN.js.map