Files
suaveplan/packages/api-stack/api/api-graphql-apollo/src/loaders/loaders.md
2026-05-27 23:29:53 +01:00

2.4 KiB

loaders — per-request DataLoader scope

(no submodules)

Purpose

loaders defines the per-request DataLoader scope the engine creates around every incoming GraphQL operation. Two concurrent requests receive distinct loader instances; the cache never leaks across request boundaries even though both requests draw from the same set of loader factories registered on the abstract GraphqlRegistry.

The module is deliberately the thinnest possible wrapper around the LoaderFactory.create() surface owned by @suaveplan/api-types/graphql. The engine calls createRequestLoaderScope() once per request; resolvers that need a loader call scope.get(name, factory); the engine disposes the scope at request end.

Features

  • One scope per request — distinct cache per request guaranteed.
  • Loader lazily instantiated on first get(name, factory) call; subsequent calls reuse the instance.
  • dispose() blocks subsequent get() calls so a buggy late resolver cannot resurrect a freed cache.
  • Zero runtime dependencies — pure JS, fits in ~40 lines.

Usage: Basic

import { createRequestLoaderScope } from "@suaveplan/api-graphql-apollo/loaders";

const scope = createRequestLoaderScope();
const userLoader = scope.get("users", userLoaderFactory);
const user = await userLoader.load("u_42");
scope.dispose();

Usage: Advanced

Wire the scope into a resolver context object so resolvers consume loaders by name through the request context:

import { createRequestLoaderScope } from "@suaveplan/api-graphql-apollo/loaders";

function makeContext(): RequestContext {
    const scope = createRequestLoaderScope();
    return {
        loaders: {
            users: () => scope.get("users", userLoaderFactory),
            orders: () => scope.get("orders", orderLoaderFactory),
        },
        finalize: () => scope.dispose(),
    } as unknown as RequestContext;
}

API Reference

  • RequestLoaderScope:
    • get<TKey, TValue>(name, factory): { load }
    • dispose(): void
    • size(): number
  • createRequestLoaderScope(): RequestLoaderScope.

Implementation Notes

The scope is a closure over a Map<string, LoaderInstance> plus a disposed boolean. Throwing on get after dispose surfaces the late-resolver bug at the call site instead of silently producing inconsistent reads. The size accessor is provided so OTel histograms can record loader-fanout per request.