effect-start 0.14.0 → 0.15.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 +500 -0
- package/src/ContentNegotiation.ts +535 -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 +24 -0
- package/src/Http.ts +25 -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 +483 -0
- package/src/Route.ts +258 -1073
- package/src/RouteBody.test.ts +182 -0
- package/src/RouteBody.ts +106 -0
- package/src/RouteHook.test.ts +40 -0
- package/src/RouteHook.ts +105 -0
- package/src/RouteHttp.test.ts +443 -0
- package/src/RouteHttp.ts +219 -0
- package/src/RouteMount.test.ts +468 -0
- package/src/RouteMount.ts +313 -0
- package/src/RouteSchema.test.ts +81 -0
- package/src/RouteSchema.ts +44 -0
- package/src/RouteTree.test.ts +346 -0
- package/src/RouteTree.ts +165 -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/TuplePathPattern.ts +64 -0
- package/src/Values.ts +16 -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 +56 -32
- package/src/bun/BunHttpServer_web.ts +18 -6
- package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
- package/src/bun/BunRoute.ts +29 -210
- 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 +2 -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 -11
- 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.test.ts +0 -480
- 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.ts
CHANGED
|
@@ -1,1146 +1,331 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
4
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
5
|
-
import * as Effect from "effect/Effect"
|
|
1
|
+
import * as Context from "effect/Context"
|
|
2
|
+
import type * as Effect from "effect/Effect"
|
|
3
|
+
import * as Layer from "effect/Layer"
|
|
6
4
|
import * as Pipeable from "effect/Pipeable"
|
|
7
5
|
import * as Predicate from "effect/Predicate"
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
10
|
-
import * as
|
|
6
|
+
import * as RouteBody from "./RouteBody.ts"
|
|
7
|
+
import * as RouteTree from "./RouteTree.ts"
|
|
8
|
+
import * as Values from "./Values.ts"
|
|
11
9
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type RouteModule = typeof import("./Route.ts")
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 'this' argument type for {@link RouteBuilder} functions.
|
|
20
|
-
* Its value depend on how the function is called as described below.
|
|
21
|
-
*/
|
|
22
|
-
type Self =
|
|
23
|
-
/**
|
|
24
|
-
* Called as {@link RouteSet} method:
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```ts
|
|
28
|
-
* let route: Route
|
|
29
|
-
*
|
|
30
|
-
* route.text("Hello")
|
|
31
|
-
*
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
| RouteSet.Default
|
|
35
|
-
| RouteSet<Route.Empty, RouteSchemas>
|
|
36
|
-
/**
|
|
37
|
-
* Called from namespaced import.
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```ts
|
|
41
|
-
* import * as Route from "./Route.ts"
|
|
42
|
-
*
|
|
43
|
-
* let route: Route
|
|
44
|
-
*
|
|
45
|
-
* Route.text("Hello")
|
|
46
|
-
*
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
| RouteModule
|
|
50
|
-
/**
|
|
51
|
-
* Called directly from exported function. Don't do it.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```ts
|
|
55
|
-
* import { text } from "./Route.ts"
|
|
56
|
-
*
|
|
57
|
-
* text("Hello")
|
|
58
|
-
*
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
|
-
| undefined
|
|
62
|
-
|
|
63
|
-
const TypeId: unique symbol = Symbol.for("effect-start/Route")
|
|
64
|
-
const RouteSetTypeId: unique symbol = Symbol.for("effect-start/RouteSet")
|
|
65
|
-
const RouteLayerTypeId: unique symbol = Symbol.for("effect-start/RouteLayer")
|
|
66
|
-
|
|
67
|
-
export type RouteMethod =
|
|
68
|
-
| "*"
|
|
69
|
-
| HttpMethod.HttpMethod
|
|
70
|
-
|
|
71
|
-
// TODO: This should be a RouterPattern and moved to its file?
|
|
72
|
-
export type RoutePattern = `/${string}`
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Route media type used for content negotiation.
|
|
76
|
-
* This allows to create routes that serve different media types
|
|
77
|
-
* for the same path & method, depending on the `Accept` header
|
|
78
|
-
* of the request.
|
|
79
|
-
*/
|
|
80
|
-
export type RouteMedia =
|
|
81
|
-
| "*"
|
|
82
|
-
| "text/plain"
|
|
83
|
-
| "text/html"
|
|
84
|
-
| "application/json"
|
|
10
|
+
export const RouteItems: unique symbol = Symbol()
|
|
11
|
+
export const RouteDescriptor: unique symbol = Symbol()
|
|
12
|
+
// only for structural type matching
|
|
13
|
+
export const RouteBindings: unique symbol = Symbol()
|
|
85
14
|
|
|
86
|
-
|
|
87
|
-
* A handler function that produces a raw value.
|
|
88
|
-
* The value will be rendered to an HttpServerResponse by RouteRender
|
|
89
|
-
* based on the route's media type.
|
|
90
|
-
*
|
|
91
|
-
* Receives RouteContext which includes an optional next() for layers.
|
|
92
|
-
*/
|
|
93
|
-
export type RouteHandler<
|
|
94
|
-
A = unknown,
|
|
95
|
-
E = any,
|
|
96
|
-
R = any,
|
|
97
|
-
> = (context: RouteContext) => Effect.Effect<A, E, R>
|
|
15
|
+
export const TypeId: unique symbol = Symbol.for("effect-start/RouteSet")
|
|
98
16
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
export type OneOrMany<T> = T | T[] | readonly T[]
|
|
103
|
-
|
|
104
|
-
export type RouteSchemas = {
|
|
105
|
-
readonly PathParams?: Schema.Struct<any>
|
|
106
|
-
readonly UrlParams?: Schema.Struct<any>
|
|
107
|
-
readonly Payload?: Schema.Schema.Any
|
|
108
|
-
readonly Success?: Schema.Schema.Any
|
|
109
|
-
readonly Error?: Schema.Schema.Any
|
|
110
|
-
readonly Headers?: Schema.Struct<any>
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export namespace RouteSchemas {
|
|
114
|
-
export type Empty = {
|
|
115
|
-
readonly PathParams?: never
|
|
116
|
-
readonly UrlParams?: never
|
|
117
|
-
readonly Payload?: never
|
|
118
|
-
readonly Success?: never
|
|
119
|
-
readonly Error?: never
|
|
120
|
-
readonly Headers?: never
|
|
17
|
+
export namespace RouteDescriptor {
|
|
18
|
+
export type Any = {
|
|
19
|
+
[key: string]: unknown
|
|
121
20
|
}
|
|
122
21
|
}
|
|
123
22
|
|
|
124
|
-
export
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
23
|
+
export namespace RouteSet {
|
|
24
|
+
export type RouteSet<
|
|
25
|
+
D extends RouteDescriptor.Any = {},
|
|
26
|
+
B = {},
|
|
27
|
+
M extends Route.Tuple = [],
|
|
28
|
+
> =
|
|
29
|
+
& Data<D, B, M>
|
|
30
|
+
& {
|
|
31
|
+
[TypeId]: typeof TypeId
|
|
32
|
+
}
|
|
33
|
+
& Pipeable.Pipeable
|
|
34
|
+
& Iterable<M[number]>
|
|
136
35
|
|
|
137
|
-
/**
|
|
138
|
-
* Describes a single route that varies by method & media type.
|
|
139
|
-
*
|
|
140
|
-
* Implements {@link RouteSet} interface that contains itself.
|
|
141
|
-
*/
|
|
142
|
-
export namespace Route {
|
|
143
36
|
export type Data<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
Schemas extends RouteSchemas = RouteSchemas.Empty,
|
|
37
|
+
D extends RouteDescriptor.Any = {},
|
|
38
|
+
B = {},
|
|
39
|
+
M extends Route.Tuple = [],
|
|
148
40
|
> = {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
readonly schemas: Schemas
|
|
41
|
+
[RouteItems]: M
|
|
42
|
+
[RouteDescriptor]: D
|
|
43
|
+
[RouteBindings]: B
|
|
153
44
|
}
|
|
154
45
|
|
|
155
|
-
export type Default = Route<
|
|
156
|
-
RouteMethod,
|
|
157
|
-
RouteMedia,
|
|
158
|
-
RouteHandler,
|
|
159
|
-
RouteSchemas
|
|
160
|
-
>
|
|
161
|
-
|
|
162
|
-
export type Tuple = readonly [Default, ...Default[]]
|
|
163
|
-
|
|
164
|
-
export type Empty = readonly []
|
|
165
|
-
|
|
166
46
|
export type Proto =
|
|
167
47
|
& Pipeable.Pipeable
|
|
48
|
+
& Iterable<Route.Route<any, any, any, any, any>>
|
|
168
49
|
& {
|
|
169
50
|
[TypeId]: typeof TypeId
|
|
170
51
|
}
|
|
171
|
-
}
|
|
172
52
|
|
|
173
|
-
|
|
174
|
-
* Consists of function to build {@link RouteSet}.
|
|
175
|
-
* This should include all exported functions in this module ({@link RouteModule})
|
|
176
|
-
* that have `this` as {@link Self}.
|
|
177
|
-
*
|
|
178
|
-
* Method functions, like {@link post}, modify the method of existing routes.
|
|
179
|
-
* Media functions, like {@link json}, create new routes with specific media type.
|
|
180
|
-
*/
|
|
181
|
-
type RouteBuilder = {
|
|
182
|
-
post: typeof post
|
|
183
|
-
get: typeof get
|
|
184
|
-
put: typeof put
|
|
185
|
-
patch: typeof patch
|
|
186
|
-
delete: typeof _delete
|
|
187
|
-
options: typeof options
|
|
188
|
-
head: typeof head
|
|
53
|
+
export type Any = RouteSet<{}, {}, Route.Tuple>
|
|
189
54
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
55
|
+
export type Infer<R> = R extends RouteSet<infer D, infer B, infer I>
|
|
56
|
+
? RouteSet<D, B, I>
|
|
57
|
+
: R
|
|
193
58
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
59
|
+
export type Items<
|
|
60
|
+
T extends Data<any, any, any>,
|
|
61
|
+
> = T extends Data<
|
|
62
|
+
any,
|
|
63
|
+
any,
|
|
64
|
+
infer M
|
|
65
|
+
> ? M
|
|
66
|
+
: never
|
|
201
67
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
export type RouteSet<
|
|
207
|
-
M extends ReadonlyArray<Route.Default>,
|
|
208
|
-
Schemas extends RouteSchemas = RouteSchemas.Empty,
|
|
209
|
-
> =
|
|
210
|
-
& Pipeable.Pipeable
|
|
211
|
-
& RouteSet.Instance<M, Schemas>
|
|
212
|
-
& {
|
|
213
|
-
[RouteSetTypeId]: typeof RouteSetTypeId
|
|
214
|
-
}
|
|
215
|
-
& RouteBuilder
|
|
68
|
+
export type Descriptor<
|
|
69
|
+
T extends Data<any, any, any>,
|
|
70
|
+
> = T extends Data<infer D, any, any> ? D : never
|
|
71
|
+
}
|
|
216
72
|
|
|
217
|
-
export namespace
|
|
218
|
-
export
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
73
|
+
export namespace Route {
|
|
74
|
+
export interface Route<
|
|
75
|
+
D extends RouteDescriptor.Any = {},
|
|
76
|
+
B = {},
|
|
77
|
+
A = any,
|
|
78
|
+
E = never,
|
|
79
|
+
R = never,
|
|
80
|
+
> extends
|
|
81
|
+
RouteSet.RouteSet<D, {}, [
|
|
82
|
+
Route<D, B, A, E, R>,
|
|
83
|
+
]>
|
|
84
|
+
{
|
|
85
|
+
readonly handler: Handler<B & D, A, E, R>
|
|
224
86
|
}
|
|
225
87
|
|
|
226
|
-
export type
|
|
227
|
-
|
|
228
|
-
export type Proto =
|
|
88
|
+
export type With<D extends RouteDescriptor.Any> =
|
|
89
|
+
& Route<any, any, any, any, any>
|
|
229
90
|
& {
|
|
230
|
-
[
|
|
91
|
+
[RouteDescriptor]: D
|
|
231
92
|
}
|
|
232
|
-
& RouteBuilder
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Type for HTTP middleware function
|
|
237
|
-
*/
|
|
238
|
-
export type HttpMiddlewareFunction = ReturnType<typeof HttpMiddleware.make>
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Marker type for route middleware specification.
|
|
242
|
-
* Used to distinguish middleware from routes in Route.layer() arguments.
|
|
243
|
-
*/
|
|
244
|
-
export interface RouteMiddleware {
|
|
245
|
-
readonly _tag: "RouteMiddleware"
|
|
246
|
-
readonly middleware: HttpMiddlewareFunction
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export type RouteLayer<
|
|
250
|
-
M extends ReadonlyArray<Route.Default> = ReadonlyArray<Route.Default>,
|
|
251
|
-
Schemas extends RouteSchemas = RouteSchemas.Empty,
|
|
252
|
-
> =
|
|
253
|
-
& Pipeable.Pipeable
|
|
254
|
-
& {
|
|
255
|
-
[RouteLayerTypeId]: typeof RouteLayerTypeId
|
|
256
|
-
[RouteSetTypeId]: typeof RouteSetTypeId
|
|
257
|
-
set: M
|
|
258
|
-
schema: Schemas
|
|
259
|
-
httpMiddleware?: HttpMiddlewareFunction
|
|
260
|
-
}
|
|
261
|
-
& RouteBuilder
|
|
262
|
-
|
|
263
|
-
export const isRouteLayer = (u: unknown): u is RouteLayer =>
|
|
264
|
-
Predicate.hasProperty(u, RouteLayerTypeId)
|
|
265
93
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
a: Route.Default,
|
|
272
|
-
b: Route.Default,
|
|
273
|
-
): boolean {
|
|
274
|
-
const methodMatches = a.method === "*"
|
|
275
|
-
|| b.method === "*"
|
|
276
|
-
|| a.method === b.method
|
|
277
|
-
|
|
278
|
-
const mediaMatches = a.media === "*"
|
|
279
|
-
|| b.media === "*"
|
|
280
|
-
|| a.media === b.media
|
|
281
|
-
|
|
282
|
-
return methodMatches && mediaMatches
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export const post = makeMethodModifier("POST")
|
|
286
|
-
export const get = makeMethodModifier("GET")
|
|
287
|
-
export const put = makeMethodModifier("PUT")
|
|
288
|
-
export const patch = makeMethodModifier("PATCH")
|
|
289
|
-
export const options = makeMethodModifier("OPTIONS")
|
|
290
|
-
export const head = makeMethodModifier("HEAD")
|
|
291
|
-
const _delete = makeMethodModifier("DELETE")
|
|
292
|
-
export {
|
|
293
|
-
_delete as delete,
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export const text = makeMediaFunction<"GET", "text/plain", string>(
|
|
297
|
-
"GET",
|
|
298
|
-
"text/plain",
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
export const html = makeMediaFunction<
|
|
302
|
-
"GET",
|
|
303
|
-
"text/html",
|
|
304
|
-
string | GenericJsxObject
|
|
305
|
-
>(
|
|
306
|
-
"GET",
|
|
307
|
-
"text/html",
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
export const json = makeMediaFunction<"GET", "application/json", JsonValue>(
|
|
311
|
-
"GET",
|
|
312
|
-
"application/json",
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Schema type that accepts string-encoded input.
|
|
317
|
-
* Used for path parameters which are always strings.
|
|
318
|
-
*/
|
|
319
|
-
type StringEncodedSchema =
|
|
320
|
-
| Schema.Schema<any, string, any>
|
|
321
|
-
// TODO: we accept PropertySignature to support Schema.optional
|
|
322
|
-
// not great but Effect 4 should be better about it
|
|
323
|
-
| Schema.PropertySignature.All
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Schema type that accepts string or string array encoded input.
|
|
327
|
-
* Used for URL params and headers which can have multiple values.
|
|
328
|
-
*/
|
|
329
|
-
type StringOrArrayEncodedSchema =
|
|
330
|
-
| Schema.Schema<any, OneOrMany<string>, any>
|
|
331
|
-
| Schema.PropertySignature.All
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Helper type to extract the Encoded type from a Schema.
|
|
335
|
-
*/
|
|
336
|
-
type GetEncoded<S> = S extends { Encoded: infer E } ? E : never
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Check if a schema's encoded type is string.
|
|
340
|
-
*/
|
|
341
|
-
type IsStringEncoded<S> = S extends Schema.PropertySignature.All ? true
|
|
342
|
-
: GetEncoded<S> extends string ? true
|
|
343
|
-
: false
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Check if a schema's encoded type is string or string array.
|
|
347
|
-
*/
|
|
348
|
-
type IsStringOrArrayEncoded<S> = S extends Schema.PropertySignature.All ? true
|
|
349
|
-
: GetEncoded<S> extends OneOrMany<string> ? true
|
|
350
|
-
: false
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Validate that all fields have string-encoded schemas.
|
|
354
|
-
*/
|
|
355
|
-
type ValidateStringEncodedFields<T extends Record<PropertyKey, any>> = {
|
|
356
|
-
[K in keyof T]: IsStringEncoded<T[K]> extends true ? T[K]
|
|
357
|
-
: StringEncodedSchema
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Validate that all fields have string or array-encoded schemas.
|
|
362
|
-
*/
|
|
363
|
-
type ValidateStringOrArrayEncodedFields<T extends Record<PropertyKey, any>> = {
|
|
364
|
-
[K in keyof T]: IsStringOrArrayEncoded<T[K]> extends true ? T[K]
|
|
365
|
-
: StringOrArrayEncodedSchema
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function makeSingleStringSchemaModifier<
|
|
369
|
-
K extends string,
|
|
370
|
-
>(key: K) {
|
|
371
|
-
return function<
|
|
372
|
-
S extends Self,
|
|
373
|
-
const Fields extends Record<PropertyKey, any>,
|
|
374
|
-
>(
|
|
375
|
-
this: S,
|
|
376
|
-
fieldsOrSchema: Fields extends Schema.Struct<any> ? Fields
|
|
377
|
-
: ValidateStringEncodedFields<Fields>,
|
|
378
|
-
): S extends RouteSet<infer Routes, infer Schemas> ? RouteSet<
|
|
379
|
-
Routes,
|
|
380
|
-
& Schemas
|
|
381
|
-
& {
|
|
382
|
-
[P in K]: Fields extends Schema.Struct<infer F> ? Schema.Struct<F>
|
|
383
|
-
: Schema.Struct<
|
|
384
|
-
Fields extends Record<PropertyKey, infer _> ? Fields : never
|
|
385
|
-
>
|
|
386
|
-
}
|
|
387
|
-
>
|
|
388
|
-
: RouteSet<
|
|
389
|
-
[],
|
|
390
|
-
{
|
|
391
|
-
[P in K]: Fields extends Schema.Struct<infer F> ? Schema.Struct<F>
|
|
392
|
-
: Schema.Struct<
|
|
393
|
-
Fields extends Record<PropertyKey, infer _> ? Fields : never
|
|
394
|
-
>
|
|
395
|
-
}
|
|
396
|
-
>
|
|
397
|
-
{
|
|
398
|
-
const baseRoutes = isRouteSet(this)
|
|
399
|
-
? this.set
|
|
400
|
-
: [] as const
|
|
401
|
-
const baseSchema = isRouteSet(this)
|
|
402
|
-
? this.schema
|
|
403
|
-
: {} as RouteSchemas.Empty
|
|
404
|
-
|
|
405
|
-
const schema = Schema.isSchema(fieldsOrSchema)
|
|
406
|
-
? fieldsOrSchema
|
|
407
|
-
: Schema.Struct(fieldsOrSchema as Schema.Struct.Fields)
|
|
408
|
-
|
|
409
|
-
return makeSet(
|
|
410
|
-
baseRoutes as ReadonlyArray<Route.Default>,
|
|
411
|
-
{
|
|
412
|
-
...baseSchema,
|
|
413
|
-
[key]: schema,
|
|
414
|
-
},
|
|
415
|
-
) as never
|
|
416
|
-
}
|
|
417
|
-
}
|
|
94
|
+
export type Tuple<
|
|
95
|
+
_D extends RouteDescriptor.Any = {},
|
|
96
|
+
> = [
|
|
97
|
+
...Route<any, any, any, any, any>[],
|
|
98
|
+
]
|
|
418
99
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
S extends Self,
|
|
424
|
-
const Fields extends Record<PropertyKey, any>,
|
|
425
|
-
>(
|
|
426
|
-
this: S,
|
|
427
|
-
fieldsOrSchema: Fields extends Schema.Struct<any> ? Fields
|
|
428
|
-
: ValidateStringOrArrayEncodedFields<Fields>,
|
|
429
|
-
): S extends RouteSet<infer Routes, infer Schemas> ? RouteSet<
|
|
430
|
-
Routes,
|
|
431
|
-
& Schemas
|
|
432
|
-
& {
|
|
433
|
-
[P in K]: Fields extends Schema.Struct<infer F> ? Schema.Struct<F>
|
|
434
|
-
: Schema.Struct<
|
|
435
|
-
Fields extends Record<PropertyKey, infer _> ? Fields : never
|
|
436
|
-
>
|
|
437
|
-
}
|
|
438
|
-
>
|
|
439
|
-
: RouteSet<
|
|
440
|
-
[],
|
|
441
|
-
{
|
|
442
|
-
[P in K]: Fields extends Schema.Struct<infer F> ? Schema.Struct<F>
|
|
443
|
-
: Schema.Struct<
|
|
444
|
-
Fields extends Record<PropertyKey, infer _> ? Fields : never
|
|
445
|
-
>
|
|
446
|
-
}
|
|
447
|
-
>
|
|
448
|
-
{
|
|
449
|
-
const baseRoutes = isRouteSet(this)
|
|
450
|
-
? this.set
|
|
451
|
-
: [] as const
|
|
452
|
-
const baseSchema = isRouteSet(this)
|
|
453
|
-
? this.schema
|
|
454
|
-
: {} as RouteSchemas.Empty
|
|
100
|
+
export type Handler<B, A, E, R> = (
|
|
101
|
+
context: B,
|
|
102
|
+
next: (context: B) => Effect.Effect<A>,
|
|
103
|
+
) => Effect.Effect<A, E, R>
|
|
455
104
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
105
|
+
// handler that cannot modify the context
|
|
106
|
+
export type HandlerImmutable<B, A, E, R> = (
|
|
107
|
+
context: B,
|
|
108
|
+
next: () => Effect.Effect<A>,
|
|
109
|
+
) => Effect.Effect<A, E, R>
|
|
459
110
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Extracts only the bindings (B) from routes, excluding descriptors.
|
|
113
|
+
*/
|
|
114
|
+
export type Bindings<
|
|
115
|
+
T extends RouteSet.Any,
|
|
116
|
+
M extends Tuple = RouteSet.Items<T>,
|
|
117
|
+
> = M extends [
|
|
118
|
+
infer Head,
|
|
119
|
+
...infer Tail extends Tuple,
|
|
120
|
+
] ? (
|
|
121
|
+
Head extends Route<any, infer B, any, any, any>
|
|
122
|
+
? ShallowMerge<B, Bindings<T, Tail>>
|
|
123
|
+
: Bindings<T, Tail>
|
|
124
|
+
)
|
|
125
|
+
: {}
|
|
469
126
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
>
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
Routes,
|
|
481
|
-
& Schemas
|
|
482
|
-
& {
|
|
483
|
-
[P in K]: Fields extends Schema.Schema.Any ? Fields
|
|
484
|
-
: Fields extends Schema.Struct.Fields ? Schema.Struct<Fields>
|
|
485
|
-
: never
|
|
486
|
-
}
|
|
487
|
-
>
|
|
488
|
-
: RouteSet<
|
|
489
|
-
[],
|
|
490
|
-
{
|
|
491
|
-
[P in K]: Fields extends Schema.Schema.Any ? Fields
|
|
492
|
-
: Fields extends Schema.Struct.Fields ? Schema.Struct<Fields>
|
|
493
|
-
: never
|
|
494
|
-
}
|
|
127
|
+
/**
|
|
128
|
+
* Extracts the full handler context from a RouteSet.
|
|
129
|
+
* Merges descriptors and bindings from all routes, with later values
|
|
130
|
+
* taking precedence via ShallowMerge to avoid `never` from conflicting
|
|
131
|
+
* literal types (e.g. `{ method: "*" } & { method: "GET" }`).
|
|
132
|
+
*/
|
|
133
|
+
export type Context<T extends RouteSet.Any> =
|
|
134
|
+
& Omit<
|
|
135
|
+
RouteSet.Descriptor<T>,
|
|
136
|
+
keyof ExtractContext<RouteSet.Items<T>>
|
|
495
137
|
>
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
138
|
+
& ExtractContext<RouteSet.Items<T>>
|
|
139
|
+
|
|
140
|
+
type ExtractContext<
|
|
141
|
+
M extends Tuple,
|
|
142
|
+
> = M extends [
|
|
143
|
+
infer Head,
|
|
144
|
+
...infer Tail extends Tuple,
|
|
145
|
+
] ? (
|
|
146
|
+
Head extends Route<
|
|
147
|
+
infer D,
|
|
148
|
+
infer B,
|
|
149
|
+
any,
|
|
150
|
+
any,
|
|
151
|
+
any
|
|
152
|
+
> ? ShallowMerge<
|
|
153
|
+
& Omit<D, keyof B>
|
|
154
|
+
& B,
|
|
155
|
+
ExtractContext<Tail>
|
|
156
|
+
>
|
|
157
|
+
: ExtractContext<Tail>
|
|
158
|
+
)
|
|
159
|
+
: {}
|
|
516
160
|
}
|
|
517
161
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const SetProto = {
|
|
526
|
-
[RouteSetTypeId]: RouteSetTypeId,
|
|
527
|
-
|
|
528
|
-
post,
|
|
529
|
-
get,
|
|
530
|
-
put,
|
|
531
|
-
patch,
|
|
532
|
-
delete: _delete,
|
|
533
|
-
options,
|
|
534
|
-
head,
|
|
535
|
-
|
|
536
|
-
text,
|
|
537
|
-
html,
|
|
538
|
-
json,
|
|
539
|
-
|
|
540
|
-
schemaPathParams,
|
|
541
|
-
schemaUrlParams,
|
|
542
|
-
schemaPayload,
|
|
543
|
-
schemaSuccess,
|
|
544
|
-
schemaError,
|
|
545
|
-
schemaHeaders,
|
|
546
|
-
} satisfies RouteSet.Proto
|
|
547
|
-
|
|
548
|
-
const RouteProto = Object.assign(
|
|
549
|
-
Object.create(SetProto),
|
|
550
|
-
{
|
|
551
|
-
[TypeId]: TypeId,
|
|
552
|
-
|
|
553
|
-
pipe() {
|
|
554
|
-
return Pipeable.pipeArguments(this, arguments)
|
|
555
|
-
},
|
|
556
|
-
} satisfies Route.Proto,
|
|
557
|
-
)
|
|
558
|
-
|
|
559
|
-
const RouteLayerProto = Object.assign(
|
|
560
|
-
Object.create(SetProto),
|
|
561
|
-
{
|
|
562
|
-
[RouteLayerTypeId]: RouteLayerTypeId,
|
|
162
|
+
const Proto: RouteSet.Proto = {
|
|
163
|
+
[TypeId]: TypeId,
|
|
164
|
+
pipe() {
|
|
165
|
+
return Pipeable.pipeArguments(this, arguments)
|
|
166
|
+
},
|
|
167
|
+
*[Symbol.iterator](this: RouteSet.Any) {
|
|
168
|
+
yield* items(this)
|
|
563
169
|
},
|
|
564
|
-
)
|
|
565
|
-
|
|
566
|
-
export function isRoute(input: unknown): input is Route {
|
|
567
|
-
return Predicate.hasProperty(input, TypeId)
|
|
568
170
|
}
|
|
569
171
|
|
|
570
172
|
export function isRouteSet(
|
|
571
173
|
input: unknown,
|
|
572
|
-
): input is RouteSet.
|
|
573
|
-
return Predicate.hasProperty(input,
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
export type JsonValue =
|
|
577
|
-
| string
|
|
578
|
-
| number
|
|
579
|
-
| boolean
|
|
580
|
-
| null
|
|
581
|
-
| JsonValue[]
|
|
582
|
-
| {
|
|
583
|
-
[key: string]: JsonValue
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
type RouteContextDecoded = {
|
|
587
|
-
readonly pathParams?: Record<string, any>
|
|
588
|
-
readonly urlParams?: Record<string, any>
|
|
589
|
-
readonly payload?: any
|
|
590
|
-
readonly headers?: Record<string, any>
|
|
174
|
+
): input is RouteSet.Any {
|
|
175
|
+
return Predicate.hasProperty(input, TypeId)
|
|
591
176
|
}
|
|
592
177
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
export type DecodeRouteSchemas<Schemas extends RouteSchemas> =
|
|
599
|
-
& (Schemas["PathParams"] extends Schema.Struct<any> ? {
|
|
600
|
-
pathParams: Schema.Schema.Type<Schemas["PathParams"]>
|
|
601
|
-
}
|
|
602
|
-
: {})
|
|
603
|
-
& (Schemas["UrlParams"] extends Schema.Struct<any> ? {
|
|
604
|
-
urlParams: Schema.Schema.Type<Schemas["UrlParams"]>
|
|
605
|
-
}
|
|
606
|
-
: {})
|
|
607
|
-
& (Schemas["Payload"] extends Schema.Schema.Any ? {
|
|
608
|
-
payload: Schema.Schema.Type<Schemas["Payload"]>
|
|
609
|
-
}
|
|
610
|
-
: {})
|
|
611
|
-
& (Schemas["Headers"] extends Schema.Struct<any> ? {
|
|
612
|
-
headers: Schema.Schema.Type<Schemas["Headers"]>
|
|
613
|
-
}
|
|
614
|
-
: {})
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Context passed to route handler functions.
|
|
618
|
-
*
|
|
619
|
-
* @template {Input} Decoded schema values (pathParams, urlParams, etc.)
|
|
620
|
-
* @template {Next} Return type of next() based on media type
|
|
621
|
-
*/
|
|
622
|
-
export type RouteContext<
|
|
623
|
-
Input extends RouteContextDecoded = {},
|
|
624
|
-
Next = unknown,
|
|
625
|
-
> =
|
|
626
|
-
& {
|
|
627
|
-
request: HttpServerRequest.HttpServerRequest
|
|
628
|
-
get url(): URL
|
|
629
|
-
slots: Record<string, string>
|
|
630
|
-
next: <E = unknown, R = unknown>() => Effect.Effect<Next, E, R>
|
|
631
|
-
}
|
|
632
|
-
& Input
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Merges two RouteSchemas types.
|
|
636
|
-
* For PathParams, UrlParams, and Headers: merges struct fields.
|
|
637
|
-
* For Payload, Success, and Error: creates Schema.Union.
|
|
638
|
-
*/
|
|
639
|
-
type MergeSchemas<
|
|
640
|
-
A extends RouteSchemas,
|
|
641
|
-
B extends RouteSchemas,
|
|
642
|
-
> = {
|
|
643
|
-
readonly PathParams: [A["PathParams"], B["PathParams"]] extends [
|
|
644
|
-
Schema.Struct<infer AFields>,
|
|
645
|
-
Schema.Struct<infer BFields>,
|
|
646
|
-
] ? Schema.Struct<AFields & BFields>
|
|
647
|
-
: A["PathParams"] extends Schema.Struct<any> ? A["PathParams"]
|
|
648
|
-
: B["PathParams"] extends Schema.Struct<any> ? B["PathParams"]
|
|
649
|
-
: never
|
|
650
|
-
readonly UrlParams: [A["UrlParams"], B["UrlParams"]] extends [
|
|
651
|
-
Schema.Struct<infer AFields>,
|
|
652
|
-
Schema.Struct<infer BFields>,
|
|
653
|
-
] ? Schema.Struct<AFields & BFields>
|
|
654
|
-
: A["UrlParams"] extends Schema.Struct<any> ? A["UrlParams"]
|
|
655
|
-
: B["UrlParams"] extends Schema.Struct<any> ? B["UrlParams"]
|
|
656
|
-
: never
|
|
657
|
-
readonly Payload: [A["Payload"], B["Payload"]] extends [
|
|
658
|
-
Schema.Schema.Any,
|
|
659
|
-
Schema.Schema.Any,
|
|
660
|
-
] ? Schema.Union<[A["Payload"], B["Payload"]]>
|
|
661
|
-
: A["Payload"] extends Schema.Schema.Any ? A["Payload"]
|
|
662
|
-
: B["Payload"] extends Schema.Schema.Any ? B["Payload"]
|
|
663
|
-
: never
|
|
664
|
-
readonly Success: [A["Success"], B["Success"]] extends [
|
|
665
|
-
Schema.Schema.Any,
|
|
666
|
-
Schema.Schema.Any,
|
|
667
|
-
] ? Schema.Union<[A["Success"], B["Success"]]>
|
|
668
|
-
: A["Success"] extends Schema.Schema.Any ? A["Success"]
|
|
669
|
-
: B["Success"] extends Schema.Schema.Any ? B["Success"]
|
|
670
|
-
: never
|
|
671
|
-
readonly Error: [A["Error"], B["Error"]] extends [
|
|
672
|
-
Schema.Schema.Any,
|
|
673
|
-
Schema.Schema.Any,
|
|
674
|
-
] ? Schema.Union<[A["Error"], B["Error"]]>
|
|
675
|
-
: A["Error"] extends Schema.Schema.Any ? A["Error"]
|
|
676
|
-
: B["Error"] extends Schema.Schema.Any ? B["Error"]
|
|
677
|
-
: never
|
|
678
|
-
readonly Headers: [A["Headers"], B["Headers"]] extends [
|
|
679
|
-
Schema.Struct<infer AFields>,
|
|
680
|
-
Schema.Struct<infer BFields>,
|
|
681
|
-
] ? Schema.Struct<AFields & BFields>
|
|
682
|
-
: A["Headers"] extends Schema.Struct<any> ? A["Headers"]
|
|
683
|
-
: B["Headers"] extends Schema.Struct<any> ? B["Headers"]
|
|
684
|
-
: never
|
|
178
|
+
export function isRoute(
|
|
179
|
+
input: unknown,
|
|
180
|
+
): input is Route.Route {
|
|
181
|
+
return isRouteSet(input)
|
|
182
|
+
&& Predicate.hasProperty(input, "handler")
|
|
685
183
|
}
|
|
686
184
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
*/
|
|
692
|
-
function mergeSchemas<
|
|
693
|
-
A extends RouteSchemas,
|
|
694
|
-
B extends RouteSchemas,
|
|
185
|
+
export function set<
|
|
186
|
+
D extends RouteDescriptor.Any = {},
|
|
187
|
+
B = {},
|
|
188
|
+
I extends Route.Tuple = [],
|
|
695
189
|
>(
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
):
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const unionKeys: Array<keyof RouteSchemas> = [
|
|
708
|
-
"Payload",
|
|
709
|
-
"Success",
|
|
710
|
-
"Error",
|
|
711
|
-
]
|
|
712
|
-
|
|
713
|
-
for (const key of structKeys) {
|
|
714
|
-
if (a[key] && b[key]) {
|
|
715
|
-
const aSchema = a[key]! as Schema.Struct<any>
|
|
716
|
-
const bSchema = b[key]! as Schema.Struct<any>
|
|
717
|
-
const mergedFields = {
|
|
718
|
-
...aSchema.fields,
|
|
719
|
-
...bSchema.fields,
|
|
720
|
-
}
|
|
721
|
-
result[key] = Schema.Struct(mergedFields)
|
|
722
|
-
} else if (a[key]) {
|
|
723
|
-
result[key] = a[key]
|
|
724
|
-
} else if (b[key]) {
|
|
725
|
-
result[key] = b[key]
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
for (const key of unionKeys) {
|
|
730
|
-
if (a[key] && b[key]) {
|
|
731
|
-
result[key] = Schema.Union(a[key]!, b[key]!)
|
|
732
|
-
} else if (a[key]) {
|
|
733
|
-
result[key] = a[key]
|
|
734
|
-
} else if (b[key]) {
|
|
735
|
-
result[key] = b[key]
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
return result
|
|
190
|
+
items: I = [] as unknown as I,
|
|
191
|
+
descriptor: D = {} as D,
|
|
192
|
+
): RouteSet.RouteSet<D, B, I> {
|
|
193
|
+
return Object.assign(
|
|
194
|
+
Object.create(Proto),
|
|
195
|
+
{
|
|
196
|
+
[RouteItems]: items,
|
|
197
|
+
[RouteDescriptor]: descriptor,
|
|
198
|
+
},
|
|
199
|
+
) as RouteSet.RouteSet<D, B, I>
|
|
740
200
|
}
|
|
741
201
|
|
|
742
202
|
export function make<
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
203
|
+
D extends RouteDescriptor.Any,
|
|
204
|
+
B,
|
|
205
|
+
A,
|
|
206
|
+
E = never,
|
|
207
|
+
R = never,
|
|
747
208
|
>(
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
): Route<
|
|
755
|
-
Method,
|
|
756
|
-
Media,
|
|
757
|
-
Handler,
|
|
758
|
-
Schemas
|
|
759
|
-
> {
|
|
760
|
-
const route = Object.assign(
|
|
761
|
-
Object.create(RouteProto),
|
|
209
|
+
handler: Route.Handler<B & D, A, E, R>,
|
|
210
|
+
descriptor?: D,
|
|
211
|
+
): Route.Route<D, B, A, E, R> {
|
|
212
|
+
const items: any = []
|
|
213
|
+
const route: Route.Route<D, B, A, E, R> = Object.assign(
|
|
214
|
+
Object.create(Proto),
|
|
762
215
|
{
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
media: input.media,
|
|
768
|
-
handler: input.handler,
|
|
769
|
-
} satisfies Route.Data,
|
|
216
|
+
[RouteItems]: items,
|
|
217
|
+
[RouteDescriptor]: descriptor,
|
|
218
|
+
handler,
|
|
219
|
+
},
|
|
770
220
|
)
|
|
771
221
|
|
|
772
|
-
route
|
|
773
|
-
route,
|
|
774
|
-
]
|
|
775
|
-
route.schemas = input.schemas
|
|
222
|
+
items.push(route)
|
|
776
223
|
|
|
777
224
|
return route
|
|
778
225
|
}
|
|
779
226
|
|
|
780
|
-
|
|
781
|
-
M extends ReadonlyArray<Route.Default>,
|
|
782
|
-
Schemas extends RouteSchemas = RouteSchemas.Empty,
|
|
783
|
-
>(
|
|
784
|
-
routes: M,
|
|
785
|
-
schema: Schemas = {} as Schemas,
|
|
786
|
-
): RouteSet<M, Schemas> {
|
|
787
|
-
return Object.assign(
|
|
788
|
-
Object.create(SetProto),
|
|
789
|
-
{
|
|
790
|
-
set: routes,
|
|
791
|
-
schema,
|
|
792
|
-
},
|
|
793
|
-
) as RouteSet<M, Schemas>
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
type HandlerInput<A, E, R> =
|
|
797
|
-
| A
|
|
798
|
-
| Effect.Effect<A, E, R>
|
|
799
|
-
| ((context: RouteContext) =>
|
|
800
|
-
| Effect.Effect<A, E, R>
|
|
801
|
-
| Generator<YieldWrap<Effect.Effect<A, E, R>>, A, never>)
|
|
227
|
+
export const empty = set()
|
|
802
228
|
|
|
803
|
-
function
|
|
804
|
-
|
|
805
|
-
): RouteHandler<A, E, R> {
|
|
806
|
-
if (typeof handler === "function") {
|
|
807
|
-
return (context): Effect.Effect<A, E, R> => {
|
|
808
|
-
const result = (handler as Function)(context)
|
|
809
|
-
if (Effect.isEffect(result)) {
|
|
810
|
-
return result as Effect.Effect<A, E, R>
|
|
811
|
-
}
|
|
812
|
-
return Effect.gen(() => result) as Effect.Effect<A, E, R>
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
if (Effect.isEffect(handler)) {
|
|
816
|
-
return () => handler
|
|
817
|
-
}
|
|
818
|
-
return () => Effect.succeed(handler as A)
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
/**
|
|
822
|
-
* Factory function that creates Route for a specific method & media.
|
|
823
|
-
* Accepts Effect, function that returns Effect, and effectful generator.
|
|
824
|
-
*/
|
|
825
|
-
function makeMediaFunction<
|
|
826
|
-
Method extends HttpMethod.HttpMethod,
|
|
827
|
-
Media extends RouteMedia,
|
|
828
|
-
ExpectedValue,
|
|
229
|
+
export function describe<
|
|
230
|
+
D extends RouteDescriptor.Any,
|
|
829
231
|
>(
|
|
830
|
-
|
|
831
|
-
media: Media,
|
|
232
|
+
descriptor: D,
|
|
832
233
|
) {
|
|
833
|
-
return
|
|
834
|
-
S extends Self,
|
|
835
|
-
A extends ExpectedValue,
|
|
836
|
-
E = never,
|
|
837
|
-
R = never,
|
|
838
|
-
>(
|
|
839
|
-
this: S,
|
|
840
|
-
handler: S extends RouteSet<infer _Routes, infer Schemas> ?
|
|
841
|
-
| A
|
|
842
|
-
| Effect.Effect<A, E, R>
|
|
843
|
-
| ((
|
|
844
|
-
context: RouteContext<DecodeRouteSchemas<Schemas>, ExpectedValue>,
|
|
845
|
-
) =>
|
|
846
|
-
| Effect.Effect<A, E, R>
|
|
847
|
-
| Generator<YieldWrap<Effect.Effect<A, E, R>>, A, never>)
|
|
848
|
-
:
|
|
849
|
-
| A
|
|
850
|
-
| Effect.Effect<A, E, R>
|
|
851
|
-
| ((context: RouteContext<{}, ExpectedValue>) =>
|
|
852
|
-
| Effect.Effect<A, E, R>
|
|
853
|
-
| Generator<YieldWrap<Effect.Effect<A, E, R>>, A, never>),
|
|
854
|
-
): S extends RouteSet<infer Routes, infer Schemas> ? RouteSet<[
|
|
855
|
-
...Routes,
|
|
856
|
-
Route<
|
|
857
|
-
Method,
|
|
858
|
-
Media,
|
|
859
|
-
RouteHandler<A, E, R>,
|
|
860
|
-
Schemas
|
|
861
|
-
>,
|
|
862
|
-
], Schemas>
|
|
863
|
-
: RouteSet<[
|
|
864
|
-
Route<
|
|
865
|
-
Method,
|
|
866
|
-
Media,
|
|
867
|
-
RouteHandler<A, E, R>,
|
|
868
|
-
RouteSchemas.Empty
|
|
869
|
-
>,
|
|
870
|
-
], RouteSchemas.Empty>
|
|
871
|
-
{
|
|
872
|
-
const baseRoutes = isRouteSet(this)
|
|
873
|
-
? this.set
|
|
874
|
-
: [] as const
|
|
875
|
-
const baseSchema = isRouteSet(this)
|
|
876
|
-
? this.schema
|
|
877
|
-
: {} as RouteSchemas.Empty
|
|
878
|
-
|
|
879
|
-
return makeSet(
|
|
880
|
-
[
|
|
881
|
-
...baseRoutes,
|
|
882
|
-
make({
|
|
883
|
-
method,
|
|
884
|
-
media,
|
|
885
|
-
handler: normalizeHandler(handler),
|
|
886
|
-
schemas: baseSchema,
|
|
887
|
-
}),
|
|
888
|
-
] as ReadonlyArray<Route.Default>,
|
|
889
|
-
baseSchema,
|
|
890
|
-
) as never
|
|
891
|
-
}
|
|
234
|
+
return set([], descriptor)
|
|
892
235
|
}
|
|
893
236
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
return function<
|
|
901
|
-
S extends Self,
|
|
902
|
-
T extends Route.Tuple,
|
|
903
|
-
InSchemas extends RouteSchemas,
|
|
904
|
-
>(
|
|
905
|
-
this: S,
|
|
906
|
-
routes: RouteSet<T, InSchemas>,
|
|
907
|
-
): S extends RouteSet<infer B, infer BaseSchemas>
|
|
908
|
-
// append to existing RouteSet
|
|
909
|
-
? RouteSet<
|
|
910
|
-
[
|
|
911
|
-
...B,
|
|
912
|
-
...{
|
|
913
|
-
[K in keyof T]: T[K] extends Route<
|
|
914
|
-
infer _,
|
|
915
|
-
infer Media,
|
|
916
|
-
infer H,
|
|
917
|
-
infer RouteSchemas
|
|
918
|
-
> ? Route<
|
|
919
|
-
M,
|
|
920
|
-
Media,
|
|
921
|
-
H,
|
|
922
|
-
MergeSchemas<BaseSchemas, RouteSchemas>
|
|
923
|
-
>
|
|
924
|
-
: T[K]
|
|
925
|
-
},
|
|
926
|
-
],
|
|
927
|
-
BaseSchemas
|
|
928
|
-
>
|
|
929
|
-
// otherwise create new RouteSet
|
|
930
|
-
: RouteSet<
|
|
931
|
-
{
|
|
932
|
-
[K in keyof T]: T[K] extends Route<
|
|
933
|
-
infer _,
|
|
934
|
-
infer Media,
|
|
935
|
-
infer H,
|
|
936
|
-
infer RouteSchemas
|
|
937
|
-
> ? Route<
|
|
938
|
-
M,
|
|
939
|
-
Media,
|
|
940
|
-
H,
|
|
941
|
-
RouteSchemas
|
|
942
|
-
>
|
|
943
|
-
: T[K]
|
|
944
|
-
},
|
|
945
|
-
InSchemas
|
|
946
|
-
>
|
|
947
|
-
{
|
|
948
|
-
const baseRoutes = isRouteSet(this)
|
|
949
|
-
? this.set
|
|
950
|
-
: [] as const
|
|
951
|
-
const baseSchema = isRouteSet(this)
|
|
952
|
-
? this.schema
|
|
953
|
-
: {} as RouteSchemas.Empty
|
|
954
|
-
|
|
955
|
-
return makeSet(
|
|
956
|
-
[
|
|
957
|
-
...baseRoutes,
|
|
958
|
-
...routes.set.map(route => {
|
|
959
|
-
return make({
|
|
960
|
-
...route,
|
|
961
|
-
method,
|
|
962
|
-
schemas: mergeSchemas(baseSchema, route.schemas),
|
|
963
|
-
})
|
|
964
|
-
}),
|
|
965
|
-
] as ReadonlyArray<Route.Default>,
|
|
966
|
-
baseSchema,
|
|
967
|
-
) as never
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
export type GenericJsxObject = {
|
|
972
|
-
type: any
|
|
973
|
-
props: any
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
export function isGenericJsxObject(value: unknown): value is GenericJsxObject {
|
|
977
|
-
return typeof value === "object"
|
|
978
|
-
&& value !== null
|
|
979
|
-
&& "type" in value
|
|
980
|
-
&& "props" in value
|
|
237
|
+
export function items<
|
|
238
|
+
T extends RouteSet.Data<any, any, any>,
|
|
239
|
+
>(
|
|
240
|
+
self: T,
|
|
241
|
+
): RouteSet.Items<T> {
|
|
242
|
+
return self[RouteItems]
|
|
981
243
|
}
|
|
982
244
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
* Route.http(middleware1),
|
|
990
|
-
* Route.http(middleware2),
|
|
991
|
-
* Route.http(middleware3)
|
|
992
|
-
* )
|
|
993
|
-
*/
|
|
994
|
-
export function http(
|
|
995
|
-
middleware: HttpMiddlewareFunction,
|
|
996
|
-
): RouteMiddleware {
|
|
997
|
-
return {
|
|
998
|
-
_tag: "RouteMiddleware",
|
|
999
|
-
middleware,
|
|
1000
|
-
}
|
|
245
|
+
export function descriptor<
|
|
246
|
+
T extends RouteSet.Data<any, any, any>,
|
|
247
|
+
>(
|
|
248
|
+
self: T,
|
|
249
|
+
): T[typeof RouteDescriptor] {
|
|
250
|
+
return self[RouteDescriptor]
|
|
1001
251
|
}
|
|
1002
252
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
* return <html><body>{props.children}</body></html>
|
|
1020
|
-
* })
|
|
1021
|
-
* )
|
|
1022
|
-
*/
|
|
1023
|
-
export function layer(
|
|
1024
|
-
...items: Array<RouteMiddleware | RouteSet.Default>
|
|
1025
|
-
): RouteLayer {
|
|
1026
|
-
const routeMiddleware: RouteMiddleware[] = []
|
|
1027
|
-
const routeSets: RouteSet.Default[] = []
|
|
253
|
+
export type ExtractBindings<
|
|
254
|
+
M extends Route.Tuple,
|
|
255
|
+
> = M extends [
|
|
256
|
+
infer Head,
|
|
257
|
+
...infer Tail extends Route.Tuple,
|
|
258
|
+
] ? (
|
|
259
|
+
Head extends Route.Route<
|
|
260
|
+
any,
|
|
261
|
+
infer B,
|
|
262
|
+
any,
|
|
263
|
+
any,
|
|
264
|
+
any
|
|
265
|
+
> ? ShallowMerge<B, ExtractBindings<Tail>>
|
|
266
|
+
: ExtractBindings<Tail>
|
|
267
|
+
)
|
|
268
|
+
: {}
|
|
1028
269
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
270
|
+
// Shallow merge two object types.
|
|
271
|
+
// For overlapping keys, intersect the values.
|
|
272
|
+
type ShallowMerge<A, B> =
|
|
273
|
+
& Omit<A, keyof B>
|
|
274
|
+
& {
|
|
275
|
+
[K in keyof B]: K extends keyof A ? A[K] & B[K] : B[K]
|
|
1035
276
|
}
|
|
1036
277
|
|
|
1037
|
-
|
|
278
|
+
export type ExtractContext<
|
|
279
|
+
Items extends Route.Tuple,
|
|
280
|
+
Descriptor extends RouteDescriptor.Any,
|
|
281
|
+
> = ExtractBindings<Items> & Descriptor
|
|
1038
282
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
const middlewareFunctions = routeMiddleware.map((spec) => spec.middleware)
|
|
1044
|
-
const httpMiddleware = middlewareFunctions.length === 0
|
|
1045
|
-
? undefined
|
|
1046
|
-
: middlewareFunctions.length === 1
|
|
1047
|
-
? middlewareFunctions[0]
|
|
1048
|
-
: (app: any) => middlewareFunctions.reduceRight((acc, mw) => mw(acc), app)
|
|
283
|
+
export * from "./RouteHook.ts"
|
|
284
|
+
export * from "./RouteSchema.ts"
|
|
1049
285
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
286
|
+
export {
|
|
287
|
+
add,
|
|
288
|
+
del,
|
|
289
|
+
get,
|
|
290
|
+
head,
|
|
291
|
+
options,
|
|
292
|
+
patch,
|
|
293
|
+
post,
|
|
294
|
+
put,
|
|
295
|
+
use,
|
|
296
|
+
} from "./RouteMount.ts"
|
|
297
|
+
|
|
298
|
+
export const text = RouteBody.build<string, "text">({
|
|
299
|
+
format: "text",
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
export const html = RouteBody.build<string, "html">({
|
|
303
|
+
format: "html",
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
export const json = RouteBody.build<Values.Json, "json">({
|
|
307
|
+
format: "json",
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
export const bytes = RouteBody.build<Uint8Array, "bytes">({
|
|
311
|
+
format: "bytes",
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
export class Routes extends Context.Tag("effect-start/Routes")<
|
|
315
|
+
Routes,
|
|
316
|
+
RouteTree.RouteTree
|
|
317
|
+
>() {}
|
|
318
|
+
|
|
319
|
+
export function layer(routes: RouteTree.RouteMap | RouteTree.RouteTree) {
|
|
320
|
+
return Layer.sync(
|
|
321
|
+
Routes,
|
|
322
|
+
() =>
|
|
323
|
+
RouteTree.isRouteTree(routes)
|
|
324
|
+
? routes
|
|
325
|
+
: RouteTree.make(routes),
|
|
1057
326
|
)
|
|
1058
327
|
}
|
|
1059
328
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
type ExtractMethods<T extends ReadonlyArray<Route.Default>> =
|
|
1065
|
-
T[number]["method"]
|
|
1066
|
-
|
|
1067
|
-
/**
|
|
1068
|
-
* Extract media union from a RouteSet's routes
|
|
1069
|
-
*/
|
|
1070
|
-
type ExtractMedia<T extends ReadonlyArray<Route.Default>> = T[number]["media"]
|
|
1071
|
-
|
|
1072
|
-
/**
|
|
1073
|
-
* Merge two RouteSets into one with content negotiation.
|
|
1074
|
-
* Properly infers union types for method/media and merges schemas.
|
|
1075
|
-
*/
|
|
1076
|
-
export function merge<
|
|
1077
|
-
RoutesA extends ReadonlyArray<Route.Default>,
|
|
1078
|
-
SchemasA extends RouteSchemas,
|
|
1079
|
-
RoutesB extends ReadonlyArray<Route.Default>,
|
|
1080
|
-
SchemasB extends RouteSchemas,
|
|
1081
|
-
>(
|
|
1082
|
-
self: RouteSet<RoutesA, SchemasA>,
|
|
1083
|
-
other: RouteSet<RoutesB, SchemasB>,
|
|
1084
|
-
): RouteSet<
|
|
1085
|
-
[
|
|
1086
|
-
Route<
|
|
1087
|
-
ExtractMethods<RoutesA> | ExtractMethods<RoutesB>,
|
|
1088
|
-
ExtractMedia<RoutesA> | ExtractMedia<RoutesB>,
|
|
1089
|
-
RouteHandler<HttpServerResponse.HttpServerResponse, any, never>,
|
|
1090
|
-
MergeSchemas<SchemasA, SchemasB>
|
|
1091
|
-
>,
|
|
1092
|
-
],
|
|
1093
|
-
MergeSchemas<SchemasA, SchemasB>
|
|
1094
|
-
> {
|
|
1095
|
-
const allRoutes = [...self.set, ...other.set]
|
|
1096
|
-
const mergedSchemas = mergeSchemas(self.schema, other.schema)
|
|
1097
|
-
|
|
1098
|
-
const handler: RouteHandler<HttpServerResponse.HttpServerResponse> = (
|
|
1099
|
-
context,
|
|
1100
|
-
) =>
|
|
1101
|
-
Effect.gen(function*() {
|
|
1102
|
-
const accept = context.request.headers.accept ?? ""
|
|
1103
|
-
|
|
1104
|
-
if (accept.includes("application/json")) {
|
|
1105
|
-
const jsonRoute = allRoutes.find((r) => r.media === "application/json")
|
|
1106
|
-
if (jsonRoute) {
|
|
1107
|
-
return yield* RouteRender.render(jsonRoute, context)
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
if (accept.includes("text/plain")) {
|
|
1112
|
-
const textRoute = allRoutes.find((r) => r.media === "text/plain")
|
|
1113
|
-
if (textRoute) {
|
|
1114
|
-
return yield* RouteRender.render(textRoute, context)
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
if (
|
|
1119
|
-
accept.includes("text/html") || accept.includes("*/*") || !accept
|
|
1120
|
-
) {
|
|
1121
|
-
const htmlRoute = allRoutes.find((r) => r.media === "text/html")
|
|
1122
|
-
if (htmlRoute) {
|
|
1123
|
-
return yield* RouteRender.render(htmlRoute, context)
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
const firstRoute = allRoutes[0]
|
|
1128
|
-
if (firstRoute) {
|
|
1129
|
-
return yield* RouteRender.render(firstRoute, context)
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
return HttpServerResponse.empty({ status: 406 })
|
|
1133
|
-
})
|
|
1134
|
-
|
|
1135
|
-
return makeSet(
|
|
1136
|
-
[
|
|
1137
|
-
make({
|
|
1138
|
-
method: allRoutes[0]?.method ?? "*",
|
|
1139
|
-
media: allRoutes[0]?.media ?? "*",
|
|
1140
|
-
handler,
|
|
1141
|
-
schemas: mergedSchemas,
|
|
1142
|
-
}),
|
|
1143
|
-
] as any,
|
|
1144
|
-
mergedSchemas,
|
|
1145
|
-
) as any
|
|
1146
|
-
}
|
|
329
|
+
export {
|
|
330
|
+
make as tree,
|
|
331
|
+
} from "./RouteTree.ts"
|