effect-start 0.29.0 → 0.30.1

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 (229) hide show
  1. package/README.md +1 -1
  2. package/dist/{_Development.d.ts → Development.d.ts} +2 -1
  3. package/dist/Development.d.ts.map +1 -0
  4. package/dist/{_Development.js → Development.js} +2 -1
  5. package/dist/Development.js.map +1 -0
  6. package/dist/Fetch.d.ts +0 -8
  7. package/dist/Fetch.d.ts.map +1 -1
  8. package/dist/Fetch.js +6 -22
  9. package/dist/Fetch.js.map +1 -1
  10. package/dist/FileRouter.d.ts.map +1 -1
  11. package/dist/FileRouter.js +2 -1
  12. package/dist/FileRouter.js.map +1 -1
  13. package/dist/GlobalLayer.d.ts.map +1 -1
  14. package/dist/GlobalLayer.js +1 -1
  15. package/dist/GlobalLayer.js.map +1 -1
  16. package/dist/Html.d.ts +32 -0
  17. package/dist/Html.d.ts.map +1 -0
  18. package/dist/{hyper/HyperHtml.js → Html.js} +45 -26
  19. package/dist/Html.js.map +1 -0
  20. package/dist/Route.d.ts +20 -7
  21. package/dist/Route.d.ts.map +1 -1
  22. package/dist/Route.js +24 -3
  23. package/dist/Route.js.map +1 -1
  24. package/dist/RouteBody.d.ts +13 -6
  25. package/dist/RouteBody.d.ts.map +1 -1
  26. package/dist/RouteBody.js +38 -27
  27. package/dist/RouteBody.js.map +1 -1
  28. package/dist/RouteHttp.d.ts.map +1 -1
  29. package/dist/RouteHttp.js +18 -1
  30. package/dist/RouteHttp.js.map +1 -1
  31. package/dist/RouteMount.js +1 -1
  32. package/dist/RouteMount.js.map +1 -1
  33. package/dist/System.d.ts +1 -1
  34. package/dist/System.d.ts.map +1 -1
  35. package/dist/System.js.map +1 -1
  36. package/dist/_ChildProcess.d.ts +1 -1
  37. package/dist/_ChildProcess.d.ts.map +1 -1
  38. package/dist/_ChildProcess.js.map +1 -1
  39. package/dist/bun/BunRoute.d.ts +1 -1
  40. package/dist/bun/BunRoute.d.ts.map +1 -1
  41. package/dist/bun/BunRoute.js +102 -33
  42. package/dist/bun/BunRoute.js.map +1 -1
  43. package/dist/bun/BunServer.d.ts.map +1 -1
  44. package/dist/bun/BunServer.js.map +1 -1
  45. package/dist/cloudflare/CloudflareTunnel.d.ts +12 -0
  46. package/dist/cloudflare/CloudflareTunnel.d.ts.map +1 -0
  47. package/dist/{x/cloudflare → cloudflare}/CloudflareTunnel.js +1 -1
  48. package/dist/cloudflare/CloudflareTunnel.js.map +1 -0
  49. package/dist/cloudflare/index.d.ts.map +1 -0
  50. package/dist/cloudflare/index.js.map +1 -0
  51. package/dist/index.d.ts +3 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +3 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/jsx-runtime.d.ts +8 -0
  56. package/dist/jsx-runtime.d.ts.map +1 -0
  57. package/dist/{hyper/jsx-runtime.js → jsx-runtime.js} +2 -2
  58. package/dist/jsx-runtime.js.map +1 -0
  59. package/dist/studio/routes/errors/route.d.ts.map +1 -1
  60. package/dist/studio/routes/errors/route.js +3 -4
  61. package/dist/studio/routes/errors/route.js.map +1 -1
  62. package/dist/studio/routes/fiberDetail.d.ts.map +1 -1
  63. package/dist/studio/routes/fiberDetail.js +1 -2
  64. package/dist/studio/routes/fiberDetail.js.map +1 -1
  65. package/dist/studio/routes/fibers/route.d.ts.map +1 -1
  66. package/dist/studio/routes/fibers/route.js +3 -4
  67. package/dist/studio/routes/fibers/route.js.map +1 -1
  68. package/dist/studio/routes/logs/route.d.ts.map +1 -1
  69. package/dist/studio/routes/logs/route.js +3 -4
  70. package/dist/studio/routes/logs/route.js.map +1 -1
  71. package/dist/studio/routes/metrics/route.d.ts.map +1 -1
  72. package/dist/studio/routes/metrics/route.js +3 -4
  73. package/dist/studio/routes/metrics/route.js.map +1 -1
  74. package/dist/studio/routes/route.d.ts +1 -1
  75. package/dist/studio/routes/routes/route.d.ts.map +1 -1
  76. package/dist/studio/routes/routes/route.js +1 -2
  77. package/dist/studio/routes/routes/route.js.map +1 -1
  78. package/dist/studio/routes/services/route.d.ts.map +1 -1
  79. package/dist/studio/routes/services/route.js +1 -2
  80. package/dist/studio/routes/services/route.js.map +1 -1
  81. package/dist/studio/routes/system/route.d.ts.map +1 -1
  82. package/dist/studio/routes/system/route.js +3 -4
  83. package/dist/studio/routes/system/route.js.map +1 -1
  84. package/dist/studio/routes/traceDetail.d.ts.map +1 -1
  85. package/dist/studio/routes/traceDetail.js +1 -2
  86. package/dist/studio/routes/traceDetail.js.map +1 -1
  87. package/dist/studio/routes/traces/route.d.ts.map +1 -1
  88. package/dist/studio/routes/traces/route.js +3 -4
  89. package/dist/studio/routes/traces/route.js.map +1 -1
  90. package/dist/studio/routes/tree.d.ts +1 -1
  91. package/dist/studio/ui/Errors.d.ts +1 -1
  92. package/dist/studio/ui/Errors.d.ts.map +1 -1
  93. package/dist/studio/ui/Fibers.d.ts +2 -2
  94. package/dist/studio/ui/Fibers.d.ts.map +1 -1
  95. package/dist/studio/ui/Logs.d.ts +1 -1
  96. package/dist/studio/ui/Logs.d.ts.map +1 -1
  97. package/dist/studio/ui/Metrics.d.ts +1 -1
  98. package/dist/studio/ui/Metrics.d.ts.map +1 -1
  99. package/dist/studio/ui/Routes.d.ts +1 -1
  100. package/dist/studio/ui/Routes.d.ts.map +1 -1
  101. package/dist/studio/ui/Services.d.ts +1 -1
  102. package/dist/studio/ui/Services.d.ts.map +1 -1
  103. package/dist/studio/ui/Shell.d.ts +2 -2
  104. package/dist/studio/ui/Shell.d.ts.map +1 -1
  105. package/dist/studio/ui/System.d.ts +1 -1
  106. package/dist/studio/ui/System.d.ts.map +1 -1
  107. package/dist/studio/ui/Traces.d.ts +3 -3
  108. package/dist/studio/ui/Traces.d.ts.map +1 -1
  109. package/dist/tailscale/TailscaleTunnel.d.ts +16 -0
  110. package/dist/tailscale/TailscaleTunnel.d.ts.map +1 -0
  111. package/dist/{x/tailscale → tailscale}/TailscaleTunnel.js +2 -2
  112. package/dist/tailscale/TailscaleTunnel.js.map +1 -0
  113. package/dist/tailscale/index.d.ts.map +1 -0
  114. package/dist/tailscale/index.js.map +1 -0
  115. package/dist/tailwind/TailwindPlugin.d.ts.map +1 -0
  116. package/dist/tailwind/TailwindPlugin.js.map +1 -0
  117. package/dist/tailwind/compile.d.ts.map +1 -0
  118. package/dist/{x/tailwind → tailwind}/compile.js +1 -1
  119. package/dist/tailwind/compile.js.map +1 -0
  120. package/dist/tailwind/index.d.ts +3 -0
  121. package/dist/tailwind/index.d.ts.map +1 -0
  122. package/dist/tailwind/index.js +3 -0
  123. package/dist/tailwind/index.js.map +1 -0
  124. package/dist/tailwind/plugin.d.ts.map +1 -0
  125. package/dist/{x/tailwind → tailwind}/plugin.js +1 -1
  126. package/dist/tailwind/plugin.js.map +1 -0
  127. package/package.json +37 -37
  128. package/src/{_Development.ts → Development.ts} +5 -0
  129. package/src/Fetch.ts +10 -37
  130. package/src/FileRouter.ts +2 -1
  131. package/src/GlobalLayer.ts +3 -1
  132. package/src/{hyper/HyperHtml.ts → Html.ts} +90 -30
  133. package/src/Route.ts +67 -11
  134. package/src/RouteBody.ts +87 -62
  135. package/src/RouteHttp.ts +19 -1
  136. package/src/RouteMount.ts +1 -1
  137. package/src/System.ts +1 -1
  138. package/src/_ChildProcess.ts +1 -1
  139. package/src/bun/BunRoute.ts +125 -37
  140. package/src/bun/BunServer.ts +1 -0
  141. package/src/{x/cloudflare → cloudflare}/CloudflareTunnel.ts +1 -1
  142. package/src/index.ts +3 -1
  143. package/src/jsx-runtime.ts +15 -0
  144. package/src/{hyper/jsx.d.ts → jsx.d.ts} +3 -3
  145. package/src/studio/routes/errors/route.tsx +3 -4
  146. package/src/studio/routes/fiberDetail.tsx +1 -2
  147. package/src/studio/routes/fibers/route.tsx +3 -4
  148. package/src/studio/routes/logs/route.tsx +3 -4
  149. package/src/studio/routes/metrics/route.tsx +3 -4
  150. package/src/studio/routes/routes/route.tsx +1 -2
  151. package/src/studio/routes/services/route.tsx +1 -2
  152. package/src/studio/routes/system/route.tsx +3 -4
  153. package/src/studio/routes/traceDetail.tsx +1 -2
  154. package/src/studio/routes/traces/route.tsx +3 -4
  155. package/src/{x/tailscale → tailscale}/TailscaleTunnel.ts +2 -2
  156. package/src/{x/tailwind → tailwind}/compile.ts +1 -1
  157. package/src/tailwind/index.ts +2 -0
  158. package/src/{x/tailwind → tailwind}/plugin.ts +1 -1
  159. package/dist/_Development.d.ts.map +0 -1
  160. package/dist/_Development.js.map +0 -1
  161. package/dist/hyper/Hyper.d.ts +0 -26
  162. package/dist/hyper/Hyper.d.ts.map +0 -1
  163. package/dist/hyper/Hyper.js +0 -24
  164. package/dist/hyper/Hyper.js.map +0 -1
  165. package/dist/hyper/HyperHtml.d.ts +0 -24
  166. package/dist/hyper/HyperHtml.d.ts.map +0 -1
  167. package/dist/hyper/HyperHtml.js.map +0 -1
  168. package/dist/hyper/HyperHtml.test.d.ts +0 -2
  169. package/dist/hyper/HyperHtml.test.d.ts.map +0 -1
  170. package/dist/hyper/HyperHtml.test.js +0 -283
  171. package/dist/hyper/HyperHtml.test.js.map +0 -1
  172. package/dist/hyper/HyperNode.d.ts +0 -14
  173. package/dist/hyper/HyperNode.d.ts.map +0 -1
  174. package/dist/hyper/HyperNode.js +0 -12
  175. package/dist/hyper/HyperNode.js.map +0 -1
  176. package/dist/hyper/HyperRoute.d.ts +0 -9
  177. package/dist/hyper/HyperRoute.d.ts.map +0 -1
  178. package/dist/hyper/HyperRoute.js +0 -33
  179. package/dist/hyper/HyperRoute.js.map +0 -1
  180. package/dist/hyper/HyperRoute.test.d.ts +0 -2
  181. package/dist/hyper/HyperRoute.test.d.ts.map +0 -1
  182. package/dist/hyper/HyperRoute.test.js +0 -84
  183. package/dist/hyper/HyperRoute.test.js.map +0 -1
  184. package/dist/hyper/html.d.ts +0 -12
  185. package/dist/hyper/html.d.ts.map +0 -1
  186. package/dist/hyper/html.js +0 -31
  187. package/dist/hyper/html.js.map +0 -1
  188. package/dist/hyper/index.d.ts +0 -7
  189. package/dist/hyper/index.d.ts.map +0 -1
  190. package/dist/hyper/index.js +0 -6
  191. package/dist/hyper/index.js.map +0 -1
  192. package/dist/hyper/jsx-runtime.d.ts +0 -8
  193. package/dist/hyper/jsx-runtime.d.ts.map +0 -1
  194. package/dist/hyper/jsx-runtime.js.map +0 -1
  195. package/dist/x/cloudflare/CloudflareTunnel.d.ts +0 -12
  196. package/dist/x/cloudflare/CloudflareTunnel.d.ts.map +0 -1
  197. package/dist/x/cloudflare/CloudflareTunnel.js.map +0 -1
  198. package/dist/x/cloudflare/index.d.ts.map +0 -1
  199. package/dist/x/cloudflare/index.js.map +0 -1
  200. package/dist/x/tailscale/TailscaleTunnel.d.ts +0 -16
  201. package/dist/x/tailscale/TailscaleTunnel.d.ts.map +0 -1
  202. package/dist/x/tailscale/TailscaleTunnel.js.map +0 -1
  203. package/dist/x/tailscale/index.d.ts.map +0 -1
  204. package/dist/x/tailscale/index.js.map +0 -1
  205. package/dist/x/tailwind/TailwindPlugin.d.ts.map +0 -1
  206. package/dist/x/tailwind/TailwindPlugin.js.map +0 -1
  207. package/dist/x/tailwind/compile.d.ts.map +0 -1
  208. package/dist/x/tailwind/compile.js.map +0 -1
  209. package/dist/x/tailwind/plugin.d.ts.map +0 -1
  210. package/dist/x/tailwind/plugin.js.map +0 -1
  211. package/src/hyper/Hyper.ts +0 -55
  212. package/src/hyper/HyperHtml.test.tsx +0 -395
  213. package/src/hyper/HyperNode.ts +0 -33
  214. package/src/hyper/HyperRoute.test.tsx +0 -166
  215. package/src/hyper/HyperRoute.ts +0 -59
  216. package/src/hyper/html.ts +0 -47
  217. package/src/hyper/index.ts +0 -6
  218. package/src/hyper/jsx-runtime.ts +0 -15
  219. /package/dist/{x/cloudflare → cloudflare}/index.d.ts +0 -0
  220. /package/dist/{x/cloudflare → cloudflare}/index.js +0 -0
  221. /package/dist/{x/tailscale → tailscale}/index.d.ts +0 -0
  222. /package/dist/{x/tailscale → tailscale}/index.js +0 -0
  223. /package/dist/{x/tailwind → tailwind}/TailwindPlugin.d.ts +0 -0
  224. /package/dist/{x/tailwind → tailwind}/TailwindPlugin.js +0 -0
  225. /package/dist/{x/tailwind → tailwind}/compile.d.ts +0 -0
  226. /package/dist/{x/tailwind → tailwind}/plugin.d.ts +0 -0
  227. /package/src/{x/cloudflare → cloudflare}/index.ts +0 -0
  228. /package/src/{x/tailscale → tailscale}/index.ts +0 -0
  229. /package/src/{x/tailwind → tailwind}/TailwindPlugin.ts +0 -0
