effect 4.0.0-beta.26 → 4.0.0-beta.28

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 (223) hide show
  1. package/dist/Cause.d.ts +1 -1
  2. package/dist/ConfigProvider.d.ts +1 -1
  3. package/dist/Cron.d.ts +1 -1
  4. package/dist/Data.d.ts +3 -3
  5. package/dist/Data.d.ts.map +1 -1
  6. package/dist/Data.js +2 -2
  7. package/dist/Data.js.map +1 -1
  8. package/dist/Effect.d.ts +331 -206
  9. package/dist/Effect.d.ts.map +1 -1
  10. package/dist/Effect.js +113 -72
  11. package/dist/Effect.js.map +1 -1
  12. package/dist/Encoding.d.ts +1 -1
  13. package/dist/ErrorReporter.d.ts +2 -4
  14. package/dist/ErrorReporter.d.ts.map +1 -1
  15. package/dist/ErrorReporter.js +1 -3
  16. package/dist/ErrorReporter.js.map +1 -1
  17. package/dist/Exit.d.ts +24 -12
  18. package/dist/Exit.d.ts.map +1 -1
  19. package/dist/Exit.js +8 -4
  20. package/dist/Exit.js.map +1 -1
  21. package/dist/Fiber.d.ts +1 -0
  22. package/dist/Fiber.d.ts.map +1 -1
  23. package/dist/Fiber.js.map +1 -1
  24. package/dist/Graph.d.ts +1 -1
  25. package/dist/Layer.d.ts +112 -117
  26. package/dist/Layer.d.ts.map +1 -1
  27. package/dist/Layer.js +43 -44
  28. package/dist/Layer.js.map +1 -1
  29. package/dist/LayerMap.d.ts +4 -4
  30. package/dist/LayerMap.js +3 -3
  31. package/dist/ManagedRuntime.d.ts +1 -1
  32. package/dist/ManagedRuntime.js +1 -1
  33. package/dist/Metric.d.ts +2 -4
  34. package/dist/Metric.d.ts.map +1 -1
  35. package/dist/Metric.js +2 -4
  36. package/dist/Metric.js.map +1 -1
  37. package/dist/PlatformError.d.ts +2 -2
  38. package/dist/References.d.ts +6 -1
  39. package/dist/References.d.ts.map +1 -1
  40. package/dist/References.js +6 -1
  41. package/dist/References.js.map +1 -1
  42. package/dist/RequestResolver.d.ts +19 -19
  43. package/dist/RequestResolver.js +10 -10
  44. package/dist/RequestResolver.js.map +1 -1
  45. package/dist/Schedule.d.ts +144 -82
  46. package/dist/Schedule.d.ts.map +1 -1
  47. package/dist/Schedule.js +58 -32
  48. package/dist/Schedule.js.map +1 -1
  49. package/dist/Scheduler.d.ts +9 -0
  50. package/dist/Scheduler.d.ts.map +1 -1
  51. package/dist/Scheduler.js +11 -0
  52. package/dist/Scheduler.js.map +1 -1
  53. package/dist/Schema.d.ts.map +1 -1
  54. package/dist/Schema.js +3 -1
  55. package/dist/Schema.js.map +1 -1
  56. package/dist/Stdio.d.ts +6 -2
  57. package/dist/Stdio.d.ts.map +1 -1
  58. package/dist/Stdio.js +2 -2
  59. package/dist/Stdio.js.map +1 -1
  60. package/dist/Stream.d.ts +8 -4
  61. package/dist/Stream.d.ts.map +1 -1
  62. package/dist/Stream.js +8 -4
  63. package/dist/Stream.js.map +1 -1
  64. package/dist/Types.d.ts +1 -22
  65. package/dist/Types.d.ts.map +1 -1
  66. package/dist/index.d.ts +5 -3
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +5 -3
  69. package/dist/index.js.map +1 -1
  70. package/dist/internal/effect.js +3 -1
  71. package/dist/internal/effect.js.map +1 -1
  72. package/dist/unstable/ai/LanguageModel.d.ts +12 -28
  73. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  74. package/dist/unstable/ai/LanguageModel.js +4 -18
  75. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  76. package/dist/unstable/ai/McpSchema.d.ts +20 -1
  77. package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
  78. package/dist/unstable/ai/McpSchema.js +8 -0
  79. package/dist/unstable/ai/McpSchema.js.map +1 -1
  80. package/dist/unstable/ai/McpServer.d.ts +65 -12
  81. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  82. package/dist/unstable/ai/McpServer.js +159 -45
  83. package/dist/unstable/ai/McpServer.js.map +1 -1
  84. package/dist/unstable/ai/Toolkit.d.ts +1 -1
  85. package/dist/unstable/ai/Toolkit.d.ts.map +1 -1
  86. package/dist/unstable/ai/Toolkit.js +4 -11
  87. package/dist/unstable/ai/Toolkit.js.map +1 -1
  88. package/dist/unstable/ai/internal/codec-transformer.js +0 -5
  89. package/dist/unstable/ai/internal/codec-transformer.js.map +1 -1
  90. package/dist/unstable/cli/CliError.d.ts +4 -4
  91. package/dist/unstable/cli/CliError.js +4 -4
  92. package/dist/unstable/cli/Primitive.d.ts +1 -1
  93. package/dist/unstable/cli/Primitive.js +1 -1
  94. package/dist/unstable/cli/Prompt.js +31 -0
  95. package/dist/unstable/cli/Prompt.js.map +1 -1
  96. package/dist/unstable/cluster/Message.d.ts +5 -5
  97. package/dist/unstable/cluster/Reply.d.ts +3 -3
  98. package/dist/unstable/encoding/Msgpack.d.ts +1 -1
  99. package/dist/unstable/encoding/Ndjson.d.ts +1 -1
  100. package/dist/unstable/encoding/Sse.d.ts +1 -1
  101. package/dist/unstable/eventlog/EventJournal.d.ts +1 -1
  102. package/dist/unstable/eventlog/EventLogRemote.d.ts +1 -1
  103. package/dist/unstable/http/Cookies.d.ts +45 -1
  104. package/dist/unstable/http/Cookies.d.ts.map +1 -1
  105. package/dist/unstable/http/Cookies.js +22 -0
  106. package/dist/unstable/http/Cookies.js.map +1 -1
  107. package/dist/unstable/http/Headers.d.ts +16 -0
  108. package/dist/unstable/http/Headers.d.ts.map +1 -1
  109. package/dist/unstable/http/Headers.js +11 -0
  110. package/dist/unstable/http/Headers.js.map +1 -1
  111. package/dist/unstable/http/HttpBody.d.ts +1 -1
  112. package/dist/unstable/http/HttpClientError.d.ts +7 -7
  113. package/dist/unstable/http/HttpClientRequest.d.ts +5 -0
  114. package/dist/unstable/http/HttpClientRequest.d.ts.map +1 -1
  115. package/dist/unstable/http/HttpClientRequest.js +21 -17
  116. package/dist/unstable/http/HttpClientRequest.js.map +1 -1
  117. package/dist/unstable/http/HttpEffect.d.ts +7 -0
  118. package/dist/unstable/http/HttpEffect.d.ts.map +1 -1
  119. package/dist/unstable/http/HttpEffect.js +6 -0
  120. package/dist/unstable/http/HttpEffect.js.map +1 -1
  121. package/dist/unstable/http/HttpServerError.d.ts +6 -6
  122. package/dist/unstable/http/HttpServerRequest.d.ts +11 -0
  123. package/dist/unstable/http/HttpServerRequest.d.ts.map +1 -1
  124. package/dist/unstable/http/HttpServerRequest.js +291 -1
  125. package/dist/unstable/http/HttpServerRequest.js.map +1 -1
  126. package/dist/unstable/http/HttpServerResponse.d.ts +47 -1
  127. package/dist/unstable/http/HttpServerResponse.d.ts.map +1 -1
  128. package/dist/unstable/http/HttpServerResponse.js +227 -0
  129. package/dist/unstable/http/HttpServerResponse.js.map +1 -1
  130. package/dist/unstable/http/HttpStaticServer.d.ts +69 -0
  131. package/dist/unstable/http/HttpStaticServer.d.ts.map +1 -0
  132. package/dist/unstable/http/HttpStaticServer.js +353 -0
  133. package/dist/unstable/http/HttpStaticServer.js.map +1 -0
  134. package/dist/unstable/http/Multipart.d.ts +1 -1
  135. package/dist/unstable/http/UrlParams.d.ts +1 -1
  136. package/dist/unstable/http/index.d.ts +4 -0
  137. package/dist/unstable/http/index.d.ts.map +1 -1
  138. package/dist/unstable/http/index.js +4 -0
  139. package/dist/unstable/http/index.js.map +1 -1
  140. package/dist/unstable/httpapi/HttpApiBuilder.js +5 -0
  141. package/dist/unstable/httpapi/HttpApiBuilder.js.map +1 -1
  142. package/dist/unstable/httpapi/HttpApiGroup.d.ts +1 -0
  143. package/dist/unstable/httpapi/HttpApiGroup.d.ts.map +1 -1
  144. package/dist/unstable/httpapi/HttpApiGroup.js.map +1 -1
  145. package/dist/unstable/persistence/KeyValueStore.d.ts +1 -1
  146. package/dist/unstable/reactivity/Atom.d.ts.map +1 -1
  147. package/dist/unstable/reactivity/Atom.js +4 -10
  148. package/dist/unstable/reactivity/Atom.js.map +1 -1
  149. package/dist/unstable/reactivity/AtomHttpApi.d.ts +4 -6
  150. package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
  151. package/dist/unstable/reactivity/AtomHttpApi.js +39 -9
  152. package/dist/unstable/reactivity/AtomHttpApi.js.map +1 -1
  153. package/dist/unstable/reactivity/AtomRpc.d.ts +8 -8
  154. package/dist/unstable/reactivity/AtomRpc.d.ts.map +1 -1
  155. package/dist/unstable/reactivity/AtomRpc.js +46 -20
  156. package/dist/unstable/reactivity/AtomRpc.js.map +1 -1
  157. package/dist/unstable/rpc/Rpc.d.ts +1 -1
  158. package/dist/unstable/rpc/Rpc.d.ts.map +1 -1
  159. package/dist/unstable/rpc/Rpc.js.map +1 -1
  160. package/dist/unstable/rpc/RpcMiddleware.d.ts +5 -5
  161. package/dist/unstable/rpc/RpcMiddleware.d.ts.map +1 -1
  162. package/dist/unstable/rpc/RpcMiddleware.js.map +1 -1
  163. package/dist/unstable/rpc/RpcServer.js +2 -2
  164. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  165. package/dist/unstable/rpc/Utils.js +1 -1
  166. package/dist/unstable/rpc/Utils.js.map +1 -1
  167. package/dist/unstable/schema/Model.d.ts +21 -0
  168. package/dist/unstable/schema/Model.d.ts.map +1 -1
  169. package/dist/unstable/schema/Model.js +15 -0
  170. package/dist/unstable/schema/Model.js.map +1 -1
  171. package/dist/unstable/socket/SocketServer.d.ts +3 -3
  172. package/dist/unstable/sql/Migrator.d.ts +1 -1
  173. package/dist/unstable/sql/SqlResolver.js +2 -2
  174. package/dist/unstable/sql/SqlResolver.js.map +1 -1
  175. package/dist/unstable/workflow/Workflow.d.ts +1 -1
  176. package/package.json +1 -1
  177. package/src/Cause.ts +1 -1
  178. package/src/Data.ts +3 -4
  179. package/src/Effect.ts +331 -206
  180. package/src/ErrorReporter.ts +2 -4
  181. package/src/Exit.ts +24 -12
  182. package/src/Fiber.ts +1 -0
  183. package/src/Layer.ts +112 -117
  184. package/src/LayerMap.ts +4 -4
  185. package/src/ManagedRuntime.ts +1 -1
  186. package/src/Metric.ts +2 -4
  187. package/src/References.ts +6 -1
  188. package/src/RequestResolver.ts +20 -20
  189. package/src/Schedule.ts +144 -82
  190. package/src/Scheduler.ts +12 -0
  191. package/src/Schema.ts +3 -1
  192. package/src/Stdio.ts +8 -4
  193. package/src/Stream.ts +8 -4
  194. package/src/Types.ts +1 -23
  195. package/src/index.ts +5 -3
  196. package/src/internal/effect.ts +3 -0
  197. package/src/unstable/ai/LanguageModel.ts +16 -37
  198. package/src/unstable/ai/McpSchema.ts +14 -0
  199. package/src/unstable/ai/McpServer.ts +224 -53
  200. package/src/unstable/ai/Toolkit.ts +5 -14
  201. package/src/unstable/ai/internal/codec-transformer.ts +0 -7
  202. package/src/unstable/cli/CliError.ts +4 -4
  203. package/src/unstable/cli/Primitive.ts +1 -1
  204. package/src/unstable/cli/Prompt.ts +27 -0
  205. package/src/unstable/http/Cookies.ts +84 -0
  206. package/src/unstable/http/Headers.ts +34 -0
  207. package/src/unstable/http/HttpClientRequest.ts +21 -17
  208. package/src/unstable/http/HttpEffect.ts +8 -0
  209. package/src/unstable/http/HttpServerRequest.ts +388 -1
  210. package/src/unstable/http/HttpServerResponse.ts +328 -1
  211. package/src/unstable/http/HttpStaticServer.ts +456 -0
  212. package/src/unstable/http/index.ts +5 -0
  213. package/src/unstable/httpapi/HttpApiBuilder.ts +3 -0
  214. package/src/unstable/httpapi/HttpApiGroup.ts +1 -0
  215. package/src/unstable/reactivity/Atom.ts +20 -26
  216. package/src/unstable/reactivity/AtomHttpApi.ts +45 -11
  217. package/src/unstable/reactivity/AtomRpc.ts +48 -17
  218. package/src/unstable/rpc/Rpc.ts +1 -3
  219. package/src/unstable/rpc/RpcMiddleware.ts +12 -6
  220. package/src/unstable/rpc/RpcServer.ts +2 -2
  221. package/src/unstable/rpc/Utils.ts +1 -1
  222. package/src/unstable/schema/Model.ts +31 -0
  223. 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
  */
