effect-start 0.9.0 → 0.11.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 +15 -14
- package/src/BundleHttp.test.ts +1 -1
- package/src/Commander.test.ts +15 -15
- package/src/Commander.ts +58 -88
- package/src/EncryptedCookies.test.ts +4 -4
- package/src/FileHttpRouter.test.ts +85 -16
- package/src/FileHttpRouter.ts +119 -32
- package/src/FileRouter.ts +62 -166
- package/src/FileRouterCodegen.test.ts +252 -66
- package/src/FileRouterCodegen.ts +13 -56
- package/src/FileRouterPattern.test.ts +116 -0
- package/src/FileRouterPattern.ts +59 -0
- package/src/FileRouter_path.test.ts +63 -102
- package/src/FileSystemExtra.test.ts +226 -0
- package/src/FileSystemExtra.ts +24 -60
- package/src/HttpAppExtra.test.ts +84 -0
- package/src/HttpAppExtra.ts +399 -47
- package/src/HttpUtils.test.ts +68 -0
- package/src/HttpUtils.ts +15 -0
- package/src/HyperHtml.ts +24 -5
- package/src/JsModule.test.ts +1 -1
- package/src/NodeFileSystem.ts +764 -0
- package/src/Random.ts +59 -0
- package/src/Route.test.ts +515 -18
- package/src/Route.ts +321 -166
- package/src/RouteRender.ts +40 -0
- package/src/Router.test.ts +416 -0
- package/src/Router.ts +288 -31
- package/src/RouterPattern.test.ts +655 -0
- package/src/RouterPattern.ts +416 -0
- package/src/Start.ts +14 -52
- package/src/TestHttpClient.test.ts +29 -0
- package/src/TestHttpClient.ts +122 -73
- package/src/assets.d.ts +39 -0
- package/src/bun/BunBundle.test.ts +0 -3
- package/src/bun/BunHttpServer.test.ts +74 -0
- package/src/bun/BunHttpServer.ts +259 -0
- package/src/bun/BunHttpServer_web.ts +384 -0
- package/src/bun/BunRoute.test.ts +514 -0
- package/src/bun/BunRoute.ts +427 -0
- package/src/bun/BunRoute_bundles.test.ts +218 -0
- package/src/bun/BunRuntime.ts +33 -0
- package/src/bun/BunTailwindPlugin.test.ts +1 -1
- package/src/bun/_empty.html +1 -0
- package/src/bun/index.ts +2 -1
- package/src/index.ts +14 -14
- package/src/middlewares/BasicAuthMiddleware.test.ts +74 -0
- package/src/middlewares/BasicAuthMiddleware.ts +36 -0
- package/src/testing.ts +12 -3
- package/src/Datastar.test.ts +0 -267
- package/src/Datastar.ts +0 -68
- package/src/bun/BunFullstackServer.ts +0 -45
- package/src/bun/BunFullstackServer_httpServer.ts +0 -541
- package/src/jsx-datastar.d.ts +0 -63
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
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.RouterBuilder.Error<typeof router>
|
|
268
|
+
type RouterContext = Router.RouterBuilder.Context<typeof router>
|
|
269
|
+
|
|
270
|
+
const _checkError: RouterError = undefined as never
|
|
271
|
+
const _checkContext: RouterContext = 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.RouterBuilder.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 RouterContext = Router.RouterBuilder.Context<typeof router>
|
|
310
|
+
|
|
311
|
+
const _checkContext: MyService extends RouterContext ? true : false = true
|
|
312
|
+
|
|
313
|
+
t.expect(true).toBe(true)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
t.test("unions error types from multiple routes", () => {
|
|
317
|
+
class ErrorA {
|
|
318
|
+
readonly _tag = "ErrorA"
|
|
319
|
+
}
|
|
320
|
+
class ErrorB {
|
|
321
|
+
readonly _tag = "ErrorB"
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const router = Router
|
|
325
|
+
.mount("/a", Route.text(Effect.fail(new ErrorA())))
|
|
326
|
+
.mount("/b", Route.text(Effect.fail(new ErrorB())))
|
|
327
|
+
|
|
328
|
+
type RouterError = Router.RouterBuilder.Error<typeof router>
|
|
329
|
+
|
|
330
|
+
const _checkA: ErrorA extends RouterError ? true : false = true
|
|
331
|
+
const _checkB: ErrorB extends RouterError ? true : false = true
|
|
332
|
+
|
|
333
|
+
t.expect(true).toBe(true)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
t.test("unions context types from multiple routes", () => {
|
|
337
|
+
class ServiceA extends Effect.Tag("ServiceA")<
|
|
338
|
+
ServiceA,
|
|
339
|
+
{ getA(): string }
|
|
340
|
+
>() {}
|
|
341
|
+
class ServiceB extends Effect.Tag("ServiceB")<
|
|
342
|
+
ServiceB,
|
|
343
|
+
{ getB(): string }
|
|
344
|
+
>() {}
|
|
345
|
+
|
|
346
|
+
const router = Router
|
|
347
|
+
.mount(
|
|
348
|
+
"/a",
|
|
349
|
+
Route.text(
|
|
350
|
+
Effect.gen(function*() {
|
|
351
|
+
const svc = yield* ServiceA
|
|
352
|
+
return svc.getA()
|
|
353
|
+
}),
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
.mount(
|
|
357
|
+
"/b",
|
|
358
|
+
Route.text(
|
|
359
|
+
Effect.gen(function*() {
|
|
360
|
+
const svc = yield* ServiceB
|
|
361
|
+
return svc.getB()
|
|
362
|
+
}),
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
type RouterContext = Router.RouterBuilder.Context<typeof router>
|
|
367
|
+
|
|
368
|
+
const _checkA: ServiceA extends RouterContext ? true : false = true
|
|
369
|
+
const _checkB: ServiceB extends RouterContext ? true : false = true
|
|
370
|
+
|
|
371
|
+
t.expect(true).toBe(true)
|
|
372
|
+
})
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
t.describe("fromManifest", () => {
|
|
376
|
+
t.test("loads routes from manifest", async () => {
|
|
377
|
+
const manifest: Router.RouterManifest = {
|
|
378
|
+
routes: [
|
|
379
|
+
{
|
|
380
|
+
path: "/test",
|
|
381
|
+
load: () => Promise.resolve({ default: Route.text("Test") }),
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const router = await Effect.runPromise(Router.fromManifest(manifest))
|
|
387
|
+
|
|
388
|
+
t.expect(router.entries).toHaveLength(1)
|
|
389
|
+
t.expect(router.entries[0].path).toBe("/test")
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
t.test("loads layers from manifest", async () => {
|
|
393
|
+
const layer = Route.layer(
|
|
394
|
+
Route.html(function*(c) {
|
|
395
|
+
const inner = yield* c.next()
|
|
396
|
+
return `<wrap>${inner}</wrap>`
|
|
397
|
+
}),
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
const manifest: Router.RouterManifest = {
|
|
401
|
+
routes: [
|
|
402
|
+
{
|
|
403
|
+
path: "/test",
|
|
404
|
+
load: () => Promise.resolve({ default: Route.text("Test") }),
|
|
405
|
+
layers: [() => Promise.resolve({ default: layer })],
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const router = await Effect.runPromise(Router.fromManifest(manifest))
|
|
411
|
+
|
|
412
|
+
t.expect(router.entries).toHaveLength(1)
|
|
413
|
+
t.expect(router.entries[0].layers).toHaveLength(1)
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
})
|