effect-start 0.18.0 → 0.20.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 (203) hide show
  1. package/README.md +3 -3
  2. package/dist/Development.d.ts +8 -3
  3. package/dist/Development.js +14 -7
  4. package/dist/Effectify.d.ts +212 -0
  5. package/dist/Effectify.js +19 -0
  6. package/dist/FilePathPattern.d.ts +29 -0
  7. package/dist/FilePathPattern.js +86 -0
  8. package/dist/FileRouter.d.ts +39 -41
  9. package/dist/FileRouter.js +104 -158
  10. package/dist/FileRouterCodegen.d.ts +7 -8
  11. package/dist/FileRouterCodegen.js +97 -66
  12. package/dist/PlatformError.d.ts +46 -0
  13. package/dist/PlatformError.js +43 -0
  14. package/dist/PlatformRuntime.d.ts +27 -0
  15. package/dist/PlatformRuntime.js +51 -0
  16. package/dist/Route.d.ts +6 -2
  17. package/dist/Route.js +22 -0
  18. package/dist/RouteBody.d.ts +1 -1
  19. package/dist/RouteHttp.d.ts +1 -1
  20. package/dist/RouteHttp.js +12 -19
  21. package/dist/RouteMount.d.ts +2 -1
  22. package/dist/Start.d.ts +33 -6
  23. package/dist/Start.js +31 -13
  24. package/dist/Unique.d.ts +50 -0
  25. package/dist/Unique.js +187 -0
  26. package/dist/bun/BunHttpServer.js +5 -6
  27. package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
  28. package/dist/bun/BunPlatformHttpServer.js +53 -0
  29. package/dist/bun/BunRoute.d.ts +4 -6
  30. package/dist/bun/BunRoute.js +10 -18
  31. package/dist/bun/BunRuntime.d.ts +2 -1
  32. package/dist/bun/BunRuntime.js +10 -5
  33. package/dist/bun/BunServer.d.ts +33 -0
  34. package/dist/bun/BunServer.js +133 -0
  35. package/dist/bun/BunServerRequest.d.ts +60 -0
  36. package/dist/bun/BunServerRequest.js +252 -0
  37. package/dist/bun/index.d.ts +1 -1
  38. package/dist/bun/index.js +1 -1
  39. package/dist/datastar/actions/fetch.d.ts +30 -0
  40. package/dist/datastar/actions/fetch.js +411 -0
  41. package/dist/datastar/actions/peek.d.ts +1 -0
  42. package/dist/datastar/actions/peek.js +14 -0
  43. package/dist/datastar/actions/setAll.d.ts +1 -0
  44. package/dist/datastar/actions/setAll.js +13 -0
  45. package/dist/datastar/actions/toggleAll.d.ts +1 -0
  46. package/dist/datastar/actions/toggleAll.js +13 -0
  47. package/dist/datastar/attributes/attr.d.ts +1 -0
  48. package/dist/datastar/attributes/attr.js +49 -0
  49. package/dist/datastar/attributes/bind.d.ts +1 -0
  50. package/dist/datastar/attributes/bind.js +183 -0
  51. package/dist/datastar/attributes/class.d.ts +1 -0
  52. package/dist/datastar/attributes/class.js +50 -0
  53. package/dist/datastar/attributes/computed.d.ts +1 -0
  54. package/dist/datastar/attributes/computed.js +27 -0
  55. package/dist/datastar/attributes/effect.d.ts +1 -0
  56. package/dist/datastar/attributes/effect.js +10 -0
  57. package/dist/datastar/attributes/indicator.d.ts +1 -0
  58. package/dist/datastar/attributes/indicator.js +32 -0
  59. package/dist/datastar/attributes/init.d.ts +1 -0
  60. package/dist/datastar/attributes/init.js +27 -0
  61. package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
  62. package/dist/datastar/attributes/jsonSignals.js +31 -0
  63. package/dist/datastar/attributes/on.d.ts +1 -0
  64. package/dist/datastar/attributes/on.js +59 -0
  65. package/dist/datastar/attributes/onIntersect.d.ts +1 -0
  66. package/dist/datastar/attributes/onIntersect.js +54 -0
  67. package/dist/datastar/attributes/onInterval.d.ts +1 -0
  68. package/dist/datastar/attributes/onInterval.js +31 -0
  69. package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
  70. package/dist/datastar/attributes/onSignalPatch.js +44 -0
  71. package/dist/datastar/attributes/ref.d.ts +1 -0
  72. package/dist/datastar/attributes/ref.js +11 -0
  73. package/dist/datastar/attributes/show.d.ts +1 -0
  74. package/dist/datastar/attributes/show.js +32 -0
  75. package/dist/datastar/attributes/signals.d.ts +1 -0
  76. package/dist/datastar/attributes/signals.js +18 -0
  77. package/dist/datastar/attributes/style.d.ts +1 -0
  78. package/dist/datastar/attributes/style.js +56 -0
  79. package/dist/datastar/attributes/text.d.ts +1 -0
  80. package/dist/datastar/attributes/text.js +27 -0
  81. package/dist/datastar/engine.d.ts +156 -0
  82. package/dist/datastar/engine.js +971 -0
  83. package/dist/datastar/index.d.ts +24 -0
  84. package/dist/datastar/index.js +24 -0
  85. package/dist/datastar/load.d.ts +24 -0
  86. package/dist/datastar/load.js +24 -0
  87. package/dist/datastar/utils.d.ts +51 -0
  88. package/dist/datastar/utils.js +205 -0
  89. package/dist/datastar/watchers/patchElements.d.ts +1 -0
  90. package/dist/datastar/watchers/patchElements.js +420 -0
  91. package/dist/datastar/watchers/patchSignals.d.ts +1 -0
  92. package/dist/datastar/watchers/patchSignals.js +15 -0
  93. package/dist/index.d.ts +1 -0
  94. package/dist/index.js +1 -0
  95. package/dist/node/Effectify.d.ts +209 -0
  96. package/dist/node/Effectify.js +19 -0
  97. package/dist/node/FileSystem.d.ts +3 -5
  98. package/dist/node/FileSystem.js +42 -62
  99. package/dist/node/NodeFileSystem.d.ts +7 -0
  100. package/dist/node/NodeFileSystem.js +420 -0
  101. package/dist/node/NodeUtils.d.ts +2 -0
  102. package/dist/node/NodeUtils.js +20 -0
  103. package/dist/node/PlatformError.d.ts +46 -0
  104. package/dist/node/PlatformError.js +43 -0
  105. package/dist/testing/TestLogger.js +1 -1
  106. package/dist/x/tailwind/plugin.js +1 -1
  107. package/package.json +18 -7
  108. package/src/Development.ts +36 -40
  109. package/src/Effectify.ts +269 -0
  110. package/src/FilePathPattern.ts +115 -0
  111. package/src/FileRouter.ts +178 -255
  112. package/src/FileRouterCodegen.ts +135 -92
  113. package/src/PlatformError.ts +117 -0
  114. package/src/PlatformRuntime.ts +108 -0
  115. package/src/Route.ts +31 -2
  116. package/src/RouteBody.ts +1 -1
  117. package/src/RouteHttp.ts +15 -29
  118. package/src/RouteMount.ts +1 -1
  119. package/src/Start.ts +61 -27
  120. package/src/Unique.ts +232 -0
  121. package/src/bun/BunPlatformHttpServer.ts +88 -0
  122. package/src/bun/BunRoute.ts +14 -24
  123. package/src/bun/BunRuntime.ts +21 -5
  124. package/src/bun/BunServer.ts +228 -0
  125. package/src/bun/index.ts +1 -1
  126. package/src/datastar/README.md +18 -0
  127. package/src/datastar/actions/fetch.ts +609 -0
  128. package/src/datastar/actions/peek.ts +17 -0
  129. package/src/datastar/actions/setAll.ts +20 -0
  130. package/src/datastar/actions/toggleAll.ts +20 -0
  131. package/src/datastar/attributes/attr.ts +50 -0
  132. package/src/datastar/attributes/bind.ts +220 -0
  133. package/src/datastar/attributes/class.ts +57 -0
  134. package/src/datastar/attributes/computed.ts +33 -0
  135. package/src/datastar/attributes/effect.ts +11 -0
  136. package/src/datastar/attributes/indicator.ts +39 -0
  137. package/src/datastar/attributes/init.ts +35 -0
  138. package/src/datastar/attributes/jsonSignals.ts +38 -0
  139. package/src/datastar/attributes/on.ts +71 -0
  140. package/src/datastar/attributes/onIntersect.ts +65 -0
  141. package/src/datastar/attributes/onInterval.ts +39 -0
  142. package/src/datastar/attributes/onSignalPatch.ts +63 -0
  143. package/src/datastar/attributes/ref.ts +12 -0
  144. package/src/datastar/attributes/show.ts +33 -0
  145. package/src/datastar/attributes/signals.ts +22 -0
  146. package/src/datastar/attributes/style.ts +63 -0
  147. package/src/datastar/attributes/text.ts +30 -0
  148. package/src/datastar/engine.ts +1341 -0
  149. package/src/datastar/index.ts +25 -0
  150. package/src/datastar/utils.ts +286 -0
  151. package/src/datastar/watchers/patchElements.ts +554 -0
  152. package/src/datastar/watchers/patchSignals.ts +15 -0
  153. package/src/index.ts +1 -0
  154. package/src/node/{FileSystem.ts → NodeFileSystem.ts} +59 -97
  155. package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
  156. package/src/testing/TestLogger.ts +1 -1
  157. package/src/x/tailwind/plugin.ts +1 -1
  158. package/dist/Random.d.ts +0 -5
  159. package/dist/Random.js +0 -49
  160. package/src/Commander.test.ts +0 -1639
  161. package/src/ContentNegotiation.test.ts +0 -603
  162. package/src/Development.test.ts +0 -119
  163. package/src/Entity.test.ts +0 -592
  164. package/src/FileRouterCodegen.todo.ts +0 -1133
  165. package/src/FileRouterPattern.test.ts +0 -147
  166. package/src/FileRouterPattern.ts +0 -59
  167. package/src/FileRouter_files.test.ts +0 -64
  168. package/src/FileRouter_path.test.ts +0 -145
  169. package/src/FileRouter_tree.test.ts +0 -132
  170. package/src/Http.test.ts +0 -319
  171. package/src/HttpAppExtra.test.ts +0 -103
  172. package/src/HttpUtils.test.ts +0 -85
  173. package/src/PathPattern.test.ts +0 -648
  174. package/src/Random.ts +0 -59
  175. package/src/RouteBody.test.ts +0 -232
  176. package/src/RouteHook.test.ts +0 -40
  177. package/src/RouteHttp.test.ts +0 -2909
  178. package/src/RouteMount.test.ts +0 -481
  179. package/src/RouteSchema.test.ts +0 -427
  180. package/src/RouteSse.test.ts +0 -249
  181. package/src/RouteTree.test.ts +0 -494
  182. package/src/RouteTrie.test.ts +0 -322
  183. package/src/RouterPattern.test.ts +0 -676
  184. package/src/RouterPattern.ts +0 -416
  185. package/src/StartApp.ts +0 -47
  186. package/src/Values.test.ts +0 -263
  187. package/src/bun/BunBundle.test.ts +0 -268
  188. package/src/bun/BunBundle_imports.test.ts +0 -48
  189. package/src/bun/BunHttpServer.test.ts +0 -251
  190. package/src/bun/BunHttpServer.ts +0 -306
  191. package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
  192. package/src/bun/BunRoute.test.ts +0 -162
  193. package/src/bundler/BundleHttp.test.ts +0 -132
  194. package/src/effect/HttpRouter.test.ts +0 -548
  195. package/src/experimental/EncryptedCookies.test.ts +0 -488
  196. package/src/hyper/HyperHtml.test.ts +0 -209
  197. package/src/hyper/HyperRoute.test.tsx +0 -197
  198. package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
  199. package/src/testing/TestHttpClient.test.ts +0 -83
  200. package/src/testing/TestLogger.test.ts +0 -51
  201. package/src/x/datastar/Datastar.test.ts +0 -266
  202. package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
  203. /package/src/bun/{BunHttpServer_web.ts → BunServerRequest.ts} +0 -0
@@ -1,1133 +0,0 @@
1
- import * as Error from "@effect/platform/Error"
2
- import * as FileSystem from "@effect/platform/FileSystem"
3
- import * as test from "bun:test"
4
- import * as Effect from "effect/Effect"
5
- import * as Schema from "effect/Schema"
6
- import * as Scope from "effect/Scope"
7
- import * as path from "node:path"
8
- import * as NodeFileSystem from "../node/FileSystem.ts"
9
- import * as SchemaExtra from "../SchemaExtra.ts"
10
- import * as TestLogger from "../testing/TestLogger.ts"
11
- import * as FileRouter from "./FileRouter.ts"
12
- import { parseRoute } from "./FileRouter.ts"
13
- import type { RouteHandle } from "./FileRouter.ts"
14
- import * as FileRouterCodegen from "./FileRouterCodegen.ts"
15
-
16
- function createTempDirWithFiles(
17
- files: Record<string, string>,
18
- ): Effect.Effect<
19
- string,
20
- Error.PlatformError,
21
- FileSystem.FileSystem | Scope.Scope
22
- > {
23
- return Effect.gen(function*() {
24
- const fs = yield* FileSystem.FileSystem
25
- const tempDir = yield* fs.makeTempDirectoryScoped()
26
-
27
- for (const [filePath, content] of Object.entries(files)) {
28
- const fullPath = path.join(tempDir, filePath)
29
- const dir = path.dirname(fullPath)
30
-
31
- yield* fs.makeDirectory(dir, { recursive: true })
32
- yield* fs.writeFileString(fullPath, content)
33
- }
34
-
35
- return tempDir
36
- })
37
- }
38
-
39
- test.it("generates code for routes only", () => {
40
- const handles: RouteHandle[] = [
41
- parseRoute("route.tsx"),
42
- parseRoute("about/route.tsx"),
43
- ]
44
-
45
- const code = FileRouterCodegen.generateCode(handles)
46
-
47
- const expected = `/**
48
- * Auto-generated by effect-start.
49
- */
50
-
51
- export const routes = [
52
- {
53
- path: "/",
54
- load: () => import("./route.tsx"),
55
- },
56
- {
57
- path: "/about",
58
- load: () => import("./about/route.tsx"),
59
- },
60
- ] as const
61
- `
62
-
63
- test
64
- .expect(code)
65
- .toBe(expected)
66
- })
67
-
68
- test.it("generates code with layers", () => {
69
- const handles: RouteHandle[] = [
70
- parseRoute("layer.tsx"),
71
- parseRoute("route.tsx"),
72
- parseRoute("about/route.tsx"),
73
- ]
74
-
75
- const code = FileRouterCodegen.generateCode(handles)
76
-
77
- const expected = `/**
78
- * Auto-generated by effect-start.
79
- */
80
-
81
- export const routes = [
82
- {
83
- path: "/",
84
- load: () => import("./route.tsx"),
85
- layers: [
86
- () => import("./layer.tsx"),
87
- ],
88
- },
89
- {
90
- path: "/about",
91
- load: () => import("./about/route.tsx"),
92
- layers: [
93
- () => import("./layer.tsx"),
94
- ],
95
- },
96
- ] as const
97
- `
98
-
99
- test
100
- .expect(code)
101
- .toBe(expected)
102
- })
103
-
104
- test.it("generates code with nested layers", () => {
105
- const handles: RouteHandle[] = [
106
- parseRoute("layer.tsx"),
107
- parseRoute("dashboard/layer.tsx"),
108
- parseRoute("dashboard/route.tsx"),
109
- parseRoute("dashboard/settings/route.tsx"),
110
- ]
111
-
112
- const code = FileRouterCodegen.generateCode(handles)
113
-
114
- const expected = `/**
115
- * Auto-generated by effect-start.
116
- */
117
-
118
- export const routes = [
119
- {
120
- path: "/dashboard",
121
- load: () => import("./dashboard/route.tsx"),
122
- layers: [
123
- () => import("./layer.tsx"),
124
- () => import("./dashboard/layer.tsx"),
125
- ],
126
- },
127
- {
128
- path: "/dashboard/settings",
129
- load: () => import("./dashboard/settings/route.tsx"),
130
- layers: [
131
- () => import("./layer.tsx"),
132
- () => import("./dashboard/layer.tsx"),
133
- ],
134
- },
135
- ] as const
136
- `
137
-
138
- test
139
- .expect(code)
140
- .toBe(expected)
141
- })
142
-
143
- test.it("only includes group layers for routes in that group", () => {
144
- const handles: RouteHandle[] = [
145
- parseRoute("layer.tsx"),
146
- parseRoute("(admin)/layer.ts"),
147
- parseRoute("(admin)/users/route.tsx"),
148
- parseRoute("movies/route.tsx"),
149
- ]
150
-
151
- const code = FileRouterCodegen.generateCode(handles)
152
-
153
- test
154
- .expect(code)
155
- .toContain("path: \"/users\"")
156
- test
157
- .expect(code)
158
- .toContain("path: \"/movies\"")
159
- test
160
- .expect(code)
161
- .toContain("() => import(\"./layer.tsx\")")
162
- test
163
- .expect(code)
164
- .toContain("() => import(\"./(admin)/layer.ts\")")
165
-
166
- const expectedMovies = ` {
167
- path: "/movies",
168
- load: () => import("./movies/route.tsx"),
169
- layers: [
170
- () => import("./layer.tsx"),
171
- ],
172
- },`
173
-
174
- test
175
- .expect(code)
176
- .toContain(expectedMovies)
177
- })
178
-
179
- test.it("handles dynamic routes with params", () => {
180
- const handles: RouteHandle[] = [
181
- parseRoute("users/route.tsx"),
182
- parseRoute("users/[userId]/route.tsx"),
183
- parseRoute("posts/[postId]/comments/[commentId]/route.tsx"),
184
- ]
185
-
186
- const code = FileRouterCodegen.generateCode(handles)
187
-
188
- test
189
- .expect(code)
190
- .toContain("path: \"/users\"")
191
- test
192
- .expect(code)
193
- .toContain("path: \"/users/[userId]\"")
194
- test
195
- .expect(code)
196
- .toContain("path: \"/posts/[postId]/comments/[commentId]\"")
197
- })
198
-
199
- test.it("handles rest parameters", () => {
200
- const handles: RouteHandle[] = [
201
- parseRoute("docs/[[...slug]]/route.tsx"),
202
- parseRoute("api/[...path]/route.tsx"),
203
- ]
204
-
205
- const code = FileRouterCodegen.generateCode(handles)
206
-
207
- test
208
- .expect(code)
209
- .toContain("path: \"/docs/[[...slug]]\"")
210
- test
211
- .expect(code)
212
- .toContain("path: \"/api/[...path]\"")
213
- })
214
-
215
- test.it("handles groups in path", () => {
216
- const handles: RouteHandle[] = [
217
- parseRoute("(admin)/users/route.tsx"),
218
- parseRoute("(admin)/layer.tsx"),
219
- ]
220
-
221
- const code = FileRouterCodegen.generateCode(handles)
222
-
223
- test
224
- .expect(code)
225
- .toContain("path: \"/users\"")
226
- test
227
- .expect(code)
228
- .toContain(
229
- "layers: [\n () => import(\"./(admin)/layer.tsx\"),\n ]",
230
- )
231
- })
232
-
233
- test.it("generates correct variable names for root routes", () => {
234
- const handles: RouteHandle[] = [
235
- parseRoute("route.tsx"),
236
- ]
237
-
238
- const code = FileRouterCodegen.generateCode(handles)
239
-
240
- test
241
- .expect(code)
242
- .toContain("path: \"/\"")
243
- })
244
-
245
- test.it("handles routes with dots in path segments", () => {
246
- const handles: RouteHandle[] = [
247
- parseRoute("events.json/route.ts"),
248
- parseRoute("config.yaml.backup/route.ts"),
249
- ]
250
-
251
- const code = FileRouterCodegen.generateCode(handles)
252
-
253
- test
254
- .expect(code)
255
- .toContain("path: \"/events.json\"")
256
- test
257
- .expect(code)
258
- .toContain("path: \"/config.yaml.backup\"")
259
- })
260
-
261
- test.it("uses default module identifier", () => {
262
- const handles: RouteHandle[] = [
263
- parseRoute("route.tsx"),
264
- ]
265
-
266
- const code = FileRouterCodegen.generateCode(handles)
267
- })
268
-
269
- test.it("generates empty routes array when no handles provided", () => {
270
- const handles: RouteHandle[] = []
271
-
272
- const code = FileRouterCodegen.generateCode(handles)
273
-
274
- test
275
- .expect(code)
276
- .toContain("export const routes = [] as const")
277
- })
278
-
279
- test.it("only includes routes, not layers", () => {
280
- const handles: RouteHandle[] = [
281
- parseRoute("layer.tsx"),
282
- parseRoute("users/layer.tsx"),
283
- ]
284
-
285
- const code = FileRouterCodegen.generateCode(handles)
286
-
287
- test
288
- .expect(code)
289
- .toContain("export const routes = [] as const")
290
- })
291
-
292
- test.it("complex nested routes with multiple layers", () => {
293
- const handles: RouteHandle[] = [
294
- parseRoute("layer.tsx"),
295
- parseRoute("(auth)/layer.tsx"),
296
- parseRoute("(auth)/login/route.tsx"),
297
- parseRoute("(auth)/signup/route.tsx"),
298
- parseRoute("dashboard/layer.tsx"),
299
- parseRoute("dashboard/route.tsx"),
300
- parseRoute("dashboard/settings/route.tsx"),
301
- ]
302
-
303
- const code = FileRouterCodegen.generateCode(handles)
304
-
305
- test
306
- .expect(code)
307
- .toContain("path: \"/login\"")
308
- test
309
- .expect(code)
310
- .toContain("path: \"/signup\"")
311
- test
312
- .expect(code)
313
- .toContain("path: \"/dashboard\"")
314
- test
315
- .expect(code)
316
- .toContain("path: \"/dashboard/settings\"")
317
- test
318
- .expect(code)
319
- .toContain("() => import(\"./layer.tsx\")")
320
- test
321
- .expect(code)
322
- .toContain("() => import(\"./(auth)/layer.tsx\")")
323
- test
324
- .expect(code)
325
- .toContain("() => import(\"./dashboard/layer.tsx\")")
326
- })
327
-
328
- test.it("handles routes with hyphens and underscores in path segments", () => {
329
- const handles: RouteHandle[] = [
330
- parseRoute("api-v1/route.ts"),
331
- parseRoute("my_resource/route.ts"),
332
- ]
333
-
334
- const code = FileRouterCodegen.generateCode(handles)
335
-
336
- test
337
- .expect(code)
338
- .toContain("path: \"/api-v1\"")
339
- test
340
- .expect(code)
341
- .toContain("path: \"/my_resource\"")
342
- })
343
-
344
- test.it.skip("validateRouteModule returns false for invalid modules", () => {
345
- test
346
- .expect(FileRouterCodegen.validateRouteModule({}))
347
- .toBe(false)
348
- test
349
- .expect(FileRouterCodegen.validateRouteModule({ default: {} }))
350
- .toBe(false)
351
- test
352
- .expect(FileRouterCodegen.validateRouteModule({ default: "not a route" }))
353
- .toBe(false)
354
- test
355
- .expect(FileRouterCodegen.validateRouteModule({ foo: "bar" }))
356
- .toBe(false)
357
- test
358
- .expect(FileRouterCodegen.validateRouteModule(null))
359
- .toBe(false)
360
- test
361
- .expect(FileRouterCodegen.validateRouteModule(undefined))
362
- .toBe(false)
363
- test
364
- .expect(FileRouterCodegen.validateRouteModule("string"))
365
- .toBe(false)
366
- test
367
- .expect(FileRouterCodegen.validateRouteModule(42))
368
- .toBe(false)
369
- })
370
-
371
- test.it("mixed params and rest in same route", () => {
372
- const handles: RouteHandle[] = [
373
- parseRoute("users/[userId]/files/[...path]/route.tsx"),
374
- ]
375
-
376
- const code = FileRouterCodegen.generateCode(handles)
377
-
378
- test
379
- .expect(code)
380
- .toContain("path: \"/users/[userId]/files/[...path]\"")
381
- })
382
-
383
- test.describe("layerMatchesRoute", () => {
384
- test.it("layer in dynamic param dir only applies to routes in that dir", () => {
385
- const handles: RouteHandle[] = [
386
- parseRoute("[userId]/layer.tsx"),
387
- parseRoute("[userId]/posts/route.tsx"),
388
- parseRoute("[otherId]/route.tsx"),
389
- ]
390
-
391
- const code = FileRouterCodegen.generateCode(handles)
392
-
393
- const expectedUserIdPosts = ` {
394
- path: "/[userId]/posts",
395
- load: () => import("./[userId]/posts/route.tsx"),
396
- layers: [
397
- () => import("./[userId]/layer.tsx"),
398
- ],
399
- },`
400
-
401
- test
402
- .expect(code)
403
- .toContain(expectedUserIdPosts)
404
-
405
- const expectedOtherId = ` {
406
- path: "/[otherId]",
407
- load: () => import("./[otherId]/route.tsx"),
408
- },`
409
-
410
- test
411
- .expect(code)
412
- .toContain(expectedOtherId)
413
- })
414
-
415
- test.it("nested groups only apply to routes in those groups", () => {
416
- const handles: RouteHandle[] = [
417
- parseRoute("layer.tsx"),
418
- parseRoute("(admin)/(dashboard)/layer.tsx"),
419
- parseRoute("(admin)/(dashboard)/users/route.tsx"),
420
- parseRoute("(admin)/settings/route.tsx"),
421
- parseRoute("(other)/(dashboard)/route.tsx"),
422
- ]
423
-
424
- const code = FileRouterCodegen.generateCode(handles)
425
-
426
- const expectedAdminDashboardUsers = ` {
427
- path: "/users",
428
- load: () => import("./(admin)/(dashboard)/users/route.tsx"),
429
- layers: [
430
- () => import("./layer.tsx"),
431
- () => import("./(admin)/(dashboard)/layer.tsx"),
432
- ],
433
- },`
434
-
435
- test
436
- .expect(code)
437
- .toContain(expectedAdminDashboardUsers)
438
-
439
- const expectedAdminSettings = ` {
440
- path: "/settings",
441
- load: () => import("./(admin)/settings/route.tsx"),
442
- layers: [
443
- () => import("./layer.tsx"),
444
- ],
445
- },`
446
-
447
- test
448
- .expect(code)
449
- .toContain(expectedAdminSettings)
450
-
451
- const expectedOtherDashboard = ` {
452
- path: "/",
453
- load: () => import("./(other)/(dashboard)/route.tsx"),
454
- layers: [
455
- () => import("./layer.tsx"),
456
- ],
457
- },`
458
-
459
- test
460
- .expect(code)
461
- .toContain(expectedOtherDashboard)
462
- })
463
-
464
- test.it("similar directory names do not match (user vs users)", () => {
465
- const handles: RouteHandle[] = [
466
- parseRoute("user/layer.tsx"),
467
- parseRoute("user/route.tsx"),
468
- parseRoute("users/route.tsx"),
469
- ]
470
-
471
- const code = FileRouterCodegen.generateCode(handles)
472
-
473
- const expectedUser = ` {
474
- path: "/user",
475
- load: () => import("./user/route.tsx"),
476
- layers: [
477
- () => import("./user/layer.tsx"),
478
- ],
479
- },`
480
-
481
- test
482
- .expect(code)
483
- .toContain(expectedUser)
484
-
485
- const expectedUsers = ` {
486
- path: "/users",
487
- load: () => import("./users/route.tsx"),
488
- },`
489
-
490
- test
491
- .expect(code)
492
- .toContain(expectedUsers)
493
- })
494
-
495
- test.it("mixed groups and literals layer matching", () => {
496
- const handles: RouteHandle[] = [
497
- parseRoute("(admin)/users/layer.tsx"),
498
- parseRoute("(admin)/users/[userId]/route.tsx"),
499
- parseRoute("users/route.tsx"),
500
- parseRoute("(admin)/posts/route.tsx"),
501
- ]
502
-
503
- const code = FileRouterCodegen.generateCode(handles)
504
-
505
- const expectedAdminUsersId = ` {
506
- path: "/users/[userId]",
507
- load: () => import("./(admin)/users/[userId]/route.tsx"),
508
- layers: [
509
- () => import("./(admin)/users/layer.tsx"),
510
- ],
511
- },`
512
-
513
- test
514
- .expect(code)
515
- .toContain(expectedAdminUsersId)
516
-
517
- const expectedUsers = ` {
518
- path: "/users",
519
- load: () => import("./users/route.tsx"),
520
- },`
521
-
522
- test
523
- .expect(code)
524
- .toContain(expectedUsers)
525
-
526
- const expectedAdminPosts = ` {
527
- path: "/posts",
528
- load: () => import("./(admin)/posts/route.tsx"),
529
- },`
530
-
531
- test
532
- .expect(code)
533
- .toContain(expectedAdminPosts)
534
- })
535
-
536
- test.it("param directory layer only applies to routes in that dir", () => {
537
- const handles: RouteHandle[] = [
538
- parseRoute("[tenantId]/layer.tsx"),
539
- parseRoute("[tenantId]/settings/route.tsx"),
540
- parseRoute("other/route.tsx"),
541
- ]
542
-
543
- const code = FileRouterCodegen.generateCode(handles)
544
-
545
- const expectedTenantSettings = ` {
546
- path: "/[tenantId]/settings",
547
- load: () => import("./[tenantId]/settings/route.tsx"),
548
- layers: [
549
- () => import("./[tenantId]/layer.tsx"),
550
- ],
551
- },`
552
-
553
- test
554
- .expect(code)
555
- .toContain(expectedTenantSettings)
556
-
557
- const expectedOther = ` {
558
- path: "/other",
559
- load: () => import("./other/route.tsx"),
560
- },`
561
-
562
- test
563
- .expect(code)
564
- .toContain(expectedOther)
565
- })
566
-
567
- test.it(
568
- "optional param directory layer only applies to routes in that dir",
569
- () => {
570
- const handles: RouteHandle[] = [
571
- parseRoute("[[id]]/layer.tsx"),
572
- parseRoute("[[id]]/settings/route.tsx"),
573
- parseRoute("other/route.tsx"),
574
- ]
575
-
576
- const code = FileRouterCodegen.generateCode(handles)
577
-
578
- const expectedIdSettings = ` {
579
- path: "/[[id]]/settings",
580
- load: () => import("./[[id]]/settings/route.tsx"),
581
- layers: [
582
- () => import("./[[id]]/layer.tsx"),
583
- ],
584
- },`
585
-
586
- test
587
- .expect(code)
588
- .toContain(expectedIdSettings)
589
-
590
- const expectedOther = ` {
591
- path: "/other",
592
- load: () => import("./other/route.tsx"),
593
- },`
594
-
595
- test
596
- .expect(code)
597
- .toContain(expectedOther)
598
- },
599
- )
600
-
601
- test.it("layer and route at same directory level", () => {
602
- const handles: RouteHandle[] = [
603
- parseRoute("users/layer.tsx"),
604
- parseRoute("users/route.tsx"),
605
- ]
606
-
607
- const code = FileRouterCodegen.generateCode(handles)
608
-
609
- const expected = ` {
610
- path: "/users",
611
- load: () => import("./users/route.tsx"),
612
- layers: [
613
- () => import("./users/layer.tsx"),
614
- ],
615
- },`
616
-
617
- test
618
- .expect(code)
619
- .toContain(expected)
620
- })
621
- })
622
-
623
- const simpleRouteContent = `import * as Route from "${
624
- path.resolve(import.meta.dirname, "./Route.ts")
625
- }"
626
- export default Route.text("Hello")
627
- `
628
-
629
- test.it("update() > writes file", () =>
630
- Effect
631
- .gen(function*() {
632
- const fs = yield* FileSystem.FileSystem
633
- const tempDir = yield* createTempDirWithFiles({
634
- "routes/route.tsx": simpleRouteContent,
635
- "routes/about/route.tsx": simpleRouteContent,
636
- })
637
- const routesPath = path.join(tempDir, "routes")
638
-
639
- yield* FileRouterCodegen.update(routesPath)
640
-
641
- const content = yield* fs.readFileString(
642
- path.join(routesPath, "manifest.ts"),
643
- )
644
-
645
- test
646
- .expect(content)
647
- .toContain("export const routes =")
648
- })
649
- .pipe(
650
- Effect.scoped,
651
- Effect.provide(NodeFileSystem.layer),
652
- Effect.runPromise,
653
- ))
654
-
655
- test.it("update() > writes only when it changes", () =>
656
- Effect
657
- .gen(function*() {
658
- const fs = yield* FileSystem.FileSystem
659
- const tempDir = yield* createTempDirWithFiles({
660
- "routes/route.tsx": simpleRouteContent,
661
- "routes/about/route.tsx": simpleRouteContent,
662
- })
663
- const routesPath = path.join(tempDir, "routes")
664
-
665
- yield* FileRouterCodegen.update(routesPath)
666
-
667
- const content = yield* fs.readFileString(
668
- path.join(routesPath, "manifest.ts"),
669
- )
670
-
671
- yield* FileRouterCodegen.update(routesPath)
672
-
673
- const content2 = yield* fs.readFileString(
674
- path.join(routesPath, "manifest.ts"),
675
- )
676
-
677
- test
678
- .expect(content2)
679
- .not
680
- .toBe("")
681
- test
682
- .expect(content2)
683
- .toBe(content)
684
- })
685
- .pipe(
686
- Effect.scoped,
687
- Effect.provide(NodeFileSystem.layer),
688
- Effect.runPromise,
689
- ))
690
-
691
- test.it(
692
- "update() > removes deleted routes from manifest",
693
- () =>
694
- Effect
695
- .gen(function*() {
696
- const fs = yield* FileSystem.FileSystem
697
- const tempDir = yield* createTempDirWithFiles({
698
- "routes/route.tsx": simpleRouteContent,
699
- "routes/about/route.tsx": simpleRouteContent,
700
- })
701
- const routesPath = path.join(tempDir, "routes")
702
-
703
- yield* FileRouterCodegen.update(routesPath)
704
-
705
- const content = yield* fs.readFileString(
706
- path.join(routesPath, "manifest.ts"),
707
- )
708
-
709
- test
710
- .expect(content)
711
- .toContain("path: \"/\"")
712
- test
713
- .expect(content)
714
- .toContain("path: \"/about\"")
715
-
716
- yield* fs.remove(path.join(routesPath, "about/route.tsx"))
717
-
718
- yield* FileRouterCodegen.update(routesPath)
719
-
720
- const content2 = yield* fs.readFileString(
721
- path.join(routesPath, "manifest.ts"),
722
- )
723
-
724
- test
725
- .expect(content2)
726
- .toContain("path: \"/\"")
727
- test
728
- .expect(content2)
729
- .not
730
- .toContain("path: \"/about\"")
731
- })
732
- .pipe(
733
- Effect.scoped,
734
- Effect.provide(NodeFileSystem.layer),
735
- Effect.runPromise,
736
- ),
737
- )
738
-
739
- test.it(
740
- "update() > removes routes when entire directory is deleted",
741
- () =>
742
- Effect
743
- .gen(function*() {
744
- const fs = yield* FileSystem.FileSystem
745
- const tempDir = yield* createTempDirWithFiles({
746
- "routes/route.tsx": simpleRouteContent,
747
- "routes/about/route.tsx": simpleRouteContent,
748
- "routes/users/route.tsx": simpleRouteContent,
749
- })
750
- const routesPath = path.join(tempDir, "routes")
751
-
752
- yield* FileRouterCodegen.update(routesPath)
753
-
754
- const content = yield* fs.readFileString(
755
- path.join(routesPath, "manifest.ts"),
756
- )
757
-
758
- test
759
- .expect(content)
760
- .toContain("path: \"/\"")
761
- test
762
- .expect(content)
763
- .toContain("path: \"/about\"")
764
- test
765
- .expect(content)
766
- .toContain("path: \"/users\"")
767
-
768
- yield* fs.remove(path.join(routesPath, "users"), {
769
- recursive: true,
770
- })
771
-
772
- yield* FileRouterCodegen.update(routesPath)
773
-
774
- const content2 = yield* fs.readFileString(
775
- path.join(routesPath, "manifest.ts"),
776
- )
777
-
778
- test
779
- .expect(content2)
780
- .toContain("path: \"/\"")
781
- test
782
- .expect(content2)
783
- .toContain("path: \"/about\"")
784
- test
785
- .expect(content2)
786
- .not
787
- .toContain("path: \"/users\"")
788
- })
789
- .pipe(
790
- Effect.scoped,
791
- Effect.provide(NodeFileSystem.layer),
792
- Effect.runPromise,
793
- ),
794
- )
795
-
796
- test.describe("PathParams schema generation and validation", () => {
797
- test.describe("generatePathParamsSchema", () => {
798
- test.it("returns null for routes with no params", () => {
799
- const handle = parseRoute("users/route.tsx")
800
- const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
801
- test
802
- .expect(schema)
803
- .toBe(null)
804
- })
805
-
806
- test.it("generates schema for single required param", () => {
807
- const handle = parseRoute("users/[id]/route.tsx")
808
- const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
809
- test
810
- .expect(schema)
811
- .not
812
- .toBe(null)
813
- test
814
- .expect(Object.keys(schema!.fields))
815
- .toEqual(["id"])
816
- })
817
-
818
- test.it("generates schema for single optional param", () => {
819
- const handle = parseRoute("about/[[section]]/route.tsx")
820
- const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
821
- test
822
- .expect(schema)
823
- .not
824
- .toBe(null)
825
- test
826
- .expect(Object.keys(schema!.fields))
827
- .toEqual(["section"])
828
- })
829
-
830
- test.it("generates schema for rest segment", () => {
831
- const handle = parseRoute("docs/[...path]/route.tsx")
832
- const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
833
- test
834
- .expect(schema)
835
- .not
836
- .toBe(null)
837
- test
838
- .expect(Object.keys(schema!.fields))
839
- .toEqual(["path"])
840
- })
841
-
842
- test.it("rest segment should capture path starting with /", () => {
843
- const handle = parseRoute("docs/[...path]/route.tsx")
844
- const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
845
- test
846
- .expect(schema)
847
- .not
848
- .toBe(null)
849
-
850
- const formatted = SchemaExtra.formatSchemaCode(schema!)
851
- test
852
- .expect(formatted)
853
- .toBe("{ path: Schema.String }")
854
- })
855
-
856
- test.it("generates schema for optional rest segment", () => {
857
- const handle = parseRoute("docs/[[...slug]]/route.tsx")
858
- const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
859
- test
860
- .expect(schema)
861
- .not
862
- .toBe(null)
863
- test
864
- .expect(Object.keys(schema!.fields))
865
- .toEqual(["slug"])
866
- })
867
-
868
- test.it("generates schema for multiple params", () => {
869
- const handle = parseRoute("posts/[postId]/comments/[commentId]/route.tsx")
870
- const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
871
- test
872
- .expect(schema)
873
- .not
874
- .toBe(null)
875
- test
876
- .expect(Object.keys(schema!.fields).sort())
877
- .toEqual([
878
- "commentId",
879
- "postId",
880
- ])
881
- })
882
-
883
- test.it("generates schema for mixed required and optional params", () => {
884
- const handle = parseRoute("users/[userId]/posts/[[postId]]/route.tsx")
885
- const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
886
- test
887
- .expect(schema)
888
- .not
889
- .toBe(null)
890
- test
891
- .expect(Object.keys(schema!.fields).sort())
892
- .toEqual(["postId", "userId"])
893
- })
894
-
895
- test.it("ignores group segments", () => {
896
- const handle = parseRoute("(admin)/users/[id]/route.tsx")
897
- const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
898
- test
899
- .expect(schema)
900
- .not
901
- .toBe(null)
902
- test
903
- .expect(Object.keys(schema!.fields))
904
- .toEqual(["id"])
905
- })
906
- })
907
-
908
- test.describe("schemaEqual", () => {
909
- test.it("returns true when both schemas are undefined/null", () => {
910
- test
911
- .expect(SchemaExtra.schemaEqual(undefined, null))
912
- .toBe(true)
913
- })
914
-
915
- test.it("returns false when only one schema is undefined", () => {
916
- const schema = Schema.Struct({ id: Schema.String })
917
- test
918
- .expect(SchemaExtra.schemaEqual(undefined, schema))
919
- .toBe(false)
920
- test
921
- .expect(SchemaExtra.schemaEqual(schema, null))
922
- .toBe(false)
923
- })
924
-
925
- test.it("returns true for exact matches", () => {
926
- const schema1 = Schema.Struct({ id: Schema.String })
927
- const schema2 = Schema.Struct({ id: Schema.String })
928
- test
929
- .expect(SchemaExtra.schemaEqual(schema1, schema2))
930
- .toBe(true)
931
- })
932
-
933
- test.it("returns true for refinement matches (UUID = String)", () => {
934
- const userSchema = Schema.Struct({ id: Schema.UUID })
935
- const expectedSchema = Schema.Struct({ id: Schema.String })
936
- test
937
- .expect(SchemaExtra.schemaEqual(userSchema, expectedSchema))
938
- .toBe(true)
939
- })
940
-
941
- test.it("returns false for type mismatches", () => {
942
- const schema1 = Schema.Struct({ id: Schema.String })
943
- const schema2 = Schema.Struct({ id: Schema.Number })
944
- test
945
- .expect(SchemaExtra.schemaEqual(schema1, schema2))
946
- .toBe(false)
947
- })
948
-
949
- test.it("returns false for field name mismatches", () => {
950
- const schema1 = Schema.Struct({ id: Schema.String })
951
- const schema2 = Schema.Struct({ userId: Schema.String })
952
- test
953
- .expect(SchemaExtra.schemaEqual(schema1, schema2))
954
- .toBe(false)
955
- })
956
-
957
- test.it("returns false for field count mismatches", () => {
958
- const schema1 = Schema.Struct({ id: Schema.String })
959
- const schema2 = Schema.Struct({ id: Schema.String, name: Schema.String })
960
- test
961
- .expect(SchemaExtra.schemaEqual(schema1, schema2))
962
- .toBe(false)
963
- })
964
-
965
- test.it("returns true for multiple fields with UUID refinement", () => {
966
- const userSchema = Schema.Struct({
967
- id: Schema.UUID,
968
- name: Schema.String,
969
- })
970
- const expectedSchema = Schema.Struct({
971
- id: Schema.String,
972
- name: Schema.String,
973
- })
974
- test
975
- .expect(SchemaExtra.schemaEqual(userSchema, expectedSchema))
976
- .toBe(true)
977
- })
978
-
979
- test.it("handles optional fields correctly", () => {
980
- const schema1 = Schema.Struct({
981
- id: Schema.String,
982
- name: Schema.optional(Schema.String),
983
- })
984
- const schema2 = Schema.Struct({
985
- id: Schema.String,
986
- name: Schema.optional(Schema.String),
987
- })
988
- test
989
- .expect(SchemaExtra.schemaEqual(schema1, schema2))
990
- .toBe(true)
991
- })
992
- })
993
-
994
- test.describe("formatSchemaCode", () => {
995
- test.it("formats single required field", () => {
996
- const schema = Schema.Struct({ id: Schema.String })
997
- const formatted = SchemaExtra.formatSchemaCode(schema)
998
- test
999
- .expect(formatted)
1000
- .toBe("{ id: Schema.String }")
1001
- })
1002
-
1003
- test.it("formats multiple fields", () => {
1004
- const schema = Schema.Struct({
1005
- id: Schema.String,
1006
- count: Schema.Number,
1007
- })
1008
- const formatted = SchemaExtra.formatSchemaCode(schema)
1009
- test
1010
- .expect(formatted)
1011
- .toContain("id: Schema.String")
1012
- test
1013
- .expect(formatted)
1014
- .toContain("count: Schema.Number")
1015
- })
1016
-
1017
- test.it("formats optional fields with ? marker", () => {
1018
- const schema = Schema.Struct({
1019
- id: Schema.String,
1020
- name: Schema.optional(Schema.String),
1021
- })
1022
- const formatted = SchemaExtra.formatSchemaCode(schema)
1023
- test
1024
- .expect(formatted)
1025
- .toContain("id: Schema.String")
1026
- test
1027
- .expect(formatted)
1028
- .toContain("name")
1029
- })
1030
-
1031
- test.it("formats boolean fields", () => {
1032
- const schema = Schema.Struct({
1033
- active: Schema.Boolean,
1034
- })
1035
- const formatted = SchemaExtra.formatSchemaCode(schema)
1036
- test
1037
- .expect(formatted)
1038
- .toBe("{ active: Schema.Boolean }")
1039
- })
1040
- })
1041
-
1042
- test.describe("validateRouteModules", () => {
1043
- test.it(
1044
- "does not log when PathParams schema is missing",
1045
- () =>
1046
- Effect
1047
- .gen(function*() {
1048
- const fs = yield* FileSystem.FileSystem
1049
- const routeContent = `import * as Route from "${
1050
- path.resolve(import.meta.dirname, "./Route.ts")
1051
- }"
1052
- export default Route.text("User")
1053
- `
1054
- const tempDir = yield* createTempDirWithFiles({
1055
- "routes/users/[id]/route.tsx": routeContent,
1056
- })
1057
- const routesPath = path.join(tempDir, "routes")
1058
-
1059
- const files = yield* fs.readDirectory(routesPath, {
1060
- recursive: true,
1061
- })
1062
- const handles = FileRouter.getRouteHandlesFromPaths(files)
1063
-
1064
- yield* FileRouterCodegen.validateRouteModules(routesPath, handles)
1065
-
1066
- const messages = yield* TestLogger.messages
1067
- test
1068
- .expect(messages)
1069
- .toHaveLength(0)
1070
- })
1071
- .pipe(
1072
- Effect.scoped,
1073
- Effect.provide([
1074
- TestLogger.layer(),
1075
- NodeFileSystem.layer,
1076
- ]),
1077
- Effect.runPromise,
1078
- ),
1079
- )
1080
-
1081
- test.it(
1082
- "logs error when PathParams schema is incorrect",
1083
- () =>
1084
- Effect
1085
- .gen(function*() {
1086
- const fs = yield* FileSystem.FileSystem
1087
- const schemaPath = path.resolve(
1088
- import.meta.dirname,
1089
- "../../node_modules/effect/Schema",
1090
- )
1091
- const routeContent = `import * as Route from "${
1092
- path.resolve(import.meta.dirname, "./Route.ts")
1093
- }"
1094
- import * as Schema from "${schemaPath}"
1095
- export default Route.text("User").schemaPathParams({ userId: Schema.String })
1096
- `
1097
- const tempDir = yield* createTempDirWithFiles({
1098
- "routes/users/[id]/route.tsx": routeContent,
1099
- })
1100
- const routesPath = path.join(tempDir, "routes")
1101
-
1102
- const files = yield* fs.readDirectory(routesPath, {
1103
- recursive: true,
1104
- })
1105
- const handles = FileRouter.getRouteHandlesFromPaths(files)
1106
-
1107
- yield* FileRouterCodegen.validateRouteModules(routesPath, handles)
1108
-
1109
- const messages = yield* TestLogger.messages
1110
- test
1111
- .expect(messages)
1112
- .toHaveLength(1)
1113
- test
1114
- .expect(messages[0])
1115
- .toContain("[Error]")
1116
- test
1117
- .expect(messages[0])
1118
- .toContain("incorrect PathParams schema")
1119
- test
1120
- .expect(messages[0])
1121
- .toContain("expected schemaPathParams")
1122
- })
1123
- .pipe(
1124
- Effect.scoped,
1125
- Effect.provide([
1126
- TestLogger.layer(),
1127
- NodeFileSystem.layer,
1128
- ]),
1129
- Effect.runPromise,
1130
- ),
1131
- )
1132
- })
1133
- })