gpt4 book ai didi

node.js - 在ApolloServer中支持异议 `$formatJson`

转载 作者:行者123 更新时间:2023-12-02 13:19:58 24 4
gpt4 key购买 nike

我是Objection.jsGraphQL的忠实拥护者,并且我正在努力让他们一起为自己的企业开发新的API。不幸的是,我遇到了一些困难。

关于Objection的最好的事情之一就是model data lifecyle,它允许您在模型类中指定如何在各种格式之间转换其属性。您可以以一种方式将其存储在数据库中,以另一种方式在代码中使用它,并在以另一种方式将其发送给客户端之前对其进行序列化。

例如,$formatJson可用于更改使用模型时作为JS Date对象的Date属性,但在响应中发送时将其转换为ISO字符串:

$formatJson(json: Pojo): Pojo {
json = super.$formatJson(json);
if (json.lastActive) json.lastActive = json.lastActive.toISOString();
return json;
}

该方法在实例的 #toJSON方法中调用,通常在按 here描述进行字符串化时调用。

但是,ApolloServer(特别是我正在使用的 apollo-server-koa)不会直接对这些模型实例进行字符串化处理。似乎(合理地)将属性的子集复制到新对象,从而将数据与其实例方法分开。因此,永远不会调用 #$formatJson,并且我的日期会作为时间戳返回,因为这是JS日期默认情况下的字符串化方式。

似乎需要以某种方式在解析器功能与从其返回值复制属性之间注入(inject)一些 #toJSON调用。我查看了 formatResponse here,但是看起来它已经从Model类中分离出了数据。

任何熟悉ApolloServer的人都能向我指出正确的方向吗?我需要研究某种插件API吗?

我发现 objection-graphql非常酷,但是看起来它可以通过在顶级解析器中处理整个查询并在解析器甚至返回之前递归调用所有内容上的 #toJSON来处理此问题。默认解析器从预先加载的结构中提取其他所有内容。 super 酷,但看起来似乎不够灵活,无法满足我的需求。真的,我只想解决这个特定的问题,而不是迷失我的整个api。 :\

编辑:

我已经对此进行了更多研究,并且现在我对GraphQL的实际实现有了更好的了解,所以我想更清楚地说明这个问题。

GraphQL通过调用解析器并按属性组装响应属性来工作。首先调用您的顶级解析器,然后返回它们,然后调用下一级的每个属性的解析器,依此类推,直到只剩下叶子为止。每个叶子的返回值最终分配给一个字符串化为JSON的对象结构。这意味着这些属性最初来自这些对象的任何实例方法到此为止都将完全丢失。因此,如果您打算使用这些实例方法(在本例中为Objection的 $formatJSON)对整个结果进行某种“最终”转换,则会遇到一些困难。

困难

可以使用 graphql-middleware拦截任何解析器的结果,并使用类似以下内容的方式递归地调用您的转换:
import { isArray, isObjectLike, map, mapValues } from 'lodash';
import { resolvers, typeDefs } from './api';
import { ApolloServer } from 'apollo-server-koa';
import Koa from 'koa';
import { Model } from 'objection';
import { applyMiddleware } from 'graphql-middleware';

function toResponse(value: any): any {
// If this isn't an object or an array, just return it.
if (!isObjectLike(value)) return value;

// If this is an instance of Model, convert it using #$toJson
if (value instanceof Model) return value.$toJson();

// Create a recursive mapping function.
const mapFn = (item: any) => toResponse(item);

/*
* If this an array, convert each one of its items with the mapping
* function.
*/
if (isArray(value)) return map(value, mapFn);

/*
* Otherwise, this must be an object. Convert its values with the mapping
* function.
*/
return mapValues(value, mapFn);
}

const schema = applyMiddleware(
makeExecutableSchema({ typeDefs, resolvers }),
async(
resolve,
root,
args,
info,
) => toResponse(await resolve(root, args, ctx, info)),
);

const app = new Koa();
const server = new ApolloServer({ schema });
server.applyMiddleware({ app });

app.listen(3000);

这里的问题是,转换将在调用您的下层解析器以更深入地遍历图形之前发生。因此,这些解析器的第一个参数将接收这些转换后的Model实例,并且它们将不再具有您可能要实现解析器的原始Model类(例如 $relatedQuery)的任何有用的实例方法。

例如,我可能有一个简单的架构,如下所示:
type Query {
person(id: Int!): Person
}

type Person {
id: Int!
name: String!
children: [Person!]
}

并像这样实现我的解析器:
{
Query: {
person: (
root: undefined,
args: { id: number },
) => Person.query().findById(args.id),
},
Person: {
children: (person: Person) => person.$relatedQuery('children'),
},
}

使用上面显示的中间件设置,此Person.children的解析器将不起作用,因为其 person参数将不会接收 Model实例,因此它将没有 $relatedQuery实例方法。

原因:

为什么有人要这个?除了仅希望使用Objection的最佳功能之一(如Herku正确指出的那样,在这种情况下,使用更特定于GQL的功能可能会更好地处理),还存在与其他库进行集成的可能性,这些库对您的API框架(Objection可以做到)。

为了简单起见,我本来就忽略了这一点,但是我还有一个(当前专有的)权限库,该库能够过滤查询结果以删除基于各种因素不允许用户查看的属性。我目前计划集成的方式还涉及需要原始Model实例的模型的“后解析器”转换。我可能会也可能不会走这条路,我现在不想过多地分享该库的工作方式,但希望这样可以使情况更加清楚。

我发现了一种变通方法,该方法似乎可行,并且很快就会发布答案,除非其他人提出了更好的建议。

最佳答案

因此,在GraphQL中,对象不会直接转换为JSON。相反,只有少数几个地方将值转换为JSON值。那是在叶子类型里面。

叶子类型是在GraphQL中没有子选择的类型。具体来说,它们是标量和枚举。让我们看看如何在内部使用值,而在外部使用不同的值:

对于枚举类型,我们可以在内部定义不同的值,而在外部定义不同的值。您可以将所需的任何内容分配给value属性:

import { GraphQLEnumType } from 'graphql';

const MyEnum = new GraphQLEnumType({
name: 'MyEnum',
values: {
VALUE_A: {
value: 'value-a',
},
VALUE_B: {
value: 'value-b',
}
}
});

如果您使用Apollo和 graphql-tools,则必须使用 slightly differently。现在来看更有趣的Scalar案例。在您的示例中,日期可以由日期标量序列化。

import { GraphQLScalarType } from 'graphql';
import { Kind } from 'graphql/language';

const Date = new GraphQLScalarType({
name: 'Date',
description: 'Date custom scalar type',
parseValue(value) {
return new Date(value); // value from JSON (from variable)
},
serialize(value) {
return value.toISOString(); // serialization happens here
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return new Date(ast.value); // value inlined in query
}
return null;
},
});

现在,我们将序列化为ISO字符串,并在内部使用JavaScript日期!同样,阿波罗(Apollo)是 a bit different

如果您不想为此编写自己的Scalar类型,请查看 Uri's GraphQL Scalars。或 GraphQL ISO Date在我的示例中几乎可以完成所有操作。

关于node.js - 在ApolloServer中支持异议 `$formatJson`,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58616339/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com