KeystoneJs virtual field cannot use a custom field type - keystonejs

I need to display a json/object in a readonly form and I wrote a custom field type for it but then when I use a virtual to transform it to a string for passing it to the custom field type. I put the custom type into the args but it shows Error: Unknown type "JsonViewer".
Any idea of how to make it work?
const { Virtual } = require("#keystonejs/fields");
const JsonViewer = require("#/components/fields/jsonViewer");
module.exports = {
fields: {
requestData: {
type: Virtual,
args: [{ name: "requestData", type: "JsonViewer" }],
resolver: async (json) => {
return JSON.stringify(json);
},
},
}
}

You have to provide complex type details if they do not exist in the generated schema for graphql.
In your case as you are doing JSON.stringify you can use String return type like this. BTW String type is default return type and you should not need any type declaration there for string type.
Also there is no args option in keystone Virtual field.
const { Virtual } = require("#keystonejs/fields");
module.exports = {
fields: {
requestData: {
type: Virtual,
graphQLReturnType: `String`,
resolver: async (json) => {
return JSON.stringify(json);
},
},
}
}

Related

Custom id generation for Redis-OM nodejs using provided entity data

for example:
const fooSchema = new Schema(Foo, {
userId: { type: 'number' },
channelId: { type: 'number' }
}, {
idStrategy: () => `${userId}${channelId}`
});
Is it possible to provide the idStrategy function with the entity data?

How to set vue-i18n in a component's property reactively

I am trying to display some messages or errors depends on an async function result.
When a response's request doesn't include the message property I should use my own.
The problem is that when I use this.$t("message") inside the someMethod method the response.message (data) it isn't reactive. So it won't change after the locale change.
<v-alert v-if="response.error || response.message"
:type="response.error ? 'error' : 'info'">
{{response.error ? response.error : response.message}}
</v-alert>
export default {
data() {
return {
response: {
message: "",
error: ""
}
};
},
methods: {
async someMethod() {
const apiResponse = await sendRequestToAnApi();
if (apiResponse.message) {
this.response.message = apiResponse.message;
} else {
this.response.message = this.$t("translation")
}
}
}
}
I guess I should use computed property to make it work, but I don't have a clue how to mix response object with possible API response.
Any help will be appreciated
You could have your API return not only a message but also a code, where the message could be a textual string based on user locale settings, and the code a slug that will always be English, like users.added or project.created. You can store this code in response.code and create a computed property checking this code and returning the correct vue-i18n translation.
export default {
data() {
return {
response: {
code: "",
error: ""
}
};
},
methods: {
async someMethod() {
const apiResponse = await sendRequestToAnApi();
if (apiResponse.code) {
this.response.code = apiResponse.code;
} else {
this.response.code = null
}
}
},
computed: {
translatedMessage () {
if (this.response.code === 'users.added') {
return this.$t('translate')
}
return 'unknown code'
}
}
}
Of course you could also make this work with a textual string coming from the API, but personally I like working with a specific code that is independent from language settings.

How to include info from schema directives as returned data using Apollo Server 2.0 GraphQL?

I'm using schema directives for authorization on fields. Apollo server calls the directives after the resolvers have returned. Because of this the directives don't have access to the output so when authorization fails I can't include relevant information for the user without a convoluted workaround throwing errors that ends up always returning the error data whether the query requests them or not.
I'm hoping someone understands the internals of Apollo better than I and can point out where I can insert the proper information from directives so I don't have to break the standard functionality of GraphQL.
I tried including my output in the context, but that doesn't work despite the directive having access since the data has already been returned from the resolvers and the context version isn't needed after that.
As of right now I throw a custom error in the directive with a code DIRECTIVE_ERROR and include the message I want to return to the user. In the formatResponse function I look for directive errors and filter the errors array by transferring them into data's internal errors array. I know formatResponse is not meant for modifying the content of the data, but as far as I know this is the only place left where I can access what I need. Also frustrating is the error objects within the response don't include all of the fields from the error.
type User implements Node {
id: ID!
email: String #requireRole(requires: "error")
}
type UserError implements Error {
path: [String!]!
message: String!
}
type UserPayload implements Payload {
isSuccess: Boolean!
errors: [UserError]
data: User
}
type UserOutput implements Output {
isSuccess: Boolean!
payload: [UserPayload]
}
/**
* All output responses should be of format:
* {
* isSuccess: Boolean
* payload: {
* isSuccess: Boolean
* errors: {
* path: [String]
* message: String
* }
* data: [{Any}]
* }
* }
*/
const formatResponse = response => {
if (response.errors) {
response.errors = response.errors.filter(error => {
// if error is from a directive, extract into errors
if (error.extensions.code === "DIRECTIVE_ERROR") {
const path = error.path;
const resolverKey = path[0];
const payloadIndex = path[2];
// protect from null
if (response.data[resolverKey] == null) {
response.data[resolverKey] = {
isSuccess: false,
payload: [{ isSuccess: false, errors: [], data: null }]
};
} else if (
response.data[resolverKey].payload[payloadIndex].errors == null
) {
response.data[resolverKey].payload[payloadIndex].errors = [];
}
// push error into data errors array
response.data[resolverKey].payload[payloadIndex].errors.push({
path: [path[path.length - 1]],
message: error.message,
__typename: "DirectiveError"
});
} else {
return error;
}
});
if (response.errors.length === 0) {
return { data: response.data };
}
}
return response;
};
My understanding of the order of operations in Apollo is:
resolvers return data
data filtered based on query parameters?
directives are called on the object/field where applied
data filtered based on query parameters?
formatResponse has opportunity to modify output
formatError has opportunity to modify errors
return to client
What I'd like is to not have to throw errors in the directives in order to create info to pass to the user by extracting it in formatResponse. The expected result is for the client to receive only the fields it requests, but the current method breaks that and returns the data errors and all fields whether or not the client requests them.
You can inject it using destruct:
const { SchemaDirectiveVisitor } = require("apollo-server-express");
const { defaultFieldResolver } = require("graphql");
const _ = require("lodash");
class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function (parent, args, context, info) {
// You could e.g get something from the header
//
// The verification below its necessary because
// my application runs locally and on Serverless
const authorization = _.has(context, "req")
? context.req.headers.authorization
: context.headers.Authorization;
return resolve.apply(this, [
parent,
args,
{
...context,
user: { authorization, name: "", id: "" }
},
info,
]);
};
}
}
Then on your resolver, you can access it through context.user.