package/src/RouteBody.ts CHANGED
@@ -3,6 +3,7 @@ import type * as Stream from "effect/Stream"
3
3
  import type * as Utils from "effect/Utils"
4
4
  import * as Entity from "./Entity.ts"
5
5
  import * as Route from "./Route.ts"
6
+ import * as StreamExtra from "./_StreamExtra.ts"
6
7
  import type * as Values from "./_Values.ts"
7
8
 
8
9
  export type Format = "text" | "html" | "json" | "bytes" | "*"
@@ -21,21 +22,36 @@ type YieldError<T> = T extends Utils.YieldWrap<Effect.Effect<any, infer E, any>>
21
22
 
22
23
  type YieldContext<T> = T extends Utils.YieldWrap<Effect.Effect<any, any, infer R>> ? R : never
23
24
 
25
+ type Next<B, A> = (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>
26
+
27
+ type HandlerReturn<A> =
28
+ | A
29
+ | Entity.Entity<A>
30
+ | ((self: Route.RouteSet.Any) => Route.RouteSet.Any)
31
+
32
+ type HandlerFunction<B, A, E, R> = (
33
+ context: Values.Simplify<B>,
34
+ next: Next<B, A>,
35
+ ) =>
36
+ | Effect.Effect<HandlerReturn<A>, E, R>
37
+ | Generator<Utils.YieldWrap<Effect.Effect<unknown, E, R>>, HandlerReturn<A>, unknown>
38
+
24
39
  export type GeneratorHandler<B, A, Y> = (
25
40
  context: Values.Simplify<B>,
26
- next: (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>,
27
- ) => Generator<Y, A | Entity.Entity<A>, never>
41
+ next: Next<B, A>,
42
+ ) => Generator<Y, HandlerReturn<A>, never>
28
43
 
29
44
  export type HandlerInput<B, A, E, R> =
30
45
  | A
31
46
  | Entity.Entity<A>
32
- | Effect.Effect<A | Entity.Entity<A>, E, R>
33
- | ((
34
- context: Values.Simplify<B>,
35
- next: (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>,
36
- ) =>
37
- | Effect.Effect<A | Entity.Entity<A>, E, R>
38
- | Generator<Utils.YieldWrap<Effect.Effect<unknown, E, R>>, A | Entity.Entity<A>, unknown>)
47
+ | Effect.Effect<HandlerReturn<A>, E, R>
48
+ | HandlerFunction<B, A, E, R>
49
+
50
+ function isHandlerFunction<B, A, E, R>(
51
+ handler: HandlerInput<B, A, E, R>,
52
+ ): handler is HandlerFunction<B, A, E, R> {
53
+ return typeof handler === "function"
54
+ }
39
55
 
40
56
  export function handle<B, A, Y extends Utils.YieldWrap<Effect.Effect<any, any, any>>>(
41
57
  handler: GeneratorHandler<B, A, Y>,
@@ -46,38 +62,44 @@ export function handle<B, A, E, R>(
46
62
  export function handle<B, A, E, R>(
47
63
  handler: HandlerInput<B, A, E, R>,
48
64
  ): Route.Route.Handler<B, A, E, R> {
49
- if (typeof handler === "function") {
50
- return (
51
- context: B,
52
- next: (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<A>,
53
- ): Effect.Effect<Entity.Entity<A>, E, R> => {
54
- const result = (handler as Function)(context, next)
65
+ if (isHandlerFunction(handler)) {
66
+ return ((context: any, next: any) => {
67
+ const result = handler(context, next)
55
68
  const effect = Effect.isEffect(result)
56
- ? (result as Effect.Effect<A | Entity.Entity<A>, E, R>)
57
- : (Effect.gen(function* () {
69
+ ? result
70
+ : Effect.gen(function* () {
58
71
  return yield* result
59
- }) as Effect.Effect<A | Entity.Entity<A>, E, R>)
60
- return Effect.map(effect, normalizeToEntity)
61
- }
72
+ })
73
+ return Effect.flatMap(effect, normalizeToEntity)
74
+ }) as Route.Route.Handler<B, A, E, R>
62
75
  }
63
76
  if (Effect.isEffect(handler)) {
64
- return (_context, _next) =>
65
- Effect.map(handler, normalizeToEntity) as Effect.Effect<Entity.Entity<A>, E, R>
77
+ return ((_context: any, _next: any) =>
78
+ Effect.flatMap(handler, normalizeToEntity)) as Route.Route.Handler<B, A, E, R>
66
79
  }
67
80
  if (Entity.isEntity(handler)) {
68
81
  return (_context, _next) => Effect.succeed(handler as Entity.Entity<A>)
69
82
  }
70
- return (_context, _next) => Effect.succeed(normalizeToEntity(handler as A) as Entity.Entity<A>)
83
+ return ((_context: any, _next: any) =>
84
+ normalizeToEntity(handler)) as Route.Route.Handler<B, A, E, R>
71
85
  }
72
86
 
73
- function normalizeToEntity<A>(value: A | Entity.Entity<A>): Entity.Entity<A> {
87
+ function normalizeToEntity(value: unknown): Effect.Effect<Entity.Entity<any>> {
88
+ if (typeof value === "function") {
89
+ const result = (value as (self: Route.RouteSet.Any) => Route.RouteSet.Any)(Route.empty)
90
+ const routes = Route.items(result)
91
+ const route = routes[0]
92
+ if (route) {
93
+ return route.handler({}, () => Entity.make("")) as Effect.Effect<Entity.Entity<any>>
94
+ }
95
+ }
74
96
  if (Entity.isEntity(value)) {
75
- return value as Entity.Entity<A>
97
+ return Effect.succeed(value)
76
98
  }
77
- return Entity.make(value as A, { status: 200 })
99
+ return Effect.succeed(Entity.make(value, { status: 200 }))
78
100
  }
79
101
 
80
- export interface BuildReturn<Value, F extends Format> {
102
+ export interface BuildReturn<Value, F extends Format, Body = never> {
81
103
  <
82
104
  D extends Route.RouteDescriptor.Any,
83
105
  B,
@@ -91,7 +113,7 @@ export interface BuildReturn<Value, F extends Format> {
91
113
  ) => Route.RouteSet.RouteSet<
92
114
  D,
93
115
  B,
94
- [...I, Route.Route.Route<{ format: F }, {}, A, YieldError<Y>, YieldContext<Y>>]
116
+ [...I, Route.Route.Route<{ format: F }, {}, [Body] extends [never] ? A : Body, YieldError<Y>, YieldContext<Y>>]
95
117
  >
96
118
 
97
119
  <
@@ -105,50 +127,54 @@ export interface BuildReturn<Value, F extends Format> {
105
127
  handler: HandlerInput<NoInfer<D & B & Route.ExtractBindings<I> & { format: F }>, A, E, R>,
106
128
  ): (
107
129
  self: Route.RouteSet.RouteSet<D, B, I>,
108
- ) => Route.RouteSet.RouteSet<D, B, [...I, Route.Route.Route<{ format: F }, {}, A, E, R>]>
130
+ ) => Route.RouteSet.RouteSet<D, B, [...I, Route.Route.Route<{ format: F }, {}, [Body] extends [never] ? A : Body, E, R>]>
109
131
  }
110
132
 
111
- export function build<Value, F extends Format>(descriptors: { format: F }) {
133
+ export function build<Value, F extends Format>(options: { format: F }): BuildReturn<Value, F>
134
+ export function build<Value, Body, F extends Format>(options: {
135
+ format: F
136
+ handle: (body: Value) => Body
137
+ }): BuildReturn<Value, F, Body>
138
+ export function build<Value, F extends Format>(options: {
139
+ format: F
140
+ handle?: (body: any) => any
141
+ }): any {
142
+ const { handle: handleBody, ...descriptors } = options
112
143
  return function <
113
144
  D extends Route.RouteDescriptor.Any,
114
- B extends {},
145
+ B,
115
146
  I extends Route.Route.Tuple,
116
147
  A extends F extends "json" ? Value : Value | Stream.Stream<Value, any, any>,
117
148
  E = never,
118
149
  R = never,
119
150
  >(handler: HandlerInput<NoInfer<D & B & Route.ExtractBindings<I> & { format: F }>, A, E, R>) {
120
- return function (self: Route.RouteSet.RouteSet<D, B, I>) {
151
+ return (self: Route.RouteSet.RouteSet<D, B, I>) => {
121
152
  const contentType = formatToContentType[descriptors.format]
122
153
  const baseHandler = handle(handler)
123
- const wrappedHandler: Route.Route.Handler<
124
- D & B & Route.ExtractBindings<I> & { format: F },
125
- A,
126
- E,
127
- R
128
- > = (ctx, next) =>
129
- Effect.map(baseHandler(ctx as any, next as any), (entity) =>
130
- entity.headers["content-type"]
131
- ? entity
132
- : Entity.make(entity.body, {
133
- status: entity.status,
134
- url: entity.url,
135
- headers: { ...entity.headers, "content-type": contentType },
136
- }),
154
+ const wrappedHandler: Route.Route.Handler<{ format: F }, A, E, R> = (ctx, next) =>
155
+ baseHandler(ctx as D & B & Route.ExtractBindings<I> & { format: F }, next).pipe(
156
+ Effect.map((entity) => {
157
+ const body = handleBody && !StreamExtra.isStream(entity.body) ? handleBody(entity.body) : entity.body
158
+ if (body === entity.body && (entity.headers["content-type"] || contentType === undefined))
159
+ return entity
160
+ return Entity.make(body as A, {
161
+ status: entity.status,
162
+ url: entity.url,
163
+ headers: entity.headers["content-type"] || contentType === undefined
164
+ ? entity.headers
165
+ : { ...entity.headers, "content-type": contentType },
166
+ })
167
+ }),
137
168
  )
138
169
 
139
- const route = Route.make<{ format: F }, {}, A, E, R>(wrappedHandler as any, descriptors)
140
-
141
- const items: [...I, Route.Route.Route<{ format: F }, {}, A, E, R>] = [
142
- ...Route.items(self),
143
- route,
144
- ]
170
+ const route = Route.make<{ format: F }, {}, A, E, R>(wrappedHandler, descriptors)
145
171
 
146
172
  return Route.set<D, B, [...I, Route.Route.Route<{ format: F }, {}, A, E, R>]>(
147
- items,
173
+ [...Route.items(self), route],
148
174
  Route.descriptor(self),
149
175
  )
150
176
  }
151
- } as unknown as BuildReturn<Value, F>
177
+ } as BuildReturn<Value, F>
152
178
  }
153
179
 
154
180
  export type RenderValue = string | Uint8Array | Stream.Stream<string | Uint8Array, any, any>
@@ -188,16 +214,15 @@ export function render<
188
214
  E = never,
189
215
  R = never,
190
216
  >(handler: HandlerInput<NoInfer<D & B & Route.ExtractBindings<I> & { format: "*" }>, A, E, R>) {
191
- return function (self: Route.RouteSet.RouteSet<D, B, I>) {
192
- const route = Route.make<{ format: "*" }, {}, A, E, R>(handle(handler) as any, { format: "*" })
193
-
194
- const items: [...I, Route.Route.Route<{ format: "*" }, {}, A, E, R>] = [
195
- ...Route.items(self),
196
- route,
197
- ]
217
+ return (self: Route.RouteSet.RouteSet<D, B, I>) => {
218
+ const baseHandler = handle(handler)
219
+ const route = Route.make<{ format: "*" }, {}, A, E, R>(
220
+ (ctx, next) => baseHandler(ctx as D & B & Route.ExtractBindings<I> & { format: "*" }, next),
221
+ { format: "*" },
222
+ )
198
223
 
199
224
  return Route.set<D, B, [...I, Route.Route.Route<{ format: "*" }, {}, A, E, R>]>(
200
- items,
225
+ [...Route.items(self), route],
201
226
  Route.descriptor(self),
202
227
  )
203
228
  }
package/src/RouteHttp.ts CHANGED
@@ -8,6 +8,7 @@ import type * as ParseResult from "effect/ParseResult"
8
8
  import * as Runtime from "effect/Runtime"
9
9
  import * as Stream from "effect/Stream"
10
10
  import * as ContentNegotiation from "./_ContentNegotiation.ts"
11
+ import * as Development from "./Development.ts"
11
12
  import * as Entity from "./Entity.ts"
12
13
  import type * as Http from "./_Http.ts"
13
14
  import * as Route from "./Route.ts"
@@ -388,9 +389,26 @@ export function* walkHandles(
388
389
  runtime: Runtime.Runtime<never> = Runtime.defaultRuntime,
389
390
  ): Generator<[path: string, handler: Http.WebHandler]> {
390
391
  const pathGroups = new Map<string, Array<RouteMount.MountedRoute>>()
392
+ const runSync = Runtime.runSync(runtime)
393
+ const inDevelopment = Option.isSome(runSync(Development.option))
394
+ const developmentPaths = new Set<string>()
391
395
 
392
396
  for (const route of RouteTree.walk(tree)) {
393
- const path = Route.descriptor(route).path
397
+ const descriptor = Route.descriptor<{ path: string; dev?: boolean }>(route)
398
+ if (descriptor.dev === true) {
399
+ developmentPaths.add(descriptor.path)
400
+ }
401
+ }
402
+
403
+ for (const route of RouteTree.walk(tree)) {
404
+ const descriptor = Route.descriptor<{ path: string; dev?: boolean }>(route)
405
+ if (descriptor.dev === true) {
406
+ continue
407
+ }
408
+ const path = descriptor.path
409
+ if (!inDevelopment && developmentPaths.has(path)) {
410
+ continue
411
+ }
394
412
  const group = pathGroups.get(path) ?? []
395
413
  group.push(route)
396
414
  pathGroups.set(path, group)
package/src/RouteMount.ts CHANGED
@@ -83,7 +83,7 @@ function makeMethodDescriber<M extends RouteMount.Method>(method: M): RouteMount
83
83
  const result = f(methodSet)
84
84
  const resultItems = Route.items(result)
85
85
 
86
- // Items are already flat (only Routes), just merge method into each descriptor
86
+ // Items are already flat (only Routes), merge method into each descriptor
87
87
  const flattenedItems = resultItems.map((item) => {
88
88
  const itemDescriptor = Route.descriptor(item)
89
89
  const newDescriptor = { method, ...itemDescriptor }
package/src/System.ts CHANGED
@@ -67,7 +67,7 @@ export const which = (name: string): Effect.Effect<string, SystemError> =>
67
67
  )
68
68
 
69
69
  export const spawn = (
70
- cmd: readonly [string, ...Array<string>],
70
+ cmd: [string, ...Array<string>] | string[],
71
71
  options?: ChildProcess.Command.Options,
72
72
  ): Effect.Effect<
73
73
  ChildProcess.ChildProcessHandle,
@@ -57,7 +57,7 @@ const CommandProto = {
57
57
  export const isCommand = (u: unknown): u is Command => Predicate.hasProperty(u, TypeId)
58
58
 
59
59
  export const make = (
60
- cmd: readonly [string, ...Array<string>],
60
+ cmd: [string, ...Array<string>] | string[],
61
61
  options?: Command.Options,
62
62
  ): Command =>
63
63
  Object.assign(Object.create(CommandProto), {
@@ -1,11 +1,12 @@
1
- import type * as Bun from "bun"
1
+ import * as Bun from "bun"
2
+ import * as NPath from "node:path"
2
3
  import * as Data from "effect/Data"
3
4
  import * as Either from "effect/Either"
4
5
  import * as Effect from "effect/Effect"
6
+ import * as FiberRef from "effect/FiberRef"
5
7
  import * as Option from "effect/Option"
6
8
  import * as Entity from "../Entity.ts"
7
- import * as Hyper from "../hyper/Hyper.ts"
8
- import * as HyperHtml from "../hyper/HyperHtml.ts"
9
+ import * as Html from "../Html.ts"
9
10
  import * as PathPattern from "../_PathPattern.ts"
10
11
  import * as Route from "../Route.ts"
11
12
  import * as Unique from "../Unique.ts"
@@ -36,6 +37,8 @@ export function descriptors(
36
37
 
37
38
  type HTMLBundleModule = Bun.HTMLBundle | { default: Bun.HTMLBundle }
38
39
 
40
+ const bundleDepthRef = FiberRef.unsafeMake(0)
41
+
39
42
  export function htmlBundle(load: () => HTMLBundleModule | Promise<HTMLBundleModule>) {
40
43
  const bunPrefix = `/.BunRoute-${Unique.token(10)}`
41
44
  const bunLoad = () =>
@@ -66,6 +69,7 @@ export function htmlBundle(load: () => HTMLBundleModule | Promise<HTMLBundleModu
66
69
  > = (context, next) =>
67
70
  Effect.gen(function* () {
68
71
  const originalRequest = context.request
72
+ const bundleDepth = yield* FiberRef.get(bundleDepthRef)
69
73
 
70
74
  if (originalRequest.headers.get(INTERNAL_FETCH_HEADER) === "true") {
71
75
  const url = new URL(originalRequest.url)
@@ -79,60 +83,83 @@ export function htmlBundle(load: () => HTMLBundleModule | Promise<HTMLBundleModu
79
83
  )
80
84
  }
81
85
 
82
- const bunServer = yield* BunServer.BunServer
83
- const url = new URL(originalRequest.url)
86
+ let html = ""
87
+ let status = 200
88
+ let contentType = "text/html;charset=utf-8"
84
89
 
85
- const internalPath = `${bunPrefix}${url.pathname}`
86
- const internalUrl = new URL(internalPath, bunServer.server.url)
90
+ if (bundleDepth === 0) {
91
+ const bunServer = yield* BunServer.BunServer
92
+ const url = new URL(originalRequest.url)
87
93
 
88
- const headers = new Headers(originalRequest.headers)
89
- headers.set(INTERNAL_FETCH_HEADER, "true")
94
+ const internalPath = `${bunPrefix}${url.pathname}`
95
+ const internalUrl = new URL(internalPath, bunServer.server.url)
90
96
 
91
- const proxyRequest = new Request(internalUrl, {
92
- method: originalRequest.method,
93
- headers,
94
- })
97
+ const headers = new Headers(originalRequest.headers)
98
+ headers.set(INTERNAL_FETCH_HEADER, "true")
95
99
 
96
- const response = yield* Effect.tryPromise({
97
- try: () => fetch(proxyRequest),
98
- catch: (error) =>
99
- new BunRouteError({
100
- reason: "ProxyError",
101
- pattern: internalPath,
102
- message: `Failed to fetch internal HTML bundle: ${String(error)}`,
103
- }),
104
- })
100
+ const proxyRequest = new Request(internalUrl, {
101
+ method: originalRequest.method,
102
+ headers,
103
+ })
105
104
 
106
- let html = yield* Effect.tryPromise({
107
- try: () => response.text(),
108
- catch: (error) =>
109
- new BunRouteError({
110
- reason: "ProxyError",
111
- pattern: internalPath,
112
- message: String(error),
113
- }),
114
- })
105
+ const response = yield* Effect.tryPromise({
106
+ try: () => fetch(proxyRequest),
107
+ catch: (error) =>
108
+ new BunRouteError({
109
+ reason: "ProxyError",
110
+ pattern: internalPath,
111
+ message: `Failed to fetch internal HTML bundle: ${String(error)}`,
112
+ }),
113
+ })
115
114
 
116
- const childEntity = yield* next(context)
115
+ html = yield* Effect.tryPromise({
116
+ try: () => response.text(),
117
+ catch: (error) =>
118
+ new BunRouteError({
119
+ reason: "ProxyError",
120
+ pattern: internalPath,
121
+ message: String(error),
122
+ }),
123
+ })
124
+ status = response.status
125
+ contentType = response.headers.get("content-type") ?? contentType
126
+ } else {
127
+ html = yield* readBundleHtml(bunLoad).pipe(
128
+ Effect.mapError(
129
+ (error) =>
130
+ new BunRouteError({
131
+ reason: "ProxyError",
132
+ pattern: bunPrefix,
133
+ message: `Failed to load nested HTML bundle: ${String(error)}`,
134
+ }),
135
+ ),
136
+ )
137
+ }
138
+
139
+ const childEntity = yield* next(context).pipe(
140
+ Effect.locally(bundleDepthRef, bundleDepth + 1),
141
+ )
117
142
  const children = childEntity?.body ?? childEntity
118
143
 
119
144
  let childrenHtml = ""
120
145
  if (children != null) {
121
146
  if ((children as unknown) instanceof Response) {
122
147
  childrenHtml = yield* Effect.promise(() => (children as unknown as Response).text())
123
- } else if (Hyper.isGenericJsxObject(children)) {
124
- childrenHtml = HyperHtml.renderToString(children)
148
+ } else if (Html.isGenericJsxObject(children)) {
149
+ childrenHtml = Html.renderToString(children)
125
150
  } else {
126
151
  childrenHtml = String(children)
127
152
  }
128
153
  }
129
154
 
130
- html = html.replace(/%children%/g, childrenHtml)
155
+ childrenHtml = yield* stripInjectedBunScripts(childrenHtml)
156
+
157
+ html = html.replaceAll("%children%", childrenHtml)
131
158
 
132
159
  return Entity.make(html, {
133
- status: response.status,
160
+ status,
134
161
  headers: {
135
- "content-type": response.headers.get("content-type"),
162
+ "content-type": contentType,
136
163
  },
137
164
  })
138
165
  })
@@ -149,6 +176,67 @@ export function htmlBundle(load: () => HTMLBundleModule | Promise<HTMLBundleModu
149
176
  }
150
177
  }
151
178
 
179
+ function stripInjectedBunScripts(html: string) {
180
+ let removeNextInlineScript = false
181
+ const rewriter = new HTMLRewriter().on("script", {
182
+ element(element) {
183
+ const src = element.getAttribute("src")
184
+ const hasDevAttribute = element.getAttribute("data-bun-dev-server-script") !== null
185
+
186
+ if (hasDevAttribute || (src !== null && src.startsWith("/_bun/client/"))) {
187
+ element.remove()
188
+ removeNextInlineScript = true
189
+ return
190
+ }
191
+
192
+ if (removeNextInlineScript && src === null) {
193
+ element.remove()
194
+ removeNextInlineScript = false
195
+ return
196
+ }
197
+
198
+ removeNextInlineScript = false
199
+ },
200
+ })
201
+
202
+ return Effect.tryPromise({
203
+ try: () => rewriter.transform(new Response(html)).text(),
204
+ catch: (error) =>
205
+ new BunRouteError({
206
+ reason: "ProxyError",
207
+ pattern: "stripInjectedBunScripts",
208
+ message: String(error),
209
+ }),
210
+ })
211
+ }
212
+
213
+ function readBundleHtml(bunLoad: () => Promise<Bun.HTMLBundle>) {
214
+ return Effect.tryPromise({
215
+ try: () => bunLoad(),
216
+ catch: (error) =>
217
+ new BunRouteError({
218
+ reason: "ProxyError",
219
+ pattern: "readBundleHtml",
220
+ message: String(error),
221
+ }),
222
+ }).pipe(
223
+ Effect.andThen((bundle) => {
224
+ const indexPath = NPath.isAbsolute(bundle.index)
225
+ ? bundle.index
226
+ : NPath.resolve(NPath.dirname(Bun.main), bundle.index)
227
+ return Effect.tryPromise({
228
+ try: () => Bun.file(indexPath).text(),
229
+ catch: (error) =>
230
+ new BunRouteError({
231
+ reason: "ProxyError",
232
+ pattern: indexPath,
233
+ message: String(error),
234
+ }),
235
+ })
236
+ }),
237
+ )
238
+ }
239
+
152
240
  type BunServerFetchHandler = (
153
241
  request: Request,
154
242
  server: Bun.Server<unknown>,
@@ -19,6 +19,7 @@ import * as StartApp from "../_StartApp.ts"
19
19
  import type * as RouteMount from "../RouteMount.ts"
20
20
  import * as RouteTree from "../RouteTree.ts"
21
21
  import * as BunRoute from "./BunRoute.ts"
22
+
22
23
  export interface WebSocketContext {
23
24
  readonly deferred: Deferred.Deferred<Bun.ServerWebSocket<WebSocketContext>>
24
25
  readonly closeDeferred: Deferred.Deferred<void, Socket.SocketError>
@@ -1,4 +1,4 @@
1
- import * as System from "../../System.ts"
1
+ import * as System from "../System.ts"
2
2
  import { Config, Effect, Layer, LogLevel, Option, pipe, Stream, String } from "effect"
3
3
 
4
4
  export const start = (opts: {
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * as Bundle from "./bundler/Bundle.ts"
2
- export * as Development from "./_Development.ts"
2
+ export * as Development from "./Development.ts"
3
3
  export * as Entity from "./Entity.ts"
4
+ export { html } from "./Html.ts"
5
+ export * as Html from "./Html.ts"
4
6
  export * as FileRouter from "./FileRouter.ts"
5
7
  export * as Route from "./Route.ts"
6
8
  export * as Start from "./Start.ts"
@@ -0,0 +1,15 @@
1
+ import * as Html from "./Html.ts"
2
+ import type { JSX } from "./jsx.d.ts"
3
+
4
+ function Fragment(props: { children: JSX.Element }) {
5
+ return props.children
6
+ }
7
+
8
+ function jsx<T extends Html.ElementType>(
9
+ type: T,
10
+ props: T extends string ? Html.ElemenetProps : T extends (props: infer P) => any ? P : never,
11
+ ): Html.Element {
12
+ return Html.make(type, props)
13
+ }
14
+
15
+ export { Fragment, type JSX, jsx, jsx as jsxDEV, jsx as jsxs }
@@ -1,5 +1,5 @@
1
- import type * as HyperNode from "./HyperNode.ts"
2
- import type { DatastarAttributes } from "../datastar/jsx.d.ts"
1
+ import type * as Html from "./Html.ts"
2
+ import type { DatastarAttributes } from "./datastar/jsx.d.ts"
3
3
 
4
4
  /**
5
5
  * Based on JSX types for Surplus, Inferno, and dom-expressions.
@@ -17,7 +17,7 @@ import type { DatastarAttributes } from "../datastar/jsx.d.ts"
17
17
  type DOMElement = never
18
18
 
19
19
  export namespace JSX {
20
- type Element = HyperNode.HyperNode
20
+ type Element = Html.Element
21
21
  type Child = Element | string | number | bigint
22
22
  type Children = Child | SilentChild | Iterable<Children>
23
23
 
@@ -1,7 +1,6 @@
1
1
  import * as Stream from "effect/Stream"
2
2
  import * as Route from "../../../Route.ts"
3
- import * as HyperHtml from "../../../hyper/HyperHtml.ts"
4
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
3
+ import * as Html from "../../../Html.ts"
5
4
  import * as StudioStore from "../../StudioStore.ts"
6
5
  import * as Errors from "../../ui/Errors.tsx"
7
6
  import * as Shell from "../../ui/Shell.tsx"
@@ -9,7 +8,7 @@ import * as Shell from "../../ui/Shell.tsx"
9
8
  const prefix = StudioStore.store.prefix
10
9
 
11
10
  export default Route.get(
12
- HyperRoute.html(function* (ctx) {
11
+ Route.html(function* (ctx) {
13
12
  const url = new URL(ctx.request.url)
14
13
  const search = url.searchParams.get("errorSearch") || ""
15
14
  const tag = url.searchParams.get("errorTag") || ""
@@ -76,7 +75,7 @@ export default Route.get(
76
75
  Stream.fromPubSub(StudioStore.store.events).pipe(
77
76
  Stream.filter((e) => e._tag === "Error"),
78
77
  Stream.map((e) => {
79
- const html = HyperHtml.renderToString(<Errors.ErrorLine error={e.error} />).replace(
78
+ const html = Html.renderToString(<Errors.ErrorLine error={e.error} />).replace(
80
79
  /\n/g,
81
80
  "",
82
81
  )
@@ -2,14 +2,13 @@ import * as Schema from "effect/Schema"
2
2
  import * as Route from "../../Route.ts"
3
3
  import * as RouteSchema from "../../RouteSchema.ts"
4
4
  import * as Unique from "../../Unique.ts"
5
- import * as HyperRoute from "../../hyper/HyperRoute.ts"
6
5
  import * as StudioStore from "../StudioStore.ts"
7
6
  import * as Fibers from "../ui/Fibers.tsx"
8
7
  import * as Shell from "../ui/Shell.tsx"
9
8
 
10
9
  export default Route.get(
11
10
  RouteSchema.schemaPathParams(Schema.Struct({ id: Schema.String })),
12
- HyperRoute.html(function* (ctx) {
11
+ Route.html(function* (ctx) {
13
12
  const fiberId = ctx.pathParams.id
14
13
  const fiberName = fiberId.startsWith("#") ? fiberId : `#${fiberId}`
15
14
 
@@ -1,14 +1,13 @@
1
1
  import * as Effect from "effect/Effect"
2
2
  import * as Stream from "effect/Stream"
3
3
  import * as Route from "../../../Route.ts"
4
- import * as HyperHtml from "../../../hyper/HyperHtml.ts"
5
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
4
+ import * as Html from "../../../Html.ts"
6
5
  import * as StudioStore from "../../StudioStore.ts"
7
6
  import * as Fibers from "../../ui/Fibers.tsx"
8
7
  import * as Shell from "../../ui/Shell.tsx"
9
8
 
10
9
  export default Route.get(
11
- HyperRoute.html(function* () {
10
+ Route.html(function* () {
12
11
  const logs = yield* StudioStore.allLogs(StudioStore.store.sql)
13
12
  const spans = yield* StudioStore.allSpans(StudioStore.store.sql)
14
13
  const fibers = Fibers.collectFibers(logs, spans)
@@ -32,7 +31,7 @@ export default Route.get(
32
31
  const logs = yield* StudioStore.allLogs(StudioStore.store.sql)
33
32
  const spans = yield* StudioStore.allSpans(StudioStore.store.sql)
34
33
  const fibers = Fibers.collectFibers(logs, spans)
35
- const html = HyperHtml.renderToString(
34
+ const html = Html.renderToString(
36
35
  <Fibers.FiberList fibers={fibers} prefix={StudioStore.store.prefix} />,
37
36
  ).replace(/\n/g, "")
38
37
  return {