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,494 +0,0 @@
1
- import * as test from "bun:test"
2
- import * as Route from "./Route.ts"
3
- import * as RouteTree from "./RouteTree.ts"
4
-
5
- test.describe("layer route", () => {
6
- test.it("merges LayerRoute into other routes", () => {
7
- const tree = RouteTree.make({
8
- "*": Route.use(Route.filter({ context: { authenticated: true } })),
9
- "/users": Route.get(Route.text("users")),
10
- })
11
-
12
- type TreeRoutes = RouteTree.Routes<typeof tree>
13
-
14
- test
15
- .expectTypeOf<TreeRoutes>()
16
- .toExtend<{
17
- "/users": Route.Route.Tuple
18
- }>()
19
-
20
- // "*" key should not exist in the resulting type
21
- test
22
- .expectTypeOf<TreeRoutes>()
23
- .not
24
- .toHaveProperty("*")
25
-
26
- // layer route should be first in the tuple (method: "*")
27
- test
28
- .expectTypeOf<TreeRoutes["/users"][0]>()
29
- .toExtend<Route.Route.With<{ method: "*" }>>()
30
-
31
- // actual route should be second (method: "GET")
32
- test
33
- .expectTypeOf<TreeRoutes["/users"][1]>()
34
- .toExtend<Route.Route.With<{ method: "GET" }>>()
35
- })
36
-
37
- test.it("prepends LayerRoute to all other routes when walking", () => {
38
- const tree = RouteTree.make({
39
- "*": Route.use(Route.filter({ context: { layer: true } })),
40
- "/users": Route.get(Route.text("users")),
41
- "/admin": Route.post(Route.json({ ok: true })),
42
- })
43
-
44
- test.expect(Route.descriptor(RouteTree.walk(tree))).toEqual([
45
- { path: "/admin", method: "*" },
46
- { path: "/admin", method: "POST", format: "json" },
47
- { path: "/users", method: "*" },
48
- { path: "/users", method: "GET", format: "text" },
49
- ])
50
- })
51
-
52
- test.it("prepends multiple LayerRoutes to all other routes", () => {
53
- const tree = RouteTree.make({
54
- "*": Route
55
- .use(Route.filter({ context: { first: true } }))
56
- .use(Route.filter({ context: { second: true } })),
57
- "/users": Route.get(Route.text("users")),
58
- })
59
-
60
- test.expect(Route.descriptor(RouteTree.walk(tree))).toEqual([
61
- { path: "/users", method: "*" },
62
- { path: "/users", method: "*" },
63
- { path: "/users", method: "GET", format: "text" },
64
- ])
65
- })
66
-
67
- test.it("only allows method '*' routes under '*' key", () => {
68
- const _tree = RouteTree.make({
69
- // @ts-expect-error - LayerRoute must have method "*"
70
- "*": Route.get(Route.text("invalid")),
71
- "/users": Route.get(Route.text("users")),
72
- })
73
- })
74
-
75
- test.it("lookup finds LayerRoute first", () => {
76
- const tree = RouteTree.make({
77
- "*": Route.use(Route.filter({ context: { layer: true } })),
78
- "/users": Route.get(Route.text("users")),
79
- })
80
-
81
- const result = RouteTree.lookup(tree, "GET", "/users")
82
- test.expect(result).not.toBeNull()
83
- test.expect(Route.descriptor(result!.route).method).toBe("*")
84
- })
85
-
86
- test.it("works without LayerRoute (no '*' key)", () => {
87
- const tree = RouteTree.make({
88
- "/users": Route.get(Route.text("users")),
89
- "/admin": Route.post(Route.json({ ok: true })),
90
- })
91
-
92
- test
93
- .expect(Route.descriptor(RouteTree.walk(tree)).map((d) => d.path))
94
- .toEqual(["/admin", "/users"])
95
- })
96
- })
97
-
98
- test.describe(RouteTree.make, () => {
99
- test.it("makes", () => {
100
- const routes = RouteTree.make({
101
- "/admin": Route
102
- .use(
103
- Route.filter({
104
- context: {
105
- isAdmin: true,
106
- },
107
- }),
108
- )
109
- .get(
110
- Route.text("admin home"),
111
- ),
112
-
113
- "/users": Route
114
- .get(
115
- Route.text("users list"),
116
- ),
117
- })
118
-
119
- test
120
- .expectTypeOf<RouteTree.Routes<typeof routes>>()
121
- .toExtend<{
122
- "/admin": unknown
123
- "/users": unknown
124
- }>()
125
- })
126
-
127
- test.it("flattens nested route trees with prefixed paths", () => {
128
- const apiTree = RouteTree.make({
129
- "/users": Route.get(Route.json({ users: [] })),
130
- "/posts": Route.get(Route.json({ posts: [] })),
131
- })
132
-
133
- const tree = RouteTree.make({
134
- "/": Route.get(Route.text("home")),
135
- "/api": apiTree,
136
- })
137
-
138
- type TreeRoutes = RouteTree.Routes<typeof tree>
139
-
140
- test
141
- .expectTypeOf<TreeRoutes["/"]>()
142
- .toExtend<Route.Route.Tuple>()
143
-
144
- test
145
- .expectTypeOf<TreeRoutes["/"][0]>()
146
- .toExtend<
147
- Route.Route.With<
148
- { method: "GET"; format: "text" }
149
- >
150
- >()
151
-
152
- test
153
- .expectTypeOf<TreeRoutes["/api/users"]>()
154
- .toExtend<Route.Route.Tuple>()
155
-
156
- test
157
- .expectTypeOf<TreeRoutes["/api/users"][0]>()
158
- .toExtend<
159
- Route.Route.With<
160
- { method: "GET"; format: "json" }
161
- >
162
- >()
163
-
164
- test
165
- .expectTypeOf<TreeRoutes["/api/posts"]>()
166
- .toExtend<Route.Route.Tuple>()
167
-
168
- test
169
- .expectTypeOf<TreeRoutes["/api/posts"][0]>()
170
- .toExtend<
171
- Route.Route.With<
172
- { method: "GET"; format: "json" }
173
- >
174
- >()
175
- })
176
-
177
- test.it("walks nested route trees with prefixed paths", () => {
178
- const apiTree = RouteTree.make({
179
- "/users": Route.get(Route.json({ users: [] })),
180
- "/posts": Route.post(Route.json({ ok: true })),
181
- })
182
-
183
- const tree = RouteTree.make({
184
- "/": Route.get(Route.text("home")),
185
- "/api": apiTree,
186
- })
187
-
188
- test.expect(Route.descriptor(RouteTree.walk(tree))).toEqual([
189
- { path: "/", method: "GET", format: "text" },
190
- { path: "/api/posts", method: "POST", format: "json" },
191
- { path: "/api/users", method: "GET", format: "json" },
192
- ])
193
- })
194
-
195
- test.it("deeply nested route trees", () => {
196
- const v1Tree = RouteTree.make({
197
- "/health": Route.get(Route.text("ok")),
198
- })
199
-
200
- const apiTree = RouteTree.make({
201
- "/v1": v1Tree,
202
- })
203
-
204
- const tree = RouteTree.make({
205
- "/api": apiTree,
206
- })
207
-
208
- test
209
- .expect(Route.descriptor(RouteTree.walk(tree)).map((d) => d.path))
210
- .toEqual(["/api/v1/health"])
211
- })
212
-
213
- test.it("lookup works with nested trees", () => {
214
- const apiTree = RouteTree.make({
215
- "/users": Route.get(Route.json({ users: [] })),
216
- "/users/:id": Route.get(Route.json({ user: null })),
217
- })
218
-
219
- const tree = RouteTree.make({
220
- "/": Route.get(Route.text("home")),
221
- "/api": apiTree,
222
- })
223
-
224
- const home = RouteTree.lookup(tree, "GET", "/")
225
- test.expect(Route.descriptor(home!.route).path).toBe("/")
226
-
227
- const users = RouteTree.lookup(tree, "GET", "/api/users")
228
- test.expect(Route.descriptor(users!.route).path).toBe("/api/users")
229
-
230
- const user = RouteTree.lookup(tree, "GET", "/api/users/123")
231
- test.expect(Route.descriptor(user!.route).path).toBe("/api/users/:id")
232
- test.expect(user!.params).toEqual({ id: "123" })
233
- })
234
- })
235
-
236
- test.describe(RouteTree.walk, () => {
237
- test.it("walks in sorted order: by depth, static before params", () => {
238
- // routes defined in random order
239
- const routes = RouteTree.make({
240
- "/users/:userId": Route.get(Route.text("user detail")),
241
- "/admin/stats": Route.get(Route.html("admin stats")),
242
- "/": Route.get(Route.text("home")),
243
- "/admin/users": Route.post(Route.json({ ok: true })),
244
- "/users": Route.get(Route.text("users list")),
245
- "/admin": Route.use(Route.filter({ context: { admin: true } })),
246
- })
247
-
248
- // expected order:
249
- // depth 0: /
250
- // depth 1: /admin, /users (alphabetical)
251
- // depth 2: /admin/stats, /admin/users, /users/:userId (static first, param last)
252
- test
253
- .expect(Route.descriptor(RouteTree.walk(routes)).map((d) => d.path))
254
- .toEqual([
255
- "/",
256
- "/admin",
257
- "/users",
258
- "/admin/stats",
259
- "/admin/users",
260
- "/users/:userId",
261
- ])
262
- })
263
-
264
- test.it("static < :param < :param? < :param+ < :param*", () => {
265
- const routes = RouteTree.make({
266
- "/:path*": Route.get(Route.text("catch all")),
267
- "/about": Route.get(Route.text("about")),
268
- "/:path+": Route.get(Route.text("one or more")),
269
- "/:page": Route.get(Route.text("single param")),
270
- "/:page?": Route.get(Route.text("optional param")),
271
- })
272
-
273
- test
274
- .expect(Route.descriptor(RouteTree.walk(routes)).map((d) => d.path))
275
- .toEqual([
276
- "/about",
277
- "/:page",
278
- "/:page?",
279
- "/:path+",
280
- "/:path*",
281
- ])
282
- })
283
-
284
- test.it("greedy routes come after all non-greedy across depth", () => {
285
- const routes = RouteTree.make({
286
- "/:path*": Route.get(Route.text("catch all")),
287
- "/users/:id": Route.get(Route.text("user detail")),
288
- "/users": Route.get(Route.text("users")),
289
- "/users/:id/posts/:postId": Route.get(Route.text("post detail")),
290
- })
291
-
292
- test
293
- .expect(Route.descriptor(RouteTree.walk(routes)).map((d) => d.path))
294
- .toEqual([
295
- "/users",
296
- "/users/:id",
297
- "/users/:id/posts/:postId",
298
- "/:path*",
299
- ])
300
- })
301
-
302
- test.it("greedy routes sorted by greedy position", () => {
303
- const routes = RouteTree.make({
304
- "/:path*": Route.get(Route.text("root catch all")),
305
- "/api/:rest*": Route.get(Route.text("api catch all")),
306
- "/api/v1/:rest*": Route.get(Route.text("api v1 catch all")),
307
- })
308
-
309
- test
310
- .expect(Route.descriptor(RouteTree.walk(routes)).map((d) => d.path))
311
- .toEqual([
312
- "/api/v1/:rest*",
313
- "/api/:rest*",
314
- "/:path*",
315
- ])
316
- })
317
-
318
- test.it("greedy routes with same position sorted by prefix then type", () => {
319
- const routes = RouteTree.make({
320
- "/docs/:path*": Route.get(Route.text("docs catch all")),
321
- "/api/:rest+": Route.get(Route.text("api one or more")),
322
- "/api/:rest*": Route.get(Route.text("api catch all")),
323
- })
324
-
325
- // /api before /docs (alphabetical), then + before * for same prefix
326
- test
327
- .expect(Route.descriptor(RouteTree.walk(routes)).map((d) => d.path))
328
- .toEqual([
329
- "/api/:rest+",
330
- "/api/:rest*",
331
- "/docs/:path*",
332
- ])
333
- })
334
- })
335
-
336
- test.describe(RouteTree.lookup, () => {
337
- test.it("matches static paths", () => {
338
- const tree = RouteTree.make({
339
- "/users": Route.get(Route.text("users list")),
340
- "/admin": Route.get(Route.text("admin")),
341
- })
342
-
343
- const result = RouteTree.lookup(tree, "GET", "/users")
344
- test.expect(result).not.toBeNull()
345
- test.expect(result!.params).toEqual({})
346
- test.expect(Route.descriptor(result!.route).path).toBe("/users")
347
- })
348
-
349
- test.it("extracts path parameters", () => {
350
- const tree = RouteTree.make({
351
- "/users/:id": Route.get(Route.text("user detail")),
352
- })
353
-
354
- const result = RouteTree.lookup(tree, "GET", "/users/123")
355
- test.expect(result).not.toBeNull()
356
- test.expect(result!.params).toEqual({ id: "123" })
357
- })
358
-
359
- test.it("filters by HTTP method", () => {
360
- const tree = RouteTree.make({
361
- "/users": Route.get(Route.text("get users")),
362
- "/admin": Route.post(Route.text("post admin")),
363
- })
364
-
365
- const getResult = RouteTree.lookup(tree, "GET", "/users")
366
- test.expect(getResult).not.toBeNull()
367
-
368
- const postOnGet = RouteTree.lookup(tree, "POST", "/users")
369
- test.expect(postOnGet).toBeNull()
370
-
371
- const postResult = RouteTree.lookup(tree, "POST", "/admin")
372
- test.expect(postResult).not.toBeNull()
373
- })
374
-
375
- test.it("wildcard method matches any method", () => {
376
- const tree = RouteTree.make({
377
- "/api": Route.use(Route.filter({ context: { api: true } })),
378
- })
379
-
380
- const getResult = RouteTree.lookup(tree, "GET", "/api")
381
- test.expect(getResult).not.toBeNull()
382
-
383
- const postResult = RouteTree.lookup(tree, "POST", "/api")
384
- test.expect(postResult).not.toBeNull()
385
-
386
- const deleteResult = RouteTree.lookup(tree, "DELETE", "/api")
387
- test.expect(deleteResult).not.toBeNull()
388
- })
389
-
390
- test.it("static routes take priority over param routes", () => {
391
- const tree = RouteTree.make({
392
- "/users/:id": Route.get(Route.text("user by id")),
393
- "/users/me": Route.get(Route.text("current user")),
394
- })
395
-
396
- const result = RouteTree.lookup(tree, "GET", "/users/me")
397
- test.expect(result).not.toBeNull()
398
- test.expect(Route.descriptor(result!.route).path).toBe("/users/me")
399
- test.expect(result!.params).toEqual({})
400
- })
401
-
402
- test.it("matches greedy params with +", () => {
403
- const tree = RouteTree.make({
404
- "/docs/:path+": Route.get(Route.text("docs")),
405
- })
406
-
407
- const result = RouteTree.lookup(tree, "GET", "/docs/api/v1/users")
408
- test.expect(result).not.toBeNull()
409
- test.expect(result!.params).toEqual({ path: "api/v1/users" })
410
-
411
- const noMatch = RouteTree.lookup(tree, "GET", "/docs")
412
- test.expect(noMatch).toBeNull()
413
- })
414
-
415
- test.it("matches greedy params with *", () => {
416
- const tree = RouteTree.make({
417
- "/files/:path*": Route.get(Route.text("files")),
418
- })
419
-
420
- const withPath = RouteTree.lookup(tree, "GET", "/files/a/b/c")
421
- test.expect(withPath).not.toBeNull()
422
- test.expect(withPath!.params).toEqual({ path: "a/b/c" })
423
-
424
- const withoutPath = RouteTree.lookup(tree, "GET", "/files")
425
- test.expect(withoutPath).not.toBeNull()
426
- test.expect(withoutPath!.params).toEqual({})
427
- })
428
-
429
- test.it("returns null for no match", () => {
430
- const tree = RouteTree.make({
431
- "/users": Route.get(Route.text("users")),
432
- })
433
-
434
- const result = RouteTree.lookup(tree, "GET", "/not-found")
435
- test.expect(result).toBeNull()
436
- })
437
-
438
- test.it("matches optional params with ?", () => {
439
- const tree = RouteTree.make({
440
- "/files/:name?": Route.get(Route.text("files")),
441
- })
442
-
443
- const withParam = RouteTree.lookup(tree, "GET", "/files/readme")
444
- test.expect(withParam).not.toBeNull()
445
- test.expect(withParam!.params).toEqual({ name: "readme" })
446
-
447
- const withoutParam = RouteTree.lookup(tree, "GET", "/files")
448
- test.expect(withoutParam).not.toBeNull()
449
- test.expect(withoutParam!.params).toEqual({})
450
- })
451
-
452
- test.it("respects route priority for complex trees", () => {
453
- const tree = RouteTree.make({
454
- "/:path*": Route.get(Route.text("catch all")),
455
- "/api/:rest+": Route.get(Route.text("api wildcard")),
456
- "/api/users": Route.get(Route.text("api users")),
457
- "/api/users/:id": Route.get(Route.text("api user detail")),
458
- })
459
-
460
- const staticMatch = RouteTree.lookup(tree, "GET", "/api/users")
461
- test.expect(Route.descriptor(staticMatch!.route).path).toBe("/api/users")
462
-
463
- const paramMatch = RouteTree.lookup(tree, "GET", "/api/users/123")
464
- test.expect(Route.descriptor(paramMatch!.route).path).toBe("/api/users/:id")
465
- test.expect(paramMatch!.params).toEqual({ id: "123" })
466
-
467
- const greedyMatch = RouteTree.lookup(tree, "GET", "/api/something/else")
468
- test.expect(Route.descriptor(greedyMatch!.route).path).toBe("/api/:rest+")
469
- test.expect(greedyMatch!.params).toEqual({ rest: "something/else" })
470
-
471
- const catchAll = RouteTree.lookup(tree, "GET", "/random/path")
472
- test.expect(Route.descriptor(catchAll!.route).path).toBe("/:path*")
473
- })
474
-
475
- test.it("static routes take priority over optional param routes", () => {
476
- const tree = RouteTree.make({
477
- "/files/:name?": Route.get(Route.text("files optional")),
478
- "/files/latest": Route.get(Route.text("files latest")),
479
- })
480
-
481
- const staticMatch = RouteTree.lookup(tree, "GET", "/files/latest")
482
- test.expect(Route.descriptor(staticMatch!.route).path).toBe("/files/latest")
483
-
484
- const optionalMatch = RouteTree.lookup(tree, "GET", "/files/other")
485
- test.expect(Route.descriptor(optionalMatch!.route).path).toBe(
486
- "/files/:name?",
487
- )
488
- test.expect(optionalMatch!.params).toEqual({ name: "other" })
489
-
490
- const noParam = RouteTree.lookup(tree, "GET", "/files")
491
- test.expect(Route.descriptor(noParam!.route).path).toBe("/files/:name?")
492
- test.expect(noParam!.params).toEqual({})
493
- })
494
- })