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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-start",
3
- "version": "0.29.0",
3
+ "version": "0.30.1",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "src/",
@@ -11,75 +11,75 @@
11
11
  "type": "module",
12
12
  "exports": {
13
13
  ".": {
14
- "source": "./src/index.ts",
14
+ "bun": "./src/index.ts",
15
15
  "default": "./dist/index.js"
16
16
  },
17
+ "./*": {
18
+ "bun": "./src/*.ts",
19
+ "default": "./dist/*.js"
20
+ },
21
+ "./_*": null,
17
22
  "./client/assets.d.ts": "./src/assets.d.ts",
18
23
  "./jsx-runtime": {
19
- "source": "./src/hyper/jsx-runtime.ts",
20
- "default": "./dist/hyper/jsx-runtime.js"
24
+ "bun": "./src/jsx-runtime.ts",
25
+ "default": "./dist/jsx-runtime.js"
21
26
  },
22
27
  "./jsx-dev-runtime": {
23
- "source": "./src/hyper/jsx-runtime.ts",
24
- "default": "./dist/hyper/jsx-runtime.js"
28
+ "bun": "./src/jsx-runtime.ts",
29
+ "default": "./dist/jsx-runtime.js"
25
30
  },
26
31
  "./lint/plugin": {
27
- "source": "./src/lint/plugin.js",
32
+ "bun": "./src/lint/plugin.js",
28
33
  "default": "./dist/lint/plugin.js"
29
34
  },
30
- "./x/*": {
31
- "source": "./src/x/*/index.ts",
32
- "default": "./dist/x/*/index.js"
33
- },
34
- "./x/tailwind/plugin": {
35
- "source": "./src/x/tailwind/plugin.ts",
36
- "default": "./dist/x/tailwind/plugin.js"
37
- },
38
35
  "./bun": {
39
- "source": "./src/bun/index.ts",
36
+ "bun": "./src/bun/index.ts",
40
37
  "default": "./dist/bun/index.js"
41
38
  },
42
39
  "./cli": {
43
- "source": "./src/cli/index.ts",
40
+ "bun": "./src/cli/index.ts",
44
41
  "default": "./dist/cli/index.js"
45
42
  },
46
43
  "./client": {
47
- "source": "./src/client/index.ts",
44
+ "bun": "./src/client/index.ts",
48
45
  "default": "./dist/client/index.js"
49
46
  },
47
+ "./cloudflare": {
48
+ "bun": "./src/cloudflare/index.ts",
49
+ "default": "./dist/cloudflare/index.js"
50
+ },
50
51
  "./datastar": {
51
- "source": "./src/datastar/index.ts",
52
+ "bun": "./src/datastar/index.ts",
52
53
  "default": "./dist/datastar/index.js"
53
54
  },
54
55
  "./experimental": {
55
- "source": "./src/experimental/index.ts",
56
+ "bun": "./src/experimental/index.ts",
56
57
  "default": "./dist/experimental/index.js"
57
58
  },
58
- "./hyper": {
59
- "source": "./src/hyper/index.ts",
60
- "default": "./dist/hyper/index.js"
59
+ "./sql": {
60
+ "bun": "./src/sql/index.ts",
61
+ "default": "./dist/sql/index.js"
61
62
  },
62
63
  "./sql/*": {
63
- "source": "./src/sql/*/index.ts",
64
+ "bun": "./src/sql/*/index.ts",
64
65
  "default": "./dist/sql/*/index.js"
65
66
  },
66
- "./sql": {
67
- "source": "./src/sql/index.ts",
68
- "default": "./dist/sql/index.js"
69
- },
70
- "./testing": {
71
- "source": "./src/testing/index.ts",
72
- "default": "./dist/testing/index.js"
73
- },
74
67
  "./studio": {
75
- "source": "./src/studio/index.ts",
68
+ "bun": "./src/studio/index.ts",
76
69
  "default": "./dist/studio/index.js"
77
70
  },
78
- "./*": {
79
- "source": "./src/*.ts",
80
- "default": "./dist/*.js"
71
+ "./tailscale": {
72
+ "bun": "./src/tailscale/index.ts",
73
+ "default": "./dist/tailscale/index.js"
74
+ },
75
+ "./tailwind": {
76
+ "bun": "./src/tailwind/index.ts",
77
+ "default": "./dist/tailwind/index.js"
78
+ },
79
+ "./testing": {
80
+ "bun": "./src/testing/index.ts",
81
+ "default": "./dist/testing/index.js"
81
82
  },
82
- "./_*": null,
83
83
  "./package.json": "./package.json"
84
84
  },
85
85
  "scripts": {
@@ -100,6 +100,11 @@ export const layer = (opts?: {
100
100
  filter?: (event: FileSystem.WatchEvent) => boolean
101
101
  }) => Layer.scoped(Development, watch(opts))
102
102
 
103
+ export const layerTest = Layer.effect(
104
+ Development,
105
+ Effect.map(PubSub.unbounded<DevelopmentEvent>(), (events) => ({ events })),
106
+ )
107
+
103
108
  export const option = Effect.serviceOption(Development)
104
109
 
105
110
  export const events: Stream.Stream<DevelopmentEvent> = Stream.unwrap(
package/src/Fetch.ts CHANGED
@@ -3,7 +3,6 @@ import * as Effect from "effect/Effect"
3
3
  import * as Schedule from "effect/Schedule"
4
4
  import * as Stream from "effect/Stream"
5
5
  import * as Entity from "./Entity.ts"
6
- import * as Values from "./_Values.ts"
7
6
 
8
7
  const TypeId = "~effect-start/FetchClient" as const
9
8
 
@@ -175,42 +174,16 @@ const ClientProto: any = {
175
174
 
176
175
  type WebHandler = (request: Request) => Response | Promise<Response>
177
176
 
178
- type FromHandlerInit = Omit<RequestInit, "body"> &
179
- ({ url: string } | { path: `/${string}` }) & {
180
- body?: RequestInit["body"] | Record<string, unknown>
181
- }
182
-
183
- export function fromHandler(handler: WebHandler): FetchClient
184
- export function fromHandler(handler: WebHandler, init: FromHandlerInit): Promise<Response>
185
- export function fromHandler(
186
- handler: WebHandler,
187
- init?: FromHandlerInit,
188
- ): FetchClient | Promise<Response> {
189
- if (init === undefined) {
190
- const transport: Middleware = (request) =>
191
- Effect.map(
192
- Effect.tryPromise({
193
- try: () => Promise.resolve(handler(request)),
194
- catch: (e) => new FetchError({ reason: "Network", cause: e, request }),
195
- }),
196
- (response) => Entity.fromResponse<FetchError>(response, request),
197
- )
198
- return use(transport)
199
- }
200
-
201
- const url = "path" in init ? `http://localhost${init.path}` : init.url
202
- const isPlain = Values.isPlainObject(init.body)
203
- const headers = new Headers(init.headers)
204
- if (isPlain && !headers.has("Content-Type")) {
205
- headers.set("Content-Type", "application/json")
206
- }
207
- const body = isPlain ? JSON.stringify(init.body) : init.body
208
- const request = new Request(url, {
209
- ...init,
210
- headers,
211
- body: body as BodyInit,
212
- })
213
- return Promise.resolve(handler(request))
177
+ export function fromHandler(handler: WebHandler): FetchClient {
178
+ const transport: Middleware = (request) =>
179
+ Effect.map(
180
+ Effect.tryPromise({
181
+ try: () => Promise.resolve(handler(request)),
182
+ catch: (e) => new FetchError({ reason: "Network", cause: e, request }),
183
+ }),
184
+ (response) => Entity.fromResponse<FetchError>(response, request),
185
+ )
186
+ return use(transport)
214
187
  }
215
188
 
216
189
  export function filterStatus(
package/src/FileRouter.ts CHANGED
@@ -7,7 +7,7 @@ import * as Layer from "effect/Layer"
7
7
  import * as Stream from "effect/Stream"
8
8
  import * as NPath from "node:path"
9
9
  import * as NUrl from "node:url"
10
- import * as Development from "./_Development.ts"
10
+ import * as Development from "./Development.ts"
11
11
  import * as FileRouterCodegen from "./FileRouterCodegen.ts"
12
12
  import * as NodeUtils from "./node/NodeUtils.ts"
13
13
  import * as PathPattern from "./_PathPattern.ts"
@@ -190,6 +190,7 @@ export function getFileRoutes(
190
190
  ): Effect.Effect<OrderedFileRoutes, FileRouterError> {
191
191
  return Effect.gen(function* () {
192
192
  const routes = paths
193
+ .map((f) => f.replaceAll("\\", "/"))
193
194
  .map((f) => f.match(ROUTE_PATH_REGEX))
194
195
  .filter(Boolean)
195
196
  .map((v) => {
@@ -67,7 +67,9 @@ export const globalLayer =
67
67
  )
68
68
  yield* Deferred.succeed(deferred, ctx)
69
69
  yield* Effect.never
70
- }),
70
+ }).pipe(
71
+ Effect.catchAllCause((cause) => Deferred.failCause(deferred, cause)),
72
+ ),
71
73
  ),
72
74
  ) as Fiber.RuntimeFiber<void>
73
75
 
@@ -1,27 +1,46 @@
1
- /**
2
- * Renders Hyper JSX nodes to HTML.
3
- *
4
- * Effect Start comes with {@link Hyper} and {@link JsxRuntime} to enable
5
- * JSX support. The advantage of using JSX over HTML strings or templates
6
- * is type safety and better editor support.
7
- *
8
- * JSX nodes are compatible with React's and Solid's.
9
-
10
- * You can enable JSX support by updating `tsconfig.json`:
11
- *
12
- * {
13
- * compilerOptions: {
14
- * jsx: "react-jsx",
15
- * jsxImportSource: "effect-start" | "react" | "praect" // etc.
16
- * }
17
- * }
18
- */
19
-
20
- import type * as Hyper from "./Hyper.ts"
21
- import type * as HyperNode from "./HyperNode.ts"
22
- import type * as JsxRuntime from "./jsx-runtime.ts"
23
1
  import type { JSX } from "./jsx.d.ts"
24
2
 
3
+ export const TypeId = "~effect-start/HyperNode" as const
4
+
5
+ const NoChildren: ReadonlyArray<never> = Object.freeze([])
6
+
7
+ type Primitive = string | number | boolean | null | undefined
8
+
9
+ export type ElementType = string | Component
10
+
11
+ export type ElemenetProps = {
12
+ [key: string]:
13
+ | Primitive
14
+ | Element
15
+ | Iterable<Primitive | Element>
16
+ | Record<string, unknown>
17
+ | ((window: Window) => void)
18
+ }
19
+
20
+ export type Component = (props: ElemenetProps) => Element | Primitive
21
+
22
+ export interface Element {
23
+ type: ElementType
24
+ props: ElemenetProps
25
+ }
26
+
27
+ export function make(type: ElementType, props: ElemenetProps): Element {
28
+ return {
29
+ type,
30
+ props: {
31
+ ...props,
32
+ children: props.children ?? NoChildren,
33
+ },
34
+ }
35
+ }
36
+
37
+ export function isGenericJsxObject(value: unknown): value is {
38
+ type: any
39
+ props: any
40
+ } {
41
+ return typeof value === "object" && value !== null && "type" in value && "props" in value
42
+ }
43
+
25
44
  const EMPTY_TAGS = [
26
45
  "area",
27
46
  "base",
@@ -53,12 +72,11 @@ let map = {
53
72
 
54
73
  const RAW_TEXT_TAGS = ["script", "style"]
55
74
 
56
- // Prevents closing html tags in embedded css/js source
57
75
  const escapeRawText = (text: string) => text.replaceAll("</", "<\\/")
58
76
 
59
77
  export function renderToString(
60
78
  node: JSX.Children,
61
- hooks?: { onNode?: (node: HyperNode.HyperNode) => void },
79
+ hooks?: { onNode?: (node: Element) => void },
62
80
  ): string {
63
81
  const stack: Array<any> = [node]
64
82
  let result = ""
@@ -68,7 +86,6 @@ export function renderToString(
68
86
 
69
87
  if (typeof current === "string") {
70
88
  if (current.startsWith("<") && current.endsWith(">")) {
71
- // This is a closing tag, don't escape it
72
89
  result += current
73
90
  } else {
74
91
  result += esc(current)
@@ -82,17 +99,14 @@ export function renderToString(
82
99
  }
83
100
 
84
101
  if (typeof current === "boolean") {
85
- // React-like behavior: booleans render nothing
86
102
  continue
87
103
  }
88
104
 
89
105
  if (current === null || current === undefined) {
90
- // React-like behavior: null/undefined render nothing
91
106
  continue
92
107
  }
93
108
 
94
109
  if (Array.isArray(current)) {
95
- // Handle arrays by pushing all items to stack in reverse order
96
110
  for (let i = current.length - 1; i >= 0; i--) {
97
111
  stack.push(current[i])
98
112
  }
@@ -165,10 +179,56 @@ export function renderToString(
165
179
  }
166
180
  }
167
181
  } else if (current && typeof current === "object") {
168
- // Handle objects without type property - convert to string or ignore
169
- // This prevents [object Object] from appearing
170
182
  continue
171
183
  }
172
184
  }
173
185
  return result
174
186
  }
187
+
188
+ const HtmlStringSymbol = Symbol.for("HtmlString")
189
+
190
+ export interface HtmlString {
191
+ readonly [HtmlStringSymbol]: true
192
+ readonly value: string
193
+ }
194
+
195
+ const makeHtmlString = (value: string): HtmlString => ({
196
+ [HtmlStringSymbol]: true,
197
+ value,
198
+ })
199
+
200
+ const isHtmlString = (value: unknown): value is HtmlString =>
201
+ typeof value === "object" && value !== null && HtmlStringSymbol in value
202
+
203
+ type HtmlValue =
204
+ | string
205
+ | number
206
+ | bigint
207
+ | boolean
208
+ | null
209
+ | undefined
210
+ | HtmlString
211
+ | Function
212
+ | Record<string, unknown>
213
+ | ReadonlyArray<HtmlValue>
214
+
215
+ const resolveValue = (value: HtmlValue): string => {
216
+ if (value === null || value === undefined || value === false || value === true) return ""
217
+ if (isHtmlString(value)) return value.value
218
+ if (Array.isArray(value)) return (value as Array<HtmlValue>).map(resolveValue).join("")
219
+ if (typeof value === "function") return value.toString()
220
+ if (typeof value === "object") return JSON.stringify(value)
221
+ if (typeof value === "string") return value
222
+ return String(value)
223
+ }
224
+
225
+ export const html = (strings: TemplateStringsArray, ...values: Array<HtmlValue>): HtmlString => {
226
+ let result = strings[0]
227
+ for (let i = 0; i < values.length; i++) {
228
+ result += resolveValue(values[i])
229
+ result += strings[i + 1]
230
+ }
231
+ return makeHtmlString(result)
232
+ }
233
+
234
+ html.raw = (value: string): HtmlString => makeHtmlString(value)
package/src/Route.ts CHANGED
@@ -1,12 +1,18 @@
1
1
  import * as Context from "effect/Context"
2
2
  import * as Effect from "effect/Effect"
3
3
  import * as Layer from "effect/Layer"
4
+ import * as Option from "effect/Option"
4
5
  import * as Pipeable from "effect/Pipeable"
5
6
  import * as Predicate from "effect/Predicate"
7
+ import * as Development from "./Development.ts"
6
8
  import * as Entity from "./Entity.ts"
7
9
  import * as RouteBody from "./RouteBody.ts"
8
10
  import * as RouteTree from "./RouteTree.ts"
9
11
  import type * as Values from "./_Values.ts"
12
+ import * as Html from "./Html.ts"
13
+ import type { JSX } from "./jsx.d.ts"
14
+
15
+ export const render = RouteBody.render
10
16
 
11
17
  export const RouteItems: unique symbol = Symbol()
12
18
  export const RouteDescriptor: unique symbol = Symbol()
@@ -162,6 +168,7 @@ export function items<T extends RouteSet.Data<any, any, any>>(self: T): RouteSet
162
168
  export function descriptor<T extends RouteSet.Data<any, any, any>>(
163
169
  self: T,
164
170
  ): T[typeof RouteDescriptor]
171
+ export function descriptor<E extends RouteDescriptor.Any>(self: RouteSet.Data<any, any, any>): E
165
172
  export function descriptor<T extends RouteSet.Data<any, any, any>>(
166
173
  self: Iterable<T>,
167
174
  ): Array<T[typeof RouteDescriptor]>
@@ -203,8 +210,10 @@ export const text = RouteBody.build<string, "text">({
203
210
  format: "text",
204
211
  })
205
212
 
206
- export const html = RouteBody.build<string, "html">({
213
+ export const html = RouteBody.build<string | JSX.Children, string, "html">({
207
214
  format: "html",
215
+ handle: (body) =>
216
+ typeof body === "string" ? body : Html.renderToString(body as JSX.Children),
208
217
  })
209
218
 
210
219
  export const json = RouteBody.build<Values.Json, "json">({
@@ -215,20 +224,31 @@ export const bytes = RouteBody.build<Uint8Array, "bytes">({
215
224
  format: "bytes",
216
225
  })
217
226
 
218
- export { render } from "./RouteBody.ts"
219
-
220
227
  export { sse } from "./RouteSse.ts"
221
228
 
222
- export function redirect(
229
+ export function redirect<D extends RouteDescriptor.Any, B, I extends Route.Tuple>(
223
230
  url: string | URL,
224
231
  options?: { status?: 301 | 302 | 303 | 307 | 308 },
225
- ): Entity.Entity<""> {
226
- return Entity.make("", {
227
- status: options?.status ?? 302,
228
- headers: {
229
- location: url instanceof URL ? url.href : url,
230
- },
231
- })
232
+ ): (
233
+ self: RouteSet.RouteSet<D, B, I>,
234
+ ) => RouteSet.RouteSet<D, B, [...I, Route.Route<{}, {}, "", never, never>]> {
235
+ const route = make<{}, {}, "">(
236
+ () =>
237
+ Effect.succeed(
238
+ Entity.make("", {
239
+ status: options?.status ?? 302,
240
+ headers: {
241
+ location: url instanceof URL ? url.href : url,
242
+ },
243
+ }),
244
+ ),
245
+ )
246
+
247
+ return (self) =>
248
+ set<D, B, [...I, Route.Route<{}, {}, "", never, never>]>(
249
+ [...items(self), route],
250
+ descriptor(self),
251
+ )
232
252
  }
233
253
 
234
254
  export class Routes extends Context.Tag("effect-start/Routes")<Routes, RouteTree.RouteTree>() {}
@@ -237,6 +257,42 @@ export function layer(routes: RouteTree.RouteMap | RouteTree.RouteTree) {
237
257
  return Layer.sync(Routes, () => (RouteTree.isRouteTree(routes) ? routes : RouteTree.make(routes)))
238
258
  }
239
259
 
260
+ /**
261
+ * Creates a route that short-curcits in development.
262
+ *
263
+ * Note that when we convert the routes to web handles in {@link import("./RouteHttp.ts")},
264
+ * we exclude them altogeteher in development.
265
+ */
266
+ export function devOnly<D extends RouteDescriptor.Any, B, I extends Route.Tuple>(
267
+ self: RouteSet.RouteSet<D, B, I>,
268
+ ): RouteSet.RouteSet<D, B, [...I, Route.Route<{ dev: true }, { dev: true }, unknown, any, any>]> {
269
+ const route: Route.Route<{ dev: true }, { dev: true }, unknown, any, any> = make<
270
+ { dev: true },
271
+ { dev: true },
272
+ unknown,
273
+ any,
274
+ any
275
+ >(
276
+ (context, next) =>
277
+ Effect.flatMap(Development.option, (developmentOption) =>
278
+ Option.isSome(developmentOption)
279
+ ? Effect.succeed(next({ ...context, dev: true }))
280
+ : Effect.succeed(Entity.make("", { status: 404 })),
281
+ ),
282
+ { dev: true },
283
+ )
284
+
285
+ const nextItems: [...I, Route.Route<{ dev: true }, { dev: true }, unknown, any, any>] = [
286
+ ...items(self),
287
+ route,
288
+ ]
289
+
290
+ return set<D, B, [...I, Route.Route<{ dev: true }, { dev: true }, unknown, any, any>]>(
291
+ nextItems,
292
+ descriptor(self),
293
+ )
294
+ }
295
+
240
296
  export { make as tree } from "./RouteTree.ts"
241
297
 
242
298
  export function lazy<T extends RouteSet.Any>(