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.
Files changed (38) hide show
  1. package/README.md +4 -4
  2. package/package.json +12 -17
  3. package/src/Bundle.ts +0 -35
  4. package/src/BundleHttp.test.ts +4 -6
  5. package/src/BundleHttp.ts +2 -1
  6. package/src/Effect_HttpRouter.test.ts +2 -3
  7. package/src/FileHttpRouter.test.ts +2 -2
  8. package/src/FileRouterCodegen.test.ts +1 -1
  9. package/src/FileRouter_files.test.ts +1 -1
  10. package/src/HttpAppExtra.test.ts +1 -1
  11. package/src/Start.ts +0 -34
  12. package/src/StartApp.ts +20 -16
  13. package/src/bun/BunBundle.test.ts +1 -1
  14. package/src/bun/BunBundle_imports.test.ts +2 -2
  15. package/src/bun/BunRoute_bundles.test.ts +1 -1
  16. package/src/bun/_BunEnhancedResolve.ts +201 -0
  17. package/src/bun/index.ts +0 -1
  18. package/src/{SseHttpResponse.ts → experimental/SseHttpResponse.ts} +2 -1
  19. package/src/experimental/index.ts +2 -0
  20. package/src/index.ts +2 -20
  21. package/src/middlewares/index.ts +1 -0
  22. package/src/{TestHttpClient.test.ts → testing/TestHttpClient.test.ts} +1 -1
  23. package/src/{TestHttpClient.ts → testing/TestHttpClient.ts} +0 -1
  24. package/src/testing/index.ts +3 -0
  25. package/src/{bun/BunTailwindPlugin.test.ts → x/tailwind/TailwindPlugin.test.ts} +1 -1
  26. package/src/{bun/BunTailwindPlugin.ts → x/tailwind/TailwindPlugin.ts} +32 -42
  27. package/src/x/tailwind/compile.ts +243 -0
  28. package/src/x/tailwind/plugin.ts +2 -2
  29. package/src/JsModule.test.ts +0 -14
  30. package/src/JsModule.ts +0 -116
  31. package/src/PublicDirectory.test.ts +0 -280
  32. package/src/PublicDirectory.ts +0 -108
  33. package/src/StartHttp.ts +0 -42
  34. /package/src/{EncryptedCookies.test.ts → experimental/EncryptedCookies.test.ts} +0 -0
  35. /package/src/{EncryptedCookies.ts → experimental/EncryptedCookies.ts} +0 -0
  36. /package/src/{TestLogger.test.ts → testing/TestLogger.test.ts} +0 -0
  37. /package/src/{TestLogger.ts → testing/TestLogger.ts} +0 -0
  38. /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
- })
@@ -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