effect 4.0.2 → 4.0.3

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 (243) hide show
  1. package/dist/Context.js +3 -3
  2. package/dist/Context.js.map +1 -1
  3. package/dist/Crypto.d.ts +33 -5
  4. package/dist/Crypto.d.ts.map +1 -1
  5. package/dist/Crypto.js +8 -2
  6. package/dist/Crypto.js.map +1 -1
  7. package/dist/DateTime.d.ts +9 -6
  8. package/dist/DateTime.d.ts.map +1 -1
  9. package/dist/DateTime.js +9 -6
  10. package/dist/DateTime.js.map +1 -1
  11. package/dist/Duration.d.ts +6 -6
  12. package/dist/Duration.d.ts.map +1 -1
  13. package/dist/Duration.js +27 -8
  14. package/dist/Duration.js.map +1 -1
  15. package/dist/FileSystem.d.ts +44 -2
  16. package/dist/FileSystem.d.ts.map +1 -1
  17. package/dist/FileSystem.js.map +1 -1
  18. package/dist/Schema.d.ts +473 -1
  19. package/dist/Schema.d.ts.map +1 -1
  20. package/dist/Schema.js +355 -4
  21. package/dist/Schema.js.map +1 -1
  22. package/dist/Stdio.d.ts +7 -1
  23. package/dist/Stdio.d.ts.map +1 -1
  24. package/dist/Stdio.js +1 -0
  25. package/dist/Stdio.js.map +1 -1
  26. package/dist/Stream.d.ts +11 -11
  27. package/dist/Stream.js +5 -5
  28. package/dist/ai/McpServer.d.ts +6 -0
  29. package/dist/ai/McpServer.d.ts.map +1 -1
  30. package/dist/ai/McpServer.js +65 -23
  31. package/dist/ai/McpServer.js.map +1 -1
  32. package/dist/ai/internal/mcpProtocol/v2026_07_28.js +5 -0
  33. package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
  34. package/dist/ai/internal/mcpProtocol.js.map +1 -1
  35. package/dist/ai/internal/mcpRuntime.js +1 -0
  36. package/dist/ai/internal/mcpRuntime.js.map +1 -1
  37. package/dist/cli/CliOutput.d.ts +27 -0
  38. package/dist/cli/CliOutput.d.ts.map +1 -1
  39. package/dist/cli/CliOutput.js +33 -10
  40. package/dist/cli/CliOutput.js.map +1 -1
  41. package/dist/cli/Command.d.ts.map +1 -1
  42. package/dist/cli/Command.js +6 -3
  43. package/dist/cli/Command.js.map +1 -1
  44. package/dist/cli/Completions.d.ts +3 -3
  45. package/dist/cli/Completions.d.ts.map +1 -1
  46. package/dist/cli/Completions.js +8 -5
  47. package/dist/cli/Completions.js.map +1 -1
  48. package/dist/cli/GlobalFlag.d.ts +5 -4
  49. package/dist/cli/GlobalFlag.d.ts.map +1 -1
  50. package/dist/cli/GlobalFlag.js +3 -3
  51. package/dist/cli/GlobalFlag.js.map +1 -1
  52. package/dist/cli/Prompt.d.ts +17 -1
  53. package/dist/cli/Prompt.d.ts.map +1 -1
  54. package/dist/cli/Prompt.js +119 -114
  55. package/dist/cli/Prompt.js.map +1 -1
  56. package/dist/cli/internal/ansi.d.ts +0 -1
  57. package/dist/cli/internal/ansi.d.ts.map +1 -1
  58. package/dist/cli/internal/ansi.js +20 -3
  59. package/dist/cli/internal/ansi.js.map +1 -1
  60. package/dist/cli/internal/completions/powershell.d.ts +2 -0
  61. package/dist/cli/internal/completions/powershell.d.ts.map +1 -0
  62. package/dist/cli/internal/completions/powershell.js +273 -0
  63. package/dist/cli/internal/completions/powershell.js.map +1 -0
  64. package/dist/cli/internal/completions/zsh.js +3 -2
  65. package/dist/cli/internal/completions/zsh.js.map +1 -1
  66. package/dist/cli/internal/wizard.d.ts +3 -3
  67. package/dist/cli/internal/wizard.d.ts.map +1 -1
  68. package/dist/cli/internal/wizard.js +21 -18
  69. package/dist/cli/internal/wizard.js.map +1 -1
  70. package/dist/cluster/ClusterSchema.d.ts +28 -0
  71. package/dist/cluster/ClusterSchema.d.ts.map +1 -1
  72. package/dist/cluster/ClusterSchema.js +30 -0
  73. package/dist/cluster/ClusterSchema.js.map +1 -1
  74. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  75. package/dist/cluster/ClusterWorkflowEngine.js +10 -18
  76. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  77. package/dist/cluster/MessageStorage.d.ts +18 -3
  78. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  79. package/dist/cluster/MessageStorage.js +31 -11
  80. package/dist/cluster/MessageStorage.js.map +1 -1
  81. package/dist/cluster/Runners.d.ts.map +1 -1
  82. package/dist/cluster/Runners.js +8 -2
  83. package/dist/cluster/Runners.js.map +1 -1
  84. package/dist/cluster/Sharding.d.ts.map +1 -1
  85. package/dist/cluster/Sharding.js +181 -62
  86. package/dist/cluster/Sharding.js.map +1 -1
  87. package/dist/cluster/SqlMessageStorage.d.ts +59 -19
  88. package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
  89. package/dist/cluster/SqlMessageStorage.js +84 -55
  90. package/dist/cluster/SqlMessageStorage.js.map +1 -1
  91. package/dist/cluster/SqlRunnerStorage.d.ts +68 -19
  92. package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
  93. package/dist/cluster/SqlRunnerStorage.js +191 -113
  94. package/dist/cluster/SqlRunnerStorage.js.map +1 -1
  95. package/dist/cluster/internal/entityManager.js +104 -37
  96. package/dist/cluster/internal/entityManager.js.map +1 -1
  97. package/dist/http/FindMyWay/internal/router.js +3 -6
  98. package/dist/http/FindMyWay/internal/router.js.map +1 -1
  99. package/dist/http/HttpClient.d.ts +6 -0
  100. package/dist/http/HttpClient.d.ts.map +1 -1
  101. package/dist/http/HttpClient.js +39 -2
  102. package/dist/http/HttpClient.js.map +1 -1
  103. package/dist/http/HttpTraceContext.d.ts.map +1 -1
  104. package/dist/http/HttpTraceContext.js +4 -2
  105. package/dist/http/HttpTraceContext.js.map +1 -1
  106. package/dist/internal/effect.js +12 -7
  107. package/dist/internal/effect.js.map +1 -1
  108. package/dist/internal/version.js +1 -1
  109. package/dist/net/AddressResolver.d.ts +145 -0
  110. package/dist/net/AddressResolver.d.ts.map +1 -0
  111. package/dist/net/AddressResolver.js +114 -0
  112. package/dist/net/AddressResolver.js.map +1 -0
  113. package/dist/net/Dns.d.ts +596 -0
  114. package/dist/net/Dns.d.ts.map +1 -0
  115. package/dist/net/Dns.js +549 -0
  116. package/dist/net/Dns.js.map +1 -0
  117. package/dist/net/Host.d.ts +320 -0
  118. package/dist/net/Host.d.ts.map +1 -0
  119. package/dist/net/Host.js +342 -0
  120. package/dist/net/Host.js.map +1 -0
  121. package/dist/net/IpInterface.d.ts +76 -3
  122. package/dist/net/IpInterface.d.ts.map +1 -1
  123. package/dist/net/IpInterface.js +53 -4
  124. package/dist/net/IpInterface.js.map +1 -1
  125. package/dist/net/IpNetwork.d.ts +82 -8
  126. package/dist/net/IpNetwork.d.ts.map +1 -1
  127. package/dist/net/IpNetwork.js +60 -4
  128. package/dist/net/IpNetwork.js.map +1 -1
  129. package/dist/net/NetAddress.d.ts +388 -35
  130. package/dist/net/NetAddress.d.ts.map +1 -1
  131. package/dist/net/NetAddress.js +350 -34
  132. package/dist/net/NetAddress.js.map +1 -1
  133. package/dist/net/index.d.ts +15 -0
  134. package/dist/net/index.d.ts.map +1 -1
  135. package/dist/net/index.js +15 -0
  136. package/dist/net/index.js.map +1 -1
  137. package/dist/observability/OtlpMetrics.d.ts.map +1 -1
  138. package/dist/observability/OtlpMetrics.js +15 -21
  139. package/dist/observability/OtlpMetrics.js.map +1 -1
  140. package/dist/observability/PrometheusMetrics.js +1 -0
  141. package/dist/observability/PrometheusMetrics.js.map +1 -1
  142. package/dist/persistence/PersistedQueue.d.ts +32 -3
  143. package/dist/persistence/PersistedQueue.d.ts.map +1 -1
  144. package/dist/persistence/PersistedQueue.js +75 -16
  145. package/dist/persistence/PersistedQueue.js.map +1 -1
  146. package/dist/rpc/RpcClient.d.ts +22 -6
  147. package/dist/rpc/RpcClient.d.ts.map +1 -1
  148. package/dist/rpc/RpcClient.js +12 -5
  149. package/dist/rpc/RpcClient.js.map +1 -1
  150. package/dist/rpc/RpcServer.d.ts +6 -0
  151. package/dist/rpc/RpcServer.d.ts.map +1 -1
  152. package/dist/rpc/RpcServer.js +42 -20
  153. package/dist/rpc/RpcServer.js.map +1 -1
  154. package/dist/socket/DatagramSocket.d.ts +456 -0
  155. package/dist/socket/DatagramSocket.d.ts.map +1 -0
  156. package/dist/socket/DatagramSocket.js +535 -0
  157. package/dist/socket/DatagramSocket.js.map +1 -0
  158. package/dist/socket/Socket.d.ts +14 -1
  159. package/dist/socket/Socket.d.ts.map +1 -1
  160. package/dist/socket/Socket.js +9 -1
  161. package/dist/socket/Socket.js.map +1 -1
  162. package/dist/socket/index.d.ts +5 -0
  163. package/dist/socket/index.d.ts.map +1 -1
  164. package/dist/socket/index.js +5 -0
  165. package/dist/socket/index.js.map +1 -1
  166. package/dist/sql/Migrator.d.ts.map +1 -1
  167. package/dist/sql/Migrator.js +7 -2
  168. package/dist/sql/Migrator.js.map +1 -1
  169. package/dist/sql/SqlClient.d.ts +7 -1
  170. package/dist/sql/SqlClient.d.ts.map +1 -1
  171. package/dist/sql/SqlClient.js +31 -6
  172. package/dist/sql/SqlClient.js.map +1 -1
  173. package/dist/testing/TestCrypto.d.ts +73 -0
  174. package/dist/testing/TestCrypto.d.ts.map +1 -0
  175. package/dist/testing/TestCrypto.js +87 -0
  176. package/dist/testing/TestCrypto.js.map +1 -0
  177. package/dist/testing/index.d.ts +5 -0
  178. package/dist/testing/index.d.ts.map +1 -1
  179. package/dist/testing/index.js +5 -0
  180. package/dist/testing/index.js.map +1 -1
  181. package/dist/workflow/DurableDeferred.d.ts +54 -0
  182. package/dist/workflow/DurableDeferred.d.ts.map +1 -1
  183. package/dist/workflow/DurableDeferred.js +78 -49
  184. package/dist/workflow/DurableDeferred.js.map +1 -1
  185. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  186. package/dist/workflow/WorkflowEngine.js +2 -1
  187. package/dist/workflow/WorkflowEngine.js.map +1 -1
  188. package/package.json +1 -1
  189. package/src/Context.ts +3 -3
  190. package/src/Crypto.ts +43 -9
  191. package/src/DateTime.ts +9 -6
  192. package/src/Duration.ts +36 -16
  193. package/src/FileSystem.ts +48 -2
  194. package/src/Schema.ts +669 -5
  195. package/src/Stdio.ts +9 -2
  196. package/src/Stream.ts +11 -11
  197. package/src/ai/McpServer.ts +63 -23
  198. package/src/ai/internal/mcpProtocol/v2026_07_28.ts +5 -0
  199. package/src/ai/internal/mcpProtocol.ts +3 -0
  200. package/src/ai/internal/mcpRuntime.ts +2 -0
  201. package/src/cli/CliOutput.ts +33 -16
  202. package/src/cli/Command.ts +4 -3
  203. package/src/cli/Completions.ts +9 -6
  204. package/src/cli/GlobalFlag.ts +19 -11
  205. package/src/cli/Prompt.ts +159 -149
  206. package/src/cli/internal/ansi.ts +26 -3
  207. package/src/cli/internal/completions/powershell.ts +322 -0
  208. package/src/cli/internal/completions/zsh.ts +4 -2
  209. package/src/cli/internal/wizard.ts +34 -26
  210. package/src/cluster/ClusterSchema.ts +32 -0
  211. package/src/cluster/ClusterWorkflowEngine.ts +13 -24
  212. package/src/cluster/MessageStorage.ts +45 -14
  213. package/src/cluster/Runners.ts +8 -2
  214. package/src/cluster/Sharding.ts +207 -49
  215. package/src/cluster/SqlMessageStorage.ts +118 -84
  216. package/src/cluster/SqlRunnerStorage.ts +224 -128
  217. package/src/cluster/internal/entityManager.ts +151 -55
  218. package/src/http/FindMyWay/internal/router.ts +4 -7
  219. package/src/http/HttpClient.ts +56 -2
  220. package/src/http/HttpTraceContext.ts +8 -2
  221. package/src/internal/effect.ts +15 -13
  222. package/src/internal/version.ts +1 -1
  223. package/src/net/AddressResolver.ts +222 -0
  224. package/src/net/Dns.ts +1035 -0
  225. package/src/net/Host.ts +472 -0
  226. package/src/net/IpInterface.ts +117 -7
  227. package/src/net/IpNetwork.ts +116 -17
  228. package/src/net/NetAddress.ts +611 -76
  229. package/src/net/index.ts +18 -0
  230. package/src/observability/OtlpMetrics.ts +12 -23
  231. package/src/observability/PrometheusMetrics.ts +1 -0
  232. package/src/persistence/PersistedQueue.ts +146 -53
  233. package/src/rpc/RpcClient.ts +41 -19
  234. package/src/rpc/RpcServer.ts +60 -25
  235. package/src/socket/DatagramSocket.ts +919 -0
  236. package/src/socket/Socket.ts +22 -2
  237. package/src/socket/index.ts +6 -0
  238. package/src/sql/Migrator.ts +21 -5
  239. package/src/sql/SqlClient.ts +39 -15
  240. package/src/testing/TestCrypto.ts +94 -0
  241. package/src/testing/index.ts +6 -0
  242. package/src/workflow/DurableDeferred.ts +123 -48
  243. package/src/workflow/WorkflowEngine.ts +2 -1
