effect-start 0.10.0 → 0.11.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-start",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "exports": {
@@ -15,19 +15,21 @@
15
15
  "./jsx-runtime": "./src/jsx-runtime.ts",
16
16
  "./jsx-dev-runtime": "./src/jsx-runtime.ts",
17
17
  "./hyper": "./src/hyper/index.ts",
18
- "./x/*": "./src/x/*/index.ts"
18
+ "./x/*": "./src/x/*/index.ts",
19
+ "./middlewares/BasicAuthMiddleware": "./src/middlewares/BasicAuthMiddleware.ts",
20
+ "./assets.d.ts": "./src/assets.d.ts"
19
21
  },
20
22
  "scripts": {
21
23
  "format": "bunx dprint fmt",
22
- "test": "bun test src/",
24
+ "test": "bun test ./src/",
23
25
  "deploy": "bunx npm publish --access public"
24
26
  },
25
27
  "dependencies": {
26
- "@effect/platform": "^0.93.3"
28
+ "effect": ">=3.16.4",
29
+ "@effect/platform": "^0.93.6"
27
30
  },
28
31
  "peerDependencies": {
29
- "typescript": "^5.9.3",
30
- "effect": ">=3.16.4"
32
+ "typescript": "^5.9.3"
31
33
  },
32
34
  "peerDependenciesMeta": {
33
35
  "@tailwindcss/node": {
@@ -37,15 +39,15 @@
37
39
  "devDependencies": {
38
40
  "@dprint/json": "^0.21.0",
39
41
  "@dprint/markdown": "^0.20.0",
40
- "@dprint/typescript": "^0.95.12",
41
- "@effect/language-service": "^0.56.0",
42
+ "@dprint/typescript": "^0.95.13",
43
+ "@effect/language-service": "^0.61.0",
42
44
  "@tailwindcss/node": "^4.1.17",
43
- "@types/bun": "^1.3.3",
45
+ "@types/bun": "^1.3.4",
44
46
  "@types/react": "^19.2.7",
45
47
  "@types/react-dom": "^19.2.3",
46
48
  "dprint-cli": "^0.4.1",
47
49
  "dprint-markup": "nounder/dprint-markup",
48
- "effect": "^3.19.6",
50
+ "effect": "^3.19.10",
49
51
  "effect-memfs": "^0.8.0",
50
52
  "ts-namespace-import": "nounder/ts-namespace-import#140c405"
51
53
  },
@@ -61,7 +61,7 @@ t.it("HTTP methods", () =>
61
61
  .post(Route.html(Effect.succeed("POST")))
62
62
  .put(Route.html(Effect.succeed("PUT")))
63
63
  .patch(Route.html(Effect.succeed("PATCH")))
64
- .del(Route.html(Effect.succeed("DELETE")))
64
+ .delete(Route.html(Effect.succeed("DELETE")))
65
65
  .options(Route.html(Effect.succeed("OPTIONS")))
66
66
  .head(Route.html(Effect.succeed("HEAD"))),
67
67
  }),
@@ -148,19 +148,19 @@ t.it(
148
148
  {
149
149
  path: "/api-v1",
150
150
  load: async () => ({
151
- default: Route.text(Effect.succeed("API v1")),
151
+ default: Route.text("API v1"),
152
152
  }),
153
153
  },
154
154
  {
155
155
  path: "/files~backup",
156
156
  load: async () => ({
157
- default: Route.text(Effect.succeed("Backup files")),
157
+ default: Route.text("Backup files"),
158
158
  }),
159
159
  },
160
160
  {
161
161
  path: "/test-route~temp",
162
162
  load: async () => ({
163
- default: Route.post(Route.text(Effect.succeed("Test route"))),
163
+ default: Route.post(Route.text("Test route")),
164
164
  }),
165
165
  },
166
166
  ]
@@ -146,8 +146,6 @@ export function make<
146
146
  let router: HttpRouter.HttpRouter<any, any> = HttpRouter.empty
147
147
 
148
148
  for (const { path, routeSet, layers } of routesWithModules) {
149
- const httpRouterPath = RouterPattern.toHttpPath(path)
150
-
151
149
  for (const route of routeSet.set) {
152
150
  const matchingLayerRoutes = findMatchingLayerRoutes(route, layers)
153
151
 
@@ -182,12 +180,12 @@ export function make<
182
180
  finalHandler = middleware(finalHandler)
183
181
  }
184
182
 
185
- router = HttpRouter.route(route.method)(
186
- httpRouterPath,
187
- finalHandler as any,
188
- )(
189
- router,
190
- )
183
+ for (const pattern of RouterPattern.toEffect(path)) {
184
+ router = HttpRouter.route(route.method)(
185
+ pattern,
186
+ finalHandler as any,
187
+ )(router)
188
+ }
191
189
  }
192
190
  }
193
191
 
package/src/FileRouter.ts CHANGED
@@ -63,7 +63,7 @@ export function parseRoute(
63
63
  )
64
64
  }
65
65
 
66
- // Validate Route constraints: rest segments must be the last segment before the handle
66
+ // rest segments must be the last segment before the handle
67
67
  const pathSegments = segs.slice(0, -1) // All segments except the handle
68
68
  const restIndex = pathSegments.findIndex(seg => seg._tag === "RestSegment")
69
69
 
@@ -75,7 +75,7 @@ export function parseRoute(
75
75
  )
