Skip to content

perf: cache Supabase admin client as module-level singleton - #138

Merged
BigSimmo merged 2 commits into
mainfrom
copilot/task-87357024-1243497866-93b3cae7-6f2a-45d2-8adc-9d9ded40f98a
Jul 2, 2026
Merged

perf: cache Supabase admin client as module-level singleton#138
BigSimmo merged 2 commits into
mainfrom
copilot/task-87357024-1243497866-93b3cae7-6f2a-45d2-8adc-9d9ded40f98a

Conversation

Copilot AI commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • createAdminClient() previously constructed a fresh SupabaseClient — plus requireServerEnv() + assertExpectedSupabaseProjectConfig() — on every API request. This PR caches it as a module-level singleton, matching the existing openAIClient ??= new OpenAI(…) pattern in openai.ts. The service-role client holds no user-specific state (autoRefreshToken: false, persistSession: false), so sharing across requests is safe.
// before
export function createAdminClient() {
  const { NEXT_PUBLIC_SUPABASE_URL: url, SUPABASE_SERVICE_ROLE_KEY: key } = requireServerEnv();
  return createClient<Database>(url, key, { auth: { autoRefreshToken: false, persistSession: false } });
}

// after
let adminClient: ReturnType<typeof createClient<Database>> | null = null;

export function createAdminClient() {
  if (!adminClient) {
    const { NEXT_PUBLIC_SUPABASE_URL: url, SUPABASE_SERVICE_ROLE_KEY: key } = requireServerEnv();
    adminClient = createClient<Database>(url, key, { auth: { autoRefreshToken: false, persistSession: false } });
  }
  return adminClient;
}

Verification

  • npm run verify:cheap — 825/825 tests pass, no new type errors
  • npm run verify:ui when UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changed
  • npm run verify:release before release or handoff confidence claims
  • npm run format:check
  • npm run check:production-readiness when clinical workflow, privacy, environment, Supabase, source governance, or deployment behavior changed
  • npm run check:deployment-readiness when deployment startup, hosting, or rollout behavior changed

Clinical Governance Preflight

Complete this section when the change touches ingestion, answer generation, search/ranking, source rendering, document access, privacy, production env, or clinical output.

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

Notes

Every API route calls createAdminClient() on every request, which
previously instantiated a fresh SupabaseClient (plus requireServerEnv()
+ assertExpectedSupabaseProjectConfig() validation) each time.

Introduce a module-level singleton using the same lazy-init pattern
already used for the OpenAI client (openAIClient ??= new OpenAI(…)).
The service-role client carries no user-specific session state, so
reusing a single instance across requests is safe and saves per-request
object construction + repeated env validation overhead.

825/825 tests pass.
@BigSimmo
BigSimmo marked this pull request as ready for review July 2, 2026 10:32
@BigSimmo
BigSimmo enabled auto-merge July 2, 2026 10:32
@BigSimmo
BigSimmo merged commit 805a985 into main Jul 2, 2026
4 checks passed
@BigSimmo
BigSimmo deleted the copilot/task-87357024-1243497866-93b3cae7-6f2a-45d2-8adc-9d9ded40f98a branch July 2, 2026 16:31
BigSimmo pushed a commit that referenced this pull request Jul 31, 2026
Resolves the fifth conflict on docs/outstanding-issues.md by taking
main's side on both hunks, after verifying that was correct rather than
convenient: main renumbered the queue to fix a duplicate rank 18 this
branch introduced, and it archived #138 and #146 rather than dropping
them (confirmed present in the archive table). The resulting id set is
byte-identical to main's.

#150 escalated. That row records CodeRabbit reviewing none of a full
day's PRs and rests explicitly on the Codex connector being the
surviving reviewer that "found three real defects that had survived
local gates". On PR #1505 Codex posted its own usage-limit notice
alongside CodeRabbit's spending-cap notice, so that PR received zero
automated review and so will anything opened while both caps hold. This
is the same issue with its fallback removed, not a new one — which
matters because the single Codex finding on #1459 was correct and
caught a verification that had matched the wrong component, closing
#105 on bad evidence. Local gates did not catch it.

The attribution harness lands as scripts/measure-cls-attribution.mjs.
#147's next step needs a before/after CLS pair, and #118 will want
element attribution again; without this, both mean re-deriving it. Build
and serve mirror run-lighthouse-budget.mjs so the numbers sit beside the
Lighthouse reports. Two traps that cost real time are encoded rather
than left to be rediscovered: CHROME_PATH must be set where the browser
is outside a standard location, and an init script attaching a
MutationObserver to document.documentElement before <html> exists throws
and silently takes the CLS observer with it — so the script now fails
loudly on an all-zero result instead of reporting a false clean bill.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
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.

2 participants