effect-start 0.33.0 → 0.35.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/README.md +303 -36
- package/dist/Entity.d.ts +3 -1
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js +23 -0
- package/dist/Entity.js.map +1 -1
- package/dist/Fetch.d.ts +1 -1
- package/dist/Fetch.js.map +1 -1
- package/dist/FileRouter.d.ts +1 -1
- package/dist/FileRouterCodegen.d.ts.map +1 -1
- package/dist/FileRouterCodegen.js +10 -4
- package/dist/FileRouterCodegen.js.map +1 -1
- package/dist/Job.d.ts +94 -0
- package/dist/Job.d.ts.map +1 -0
- package/dist/Job.js +157 -0
- package/dist/Job.js.map +1 -0
- package/dist/Password.d.ts +1 -1
- package/dist/Route.d.ts +20 -15
- package/dist/Route.d.ts.map +1 -1
- package/dist/Route.js +12 -0
- package/dist/Route.js.map +1 -1
- package/dist/RouteBody.d.ts +7 -7
- package/dist/RouteBody.d.ts.map +1 -1
- package/dist/RouteBody.js.map +1 -1
- package/dist/RouteHook.d.ts +1 -1
- package/dist/RouteHook.d.ts.map +1 -1
- package/dist/RouteHook.js.map +1 -1
- package/dist/RouteHttp.d.ts.map +1 -1
- package/dist/RouteHttp.js +55 -39
- package/dist/RouteHttp.js.map +1 -1
- package/dist/RouteLink.d.ts +16 -0
- package/dist/RouteLink.d.ts.map +1 -0
- package/dist/RouteLink.js +23 -0
- package/dist/RouteLink.js.map +1 -0
- package/dist/RouteMount.d.ts +37 -31
- package/dist/RouteMount.d.ts.map +1 -1
- package/dist/RouteMount.js.map +1 -1
- package/dist/RouteSchema.d.ts +81 -28
- package/dist/RouteSchema.d.ts.map +1 -1
- package/dist/RouteSchema.js +56 -101
- package/dist/RouteSchema.js.map +1 -1
- package/dist/RouteSse.d.ts +1 -1
- package/dist/RouteSse.d.ts.map +1 -1
- package/dist/RouteSse.js.map +1 -1
- package/dist/Socket.d.ts +1 -1
- package/dist/Start.js +1 -1
- package/dist/Start.js.map +1 -1
- package/dist/StaticFiles.d.ts +4 -10
- package/dist/StaticFiles.d.ts.map +1 -1
- package/dist/StaticFiles.js +10 -18
- package/dist/StaticFiles.js.map +1 -1
- package/dist/System.d.ts +1 -1
- package/dist/_Docker.d.ts +1 -1
- package/dist/_HtmlScanner.d.ts +42 -0
- package/dist/_HtmlScanner.d.ts.map +1 -0
- package/dist/_HtmlScanner.js +385 -0
- package/dist/_HtmlScanner.js.map +1 -0
- package/dist/_RouteLink.d.ts +16 -0
- package/dist/_RouteLink.d.ts.map +1 -0
- package/dist/_RouteLink.js +22 -0
- package/dist/_RouteLink.js.map +1 -0
- package/dist/bun/BunRoute.d.ts +4 -6
- package/dist/bun/BunRoute.d.ts.map +1 -1
- package/dist/bun/BunRoute.js +1 -1
- package/dist/bun/BunRoute.js.map +1 -1
- package/dist/bundler/Bundle.d.ts +1 -1
- package/dist/bundler/BundleRoute.d.ts +3 -4
- package/dist/bundler/BundleRoute.d.ts.map +1 -1
- package/dist/bundler/BundleRoute.js +5 -11
- package/dist/bundler/BundleRoute.js.map +1 -1
- package/dist/datastar/watchers/patchElements.js +1 -1
- package/dist/datastar/watchers/patchElements.js.map +1 -1
- package/dist/experimental/CsrfProtection.d.ts +67 -0
- package/dist/experimental/CsrfProtection.d.ts.map +1 -0
- package/dist/experimental/CsrfProtection.js +100 -0
- package/dist/experimental/CsrfProtection.js.map +1 -0
- package/dist/experimental/EncryptedCookies.d.ts +1 -1
- package/dist/experimental/KeyValueStore.d.ts +1 -1
- package/dist/lint/plugin.js +4 -0
- package/dist/lint/plugin.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +1 -1
- package/dist/studio/Studio.d.ts +1 -1
- package/dist/studio/Studio.d.ts.map +1 -1
- package/dist/studio/Studio.js +4 -10
- package/dist/studio/Studio.js.map +1 -1
- package/dist/studio/routes/errors/route.d.ts +2 -2
- package/dist/studio/routes/errors/route.d.ts.map +1 -1
- package/dist/studio/routes/errors/route.js +3 -2
- package/dist/studio/routes/errors/route.js.map +1 -1
- package/dist/studio/routes/fiberDetail.d.ts +3 -7
- package/dist/studio/routes/fiberDetail.d.ts.map +1 -1
- package/dist/studio/routes/fibers/route.d.ts +2 -2
- package/dist/studio/routes/layout.d.ts +3 -3
- package/dist/studio/routes/logs/route.d.ts +2 -2
- package/dist/studio/routes/logs/route.d.ts.map +1 -1
- package/dist/studio/routes/logs/route.js +3 -2
- package/dist/studio/routes/logs/route.js.map +1 -1
- package/dist/studio/routes/metrics/route.d.ts +2 -2
- package/dist/studio/routes/route.d.ts +1 -1
- package/dist/studio/routes/routes/route.d.ts +1 -1
- package/dist/studio/routes/services/route.d.ts +1 -1
- package/dist/studio/routes/system/route.d.ts +2 -2
- package/dist/studio/routes/traceDetail.d.ts +3 -7
- package/dist/studio/routes/traceDetail.d.ts.map +1 -1
- package/dist/studio/routes/traces/route.d.ts +2 -2
- package/dist/studio/routes/traces/route.d.ts.map +1 -1
- package/dist/studio/routes/traces/route.js +3 -2
- package/dist/studio/routes/traces/route.js.map +1 -1
- package/dist/studio/routes/tree.d.ts +43 -51
- package/dist/studio/routes/tree.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/Entity.ts +34 -5
- package/src/Fetch.ts +1 -1
- package/src/FileRouterCodegen.ts +10 -4
- package/src/Route.ts +55 -34
- package/src/RouteBody.ts +15 -15
- package/src/RouteHook.ts +3 -3
- package/src/RouteHttp.ts +55 -51
- package/src/RouteLink.ts +56 -0
- package/src/RouteMount.ts +44 -37
- package/src/RouteSchema.ts +299 -166
- package/src/RouteSse.ts +3 -3
- package/src/Start.ts +1 -1
- package/src/StaticFiles.ts +17 -34
- package/src/_HtmlScanner.ts +415 -0
- package/src/bun/BunRoute.ts +11 -11
- package/src/bundler/BundleRoute.ts +8 -19
- package/src/datastar/watchers/patchElements.ts +1 -1
- package/src/dev.d.ts +3 -0
- package/src/experimental/CsrfProtection.ts +153 -0
- package/src/lint/plugin.js +2 -0
- package/src/studio/Studio.ts +4 -14
- package/src/studio/routes/errors/route.tsx +3 -2
- package/src/studio/routes/logs/route.tsx +3 -2
- package/src/studio/routes/traces/route.tsx +3 -2
package/src/Route.ts
CHANGED
|
@@ -27,16 +27,16 @@ export namespace RouteDescriptor {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Iterable<M[number]>
|
|
30
|
+
export type RouteSet<
|
|
31
|
+
D extends RouteDescriptor.Any = {},
|
|
32
|
+
B = {},
|
|
33
|
+
M extends Route.Tuple = [],
|
|
34
|
+
> = RouteSet.Data<D, B, M> & {
|
|
35
|
+
[TypeId]: typeof TypeId
|
|
36
|
+
} & Pipeable.Pipeable &
|
|
37
|
+
Iterable<M[number]>
|
|
39
38
|
|
|
39
|
+
export namespace RouteSet {
|
|
40
40
|
export type Data<D extends RouteDescriptor.Any = {}, B = {}, M extends Route.Tuple = []> = {
|
|
41
41
|
[RouteItems]: M
|
|
42
42
|
[RouteDescriptor]: D
|
|
@@ -44,7 +44,7 @@ export namespace RouteSet {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export type Proto = Pipeable.Pipeable &
|
|
47
|
-
Iterable<Route
|
|
47
|
+
Iterable<Route<any, any, any, any, any>> & {
|
|
48
48
|
[TypeId]: typeof TypeId
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -58,17 +58,17 @@ export namespace RouteSet {
|
|
|
58
58
|
T extends Data<infer D, any, any> ? D : never
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
61
|
+
export interface Route<
|
|
62
|
+
D extends RouteDescriptor.Any = {},
|
|
63
|
+
B = {},
|
|
64
|
+
A = any,
|
|
65
|
+
E = never,
|
|
66
|
+
R = never,
|
|
67
|
+
> extends RouteSet<D, {}, [Route<D, B, A, E, R>]> {
|
|
68
|
+
readonly handler: Route.Handler<B & D, A, E, R>
|
|
69
|
+
}
|
|
71
70
|
|
|
71
|
+
export namespace Route {
|
|
72
72
|
export type With<D extends RouteDescriptor.Any> = Route<any, any, any, any, any> & {
|
|
73
73
|
[RouteDescriptor]: D
|
|
74
74
|
}
|
|
@@ -125,26 +125,26 @@ export function isRouteSet(input: unknown): input is RouteSet.Any {
|
|
|
125
125
|
return Predicate.hasProperty(input, TypeId)
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
export function isRoute(input: unknown): input is Route
|
|
128
|
+
export function isRoute(input: unknown): input is Route {
|
|
129
129
|
return isRouteSet(input) && Predicate.hasProperty(input, "handler")
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
export function set<D extends RouteDescriptor.Any = {}, B = {}, I extends Route.Tuple = []>(
|
|
133
133
|
items: I = [] as unknown as I,
|
|
134
134
|
descriptor: D = {} as D,
|
|
135
|
-
): RouteSet
|
|
135
|
+
): RouteSet<D, B, I> {
|
|
136
136
|
return Object.assign(Object.create(Proto), {
|
|
137
137
|
[RouteItems]: items,
|
|
138
138
|
[RouteDescriptor]: descriptor,
|
|
139
|
-
}) as RouteSet
|
|
139
|
+
}) as RouteSet<D, B, I>
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
export function make<D extends RouteDescriptor.Any, B, A, E = never, R = never>(
|
|
143
143
|
handler: Route.Handler<B & D, A, E, R>,
|
|
144
144
|
descriptor?: D,
|
|
145
|
-
): Route
|
|
145
|
+
): Route<D, B, A, E, R> {
|
|
146
146
|
const items: any = []
|
|
147
|
-
const route: Route
|
|
147
|
+
const route: Route<D, B, A, E, R> = Object.assign(Object.create(Proto), {
|
|
148
148
|
[RouteItems]: items,
|
|
149
149
|
[RouteDescriptor]: descriptor,
|
|
150
150
|
handler,
|
|
@@ -185,7 +185,7 @@ export type ExtractBindings<M extends Route.Tuple> = M extends [
|
|
|
185
185
|
infer Head,
|
|
186
186
|
...infer Tail extends Route.Tuple,
|
|
187
187
|
]
|
|
188
|
-
? Head extends Route
|
|
188
|
+
? Head extends Route<any, infer B, any, any, any>
|
|
189
189
|
? ShallowMerge<B, ExtractBindings<Tail>>
|
|
190
190
|
: ExtractBindings<Tail>
|
|
191
191
|
: {}
|
|
@@ -206,6 +206,11 @@ export * from "./RouteSchema.ts"
|
|
|
206
206
|
|
|
207
207
|
export { del, get, head, options, patch, post, put, use } from "./RouteMount.ts"
|
|
208
208
|
|
|
209
|
+
export class Request extends Context.Tag("effect-start/Route/Request")<
|
|
210
|
+
Request,
|
|
211
|
+
globalThis.Request
|
|
212
|
+
>() {}
|
|
213
|
+
|
|
209
214
|
export const text = RouteBody.build<string, "text">({
|
|
210
215
|
format: "text",
|
|
211
216
|
})
|
|
@@ -229,8 +234,8 @@ export function redirect<D extends RouteDescriptor.Any, B, I extends Route.Tuple
|
|
|
229
234
|
url: string | URL,
|
|
230
235
|
options?: { status?: 301 | 302 | 303 | 307 | 308 },
|
|
231
236
|
): (
|
|
232
|
-
self: RouteSet
|
|
233
|
-
) => RouteSet
|
|
237
|
+
self: RouteSet<D, B, I>,
|
|
238
|
+
) => RouteSet<D, B, [...I, Route<{}, {}, "", never, never>]> {
|
|
234
239
|
const route = make<{}, {}, "">(() =>
|
|
235
240
|
Effect.succeed(
|
|
236
241
|
Entity.make("", {
|
|
@@ -243,7 +248,7 @@ export function redirect<D extends RouteDescriptor.Any, B, I extends Route.Tuple
|
|
|
243
248
|
)
|
|
244
249
|
|
|
245
250
|
return (self) =>
|
|
246
|
-
set<D, B, [...I, Route
|
|
251
|
+
set<D, B, [...I, Route<{}, {}, "", never, never>]>(
|
|
247
252
|
[...items(self), route],
|
|
248
253
|
descriptor(self),
|
|
249
254
|
)
|
|
@@ -255,6 +260,20 @@ export function layer(routes: RouteTree.RouteMap | RouteTree.RouteTree) {
|
|
|
255
260
|
return Layer.sync(Routes, () => (RouteTree.isRouteTree(routes) ? routes : RouteTree.make(routes)))
|
|
256
261
|
}
|
|
257
262
|
|
|
263
|
+
export function layerMerge(routes: RouteTree.InputRouteMap | RouteTree.RouteTree) {
|
|
264
|
+
return Layer.effect(
|
|
265
|
+
Routes,
|
|
266
|
+
Effect.gen(function* () {
|
|
267
|
+
const existing = yield* Effect.serviceOption(Routes).pipe(
|
|
268
|
+
Effect.andThen(Option.getOrUndefined),
|
|
269
|
+
)
|
|
270
|
+
const tree = RouteTree.isRouteTree(routes) ? routes : RouteTree.make(routes)
|
|
271
|
+
if (!existing) return tree
|
|
272
|
+
return RouteTree.merge(existing, tree)
|
|
273
|
+
}),
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
258
277
|
/**
|
|
259
278
|
* Creates a route that short-curcits in development.
|
|
260
279
|
*
|
|
@@ -262,9 +281,9 @@ export function layer(routes: RouteTree.RouteMap | RouteTree.RouteTree) {
|
|
|
262
281
|
* we exclude them altogeteher in development.
|
|
263
282
|
*/
|
|
264
283
|
export function devOnly<D extends RouteDescriptor.Any, B, I extends Route.Tuple>(
|
|
265
|
-
self: RouteSet
|
|
266
|
-
): RouteSet
|
|
267
|
-
const route: Route
|
|
284
|
+
self: RouteSet<D, B, I>,
|
|
285
|
+
): RouteSet<D, B, [...I, Route<{ dev: true }, { dev: true }, unknown, any, any>]> {
|
|
286
|
+
const route: Route<{ dev: true }, { dev: true }, unknown, any, any> = make<
|
|
268
287
|
{ dev: true },
|
|
269
288
|
{ dev: true },
|
|
270
289
|
unknown,
|
|
@@ -280,12 +299,12 @@ export function devOnly<D extends RouteDescriptor.Any, B, I extends Route.Tuple>
|
|
|
280
299
|
{ dev: true },
|
|
281
300
|
)
|
|
282
301
|
|
|
283
|
-
const nextItems: [...I, Route
|
|
302
|
+
const nextItems: [...I, Route<{ dev: true }, { dev: true }, unknown, any, any>] = [
|
|
284
303
|
...items(self),
|
|
285
304
|
route,
|
|
286
305
|
]
|
|
287
306
|
|
|
288
|
-
return set<D, B, [...I, Route
|
|
307
|
+
return set<D, B, [...I, Route<{ dev: true }, { dev: true }, unknown, any, any>]>(
|
|
289
308
|
nextItems,
|
|
290
309
|
descriptor(self),
|
|
291
310
|
)
|
|
@@ -309,3 +328,5 @@ export function lazy<T extends RouteSet.Any>(
|
|
|
309
328
|
)
|
|
310
329
|
})
|
|
311
330
|
}
|
|
331
|
+
|
|
332
|
+
export { link } from "./RouteLink.ts"
|
package/src/RouteBody.ts
CHANGED
|
@@ -111,13 +111,13 @@ export interface BuildReturn<Value, F extends Format, Body = never> {
|
|
|
111
111
|
>(
|
|
112
112
|
handler: GeneratorHandler<NoInfer<D & B & Route.ExtractBindings<I> & { format: F }>, A, Y>,
|
|
113
113
|
): (
|
|
114
|
-
self: Route.RouteSet
|
|
115
|
-
) => Route.RouteSet
|
|
114
|
+
self: Route.RouteSet<D, B, I>,
|
|
115
|
+
) => Route.RouteSet<
|
|
116
116
|
D,
|
|
117
117
|
B,
|
|
118
118
|
[
|
|
119
119
|
...I,
|
|
120
|
-
Route.Route
|
|
120
|
+
Route.Route<
|
|
121
121
|
{ format: F },
|
|
122
122
|
{},
|
|
123
123
|
[Body] extends [never] ? A : Body,
|
|
@@ -137,11 +137,11 @@ export interface BuildReturn<Value, F extends Format, Body = never> {
|
|
|
137
137
|
>(
|
|
138
138
|
handler: HandlerInput<NoInfer<D & B & Route.ExtractBindings<I> & { format: F }>, A, E, R>,
|
|
139
139
|
): (
|
|
140
|
-
self: Route.RouteSet
|
|
141
|
-
) => Route.RouteSet
|
|
140
|
+
self: Route.RouteSet<D, B, I>,
|
|
141
|
+
) => Route.RouteSet<
|
|
142
142
|
D,
|
|
143
143
|
B,
|
|
144
|
-
[...I, Route.Route
|
|
144
|
+
[...I, Route.Route<{ format: F }, {}, [Body] extends [never] ? A : Body, E, R>]
|
|
145
145
|
>
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -163,7 +163,7 @@ export function build<Value, F extends Format>(options: {
|
|
|
163
163
|
E = never,
|
|
164
164
|
R = never,
|
|
165
165
|
>(handler: HandlerInput<NoInfer<D & B & Route.ExtractBindings<I> & { format: F }>, A, E, R>) {
|
|
166
|
-
return (self: Route.RouteSet
|
|
166
|
+
return (self: Route.RouteSet<D, B, I>) => {
|
|
167
167
|
const contentType = formatToContentType[descriptors.format]
|
|
168
168
|
const baseHandler = handle(handler)
|
|
169
169
|
const wrappedHandler: Route.Route.Handler<{ format: F }, A, E, R> = (ctx, next) =>
|
|
@@ -191,7 +191,7 @@ export function build<Value, F extends Format>(options: {
|
|
|
191
191
|
|
|
192
192
|
const route = Route.make<{ format: F }, {}, A, E, R>(wrappedHandler, descriptors)
|
|
193
193
|
|
|
194
|
-
return Route.set<D, B, [...I, Route.Route
|
|
194
|
+
return Route.set<D, B, [...I, Route.Route<{ format: F }, {}, A, E, R>]>(
|
|
195
195
|
[...Route.items(self), route],
|
|
196
196
|
Route.descriptor(self),
|
|
197
197
|
)
|
|
@@ -210,11 +210,11 @@ export function render<
|
|
|
210
210
|
>(
|
|
211
211
|
handler: GeneratorHandler<NoInfer<D & B & Route.ExtractBindings<I> & { format: "*" }>, A, Y>,
|
|
212
212
|
): (
|
|
213
|
-
self: Route.RouteSet
|
|
214
|
-
) => Route.RouteSet
|
|
213
|
+
self: Route.RouteSet<D, B, I>,
|
|
214
|
+
) => Route.RouteSet<
|
|
215
215
|
D,
|
|
216
216
|
B,
|
|
217
|
-
[...I, Route.Route
|
|
217
|
+
[...I, Route.Route<{ format: "*" }, {}, A, YieldError<Y>, YieldContext<Y>>]
|
|
218
218
|
>
|
|
219
219
|
export function render<
|
|
220
220
|
D extends Route.RouteDescriptor.Any,
|
|
@@ -226,8 +226,8 @@ export function render<
|
|
|
226
226
|
>(
|
|
227
227
|
handler: HandlerInput<NoInfer<D & B & Route.ExtractBindings<I> & { format: "*" }>, A, E, R>,
|
|
228
228
|
): (
|
|
229
|
-
self: Route.RouteSet
|
|
230
|
-
) => Route.RouteSet
|
|
229
|
+
self: Route.RouteSet<D, B, I>,
|
|
230
|
+
) => Route.RouteSet<D, B, [...I, Route.Route<{ format: "*" }, {}, A, E, R>]>
|
|
231
231
|
export function render<
|
|
232
232
|
D extends Route.RouteDescriptor.Any,
|
|
233
233
|
B extends {},
|
|
@@ -236,14 +236,14 @@ export function render<
|
|
|
236
236
|
E = never,
|
|
237
237
|
R = never,
|
|
238
238
|
>(handler: HandlerInput<NoInfer<D & B & Route.ExtractBindings<I> & { format: "*" }>, A, E, R>) {
|
|
239
|
-
return (self: Route.RouteSet
|
|
239
|
+
return (self: Route.RouteSet<D, B, I>) => {
|
|
240
240
|
const baseHandler = handle(handler)
|
|
241
241
|
const route = Route.make<{ format: "*" }, {}, A, E, R>(
|
|
242
242
|
(ctx, next) => baseHandler(ctx as D & B & Route.ExtractBindings<I> & { format: "*" }, next),
|
|
243
243
|
{ format: "*" },
|
|
244
244
|
)
|
|
245
245
|
|
|
246
|
-
return Route.set<D, B, [...I, Route.Route
|
|
246
|
+
return Route.set<D, B, [...I, Route.Route<{ format: "*" }, {}, A, E, R>]>(
|
|
247
247
|
[...Route.items(self), route],
|
|
248
248
|
Route.descriptor(self),
|
|
249
249
|
)
|
package/src/RouteHook.ts
CHANGED
|
@@ -25,8 +25,8 @@ export function filter<
|
|
|
25
25
|
const normalized = normalizeFilterHandler(filterHandler)
|
|
26
26
|
|
|
27
27
|
return function (
|
|
28
|
-
self: Route.RouteSet
|
|
29
|
-
): Route.RouteSet
|
|
28
|
+
self: Route.RouteSet<D, SB, P>,
|
|
29
|
+
): Route.RouteSet<D, SB, [...P, Route.Route<{}, BOut, unknown, E, R>]> {
|
|
30
30
|
const route = Route.make<{}, BOut, unknown, E, R>(
|
|
31
31
|
(context: BOut, next: (ctx?: Partial<BOut>) => Entity.Entity<unknown>) =>
|
|
32
32
|
Effect.gen(function* () {
|
|
@@ -39,7 +39,7 @@ export function filter<
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
return Route.set(
|
|
42
|
-
[...Route.items(self), route] as [...P, Route.Route
|
|
42
|
+
[...Route.items(self), route] as [...P, Route.Route<{}, BOut, unknown, E, R>],
|
|
43
43
|
Route.descriptor(self),
|
|
44
44
|
)
|
|
45
45
|
}
|
package/src/RouteHttp.ts
CHANGED
|
@@ -76,7 +76,7 @@ const respondError = (
|
|
|
76
76
|
|
|
77
77
|
function streamResponse(
|
|
78
78
|
stream: Stream.Stream<unknown, unknown, unknown>,
|
|
79
|
-
headers:
|
|
79
|
+
headers: globalThis.Headers,
|
|
80
80
|
status: number,
|
|
81
81
|
runtime: Runtime.Runtime<any>,
|
|
82
82
|
): Response {
|
|
@@ -92,10 +92,27 @@ function streamResponse(
|
|
|
92
92
|
)
|
|
93
93
|
return new Response(Stream.toReadableStreamRuntime(byteStream, runtime), {
|
|
94
94
|
status,
|
|
95
|
-
headers
|
|
95
|
+
headers,
|
|
96
96
|
})
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function toHeaders(entityHeaders: Entity.Headers, contentType: string): globalThis.Headers {
|
|
100
|
+
const headers = new Headers()
|
|
101
|
+
for (const key in entityHeaders) {
|
|
102
|
+
const value = entityHeaders[key]
|
|
103
|
+
if (value == null) continue
|
|
104
|
+
if (typeof value === "string") {
|
|
105
|
+
headers.set(key, value)
|
|
106
|
+
} else {
|
|
107
|
+
for (const v of value) headers.append(key, v)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!headers.has("content-type")) {
|
|
111
|
+
headers.set("content-type", contentType)
|
|
112
|
+
}
|
|
113
|
+
return headers
|
|
114
|
+
}
|
|
115
|
+
|
|
99
116
|
function toResponse(
|
|
100
117
|
entity: Entity.Entity<any>,
|
|
101
118
|
format: string | undefined,
|
|
@@ -103,7 +120,7 @@ function toResponse(
|
|
|
103
120
|
): Effect.Effect<Response, ParseResult.ParseError> {
|
|
104
121
|
const contentType = Entity.type(entity)
|
|
105
122
|
const status = entity.status ?? 200
|
|
106
|
-
const headers =
|
|
123
|
+
const headers = toHeaders(entity.headers, contentType)
|
|
107
124
|
|
|
108
125
|
if (StreamExtra.isStream(entity.body)) {
|
|
109
126
|
return Effect.succeed(streamResponse(entity.body, headers, status, runtime))
|
|
@@ -168,66 +185,49 @@ function determineSelectedFormat(
|
|
|
168
185
|
|
|
169
186
|
export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {
|
|
170
187
|
const runFork = Runtime.runFork(runtime)
|
|
188
|
+
const runSync = Runtime.runSync(runtime)
|
|
189
|
+
const inDevelopment = Option.isSome(runSync(Development.option))
|
|
171
190
|
|
|
172
191
|
return (routes: Iterable<UnboundedRouteWithMethod>): Http.WebHandler => {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const methodGroups: {
|
|
179
|
-
[method in Http.Method]?: Array<UnboundedRouteWithMethod>
|
|
180
|
-
} = {
|
|
181
|
-
GET: undefined,
|
|
182
|
-
POST: undefined,
|
|
183
|
-
PUT: undefined,
|
|
184
|
-
PATCH: undefined,
|
|
185
|
-
DELETE: undefined,
|
|
186
|
-
HEAD: undefined,
|
|
187
|
-
OPTIONS: undefined,
|
|
192
|
+
const allRoutes = Array.from(routes)
|
|
193
|
+
const methods = new Set<string>()
|
|
194
|
+
for (const route of allRoutes) {
|
|
195
|
+
const m = Route.descriptor(route).method?.toUpperCase()
|
|
196
|
+
if (m && m !== "*") methods.add(m)
|
|
188
197
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (method !== "*") {
|
|
192
|
-
methodGroups[method] = grouped[method]
|
|
193
|
-
}
|
|
198
|
+
if (methods.has("GET") && !methods.has("HEAD")) {
|
|
199
|
+
methods.add("HEAD")
|
|
194
200
|
}
|
|
195
|
-
|
|
196
|
-
if (methodGroups["GET"] !== undefined && methodGroups["HEAD"] === undefined) {
|
|
197
|
-
methodGroups["HEAD"] = methodGroups["GET"]
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const allowedMethods = Object.keys(methodGroups)
|
|
201
|
-
.filter((m) => methodGroups[m] !== undefined && methodGroups[m]!.length > 0)
|
|
202
|
-
.join(", ")
|
|
201
|
+
const allowedMethods = Array.from(methods).join(", ")
|
|
203
202
|
|
|
204
203
|
return (request) =>
|
|
205
204
|
new Promise((resolve) => {
|
|
206
205
|
const method = request.method.toUpperCase()
|
|
207
206
|
const accept = request.headers.get("accept")
|
|
208
|
-
const
|
|
207
|
+
const matchingRoutes = allRoutes.filter((route) => {
|
|
208
|
+
const m = Route.descriptor(route).method?.toUpperCase()
|
|
209
|
+
return m === "*" || m === method || (method === "HEAD" && m === "GET")
|
|
210
|
+
})
|
|
209
211
|
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
212
|
+
if (matchingRoutes.length === 0) {
|
|
213
|
+
if (method === "OPTIONS" || methods.size === 0) {
|
|
214
|
+
return resolve(
|
|
215
|
+
new Response(null, {
|
|
216
|
+
status: 204,
|
|
217
|
+
headers: { allow: allowedMethods },
|
|
218
|
+
}),
|
|
219
|
+
)
|
|
220
|
+
}
|
|
218
221
|
|
|
219
|
-
if (methodRoutes.length === 0 && wildcards.length === 0) {
|
|
220
222
|
return resolve(
|
|
221
223
|
respondError({ status: 405, message: "method not allowed" }, { allow: allowedMethods }),
|
|
222
224
|
)
|
|
223
225
|
}
|
|
224
|
-
|
|
225
|
-
const allRoutes = [...wildcards, ...methodRoutes]
|
|
226
|
-
const selectedFormat = determineSelectedFormat(accept, allRoutes)
|
|
226
|
+
const selectedFormat = determineSelectedFormat(accept, matchingRoutes)
|
|
227
227
|
|
|
228
228
|
const specificFormats = new Set<string>()
|
|
229
229
|
let hasWildcardFormatRoutes = false
|
|
230
|
-
for (const r of
|
|
230
|
+
for (const r of matchingRoutes) {
|
|
231
231
|
const format = Route.descriptor(r).format
|
|
232
232
|
if (format === "*") hasWildcardFormatRoutes = true
|
|
233
233
|
else if (format) specificFormats.add(format)
|
|
@@ -249,13 +249,13 @@ export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {
|
|
|
249
249
|
currentContext = passedContext
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
if (index >=
|
|
252
|
+
if (index >= matchingRoutes.length) {
|
|
253
253
|
return Effect.succeed(
|
|
254
254
|
Entity.make({ status: 404, message: "route not found" }, { status: 404 }),
|
|
255
255
|
)
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
const route =
|
|
258
|
+
const route = matchingRoutes[index++]
|
|
259
259
|
const descriptor = Route.descriptor(route)
|
|
260
260
|
const format = descriptor.format
|
|
261
261
|
const handler = route.handler as unknown as Handler
|
|
@@ -292,7 +292,7 @@ export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {
|
|
|
292
292
|
|
|
293
293
|
const url = new URL(request.url)
|
|
294
294
|
|
|
295
|
-
const innerEffect = Effect.gen(function* () {
|
|
295
|
+
const innerEffect = Effect.provideService(Effect.gen(function* () {
|
|
296
296
|
const result = yield* createChain({ request, selectedFormat })
|
|
297
297
|
|
|
298
298
|
const entity = Entity.isEntity(result) ? result : Entity.make(result, { status: 200 })
|
|
@@ -306,7 +306,7 @@ export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {
|
|
|
306
306
|
response.headers.set("vary", "Accept")
|
|
307
307
|
}
|
|
308
308
|
return response
|
|
309
|
-
})
|
|
309
|
+
}), Route.Request, request)
|
|
310
310
|
|
|
311
311
|
if (tracerDisabled) {
|
|
312
312
|
return innerEffect
|
|
@@ -356,7 +356,9 @@ export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {
|
|
|
356
356
|
Effect.gen(function* () {
|
|
357
357
|
yield* Effect.logError(cause)
|
|
358
358
|
const status = getStatusFromCause(cause)
|
|
359
|
-
const message =
|
|
359
|
+
const message = inDevelopment
|
|
360
|
+
? Cause.pretty(cause, { renderErrorCause: true })
|
|
361
|
+
: "Internal Server Error"
|
|
360
362
|
return respondError({ status, message })
|
|
361
363
|
}),
|
|
362
364
|
),
|
|
@@ -384,7 +386,9 @@ export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {
|
|
|
384
386
|
resolve(respondError({ status: 499, message: "client closed request" }))
|
|
385
387
|
} else {
|
|
386
388
|
const status = getStatusFromCause(exit.cause)
|
|
387
|
-
const message =
|
|
389
|
+
const message = inDevelopment
|
|
390
|
+
? Cause.pretty(exit.cause, { renderErrorCause: true })
|
|
391
|
+
: "Internal Server Error"
|
|
388
392
|
resolve(respondError({ status, message }))
|
|
389
393
|
}
|
|
390
394
|
})
|
package/src/RouteLink.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type * as PathPattern from "./_PathPattern.ts"
|
|
2
|
+
import type * as Route from "./Route.ts"
|
|
3
|
+
import type { Routes as DevRoutes } from "effect-start/dev"
|
|
4
|
+
|
|
5
|
+
export type LinkParams<
|
|
6
|
+
Routes,
|
|
7
|
+
P extends string,
|
|
8
|
+
B = P extends keyof Routes
|
|
9
|
+
? Routes[P] extends [...any[], infer L] // last route
|
|
10
|
+
? L extends () => Promise<{ default: infer R extends Route.RouteSet.Any }>
|
|
11
|
+
? Route.Route.Bindings<R>
|
|
12
|
+
: {}
|
|
13
|
+
: {}
|
|
14
|
+
: {},
|
|
15
|
+
> = {
|
|
16
|
+
[K in keyof PathPattern.Params<P>]: B extends { pathParams: infer S }
|
|
17
|
+
? K extends keyof S
|
|
18
|
+
? S[K]
|
|
19
|
+
: string | number
|
|
20
|
+
: string | number
|
|
21
|
+
} & (B extends { searchParams: infer S } ? { [K in keyof S]?: S[K] } : {})
|
|
22
|
+
|
|
23
|
+
export function link<
|
|
24
|
+
Routes = DevRoutes,
|
|
25
|
+
P extends keyof Routes extends never
|
|
26
|
+
? // Falls back to any string when no routes are registered
|
|
27
|
+
string
|
|
28
|
+
: keyof Routes & string = keyof Routes extends never ? string : keyof Routes & string,
|
|
29
|
+
>(
|
|
30
|
+
...args: {} extends LinkParams<Routes, P>
|
|
31
|
+
? [path: P, params?: LinkParams<Routes, P>]
|
|
32
|
+
: [path: P, params: LinkParams<Routes, P>]
|
|
33
|
+
): string {
|
|
34
|
+
const path = args[0] as string
|
|
35
|
+
// Substitute path params, deleting used keys so the rest become search params
|
|
36
|
+
const remaining = { ...args[1] } as Record<string, string | number | undefined>
|
|
37
|
+
const result = path.replace(
|
|
38
|
+
/\/:(\w+)([?*+])?/g,
|
|
39
|
+
(_, name: string, modifier: string | undefined) => {
|
|
40
|
+
const value = remaining[name]
|
|
41
|
+
delete remaining[name]
|
|
42
|
+
if (value == null) {
|
|
43
|
+
if (modifier === "?" || modifier === "*") return ""
|
|
44
|
+
return "/"
|
|
45
|
+
}
|
|
46
|
+
return "/" + encodeURIComponent(String(value))
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const search = new URLSearchParams()
|
|
51
|
+
for (const key in remaining) {
|
|
52
|
+
if (remaining[key] != null) search.set(key, String(remaining[key]))
|
|
53
|
+
}
|
|
54
|
+
const qs = search.toString()
|
|
55
|
+
return qs ? result + "?" + qs : result
|
|
56
|
+
}
|