@@ -743,6 +743,9 @@ function getResponseEncode<E>(
743
743
  switch (encoding._tag) {
744
744
  case "Json": {
745
745
  return ((e) => {
746
+ if (e === undefined) {
747
+ return Effect.succeed(Response.empty({ status }))
748
+ }
746
749
  try {
747
750
  const s = JSON.stringify(e)
748
751
  return Effect.succeed(Response.text(s, { status, contentType: encoding.contentType }))
@@ -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
  /**
@@ -1609,22 +1609,24 @@ export const transform: {
1609
1609
  } = dual(
1610
1610
  2,
1611
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
1612
+ removeTtl(
1613
+ isWritable(self)
1614
+ ? writable(
1615
+ (get) => f(get, self),
1616
+ function(ctx, value) {
1617
+ ctx.set(self, value)
1618
+ },
1619
+ self.refresh ?? function(refresh) {
1620
+ refresh(self)
1621
+ }
1622
+ )
1623
+ : readable(
1624
+ (get) => f(get, self),
1625
+ self.refresh ?? function(refresh) {
1626
+ refresh(self)
1627
+ }
1628
+ )
1629
+ )) as any
1628
1630
  )
1629
1631
 
1630
1632
  /**
@@ -1822,10 +1824,7 @@ export const swr: {
1822
1824
  }
1823
1825
  ): Atom<AsyncResult.AsyncResult<A, E>> => {
1824
1826
  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) {
1827
+ return transform(self, (get) => {
1829
1828
  const current = get.once(self)
1830
1829
  get.subscribe(self, (value) => {
1831
1830
  get.setSelf(value)
@@ -1850,12 +1849,7 @@ export const swr: {
1850
1849
  get.refresh(self)
1851
1850
  }
1852
1851
  return current
1853
- }
1854
- return isWritable(self)
1855
- ? writable(read, (ctx, value) => {
1856
- ctx.set(self, value)
1857
- }, refresh)
1858
- : readable(read, refresh)
1852
+ })
1859
1853
  }
1860
1854
  ) as any
1861
1855
 
@@ -3,19 +3,21 @@
3
3
  */
4
4
  import * as Duration from "../../Duration.ts"
5
5
  import * as Effect from "../../Effect.ts"
6
+ import * as Hash from "../../Hash.ts"
6
7
  import * as Layer from "../../Layer.ts"
7
8
  import type { ReadonlyRecord } from "../../Record.ts"
9
+ import * as Schema from "../../Schema.ts"
8
10
  import type { SchemaError } from "../../Schema.ts"
9
11
  import * as ServiceMap from "../../ServiceMap.ts"
10
12
  import type { Mutable, Simplify } from "../../Types.ts"
11
13
  import type * as HttpClient from "../http/HttpClient.ts"
12
- import type * as HttpClientError from "../http/HttpClientError.ts"
14
+ import * as HttpClientError from "../http/HttpClientError.ts"
13
15
  import type { HttpClientResponse } from "../http/HttpClientResponse.ts"
14
16
  import type * as HttpApi from "../httpapi/HttpApi.ts"
15
17
  import * as HttpApiClient from "../httpapi/HttpApiClient.ts"
16
- import type * as HttpApiEndpoint from "../httpapi/HttpApiEndpoint.ts"
18
+ import * as HttpApiEndpoint from "../httpapi/HttpApiEndpoint.ts"
17
19
  import type * as HttpApiGroup from "../httpapi/HttpApiGroup.ts"
18
- import type * as AsyncResult from "./AsyncResult.ts"
20
+ import * as AsyncResult from "./AsyncResult.ts"
19
21
  import * as Atom from "./Atom.ts"
20
22
  import * as Reactivity from "./Reactivity.ts"
21
23
 
@@ -67,7 +69,7 @@ export interface AtomHttpApiClient<Self, Id extends string, Groups extends HttpA
67
69
  }
68
70
  >,
69
71
  WithResponse extends true ? [_Success["Type"], HttpClientResponse] : _Success["Type"],
70
- _Error["Type"] | HttpClientError.HttpClientError | SchemaError
72
+ _Error["Type"]
71
73
  >
72
74
  : never
73
75
 
@@ -124,7 +126,7 @@ export interface AtomHttpApiClient<Self, Id extends string, Groups extends HttpA
124
126
  ] ? Atom.Atom<
125
127
  AsyncResult.AsyncResult<
126
128
  WithResponse extends true ? [_Success["Type"], HttpClientResponse] : _Success["Type"],
127
- _Error["Type"] | HttpClientError.HttpClientError | SchemaError
129
+ _Error["Type"]
128
130
  >
129
131
  >
130
132
  : never
@@ -169,8 +171,12 @@ export const Service = <Self>() =>
169
171
  const runtimeFactory = options.runtime ?? Atom.runtime
170
172
  self.runtime = runtimeFactory(self.layer)
171
173
 
172
- const mutationFamily = Atom.family(({ endpoint, group, withResponse }: MutationKey) =>
173
- self.runtime.fn<{
174
+ const catchErrors = Effect.catch((e: unknown) =>
175
+ Schema.isSchemaError(e) || HttpClientError.isHttpClientError(e) ? Effect.die(e) : Effect.fail(e)
176
+ )
177
+
178
+ const mutationFamily = Atom.family(({ endpoint, group, withResponse }: MutationKey) => {
179
+ const atom = self.runtime.fn<{
174
180
  params: any
175
181
  query: any
176
182
  headers: any
@@ -179,16 +185,27 @@ export const Service = <Self>() =>
179
185
  }>()(
180
186
  Effect.fnUntraced(function*(opts) {
181
187
  const client = (yield* self) as any
182
- const effect = client[group][endpoint]({
188
+ const effect = catchErrors(client[group][endpoint]({
183
189
  ...opts,
184
190
  withResponse
185
- }) as Effect.Effect<any>
191
+ }) as Effect.Effect<any>)
186
192
  return yield* opts.reactivityKeys
187
193
  ? Reactivity.mutation(effect, opts.reactivityKeys)
188
194
  : effect
189
195
  })
190
196
  )
191
- ) as any
197
+ if (withResponse === false) {
198
+ const definition = options.api.groups[group]!.endpoints[endpoint]! as HttpApiEndpoint.AnyWithProps
199
+ return Atom.serializable(atom, {
200
+ key: `AtomHttpApi:mutation:${group}:${endpoint}`,
201
+ schema: AsyncResult.Schema({
202
+ success: Schema.Union(HttpApiEndpoint.getSuccessSchemas(definition)),
203
+ error: Schema.Union(HttpApiEndpoint.getErrorSchemas(definition))
204
+ }) as any
205
+ })
206
+ }
207
+ return atom
208
+ }) as any
192
209
 
193
210
  self.mutation = ((group: string, endpoint: string, options?: {
194
211
  readonly withResponse?: boolean | undefined
@@ -202,8 +219,21 @@ export const Service = <Self>() =>
202
219
  const queryFamily = Atom.family((opts: QueryKey) => {
203
220
  let atom = self.runtime.atom(self.use((client_) => {
204
221
  const client = client_ as any
205
- return client[opts.group][opts.endpoint](opts) as Effect.Effect<any>
222
+ return catchErrors(client[opts.group][opts.endpoint](opts) as Effect.Effect<
223
+ any,
224
+ HttpClientError.HttpClientError | SchemaError
225
+ >)
206
226
  }))
227
+ if (opts.withResponse === false) {
228
+ const endpoint = options.api.groups[opts.group]!.endpoints[opts.endpoint]! as HttpApiEndpoint.AnyWithProps
229
+ atom = Atom.serializable(atom, {
230
+ key: makeSerializableKey(opts),
231
+ schema: AsyncResult.Schema({
232
+ success: Schema.Union(HttpApiEndpoint.getSuccessSchemas(endpoint)),
233
+ error: Schema.Union(HttpApiEndpoint.getErrorSchemas(endpoint))
234
+ }) as any
235
+ })
236
+ }
207
237
  if (opts.timeToLive) {
208
238
  atom = Duration.isFinite(opts.timeToLive)
209
239
  ? Atom.setIdleTTL(atom, opts.timeToLive)
@@ -261,3 +291,7 @@ interface QueryKey {
261
291
  reactivityKeys?: ReadonlyArray<unknown> | ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
262
292
  timeToLive?: Duration.Duration | undefined
263
293
  }
294
+
295
+ const makeSerializableKey = (
296
+ key: QueryKey
297
+ ): string => `AtomHttpApi:${key.group}:${key.endpoint}:${Hash.hash(key)}`