effect-start 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -14
- package/src/BundleHttp.test.ts +1 -1
- package/src/Commander.test.ts +15 -15
- package/src/Commander.ts +58 -88
- package/src/EncryptedCookies.test.ts +4 -4
- package/src/FileHttpRouter.test.ts +85 -16
- package/src/FileHttpRouter.ts +119 -32
- package/src/FileRouter.ts +62 -166
- package/src/FileRouterCodegen.test.ts +252 -66
- package/src/FileRouterCodegen.ts +13 -56
- package/src/FileRouterPattern.test.ts +116 -0
- package/src/FileRouterPattern.ts +59 -0
- package/src/FileRouter_path.test.ts +63 -102
- package/src/FileSystemExtra.test.ts +226 -0
- package/src/FileSystemExtra.ts +24 -60
- package/src/HttpAppExtra.test.ts +84 -0
- package/src/HttpAppExtra.ts +399 -47
- package/src/HttpUtils.test.ts +68 -0
- package/src/HttpUtils.ts +15 -0
- package/src/HyperHtml.ts +24 -5
- package/src/JsModule.test.ts +1 -1
- package/src/NodeFileSystem.ts +764 -0
- package/src/Random.ts +59 -0
- package/src/Route.test.ts +515 -18
- package/src/Route.ts +321 -166
- package/src/RouteRender.ts +40 -0
- package/src/Router.test.ts +416 -0
- package/src/Router.ts +288 -31
- package/src/RouterPattern.test.ts +655 -0
- package/src/RouterPattern.ts +416 -0
- package/src/Start.ts +14 -52
- package/src/TestHttpClient.test.ts +29 -0
- package/src/TestHttpClient.ts +122 -73
- package/src/assets.d.ts +39 -0
- package/src/bun/BunBundle.test.ts +0 -3
- package/src/bun/BunHttpServer.test.ts +74 -0
- package/src/bun/BunHttpServer.ts +259 -0
- package/src/bun/BunHttpServer_web.ts +384 -0
- package/src/bun/BunRoute.test.ts +514 -0
- package/src/bun/BunRoute.ts +427 -0
- package/src/bun/BunRoute_bundles.test.ts +218 -0
- package/src/bun/BunRuntime.ts +33 -0
- package/src/bun/BunTailwindPlugin.test.ts +1 -1
- package/src/bun/_empty.html +1 -0
- package/src/bun/index.ts +2 -1
- package/src/index.ts +14 -14
- package/src/middlewares/BasicAuthMiddleware.test.ts +74 -0
- package/src/middlewares/BasicAuthMiddleware.ts +36 -0
- package/src/testing.ts +12 -3
- package/src/Datastar.test.ts +0 -267
- package/src/Datastar.ts +0 -68
- package/src/bun/BunFullstackServer.ts +0 -45
- package/src/bun/BunFullstackServer_httpServer.ts +0 -541
- package/src/jsx-datastar.d.ts +0 -63
package/src/Router.ts
CHANGED
|
@@ -1,44 +1,39 @@
|
|
|
1
|
-
import * as HttpApp from "@effect/platform/HttpApp"
|
|
2
|
-
import * as HttpRouter from "@effect/platform/HttpRouter"
|
|
3
1
|
import * as Context from "effect/Context"
|
|
2
|
+
import * as Data from "effect/Data"
|
|
4
3
|
import * as Effect from "effect/Effect"
|
|
5
4
|
import * as Function from "effect/Function"
|
|
6
5
|
import * as Layer from "effect/Layer"
|
|
7
|
-
import * as
|
|
6
|
+
import * as Pipeable from "effect/Pipeable"
|
|
7
|
+
import * as Predicate from "effect/Predicate"
|
|
8
8
|
import * as FileRouter from "./FileRouter.ts"
|
|
9
9
|
import * as Route from "./Route"
|
|
10
10
|
|
|
11
|
-
export
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"PUT",
|
|
15
|
-
"PATCH",
|
|
16
|
-
"DELETE",
|
|
17
|
-
"OPTIONS",
|
|
18
|
-
"HEAD",
|
|
19
|
-
] as const
|
|
11
|
+
export type RouterErrorReason =
|
|
12
|
+
| "UnsupportedPattern"
|
|
13
|
+
| "ProxyError"
|
|
20
14
|
|
|
21
|
-
export
|
|
15
|
+
export class RouterError extends Data.TaggedError("RouterError")<{
|
|
16
|
+
reason: RouterErrorReason
|
|
17
|
+
pattern: string
|
|
18
|
+
message: string
|
|
19
|
+
}> {}
|
|
22
20
|
|
|
23
21
|
export type ServerModule = {
|
|
24
|
-
default: Route.
|
|
22
|
+
default: Route.RouteSet.Default
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
export type
|
|
25
|
+
export type LazyRoute = {
|
|
28
26
|
path: `/${string}`
|
|
29
|
-
segments: readonly FileRouter.Segment[]
|
|
30
27
|
load: () => Promise<ServerModule>
|
|
28
|
+
layers?: ReadonlyArray<() => Promise<unknown>>
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
export type
|
|
34
|
-
|
|
31
|
+
export type RouterManifest = {
|
|
32
|
+
routes: readonly LazyRoute[]
|
|
33
|
+
layers?: any[]
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
export type RouterContext =
|
|
38
|
-
& RouteManifest
|
|
39
|
-
& {
|
|
40
|
-
httpRouter: HttpRouter.HttpRouter
|
|
41
|
-
}
|
|
36
|
+
export type RouterContext = RouterManifest
|
|
42
37
|
|
|
43
38
|
export class Router extends Context.Tag("effect-start/Router")<
|
|
44
39
|
Router,
|
|
@@ -46,26 +41,22 @@ export class Router extends Context.Tag("effect-start/Router")<
|
|
|
46
41
|
>() {}
|
|
47
42
|
|
|
48
43
|
export function layer(
|
|
49
|
-
manifest:
|
|
44
|
+
manifest: RouterManifest,
|
|
50
45
|
): Layer.Layer<Router, never, never> {
|
|
51
46
|
return Layer.effect(
|
|
52
47
|
Router,
|
|
53
48
|
Effect.gen(function*() {
|
|
54
|
-
const serverRoutes = manifest.modules.map((mod) => ({
|
|
55
|
-
path: mod.path,
|
|
56
|
-
load: mod.load,
|
|
57
|
-
}))
|
|
58
|
-
const httpRouter = yield* FileHttpRouter.make(serverRoutes)
|
|
59
49
|
return {
|
|
60
50
|
...manifest,
|
|
61
|
-
httpRouter,
|
|
62
51
|
}
|
|
63
52
|
}),
|
|
64
53
|
)
|
|
65
54
|
}
|
|
66
55
|
|
|
56
|
+
export const layerFiles = FileRouter.layer
|
|
57
|
+
|
|
67
58
|
export function layerPromise(
|
|
68
|
-
load: () => Promise<
|
|
59
|
+
load: () => Promise<RouterManifest>,
|
|
69
60
|
): Layer.Layer<Router, never, never> {
|
|
70
61
|
return Layer.unwrapEffect(
|
|
71
62
|
Effect.gen(function*() {
|
|
@@ -78,3 +69,269 @@ export function layerPromise(
|
|
|
78
69
|
}),
|
|
79
70
|
)
|
|
80
71
|
}
|
|
72
|
+
|
|
73
|
+
const RouterBuilderTypeId: unique symbol = Symbol.for(
|
|
74
|
+
"effect-start/RouterBuilder",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
type RouterModule = typeof import("./Router.ts")
|
|
78
|
+
|
|
79
|
+
type Self =
|
|
80
|
+
| RouterBuilder<any, any>
|
|
81
|
+
| RouterModule
|
|
82
|
+
| undefined
|
|
83
|
+
|
|
84
|
+
export type RouterEntry = {
|
|
85
|
+
path: `/${string}`
|
|
86
|
+
route: Route.RouteSet.Default
|
|
87
|
+
layers: Route.RouteLayer[]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type RouterBuilderMethods = {
|
|
91
|
+
use: typeof use
|
|
92
|
+
mount: typeof mount
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface RouterBuilder<
|
|
96
|
+
out E = never,
|
|
97
|
+
out R = never,
|
|
98
|
+
> extends Pipeable.Pipeable, RouterBuilderMethods {
|
|
99
|
+
[RouterBuilderTypeId]: typeof RouterBuilderTypeId
|
|
100
|
+
readonly entries: readonly RouterEntry[]
|
|
101
|
+
readonly globalLayers: readonly Route.RouteLayer[]
|
|
102
|
+
readonly mounts: Record<`/${string}`, Route.RouteSet.Default>
|
|
103
|
+
readonly _E: () => E
|
|
104
|
+
readonly _R: () => R
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export namespace RouterBuilder {
|
|
108
|
+
export type Any = RouterBuilder<any, any>
|
|
109
|
+
|
|
110
|
+
export type Error<T> = T extends RouterBuilder<infer E, any> ? E : never
|
|
111
|
+
export type Context<T> = T extends RouterBuilder<any, infer R> ? R : never
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const RouterBuilderProto: RouterBuilderMethods & {
|
|
115
|
+
[RouterBuilderTypeId]: typeof RouterBuilderTypeId
|
|
116
|
+
pipe: Pipeable.Pipeable["pipe"]
|
|
117
|
+
} = {
|
|
118
|
+
[RouterBuilderTypeId]: RouterBuilderTypeId,
|
|
119
|
+
|
|
120
|
+
pipe() {
|
|
121
|
+
return Pipeable.pipeArguments(this, arguments)
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
use,
|
|
125
|
+
mount,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
type ExtractRouteSetError<T> = T extends Route.RouteSet<infer Routes, any>
|
|
129
|
+
? Routes[number] extends Route.Route<any, any, infer H, any>
|
|
130
|
+
? H extends Route.RouteHandler<any, infer E, any> ? E : never
|
|
131
|
+
: never
|
|
132
|
+
: never
|
|
133
|
+
|
|
134
|
+
type ExtractRouteSetContext<T> = T extends Route.RouteSet<infer Routes, any>
|
|
135
|
+
? Routes[number] extends Route.Route<any, any, infer H, any>
|
|
136
|
+
? H extends Route.RouteHandler<any, any, infer R> ? R : never
|
|
137
|
+
: never
|
|
138
|
+
: never
|
|
139
|
+
|
|
140
|
+
function addRoute<
|
|
141
|
+
E,
|
|
142
|
+
R,
|
|
143
|
+
RouteE,
|
|
144
|
+
RouteR,
|
|
145
|
+
>(
|
|
146
|
+
builder: RouterBuilder<E, R>,
|
|
147
|
+
path: `/${string}`,
|
|
148
|
+
route: Route.RouteSet.Default,
|
|
149
|
+
): RouterBuilder<E | RouteE, R | RouteR> {
|
|
150
|
+
const existingEntry = builder.entries.find((e) => e.path === path)
|
|
151
|
+
if (existingEntry) {
|
|
152
|
+
const updatedEntry: RouterEntry = {
|
|
153
|
+
...existingEntry,
|
|
154
|
+
route: Route.merge(existingEntry.route, route),
|
|
155
|
+
}
|
|
156
|
+
return makeBuilder(
|
|
157
|
+
builder.entries.map((e) => (e.path === path ? updatedEntry : e)),
|
|
158
|
+
builder.globalLayers,
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const newEntry: RouterEntry = {
|
|
163
|
+
path,
|
|
164
|
+
route,
|
|
165
|
+
layers: [...builder.globalLayers],
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return makeBuilder([...builder.entries, newEntry], builder.globalLayers)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function addGlobalLayer<E, R>(
|
|
172
|
+
builder: RouterBuilder<E, R>,
|
|
173
|
+
layerRoute: Route.RouteLayer,
|
|
174
|
+
): RouterBuilder<E, R> {
|
|
175
|
+
const newGlobalLayers = [...builder.globalLayers, layerRoute]
|
|
176
|
+
return makeBuilder(builder.entries, newGlobalLayers)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
function findMatchingLayerRoutes(
|
|
181
|
+
route: Route.Route.Default,
|
|
182
|
+
layers: readonly Route.RouteLayer[],
|
|
183
|
+
): Route.Route.Default[] {
|
|
184
|
+
const matchingRoutes: Route.Route.Default[] = []
|
|
185
|
+
for (const layer of layers) {
|
|
186
|
+
for (const layerRoute of layer.set) {
|
|
187
|
+
if (Route.matches(layerRoute, route)) {
|
|
188
|
+
matchingRoutes.push(layerRoute)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return matchingRoutes
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function wrapWithLayerRoute(
|
|
196
|
+
innerRoute: Route.Route.Default,
|
|
197
|
+
layerRoute: Route.Route.Default,
|
|
198
|
+
): Route.Route.Default {
|
|
199
|
+
const handler: Route.RouteHandler = (context) => {
|
|
200
|
+
const contextWithNext: Route.RouteContext = {
|
|
201
|
+
...context,
|
|
202
|
+
next: () => innerRoute.handler(context),
|
|
203
|
+
}
|
|
204
|
+
return layerRoute.handler(contextWithNext)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return Route.make({
|
|
208
|
+
method: layerRoute.method,
|
|
209
|
+
media: layerRoute.media,
|
|
210
|
+
handler,
|
|
211
|
+
schemas: {},
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function applyLayersToRoute(
|
|
216
|
+
route: Route.Route.Default,
|
|
217
|
+
layers: readonly Route.RouteLayer[],
|
|
218
|
+
): Route.Route.Default {
|
|
219
|
+
const matchingLayerRoutes = findMatchingLayerRoutes(route, layers)
|
|
220
|
+
let wrappedRoute = route
|
|
221
|
+
|
|
222
|
+
for (const layerRoute of matchingLayerRoutes.reverse()) {
|
|
223
|
+
wrappedRoute = wrapWithLayerRoute(wrappedRoute, layerRoute)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return wrappedRoute
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function applyLayersToRouteSet(
|
|
230
|
+
routeSet: Route.RouteSet.Default,
|
|
231
|
+
layers: readonly Route.RouteLayer[],
|
|
232
|
+
): Route.RouteSet.Default {
|
|
233
|
+
if (layers.length === 0) {
|
|
234
|
+
return routeSet
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const wrappedRoutes = routeSet.set.map((route) =>
|
|
238
|
+
applyLayersToRoute(route, layers)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
set: wrappedRoutes,
|
|
243
|
+
schema: routeSet.schema,
|
|
244
|
+
} as unknown as Route.RouteSet.Default
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function makeBuilder<E, R>(
|
|
248
|
+
entries: readonly RouterEntry[],
|
|
249
|
+
globalLayers: readonly Route.RouteLayer[] = [],
|
|
250
|
+
): RouterBuilder<E, R> {
|
|
251
|
+
const mounts: Record<`/${string}`, Route.RouteSet.Default> = {}
|
|
252
|
+
|
|
253
|
+
for (const entry of entries) {
|
|
254
|
+
if (entry.route.set.length > 0) {
|
|
255
|
+
mounts[entry.path] = applyLayersToRouteSet(entry.route, entry.layers)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return Object.assign(Object.create(RouterBuilderProto), {
|
|
260
|
+
entries,
|
|
261
|
+
globalLayers,
|
|
262
|
+
mounts,
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function isRouterBuilder(input: unknown): input is RouterBuilder.Any {
|
|
267
|
+
return Predicate.hasProperty(input, RouterBuilderTypeId)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function use<
|
|
271
|
+
S extends Self,
|
|
272
|
+
>(
|
|
273
|
+
this: S,
|
|
274
|
+
layerRoute: Route.RouteLayer,
|
|
275
|
+
): S extends RouterBuilder<infer E, infer R> ? RouterBuilder<E, R>
|
|
276
|
+
: RouterBuilder<never, never>
|
|
277
|
+
{
|
|
278
|
+
const builder = isRouterBuilder(this)
|
|
279
|
+
? this
|
|
280
|
+
: makeBuilder<never, never>([], [])
|
|
281
|
+
return addGlobalLayer(builder, layerRoute) as any
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function mount<
|
|
285
|
+
S extends Self,
|
|
286
|
+
Routes extends Route.Route.Tuple,
|
|
287
|
+
Schemas extends Route.RouteSchemas,
|
|
288
|
+
>(
|
|
289
|
+
this: S,
|
|
290
|
+
path: `/${string}`,
|
|
291
|
+
route: Route.RouteSet<Routes, Schemas>,
|
|
292
|
+
): S extends RouterBuilder<infer E, infer R> ? RouterBuilder<
|
|
293
|
+
E | ExtractRouteSetError<Route.RouteSet<Routes, Schemas>>,
|
|
294
|
+
R | ExtractRouteSetContext<Route.RouteSet<Routes, Schemas>>
|
|
295
|
+
>
|
|
296
|
+
: RouterBuilder<
|
|
297
|
+
ExtractRouteSetError<Route.RouteSet<Routes, Schemas>>,
|
|
298
|
+
ExtractRouteSetContext<Route.RouteSet<Routes, Schemas>>
|
|
299
|
+
>
|
|
300
|
+
{
|
|
301
|
+
const builder = isRouterBuilder(this)
|
|
302
|
+
? this
|
|
303
|
+
: makeBuilder<never, never>([], [])
|
|
304
|
+
return addRoute(builder, path, route as Route.RouteSet.Default) as any
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function fromManifest(
|
|
308
|
+
manifest: RouterManifest,
|
|
309
|
+
): Effect.Effect<RouterBuilder.Any> {
|
|
310
|
+
return Effect.gen(function*() {
|
|
311
|
+
const loadedEntries = yield* Effect.forEach(
|
|
312
|
+
manifest.routes,
|
|
313
|
+
(lazyRoute) =>
|
|
314
|
+
Effect.gen(function*() {
|
|
315
|
+
const routeModule = yield* Effect.promise(() => lazyRoute.load())
|
|
316
|
+
const layerModules = lazyRoute.layers
|
|
317
|
+
? yield* Effect.forEach(
|
|
318
|
+
lazyRoute.layers,
|
|
319
|
+
(loadLayer) => Effect.promise(() => loadLayer()),
|
|
320
|
+
)
|
|
321
|
+
: []
|
|
322
|
+
|
|
323
|
+
const layers = layerModules
|
|
324
|
+
.map((m: any) => m.default)
|
|
325
|
+
.filter(Route.isRouteLayer)
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
path: lazyRoute.path,
|
|
329
|
+
route: routeModule.default,
|
|
330
|
+
layers,
|
|
331
|
+
}
|
|
332
|
+
}),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return makeBuilder(loadedEntries, [])
|
|
336
|
+
})
|
|
337
|
+
}
|