I would like to set a resolver, on an individual field that returns a string.
For this example. I want to take the title attribute, and make it .toUpperCase
Schema
type Product {
title(uppercase:Boolean!): String!
}
type Query {
products: [Product]
}
Resolver
Query: {
products: () => [{title:'foo'}],
products.title: (stringToRtn, { action }) => {
return action ? stringToRtn.toUpperCase : stringToRtn
}
}
Here is the solution:
const resolvers = {
Product: {
title: product => {
return product.title.toUpperCase();
}
},
Query: {
products: () => [{title:'foo'}]
}
};
If you're using TypeScript, use these typeDefs:
type Product {
title: String!
}
type Query {
products: [Product]
}
Another way is to use custom directive like "#upperCase", but it's too complex for this.
TypeScript update directive way
Remove : GraphQLField<any, any> if you're not using TypeScript.
#uppercase directive implementation:
import { SchemaDirectiveVisitor } from 'graphql-tools';
import { GraphQLField, defaultFieldResolver } from 'graphql';
class UppercaseDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field: GraphQLField<any, any>) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function resolver(...args) {
const result = resolve.apply(this, args);
if (typeof result === 'string') {
return result.toUpperCase();
}
return result;
};
}
}
export { UppercaseDirective };
If you're using TypeScript use these typeDefs:
const typeDefs: string = `
enum Status {
SOLD_OUT
NO_STOCK
OUT_OF_DATE #deprecated(reason: "This value is deprecated")
}
type Book {
id: ID!
title: String #uppercase
author: String
status: Status
name: String #deprecated(reason: "Use title instead")
}
type Query {
books: [Book]!
bookByStatus(status: Status!): [Book]!
}
`;
schema:
(Remove : GraphQLSchema if you're not using TypeScript.)
const schema: GraphQLSchema = makeExecutableSchema({
typeDefs,
resolvers,
schemaDirectives: {
deprecated: DeprecatedDirective,
uppercase: UppercaseDirective
}
});
Here is a link to source code using TypeScript
Related
I have this DTO and when I try to convert it to an object it doesn't convert to the way I want. Value converted to object but field name remains the same.
export class CreatePageGroupsDto {
#IsString()
#Expose()
name: string;
#IsString()
#Expose()
url: string;
#IsEnum(CategoryEnum)
#Expose()
category: CategoryEnum;
#Expose({ name: 'page_view' })
#Transform(({
value = false,
}, ) => {
const pageView: PageView = { stand_alone: value };
return pageView;
} )
stand_alone?: boolean;
}
I have this DTO and want to convert it to an object like this
{
'name': 'string',
'url': 'string',
'category': 'legal',
'page_view': {
stand_alone: false,
},
}
If you have the dto instance, and you want to covert it to an object. Your code is right.
import { Expose, instanceToPlain, Transform } from 'class-transformer';
class CreatePageGroupsDto {
#Expose({ name: 'page_view' })
#Transform(({ value = false }) => {
const pageView = { stand_alone: value };
return pageView;
})
stand_alone?: boolean;
}
const dto = new CreatePageGroupsDto();
dto.stand_alone = false;
console.log(instanceToPlain(dto));
Output:
{ page_view: { stand_alone: false } }
So I think you actually have a plain object from http request. And your framework like Nestjs convert the request object to the dto instance. This process is plainToInstance, not instanceToPlain. You can try swapping page_view and stand_alone like the following code:
class CreatePageGroupsDto {
#Expose({ name: 'stand_alone' })
#Transform(({ value = false }) => {
const pageView = { stand_alone: value };
return pageView;
})
page_view?: boolean;
}
I am trying to type de values of an array in a template object.
Currently I have achieved my goal using objects like so :
// defining the model type
interface RouteModel {
route: string
params?: Record<string, string>
}
interface RoutesModel {
[routeName: string]: RouteModel
}
// value constructor
function makeRoutes<T extends RoutesModel>(input: T) {
return input
}
// type safe creation for routes
const routes = makeRoutes({
potato: {
route: '/potato/:potatoId/rate',
params: { potatoId: '' },
},
grapes: {
route: 'grapes',
},
banana: {
route: 'bag/:bagId/:bananaId',
params: { bagId: '', bananaId: '' },
},
})
const useTypedHistory = <T extends RoutesModel>() => {
const navigate = <K extends keyof T>(route: K, params: Record<keyof T[K]['params'], string>) => {
}
return { navigate }
}
const Component = () => {
const { navigate } = useTypedHistory<typeof routes>()
navigate('banana', { bagId: '123', bananaId: '567' })
ʌ --- type safety works here, it requires the right object depending on the first param
return null
}
export default useTypedHistory
My problem is that at the beginning, I declare my params as an object with the correct keys but empty string to make it work.
I would like to an array of strings instead, so it would look like this:
// defining the model type
interface RouteModel {
route: string
params?: string[]
}
interface RoutesModel {
[routeName: string]: RouteModel
}
// value constructor
function makeRoutes<T extends RoutesModel>(input: T) {
return input
}
// type safe creation for routes
const routes = makeRoutes({
potato: {
route: '/potato/:potatoId/rate',
params: ['potatoId'],
},
grapes: {
route: 'grapes',
},
banana: {
route: 'bag/:bagId/:bananaId',
params: ['bagId', 'bananaId'],
},
})
const useTypedHistory = <T extends RoutesModel>() => {
const navigate = <K extends keyof T>(route: K, params: Record<(valueof T[K]['params']), string>) => {
ʌ --- does not work
}
return { navigate }
}
But that doesn't work at all. From what I've seen, valueof would allow me to infer the values, but I can't seem to make it work.
Any help would be very much appreciated
TS 4.1.3. The Error is called as Argument of type 'Task' is not assignable to parameter of type '{ id: string; }'. Property 'id' is optional in type 'Task' but required in type '{ id: string; }'. The error occured in functions below
interface Task {
id?: string
message: string,
}
const handleClickTask = (task: Task) => {
if (task.id) {
getTask(task)
}
}
const getTask = (taskWithId: { id: string }) => {
}
Link to the TS playground https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgCpwM4GtkG8CwAUMicsACYD8AXMhmFKAOZGnIC2EGGcTEt9RiCYAaIgF8iRBAHsQ9ZAAs4IcgBsIAYTXAEWdNmQBeZAAowmLLQNYAlMYB8eVqWAwzF7ADoK9gsTZSPjAbc0tbFxJJQmjpOQVgm2MPSwB1YDBFAElyWlwyXLoGZmRxeyMnfwkgA
How to fix it and why the error is occured?
I think the best way here is to use union types
First approach:
/**
* First approach
*/
type Task_0 = {
id: string
message: string,
}
type Task_1 = {
id: undefined,
message: string,
}
type Task = Task_0 | Task_1
const handleClickTask = (task: Task) => {
if (task.id) {
getTask(task)
}
}
const getTask = (taskWithId: { id: string }) => {
}
Second approach:
/**
* Second approach
*/
type Task_0 = {
id: string
message: string,
}
type Task_1 = {
message: string,
}
type Task = Task_0 | Task_1
const isId = (task: any): task is Task_0 => typeof task.id === 'string'
const handleClickTask = (task: Task) => {
if (isId(task)) {
getTask(task)
}
}
const getTask = (taskWithId: { id: string }) => {
}
More explanations:
interface Task {
id?: string
message: string,
}
const handleClickTask = (task: Task) => {
if (task.id) {
/**
* Here, TS still treats taks as a Task type, where your ID can be optional
*/
getTask(task)
}
}
const getTask = (taskWithId: { id: string }) => {
}
{
interface Task {
id?: string,
message: string,
}
const handleClickTask = (task: Task) => {
if (typeof task.id === 'string') {
const id = task.id; // string
const oldTask = task; // Task, where id still can be undefined
const test1 = getTask(oldTask) // error, type is referenced to Task type
getTask({
...task,
id
}) // ok
}
}
/**
* this function expects {id: string} which is not compatible with {id?: string}
* So, you need to pass as an argument to this function type, which will be
* compatible with {id: string}
*
* THat's why I created union type, when one of property is {id: string}
*/
const getTask = (taskWithId: { id: string }) => {
}
}
Here you can find more examples/approaches with unions
I think the problem is the way in which you defined id in the interface Task. There, id is defined using ?, which in typescript translates to: this is either a string or undefined.
When you pass a task to getTask, you take out the id from task, but now you said that id is required, i.e. it is of type string, whereas the interface says it is either string, either undefined.
For it to work, getTask should be defined with ? after id (just like in the interface), like so:
const getTask = (taskWithId: { id?: string }) => {
}
fix
interface Task {
id?: string,
message: string,
}
const handleClickTask = (task: Task) => {
if (task.id) {
getTask(task)
}
}
const getTask = (taskWithId: { id?: string}) => {
}
Why the error happened?
The error happened because the interface Task has made id property as optional.
But in taskWithId the id is mandatory and therefore it complains because the types didn't match.
In the interface Task the type of id is string or undefined.But in taskWithId it is only string.
Suppose I have the following interfaces:
interface Person {
name: string;
}
interface Attendee {
person: Person;
id: number;
}
I have already figured out how to use the compiler API to extract string representations of every property's type, e.g.:
{
Attendee: {
person: "Person",
id: "number"
}
}
Here's how I do it: https://github.com/jlkiri/tsx-ray/blob/master/src/index.ts.
It's a combination of typeToString and getTypeOfSymbolAtLocation of the Type Checker.
However I would like to resolve types likes Person to their definition so that I get:
{
Attendee: {
person: {
name: "string";
},
id: "number"
}
}
Is there API I can use to easily do this, or do I have to implement the logic myself?
Check ts-morph. I recently discovered it and it looks promising.
Here is a minimal code that can do what you want:
import {ClassDeclaration, Project} from 'ts-morph';
const project = new Project({);
project.addSourceFilesAtPaths("src/**/*.ts");
const allSourceFiles = project.getSourceFiles();
allSourceFiles.forEach(sourceFile => {
const classes = sourceFile.getClasses();
classes.forEach(cls => {
console.log(`class ${cls.getName()} {`);
const properties = cls.getProperties();
properties.forEach(prop => {
const type = prop.getType();
if(type.isClassOrInterface()) {
const typeSymbol = type.getSymbol();
console.log(` ${prop.getName()} :
${typeSymbol?.getName()} {`);
const clsDeclaration = typeSymbol?.getDeclarations()[0] as ClassDeclaration;
const members = clsDeclaration.getMembers();
members.forEach(m => {
console.log(` ${m.getText()}`);
});
console.log(` }`);
} else {
console.log(` ${prop.getName()} : ${type.getText()}`);
}
});
console.log(`}`);
});
})
For the following two files:
// ./src/property.ts
class Category {
description: string;
id: number;
}
export default Category;
// ./src/product.ts
import Category from './category';
class Product {
name: string;
price: number;
category: Category;
}
export default Product;
you will get the following printout:
class Category {
description : string
id : number
}
class Product {
name : string
price : number
category : Category {
description: string;
id: number;
}
}
I build ToDoApp and connect firebase to my project but i have error.
ERROR in src/app/todo-component/todo-component.component.ts(25,7): error TS2740: Type 'DocumentChangeAction<{}>[]' is missing the following properties from type 'Observable<ToDoInterface[]>': _isScalar, source, operator, lift, and 5 more.
Here my ToDOInterface:
export interface ToDoInterface {
id?: string;
title?: string;
completed?: boolean;
}
My ToDoService:
import { Injectable } from '#angular/core';
import {ToDoInterface} from './ToDoInterface'
import {AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument} from "#angular/fire/firestore";
import {Observable} from "rxjs";
#Injectable({
providedIn: 'root'
})
export class ToDoService {
public toDoArray:ToDoInterface[] = [
{
id: "sdfsdf",
title: 'Todo one',
completed: false
},
{
id: "13dsfsdf",
title: 'Todo two',
completed: false
},
{
id: "safsdf",
title: 'Todo third',
completed: false
}
];
ToDoCollection: AngularFirestoreCollection<ToDoInterface>;
ToDo: Observable<ToDoInterface[]>;
ToDoCollectionName: string = "ToDo";
constructor(private firestore: AngularFirestore) {
}
getToDos(){
return this.toDoArray;
}
getToDosFirebase(){
return this.firestore.collection(this.ToDoCollectionName).snapshotChanges();
}
changeToDos(index,property,value){
this.toDoArray[index][property] = value;
}
deleteToDos(index){
this.toDoArray.splice(index,1);
}
deleteToDosFirebase(index){
// this.firestore.collection("ToDO").doc(index).delete();
}
addToDos(obj: ToDoInterface){
this.toDoArray.push(obj);
}
addToDosFirebase(obj: ToDoInterface){
return new Promise<any>(((resolve, reject) => {
this.firestore
.collection("ToDo")
.add(obj)
.then(res => console.log('data send!'), err => reject(err))
}))
}
}
And my function what i call in ngOnInit
getToDo(){
this._toDoService.getToDosFirebase().subscribe(items => {
this.toDosFirebase = items;
console.log(items);
});
Maybe i dont know some about rxjs but here data from that func if toDosFirebase: Observable<any[]>; and how i can see it normal for my Interface
In service i hardcode data and all works fine, and my hardcoded data by types equal data from firebase.
Do like in official documentation:
private ToDoCollection: AngularFirestoreCollection<ToDoInterface>;
ToDo: Observable<ToDoIdInterface[]>;
ToDoCollectionName: string = "ToDo";
constructor(private readonly firestore: AngularFirestore) {
this.ToDoCollection = firestore.collection<ToDoInterface>(this.ToDoCollectionName);
this.ToDo = this.ToDoCollection.snapshotChanges().pipe(
map(a => {
const data = a.payload.doc.data() as ToDoInterface; //ERROR
const id = a.payload.doc.id;
return {id, ...data}
}
));
console.log(this.ToDo);
}
In your "getToDosFirebase" method you are calling a ".snapshotChanges();" method.
Actions returned by snapshotChanges are of type DocumentChangeAction
and contain a type and a payload. The type is either added, modified
or removed and the payload contains the document's id, metadata and
data.
In other words you need to map the received data something like this:
.snapshotChanges()
.map(actions => {
return actions.map(a => {
//Get document data
const data = a.payload.doc.data() as Task;
//Get document id
const id = a.payload.doc.id;
//Use spread operator to add the id to the document data
return { id, …data };
});
});
For more information see these links: link1, link2
update
In your "to-do-service.service.ts"
change this line:
ToDo: Observable<ToDoIdInterface[]>;
to this:
ToDo: Observable<any>;
also change this:
map(a => {
to this:
map((a: any) => {
After this the project should build correctly.