MCPcopy Index your code
hub / github.com/a-type/graphql-arangodb

github.com/a-type/graphql-arangodb @v0.1.24

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.1.24 ↗ · + Follow
52 symbols 171 edges 40 files 0 documented · 0%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

graphql-arangodb

An experimental library for 'translating' GraphQL operations into ArangoDB AQL queries which are designed to fetch all requested data in as few queries as possible. Flexibility is another objective; I want to empower the developer to define exactly how they want their GraphQL schema without being forced into a particular schema shape due to their database structure.

Maintainers Welcome

I've moved on from the side project which drove me to create this library and I don't actively use it anymore. If there's a feature or bugfix you need, I encourage you to open a PR. If you'd like to take this project in a new direction, you're welcome to fork it!

Example

type Query {
  user(id: ID!): User @aqlDocument(collection: "users", key: "$args.id")
}

type User {
  friends: [FriendOfEdge!]!
    @aqlEdge(
      collection: "friendOf"
      direction: ANY
      sort: { property: "name", sortOn: "$field_node" }
    )
}

type FriendOfEdge {
  strength: Int
  user: User! @aqlEdgeNode
}

type Mutation {
  createPost(input: PostCreateInput!): Post!
    @aqlSubquery(
      query: """
      INSERT { title: $args.input.title, body: $args.input.body } INTO posts
      """
      return: "NEW"
    )
}

Code Examples

For a simple and small example, see the example directory.

For a larger scale app that uses this library, check out my now-defunct startup idea Toast. The toast-core microservice (linked) drove the entire development of this library and almost every feature is utilized in that codebase.

Known Limitations

  • This library is not designed to run on the Foxx framework. Because Foxx code runs with direct database memory access, the concern of batching up queries is probably not as important, and designing for Foxx posed too many complexities to getting the library working.
  • Experimental features, like Relay support, have known shortcomings. See the documentation for those sections.
  • Writing database queries inside a GraphQL schema can be error prone! That's just a fact of life for the way this library is implemented.

Table of Contents

Setup

Installing

Start by installing the library

npm i --save graphql-arangodb

You may also need to install peer dependencies if you don't have them:

npm i --save graphql arangojs

Directive type definitions

To use the directives in this library, you need to add type definitions for them. The library exports pre-built type definitions for all directives, you just need to include them in your type definitions.

import { directiveTypeDefs } from 'graphql-arangodb';

const typeDefs = [directiveTypeDefs, ...allYourAppsOtherTypeDefs];

makeExecutableSchema({ typeDefs });

Adding a Database instance

The easiest way to connect graphql-arangodb to your ArangoDB database is to instantiate a Database class from arangojs and assign it to the arangoDb field of your GraphQL context:

const arangoDb = new Database({
  url: 'http://localhost:8529',
});
arangoDb.useDatabase('mydb');
arangoDb.useBasicAuth('mysecretuser', 'mysecretpassword');

const context = {
  arangoDb,
};

// pass the context into your GraphQL server according to documentation of the server

Resolvers

To start resolving queries using AQL, you need to set up resolvers for fields which will be resolved using those queries. For most use cases, this means all of the top-level fields in the root query and mutation types.

For most people, adding the default aqlResolver from graphql-arangodb should be enough:

import aqlResolver from 'graphql-arangodb';

const resolvers = {
  Query: {
    user: aqlResolver,
    users: aqlResolver,
    // ...
  },
};

Customizing the resolver

However, there are some advanced scenarios where you may want to customize how the resolver works. To do this, you can import createResolver and create your own version of the default resolver. All config properties are optional.

import { createResolver, plugins as defaultPlugins } from 'graphql-arangodb';

