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/Route.test.ts
DELETED
|
@@ -1,1370 +0,0 @@
|
|
|
1
|
-
import * as HttpApp from "@effect/platform/HttpApp"
|
|
2
|
-
import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
|
|
3
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
4
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
5
|
-
import * as t from "bun:test"
|
|
6
|
-
|
|
7
|
-
import * as Effect from "effect/Effect"
|
|
8
|
-
import * as Function from "effect/Function"
|
|
9
|
-
import * as Schema from "effect/Schema"
|
|
10
|
-
import * as Route from "./Route.ts"
|
|
11
|
-
|
|
12
|
-
t.it("types default routes", () => {
|
|
13
|
-
const implicit = Route
|
|
14
|
-
.text(
|
|
15
|
-
Effect.succeed("hello"),
|
|
16
|
-
)
|
|
17
|
-
.html(
|
|
18
|
-
Effect.succeed(""),
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
const explicit = Route
|
|
22
|
-
.get(
|
|
23
|
-
Route
|
|
24
|
-
.text(
|
|
25
|
-
Effect.succeed("hello"),
|
|
26
|
-
)
|
|
27
|
-
.html(
|
|
28
|
-
Effect.succeed(""),
|
|
29
|
-
),
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
type Expected = Route.RouteSet<[
|
|
33
|
-
Route.Route<"GET", "text/plain">,
|
|
34
|
-
Route.Route<"GET", "text/html">,
|
|
35
|
-
]>
|
|
36
|
-
|
|
37
|
-
Function.satisfies<Expected>()(implicit)
|
|
38
|
-
Function.satisfies<Expected>()(explicit)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
t.it("types GET & POST routes", () => {
|
|
42
|
-
const implicit = Route
|
|
43
|
-
.text(
|
|
44
|
-
Effect.succeed("hello"),
|
|
45
|
-
)
|
|
46
|
-
.html(
|
|
47
|
-
Effect.succeed(""),
|
|
48
|
-
)
|
|
49
|
-
.post(
|
|
50
|
-
Route.json(
|
|
51
|
-
Effect.succeed({
|
|
52
|
-
message: "created",
|
|
53
|
-
}),
|
|
54
|
-
),
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
const explicit = Route
|
|
58
|
-
.get(
|
|
59
|
-
Route
|
|
60
|
-
.text(
|
|
61
|
-
Effect.succeed("hello"),
|
|
62
|
-
)
|
|
63
|
-
.html(
|
|
64
|
-
Effect.succeed(""),
|
|
65
|
-
),
|
|
66
|
-
)
|
|
67
|
-
.post(
|
|
68
|
-
Route.json(
|
|
69
|
-
Effect.succeed({
|
|
70
|
-
message: "created",
|
|
71
|
-
}),
|
|
72
|
-
),
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
type Expected = Route.RouteSet<[
|
|
76
|
-
Route.Route<"GET", "text/plain">,
|
|
77
|
-
Route.Route<"GET", "text/html">,
|
|
78
|
-
Route.Route<"POST", "application/json">,
|
|
79
|
-
]>
|
|
80
|
-
|
|
81
|
-
Function.satisfies<Expected>()(implicit)
|
|
82
|
-
Function.satisfies<Expected>()(explicit)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
t.it("schemaPathParams adds schema to RouteSet", () => {
|
|
86
|
-
const IdSchema = Schema.Struct({
|
|
87
|
-
id: Schema.String,
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const routes = Route
|
|
91
|
-
.schemaPathParams(IdSchema)
|
|
92
|
-
.text(
|
|
93
|
-
Effect.succeed("hello"),
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
type ExpectedSchemas = {
|
|
97
|
-
readonly PathParams: typeof IdSchema
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
type Expected = Route.RouteSet<
|
|
101
|
-
[Route.Route<"GET", "text/plain", any, ExpectedSchemas>],
|
|
102
|
-
ExpectedSchemas
|
|
103
|
-
>
|
|
104
|
-
|
|
105
|
-
Function.satisfies<Expected>()(routes)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
t.it("schemaPathParams accepts struct fields directly", () => {
|
|
109
|
-
const routes = Route
|
|
110
|
-
.schemaPathParams({
|
|
111
|
-
id: Schema.String,
|
|
112
|
-
})
|
|
113
|
-
.text(
|
|
114
|
-
Effect.succeed("hello"),
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
type ExpectedSchemas = {
|
|
118
|
-
readonly PathParams: Schema.Struct<{
|
|
119
|
-
id: typeof Schema.String
|
|
120
|
-
}>
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
type Expected = Route.RouteSet<
|
|
124
|
-
[Route.Route<"GET", "text/plain", any, ExpectedSchemas>],
|
|
125
|
-
ExpectedSchemas
|
|
126
|
-
>
|
|
127
|
-
|
|
128
|
-
Function.satisfies<Expected>()(routes)
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
t.it("schemaPathParams with struct fields types context correctly", () => {
|
|
132
|
-
Route
|
|
133
|
-
.schemaPathParams({
|
|
134
|
-
id: Schema.String,
|
|
135
|
-
})
|
|
136
|
-
.text(
|
|
137
|
-
(context) => {
|
|
138
|
-
Function.satisfies<string>()(context.pathParams.id)
|
|
139
|
-
|
|
140
|
-
return Effect.succeed("hello")
|
|
141
|
-
},
|
|
142
|
-
)
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
t.it("schemaPayload propagates to all routes", () => {
|
|
146
|
-
const PayloadSchema = Schema.Struct({
|
|
147
|
-
name: Schema.String,
|
|
148
|
-
age: Schema.Number,
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
const routes = Route
|
|
152
|
-
.schemaPayload(PayloadSchema)
|
|
153
|
-
.get(
|
|
154
|
-
Route.text(
|
|
155
|
-
Effect.succeed("get"),
|
|
156
|
-
),
|
|
157
|
-
)
|
|
158
|
-
.post(
|
|
159
|
-
Route.text(
|
|
160
|
-
Effect.succeed("post"),
|
|
161
|
-
),
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
type ExpectedSchemas = {
|
|
165
|
-
readonly Payload: typeof PayloadSchema
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
type Expected = Route.RouteSet<
|
|
169
|
-
[
|
|
170
|
-
Route.Route<"GET", "text/plain", any, ExpectedSchemas>,
|
|
171
|
-
Route.Route<"POST", "text/plain", any, ExpectedSchemas>,
|
|
172
|
-
],
|
|
173
|
-
ExpectedSchemas
|
|
174
|
-
>
|
|
175
|
-
|
|
176
|
-
Function.satisfies<Expected>()(routes)
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
t.it(
|
|
180
|
-
"context is typed with pathParams when schemaPathParams is provided",
|
|
181
|
-
() => {
|
|
182
|
-
const IdSchema = Schema.Struct({
|
|
183
|
-
id: Schema.String,
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
Route
|
|
187
|
-
.schemaPathParams(IdSchema)
|
|
188
|
-
.text(
|
|
189
|
-
(context) => {
|
|
190
|
-
type ContextType = typeof context
|
|
191
|
-
|
|
192
|
-
type Expected = Route.RouteContext<{
|
|
193
|
-
pathParams: {
|
|
194
|
-
id: string
|
|
195
|
-
}
|
|
196
|
-
}>
|
|
197
|
-
|
|
198
|
-
Function.satisfies<Expected>()(context)
|
|
199
|
-
|
|
200
|
-
Function.satisfies<string>()(context.pathParams.id)
|
|
201
|
-
|
|
202
|
-
return Effect.succeed("hello")
|
|
203
|
-
},
|
|
204
|
-
)
|
|
205
|
-
},
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
t.it("context is typed with urlParams when schemaUrlParams is provided", () => {
|
|
209
|
-
const QuerySchema = Schema.Struct({
|
|
210
|
-
page: Schema.NumberFromString,
|
|
211
|
-
limit: Schema.NumberFromString,
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
Route
|
|
215
|
-
.schemaUrlParams(QuerySchema)
|
|
216
|
-
.text(
|
|
217
|
-
(context) => {
|
|
218
|
-
type Expected = Route.RouteContext<{
|
|
219
|
-
urlParams: {
|
|
220
|
-
page: number
|
|
221
|
-
limit: number
|
|
222
|
-
}
|
|
223
|
-
}>
|
|
224
|
-
|
|
225
|
-
Function.satisfies<Expected>()(context)
|
|
226
|
-
|
|
227
|
-
Function.satisfies<number>()(context.urlParams.page)
|
|
228
|
-
|
|
229
|
-
Function.satisfies<number>()(context.urlParams.limit)
|
|
230
|
-
|
|
231
|
-
return Effect.succeed("hello")
|
|
232
|
-
},
|
|
233
|
-
)
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
t.it("context is typed with payload when schemaPayload is provided", () => {
|
|
237
|
-
const PayloadSchema = Schema.Struct({
|
|
238
|
-
name: Schema.String,
|
|
239
|
-
age: Schema.Number,
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
Route
|
|
243
|
-
.schemaPayload(PayloadSchema)
|
|
244
|
-
.text(
|
|
245
|
-
(context) => {
|
|
246
|
-
type Expected = Route.RouteContext<{
|
|
247
|
-
payload: {
|
|
248
|
-
name: string
|
|
249
|
-
age: number
|
|
250
|
-
}
|
|
251
|
-
}>
|
|
252
|
-
|
|
253
|
-
Function.satisfies<Expected>()(context)
|
|
254
|
-
|
|
255
|
-
Function.satisfies<string>()(context.payload.name)
|
|
256
|
-
|
|
257
|
-
Function.satisfies<number>()(context.payload.age)
|
|
258
|
-
|
|
259
|
-
return Effect.succeed("hello")
|
|
260
|
-
},
|
|
261
|
-
)
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
t.it("context is typed with headers when schemaHeaders is provided", () => {
|
|
265
|
-
const HeadersSchema = Schema.Struct({
|
|
266
|
-
authorization: Schema.String,
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
Route
|
|
270
|
-
.schemaHeaders(HeadersSchema)
|
|
271
|
-
.text(
|
|
272
|
-
(context) => {
|
|
273
|
-
type Expected = Route.RouteContext<{
|
|
274
|
-
headers: {
|
|
275
|
-
authorization: string
|
|
276
|
-
}
|
|
277
|
-
}>
|
|
278
|
-
|
|
279
|
-
Function.satisfies<Expected>()(context)
|
|
280
|
-
|
|
281
|
-
Function.satisfies<string>()(context.headers.authorization)
|
|
282
|
-
|
|
283
|
-
return Effect.succeed("hello")
|
|
284
|
-
},
|
|
285
|
-
)
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
t.it("context is typed with multiple schemas", () => {
|
|
289
|
-
const IdSchema = Schema.Struct({
|
|
290
|
-
id: Schema.String,
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
const QuerySchema = Schema.Struct({
|
|
294
|
-
page: Schema.NumberFromString,
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
const PayloadSchema = Schema.Struct({
|
|
298
|
-
name: Schema.String,
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
Route
|
|
302
|
-
.schemaPathParams(IdSchema)
|
|
303
|
-
.schemaUrlParams(QuerySchema)
|
|
304
|
-
.schemaPayload(PayloadSchema)
|
|
305
|
-
.text(
|
|
306
|
-
(context) => {
|
|
307
|
-
type Expected = Route.RouteContext<{
|
|
308
|
-
pathParams: {
|
|
309
|
-
id: string
|
|
310
|
-
}
|
|
311
|
-
urlParams: {
|
|
312
|
-
page: number
|
|
313
|
-
}
|
|
314
|
-
payload: {
|
|
315
|
-
name: string
|
|
316
|
-
}
|
|
317
|
-
}>
|
|
318
|
-
|
|
319
|
-
Function.satisfies<Expected>()(context)
|
|
320
|
-
|
|
321
|
-
Function.satisfies<string>()(context.pathParams.id)
|
|
322
|
-
|
|
323
|
-
Function.satisfies<number>()(context.urlParams.page)
|
|
324
|
-
|
|
325
|
-
Function.satisfies<string>()(context.payload.name)
|
|
326
|
-
|
|
327
|
-
return Effect.succeed("hello")
|
|
328
|
-
},
|
|
329
|
-
)
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
t.it("schemaSuccess and schemaError are stored in RouteSet", () => {
|
|
333
|
-
const SuccessSchema = Schema.Struct({
|
|
334
|
-
ok: Schema.Boolean,
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
const ErrorSchema = Schema.Struct({
|
|
338
|
-
error: Schema.String,
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
const routes = Route
|
|
342
|
-
.schemaSuccess(SuccessSchema)
|
|
343
|
-
.schemaError(ErrorSchema)
|
|
344
|
-
.text(
|
|
345
|
-
Effect.succeed("hello"),
|
|
346
|
-
)
|
|
347
|
-
|
|
348
|
-
type ExpectedSchemas = {
|
|
349
|
-
readonly Success: typeof SuccessSchema
|
|
350
|
-
readonly Error: typeof ErrorSchema
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
type Expected = Route.RouteSet<
|
|
354
|
-
[Route.Route<"GET", "text/plain", any, ExpectedSchemas>],
|
|
355
|
-
ExpectedSchemas
|
|
356
|
-
>
|
|
357
|
-
|
|
358
|
-
Function.satisfies<Expected>()(routes)
|
|
359
|
-
})
|
|
360
|
-
|
|
361
|
-
t.it("all schema methods work together", () => {
|
|
362
|
-
const PathSchema = Schema.Struct({
|
|
363
|
-
id: Schema.String,
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
const QuerySchema = Schema.Struct({
|
|
367
|
-
page: Schema.NumberFromString,
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
const PayloadSchema = Schema.Struct({
|
|
371
|
-
name: Schema.String,
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
const SuccessSchema = Schema.Struct({
|
|
375
|
-
ok: Schema.Boolean,
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
const ErrorSchema = Schema.Struct({
|
|
379
|
-
error: Schema.String,
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
const HeadersSchema = Schema.Struct({
|
|
383
|
-
authorization: Schema.String,
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
const routes = Route
|
|
387
|
-
.schemaPathParams(PathSchema)
|
|
388
|
-
.schemaUrlParams(QuerySchema)
|
|
389
|
-
.schemaPayload(PayloadSchema)
|
|
390
|
-
.schemaSuccess(SuccessSchema)
|
|
391
|
-
.schemaError(ErrorSchema)
|
|
392
|
-
.schemaHeaders(HeadersSchema)
|
|
393
|
-
.text(
|
|
394
|
-
Effect.succeed("hello"),
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
type ExpectedSchemas = {
|
|
398
|
-
readonly PathParams: typeof PathSchema
|
|
399
|
-
readonly UrlParams: typeof QuerySchema
|
|
400
|
-
readonly Payload: typeof PayloadSchema
|
|
401
|
-
readonly Success: typeof SuccessSchema
|
|
402
|
-
readonly Error: typeof ErrorSchema
|
|
403
|
-
readonly Headers: typeof HeadersSchema
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
type Expected = Route.RouteSet<
|
|
407
|
-
[Route.Route<"GET", "text/plain", any, ExpectedSchemas>],
|
|
408
|
-
ExpectedSchemas
|
|
409
|
-
>
|
|
410
|
-
|
|
411
|
-
Function.satisfies<Expected>()(routes)
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
t.it("schemas merge when RouteSet and Route both define same schema", () => {
|
|
415
|
-
const BaseSchema = Schema.Struct({
|
|
416
|
-
id: Schema.String,
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
const ExtendedSchema = Schema.Struct({
|
|
420
|
-
name: Schema.String,
|
|
421
|
-
})
|
|
422
|
-
|
|
423
|
-
const routes = Route
|
|
424
|
-
.schemaPathParams(BaseSchema)
|
|
425
|
-
.get(
|
|
426
|
-
Route
|
|
427
|
-
.schemaPathParams(ExtendedSchema)
|
|
428
|
-
.text(Effect.succeed("hello")),
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
type Expected = Route.RouteSet<
|
|
432
|
-
[
|
|
433
|
-
Route.Route<
|
|
434
|
-
"GET",
|
|
435
|
-
"text/plain",
|
|
436
|
-
any,
|
|
437
|
-
{
|
|
438
|
-
readonly PathParams: Schema.Struct<
|
|
439
|
-
{
|
|
440
|
-
id: typeof Schema.String
|
|
441
|
-
name: typeof Schema.String
|
|
442
|
-
}
|
|
443
|
-
>
|
|
444
|
-
}
|
|
445
|
-
>,
|
|
446
|
-
],
|
|
447
|
-
{
|
|
448
|
-
readonly PathParams: typeof BaseSchema
|
|
449
|
-
}
|
|
450
|
-
>
|
|
451
|
-
|
|
452
|
-
Function.satisfies<Expected>()(routes)
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
t.it("context has only request and url when no schemas provided", () => {
|
|
456
|
-
Route
|
|
457
|
-
.text(
|
|
458
|
-
(context) => {
|
|
459
|
-
type Expected = Route.RouteContext<{}>
|
|
460
|
-
|
|
461
|
-
Function.satisfies<Expected>()(context)
|
|
462
|
-
|
|
463
|
-
Function.satisfies<typeof context.request>()(context.request)
|
|
464
|
-
|
|
465
|
-
Function.satisfies<URL>()(context.url)
|
|
466
|
-
|
|
467
|
-
// @ts-expect-error - pathParams should not exist
|
|
468
|
-
context.pathParams
|
|
469
|
-
|
|
470
|
-
return Effect.succeed("hello")
|
|
471
|
-
},
|
|
472
|
-
)
|
|
473
|
-
})
|
|
474
|
-
|
|
475
|
-
t.it("context.next() returns correct type for text handler", () => {
|
|
476
|
-
Route.text(function*(context) {
|
|
477
|
-
const next = context.next()
|
|
478
|
-
type NextType = Effect.Effect.Success<typeof next>
|
|
479
|
-
type _check = [NextType] extends [string] ? true : false
|
|
480
|
-
const _assert: _check = true
|
|
481
|
-
return "hello"
|
|
482
|
-
})
|
|
483
|
-
})
|
|
484
|
-
|
|
485
|
-
t.it("context.next() returns correct type for html handler", () => {
|
|
486
|
-
Route.html(function*(context) {
|
|
487
|
-
const next = context.next()
|
|
488
|
-
type NextType = Effect.Effect.Success<typeof next>
|
|
489
|
-
type _check = [NextType] extends [string | Route.GenericJsxObject] ? true
|
|
490
|
-
: false
|
|
491
|
-
const _assert: _check = true
|
|
492
|
-
return "<div>hello</div>"
|
|
493
|
-
})
|
|
494
|
-
})
|
|
495
|
-
|
|
496
|
-
t.it("context.next() returns correct type for json handler", () => {
|
|
497
|
-
Route.json(function*(context) {
|
|
498
|
-
const next = context.next()
|
|
499
|
-
type NextType = Effect.Effect.Success<typeof next>
|
|
500
|
-
type _check = [NextType] extends [Route.JsonValue] ? true : false
|
|
501
|
-
const _assert: _check = true
|
|
502
|
-
return { message: "hello" }
|
|
503
|
-
})
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
t.it("schemas work with all media types", () => {
|
|
507
|
-
const PathSchema = Schema.Struct({
|
|
508
|
-
id: Schema.String,
|
|
509
|
-
})
|
|
510
|
-
|
|
511
|
-
Route
|
|
512
|
-
.schemaPathParams(PathSchema)
|
|
513
|
-
.html((context) => {
|
|
514
|
-
Function.satisfies<string>()(context.pathParams.id)
|
|
515
|
-
|
|
516
|
-
return Effect.succeed("<h1>Hello</h1>")
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
Route
|
|
520
|
-
.schemaPathParams(PathSchema)
|
|
521
|
-
.json((context) => {
|
|
522
|
-
Function.satisfies<string>()(context.pathParams.id)
|
|
523
|
-
|
|
524
|
-
return Effect.succeed({ message: "hello" })
|
|
525
|
-
})
|
|
526
|
-
})
|
|
527
|
-
|
|
528
|
-
t.it("schemas work with generator functions", () => {
|
|
529
|
-
const IdSchema = Schema.Struct({
|
|
530
|
-
id: Schema.String,
|
|
531
|
-
})
|
|
532
|
-
|
|
533
|
-
Route
|
|
534
|
-
.schemaPathParams(IdSchema)
|
|
535
|
-
.text(function*(context) {
|
|
536
|
-
Function.satisfies<string>()(context.pathParams.id)
|
|
537
|
-
|
|
538
|
-
return "hello"
|
|
539
|
-
})
|
|
540
|
-
})
|
|
541
|
-
|
|
542
|
-
t.it("schema property is correctly set on RouteSet", () => {
|
|
543
|
-
const PathSchema = Schema.Struct({
|
|
544
|
-
id: Schema.String,
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
const routes = Route
|
|
548
|
-
.schemaPathParams(PathSchema)
|
|
549
|
-
.text(Effect.succeed("hello"))
|
|
550
|
-
|
|
551
|
-
type Expected = {
|
|
552
|
-
readonly PathParams: typeof PathSchema
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
Function.satisfies<Expected>()(routes.schema)
|
|
556
|
-
})
|
|
557
|
-
|
|
558
|
-
t.it("schemas don't leak between independent route chains", () => {
|
|
559
|
-
const Schema1 = Schema.Struct({
|
|
560
|
-
id: Schema.String,
|
|
561
|
-
})
|
|
562
|
-
|
|
563
|
-
const Schema2 = Schema.Struct({
|
|
564
|
-
userId: Schema.String,
|
|
565
|
-
})
|
|
566
|
-
|
|
567
|
-
const route1 = Route
|
|
568
|
-
.schemaPathParams(Schema1)
|
|
569
|
-
.text(Effect.succeed("route1"))
|
|
570
|
-
|
|
571
|
-
const route2 = Route
|
|
572
|
-
.schemaPathParams(Schema2)
|
|
573
|
-
.text(Effect.succeed("route2"))
|
|
574
|
-
|
|
575
|
-
type Expected1 = Route.RouteSet<
|
|
576
|
-
[
|
|
577
|
-
Route.Route<
|
|
578
|
-
"GET",
|
|
579
|
-
"text/plain",
|
|
580
|
-
any,
|
|
581
|
-
{ readonly PathParams: typeof Schema1 }
|
|
582
|
-
>,
|
|
583
|
-
],
|
|
584
|
-
{ readonly PathParams: typeof Schema1 }
|
|
585
|
-
>
|
|
586
|
-
|
|
587
|
-
type Expected2 = Route.RouteSet<
|
|
588
|
-
[
|
|
589
|
-
Route.Route<
|
|
590
|
-
"GET",
|
|
591
|
-
"text/plain",
|
|
592
|
-
any,
|
|
593
|
-
{ readonly PathParams: typeof Schema2 }
|
|
594
|
-
>,
|
|
595
|
-
],
|
|
596
|
-
{ readonly PathParams: typeof Schema2 }
|
|
597
|
-
>
|
|
598
|
-
|
|
599
|
-
Function.satisfies<Expected1>()(route1)
|
|
600
|
-
Function.satisfies<Expected2>()(route2)
|
|
601
|
-
})
|
|
602
|
-
|
|
603
|
-
t.it("schema order doesn't matter", () => {
|
|
604
|
-
const PathSchema = Schema.Struct({
|
|
605
|
-
id: Schema.String,
|
|
606
|
-
})
|
|
607
|
-
|
|
608
|
-
const PayloadSchema = Schema.Struct({
|
|
609
|
-
name: Schema.String,
|
|
610
|
-
})
|
|
611
|
-
|
|
612
|
-
const routes1 = Route
|
|
613
|
-
.schemaPathParams(PathSchema)
|
|
614
|
-
.schemaPayload(PayloadSchema)
|
|
615
|
-
.text(Effect.succeed("hello"))
|
|
616
|
-
|
|
617
|
-
const routes2 = Route
|
|
618
|
-
.schemaPayload(PayloadSchema)
|
|
619
|
-
.schemaPathParams(PathSchema)
|
|
620
|
-
.text(Effect.succeed("hello"))
|
|
621
|
-
|
|
622
|
-
type Expected = {
|
|
623
|
-
readonly PathParams: typeof PathSchema
|
|
624
|
-
readonly Payload: typeof PayloadSchema
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
Function.satisfies<Expected>()(routes1.schema)
|
|
628
|
-
Function.satisfies<Expected>()(routes2.schema)
|
|
629
|
-
})
|
|
630
|
-
|
|
631
|
-
t.it("multiple routes in RouteSet each get the schema", () => {
|
|
632
|
-
const PathSchema = Schema.Struct({
|
|
633
|
-
id: Schema.String,
|
|
634
|
-
})
|
|
635
|
-
|
|
636
|
-
const routes = Route
|
|
637
|
-
.schemaPathParams(PathSchema)
|
|
638
|
-
.text(Effect.succeed("text"))
|
|
639
|
-
.html(Effect.succeed("<p>html</p>"))
|
|
640
|
-
.json(Effect.succeed({ data: "json" }))
|
|
641
|
-
|
|
642
|
-
type ExpectedSchemas = {
|
|
643
|
-
readonly PathParams: typeof PathSchema
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
type Expected = Route.RouteSet<
|
|
647
|
-
[
|
|
648
|
-
Route.Route<"GET", "text/plain", any, ExpectedSchemas>,
|
|
649
|
-
Route.Route<"GET", "text/html", any, ExpectedSchemas>,
|
|
650
|
-
Route.Route<"GET", "application/json", any, ExpectedSchemas>,
|
|
651
|
-
],
|
|
652
|
-
ExpectedSchemas
|
|
653
|
-
>
|
|
654
|
-
|
|
655
|
-
Function.satisfies<Expected>()(routes)
|
|
656
|
-
})
|
|
657
|
-
|
|
658
|
-
t.it("schemas merge correctly with struct fields syntax", () => {
|
|
659
|
-
const routes = Route
|
|
660
|
-
.schemaPathParams({ id: Schema.String })
|
|
661
|
-
.get(
|
|
662
|
-
Route
|
|
663
|
-
.schemaPathParams({ userId: Schema.String })
|
|
664
|
-
.text(Effect.succeed("hello")),
|
|
665
|
-
)
|
|
666
|
-
|
|
667
|
-
routes
|
|
668
|
-
.set[0]
|
|
669
|
-
.text(
|
|
670
|
-
(context) => {
|
|
671
|
-
Function.satisfies<string>()(context.pathParams.id)
|
|
672
|
-
Function.satisfies<string>()(context.pathParams.userId)
|
|
673
|
-
|
|
674
|
-
return Effect.succeed("hello")
|
|
675
|
-
},
|
|
676
|
-
)
|
|
677
|
-
})
|
|
678
|
-
|
|
679
|
-
t.it("method modifiers preserve and merge schemas", () => {
|
|
680
|
-
const PathSchema = Schema.Struct({
|
|
681
|
-
id: Schema.String,
|
|
682
|
-
})
|
|
683
|
-
|
|
684
|
-
const PayloadSchema = Schema.Struct({
|
|
685
|
-
name: Schema.String,
|
|
686
|
-
})
|
|
687
|
-
|
|
688
|
-
const routes = Route
|
|
689
|
-
.schemaPathParams(PathSchema)
|
|
690
|
-
.post(
|
|
691
|
-
Route
|
|
692
|
-
.schemaPayload(PayloadSchema)
|
|
693
|
-
.text(Effect.succeed("created")),
|
|
694
|
-
)
|
|
695
|
-
|
|
696
|
-
type Expected = Route.RouteSet<
|
|
697
|
-
[
|
|
698
|
-
Route.Route<
|
|
699
|
-
"POST",
|
|
700
|
-
"text/plain",
|
|
701
|
-
any,
|
|
702
|
-
{
|
|
703
|
-
readonly PathParams: typeof PathSchema
|
|
704
|
-
readonly Payload: typeof PayloadSchema
|
|
705
|
-
}
|
|
706
|
-
>,
|
|
707
|
-
],
|
|
708
|
-
{
|
|
709
|
-
readonly PathParams: typeof PathSchema
|
|
710
|
-
}
|
|
711
|
-
>
|
|
712
|
-
|
|
713
|
-
Function.satisfies<Expected>()(routes)
|
|
714
|
-
})
|
|
715
|
-
|
|
716
|
-
t.it("method modifiers require routes with handlers", () => {
|
|
717
|
-
const PathSchema = Schema.Struct({
|
|
718
|
-
id: Schema.String,
|
|
719
|
-
})
|
|
720
|
-
|
|
721
|
-
Route
|
|
722
|
-
.schemaPathParams(PathSchema)
|
|
723
|
-
.get(
|
|
724
|
-
Route
|
|
725
|
-
.schemaPathParams({ userId: Schema.String })
|
|
726
|
-
.text(Effect.succeed("hello")),
|
|
727
|
-
)
|
|
728
|
-
|
|
729
|
-
Route
|
|
730
|
-
.schemaPathParams(PathSchema)
|
|
731
|
-
.get(
|
|
732
|
-
// @ts-expect-error - method modifiers should reject empty RouteSet
|
|
733
|
-
Route.schemaPathParams({ userId: Schema.String }),
|
|
734
|
-
)
|
|
735
|
-
})
|
|
736
|
-
|
|
737
|
-
t.it("method modifiers preserve proper types when nesting schemas", () => {
|
|
738
|
-
const PathSchema = Schema.Struct({
|
|
739
|
-
id: Schema.String,
|
|
740
|
-
})
|
|
741
|
-
|
|
742
|
-
const route = Route
|
|
743
|
-
.schemaPathParams(PathSchema)
|
|
744
|
-
.get(
|
|
745
|
-
Route
|
|
746
|
-
.schemaPathParams({ userId: Schema.String })
|
|
747
|
-
.text(Effect.succeed("hello")),
|
|
748
|
-
)
|
|
749
|
-
|
|
750
|
-
type BaseSchemas = {
|
|
751
|
-
readonly PathParams: typeof PathSchema
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
type MergedPathParams = Schema.Struct<{
|
|
755
|
-
id: typeof Schema.String
|
|
756
|
-
userId: typeof Schema.String
|
|
757
|
-
}>
|
|
758
|
-
|
|
759
|
-
type Expected = Route.RouteSet<
|
|
760
|
-
[
|
|
761
|
-
Route.Route<"GET", "text/plain", any, {
|
|
762
|
-
readonly PathParams: MergedPathParams
|
|
763
|
-
}>,
|
|
764
|
-
],
|
|
765
|
-
BaseSchemas
|
|
766
|
-
>
|
|
767
|
-
|
|
768
|
-
Function.satisfies<Expected>()(route)
|
|
769
|
-
})
|
|
770
|
-
|
|
771
|
-
t.it("schemaUrlParams accepts optional fields", () => {
|
|
772
|
-
const routes = Route
|
|
773
|
-
.schemaUrlParams({
|
|
774
|
-
hello: Function.pipe(
|
|
775
|
-
Schema.String,
|
|
776
|
-
Schema.optional,
|
|
777
|
-
),
|
|
778
|
-
})
|
|
779
|
-
.html(
|
|
780
|
-
(ctx) => {
|
|
781
|
-
Function.satisfies<string | undefined>()(ctx.urlParams.hello)
|
|
782
|
-
|
|
783
|
-
const page = ctx.urlParams.hello ?? "default"
|
|
784
|
-
|
|
785
|
-
return Effect.succeed(`<div><h1>About ${page}</h1></div>`)
|
|
786
|
-
},
|
|
787
|
-
)
|
|
788
|
-
|
|
789
|
-
type ExpectedSchemas = {
|
|
790
|
-
readonly UrlParams: Schema.Struct<{
|
|
791
|
-
hello: Schema.optional<typeof Schema.String>
|
|
792
|
-
}>
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
type Expected = Route.RouteSet<
|
|
796
|
-
[Route.Route<"GET", "text/html", any, ExpectedSchemas>],
|
|
797
|
-
ExpectedSchemas
|
|
798
|
-
>
|
|
799
|
-
|
|
800
|
-
Function.satisfies<Expected>()(routes)
|
|
801
|
-
})
|
|
802
|
-
|
|
803
|
-
t.it("schemaPathParams only accepts string-encoded schemas", () => {
|
|
804
|
-
Route
|
|
805
|
-
.schemaPathParams({
|
|
806
|
-
id: Schema.String,
|
|
807
|
-
})
|
|
808
|
-
.text(Effect.succeed("ok"))
|
|
809
|
-
|
|
810
|
-
Route
|
|
811
|
-
.schemaPathParams({
|
|
812
|
-
id: Schema.NumberFromString,
|
|
813
|
-
})
|
|
814
|
-
.text(Effect.succeed("ok"))
|
|
815
|
-
|
|
816
|
-
Route
|
|
817
|
-
.schemaPathParams({
|
|
818
|
-
// @ts-expect-error - Schema.Number is not string-encoded
|
|
819
|
-
id: Schema.Number,
|
|
820
|
-
})
|
|
821
|
-
.text(Effect.succeed("ok"))
|
|
822
|
-
|
|
823
|
-
Route
|
|
824
|
-
.schemaPathParams({
|
|
825
|
-
// @ts-expect-error - Schema.Struct is not string-encoded
|
|
826
|
-
nested: Schema.Struct({
|
|
827
|
-
field: Schema.String,
|
|
828
|
-
}),
|
|
829
|
-
})
|
|
830
|
-
.text(Effect.succeed("ok"))
|
|
831
|
-
})
|
|
832
|
-
|
|
833
|
-
t.it("schemaUrlParams accepts string and string array encoded schemas", () => {
|
|
834
|
-
Route
|
|
835
|
-
.schemaUrlParams({
|
|
836
|
-
page: Schema.String,
|
|
837
|
-
})
|
|
838
|
-
.text(Effect.succeed("ok"))
|
|
839
|
-
|
|
840
|
-
Route
|
|
841
|
-
.schemaUrlParams({
|
|
842
|
-
page: Schema.NumberFromString,
|
|
843
|
-
})
|
|
844
|
-
.text(Effect.succeed("ok"))
|
|
845
|
-
|
|
846
|
-
Route
|
|
847
|
-
.schemaUrlParams({
|
|
848
|
-
tags: Schema.Array(Schema.String),
|
|
849
|
-
})
|
|
850
|
-
.text(Effect.succeed("ok"))
|
|
851
|
-
|
|
852
|
-
Route
|
|
853
|
-
.schemaUrlParams({
|
|
854
|
-
// @ts-expect-error - Schema.Number is not string-encoded
|
|
855
|
-
page: Schema.Number,
|
|
856
|
-
})
|
|
857
|
-
.text(Effect.succeed("ok"))
|
|
858
|
-
|
|
859
|
-
Route
|
|
860
|
-
.schemaUrlParams({
|
|
861
|
-
// @ts-expect-error - Schema.Struct is not string-encoded
|
|
862
|
-
nested: Schema.Struct({
|
|
863
|
-
field: Schema.String,
|
|
864
|
-
}),
|
|
865
|
-
})
|
|
866
|
-
.text(Effect.succeed("ok"))
|
|
867
|
-
})
|
|
868
|
-
|
|
869
|
-
t.it("schemaHeaders accepts string and string array encoded schemas", () => {
|
|
870
|
-
Route
|
|
871
|
-
.schemaHeaders({
|
|
872
|
-
authorization: Schema.String,
|
|
873
|
-
})
|
|
874
|
-
.text(Effect.succeed("ok"))
|
|
875
|
-
|
|
876
|
-
Route
|
|
877
|
-
.schemaHeaders({
|
|
878
|
-
"x-custom-header": Schema.NumberFromString,
|
|
879
|
-
})
|
|
880
|
-
.text(Effect.succeed("ok"))
|
|
881
|
-
|
|
882
|
-
Route
|
|
883
|
-
.schemaHeaders({
|
|
884
|
-
"accept-encoding": Schema.Array(Schema.String),
|
|
885
|
-
})
|
|
886
|
-
.text(Effect.succeed("ok"))
|
|
887
|
-
|
|
888
|
-
Route
|
|
889
|
-
.schemaHeaders({
|
|
890
|
-
// @ts-expect-error - Schema.Number is not string-encoded
|
|
891
|
-
"x-count": Schema.Number,
|
|
892
|
-
})
|
|
893
|
-
.text(Effect.succeed("ok"))
|
|
894
|
-
|
|
895
|
-
Route
|
|
896
|
-
.schemaHeaders({
|
|
897
|
-
// @ts-expect-error - Schema.Struct is not string-encoded
|
|
898
|
-
"x-metadata": Schema.Struct({
|
|
899
|
-
field: Schema.String,
|
|
900
|
-
}),
|
|
901
|
-
})
|
|
902
|
-
.text(Effect.succeed("ok"))
|
|
903
|
-
})
|
|
904
|
-
|
|
905
|
-
t.it("Route.http creates RouteMiddleware", () => {
|
|
906
|
-
const middleware = (app: any) => app
|
|
907
|
-
|
|
908
|
-
const spec = Route.http(middleware)
|
|
909
|
-
|
|
910
|
-
t.expect(spec._tag).toBe("RouteMiddleware")
|
|
911
|
-
t.expect(spec.middleware).toBe(middleware)
|
|
912
|
-
})
|
|
913
|
-
|
|
914
|
-
t.it("Route.layer creates RouteLayer with middleware", () => {
|
|
915
|
-
const middleware = (app: any) => app
|
|
916
|
-
|
|
917
|
-
const layer = Route.layer(
|
|
918
|
-
Route.http(middleware),
|
|
919
|
-
Route.html(Effect.succeed("<div>test</div>")),
|
|
920
|
-
)
|
|
921
|
-
|
|
922
|
-
t.expect(Route.isRouteLayer(layer)).toBe(true)
|
|
923
|
-
t.expect(layer.httpMiddleware).toBe(middleware)
|
|
924
|
-
t.expect(layer.set.length).toBe(1)
|
|
925
|
-
})
|
|
926
|
-
|
|
927
|
-
t.it("Route.layer merges multiple route sets", () => {
|
|
928
|
-
const routes1 = Route.html(Effect.succeed("<div>1</div>"))
|
|
929
|
-
const routes2 = Route.text("text")
|
|
930
|
-
|
|
931
|
-
const layer = Route.layer(routes1, routes2)
|
|
932
|
-
|
|
933
|
-
t.expect(layer.set.length).toBe(2)
|
|
934
|
-
t.expect(Route.isRouteLayer(layer)).toBe(true)
|
|
935
|
-
})
|
|
936
|
-
|
|
937
|
-
t.it("Route.layer merges routes from all route sets", () => {
|
|
938
|
-
const routes1 = Route
|
|
939
|
-
.schemaPathParams({ id: Schema.String })
|
|
940
|
-
.html(Effect.succeed("<div>test</div>"))
|
|
941
|
-
|
|
942
|
-
const routes2 = Route
|
|
943
|
-
.schemaUrlParams({ page: Schema.NumberFromString })
|
|
944
|
-
.text(Effect.succeed("text"))
|
|
945
|
-
|
|
946
|
-
const layer = Route.layer(routes1, routes2)
|
|
947
|
-
|
|
948
|
-
t.expect(layer.set.length).toBe(2)
|
|
949
|
-
t.expect(layer.set[0]!.media).toBe("text/html")
|
|
950
|
-
t.expect(layer.set[1]!.media).toBe("text/plain")
|
|
951
|
-
})
|
|
952
|
-
|
|
953
|
-
t.it("Route.layer works with no middleware", () => {
|
|
954
|
-
const layer = Route.layer(
|
|
955
|
-
Route.html(Effect.succeed("<div>test</div>")),
|
|
956
|
-
)
|
|
957
|
-
|
|
958
|
-
t.expect(Route.isRouteLayer(layer)).toBe(true)
|
|
959
|
-
t.expect(layer.httpMiddleware).toBeUndefined()
|
|
960
|
-
t.expect(layer.set.length).toBe(1)
|
|
961
|
-
})
|
|
962
|
-
|
|
963
|
-
t.it("Route.layer works with no routes", () => {
|
|
964
|
-
const middleware = (app: any) => app
|
|
965
|
-
|
|
966
|
-
const layer = Route.layer(
|
|
967
|
-
Route.http(middleware),
|
|
968
|
-
)
|
|
969
|
-
|
|
970
|
-
t.expect(Route.isRouteLayer(layer)).toBe(true)
|
|
971
|
-
t.expect(layer.httpMiddleware).toBe(middleware)
|
|
972
|
-
t.expect(layer.set.length).toBe(0)
|
|
973
|
-
})
|
|
974
|
-
|
|
975
|
-
t.it("isRouteLayer type guard works correctly", () => {
|
|
976
|
-
const middleware = (app: any) => app
|
|
977
|
-
const layer = Route.layer(Route.http(middleware))
|
|
978
|
-
const regularRoutes = Route.html(Effect.succeed("<div>test</div>"))
|
|
979
|
-
|
|
980
|
-
t.expect(Route.isRouteLayer(layer)).toBe(true)
|
|
981
|
-
t.expect(Route.isRouteLayer(regularRoutes)).toBe(false)
|
|
982
|
-
t.expect(Route.isRouteLayer(null)).toBe(false)
|
|
983
|
-
t.expect(Route.isRouteLayer(undefined)).toBe(false)
|
|
984
|
-
t.expect(Route.isRouteLayer({})).toBe(false)
|
|
985
|
-
})
|
|
986
|
-
|
|
987
|
-
t.it("Route.layer composes multiple middleware in order", async () => {
|
|
988
|
-
const executionOrder: string[] = []
|
|
989
|
-
|
|
990
|
-
const middleware1 = HttpMiddleware.make((app) =>
|
|
991
|
-
Effect.gen(function*() {
|
|
992
|
-
executionOrder.push("middleware1-before")
|
|
993
|
-
const result = yield* app
|
|
994
|
-
executionOrder.push("middleware1-after")
|
|
995
|
-
return result
|
|
996
|
-
})
|
|
997
|
-
) as Route.HttpMiddlewareFunction
|
|
998
|
-
|
|
999
|
-
const middleware2 = HttpMiddleware.make((app) =>
|
|
1000
|
-
Effect.gen(function*() {
|
|
1001
|
-
executionOrder.push("middleware2-before")
|
|
1002
|
-
const result = yield* app
|
|
1003
|
-
executionOrder.push("middleware2-after")
|
|
1004
|
-
return result
|
|
1005
|
-
})
|
|
1006
|
-
) as Route.HttpMiddlewareFunction
|
|
1007
|
-
|
|
1008
|
-
const middleware3 = HttpMiddleware.make((app) =>
|
|
1009
|
-
Effect.gen(function*() {
|
|
1010
|
-
executionOrder.push("middleware3-before")
|
|
1011
|
-
const result = yield* app
|
|
1012
|
-
executionOrder.push("middleware3-after")
|
|
1013
|
-
return result
|
|
1014
|
-
})
|
|
1015
|
-
) as Route.HttpMiddlewareFunction
|
|
1016
|
-
|
|
1017
|
-
const layer = Route.layer(
|
|
1018
|
-
Route.http(middleware1),
|
|
1019
|
-
Route.http(middleware2),
|
|
1020
|
-
Route.http(middleware3),
|
|
1021
|
-
)
|
|
1022
|
-
|
|
1023
|
-
t.expect(layer.httpMiddleware).toBeDefined()
|
|
1024
|
-
|
|
1025
|
-
const mockApp = Effect.sync(() => {
|
|
1026
|
-
executionOrder.push("app")
|
|
1027
|
-
return HttpServerResponse.text("result")
|
|
1028
|
-
})
|
|
1029
|
-
|
|
1030
|
-
const composed = layer.httpMiddleware!(mockApp) as Effect.Effect<
|
|
1031
|
-
HttpServerResponse.HttpServerResponse,
|
|
1032
|
-
never,
|
|
1033
|
-
never
|
|
1034
|
-
>
|
|
1035
|
-
await Effect.runPromise(composed.pipe(Effect.orDie))
|
|
1036
|
-
|
|
1037
|
-
t.expect(executionOrder).toEqual([
|
|
1038
|
-
"middleware1-before",
|
|
1039
|
-
"middleware2-before",
|
|
1040
|
-
"middleware3-before",
|
|
1041
|
-
"app",
|
|
1042
|
-
"middleware3-after",
|
|
1043
|
-
"middleware2-after",
|
|
1044
|
-
"middleware1-after",
|
|
1045
|
-
])
|
|
1046
|
-
})
|
|
1047
|
-
|
|
1048
|
-
t.it("Route.layer with single middleware works correctly", async () => {
|
|
1049
|
-
let middlewareCalled = false
|
|
1050
|
-
|
|
1051
|
-
const middleware = HttpMiddleware.make((app) =>
|
|
1052
|
-
Effect.gen(function*() {
|
|
1053
|
-
middlewareCalled = true
|
|
1054
|
-
return yield* app
|
|
1055
|
-
})
|
|
1056
|
-
) as Route.HttpMiddlewareFunction
|
|
1057
|
-
|
|
1058
|
-
const layer = Route.layer(Route.http(middleware))
|
|
1059
|
-
|
|
1060
|
-
t.expect(layer.httpMiddleware).toBeDefined()
|
|
1061
|
-
|
|
1062
|
-
const mockApp = Effect.succeed(HttpServerResponse.text("result"))
|
|
1063
|
-
const composed = layer.httpMiddleware!(mockApp) as Effect.Effect<
|
|
1064
|
-
HttpServerResponse.HttpServerResponse,
|
|
1065
|
-
never,
|
|
1066
|
-
never
|
|
1067
|
-
>
|
|
1068
|
-
await Effect.runPromise(composed.pipe(Effect.orDie))
|
|
1069
|
-
|
|
1070
|
-
t.expect(middlewareCalled).toBe(true)
|
|
1071
|
-
})
|
|
1072
|
-
|
|
1073
|
-
t.it("Route.layer middleware can modify responses", async () => {
|
|
1074
|
-
const addHeader1 = HttpMiddleware.make((app) =>
|
|
1075
|
-
Effect.gen(function*() {
|
|
1076
|
-
const result = yield* app
|
|
1077
|
-
return HttpServerResponse.setHeader(result, "X-Custom-1", "value1")
|
|
1078
|
-
})
|
|
1079
|
-
) as Route.HttpMiddlewareFunction
|
|
1080
|
-
|
|
1081
|
-
const addHeader2 = HttpMiddleware.make((app) =>
|
|
1082
|
-
Effect.gen(function*() {
|
|
1083
|
-
const result = yield* app
|
|
1084
|
-
return HttpServerResponse.setHeader(result, "X-Custom-2", "value2")
|
|
1085
|
-
})
|
|
1086
|
-
) as Route.HttpMiddlewareFunction
|
|
1087
|
-
|
|
1088
|
-
const layer = Route.layer(
|
|
1089
|
-
Route.http(addHeader1),
|
|
1090
|
-
Route.http(addHeader2),
|
|
1091
|
-
)
|
|
1092
|
-
|
|
1093
|
-
const mockApp = Effect.succeed(HttpServerResponse.text("data"))
|
|
1094
|
-
const composed = layer.httpMiddleware!(mockApp) as Effect.Effect<
|
|
1095
|
-
HttpServerResponse.HttpServerResponse,
|
|
1096
|
-
never,
|
|
1097
|
-
never
|
|
1098
|
-
>
|
|
1099
|
-
const result = await Effect.runPromise(composed.pipe(Effect.orDie))
|
|
1100
|
-
|
|
1101
|
-
t.expect(result.headers["x-custom-1"]).toBe("value1")
|
|
1102
|
-
t.expect(result.headers["x-custom-2"]).toBe("value2")
|
|
1103
|
-
})
|
|
1104
|
-
|
|
1105
|
-
t.it("Route.matches returns true for exact method and media match", () => {
|
|
1106
|
-
const route1 = Route.get(Route.html(Effect.succeed("<div>test</div>")))
|
|
1107
|
-
const route2 = Route.get(Route.html(Effect.succeed("<div>other</div>")))
|
|
1108
|
-
|
|
1109
|
-
t.expect(Route.matches(route1.set[0]!, route2.set[0]!)).toBe(true)
|
|
1110
|
-
})
|
|
1111
|
-
|
|
1112
|
-
t.it("Route.matches returns false for different methods", () => {
|
|
1113
|
-
const route1 = Route.get(Route.html(Effect.succeed("<div>test</div>")))
|
|
1114
|
-
const route2 = Route.post(Route.html(Effect.succeed("<div>other</div>")))
|
|
1115
|
-
|
|
1116
|
-
t.expect(Route.matches(route1.set[0]!, route2.set[0]!)).toBe(false)
|
|
1117
|
-
})
|
|
1118
|
-
|
|
1119
|
-
t.it("Route.matches returns false for different media types", () => {
|
|
1120
|
-
const route1 = Route.get(Route.html(Effect.succeed("<div>test</div>")))
|
|
1121
|
-
const route2 = Route.get(Route.json({ data: "test" }))
|
|
1122
|
-
|
|
1123
|
-
t.expect(Route.matches(route1.set[0]!, route2.set[0]!)).toBe(false)
|
|
1124
|
-
})
|
|
1125
|
-
|
|
1126
|
-
t.it("Route.matches returns true when method is wildcard", () => {
|
|
1127
|
-
const route1 = Route.html(Effect.succeed("<div>test</div>"))
|
|
1128
|
-
const route2 = Route.get(Route.html(Effect.succeed("<div>other</div>")))
|
|
1129
|
-
|
|
1130
|
-
t.expect(Route.matches(route1.set[0]!, route2.set[0]!)).toBe(true)
|
|
1131
|
-
t.expect(Route.matches(route2.set[0]!, route1.set[0]!)).toBe(true)
|
|
1132
|
-
})
|
|
1133
|
-
|
|
1134
|
-
t.it("Route.matches returns true when one route has wildcard method", () => {
|
|
1135
|
-
const wildcardRoute = Route.html(Effect.succeed("<div>test</div>"))
|
|
1136
|
-
const specificRoute = Route.get(
|
|
1137
|
-
Route.html(Effect.succeed("<div>other</div>")),
|
|
1138
|
-
)
|
|
1139
|
-
|
|
1140
|
-
t.expect(Route.matches(wildcardRoute.set[0]!, specificRoute.set[0]!)).toBe(
|
|
1141
|
-
true,
|
|
1142
|
-
)
|
|
1143
|
-
})
|
|
1144
|
-
|
|
1145
|
-
t.describe("Route.merge", () => {
|
|
1146
|
-
t.it("types merged routes with union of methods", () => {
|
|
1147
|
-
const textRoute = Route.text("hello")
|
|
1148
|
-
|
|
1149
|
-
const htmlRoute = Route.html(Effect.succeed("<div>world</div>"))
|
|
1150
|
-
|
|
1151
|
-
const merged = Route.merge(textRoute, htmlRoute)
|
|
1152
|
-
|
|
1153
|
-
type Expected = Route.RouteSet<
|
|
1154
|
-
[
|
|
1155
|
-
Route.Route<
|
|
1156
|
-
"GET",
|
|
1157
|
-
"text/plain" | "text/html",
|
|
1158
|
-
Route.RouteHandler<HttpServerResponse.HttpServerResponse>
|
|
1159
|
-
>,
|
|
1160
|
-
]
|
|
1161
|
-
>
|
|
1162
|
-
|
|
1163
|
-
Function.satisfies<Expected>()(merged)
|
|
1164
|
-
})
|
|
1165
|
-
|
|
1166
|
-
t.it("types merged routes with different methods", () => {
|
|
1167
|
-
const getRoute = Route.get(Route.text("get"))
|
|
1168
|
-
const postRoute = Route.post(Route.json({ ok: true }))
|
|
1169
|
-
|
|
1170
|
-
const merged = Route.merge(getRoute, postRoute)
|
|
1171
|
-
|
|
1172
|
-
type Expected = Route.RouteSet<
|
|
1173
|
-
[
|
|
1174
|
-
Route.Route<
|
|
1175
|
-
"GET" | "POST",
|
|
1176
|
-
"text/plain" | "application/json",
|
|
1177
|
-
Route.RouteHandler<HttpServerResponse.HttpServerResponse>
|
|
1178
|
-
>,
|
|
1179
|
-
]
|
|
1180
|
-
>
|
|
1181
|
-
|
|
1182
|
-
Function.satisfies<Expected>()(merged)
|
|
1183
|
-
})
|
|
1184
|
-
|
|
1185
|
-
t.it("types merged schemas using MergeSchemas", () => {
|
|
1186
|
-
const routeA = Route
|
|
1187
|
-
.schemaPathParams({ id: Schema.NumberFromString })
|
|
1188
|
-
.text(Effect.succeed("a"))
|
|
1189
|
-
|
|
1190
|
-
const routeB = Route
|
|
1191
|
-
.schemaUrlParams({ page: Schema.NumberFromString })
|
|
1192
|
-
.html(Effect.succeed("<div>b</div>"))
|
|
1193
|
-
|
|
1194
|
-
const merged = Route.merge(routeA, routeB)
|
|
1195
|
-
|
|
1196
|
-
type MergedSchemas = typeof merged.schema
|
|
1197
|
-
|
|
1198
|
-
type ExpectedPathParams = {
|
|
1199
|
-
readonly id: typeof Schema.NumberFromString
|
|
1200
|
-
}
|
|
1201
|
-
type ExpectedUrlParams = {
|
|
1202
|
-
readonly page: typeof Schema.NumberFromString
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
type CheckPathParams = MergedSchemas["PathParams"] extends
|
|
1206
|
-
Schema.Struct<ExpectedPathParams> ? true : false
|
|
1207
|
-
type CheckUrlParams = MergedSchemas["UrlParams"] extends
|
|
1208
|
-
Schema.Struct<ExpectedUrlParams> ? true : false
|
|
1209
|
-
|
|
1210
|
-
const _pathParamsCheck: CheckPathParams = true
|
|
1211
|
-
const _urlParamsCheck: CheckUrlParams = true
|
|
1212
|
-
})
|
|
1213
|
-
|
|
1214
|
-
t.it("merged route does content negotiation for text/plain", async () => {
|
|
1215
|
-
const textRoute = Route.text("plain text")
|
|
1216
|
-
const htmlRoute = Route.html("<div>html</div>")
|
|
1217
|
-
|
|
1218
|
-
const merged = Route.merge(textRoute, htmlRoute)
|
|
1219
|
-
const route = merged.set[0]!
|
|
1220
|
-
|
|
1221
|
-
const request = HttpServerRequest.fromWeb(
|
|
1222
|
-
new Request("http://localhost/test", {
|
|
1223
|
-
headers: { Accept: "text/plain" },
|
|
1224
|
-
}),
|
|
1225
|
-
)
|
|
1226
|
-
|
|
1227
|
-
const context: Route.RouteContext = {
|
|
1228
|
-
request,
|
|
1229
|
-
get url() {
|
|
1230
|
-
return new URL(request.url)
|
|
1231
|
-
},
|
|
1232
|
-
slots: {},
|
|
1233
|
-
next: () => Effect.void,
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
const result = await Effect.runPromise(route.handler(context))
|
|
1237
|
-
|
|
1238
|
-
const webResponse = HttpServerResponse.toWeb(result)
|
|
1239
|
-
const text = await webResponse.text()
|
|
1240
|
-
|
|
1241
|
-
t.expect(text).toBe("plain text")
|
|
1242
|
-
t.expect(result.headers["content-type"]).toBe("text/plain")
|
|
1243
|
-
})
|
|
1244
|
-
|
|
1245
|
-
t.it("merged route does content negotiation for text/html", async () => {
|
|
1246
|
-
const textRoute = Route.text("plain text")
|
|
1247
|
-
const htmlRoute = Route.html("<div>html</div>")
|
|
1248
|
-
|
|
1249
|
-
const merged = Route.merge(textRoute, htmlRoute)
|
|
1250
|
-
const route = merged.set[0]!
|
|
1251
|
-
|
|
1252
|
-
const request = HttpServerRequest.fromWeb(
|
|
1253
|
-
new Request("http://localhost/test", {
|
|
1254
|
-
headers: { Accept: "text/html" },
|
|
1255
|
-
}),
|
|
1256
|
-
)
|
|
1257
|
-
|
|
1258
|
-
const context: Route.RouteContext = {
|
|
1259
|
-
request,
|
|
1260
|
-
get url() {
|
|
1261
|
-
return new URL(request.url)
|
|
1262
|
-
},
|
|
1263
|
-
slots: {},
|
|
1264
|
-
next: () => Effect.void,
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
const result = await Effect.runPromise(route.handler(context))
|
|
1268
|
-
|
|
1269
|
-
const webResponse = HttpServerResponse.toWeb(result)
|
|
1270
|
-
const text = await webResponse.text()
|
|
1271
|
-
|
|
1272
|
-
t.expect(text).toBe("<div>html</div>")
|
|
1273
|
-
t.expect(result.headers["content-type"]).toContain("text/html")
|
|
1274
|
-
})
|
|
1275
|
-
|
|
1276
|
-
t.it(
|
|
1277
|
-
"merged route does content negotiation for application/json",
|
|
1278
|
-
async () => {
|
|
1279
|
-
const textRoute = Route.text("plain text")
|
|
1280
|
-
const jsonRoute = Route.json({ message: "json" })
|
|
1281
|
-
|
|
1282
|
-
const merged = Route.merge(textRoute, jsonRoute)
|
|
1283
|
-
const route = merged.set[0]!
|
|
1284
|
-
|
|
1285
|
-
const request = HttpServerRequest.fromWeb(
|
|
1286
|
-
new Request("http://localhost/test", {
|
|
1287
|
-
headers: { Accept: "application/json" },
|
|
1288
|
-
}),
|
|
1289
|
-
)
|
|
1290
|
-
|
|
1291
|
-
const context: Route.RouteContext = {
|
|
1292
|
-
request,
|
|
1293
|
-
get url() {
|
|
1294
|
-
return new URL(request.url)
|
|
1295
|
-
},
|
|
1296
|
-
slots: {},
|
|
1297
|
-
next: () => Effect.void,
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
const result = await Effect.runPromise(route.handler(context))
|
|
1301
|
-
|
|
1302
|
-
const webResponse = HttpServerResponse.toWeb(result)
|
|
1303
|
-
const text = await webResponse.text()
|
|
1304
|
-
|
|
1305
|
-
t.expect(text).toBe("{\"message\":\"json\"}")
|
|
1306
|
-
t.expect(result.headers["content-type"]).toContain("application/json")
|
|
1307
|
-
},
|
|
1308
|
-
)
|
|
1309
|
-
|
|
1310
|
-
t.it("merged route defaults to html for */* accept", async () => {
|
|
1311
|
-
const textRoute = Route.text("plain text")
|
|
1312
|
-
const htmlRoute = Route.html("<div>html</div>")
|
|
1313
|
-
|
|
1314
|
-
const merged = Route.merge(textRoute, htmlRoute)
|
|
1315
|
-
const route = merged.set[0]!
|
|
1316
|
-
|
|
1317
|
-
const request = HttpServerRequest.fromWeb(
|
|
1318
|
-
new Request("http://localhost/test", {
|
|
1319
|
-
headers: { Accept: "*/*" },
|
|
1320
|
-
}),
|
|
1321
|
-
)
|
|
1322
|
-
|
|
1323
|
-
const context: Route.RouteContext = {
|
|
1324
|
-
request,
|
|
1325
|
-
get url() {
|
|
1326
|
-
return new URL(request.url)
|
|
1327
|
-
},
|
|
1328
|
-
slots: {},
|
|
1329
|
-
next: () => Effect.void,
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
const result = await Effect.runPromise(route.handler(context))
|
|
1333
|
-
|
|
1334
|
-
const webResponse = HttpServerResponse.toWeb(result)
|
|
1335
|
-
const text = await webResponse.text()
|
|
1336
|
-
|
|
1337
|
-
t.expect(text).toBe("<div>html</div>")
|
|
1338
|
-
})
|
|
1339
|
-
|
|
1340
|
-
t.it(
|
|
1341
|
-
"merged route defaults to first route when no Accept header",
|
|
1342
|
-
async () => {
|
|
1343
|
-
const textRoute = Route.text("plain text")
|
|
1344
|
-
const htmlRoute = Route.html("<div>html</div>")
|
|
1345
|
-
|
|
1346
|
-
const merged = Route.merge(textRoute, htmlRoute)
|
|
1347
|
-
const route = merged.set[0]!
|
|
1348
|
-
|
|
1349
|
-
const request = HttpServerRequest.fromWeb(
|
|
1350
|
-
new Request("http://localhost/test"),
|
|
1351
|
-
)
|
|
1352
|
-
|
|
1353
|
-
const context: Route.RouteContext = {
|
|
1354
|
-
request,
|
|
1355
|
-
get url() {
|
|
1356
|
-
return new URL(request.url)
|
|
1357
|
-
},
|
|
1358
|
-
slots: {},
|
|
1359
|
-
next: () => Effect.void,
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
const result = await Effect.runPromise(route.handler(context))
|
|
1363
|
-
|
|
1364
|
-
const webResponse = HttpServerResponse.toWeb(result)
|
|
1365
|
-
const text = await webResponse.text()
|
|
1366
|
-
|
|
1367
|
-
t.expect(text).toBe("<div>html</div>")
|
|
1368
|
-
},
|
|
1369
|
-
)
|
|
1370
|
-
})
|