Documentation
¶
Overview ¶
Package graphql is a schema-first GraphQL runtime for Go with a code-first, type-safe binding API.
A Schema is built from SDL plus bindings that connect GraphQL types to Go types: Object, Field, Resolve, Input, Args, Enum, Scalar, Interface, Union and Directive. Generated code and hand-written code use the same constructors, so code generation is a convenience rather than a requirement. Bindings are validated against the SDL once, at NewSchema.
After start-up, output fields, explicit InputField setters and scalar writers are typed function values. Args[T]() / Input[T](name) may use reflection once per input value to fill struct fields; that is decode only and is not on the JSON write path. Composite lists deeper than one level also use a reflective traverser recorded at start-up. A composite list field may return iter.Seq[E] or iter.Seq[*E] wherever it may return []E or []*E.
An Executor compiles each operation into an immutable, cached plan and writes the response directly into a pooled JSON buffer. Fields bound with Field are treated as pure data access and run inline; fields bound with Resolve may perform I/O and are scheduled concurrently under a bounded semaphore. The loader package batches I/O across those resolvers in one request through WaveCoordinator; WithMaxComplexity, WithMaxDepth and WithQueryCost reject expensive operations before they run.
Subscriptions are bound with Subscribe and SubscribeArgs, which return the source channel for a subscription root field. Executor.Subscribe runs one such operation and yields a Response per event, executing the field's sub-selection against the event exactly as a query would be executed against a resolver's result.
Authorization is a compiled pass over the plan, not a wrapper around resolvers, and keeps three concerns separate: what an operation touches, what a principal may do with it, and how that decision is enforced. A schema author marks a field with @requiresScopes(scopes: [[String!]!]!), an OR of ANDs read into a Requirement; NewSchema rejects a malformed scopes value. That spelling is the default rather than the only one: RequirementDirective(name, arg, shape) declares another SDL directive read into a Requirement, so a schema that already spells its requirements @auth(requires: [String!]) is enforced without renaming every site. The shape is declared rather than inferred, because @auth(requires: ["a","b"]) is the same text whether the author meant AND or OR and guessing wrongly widens access. MarkerDirective(name, scope) covers a directive with no argument, whose presence alone is the requirement, which is the shape of Apollo's @authenticated. @requiresScopes and @authorizeInput only describe positions in the schema; nothing is enforced against them unless an Authorizer is configured with WithAuthorizer. At plan compile, buildAuthShape walks the plan once and records every such field, and every field that selects an @authorizeInput argument, as a Site in an AuthShape, cached with the plan itself -- the shape does not depend on who is asking, so the plan cache is not multiplied by policy, and a field that declares nothing and selects no @authorizeInput argument carries authIdx -1 and costs one integer compare on the request path. An Authorizer turns a shape into a Decision once per query or mutation -- and, for a subscription, once when the stream opens plus once more per event. At open, exactly three things refuse the subscription before its source is opened: an Authorize error, a Deny recorded for the subscription root field, or a Deny recorded for one of that field's argument sites; any other outcome on the root, and every outcome below it, is left to the per-event pass. That pass gates each event's Response, so a scope revoked mid-stream takes effect on the next event without tearing the subscription down; a refused event is an error response, not a closed stream. Decision.Set assigns each Site an Outcome: Allow (the zero value), Deny (an AIP-211-worded error that reveals neither the value nor whether the resource exists), Null, Zero (a non-null field's zero value, for withholding without null-bubbling its parent), or Redact (resolves the field and rewrites the value). Drop is valid only as an ObjectAuthorizer's reply for an instance site at a list element; Decision.Set rejects it everywhere else. An outcome Set rejects is recorded as a denial, so a policy that ignores the error still fails closed. Deny, Null and Zero write their response without calling the resolver, so a FieldInterceptor never observes those fields; Redact does resolve, so a FieldInterceptor sees the pre-redaction value.
WithSubscriptionInterceptor wraps the opening of a subscription's stream, the way OperationInterceptor wraps a query or mutation -- gRPC's Unary/Stream split. It exists because an OperationInterceptor only sees a subscription's individual events, by which point the source is already open and refusing the subscription outright is no longer possible. Relatedly, NewSchema rejects a bound directive (one that wraps a field executor) on a subscription root field: the per-event writer substitutes that field's executor outright to yield the event, so a directive bound there would compose cleanly and then never run.
RequireAuthCoverage is an opt-in, build-time default deny: NewSchema fails for any bound field that declares neither @requiresScopes nor @public. A field's effective requirement is the AND of its own @requiresScopes, its object type's, and each interface it implements -- both that interface's type-level requirement and its same-named field's. resolveAuthRequirements computes it once, at NewSchema, and stores the result on the field's fieldDef and the object's objectType; the shape builder and RequireAuthCoverage both read those stored values, so what a request enforces and what coverage accepts cannot diverge. __typename is guarded the same way, through a SiteObject built from its object's effective type-level requirement; Decision.Set admits only Allow and Deny there, since __typename is String! and has no value to null, zero or redact. That guard is for consistency, not confidentiality: authorization at the field or instance level does not hide how many objects of a guarded type exist or that they exist, and an object whose selection folds to empty is written without consulting any site. __schema and __type are fields of the query root, so an @requiresScopes on the Query type guards introspection as well; that fails closed on purpose. @requiresScopes anywhere the engine does not enforce it -- a union, enum, enum value, scalar, input object, input field, an argument, the schema definition itself, or a directive definition's own argument -- is a build error, not a silent no-op. RequireAuthCoverage counts a field as covered exactly when its effective requirement is non-zero, or when the field or its own object type carries @public; @public on an interface does not exempt its implementers. An argument marked @authorizeInput(kind: FILTER | WRITE) gets a SiteFilterArg or SiteInputWrite site on every field that selects it, whether or not the client supplied a value for it. Decision.Input reports what the client actually supplied there: input-object key paths, enum values and explicit nulls, but never a scalar value, since a policy decides on schema identifiers and scalars are user data; a default an operation variable itself carries counts, because the operation's author chose it, while a default the SDL supplies for the argument or an input field does not, because the client never chose it. Only Allow and Deny apply to an argument site; Deny refuses the field before its resolver runs, whatever outcome is recorded for the field's own output site. ScopeAuthorizer leaves every argument site at Allow, since its Requirement is zero and mapping a key or enum value to the field it restricts is the consumer's naming convention rather than the engine's; RequireAuthCoverage does not require an argument to declare @authorizeInput, for the same reason. Individual values are decided separately. An object type marked @authorizeObject has every value of it offered to the ObjectAuthorizer registered with WithObjectAuthorizer before it is written; without one the marker describes positions and nothing is enforced. These decisions cannot live in a Decision, which is built before any resolver runs and so cannot name values that do not exist yet. Checks are batched per list -- a list of guarded values is drained and decided in one call before any element is written, split only by WithObjectAuthBatch (50 by default) -- while a guarded object reached through a field that is not a list is one call carrying one check. Allow, Deny, Null and Drop apply; Null only where the position is nullable, and Drop only to a list element, where it omits the value and renumbers the indices that follow, so a later denial is reported at the index the client actually sees. A batch that fails -- an error, a mismatched length, an outcome the site cannot represent, a panic -- fails every check still outstanding rather than allowing the ones a later batch would have covered. The marker is rejected at NewSchema anywhere the executor would not consult an ObjectAuthorizer, a root operation type included. An instance site appears in AuthShape.Sites so an Authorizer can see that instance checks will happen, but its outcome comes from the ObjectAuthorizer: Decision.Set refuses it, and an operation whose only sites are instance sites does not consult the Authorizer at all.
An Authorizer failure that is not a *Error -- typically a policy backend's own transport failure -- is never shown to a client: it is presented as a generic internal error, with the original logged and kept behind an unexported cause for a presenter that wants to test it.
Index ¶
- Constants
- func PrintSDL(s *Schema) string
- func Validate(src Source, opts ...SchemaOption) error
- type AbstractOption
- type AuthShape
- type AuthSite
- type Authorizer
- type AuthorizerFunc
- type Decision
- type Error
- type ErrorPresenter
- type Executor
- func (e *Executor) Execute(ctx context.Context, req *Request) *Response
- func (e *Executor) OperationKind(query, operationName string) (ast.Operation, error)
- func (e *Executor) Schema() *Schema
- func (e *Executor) Stats() ExecutorStats
- func (e *Executor) Subscribe(ctx context.Context, req *Request) (<-chan *Response, error)
- type ExecutorOption
- func DisableSuggestions() ExecutorOption
- func WithAuthorizer(a Authorizer) ExecutorOption
- func WithErrorPresenter(p ErrorPresenter) ExecutorOption
- func WithFieldInterceptor(is ...FieldInterceptor) ExecutorOption
- func WithFieldObserver(o ...FieldObserver) ExecutorOption
- func WithMaxComplexity(n int) ExecutorOption
- func WithMaxConcurrency(n int) ExecutorOption
- func WithMaxDepth(n int) ExecutorOption
- func WithMaxErrors(n int) ExecutorOption
- func WithMaxNesting(n int) ExecutorOption
- func WithMaxResponseBytes(n int64) ExecutorOption
- func WithMaxTokens(n int) ExecutorOption
- func WithObjectAuthBatch(n int) ExecutorOption
- func WithObjectAuthorizer(a ObjectAuthorizer) ExecutorOption
- func WithOperationInterceptor(is ...OperationInterceptor) ExecutorOption
- func WithOperationTimeout(d time.Duration) ExecutorOption
- func WithPlanCache(size int) ExecutorOption
- func WithPlanCacheBytes(n int64) ExecutorOption
- func WithQueryCost(c QueryCost) ExecutorOption
- func WithRecover(enabled bool) ExecutorOption
- func WithRequestInterceptor(is ...RequestInterceptor) ExecutorOption
- func WithSubscriptionInterceptor(is ...SubscriptionInterceptor) ExecutorOption
- type ExecutorStats
- type ExtensionsProvider
- type FieldContext
- type FieldFunc
- type FieldHandler
- type FieldInfo
- type FieldInterceptor
- type FieldInterceptorFunc
- type FieldObserver
- type FieldOption
- func Field[P, R any](name string, fn func(P) R, opts ...FieldSchedule) FieldOption
- func FieldArgs[P, A, R any](name string, fn func(P, A) R, opts ...FieldSchedule) FieldOption
- func Resolve[P, R any](name string, fn func(context.Context, P) (R, error), opts ...FieldSchedule) FieldOption
- func ResolveArgs[P, A, R any](name string, fn func(context.Context, P, A) (R, error), opts ...FieldSchedule) FieldOption
- func Subscribe[R any](name string, fn func(context.Context) (<-chan R, error), opts ...FieldSchedule) FieldOption
- func SubscribeArgs[A, R any](name string, fn func(context.Context, A) (<-chan R, error), ...) FieldOption
- type FieldSchedule
- type ID
- type InputFieldOption
- type InputKey
- type Location
- type Marshaler
- type ObjectAuthorizer
- type ObjectCheck
- type Omittable
- type OperationContext
- func (oc *OperationContext) ActualCost() (int, bool)
- func (oc *OperationContext) AuthShape() *AuthShape
- func (oc *OperationContext) Complexity() int
- func (oc *OperationContext) Cost() int
- func (oc *OperationContext) Depth() int
- func (oc *OperationContext) Get(key any) (any, bool)
- func (oc *OperationContext) GetOrSet(key, value any) (actual any, loaded bool)
- func (oc *OperationContext) Set(key, value any)
- func (oc *OperationContext) SetExtension(key string, value any)
- func (oc *OperationContext) Waves() *WaveCoordinator
- type OperationHandler
- type OperationInterceptor
- type OperationInterceptorFunc
- type OperationStats
- type Outcome
- type Path
- type PathElem
- type QueryCost
- type Request
- type RequestHandler
- type RequestInterceptor
- type RequestInterceptorFunc
- type Requirement
- type Response
- type Root
- type Schema
- type SchemaOption
- func Args[A any](fields ...InputFieldOption) SchemaOption
- func Directive(name string, fn func(next FieldFunc) FieldFunc) SchemaOption
- func DirectiveArgs[A any](name string, fn func(next FieldFunc, args A) FieldFunc) SchemaOption
- func DisableIntrospection() SchemaOption
- func Enum[T comparable](name string, values map[T]string) SchemaOption
- func EnumMarshaler[T any, PT Marshaler[T]](name string) SchemaOption
- func Input[T any](name string, fields ...InputFieldOption) SchemaOption
- func Interface[T any](name string, opts ...AbstractOption) SchemaOption
- func IntrospectDeprecatedInputValues() SchemaOption
- func MarkerDirective(name, scope string) SchemaOption
- func Mutation(fields ...FieldOption) SchemaOption
- func Object[T any](name string, fields ...FieldOption) SchemaOption
- func Options(opts ...SchemaOption) SchemaOption
- func Query(fields ...FieldOption) SchemaOption
- func RequireAuthCoverage() SchemaOption
- func RequirementDirective(name, arg string, shape ScopeShape) SchemaOption
- func Scalar[T any](name string, marshal func(*Writer, T) error, unmarshal func(any) (T, error)) SchemaOption
- func ScalarMarshaler[T any, PT Marshaler[T]](name string) SchemaOption
- func Subscription(fields ...FieldOption) SchemaOption
- func SubscriptionRootDirective(names ...string) SchemaOption
- func Time(name string) SchemaOption
- func Union[T any](name string, opts ...AbstractOption) SchemaOption
- type ScopeShape
- type SelectedField
- type Selection
- type SiteKind
- type Source
- type SubscribeError
- type SubscriptionHandler
- type SubscriptionInterceptor
- type SubscriptionInterceptorFunc
- type WaveCoordinator
- type Writer
Constants ¶
const ( CodeInternal = "INTERNAL_SERVER_ERROR" CodeParseFailed = "GRAPHQL_PARSE_FAILED" CodeValidationFailed = "GRAPHQL_VALIDATION_FAILED" CodeBadUserInput = "BAD_USER_INPUT" CodeOperationResolution = "OPERATION_RESOLUTION_FAILURE" CodeRequestCancelled = "REQUEST_CANCELLED" // CodeTooComplex is returned when static field count or query cost // exceeds the executor limit (GitHub / Shopify style). CodeTooComplex = "COMPLEXITY_LIMIT_EXCEEDED" // CodeMaxDepth is returned when the selection nesting exceeds WithMaxDepth. CodeMaxDepth = "MAX_DEPTH_EXCEEDED" // CodeForbidden is returned when an Authorizer denies a site. CodeForbidden = "FORBIDDEN" // CodeResponseTooLarge is returned when a response's data exceeds // WithMaxResponseBytes. CodeResponseTooLarge = "RESPONSE_TOO_LARGE" // CodeOperationTimeout is returned for work cut short by // WithOperationTimeout. When a deadline the caller set ends execution, // the executor reports CodeRequestCancelled instead. CodeOperationTimeout = "OPERATION_TIMEOUT" // CodeErrorLimitExceeded marks the one error that stands in for those // omitted past WithMaxErrors. CodeErrorLimitExceeded = "ERROR_LIMIT_EXCEEDED" )
Error codes reported in the "code" entry of an error's extensions. They follow the conventions used by the wider GraphQL ecosystem so that generic clients can act on them.
Variables ¶
This section is empty.
Functions ¶
func PrintSDL ¶
PrintSDL renders the schema as SDL, excluding built-in types and directives.
Every applied directive is printed, including ones the schema uses internally such as authorization requirements; a schema published from this output publishes those too. fed.Subgraph serves the author's own SDL instead.
The output reloads as the same schema, descriptions and string values included, which gqlparser's formatter does not guarantee; see sdlprint.
func Validate ¶
func Validate(src Source, opts ...SchemaOption) error
Validate builds the schema and discards it. It exists for tests that want to assert bindings are complete and shape-compatible without executing anything.
Types ¶
type AbstractOption ¶
type AbstractOption interface {
// contains filtered or unexported methods
}
AbstractOption tunes an Interface or Union binding.
func TypeResolver ¶
func TypeResolver[T any](fn func(T) string) AbstractOption
TypeResolver overrides dynamic Go type lookup with an explicit function returning the concrete GraphQL object type name for a value.
type AuthShape ¶
type AuthShape struct {
// contains filtered or unexported fields
}
AuthShape is what an operation touches, independent of who is asking. It is computed once per compiled plan and cached with it, which is why the plan cache is not multiplied by the number of distinct policies.
func (*AuthShape) IsEmpty ¶
IsEmpty reports whether the operation touches nothing an Authorizer can decide: it declares no requirement, selects no field with an @authorizeInput argument, and -- since an instance site is decided by the ObjectAuthorizer rather than here -- an operation whose only sites are instance sites is empty too. An Authorizer is not consulted for such an operation. Sites still reports the instance sites, so an Authorizer that is consulted for some other reason can see that instance checks will happen.
func (*AuthShape) Scopes ¶
Scopes returns every scope named anywhere in the operation, sorted and deduplicated, so an Authorizer can load them in one round trip. Like Sites, this is a copy of the plan's own slice, for the same reason: the shape is cached with the plan and shared by every later request.
func (*AuthShape) Sites ¶
Sites returns the positions needing a decision, indexed by site index. planField.authIdx indexes into this slice.
The shape is cached with its plan and shared by every request that reuses it, and Authorize hands it to caller-supplied code, so this returns an independent copy rather than the plan's own slice: a caller that mutates a returned AuthSite's Coord, Requires or Grants must not corrupt authorization for a later request. Field and Object remain shared *ast.FieldDefinition/*ast.Definition pointers into the parsed schema — cloning gqlparser's AST per call would be its own, larger cost, and nothing in this package ever mutates that AST after NewSchema returns, so a caller would have to go out of its way to reach for a mutation this package itself never performs.
type AuthSite ¶
type AuthSite struct {
Coord string
// Field and Object describe the position. Neither names a field for the
// two kinds that are about a type rather than a field position:
// SiteObject leaves Field nil, and SiteInstance leaves both nil, since an
// instance is decided by its type and the value alone and the same
// resolved object can reach several field positions. Coord carries the
// type name for both.
Field *ast.FieldDefinition
Object *ast.Definition
Kind SiteKind
Requires Requirement
Grants []string
// Arg names the argument for SiteFilterArg and SiteInputWrite sites. For
// those kinds, Coord is Type.field(arg:) and Decision.Input reports what
// the client supplied there.
Arg string
// ListElement reports that the value at this position is an element of a
// list. It exists for SiteInstance, whose Field is nil: an
// ObjectAuthorizer has to choose between Drop and Deny for a row it
// withholds, Drop is valid only at a list element position, and without
// this the only way to tell was to know the schema by heart. Every other
// kind can read the same fact from Field.Type.
ListElement bool
// NonNull reports that the position a SiteInstance value occupies is
// non-null: the field's type with every list wrapper stripped, so
// Customer! and [Customer!]! say true and [Customer]! says false. It is
// the other half of what ListElement is for. Null is the outcome that
// gives nothing away -- a withheld row answers exactly as a missing one
// does -- and it is valid only where the position can hold null, which a
// policy otherwise had to know the schema to tell. Every other kind can
// read the same fact from Field.Type.
NonNull bool
// contains filtered or unexported fields
}
AuthSite is one position in a plan that may need a decision.
type Authorizer ¶
Authorizer turns an operation's shape into a decision for one principal. It runs once per query or mutation, before any field resolves, and is not called at all when the operation touches nothing that declares a requirement and selects no field with an @authorizeInput argument. A subscription calls it once when the stream opens and again for every event, so a scope revoked mid-stream applies to the next event.
A returned error rejects the whole operation, or for a subscription the opening of the stream or the one event being evaluated. The interface deliberately holds no policy of its own: it is the adapter to whatever decides, be that OPA, Cedar, OpenFGA, Casbin or a hand-written checker.
func ScopeAuthorizer ¶
func ScopeAuthorizer(held func(context.Context) map[string]bool) Authorizer
ScopeAuthorizer is the default policy: a site is allowed when held covers its Requirement, and denied with the AIP-211 wording otherwise. It exists so the common case needs no Authorizer of its own.
type AuthorizerFunc ¶
AuthorizerFunc adapts a function to Authorizer.
type Decision ¶
type Decision struct {
// contains filtered or unexported fields
}
Decision records the outcome for each site in a shape. It is passed to the Authorizer rather than returned by it so the framework owns the allocation and sizes it from the shape. The zero value of every entry allows.
func (*Decision) Input ¶
Input returns what the client supplied for an argument site: key paths, enum values and explicit nulls, never scalar values. It is empty for any other site and for an argument the client did not supply.
func (*Decision) Set ¶
Set records the outcome for one site. It reports an error for an outcome the site cannot represent, so a policy mistake surfaces once per request with the coordinate attached rather than as a null-bubbled parent, and records a denial in its place so the mistake never widens access.
type Error ¶
type Error struct {
Message string
Locations []Location
Path Path
Extensions map[string]any
Err error
// contains filtered or unexported fields
}
Error is a GraphQL error as it appears in the "errors" array of a response. Err carries the underlying cause for logging and is never serialized.
func DefaultErrorPresenter ¶
DefaultErrorPresenter returns the first *Error or *gqlerror.Error found in err's chain (copied, so annotating it is safe), or wraps err verbatim. Extensions contributed by an ExtensionsProvider in the chain are merged in.
func (*Error) MarshalJSON ¶
MarshalJSON renders the error in the specification's format, emitting message, locations, path and extensions in that order and omitting empty members.
func (*Error) WithExtension ¶
WithExtension sets one extension entry and returns e.
v must marshal with encoding/json. It is not checked here, and the failure surfaces late and badly: Response.WriteTo and MarshalJSON return an error for the whole envelope, and a transport has already written its status header by then, so the client receives an empty body with no explanation while the server logs a warning. One decorative extension can therefore cost the error it was decorating. Marshal it yourself if the value is anything but a string, number, bool, slice or map of those.
type ErrorPresenter ¶
ErrorPresenter converts any error raised during a request into the Error that is sent to the client. Implementations typically mask internal messages and attach codes. It may return the same *Error for many calls: the executor copies it before setting the path and locations. Returning nil drops a field error; a request error, which the response cannot omit, is replaced by a generic one carrying the original's code.
type Executor ¶
type Executor struct {
// contains filtered or unexported fields
}
Executor runs operations against a Schema. It owns the plan cache and the concurrency budget and is safe for concurrent use.
func NewExecutor ¶
func NewExecutor(s *Schema, opts ...ExecutorOption) *Executor
NewExecutor creates an executor for s.
func (*Executor) Execute ¶
Execute runs a query or mutation and returns its response. Call Response.Release once the response has been serialized.
func (*Executor) OperationKind ¶
OperationKind reports the kind of the operation a request would execute, using the document cache. Transports use it to reject mutations over GET.
func (*Executor) Stats ¶
func (e *Executor) Stats() ExecutorStats
Stats reports the executor's current shared state.
func (*Executor) Subscribe ¶
Subscribe runs a subscription operation. It parses, validates and plans the document, opens the field's source stream, and returns a channel carrying one response per event. The channel is closed when the source closes or ctx is cancelled; cancelling ctx is how a caller unsubscribes.
Responses are sent unbuffered, so a slow consumer applies backpressure to the source rather than accumulating events. Call Response.Release on each one after serializing it.
On a request error nothing is started: the channel is nil and the error is a *SubscribeError carrying the response to send.
type ExecutorOption ¶
type ExecutorOption func(*Executor)
ExecutorOption configures an Executor.
func DisableSuggestions ¶
func DisableSuggestions() ExecutorOption
DisableSuggestions withholds the "Did you mean" clause from validation errors. DisableIntrospection alone does not close that channel: the suggestions name neighbouring fields, types, arguments and input fields, so a schema stays enumerable one typo at a time. The errors still report what was wrong, only without naming what would have been right.
func WithAuthorizer ¶
func WithAuthorizer(a Authorizer) ExecutorOption
WithAuthorizer sets the authorizer consulted once per operation.
func WithErrorPresenter ¶
func WithErrorPresenter(p ErrorPresenter) ExecutorOption
WithErrorPresenter replaces DefaultErrorPresenter.
func WithFieldInterceptor ¶
func WithFieldInterceptor(is ...FieldInterceptor) ExecutorOption
WithFieldInterceptor registers field interceptors. The first is the outermost. Field interceptors force every field through the type-erased path; to observe fields without that cost, use WithFieldObserver.
func WithFieldObserver ¶
func WithFieldObserver(o ...FieldObserver) ExecutorOption
WithFieldObserver registers field observers. The first is outermost, as with the interceptor options.
func WithMaxComplexity ¶
func WithMaxComplexity(n int) ExecutorOption
WithMaxComplexity rejects operations whose static field count (aliases included) exceeds n. Zero, the default, means unlimited. __schema and __type count as one field each: see WithMaxDepth.
func WithMaxConcurrency ¶
func WithMaxConcurrency(n int) ExecutorOption
WithMaxConcurrency sets the slot budget shared by every request's concurrently scheduled fields, which Stats reports as ConcurrencyInUse and ConcurrencyLimit. It does not cap goroutines: each concurrently scheduled field or list element runs on its own goroutine and takes a slot only if one is free, because a field waiting for a slot would hold back the DataLoader wave its siblings are parked in. What bounds the work of one request is its size, so bound that with WithMaxComplexity, WithQueryCost and WithMaxResponseBytes. Zero disables concurrent resolution: every field runs inline, in order. The default is 4 × GOMAXPROCS.
func WithMaxDepth ¶
func WithMaxDepth(n int) ExecutorOption
WithMaxDepth rejects operations whose selection nesting exceeds n. Zero, the default, means unlimited.
__schema and __type count as one level, whatever they select, here and in WithMaxComplexity and QueryCost. These limits are set against an API's own queries, and the introspection query IDEs and client generators send nests its type references seven levels deep, so charging it would lock the tools out of any API that sets them. Introspection is bounded instead by graphql-js's MaxIntrospectionDepth rule: a path may pass through at most two of fields, interfaces, possibleTypes and inputFields. A public API should still DisableIntrospection.
func WithMaxErrors ¶
func WithMaxErrors(n int) ExecutorOption
WithMaxErrors bounds how many errors one response carries. Past n, field errors are dropped, and a single ERROR_LIMIT_EXCEEDED error is appended in their place; the data is unaffected. A dropped error is not presented or given a path, except that concurrent fields racing the limit may be presented before they are dropped, so a presenter that logs can run a few more than n times. Errors that explain why a request stopped (cancellation, timeout, an oversized response) are always kept. Request errors from parsing and validation are cut the same way. Which field errors are kept is the order they were recorded in, which for concurrent fields is not deterministic. Zero means unlimited. The default is 1000: generous enough that a list with a failed field on each of hundreds of rows keeps every error, bounded enough that a million failing elements do not become a million errors in memory.
func WithMaxNesting ¶
func WithMaxNesting(n int) ExecutorOption
WithMaxNesting caps how deeply a document's selection sets, or its input value literals, may nest as written: { a { b } } and [[1]] are both 2. A deeper document is refused with GRAPHQL_PARSE_FAILED before validation, whose ValuesOfCorrectType check does work at every level proportional to what is below it. It is not WithMaxDepth: that counts an operation's depth after fragments are expanded, this counts the text. Zero means unlimited. The default is 100.
func WithMaxResponseBytes ¶
func WithMaxResponseBytes(n int64) ExecutorOption
WithMaxResponseBytes bounds the size of each response's data: one query or mutation, or one subscription event. A response over the limit has null data and a single RESPONSE_TOO_LARGE error in place of any other errors, and execution stops resolving fields once it sees the limit passed.
A response is rejected when its finished data is over the limit, or when the limit was passed at any point while executing: execution was cut short from then on, so the result is rejected even if null bubbling later discarded the bytes that passed it. While executing, a field other than __typename is checked before it is written, so each buffer being written concurrently can pass the limit by about one field's output before execution notices. The errors list is not counted. Zero means unlimited. The default is 64 MiB, which measured no cost distinguishable from no limit.
func WithMaxTokens ¶
func WithMaxTokens(n int) ExecutorOption
WithMaxTokens caps the tokens a document may have: parsing stops at the limit and the request is refused with GRAPHQL_PARSE_FAILED. Parsing and validation run before WithMaxDepth, WithMaxComplexity and WithQueryCost can, on every document, valid or not, and gqlparser's default rules are not all linear in it, so this and WithMaxNesting are what bound that work. Zero means unlimited, which leaves gqlparser's recursive parser bounded only by the request body: about a million levels of [ overflows the goroutine stack, which no recover catches, and the transports' 1 MiB default body keeps a document under that. The default is 15 000, gqlgen's and Apollo Router's.
func WithObjectAuthBatch ¶
func WithObjectAuthBatch(n int) ExecutorOption
WithObjectAuthBatch bounds how many checks one AuthorizeObjects call carries; larger waves are split into sequential calls. The default of 50 matches OpenFGA's BatchCheck default, which is the shape most remote policy backends are tuned for.
Sequential is the word to read twice: the batches are issued one after another, so a remote policy's latency is paid once per batch and not once per list. A thousand rows at the default of 50 is twenty round trips -- 100ms of added latency at 5ms each, measured, and halving the batch doubles it (TestObjectAuthBatchesAreSequential). Size this against what one call costs in latency, not against what the backend can accept in one request.
func WithObjectAuthorizer ¶
func WithObjectAuthorizer(a ObjectAuthorizer) ExecutorOption
WithObjectAuthorizer enables instance-level authorization. Without it, @authorizeObject describes positions and nothing is enforced.
func WithOperationInterceptor ¶
func WithOperationInterceptor(is ...OperationInterceptor) ExecutorOption
WithOperationInterceptor registers operation interceptors. The first is the outermost.
func WithOperationTimeout ¶
func WithOperationTimeout(d time.Duration) ExecutorOption
WithOperationTimeout bounds how long one operation may run: a query or mutation from the moment Execute is called, parsing and every interceptor included, or one subscription event, never a stream as a whole. Opening a subscription is not bounded either — its interceptors, authorization and source opener share the context the stream lives on, which a deadline would end. Each entry of an HTTP batch is its own operation, so a batch of n may take n times the timeout.
At the deadline the operation's context is done, so fields not yet started are skipped and resolvers that honour their context return; the response keeps the data already written, with OPERATION_TIMEOUT errors where work was cut. A resolver's own error is kept in that error's chain, with its extensions. It does not force a response out on time: the executor waits for every resolver it started, so one that ignores its context still holds the response until it returns.
Only this executor's own deadline is OPERATION_TIMEOUT. When a deadline the caller set ends execution, the executor reports REQUEST_CANCELLED, and a resolver's error for it is passed through unchanged. Zero, the default, means no timeout: the right bound depends on the workload, and a wrong default would cut short legitimate slow operations.
func WithPlanCache ¶
func WithPlanCache(size int) ExecutorOption
WithPlanCache sets the number of parsed documents kept in the LRU cache. Zero disables caching. The default is 1024.
func WithPlanCacheBytes ¶
func WithPlanCacheBytes(n int64) ExecutorOption
WithPlanCacheBytes bounds the query text the plan cache holds, evicting the least recently used documents to stay within it. Zero means no byte limit; the entry count from WithPlanCache still applies. The default is 16 MiB.
The budget is on query text because that is what can be counted cheaply, but what it actually bounds is memory: a parsed and validated document retained roughly 26 times its query text when measured on an alias-heavy query (other shapes will differ), so without this limit 1024 distinct valid 1 MiB queries of that shape hold tens of gigabytes. Ordinary queries are a few kilobytes, so the default entry count binds long before this does and only unusually large queries ever reach it. A query larger than the whole budget is not cached but still executes.
func WithQueryCost ¶
func WithQueryCost(c QueryCost) ExecutorOption
WithQueryCost enables Shopify-style query cost. When c.Max > 0, operations above the cap are rejected before execution.
func WithRecover ¶
func WithRecover(enabled bool) ExecutorOption
WithRecover controls whether resolver panics are converted into INTERNAL_SERVER_ERROR field errors, and Authorizer panics into the same error for the whole operation or event. A panic anywhere else in Execute or in opening a subscription -- an interceptor, the presenter, a scalar's unmarshal, an input setter, a TypeResolver, a lazy list's body -- costs the whole operation that same error and no data, and one in a subscription event costs that event. It is enabled by default; disable it only in tests that want panics to surface.
func WithRequestInterceptor ¶
func WithRequestInterceptor(is ...RequestInterceptor) ExecutorOption
WithRequestInterceptor registers request interceptors. The first is the outermost, matching grpc.ChainUnaryInterceptor.
func WithSubscriptionInterceptor ¶
func WithSubscriptionInterceptor(is ...SubscriptionInterceptor) ExecutorOption
WithSubscriptionInterceptor registers subscription interceptors. The first is the outermost.
type ExecutorStats ¶
type ExecutorStats struct {
// PlanCacheEntries and PlanCacheBytes are the documents the plan cache
// holds and their total query text, the quantity WithPlanCacheBytes bounds.
PlanCacheEntries int
PlanCacheBytes int64
// PlanCacheHits and PlanCacheMisses count operations that were planned: a
// hit found its compiled plan cached, a miss compiled one. A request
// rejected by parsing, validation or a depth or complexity limit is
// neither, and so is OperationKind, which transports call before Execute;
// one refused by the query cost limit has already been planned and counts.
// A document with too many conditional variables to cache its plans
// (OperationStats.PlanUncacheable) is a miss on every request. A
// subscription counts once, when it opens: its events reuse that plan.
// Per operation this is the judgement OperationStats.CacheHit reports,
// though CacheHit is repeated on every subscription event.
PlanCacheHits int64
PlanCacheMisses int64
// ConcurrencyInUse and ConcurrencyLimit are the resolver slots held right
// now and the WithMaxConcurrency budget; both are zero when concurrency is
// disabled.
ConcurrencyInUse int
ConcurrencyLimit int
}
ExecutorStats is a point-in-time view of an Executor's shared state, for exporting as metrics (ext/otel.ObserveExecutor does). Taking one is cheap and safe from any goroutine.
type ExtensionsProvider ¶
ExtensionsProvider may be implemented by error types to contribute entries to the "extensions" member of the presented GraphQL error.
type FieldContext ¶
type FieldContext struct {
Field *ast.FieldDefinition
Object *ast.Definition
Args any
Parent any
// contains filtered or unexported fields
}
FieldContext describes the field a resolver is executing. It is attached to the context passed to Resolve and ResolveArgs functions only: a Field or FieldArgs accessor takes no context, so nothing is attached for one, and a FieldInterceptor must use the FieldContext it is handed as a parameter rather than FieldFrom.
Args is the decoded argument struct, a pointer to the type the field was bound with. A FieldInterceptor may replace it with a pointer to a value of that same type, and the field then runs with the replacement; that is how an interceptor supplies an argument the client did not send, such as a default page size. Copy before changing: literal arguments are decoded once per plan and shared by every request with the same query text, so writing through the original would alter all of them.
func FieldFrom ¶
func FieldFrom(ctx context.Context) *FieldContext
FieldFrom returns the FieldContext, or nil when ctx does not belong to a resolver invocation.
func (*FieldContext) ArgumentMap ¶
ArgumentMap returns this field's argument values as the request gave them, before binding; see SelectedField.ArgumentMap for what that means for variables, defaults and absent arguments. It is for a FieldInterceptor that enforces a rule over what the client sent, such as refusing a write to a read-only key or a filter on a hidden column: the decoded Args struct cannot say whether a key was omitted or sent as null, and the interceptor cannot name it. vars is OperationFrom(ctx).Variables. A nil or hand-built FieldContext has no arguments and returns nil.
func (*FieldContext) Path ¶
func (fc *FieldContext) Path() Path
Path returns the response path of the field. The node is built here rather than when the FieldContext is, because most fields are never asked for it.
func (*FieldContext) Selection ¶
func (fc *FieldContext) Selection() Selection
Selection returns the sub-selection requested beneath the field.
type FieldFunc ¶
FieldFunc is the type-erased field executor seen by directives and field interceptors. args is the decoded argument struct pointer (*A) or nil for fields without a bound argument type.
type FieldHandler ¶
FieldHandler resolves a field.
type FieldInfo ¶
type FieldInfo struct {
Object string
Field string
Alias string
// Resolver reports whether the field is bound with Resolve or
// ResolveArgs, which may do I/O, rather than Field, which reads data it
// was handed. A tracer that wants to see where time and queries go
// spans these and skips the rest.
Resolver bool
// contains filtered or unexported fields
}
FieldInfo identifies a field to an observer. It is a value, and its path is materialized only if asked for, so the engine allocates nothing to observe a field until the observer itself spends something. It is not free in time: each observed field still pays two interface calls and a defer.
It deliberately carries neither the arguments nor the parent value: those are what a FieldContext is for, and wanting them means wanting a FieldInterceptor.
type FieldInterceptor ¶
type FieldInterceptor interface {
InterceptField(ctx context.Context, fc *FieldContext, next FieldHandler) (any, error)
}
FieldInterceptor wraps every field executor in the plan. It routes field results through any, so use it only to change or replace a result; to watch fields without doing that, use WithFieldObserver, which keeps them typed.
Read the field from the fc parameter. The FieldContext is attached to the context only for Resolve and ResolveArgs fields, so inside a pure field FieldFrom, PathFrom and SelectionFrom find nothing.
func ChainFieldInterceptors ¶
func ChainFieldInterceptors(is ...FieldInterceptor) FieldInterceptor
ChainFieldInterceptors composes interceptors so that the first is outermost. A nil or empty list is a no-op interceptor.
type FieldInterceptorFunc ¶
type FieldInterceptorFunc func(ctx context.Context, fc *FieldContext, next FieldHandler) (any, error)
FieldInterceptorFunc adapts a function to FieldInterceptor.
func (FieldInterceptorFunc) InterceptField ¶
func (f FieldInterceptorFunc) InterceptField(ctx context.Context, fc *FieldContext, next FieldHandler) (any, error)
InterceptField implements FieldInterceptor.
type FieldObserver ¶
type FieldObserver interface {
// BeginField runs before the field. The context it returns is the one the
// field's own resolver runs under, so a span the resolver starts nests
// beneath one the observer started. It is not the context of the field's
// sub-selection: children run under the context the field itself
// received.
BeginField(ctx context.Context, f FieldInfo) context.Context
// EndField runs when the field's resolver returns, with the context this
// observer's BeginField returned. For a composite field that is before its
// sub-selection is written, so a span ended here covers the resolver and
// not the children.
EndField(ctx context.Context, f FieldInfo, err error)
}
FieldObserver watches every field, pure ones included, without seeing its value. Not seeing the value is what lets the engine leave each field on its typed write path: a FieldInterceptor, which may replace a result, cannot.
A field that never runs is never observed: __typename, a field whose arguments fail to decode, a field skipped because the request context was already cancelled, and a field an Authorizer's decision denied, nulled or zeroed without resolving it. A redacted field does run -- Redact rewrites the resolved value -- and is observed like any other.
Sibling resolver fields are scheduled concurrently, so both methods may be called from several goroutines at once; an observer that holds state of its own must synchronise it.
A panic inside BeginField or EndField is not recovered by WithRecover: BeginField runs before the field's recovery is in place, and EndField after it has finished, so that EndField can see a recovered panic as err.
type FieldOption ¶
type FieldOption interface {
// contains filtered or unexported methods
}
FieldOption declares one field of an Object binding.
func Field ¶
func Field[P, R any](name string, fn func(P) R, opts ...FieldSchedule) FieldOption
Field binds a pure field: fn reads data from the parent without I/O. Pure fields are executed inline and never scheduled on their own goroutine. A composite list result may be spelled iter.Seq[E] or iter.Seq[*E] wherever it may be spelled []E or []*E.
func FieldArgs ¶
func FieldArgs[P, A, R any](name string, fn func(P, A) R, opts ...FieldSchedule) FieldOption
FieldArgs binds a pure field that takes arguments decoded into A.
func Resolve ¶
func Resolve[P, R any](name string, fn func(context.Context, P) (R, error), opts ...FieldSchedule) FieldOption
Resolve binds a resolver field that may perform I/O. Resolver fields are eligible for concurrent scheduling. A composite list result may be spelled iter.Seq[E] or iter.Seq[*E] wherever it may be spelled []E or []*E.
func ResolveArgs ¶
func ResolveArgs[P, A, R any](name string, fn func(context.Context, P, A) (R, error), opts ...FieldSchedule) FieldOption
ResolveArgs binds a resolver field that takes arguments decoded into A. An Args[A] registration must be present in the same schema.
func Subscribe ¶
func Subscribe[R any](name string, fn func(context.Context) (<-chan R, error), opts ...FieldSchedule) FieldOption
Subscribe binds a subscription root field to the function that opens its source event stream. Each value received becomes one response, with the value taking the place of the field's result, so the field's sub-selection is executed against it exactly as a query's would be.
fn is called once per operation. It should return promptly and do its work in a goroutine feeding the channel; closing the channel ends the subscription. An error returned here prevents the subscription from starting. A channel carries no error of its own, so a source that can fail mid-stream should carry the failure in its event type.
func SubscribeArgs ¶
func SubscribeArgs[A, R any](name string, fn func(context.Context, A) (<-chan R, error), opts ...FieldSchedule) FieldOption
SubscribeArgs binds a subscription root field that takes arguments decoded into A. An Args[A] registration must be present in the same schema.
type FieldSchedule ¶
type FieldSchedule func(*fieldSpec)
FieldSchedule tunes whether a field runs inline or concurrently. The name follows the gRPC CallOption style: it only affects scheduling.
func Concurrent ¶
func Concurrent() FieldSchedule
Concurrent allows a Field, which is normally executed inline, to be scheduled concurrently. Use it for pure fields that are CPU-heavy.
func Inline ¶
func Inline() FieldSchedule
Inline forces a Resolve field to run synchronously in the parent's goroutine even when siblings are scheduled concurrently.
type InputFieldOption ¶
type InputFieldOption interface {
// contains filtered or unexported methods
}
InputFieldOption declares one field of an Input or Args binding.
func InputField ¶
func InputField[T, V any](name string, set func(*T, V)) InputFieldOption
InputField binds the input field or argument name to a setter on *T. V must be registered for the field's GraphQL type through a scalar, enum or input binding.
func OmittableField ¶
func OmittableField[T, V any](name string, set func(*T, Omittable[V])) InputFieldOption
OmittableField is like InputField but the setter receives an Omittable that is set whenever the field was present or a default applied, even if its value is null.
func ZeroForNull ¶
func ZeroForNull() InputFieldOption
ZeroForNull lets a Go type that cannot represent null back a nullable input position on this binding: absent, explicit null and the zero value all decode to the zero value.
The rule it relaxes is there for a reason -- a bool field cannot tell an omitted flag from a false one, and for a PATCH-style input those are different requests, which is what Omittable exists for. It is opt-in per binding so that the author states where the two genuinely mean the same thing, rather than the engine assuming it everywhere.
The case it was added for is an ORM filter. entgql emits VariantIDIsNil bool under variantIdIsNil: Boolean and reads false as "apply no predicate", so absent and false really are one value; one real schema has 11 035 such fields, in SDL its author does not hand-write.
It applies to the fields this binding derives from the struct. A field declared explicitly with InputField carries its own setter, so its Go type is already the author's choice.
type InputKey ¶
InputKey is one position a client supplied inside an argument that an Authorizer is asked about: the input-object keys from the argument down, list indices omitted. Enum holds the value when that position is an enum, because ordering and grouping name a column by enum value rather than by key. Null is set for an explicit null, which is a write where an absent key is not. Scalar values are never carried: a policy decides on schema identifiers, and the values are user data.
type Marshaler ¶
Marshaler is the encoding contract gqlgen defines and that ent-, velox- and gqlgen-generated leaf types implement: MarshalGQL writes the value's JSON encoding, UnmarshalGQL decodes an input value into the receiver. PT is *T; MarshalGQL may have either receiver.
type ObjectAuthorizer ¶
type ObjectAuthorizer interface {
AuthorizeObjects(ctx context.Context, checks []ObjectCheck) ([]Outcome, error)
}
ObjectAuthorizer decides whether a principal may see individual objects.
Batching is per list: a list of guarded values is drained and decided in one call before any element is written, split into several only by WithObjectAuthBatch. That is what keeps a policy backed by a remote decision point to one call per list rather than one per row. It is not per wave -- a guarded object reached through a field that is not a list is one call carrying one check, so a selection that reaches guarded objects through nested single-object fields issues one call per such field. Where that matters, model the position as a list or cache inside the implementation.
The returned slice is positional over checks and must have the same length; any other length is an error, because a short slice would silently allow the rows it does not cover. checks is only valid for the duration of the call: do not retain it, and do not reorder it in place, since the outcomes are mapped back by position in the order it was given.
type ObjectCheck ¶
ObjectCheck is one resolved object an ObjectAuthorizer decides on. Type is the concrete object type's name, which differs from Site.Coord when the field's declared type is an interface or a union.
type Omittable ¶
type Omittable[T any] struct { // contains filtered or unexported fields }
Omittable distinguishes an input field that was absent from one that was explicitly set to null. Use it on PATCH-style input structs; Auto bindings and OmittableField both understand it.
func OmittableOf ¶
OmittableOf returns an Omittable that is set to v.
func (Omittable[T]) IsSet ¶
IsSet reports whether the field was present in the input, even if its value was null.
func (Omittable[T]) MarshalJSON ¶
MarshalJSON encodes the value, or null when unset.
func (Omittable[T]) Or ¶
func (o Omittable[T]) Or(def T) T
Or returns the set value, or def when the field was absent.
func (*Omittable[T]) UnmarshalJSON ¶
UnmarshalJSON decodes into the value and marks it set. A field absent from the JSON never reaches this method, so it stays unset; an explicit null does, and is set with the zero value. That is the distinction Omittable exists for, and it lets an input struct be filled from JSON (a stored definition, a test fixture) as well as from a GraphQL request.
type OperationContext ¶
type OperationContext struct {
Operation *ast.OperationDefinition
Doc *ast.QueryDocument
RawQuery string
OperationName string
Variables map[string]any
Stats OperationStats
// contains filtered or unexported fields
}
OperationContext describes the operation being executed. It is available from any context derived from the request through OperationFrom.
func OperationFrom ¶
func OperationFrom(ctx context.Context) *OperationContext
OperationFrom returns the OperationContext, or nil outside a request.
func (*OperationContext) ActualCost ¶
func (oc *OperationContext) ActualCost() (int, bool)
ActualCost returns the cost summed from the fields this operation really resolved, and whether it was measured. It is measured only when the executor was built with a QueryCost whose Actual is set, and only after execution.
func (*OperationContext) AuthShape ¶
func (oc *OperationContext) AuthShape() *AuthShape
AuthShape returns what this operation touches, or nil when it touches nothing that declares an authorization requirement.
func (*OperationContext) Complexity ¶
func (oc *OperationContext) Complexity() int
Complexity returns the static field count computed by operationMetrics over the document, copied onto the plan at compile time.
func (*OperationContext) Cost ¶
func (oc *OperationContext) Cost() int
Cost returns the Shopify-style query cost. Without WithQueryCost it uses a default list size of 1 so callers can still observe a number.
func (*OperationContext) Depth ¶
func (oc *OperationContext) Depth() int
Depth returns the maximum selection nesting computed by operationMetrics over the document, copied onto the plan at compile time.
func (*OperationContext) Get ¶
func (oc *OperationContext) Get(key any) (any, bool)
Get retrieves a value stored with Set.
func (*OperationContext) GetOrSet ¶
func (oc *OperationContext) GetOrSet(key, value any) (actual any, loaded bool)
GetOrSet returns the existing value for key, or stores and returns value when the key is absent; loaded reports which happened. It is the atomic form of Get followed by Set.
Request-scoped extensions must use it rather than Get-then-Set: concurrent resolvers reach their first Load at the same time, and a check-then-act pair lets every one of them install its own state, so only the last write survives while the others keep using orphaned copies.
func (*OperationContext) Set ¶
func (oc *OperationContext) Set(key, value any)
Set stores an arbitrary value scoped to the operation, for use by interceptors and extensions.
Use OperationContext.GetOrSet to install request-scoped state. Get followed by Set is a check-then-act pair, and sibling resolvers running concurrently reach their first call together: each one then sees no value, installs its own, and state meant to be shared is silently duplicated. For a DataLoader that means the pending queue and the cache split and batching degrades to N+1, with no error and no race report -- the repository ships an analyzer (lint/cmd/gqlvet) for that pair because -race does not find it. Set is for a value the caller knows it is the only writer of.
func (*OperationContext) SetExtension ¶
func (oc *OperationContext) SetExtension(key string, value any)
SetExtension records a response-level extension (Netflix / Apollo tracing, GitHub rate-limit metadata). Values are copied onto the Response after the operation completes.
func (*OperationContext) Waves ¶
func (oc *OperationContext) Waves() *WaveCoordinator
Waves returns the coordinator for this operation. It is nil when the operation is not running under an Executor, in which case extensions fall back to their own scheduling.
type OperationHandler ¶
type OperationHandler func(ctx context.Context, oc *OperationContext) *Response
OperationHandler executes a compiled operation.
type OperationInterceptor ¶
type OperationInterceptor interface {
InterceptOperation(ctx context.Context, oc *OperationContext, next OperationHandler) *Response
}
OperationInterceptor wraps execution of a parsed and planned operation. Use it for complexity limits, tracing spans and metrics.
func ChainOperationInterceptors ¶
func ChainOperationInterceptors(is ...OperationInterceptor) OperationInterceptor
ChainOperationInterceptors composes interceptors so that the first is outermost.
type OperationInterceptorFunc ¶
type OperationInterceptorFunc func(ctx context.Context, oc *OperationContext, next OperationHandler) *Response
OperationInterceptorFunc adapts a function to OperationInterceptor.
func (OperationInterceptorFunc) InterceptOperation ¶
func (f OperationInterceptorFunc) InterceptOperation(ctx context.Context, oc *OperationContext, next OperationHandler) *Response
InterceptOperation implements OperationInterceptor.
type OperationStats ¶
type OperationStats struct {
Start time.Time
CacheHit bool
// PlanUncacheable reports that this document references more Boolean
// @skip/@include variables than the variant cache holds, so its plan is
// compiled on every request and CacheHit can never become true. It is
// reported separately because a false CacheHit alone is indistinguishable
// from the first request for a perfectly cacheable document.
PlanUncacheable bool
}
OperationStats records timing and cache information for observability.
type Outcome ¶
type Outcome struct {
// contains filtered or unexported fields
}
Outcome is what the executor does with a site. The zero Outcome allows, so a Decision an Authorizer leaves untouched changes nothing.
func Deny ¶
Deny refuses the field. The message follows AIP-211: it reveals neither the value nor whether the resource exists, because choosing between PERMISSION_DENIED and NOT_FOUND is itself an existence oracle.
That holds for the wording, not for the error being there. For a row looked up by id, a Deny is an error a missing id does not get, and that difference is the oracle; where existence must not show, an ObjectAuthorizer answers Null at a nullable position (AuthSite.NonNull says which) and Drop in a list.
func Drop ¶
func Drop() Outcome
Drop omits the value from its enclosing list. It is valid only for an instance site at a list element position; dropping renumbers the indices that follow, so a later denial is reported at the index the client actually sees.
func RedactRow ¶
RedactRow is Redact decided per row: fn receives the field's context, the object the field belongs to (as its Object binding holds it, a *T) and the resolved value, and returns what is written. It is what "a customer sees their own email, support sees everyone's masked" needs, which a Decision cannot say by itself because it is made before any row exists.
It is valid wherever Redact is -- a leaf field -- and runs once per row the field resolves on, so it must be cheap and must not fail open: return the masked value when the row cannot be judged.
type Path ¶
type Path []PathElem
Path locates a value in the response data, as defined by the GraphQL specification's error format.
func (Path) MarshalJSON ¶
MarshalJSON renders the path as a JSON array of strings and integers.
type QueryCost ¶
type QueryCost struct {
// Max is the maximum allowed cost. Zero means unlimited.
Max int
// DefaultListSize is used when a list field has no first/last.
// Zero is treated as 10.
DefaultListSize int
// ListArguments names the arguments that bound a connection or list.
// Empty means first, last.
ListArguments []string
// FieldWeight overrides the default weight of 1 for a schema
// coordinate such as "Query.search".
FieldWeight map[string]int
// Report writes extensions.cost on every response, including operations
// rejected by Max. Operations refused by WithMaxDepth/WithMaxComplexity
// are rejected before a plan exists and so carry no cost.
Report bool
// Connections prices a Relay connection by the page size it asked for.
// first/last sit on the connection field, one level above the edges list
// they bound, so without this the arguments are invisible to the cost and
// every connection is charged DefaultListSize whether it asked for one
// element or a thousand.
//
// It is off by default because turning it on changes the number an
// existing deployment has set its Max against.
Connections bool
// Actual also reports actualQueryCost, summed from the fields that were
// really resolved rather than from assumed list sizes. Requested cost has
// to guess how long a list will be; actual cost counts it, so the two
// together say whether DefaultListSize is set anywhere near reality.
//
// It costs one integer add per resolved field on the write path, which is
// why it is separate from Report rather than implied by it.
Actual bool
}
QueryCost is a Shopify / GitHub-style cost model computed from the compiled plan and variable values, before resolvers run.
A field costs FieldWeight[Type.field] (default 1) plus the cost of its sub-selection multiplied by the list size. List size is the largest positive ListArguments value sent (default "first", "last"), or DefaultListSize when the field returns a list and neither argument is present. __typename is free, and so are __schema and __type with all they select: see WithMaxDepth.
type Request ¶
type Request struct {
Query string `json:"query"`
OperationName string `json:"operationName,omitempty"`
Variables json.RawMessage `json:"variables,omitempty"`
Extensions map[string]any `json:"extensions,omitempty"`
}
Request is a GraphQL request as delivered by a transport.
type RequestHandler ¶
RequestHandler executes a request; interceptors call it to continue.
type RequestInterceptor ¶
type RequestInterceptor interface {
InterceptRequest(ctx context.Context, req *Request, next RequestHandler) *Response
}
RequestInterceptor wraps the whole request, before parsing. Use it for authentication, logging, persisted query lookups and response extensions. The shape follows gRPC's UnaryServerInterceptor: observe, call next, observe.
func ChainRequestInterceptors ¶
func ChainRequestInterceptors(is ...RequestInterceptor) RequestInterceptor
ChainRequestInterceptors composes interceptors so that the first is outermost. A nil or empty list is a no-op interceptor.
type RequestInterceptorFunc ¶
type RequestInterceptorFunc func(ctx context.Context, req *Request, next RequestHandler) *Response
RequestInterceptorFunc adapts a function to RequestInterceptor.
func (RequestInterceptorFunc) InterceptRequest ¶
func (f RequestInterceptorFunc) InterceptRequest(ctx context.Context, req *Request, next RequestHandler) *Response
InterceptRequest implements RequestInterceptor.
type Requirement ¶
type Requirement struct {
// contains filtered or unexported fields
}
Requirement is an OR of ANDs: satisfied when every scope in any one group is held. The zero Requirement is satisfied by everyone, so a field that declares nothing is not accidentally locked.
The shape mirrors Apollo's @requiresScopes(scopes: [[String!]!]!), which is the vocabulary most tooling already reads.
func NewRequirement ¶
func NewRequirement(anyOf ...[]string) Requirement
NewRequirement builds a Requirement from its groups. Each group is cloned to prevent external mutations from changing the requirement after construction, which is important for Requirement's use on the concurrent request path and its immutability contract everywhere else.
func (Requirement) And ¶
func (r Requirement) And(o Requirement) Requirement
And returns the requirement satisfied only when both r and o are. It is the cross product of their groups, each group sorted and deduplicated. The zero Requirement admits everyone, so it is the identity.
func (Requirement) IsZero ¶
func (r Requirement) IsZero() bool
IsZero reports whether the requirement admits everyone.
func (Requirement) Satisfied ¶
func (r Requirement) Satisfied(held map[string]bool) bool
Satisfied reports whether held covers any one of the groups.
func (Requirement) Scopes ¶
func (r Requirement) Scopes() []string
Scopes returns every scope the requirement names, sorted and deduplicated, so a caller can load them from a policy source in one round trip.
type Response ¶
type Response struct {
Data []byte
Errors []*Error
Extensions map[string]any
// contains filtered or unexported fields
}
Response is the result of executing a Request.
Data holds the already-encoded "data" member, or nil when the request failed before execution began. Call Release once the response has been written to return its buffer to the pool.
func (*Response) HasRequestErrors ¶
HasRequestErrors reports whether the request failed before execution, in which case the response carries errors and no data.
func (*Response) MarshalJSON ¶
MarshalJSON renders the response envelope as a single byte slice.
func (*Response) Release ¶
func (r *Response) Release()
Release returns the response buffer to the pool. Data must not be used afterwards. Calling Release more than once is a no-op.
func (*Response) WriteTo ¶
WriteTo writes the JSON response envelope to w with errors, data and extensions in that order, omitting members that are empty. Data is written straight from the execution buffer without an intermediate copy.
It returns an error, and writes nothing, when an extension value on the response or on any error does not marshal. Nothing is written rather than a truncated envelope, so a caller that checks the error can still send something of its own -- but the HTTP transports discover it after the status header is on the wire and can only log, which leaves the client an empty body. See Error.WithExtension.
type Root ¶
type Root struct{}
Root is the parent value passed to fields of the Query, Mutation and Subscription root types.
type Schema ¶
type Schema struct {
// contains filtered or unexported fields
}
Schema is a validated GraphQL schema with all bindings resolved. It is immutable and safe for concurrent use.
func NewSchema ¶
func NewSchema(src Source, opts ...SchemaOption) (*Schema, error)
NewSchema parses the SDL, applies every binding option and validates that the bindings cover the schema with compatible Go shapes. All problems are reported together in the returned error.
func (*Schema) IntrospectionEnabled ¶
IntrospectionEnabled reports whether __schema and __type may be queried.
type SchemaOption ¶
type SchemaOption interface {
// contains filtered or unexported methods
}
SchemaOption configures a Schema. All binding constructors return one.
func Args ¶
func Args[A any](fields ...InputFieldOption) SchemaOption
Args declares how a field's arguments are decoded into the Go struct A. The same A may be shared by several fields with identical arguments.
func Directive ¶
func Directive(name string, fn func(next FieldFunc) FieldFunc) SchemaOption
Directive binds a schema directive that has no arguments. For every field (or every field of an object) carrying @name, fn wraps the field executor. Directives with arguments use DirectiveArgs.
func DirectiveArgs ¶
func DirectiveArgs[A any](name string, fn func(next FieldFunc, args A) FieldFunc) SchemaOption
DirectiveArgs binds a schema directive whose arguments decode into A. Register Args[A] when A is not struct{}. Locations FIELD_DEFINITION and OBJECT are applied; an OBJECT directive wraps every field of that type. An interface's field has no executor to wrap, so a bound directive written there must also be written on each implementing object's field, and the build fails when it is not.
func DisableIntrospection ¶
func DisableIntrospection() SchemaOption
DisableIntrospection rejects operations that select __schema or __type. __typename remains available.
func Enum ¶
func Enum[T comparable](name string, values map[T]string) SchemaOption
Enum binds the GraphQL enum name to the Go type T using an explicit mapping from Go values to SDL enum value names. Every SDL value must be mapped exactly once.
func EnumMarshaler ¶
func EnumMarshaler[T any, PT Marshaler[T]](name string) SchemaOption
EnumMarshaler binds the GraphQL enum name to a Go type that encodes itself.
It is for enum types Enum cannot take: an ORM's order field is a struct holding a cursor function, not comparable, whose values are unexported and constructed only by UnmarshalGQL. Output is checked against the enum's declared values, so a MarshalGQL naming an undeclared one is a field error, as it is for Enum.
func Input ¶
func Input[T any](name string, fields ...InputFieldOption) SchemaOption
Input binds the GraphQL input object type name to the Go struct T.
func Interface ¶
func Interface[T any](name string, opts ...AbstractOption) SchemaOption
Interface binds the GraphQL interface name to the Go type T, typically a Go interface. Binding is optional: unbound abstract types resolve their concrete type from the dynamic Go type of the value.
func IntrospectDeprecatedInputValues ¶
func IntrospectDeprecatedInputValues() SchemaOption
IntrospectDeprecatedInputValues makes introspection list deprecated input fields and deprecated arguments when the client does not say whether it wants them.
The specification hides them unless the query passes includeDeprecated: true, and graphql-js's getIntrospectionQuery passes nothing for inputFields and args. gqlgen returned them whatever was asked, so a client generated against it (a codegen that introspects the live server) lists every deprecated input field it still sends; against a server that follows the specification those fields disappear from the generated types without an error. This option changes the default the introspection schema declares, not the rule: a client that passes includeDeprecated: false still gets them hidden.
func MarkerDirective ¶
func MarkerDirective(name, scope string) SchemaOption
MarkerDirective declares a no-argument SDL directive whose presence alone is a requirement for scope, which is the shape of Apollo's @authenticated: the field is guarded, and what the guard means is whatever the Authorizer takes that scope to be.
It composes like any other spelling. A coordinate carrying both a marker and a scope directive must satisfy both, which is how @authenticated and @requiresScopes sit together.
func Mutation ¶
func Mutation(fields ...FieldOption) SchemaOption
Mutation binds fields of the schema's mutation root.
func Object ¶
func Object[T any](name string, fields ...FieldOption) SchemaOption
Object binds the GraphQL object type name to the Go type T. Field functions receive *T (or T, which costs a copy). T must not itself be a pointer type. Several Object calls for the same name are merged.
func Options ¶
func Options(opts ...SchemaOption) SchemaOption
Options combines schema options into one. Generated Bindings use it so a group can return a single SchemaOption.
func Query ¶
func Query(fields ...FieldOption) SchemaOption
Query binds fields of the schema's query root, which is named Query unless the schema declaration says otherwise.
func RequireAuthCoverage ¶
func RequireAuthCoverage() SchemaOption
RequireAuthCoverage fails NewSchema for any field that declares neither an authorization requirement nor @public. It is opt-in because it breaks every schema that has not adopted it.
The failure it prevents is not a field someone forgot to guard but a type nobody noticed arriving: a generated aggregate surface keyed by a resource no role grants and no role restricts is unguarded in both directions.
func RequirementDirective ¶
func RequirementDirective(name, arg string, shape ScopeShape) SchemaOption
RequirementDirective declares an additional SDL directive read into a Requirement, so a schema that spells its requirements @auth(requires:) is enforced without renaming every site. @requiresScopes stays active, so one schema can carry a legacy spelling for existing coordinates and the Apollo one for new work; a coordinate carrying both must satisfy both.
func Scalar ¶
func Scalar[T any](name string, marshal func(*Writer, T) error, unmarshal func(any) (T, error)) SchemaOption
Scalar binds the GraphQL scalar name to the Go type T. marshal writes the output representation; unmarshal coerces an input value, which is one of string, bool, json.Number, int64, float64, []any or map[string]any. Registering a Go type for a built-in scalar (for example ID as uuid.UUID) adds to the built-in mappings rather than replacing them.
unmarshal receives the same representation whether the value arrived as a literal in the query or as a variable -- a number is a json.Number carrying its original text either way, so a decimal or an id keeps the digits the client sent.
**Write it pure and cheap.** It runs twice for a value supplied as a variable: once from coerceVariables, which decodes to find out whether the value is acceptable and discards the result so a variable error is reported before execution starts, and once when the argument is decoded for real. A literal runs it once.
func ScalarMarshaler ¶
func ScalarMarshaler[T any, PT Marshaler[T]](name string) SchemaOption
ScalarMarshaler binds the custom scalar name to a Go type that encodes itself -- a Relay cursor, a decimal, anything a generator emitted with MarshalGQL and UnmarshalGQL. It is Scalar with the two functions taken from the type.
func Subscription ¶
func Subscription(fields ...FieldOption) SchemaOption
Subscription binds fields of the schema's subscription root.
func SubscriptionRootDirective ¶
func SubscriptionRootDirective(names ...string) SchemaOption
SubscriptionRootDirective declares that the named directives, bound with Directive or DirectiveArgs for ordinary fields, are enforced on subscription root fields by a SubscriptionInterceptor the application installs.
A bound directive cannot wrap a subscription root field (the field is served by its stream, not its resolver), so a schema that carries one there is refused rather than built with a check that never runs. An application that enforces the directive itself when a subscription opens says so here, and the refusal is lifted for exactly those names. Naming a directive that is not bound is an error: a typo must not switch the refusal off for nothing.
func Time ¶
func Time(name string) SchemaOption
Time binds the scalar name to time.Time as an RFC 3339 string, the wire format of gqlgen's built-in Time, so a service moving over answers with the same bytes: the value's own offset is kept, and the fraction is written only as far as it is non-zero (time.RFC3339Nano). *time.Time binds with it, and nil is null.
Two of gqlgen's choices are not taken. The zero time is written as "0001-01-01T00:00:00Z", not null, because null at a Time! position is an error the value did not cause; a column that can be empty is a *time.Time. And input must be RFC 3339: gqlgen also reads "" as the zero time, which hides a missing value, and "2006-01-02 15:04:05" as UTC, which is a wall-clock time each client means in its own zone.
func Union ¶
func Union[T any](name string, opts ...AbstractOption) SchemaOption
Union binds the GraphQL union name to the Go type T.
type ScopeShape ¶
type ScopeShape uint8
ScopeShape says how a requirement directive's argument composes into a Requirement. There is no valid zero value: reading a flat [String!] as AND when the author meant OR silently widens access, and the SDL cannot tell the two apart -- @auth(requires: ["a","b"]) is the same text either way.
const ( // ScopesNested reads [[String!]!] as an OR of ANDs, which is Apollo's // @requiresScopes. ScopesNested ScopeShape = iota + 1 // ScopesAllOf reads [String!] as one AND group: every scope is required. ScopesAllOf // ScopesAnyOf reads [String!] as one group per scope: any one suffices. ScopesAnyOf )
type SelectedField ¶
type SelectedField struct {
Name string
Alias string
// Args holds the decoded argument struct pointer when the arguments are
// literal, or nil when they depend on variables or the field has none.
// ArgumentMap answers both cases.
Args any
// contains filtered or unexported fields
}
SelectedField describes one requested field.
func (SelectedField) ArgumentMap ¶
ArgumentMap returns the field's argument values as the request gave them, before binding: variables substituted from vars, defaults applied, and an absent argument without a default left out. A number is a json.Number whether it was written in the query or sent as a variable, because the executor decodes variables with precision kept. It is nil for a field with no arguments.
It is for code that cannot know the field's argument struct, such as an ORM planning which rows the selection will read. vars is the operation's variables, OperationFrom(ctx).Variables; the plan is shared by every request with the same query text, so it holds none.
func (SelectedField) Selection ¶
func (f SelectedField) Selection() Selection
Selection returns the sub-selection requested beneath the field, empty for a leaf. Two aliases of one field are two SelectedFields, each with its own sub-selection, where Sub returns only the first.
func (SelectedField) Withheld ¶
func (f SelectedField) Withheld(ctx context.Context) bool
Withheld reports whether authorization withholds the field in this request: the Authorizer's Outcome for it is Deny, Null or Zero, so it is never resolved and nothing beneath it is read. Code that loads data for a selection ahead of the resolvers -- an ORM eager-loading edges -- skips such a field, so a subtree the caller may not see costs no query and never enters memory. ctx is the executing resolver's or interceptor's.
A Redact outcome resolves the field and is not withheld. Instance-level decisions (ObjectAuthorizer) are made per object as values arrive, after loading is planned, and are not reflected here.
type Selection ¶
type Selection struct {
// contains filtered or unexported fields
}
Selection is a read-only view of the fields requested beneath a field. It lets resolvers fetch only what the client asked for.
func SelectionFrom ¶
SelectionFrom returns the sub-selection of the executing resolver. Outside a resolver it returns an empty Selection.
func (Selection) Collect ¶
func (s Selection) Collect[T any](fn func(SelectedField) T) []T
Collect builds a slice from the selected fields. It is a generic method (Go 1.27) so callers write sel.Collect(func(f SelectedField) string { ... }).
func (Selection) Fields ¶
func (s Selection) Fields() iter.Seq[SelectedField]
Fields iterates over the selected fields. For abstract parents, fields from all possible types are visited once per response key.
func (Selection) ForType ¶
ForType returns what is selected when the value is of the named object type: for an abstract parent, the fields that apply to that type, fragments on it and on the interfaces it implements included; empty when typeName is not one of the parent's possible types. A concrete parent's selection already applies to its one type and is returned as it is.
func (Selection) Has ¶
Has reports whether a field with the given name is selected, on any possible type when the parent is abstract.
type SiteKind ¶
type SiteKind uint8
SiteKind says what kind of position needs an authorization decision.
const ( // SiteOutput is a selected output field. A SiteOutput site can carry a // zero Requires: buildAuthShape adds one for a field that has argument // sites but no requirement of its own, purely to route the field through // enforceAuth, so an Authorizer iterating output sites will see it. SiteOutput SiteKind = iota // SiteObject is a whole object type, checked once per instance rather // than once per field of it. SiteObject // SiteFilterArg is an argument marked @authorizeInput(kind: FILTER) on // an object field. A restricted field must not be filterable either, or // its value leaks by bisection without ever being selected. Only Allow // and Deny are valid outcomes for it. SiteFilterArg // SiteInputWrite is an argument marked @authorizeInput(kind: WRITE) on // an object field. Only Allow and Deny are valid outcomes for it. SiteInputWrite // SiteInstance is one resolved object of an @authorizeObject type, about // to be written at this field position. Unlike every other site it is // decided during execution by an ObjectAuthorizer, because the values do // not exist when a Decision is built. SiteInstance )
type Source ¶
type Source struct {
// contains filtered or unexported fields
}
Source supplies SDL to NewSchema.
type SubscribeError ¶
type SubscribeError struct {
Response *Response
}
SubscribeError reports that a subscription could not be started: the document failed to parse or validate, variables were invalid, or the binding's stream opener returned an error. Response holds the errors in the form a transport sends them, which for the streaming protocols is a protocol-level error rather than a payload.
func (*SubscribeError) Error ¶
func (e *SubscribeError) Error() string
func (*SubscribeError) Unwrap ¶
func (e *SubscribeError) Unwrap() []error
Unwrap exposes the response's errors to errors.Is and errors.As, so a caller reaches a *Error or a sentinel the ordinary way instead of having to know to walk Response.Errors by hand. It returns several because a subscription can be refused for more than one reason at once.
type SubscriptionHandler ¶
type SubscriptionHandler func(ctx context.Context, oc *OperationContext) (<-chan *Response, error)
SubscriptionHandler opens a subscription's stream of responses.
type SubscriptionInterceptor ¶
type SubscriptionInterceptor interface {
InterceptSubscription(ctx context.Context, oc *OperationContext, next SubscriptionHandler) (<-chan *Response, error)
}
SubscriptionInterceptor wraps the opening of a subscription stream. It is the stream-shaped counterpart of OperationInterceptor, following gRPC's split between UnaryInterceptor and StreamInterceptor.
Operation interceptors see each event of a live subscription, which is too late to refuse one: by then the source is open. Refusing a subscription is this interceptor's job.
func ChainSubscriptionInterceptors ¶
func ChainSubscriptionInterceptors(is ...SubscriptionInterceptor) SubscriptionInterceptor
ChainSubscriptionInterceptors composes interceptors so that the first is outermost. A nil or empty list is a no-op interceptor.
type SubscriptionInterceptorFunc ¶
type SubscriptionInterceptorFunc func(ctx context.Context, oc *OperationContext, next SubscriptionHandler) (<-chan *Response, error)
SubscriptionInterceptorFunc adapts a function to SubscriptionInterceptor.
func (SubscriptionInterceptorFunc) InterceptSubscription ¶
func (f SubscriptionInterceptorFunc) InterceptSubscription(ctx context.Context, oc *OperationContext, next SubscriptionHandler) (<-chan *Response, error)
InterceptSubscription implements SubscriptionInterceptor.
type WaveCoordinator ¶
type WaveCoordinator struct {
// contains filtered or unexported fields
}
WaveCoordinator lets request-scoped batching extensions take part in the executor's dispatch of concurrent waves. A wave is announced before sibling tasks are launched, so a resolver that blocks on batched work cannot dispatch before its siblings have queued theirs — the GraphQL equivalent of DataLoader.dispatch at the end of a tick.
The protocol for an extension is:
w := graphql.OperationFrom(ctx).Waves() w.OnReady(flush) // once per operation w.Park() // before blocking on batched work defer w.Unpark()
When nothing in the operation is left running -- every task begun, and each either parked or waiting for tasks of its own -- the registered callbacks run so the keys queued by all of them dispatch together. The loader package is built on this; most applications use that instead.
A nil *WaveCoordinator is valid and does nothing, which is what Waves returns outside an Execute call.
func (*WaveCoordinator) OnReady ¶
func (w *WaveCoordinator) OnReady(fn func())
OnReady registers a callback to run when a wave is ready to dispatch. Callbacks are kept for the life of the operation and must be safe to call when there is nothing queued.
func (*WaveCoordinator) Park ¶
func (w *WaveCoordinator) Park()
Park reports that the caller is about to block on batched work. When that leaves nothing running and no announced task still to begin, every key the operation can queue before it blocks has been queued, so the ready callbacks run.
func (*WaveCoordinator) Unpark ¶
func (w *WaveCoordinator) Unpark()
Unpark reports that the caller is no longer blocked.
type Writer ¶
Writer is handed to scalar marshalers to emit exactly one JSON value. Conversions to and from the internal writer are free, so custom scalars add no allocation on the hot path.
Source Files
¶
- abstract.go
- authz.go
- authz_exec.go
- authz_input.go
- authz_instance.go
- authz_shape.go
- builderror.go
- coerce.go
- context.go
- directive.go
- doc.go
- document_limits.go
- enum.go
- errors.go
- exec.go
- exec_object.go
- graphql.go
- input.go
- interceptor.go
- introspection.go
- limits.go
- marshaler.go
- object.go
- observer.go
- overlap.go
- plan.go
- plan_metrics.go
- registry.go
- request.go
- scalar.go
- schema.go
- source.go
- stats.go
- subscription.go
- validate.go
- wave.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
gqlc
command
Command gqlc generates GraphQL bindings from SDL.
|
Command gqlc generates GraphQL bindings from SDL. |
|
Package codegen generates models, argument structs, a Resolver interface and bindings from SDL.
|
Package codegen generates models, argument structs, a Resolver interface and bindings from SDL. |
|
examples
|
|
|
blog
Package blog is the composition root for the layered example.
|
Package blog is the composition root for the layered example. |
|
blog/cmd/server
command
Command server serves the example blog schema on every transport:
|
Command server serves the example blog schema on every transport: |
|
blog/graph
Package graph wires the generated GraphQL schema and its bindings.
|
Package graph wires the generated GraphQL schema and its bindings. |
|
blog/graph/model
Package model holds the types generated from the GraphQL schema.
|
Package model holds the types generated from the GraphQL schema. |
|
blog/internal/app
Package app holds the use cases: the work that is neither storage nor protocol.
|
Package app holds the use cases: the work that is neither storage nor protocol. |
|
blog/internal/domain
Package domain holds the blog's business entities.
|
Package domain holds the blog's business entities. |
|
blog/internal/repository
Package repository is the in-memory store standing in for a database.
|
Package repository is the in-memory store standing in for a database. |
|
blog/internal/transport/graphql
Package graphql adapts the domain to the generated GraphQL bindings.
|
Package graphql adapts the domain to the generated GraphQL bindings. |
|
echo
command
Command echo serves the examples/blog schema through Echo v5, on every transport:
|
Command echo serves the examples/blog schema through Echo v5, on every transport: |
|
federation
Package federation is two Apollo Federation subgraphs and the fetch a router performs across them.
|
Package federation is two Apollo Federation subgraphs and the fetch a router performs across them. |
|
federation/cmd/subgraphs
command
Command subgraphs serves both federation subgraphs so a real router can be pointed at them:
|
Command subgraphs serves both federation subgraphs so a real router can be pointed at them: |
|
fiber
command
Command fiber serves the examples/blog schema through Fiber v3, on every transport:
|
Command fiber serves the examples/blog schema through Fiber v3, on every transport: |
|
quickstart
command
Command quickstart is the smallest thing in this repository that runs: one SDL file, hand-written bindings, no code generation and no layers.
|
Command quickstart is the smallest thing in this repository that runs: one SDL file, hand-written bindings, no code generation and no layers. |
|
relaynode
Package relaynode is the Relay server contract end to end: global object identification and cursor connections, with nothing hand-rolled.
|
Package relaynode is the Relay server contract end to end: global object identification and cursor connections, with nothing hand-rolled. |
|
relaynode/cmd/server
command
Command server serves the Relay example on /graphql, so the global-id round trip can be run against a real endpoint:
|
Command server serves the Relay example on /graphql, so the global-id round trip can be run against a real endpoint: |
|
storefront
Package storefront is a back-office API written the way a deployment has to write one: every position says what it requires, and nothing is visible because nobody thought to ask for it.
|
Package storefront is a back-office API written the way a deployment has to write one: every position says what it requires, and nothing is visible because nobody thought to ask for it. |
|
storefront/cmd/server
command
Command server serves the storefront schema with the wiring a deployment needs and the other examples leave out: request limits, a cost budget per caller, persisted queries, OpenTelemetry, and a shutdown that winds down streams the HTTP server cannot.
|
Command server serves the storefront schema with the wiring a deployment needs and the other examples leave out: request limits, a cost budget per caller, persisted queries, OpenTelemetry, and a shutdown that winds down streams the HTTP server cannot. |
|
ext
|
|
|
apq
Package apq implements automatic persisted queries.
|
Package apq implements automatic persisted queries. |
|
otel
Package otel instruments an Executor with OpenTelemetry traces and metrics.
|
Package otel instruments an Executor with OpenTelemetry traces and metrics. |
|
throttle
Package throttle rate limits by query cost, the way Shopify's Admin API does: every caller holds a bucket of points that refills at a fixed rate, a query is quoted before it runs, and what it really cost is charged afterwards.
|
Package throttle rate limits by query cost, the way Shopify's Admin API does: every caller holds a bucket of points that refills at a fixed rate, a query is quoted before it runs, and what it really cost is charged afterwards. |
|
trusted
Package trusted turns persisted queries into a safelist: the server executes only documents a build step registered, and refuses query text whatever hash accompanies it.
|
Package trusted turns persisted queries into a safelist: the server executes only documents a build step registered, and refuses query text whatever hash accompanies it. |
|
Package fed makes a schema an Apollo Federation subgraph: the _service and _entities root fields, the federation directives, and the _Entity union.
|
Package fed makes a schema an Apollo Federation subgraph: the _service and _entities root fields, the federation directives, and the _Entity union. |
|
internal
|
|
|
bindhook
Package bindhook lets this module's own packages ask the schema builder a question the public API has no word for.
|
Package bindhook lets this module's own packages ask the schema builder a question the public API has no word for. |
|
gqlwsproto
Package gqlwsproto implements the graphql-transport-ws protocol — connection_init/connection_ack, ping/pong, and subscribe/next/error/complete per operation — independently of any WebSocket library.
|
Package gqlwsproto implements the graphql-transport-ws protocol — connection_init/connection_ack, ping/pong, and subscribe/next/error/complete per operation — independently of any WebSocket library. |
|
httpreq
Package httpreq holds the request handling the HTTP transports share: query-parameter and JSON-body decoding, the body limit and the CSRF check.
|
Package httpreq holds the request handling the HTTP transports share: query-parameter and JSON-body decoding, the body limit and the CSRF check. |
|
jitter
Package jitter spreads durations so that connections opened together do not all expire together and reconnect in one storm.
|
Package jitter spreads durations so that connections opened together do not all expire together and reconnect in one storm. |
|
jsonw
Package jsonw implements a streaming JSON writer that appends directly to a byte buffer, tracks value separators per nesting level, and supports rewinding to an earlier mark so that partially written values can be replaced with null without re-serialization.
|
Package jsonw implements a streaming JSON writer that appends directly to a byte buffer, tracks value separators per nesting level, and supports rewinding to an earlier mark so that partially written values can be replaced with null without re-serialization. |
|
sdlprint
Package sdlprint renders a schema as SDL.
|
Package sdlprint renders a schema as SDL. |
|
Package loader provides a Facebook-style DataLoader for graphql-go: a per-request cache plus a batch function that coalesces the keys requested by concurrent resolvers into one call.
|
Package loader provides a Facebook-style DataLoader for graphql-go: a per-request cache plus a batch function that coalesces the keys requested by concurrent resolvers into one call. |
|
Package relay implements the Relay server contract for graphql-go: global object identification and cursor connections.
|
Package relay implements the Relay server contract for graphql-go: global object identification and cursor connections. |
|
transport
|
|
|
drain
Package drain winds down long-lived connections when a server shuts down.
|
Package drain winds down long-lived connections when a server shuts down. |
|
gqlecho
Package gqlecho serves GraphQL through Echo v5.
|
Package gqlecho serves GraphQL through Echo v5. |
|
gqlfiber
Package gqlfiber serves GraphQL through Fiber v3.
|
Package gqlfiber serves GraphQL through Fiber v3. |
|
gqlhttp
Package gqlhttp serves GraphQL over HTTP following the GraphQL over HTTP specification: GET and POST requests, content negotiation between application/graphql-response+json and application/json, and CSRF prevention for requests browsers can send without a preflight.
|
Package gqlhttp serves GraphQL over HTTP following the GraphQL over HTTP specification: GET and POST requests, content negotiation between application/graphql-response+json and application/json, and CSRF prevention for requests browsers can send without a preflight. |
|
gqlsse
Package gqlsse serves GraphQL over Server-Sent Events in the distinct connections mode of the graphql-sse protocol: one HTTP request per operation, results streamed back as "next" events and terminated by a "complete" event.
|
Package gqlsse serves GraphQL over Server-Sent Events in the distinct connections mode of the graphql-sse protocol: one HTTP request per operation, results streamed back as "next" events and terminated by a "complete" event. |
|
gqlws
Package gqlws serves GraphQL over WebSocket using the graphql-transport-ws protocol: connection_init/connection_ack, ping/pong, and subscribe/next/ error/complete per operation.
|
Package gqlws serves GraphQL over WebSocket using the graphql-transport-ws protocol: connection_init/connection_ack, ping/pong, and subscribe/next/ error/complete per operation. |