effect 4.0.0-beta.27 → 4.0.0-beta.29

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 (208) hide show
  1. package/dist/Brand.d.ts +1 -1
  2. package/dist/Brand.d.ts.map +1 -1
  3. package/dist/Brand.js +1 -1
  4. package/dist/Brand.js.map +1 -1
  5. package/dist/ConfigProvider.d.ts +1 -1
  6. package/dist/Cron.d.ts +1 -1
  7. package/dist/Data.d.ts +1 -1
  8. package/dist/Data.d.ts.map +1 -1
  9. package/dist/Data.js.map +1 -1
  10. package/dist/Effect.d.ts +300 -184
  11. package/dist/Effect.d.ts.map +1 -1
  12. package/dist/Effect.js +99 -61
  13. package/dist/Effect.js.map +1 -1
  14. package/dist/Encoding.d.ts +1 -1
  15. package/dist/Exit.d.ts +24 -12
  16. package/dist/Exit.d.ts.map +1 -1
  17. package/dist/Exit.js +8 -4
  18. package/dist/Exit.js.map +1 -1
  19. package/dist/Fiber.d.ts +1 -0
  20. package/dist/Fiber.d.ts.map +1 -1
  21. package/dist/Fiber.js.map +1 -1
  22. package/dist/Function.d.ts +1 -9
  23. package/dist/Function.d.ts.map +1 -1
  24. package/dist/Function.js +2 -10
  25. package/dist/Function.js.map +1 -1
  26. package/dist/Graph.d.ts +1 -1
  27. package/dist/Newtype.d.ts +291 -0
  28. package/dist/Newtype.d.ts.map +1 -0
  29. package/dist/Newtype.js +161 -0
  30. package/dist/Newtype.js.map +1 -0
  31. package/dist/PlatformError.d.ts +2 -2
  32. package/dist/References.d.ts +6 -1
  33. package/dist/References.d.ts.map +1 -1
  34. package/dist/References.js +6 -1
  35. package/dist/References.js.map +1 -1
  36. package/dist/RequestResolver.d.ts +19 -19
  37. package/dist/RequestResolver.js +10 -10
  38. package/dist/RequestResolver.js.map +1 -1
  39. package/dist/Schedule.d.ts +142 -80
  40. package/dist/Schedule.d.ts.map +1 -1
  41. package/dist/Schedule.js +58 -32
  42. package/dist/Schedule.js.map +1 -1
  43. package/dist/Scheduler.d.ts +9 -0
  44. package/dist/Scheduler.d.ts.map +1 -1
  45. package/dist/Scheduler.js +11 -0
  46. package/dist/Scheduler.js.map +1 -1
  47. package/dist/Schema.d.ts +1 -1
  48. package/dist/Schema.d.ts.map +1 -1
  49. package/dist/Schema.js +3 -1
  50. package/dist/Schema.js.map +1 -1
  51. package/dist/SchemaAST.d.ts.map +1 -1
  52. package/dist/SchemaAST.js +1 -1
  53. package/dist/SchemaAST.js.map +1 -1
  54. package/dist/Stdio.d.ts +6 -2
  55. package/dist/Stdio.d.ts.map +1 -1
  56. package/dist/Stdio.js +2 -2
  57. package/dist/Stdio.js.map +1 -1
  58. package/dist/Stream.d.ts +8 -4
  59. package/dist/Stream.d.ts.map +1 -1
  60. package/dist/Stream.js +8 -4
  61. package/dist/Stream.js.map +1 -1
  62. package/dist/Types.d.ts +1 -22
  63. package/dist/Types.d.ts.map +1 -1
  64. package/dist/index.d.ts +71 -3
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +71 -3
  67. package/dist/index.js.map +1 -1
  68. package/dist/internal/effect.js +3 -1
  69. package/dist/internal/effect.js.map +1 -1
  70. package/dist/unstable/ai/LanguageModel.d.ts +12 -28
  71. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  72. package/dist/unstable/ai/LanguageModel.js +4 -18
  73. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  74. package/dist/unstable/ai/McpSchema.d.ts +20 -1
  75. package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
  76. package/dist/unstable/ai/McpSchema.js +8 -0
  77. package/dist/unstable/ai/McpSchema.js.map +1 -1
  78. package/dist/unstable/ai/McpServer.d.ts +65 -12
  79. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  80. package/dist/unstable/ai/McpServer.js +159 -45
  81. package/dist/unstable/ai/McpServer.js.map +1 -1
  82. package/dist/unstable/ai/Toolkit.d.ts +1 -1
  83. package/dist/unstable/ai/Toolkit.d.ts.map +1 -1
  84. package/dist/unstable/ai/Toolkit.js +4 -11
  85. package/dist/unstable/ai/Toolkit.js.map +1 -1
  86. package/dist/unstable/ai/internal/codec-transformer.js +0 -5
  87. package/dist/unstable/ai/internal/codec-transformer.js.map +1 -1
  88. package/dist/unstable/cli/Prompt.js +35 -8
  89. package/dist/unstable/cli/Prompt.js.map +1 -1
  90. package/dist/unstable/cluster/Message.d.ts +5 -5
  91. package/dist/unstable/cluster/Reply.d.ts +3 -3
  92. package/dist/unstable/encoding/Msgpack.d.ts +1 -1
  93. package/dist/unstable/encoding/Ndjson.d.ts +1 -1
  94. package/dist/unstable/encoding/Sse.d.ts +1 -1
  95. package/dist/unstable/eventlog/EventJournal.d.ts +1 -1
  96. package/dist/unstable/eventlog/EventLogRemote.d.ts +1 -1
  97. package/dist/unstable/http/Cookies.d.ts +45 -1
  98. package/dist/unstable/http/Cookies.d.ts.map +1 -1
  99. package/dist/unstable/http/Cookies.js +22 -0
  100. package/dist/unstable/http/Cookies.js.map +1 -1
  101. package/dist/unstable/http/HttpBody.d.ts +1 -1
  102. package/dist/unstable/http/HttpClient.d.ts.map +1 -1
  103. package/dist/unstable/http/HttpClient.js +3 -7
  104. package/dist/unstable/http/HttpClient.js.map +1 -1
  105. package/dist/unstable/http/HttpClientError.d.ts +7 -7
  106. package/dist/unstable/http/HttpClientRequest.d.ts +5 -0
  107. package/dist/unstable/http/HttpClientRequest.d.ts.map +1 -1
  108. package/dist/unstable/http/HttpClientRequest.js +21 -17
  109. package/dist/unstable/http/HttpClientRequest.js.map +1 -1
  110. package/dist/unstable/http/HttpEffect.d.ts +7 -0
  111. package/dist/unstable/http/HttpEffect.d.ts.map +1 -1
  112. package/dist/unstable/http/HttpEffect.js +6 -0
  113. package/dist/unstable/http/HttpEffect.js.map +1 -1
  114. package/dist/unstable/http/HttpServerError.d.ts +6 -6
  115. package/dist/unstable/http/HttpServerRequest.d.ts +11 -0
  116. package/dist/unstable/http/HttpServerRequest.d.ts.map +1 -1
  117. package/dist/unstable/http/HttpServerRequest.js +291 -1
  118. package/dist/unstable/http/HttpServerRequest.js.map +1 -1
  119. package/dist/unstable/http/HttpServerResponse.d.ts +47 -1
  120. package/dist/unstable/http/HttpServerResponse.d.ts.map +1 -1
  121. package/dist/unstable/http/HttpServerResponse.js +227 -0
  122. package/dist/unstable/http/HttpServerResponse.js.map +1 -1
  123. package/dist/unstable/http/HttpStaticServer.d.ts +69 -0
  124. package/dist/unstable/http/HttpStaticServer.d.ts.map +1 -0
  125. package/dist/unstable/http/HttpStaticServer.js +353 -0
  126. package/dist/unstable/http/HttpStaticServer.js.map +1 -0
  127. package/dist/unstable/http/Multipart.d.ts +1 -1
  128. package/dist/unstable/http/UrlParams.d.ts +1 -1
  129. package/dist/unstable/http/index.d.ts +4 -0
  130. package/dist/unstable/http/index.d.ts.map +1 -1
  131. package/dist/unstable/http/index.js +4 -0
  132. package/dist/unstable/http/index.js.map +1 -1
  133. package/dist/unstable/httpapi/HttpApiGroup.d.ts +1 -0
  134. package/dist/unstable/httpapi/HttpApiGroup.d.ts.map +1 -1
  135. package/dist/unstable/httpapi/HttpApiGroup.js.map +1 -1
  136. package/dist/unstable/persistence/KeyValueStore.d.ts +1 -1
  137. package/dist/unstable/reactivity/Atom.d.ts +7 -2
  138. package/dist/unstable/reactivity/Atom.d.ts.map +1 -1
  139. package/dist/unstable/reactivity/Atom.js +33 -15
  140. package/dist/unstable/reactivity/Atom.js.map +1 -1
  141. package/dist/unstable/reactivity/AtomHttpApi.d.ts +4 -6
  142. package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
  143. package/dist/unstable/reactivity/AtomHttpApi.js +39 -9
  144. package/dist/unstable/reactivity/AtomHttpApi.js.map +1 -1
  145. package/dist/unstable/reactivity/AtomRegistry.js +26 -2
  146. package/dist/unstable/reactivity/AtomRegistry.js.map +1 -1
  147. package/dist/unstable/reactivity/AtomRpc.d.ts +8 -8
  148. package/dist/unstable/reactivity/AtomRpc.d.ts.map +1 -1
  149. package/dist/unstable/reactivity/AtomRpc.js +46 -20
  150. package/dist/unstable/reactivity/AtomRpc.js.map +1 -1
  151. package/dist/unstable/rpc/Rpc.d.ts +1 -1
  152. package/dist/unstable/rpc/Rpc.d.ts.map +1 -1
  153. package/dist/unstable/rpc/Rpc.js.map +1 -1
  154. package/dist/unstable/rpc/RpcMiddleware.d.ts +5 -5
  155. package/dist/unstable/rpc/RpcMiddleware.d.ts.map +1 -1
  156. package/dist/unstable/rpc/RpcMiddleware.js.map +1 -1
  157. package/dist/unstable/rpc/RpcServer.js +2 -2
  158. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  159. package/dist/unstable/rpc/Utils.js +1 -1
  160. package/dist/unstable/rpc/Utils.js.map +1 -1
  161. package/dist/unstable/socket/SocketServer.d.ts +3 -3
  162. package/dist/unstable/sql/Migrator.d.ts +1 -1
  163. package/dist/unstable/sql/SqlResolver.js +2 -2
  164. package/dist/unstable/sql/SqlResolver.js.map +1 -1
  165. package/dist/unstable/workflow/Workflow.d.ts +1 -1
  166. package/package.json +1 -1
  167. package/src/Brand.ts +1 -1
  168. package/src/Data.ts +1 -2
  169. package/src/Effect.ts +300 -184
  170. package/src/Exit.ts +24 -12
  171. package/src/Fiber.ts +1 -0
  172. package/src/Function.ts +2 -10
  173. package/src/Newtype.ts +308 -0
  174. package/src/References.ts +6 -1
  175. package/src/RequestResolver.ts +20 -20
  176. package/src/Schedule.ts +142 -80
  177. package/src/Scheduler.ts +12 -0
  178. package/src/Schema.ts +4 -2
  179. package/src/SchemaAST.ts +1 -4
  180. package/src/Stdio.ts +8 -4
  181. package/src/Stream.ts +8 -4
  182. package/src/Types.ts +1 -23
  183. package/src/index.ts +72 -3
  184. package/src/internal/effect.ts +3 -0
  185. package/src/unstable/ai/LanguageModel.ts +16 -37
  186. package/src/unstable/ai/McpSchema.ts +14 -0
  187. package/src/unstable/ai/McpServer.ts +224 -53
  188. package/src/unstable/ai/Toolkit.ts +5 -14
  189. package/src/unstable/ai/internal/codec-transformer.ts +0 -7
  190. package/src/unstable/cli/Prompt.ts +31 -9
  191. package/src/unstable/http/Cookies.ts +84 -0
  192. package/src/unstable/http/HttpClient.ts +5 -6
  193. package/src/unstable/http/HttpClientRequest.ts +21 -17
  194. package/src/unstable/http/HttpEffect.ts +8 -0
  195. package/src/unstable/http/HttpServerRequest.ts +388 -1
  196. package/src/unstable/http/HttpServerResponse.ts +328 -1
  197. package/src/unstable/http/HttpStaticServer.ts +456 -0
  198. package/src/unstable/http/index.ts +5 -0
  199. package/src/unstable/httpapi/HttpApiGroup.ts +1 -0
  200. package/src/unstable/reactivity/Atom.ts +62 -35
  201. package/src/unstable/reactivity/AtomHttpApi.ts +45 -11
  202. package/src/unstable/reactivity/AtomRegistry.ts +30 -2
  203. package/src/unstable/reactivity/AtomRpc.ts +48 -17
  204. package/src/unstable/rpc/Rpc.ts +1 -3
  205. package/src/unstable/rpc/RpcMiddleware.ts +12 -6
  206. package/src/unstable/rpc/RpcServer.ts +2 -2
  207. package/src/unstable/rpc/Utils.ts +1 -1
  208. package/src/unstable/sql/SqlResolver.ts +2 -2
