leto 0.0.1-dev.2 copy "leto: ^0.0.1-dev.2" to clipboard
leto: ^0.0.1-dev.2 copied to clipboard

Base package for implementing GraphQL servers. You might prefer `package:leto_shelf` for building GraphQL web server with leto and shelf.

Pub

Leto #

Base package for implementing GraphQL servers executors. The main entrypoint is the GraphQL.parseAndExecute method which parses a GraphQL document and executes it with the configured GraphQLSchema from package:leto_schema.

package:leto does not require any specific framework, and thus can be used in any Dart project.

Ad-hoc Usage #

The actual querying functionality is handled by the GraphQL class, which takes a schema (from package:leto_schema). In most cases, you'll want to call parseAndExecute on some string of GraphQL text. It returns a GraphQLResult with either a Map<String, dynamic> or a Stream<GraphQLResult> for subscriptions:

try {
    final GraphQLResult result = await graphQL.parseAndExecute(responseText);
    final data = result.data;
    if (data is Stream<GraphQLResult>) {
        // Handle a subscription somehow...
    } else if (data is Map<String, Object?>) {
        response.send({'data': data});
    } else {
        // Handle errors
        final bool didExecute = result.didExecute;
        final List<GraphQLError> errors = result.errors;
    }
} catch (e) {
    // Not usually necessary, only when a specify extension throws.
    response.send(e.toJson());
}

Consult the API reference for more: https://pub.dev/documentation/leto/latest/leto/GraphQL/parseAndExecute.html

If you're looking for functionality like graphQLHttp in graphql-js, that is not included in this package, because it is typically specific to the framework/platform you are using. The graphQLHttp implementation in package:leto_shelf is a good example.

Table of Contents #

GraphQL Executor #

The executor can be configured with the following parameters:

  /// Extensions implement additional functionalities to the
  /// server's parsing, validation and execution.
  /// For example, extensions for tracing [GraphQLTracingExtension],
  /// logging, error handling or caching [GraphQLPersistedQueries]
  final List<GraphQLExtension> extensions;

  /// An optional callback that can be used to resolve fields
  /// from objects that are not [Map]s, when the related field has no resolver.
  final FutureOr<Object?> Function(Object? parent, Ctx)? defaultFieldResolver;

  /// Variables passed to all executed requests
  final ScopedMap baseGlobalVariables;

  /// If validate is false, a parsed document is executed without
  /// being validated with the provided schema
  final bool validate;

  /// Whether to introspect the [GraphQLSchema]
  ///
  /// This will change the Query type of the [schema] by adding
  /// introspection fields, useful for client code generators or other
  /// tools like UI explorers.
  final bool introspect;

  /// The schema used for executing GraphQL requests
  final GraphQLSchema schema;

  /// Custom validation rules performed to a request's document
  /// before the execution phase
  final List<ValidationRule> customValidationRules;

GraphQLConfig #

You can also use the GraphQLConfig class and the GraphQL.fromConfig constructor for creating an executor from a configuration class.

GraphQL.parseAndExecute #

The main entry point. The implementation follows closely the execution section in the specification, most of the method and variable names are taken from there.

Exceptions may be thrown by extensions

GraphQL Request Arguments #

The main GraphQL request arguments are the following:

Name Type Description Example
query String The GraphQL query "mutation createUser ($name: String!) { createUser(name: $name) { name createdAt } }"
operationName String? The operation to execute within the query "createUser"
variableValues Map<String, Object?>? The variables specified in the query that should be used as parameter {"name": "Example Name"}
extensions Map<String, Object?>? The extensions passed in the GraphQL request {"persistedQuery": {"version": 1, "sha256Hash": "dpiw2ndabo389hd9bs"}}
rootValue Object? A value passed as parent to the root resolvers. If null, the global ScopedMap will be used {"arg1":"value"}

ScopedOverride List #

You can provide ScopedOverrides which will only apply to this request. It it is a Subscription, then the overrides will apply to all events in the subscription. If you want to override a ScopedRef for all requests in a executor you can pass a ScopedMap to the argument GraphQL.globalVariables in the executor's constructor.

InvalidOperationType #

The execution may throw an InvalidOperationType if the validOperationTypes argument is passed and the operation in the document is not one of the valid operations. Useful to prevent HTTP safe methods (like GET) from being used for mutations (this is already handled in package:leto_shelf).

Introspection #

Introspection of a GraphQL schema allows clients to query the schema itself, and get information about the response the server expects. The GraphQL class handles this automatically, so you don't have to write any code for it.

However, you can call the reflectSchema method to manually reflect a schema: https://pub.dartlang.org/documentation/leto/latest/introspection/reflectSchema.html

