ReviewOS

also looking at this

stacks/stx

fix(serve): dev no-store caching + nested dynamic route regex

#1731
Merged glennmichael123 wants to merge fix/serve-cache-and-nested-routes into main
2 files +120 -35

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

packages/bun-plugin/test/dynamic-routes.test.tsadded+63-0
Changes to packages/bun-plugin/test/dynamic-routes.test.ts
@@ -0,0 +1,63 @@
1import { describe, expect, it } from 'bun:test'
2import { buildDynamicRouteRegexes } from '../src/serve'
3
4/**
5 * Dynamic-route regex construction (stacksjs/stx#1927).
6 *
7 * Nested dynamic routes (e.g. `judges/[id]/profile/index.stx`) used to
8 * 404 on hard reload because the route regex kept the trailing
9 * `/index`, so `^judges/([^/]+)/profile/index$` never matched the URL
10 * `judges/35/profile`. These tests pin the `/index`-stripped variant.
11 */
12
13function matchesAny(fileRouteBase: string, url: string): boolean {
14 return buildDynamicRouteRegexes(fileRouteBase).some(re => re.test(url))
15}
16
17describe('buildDynamicRouteRegexes — single-segment dynamic routes', () => {
18 it('matches /judges/35 from judges/[id]', () => {
19 expect(matchesAny('judges/[id]', 'judges/35')).toBe(true)
20 })
21
22 it('matches /judges/35 from judges/[id]/index', () => {
23 expect(matchesAny('judges/[id]/index', 'judges/35')).toBe(true)
24 })
25})
26
27describe('buildDynamicRouteRegexes — nested dynamic routes (the bug)', () => {
28 it('matches /judges/35/profile from judges/[id]/profile/index', () => {
29 expect(matchesAny('judges/[id]/profile/index', 'judges/35/profile')).toBe(true)
30 })
31
32 it('matches /judges/35/reviews from judges/[id]/reviews/index', () => {
33 expect(matchesAny('judges/[id]/reviews/index', 'judges/35/reviews')).toBe(true)
34 })
35
36 it('matches /court-houses/1/bench from court-houses/[id]/bench/index', () => {
37 expect(matchesAny('court-houses/[id]/bench/index', 'court-houses/1/bench')).toBe(true)
38 })
39
40 it('still matches the index-included URL for back-compat', () => {
41 expect(matchesAny('judges/[id]/profile/index', 'judges/35/profile/index')).toBe(true)
42 })
43
44 it('does NOT match an unrelated path', () => {
45 expect(matchesAny('judges/[id]/profile/index', 'judges/35/cases')).toBe(false)
46 })
47
48 it('captures the dynamic segment, not the static suffix', () => {
49 const [, noIndexRe] = buildDynamicRouteRegexes('judges/[id]/profile/index')
50 const m = 'judges/35/profile'.match(noIndexRe)
51 expect(m?.[1]).toBe('35')
52 })
53})
54
55describe('buildDynamicRouteRegexes — pages/ prefix', () => {
56 it('matches the pages/-stripped URL', () => {
57 expect(matchesAny('pages/judges/[id]/profile/index', 'judges/35/profile')).toBe(true)
58 })
59
60 it('matches the full pages/ path too', () => {
61 expect(matchesAny('pages/data/[model]', 'pages/data/users')).toBe(true)
62 })
63})