effect-start 0.17.2 → 0.19.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 (80) hide show
  1. package/dist/Development.d.ts +7 -2
  2. package/dist/Development.js +12 -6
  3. package/dist/PlatformRuntime.d.ts +4 -0
  4. package/dist/PlatformRuntime.js +9 -0
  5. package/dist/Route.d.ts +6 -2
  6. package/dist/Route.js +22 -0
  7. package/dist/RouteHttp.d.ts +1 -1
  8. package/dist/RouteHttp.js +12 -19
  9. package/dist/RouteMount.d.ts +2 -1
  10. package/dist/Start.d.ts +1 -5
  11. package/dist/Start.js +1 -8
  12. package/dist/Unique.d.ts +50 -0
  13. package/dist/Unique.js +187 -0
  14. package/dist/bun/BunHttpServer.js +5 -6
  15. package/dist/bun/BunRoute.d.ts +1 -1
  16. package/dist/bun/BunRoute.js +2 -2
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/node/Effectify.d.ts +209 -0
  20. package/dist/node/Effectify.js +19 -0
  21. package/dist/node/FileSystem.d.ts +3 -5
  22. package/dist/node/FileSystem.js +42 -62
  23. package/dist/node/PlatformError.d.ts +46 -0
  24. package/dist/node/PlatformError.js +43 -0
  25. package/dist/testing/TestLogger.js +1 -1
  26. package/package.json +10 -5
  27. package/src/Development.ts +13 -18
  28. package/src/PlatformRuntime.ts +11 -0
  29. package/src/Route.ts +31 -2
  30. package/src/RouteHttp.ts +15 -31
  31. package/src/RouteMount.ts +1 -1
  32. package/src/Start.ts +1 -15
  33. package/src/Unique.ts +232 -0
  34. package/src/bun/BunHttpServer.ts +6 -9
  35. package/src/bun/BunRoute.ts +3 -3
  36. package/src/index.ts +1 -0
  37. package/src/node/Effectify.ts +262 -0
  38. package/src/node/FileSystem.ts +59 -97
  39. package/src/node/PlatformError.ts +102 -0
  40. package/src/testing/TestLogger.ts +1 -1
  41. package/dist/Random.d.ts +0 -5
  42. package/dist/Random.js +0 -49
  43. package/src/Commander.test.ts +0 -1639
  44. package/src/ContentNegotiation.test.ts +0 -603
  45. package/src/Development.test.ts +0 -119
  46. package/src/Entity.test.ts +0 -592
  47. package/src/FileRouterPattern.test.ts +0 -147
  48. package/src/FileRouter_files.test.ts +0 -64
  49. package/src/FileRouter_path.test.ts +0 -145
  50. package/src/FileRouter_tree.test.ts +0 -132
  51. package/src/Http.test.ts +0 -319
  52. package/src/HttpAppExtra.test.ts +0 -103
  53. package/src/HttpUtils.test.ts +0 -85
  54. package/src/PathPattern.test.ts +0 -648
  55. package/src/Random.ts +0 -59
  56. package/src/RouteBody.test.ts +0 -232
  57. package/src/RouteHook.test.ts +0 -40
  58. package/src/RouteHttp.test.ts +0 -2909
  59. package/src/RouteMount.test.ts +0 -481
  60. package/src/RouteSchema.test.ts +0 -427
  61. package/src/RouteSse.test.ts +0 -249
  62. package/src/RouteTree.test.ts +0 -494
  63. package/src/RouteTrie.test.ts +0 -322
  64. package/src/RouterPattern.test.ts +0 -676
  65. package/src/Values.test.ts +0 -263
  66. package/src/bun/BunBundle.test.ts +0 -268
  67. package/src/bun/BunBundle_imports.test.ts +0 -48
  68. package/src/bun/BunHttpServer.test.ts +0 -251
  69. package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
  70. package/src/bun/BunRoute.test.ts +0 -162
  71. package/src/bundler/BundleHttp.test.ts +0 -132
  72. package/src/effect/HttpRouter.test.ts +0 -548
  73. package/src/experimental/EncryptedCookies.test.ts +0 -488
  74. package/src/hyper/HyperHtml.test.ts +0 -209
  75. package/src/hyper/HyperRoute.test.tsx +0 -197
  76. package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
  77. package/src/testing/TestHttpClient.test.ts +0 -83
  78. package/src/testing/TestLogger.test.ts +0 -51
  79. package/src/x/datastar/Datastar.test.ts +0 -266
  80. package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
