自定义逻辑

这是 GraphQL Library 7 版本的文档。对于长期支持 (LTS) 版本 5,请参考 GraphQL Library 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 和 RETURN 子句的 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 | INTERFACE

用法

该指令有两个参数

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

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

如果未设置 default 值,则 max 将用于没有限制的查询。
type Movie @limit(max: 100, default: 10) @node {
    id: ID
}

@customResolver

在 Aura Console 中创建的数据 API 当前不支持自定义解析器。

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

若要向对象类型添加一个从类型中的现有值解析得出(而不是存储新值)的字段,你应该用 @customResolver 指令标记它,并为其定义一个自定义解析器。

以这个 schema 为例

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 参数可用于

  • 选择集字符串。

  • 任何字段(只要它不是另一个 @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

在 Aura Console 中创建的数据 API 当前不支持 @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])
}

Schema 构建(注意回调是异步的)

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>,因此它始终是一个空对象。这是为了保持与 GraphQL 解析器兼容的签名。

populatedByOperation

context 参数包含 populatedByOperation 字段。此字段是触发回调的变更类型(CREATEUPDATE)。populatedByOperation 允许根据操作类型执行不同的逻辑。例如

const modifiedByCallback = async (_parent, _args, context) => {
    if(context.populatedByOperation === "UPDATE"){
        return context.username;
    } else {
        return "";
    }
}