effect-start 0.14.0 → 0.16.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 +8 -9
- package/src/Commander.test.ts +507 -245
- package/src/ContentNegotiation.test.ts +603 -0
- package/src/ContentNegotiation.ts +542 -0
- package/src/Entity.test.ts +592 -0
- package/src/Entity.ts +362 -0
- package/src/FileRouter.ts +16 -12
- package/src/{FileRouterCodegen.test.ts → FileRouterCodegen.todo.ts} +384 -219
- package/src/FileRouterCodegen.ts +6 -6
- package/src/FileRouterPattern.test.ts +93 -62
- package/src/FileRouter_files.test.ts +5 -5
- package/src/FileRouter_path.test.ts +121 -69
- package/src/FileRouter_tree.test.ts +62 -56
- package/src/FileSystemExtra.test.ts +46 -30
- package/src/Http.test.ts +319 -0
- package/src/Http.ts +167 -0
- package/src/HttpAppExtra.test.ts +39 -20
- package/src/HttpAppExtra.ts +0 -1
- package/src/HttpUtils.test.ts +35 -18
- package/src/HttpUtils.ts +2 -0
- package/src/PathPattern.test.ts +648 -0
- package/src/PathPattern.ts +485 -0
- package/src/Route.ts +266 -1069
- package/src/RouteBody.test.ts +234 -0
- package/src/RouteBody.ts +193 -0
- package/src/RouteHook.test.ts +40 -0
- package/src/RouteHook.ts +106 -0
- package/src/RouteHttp.test.ts +2906 -0
- package/src/RouteHttp.ts +427 -0
- package/src/RouteHttpTracer.ts +92 -0
- package/src/RouteMount.test.ts +481 -0
- package/src/RouteMount.ts +470 -0
- package/src/RouteSchema.test.ts +427 -0
- package/src/RouteSchema.ts +423 -0
- package/src/RouteTree.test.ts +494 -0
- package/src/RouteTree.ts +219 -0
- package/src/RouteTrie.test.ts +322 -0
- package/src/RouteTrie.ts +224 -0
- package/src/RouterPattern.test.ts +569 -548
- package/src/RouterPattern.ts +7 -7
- package/src/Start.ts +3 -3
- package/src/StreamExtra.ts +21 -1
- package/src/TuplePathPattern.ts +64 -0
- package/src/Values.test.ts +263 -0
- package/src/Values.ts +76 -0
- package/src/bun/BunBundle.test.ts +36 -42
- package/src/bun/BunBundle.ts +2 -2
- package/src/bun/BunBundle_imports.test.ts +4 -6
- package/src/bun/BunHttpServer.test.ts +183 -6
- package/src/bun/BunHttpServer.ts +72 -32
- package/src/bun/BunHttpServer_web.ts +18 -6
- package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
- package/src/bun/BunRoute.test.ts +124 -442
- package/src/bun/BunRoute.ts +146 -286
- package/src/{BundleHttp.test.ts → bundler/BundleHttp.test.ts} +34 -60
- package/src/{BundleHttp.ts → bundler/BundleHttp.ts} +1 -2
- package/src/client/index.ts +1 -1
- package/src/{Effect_HttpRouter.test.ts → effect/HttpRouter.test.ts} +69 -90
- package/src/experimental/EncryptedCookies.test.ts +125 -64
- package/src/experimental/SseHttpResponse.ts +0 -1
- package/src/hyper/Hyper.ts +89 -0
- package/src/{HyperHtml.test.ts → hyper/HyperHtml.test.ts} +13 -13
- package/src/{HyperHtml.ts → hyper/HyperHtml.ts} +2 -2
- package/src/{jsx.d.ts → hyper/jsx.d.ts} +1 -1
- package/src/index.ts +3 -4
- package/src/middlewares/BasicAuthMiddleware.test.ts +29 -19
- package/src/{NodeFileSystem.ts → node/FileSystem.ts} +6 -2
- package/src/testing/TestHttpClient.test.ts +26 -26
- package/src/testing/TestLogger.test.ts +27 -14
- package/src/testing/TestLogger.ts +15 -9
- package/src/x/datastar/Datastar.test.ts +47 -48
- package/src/x/datastar/Datastar.ts +1 -1
- package/src/x/tailwind/TailwindPlugin.test.ts +56 -58
- package/src/x/tailwind/plugin.ts +1 -1
- package/src/FileHttpRouter.test.ts +0 -239
- package/src/FileHttpRouter.ts +0 -194
- package/src/Hyper.ts +0 -194
- package/src/Route.test.ts +0 -1370
- package/src/RouteRender.ts +0 -40
- package/src/Router.test.ts +0 -375
- package/src/Router.ts +0 -255
- package/src/bun/BunRoute_bundles.test.ts +0 -219
- /package/src/{Bundle.ts → bundler/Bundle.ts} +0 -0
- /package/src/{BundleFiles.ts → bundler/BundleFiles.ts} +0 -0
- /package/src/{HyperNode.ts → hyper/HyperNode.ts} +0 -0
- /package/src/{jsx-runtime.ts → hyper/jsx-runtime.ts} +0 -0
- /package/src/{NodeUtils.ts → node/Utils.ts} +0 -0
package/src/RouteRender.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
2
|
-
import * as Effect from "effect/Effect"
|
|
3
|
-
import * as HyperHtml from "./HyperHtml.ts"
|
|
4
|
-
import * as Route from "./Route.ts"
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Renders a route handler to an HttpServerResponse.
|
|
8
|
-
* Converts the raw handler value to a response based on the route's media type.
|
|
9
|
-
*/
|
|
10
|
-
export function render<E, R>(
|
|
11
|
-
route: Route.Route<any, any, Route.RouteHandler<any, E, R>, any>,
|
|
12
|
-
context: Route.RouteContext,
|
|
13
|
-
): Effect.Effect<HttpServerResponse.HttpServerResponse, E, R> {
|
|
14
|
-
return Effect.gen(function*() {
|
|
15
|
-
const raw = yield* route.handler(context)
|
|
16
|
-
|
|
17
|
-
// Allow handlers to return HttpServerResponse directly (e.g. BunRoute proxy)
|
|
18
|
-
if (HttpServerResponse.isServerResponse(raw)) {
|
|
19
|
-
return raw
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
switch (route.media) {
|
|
23
|
-
case "text/plain":
|
|
24
|
-
return HttpServerResponse.text(raw as string)
|
|
25
|
-
|
|
26
|
-
case "text/html":
|
|
27
|
-
if (Route.isGenericJsxObject(raw)) {
|
|
28
|
-
return HttpServerResponse.html(HyperHtml.renderToString(raw))
|
|
29
|
-
}
|
|
30
|
-
return HttpServerResponse.html(raw as string)
|
|
31
|
-
|
|
32
|
-
case "application/json":
|
|
33
|
-
return HttpServerResponse.unsafeJson(raw)
|
|
34
|
-
|
|
35
|
-
case "*":
|
|
36
|
-
default:
|
|
37
|
-
return HttpServerResponse.text(String(raw))
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
}
|
package/src/Router.test.ts
DELETED
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
import * as t from "bun:test"
|
|
2
|
-
import * as Effect from "effect/Effect"
|
|
3
|
-
import * as Route from "./Route.ts"
|
|
4
|
-
import * as Router from "./Router.ts"
|
|
5
|
-
|
|
6
|
-
t.describe("Router", () => {
|
|
7
|
-
t.describe("mount", () => {
|
|
8
|
-
t.test("creates router with single route", () => {
|
|
9
|
-
const router = Router.mount("/hello", Route.text("Hello World"))
|
|
10
|
-
|
|
11
|
-
t.expect(router.entries).toHaveLength(1)
|
|
12
|
-
t.expect(router.entries[0].path).toBe("/hello")
|
|
13
|
-
t.expect(router.entries[0].route.set).toHaveLength(1)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
t.test("chains multiple routes", () => {
|
|
17
|
-
const router = Router
|
|
18
|
-
.mount("/hello", Route.text("Hello"))
|
|
19
|
-
.mount("/world", Route.text("World"))
|
|
20
|
-
|
|
21
|
-
t.expect(router.entries).toHaveLength(2)
|
|
22
|
-
t.expect(router.entries[0].path).toBe("/hello")
|
|
23
|
-
t.expect(router.entries[1].path).toBe("/world")
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
t.test("merges routes at same path", () => {
|
|
27
|
-
const router = Router
|
|
28
|
-
.mount("/api", Route.get(Route.json({ method: "get" })))
|
|
29
|
-
.mount("/api", Route.post(Route.json({ method: "post" })))
|
|
30
|
-
|
|
31
|
-
t.expect(router.entries).toHaveLength(1)
|
|
32
|
-
t.expect(router.entries[0].path).toBe("/api")
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
t.describe("mounts", () => {
|
|
37
|
-
t.test("exposes mounted routes as Record", () => {
|
|
38
|
-
const router = Router
|
|
39
|
-
.mount("/hello", Route.text("Hello"))
|
|
40
|
-
.mount("/world", Route.text("World"))
|
|
41
|
-
|
|
42
|
-
t.expect(router.mounts["/hello"]).toBeDefined()
|
|
43
|
-
t.expect(router.mounts["/world"]).toBeDefined()
|
|
44
|
-
t.expect(router.mounts["/hello"].set).toHaveLength(1)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
t.test("mounts contain routes with layers applied", async () => {
|
|
48
|
-
const layer = Route.layer(
|
|
49
|
-
Route.html(function*(c) {
|
|
50
|
-
const inner = yield* c.next()
|
|
51
|
-
return `<wrap>${inner}</wrap>`
|
|
52
|
-
}),
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
const router = Router
|
|
56
|
-
.use(layer)
|
|
57
|
-
.mount("/page", Route.html(Effect.succeed("content")))
|
|
58
|
-
|
|
59
|
-
const mountedRoute = router.mounts["/page"]
|
|
60
|
-
t.expect(mountedRoute).toBeDefined()
|
|
61
|
-
t.expect(mountedRoute.set).toHaveLength(1)
|
|
62
|
-
|
|
63
|
-
const route = mountedRoute.set[0]
|
|
64
|
-
const mockContext: Route.RouteContext = {
|
|
65
|
-
request: {} as any,
|
|
66
|
-
url: new URL("http://localhost/page"),
|
|
67
|
-
slots: {},
|
|
68
|
-
next: () => Effect.void,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const result = await Effect.runPromise(
|
|
72
|
-
route.handler(mockContext) as Effect.Effect<unknown>,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
t.expect(result).toBe("<wrap>content</wrap>")
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
t.describe("use", () => {
|
|
80
|
-
t.test("adds global layer", () => {
|
|
81
|
-
const layer = Route.layer(
|
|
82
|
-
Route.html(function*(c) {
|
|
83
|
-
const inner = yield* c.next()
|
|
84
|
-
return `<html><body>${inner}</body></html>`
|
|
85
|
-
}),
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
const router = Router.use(layer)
|
|
89
|
-
|
|
90
|
-
t.expect(router.globalLayers).toHaveLength(1)
|
|
91
|
-
t.expect(router.entries).toHaveLength(0)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
t.test("applies layer to subsequently mounted routes", () => {
|
|
95
|
-
const layer = Route.layer(
|
|
96
|
-
Route.html(function*(c) {
|
|
97
|
-
const inner = yield* c.next()
|
|
98
|
-
return `<html><body>${inner}</body></html>`
|
|
99
|
-
}),
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
const router = Router
|
|
103
|
-
.use(layer)
|
|
104
|
-
.mount("/", Route.text("Hello world!"))
|
|
105
|
-
|
|
106
|
-
t.expect(router.globalLayers).toHaveLength(1)
|
|
107
|
-
t.expect(router.entries).toHaveLength(1)
|
|
108
|
-
t.expect(router.entries[0].layers).toHaveLength(1)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
t.test("layer only applies to routes mounted after use()", async () => {
|
|
112
|
-
const layer = Route.layer(
|
|
113
|
-
Route.html(function*(c) {
|
|
114
|
-
const inner = yield* c.next()
|
|
115
|
-
return `<wrap>${inner}</wrap>`
|
|
116
|
-
}),
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
const router = Router
|
|
120
|
-
.mount("/before", Route.html(Effect.succeed("before-content")))
|
|
121
|
-
.use(layer)
|
|
122
|
-
.mount("/after", Route.html(Effect.succeed("after-content")))
|
|
123
|
-
|
|
124
|
-
const mockContext = (path: string): Route.RouteContext => ({
|
|
125
|
-
request: {} as any,
|
|
126
|
-
url: new URL(`http://localhost${path}`),
|
|
127
|
-
slots: {},
|
|
128
|
-
next: () => Effect.void,
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
const beforeRoute = router.mounts["/before"].set[0]
|
|
132
|
-
const afterRoute = router.mounts["/after"].set[0]
|
|
133
|
-
|
|
134
|
-
const beforeResult = await Effect.runPromise(
|
|
135
|
-
beforeRoute.handler(mockContext("/before")) as Effect.Effect<unknown>,
|
|
136
|
-
)
|
|
137
|
-
const afterResult = await Effect.runPromise(
|
|
138
|
-
afterRoute.handler(mockContext("/after")) as Effect.Effect<unknown>,
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
t.expect(beforeResult).toBe("before-content")
|
|
142
|
-
t.expect(afterResult).toBe("<wrap>after-content</wrap>")
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
t.describe("layer application - runtime behavior", () => {
|
|
147
|
-
t.test("layer handler wraps route handler", async () => {
|
|
148
|
-
const layer = Route.layer(
|
|
149
|
-
Route.html(function*(c) {
|
|
150
|
-
const inner = yield* c.next()
|
|
151
|
-
return `<wrap>${inner}</wrap>`
|
|
152
|
-
}),
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
const router = Router
|
|
156
|
-
.use(layer)
|
|
157
|
-
.mount("/page", Route.html(Effect.succeed("content")))
|
|
158
|
-
|
|
159
|
-
const mountedRoute = router.mounts["/page"]
|
|
160
|
-
const route = mountedRoute.set[0]
|
|
161
|
-
|
|
162
|
-
const mockContext: Route.RouteContext = {
|
|
163
|
-
request: {} as any,
|
|
164
|
-
url: new URL("http://localhost/page"),
|
|
165
|
-
slots: {},
|
|
166
|
-
next: () => Effect.succeed("unused"),
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const result = await Effect.runPromise(
|
|
170
|
-
route.handler(mockContext) as Effect.Effect<unknown>,
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
t.expect(result).toBe("<wrap>content</wrap>")
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
t.test("multiple layers are applied in order", async () => {
|
|
177
|
-
const outerLayer = Route.layer(
|
|
178
|
-
Route.html(function*(c) {
|
|
179
|
-
const inner = yield* c.next()
|
|
180
|
-
return `<outer>${inner}</outer>`
|
|
181
|
-
}),
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
const innerLayer = Route.layer(
|
|
185
|
-
Route.html(function*(c) {
|
|
186
|
-
const inner = yield* c.next()
|
|
187
|
-
return `<inner>${inner}</inner>`
|
|
188
|
-
}),
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
const router = Router
|
|
192
|
-
.use(outerLayer)
|
|
193
|
-
.use(innerLayer)
|
|
194
|
-
.mount("/page", Route.html(Effect.succeed("content")))
|
|
195
|
-
|
|
196
|
-
const mountedRoute = router.mounts["/page"]
|
|
197
|
-
const route = mountedRoute.set[0]
|
|
198
|
-
|
|
199
|
-
const mockContext: Route.RouteContext = {
|
|
200
|
-
request: {} as any,
|
|
201
|
-
url: new URL("http://localhost/page"),
|
|
202
|
-
slots: {},
|
|
203
|
-
next: () => Effect.succeed("unused"),
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const result = await Effect.runPromise(
|
|
207
|
-
route.handler(mockContext) as Effect.Effect<unknown>,
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
t.expect(result).toBe("<outer><inner>content</inner></outer>")
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
t.test("layer only applies to matching media type", async () => {
|
|
214
|
-
const htmlLayer = Route.layer(
|
|
215
|
-
Route.html(function*(c) {
|
|
216
|
-
const inner = yield* c.next()
|
|
217
|
-
return `<wrap>${inner}</wrap>`
|
|
218
|
-
}),
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
const router = Router
|
|
222
|
-
.use(htmlLayer)
|
|
223
|
-
.mount("/api", Route.json({ data: "value" }))
|
|
224
|
-
|
|
225
|
-
const mountedRoute = router.mounts["/api"]
|
|
226
|
-
const route = mountedRoute.set[0]
|
|
227
|
-
|
|
228
|
-
const mockContext: Route.RouteContext = {
|
|
229
|
-
request: {} as any,
|
|
230
|
-
url: new URL("http://localhost/api"),
|
|
231
|
-
slots: {},
|
|
232
|
-
next: () => Effect.succeed("unused"),
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const result = await Effect.runPromise(
|
|
236
|
-
route.handler(mockContext) as Effect.Effect<unknown>,
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
t.expect(result).toEqual({ data: "value" })
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
t.test("route without layer is not wrapped", async () => {
|
|
243
|
-
const router = Router.mount("/hello", Route.text("Hello"))
|
|
244
|
-
|
|
245
|
-
const mountedRoute = router.mounts["/hello"]
|
|
246
|
-
const route = mountedRoute.set[0]
|
|
247
|
-
|
|
248
|
-
const mockContext: Route.RouteContext = {
|
|
249
|
-
request: {} as any,
|
|
250
|
-
url: new URL("http://localhost/hello"),
|
|
251
|
-
slots: {},
|
|
252
|
-
next: () => Effect.succeed("unused"),
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const result = await Effect.runPromise(
|
|
256
|
-
route.handler(mockContext) as Effect.Effect<unknown>,
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
t.expect(result).toBe("Hello")
|
|
260
|
-
})
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
t.describe("type inference", () => {
|
|
264
|
-
t.test("infers never for routes without requirements", () => {
|
|
265
|
-
const router = Router.mount("/hello", Route.text("Hello"))
|
|
266
|
-
|
|
267
|
-
type RouterError = Router.Router.Error<typeof router>
|
|
268
|
-
type RouterRequirements = Router.Router.Requirements<typeof router>
|
|
269
|
-
|
|
270
|
-
const _checkError: RouterError = undefined as never
|
|
271
|
-
const _checkRequirements: RouterRequirements = undefined as never
|
|
272
|
-
|
|
273
|
-
t.expect(true).toBe(true)
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
t.test("infers error type from route handler", () => {
|
|
277
|
-
class MyError {
|
|
278
|
-
readonly _tag = "MyError"
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const router = Router.mount(
|
|
282
|
-
"/fail",
|
|
283
|
-
Route.text(Effect.fail(new MyError())),
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
type RouterError = Router.Router.Error<typeof router>
|
|
287
|
-
|
|
288
|
-
const _checkError: MyError extends RouterError ? true : false = true
|
|
289
|
-
|
|
290
|
-
t.expect(true).toBe(true)
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
t.test("infers context type from route handler", () => {
|
|
294
|
-
class MyService extends Effect.Tag("MyService")<
|
|
295
|
-
MyService,
|
|
296
|
-
{ getValue(): string }
|
|
297
|
-
>() {}
|
|
298
|
-
|
|
299
|
-
const router = Router.mount(
|
|
300
|
-
"/service",
|
|
301
|
-
Route.text(
|
|
302
|
-
Effect.gen(function*() {
|
|
303
|
-
const svc = yield* MyService
|
|
304
|
-
return svc.getValue()
|
|
305
|
-
}),
|
|
306
|
-
),
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
type RouterRequirements = Router.Router.Requirements<typeof router>
|
|
310
|
-
|
|
311
|
-
const _checkRequirements: MyService extends RouterRequirements ? true
|
|
312
|
-
: false = true
|
|
313
|
-
|
|
314
|
-
t.expect(true).toBe(true)
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
t.test("unions error types from multiple routes", () => {
|
|
318
|
-
class ErrorA {
|
|
319
|
-
readonly _tag = "ErrorA"
|
|
320
|
-
}
|
|
321
|
-
class ErrorB {
|
|
322
|
-
readonly _tag = "ErrorB"
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const router = Router
|
|
326
|
-
.mount("/a", Route.text(Effect.fail(new ErrorA())))
|
|
327
|
-
.mount("/b", Route.text(Effect.fail(new ErrorB())))
|
|
328
|
-
|
|
329
|
-
type RouterError = Router.Router.Error<typeof router>
|
|
330
|
-
|
|
331
|
-
const _checkA: ErrorA extends RouterError ? true : false = true
|
|
332
|
-
const _checkB: ErrorB extends RouterError ? true : false = true
|
|
333
|
-
|
|
334
|
-
t.expect(true).toBe(true)
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
t.test("unions context types from multiple routes", () => {
|
|
338
|
-
class ServiceA extends Effect.Tag("ServiceA")<
|
|
339
|
-
ServiceA,
|
|
340
|
-
{ getA(): string }
|
|
341
|
-
>() {}
|
|
342
|
-
class ServiceB extends Effect.Tag("ServiceB")<
|
|
343
|
-
ServiceB,
|
|
344
|
-
{ getB(): string }
|
|
345
|
-
>() {}
|
|
346
|
-
|
|
347
|
-
const router = Router
|
|
348
|
-
.mount(
|
|
349
|
-
"/a",
|
|
350
|
-
Route.text(
|
|
351
|
-
Effect.gen(function*() {
|
|
352
|
-
const svc = yield* ServiceA
|
|
353
|
-
return svc.getA()
|
|
354
|
-
}),
|
|
355
|
-
),
|
|
356
|
-
)
|
|
357
|
-
.mount(
|
|
358
|
-
"/b",
|
|
359
|
-
Route.text(
|
|
360
|
-
Effect.gen(function*() {
|
|
361
|
-
const svc = yield* ServiceB
|
|
362
|
-
return svc.getB()
|
|
363
|
-
}),
|
|
364
|
-
),
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
type RouterRequirements = Router.Router.Requirements<typeof router>
|
|
368
|
-
|
|
369
|
-
const _checkA: ServiceA extends RouterRequirements ? true : false = true
|
|
370
|
-
const _checkB: ServiceB extends RouterRequirements ? true : false = true
|
|
371
|
-
|
|
372
|
-
t.expect(true).toBe(true)
|
|
373
|
-
})
|
|
374
|
-
})
|
|
375
|
-
})
|
package/src/Router.ts
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import * as Data from "effect/Data"
|
|
2
|
-
import * as Pipeable from "effect/Pipeable"
|
|
3
|
-
import * as Predicate from "effect/Predicate"
|
|
4
|
-
import * as Route from "./Route.ts"
|
|
5
|
-
|
|
6
|
-
type RouterModule = typeof import("./Router.ts")
|
|
7
|
-
|
|
8
|
-
type Self =
|
|
9
|
-
| Router.Any
|
|
10
|
-
| RouterModule
|
|
11
|
-
| undefined
|
|
12
|
-
|
|
13
|
-
export type RouterErrorReason =
|
|
14
|
-
| "UnsupportedPattern"
|
|
15
|
-
| "ProxyError"
|
|
16
|
-
|
|
17
|
-
export class RouterError extends Data.TaggedError("RouterError")<{
|
|
18
|
-
reason: RouterErrorReason
|
|
19
|
-
pattern: string
|
|
20
|
-
message: string
|
|
21
|
-
}> {}
|
|
22
|
-
|
|
23
|
-
const TypeId: unique symbol = Symbol.for(
|
|
24
|
-
"effect-start/Router",
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
export type RouterEntry = {
|
|
28
|
-
path: `/${string}`
|
|
29
|
-
route: Route.RouteSet.Default
|
|
30
|
-
layers: Route.RouteLayer[]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type Methods = {
|
|
34
|
-
use: typeof use
|
|
35
|
-
mount: typeof mount
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface Router<
|
|
39
|
-
out E = never,
|
|
40
|
-
out R = never,
|
|
41
|
-
> extends Pipeable.Pipeable, Methods {
|
|
42
|
-
[TypeId]: typeof TypeId
|
|
43
|
-
readonly entries: readonly RouterEntry[]
|
|
44
|
-
readonly globalLayers: readonly Route.RouteLayer[]
|
|
45
|
-
readonly mounts: Record<`/${string}`, Route.RouteSet.Default>
|
|
46
|
-
readonly _E: () => E
|
|
47
|
-
readonly _R: () => R
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export namespace Router {
|
|
51
|
-
export type Any = Router<any, any>
|
|
52
|
-
export type Error<T> = T extends Router<infer E, any> ? E : never
|
|
53
|
-
export type Requirements<T> = T extends Router<any, infer R> ? R : never
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const Proto: Methods & {
|
|
57
|
-
[TypeId]: typeof TypeId
|
|
58
|
-
pipe: Pipeable.Pipeable["pipe"]
|
|
59
|
-
} = {
|
|
60
|
-
[TypeId]: TypeId,
|
|
61
|
-
|
|
62
|
-
pipe() {
|
|
63
|
-
return Pipeable.pipeArguments(this, arguments)
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
use,
|
|
67
|
-
mount,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
type ExtractRouteSetError<T> = T extends Route.RouteSet<infer Routes, any>
|
|
71
|
-
? Routes[number] extends Route.Route<any, any, infer H, any>
|
|
72
|
-
? H extends Route.RouteHandler<any, infer E, any> ? E : never
|
|
73
|
-
: never
|
|
74
|
-
: never
|
|
75
|
-
|
|
76
|
-
type ExtractRouteSetContext<T> = T extends Route.RouteSet<infer Routes, any>
|
|
77
|
-
? Routes[number] extends Route.Route<any, any, infer H, any>
|
|
78
|
-
? H extends Route.RouteHandler<any, any, infer R> ? R : never
|
|
79
|
-
: never
|
|
80
|
-
: never
|
|
81
|
-
|
|
82
|
-
function addRoute<
|
|
83
|
-
E,
|
|
84
|
-
R,
|
|
85
|
-
RouteE,
|
|
86
|
-
RouteR,
|
|
87
|
-
>(
|
|
88
|
-
builder: Router<E, R>,
|
|
89
|
-
path: `/${string}`,
|
|
90
|
-
route: Route.RouteSet.Default,
|
|
91
|
-
): Router<E | RouteE, R | RouteR> {
|
|
92
|
-
const existingEntry = builder.entries.find((e) => e.path === path)
|
|
93
|
-
if (existingEntry) {
|
|
94
|
-
const updatedEntry: RouterEntry = {
|
|
95
|
-
...existingEntry,
|
|
96
|
-
route: Route.merge(existingEntry.route, route),
|
|
97
|
-
}
|
|
98
|
-
return make(
|
|
99
|
-
builder.entries.map((e) => (e.path === path ? updatedEntry : e)),
|
|
100
|
-
builder.globalLayers,
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const newEntry: RouterEntry = {
|
|
105
|
-
path,
|
|
106
|
-
route,
|
|
107
|
-
layers: [...builder.globalLayers],
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return make([...builder.entries, newEntry], builder.globalLayers)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function addGlobalLayer<E, R>(
|
|
114
|
-
builder: Router<E, R>,
|
|
115
|
-
layerRoute: Route.RouteLayer,
|
|
116
|
-
): Router<E, R> {
|
|
117
|
-
const newGlobalLayers = [...builder.globalLayers, layerRoute]
|
|
118
|
-
return make(builder.entries, newGlobalLayers)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function findMatchingLayerRoutes(
|
|
122
|
-
route: Route.Route.Default,
|
|
123
|
-
layers: readonly Route.RouteLayer[],
|
|
124
|
-
): Route.Route.Default[] {
|
|
125
|
-
const matchingRoutes: Route.Route.Default[] = []
|
|
126
|
-
for (const layer of layers) {
|
|
127
|
-
for (const layerRoute of layer.set) {
|
|
128
|
-
if (Route.matches(layerRoute, route)) {
|
|
129
|
-
matchingRoutes.push(layerRoute)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return matchingRoutes
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function wrapWithLayerRoute(
|
|
137
|
-
innerRoute: Route.Route.Default,
|
|
138
|
-
layerRoute: Route.Route.Default,
|
|
139
|
-
): Route.Route.Default {
|
|
140
|
-
const handler: Route.RouteHandler = (context) => {
|
|
141
|
-
const contextWithNext: Route.RouteContext = {
|
|
142
|
-
...context,
|
|
143
|
-
next: () => innerRoute.handler(context),
|
|
144
|
-
}
|
|
145
|
-
return layerRoute.handler(contextWithNext)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return Route.make({
|
|
149
|
-
method: layerRoute.method,
|
|
150
|
-
media: layerRoute.media,
|
|
151
|
-
handler,
|
|
152
|
-
schemas: {},
|
|
153
|
-
})
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function applyLayersToRoute(
|
|
157
|
-
route: Route.Route.Default,
|
|
158
|
-
layers: readonly Route.RouteLayer[],
|
|
159
|
-
): Route.Route.Default {
|
|
160
|
-
const matchingLayerRoutes = findMatchingLayerRoutes(route, layers)
|
|
161
|
-
let wrappedRoute = route
|
|
162
|
-
|
|
163
|
-
for (const layerRoute of matchingLayerRoutes.reverse()) {
|
|
164
|
-
wrappedRoute = wrapWithLayerRoute(wrappedRoute, layerRoute)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return wrappedRoute
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function applyLayersToRouteSet(
|
|
171
|
-
routeSet: Route.RouteSet.Default,
|
|
172
|
-
layers: readonly Route.RouteLayer[],
|
|
173
|
-
): Route.RouteSet.Default {
|
|
174
|
-
if (layers.length === 0) {
|
|
175
|
-
return routeSet
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const wrappedRoutes = routeSet.set.map((route) =>
|
|
179
|
-
applyLayersToRoute(route, layers)
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
set: wrappedRoutes,
|
|
184
|
-
schema: routeSet.schema,
|
|
185
|
-
} as unknown as Route.RouteSet.Default
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function make<E, R>(
|
|
189
|
-
entries: readonly RouterEntry[],
|
|
190
|
-
globalLayers: readonly Route.RouteLayer[] = [],
|
|
191
|
-
): Router<E, R> {
|
|
192
|
-
const mounts: Record<`/${string}`, Route.RouteSet.Default> = {}
|
|
193
|
-
|
|
194
|
-
for (const entry of entries) {
|
|
195
|
-
if (entry.route.set.length > 0) {
|
|
196
|
-
mounts[entry.path] = applyLayersToRouteSet(entry.route, entry.layers)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return Object.assign(
|
|
201
|
-
Object.create(Proto),
|
|
202
|
-
{
|
|
203
|
-
entries,
|
|
204
|
-
globalLayers,
|
|
205
|
-
mounts,
|
|
206
|
-
},
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function isRouter(input: unknown): input is Router.Any {
|
|
211
|
-
return Predicate.hasProperty(input, TypeId)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export function use<
|
|
215
|
-
S extends Self,
|
|
216
|
-
>(
|
|
217
|
-
this: S,
|
|
218
|
-
layerRoute: Route.RouteLayer,
|
|
219
|
-
): S extends Router<infer E, infer R> ? Router<E, R>
|
|
220
|
-
: Router<never, never>
|
|
221
|
-
{
|
|
222
|
-
const router = isRouter(this)
|
|
223
|
-
? this
|
|
224
|
-
: make<never, never>([], [])
|
|
225
|
-
|
|
226
|
-
return addGlobalLayer(router, layerRoute) as any
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export function mount<
|
|
230
|
-
S extends Self,
|
|
231
|
-
Routes extends Route.Route.Tuple,
|
|
232
|
-
Schemas extends Route.RouteSchemas,
|
|
233
|
-
>(
|
|
234
|
-
this: S,
|
|
235
|
-
path: `/${string}`,
|
|
236
|
-
route: Route.RouteSet<Routes, Schemas>,
|
|
237
|
-
): S extends Router<infer E, infer R> ? Router<
|
|
238
|
-
E | ExtractRouteSetError<Route.RouteSet<Routes, Schemas>>,
|
|
239
|
-
R | ExtractRouteSetContext<Route.RouteSet<Routes, Schemas>>
|
|
240
|
-
>
|
|
241
|
-
: Router<
|
|
242
|
-
ExtractRouteSetError<Route.RouteSet<Routes, Schemas>>,
|
|
243
|
-
ExtractRouteSetContext<Route.RouteSet<Routes, Schemas>>
|
|
244
|
-
>
|
|
245
|
-
{
|
|
246
|
-
const router = isRouter(this)
|
|
247
|
-
? this
|
|
248
|
-
: make<never, never>([], [])
|
|
249
|
-
|
|
250
|
-
return addRoute(
|
|
251
|
-
router,
|
|
252
|
-
path,
|
|
253
|
-
route as Route.RouteSet.Default,
|
|
254
|
-
) as any
|
|
255
|
-
}
|