@@ -0,0 +1,472 @@
1
+ /**
2
+ * Host names and unresolved host and port endpoints.
3
+ *
4
+ * A `Host` is either a numeric IP address, an IPv6 literal with a zone such as
5
+ * `fe80::1%eth0`, or a DNS domain name. Numeric addresses are parsed directly
6
+ * into `NetAddress` values. The `AddressResolver` service turns a `HostPort`
7
+ * into internet addresses, looking up domain names with the `Dns` service.
8
+ *
9
+ * @stability experimental
10
+ * @since 4.0.0
11
+ */
12
+ import type * as Brand from "../Brand.ts"
13
+ import * as Equal from "../Equal.ts"
14
+ import * as Hash from "../Hash.ts"
15
+ import * as Inspectable from "../Inspectable.ts"
16
+ import { hasProperty } from "../Predicate.ts"
17
+ import * as Result from "../Result.ts"
18
+ import * as NetAddress from "./NetAddress.ts"
19
+
20
+ const TypeId = "~effect/net/Host" as const
21
+
22
+ /**
23
+ * A syntactically valid DNS domain name in lowercase ASCII form.
24
+ *
25
+ * **Details**
26
+ *
27
+ * Names follow the rules used by Go's resolver: at most 253 characters, labels
28
+ * of 1 to 63 letters, digits, hyphens, or underscores, and no label starting or
29
+ * ending with a hyphen. Underscores are allowed so that service names such as
30
+ * `_http._tcp.example.com` are valid. The last label must not be numeric, which
31
+ * keeps IPv4-like strings out. A trailing dot marks a fully qualified name and
32
+ * is preserved. The root name `.` is valid.
33
+ *
34
+ * Internationalized names are stored in their ASCII (`xn--`) form.
35
+ *
36
+ * @see {@link domainNameFromString} for parsing and normalizing domain names
37
+ * @stability experimental
38
+ * @category models
39
+ * @since 4.0.0
40
+ */
41
+ export type DomainName = Brand.Branded<string, "~effect/net/Host/DomainName">
42
+
43
+ /**
44
+ * A domain name or a string to parse and normalize.
45
+ *
46
+ * @stability experimental
47
+ * @category models
48
+ * @since 4.0.0
49
+ */
50
+ export type DomainNameInput = DomainName | string
51
+
52
+ /**
53
+ * A host as written in configuration or a URL authority: a numeric IP address,
54
+ * a scoped IPv6 literal, or a domain name.
55
+ *
56
+ * @see {@link hostFromString} for parsing hosts
57
+ * @stability experimental
58
+ * @category models
59
+ * @since 4.0.0
60
+ */
61
+ export type Host = NetAddress.IpAddress | NetAddress.ScopedIpv6Literal | DomainName
62
+
63
+ /**
64
+ * A host, or a string or any `IpAddressInput` to convert to one.
65
+ *
66
+ * @stability experimental
67
+ * @category models
68
+ * @since 4.0.0
69
+ */
70
+ export type HostInput = Host | NetAddress.IpAddressInput
71
+
72
+ /**
73
+ * An unresolved host and port.
74
+ *
75
+ * @see {@link hostPortFromString} for parsing `host:port` strings
76
+ * @see {@link formatHostPort} for formatting `host:port` strings
77
+ * @stability experimental
78
+ * @category models
79
+ * @since 4.0.0
80
+ */
81
+ export interface HostPort extends Equal.Equal, Hash.Hash, Inspectable.Inspectable {
82
+ readonly _tag: "HostPort"
83
+ readonly host: Host
84
+ readonly port: number
85
+ readonly [TypeId]: typeof TypeId
86
+ }
87
+
88
+ /**
89
+ * The host and port of an endpoint.
90
+ *
91
+ * @see {@link hostPort} for the checked constructor
92
+ * @stability experimental
93
+ * @category models
94
+ * @since 4.0.0
95
+ */
96
+ export interface HostPortParts {
97
+ readonly host: HostInput
98
+ readonly port: number
99
+ }
100
+
101
+ /**
102
+ * An endpoint, or a `host:port` string or parts to convert to one.
103
+ *
104
+ * @stability experimental
105
+ * @category models
106
+ * @since 4.0.0
107
+ */
108
+ export type HostPortInput = HostPort | string | HostPortParts
109
+
110
+ const hostError = (input: unknown, message: string): Result.Result<never, NetAddress.NetAddressError> =>
111
+ Result.fail(new NetAddress.NetAddressError({ input, message }))
112
+
113
+ const asciiNameCharacter = /^[a-z0-9_.-]$/i
114
+ const labelPattern = /^[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9_])?$/
115
+ const numericLabel = /^(?:\d+|0x[0-9a-f]*)$/
116
+
117
+ const validateDomainName = (name: string): string | undefined => {
118
+ if (name === ".") return undefined
119
+ const relative = name.endsWith(".") ? name.slice(0, -1) : name
120
+ if (relative.length === 0) return "domain name must not be empty"
121
+ if (relative.length > 253) return "domain name must be at most 253 characters"
122
+ const labels = relative.split(".")
123
+ for (const label of labels) {
124
+ if (label.length === 0) return "domain name labels must not be empty"
125
+ if (label.length > 63) return "domain name labels must be at most 63 characters"
126
+ if (!labelPattern.test(label)) {
127
+ return "domain name labels must contain only letters, digits, hyphens, or underscores and must not start or end with a hyphen"
128
+ }
129
+ }
130
+ if (numericLabel.test(labels[labels.length - 1])) {
131
+ return "the last domain name label must not be numeric"
132
+ }
133
+ return undefined
134
+ }
135
+
136
+ const toAsciiDomainName = (input: string): string | undefined => {
137
+ let ascii = true
138
+ for (const character of input) {
139
+ if (character.charCodeAt(0) < 0x80) {
140
+ if (!asciiNameCharacter.test(character)) return undefined
141
+ } else {
142
+ ascii = false
143
+ }
144
+ }
145
+ if (ascii) return input.toLowerCase()
146
+ try {
147
+ return new URL(`http://${input}`).hostname
148
+ } catch {
149
+ return undefined
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Returns `true` when a value is a valid, normalized domain name.
155
+ *
156
+ * @stability experimental
157
+ * @category guards
158
+ * @since 4.0.0
159
+ */
160
+ export const isDomainName = (u: unknown): u is DomainName =>
161
+ typeof u === "string" && /^[a-z0-9_.-]+$/.test(u) && validateDomainName(u) === undefined
162
+
163
+ /**
164
+ * Returns `true` when a value is a host.
165
+ *
166
+ * @stability experimental
167
+ * @category guards
168
+ * @since 4.0.0
169
+ */
170
+ export const isHost = (u: unknown): u is Host =>
171
+ NetAddress.isIpAddress(u) || NetAddress.isScopedIpv6Literal(u) || isDomainName(u)
172
+
173
+ /**
174
+ * Returns `true` when a value is an unresolved host and port.
175
+ *
176
+ * @stability experimental
177
+ * @category guards
178
+ * @since 4.0.0
179
+ */
180
+ export const isHostPort = (u: unknown): u is HostPort => hasProperty(u, TypeId)
181
+
182
+ /**
183
+ * Parses and normalizes a domain name.
184
+ *
185
+ * **Details**
186
+ *
187
+ * ASCII names are lowercased. Names containing non-ASCII characters are
188
+ * converted to their ASCII (`xn--`) form using the WHATWG URL host parser, then
189
+ * validated with the same rules as ASCII names. A trailing dot is preserved.
190
+ *
191
+ * **Example** (Normalizing domain names)
192
+ *
193
+ * ```ts import.meta.vitest
194
+ * import { Result } from "effect"
195
+ * import { Host } from "effect/net"
196
+ *
197
+ * Result.getOrThrow(Host.domainNameFromString("Example.COM")) // => "example.com"
198
+ * Result.getOrThrow(Host.domainNameFromString("bücher.example")) // => "xn--bcher-kva.example"
199
+ * Result.isFailure(Host.domainNameFromString("1.2.3.4")) // => true
200
+ * ```
201
+ *
202
+ * @stability experimental
203
+ * @category decoding
204
+ * @since 4.0.0
205
+ */
206
+ export const domainNameFromString = (input: string): Result.Result<DomainName, NetAddress.NetAddressError> => {
207
+ const name = toAsciiDomainName(input)
208
+ if (name === undefined) return hostError(input, "invalid domain name")
209
+ const error = validateDomainName(name)
210
+ return error === undefined ? Result.succeed(name as DomainName) : hostError(input, error)
211
+ }
212
+
213
+ /**
214
+ * Parses a trusted domain name, throwing on failure.
215
+ *
216
+ * @stability experimental
217
+ * @category unsafe
218
+ * @since 4.0.0
219
+ */
220
+ export const domainNameFromStringUnsafe = (input: string): DomainName => Result.getOrThrow(domainNameFromString(input))
221
+
222
+ /**
223
+ * Converts a `DomainNameInput` to a domain name, parsing and normalizing it like
224
+ * `domainNameFromString`.
225
+ *
226
+ * **Details**
227
+ *
228
+ * Domain names are strings, so every input is parsed.
229
+ *
230
+ * @stability experimental
231
+ * @category constructors
232
+ * @since 4.0.0
233
+ */
234
+ export const domainNameFromInput = (input: DomainNameInput): Result.Result<DomainName, NetAddress.NetAddressError> =>
235
+ domainNameFromString(input)
236
+
237
+ /**
238
+ * Converts a trusted `DomainNameInput` to a domain name, throwing on failure.
239
+ *
240
+ * @stability experimental
241
+ * @category unsafe
242
+ * @since 4.0.0
243
+ */
244
+ export const domainNameFromInputUnsafe = (input: DomainNameInput): DomainName =>
245
+ Result.getOrThrow(domainNameFromInput(input))
246
+
247
+ /**
248
+ * Parses a host as a numeric IP address, a scoped IPv6 literal, or a domain
249
+ * name, in that order.
250
+ *
251
+ * **Example** (Parsing hosts)
252
+ *
253
+ * ```ts import.meta.vitest
254
+ * import { Result } from "effect"
255
+ * import { Host, NetAddress } from "effect/net"
256
+ *
257
+ * NetAddress.isIpAddress(Result.getOrThrow(Host.hostFromString("10.0.0.5"))) // => true
258
+ * Result.getOrThrow(Host.hostFromString("fe80::1%eth0")) // => "fe80::1%eth0"
259
+ * Result.getOrThrow(Host.hostFromString("DB.internal")) // => "db.internal"
260
+ * ```
261
+ *
262
+ * @stability experimental
263
+ * @category decoding
264
+ * @since 4.0.0
265
+ */
266
+ export const hostFromString = (input: string): Result.Result<Host, NetAddress.NetAddressError> => {
267
+ if (input.includes("%")) return NetAddress.scopedIpv6LiteralFromString(input)
268
+ if (input.includes(":")) return NetAddress.ipFromString(input)
269
+ if (/^[\d.]+$/.test(input)) {
270
+ const address = NetAddress.ipFromString(input)
271
+ if (Result.isSuccess(address)) return address
272
+ }
273
+ return domainNameFromString(input)
274
+ }
275
+
276
+ /**
277
+ * Parses a trusted host, throwing on failure.
278
+ *
279
+ * @stability experimental
280
+ * @category unsafe
281
+ * @since 4.0.0
282
+ */
283
+ export const hostFromStringUnsafe = (input: string): Host => Result.getOrThrow(hostFromString(input))
284
+
285
+ /**
286
+ * Converts a `HostInput` to a host, parsing strings like `hostFromString` and
287
+ * converting other inputs like `NetAddress.ipFromInput`.
288
+ *
289
+ * @stability experimental
290
+ * @category constructors
291
+ * @since 4.0.0
292
+ */
293
+ export const hostFromInput = (input: HostInput): Result.Result<Host, NetAddress.NetAddressError> =>
294
+ typeof input === "string" ? hostFromString(input) : NetAddress.ipFromInput(input)
295
+
296
+ /**
297
+ * Converts a trusted `HostInput` to a host, throwing on failure.
298
+ *
299
+ * @stability experimental
300
+ * @category unsafe
301
+ * @since 4.0.0
302
+ */
303
+ export const hostFromInputUnsafe = (input: HostInput): Host => Result.getOrThrow(hostFromInput(input))
304
+
305
+ /**
306
+ * Formats a host without brackets.
307
+ *
308
+ * @stability experimental
309
+ * @category encoding
310
+ * @since 4.0.0
311
+ */
312
+ export const formatHost = (self: Host): string => typeof self === "string" ? self : NetAddress.formatIp(self)
313
+
314
+ /**
315
+ * Returns `true` when a domain name ends with a dot, which excludes it from
316
+ * resolver search domains.
317
+ *
318
+ * @stability experimental
319
+ * @category predicates
320
+ * @since 4.0.0
321
+ */
322
+ export const isFullyQualified = (self: DomainName): boolean => self.endsWith(".")
323
+
324
+ const HostPortProto = {
325
+ ...Inspectable.BaseProto,
326
+ _tag: "HostPort",
327
+ [TypeId]: TypeId,
328
+ [Equal.symbol](this: HostPort, that: Equal.Equal): boolean {
329
+ return isHostPort(that) && this.port === that.port && Equal.equals(this.host, that.host)
330
+ },
331
+ [Hash.symbol](this: HostPort): number {
332
+ return Hash.combine(Hash.hash(this.host), Hash.number(this.port))
333
+ },
334
+ toString(this: HostPort): string {
335
+ return formatHostPort(this)
336
+ },
337
+ toJSON(this: HostPort): string {
338
+ return this.toString()
339
+ }
340
+ }
341
+
342
+ const isPort = (port: number): boolean => Number.isInteger(port) && port >= 0 && port <= 0xffff
343
+
344
+ const makeHostPort = (host: Host, port: number): HostPort => {
345
+ const self = Object.create(HostPortProto)
346
+ self.host = host
347
+ self.port = port
348
+ return Object.freeze(self)
349
+ }
350
+
351
+ /**
352
+ * Creates a checked host and port.
353
+ *
354
+ * @stability experimental
355
+ * @category constructors
356
+ * @since 4.0.0
357
+ */
358
+ export const hostPort = (host: Host, port: number): Result.Result<HostPort, NetAddress.NetAddressError> => {
359
+ if (!isHost(host)) return hostError(host, "expected a host")
360
+ if (!isPort(port)) return hostError(port, "port must be an integer from 0 through 65535")
361
+ return Result.succeed(makeHostPort(host, port))
362
+ }
363
+
364
+ /**
365
+ * Creates a trusted host and port, throwing on failure.
366
+ *
367
+ * @stability experimental
368
+ * @category unsafe
369
+ * @since 4.0.0
370
+ */
371
+ export const hostPortUnsafe = (host: Host, port: number): HostPort => Result.getOrThrow(hostPort(host, port))
372
+
373
+ /**
374
+ * Parses `host:port`, `[IPv6]:port`, or `[IPv6%zone]:port`.
375
+ *
376
+ * **Details**
377
+ *
378
+ * IPv6 hosts must be bracketed and other hosts must not be. The port is
379
+ * required and must be an unpadded decimal integer from 0 through 65535. A
380
+ * zone follows the `%` directly; the percent-encoded `%25` separator of RFC
381
+ * 6874 URLs is not decoded, because `[fe80::1%253]:80` would be ambiguous.
382
+ *
383
+ * **Example** (Parsing host and port strings)
384
+ *
385
+ * ```ts import.meta.vitest
386
+ * import { Result } from "effect"
387
+ * import { Host } from "effect/net"
388
+ *
389
+ * const endpoint = Result.getOrThrow(Host.hostPortFromString("DB.internal:5432"))
390
+ * endpoint.host // => "db.internal"
391
+ * endpoint.port // => 5432
392
+ * Host.formatHostPort(Result.getOrThrow(Host.hostPortFromString("[::1]:80"))) // => "[::1]:80"
393
+ * ```
394
+ *
395
+ * @see {@link formatHostPort} for the inverse operation
396
+ * @stability experimental
397
+ * @category decoding
398
+ * @since 4.0.0
399
+ */
400
+ export const hostPortFromString = (input: string): Result.Result<HostPort, NetAddress.NetAddressError> => {
401
+ let hostText: string
402
+ let portText: string
403
+ if (input.startsWith("[")) {
404
+ const end = input.indexOf("]")
405
+ if (end === -1 || input[end + 1] !== ":") return hostError(input, "expected [IPv6]:port")
406
+ hostText = input.slice(1, end)
407
+ portText = input.slice(end + 2)
408
+ if (!hostText.includes(":")) return hostError(input, "only IPv6 addresses use brackets")
409
+ } else {
410
+ const separator = input.lastIndexOf(":")
411
+ if (separator === -1) return hostError(input, "expected host:port")
412
+ if (input.indexOf(":") !== separator) return hostError(input, "IPv6 addresses must be bracketed")
413
+ hostText = input.slice(0, separator)
414
+ portText = input.slice(separator + 1)
415
+ }
416
+ if (!/^(0|[1-9]\d*)$/.test(portText)) {
417
+ return hostError(input, "port must be an unpadded decimal integer")
418
+ }
419
+ const port = Number(portText)
420
+ if (!isPort(port)) return hostError(input, "port must be an integer from 0 through 65535")
421
+ return Result.map(hostFromString(hostText), (host) => makeHostPort(host, port))
422
+ }
423
+
424
+ /**
425
+ * Parses a trusted `host:port` string, throwing on failure.
426
+ *
427
+ * @stability experimental
428
+ * @category unsafe
429
+ * @since 4.0.0
430
+ */
431
+ export const hostPortFromStringUnsafe = (input: string): HostPort => Result.getOrThrow(hostPortFromString(input))
432
+
433
+ /**
434
+ * Converts a `HostPortInput` to an endpoint, parsing strings like
435
+ * `hostPortFromString`, converting parts like `hostPort`, and returning
436
+ * endpoints unchanged.
437
+ *
438
+ * @stability experimental
439
+ * @category constructors
440
+ * @since 4.0.0
441
+ */
442
+ export const hostPortFromInput = (input: HostPortInput): Result.Result<HostPort, NetAddress.NetAddressError> => {
443
+ if (isHostPort(input)) return Result.succeed(input)
444
+ if (typeof input === "string") return hostPortFromString(input)
445
+ if (!hasProperty(input, "host") || !hasProperty(input, "port")) {
446
+ return hostError(input, "expected a host and port")
447
+ }
448
+ const port = input.port
449
+ return Result.flatMap(hostFromInput(input.host), (host) => hostPort(host, port))
450
+ }
451
+
452
+ /**
453
+ * Converts a trusted `HostPortInput` to an endpoint, throwing on failure.
454
+ *
455
+ * @stability experimental
456
+ * @category unsafe
457
+ * @since 4.0.0
458
+ */
459
+ export const hostPortFromInputUnsafe = (input: HostPortInput): HostPort => Result.getOrThrow(hostPortFromInput(input))
460
+
461
+ /**
462
+ * Formats a host and port, bracketing IPv6 hosts.
463
+ *
464
+ * @see {@link hostPortFromString} for the inverse operation
465
+ * @stability experimental
466
+ * @category encoding
467
+ * @since 4.0.0
468
+ */
469
+ export const formatHostPort = (self: HostPort): string => {
470
+ const host = formatHost(self.host)
471
+ return host.includes(":") ? `[${host}]:${self.port}` : `${host}:${self.port}`
472
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import * as Equal from "../Equal.ts"
8
8
  import * as Hash from "../Hash.ts"
9
- import { NodeInspectSymbol } from "../Inspectable.ts"
9
+ import * as Inspectable from "../Inspectable.ts"
10
10
  import { hasProperty } from "../Predicate.ts"
11
11
  import * as Result from "../Result.ts"
12
12
  import * as NetAddress from "./NetAddress.ts"
@@ -23,13 +23,13 @@ const TypeId = "~effect/net/IpInterface" as const
23
23
  * @category models
24
24
  * @since 4.0.0
25
25
  */
26
- export interface IpInterface<out A extends NetAddress.IpAddress = NetAddress.IpAddress> extends Equal.Equal, Hash.Hash {
26
+ export interface IpInterface<out A extends NetAddress.IpAddress = NetAddress.IpAddress>
27
+ extends Equal.Equal, Hash.Hash, Inspectable.Inspectable
28
+ {
27
29
  readonly _tag: "IpInterface"
28
30
  readonly address: A
29
31
  readonly prefixLength: number
30
32
  readonly [TypeId]: typeof TypeId
31
- toString(): string
32
- toJSON(): string
33
33
  }
34
34
 
35
35
  /**
@@ -50,6 +50,46 @@ export type Ipv4Interface = IpInterface<NetAddress.Ipv4Address>
50
50
  */
51
51
  export type Ipv6Interface = IpInterface<NetAddress.Ipv6Address>
52
52
 
53
+ /**
54
+ * An address and prefix length, generic over the accepted address input.
55
+ *
56
+ * @see {@link make} for the checked constructor
57
+ * @stability unstable
58
+ * @category models
59
+ * @since 4.0.0
60
+ */
61
+ export interface IpInterfaceParts<A extends NetAddress.IpAddressInput = NetAddress.IpAddressInput> {
62
+ readonly address: A
63
+ readonly prefixLength: number
64
+ }
65
+
66
+ /**
67
+ * An interface address, or a string or parts to convert to one.
68
+ *
69
+ * @stability unstable
70
+ * @category models
71
+ * @since 4.0.0
72
+ */
73
+ export type IpInterfaceInput = IpInterface | string | IpInterfaceParts
74
+
75
+ /**
76
+ * An IPv4 interface address, or a string or parts to convert to one.
77
+ *
78
+ * @stability unstable
79
+ * @category models
80
+ * @since 4.0.0
81
+ */
82
+ export type Ipv4InterfaceInput = Ipv4Interface | string | IpInterfaceParts<NetAddress.Ipv4AddressInput>
83
+
84
+ /**
85
+ * An IPv6 interface address, or a string or parts to convert to one.
86
+ *
87
+ * @stability unstable
88
+ * @category models
89
+ * @since 4.0.0
90
+ */
91
+ export type Ipv6InterfaceInput = Ipv6Interface | string | IpInterfaceParts<NetAddress.Ipv6AddressInput>
92
+
53
93
  /**
54
94
  * Companion types for parsing IP interface addresses.
55
95
  *
@@ -100,6 +140,7 @@ export const isIpv6Interface = (u: unknown): u is Ipv6Interface =>
100
140
  export const isIpInterface = (u: unknown): u is IpInterface => hasProperty(u, TypeId)
101
141
 
102
142
  const IpInterfaceProto = {
143
+ ...Inspectable.BaseProto,
103
144
  _tag: "IpInterface",
104
145
  [TypeId]: TypeId,
105
146
  [Equal.symbol](this: IpInterface, that: Equal.Equal): boolean {
@@ -115,9 +156,6 @@ const IpInterfaceProto = {
115
156
  },
116
157
  toJSON(this: IpInterface): string {
117
158
  return this.toString()
118
- },
119
- [NodeInspectSymbol](this: IpInterface): string {
120
- return this.toJSON()
121
159
  }
122
160
  }
123
161
 
@@ -231,6 +269,78 @@ export const fromString = (
231
269
  make(address, parts.prefixLength ?? NetAddress.width(address)))
232
270
  )
233
271
 
272
+ const fromParts = <I extends NetAddress.IpAddressInput, A extends NetAddress.IpAddress>(
273
+ input: IpInterfaceParts<I>,
274
+ toAddress: (input: I) => Result.Result<A, NetAddress.NetAddressError>
275
+ ): Result.Result<IpInterface<A>, NetAddress.NetAddressError> =>
276
+ hasProperty(input, "address") && hasProperty(input, "prefixLength")
277
+ ? Result.flatMap(toAddress(input.address), (address) => make(address, input.prefixLength))
278
+ : interfaceError(input, "expected an address and prefix length")
279
+
280
+ /**
281
+ * Converts an `Ipv4InterfaceInput` to an IPv4 interface address, parsing
282
+ * strings like `ipv4FromString` with the given options, converting parts like
283
+ * `make`, and returning interface addresses unchanged.
284
+ *
285
+ * @stability unstable
286
+ * @category constructors
287
+ * @since 4.0.0
288
+ */
289
+ export const ipv4FromInput = (
290
+ input: Ipv4InterfaceInput,
291
+ options?: IpInterface.ParseOptions
292
+ ): Result.Result<Ipv4Interface, NetAddress.NetAddressError> => {
293
+ if (isIpv4Interface(input)) return Result.succeed(input)
294
+ if (typeof input === "string") return ipv4FromString(input, options)
295
+ return fromParts(input, NetAddress.ipv4FromInput)
296
+ }
297
+
298
+ /**
299
+ * Converts an `Ipv6InterfaceInput` to an IPv6 interface address, parsing
300
+ * strings like `ipv6FromString` with the given options, converting parts like
301
+ * `make`, and returning interface addresses unchanged.
302
+ *
303
+ * @stability unstable
304
+ * @category constructors
305
+ * @since 4.0.0
306
+ */
307
+ export const ipv6FromInput = (
308
+ input: Ipv6InterfaceInput,
309
+ options?: IpInterface.ParseOptions
310
+ ): Result.Result<Ipv6Interface, NetAddress.NetAddressError> => {
311
+ if (isIpv6Interface(input)) return Result.succeed(input)
312
+ if (typeof input === "string") return ipv6FromString(input, options)
313
+ return fromParts(input, NetAddress.ipv6FromInput)
314
+ }
315
+
316
+ /**
317
+ * Converts an `IpInterfaceInput` to an interface address, parsing strings like
318
+ * `fromString` with the given options, converting parts like `make`, and
319
+ * returning interface addresses unchanged.
320
+ *
321
+ * @stability unstable
322
+ * @category constructors
323
+ * @since 4.0.0
324
+ */
325
+ export const fromInput = (
326
+ input: IpInterfaceInput,
327
+ options?: IpInterface.ParseOptions
328
+ ): Result.Result<IpInterface, NetAddress.NetAddressError> => {
329
+ if (isIpInterface(input)) return Result.succeed(input)
330
+ if (typeof input === "string") return fromString(input, options)
331
+ return fromParts(input, NetAddress.ipFromInput)
332
+ }
333
+
334
+ /**
335
+ * Converts a trusted `IpInterfaceInput` to an interface address, throwing on failure.
336
+ *
337
+ * @stability unstable
338
+ * @category unsafe
339
+ * @since 4.0.0
340
+ */
341
+ export const fromInputUnsafe = (input: IpInterfaceInput, options?: IpInterface.ParseOptions): IpInterface =>
342
+ Result.getOrThrow(fromInput(input, options))
343
+
234
344
  /**
235
345
  * Creates a trusted interface address, throwing when its prefix length is invalid.
236
346
  *