const resolver = createResolver({
  // argument resolvers are called like regular resolvers, but they are used only by
  // graphql-arangodb to apply custom transformations to field arguments before
  // adding them to the AQL query. They are separated from normal resolvers for
  // technical reasons related to how queries are extracted and built by the library.
  // Whenver possible, prefer to put this logic inside the AQL query itself.
  argumentResolvers: {
    Query: {
      searchUsers: args => ({
        ...args,
        // apply Lucene fuzzy indicator to user's match string before passing it to AQL
        match: `${args.match}~`,
      }),
    },
  },

  // customize the key in your context which stores data which will be passed down
  // into AQL queries via the $context interpolation
  contextKey: 'arango_context',

  // customize the context property which is used to get your Database instance
  contextDbKey: 'arango_db',

  // advanced: you can reassign the names of the default directive plugins, or
  // create your own plugin here. Plugins aren't documented yet, see source.
  plugins: {
    ...defaultPlugin,
    custom: myCustomPlugin,
  },

  // you can specify a static database instance instead of passing one through context
  db: new Database(),
});

Usage

Now that the library is configured, you can start adding directives to indicate how to query for your data.

Usage of these directives is fairly similar to writing subqueries directly in AQL. The main thing to know is that you never write the RETURN statement. This library automatically constructs the correct RETURN projections based on the selected fields in the GraphQL query.

Enums

Before we begin with the directives, this library also ships some enums which will be used in directive parameters. To use an enum, just supply its literal value to the parameter (don't enclose it in " marks).

  • AqlEdgeDirection: OUTBOUND | INBOUND | ANY
  • AqlSortOrder: DESC | ASC
  • AqlRelayConnectionSource: Default | FullText

Inputs

Some directives take complex inputs:

input AqlSortInput {
  """
  The property to sort on
  """
  property: String!
  """
  The order to sort in. Defaults ASC
  """
  order: AqlSortOrder = ASC
  """
  Change the object being sorted. Defaults to $field
  """
  sortOn: String
}

input AqlLimitInput {
  """
  The upper limit of documents to return
  """
  count: String!
  """
  The number of documents to skip
  """
  skip: String
}

"""
These are the same as the OPTIONS for a regular edge traversal in AQL
"""
input AqlTraversalOptionsInput {
  bfs: Boolean
  uniqueVertices: String
  uniqueEdges: String
}

Interpolations

All directives support the following interpolations in their parameter values:

  • $parent: Reference the parent document. If there is no parent (this is a root field in the query), references the parent from GraphQL, if that exists.
  • $field: Reference the field itself. In @aql directives, you must assign something to this binding to be returned as the value of the field. For all other purposes, you can use this to reference the current value (for instance, if you want to do a filter on $field.name or some other property).
  • $args: Reference the field args of the GraphQL query. You can use nested arg values. Usages of $args get turned into bind variables when the query is executed, and all field args are passed in as values.
  • $context: Reference values from the arangoContext key in your GraphQL context. Use this for global values across all queries, like the authenticated user ID.

Directives

@aqlDocument

Selects a single or multiple documents (depending on whether the return type of the field is a list) from a specified collection. If a single document is selected, you can supply an key parameter to select it directly. This key parameter may be an argument interpolation ($args.id, etc), or a concrete value. It is passed directly into the DOCUMENT AQL function as the second parameter. If you do not specify an key parameter, the first item from the collection will be returned. To select a single item with a filter, use @aql.

Parameters

  • collection: String!: The name of the collection of documents
  • key: String: A string value or interpolation that indicates the database key of the document.
  • filter: String: Adds a filter expression. Applies to key-based single document fetching (the first document will be taken after filter is applied).
  • sort: AqlSortInput: Adds a sort expression. Applies to key-based single document fetching (the first document will be taken after sort is applied).
  • limit: AqlLimitInput: Adds a limit expression. Only works when key is not provided.

Example

type Query {
  user(id: ID!): User @aqlDocument(collection: "users", key: "$args.id")
}

@aqlNode

Traverses a relationship from the parent document to another document across an edge. @aqlNode skips over the edge and returns the related document as the field value. If you want to utilize properties from the edge, use @aqlEdge/@aqlEdgeNode instead.

Parameters

  • edgeCollection: String!: The name of the collection which the edge belongs to
  • direction: AqlEdgeDirection!: The direction to traverse. Can be ANY.
  • filter: String: Adds a filter expression.
  • sort: AqlSortInput: Adds a sort expression.
  • limit: AqlLimitInput: Adds a limit expression.
  • options: AqlTraverseOptionsInput: Modify OPTIONS parameters on the traversal.

Example

type User {
  posts: [Post!]! @aqlNode(edgeCollection: "posted", direction: OUTBOUND)
}

@aqlEdge/@aqlEdgeNode

@aqlEdge traverses an edge from the parent document, returning the edge itself as the field value. @aqlEdgeNode can be used on the type which represents the edge to reference the document at the other end of it. @aqlEdgeNode should only be used on a field within a type represented by an edge. It has no directive parameters.

Parameters

Only @aqlEdge takes parameters:

  • collection: String!: The name of the collection for the edge
  • direction: AqlEdgeDirection!: The direction to traverse. Can be ANY.
  • filter: String: Adds a filter expression. To filter on the node, you can use $field_node as an interpolation. Defaults sortOn to $field.
  • sort: AqlSortInput Adds a sort expression.
  • limit: AqlLimitInput: Adds a limit expression.
  • options: AqlTraverseOptionsInput: Modify OPTIONS parameters on the traversal.

@aqlEdgeNode has no parameters.

Example

type User {
  friends: [FriendOfEdge!]!
    @aqlEdge(
      collection: "friendOf"
      direction: ANY
      sort: { property: "name", sortOn: "$field_node" }
    )
}

type FriendOfEdge {
  strength: Int
  user: User! @aqlEdgeNode
}

@aqlSubquery

Construct a free-form subquery to resolve a field. There are important rules for your subquery:

  • Important: You must assign the value you wish to resolve to the $field binding. This can be done for a single value using LET $field = value, or for a list by ending the subquery with FOR $field IN list. See the examples.
  • Do not wrap in (). This is done by the library.
  • Do not include a RETURN statement. All RETURN projections are constructed by the library for you to match the GraphQL query.

Parameters

  • query: String!: Your subquery string, following the rules listed above.
  • return: String: An optional way to specify the name of a binding to return. By default, in a subquery, you must follow the important rule marked above and assign to $field. However, if you prefer, you may specify which variable binding you want to return within your subquery, and we will do this for you.

Examples

Resolving a single value

type Query {
  userCount: Int!
    @aqlSubquery(
      query: """
      LET $field = LENGTH(users)
      """
    )
}

Resolving multiple values

```graphql type Query { """ Merges the list of public posts with the list of posts the user has posted (even private) to create a master list of all posts accessible by the user. """ authorizedPosts: [Post!]! @aqlSubquery( query: """ LET authenticatedUser = DOCUMENT('users', $context.userId) LET allAuthorizedPosts = UNION_DISTINCT( (FOR post IN posts FILTER post.public == true RETURN post), (FOR post in OUTBOUND authenticatedUser posted RETURN post)

Core symbols most depended-on inside this repo

lines
called by 23
src/utils/strings.ts
indent
called by 17
src/utils/strings.ts
buildSubquery
called by 15
src/utils/aql.ts
children
called by 12
src/buildQuery.ts
buildQueryModifiers
called by 4
src/utils/aql.ts
log
called by 3
src/logger.ts
getFieldDirectives
called by 3
src/utils/directives.ts
replaceAll
called by 3
src/utils/plugins.ts

Shape

Function 49
Class 2
Method 1

Languages

TypeScript100%

Modules by API surface

src/utils/plugins.ts8 symbols
src/utils/graphql.ts8 symbols
src/utils/aql.ts7 symbols
src/buildQuery.ts5 symbols
src/utils/directives.ts4 symbols
src/utils/variables.ts3 symbols
src/extractQueries.ts3 symbols
src/errors.ts3 symbols
src/utils/strings.ts2 symbols
src/resolver.ts2 symbols
src/builders/aqlRelayConnection.ts2 symbols
src/runQuery.ts1 symbols

For agents

$ claude mcp add graphql-arangodb \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page