@@ -0,0 +1,456 @@
1
+ /**
2
+ * @since 4.0.0
3
+ */
4
+ import * as Effect from "../../Effect.ts"
5
+ import * as FileSystem from "../../FileSystem.ts"
6
+ import * as Layer from "../../Layer.ts"
7
+ import * as Path from "../../Path.ts"
8
+ import type { PlatformError } from "../../PlatformError.ts"
9
+ import * as HttpPlatform from "./HttpPlatform.ts"
10
+ import * as HttpRouter from "./HttpRouter.ts"
11
+ import * as HttpServerError from "./HttpServerError.ts"
12
+ import * as HttpServerRequest from "./HttpServerRequest.ts"
13
+ import * as HttpServerRespondable from "./HttpServerRespondable.ts"
14
+ import * as HttpServerResponse from "./HttpServerResponse.ts"
15
+
16
+ /**
17
+ * Creates an `HttpApp` that serves files from a directory.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { Effect } from "effect"
22
+ * import * as HttpStaticServer from "effect/unstable/http/HttpStaticServer"
23
+ *
24
+ * const program = Effect.gen(function*() {
25
+ * const app = yield* HttpStaticServer.make({ root: "./public" })
26
+ * return app
27
+ * })
28
+ * ```
29
+ *
30
+ * @since 4.0.0
31
+ * @category constructors
32
+ */
33
+ export const make: (options: {
34
+ readonly root: string
35
+ readonly index?: string | undefined
36
+ readonly spa?: boolean | undefined
37
+ readonly cacheControl?: string | undefined
38
+ readonly mimeTypes?: Record<string, string> | undefined
39
+ }) => Effect.Effect<
40
+ Effect.Effect<
41
+ HttpServerResponse.HttpServerResponse,
42
+ HttpServerError.HttpServerError,
43
+ HttpServerRequest.HttpServerRequest
44
+ >,
45
+ PlatformError,
46
+ FileSystem.FileSystem | Path.Path | HttpPlatform.HttpPlatform
47
+ > = Effect.fnUntraced(function*(options) {
48
+ const fileSystem = yield* FileSystem.FileSystem
49
+ const path = yield* Path.Path
50
+ const platform = yield* HttpPlatform.HttpPlatform
51
+
52
+ const resolvedRoot = path.resolve(options.root)
53
+ const index = "index" in options ? options.index : "index.html"
54
+ const spa = options.spa === true
55
+ const cacheControl = options.cacheControl
56
+ const mimeTypes = {
57
+ ...defaultMimeTypes,
58
+ ...options.mimeTypes
59
+ }
60
+
61
+ const setFileHeaders = (
62
+ response: HttpServerResponse.HttpServerResponse,
63
+ filePath: string
64
+ ): HttpServerResponse.HttpServerResponse => {
65
+ let currentResponse = HttpServerResponse.setHeaders(response, {
66
+ "Content-Type": resolveMimeType(path, filePath, mimeTypes),
67
+ "Accept-Ranges": "bytes"
68
+ })
69
+ if (cacheControl !== undefined) {
70
+ currentResponse = HttpServerResponse.setHeader(currentResponse, "Cache-Control", cacheControl)
71
+ }
72
+ return currentResponse
73
+ }
74
+
75
+ const serveFile: (
76
+ request: HttpServerRequest.HttpServerRequest,
77
+ filePath: string,
78
+ fileSize?: number
79
+ ) => Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError.HttpServerError> = Effect.fnUntraced(
80
+ function*(request, filePath, fileSize) {
81
+ const rangeHeader = request.headers["range"]
82
+ const shouldEvaluateConditionals = request.headers["if-none-match"] !== undefined ||
83
+ request.headers["if-modified-since"] !== undefined
84
+
85
+ let fullResponse: HttpServerResponse.HttpServerResponse | undefined
86
+ if (shouldEvaluateConditionals) {
87
+ fullResponse = setFileHeaders(yield* handlePlatformError(request, platform.fileResponse(filePath)), filePath)
88
+ const conditionalResponse = evaluateConditionalRequest(request, fullResponse)
89
+ if (conditionalResponse !== undefined) {
90
+ return conditionalResponse
91
+ }
92
+ if (rangeHeader === undefined) {
93
+ return fullResponse
94
+ }
95
+ }
96
+
97
+ const resolvedFileSize = rangeHeader === undefined
98
+ ? undefined
99
+ : fileSize ?? Number((yield* handlePlatformError(request, fileSystem.stat(filePath))).size)
100
+ const parsedRange = rangeHeader === undefined || resolvedFileSize === undefined
101
+ ? undefined
102
+ : parseRange(rangeHeader, resolvedFileSize)
103
+
104
+ if (parsedRange === undefined) {
105
+ return fullResponse ??
106
+ setFileHeaders(yield* handlePlatformError(request, platform.fileResponse(filePath)), filePath)
107
+ }
108
+
109
+ if (parsedRange === "unsatisfiable") {
110
+ return HttpServerResponse.empty({
111
+ status: 416,
112
+ headers: {
113
+ "Content-Range": `bytes */${resolvedFileSize}`
114
+ }
115
+ })
116
+ }
117
+
118
+ let response = setFileHeaders(
119
+ yield* handlePlatformError(
120
+ request,
121
+ platform.fileResponse(filePath, {
122
+ status: 206,
123
+ offset: parsedRange.start,
124
+ bytesToRead: parsedRange.end - parsedRange.start + 1
125
+ })
126
+ ),
127
+ filePath
128
+ )
129
+
130
+ response = HttpServerResponse.setHeader(
131
+ response,
132
+ "Content-Range",
133
+ `bytes ${parsedRange.start}-${parsedRange.end}/${resolvedFileSize}`
134
+ )
135
+
136
+ return response
137
+ }
138
+ )
139
+
140
+ // @effect-diagnostics-next-line returnEffectInGen:off
141
+ return HttpServerRequest.HttpServerRequest.use((request) => {
142
+ const resolvedPath = resolveFilePath(path, resolvedRoot, request.url)
143
+ if (resolvedPath === undefined) {
144
+ return Effect.fail(toRouteNotFoundError(request))
145
+ }
146
+
147
+ return Effect.matchEffect(fileSystem.stat(resolvedPath), {
148
+ onFailure: (error) =>
149
+ error.reason._tag === "NotFound" &&
150
+ spa && index !== undefined && path.extname(resolvedPath) === "" && acceptsHtml(request.headers["accept"])
151
+ ? serveFile(request, path.join(resolvedRoot, index))
152
+ : error.reason._tag === "NotFound"
153
+ ? Effect.fail(toRouteNotFoundError(request))
154
+ : Effect.fail(toInternalServerError(request, error)),
155
+ onSuccess(info) {
156
+ if (info.type === "File") {
157
+ return serveFile(request, resolvedPath, Number(info.size))
158
+ }
159
+ if (info.type === "Directory" && index !== undefined) {
160
+ return serveFile(request, path.join(resolvedPath, index))
161
+ }
162
+ return Effect.fail(toRouteNotFoundError(request))
163
+ }
164
+ })
165
+ })
166
+ })
167
+
168
+ /**
169
+ * Creates a layer that mounts static files on an `HttpRouter`.
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * import { Layer } from "effect"
174
+ * import * as HttpRouter from "effect/unstable/http/HttpRouter"
175
+ * import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"
176
+ * import * as HttpStaticServer from "effect/unstable/http/HttpStaticServer"
177
+ *
178
+ * const ApiLayer = HttpRouter.add("GET", "/health", HttpServerResponse.text("ok"))
179
+ *
180
+ * const StaticFilesLayer = HttpStaticServer.layer({
181
+ * root: "./public",
182
+ * prefix: "/static"
183
+ * })
184
+ *
185
+ * const AppLayer = Layer.mergeAll(ApiLayer, StaticFilesLayer)
186
+ * ```
187
+ *
188
+ * @since 4.0.0
189
+ * @category layers
190
+ */
191
+ export const layer = (options: {
192
+ readonly root: string
193
+ readonly index?: string | undefined
194
+ readonly spa?: boolean | undefined
195
+ readonly cacheControl?: string | undefined
196
+ readonly mimeTypes?: Record<string, string> | undefined
197
+ readonly prefix?: string | undefined
198
+ }): Layer.Layer<
199
+ never,
200
+ PlatformError,
201
+ HttpRouter.HttpRouter | FileSystem.FileSystem | Path.Path | HttpPlatform.HttpPlatform
202
+ > =>
203
+ Layer.effectDiscard(Effect.gen(function*() {
204
+ const router = yield* HttpRouter.HttpRouter
205
+ const handler = (yield* make(options)).pipe(
206
+ Effect.catch(HttpServerRespondable.toResponse)
207
+ )
208
+ if (options.prefix !== undefined) {
209
+ yield* router.prefixed(options.prefix).add("GET", "/*", handler)
210
+ return
211
+ }
212
+ yield* router.add("GET", "/*", handler)
213
+ }))
214
+
215
+ const defaultMimeTypes: Record<string, string> = {
216
+ html: "text/html; charset=utf-8",
217
+ htm: "text/html; charset=utf-8",
218
+ css: "text/css; charset=utf-8",
219
+ js: "text/javascript; charset=utf-8",
220
+ mjs: "text/javascript; charset=utf-8",
221
+ json: "application/json; charset=utf-8",
222
+ xml: "application/xml; charset=utf-8",
223
+ txt: "text/plain; charset=utf-8",
224
+ csv: "text/csv; charset=utf-8",
225
+ md: "text/markdown; charset=utf-8",
226
+ yaml: "text/yaml; charset=utf-8",
227
+ yml: "text/yaml; charset=utf-8",
228
+ png: "image/png",
229
+ jpg: "image/jpeg",
230
+ jpeg: "image/jpeg",
231
+ gif: "image/gif",
232
+ svg: "image/svg+xml; charset=utf-8",
233
+ ico: "image/x-icon",
234
+ webp: "image/webp",
235
+ avif: "image/avif",
236
+ woff: "font/woff",
237
+ woff2: "font/woff2",
238
+ ttf: "font/ttf",
239
+ otf: "font/otf",
240
+ eot: "application/vnd.ms-fontobject",
241
+ mp3: "audio/mpeg",
242
+ mp4: "video/mp4",
243
+ webm: "video/webm",
244
+ ogg: "audio/ogg",
245
+ wav: "audio/wav",
246
+ flac: "audio/flac",
247
+ aac: "audio/aac",
248
+ pdf: "application/pdf",
249
+ zip: "application/zip",
250
+ gz: "application/gzip",
251
+ wasm: "application/wasm",
252
+ map: "application/json",
253
+ webmanifest: "application/manifest+json"
254
+ }
255
+
256
+ const stripQueryString = (url: string): string => {
257
+ const queryIndex = url.indexOf("?")
258
+ return queryIndex === -1 ? url : url.slice(0, queryIndex)
259
+ }
260
+
261
+ const resolveMimeType = (path: Path.Path, filePath: string, mimeTypes: Record<string, string>): string => {
262
+ const extension = path.extname(filePath).toLowerCase()
263
+ if (extension.length <= 1) {
264
+ return "application/octet-stream"
265
+ }
266
+ return mimeTypes[extension.slice(1)] ?? "application/octet-stream"
267
+ }
268
+
269
+ const parseInteger = (value: string): number | undefined => {
270
+ if (!/^\d+$/.test(value)) {
271
+ return undefined
272
+ }
273
+ const parsed = Number(value)
274
+ return Number.isSafeInteger(parsed) ? parsed : undefined
275
+ }
276
+
277
+ const parseRange = (
278
+ header: string,
279
+ fileSize: number
280
+ ):
281
+ | { readonly start: number; readonly end: number }
282
+ | "unsatisfiable"
283
+ | undefined =>
284
+ {
285
+ const value = header.trim()
286
+ if (!value.toLowerCase().startsWith("bytes=")) {
287
+ return undefined
288
+ }
289
+ const rangeValue = value.slice(6).trim()
290
+ if (rangeValue.length === 0 || rangeValue.includes(",")) {
291
+ return undefined
292
+ }
293
+ const separatorIndex = rangeValue.indexOf("-")
294
+ if (separatorIndex === -1) {
295
+ return undefined
296
+ }
297
+ const startPart = rangeValue.slice(0, separatorIndex).trim()
298
+ const endPart = rangeValue.slice(separatorIndex + 1).trim()
299
+ if (startPart === "" && endPart === "") {
300
+ return undefined
301
+ }
302
+ if (startPart === "") {
303
+ const suffixLength = parseInteger(endPart)
304
+ if (suffixLength === undefined) {
305
+ return undefined
306
+ }
307
+ if (suffixLength === 0 || fileSize === 0) {
308
+ return "unsatisfiable"
309
+ }
310
+ return {
311
+ start: Math.max(fileSize - suffixLength, 0),
312
+ end: fileSize - 1
313
+ }
314
+ }
315
+ const start = parseInteger(startPart)
316
+ if (start === undefined) {
317
+ return undefined
318
+ }
319
+ if (endPart === "") {
320
+ if (start >= fileSize) {
321
+ return "unsatisfiable"
322
+ }
323
+ return {
324
+ start,
325
+ end: fileSize - 1
326
+ }
327
+ }
328
+ const end = parseInteger(endPart)
329
+ if (end === undefined) {
330
+ return undefined
331
+ }
332
+ if (start > end || start >= fileSize) {
333
+ return "unsatisfiable"
334
+ }
335
+ return {
336
+ start,
337
+ end: Math.min(end, fileSize - 1)
338
+ }
339
+ }
340
+
341
+ const resolveFilePath = (path: Path.Path, root: string, url: string): string | undefined => {
342
+ const urlPath = stripQueryString(url)
343
+ let decodedPath: string
344
+ try {
345
+ decodedPath = decodeURIComponent(urlPath)
346
+ } catch {
347
+ return undefined
348
+ }
349
+ if (decodedPath.includes("\u0000")) {
350
+ return undefined
351
+ }
352
+ const normalizedPath = path.normalize(decodedPath.startsWith("/") ? decodedPath.slice(1) : decodedPath)
353
+ if (normalizedPath === ".." || normalizedPath.startsWith(`..${path.sep}`)) {
354
+ return undefined
355
+ }
356
+ const resolvedPath = path.join(root, normalizedPath)
357
+ const rootPrefix = root.endsWith(path.sep) ? root : `${root}${path.sep}`
358
+ if (resolvedPath !== root && !resolvedPath.startsWith(rootPrefix)) {
359
+ return undefined
360
+ }
361
+ return resolvedPath
362
+ }
363
+
364
+ const toRouteNotFoundError = (request: HttpServerRequest.HttpServerRequest) =>
365
+ new HttpServerError.HttpServerError({ reason: new HttpServerError.RouteNotFound({ request }) })
366
+
367
+ const toInternalServerError = (request: HttpServerRequest.HttpServerRequest, cause: unknown) =>
368
+ new HttpServerError.HttpServerError({ reason: new HttpServerError.InternalError({ request, cause }) })
369
+
370
+ const handlePlatformError = <A>(
371
+ request: HttpServerRequest.HttpServerRequest,
372
+ self: Effect.Effect<A, PlatformError>
373
+ ): Effect.Effect<A, HttpServerError.HttpServerError> =>
374
+ Effect.catchIf(
375
+ self,
376
+ (error): error is PlatformError => error.reason._tag === "NotFound",
377
+ () => Effect.fail(toRouteNotFoundError(request)),
378
+ (error) => Effect.fail(toInternalServerError(request, error))
379
+ )
380
+
381
+ const acceptsHtml = (accept: string | undefined): boolean =>
382
+ accept !== undefined && accept.toLowerCase().includes("text/html")
383
+
384
+ const stripWeakEtagPrefix = (value: string): string => {
385
+ const trimmed = value.trim()
386
+ return /^w\//i.test(trimmed) ? trimmed.slice(2) : trimmed;
387
+ }
388
+
389
+ const matchesIfNoneMatch = (ifNoneMatch: string, etag: string | undefined): boolean => {
390
+ const normalizedEtag = etag === undefined ? undefined : stripWeakEtagPrefix(etag)
391
+ for (const candidate of ifNoneMatch.split(",")) {
392
+ const value = candidate.trim()
393
+ if (value === "") {
394
+ continue
395
+ }
396
+ if (value === "*") {
397
+ return true
398
+ }
399
+ if (normalizedEtag !== undefined && stripWeakEtagPrefix(value) === normalizedEtag) {
400
+ return true
401
+ }
402
+ }
403
+ return false
404
+ }
405
+
406
+ const isNotModifiedSince = (ifModifiedSince: string, lastModified: string | undefined): boolean => {
407
+ if (lastModified === undefined) {
408
+ return false
409
+ }
410
+ const ifModifiedSinceMs = Date.parse(ifModifiedSince)
411
+ if (Number.isNaN(ifModifiedSinceMs)) {
412
+ return false
413
+ }
414
+ const lastModifiedMs = Date.parse(lastModified)
415
+ if (Number.isNaN(lastModifiedMs)) {
416
+ return false
417
+ }
418
+ return lastModifiedMs <= ifModifiedSinceMs
419
+ }
420
+
421
+ const notModifiedResponse = (
422
+ response: HttpServerResponse.HttpServerResponse
423
+ ): HttpServerResponse.HttpServerResponse => {
424
+ const headers: Record<string, string> = {}
425
+ const etag = response.headers["etag"]
426
+ if (etag !== undefined) {
427
+ headers["ETag"] = etag
428
+ }
429
+ const cacheControl = response.headers["cache-control"]
430
+ if (cacheControl !== undefined) {
431
+ headers["Cache-Control"] = cacheControl
432
+ }
433
+ const lastModified = response.headers["last-modified"]
434
+ if (lastModified !== undefined) {
435
+ headers["Last-Modified"] = lastModified
436
+ }
437
+ return HttpServerResponse.empty({
438
+ status: 304,
439
+ headers
440
+ })
441
+ }
442
+
443
+ const evaluateConditionalRequest = (
444
+ request: HttpServerRequest.HttpServerRequest,
445
+ response: HttpServerResponse.HttpServerResponse
446
+ ): HttpServerResponse.HttpServerResponse | undefined => {
447
+ const ifNoneMatch = request.headers["if-none-match"]
448
+ if (ifNoneMatch !== undefined) {
449
+ return matchesIfNoneMatch(ifNoneMatch, response.headers["etag"]) ? notModifiedResponse(response) : undefined
450
+ }
451
+ const ifModifiedSince = request.headers["if-modified-since"]
452
+ if (ifModifiedSince !== undefined && isNotModifiedSince(ifModifiedSince, response.headers["last-modified"])) {
453
+ return notModifiedResponse(response)
454
+ }
455
+ return undefined
456
+ }
@@ -109,6 +109,11 @@ export * as HttpServerRespondable from "./HttpServerRespondable.ts"
109
109
  */
