effect-start 0.14.0 → 0.16.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/package.json +8 -9
- package/src/Commander.test.ts +507 -245
- package/src/ContentNegotiation.test.ts +603 -0
- package/src/ContentNegotiation.ts +542 -0
- package/src/Entity.test.ts +592 -0
- package/src/Entity.ts +362 -0
- package/src/FileRouter.ts +16 -12
- package/src/{FileRouterCodegen.test.ts → FileRouterCodegen.todo.ts} +384 -219
- package/src/FileRouterCodegen.ts +6 -6
- package/src/FileRouterPattern.test.ts +93 -62
- package/src/FileRouter_files.test.ts +5 -5
- package/src/FileRouter_path.test.ts +121 -69
- package/src/FileRouter_tree.test.ts +62 -56
- package/src/FileSystemExtra.test.ts +46 -30
- package/src/Http.test.ts +319 -0
- package/src/Http.ts +167 -0
- package/src/HttpAppExtra.test.ts +39 -20
- package/src/HttpAppExtra.ts +0 -1
- package/src/HttpUtils.test.ts +35 -18
- package/src/HttpUtils.ts +2 -0
- package/src/PathPattern.test.ts +648 -0
- package/src/PathPattern.ts +485 -0
- package/src/Route.ts +266 -1069
- package/src/RouteBody.test.ts +234 -0
- package/src/RouteBody.ts +193 -0
- package/src/RouteHook.test.ts +40 -0
- package/src/RouteHook.ts +106 -0
- package/src/RouteHttp.test.ts +2906 -0
- package/src/RouteHttp.ts +427 -0
- package/src/RouteHttpTracer.ts +92 -0
- package/src/RouteMount.test.ts +481 -0
- package/src/RouteMount.ts +470 -0
- package/src/RouteSchema.test.ts +427 -0
- package/src/RouteSchema.ts +423 -0
- package/src/RouteTree.test.ts +494 -0
- package/src/RouteTree.ts +219 -0
- package/src/RouteTrie.test.ts +322 -0
- package/src/RouteTrie.ts +224 -0
- package/src/RouterPattern.test.ts +569 -548
- package/src/RouterPattern.ts +7 -7
- package/src/Start.ts +3 -3
- package/src/StreamExtra.ts +21 -1
- package/src/TuplePathPattern.ts +64 -0
- package/src/Values.test.ts +263 -0
- package/src/Values.ts +76 -0
- package/src/bun/BunBundle.test.ts +36 -42
- package/src/bun/BunBundle.ts +2 -2
- package/src/bun/BunBundle_imports.test.ts +4 -6
- package/src/bun/BunHttpServer.test.ts +183 -6
- package/src/bun/BunHttpServer.ts +72 -32
- package/src/bun/BunHttpServer_web.ts +18 -6
- package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
- package/src/bun/BunRoute.test.ts +124 -442
- package/src/bun/BunRoute.ts +146 -286
- package/src/{BundleHttp.test.ts → bundler/BundleHttp.test.ts} +34 -60
- package/src/{BundleHttp.ts → bundler/BundleHttp.ts} +1 -2
- package/src/client/index.ts +1 -1
- package/src/{Effect_HttpRouter.test.ts → effect/HttpRouter.test.ts} +69 -90
- package/src/experimental/EncryptedCookies.test.ts +125 -64
- package/src/experimental/SseHttpResponse.ts +0 -1
- package/src/hyper/Hyper.ts +89 -0
- package/src/{HyperHtml.test.ts → hyper/HyperHtml.test.ts} +13 -13
- package/src/{HyperHtml.ts → hyper/HyperHtml.ts} +2 -2
- package/src/{jsx.d.ts → hyper/jsx.d.ts} +1 -1
- package/src/index.ts +3 -4
- package/src/middlewares/BasicAuthMiddleware.test.ts +29 -19
- package/src/{NodeFileSystem.ts → node/FileSystem.ts} +6 -2
- package/src/testing/TestHttpClient.test.ts +26 -26
- package/src/testing/TestLogger.test.ts +27 -14
- package/src/testing/TestLogger.ts +15 -9
- package/src/x/datastar/Datastar.test.ts +47 -48
- package/src/x/datastar/Datastar.ts +1 -1
- package/src/x/tailwind/TailwindPlugin.test.ts +56 -58
- package/src/x/tailwind/plugin.ts +1 -1
- package/src/FileHttpRouter.test.ts +0 -239
- package/src/FileHttpRouter.ts +0 -194
- package/src/Hyper.ts +0 -194
- package/src/Route.test.ts +0 -1370
- package/src/RouteRender.ts +0 -40
- package/src/Router.test.ts +0 -375
- package/src/Router.ts +0 -255
- package/src/bun/BunRoute_bundles.test.ts +0 -219
- /package/src/{Bundle.ts → bundler/Bundle.ts} +0 -0
- /package/src/{BundleFiles.ts → bundler/BundleFiles.ts} +0 -0
- /package/src/{HyperNode.ts → hyper/HyperNode.ts} +0 -0
- /package/src/{jsx-runtime.ts → hyper/jsx-runtime.ts} +0 -0
- /package/src/{NodeUtils.ts → node/Utils.ts} +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import * as test from "bun:test"
|
|
2
|
+
import * as Route from "./Route.ts"
|
|
3
|
+
import * as RouteMount from "./RouteMount.ts"
|
|
4
|
+
import * as RouteTrie from "./RouteTrie.ts"
|
|
5
|
+
|
|
6
|
+
test.describe(RouteTrie.make, () => {
|
|
7
|
+
test.it("creates trie from route set", () => {
|
|
8
|
+
const routes = Route
|
|
9
|
+
.add("/about", Route.get(Route.text("About")))
|
|
10
|
+
|
|
11
|
+
const trie = RouteTrie.make(routes)
|
|
12
|
+
|
|
13
|
+
test
|
|
14
|
+
.expect(trie.methods["GET"])
|
|
15
|
+
.toBeDefined()
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test.describe(RouteTrie.lookup, () => {
|
|
20
|
+
test.it("matches exact static path", () => {
|
|
21
|
+
const routes = Route
|
|
22
|
+
.add("/about", Route.get(Route.text("About")))
|
|
23
|
+
const trie = RouteTrie.make(routes)
|
|
24
|
+
|
|
25
|
+
const results = RouteTrie.lookup(trie, "GET", "/about")
|
|
26
|
+
|
|
27
|
+
test
|
|
28
|
+
.expect(results.length)
|
|
29
|
+
.toBe(1)
|
|
30
|
+
test
|
|
31
|
+
.expect(results[0].params)
|
|
32
|
+
.toEqual({})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test.it("returns empty for non-matching path", () => {
|
|
36
|
+
const routes = Route
|
|
37
|
+
.add("/about", Route.get(Route.text("About")))
|
|
38
|
+
const trie = RouteTrie.make(routes)
|
|
39
|
+
|
|
40
|
+
const results = RouteTrie.lookup(trie, "GET", "/contact")
|
|
41
|
+
|
|
42
|
+
test
|
|
43
|
+
.expect(results.length)
|
|
44
|
+
.toBe(0)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test.it("matches path with single param", () => {
|
|
48
|
+
const routes = Route
|
|
49
|
+
.add("/users/:id", Route.get(Route.text("User")))
|
|
50
|
+
const trie = RouteTrie.make(routes)
|
|
51
|
+
|
|
52
|
+
const results = RouteTrie.lookup(trie, "GET", "/users/123")
|
|
53
|
+
|
|
54
|
+
test
|
|
55
|
+
.expect(results.length)
|
|
56
|
+
.toBe(1)
|
|
57
|
+
test
|
|
58
|
+
.expect(results[0].params)
|
|
59
|
+
.toEqual({ id: "123" })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test.it("matches path with multiple params", () => {
|
|
63
|
+
const routes = Route
|
|
64
|
+
.add("/users/:userId/posts/:postId", Route.get(Route.text("Post")))
|
|
65
|
+
const trie = RouteTrie.make(routes)
|
|
66
|
+
|
|
67
|
+
const results = RouteTrie.lookup(trie, "GET", "/users/42/posts/7")
|
|
68
|
+
|
|
69
|
+
test
|
|
70
|
+
.expect(results.length)
|
|
71
|
+
.toBe(1)
|
|
72
|
+
test
|
|
73
|
+
.expect(results[0].params)
|
|
74
|
+
.toEqual({
|
|
75
|
+
userId: "42",
|
|
76
|
+
postId: "7",
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test.it("matches path with optional param present", () => {
|
|
81
|
+
const routes = Route
|
|
82
|
+
.add("/files/:name?", Route.get(Route.text("File")))
|
|
83
|
+
const trie = RouteTrie.make(routes)
|
|
84
|
+
|
|
85
|
+
const results = RouteTrie.lookup(trie, "GET", "/files/readme")
|
|
86
|
+
|
|
87
|
+
test
|
|
88
|
+
.expect(results.length)
|
|
89
|
+
.toBe(1)
|
|
90
|
+
test
|
|
91
|
+
.expect(results[0].params)
|
|
92
|
+
.toEqual({ name: "readme" })
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test.it("matches path with optional param absent", () => {
|
|
96
|
+
const routes = Route
|
|
97
|
+
.add("/files/:name?", Route.get(Route.text("File")))
|
|
98
|
+
const trie = RouteTrie.make(routes)
|
|
99
|
+
|
|
100
|
+
const results = RouteTrie.lookup(trie, "GET", "/files")
|
|
101
|
+
|
|
102
|
+
test
|
|
103
|
+
.expect(results.length)
|
|
104
|
+
.toBe(1)
|
|
105
|
+
test
|
|
106
|
+
.expect(results[0].params)
|
|
107
|
+
.toEqual({})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test.it("matches path with optional wildcard param", () => {
|
|
111
|
+
const routes = Route
|
|
112
|
+
.add("/docs/:path*", Route.get(Route.text("Docs")))
|
|
113
|
+
const trie = RouteTrie.make(routes)
|
|
114
|
+
|
|
115
|
+
const results = RouteTrie.lookup(trie, "GET", "/docs/api/users/create")
|
|
116
|
+
|
|
117
|
+
test
|
|
118
|
+
.expect(results.length)
|
|
119
|
+
.toBe(1)
|
|
120
|
+
test
|
|
121
|
+
.expect(results[0].params)
|
|
122
|
+
.toEqual({
|
|
123
|
+
path: "api/users/create",
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test.it("matches path with optional wildcard when empty", () => {
|
|
128
|
+
const routes = Route
|
|
129
|
+
.add("/docs/:path*", Route.get(Route.text("Docs")))
|
|
130
|
+
const trie = RouteTrie.make(routes)
|
|
131
|
+
|
|
132
|
+
const results = RouteTrie.lookup(trie, "GET", "/docs")
|
|
133
|
+
|
|
134
|
+
test
|
|
135
|
+
.expect(results.length)
|
|
136
|
+
.toBe(1)
|
|
137
|
+
test
|
|
138
|
+
.expect(results[0].params)
|
|
139
|
+
.toEqual({})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test.it("matches path with required wildcard param", () => {
|
|
143
|
+
const routes = Route
|
|
144
|
+
.add("/docs/:path+", Route.get(Route.text("Docs")))
|
|
145
|
+
const trie = RouteTrie.make(routes)
|
|
146
|
+
|
|
147
|
+
const results = RouteTrie.lookup(trie, "GET", "/docs/api/users/create")
|
|
148
|
+
|
|
149
|
+
test
|
|
150
|
+
.expect(results.length)
|
|
151
|
+
.toBe(1)
|
|
152
|
+
test
|
|
153
|
+
.expect(results[0].params)
|
|
154
|
+
.toEqual({
|
|
155
|
+
path: "api/users/create",
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test.it("does not match required wildcard when empty", () => {
|
|
160
|
+
const routes = Route
|
|
161
|
+
.add("/docs/:path+", Route.get(Route.text("Docs")))
|
|
162
|
+
const trie = RouteTrie.make(routes)
|
|
163
|
+
|
|
164
|
+
const results = RouteTrie.lookup(trie, "GET", "/docs")
|
|
165
|
+
|
|
166
|
+
test
|
|
167
|
+
.expect(results.length)
|
|
168
|
+
.toBe(0)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test.it("required wildcard beats optional wildcard in priority", () => {
|
|
172
|
+
const routes = Route
|
|
173
|
+
.add("/files/:path*", Route.get(Route.text("Optional")))
|
|
174
|
+
.add("/files/:path+", Route.get(Route.text("Required")))
|
|
175
|
+
const trie = RouteTrie.make(routes)
|
|
176
|
+
|
|
177
|
+
const multiResults = RouteTrie.lookup(trie, "GET", "/files/a/b/c")
|
|
178
|
+
|
|
179
|
+
test
|
|
180
|
+
.expect(multiResults.length)
|
|
181
|
+
.toBe(2)
|
|
182
|
+
test
|
|
183
|
+
.expect(multiResults[0].params)
|
|
184
|
+
.toEqual({ path: "a/b/c" })
|
|
185
|
+
test
|
|
186
|
+
.expect(multiResults[1].params)
|
|
187
|
+
.toEqual({ path: "a/b/c" })
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test.it("optional wildcard matches when required cannot", () => {
|
|
191
|
+
const routes = Route
|
|
192
|
+
.add("/files/:path*", Route.get(Route.text("Optional")))
|
|
193
|
+
.add("/files/:path+", Route.get(Route.text("Required")))
|
|
194
|
+
const trie = RouteTrie.make(routes)
|
|
195
|
+
|
|
196
|
+
const emptyResults = RouteTrie.lookup(trie, "GET", "/files")
|
|
197
|
+
|
|
198
|
+
test
|
|
199
|
+
.expect(emptyResults.length)
|
|
200
|
+
.toBe(1)
|
|
201
|
+
test
|
|
202
|
+
.expect(emptyResults[0].params)
|
|
203
|
+
.toEqual({})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test.it("prioritizes static over param routes", () => {
|
|
207
|
+
const routes = Route
|
|
208
|
+
.add("/users/:id", Route.get(Route.text("User by ID")))
|
|
209
|
+
.add("/users/me", Route.get(Route.text("Current user")))
|
|
210
|
+
const trie = RouteTrie.make(routes)
|
|
211
|
+
|
|
212
|
+
const results = RouteTrie.lookup(trie, "GET", "/users/me")
|
|
213
|
+
|
|
214
|
+
test
|
|
215
|
+
.expect(results.length)
|
|
216
|
+
.toBe(2)
|
|
217
|
+
test
|
|
218
|
+
.expect(results[0].params)
|
|
219
|
+
.toEqual({})
|
|
220
|
+
test
|
|
221
|
+
.expect(results[1].params)
|
|
222
|
+
.toEqual({ id: "me" })
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test.it("matches nested mounted routes", () => {
|
|
226
|
+
const routes = Route
|
|
227
|
+
.add(
|
|
228
|
+
"/admin",
|
|
229
|
+
Route
|
|
230
|
+
.add("/users", Route.get(Route.text("Admin users")))
|
|
231
|
+
.add("/settings", Route.get(Route.text("Admin settings"))),
|
|
232
|
+
)
|
|
233
|
+
const trie = RouteTrie.make(routes)
|
|
234
|
+
|
|
235
|
+
const results = RouteTrie.lookup(trie, "GET", "/admin/users")
|
|
236
|
+
|
|
237
|
+
test
|
|
238
|
+
.expect(results.length)
|
|
239
|
+
.toBe(1)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test.it("normalizes paths with trailing slashes", () => {
|
|
243
|
+
const routes = Route
|
|
244
|
+
.add("/about", Route.get(Route.text("About")))
|
|
245
|
+
const trie = RouteTrie.make(routes)
|
|
246
|
+
|
|
247
|
+
const results = RouteTrie.lookup(trie, "GET", "/about/")
|
|
248
|
+
|
|
249
|
+
test
|
|
250
|
+
.expect(results.length)
|
|
251
|
+
.toBe(1)
|
|
252
|
+
test
|
|
253
|
+
.expect(results[0].params)
|
|
254
|
+
.toEqual({})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test.it("matches root path", () => {
|
|
258
|
+
const routes = Route
|
|
259
|
+
.add("/", Route.get(Route.text("Home")))
|
|
260
|
+
const trie = RouteTrie.make(routes)
|
|
261
|
+
|
|
262
|
+
const results = RouteTrie.lookup(trie, "GET", "/")
|
|
263
|
+
|
|
264
|
+
test
|
|
265
|
+
.expect(results.length)
|
|
266
|
+
.toBe(1)
|
|
267
|
+
test
|
|
268
|
+
.expect(results[0].params)
|
|
269
|
+
.toEqual({})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test.it("matches method-specific routes", () => {
|
|
273
|
+
const routes = Route
|
|
274
|
+
.add("/users", Route.get(Route.text("List users")))
|
|
275
|
+
.add("/users", Route.post(Route.text("Create user")))
|
|
276
|
+
const trie = RouteTrie.make(routes)
|
|
277
|
+
|
|
278
|
+
const getResults = RouteTrie.lookup(trie, "GET", "/users")
|
|
279
|
+
const postResults = RouteTrie.lookup(trie, "POST", "/users")
|
|
280
|
+
|
|
281
|
+
test
|
|
282
|
+
.expect(getResults.length)
|
|
283
|
+
.toBe(1)
|
|
284
|
+
test
|
|
285
|
+
.expect(postResults.length)
|
|
286
|
+
.toBe(1)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test.it("matches wildcard method routes", () => {
|
|
290
|
+
const routes = Route
|
|
291
|
+
.add("/health", Route.use(Route.text("OK")))
|
|
292
|
+
const trie = RouteTrie.make(routes)
|
|
293
|
+
|
|
294
|
+
const getResults = RouteTrie.lookup(trie, "GET", "/health")
|
|
295
|
+
const postResults = RouteTrie.lookup(trie, "POST", "/health")
|
|
296
|
+
|
|
297
|
+
test
|
|
298
|
+
.expect(getResults.length)
|
|
299
|
+
.toBe(1)
|
|
300
|
+
test
|
|
301
|
+
.expect(postResults.length)
|
|
302
|
+
.toBe(1)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
test.it("returns multiple matches for content negotiation", () => {
|
|
306
|
+
const routes = Route
|
|
307
|
+
.add(
|
|
308
|
+
"/page",
|
|
309
|
+
Route.get(
|
|
310
|
+
Route.text("Plain text"),
|
|
311
|
+
Route.html("<p>HTML</p>"),
|
|
312
|
+
),
|
|
313
|
+
)
|
|
314
|
+
const trie = RouteTrie.make(routes)
|
|
315
|
+
|
|
316
|
+
const results = RouteTrie.lookup(trie, "GET", "/page")
|
|
317
|
+
|
|
318
|
+
test
|
|
319
|
+
.expect(results.length)
|
|
320
|
+
.toBe(2)
|
|
321
|
+
})
|
|
322
|
+
})
|
package/src/RouteTrie.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import * as PathPattern from "./PathPattern.ts"
|
|
2
|
+
import * as Route from "./Route.ts"
|
|
3
|
+
|
|
4
|
+
export interface Node {
|
|
5
|
+
children: Record<string, Node>
|
|
6
|
+
paramChild: Node | null
|
|
7
|
+
paramName: string | null
|
|
8
|
+
requiredWildcardChild: Node | null
|
|
9
|
+
requiredWildcardName: string | null
|
|
10
|
+
optionalWildcardChild: Node | null
|
|
11
|
+
optionalWildcardName: string | null
|
|
12
|
+
routes: Route.Route.Route[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RouteTrie {
|
|
16
|
+
readonly methods: Record<string, Node>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LookupResult {
|
|
20
|
+
route: Route.Route.Route
|
|
21
|
+
params: Record<string, string>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createNode(): Node {
|
|
25
|
+
return {
|
|
26
|
+
children: {},
|
|
27
|
+
paramChild: null,
|
|
28
|
+
paramName: null,
|
|
29
|
+
requiredWildcardChild: null,
|
|
30
|
+
requiredWildcardName: null,
|
|
31
|
+
optionalWildcardChild: null,
|
|
32
|
+
optionalWildcardName: null,
|
|
33
|
+
routes: [],
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function insertRoute(
|
|
38
|
+
node: Node,
|
|
39
|
+
segments: string[],
|
|
40
|
+
route: Route.Route.Route,
|
|
41
|
+
): void {
|
|
42
|
+
if (segments.length === 0) {
|
|
43
|
+
node.routes.push(route)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const segment = segments[0]
|
|
48
|
+
const rest = segments.slice(1)
|
|
49
|
+
|
|
50
|
+
if (segment.startsWith(":")) {
|
|
51
|
+
const name = segment.slice(1)
|
|
52
|
+
|
|
53
|
+
if (name.endsWith("+")) {
|
|
54
|
+
if (!node.requiredWildcardChild) {
|
|
55
|
+
node.requiredWildcardChild = createNode()
|
|
56
|
+
}
|
|
57
|
+
node.requiredWildcardChild.requiredWildcardName = name.slice(0, -1)
|
|
58
|
+
node.requiredWildcardChild.routes.push(route)
|
|
59
|
+
} else if (name.endsWith("*")) {
|
|
60
|
+
if (!node.optionalWildcardChild) {
|
|
61
|
+
node.optionalWildcardChild = createNode()
|
|
62
|
+
}
|
|
63
|
+
node.optionalWildcardChild.optionalWildcardName = name.slice(0, -1)
|
|
64
|
+
node.optionalWildcardChild.routes.push(route)
|
|
65
|
+
} else if (name.endsWith("?")) {
|
|
66
|
+
if (!node.paramChild) {
|
|
67
|
+
node.paramChild = createNode()
|
|
68
|
+
}
|
|
69
|
+
node.paramChild.paramName = name.slice(0, -1)
|
|
70
|
+
insertRoute(node.paramChild, rest, route)
|
|
71
|
+
insertRoute(node, rest, route)
|
|
72
|
+
} else {
|
|
73
|
+
if (!node.paramChild) {
|
|
74
|
+
node.paramChild = createNode()
|
|
75
|
+
}
|
|
76
|
+
node.paramChild.paramName = name
|
|
77
|
+
insertRoute(node.paramChild, rest, route)
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
if (!node.children[segment]) {
|
|
81
|
+
node.children[segment] = createNode()
|
|
82
|
+
}
|
|
83
|
+
insertRoute(node.children[segment], rest, route)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface CollectedRoute {
|
|
88
|
+
route: Route.Route.Route
|
|
89
|
+
method: string
|
|
90
|
+
path: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function collectRoutes(
|
|
94
|
+
items: Route.Route.Tuple,
|
|
95
|
+
parentPath: string,
|
|
96
|
+
parentMethod: string,
|
|
97
|
+
): CollectedRoute[] {
|
|
98
|
+
const results: CollectedRoute[] = []
|
|
99
|
+
|
|
100
|
+
for (const item of items) {
|
|
101
|
+
const desc = Route.descriptor(item) as { path?: string; method?: string }
|
|
102
|
+
const currentPath = typeof desc?.path === "string"
|
|
103
|
+
? parentPath + desc.path
|
|
104
|
+
: parentPath
|
|
105
|
+
const currentMethod = desc?.method ?? parentMethod
|
|
106
|
+
|
|
107
|
+
if (Route.isRoute(item)) {
|
|
108
|
+
if (currentPath !== "") {
|
|
109
|
+
results.push({
|
|
110
|
+
route: item,
|
|
111
|
+
method: currentMethod,
|
|
112
|
+
path: currentPath,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
const nestedItems = Route.items(item)
|
|
117
|
+
results.push(...collectRoutes(nestedItems, currentPath, currentMethod))
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return results
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function make(set: Route.RouteSet.Any): RouteTrie {
|
|
125
|
+
const methods: Record<string, Node> = {}
|
|
126
|
+
const collected = collectRoutes(Route.items(set), "", "*")
|
|
127
|
+
|
|
128
|
+
for (const { route, method, path } of collected) {
|
|
129
|
+
if (!methods[method]) {
|
|
130
|
+
methods[method] = createNode()
|
|
131
|
+
}
|
|
132
|
+
const result = PathPattern.validate(path)
|
|
133
|
+
if (!result.ok) {
|
|
134
|
+
throw new Error(result.error)
|
|
135
|
+
}
|
|
136
|
+
insertRoute(methods[method], result.segments, route)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { methods }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function lookupNode(
|
|
143
|
+
node: Node,
|
|
144
|
+
segments: string[],
|
|
145
|
+
params: Record<string, string>,
|
|
146
|
+
): LookupResult[] {
|
|
147
|
+
const results: LookupResult[] = []
|
|
148
|
+
|
|
149
|
+
if (segments.length === 0) {
|
|
150
|
+
for (const route of node.routes) {
|
|
151
|
+
results.push({ route, params })
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
node.optionalWildcardChild
|
|
155
|
+
&& node.optionalWildcardChild.optionalWildcardName
|
|
156
|
+
) {
|
|
157
|
+
for (const route of node.optionalWildcardChild.routes) {
|
|
158
|
+
results.push({ route, params })
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return results
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const segment = segments[0]
|
|
165
|
+
const rest = segments.slice(1)
|
|
166
|
+
|
|
167
|
+
if (node.children[segment]) {
|
|
168
|
+
results.push(...lookupNode(node.children[segment], rest, params))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (node.paramChild && node.paramChild.paramName) {
|
|
172
|
+
const newParams = { ...params, [node.paramChild.paramName]: segment }
|
|
173
|
+
results.push(...lookupNode(node.paramChild, rest, newParams))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (
|
|
177
|
+
node.requiredWildcardChild
|
|
178
|
+
&& node.requiredWildcardChild.requiredWildcardName
|
|
179
|
+
) {
|
|
180
|
+
const wildcardValue = segments.join("/")
|
|
181
|
+
const newParams = {
|
|
182
|
+
...params,
|
|
183
|
+
[node.requiredWildcardChild.requiredWildcardName]: wildcardValue,
|
|
184
|
+
}
|
|
185
|
+
for (const route of node.requiredWildcardChild.routes) {
|
|
186
|
+
results.push({ route, params: newParams })
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
node.optionalWildcardChild
|
|
192
|
+
&& node.optionalWildcardChild.optionalWildcardName
|
|
193
|
+
) {
|
|
194
|
+
const wildcardValue = segments.join("/")
|
|
195
|
+
const newParams = {
|
|
196
|
+
...params,
|
|
197
|
+
[node.optionalWildcardChild.optionalWildcardName]: wildcardValue,
|
|
198
|
+
}
|
|
199
|
+
for (const route of node.optionalWildcardChild.routes) {
|
|
200
|
+
results.push({ route, params: newParams })
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return results
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function lookup(
|
|
208
|
+
trie: RouteTrie,
|
|
209
|
+
method: string,
|
|
210
|
+
path: string,
|
|
211
|
+
): LookupResult[] {
|
|
212
|
+
const segments = path.split("/").filter(Boolean)
|
|
213
|
+
const results: LookupResult[] = []
|
|
214
|
+
|
|
215
|
+
if (trie.methods[method]) {
|
|
216
|
+
results.push(...lookupNode(trie.methods[method], segments, {}))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (method !== "*" && trie.methods["*"]) {
|
|
220
|
+
results.push(...lookupNode(trie.methods["*"], segments, {}))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return results
|
|
224
|
+
}
|