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.
- package/dist/Cause.d.ts +1 -1
- package/dist/ConfigProvider.d.ts +1 -1
- package/dist/Cron.d.ts +1 -1
- package/dist/Data.d.ts +3 -3
- package/dist/Data.d.ts.map +1 -1
- package/dist/Data.js +2 -2
- package/dist/Data.js.map +1 -1
- package/dist/Effect.d.ts +331 -206
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +113 -72
- package/dist/Effect.js.map +1 -1
- package/dist/Encoding.d.ts +1 -1
- package/dist/ErrorReporter.d.ts +2 -4
- package/dist/ErrorReporter.d.ts.map +1 -1
- package/dist/ErrorReporter.js +1 -3
- package/dist/ErrorReporter.js.map +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/Graph.d.ts +1 -1
- package/dist/Layer.d.ts +112 -117
- package/dist/Layer.d.ts.map +1 -1
- package/dist/Layer.js +43 -44
- package/dist/Layer.js.map +1 -1
- package/dist/LayerMap.d.ts +4 -4
- package/dist/LayerMap.js +3 -3
- package/dist/ManagedRuntime.d.ts +1 -1
- package/dist/ManagedRuntime.js +1 -1
- package/dist/Metric.d.ts +2 -4
- package/dist/Metric.d.ts.map +1 -1
- package/dist/Metric.js +2 -4
- package/dist/Metric.js.map +1 -1
- 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 +144 -82
- 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.map +1 -1
- package/dist/Schema.js +3 -1
- package/dist/Schema.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 +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -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/CliError.d.ts +4 -4
- package/dist/unstable/cli/CliError.js +4 -4
- package/dist/unstable/cli/Primitive.d.ts +1 -1
- package/dist/unstable/cli/Primitive.js +1 -1
- package/dist/unstable/cli/Prompt.js +31 -0
- 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/Headers.d.ts +16 -0
- package/dist/unstable/http/Headers.d.ts.map +1 -1
- package/dist/unstable/http/Headers.js +11 -0
- package/dist/unstable/http/Headers.js.map +1 -1
- package/dist/unstable/http/HttpBody.d.ts +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/HttpApiBuilder.js +5 -0
- package/dist/unstable/httpapi/HttpApiBuilder.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.map +1 -1
- package/dist/unstable/reactivity/Atom.js +4 -10
- 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/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/schema/Model.d.ts +21 -0
- package/dist/unstable/schema/Model.d.ts.map +1 -1
- package/dist/unstable/schema/Model.js +15 -0
- package/dist/unstable/schema/Model.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/Cause.ts +1 -1
- package/src/Data.ts +3 -4
- package/src/Effect.ts +331 -206
- package/src/ErrorReporter.ts +2 -4
- package/src/Exit.ts +24 -12
- package/src/Fiber.ts +1 -0
- package/src/Layer.ts +112 -117
- package/src/LayerMap.ts +4 -4
- package/src/ManagedRuntime.ts +1 -1
- package/src/Metric.ts +2 -4
- package/src/References.ts +6 -1
- package/src/RequestResolver.ts +20 -20
- package/src/Schedule.ts +144 -82
- package/src/Scheduler.ts +12 -0
- package/src/Schema.ts +3 -1
- package/src/Stdio.ts +8 -4
- package/src/Stream.ts +8 -4
- package/src/Types.ts +1 -23
- package/src/index.ts +5 -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/CliError.ts +4 -4
- package/src/unstable/cli/Primitive.ts +1 -1
- package/src/unstable/cli/Prompt.ts +27 -0
- package/src/unstable/http/Cookies.ts +84 -0
- package/src/unstable/http/Headers.ts +34 -0
- 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/HttpApiBuilder.ts +3 -0
- package/src/unstable/httpapi/HttpApiGroup.ts +1 -0
- package/src/unstable/reactivity/Atom.ts +20 -26
- package/src/unstable/reactivity/AtomHttpApi.ts +45 -11
- 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/schema/Model.ts +31 -0
- 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 }))
|
|
@@ -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
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
ctx
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
refresh(
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
refresh(
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
18
|
+
import * as HttpApiEndpoint from "../httpapi/HttpApiEndpoint.ts"
|
|
17
19
|
import type * as HttpApiGroup from "../httpapi/HttpApiGroup.ts"
|
|
18
|
-
import
|
|
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"]
|
|
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"]
|
|
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
|
|
173
|
-
|
|
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
|
-
|
|
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<
|
|
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)}`
|