effect-start 0.13.0 → 0.14.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/README.md +4 -4
- package/package.json +12 -17
- package/src/Bundle.ts +0 -35
- package/src/BundleHttp.test.ts +4 -6
- package/src/BundleHttp.ts +2 -1
- package/src/Effect_HttpRouter.test.ts +2 -3
- package/src/FileHttpRouter.test.ts +2 -2
- package/src/FileRouterCodegen.test.ts +1 -1
- package/src/FileRouter_files.test.ts +1 -1
- package/src/HttpAppExtra.test.ts +1 -1
- package/src/Start.ts +0 -34
- package/src/StartApp.ts +20 -16
- package/src/bun/BunBundle.test.ts +1 -1
- package/src/bun/BunBundle_imports.test.ts +2 -2
- package/src/bun/BunRoute_bundles.test.ts +1 -1
- package/src/bun/_BunEnhancedResolve.ts +201 -0
- package/src/bun/index.ts +0 -1
- package/src/{SseHttpResponse.ts → experimental/SseHttpResponse.ts} +2 -1
- package/src/experimental/index.ts +2 -0
- package/src/index.ts +2 -20
- package/src/middlewares/index.ts +1 -0
- package/src/{TestHttpClient.test.ts → testing/TestHttpClient.test.ts} +1 -1
- package/src/{TestHttpClient.ts → testing/TestHttpClient.ts} +0 -1
- package/src/testing/index.ts +3 -0
- package/src/{bun/BunTailwindPlugin.test.ts → x/tailwind/TailwindPlugin.test.ts} +1 -1
- package/src/{bun/BunTailwindPlugin.ts → x/tailwind/TailwindPlugin.ts} +32 -42
- package/src/x/tailwind/compile.ts +243 -0
- package/src/x/tailwind/plugin.ts +2 -2
- package/src/JsModule.test.ts +0 -14
- package/src/JsModule.ts +0 -116
- package/src/PublicDirectory.test.ts +0 -280
- package/src/PublicDirectory.ts +0 -108
- package/src/StartHttp.ts +0 -42
- /package/src/{EncryptedCookies.test.ts → experimental/EncryptedCookies.test.ts} +0 -0
- /package/src/{EncryptedCookies.ts → experimental/EncryptedCookies.ts} +0 -0
- /package/src/{TestLogger.test.ts → testing/TestLogger.test.ts} +0 -0
- /package/src/{TestLogger.ts → testing/TestLogger.ts} +0 -0
- /package/src/{testing.ts → testing/utils.ts} +0 -0
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
2
|
-
import * as t from "bun:test"
|
|
3
|
-
import { MemoryFileSystem } from "effect-memfs"
|
|
4
|
-
import {
|
|
5
|
-
effectFn,
|
|
6
|
-
TestHttpClient,
|
|
7
|
-
} from "effect-start"
|
|
8
|
-
import * as Effect from "effect/Effect"
|
|
9
|
-
import * as PublicDirectory from "./PublicDirectory.ts"
|
|
10
|
-
|
|
11
|
-
const TestFiles = {
|
|
12
|
-
"/test-public/index.html": "<html><body>Hello World</body></html>",
|
|
13
|
-
"/test-public/style.css": "body { color: red; }",
|
|
14
|
-
"/test-public/script.js": "console.log('hello');",
|
|
15
|
-
"/test-public/data.json": "{\"message\": \"test\"}",
|
|
16
|
-
"/test-public/image.png": "fake-png-data",
|
|
17
|
-
"/test-public/nested/file.txt": "nested content",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const effect = effectFn()
|
|
21
|
-
|
|
22
|
-
t.it("serves index.html for root path", () => {
|
|
23
|
-
effect(function*() {
|
|
24
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
25
|
-
const Client = TestHttpClient.make(app)
|
|
26
|
-
|
|
27
|
-
const res = yield* Client.get("/").pipe(
|
|
28
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
t
|
|
32
|
-
.expect(
|
|
33
|
-
res.status,
|
|
34
|
-
)
|
|
35
|
-
.toBe(200)
|
|
36
|
-
|
|
37
|
-
const body = yield* res.text
|
|
38
|
-
t
|
|
39
|
-
.expect(
|
|
40
|
-
body,
|
|
41
|
-
)
|
|
42
|
-
.toBe("<html><body>Hello World</body></html>")
|
|
43
|
-
|
|
44
|
-
t
|
|
45
|
-
.expect(
|
|
46
|
-
res.headers["content-type"],
|
|
47
|
-
)
|
|
48
|
-
.toBe("text/html")
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
t.it("serves CSS files with correct content type", () => {
|
|
53
|
-
effect(function*() {
|
|
54
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
55
|
-
const Client = TestHttpClient.make(app)
|
|
56
|
-
|
|
57
|
-
const res = yield* Client.get("/style.css").pipe(
|
|
58
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
t
|
|
62
|
-
.expect(
|
|
63
|
-
res.status,
|
|
64
|
-
)
|
|
65
|
-
.toBe(200)
|
|
66
|
-
|
|
67
|
-
const body = yield* res.text
|
|
68
|
-
t
|
|
69
|
-
.expect(
|
|
70
|
-
body,
|
|
71
|
-
)
|
|
72
|
-
.toBe("body { color: red; }")
|
|
73
|
-
|
|
74
|
-
t
|
|
75
|
-
.expect(
|
|
76
|
-
res.headers["content-type"],
|
|
77
|
-
)
|
|
78
|
-
.toBe("text/css")
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
t.it("serves JavaScript files with correct content type", () => {
|
|
83
|
-
effect(function*() {
|
|
84
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
85
|
-
const Client = TestHttpClient.make(app)
|
|
86
|
-
|
|
87
|
-
const res = yield* Client.get("/script.js").pipe(
|
|
88
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
t
|
|
92
|
-
.expect(
|
|
93
|
-
res.status,
|
|
94
|
-
)
|
|
95
|
-
.toBe(200)
|
|
96
|
-
|
|
97
|
-
const body = yield* res.text
|
|
98
|
-
t
|
|
99
|
-
.expect(
|
|
100
|
-
body,
|
|
101
|
-
)
|
|
102
|
-
.toBe("console.log('hello');")
|
|
103
|
-
|
|
104
|
-
t
|
|
105
|
-
.expect(
|
|
106
|
-
res.headers["content-type"],
|
|
107
|
-
)
|
|
108
|
-
.toBe("application/javascript")
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
t.it("serves JSON files with correct content type", () => {
|
|
113
|
-
effect(function*() {
|
|
114
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
115
|
-
const Client = TestHttpClient.make(app)
|
|
116
|
-
|
|
117
|
-
const res = yield* Client.get("/data.json").pipe(
|
|
118
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
t
|
|
122
|
-
.expect(
|
|
123
|
-
res.status,
|
|
124
|
-
)
|
|
125
|
-
.toBe(200)
|
|
126
|
-
|
|
127
|
-
const body = yield* res.text
|
|
128
|
-
t
|
|
129
|
-
.expect(
|
|
130
|
-
body,
|
|
131
|
-
)
|
|
132
|
-
.toBe("{\"message\": \"test\"}")
|
|
133
|
-
|
|
134
|
-
t
|
|
135
|
-
.expect(
|
|
136
|
-
res.headers["content-type"],
|
|
137
|
-
)
|
|
138
|
-
.toBe("application/json")
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
t.it("serves nested files", () => {
|
|
143
|
-
effect(function*() {
|
|
144
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
145
|
-
const Client = TestHttpClient.make(app)
|
|
146
|
-
|
|
147
|
-
const res = yield* Client.get("/nested/file.txt").pipe(
|
|
148
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
t
|
|
152
|
-
.expect(
|
|
153
|
-
res.status,
|
|
154
|
-
)
|
|
155
|
-
.toBe(200)
|
|
156
|
-
|
|
157
|
-
const body = yield* res.text
|
|
158
|
-
t
|
|
159
|
-
.expect(
|
|
160
|
-
body,
|
|
161
|
-
)
|
|
162
|
-
.toBe("nested content")
|
|
163
|
-
|
|
164
|
-
t
|
|
165
|
-
.expect(
|
|
166
|
-
res.headers["content-type"],
|
|
167
|
-
)
|
|
168
|
-
.toBe("text/plain")
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
t.it("returns 404 for non-existent files", () => {
|
|
173
|
-
effect(function*() {
|
|
174
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
175
|
-
const Client = TestHttpClient.make(app)
|
|
176
|
-
|
|
177
|
-
const res = yield* Client.get("/nonexistent.txt").pipe(
|
|
178
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
179
|
-
Effect.catchTag(
|
|
180
|
-
"RouteNotFound",
|
|
181
|
-
() => HttpServerResponse.empty({ status: 404 }),
|
|
182
|
-
),
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
t
|
|
186
|
-
.expect(
|
|
187
|
-
res.status,
|
|
188
|
-
)
|
|
189
|
-
.toBe(404)
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
t.it("prevents directory traversal attacks", () => {
|
|
194
|
-
effect(function*() {
|
|
195
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
196
|
-
const Client = TestHttpClient.make(app)
|
|
197
|
-
|
|
198
|
-
const res = yield* Client.get("/../../../etc/passwd").pipe(
|
|
199
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
200
|
-
Effect.catchTag(
|
|
201
|
-
"RouteNotFound",
|
|
202
|
-
() => HttpServerResponse.empty({ status: 404 }),
|
|
203
|
-
),
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
t
|
|
207
|
-
.expect(
|
|
208
|
-
res.status,
|
|
209
|
-
)
|
|
210
|
-
.toBe(404)
|
|
211
|
-
})
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
t.it("works with custom prefix", () => {
|
|
215
|
-
effect(function*() {
|
|
216
|
-
const app = PublicDirectory.make({
|
|
217
|
-
directory: "/test-public",
|
|
218
|
-
prefix: "/static",
|
|
219
|
-
})
|
|
220
|
-
const Client = TestHttpClient.make(app)
|
|
221
|
-
|
|
222
|
-
const res = yield* Client.get("/static/style.css").pipe(
|
|
223
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
t
|
|
227
|
-
.expect(
|
|
228
|
-
res.status,
|
|
229
|
-
)
|
|
230
|
-
.toBe(200)
|
|
231
|
-
|
|
232
|
-
const body = yield* res.text
|
|
233
|
-
t
|
|
234
|
-
.expect(
|
|
235
|
-
body,
|
|
236
|
-
)
|
|
237
|
-
.toBe("body { color: red; }")
|
|
238
|
-
})
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
t.it("ignores requests without prefix when prefix is set", () => {
|
|
242
|
-
effect(function*() {
|
|
243
|
-
const app = PublicDirectory.make({
|
|
244
|
-
directory: "/test-public",
|
|
245
|
-
prefix: "/static",
|
|
246
|
-
})
|
|
247
|
-
const Client = TestHttpClient.make(app)
|
|
248
|
-
|
|
249
|
-
const res = yield* Client.get("/style.css").pipe(
|
|
250
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
251
|
-
Effect.catchTag(
|
|
252
|
-
"RouteNotFound",
|
|
253
|
-
() => HttpServerResponse.empty({ status: 404 }),
|
|
254
|
-
),
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
t
|
|
258
|
-
.expect(
|
|
259
|
-
res.status,
|
|
260
|
-
)
|
|
261
|
-
.toBe(404)
|
|
262
|
-
})
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
t.it("sets cache control headers", () => {
|
|
266
|
-
effect(function*() {
|
|
267
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
268
|
-
const Client = TestHttpClient.make(app)
|
|
269
|
-
|
|
270
|
-
const res = yield* Client.get("/style.css").pipe(
|
|
271
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
t
|
|
275
|
-
.expect(
|
|
276
|
-
res.headers["cache-control"],
|
|
277
|
-
)
|
|
278
|
-
.toBe("public, max-age=3600")
|
|
279
|
-
})
|
|
280
|
-
})
|
package/src/PublicDirectory.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import * as FileSystem from "@effect/platform/FileSystem"
|
|
2
|
-
import * as HttpApp from "@effect/platform/HttpApp"
|
|
3
|
-
import { RouteNotFound } from "@effect/platform/HttpServerError"
|
|
4
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
5
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
6
|
-
import * as Effect from "effect/Effect"
|
|
7
|
-
import * as Function from "effect/Function"
|
|
8
|
-
import * as NPath from "node:path"
|
|
9
|
-
|
|
10
|
-
export interface PublicDirectoryOptions {
|
|
11
|
-
readonly directory?: string
|
|
12
|
-
readonly prefix?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const make = (
|
|
16
|
-
options: PublicDirectoryOptions = {},
|
|
17
|
-
): HttpApp.Default<RouteNotFound, FileSystem.FileSystem> =>
|
|
18
|
-
Effect.gen(function*() {
|
|
19
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
20
|
-
const fs = yield* FileSystem.FileSystem
|
|
21
|
-
|
|
22
|
-
const directory = options.directory ?? NPath.join(process.cwd(), "public")
|
|
23
|
-
const prefix = options.prefix ?? ""
|
|
24
|
-
|
|
25
|
-
let pathname = request.url
|
|
26
|
-
|
|
27
|
-
if (prefix && !pathname.startsWith(prefix)) {
|
|
28
|
-
return yield* Effect.fail(new RouteNotFound({ request }))
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (prefix) {
|
|
32
|
-
pathname = pathname.slice(prefix.length)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (pathname.startsWith("/")) {
|
|
36
|
-
pathname = pathname.slice(1)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (pathname === "") {
|
|
40
|
-
pathname = "index.html"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const filePath = NPath.join(directory, pathname)
|
|
44
|
-
|
|
45
|
-
if (!filePath.startsWith(directory)) {
|
|
46
|
-
return yield* Effect.fail(new RouteNotFound({ request }))
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const exists = yield* Function.pipe(
|
|
50
|
-
fs.exists(filePath),
|
|
51
|
-
Effect.catchAll(() => Effect.succeed(false)),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
if (!exists) {
|
|
55
|
-
return yield* Effect.fail(new RouteNotFound({ request }))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const stat = yield* Function.pipe(
|
|
59
|
-
fs.stat(filePath),
|
|
60
|
-
Effect.catchAll(() => Effect.fail(new RouteNotFound({ request }))),
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
if (stat.type !== "File") {
|
|
64
|
-
return yield* Effect.fail(new RouteNotFound({ request }))
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const content = yield* Function.pipe(
|
|
68
|
-
fs.readFile(filePath),
|
|
69
|
-
Effect.catchAll(() => Effect.fail(new RouteNotFound({ request }))),
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
const mimeType = getMimeType(filePath)
|
|
73
|
-
|
|
74
|
-
return HttpServerResponse.uint8Array(content, {
|
|
75
|
-
headers: {
|
|
76
|
-
"Content-Type": mimeType,
|
|
77
|
-
"Cache-Control": "public, max-age=3600",
|
|
78
|
-
},
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
function getMimeType(filePath: string): string {
|
|
83
|
-
const ext = NPath.extname(filePath).toLowerCase()
|
|
84
|
-
|
|
85
|
-
const mimeTypes: Record<string, string> = {
|
|
86
|
-
".html": "text/html",
|
|
87
|
-
".htm": "text/html",
|
|
88
|
-
".css": "text/css",
|
|
89
|
-
".js": "application/javascript",
|
|
90
|
-
".mjs": "application/javascript",
|
|
91
|
-
".json": "application/json",
|
|
92
|
-
".png": "image/png",
|
|
93
|
-
".jpg": "image/jpeg",
|
|
94
|
-
".jpeg": "image/jpeg",
|
|
95
|
-
".gif": "image/gif",
|
|
96
|
-
".svg": "image/svg+xml",
|
|
97
|
-
".ico": "image/x-icon",
|
|
98
|
-
".txt": "text/plain",
|
|
99
|
-
".pdf": "application/pdf",
|
|
100
|
-
".woff": "font/woff",
|
|
101
|
-
".woff2": "font/woff2",
|
|
102
|
-
".ttf": "font/ttf",
|
|
103
|
-
".otf": "font/otf",
|
|
104
|
-
".eot": "application/vnd.ms-fontobject",
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return mimeTypes[ext] ?? "application/octet-stream"
|
|
108
|
-
}
|
package/src/StartHttp.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import * as HttpApp from "@effect/platform/HttpApp"
|
|
2
|
-
import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
|
|
3
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
4
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
5
|
-
import * as Effect from "effect/Effect"
|
|
6
|
-
import {
|
|
7
|
-
Bundle,
|
|
8
|
-
BundleHttp,
|
|
9
|
-
} from "."
|
|
10
|
-
|
|
11
|
-
type SsrRenderer = (req: Request) => PromiseLike<Response>
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Attempts to render SSR page. If the renderer returns 404,
|
|
15
|
-
* we fall back to app.
|
|
16
|
-
*/
|
|
17
|
-
export function ssr(renderer: SsrRenderer) {
|
|
18
|
-
return Effect.gen(function*() {
|
|
19
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
20
|
-
const webRequest = request.source as Request
|
|
21
|
-
const ssrRes = yield* Effect.tryPromise(() => renderer(webRequest))
|
|
22
|
-
|
|
23
|
-
return HttpServerResponse.raw(ssrRes.body, {
|
|
24
|
-
status: ssrRes.status,
|
|
25
|
-
headers: ssrRes.headers,
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function withBundleAssets(opts?: {
|
|
31
|
-
path?: string
|
|
32
|
-
}) {
|
|
33
|
-
return HttpMiddleware.make(app =>
|
|
34
|
-
Effect.gen(function*() {
|
|
35
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
36
|
-
const bundleResponse = yield* BundleHttp.httpApp()
|
|
37
|
-
|
|
38
|
-
// Fallback to original app
|
|
39
|
-
return yield* app
|
|
40
|
-
})
|
|
41
|
-
)
|
|
42
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|