What's proper way of escaping input string in JavaScript? - javascript

See graphql mutation with JavaScript variables injected (please ignore that it's constructed manually and not through some gql helper):
mutation {
createPost(
name: "${name}",
description: "${description}"
) { id }
}
Is there any standard function for escaping name & description string variables ?
Eg. some function gqlEscape which will make query safe:
mutation {
createPost(
name: "${gqlEscape(name)}",
description: "${gqlEscape(description)}"
) { id }
}
How would that function look like?

Instead of doing string interpolation, you can use GraphQL variables which are designed for your use case. You can do this using raw HTTP, too, but I usually use graphql-request in this case.
Here's an example:
import { request } from 'graphql-request'
const mutation = `
mutation newPost($name: String!, $description: String) {
createPost(
name: $name,
description: $description
) {
id
}
}
`
const variables = {
name: 'New Post',
}
request('my-endpoint', mutation, variables).then(data => console.log(data))
A few notes:
I defined two variables, name: String! and description: String. String! means, it's a required variable of type String, while String means it's an optional variable. You can see that I did not pass a value for description, which is possible because it's optional.
I did not specify the double quotes for the two strings, that's already handled by the GraphQL Variables themselves.
You can read more about GraphQL Variables here and here.

Related

filling an array in typescript/react with objects converted from JSON array

I have a JSON-File with about 700 lines filled with devices and some information about them (i.e. serial number,macadress, port ...).
In another file I have created an object like
type jsonObj = {
serialNumber: string;
macAdress: string;
port: string;
}
And now I want to populate a table with this jsonDevice objects in a loop but somehow I dont get it to work.
My code looks like that:
const ObjectTable: FC = () => {
const entities: jsonObj[] = [
{
serNom: jsonDevices[0].serNom, //jsonDevices is my json file
macAdr: jsonDevices[0].macAdr,
tunPort: jsonDevices[0].tunPort
},
];
FC is an import from react and with the code above it is working. Im receiving a table-output with the data for this line (e.g jsonDevices[0].serNom gives me correctly 123456789) but now I want to automatically fill the whole list/array with all the remaining lines from the json devices. In Java I would have solved it with a loop like that:
for (int i = 0, i<jsonObj.length, i++){
ObjectTable.add(jsonObj[i].serNom);
ObjectTable.add(jsonObj[i].macAdr);
ObjectTable.add(jsonObj[i].tunPort);
}
Please help me. I getting headache from this issue.
Let's assume you have some data like this:
[
{ id: 1, name: "test-1" },
{ id: 2, name: "test-random" },
{ id: 3, desc: "444" }
]
And you have some structure of this data, create an interface
interface SomeInterface {
id: number;
name?: string;
desc?: string;
}
Now it's simple if that JSON input data is referred to as someData and resultant data are referred as hmm, you can some something like this:
const hmm: SomeInterface[] = [];
someData.map((data) => {
hmm.push(data as SomeInterface);
});
You can also simplify by:
hmm: SomeInterface[] = someData as SomeInterface[];
Here is a codesandbox, feel free to play with it: https://codesandbox.io/s/dreamy-chatelet-hr5ub6?file=/src/App.tsx:343-456
And here are two sof threads both on same question though, discussing why interface over types:
https://stackoverflow.com/a/65948871/10305444
https://stackoverflow.com/a/54101543/10305444
Note: Your code will not compile because you've declared the jsonObj type to have this shape:
type jsonObj = {
serialNumber: string;
macAdress: string;
port: string;
}
But then you're attempting to create an array of jsonObj objects that use different key names:
{
serNom: jsonDevices[0].serNom, //jsonDevices is my json file
macAdr: jsonDevices[0].macAdr,
tunPort: jsonDevices[0].tunPort
}
With that aside, the solution to your problem is much simpler than it seems: Array.prototype.map:
const entities: jsonObj[] = jsonDevices.map((device) => {
return {
serialNumber: device.serNom,
macAddress: device.macAdr,
port: device.tunPort,
};
});
Another note: These abbreviated variable names are difficult to read. I recommend picking one convention and sticking with it. If your JSON file were to follow the naming convention of jsonObj, then you wouldn't even need to map the JSON to a different shape—you would just do this:
const entities = jsonDevices as jsonObj[];
(This is known as a type assertion—using the as keyword followed by a type to assure the TypeScript compiler that the left-hand value is of the specified type.)

Typescript: only name/destructure optional properties when taking an interface parameter?

I'm new to typescript and am writing some classes to work as a very simple ORM for my project. I just learned how to require that a parameter conform to an interface, like so:
interface UserInfo {
userId: string
email?: string
// many more properties...
}
class User {
constructor(connection: typeof db, init: UserInfo) {
// code here
And I understand that the syntax to set default values for those optional properties is:
class User {
constructor(connection: typeof db, {email = null, /*...*/}: UserInfo) {
// code here
But my UserInfo interface is long, what if I want to only specify/default a few of the properties, without typing out a giant object in the parameters for the constructor of this object? Is there a way to require an entire interface, but destructure only some properties?
Destructuring isn't really a great fit, because that essentially ignores any properties you don't explicitly mention. In order not to lose track of the unmentioned properties, you could destructure into a rest object:
class User {
constructor({ email = "xyz", ...rest }: UserInfo) {}
}
But now you have a variable named email with a string value in it which is either the one passed in or the default "xyz" (not null, by the way; null is not a string) if none was passed in. And you also have a variable named rest with an Omit<UserInfo, "email"> value in it (I'm using the Omit<T, K> utility type to represent what happens when you widen a type to forget about some property keys).
If you actually need a value of type Required<UserInfo> (I'm using the Required<T> utility type to represent a type with any optional properties changed to be required), then you'll need to reassemble it from those pieces, such as via shorthand property names in your object literal along with object spreading:
class User {
userInfo: Required<UserInfo>
constructor({ email = "xyz", ...rest }: UserInfo) { // <-- split it apart
this.userInfo = { email, ...rest }; // <-- put it back together
}
}
That works, but it is needlessly destructive (if you'll forgive the pun).
Instead, you can just use spreading to get more or less the same effect:
class User {
userInfo: Required<UserInfo>
constructor(init: UserInfo) {
this.userInfo = { email: "xyz", ...init };
}
}
If the passed-in constructor argument has no email property, then the default one of "xyz" will be there. Otherwise the argument's email property will overwrite the default.
Anyway in both cases you can see that it works as desired:
const u = new User({ userId: "abc" });
console.log(u.userInfo.userId) // abc
console.log(u.userInfo.email) // xyz
const v = new User({ userId: "def", email: "ghi" });
console.log(v.userInfo.userId) // def
console.log(v.userInfo.email) // ghi
Playground link to code

How can I pass an array of objects into Query?

I'm trying to figure out how to pass an array of objects into my GraphQL query, however i'm finding the documentation a little unclear on how to do so. I'm working with Apollo in the FE, Graphql-yoga in the BE and using Prisma as my database along with their API.
Here is my query with the array of objects hard coded:
const USERS = gql`
query USERS(
$userId: ID
) {
users(
where: {
id_not: $userId
hasProducts_some: {
OR: [
{ itemId: 1 },
{ itemId: 2 }
]
}
}
) {
firstName
}
}
`;
The above query returns me what I want, where i'm a bit stuck is how to get this array:
[
{ itemId: 1 },
{ itemId: 2 }
]
passed in as a variable of the query. From what I could find online, I might need to create a GraphQLObjectType on the client side to be able to pass in an object definition. Here was my implementation of that:
import { GraphQLObjectType, GraphQLString } from 'graphql';
const ProductName = new GraphQLObjectType({
name: 'ProductName',
fields: () => ({
itemId: {
type: GraphQLString,
},
})
});
const USERS = gql`
query USERS(
$userId: ID,
$hasProducts: [ProductName]
) {
users(
where: {
id_not: $userId
hasProducts_some: {
OR: $hasProducts
}
}
) {
firstName
}
}
`;
The above returns me the following error:
Unknown type "ProductName"
Have I gone with the correct approach here for passing in arrays of objects, if so what's wrong with my implementation?
Types are created and used in creating your schema server-side. Once created, the schema cannot be modified at runtime -- it has whatever types and directives were specified when it was created. In other words, defining a new type on the client-side is meaningless -- it can't be used in any queries you send to the server since the server is not aware of the type.
If a variable (like $hasProducts) is passed to an argument (like hasProducts_some), that variable's type must match the type of the argument. This type could be a scalar (like String or Int) or it could be an input object type. What exact type that that is depends on the schema itself. To determine the type to use, you can open up your schema's documentation in GraphQL Playground (or GraphiQL) and search for the field in question (in this case, hasProducts_some).
Note that you can also just pass a single variable in for the whole where field.
Since the gql function expects a template literal, you should escape the product object like so:
const USERS = gql`
query USERS(
$userId: ID,
$hasProducts: [${ProductName}]
) {
users(
where: {
id_not: $userId
hasProducts_some: {
OR: $hasProducts
}
}
) {
firstName
}
}
`;
New to graphql. But was wondering if this can resolve it.
const USERS = gql`
query USERS(
$userId: ID,
$hasProducts: GraphQLList(ProductName)
) {
users(
where: {
id_not: $userId
hasProducts_some: {
OR: $hasProducts
}
}
) {
firstName
}
}
`;
Minor change, but am not privileged to comment . So posting it as answer.

How to make multiple connects in one GQL mutation?

So I have the following in my Prisma datamodel. There's a relationship between the games and the characters, where the characters can appear in multiple games. Also the characters should be able to be listed on the games when the games alone are queried:
type Game {
id: ID! #unique
name: String! #unique
name_ja: String #unique
name_ko: String #unique
name_zh_CN: String #unique
name_zh_TW: String #unique
name_zh_HK: String #unique
characters: [Character]
filters: [GameFilter]
}
type Character {
id: ID! #unique
name: String! #unique
name_ja: String #unique
name_ko: String #unique
name_zh_CN: String #unique
name_zh_TW: String #unique
name_zh_HK: String #unique
games: [Game]
}
I then have this as a mutation for updating characters in my schema. I pass along an array of Game IDs because there's the possibility of multiple games being added within the same mutation:
updateCharacter(
name: String
name_ja: String
name_ko: String
name_zh_CN: String
name_zh_HK: String
name_zh_TW: String
id: ID!
games: [ID]
): Character!
And the following mutation written:
async updateCharacter(parent, args, ctx, info) {
if (!ctx.request.userId) {
throw new Error('You must be logged in');
}
const updates = {...args};
delete updates.id;
delete updates.games;
console.log(args.games);
const res = await ctx.db.mutation.updateCharacter({
data: updates,
games: {
connect: {
id: args.games
}
},
where: {
id: args.id
}
}, info);
return res;
}
And the following mutation written React-side. It's meant to pass along an array of IDs to the mutation (which will be expected because the schema assumes that an array of IDs will be passed along:
const UPDATE_CHARACTER_MUTATION = gql`
mutation UPDATE_CHARACTER_MUTATION(
$name: String!
$name_ja: String!
$name_ko: String!
$name_zh_CN: String!
$name_zh_TW: String!
$name_zh_HK: String!
$id: ID!
$games: [ID]!
) {
updateCharacter(
name: $name
name_ja: $name_ja
name_ko: $name_ko
name_zh_CN: $name_zh_CN
name_zh_TW: $name_zh_TW
name_zh_HK: $name_zh_HK
games: $games
id: $id
) {
id
name
name_ja
name_ko
name_zh_CN
name_zh_TW
name_zh_HK
games {
id
name
}
}
}
`;
I am also passing along the IDs of the games in an array onSubmit within the form to the mutation.
The result is that the mutation passes, but will only update the name fields, and the connection with the Games remains unchanged.
Not sure what I can do to make this mutate properly. I'm not sure whether passing the array directly into the mutation in the connect is what's causing the issue, but I tried mapping through and passing along each game id individually, but had the same result of the connection not updating.
The problem seems to be two-fold.
Regarding not seeing any changes at all:
In the mutation written, games is added to the async request json outside of data, meaning the data will never be found by the request.
Rather than:
data: ...,
games: ...
games must be part of data like such:
data: {
games: ...
}
Regarding wanting to do multiple connects:
GraphQL supports this style of nested updates using the following connect syntax:
connect: [
{ id : /* id from args */ },
{ id : /* another id from args */ },
...
]

What does the interfaces in GraphQLInterfaceType?

I have the following the code snippet:
export const NodeInterface = new GraphQLInterfaceType({
name: 'Node',
fields: {
id: {
type: new GraphQLNonNull(GraphQLID)
}
},
resolveType: (source) => {
if (source.__tableName === tables.users.getName()) {
return UserType;
}
return PostType;
}
});
and a GraphQLObjectType that is using the interface:
export const PostType = new GraphQLObjectType({
name: 'Post',
interfaces: [ NodeInterface ],
fields: {
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: resolveId
},
createdAt: {
type: new GraphQLNonNull(GraphQLString),
},
body: {
type: new GraphQLNonNull(GraphQLString)
}
}
});
For what do I have to define an interface?
In GraphQL, interfaces fulfill two purposes:
They ensure that the types implementing them also implement specific fields. For example, the Node interface here has an id field -- that means any type that implements the Node interface will also need to have an id field (and that id will need to be an ID scalar like in the interface) The same goes for arguments on those -- any arguments on the fields in the interface will also have to exist on the matching fields in the implementing type.
They can be used when two or more types are expected for a field. A field will always resolve to exactly one type or scalar, however, by using interfaces (or unions) we indicate in our schema that the field could resolve to one of a set of types.
So let's say we have a Node like in your snippet, some types that implement it, and a query that returns a Node:
interface Node {
id: ID!
}
type Foo implements Node {
id: ID!
someFooField: String!
someOtherFooField: Int!
}
type Bar implements Node {
id: ID!
someBarField: String!
someOtherFooField: Int!
}
type Query {
getNode(id: ID!): Node!
}
In our example, getNode could resolve to either a Foo or a Bar. When we write our query, we don't know which one will be resolved. But because we know the id field is required by the interface, we can write a query like this:
query OperationName {
getNode(id: "SOME_ID"){
id
}
}
If we need to query someBarField as well, though, we can't do this:
query OperationName {
getNode(id: "SOME_ID"){
id
someBarField
}
}
because Foo doesn't have that field. Instead we have to utilize a fragment, like this:
query OperationName {
getNode(id: "SOME_ID"){
id
... on Bar {
someBarField
}
}
}
Then someBarField will be returned, but only if the field resolves to the type Bar. If it's a Foo, only id will be returned. Similarly, you can request non-shared fields from any type implementing the same interface:
query OperationName {
getNode(id: "SOME_ID"){
id
... on Bar {
someBarField
}
... on Foo {
someFooField
}
}
}
Last but not least, it should be mentioned that unions work in a very similar fashion. However, unlike interfaces, there are no shared fields defined for a Union, so a type does not "implement" a union, it just is part of one. That means when requesting a field that returns a union, you'll always have to use fragments since there are no shared fields to request.

Categories