createContext in ssr.js gives handlers a setCookie(name, value, options). It appends to a local cookiesToSet array. That array is copied into responseHeaders as the last statement of createContext, before any handler has run:
setCookie(name, value, options = {}) {
cookiesToSet.push(serializeCookie(name, value, options))
},
...
}; // <- ctx object literal ends here
if (cookiesToSet.length > 0)
cookiesToSet.forEach((cookie) => {
responseHeaders.append('Set-Cookie', cookie)
})
return ctx // <- handlers run after this
Anything a handler pushes lands in an array that is never read again, so ctx.setCookie(...) from a route handler emits no Set-Cookie.
Why it looks like it works
The session cookie is pushed inside createContext itself, before the drain:
let sessionId = cookies[sessionCookieName]
if (!sessionId) {
sessionId = generateSessionId()
cookiesToSet.push(serializeCookie(sessionCookieName, sessionId, { httpOnly: true, ... }))
}
So sessions work, and cookies appear on responses, and the one call that is actually broken is the one an application makes. A smoke test that checks "does the server set cookies" passes.
Suggested fix
Drain at response-build time rather than at context-build time. Either append directly in setCookie:
setCookie(name, value, options = {}) {
responseHeaders.append('Set-Cookie', serializeCookie(name, value, options))
},
which makes the array unnecessary, or keep the array and flush it after the handler chain resolves, where the Response is assembled.
The direct-append version looks strictly better here: responseHeaders is already a live Headers on the context, and Headers.append is what the existing drain calls anyway, so there is no ordering left to get wrong.
Related
stx#1927 fixed the same class of problem on the page-action path (an action's cookies had no way out) and shipped in 0.2.177. This is the sibling case on the SSR handler path.
Environment
@stacksjs/stx 0.2.198, bun 1.3.14, macOS 15.
createContextinssr.jsgives handlers asetCookie(name, value, options). It appends to a localcookiesToSetarray. That array is copied intoresponseHeadersas the last statement ofcreateContext, before any handler has run:Anything a handler pushes lands in an array that is never read again, so
ctx.setCookie(...)from a route handler emits noSet-Cookie.Why it looks like it works
The session cookie is pushed inside
createContextitself, before the drain:So sessions work, and cookies appear on responses, and the one call that is actually broken is the one an application makes. A smoke test that checks "does the server set cookies" passes.
Suggested fix
Drain at response-build time rather than at context-build time. Either append directly in
setCookie:which makes the array unnecessary, or keep the array and flush it after the handler chain resolves, where the
Responseis assembled.The direct-append version looks strictly better here:
responseHeadersis already a liveHeaderson the context, andHeaders.appendis what the existing drain calls anyway, so there is no ordering left to get wrong.Related
stx#1927 fixed the same class of problem on the page-action path (an action's cookies had no way out) and shipped in 0.2.177. This is the sibling case on the SSR handler path.
Environment
@stacksjs/stx0.2.198, bun 1.3.14, macOS 15.