@highstate/common 0.15.0 → 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highstate/common",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -20,6 +20,14 @@
20
20
  "mode": "manual",
21
21
  "version": "1"
22
22
  },
23
+ "./units/network/address-space": {
24
+ "mode": "manual",
25
+ "version": "1"
26
+ },
27
+ "./units/network/endpoint-filter": {
28
+ "mode": "manual",
29
+ "version": "1"
30
+ },
23
31
  "./units/network/l3-endpoint": {
24
32
  "mode": "manual",
25
33
  "version": "1"
@@ -28,6 +36,10 @@
28
36
  "mode": "manual",
29
37
  "version": "1"
30
38
  },
39
+ "./units/network/l7-endpoint": {
40
+ "mode": "manual",
41
+ "version": "1"
42
+ },
31
43
  "./units/existing-server": {
32
44
  "mode": "manual",
33
45
  "version": "1"
@@ -40,11 +52,11 @@
40
52
  "mode": "manual",
41
53
  "version": "1"
42
54
  },
43
- "./units/server-dns": {
55
+ "./units/server-patch": {
44
56
  "mode": "manual",
45
57
  "version": "1"
46
58
  },
47
- "./units/server-patch": {
59
+ "./units/databases/existing-etcd": {
48
60
  "mode": "manual",
49
61
  "version": "1"
50
62
  },
@@ -63,6 +75,34 @@
63
75
  "./units/databases/existing-redis": {
64
76
  "mode": "manual",
65
77
  "version": "1"
78
+ },
79
+ "./units/databases/existing-s3": {
80
+ "mode": "manual",
81
+ "version": "1"
82
+ },
83
+ "./units/databases/etcd-patch": {
84
+ "mode": "manual",
85
+ "version": "1"
86
+ },
87
+ "./units/databases/mariadb-patch": {
88
+ "mode": "manual",
89
+ "version": "1"
90
+ },
91
+ "./units/databases/postgresql-patch": {
92
+ "mode": "manual",
93
+ "version": "1"
94
+ },
95
+ "./units/databases/mongodb-patch": {
96
+ "mode": "manual",
97
+ "version": "1"
98
+ },
99
+ "./units/databases/redis-patch": {
100
+ "mode": "manual",
101
+ "version": "1"
102
+ },
103
+ "./units/databases/s3-patch": {
104
+ "mode": "manual",
105
+ "version": "1"
66
106
  }
67
107
  }
68
108
  },
@@ -73,17 +113,27 @@
73
113
  },
74
114
  "./units/access-point": "./dist/units/access-point/index.js",
75
115
  "./units/dns/record-set": "./dist/units/dns/record-set/index.js",
116
+ "./units/network/address-space": "./dist/units/network/address-space/index.js",
117
+ "./units/network/endpoint-filter": "./dist/units/network/endpoint-filter/index.js",
76
118
  "./units/network/l3-endpoint": "./dist/units/network/l3-endpoint/index.js",
77
119
  "./units/network/l4-endpoint": "./dist/units/network/l4-endpoint/index.js",
120
+ "./units/network/l7-endpoint": "./dist/units/network/l7-endpoint/index.js",
78
121
  "./units/existing-server": "./dist/units/existing-server/index.js",
79
122
  "./units/ssh/key-pair": "./dist/units/ssh/key-pair/index.js",
80
123
  "./units/script": "./dist/units/script/index.js",
81
- "./units/server-dns": "./dist/units/server-dns/index.js",
82
124
  "./units/server-patch": "./dist/units/server-patch/index.js",
125
+ "./units/databases/existing-etcd": "./dist/units/databases/existing-etcd/index.js",
83
126
  "./units/databases/existing-mariadb": "./dist/units/databases/existing-mariadb/index.js",
84
127
  "./units/databases/existing-postgresql": "./dist/units/databases/existing-postgresql/index.js",
85
128
  "./units/databases/existing-mongodb": "./dist/units/databases/existing-mongodb/index.js",
86
- "./units/databases/existing-redis": "./dist/units/databases/existing-redis/index.js"
129
+ "./units/databases/existing-redis": "./dist/units/databases/existing-redis/index.js",
130
+ "./units/databases/existing-s3": "./dist/units/databases/existing-s3/index.js",
131
+ "./units/databases/etcd-patch": "./dist/units/databases/etcd-patch/index.js",
132
+ "./units/databases/mariadb-patch": "./dist/units/databases/mariadb-patch/index.js",
133
+ "./units/databases/postgresql-patch": "./dist/units/databases/postgresql-patch/index.js",
134
+ "./units/databases/mongodb-patch": "./dist/units/databases/mongodb-patch/index.js",
135
+ "./units/databases/redis-patch": "./dist/units/databases/redis-patch/index.js",
136
+ "./units/databases/s3-patch": "./dist/units/databases/s3-patch/index.js"
87
137
  },
88
138
  "publishConfig": {
89
139
  "access": "public"
@@ -96,29 +146,33 @@
96
146
  "dependencies": {
97
147
  "@noble/hashes": "^1.7.1",
98
148
  "@pulumi/command": "^1.0.2",
149
+ "filter-expression": "^1.0.0",
150
+ "import-meta-resolve": "^4.1.0",
99
151
  "micro-key-producer": "^0.7.3",
100
152
  "minimatch": "^10.0.3",
101
153
  "remeda": "^2.21.0",
102
154
  "tar": "^7.4.3",
103
155
  "unzipper": "^0.12.3",
104
- "import-meta-resolve": "^4.1.0",
105
- "@highstate/contract": "0.16.0",
106
- "@highstate/library": "0.15.0",
107
- "@highstate/pulumi": "0.16.0"
156
+ "@highstate/pulumi": "0.17.0",
157
+ "@highstate/library": "0.16.0",
158
+ "@highstate/contract": "0.17.0"
108
159
  },
109
160
  "devDependencies": {
110
161
  "@biomejs/biome": "2.2.0",
111
162
  "@types/tar": "^6.1.13",
112
163
  "@types/unzipper": "^0.10.11",
113
164
  "@typescript/native-preview": "^7.0.0-dev.20250920.1",
165
+ "@vitest/coverage-v8": "2.1.8",
166
+ "vitest": "^3.2.4",
114
167
  "type-fest": "^4.41.0",
115
- "@highstate/cli": "0.16.0"
168
+ "@highstate/cli": "0.17.0"
116
169
  },
117
170
  "repository": {
118
171
  "url": "https://github.com/highstate-io/highstate"
119
172
  },
120
173
  "scripts": {
121
174
  "build": "highstate build",
175
+ "test": "vitest run",
122
176
  "update-images": "../../../scripts/update-images.sh ./assets/images.json",
123
177
  "typecheck": "tsgo --noEmit --skipLibCheck",
124
178
  "biome": "biome check --write --unsafe --error-on-warnings",
@@ -13,7 +13,7 @@ import {
13
13
  import { sha256 } from "@noble/hashes/sha2"
14
14
  import { local, remote, type types } from "@pulumi/command"
15
15
  import { flat } from "remeda"
16
- import { l3EndpointToString } from "./network"
16
+ import { l3EndpointToString } from "./network/endpoints"
17
17
 
18
18
  /**
19
19
  * Creates a connection object for the given SSH credentials.
package/src/shared/dns.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ArrayPatchMode, dns, network } from "@highstate/library"
1
+ import type { dns, network } from "@highstate/library"
2
2
  import { getOrCreate, z } from "@highstate/contract"
3
3
  import {
4
4
  ComponentResource,
@@ -10,19 +10,12 @@ import {
10
10
  type Output,
11
11
  output,
12
12
  type ResourceOptions,
13
- toPromise,
14
13
  type Unwrap,
15
14
  } from "@highstate/pulumi"
16
- import { flat, groupBy, uniqueBy } from "remeda"
15
+ import { flat } from "remeda"
17
16
  import { Command, type CommandHost } from "./command"
18
17
  import { ImplementationMediator } from "./impl-ref"
19
- import {
20
- filterEndpoints,
21
- type InputL3Endpoint,
22
- l3EndpointToString,
23
- l34EndpointToString,
24
- parseL3Endpoint,
25
- } from "./network"
18
+ import { type InputEndpoint, l3EndpointToString, parseEndpoint } from "./network/endpoints"
26
19
 
27
20
  export const dnsRecordMediator = new ImplementationMediator(
28
21
  "dns-record",
@@ -52,7 +45,7 @@ export type DnsRecordArgs = {
52
45
  /**
53
46
  * The value of the DNS record.
54
47
  */
55
- value: Input<InputL3Endpoint>
48
+ value: Input<InputEndpoint>
56
49
 
57
50
  /**
58
51
  * Whether the DNS record is proxied (e.g. to provide DDoS protection).
@@ -105,12 +98,12 @@ export type DnsRecordSetArgs = Omit<DnsRecordArgs, "provider" | "value"> & {
105
98
  /**
106
99
  * The value of the DNS record.
107
100
  */
108
- value?: Input<InputL3Endpoint>
101
+ value?: Input<InputEndpoint>
109
102
 
110
103
  /**
111
104
  * The values of the DNS records.
112
105
  */
113
- values?: InputArray<InputL3Endpoint>
106
+ values?: InputArray<InputEndpoint>
114
107
  }
115
108
 
116
109
  function getTypeByEndpoint(endpoint: network.L3Endpoint): string {
@@ -145,7 +138,7 @@ export class DnsRecord extends ComponentResource {
145
138
  constructor(name: string, args: DnsRecordArgs, opts?: ResourceOptions) {
146
139
  super("highstate:common:DnsRecord", name, args, opts)
147
140
 
148
- const l3Endpoint = output(args.value).apply(value => parseL3Endpoint(value))
141
+ const l3Endpoint = output(args.value).apply(value => parseEndpoint(value))
149
142
  const resolvedValue = l3Endpoint.apply(l3EndpointToString)
150
143
  const resolvedType = args.type ? output(args.type) : l3Endpoint.apply(getTypeByEndpoint)
151
144
 
@@ -238,7 +231,9 @@ export class DnsRecordSet extends ComponentResource {
238
231
  providers: args.providers,
239
232
  name: args.name ?? name,
240
233
  }).apply(({ providers, name }) => {
241
- const matchedProviders = providers.filter(provider => name.endsWith(provider.domain))
234
+ const matchedProviders = providers.filter(provider =>
235
+ provider.zones.some(zone => name.endsWith(zone)),
236
+ )
242
237
 
243
238
  if (matchedProviders.length === 0) {
244
239
  throw new Error(`No DNS provider matched the domain "${name}"`)
@@ -253,7 +248,7 @@ export class DnsRecordSet extends ComponentResource {
253
248
  providers: matchedProviders,
254
249
  }).apply(({ name, providers }) => {
255
250
  return providers.flatMap(provider => {
256
- const l3Endpoint = parseL3Endpoint(value)
251
+ const l3Endpoint = parseEndpoint(value)
257
252
 
258
253
  return new DnsRecord(
259
254
  `${name}.${provider.id}.${l3EndpointToString(l3Endpoint)}`,
@@ -297,80 +292,3 @@ export class DnsRecordSet extends ComponentResource {
297
292
  )
298
293
  }
299
294
  }
300
-
301
- /**
302
- * Registers the DNS record set for the given endpoints and prepends the corresponding hostname endpoint to the list.
303
- *
304
- * Waits for the DNS record set to be created/updated before continuing.
305
- *
306
- * Ignores the "hostname" endpoints in the list.
307
- *
308
- * @param endpoints The list of endpoints to register. Will be modified in place.
309
- * @param fqdn The FQDN to register the DNS record set for. If not provided, no DNS record set will be created and array will not be modified.
310
- * @param fqdnEndpointFilter The filter to apply to the endpoints before passing them to the DNS record set. Does not apply to the resulted endpoint list.
311
- * @param patchMode The patch mode to use when modifying the endpoints list.
312
- * @param dnsProviders The DNS providers to use to create the DNS records.
313
- * @param dnsSetName The name of the DNS record set. If not provided, the FQDN will be used.
314
- */
315
- export async function updateEndpointsWithFqdn<TEndpoint extends network.L34Endpoint>(
316
- endpoints: Input<TEndpoint[]>,
317
- fqdn: string | undefined,
318
- fqdnEndpointFilter: network.EndpointFilter,
319
- patchMode: ArrayPatchMode,
320
- dnsProviders: Input<dns.Provider[]>,
321
- dnsSetName?: string,
322
- ): Promise<{ endpoints: TEndpoint[]; dnsRecordSet: DnsRecordSet | undefined }> {
323
- const resolvedEndpoints = await toPromise(endpoints)
324
-
325
- if (!fqdn) {
326
- return {
327
- endpoints: resolvedEndpoints as TEndpoint[],
328
- dnsRecordSet: undefined,
329
- }
330
- }
331
-
332
- const filteredEndpoints = filterEndpoints(resolvedEndpoints, fqdnEndpointFilter)
333
-
334
- const dnsRecordSet = new DnsRecordSet(dnsSetName ?? fqdn, {
335
- name: fqdn,
336
- providers: dnsProviders,
337
- values: filteredEndpoints,
338
- waitAt: "local",
339
- })
340
-
341
- const portProtocolGroups = groupBy(filteredEndpoints, endpoint =>
342
- endpoint.port ? `${endpoint.port}-${endpoint.protocol}` : "",
343
- )
344
-
345
- const newEndpoints: TEndpoint[] = []
346
-
347
- for (const group of Object.values(portProtocolGroups)) {
348
- newEndpoints.unshift({
349
- type: "hostname",
350
- hostname: fqdn,
351
- visibility: group[0].visibility,
352
- port: group[0].port,
353
- protocol: group[0].protocol,
354
- } as TEndpoint)
355
- }
356
-
357
- await toPromise(
358
- dnsRecordSet.waitCommands.apply(waitCommands => waitCommands.map(command => command.stdout)),
359
- )
360
-
361
- if (patchMode === "prepend") {
362
- return {
363
- endpoints: uniqueBy(
364
- //
365
- [...newEndpoints, ...(resolvedEndpoints as TEndpoint[])],
366
- endpoint => l34EndpointToString(endpoint),
367
- ),
368
- dnsRecordSet,
369
- }
370
- }
371
-
372
- return {
373
- endpoints: newEndpoints,
374
- dnsRecordSet,
375
- }
376
- }
@@ -11,7 +11,7 @@ import { asset, type Input, toPromise } from "@highstate/pulumi"
11
11
  import { minimatch } from "minimatch"
12
12
  import * as tar from "tar"
13
13
  import unzipper from "unzipper"
14
- import { type InputL7Endpoint, l7EndpointToString, parseL7Endpoint } from "./network"
14
+ import { type InputL7Endpoint, l7EndpointToString, parseEndpoint } from "./network/endpoints"
15
15
 
16
16
  export type FolderPackOptions = {
17
17
  /**
@@ -728,7 +728,7 @@ export async function fetchFileSize(endpoint: network.L7Endpoint): Promise<numbe
728
728
  * Extracts the name from an L7 endpoint URL without its file extension.
729
729
  */
730
730
  export function getNameByEndpoint(endpoint: InputL7Endpoint): string {
731
- const parsedEndpoint = parseL7Endpoint(endpoint)
731
+ const parsedEndpoint = parseEndpoint(endpoint, 7)
732
732
 
733
- return parsedEndpoint.resource ? basename(parsedEndpoint.resource) : ""
733
+ return parsedEndpoint.path ? basename(parsedEndpoint.path) : ""
734
734
  }
@@ -128,3 +128,7 @@ export class ImplementationMediator<
128
128
  return output(this.call(implRef, input))
129
129
  }
130
130
  }
131
+
132
+ export function areImplRefsEqual(a: ImplementationReference, b: ImplementationReference): boolean {
133
+ return a.package === b.package && JSON.stringify(a.data) === JSON.stringify(b.data)
134
+ }
@@ -5,6 +5,8 @@ export * from "./files"
5
5
  export * from "./gateway"
6
6
  export * from "./impl-ref"
7
7
  export * from "./network"
8
+ export * from "./network/address-space"
8
9
  export * from "./passwords"
9
10
  export * from "./ssh"
10
11
  export * from "./tls"
12
+ export * from "./utils"
@@ -0,0 +1,114 @@
1
+ import { network } from "@highstate/library"
2
+ import { describe, expect, it } from "vitest"
3
+ import { parseAddress } from "./address"
4
+ import { createAddressSpace } from "./address-space"
5
+ import { parseEndpoint } from "./endpoints"
6
+ import { subnetToString } from "./subnet"
7
+
8
+ describe("createAddressSpace", () => {
9
+ it("returns a single canonical CIDR for adjacent subnets", () => {
10
+ const result = createAddressSpace({
11
+ included: ["10.0.0.0/25", "10.0.0.128/25"],
12
+ })
13
+
14
+ expect(result.subnets.map(subnetToString)).toEqual(["10.0.0.0/24"])
15
+ })
16
+
17
+ it("applies exclusions and keeps canonical ordering", () => {
18
+ const result = createAddressSpace({
19
+ included: ["10.0.0.0/24"],
20
+ excluded: ["10.0.0.64/26"],
21
+ })
22
+
23
+ expect(result.subnets.map(subnetToString)).toEqual(["10.0.0.0/26", "10.0.0.128/25"])
24
+ })
25
+
26
+ it("converts dash ranges to a canonical CIDR list", () => {
27
+ const result = createAddressSpace({
28
+ included: ["10.0.0.10-10.0.0.20"],
29
+ })
30
+
31
+ expect(result.subnets.map(subnetToString)).toEqual([
32
+ "10.0.0.10/31",
33
+ "10.0.0.12/30",
34
+ "10.0.0.16/30",
35
+ "10.0.0.20/32",
36
+ ])
37
+ })
38
+
39
+ it("ignores hostname L3 endpoints", () => {
40
+ const hostnameEndpoint = parseEndpoint("example.com")
41
+
42
+ const result = createAddressSpace({
43
+ included: [hostnameEndpoint],
44
+ })
45
+
46
+ expect(result.subnets).toEqual([])
47
+ })
48
+
49
+ it("canonicalizes IPv6 addresses", () => {
50
+ const result = createAddressSpace({
51
+ included: ["2001:db8:0:0:0:0:0:1"],
52
+ })
53
+
54
+ expect(result.subnets.map(subnetToString)).toEqual(["2001:db8::1/128"])
55
+ })
56
+
57
+ it("produces a schema-valid address space", () => {
58
+ const result = createAddressSpace({
59
+ included: ["10.0.0.0/24", "2001:db8::1"],
60
+ excluded: ["10.0.0.10"],
61
+ })
62
+
63
+ const parsed = network.addressSpaceEntity.schema.safeParse(result)
64
+ expect(parsed.success).toBe(true)
65
+ })
66
+
67
+ it("accepts network.Subnet inputs", () => {
68
+ const subnet = {
69
+ type: "ipv4",
70
+ baseAddress: "10.1.0.0",
71
+ prefixLength: 24,
72
+ } satisfies network.Subnet
73
+
74
+ const result = createAddressSpace({
75
+ included: [subnet],
76
+ })
77
+
78
+ expect(result.subnets.map(subnetToString)).toEqual(["10.1.0.0/24"])
79
+ })
80
+
81
+ it("accepts network.AddressSpace inputs", () => {
82
+ const seed = createAddressSpace({
83
+ included: ["10.2.0.0/25", "10.2.0.128/25"],
84
+ })
85
+
86
+ const addressSpace = seed satisfies network.AddressSpace
87
+
88
+ const result = createAddressSpace({
89
+ included: [addressSpace],
90
+ })
91
+
92
+ expect(result.subnets.map(subnetToString)).toEqual(["10.2.0.0/24"])
93
+ })
94
+
95
+ it("accepts network.Address inputs", () => {
96
+ const address = parseAddress("10.3.0.7") satisfies network.Address
97
+
98
+ const result = createAddressSpace({
99
+ included: [address],
100
+ })
101
+
102
+ expect(result.subnets.map(subnetToString)).toEqual(["10.3.0.7/32"])
103
+ })
104
+
105
+ it("accepts non-hostname network.L3Endpoint inputs", () => {
106
+ const endpoint = parseEndpoint("10.4.0.9")
107
+
108
+ const result = createAddressSpace({
109
+ included: [endpoint],
110
+ })
111
+
112
+ expect(result.subnets.map(subnetToString)).toEqual(["10.4.0.9/32"])
113
+ })
114
+ })