76
76
  }
77
77
 
78
- // Validate that all segments before the rest are literal, param, or group
78
+ // all segments before the rest must be literal, param, or group
79
79
  for (let i = 0; i < restIndex; i++) {
80
80
  const seg = pathSegments[i]
81
81
  if (
@@ -89,7 +89,7 @@ export function parseRoute(
89
89
  }
90
90
  }
91
91
  } else {
92
- // No rest: validate that all path segments are literal, param, or group
92
+ // No rest: all path segments are literal, param, or group
93
93
  for (const seg of pathSegments) {
94
94
  if (
95
95
  seg._tag !== "LiteralSegment"
@@ -103,8 +103,6 @@ export function parseRoute(
103
103
  }
104
104
  }
105
105
 
106
- // Construct routePath from path segments (excluding groups)
107
- // Groups like (admin) are stripped from the URL path
108
106
  const routePathSegments = pathSegments.filter(
109
107
  seg => seg._tag !== "GroupSegment",
110
108
  )
@@ -166,7 +164,7 @@ export function layer(options: {
166
164
  load: () => Promise<Router.RouterManifest>
167
165
  path: string
168
166
  }) {
169
- return Layer.mergeAll(
167
+ return Layer.provide(
170
168
  Layer.effect(
171
169
  Router.Router,
172
170
  Effect.promise(() => options.load()),
@@ -329,7 +329,7 @@ t.it("handles routes with hyphens and underscores in path segments", () => {
329
329
  })
330
330
 
331
331
  t.it("validateRouteModule returns true for valid modules", () => {
332
- const validRoute = Route.text(Effect.succeed("Hello"))
332
+ const validRoute = Route.text("Hello")
333
333
 
334
334
  t
335
335
  .expect(
@@ -348,7 +348,7 @@ t.it("validateRouteModule returns true for valid modules", () => {
348
348
  t
349
349
  .expect(
350
350
  FileRouterCodegen.validateRouteModule({
351
- default: Route.json(Effect.succeed({ message: "Hello" })),
351
+ default: Route.json({ message: "Hello" }),
352
352
  }),
353
353
  )
354
354
  .toBe(true)
@@ -0,0 +1,84 @@
1
+ import { HttpServerRequest } from "@effect/platform"
2
+ import { RouteNotFound } from "@effect/platform/HttpServerError"
3
+ import * as t from "bun:test"
4
+ import {
5
+ Effect,
6
+ Layer,
7
+ } from "effect"
8
+ import * as Cause from "effect/Cause"
9
+ import * as HttpAppExtra from "./HttpAppExtra.ts"
10
+ import { effectFn } from "./testing.ts"
11
+
12
+ const mockRequest = HttpServerRequest.HttpServerRequest.of({
13
+ url: "http://localhost:3000/test",
14
+ method: "GET",
15
+ headers: {
16
+ "accept": "application/json",
17
+ "user-agent": "test",
18
+ },
19
+ } as any)
20
+
21
+ const mockRequestLayer = Layer.succeed(
22
+ HttpServerRequest.HttpServerRequest,
23
+ mockRequest,
24
+ )
25
+
26
+ const effect = effectFn(mockRequestLayer)
27
+
28
+ t.describe("renderError", () => {
29
+ const routeNotFoundCause = Cause.fail(
30
+ new RouteNotFound({ request: {} as any }),
31
+ )
32
+
33
+ t.it("returns JSON for Accept: application/json", () =>
34
+ effect(function*() {
35
+ const response = yield* HttpAppExtra.renderError(
36
+ routeNotFoundCause,
37
+ "application/json",
38
+ )
39
+
40
+ t.expect(response.status).toEqual(404)
41
+ t.expect(response.headers["content-type"]).toContain("application/json")
42
+ }))
43
+
44
+ t.it("returns HTML for Accept: text/html", () =>
45
+ effect(function*() {
46
+ const response = yield* HttpAppExtra.renderError(
47
+ routeNotFoundCause,
48
+ "text/html",
49
+ )
50
+
51
+ t.expect(response.status).toEqual(404)
52
+ t.expect(response.headers["content-type"]).toContain("text/html")
53
+ }))
54
+
55
+ t.it("returns plain text for Accept: text/plain", () =>
56
+ effect(function*() {
57
+ const response = yield* HttpAppExtra.renderError(
58
+ routeNotFoundCause,
59
+ "text/plain",
60
+ )
61
+
62
+ t.expect(response.status).toEqual(404)
63
+ t.expect(response.headers["content-type"]).toContain("text/plain")
64
+ }))
65
+
66
+ t.it("returns JSON by default (no Accept header)", () =>
67
+ effect(function*() {
68
+ const response = yield* HttpAppExtra.renderError(routeNotFoundCause, "")
69
+
70
+ t.expect(response.status).toEqual(404)
71
+ t.expect(response.headers["content-type"]).toContain("application/json")
72
+ }))
73
+
74
+ t.it("returns 500 for unexpected errors", () =>
75
+ effect(function*() {
76
+ const unexpectedCause = Cause.fail({ message: "Something went wrong" })
77
+ const response = yield* HttpAppExtra.renderError(
78
+ unexpectedCause,
79
+ "application/json",
80
+ )
81
+
82
+ t.expect(response.status).toEqual(500)
83
+ }))
84
+ })