Extensions #

You can read more about extensions in the main README. The main API with all the methods that can be overridden is found in this file.

  /// The key identifying this extension, used as the key for
  /// the extensions map in GraphQLError or GraphQLResult.
  /// Should be unique.
  String get mapKey;

  /// The entry point for each request, this is the first method
  /// executed in a [GraphQLExtension] for each request
  ///
  /// Subscriptions execute this once and then execute
  /// [executeSubscriptionEvent] for every
  /// [GraphQLResult.subscriptionStream] event
  FutureOr<GraphQLResult> executeRequest(
    FutureOr<GraphQLResult> Function() next,
    RequestCtx ctx,
  ) =>
      next();

  /// Parser or retrieves the GraphQL [DocumentNode]
  /// from [query] or [extensions]
  DocumentNode getDocumentNode(
    DocumentNode Function() next,
    RequestCtx ctx,
  ) =>
      next();

  /// Executes validations given a schema,
  /// and the operation to perform
  GraphQLException? validate(
    GraphQLException? Function() next,
    RequestCtx ctx,
    DocumentNode document,
  ) =>
      next();

  /// Parses argument values and a executes a [field] in [ctx]
  FutureOr<Object?> executeField(
    FutureOr<Object?> Function() next,
    ObjectExecutionCtx ctx,
    GraphQLObjectField field,
    String fieldAlias,
  ) =>
      next();

  /// Resolves a field with [ctx]
  FutureOr<T> resolveField<T>(
    FutureOr<T> Function() next,
    Ctx ctx,
  ) =>
      next();

  /// Called for every [GraphQLResult.subscriptionStream] event
  FutureOr<GraphQLResult> executeSubscriptionEvent(
    FutureOr<GraphQLResult> Function() next,
    ExecutionCtx ctx,
    ScopedMap parentGlobals,
  ) =>
      next();

  /// Maps a resolved value into a serialized value
  FutureOr<Object?> completeValue(
    FutureOr<Object?> Function() next,
    ObjectExecutionCtx ctx,
    String fieldName,
    GraphQLType fieldType,
    Object? result,
  ) =>
      next();

  /// Executes a callback for a [ThrownError] during execution
  ///
  /// Can be used for logging or mapping a resolver exception
  /// into a user friendly error.
  GraphQLException mapException(
    GraphQLException Function() next,
    ThrownError error,
  ) =>
      next();

DataLoader #

The DataLoader implementation is based on graphql/dataloader, the API and methods are basically the same. For usage examples please review the main README.

Subscriptions and WebSockets #

GraphQL queries involving subscription operations can return a Stream. Ultimately, the transport for relaying subscription events to clients is not specified in the GraphQL spec, so it's up to you. We provide a WebSocket Server implementation.

Note that in a schema like this:

type TodoSubscription {
    onTodo: TodoAdded!
}

type TodoAdded {
    id: ID!
    text: String!
    isComplete: Bool
}

Your Dart schema's field subscribe for onTodo should return a Stream:

field(
  'onTodo',
  todoAddedType,
  subscribe: (_, Ctx ctx) {
    return someStreamOfTodos();
  },
);

WebSocket implementation #

For the purposes of reusing existing tooling (i.e. JS clients, etc.), package:leto rolls with an implementation of Apollo's graphql-ws spec. The implementation also supports the graphql-transport-ws subprotocol.

NOTE: At this point, Apollo's spec is extremely out-of-sync with the protocol their client actually expects. See the following issue to track this: https://github.com/apollographql/subscriptions-transport-ws/issues/551

The implementation is built on package:stream_channel, and therefore can be used on any two-way transport, whether it is WebSockets, TCP sockets, Isolates, or otherwise.

Users of this package are expected to extend the Server abstract class. Server will handle the transport and communication, but again, ultimately, emitting subscription events is up to your implementation.

Here's a snippet from graphQLWebSocket in package:leto_shelf. It runs within the context of one single request:

final channel = IOWebSocketChannel(socket);
final client = stw.RemoteClient(channel.cast<String>());
final server =
    GraphQLWebSocketShelfSServer(client, graphQL, req, res, keepAliveInterval);
await server.done;

See graphQLWebSocket in package:leto_shelf for a good example: https://github.com/juancastillo0/leto/tree/main/leto_shelf/lib/src/graphql_ws.dart

11
likes
140
pub points
44%
popularity

Publisher

unverified uploader

Base package for implementing GraphQL servers. You might prefer `package:leto_shelf` for building GraphQL web server with leto and shelf.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

async, clock, collection, crypto, gql, leto_schema, meta, oxidized, source_span, stream_channel

More

Packages that depend on leto