effect-start 0.16.0 → 0.17.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 +2 -1
- package/src/Development.test.ts +119 -0
- package/src/Development.ts +137 -0
- package/src/Entity.test.ts +1 -1
- package/src/Entity.ts +3 -6
- package/src/FileRouter.ts +2 -2
- package/src/Route.ts +4 -0
- package/src/RouteBody.test.ts +25 -45
- package/src/RouteBody.ts +11 -8
- package/src/RouteHttp.test.ts +4 -1
- package/src/RouteHttp.ts +22 -6
- package/src/RouteSse.test.ts +249 -0
- package/src/RouteSse.ts +195 -0
- package/src/Values.ts +9 -7
- package/src/bun/BunBundle.ts +0 -73
- package/src/bun/BunRoute.ts +0 -2
- package/src/hyper/HyperHtml.test.ts +119 -0
- package/src/hyper/HyperHtml.ts +10 -2
- package/src/hyper/HyperNode.ts +2 -0
- package/src/hyper/HyperRoute.test.tsx +197 -0
- package/src/hyper/HyperRoute.ts +61 -0
- package/src/hyper/index.ts +4 -0
- package/src/hyper/jsx.d.ts +15 -0
- package/src/index.ts +1 -0
- package/src/node/FileSystem.ts +8 -0
- package/src/FileSystemExtra.test.ts +0 -242
- package/src/FileSystemExtra.ts +0 -66
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-start",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"./middlewares": "./src/middlewares/index.ts",
|
|
15
15
|
"./experimental": "./src/experimental/index.ts",
|
|
16
16
|
"./client/assets.d.ts": "./src/assets.d.ts",
|
|
17
|
+
"./hyper": "./src/hyper/index.ts",
|
|
17
18
|
"./jsx-runtime": "./src/hyper/jsx-runtime.ts",
|
|
18
19
|
"./jsx-dev-runtime": "./src/hyper/jsx-runtime.ts",
|
|
19
20
|
"./package.json": "./package.json"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as FileSystem from "@effect/platform/FileSystem"
|
|
2
|
+
import * as test from "bun:test"
|
|
3
|
+
import { MemoryFileSystem } from "effect-memfs"
|
|
4
|
+
import * as Chunk from "effect/Chunk"
|
|
5
|
+
import * as Effect from "effect/Effect"
|
|
6
|
+
import * as Fiber from "effect/Fiber"
|
|
7
|
+
import * as Layer from "effect/Layer"
|
|
8
|
+
import * as Stream from "effect/Stream"
|
|
9
|
+
import * as Development from "./Development.ts"
|
|
10
|
+
|
|
11
|
+
test.beforeEach(() => {
|
|
12
|
+
Development._resetForTesting()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test.describe("watch", () => {
|
|
16
|
+
test.it("creates pubsub and publishes file events", () =>
|
|
17
|
+
Effect
|
|
18
|
+
.gen(function*() {
|
|
19
|
+
const fs = yield* FileSystem.FileSystem
|
|
20
|
+
const watchDir = "/dev-watch"
|
|
21
|
+
|
|
22
|
+
const dev = yield* Development.watch({ path: watchDir })
|
|
23
|
+
|
|
24
|
+
const subFiber = yield* Effect.fork(
|
|
25
|
+
Stream.runCollect(
|
|
26
|
+
Stream.take(Stream.fromPubSub(dev.events), 1),
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
yield* Effect.sleep(1)
|
|
31
|
+
yield* fs.writeFileString(`${watchDir}/test.ts`, "const x = 1")
|
|
32
|
+
|
|
33
|
+
const events = yield* Fiber.join(subFiber)
|
|
34
|
+
|
|
35
|
+
test
|
|
36
|
+
.expect(Chunk.size(events))
|
|
37
|
+
.toBe(1)
|
|
38
|
+
const first = Chunk.unsafeGet(events, 0)
|
|
39
|
+
test
|
|
40
|
+
.expect("path" in first && first.path)
|
|
41
|
+
.toContain("test.ts")
|
|
42
|
+
})
|
|
43
|
+
.pipe(
|
|
44
|
+
Effect.scoped,
|
|
45
|
+
Effect.provide(
|
|
46
|
+
MemoryFileSystem.layerWith({ "/dev-watch/.gitkeep": "" }),
|
|
47
|
+
),
|
|
48
|
+
Effect.runPromise,
|
|
49
|
+
))
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test.describe("layerWatch", () => {
|
|
53
|
+
test.it("provides Development service", () =>
|
|
54
|
+
Effect
|
|
55
|
+
.gen(function*() {
|
|
56
|
+
const dev = yield* Development.Development
|
|
57
|
+
|
|
58
|
+
test
|
|
59
|
+
.expect(dev.events)
|
|
60
|
+
.toBeDefined()
|
|
61
|
+
})
|
|
62
|
+
.pipe(
|
|
63
|
+
Effect.scoped,
|
|
64
|
+
Effect.provide(Development.layerWatch({ path: "/layer-test" })),
|
|
65
|
+
Effect.provide(
|
|
66
|
+
MemoryFileSystem.layerWith({ "/layer-test/.gitkeep": "" }),
|
|
67
|
+
),
|
|
68
|
+
Effect.runPromise,
|
|
69
|
+
))
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test.describe("stream", () => {
|
|
73
|
+
test.it("returns stream from pubsub when Development is available", () =>
|
|
74
|
+
Effect
|
|
75
|
+
.gen(function*() {
|
|
76
|
+
const fs = yield* FileSystem.FileSystem
|
|
77
|
+
const watchDir = "/events-test"
|
|
78
|
+
|
|
79
|
+
const collectFiber = yield* Effect.fork(
|
|
80
|
+
Stream.runCollect(Stream.take(Development.stream(), 1)),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
yield* Effect.sleep(1)
|
|
84
|
+
yield* fs.writeFileString(`${watchDir}/file.ts`, "content")
|
|
85
|
+
|
|
86
|
+
const collected = yield* Fiber.join(collectFiber)
|
|
87
|
+
|
|
88
|
+
test
|
|
89
|
+
.expect(Chunk.size(collected))
|
|
90
|
+
.toBe(1)
|
|
91
|
+
const first = Chunk.unsafeGet(collected, 0)
|
|
92
|
+
test
|
|
93
|
+
.expect("path" in first && first.path)
|
|
94
|
+
.toContain("file.ts")
|
|
95
|
+
})
|
|
96
|
+
.pipe(
|
|
97
|
+
Effect.scoped,
|
|
98
|
+
Effect.provide(Development.layerWatch({ path: "/events-test" })),
|
|
99
|
+
Effect.provide(
|
|
100
|
+
MemoryFileSystem.layerWith({ "/events-test/.gitkeep": "" }),
|
|
101
|
+
),
|
|
102
|
+
Effect.runPromise,
|
|
103
|
+
))
|
|
104
|
+
|
|
105
|
+
test.it("returns empty stream when Development is not available", () =>
|
|
106
|
+
Effect
|
|
107
|
+
.gen(function*() {
|
|
108
|
+
const collected = yield* Stream.runCollect(Development.stream())
|
|
109
|
+
|
|
110
|
+
test
|
|
111
|
+
.expect(Chunk.size(collected))
|
|
112
|
+
.toBe(0)
|
|
113
|
+
})
|
|
114
|
+
.pipe(
|
|
115
|
+
Effect.scoped,
|
|
116
|
+
Effect.provide(Layer.empty),
|
|
117
|
+
Effect.runPromise,
|
|
118
|
+
))
|
|
119
|
+
})
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Context,
|
|
3
|
+
Effect,
|
|
4
|
+
Function,
|
|
5
|
+
Layer,
|
|
6
|
+
Option,
|
|
7
|
+
pipe,
|
|
8
|
+
PubSub,
|
|
9
|
+
Stream,
|
|
10
|
+
} from "effect"
|
|
11
|
+
import { globalValue } from "effect/GlobalValue"
|
|
12
|
+
import {
|
|
13
|
+
Error,
|
|
14
|
+
FileSystem,
|
|
15
|
+
} from "./node/FileSystem.ts"
|
|
16
|
+
|
|
17
|
+
export type DevelopmentEvent =
|
|
18
|
+
| FileSystem.WatchEvent
|
|
19
|
+
| {
|
|
20
|
+
readonly _tag: "Reload"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const devState = globalValue(
|
|
24
|
+
Symbol.for("effect-start/Development"),
|
|
25
|
+
() => ({
|
|
26
|
+
count: 0,
|
|
27
|
+
pubsub: null as PubSub.PubSub<DevelopmentEvent> | null,
|
|
28
|
+
}),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
/** @internal */
|
|
32
|
+
export const _resetForTesting = () => {
|
|
33
|
+
devState.count = 0
|
|
34
|
+
devState.pubsub = null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type DevelopmentService = {
|
|
38
|
+
events: PubSub.PubSub<DevelopmentEvent>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class Development extends Context.Tag("effect-start/Development")<
|
|
42
|
+
Development,
|
|
43
|
+
DevelopmentService
|
|
44
|
+
>() {}
|
|
45
|
+
|
|
46
|
+
const SOURCE_FILENAME = /\.(tsx?|jsx?|html?|css|json)$/
|
|
47
|
+
|
|
48
|
+
export const filterSourceFiles = (event: FileSystem.WatchEvent): boolean => {
|
|
49
|
+
return SOURCE_FILENAME.test(event.path)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const filterDirectory = (event: FileSystem.WatchEvent): boolean => {
|
|
53
|
+
return event.path.endsWith("/")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const watchSource = (
|
|
57
|
+
opts?: {
|
|
58
|
+
path?: string
|
|
59
|
+
recursive?: boolean
|
|
60
|
+
filter?: (event: FileSystem.WatchEvent) => boolean
|
|
61
|
+
},
|
|
62
|
+
): Stream.Stream<
|
|
63
|
+
FileSystem.WatchEvent,
|
|
64
|
+
Error.PlatformError,
|
|
65
|
+
FileSystem.FileSystem
|
|
66
|
+
> => {
|
|
67
|
+
const baseDir = opts?.path ?? process.cwd()
|
|
68
|
+
const customFilter = opts?.filter
|
|
69
|
+
|
|
70
|
+
return Function.pipe(
|
|
71
|
+
Stream.unwrap(
|
|
72
|
+
Effect.map(
|
|
73
|
+
FileSystem.FileSystem,
|
|
74
|
+
fs => fs.watch(baseDir, { recursive: opts?.recursive ?? true }),
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
customFilter ? Stream.filter(customFilter) : Function.identity,
|
|
78
|
+
Stream.rechunk(1),
|
|
79
|
+
Stream.throttle({
|
|
80
|
+
units: 1,
|
|
81
|
+
cost: () => 1,
|
|
82
|
+
duration: "400 millis",
|
|
83
|
+
strategy: "enforce",
|
|
84
|
+
}),
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const watch = (
|
|
89
|
+
opts?: {
|
|
90
|
+
path?: string
|
|
91
|
+
recursive?: boolean
|
|
92
|
+
filter?: (event: FileSystem.WatchEvent) => boolean
|
|
93
|
+
},
|
|
94
|
+
) =>
|
|
95
|
+
Effect.gen(function*() {
|
|
96
|
+
devState.count++
|
|
97
|
+
|
|
98
|
+
if (devState.count === 1) {
|
|
99
|
+
const pubsub = yield* PubSub.unbounded<DevelopmentEvent>()
|
|
100
|
+
devState.pubsub = pubsub
|
|
101
|
+
|
|
102
|
+
yield* pipe(
|
|
103
|
+
watchSource({
|
|
104
|
+
path: opts?.path,
|
|
105
|
+
recursive: opts?.recursive,
|
|
106
|
+
filter: opts?.filter ?? filterSourceFiles,
|
|
107
|
+
}),
|
|
108
|
+
Stream.runForEach((event) => PubSub.publish(pubsub, event)),
|
|
109
|
+
Effect.fork,
|
|
110
|
+
)
|
|
111
|
+
} else {
|
|
112
|
+
yield* PubSub.publish(devState.pubsub!, { _tag: "Reload" })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { events: devState.pubsub! } satisfies DevelopmentService
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
export const layerWatch = (
|
|
119
|
+
opts?: {
|
|
120
|
+
path?: string
|
|
121
|
+
recursive?: boolean
|
|
122
|
+
filter?: (event: FileSystem.WatchEvent) => boolean
|
|
123
|
+
},
|
|
124
|
+
) => Layer.scoped(Development, watch(opts))
|
|
125
|
+
|
|
126
|
+
export const stream = (): Stream.Stream<DevelopmentEvent> =>
|
|
127
|
+
Stream.unwrap(
|
|
128
|
+
pipe(
|
|
129
|
+
Effect.serviceOption(Development),
|
|
130
|
+
Effect.map(
|
|
131
|
+
Option.match({
|
|
132
|
+
onNone: () => Stream.empty,
|
|
133
|
+
onSome: (dev) => Stream.fromPubSub(dev.events),
|
|
134
|
+
}),
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
)
|
package/src/Entity.test.ts
CHANGED
package/src/Entity.ts
CHANGED
|
@@ -39,11 +39,9 @@ export interface Entity<
|
|
|
39
39
|
*/
|
|
40
40
|
readonly url: string | undefined
|
|
41
41
|
readonly status: number | undefined
|
|
42
|
-
readonly text: T extends string
|
|
43
|
-
? Effect.Effect<T, ParseResult.ParseError | E>
|
|
42
|
+
readonly text: T extends string ? Effect.Effect<T, ParseResult.ParseError | E>
|
|
44
43
|
: Effect.Effect<string, ParseResult.ParseError | E>
|
|
45
|
-
readonly json: [T] extends [Effect.Effect<infer A, any, any>]
|
|
46
|
-
? Effect.Effect<
|
|
44
|
+
readonly json: [T] extends [Effect.Effect<infer A, any, any>] ? Effect.Effect<
|
|
47
45
|
A extends string | Uint8Array | ArrayBuffer ? unknown : A,
|
|
48
46
|
ParseResult.ParseError | E
|
|
49
47
|
>
|
|
@@ -51,8 +49,7 @@ export interface Entity<
|
|
|
51
49
|
? Effect.Effect<unknown, ParseResult.ParseError | E>
|
|
52
50
|
: [T] extends [string | Uint8Array | ArrayBuffer]
|
|
53
51
|
? Effect.Effect<unknown, ParseResult.ParseError | E>
|
|
54
|
-
: [T] extends [Values.Json]
|
|
55
|
-
? Effect.Effect<T, ParseResult.ParseError | E>
|
|
52
|
+
: [T] extends [Values.Json] ? Effect.Effect<T, ParseResult.ParseError | E>
|
|
56
53
|
: Effect.Effect<unknown, ParseResult.ParseError | E>
|
|
57
54
|
readonly bytes: Effect.Effect<Uint8Array, ParseResult.ParseError | E>
|
|
58
55
|
readonly stream: T extends Stream.Stream<infer A, infer E1, any>
|
package/src/FileRouter.ts
CHANGED
|
@@ -10,9 +10,9 @@ import * as Record from "effect/Record"
|
|
|
10
10
|
import * as Stream from "effect/Stream"
|
|
11
11
|
import * as NPath from "node:path"
|
|
12
12
|
import * as NUrl from "node:url"
|
|
13
|
+
import * as Development from "./Development.ts"
|
|
13
14
|
import * as FileRouterCodegen from "./FileRouterCodegen.ts"
|
|
14
15
|
import * as FileRouterPattern from "./FileRouterPattern.ts"
|
|
15
|
-
import * as FileSystemExtra from "./FileSystemExtra.ts"
|
|
16
16
|
|
|
17
17
|
export type RouteModule = {
|
|
18
18
|
default: RouteSet.RouteSet.Default
|
|
@@ -161,7 +161,7 @@ export function layer(options: {
|
|
|
161
161
|
yield* FileRouterCodegen.update(routesPath, manifestFilename)
|
|
162
162
|
|
|
163
163
|
const stream = Function.pipe(
|
|
164
|
-
|
|
164
|
+
Development.watchSource({
|
|
165
165
|
path: routesPath,
|
|
166
166
|
filter: (e) => !e.path.includes("node_modules"),
|
|
167
167
|
}),
|
package/src/Route.ts
CHANGED
package/src/RouteBody.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as test from "bun:test"
|
|
2
2
|
import * as Effect from "effect/Effect"
|
|
3
|
+
import * as ParseResult from "effect/ParseResult"
|
|
3
4
|
import * as Stream from "effect/Stream"
|
|
4
5
|
import * as Entity from "./Entity.ts"
|
|
5
6
|
import * as Route from "./Route.ts"
|
|
@@ -107,51 +108,36 @@ test.describe(`${RouteBody.handle.name}()`, () => {
|
|
|
107
108
|
})
|
|
108
109
|
|
|
109
110
|
test.it("handles plain value", async () => {
|
|
110
|
-
const handler = RouteBody.handle("hello")
|
|
111
|
-
|
|
112
|
-
test
|
|
113
|
-
.expectTypeOf(handler)
|
|
114
|
-
.returns
|
|
115
|
-
.toEqualTypeOf<
|
|
116
|
-
Effect.Effect<Entity.Entity<string>, never, never>
|
|
117
|
-
>()
|
|
118
|
-
|
|
111
|
+
const handler = RouteBody.handle<{}, string, never, never>("hello")
|
|
119
112
|
const result = await Effect.runPromise(handler(ctx, next))
|
|
120
113
|
test.expect(result.body).toBe("hello")
|
|
121
114
|
test.expect(result.status).toBe(200)
|
|
122
115
|
})
|
|
123
116
|
|
|
124
117
|
test.it("handles Effect directly", async () => {
|
|
125
|
-
const handler = RouteBody.handle(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.expectTypeOf(handler)
|
|
129
|
-
.returns
|
|
130
|
-
.toEqualTypeOf<
|
|
131
|
-
Effect.Effect<Entity.Entity<string>, never, never>
|
|
132
|
-
>()
|
|
133
|
-
|
|
118
|
+
const handler = RouteBody.handle<{}, string, never, never>(
|
|
119
|
+
Effect.succeed("from effect"),
|
|
120
|
+
)
|
|
134
121
|
const result = await Effect.runPromise(handler(ctx, next))
|
|
135
|
-
|
|
136
|
-
test
|
|
137
|
-
.expect(result.body)
|
|
138
|
-
.toBe("from effect")
|
|
122
|
+
test.expect(result.body).toBe("from effect")
|
|
139
123
|
})
|
|
140
124
|
|
|
141
125
|
test.it("handles Effect with error", async () => {
|
|
142
|
-
const handler = RouteBody.handle
|
|
126
|
+
const handler = RouteBody.handle<{}, never, Error, never>(
|
|
127
|
+
Effect.fail(new Error("oops")),
|
|
128
|
+
)
|
|
143
129
|
|
|
144
130
|
test
|
|
145
131
|
.expectTypeOf(handler)
|
|
146
132
|
.returns
|
|
147
|
-
.
|
|
133
|
+
.toExtend<
|
|
148
134
|
Effect.Effect<Entity.Entity<never>, Error, never>
|
|
149
135
|
>()
|
|
150
136
|
})
|
|
151
137
|
|
|
152
138
|
test.it("handles function", async () => {
|
|
153
|
-
const handler = RouteBody.handle(
|
|
154
|
-
(ctx
|
|
139
|
+
const handler = RouteBody.handle<{ id: number }, number, never, never>(
|
|
140
|
+
(ctx) => Effect.succeed(ctx.id),
|
|
155
141
|
)
|
|
156
142
|
|
|
157
143
|
test
|
|
@@ -173,20 +159,18 @@ test.describe(`${RouteBody.handle.name}()`, () => {
|
|
|
173
159
|
>()
|
|
174
160
|
|
|
175
161
|
const numNext = () => Entity.effect(Effect.succeed(Entity.make(23)))
|
|
176
|
-
const result = await Effect.runPromise(
|
|
177
|
-
handler({ id: 42 }, numNext),
|
|
178
|
-
)
|
|
162
|
+
const result = await Effect.runPromise(handler({ id: 42 }, numNext))
|
|
179
163
|
|
|
180
|
-
test
|
|
181
|
-
.expect(result.body)
|
|
182
|
-
.toBe(42)
|
|
164
|
+
test.expect(result.body).toBe(42)
|
|
183
165
|
})
|
|
184
166
|
|
|
185
167
|
test.it("handles generator", async () => {
|
|
186
|
-
const handler = RouteBody.handle
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
168
|
+
const handler = RouteBody.handle<{ id: number }, number, never, never>(
|
|
169
|
+
function*(ctx) {
|
|
170
|
+
const n = yield* Effect.succeed(ctx.id)
|
|
171
|
+
return n * 2
|
|
172
|
+
},
|
|
173
|
+
)
|
|
190
174
|
|
|
191
175
|
test
|
|
192
176
|
.expectTypeOf(handler)
|
|
@@ -208,24 +192,20 @@ test.describe(`${RouteBody.handle.name}()`, () => {
|
|
|
208
192
|
>()
|
|
209
193
|
|
|
210
194
|
const numNext = () => Entity.effect(Effect.succeed(Entity.make(23)))
|
|
211
|
-
const result = await Effect.runPromise(
|
|
212
|
-
handler({ id: 21 }, numNext),
|
|
213
|
-
)
|
|
195
|
+
const result = await Effect.runPromise(handler({ id: 21 }, numNext))
|
|
214
196
|
|
|
215
|
-
test
|
|
216
|
-
.expect(result.body)
|
|
217
|
-
.toBe(42)
|
|
197
|
+
test.expect(result.body).toBe(42)
|
|
218
198
|
})
|
|
219
199
|
|
|
220
200
|
test.it("generator can call next", async () => {
|
|
221
|
-
const handler = RouteBody.handle(
|
|
222
|
-
function*(_ctx
|
|
201
|
+
const handler = RouteBody.handle<{}, string, ParseResult.ParseError, never>(
|
|
202
|
+
function*(_ctx, next) {
|
|
223
203
|
const fromNext = yield* next().text
|
|
224
204
|
return `got: ${fromNext}`
|
|
225
205
|
},
|
|
226
206
|
)
|
|
227
207
|
|
|
228
|
-
const result = await Effect.runPromise(handler(ctx, next))
|
|
208
|
+
const result = await Effect.runPromise(Effect.orDie(handler(ctx, next)))
|
|
229
209
|
|
|
230
210
|
test
|
|
231
211
|
.expect(result.body)
|
package/src/RouteBody.ts
CHANGED
|
@@ -115,14 +115,17 @@ export function build<
|
|
|
115
115
|
E,
|
|
116
116
|
R
|
|
117
117
|
> = (ctx, next) =>
|
|
118
|
-
Effect.map(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
Effect.map(
|
|
119
|
+
baseHandler(ctx as any, next as any),
|
|
120
|
+
(entity) =>
|
|
121
|
+
entity.headers["content-type"]
|
|
122
|
+
? entity
|
|
123
|
+
: Entity.make(entity.body, {
|
|
124
|
+
status: entity.status,
|
|
125
|
+
url: entity.url,
|
|
126
|
+
headers: { ...entity.headers, "content-type": contentType },
|
|
127
|
+
}),
|
|
128
|
+
)
|
|
126
129
|
|
|
127
130
|
const route = Route.make<{ format: F }, {}, A, E, R>(
|
|
128
131
|
wrappedHandler as any,
|
package/src/RouteHttp.test.ts
CHANGED
|
@@ -2571,7 +2571,10 @@ test.describe("Route.render (format=*)", () => {
|
|
|
2571
2571
|
)
|
|
2572
2572
|
|
|
2573
2573
|
const responses = await Promise.all([
|
|
2574
|
-
Http.fetch(handler, {
|
|
2574
|
+
Http.fetch(handler, {
|
|
2575
|
+
path: "/",
|
|
2576
|
+
headers: { Accept: "text/event-stream" },
|
|
2577
|
+
}),
|
|
2575
2578
|
Http.fetch(handler, { path: "/", headers: { Accept: "image/png" } }),
|
|
2576
2579
|
Http.fetch(handler, { path: "/", headers: { Accept: "*/*" } }),
|
|
2577
2580
|
Http.fetch(handler, { path: "/" }),
|
package/src/RouteHttp.ts
CHANGED
|
@@ -211,7 +211,9 @@ export const toWebHandlerRuntime = <R>(
|
|
|
211
211
|
|
|
212
212
|
if (methodRoutes.length === 0 && wildcards.length === 0) {
|
|
213
213
|
return Promise.resolve(
|
|
214
|
-
Response.json({ status: 405, message: "method not allowed" }, {
|
|
214
|
+
Response.json({ status: 405, message: "method not allowed" }, {
|
|
215
|
+
status: 405,
|
|
216
|
+
}),
|
|
215
217
|
)
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -232,7 +234,9 @@ export const toWebHandlerRuntime = <R>(
|
|
|
232
234
|
&& !hasWildcardFormatRoutes
|
|
233
235
|
) {
|
|
234
236
|
return Promise.resolve(
|
|
235
|
-
Response.json({ status: 406, message: "not acceptable" }, {
|
|
237
|
+
Response.json({ status: 406, message: "not acceptable" }, {
|
|
238
|
+
status: 406,
|
|
239
|
+
}),
|
|
236
240
|
)
|
|
237
241
|
}
|
|
238
242
|
|
|
@@ -311,7 +315,9 @@ export const toWebHandlerRuntime = <R>(
|
|
|
311
315
|
: Entity.make(result, { status: 200 })
|
|
312
316
|
|
|
313
317
|
if (entity.status === 404 && entity.body === undefined) {
|
|
314
|
-
return Response.json({ status: 406, message: "not acceptable" }, {
|
|
318
|
+
return Response.json({ status: 406, message: "not acceptable" }, {
|
|
319
|
+
status: 406,
|
|
320
|
+
})
|
|
315
321
|
}
|
|
316
322
|
|
|
317
323
|
return yield* toResponse(entity, selectedFormat, runtime)
|
|
@@ -374,7 +380,9 @@ export const toWebHandlerRuntime = <R>(
|
|
|
374
380
|
Effect.gen(function*() {
|
|
375
381
|
yield* Effect.logError(cause)
|
|
376
382
|
const status = getStatusFromCause(cause)
|
|
377
|
-
return Response.json({ status, message: Cause.pretty(cause) }, {
|
|
383
|
+
return Response.json({ status, message: Cause.pretty(cause) }, {
|
|
384
|
+
status,
|
|
385
|
+
})
|
|
378
386
|
})
|
|
379
387
|
),
|
|
380
388
|
),
|
|
@@ -392,10 +400,18 @@ export const toWebHandlerRuntime = <R>(
|
|
|
392
400
|
if (exit._tag === "Success") {
|
|
393
401
|
resolve(exit.value)
|
|
394
402
|
} else if (isClientAbort(exit.cause)) {
|
|
395
|
-
resolve(
|
|
403
|
+
resolve(
|
|
404
|
+
Response.json({ status: 499, message: "client closed request" }, {
|
|
405
|
+
status: 499,
|
|
406
|
+
}),
|
|
407
|
+
)
|
|
396
408
|
} else {
|
|
397
409
|
const status = getStatusFromCause(exit.cause)
|
|
398
|
-
resolve(
|
|
410
|
+
resolve(
|
|
411
|
+
Response.json({ status, message: Cause.pretty(exit.cause) }, {
|
|
412
|
+
status,
|
|
413
|
+
}),
|
|
414
|
+
)
|
|
399
415
|
}
|
|
400
416
|
})
|
|
401
417
|
})
|