Skip to content

refactor(router): matrix-critical de-duplications — middleware merge, loader-cache interceptor, cache-hit loaders, parallel-slot (#568) - #576

Merged
ivogt merged 4 commits into
mainfrom
refactor/xl-segment-resolution
Jun 14, 2026
Merged

ivogt merged 4 commits into
mainfrom
refactor/xl-segment-resolution

Conversation

@ivogt

@ivogt ivogt commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

The matrix-critical internal refactors deferred from #575 (issue #568) — four behavior-preserving de-duplications on the segment-resolution / middleware / cache hot paths. No consumer-observable change: no public API, response shape, generated type, or DX delta. The point is maintainability (kill copy-paste drift) without disturbing the execution contract, so the bar here is provable behavior-preservation, verified by the semantic matrix (61/61 dev+prod, no row changes), targeted dev+prod e2e, and a 4-way adversarial refutation pass.

A consumer writes nothing differently and sees nothing differently. What changes is that four pairs of near-identical blocks now share one implementation, so the next edit can't silently drift one copy from the other.


What was de-duplicated

1. Middleware merge-and-clone Response builder (middleware.ts)

Three sites cloned a downstream Response with merged stub/reqCtx headers: executeMiddleware's end-of-chain next(), its short-circuit return, and executeInterceptMiddleware's early response. Extracted mergeResponse(base, stub, { stubOverridesNonCookie, mergeReqCtx }). The two flags encode the only real differences:

Site stubOverridesNonCookie mergeReqCtx
next() end-of-chain (chain completed) true true
short-circuit (return Response) false true
intercept early response false false

The intercept path's mergeReqCtx: false is the one deliberate divergence (it does not merge RequestContext stub headers) — pinned by intercept-middleware-headers.test.ts. WebSocket-upgrade early returns and the hasStubHeaders gate stay at the call sites. (The next() site's WS check moved before the merge; proven unobservable — mergeStubHeaders/mergeReqCtxStub/_getRequestContext have no side effects beyond the local merged Headers, which the WS branch discards.)

2. Cache-hit fresh-loader resolution (match-middleware/cache-lookup.ts)

Loaders are never cached, so on every cache hit the router re-resolves them fresh. yieldFromStore (prerender-store path) and withCacheLookup (runtime cache-hit path) carried a verbatim copy of that block — the isFullMatch branch, the resolveLoadersOnly / resolveLoadersOnlyWithRevalidation calls, the matchedIds update, and the loader-resolve + cache-hit metrics. Extracted resolveFreshLoadersAndYield().

The router resolve fns are passed in (captured early by each caller) rather than re-read from getRouterContext() inside the helper — getRouterContext is AsyncLocalStorage-backed and the callers run awaits before this point, so re-reading could hit ALS disruption on workerd. The render barrier and cacheSource assignment differ between the two callers and stay at each call site.

3. loader-cache ctx.use interceptor (loader-cache.ts)

resolveLoaderData rebound ctx.use once per cached loader — chaining one wrapper per loader (O(N) dispatch) and relying on an undocumented synchronous capture-before-overwrite invariant.

// before — N cached loaders => N chained wrappers on ctx.use
const originalUse = ctx.use;
const dataPromise = (async () => { /* read-through */ })();
ctx.use = ((item) =>
  item === loaderEntry.loader || item?.$$id === loaderId
    ? dataPromise
    : originalUse(item));   // <- re-wraps every cached loader

// after — one stable interceptor; subsequent loaders just prime a Map
const overrides = (internal._loaderCacheOverrides ??= new Map());
if (firstInstall) {
  const originalUse = ctx.use;            // captured once
  internal._loaderCacheOriginalUse = originalUse;
  ctx.use = (item) => overrides.get(item?.$$id) ?? originalUse(item);
}
overrides.set(loaderId, dataPromise);     // O(1), no re-wrap

The cache-miss execute runs through the captured pre-interceptor ctx.use, so a loader never awaits its own in-flight promise. The mechanism stays agnostic to which setup mode installed ctx.use (setupLoaderAccess / setupLoaderAccessSilent / setupBuildUse), exactly like the old rebind. Two new internal InternalHandlerContext fields (_loaderCacheOverrides, _loaderCacheOriginalUse) back the table; both are read only inside resolveLoaderData.

4. Parallel-slot component resolution (segment-resolution/revalidation.ts)

resolveParallelSegmentsWithRevalidation and the orphan-inlined loop in resolveOrphanLayoutWithRevalidation carried a byte-identical ~45-line component-resolution block (static-slot lookup → run the slot handler with the _currentSegmentId slot-id pin, handlerRan flag, and streamed-handler wrap). Extracted resolveParallelSlotComponent() → { component, handlerRan }.

Critically, this block is separable from the two known divergences between those functions: the defaultOverride/shouldResolve seed is computed by the caller before the call, and loader-resolution ordering stays at the call site (orphan before push, main after). The extraction touches neither.


Deferred (with a forensic rationale) — issue #568 sub-items 1 & 2

The bigger unifications — folding the orphan-inlined parallel loop into the shared helper via policy args (sub-item 2), and unifying the fresh.ts vs revalidation.ts traversal trees (sub-item 1) — are deliberately not in this PR. A forensic investigation of the two divergences found:

Divergence Verdict Implication
(A) orphan defaultOverride: { value: true } vs main belongsToRoute || isNewParent INTENTIONAL (since #228, preserved in #482 with a justifying comment; reachable at belongsToRoute=false; avoids the #482 blank-slot staleness bug) must be carried as an explicit policy arg, never normalized — that's a product decision
(B) orphan loader-resolve-before-push vs main push-before-loaders DRIFT / cosmetic (provably unobservable: segment tree walk + handle filter discard intra-parent order; loaders memoized by $$id) safe to normalize to the shared order

The blocker for doing them now: the existing pinning test (parallel-revalidate-fns-invocation.test.ts) does not actually lock either divergence — every orphan case it runs uses belongsToRoute=true, where the policies coincide, and the revalidate fns override the seed. So it stays green under any unification and cannot catch a regression in (A). Per the stability bar, sub-items 1 & 2 get their own focused PR that first adds a pinning test for the divergent (A) case (orphan, belongsToRoute=false, slot ∉ client, parent ∈ client, zero revalidate fns → slot renders non-null + in matchedIds), then carries (A) as a policy arg and normalizes (B).


Test plan

  • pnpm run typecheck — 12/12 packages
  • pnpm run test:unit:all (repo root) — router + all consumer suites, 0 failures
  • pnpm run lint / pnpm run format — clean
  • semantic-matrix — 61/61 dev + production, no row changes (behavior-preserving across middleware scope, handler-first ordering, parallel-slot visibility, PE/JS parity, cache)
  • cache e2e (cache.test.ts) dev + production — 145/145 (covers fresh-loader-on-cache-hit + loader-cache round-trips)
  • parallel-reval e2e (parallel-reval-after-action, parallel-loader-reval) dev + production
  • middleware e2e (app-middleware, include-middleware) dev + production — 77/77
  • New unit test: multi-cached-loader accumulation (single stable interceptor, per-loader override, non-cached pass-through) — pins the Loading states 11 28 #3 mechanism
  • Adversarially refuted all four extractions (deadlock / chained-wrapper equivalence, ALS-capture timing, WS reorder side-effects, divergence separability) — all refuted: false, high confidence

Notes

ivogt added 4 commits June 14, 2026 00:55
…#568)

Replace 3 near-identical merge-and-clone Response sites in executeMiddleware
(end-of-chain next() and short-circuit) and executeInterceptMiddleware with a
private mergeResponse(base, stub, {stubOverridesNonCookie, mergeReqCtx}) helper.
The two flags encode the only real differences: stubOverridesNonCookie (chain
ran vs explicit short-circuit) and mergeReqCtx (false ONLY for the intercept
path -- the one deliberate divergence, pinned by intercept-middleware-headers).
WebSocket-upgrade early returns + the hasStubHeaders gate stay at the call
sites. Behavior-preserving; semantic matrix + middleware e2e green.
)

yieldFromStore (prerender-store path) and withCacheLookup (runtime cache-hit
path) duplicated the fresh-loader-on-cache-hit block verbatim (loaders are never
cached): the isFullMatch branch, resolveLoadersOnly[WithRevalidation] calls,
state.matchedIds update, and the loader-resolve + cache-hit metrics. Extract
resolveFreshLoadersAndYield(). The router resolve fns are passed in (captured
early by each caller) rather than re-read from the ALS-backed getRouterContext()
inside the helper, preserving workerd ALS-disruption robustness. The render
barrier + cacheSource assignment stay at each call site (they differ). Behavior-
preserving; semantic matrix + cache e2e (dev+prod) green.
…terceptor (#568)

resolveLoaderData rebound ctx.use once per cached loader, chaining one wrapper
per loader (O(N) dispatch) and relying on an undocumented synchronous
capture-before-overwrite invariant. Install ONE stable interceptor on the first
cached loader that consults a per-ctx override table (_loaderCacheOverrides);
each subsequent cached loader just primes the table (O(1) dispatch). The
cache-miss execute runs through the captured pre-interceptor ctx.use
(_loaderCacheOriginalUse), so a loader never awaits its own in-flight promise.
Stays agnostic to which setup mode installed ctx.use (loader/silent/build), like
the old rebind. Adds a multi-cached-loader accumulation test. Behavior-
preserving; matrix + loader-cache unit + cache e2e green.
resolveParallelSegmentsWithRevalidation and the orphan-inlined loop in
resolveOrphanLayoutWithRevalidation duplicated the parallel-slot
component-resolution block (~45 lines): tryStaticSlot, then run the slot handler
with the _currentSegmentId slot-id pin, handlerRan flag, and streamed-handler
wrap. Extract resolveParallelSlotComponent() returning {component, handlerRan}.
The block is byte-identical between the two and SEPARABLE from their known
divergences: the defaultOverride/shouldResolve seed is computed by the caller
before the call, and loader-resolution ordering stays at the call site (orphan
before push, main after) -- neither is touched. Behavior-preserving; semantic
matrix + parallel-revalidate-fns + parallel-reval e2e green.
@pkg-pr-new

pkg-pr-new Bot commented Jun 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@rangojs/router@576

commit: 8a6f89d

@ivogt
ivogt merged commit 849f96b into main Jun 14, 2026
25 checks passed
@ivogt
ivogt deleted the refactor/xl-segment-resolution branch June 14, 2026 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant