自定义逻辑

这是 GraphQL 库版本 6 的文档。对于长期支持 (LTS) 版本 5,请参阅 GraphQL 库版本 5 LTS

@cypher

@cypher 指令将 GraphQL 字段绑定到 Cypher 查询的结果。此指令可用于类型中的属性或作为顶级查询。

全局变量

全局变量可在 Cypher 语句中使用,并且可以应用于 @cypher 指令。

变量 描述 示例

this

引用当前解析的节点,可用于遍历图。

type Movie {
    title: String
    similarMovies(limit: Int = 10): [Movie]
        @cypher(
            statement: """
            MATCH (this)<-[:ACTED_IN]-(:Actor)-[:ACTED_IN]->(rec:Movie)
            WITH rec, COUNT(*) AS score ORDER BY score DESC
            RETURN rec LIMIT $limit
            """,
            columnName: "rec"
        )
}

auth

此值由以下 TypeScript 接口定义表示

interface Auth {
    isAuthenticated: boolean;
    roles?: string[];
    jwt: any;
}

您可以使用请求中的 JWT 返回当前登录用户的的值

type User @node {
    id: String
}

type Query {
    me: User @cypher(
        statement: """
        MATCH (user:User {id: $jwt.sub})
        RETURN user
        """,
        columnName: "user"
    )
}

cypherParams

用于从 GraphQL 上下文函数将值注入到 Cypher 查询中。

注入上下文

const server = new ApolloServer({
    typeDefs,
});

await startStandaloneServer(server, {
    context: async ({ req }) => ({ cypherParams: { userId: "user-id-01" } }),
});

在 Cypher 查询中使用

type Query {
    userPosts: [Post] @cypher(statement: """
        MATCH (:User {id: $userId})-[:POSTED]->(p:Post)
        RETURN p
    """, columnName: "p")
}

返回值

Cypher 语句的返回值必须始终与应用指令的类型相同。

变量还必须使用与传递给 columnName 的名称相同的名称进行别名化。这可以是节点、关系查询或 Cypher 语句 RETURN 语句中的别名。

标量值

Cypher 语句必须返回一个与应用指令的标量类型匹配的值。例如

type Query {
    randomNumber: Int @cypher(statement: "RETURN rand() as result", columnName: "result")
}

对象类型

返回对象类型时,该类型的所有字段都必须在 Cypher 返回值中可用。这可以通过从 Cypher 查询返回整个对象或返回对象类型所需的字段映射来实现。这里演示了这两种方法

type User @node {
    id
}

type Query {
    users: [User]
        @cypher(
            statement: """
            MATCH (u:User)
            RETURN u
            """,
            columnName: "u"
        )
}
type User @node {
    id
}

type Query {
    users: [User] @cypher(statement: """
        MATCH (u:User)
        RETURN {
            id: u.id
        } as result
    """, columnName: "result")
}

后一种方法的缺点是,当您更改对象类型定义时,需要调整返回值对象。

输入参数

@cypher 语句可以通过在参数名称前添加 $ 来访问查询参数。例如

type Query {
    name(value: String): String @cypher(statement: "RETURN $value AS res", columnName: "res")
}

以下 GraphQL 查询返回参数 value

query {
  name(value: "Jane Smith")
}

使用示例

@cypher 指令可以在不同的上下文中使用,例如本节中描述的上下文。

在对象类型字段上

在以下示例中,字段 similarMovies 绑定到 Movie 类型,用于查找具有演员重叠的其他电影

type Actor @node {
    actorId: ID!
    name: String
    movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
}

type Movie @node {
    movieId: ID!
    title: String
    description: String
    year: Int
    actors(limit: Int = 10): [Actor!]!
        @relationship(type: "ACTED_IN", direction: IN)
    similarMovies(limit: Int = 10): [Movie]
        @cypher(
            statement: """
            MATCH (this)<-[:ACTED_IN]-(:Actor)-[:ACTED_IN]->(rec:Movie)
            WITH rec, COUNT(*) AS score ORDER BY score DESC
            RETURN rec LIMIT $limit
            """,
            columnName: "rec"
        )
}

在查询类型字段上

以下示例演示了一个查询,用于返回数据库中的所有演员

type Actor @node {
    actorId: ID!
    name: String
}

type Query {
    allActors: [Actor]
        @cypher(
            statement: """
            MATCH (a:Actor)
            RETURN a
            """,
            columnName: "a"
        )
}

在变异类型字段上

以下示例演示了一个使用 Cypher 查询插入具有指定名称参数的单个演员的变异

type Actor @node {
    actorId: ID!
    name: String
}

type Mutation {
    createActor(name: String!): Actor
        @cypher(
            statement: """
            CREATE (a:Actor {name: $name})
            RETURN a
            """,
            columnName: "a"
        )
}

@coalesce

从 GraphQL 翻译到 Cypher 时,应用此指令的任何字段实例都将在 WHERE 子句中包装在 coalesce() 函数中。有关更多信息,请参阅 了解不存在的属性和处理空值

此指令有助于查询数据库中不存在的属性。但是,如果它成为规范,建议使用有意义的值填充这些属性。@coalesce 指令是该函数的原始实现,它只采用静态默认值,而不是在节点或 Cypher 表达式中使用其他属性。

定义

"""Int | Float | String | Boolean | ID | DateTime | Enum"""
scalar ScalarOrEnum

"""Instructs @neo4j/graphql to wrap the property in a coalesce() function during queries, using the single value specified."""
directive @coalesce(
    """The value to use in the coalesce() function. Must be a scalar type and must match the type of the field with which this directive decorates."""
    value: Scalar!,
) on FIELD_DEFINITION

用法

@coalesce 可与枚举一起使用。设置枚举字段的默认值时,它必须是枚举的枚举值之一

enum Status {
    ACTIVE
    INACTIVE
}
type Movie @node {
    status: Status @coalesce(value: ACTIVE)
}

@limit

在节点上可用,此指令将值注入到查询中,例如 limit

定义

"""The `@limit` is to be used on nodes, where applied will inject values into a query such as the `limit`."""
directive @limit(
    default: Int
    max: Int
) on OBJECT

用法

该指令有两个参数

  • default - 如果没有将 limit 参数传递给查询,则使用默认限制。查询仍可能传递更高的或更低的 limit

  • max - 定义传递给查询的最大限制。如果传递了更高的值,则使用该值。

如果未设置 default 值,则 max 用于没有 limit 的查询。
{
  Movie @limit(amount: 5) {
    title
    year
  }
}

@customResolver

Neo4j GraphQL 库生成查询和变异解析器,因此您无需自己实现它们。但是,如果您除了自动生成的 CRUD 操作之外还需要其他行为,则可以为这些场景指定自定义解析器。

要向对象类型添加一个从类型中现有值解析而来的字段,而不是存储新值,您应该在该字段上标记 @customResolver 指令,并为其定义一个自定义解析器。

例如,此模式

const typeDefs = `
    type User @node {
        firstName: String!
        lastName: String!
        fullName: String! @customResolver(requires: "firstName lastName")
    }
`;

const resolvers = {
    User: {
        fullName(source) {
            return `${source.firstName} ${source.lastName}`;
        },
    },
};

const neoSchema = new Neo4jGraphQL({
    typeDefs,
    resolvers,
});

此处 fullName 是从字段 firstNamelastName 解析的值。在字段定义上指定 @customResolver 指令可防止 fullName 包含在任何查询或变异字段中,因此在数据库中的 :User 节点上作为属性。

requires 参数中包含 firstNamelastName 字段意味着,在解析器的定义中,属性 firstNamelastName 将始终在 source 对象上定义。如果未指定这些字段,则无法保证这一点。

定义

"""Informs @neo4j/graphql that a field will be resolved by a custom resolver, and allows specification of any field dependencies."""
directive @customResolver(
    """Selection set of the fields that the custom resolver will depend on. These fields are passed as an object to the first argument of the custom resolver."""
    requires: SelectionSet
) on FIELD_DEFINITION

requires 参数

requires 参数可用于

  • 选择集字符串。

  • 在任何字段中,只要它不是另一个 @customResolver 字段。

  • 如果自定义解析器依赖于任何字段。这确保了在 Cypher 生成过程中,这些属性是从数据库中选择的。

使用选择集字符串可以从相关类型中选择字段,如下例所示

const typeDefs = `
    type Address @node {
        houseNumber: Int!
        street: String!
        city: String!
    }

    type User @node {
        id: ID!
        firstName: String!
        lastName: String!
        address: Address! @relationship(type: "LIVES_AT", direction: OUT)
        fullName: String
            @customResolver(requires: "firstName lastName address { city street }")
    }
`;

const resolvers = {
    User: {
        fullName({ firstName, lastName, address }) {
            return `${firstName} ${lastName} from ${address.street} in ${address.city}`;
        },
    },
};

const neoSchema = new Neo4jGraphQL({
    typeDefs,
    resolvers,
});

此处,如果选择了 fullName 字段,则始终从数据库中选择 firstNamelastNameaddress.streetaddress.city 字段,并且自定义解析器可以使用这些字段。

还可以内联片段以有条件地从接口/联合类型中选择字段

interface Publication {
    publicationYear: Int!
}

type Author @node {
    name: String!
    publications: [Publication!]! @relationship(type: "WROTE", direction: OUT)
    publicationsWithAuthor: [String!]!
        @customResolver(
            requires: "name publications { publicationYear ...on Book { title } ... on Journal { subject } }"
        )
}

type Book implements Publication @node {
    title: String!
    publicationYear: Int!
    author: [Author!]! @relationship(type: "WROTE", direction: IN)
}

type Journal implements Publication @node {
    subject: String!
    publicationYear: Int!
    author: [Author!]! @relationship(type: "WROTE", direction: IN)
}

但是,**不能**要求库生成的额外字段,例如聚合和连接。例如,以下类型定义将引发错误,因为它们尝试要求 publicationsAggregate

interface Publication {
    publicationYear: Int!
}

type Author @node {
    name: String!
    publications: [Publication!]! @relationship(type: "WROTE", direction: OUT)
    publicationsWithAuthor: [String!]!
        @customResolver(
            requires: "name publicationsAggregate { count }"
        )
}

type Book implements Publication @node {
    title: String!
    publicationYear: Int!
    author: [Author!]! @relationship(type: "WROTE", direction: IN)
}

type Journal implements Publication @node {
    subject: String!
    publicationYear: Int!
    author: [Author!]! @relationship(type: "WROTE", direction: IN)
}

@populatedBy

此指令用于指定一个回调函数,该函数在 GraphQL 查询解析期间执行,以填充输入中未提供的字段。

对于非必需值,回调函数可以返回undefined(表示未更改或添加到属性中)或null(表示将删除该属性)。

@populatedBy 指令只能用于标量字段。

定义

enum PopulatedByOperation {
    CREATE
    UPDATE
}

"""Instructs @neo4j/graphql to invoke the specified callback function to populate the field when updating or creating the properties on a node or relationship."""
directive @populatedBy(
    """The name of the callback function."""
    callback: String!
    """Which events to invoke the callback on."""
    operations: [PopulatedByOperation!]! = [CREATE, UPDATE]
) on FIELD_DEFINITION

用法

类型定义

type Product @node {
    name: String!
    slug: String! @populatedBy(callback: "slug", operations: [CREATE, UPDATE])
}

模式构建(请注意,回调函数是异步的)

const slugCallback = async (root) => {
    return `${root.name}_slug`
}

new Neo4jGraphQL({
    typeDefs,
    driver,
    features: {
        populatedBy: {
            callbacks: {
                slug: slugCallback
            }
        }
    }
})

上下文值

请求的 GraphQL 上下文作为回调函数的第三个参数提供。这映射到 GraphQL 解析器的参数模式。

例如,如果您想要一个名为modifiedBy的字段

type Record @node {
    content: String!
    modifiedBy: @populatedBy(callback: "modifiedBy", operations: [CREATE, UPDATE])
}

并且如果用户名位于context.username中,您可以定义如下回调函数:

const modifiedByCallback = async (_parent, _args, context) => {
    return context.username;
}

new Neo4jGraphQL({
    typeDefs,
    driver,
    features: {
        populatedBy: {
            callbacks: {
                modifiedBy: modifiedByCallback
            }
        }
    }
})

请注意,第二个位置参数(在本例中为_args)的类型为Record<string, never>,因此它始终为空对象。