GraphQL buildSchema vs GraphQLObjectType

I went through GraphQL's Object Types tutorial and then read through the Constructing Types part of the docs. I did a similar style trial by creating a simplecase convention converter. Why? To learn :)
When converting to using GraphQLObjectType, I wanted the same results as buildSchema.
Why does buildSchema use type CaseConventions but when using GraphQLObjectType it is not set at a type? Am I doing something wrong here?
Did I implement this with any alarming problems?
Should I be using a rootValue object with the GraphQLObjectType version as I did with the buildQuery version?
Thank you for your patience and help.
Both versions use this Object:
class CaseConventions {
constructor(text) {
this.text = text;
this.lowerCase = String.prototype.toLowerCase;
this.upperCase = String.prototype.toUpperCase;
}
splitTargetInput(caseOption) {
if(caseOption)
return caseOption.call(this.text).split(' ');
return this.text.split(' ');
}
cssCase() {
const wordList = this.splitTargetInput(this.lowerCase);
return wordList.join('-');
}
constCase() {
const wordList = this.splitTargetInput(this.upperCase);
return wordList.join('_');
}
}
module.exports = CaseConventions;
buildSchema version:
const schema = new buildSchema(`
type CaseConventions {
cssCase: String
constCase: String
}
type Query {
convertCase(textToConvert: String!): CaseConventions
}
`);
const root = {
convertCase: ({ textToConvert }) => {
return new CaseConventions(textToConvert);
}
};
app.use('/graphql', GraphQLHTTP({
graphiql: true,
rootValue: root,
schema
}));
GraphQLObjectType version:
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
cssCase: {
type: GraphQLString,
args: { textToConvert: { type: GraphQLString } },
resolve(parentValue) {
return parentValue.cssCase();
}
},
constCase: {
type: GraphQLString,
args: { textToConvert: { type: GraphQLString } },
resolve(parentValue) {
return parentValue.constCase()
}
}
}
});
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
convertCase: {
type: QueryType,
args: { textToConvert: { type: GraphQLString } },
resolve(p, { textToConvert }) {
return new CaseConventions(textToConvert);
}
}
}
});
const schema = new GraphQLSchema({
query: RootQuery
});
app.use('/graphql', GraphQLHTTP({
graphiql: true,
schema
}));
I will try to answer you question satisfactorily.
Why does buildSchema use type CaseConventions but when using GraphQLObjectType it is not set at a type? Am I doing something wrong here
They are two different ways of implementation. Using buildSchema uses the graphQL schema language while GraphQLSchema does not use the schema language, it creates the schema programmatically.
Did I implement this with any alarming problems?
Nope
Should I be using a rootValue object with the GraphQLObjectType version as I did with the buildQuery version?
No, In buildSchema, the root provides the resolvers while in using
GraphQLSchema, the root level resolvers are implemented on the Query and Mutation types rather than on a root object.

Keys in One Object Must be the Same as Keys in Another Object

Initial Setting
You have a JavaScript object for keeping configs, it may be extended by plugins, each plugin has a version and one property on the configs object.
const CONFIGS = {
plugins: {
plugins: { version: '0.15' }, // Plugins is a plugin itself.
proxies: { version: '0.15' } // Used to configure proxies.
},
proxies: {
HTTPS: ['satan.hell:666']
}
}
Question
How to express in JSON schema, that each key of CONFIGS.plugins MUST have corresponding property on root of CONFIGS object and vice versa.
My Failed Attempt
ajv is 4.8.2, prints "Valid!" but must be "Invalid"
'use strict';
var Ajv = require('ajv');
var ajv = Ajv({allErrors: true, v5: true});
var schema = {
definitions: {
pluginDescription: {
type: "object",
properties: {
version: { type: "string" }
},
required: ["version"],
additionalProperties: false
}
},
type: "object",
properties: {
plugins: {
type: "object",
properties: {
plugins: {
$ref: "#/definitions/pluginDescription"
}
},
required: ["plugins"],
additionalProperties: {
$ref: "#/definitions/pluginDescription"
}
}
},
required: { $data: "0/plugins/#" }, // current obj > plugins > all props?
additionalProperties: false
};
var validate = ajv.compile(schema);
test({
plugins: {
plugins: { version: 'def' },
proxies: { version: 'abc' }
}
// Sic! No `proxies` prop, but must be.
});
function test(data) {
var valid = validate(data);
if (valid) console.log('Valid!');
else console.log('Invalid: ' + ajv.errorsText(validate.errors));
}
There are three solutions here:
create another property on the root level, e.g. requiredProperties. Its value should be an array with the list of properties you want to have both on the top level and inside plugins. Then you can use required with $data pointing to this array both on top level and inside plugins.
See example here: https://runkit.com/esp/581ce9faca86cc0013c4f43f
use custom keyword(s).
check this requirement in code - there is no way in JSON schema to say keys in one object should be the same as keys in another (apart from above options).