import type { DirectiveDescriptor } from "./directives.types.js"; /** * `@auth(scope: String!)` directive descriptor. Engine adapters install it at * schema-build time; the handler checks `ctx.principal` against the declared * scope and raises the host's auth error when the scope is not present. * * The handler here is the **contract** body — concrete engines wire it into * the resolver pipeline. The default raises a typed error noting the missing * scope so callers see a stable failure shape during early integration. */ export const authDirective: DirectiveDescriptor<{ readonly scope: string }> = { name: "auth", sdl: "directive @auth(scope: String!) on FIELD_DEFINITION", locations: ["FIELD_DEFINITION"], args: [{ name: "scope", type: "String!" }], handler: (args, ctx) => { const scopes: readonly string[] = ctx.principal?.scopes ?? []; if (!scopes.includes(args.scope)) { throw new Error( `@auth(scope: "${args.scope}") failed — principal does not carry the required scope` ); } }, }; /** * `@rateLimit(rpm: Int!)` directive descriptor. Engine adapters install it at * schema-build time; the handler delegates to the host's rate-limit slot. * The contract body checks the argument bound and otherwise yields — concrete * engines wire it into the request pipeline. */ export const rateLimitDirective: DirectiveDescriptor<{ readonly rpm: number }> = { name: "rateLimit", sdl: "directive @rateLimit(rpm: Int!) on FIELD_DEFINITION", locations: ["FIELD_DEFINITION"], args: [{ name: "rpm", type: "Int!" }], handler: (args) => { if (!Number.isInteger(args.rpm) || args.rpm <= 0) { throw new Error( `@rateLimit(rpm: ${String(args.rpm)}) — rpm must be a positive integer` ); } }, }; /** Lookup of every host-supplied directive descriptor, keyed by name. */ export const HOST_DIRECTIVES = Object.freeze({ auth: authDirective, rateLimit: rateLimitDirective, }) as Readonly>;