An error occurs in the following configuration. Please tell me the cause of the error and how to fix it.
■Error message
Error: Cannot determine GraphQL input type for 'zzzzzInputs' of 'XxxxxInput' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper input value?
■Reproduction environment and method
Clone
git clone https://github.com/isoittech/hellpj-type-graphql
Execute commands
npm ci
npm run start
■Application/library stack
Node.js/Express
sequelize
sequelize-typescript
type-graphql
■Source
https://github.com/isoittech/hellpj-type-graphql/blob/master/src/main/graphql/ppppp.resolver.ts
#ObjectType()
export class ZzzzzInput {
#Field((type) => ZzzzzType)
zzzzz!: ZzzzzType;
// #Field()
// zzzzz!: string;
}
#InputType()
export class XxxxxInput {
#Field((type) => [ZzzzzInput])
zzzzzInputs!: ZzzzzInput[];
// #Field((type) => [String])
// zzzzzInputs!: string[];
}
#Resolver((of) => Yyyyy)
export class XxxxxResolver {
#Mutation((returns) => Yyyyy)
async addXxxxx(#Arg("Xxxxx") XxxxxInput: XxxxxInput): Promise<Yyyyy> {
const serviceOutput: XxxxxServiceOutputDto = {};
return Promise.resolve(serviceOutput.addedXxxxx!);
}
}
■I coded it by referring to here.
https://typegraphql.com/docs/types-and-fields.html
Cannot determine GraphQL input type for argument named
I solved my problem by modifying like below. Look at '★'.
(And found another one, solved too.)
■Source
// #ObjectType() // ★ Before
#InputType() // ★ After
export class ZzzzzInput {
#Field((type) => ZzzzzType)
zzzzz!: ZzzzzType;
// #Field()
// zzzzz!: string;
}
#InputType()
export class XxxxxInput {
#Field((type) => [ZzzzzInput])
zzzzzInputs!: ZzzzzInput[];
// #Field((type) => [String])
// zzzzzInputs!: string[];
}
#Resolver((of) => Yyyyy)
export class XxxxxResolver {
#Mutation((returns) => Yyyyy)
async addXxxxx(#Arg("Xxxxx") XxxxxInput: XxxxxInput): Promise<Yyyyy> {
const serviceOutput: XxxxxServiceOutputDto = {};
return Promise.resolve(serviceOutput.addedXxxxx!);
}
}
■Related source
node_modules/type-graphql/dist/schema/schema-generator.js
static getGraphQLInputType(target, propertyName, type, typeOptions = {}, parameterIndex, argName) {
let gqlType;
gqlType = types_1.convertTypeIfScalar(type);
if (!gqlType) {
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
★ Finding process below not worked when #ObjectType.
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
const inputType = this.inputTypesInfo.find(it => it.target === type);
if (inputType) {
gqlType = inputType.type;
}
}
if (!gqlType) {
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
☆ Finding process below not worked when wrong order in src/index.ts.
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
const enumType = this.enumTypesInfo.find(it => it.enumObj === type);
if (enumType) {
gqlType = enumType.type;
}
}
if (!gqlType) {
throw new errors_1.CannotDetermineGraphQLTypeError("input", target.name, propertyName, parameterIndex, argName);
}
const { nullableByDefault } = build_context_1.BuildContext;
return types_1.wrapWithTypeOptions(target, propertyName, gqlType, typeOptions, nullableByDefault);
}
■Another problem
After I've solved above problem, another one occured. Error message is below:
Error: Cannot determine GraphQL input type for 'zzzzz' of 'ZzzzzInput' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper input value?
Look at ☆ in schema-generator.js.
Enum type 'ZzzzzType' couldn't be found, so error occured.
Because this.enumTypesInfo doesn't contain 'ZzzzzType'.
Because I executed registering Enumtype after SchemaGenerating process.
I had to modify below.
// enable Enum
registerEnumType(ZzzzzType, {
name: "ZzzzzType",
});
const schema = await buildSchema({
resolvers: [__dirname + "/graphql/*.resolver.ts"],
emitSchemaFile: true,
validate: false,
});
// // enable Enum
// registerEnumType(ZzzzzType, {
// name: "ZzzzzType",
// });
Related
I want to have a function that accepts only the state declared in mobx. I currently have this code.
class DialogStore {
status: boolean = false;
title: string = '';
content: string = '';
btnText: string = '';
icon: JSX.Element | string = '';
type: 'success' | 'error' = 'success';
constructor() {
makeAutoObservable(this);
}
toggle(status: boolean) {
this.status = status
}
success(content: string) {
this.type = 'success';
this.status = true;
this.content = content;
}
error(content: string) {
this.type = 'error';
this.status = true;
this.content = content;
}
}
I want to add a dynamic function like this:
update(payload: TypeOfState) {
Object.keys(payload).forEach(property => {
this[property] = payload[property];
})
}
I tried to place any as my payload type but it gives me this error Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'DialogStore'. I want the payload to only accept the state I declared at the top.
If I understand you correctly you want to have single function instead of toggle, success and error?
Would something like this work for your case?
class DialogStore {
// ...
update(payload: StateToUpdate) {
Object.keys(payload).forEach((property) => {
this[property] = payload[property];
});
}
}
type StateToUpdate = Partial<Pick<DialogStore, 'status' | 'type' | 'content'>>
To avoid TS error you could disable flag "suppressImplicitAnyIndexErrors": true altogether in your tsconfig.json or use some typecasting:
Object.keys(payload).forEach((property) => {
(this as any)[property] = payload[property as keyof StateToUpdate];
});
I think there is no easy way to mitigate this type of error in TS right now, I might be wrong though.
I am new to FP-TS and still don't quite understand how to work with TaskEither. I am attempting to asynchronously read a file and then parse the resulting string with yaml-parse-promise.
==EDIT==
I updated the code with the full contents of the file to give more context and applied some of the suggestions provided by MnZrK. Sorry I am still new to FP-TS and I am still struggling with getting the types to match up.
Now my error is with the the map(printConfig) line:
Argument of type '<E>(fa: TaskEither<E, AppConfig>) => TaskEither<E, AppConfig>' is not assignable to parameter of type '(a: TaskEither<unknown, AppConfig>) => Either<unknown, Task<any>>'.
Type 'TaskEither<unknown, AppConfig>' is not assignable to type 'Either<unknown, Task<any>>'.
Type 'TaskEither<unknown, AppConfig>' is missing the following properties from type 'Right<Task<any>>': _tag, rightts(2345)
[ I resolved this by using the getOrElse from TaskEither rather than from Either library]
==END EDIT==
I have successfully performed this with IOEither as a synchronous operation with this project: https://github.com/anotherhale/fp-ts_sync-example.
I have also looked at the example code here:
https://gcanti.github.io/fp-ts/recipes/async.html
Full code is here: https://github.com/anotherhale/fp-ts_async-example
import { pipe } from 'fp-ts/lib/pipeable'
import { TaskEither, tryCatch, chain, map, getOrElse } from "fp-ts/lib/TaskEither";
import * as T from 'fp-ts/lib/Task';
import { promises as fsPromises } from 'fs';
const yamlPromise = require('js-yaml-promise');
// const path = require('path');
export interface AppConfig {
service: {
interface: string
port: number
};
}
function readFileAsyncAsTaskEither(path: string): TaskEither<unknown, string> {
return tryCatch(() => fsPromises.readFile(path, 'utf8'), e => e)
}
function readYamlAsTaskEither(content: string): TaskEither<unknown, AppConfig> {
return tryCatch(() => yamlPromise.safeLoad(content), e => e)
}
// function getConf(filePath:string){
// return pipe(
// readFileAsyncAsTaskEither(filePath)()).then(
// file=>pipe(file,foldE(
// e=>left(e),
// r=>right(readYamlAsTaskEither(r)().then(yaml=>
// pipe(yaml,foldE(
// e=>left(e),
// c=>right(c)
// ))
// ).catch(e=>left(e)))
// ))
// ).catch(e=>left(e))
// }
function getConf(filePath: string): TaskEither<unknown, AppConfig> {
return pipe(
readFileAsyncAsTaskEither(filePath),
chain(readYamlAsTaskEither)
)
}
function printConfig(config: AppConfig): AppConfig {
console.log("AppConfig is: ", config);
return config;
}
async function main(filePath: string): Promise<void> {
const program: T.Task<void> = pipe(
getConf(filePath),
map(printConfig),
getOrElse(e => {
return T.of(undefined);
})
);
await program();
}
main('./app-config.yaml')
The resulting output is:
{ _tag: 'Right', right: Promise { <pending> } }
But I want the resulting AppConfig:
{ service: { interface: '127.0.0.1', port: 9090 } }
All these e=>left(e) and .catch(e=>left(e)) are unnecessary.
Your second approach is more idiomatic.
// convert nodejs-callback-style function to function returning TaskEither
const readFile = taskify(fs.readFile);
// I don't think there is `taskify` alternative for Promise-returning functions but you can write it yourself quite easily
const readYamlAsTaskEither = r => tryCatch(() => readYaml(r), e => e);
function getConf(filePath: string): TaskEither<unknown, AppConfig> {
return pipe(
readFile(path.resolve(filePath)),
chain(readYamlAsTaskEither)
);
}
Now your getConf returns TaskEither<unknown, AppConfig> which is actually a () => Promise<Either<unknown, AppConfig>>. If you have more specific error type than unknown, then use that instead.
In order to "unpack" the actual value, you need to have some main entry point function where you compose other stuff you need to do with your config using map or chain (ie printing it to console), then apply some error handling to get rid of Either part and finally get just Task (which is actually simply lazy () => Promise):
import * as T from 'fp-ts/lib/Task';
function printConfig(config: AppConfig): AppConfig {
console.log("AppConfig is", config);
return config;
}
function doSomethingElseWithYourConfig(config: AppConfig): TaskEither<unknown, void> {
// ...
}
async function main(filePath: string): Promise<void> {
const program: T.Task<void> = pipe(
getConf(filePath),
map(printConfig),
chain(doSomethingElseWithYourConfig),
// getting rid of `Either` by using `getOrElse` or `fold`
getOrElse(e => {
// error handling (putting it to the console, sending to sentry.io, whatever is needed for you app)
// ...
return T.of(undefined);
})
);
await program();
}
Is it possible to retrieve specific type from mixed array using typed function?
public plugins: (Tool|Service)[] = [];
getTools(): Tool[]{
return this.plugins.filter(t => t instanceof Tool);
}
So far I have no luck. Typescript is throwing following message
TS2322: Type '(Tool | Service)[]' is not assignable to type 'Tool[]'. Property 'onclick' is missing in type 'Service'.
Is there any way how I can set function type to Tool[] here?
Here is full code:
interface Required {
id: string
title: string
owner: string
type: 'user' | 'admin'
}
class P {
id; title; owner; type;
constructor(config: Required){
this.id = config.id || 'uniqid';
this.title = config.title || 'Title';
this.owner = config.owner || 'user';
this.type = config.type;
}
}
interface ToolRequired extends Required{
onclick: () => void
}
class Tool extends P {
onclick;
constructor(config = {} as ToolRequired){
super(config);
this.type = 'tool';
this.onclick = config.onclick
}
}
class Service extends P {
constructor(config = {} as Required){
super(config);
this.type = 'service'
}
}
class Storag {
static types = {
tool: Tool,
service: Service,
undefined: Tool,
};
public plugins: (Tool|Service)[] = [];
setPlugin(config = {} as Required){
const Type = Storag.types[config.type];
this.plugins.push( new Type(config) );
}
getTools(): Tool[]{
return this.plugins.filter(t => t instanceof Tool);
}
}
Just tack on an as Tool[] at the end.
public plugins: (Tool|Service)[] = [];
getTools(): Tool[]{
return this.plugins.filter(t => t instanceof Tool) as Tool[]; // << here
}
The reason you need to do this is because the Typescript compiler isn't smart enough to know that when you do such a filter it will only return Tools. A .filter on any array will usually return the same type as the previous array, which is what the compiler assumes here - a Tool|Service array.
The compiler is smart enough to know, however, that a Tool|Service can be reduced down to only Tools - as such, you can do an as Tool[] at the end to tell the compiler I know what I'm doing - the type that ".filter" returns will only be Tools here, and the compiler will listen and respect it as such.
You can read more about the as keyword here: https://www.typescriptlang.org/docs/handbook/basic-types.html (scroll down or search for "Type assertions").
As I'm learning Angular 2 I used an observable to fetch some data via an API. Like this:
getPosts() {
return this.http.get(this._postsUrl)
.map(res => <Post[]>res.json())
.catch(this.handleError);
}
My post model looks is this:
export class Post {
constructor(
public title: string,
public content: string,
public img: string = 'test') {
}
The problem I'm facing is that the map operator doesn't do anything with the Post model. For example, I tried setting a default value for the img value but in the view post.img displays nothing. I even changed Post[] with an other model (Message[]) and the behaviour doesn't change. Can anybody explain this behaviour?
I had a similar issue when I wanted to use a computed property in a template.
I found a good solution in this article:
http://chariotsolutions.com/blog/post/angular-2-beta-0-somnambulant-inauguration-lands-small-app-rxjs-typescript/
You create a static method on your model that takes an array of objects and then call that method from the mapping function. In the static method you can then either call the constructor you've already defined or use a copy constructor:
Mapping Method
getPosts() {
return this.http.get(this._postsUrl)
.map(res => Post.fromJSONArray(res.json()))
.catch(this.handleError);
}
Existing Constructor
export class Post {
// Existing constructor.
constructor(public title:string, public content:string, public img:string = 'test') {}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj['title'], obj['content'], obj['img']));
}
}
Copy Constructor
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Object) {
this.title = obj['title'];
this.content = obj['content'];
this.img = obj['img'] || 'test';
}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj);
}
}
If you're using an editor that supports code completion, you can change the type of the obj and array parameters to Post:
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Post) {
this.title = obj.title;
this.content = obj.content;
this.img = obj.img || 'test';
}
// New static method.
static fromJSONArray(array: Array<Post>): Post[] {
return array.map(obj => new Post(obj);
}
}
You can use the as keyword to de-serialize the JSON to your object.
The Angular2 docs have a tutorial that walks you through this. However in short...
Model:
export class Hero {
id: number;
name: string;
}
Service:
...
import { Hero } from './hero';
...
get(): Observable<Hero> {
return this.http
.get('/myhero.json')
.map((r: Response) => r.json() as Hero);
}
Component:
get(id: string) {
this.myService.get()
.subscribe(
hero => {
console.log(hero);
},
error => console.log(error)
);
}
I want to update a person with the UpdatePerson mutation. I don't want to specify each and every property in the inputFields - but rather want to pass the complete person object.
When I do that I get Error: UpdatePersonInput.person field type must be Input Type but got: Person.
Is there no way to pass complete objects rather than all of their properties to a mutation?
If there isn't, could you add one - because the amount of repetition of fields across a larger app with bigger objects can become very frustrating.
Same might be an issue on getFatQuery and static fragments. Repeating all the properties over and over again would be a nightmare.
Server:
/**
* Create the GraphQL Mutation.
*/
export default mutationWithClientMutationId({
// Mutation name.
name: 'UpdatePerson',
// Fields supplied by the client.
inputFields: {
person: {type: qlPerson} // <========================================
},
// Mutated fields returned from the server.
outputFields: {
person: {
type: qlPerson,
// Parameters are payload from mutateAndGetPayload followed by outputFields.
resolve: (dbPerson, id, email) => {
return dbPerson;
}
}
},
// Take the input fields, process the mutation and return the output fields.
mutateAndGetPayload: ({qlPerson}, {rootValue}) => {
// TODO: Process Authentication {"session":{"userId":1}}
console.log(JSON.stringify(rootValue));
// Convert the client id back to a database id.
var localPersonId = fromGlobalId(qlPerson.id).id;
// Find the person with the given id in the database.
return db.person.findOne({where: {id: localPersonId}}).then((dbPerson)=> {
// Mutate the person.
dbPerson.email = qlPerson.email;
// Save it back to the database.
return dbPerson.save().then(()=> {
// Return the mutated person as an output field.
return dbPerson;
});
});
}
});
Client:
/**
* Create the GraphQL Mutation.
*/
class UpdatePersonMutation extends Relay.Mutation {
getMutation() {
return Relay.QL`mutation {updatePerson}`;
}
getVariables() {
return {person: this.props.person}; // <========================================
}
getFatQuery() {
return Relay.QL`
fragment on UpdatePersonPayload {
person {
email, // ??????????????????????????
}
}
`;
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
person: this.props.person.id
}
}];
}
static fragments = {
person: () => Relay.QL`
fragment on Person {
id,
email // ???????????????????????????
}
`
};
getOptimisticResponse() {
return {
person: this.props.person
};
}
}
/**
* Exports.
*/
export default UpdatePersonMutation;
It's error because your qlPerson type was defined by using the GraphQLObjectType class, which is not an Input type. You must define it using the GraphQLInputObjectType instead. Basically, both of them take an object as an argument which requires same properties. So, you just need to use GraphQLInputObjectType instead of GraphQLObjectType as following:
export default new GraphQLInputObjectType({
name: 'qlPerson',
description: 'Dietary preferences',
fields: () => ({
firstName: {type: GraphQLString},
...
})
});