package/src/Http.test.ts DELETED
@@ -1,319 +0,0 @@
1
- import * as test from "bun:test"
2
- import * as Http from "./Http.ts"
3
-
4
- test.describe("mapHeaders", () => {
5
- test.it("converts Headers to record with lowercase keys", () => {
6
- const headers = new Headers({
7
- "Content-Type": "application/json",
8
- "X-Custom-Header": "value",
9
- })
10
-
11
- const record = Http.mapHeaders(headers)
12
-
13
- test
14
- .expect(record)
15
- .toEqual({
16
- "content-type": "application/json",
17
- "x-custom-header": "value",
18
- })
19
- })
20
-
21
- test.it("returns empty record for empty headers", () => {
22
- const headers = new Headers()
23
- const record = Http.mapHeaders(headers)
24
-
25
- test
26
- .expect(record)
27
- .toEqual({})
28
- })
29
- })
30
-
31
- test.describe("parseCookies", () => {
32
- test.it("parses cookie header string", () => {
33
- const cookieHeader = "session=abc123; token=xyz789"
34
- const cookies = Http.parseCookies(cookieHeader)
35
-
36
- test
37
- .expect(cookies)
38
- .toEqual({
39
- session: "abc123",
40
- token: "xyz789",
41
- })
42
- })
43
-
44
- test.it("handles cookies with = in value", () => {
45
- const cookieHeader = "data=key=value"
46
- const cookies = Http.parseCookies(cookieHeader)
47
-
48
- test
49
- .expect(cookies)
50
- .toEqual({
51
- data: "key=value",
52
- })
53
- })
54
-
55
- test.it("trims whitespace from cookie names and values", () => {
56
- const cookieHeader = " session = abc123 ; token = xyz789 "
57
- const cookies = Http.parseCookies(cookieHeader)
58
-
59
- test
60
- .expect(cookies)
61
- .toEqual({
62
- session: "abc123",
63
- token: "xyz789",
64
- })
65
- })
66
-
67
- test.it("handles empty cookie values", () => {
68
- const cookieHeader = "session=; token=xyz789"
69
- const cookies = Http.parseCookies(cookieHeader)
70
-
71
- test
72
- .expect(cookies)
73
- .toEqual({
74
- session: "",
75
- token: "xyz789",
76
- })
77
- })
78
-
79
- test.it("handles cookies without values", () => {
80
- const cookieHeader = "flag; session=abc123"
81
- const cookies = Http.parseCookies(cookieHeader)
82
-
83
- test
84
- .expect(cookies)
85
- .toEqual({
86
- flag: undefined,
87
- session: "abc123",
88
- })
89
- })
90
-
91
- test.it("ignores empty parts", () => {
92
- const cookieHeader = "session=abc123;; ; token=xyz789"
93
- const cookies = Http.parseCookies(cookieHeader)
94
-
95
- test
96
- .expect(cookies)
97
- .toEqual({
98
- session: "abc123",
99
- token: "xyz789",
100
- })
101
- })
102
-
103
- test.it("returns empty record for null cookie header", () => {
104
- const cookies = Http.parseCookies(null)
105
-
106
- test
107
- .expect(cookies)
108
- .toEqual({})
109
- })
110
-
111
- test.it("returns empty record for empty cookie header", () => {
112
- const cookies = Http.parseCookies("")
113
-
114
- test
115
- .expect(cookies)
116
- .toEqual({})
117
- })
118
- })
119
-
120
- test.describe("mapUrlSearchParams", () => {
121
- test.it("converts single values to strings", () => {
122
- const params = new URLSearchParams("page=1&limit=10")
123
- const record = Http.mapUrlSearchParams(params)
124
-
125
- test
126
- .expect(record)
127
- .toEqual({
128
- page: "1",
129
- limit: "10",
130
- })
131
- })
132
-
133
- test.it("converts multiple values to arrays", () => {
134
- const params = new URLSearchParams("tags=red&tags=blue&tags=green")
135
- const record = Http.mapUrlSearchParams(params)
136
-
137
- test
138
- .expect(record)
139
- .toEqual({
140
- tags: ["red", "blue", "green"],
141
- })
142
- })
143
-
144
- test.it("handles mixed single and multiple values", () => {
145
- const params = new URLSearchParams("page=1&tags=red&tags=blue")
146
- const record = Http.mapUrlSearchParams(params)
147
-
148
- test
149
- .expect(record)
150
- .toEqual({
151
- page: "1",
152
- tags: ["red", "blue"],
153
- })
154
- })
155
-
156
- test.it("returns empty record for empty params", () => {
157
- const params = new URLSearchParams()
158
- const record = Http.mapUrlSearchParams(params)
159
-
160
- test
161
- .expect(record)
162
- .toEqual({})
163
- })
164
- })
165
-
166
- test.describe("parseFormData", () => {
167
- function createFormDataRequest(formData: FormData): Request {
168
- return new Request("http://localhost/", {
169
- method: "POST",
170
- body: formData,
171
- })
172
- }
173
-
174
- test.it("parses single string field", async () => {
175
- const formData = new FormData()
176
- formData.append("name", "John")
177
-
178
- const request = createFormDataRequest(formData)
179
- const result = await Http.parseFormData(request)
180
-
181
- test
182
- .expect(result)
183
- .toEqual({
184
- name: "John",
185
- })
186
- })
187
-
188
- test.it("parses multiple string fields", async () => {
189
- const formData = new FormData()
190
- formData.append("name", "John")
191
- formData.append("email", "john@example.com")
192
-
193
- const request = createFormDataRequest(formData)
194
- const result = await Http.parseFormData(request)
195
-
196
- test
197
- .expect(result)
198
- .toEqual({
199
- name: "John",
200
- email: "john@example.com",
201
- })
202
- })
203
-
204
- test.it("parses multiple values for same key as array", async () => {
205
- const formData = new FormData()
206
- formData.append("tags", "red")
207
- formData.append("tags", "blue")
208
- formData.append("tags", "green")
209
-
210
- const request = createFormDataRequest(formData)
211
- const result = await Http.parseFormData(request)
212
-
213
- test
214
- .expect(result)
215
- .toEqual({
216
- tags: ["red", "blue", "green"],
217
- })
218
- })
219
-
220
- test.it("parses single file upload", async () => {
221
- const formData = new FormData()
222
- const fileContent = new Uint8Array([72, 101, 108, 108, 111]) // "Hello"
223
- const file = new File([fileContent], "test.txt", { type: "text/plain" })
224
- formData.append("document", file)
225
-
226
- const request = createFormDataRequest(formData)
227
- const result = await Http.parseFormData(request)
228
-
229
- test.expect(result.document).toBeDefined()
230
- const files = result.document as ReadonlyArray<Http.FilePart>
231
- test.expect(files).toHaveLength(1)
232
- test.expect(files[0]._tag).toBe("File")
233
- test.expect(files[0].key).toBe("document")
234
- test.expect(files[0].name).toBe("test.txt")
235
- test.expect(files[0].contentType.startsWith("text/plain")).toBe(true)
236
- test.expect(files[0].content).toEqual(fileContent)
237
- })
238
-
239
- test.it("parses multiple file uploads for same key", async () => {
240
- const formData = new FormData()
241
- const file1 = new File([new Uint8Array([1, 2, 3])], "file1.bin", {
242
- type: "application/octet-stream",
243
- })
244
- const file2 = new File([new Uint8Array([4, 5, 6])], "file2.bin", {
245
- type: "application/octet-stream",
246
- })
247
- formData.append("files", file1)
248
- formData.append("files", file2)
249
-
250
- const request = createFormDataRequest(formData)
251
- const result = await Http.parseFormData(request)
252
-
253
- const files = result.files as ReadonlyArray<Http.FilePart>
254
- test.expect(files).toHaveLength(2)
255
- test.expect(files[0].name).toBe("file1.bin")
256
- test.expect(files[0].content).toEqual(new Uint8Array([1, 2, 3]))
257
- test.expect(files[1].name).toBe("file2.bin")
258
- test.expect(files[1].content).toEqual(new Uint8Array([4, 5, 6]))
259
- })
260
-
261
- test.it("uses default content type for files without type", async () => {
262
- const formData = new FormData()
263
- const file = new File([new Uint8Array([1, 2, 3])], "unknown.dat", {
264
- type: "",
265
- })
266
- formData.append("upload", file)
267
-
268
- const request = createFormDataRequest(formData)
269
- const result = await Http.parseFormData(request)
270
-
271
- const files = result.upload as ReadonlyArray<Http.FilePart>
272
- test.expect(files[0].contentType).toBe("application/octet-stream")
273
- })
274
-
275
- test.it("parses mixed string fields and file uploads", async () => {
276
- const formData = new FormData()
277
- formData.append("title", "My Document")
278
- const file = new File([new Uint8Array([1, 2, 3])], "doc.pdf", {
279
- type: "application/pdf",
280
- })
281
- formData.append("attachment", file)
282
- formData.append("description", "A test document")
283
-
284
- const request = createFormDataRequest(formData)
285
- const result = await Http.parseFormData(request)
286
-
287
- test.expect(result.title).toBe("My Document")
288
- test.expect(result.description).toBe("A test document")
289
- const files = result.attachment as ReadonlyArray<Http.FilePart>
290
- test.expect(files).toHaveLength(1)
291
- test.expect(files[0].name).toBe("doc.pdf")
292
- })
293
-
294
- test.it("returns empty record for empty form data", async () => {
295
- const formData = new FormData()
296
-
297
- const request = createFormDataRequest(formData)
298
- const result = await Http.parseFormData(request)
299
-
300
- test
301
- .expect(result)
302
- .toEqual({})
303
- })
304
-
305
- test.it("parses Blob as file", async () => {
306
- const formData = new FormData()
307
- const blob = new Blob([new Uint8Array([10, 20, 30])], { type: "image/png" })
308
- formData.append("image", blob, "image.png")
309
-
310
- const request = createFormDataRequest(formData)
311
- const result = await Http.parseFormData(request)
312
-
313
- const files = result.image as ReadonlyArray<Http.FilePart>
314
- test.expect(files).toHaveLength(1)
315
- test.expect(files[0].name).toBe("image.png")
316
- test.expect(files[0].contentType).toBe("image/png")
317
- test.expect(files[0].content).toEqual(new Uint8Array([10, 20, 30]))
318
- })
319
- })
@@ -1,103 +0,0 @@
1
- import { HttpServerRequest } from "@effect/platform"
2
- import { RouteNotFound } from "@effect/platform/HttpServerError"
3
- import * as test from "bun:test"
4
- import {
5
- Layer,
6
- Logger,
7
- } from "effect"
8
- import * as Cause from "effect/Cause"
9
- import * as LogLevel from "effect/LogLevel"
10
- import * as HttpAppExtra from "./HttpAppExtra.ts"
11
- import { effectFn } from "./testing"
12
-
13
- const mockRequest = HttpServerRequest.HttpServerRequest.of({
14
- url: "http://localhost:3000/test",
15
- method: "GET",
16
- headers: {
17
- "accept": "application/json",
18
- "user-agent": "test",
19
- },
20
- } as any)
21
-
22
- const mockRequestLayer = Layer.mergeAll(
23
- Layer.succeed(HttpServerRequest.HttpServerRequest, mockRequest),
24
- Logger.minimumLogLevel(LogLevel.None),
25
- )
26
-
27
- const effect = effectFn(mockRequestLayer)
28
-
29
- test.describe("renderError", () => {
30
- const routeNotFoundCause = Cause.fail(
31
- new RouteNotFound({ request: {} as any }),
32
- )
33
-
34
- test.it("returns JSON for Accept: application/json", () =>
35
- effect(function*() {
36
- const response = yield* HttpAppExtra.renderError(
37
- routeNotFoundCause,
38
- "application/json",
39
- )
40
-
41
- test
42
- .expect(response.status)
43
- .toEqual(404)
44
- test
45
- .expect(response.headers["content-type"])
46
- .toContain("application/json")
47
- }))
48
-
49
- test.it("returns HTML for Accept: text/html", () =>
50
- effect(function*() {
51
- const response = yield* HttpAppExtra.renderError(
52
- routeNotFoundCause,
53
- "text/html",
54
- )
55
-
56
- test
57
- .expect(response.status)
58
- .toEqual(404)
59
- test
60
- .expect(response.headers["content-type"])
61
- .toContain("text/html")
62
- }))
63
-
64
- test.it("returns plain text for Accept: text/plain", () =>
65
- effect(function*() {
66
- const response = yield* HttpAppExtra.renderError(
67
- routeNotFoundCause,
68
- "text/plain",
69
- )
70
-
71
- test
72
- .expect(response.status)
73
- .toEqual(404)
74
- test
75
- .expect(response.headers["content-type"])
76
- .toContain("text/plain")
77
- }))
78
-
79
- test.it("returns JSON by default (no Accept header)", () =>
80
- effect(function*() {
81
- const response = yield* HttpAppExtra.renderError(routeNotFoundCause, "")
82
-
83
- test
84
- .expect(response.status)
85
- .toEqual(404)
86
- test
87
- .expect(response.headers["content-type"])
88
- .toContain("application/json")
89
- }))
90
-
91
- test.it("returns 500 for unexpected errors", () =>
92
- effect(function*() {
93
- const unexpectedCause = Cause.fail({ message: "Something went wrong" })
94
- const response = yield* HttpAppExtra.renderError(
95
- unexpectedCause,
96
- "application/json",
97
- )
98
-
99
- test
100
- .expect(response.status)
101
- .toEqual(500)
102
- }))
103
- })
@@ -1,85 +0,0 @@
1
- import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
2
- import * as test from "bun:test"
3
- import * as HttpUtils from "./HttpUtils.ts"
4
-
5
- const makeRequest = (url: string, headers: Record<string, string> = {}) =>
6
- HttpServerRequest.fromWeb(
7
- new Request(`http://test${url}`, { headers }),
8
- )
9
-
10
- test.describe("makeUrlFromRequest", () => {
11
- test.it("uses Host header for relative URL", () => {
12
- const request = makeRequest("/api/users", {
13
- host: "example.com",
14
- })
15
- const url = HttpUtils.makeUrlFromRequest(request)
16
-
17
- test
18
- .expect(url.href)
19
- .toBe("http://example.com/api/users")
20
- })
21
-
22
- test.it("uses Origin header when present (takes precedence over Host)", () => {
23
- const request = makeRequest("/api/users", {
24
- origin: "https://app.example.com",
25
- host: "example.com",
26
- })
27
- const url = HttpUtils.makeUrlFromRequest(request)
28
-
29
- test
30
- .expect(url.href)
31
- .toBe("https://app.example.com/api/users")
32
- })
33
-
34
- test.it("uses X-Forwarded-Proto for protocol behind reverse proxy", () => {
35
- const request = makeRequest("/api/users", {
36
- host: "example.com",
37
- "x-forwarded-proto": "https",
38
- })
39
- const url = HttpUtils.makeUrlFromRequest(request)
40
-
41
- test
42
- .expect(url.href)
43
- .toBe("https://example.com/api/users")
44
- })
45
-
46
- test.it("falls back to http://localhost when no headers", () => {
47
- const request = makeRequest("/api/users", {})
48
- const url = HttpUtils.makeUrlFromRequest(request)
49
-
50
- test
51
- .expect(url.href)
52
- .toBe("http://localhost/api/users")
53
- })
54
-
55
- test.it("handles URL with query parameters", () => {
56
- const request = makeRequest("/search?q=test&page=1", {
57
- host: "example.com",
58
- })
59
- const url = HttpUtils.makeUrlFromRequest(request)
60
-
61
- test
62
- .expect(url.href)
63
- .toBe("http://example.com/search?q=test&page=1")
64
- test
65
- .expect(url.searchParams.get("q"))
66
- .toBe("test")
67
- test
68
- .expect(url.searchParams.get("page"))
69
- .toBe("1")
70
- })
71
-
72
- test.it("handles root path", () => {
73
- const request = makeRequest("/", {
74
- host: "example.com",
75
- })
76
- const url = HttpUtils.makeUrlFromRequest(request)
77
-
78
- test
79
- .expect(url.href)
80
- .toBe("http://example.com/")
81
- test
82
- .expect(url.pathname)
83
- .toBe("/")
84
- })
85
- })