Initial Commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# `directives`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Host-supplied GraphQL directive descriptors that engine adapters install at
|
||||
schema-build time. The contract layer ships two: `@auth(scope: String!)` for
|
||||
per-field authorisation and `@rateLimit(rpm: Int!)` for per-field rate
|
||||
limiting. Plugins reference them in SDL; engines wire the descriptor's
|
||||
`handler` into the resolver pipeline.
|
||||
|
||||
Keeping the descriptors here (rather than in each engine adapter) means a
|
||||
plugin author writes one piece of SDL and gets the same enforcement
|
||||
semantics from every concrete engine. Engine adapters install the SDL via
|
||||
the directive `sdl` string and dispatch to the descriptor's `handler` from
|
||||
their resolver wrapper.
|
||||
|
||||
## Features
|
||||
|
||||
- `authDirective` — checks `ctx.principal.scopes` against the declared scope
|
||||
and throws when missing. Locations: `FIELD_DEFINITION`.
|
||||
- `rateLimitDirective` — validates the `rpm` integer bound; the actual
|
||||
rate-limit accounting is the engine adapter's responsibility (the
|
||||
contract body asserts the argument shape).
|
||||
- `HOST_DIRECTIVES` — frozen map keyed by directive name for engine
|
||||
adapter consumption.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { authDirective } from "@suaveplan/api-graphql/directives";
|
||||
|
||||
// SDL declaration:
|
||||
// directive @auth(scope: String!) on FIELD_DEFINITION
|
||||
//
|
||||
// Used in a plugin's typeDefs:
|
||||
// type Query { invoices: [Invoice!]! @auth(scope: "billing:read") }
|
||||
console.log(authDirective.sdl);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Engine adapters install every host directive in one pass:
|
||||
|
||||
```ts
|
||||
import { HOST_DIRECTIVES } from "@suaveplan/api-graphql/directives";
|
||||
|
||||
for (const directive of Object.values(HOST_DIRECTIVES)) {
|
||||
schemaBuilder.addDirective({
|
||||
name: directive.name,
|
||||
sdl: directive.sdl,
|
||||
run: directive.handler,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The `handler` is the contract-layer enforcement body. The `@auth` handler
|
||||
reads `ctx.principal.scopes` (the typed `readonly Scope[]`) and raises a
|
||||
plain `Error` when the required scope is absent — engine adapters wrap that
|
||||
in the engine's GraphQL error type before returning it on the wire.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `authDirective: DirectiveDescriptor<{ scope: string }>` — descriptor for
|
||||
`@auth`.
|
||||
- `rateLimitDirective: DirectiveDescriptor<{ rpm: number }>` — descriptor
|
||||
for `@rateLimit`.
|
||||
- `HOST_DIRECTIVES` — frozen `{ auth, rateLimit }` lookup.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The descriptors carry **contract bodies**, not full implementations. A
|
||||
production engine adapter wraps each resolver in a check that runs the
|
||||
handler before invoking the resolver. For `@rateLimit`, the engine adapter
|
||||
also wires the per-tenant counter; the contract-layer handler only enforces
|
||||
the `rpm > 0` invariant so a misconfigured directive surfaces early.
|
||||
|
||||
Adding a new host directive: add a `DirectiveDescriptor`, append it to
|
||||
`HOST_DIRECTIVES`, and document the SDL in the package README. The
|
||||
`api-graphql-yoga` and `api-graphql-apollo` packages pick up new
|
||||
descriptors automatically through the frozen map.
|
||||
Reference in New Issue
Block a user