[PR #608] [CLOSED] feat: add per-request store API (getRequestStore) #707

Closed
opened 2026-05-06 13:09:41 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/608
Author: @JamesbbBriz
Created: 3/20/2026
Status: Closed

Base: mainHead: feat/request-store


📝 Commits (1)

  • 5e3cb12 feat: add per-request store API for user-defined data

📊 Changes

5 files changed (+106 additions, -3 deletions)

View changed files

📝 packages/vinext/package.json (+4 -0)
📝 packages/vinext/src/entries/pages-server-entry.ts (+6 -1)
📝 packages/vinext/src/server/api-handler.ts (+8 -2)
packages/vinext/src/shims/request-store.ts (+83 -0)
📝 packages/vinext/src/shims/unified-request-context.ts (+5 -0)

📄 Description

Summary

Add getRequestStore() — a per-request key-value store backed by vinext's existing unified AsyncLocalStorage scope. Values are isolated per request and automatically garbage-collected when the request completes.

Primary use case: per-request database clients on Cloudflare Workers, where global singletons cause alternating request failures (#537).

API

import { getRequestStore } from "vinext/request-store";

export function getPrisma(connectionString: string): PrismaClient {
  const store = getRequestStore();
  let prisma = store.get("prisma") as PrismaClient | undefined;
  if (!prisma) {
    const pool = new Pool({ connectionString });
    prisma = new PrismaClient({ adapter: new PrismaPg(pool) });
    store.set("prisma", prisma);
  }
  return prisma;
  // Request ends → Map GC'd → connection released
}

Similar pattern to SvelteKit event.locals, Hono c.set/get, Remix context.

Scope

Works in all code paths:

Code path Supported Notes
App Router route handlers Already wrapped by RSC entry
App Router server components/actions Already wrapped by RSC entry
Pages Router API routes (dev) Wrapped in this PR (api-handler.ts)
Pages Router API routes (prod) Wrapped in this PR (pages-server-entry.ts)
Middleware Inherits ExecutionContext into unified scope
Outside request scope (tests, top-level) ⚠️ Returns fresh empty Map each call — safe but non-persistent

Changes

File Change
shims/unified-request-context.ts Add userStore: Map<string, unknown> to UnifiedRequestContext + default in createRequestContext()
shims/request-store.ts NewgetRequestStore() public API
server/api-handler.ts Wrap Pages Router API handler in runWithRequestContext (dev server)
entries/pages-server-entry.ts Wrap Pages Router API handler in _runWithUnifiedCtx (prod/Workers)
package.json Add "./request-store" export

Motivation

We're running a production SaaS on vinext + Cloudflare Workers (Free plan) with Prisma v7 + Hyperdrive + R2 + NextAuth. We hit the exact alternating-failure pattern from #537 and initially worked around it with a 50ms TTL heuristic. This PR provides the proper framework-level solution.

vinext already has the UnifiedRequestContext + AsyncLocalStorage infrastructure. This PR exposes it to users via a clean public API — 73 lines across 5 files, zero breaking changes.

Test plan

  • Verified in production with Prisma v7 + Hyperdrive (zero alternating failures across 500+ requests)
  • Outside-scope fallback returns fresh Map each call (prevents cross-call data leakage)
  • Happy to add unit tests — let me know the preferred test location

Closes #537


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/608 **Author:** [@JamesbbBriz](https://github.com/JamesbbBriz) **Created:** 3/20/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `feat/request-store` --- ### 📝 Commits (1) - [`5e3cb12`](https://github.com/cloudflare/vinext/commit/5e3cb12ea3a700e5eb1a305bb86c0c7defcb58c4) feat: add per-request store API for user-defined data ### 📊 Changes **5 files changed** (+106 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/package.json` (+4 -0) 📝 `packages/vinext/src/entries/pages-server-entry.ts` (+6 -1) 📝 `packages/vinext/src/server/api-handler.ts` (+8 -2) ➕ `packages/vinext/src/shims/request-store.ts` (+83 -0) 📝 `packages/vinext/src/shims/unified-request-context.ts` (+5 -0) </details> ### 📄 Description ## Summary Add `getRequestStore()` — a per-request key-value store backed by vinext's existing unified `AsyncLocalStorage` scope. Values are isolated per request and automatically garbage-collected when the request completes. **Primary use case**: per-request database clients on Cloudflare Workers, where global singletons cause alternating request failures ([#537](https://github.com/cloudflare/vinext/issues/537)). ## API ```ts import { getRequestStore } from "vinext/request-store"; export function getPrisma(connectionString: string): PrismaClient { const store = getRequestStore(); let prisma = store.get("prisma") as PrismaClient | undefined; if (!prisma) { const pool = new Pool({ connectionString }); prisma = new PrismaClient({ adapter: new PrismaPg(pool) }); store.set("prisma", prisma); } return prisma; // Request ends → Map GC'd → connection released } ``` Similar pattern to SvelteKit `event.locals`, Hono `c.set/get`, Remix `context`. ## Scope Works in all code paths: | Code path | Supported | Notes | |-----------|-----------|-------| | App Router route handlers | ✅ | Already wrapped by RSC entry | | App Router server components/actions | ✅ | Already wrapped by RSC entry | | Pages Router API routes (dev) | ✅ | Wrapped in this PR (`api-handler.ts`) | | Pages Router API routes (prod) | ✅ | Wrapped in this PR (`pages-server-entry.ts`) | | Middleware | ✅ | Inherits ExecutionContext into unified scope | | Outside request scope (tests, top-level) | ⚠️ | Returns fresh empty Map each call — safe but non-persistent | ## Changes | File | Change | |------|--------| | `shims/unified-request-context.ts` | Add `userStore: Map<string, unknown>` to `UnifiedRequestContext` + default in `createRequestContext()` | | `shims/request-store.ts` | **New** — `getRequestStore()` public API | | `server/api-handler.ts` | Wrap Pages Router API handler in `runWithRequestContext` (dev server) | | `entries/pages-server-entry.ts` | Wrap Pages Router API handler in `_runWithUnifiedCtx` (prod/Workers) | | `package.json` | Add `"./request-store"` export | ## Motivation We're running a production SaaS on vinext + Cloudflare Workers (Free plan) with Prisma v7 + Hyperdrive + R2 + NextAuth. We hit the exact alternating-failure pattern from #537 and initially worked around it with a 50ms TTL heuristic. This PR provides the proper framework-level solution. vinext already has the `UnifiedRequestContext` + `AsyncLocalStorage` infrastructure. This PR exposes it to users via a clean public API — 73 lines across 5 files, zero breaking changes. ## Test plan - Verified in production with Prisma v7 + Hyperdrive (zero alternating failures across 500+ requests) - Outside-scope fallback returns fresh Map each call (prevents cross-call data leakage) - Happy to add unit tests — let me know the preferred test location Closes #537 --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:09:41 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#707
No description provided.