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.
- package/README.md +3 -3
- package/dist/Development.d.ts +8 -3
- package/dist/Development.js +14 -7
- package/dist/Effectify.d.ts +212 -0
- package/dist/Effectify.js +19 -0
- package/dist/FilePathPattern.d.ts +29 -0
- package/dist/FilePathPattern.js +86 -0
- package/dist/FileRouter.d.ts +39 -41
- package/dist/FileRouter.js +104 -158
- package/dist/FileRouterCodegen.d.ts +7 -8
- package/dist/FileRouterCodegen.js +97 -66
- package/dist/PlatformError.d.ts +46 -0
- package/dist/PlatformError.js +43 -0
- package/dist/PlatformRuntime.d.ts +27 -0
- package/dist/PlatformRuntime.js +51 -0
- package/dist/Route.d.ts +6 -2
- package/dist/Route.js +22 -0
- package/dist/RouteBody.d.ts +1 -1
- package/dist/RouteHttp.d.ts +1 -1
- package/dist/RouteHttp.js +12 -19
- package/dist/RouteMount.d.ts +2 -1
- package/dist/Start.d.ts +33 -6
- package/dist/Start.js +31 -13
- package/dist/Unique.d.ts +50 -0
- package/dist/Unique.js +187 -0
- package/dist/bun/BunHttpServer.js +5 -6
- package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
- package/dist/bun/BunPlatformHttpServer.js +53 -0
- package/dist/bun/BunRoute.d.ts +4 -6
- package/dist/bun/BunRoute.js +10 -18
- package/dist/bun/BunRuntime.d.ts +2 -1
- package/dist/bun/BunRuntime.js +10 -5
- package/dist/bun/BunServer.d.ts +33 -0
- package/dist/bun/BunServer.js +133 -0
- package/dist/bun/BunServerRequest.d.ts +60 -0
- package/dist/bun/BunServerRequest.js +252 -0
- package/dist/bun/index.d.ts +1 -1
- package/dist/bun/index.js +1 -1
- package/dist/datastar/actions/fetch.d.ts +30 -0
- package/dist/datastar/actions/fetch.js +411 -0
- package/dist/datastar/actions/peek.d.ts +1 -0
- package/dist/datastar/actions/peek.js +14 -0
- package/dist/datastar/actions/setAll.d.ts +1 -0
- package/dist/datastar/actions/setAll.js +13 -0
- package/dist/datastar/actions/toggleAll.d.ts +1 -0
- package/dist/datastar/actions/toggleAll.js +13 -0
- package/dist/datastar/attributes/attr.d.ts +1 -0
- package/dist/datastar/attributes/attr.js +49 -0
- package/dist/datastar/attributes/bind.d.ts +1 -0
- package/dist/datastar/attributes/bind.js +183 -0
- package/dist/datastar/attributes/class.d.ts +1 -0
- package/dist/datastar/attributes/class.js +50 -0
- package/dist/datastar/attributes/computed.d.ts +1 -0
- package/dist/datastar/attributes/computed.js +27 -0
- package/dist/datastar/attributes/effect.d.ts +1 -0
- package/dist/datastar/attributes/effect.js +10 -0
- package/dist/datastar/attributes/indicator.d.ts +1 -0
- package/dist/datastar/attributes/indicator.js +32 -0
- package/dist/datastar/attributes/init.d.ts +1 -0
- package/dist/datastar/attributes/init.js +27 -0
- package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
- package/dist/datastar/attributes/jsonSignals.js +31 -0
- package/dist/datastar/attributes/on.d.ts +1 -0
- package/dist/datastar/attributes/on.js +59 -0
- package/dist/datastar/attributes/onIntersect.d.ts +1 -0
- package/dist/datastar/attributes/onIntersect.js +54 -0
- package/dist/datastar/attributes/onInterval.d.ts +1 -0
- package/dist/datastar/attributes/onInterval.js +31 -0
- package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
- package/dist/datastar/attributes/onSignalPatch.js +44 -0
- package/dist/datastar/attributes/ref.d.ts +1 -0
- package/dist/datastar/attributes/ref.js +11 -0
- package/dist/datastar/attributes/show.d.ts +1 -0
- package/dist/datastar/attributes/show.js +32 -0
- package/dist/datastar/attributes/signals.d.ts +1 -0
- package/dist/datastar/attributes/signals.js +18 -0
- package/dist/datastar/attributes/style.d.ts +1 -0
- package/dist/datastar/attributes/style.js +56 -0
- package/dist/datastar/attributes/text.d.ts +1 -0
- package/dist/datastar/attributes/text.js +27 -0
- package/dist/datastar/engine.d.ts +156 -0
- package/dist/datastar/engine.js +971 -0
- package/dist/datastar/index.d.ts +24 -0
- package/dist/datastar/index.js +24 -0
- package/dist/datastar/load.d.ts +24 -0
- package/dist/datastar/load.js +24 -0
- package/dist/datastar/utils.d.ts +51 -0
- package/dist/datastar/utils.js +205 -0
- package/dist/datastar/watchers/patchElements.d.ts +1 -0
- package/dist/datastar/watchers/patchElements.js +420 -0
- package/dist/datastar/watchers/patchSignals.d.ts +1 -0
- package/dist/datastar/watchers/patchSignals.js +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/node/Effectify.d.ts +209 -0
- package/dist/node/Effectify.js +19 -0
- package/dist/node/FileSystem.d.ts +3 -5
- package/dist/node/FileSystem.js +42 -62
- package/dist/node/NodeFileSystem.d.ts +7 -0
- package/dist/node/NodeFileSystem.js +420 -0
- package/dist/node/NodeUtils.d.ts +2 -0
- package/dist/node/NodeUtils.js +20 -0
- package/dist/node/PlatformError.d.ts +46 -0
- package/dist/node/PlatformError.js +43 -0
- package/dist/testing/TestLogger.js +1 -1
- package/dist/x/tailwind/plugin.js +1 -1
- package/package.json +18 -7
- package/src/Development.ts +36 -40
- package/src/Effectify.ts +269 -0
- package/src/FilePathPattern.ts +115 -0
- package/src/FileRouter.ts +178 -255
- package/src/FileRouterCodegen.ts +135 -92
- package/src/PlatformError.ts +117 -0
- package/src/PlatformRuntime.ts +108 -0
- package/src/Route.ts +31 -2
- package/src/RouteBody.ts +1 -1
- package/src/RouteHttp.ts +15 -29
- package/src/RouteMount.ts +1 -1
- package/src/Start.ts +61 -27
- package/src/Unique.ts +232 -0
- package/src/bun/BunPlatformHttpServer.ts +88 -0
- package/src/bun/BunRoute.ts +14 -24
- package/src/bun/BunRuntime.ts +21 -5
- package/src/bun/BunServer.ts +228 -0
- package/src/bun/index.ts +1 -1
- package/src/datastar/README.md +18 -0
- package/src/datastar/actions/fetch.ts +609 -0
- package/src/datastar/actions/peek.ts +17 -0
- package/src/datastar/actions/setAll.ts +20 -0
- package/src/datastar/actions/toggleAll.ts +20 -0
- package/src/datastar/attributes/attr.ts +50 -0
- package/src/datastar/attributes/bind.ts +220 -0
- package/src/datastar/attributes/class.ts +57 -0
- package/src/datastar/attributes/computed.ts +33 -0
- package/src/datastar/attributes/effect.ts +11 -0
- package/src/datastar/attributes/indicator.ts +39 -0
- package/src/datastar/attributes/init.ts +35 -0
- package/src/datastar/attributes/jsonSignals.ts +38 -0
- package/src/datastar/attributes/on.ts +71 -0
- package/src/datastar/attributes/onIntersect.ts +65 -0
- package/src/datastar/attributes/onInterval.ts +39 -0
- package/src/datastar/attributes/onSignalPatch.ts +63 -0
- package/src/datastar/attributes/ref.ts +12 -0
- package/src/datastar/attributes/show.ts +33 -0
- package/src/datastar/attributes/signals.ts +22 -0
- package/src/datastar/attributes/style.ts +63 -0
- package/src/datastar/attributes/text.ts +30 -0
- package/src/datastar/engine.ts +1341 -0
- package/src/datastar/index.ts +25 -0
- package/src/datastar/utils.ts +286 -0
- package/src/datastar/watchers/patchElements.ts +554 -0
- package/src/datastar/watchers/patchSignals.ts +15 -0
- package/src/index.ts +1 -0
- package/src/node/{FileSystem.ts → NodeFileSystem.ts} +59 -97
- package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
- package/src/testing/TestLogger.ts +1 -1
- package/src/x/tailwind/plugin.ts +1 -1
- package/dist/Random.d.ts +0 -5
- package/dist/Random.js +0 -49
- package/src/Commander.test.ts +0 -1639
- package/src/ContentNegotiation.test.ts +0 -603
- package/src/Development.test.ts +0 -119
- package/src/Entity.test.ts +0 -592
- package/src/FileRouterCodegen.todo.ts +0 -1133
- package/src/FileRouterPattern.test.ts +0 -147
- package/src/FileRouterPattern.ts +0 -59
- package/src/FileRouter_files.test.ts +0 -64
- package/src/FileRouter_path.test.ts +0 -145
- package/src/FileRouter_tree.test.ts +0 -132
- package/src/Http.test.ts +0 -319
- package/src/HttpAppExtra.test.ts +0 -103
- package/src/HttpUtils.test.ts +0 -85
- package/src/PathPattern.test.ts +0 -648
- package/src/Random.ts +0 -59
- package/src/RouteBody.test.ts +0 -232
- package/src/RouteHook.test.ts +0 -40
- package/src/RouteHttp.test.ts +0 -2909
- package/src/RouteMount.test.ts +0 -481
- package/src/RouteSchema.test.ts +0 -427
- package/src/RouteSse.test.ts +0 -249
- package/src/RouteTree.test.ts +0 -494
- package/src/RouteTrie.test.ts +0 -322
- package/src/RouterPattern.test.ts +0 -676
- package/src/RouterPattern.ts +0 -416
- package/src/StartApp.ts +0 -47
- package/src/Values.test.ts +0 -263
- package/src/bun/BunBundle.test.ts +0 -268
- package/src/bun/BunBundle_imports.test.ts +0 -48
- package/src/bun/BunHttpServer.test.ts +0 -251
- package/src/bun/BunHttpServer.ts +0 -306
- package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
- package/src/bun/BunRoute.test.ts +0 -162
- package/src/bundler/BundleHttp.test.ts +0 -132
- package/src/effect/HttpRouter.test.ts +0 -548
- package/src/experimental/EncryptedCookies.test.ts +0 -488
- package/src/hyper/HyperHtml.test.ts +0 -209
- package/src/hyper/HyperRoute.test.tsx +0 -197
- package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
- package/src/testing/TestHttpClient.test.ts +0 -83
- package/src/testing/TestLogger.test.ts +0 -51
- package/src/x/datastar/Datastar.test.ts +0 -266
- package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
- /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
|
-
})
|