110
110
  export * as HttpServerResponse from "./HttpServerResponse.ts"
111
111
 
112
+ /**
113
+ * @since 4.0.0
114
+ */
115
+ export * as HttpStaticServer from "./HttpStaticServer.ts"
116
+
112
117
  /**
113
118
  * @since 4.0.0
114
119
  */
@@ -112,6 +112,7 @@ export interface Any {
112
112
  readonly [TypeId]: typeof TypeId
113
113
  readonly identifier: string
114
114
  readonly key: string
115
+ readonly endpoints: Record.ReadonlyRecord<string, HttpApiEndpoint.Any>
115
116
  }
116
117
 
117
118
  /**
@@ -25,7 +25,7 @@ import * as Scope from "../../Scope.ts"
25
25
  import * as ServiceMap from "../../ServiceMap.ts"
26
26
  import * as Stream from "../../Stream.ts"
27
27
  import * as SubscriptionRef from "../../SubscriptionRef.ts"
28
- import type { NoInfer } from "../../Types.ts"
28
+ import type { Mutable, NoInfer } from "../../Types.ts"
29
29
  import * as KeyValueStore from "../persistence/KeyValueStore.ts"
30
30
  import * as AsyncResult from "./AsyncResult.ts"
31
31
  import { AtomRegistry } from "./AtomRegistry.ts"
@@ -56,6 +56,7 @@ export interface Atom<A> extends Pipeable, Inspectable.Inspectable {
56
56
  readonly refresh?: (f: <A>(atom: Atom<A>) => void) => void
57
57
  readonly label?: readonly [name: string, stack: string]
58
58
  readonly idleTTL?: number
59
+ readonly initialValueTarget?: Atom<A>
59
60
  }
60
61
 
61
62
  /**
@@ -792,7 +793,7 @@ export const context: (options: {
792
793
  }))
793
794
  get.subscribe(atom, (value) => get.setSelf(value))
794
795
  return get.once(atom)
795
- }) as any as A
796
+ }, { initialValueTarget: atom }) as any as A
796
797
  return factory
797
798
  }
798
799
 
@@ -1600,33 +1601,67 @@ export const transform: {
1600
1601
  * @since 4.0.0
1601
1602
  * @category combinators
1602
1603
  */
