# `loaders.ts` _(no submodules)_ ## Purpose Defines the per-request loader bag the Yoga engine constructs at pipeline step 7 and disposes at step 11. Two concurrent requests therefore see independent `DataLoader` instances — caching that R1 establishes for a key cannot leak to R2 even when both resolvers call `rctx.loaders.customer.load(id)` for the same id within the same event loop tick. The bag is the binding between the abstract `LoaderFactory` contract (owned by `@suaveplan/api-graphql`) and the concrete request-scoped loader handles resolvers see. Disposal is a hook for future async-resource loaders; today's in-memory factories make it a no-op. ## Features - Constructs one loader handle per registered factory. - Idempotent `dispose()` so the engine can safely call it on both the happy path and the error path. - Returned `handles` map is frozen-by-convention — engine code never mutates after construction. ## Usage: Basic ```ts import { createGraphqlRegistry } from "@suaveplan/api-graphql"; import { buildRequestLoaders } from "@suaveplan/api-graphql-yoga"; const reg = createGraphqlRegistry(); reg.addLoader("customer", async (keys) => keys.map((k) => `customer:${String(k)}`)); const snap = (reg as unknown as { freeze: () => { loaders: ReadonlyMap } }).freeze(); const bag = buildRequestLoaders(snap.loaders); const customer = await bag.handles.customer.load("c1"); bag.dispose(); ``` ## Usage: Advanced ```ts import { buildRequestLoaders } from "@suaveplan/api-graphql-yoga"; function makeRctx(loaders: Awaited>) { return { loaders: loaders.handles, dispose: () => loaders.dispose(), }; } ``` ## API Reference - `RequestLoaderBag` — `{ handles, dispose() }`. - `buildRequestLoaders(factories)` — constructs the bag. ## Implementation Notes The `disposed` boolean lets the engine call `dispose()` on both happy and error paths without worrying about double-free. When async-resource loaders land, the close branch will flip from no-op to a real close sequence; the public shape stays unchanged.