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.
- package/dist/Brand.d.ts +1 -1
- package/dist/Brand.d.ts.map +1 -1
- package/dist/Brand.js +1 -1
- package/dist/Brand.js.map +1 -1
- package/dist/ConfigProvider.d.ts +1 -1
- package/dist/Cron.d.ts +1 -1
- package/dist/Data.d.ts +1 -1
- package/dist/Data.d.ts.map +1 -1
- package/dist/Data.js.map +1 -1
- package/dist/Effect.d.ts +300 -184
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +99 -61
- package/dist/Effect.js.map +1 -1
- package/dist/Encoding.d.ts +1 -1
- package/dist/Exit.d.ts +24 -12
- package/dist/Exit.d.ts.map +1 -1
- package/dist/Exit.js +8 -4
- package/dist/Exit.js.map +1 -1
- package/dist/Fiber.d.ts +1 -0
- package/dist/Fiber.d.ts.map +1 -1
- package/dist/Fiber.js.map +1 -1
- package/dist/Function.d.ts +1 -9
- package/dist/Function.d.ts.map +1 -1
- package/dist/Function.js +2 -10
- package/dist/Function.js.map +1 -1
- package/dist/Graph.d.ts +1 -1
- package/dist/Newtype.d.ts +291 -0
- package/dist/Newtype.d.ts.map +1 -0
- package/dist/Newtype.js +161 -0
- package/dist/Newtype.js.map +1 -0
- package/dist/PlatformError.d.ts +2 -2
- package/dist/References.d.ts +6 -1
- package/dist/References.d.ts.map +1 -1
- package/dist/References.js +6 -1
- package/dist/References.js.map +1 -1
- package/dist/RequestResolver.d.ts +19 -19
- package/dist/RequestResolver.js +10 -10
- package/dist/RequestResolver.js.map +1 -1
- package/dist/Schedule.d.ts +142 -80
- package/dist/Schedule.d.ts.map +1 -1
- package/dist/Schedule.js +58 -32
- package/dist/Schedule.js.map +1 -1
- package/dist/Scheduler.d.ts +9 -0
- package/dist/Scheduler.d.ts.map +1 -1
- package/dist/Scheduler.js +11 -0
- package/dist/Scheduler.js.map +1 -1
- package/dist/Schema.d.ts +1 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +3 -1
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaAST.d.ts.map +1 -1
- package/dist/SchemaAST.js +1 -1
- package/dist/SchemaAST.js.map +1 -1
- package/dist/Stdio.d.ts +6 -2
- package/dist/Stdio.d.ts.map +1 -1
- package/dist/Stdio.js +2 -2
- package/dist/Stdio.js.map +1 -1
- package/dist/Stream.d.ts +8 -4
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +8 -4
- package/dist/Stream.js.map +1 -1
- package/dist/Types.d.ts +1 -22
- package/dist/Types.d.ts.map +1 -1
- package/dist/index.d.ts +71 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +71 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/effect.js +3 -1
- package/dist/internal/effect.js.map +1 -1
- package/dist/unstable/ai/LanguageModel.d.ts +12 -28
- package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
- package/dist/unstable/ai/LanguageModel.js +4 -18
- package/dist/unstable/ai/LanguageModel.js.map +1 -1
- package/dist/unstable/ai/McpSchema.d.ts +20 -1
- package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
- package/dist/unstable/ai/McpSchema.js +8 -0
- package/dist/unstable/ai/McpSchema.js.map +1 -1
- package/dist/unstable/ai/McpServer.d.ts +65 -12
- package/dist/unstable/ai/McpServer.d.ts.map +1 -1
- package/dist/unstable/ai/McpServer.js +159 -45
- package/dist/unstable/ai/McpServer.js.map +1 -1
- package/dist/unstable/ai/Toolkit.d.ts +1 -1
- package/dist/unstable/ai/Toolkit.d.ts.map +1 -1
- package/dist/unstable/ai/Toolkit.js +4 -11
- package/dist/unstable/ai/Toolkit.js.map +1 -1
- package/dist/unstable/ai/internal/codec-transformer.js +0 -5
- package/dist/unstable/ai/internal/codec-transformer.js.map +1 -1
- package/dist/unstable/cli/Prompt.js +35 -8
- package/dist/unstable/cli/Prompt.js.map +1 -1
- package/dist/unstable/cluster/Message.d.ts +5 -5
- package/dist/unstable/cluster/Reply.d.ts +3 -3
- package/dist/unstable/encoding/Msgpack.d.ts +1 -1
- package/dist/unstable/encoding/Ndjson.d.ts +1 -1
- package/dist/unstable/encoding/Sse.d.ts +1 -1
- package/dist/unstable/eventlog/EventJournal.d.ts +1 -1
- package/dist/unstable/eventlog/EventLogRemote.d.ts +1 -1
- package/dist/unstable/http/Cookies.d.ts +45 -1
- package/dist/unstable/http/Cookies.d.ts.map +1 -1
- package/dist/unstable/http/Cookies.js +22 -0
- package/dist/unstable/http/Cookies.js.map +1 -1
- package/dist/unstable/http/HttpBody.d.ts +1 -1
- package/dist/unstable/http/HttpClient.d.ts.map +1 -1
- package/dist/unstable/http/HttpClient.js +3 -7
- package/dist/unstable/http/HttpClient.js.map +1 -1
- package/dist/unstable/http/HttpClientError.d.ts +7 -7
- package/dist/unstable/http/HttpClientRequest.d.ts +5 -0
- package/dist/unstable/http/HttpClientRequest.d.ts.map +1 -1
- package/dist/unstable/http/HttpClientRequest.js +21 -17
- package/dist/unstable/http/HttpClientRequest.js.map +1 -1
- package/dist/unstable/http/HttpEffect.d.ts +7 -0
- package/dist/unstable/http/HttpEffect.d.ts.map +1 -1
- package/dist/unstable/http/HttpEffect.js +6 -0
- package/dist/unstable/http/HttpEffect.js.map +1 -1
- package/dist/unstable/http/HttpServerError.d.ts +6 -6
- package/dist/unstable/http/HttpServerRequest.d.ts +11 -0
- package/dist/unstable/http/HttpServerRequest.d.ts.map +1 -1
- package/dist/unstable/http/HttpServerRequest.js +291 -1
- package/dist/unstable/http/HttpServerRequest.js.map +1 -1
- package/dist/unstable/http/HttpServerResponse.d.ts +47 -1
- package/dist/unstable/http/HttpServerResponse.d.ts.map +1 -1
- package/dist/unstable/http/HttpServerResponse.js +227 -0
- package/dist/unstable/http/HttpServerResponse.js.map +1 -1
- package/dist/unstable/http/HttpStaticServer.d.ts +69 -0
- package/dist/unstable/http/HttpStaticServer.d.ts.map +1 -0
- package/dist/unstable/http/HttpStaticServer.js +353 -0
- package/dist/unstable/http/HttpStaticServer.js.map +1 -0
- package/dist/unstable/http/Multipart.d.ts +1 -1
- package/dist/unstable/http/UrlParams.d.ts +1 -1
- package/dist/unstable/http/index.d.ts +4 -0
- package/dist/unstable/http/index.d.ts.map +1 -1
- package/dist/unstable/http/index.js +4 -0
- package/dist/unstable/http/index.js.map +1 -1
- package/dist/unstable/httpapi/HttpApiGroup.d.ts +1 -0
- package/dist/unstable/httpapi/HttpApiGroup.d.ts.map +1 -1
- package/dist/unstable/httpapi/HttpApiGroup.js.map +1 -1
- package/dist/unstable/persistence/KeyValueStore.d.ts +1 -1
- package/dist/unstable/reactivity/Atom.d.ts +7 -2
- package/dist/unstable/reactivity/Atom.d.ts.map +1 -1
- package/dist/unstable/reactivity/Atom.js +33 -15
- package/dist/unstable/reactivity/Atom.js.map +1 -1
- package/dist/unstable/reactivity/AtomHttpApi.d.ts +4 -6
- package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomHttpApi.js +39 -9
- package/dist/unstable/reactivity/AtomHttpApi.js.map +1 -1
- package/dist/unstable/reactivity/AtomRegistry.js +26 -2
- package/dist/unstable/reactivity/AtomRegistry.js.map +1 -1
- package/dist/unstable/reactivity/AtomRpc.d.ts +8 -8
- package/dist/unstable/reactivity/AtomRpc.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomRpc.js +46 -20
- package/dist/unstable/reactivity/AtomRpc.js.map +1 -1
- package/dist/unstable/rpc/Rpc.d.ts +1 -1
- package/dist/unstable/rpc/Rpc.d.ts.map +1 -1
- package/dist/unstable/rpc/Rpc.js.map +1 -1
- package/dist/unstable/rpc/RpcMiddleware.d.ts +5 -5
- package/dist/unstable/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/unstable/rpc/RpcMiddleware.js.map +1 -1
- package/dist/unstable/rpc/RpcServer.js +2 -2
- package/dist/unstable/rpc/RpcServer.js.map +1 -1
- package/dist/unstable/rpc/Utils.js +1 -1
- package/dist/unstable/rpc/Utils.js.map +1 -1
- package/dist/unstable/socket/SocketServer.d.ts +3 -3
- package/dist/unstable/sql/Migrator.d.ts +1 -1
- package/dist/unstable/sql/SqlResolver.js +2 -2
- package/dist/unstable/sql/SqlResolver.js.map +1 -1
- package/dist/unstable/workflow/Workflow.d.ts +1 -1
- package/package.json +1 -1
- package/src/Brand.ts +1 -1
- package/src/Data.ts +1 -2
- package/src/Effect.ts +300 -184
- package/src/Exit.ts +24 -12
- package/src/Fiber.ts +1 -0
- package/src/Function.ts +2 -10
- package/src/Newtype.ts +308 -0
- package/src/References.ts +6 -1
- package/src/RequestResolver.ts +20 -20
- package/src/Schedule.ts +142 -80
- package/src/Scheduler.ts +12 -0
- package/src/Schema.ts +4 -2
- package/src/SchemaAST.ts +1 -4
- package/src/Stdio.ts +8 -4
- package/src/Stream.ts +8 -4
- package/src/Types.ts +1 -23
- package/src/index.ts +72 -3
- package/src/internal/effect.ts +3 -0
- package/src/unstable/ai/LanguageModel.ts +16 -37
- package/src/unstable/ai/McpSchema.ts +14 -0
- package/src/unstable/ai/McpServer.ts +224 -53
- package/src/unstable/ai/Toolkit.ts +5 -14
- package/src/unstable/ai/internal/codec-transformer.ts +0 -7
- package/src/unstable/cli/Prompt.ts +31 -9
- package/src/unstable/http/Cookies.ts +84 -0
- package/src/unstable/http/HttpClient.ts +5 -6
- package/src/unstable/http/HttpClientRequest.ts +21 -17
- package/src/unstable/http/HttpEffect.ts +8 -0
- package/src/unstable/http/HttpServerRequest.ts +388 -1
- package/src/unstable/http/HttpServerResponse.ts +328 -1
- package/src/unstable/http/HttpStaticServer.ts +456 -0
- package/src/unstable/http/index.ts +5 -0
- package/src/unstable/httpapi/HttpApiGroup.ts +1 -0
- package/src/unstable/reactivity/Atom.ts +62 -35
- package/src/unstable/reactivity/AtomHttpApi.ts +45 -11
- package/src/unstable/reactivity/AtomRegistry.ts +30 -2
- package/src/unstable/reactivity/AtomRpc.ts +48 -17
- package/src/unstable/rpc/Rpc.ts +1 -3
- package/src/unstable/rpc/RpcMiddleware.ts +12 -6
- package/src/unstable/rpc/RpcServer.ts +2 -2
- package/src/unstable/rpc/Utils.ts +1 -1
- 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
|
*/
|
|
@@ -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>(
|
|
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>(
|
|
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
|
-
|
|
1611
|
-
(<A, B>(
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
)
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
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
|