1603
- <R extends Atom<any>, B>(f: (get: Context, atom: R) => B): (self: R) => [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1604
+ <R extends Atom<any>, B>(
1605
+ f: (get: Context, atom: R) => B,
1606
+ options?: {
1607
+ readonly initialValueTarget?: Atom<B> | undefined
1608
+ }
1609
+ ): (self: R) => [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1604
1610
  /**
1605
1611
  * @since 4.0.0
1606
1612
  * @category combinators
1607
1613
  */
1608
- <R extends Atom<any>, B>(self: R, f: (get: Context, atom: R) => B): [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1614
+ <R extends Atom<any>, B>(
1615
+ self: R,
1616
+ f: (get: Context, atom: R) => B,
1617
+ options?: {
1618
+ readonly initialValueTarget?: Atom<B> | undefined
1619
+ }
1620
+ ): [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1609
1621
  } = dual(
1610
- 2,
1611
- (<A, B>(self: Atom<A>, f: (get: Context, atom: Atom<A>) => B): Atom<B> =>
1612
- isWritable(self)
1613
- ? writable(
1614
- (get) => f(get, self),
1615
- function(ctx, value) {
1616
- ctx.set(self, value)
1617
- },
1618
- self.refresh ?? function(refresh) {
1619
- refresh(self)
1620
- }
1621
- )
1622
- : readable(
1623
- (get) => f(get, self),
1624
- self.refresh ?? function(refresh) {
1625
- refresh(self)
1626
- }
1627
- )) as any
1622
+ (args) => isAtom(args[0]),
1623
+ (<A, B>(
1624
+ self: Atom<A>,
1625
+ f: (get: Context, atom: Atom<A>, options?: {
1626
+ readonly initialValueTarget?: Atom<B> | undefined
1627
+ }) => B,
1628
+ options?: {
1629
+ readonly initialValueTarget?: Atom<B> | undefined
1630
+ }
1631
+ ): Atom<B> => {
1632
+ const atom = removeTtl(
1633
+ isWritable(self)
1634
+ ? writable(
1635
+ (get) => f(get, self),
1636
+ function(ctx, value) {
1637
+ ctx.set(self, value)
1638
+ },
1639
+ self.refresh ?? function(refresh) {
1640
+ refresh(self)
1641
+ }
1642
+ )
1643
+ : readable(
1644
+ (get) => f(get, self),
1645
+ self.refresh ?? function(refresh) {
1646
+ refresh(self)
1647
+ }
1648
+ )
1649
+ )
1650
+ if (options?.initialValueTarget) {
1651
+ ;(atom as Mutable<Atom<B>>).initialValueTarget = getInitialValueTarget(options.initialValueTarget)
1652
+ }
1653
+ return atom
1654
+ }) as any
1628
1655
  )
1629
1656
 
1657
+ const getInitialValueTarget = <A>(atom: Atom<A>): Atom<A> => {
1658
+ let target = atom
1659
+ while (target.initialValueTarget) {
1660
+ target = target.initialValueTarget
1661
+ }
1662
+ return target
1663
+ }
1664
+
1630
1665
  /**
1631
1666
  * @since 4.0.0
1632
1667
  * @category combinators
@@ -1710,7 +1745,7 @@ export const debounce: {
1710
1745
  timeout = setTimeout(update, millis) as any
1711
1746
  })
1712
1747
  return value
1713
- })
1748
+ }, { initialValueTarget: self })
1714
1749
  }
1715
1750
  )
1716
1751
 
@@ -1746,7 +1781,7 @@ export const withRefresh: {
1746
1781
  const handle = setTimeout(() => get.refresh(self), millis) as any
1747
1782
  get.addFinalizer(() => clearTimeout(handle))
1748
1783
  return get(self)
1749
- })
1784
+ }, { initialValueTarget: self })
1750
1785
  }
1751
1786
  )
1752
1787
 
@@ -1822,10 +1857,7 @@ export const swr: {
1822
1857
  }
1823
1858
  ): Atom<AsyncResult.AsyncResult<A, E>> => {
1824
1859
  const staleTime = Duration.toMillis(Duration.fromInputUnsafe(options.staleTime))
1825
- const refresh = self.refresh ?? function(f: <A>(atom: Atom<A>) => void) {
1826
- f(self)
1827
- }
1828
- function read(get: Context) {
1860
+ return transform(self, (get) => {
1829
1861
  const current = get.once(self)
1830
1862
  get.subscribe(self, (value) => {
1831
1863
  get.setSelf(value)
@@ -1850,12 +1882,7 @@ export const swr: {
1850
1882
  get.refresh(self)
1851
1883
  }
1852
1884
  return current
1853
- }
1854
- return isWritable(self)
1855
- ? writable(read, (ctx, value) => {
1856
- ctx.set(self, value)
1857
- }, refresh)
1858
- : readable(read, refresh)
1885
+ }, { initialValueTarget: self })
1859
1886
  }
1860
1887
  ) as any
1861
1888
 
@@ -2094,7 +2121,7 @@ export const makeRefreshOnSignal = <_>(signal: Atom<_>) => <A extends Atom<any>>
2094
2121
  get.subscribe(signal, (_) => get.refresh(self))
2095
2122
  get.subscribe(self, (value) => get.setSelf(value))
2096
2123
  return get.once(self)
2097
- }) as any
2124
+ }, { initialValueTarget: self }) as any
2098
2125
 
2099
2126
  /**
2100
2127
  * @since 4.0.0