effect-start 0.20.0 → 0.21.0
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/package.json +6 -6
- package/src/ContentNegotiation.ts +8 -9
- package/src/Cookies.ts +429 -0
- package/src/Development.ts +1 -1
- package/src/FileRouter.ts +1 -1
- package/src/FileRouterCodegen.ts +1 -1
- package/src/FileSystem.ts +403 -0
- package/src/PlatformError.ts +3 -3
- package/src/Socket.ts +48 -0
- package/src/Start.ts +1 -2
- package/src/bun/BunServer.ts +58 -32
- package/src/bundler/BundleFiles.ts +1 -1
- package/src/experimental/EncryptedCookies.ts +3 -34
- package/src/experimental/index.ts +0 -1
- package/src/hyper/HyperHtml.ts +4 -0
- package/src/node/NodeFileSystem.ts +2 -16
- package/src/testing/index.ts +0 -1
- package/src/x/cloudflare/CloudflareTunnel.ts +28 -23
- package/src/HttpAppExtra.ts +0 -478
- package/src/HttpUtils.ts +0 -17
- package/src/bun/BunPlatformHttpServer.ts +0 -88
- package/src/bun/BunServerRequest.ts +0 -396
- package/src/bundler/BundleHttp.ts +0 -259
- package/src/experimental/SseHttpResponse.ts +0 -55
- package/src/middlewares/BasicAuthMiddleware.ts +0 -36
- package/src/middlewares/index.ts +0 -1
- package/src/testing/TestHttpClient.ts +0 -148
package/src/hyper/HyperHtml.ts
CHANGED
|
@@ -51,6 +51,8 @@ let map = {
|
|
|
51
51
|
"'": "apos",
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const RAW_TEXT_TAGS = ["script", "style"]
|
|
55
|
+
|
|
54
56
|
export function renderToString(
|
|
55
57
|
node: JSX.Children,
|
|
56
58
|
hooks?: { onNode?: (node: HyperNode.HyperNode) => void },
|
|
@@ -147,6 +149,8 @@ export function renderToString(
|
|
|
147
149
|
|
|
148
150
|
if (type === "script" && typeof children === "function") {
|
|
149
151
|
result += `(${children.toString()})(window)`
|
|
152
|
+
} else if (RAW_TEXT_TAGS.includes(type) && children != null) {
|
|
153
|
+
result += Array.isArray(children) ? children.join("") : children
|
|
150
154
|
} else if (Array.isArray(children)) {
|
|
151
155
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
152
156
|
stack.push(children[i])
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Adapted from @effect/platform
|
|
3
3
|
*/
|
|
4
|
-
import * as FileSystem from "@effect/platform/FileSystem"
|
|
5
4
|
import type * as Context from "effect/Context"
|
|
6
5
|
import * as Effect from "effect/Effect"
|
|
7
6
|
import * as Function from "effect/Function"
|
|
8
7
|
import * as Layer from "effect/Layer"
|
|
9
8
|
import * as Option from "effect/Option"
|
|
9
|
+
import * as FileSystem from "../FileSystem.ts"
|
|
10
10
|
import * as Stream from "effect/Stream"
|
|
11
11
|
import * as NCrypto from "node:crypto"
|
|
12
12
|
import * as NFS from "node:fs"
|
|
@@ -665,23 +665,9 @@ const make = Effect.map(
|
|
|
665
665
|
}),
|
|
666
666
|
)
|
|
667
667
|
|
|
668
|
-
export const layer = Layer.
|
|
669
|
-
Effect
|
|
670
|
-
.gen(function*() {
|
|
671
|
-
const mod = yield* Effect.tryPromise(() =>
|
|
672
|
-
import("@effect/platform/FileSystem")
|
|
673
|
-
)
|
|
674
|
-
return Layer.effect(mod.FileSystem, make)
|
|
675
|
-
})
|
|
676
|
-
.pipe(
|
|
677
|
-
Effect.catchAll(() =>
|
|
678
|
-
Effect.die(new globalThis.Error("@effect/platform is not installed"))
|
|
679
|
-
),
|
|
680
|
-
),
|
|
681
|
-
)
|
|
668
|
+
export const layer = Layer.effect(FileSystem.FileSystem, make)
|
|
682
669
|
|
|
683
670
|
export {
|
|
684
|
-
FileSystem,
|
|
685
671
|
PlatformError as Error,
|
|
686
672
|
}
|
|
687
673
|
|
package/src/testing/index.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Command,
|
|
3
|
-
HttpServer,
|
|
4
|
-
} from "@effect/platform"
|
|
5
1
|
import {
|
|
6
2
|
Config,
|
|
3
|
+
Data,
|
|
7
4
|
Effect,
|
|
8
5
|
identity,
|
|
9
6
|
Layer,
|
|
@@ -14,9 +11,10 @@ import {
|
|
|
14
11
|
String,
|
|
15
12
|
} from "effect"
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
export class CloudflareTunnelSpawnError extends Data.TaggedError(
|
|
15
|
+
"CloudflareTunnelSpawnError",
|
|
16
|
+
)<{ cause: unknown }> {}
|
|
17
|
+
|
|
20
18
|
export const start = (opts: {
|
|
21
19
|
command?: string
|
|
22
20
|
tunnelName: string
|
|
@@ -42,21 +40,31 @@ export const start = (opts: {
|
|
|
42
40
|
]
|
|
43
41
|
.flatMap(v => v)
|
|
44
42
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
const proc = yield* Effect.try({
|
|
44
|
+
try: () =>
|
|
45
|
+
Bun.spawn(
|
|
46
|
+
[opts.command ?? "cloudflared", ...args],
|
|
47
|
+
{ stderr: "pipe", stdout: "pipe" },
|
|
48
|
+
),
|
|
49
|
+
catch: (err) => new CloudflareTunnelSpawnError({ cause: err }),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
yield* Effect.addFinalizer(() =>
|
|
53
|
+
Effect.sync(() => {
|
|
54
|
+
proc.kill()
|
|
55
|
+
})
|
|
48
56
|
)
|
|
49
57
|
|
|
50
58
|
yield* Effect.logInfo(
|
|
51
|
-
`Cloudflare tunnel started name=${opts.tunnelName} pid=${
|
|
59
|
+
`Cloudflare tunnel started name=${opts.tunnelName} pid=${proc.pid} tunnelUrl=${
|
|
52
60
|
opts.tunnelUrl ?? "<empty>"
|
|
53
61
|
}`,
|
|
54
62
|
)
|
|
55
63
|
|
|
56
64
|
yield* pipe(
|
|
57
65
|
Stream.merge(
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
Stream.fromReadableStream(() => proc.stdout, identity),
|
|
67
|
+
Stream.fromReadableStream(() => proc.stderr, identity),
|
|
60
68
|
),
|
|
61
69
|
Stream.decodeText("utf-8"),
|
|
62
70
|
Stream.splitLines,
|
|
@@ -95,16 +103,13 @@ export const layer = () =>
|
|
|
95
103
|
|
|
96
104
|
yield* Effect
|
|
97
105
|
.forkScoped(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.pipe(
|
|
106
|
-
Effect.catchAll(err =>
|
|
107
|
-
Effect.logError("Cloudflare tunnel failed", err)
|
|
106
|
+
start({
|
|
107
|
+
tunnelName,
|
|
108
|
+
tunnelUrl,
|
|
109
|
+
}).pipe(
|
|
110
|
+
Effect.catchAll(err =>
|
|
111
|
+
Effect.logError("Cloudflare tunnel failed", err)
|
|
112
|
+
),
|
|
108
113
|
),
|
|
109
114
|
)
|
|
110
115
|
}))
|
package/src/HttpAppExtra.ts
DELETED
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
HttpRouter,
|
|
3
|
-
HttpServerRequest,
|
|
4
|
-
HttpServerResponse,
|
|
5
|
-
} from "@effect/platform"
|
|
6
|
-
import * as HttpApp from "@effect/platform/HttpApp"
|
|
7
|
-
import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
|
|
8
|
-
import {
|
|
9
|
-
RequestError,
|
|
10
|
-
RouteNotFound,
|
|
11
|
-
} from "@effect/platform/HttpServerError"
|
|
12
|
-
import {
|
|
13
|
-
Cause,
|
|
14
|
-
Effect,
|
|
15
|
-
Option,
|
|
16
|
-
ParseResult,
|
|
17
|
-
Record,
|
|
18
|
-
} from "effect"
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Groups: function, path
|
|
22
|
-
*/
|
|
23
|
-
const StackLinePattern = /^at (.*?) \((.*?)\)/
|
|
24
|
-
|
|
25
|
-
type GraciousError =
|
|
26
|
-
| RouteNotFound
|
|
27
|
-
| ParseResult.ParseError
|
|
28
|
-
| RequestError
|
|
29
|
-
| ParseResult.ParseError
|
|
30
|
-
|
|
31
|
-
type StackFrame = {
|
|
32
|
-
function: string
|
|
33
|
-
file: string
|
|
34
|
-
type: "application" | "framework" | "node_modules"
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const ERROR_PAGE_CSS = `
|
|
38
|
-
:root {
|
|
39
|
-
--error-red: #c00;
|
|
40
|
-
--error-red-dark: #a00;
|
|
41
|
-
--bg-error: #fee;
|
|
42
|
-
--bg-light: #f5f5f5;
|
|
43
|
-
--bg-white: #fff;
|
|
44
|
-
--border-color: #ddd;
|
|
45
|
-
--text-dark: #333;
|
|
46
|
-
--text-gray: #666;
|
|
47
|
-
--text-mono: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
* { box-sizing: border-box; }
|
|
51
|
-
|
|
52
|
-
body {
|
|
53
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
54
|
-
margin: 0;
|
|
55
|
-
padding: 0;
|
|
56
|
-
color: var(--text-dark);
|
|
57
|
-
line-height: 1.6;
|
|
58
|
-
min-height: 100dvh;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.error-page { width: 100%; margin: 0; }
|
|
62
|
-
|
|
63
|
-
.error-header {
|
|
64
|
-
background: var(--error-red);
|
|
65
|
-
color: white;
|
|
66
|
-
padding: 2rem 2.5rem;
|
|
67
|
-
margin: 0;
|
|
68
|
-
font-family: var(--text-mono);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.error-header h1 {
|
|
72
|
-
margin: 0 0 0.5rem 0;
|
|
73
|
-
font-size: 2rem;
|
|
74
|
-
font-weight: 600;
|
|
75
|
-
font-family: var(--text-mono);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.error-message {
|
|
79
|
-
margin: 0;
|
|
80
|
-
font-size: 1.1rem;
|
|
81
|
-
opacity: 0.95;
|
|
82
|
-
font-family: var(--text-mono);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.error-content {
|
|
86
|
-
background: var(--bg-white);
|
|
87
|
-
padding: 2rem 2.5rem;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.stack-trace {
|
|
91
|
-
margin: 1.5rem 0;
|
|
92
|
-
border: 1px solid var(--border-color);
|
|
93
|
-
border-radius: 4px;
|
|
94
|
-
overflow: hidden;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.stack-trace-header {
|
|
98
|
-
font-weight: 600;
|
|
99
|
-
padding: 0.75rem 1rem;
|
|
100
|
-
background: var(--bg-light);
|
|
101
|
-
border-bottom: 1px solid var(--border-color);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.stack-list {
|
|
105
|
-
list-style: none;
|
|
106
|
-
padding: 0;
|
|
107
|
-
margin: 0;
|
|
108
|
-
max-height: 400px;
|
|
109
|
-
overflow-y: auto;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.stack-list li {
|
|
113
|
-
padding: 0.5rem 1rem;
|
|
114
|
-
font-family: var(--text-mono);
|
|
115
|
-
font-size: 0.875rem;
|
|
116
|
-
border-bottom: 1px solid var(--border-color);
|
|
117
|
-
background: var(--bg-white);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.stack-list li:last-child { border-bottom: none; }
|
|
121
|
-
|
|
122
|
-
.stack-list li:hover { background: #fafafa; }
|
|
123
|
-
|
|
124
|
-
.stack-list code {
|
|
125
|
-
background: transparent;
|
|
126
|
-
padding: 0;
|
|
127
|
-
font-weight: 600;
|
|
128
|
-
color: var(--error-red-dark);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.stack-list .path { color: var(--text-gray); margin-left: 0.5rem; }
|
|
132
|
-
|
|
133
|
-
.request-info {
|
|
134
|
-
margin: 1.5rem 0;
|
|
135
|
-
border: 1px solid var(--border-color);
|
|
136
|
-
border-radius: 4px;
|
|
137
|
-
overflow: hidden;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.request-info-header {
|
|
141
|
-
font-weight: 700;
|
|
142
|
-
padding: 0.75rem 1rem;
|
|
143
|
-
background: var(--bg-light);
|
|
144
|
-
border-bottom: 1px solid var(--border-color);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.request-info-content {
|
|
148
|
-
padding: 1rem;
|
|
149
|
-
font-family: var(--text-mono);
|
|
150
|
-
font-size: 0.875rem;
|
|
151
|
-
white-space: pre-wrap;
|
|
152
|
-
word-break: break-all;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
@media (max-width: 768px) {
|
|
156
|
-
.error-header, .error-content { padding: 1.5rem 1rem; }
|
|
157
|
-
.error-header h1 { font-size: 1.5rem; }
|
|
158
|
-
}
|
|
159
|
-
`
|
|
160
|
-
|
|
161
|
-
type ErrorHtmlData = {
|
|
162
|
-
status: number
|
|
163
|
-
tag: string
|
|
164
|
-
message?: string
|
|
165
|
-
details?: object
|
|
166
|
-
requestContext?: RequestContext
|
|
167
|
-
errorName?: string
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function errorHtml(data: ErrorHtmlData): HttpServerResponse.HttpServerResponse {
|
|
171
|
-
let detailsHtml = ""
|
|
172
|
-
|
|
173
|
-
if (data.details) {
|
|
174
|
-
const detailsObj = data.details as Record<string, unknown>
|
|
175
|
-
|
|
176
|
-
if ("stack" in detailsObj && Array.isArray(detailsObj.stack)) {
|
|
177
|
-
const stackFrames = detailsObj.stack as StackFrame[]
|
|
178
|
-
detailsHtml = renderStackTrace(stackFrames)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const requestHtml = data.requestContext
|
|
183
|
-
? renderRequestContext(data.requestContext)
|
|
184
|
-
: ""
|
|
185
|
-
|
|
186
|
-
const messageHtml = data.message
|
|
187
|
-
? `<p class="error-message">${escapeHtml(data.message)}</p>`
|
|
188
|
-
: ""
|
|
189
|
-
|
|
190
|
-
const headerTitle = data.errorName ?? "UnexpectedError"
|
|
191
|
-
|
|
192
|
-
const html = `<!DOCTYPE html>
|
|
193
|
-
<html lang="en">
|
|
194
|
-
<head>
|
|
195
|
-
<meta charset="UTF-8">
|
|
196
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
197
|
-
<title>${headerTitle} - Error ${data.status}</title>
|
|
198
|
-
<style>${ERROR_PAGE_CSS}</style>
|
|
199
|
-
</head>
|
|
200
|
-
<body>
|
|
201
|
-
<div class="error-header">
|
|
202
|
-
<h1>${escapeHtml(headerTitle)}</h1>
|
|
203
|
-
${messageHtml}
|
|
204
|
-
</div>
|
|
205
|
-
<div class="error-content">
|
|
206
|
-
${detailsHtml}
|
|
207
|
-
${requestHtml}
|
|
208
|
-
</div>
|
|
209
|
-
</body>
|
|
210
|
-
</html>`
|
|
211
|
-
return HttpServerResponse.text(html, {
|
|
212
|
-
status: data.status,
|
|
213
|
-
contentType: "text/html",
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function errorText(
|
|
218
|
-
status: number,
|
|
219
|
-
tag: string,
|
|
220
|
-
details?: object,
|
|
221
|
-
): HttpServerResponse.HttpServerResponse {
|
|
222
|
-
const text = details ? `${tag}\n${JSON.stringify(details, null, 2)}` : tag
|
|
223
|
-
return HttpServerResponse.text(text, { status })
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function respondWithError(
|
|
227
|
-
accept: string,
|
|
228
|
-
status: number,
|
|
229
|
-
tag: string,
|
|
230
|
-
message?: string,
|
|
231
|
-
details?: object,
|
|
232
|
-
requestContext?: RequestContext,
|
|
233
|
-
errorName?: string,
|
|
234
|
-
): HttpServerResponse.HttpServerResponse {
|
|
235
|
-
if (accept.includes("text/html")) {
|
|
236
|
-
return errorHtml({
|
|
237
|
-
status,
|
|
238
|
-
tag,
|
|
239
|
-
message,
|
|
240
|
-
details,
|
|
241
|
-
requestContext,
|
|
242
|
-
errorName,
|
|
243
|
-
})
|
|
244
|
-
}
|
|
245
|
-
if (accept.includes("text/plain")) {
|
|
246
|
-
return errorText(status, tag, details)
|
|
247
|
-
}
|
|
248
|
-
return HttpServerResponse.unsafeJson(
|
|
249
|
-
{ error: { _tag: tag, ...details } },
|
|
250
|
-
{ status },
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export const renderError = (
|
|
255
|
-
error: unknown,
|
|
256
|
-
accept: string = "",
|
|
257
|
-
) =>
|
|
258
|
-
Effect.gen(function*() {
|
|
259
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
260
|
-
|
|
261
|
-
const requestContext: RequestContext = {
|
|
262
|
-
url: request.url,
|
|
263
|
-
method: request.method,
|
|
264
|
-
headers: filterSensitiveHeaders(request.headers),
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
let unwrappedError: GraciousError | undefined
|
|
268
|
-
|
|
269
|
-
if (Cause.isCause(error)) {
|
|
270
|
-
const failure = Cause.failureOption(error).pipe(Option.getOrUndefined)
|
|
271
|
-
|
|
272
|
-
if (failure?.["_tag"]) {
|
|
273
|
-
unwrappedError = failure as GraciousError
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
yield* Effect.logError(error)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
switch (unwrappedError?._tag) {
|
|
280
|
-
case "RouteNotFound":
|
|
281
|
-
return respondWithError(
|
|
282
|
-
accept,
|
|
283
|
-
404,
|
|
284
|
-
"RouteNotFound",
|
|
285
|
-
"The page you were looking for doesn't exist",
|
|
286
|
-
undefined,
|
|
287
|
-
requestContext,
|
|
288
|
-
)
|
|
289
|
-
case "RequestError": {
|
|
290
|
-
const message = unwrappedError.reason === "Decode"
|
|
291
|
-
? "Request body is invalid"
|
|
292
|
-
: "Request could not be processed"
|
|
293
|
-
|
|
294
|
-
return respondWithError(
|
|
295
|
-
accept,
|
|
296
|
-
400,
|
|
297
|
-
"RequestError",
|
|
298
|
-
message,
|
|
299
|
-
{
|
|
300
|
-
reason: unwrappedError.reason,
|
|
301
|
-
},
|
|
302
|
-
requestContext,
|
|
303
|
-
)
|
|
304
|
-
}
|
|
305
|
-
case "ParseError": {
|
|
306
|
-
const issues = yield* ParseResult.ArrayFormatter.formatIssue(
|
|
307
|
-
unwrappedError.issue,
|
|
308
|
-
)
|
|
309
|
-
const cleanIssues = issues.map((v) => Record.remove(v, "_tag"))
|
|
310
|
-
|
|
311
|
-
return respondWithError(
|
|
312
|
-
accept,
|
|
313
|
-
400,
|
|
314
|
-
"ParseError",
|
|
315
|
-
"Validation failed",
|
|
316
|
-
{
|
|
317
|
-
issues: cleanIssues,
|
|
318
|
-
},
|
|
319
|
-
requestContext,
|
|
320
|
-
)
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (Cause.isCause(error)) {
|
|
325
|
-
const defects = [...Cause.defects(error)]
|
|
326
|
-
const defect = defects[0]
|
|
327
|
-
if (defect instanceof Error) {
|
|
328
|
-
const stackFrames = extractPrettyStack(defect.stack ?? "")
|
|
329
|
-
return respondWithError(
|
|
330
|
-
accept,
|
|
331
|
-
500,
|
|
332
|
-
"UnexpectedError",
|
|
333
|
-
defect.message,
|
|
334
|
-
{
|
|
335
|
-
name: defect.name,
|
|
336
|
-
stack: stackFrames,
|
|
337
|
-
},
|
|
338
|
-
requestContext,
|
|
339
|
-
defect.name,
|
|
340
|
-
)
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return respondWithError(
|
|
345
|
-
accept,
|
|
346
|
-
500,
|
|
347
|
-
"UnexpectedError",
|
|
348
|
-
"An unexpected error occurred",
|
|
349
|
-
undefined,
|
|
350
|
-
requestContext,
|
|
351
|
-
"UnexpectedError",
|
|
352
|
-
)
|
|
353
|
-
})
|
|
354
|
-
|
|
355
|
-
function parseStackFrame(line: string): StackFrame | null {
|
|
356
|
-
const match = line.trim().match(StackLinePattern)
|
|
357
|
-
if (!match) return null
|
|
358
|
-
|
|
359
|
-
const [_, fn, fullPath] = match
|
|
360
|
-
const relativePath = fullPath.replace(process.cwd(), ".")
|
|
361
|
-
|
|
362
|
-
let type: "application" | "framework" | "node_modules"
|
|
363
|
-
if (relativePath.includes("node_modules")) {
|
|
364
|
-
type = "node_modules"
|
|
365
|
-
} else if (
|
|
366
|
-
relativePath.startsWith("./src")
|
|
367
|
-
|| relativePath.startsWith("./examples")
|
|
368
|
-
) {
|
|
369
|
-
type = "application"
|
|
370
|
-
} else {
|
|
371
|
-
type = "framework"
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return {
|
|
375
|
-
function: fn,
|
|
376
|
-
file: relativePath,
|
|
377
|
-
type,
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function extractPrettyStack(stack: string): StackFrame[] {
|
|
382
|
-
return stack
|
|
383
|
-
.split("\n")
|
|
384
|
-
.slice(1)
|
|
385
|
-
.map(parseStackFrame)
|
|
386
|
-
.filter((frame): frame is StackFrame => frame !== null)
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function renderStackFrames(frames: StackFrame[]): string {
|
|
390
|
-
if (frames.length === 0) {
|
|
391
|
-
return "<li>No stack frames</li>"
|
|
392
|
-
}
|
|
393
|
-
return frames
|
|
394
|
-
.map(
|
|
395
|
-
(f) =>
|
|
396
|
-
`<li><code>${f.function}</code> at <span class="path">${f.file}</span></li>`,
|
|
397
|
-
)
|
|
398
|
-
.join("")
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function renderStackTrace(frames: StackFrame[]): string {
|
|
402
|
-
return `
|
|
403
|
-
<div class="stack-trace">
|
|
404
|
-
<div class="stack-trace-header">Stack Trace (${frames.length})</div>
|
|
405
|
-
<ul class="stack-list">${renderStackFrames(frames)}</ul>
|
|
406
|
-
</div>
|
|
407
|
-
`
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function escapeHtml(unsafe: string): string {
|
|
411
|
-
return unsafe
|
|
412
|
-
.replace(/&/g, "&")
|
|
413
|
-
.replace(/</g, "<")
|
|
414
|
-
.replace(/>/g, ">")
|
|
415
|
-
.replace(/"/g, """)
|
|
416
|
-
.replace(/'/g, "'")
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
function filterSensitiveHeaders(
|
|
420
|
-
headers: Record<string, string>,
|
|
421
|
-
): Record<string, string> {
|
|
422
|
-
const sensitive = ["authorization", "cookie", "x-api-key"]
|
|
423
|
-
return Object.fromEntries(
|
|
424
|
-
Object.entries(headers).filter(
|
|
425
|
-
([key]) => !sensitive.includes(key.toLowerCase()),
|
|
426
|
-
),
|
|
427
|
-
)
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
type RequestContext = {
|
|
431
|
-
url: string
|
|
432
|
-
method: string
|
|
433
|
-
headers: Record<string, string>
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function renderRequestContext(context: RequestContext): string {
|
|
437
|
-
const headersText = Object
|
|
438
|
-
.entries(context.headers)
|
|
439
|
-
.map(([key, value]) => `${key}: ${value}`)
|
|
440
|
-
.join("\n")
|
|
441
|
-
|
|
442
|
-
const requestText = `${context.method} ${context.url}\n${headersText}`
|
|
443
|
-
|
|
444
|
-
return `
|
|
445
|
-
<div class="request-info">
|
|
446
|
-
<div class="request-info-header">Request</div>
|
|
447
|
-
<div class="request-info-content">${escapeHtml(requestText)}</div>
|
|
448
|
-
</div>
|
|
449
|
-
`
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
export function handleErrors<
|
|
453
|
-
E,
|
|
454
|
-
R,
|
|
455
|
-
>(
|
|
456
|
-
app: HttpApp.Default<E, R>,
|
|
457
|
-
): HttpApp.Default<
|
|
458
|
-
Exclude<E, RouteNotFound>,
|
|
459
|
-
R | HttpServerRequest.HttpServerRequest
|
|
460
|
-
> {
|
|
461
|
-
return Effect.gen(function*() {
|
|
462
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
463
|
-
const accept = request.headers.accept ?? ""
|
|
464
|
-
return yield* app.pipe(
|
|
465
|
-
Effect.catchAllCause((cause) => renderError(cause, accept)),
|
|
466
|
-
)
|
|
467
|
-
})
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
export const withErrorHandled = HttpMiddleware.make(app =>
|
|
471
|
-
Effect.gen(function*() {
|
|
472
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
473
|
-
const accept = request.headers.accept ?? ""
|
|
474
|
-
return yield* app.pipe(
|
|
475
|
-
Effect.catchAllCause((cause) => renderError(cause, accept)),
|
|
476
|
-
)
|
|
477
|
-
})
|
|
478
|
-
)
|
package/src/HttpUtils.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
2
|
-
|
|
3
|
-
export type FetchHandler = (request: Request) => Promise<Response>
|
|
4
|
-
|
|
5
|
-
export function makeUrlFromRequest(
|
|
6
|
-
request: HttpServerRequest.HttpServerRequest,
|
|
7
|
-
): URL {
|
|
8
|
-
const origin = request.headers.origin
|
|
9
|
-
?? request.headers.host
|
|
10
|
-
?? "http://localhost"
|
|
11
|
-
const protocol = request.headers["x-forwarded-proto"] ?? "http"
|
|
12
|
-
const host = request.headers.host ?? "localhost"
|
|
13
|
-
const base = origin.startsWith("http")
|
|
14
|
-
? origin
|
|
15
|
-
: `${protocol}://${host}`
|
|
16
|
-
return new URL(request.url, base)
|
|
17
|
-
}
|