Skip to content

SSR safety: ResourceTypeFromIri is a module-level singleton mutated per request #264

Description

@silverbackdan

Summary

resource-utils.ts exports a module-level singleton, shared across every SSR request in the Node process:

export const ResourceTypeFromIri = new ResourceTypeFromIriCls()
export const getResourceTypeFromIri = ResourceTypeFromIri._call.bind(ResourceTypeFromIri)

…and it is mutated from per-request codecwa.ts:68, inside the Cwa constructor, which the Nuxt plugin runs once per SSR request:

ResourceTypeFromIri.setPathPrefix((new URL(this.apiUrl)).pathname)

Why it is benign today

apiUrl comes from runtimeConfig and is identical for every request, so every request writes the same pathPrefix. The mutation is idempotent and the race is unobservable.

Why it is still worth fixing

It is a genuine SSR-safety footgun: per-request state written to process-global state. It holds only by the coincidence that the value happens to be request-invariant. Anything that later makes the prefix vary — multi-tenant / per-host API URLs, a per-request apiUrl override, previewing against a different API — turns this into cross-request contamination that would be very hard to trace, because getResourceTypeFromIri is called from everywhere (IRI parsing, depth headers, resource typing).

It also sits directly adjacent to auth-cookie handling: it is one line above the CwaFetch construction in the same constructor. The eager-capture fix in #263 deliberately keeps the request cookie in a per-instance closure precisely to avoid this pattern — ResourceTypeFromIri is the counter-example that proves "constructed per request" does not imply "state is per request", and it is the shape a future contributor might copy.

Suggested direction

Make the prefix per-instance rather than process-global — e.g. resolve it from injected config at the call sites that need it, or hang it off the per-request Cwa/CwaFetch rather than a shared singleton. The awkwardness is that getResourceTypeFromIri is used as a free function throughout the codebase, so this is a non-trivial refactor and needs a plan; hence raising rather than fixing inline.

No known user-facing bug — this is a latent hazard, raised while investigating #263 so it is not lost.

Discovered during the #263 investigation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions