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.
Files changed (54) hide show
  1. package/package.json +15 -14
  2. package/src/BundleHttp.test.ts +1 -1
  3. package/src/Commander.test.ts +15 -15
  4. package/src/Commander.ts +58 -88
  5. package/src/EncryptedCookies.test.ts +4 -4
  6. package/src/FileHttpRouter.test.ts +85 -16
  7. package/src/FileHttpRouter.ts +119 -32
  8. package/src/FileRouter.ts +62 -166
  9. package/src/FileRouterCodegen.test.ts +252 -66
  10. package/src/FileRouterCodegen.ts +13 -56
  11. package/src/FileRouterPattern.test.ts +116 -0
  12. package/src/FileRouterPattern.ts +59 -0
  13. package/src/FileRouter_path.test.ts +63 -102
  14. package/src/FileSystemExtra.test.ts +226 -0
  15. package/src/FileSystemExtra.ts +24 -60
  16. package/src/HttpAppExtra.test.ts +84 -0
  17. package/src/HttpAppExtra.ts +399 -47
  18. package/src/HttpUtils.test.ts +68 -0
  19. package/src/HttpUtils.ts +15 -0
  20. package/src/HyperHtml.ts +24 -5
  21. package/src/JsModule.test.ts +1 -1
  22. package/src/NodeFileSystem.ts +764 -0
  23. package/src/Random.ts +59 -0
  24. package/src/Route.test.ts +515 -18
  25. package/src/Route.ts +321 -166
  26. package/src/RouteRender.ts +40 -0
  27. package/src/Router.test.ts +416 -0
  28. package/src/Router.ts +288 -31
  29. package/src/RouterPattern.test.ts +655 -0
  30. package/src/RouterPattern.ts +416 -0
  31. package/src/Start.ts +14 -52
  32. package/src/TestHttpClient.test.ts +29 -0
  33. package/src/TestHttpClient.ts +122 -73
  34. package/src/assets.d.ts +39 -0
  35. package/src/bun/BunBundle.test.ts +0 -3
  36. package/src/bun/BunHttpServer.test.ts +74 -0
  37. package/src/bun/BunHttpServer.ts +259 -0
  38. package/src/bun/BunHttpServer_web.ts +384 -0
  39. package/src/bun/BunRoute.test.ts +514 -0
  40. package/src/bun/BunRoute.ts +427 -0
  41. package/src/bun/BunRoute_bundles.test.ts +218 -0
  42. package/src/bun/BunRuntime.ts +33 -0
  43. package/src/bun/BunTailwindPlugin.test.ts +1 -1
  44. package/src/bun/_empty.html +1 -0
  45. package/src/bun/index.ts +2 -1
  46. package/src/index.ts +14 -14
  47. package/src/middlewares/BasicAuthMiddleware.test.ts +74 -0
  48. package/src/middlewares/BasicAuthMiddleware.ts +36 -0
  49. package/src/testing.ts +12 -3
  50. package/src/Datastar.test.ts +0 -267
  51. package/src/Datastar.ts +0 -68
  52. package/src/bun/BunFullstackServer.ts +0 -45
  53. package/src/bun/BunFullstackServer_httpServer.ts +0 -541
  54. 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 FileHttpRouter from "./FileHttpRouter.ts"
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 const ServerMethods = [
12
- "GET",
13
- "POST",
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 type ServerMethod = (typeof ServerMethods)[number]
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.Route | Route.RouteSet.Default
22
+ default: Route.RouteSet.Default
25
23
  }
26
24
 
27
- export type ServerRoute = {
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 RouteManifest = {
34
- modules: readonly FileRouter.RouteModule[]
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: RouteManifest,
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<RouteManifest>,
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
+ }