Ecmascript function parameter with enum value - javascript

Is it possible to have an enum value of parameter in Ecmascript method?
For example for this case
export const testFunc = (param1) => {
};
For example, param can only take values of "val1","val2","val3"
export const testFunc = (param = {"val1","val2","val3"}) =>{
};

There no such thing as an enum in JS, but you could just check to see if the parameter is one of the allowed values:
export const testFunc = (param) =>{
if (!["val1","val2","val3"].includes(param)) {
throw new Error('Invalid param passed');
}
// rest of function
};

As Snow says, JavaScript doesn't have enums. TypeScript does (details), so if you want enums (and various other features), you might consider using TypeScript, which compiles to JavaScript.
If you don't want to go that route, you can always define an enum-like object:
const TheEnum = {
val1: 0,
val2: 1,
val3: 2,
0: "val1",
1: "val2",
2: "val3",
valid(value) {
return typeof param === "number" && !!TheEnum[param];
}
};
...and then validate the value you receive:
export const testFunc = (param) => {
if (!TheEnum.valid(param)) {
throw new Error("'param' must be a TheEnum value");
}
// ...
};
Note that that example "enum" has mappings both from symbolic names (val1, etc.0 to the values and from values to symbolic names. They do the in TypeScript, too; it's handy for when you want to show the name "val1" instead of the value 0 in messages, etc.

Related

How to write a conditional promise.resolve properly in typescript?

I have a project in react native using typescript and I would like to use Promise.resolve().then() of an object with a condition which causing a type error in typescript. Below is the situation:
I have the first object (as sample):
const objectA = async () => {
// get the object from local storage
const user = await asyncStorage.getItem(userAKey)
return {
// types are all in string
id: user?.id || '',
email: user?.email || '',
}
}
And I have my second object (as sample):
const objectB = async () => {
// get the object from local storage
const user = await asyncStorage.getItem(userBKey)
return {
// types are all in string
id: user?.id || '',
email: user?.email || '',
organisation: user?.organisation || ''
}
}
So both objects have different types and keys with value. Here is the problem, I have a condition to check which object to be resolve based on some host type:
const objectTobeResolved = type === 'primary' ? objectA : objectB
So based on that condition, I pass it to the Promise:
const userData = () => {
Promise.resolve(objectTobeResolved).then((values) => { ... })
}
I will get the type error here in the typescript for objectTobeResolved.
Basically the type error here is mentioning something like objectA | objectB, but I actually only need to pass one of the object based on the condition above and I don't need the objectA | objectB condition as it is only either objectA or objectB based on the declared host type I had in the app launcher.
I don't quite understand how to declare it properly in typescript since I have just learned to use Typescript.
What is a right way to write the promise here? should I use race or all instead? But I only need to check one object at a time. Or should I declare or initialise in a certain way? Or maybe I don't even need to use promise at all?
Edit: I have updated the codes above to my real case scenario.
The codes are actually running correctly, just that the type error keep showing up and I couldn't pass the test. A shortcut is to use //#ts-ignore, but since our practice is to not ignore the warning, so I'm trying to find a correct way to write it without having a warning which causing the test to be failed.
More code would be helpful, but I think what you may be asking about is how to create a union type so you can effectively refer to your result being a this or that. If so, maybe you could use something like this? I believe you could state it a number of other ways too, but I just wanted to illustrate the point.
const objectA = {
a: 1,
b: 2,
}
const objectB = {
c: 3,
d: 4,
e: 5,
}
interface ABtype {
a: Number
b: Number
}
interface CDEtype {
c: Number
d: Number
e: Number
}
interface ABpromiseType extends Promise<ABtype> {
}
interface CDEpromiseType extends Promise<CDEtype> {
}
interface ABCDEunionTypeCombined extends Promise<CDEtype | ABtype> {
}
// Something randomly true or false.
let conditionalCheck = new Date().getTime() % 2 == 0;
const someFunction = () => {
const objectTobeResolved : ABCDEunionTypeCombined = (conditionalCheck ? Promise.resolve(objectA) : Promise.resolve(objectB) );
}

Typescript named function expression

I'm starting to learning typescript applied to node JS backend.
For now i'm still using function and not class.
I used to write named function for each file like
const item= {
a:1,
b:2,
function1:()=>{
console.log(item.a)
},
function2:()=>{
console.log(item.b)
} }
then export it and use like item.function1. (i sometimes use it also as function1 with import as unstructured object)
Now using typescript i'm still using this approach but with types. The problem is that i can't assign a type to a because it's seen as value. I can't heither do :
const item= {
function1:()=>{
item.a = 3
console.log(item.a)
},
function2:()=>{
item.b = 4
console.log(item.b)
}}
because it's saying that property a or b does not exist in type item.
another thing i tried but that doesn't work is:
const item = {
function1:()=>{
item.a:number = 3
console.log(item.a)
},
function2:()=>{
item.b:number = 4
console.log(item.b)
} }
Anyone can help me? hoping that this named functions are not a bad habit for write code
When you declare a variable and don't specify its type, typescript will try to infer what its type is. When you assign an object, it will try to infer the type like this: look at what properties you wrote and consider that the object only has these properties with exactly the types that the object you wrote has. So in your last code example typescript will infer that item is of type
const item: {
function1: () => void
function2: () => void
} = {
// ...
}
You can see that it didn't add any other properties.
Next, if you don't declare a property when typing an object, typescript will think that this property doesn't exist and may not exist on the object. Consider this:
const obj: { foo: number } = {
foo: 6
}
obj.bar = 7 // Error
You didn't declare the bar property, so typescript doesn't allow to read it or assign something to it. This is why you cannot write item.a = 3 in your example: typescript didn't infer that item object has property a and it thinks that it must not exist
To solve this you just need to either assign all properties you will need when creating your object:
const item = {
a: 1,
b: 2,
function1: () => { /* ... */ },
function2: () => { /* ... */ },
}
Or type item manually
interface Item {
a?: number
b?: number
function1: () => void
function2: () => void
}
const item: Item = {
function1: () => {
item.a = 3
},
function2: () => {
item.b = 4
}
}
Note the question marks before the column inside the interface, this is to tell that these properties are optional. If you don't set these question marks, typescript will think these are obligatory, so it will emit an error if you create item and don't declare a and b properties
It is possible to denote item object as any.
To accomplish a desired consistency an interface could be defined as follows
interface ItemObject {
[key: string]: any
}
var item: ItemObject= {};
to make compact:
var item: {[k: string]: any} = {};
now item can accept any string as key and any type as value

Type function's arguments only via Interface TypeScript

How do I type the function's arguments bellow and keep them clean (without Typescript), using Interface?
// external file
export interface TSomeFunctionArgs {
someKey: string
// also here should be a type for a function
}
// main file
import { TSomeFunctionArgs } from "pathToFile"
const someFunction = (args: TSomeFunctionArgs) => {
// ... function's logic
}
someFunction({ someKey: "some string" }, someAnotherFunction)
In my example I'm passing two arguments, the first one is an object with the particular key - value pair, and a second one is a function (it could be any function).
How do I describe it in the Interface above?
In this case you can use generics for, and the code will be something like this:
interface TSomeFunctionArgs {
someKey: string
}
const someFunction = <U, T>(args: U, callback: T) => {
// ... function's logic
}
function someAnotherFunction() {}
// With generics you can give the type of the two parameters when you use it.
someFunction<TSomeFunctionArgs, () => void>({ someKey: "some string" }, someAnotherFunction)

How to best describe this code using TypeScript?

Here is an object with several different key and value, and each props of value differ from each other, how to best describe this object using TypeScript? Especially the setValue method, how to limit type of the creatureType, prop and value?
const object = {
john: {
name: '',
age: 18
},
alien: {
height: 20,
power:100,
},
setValue(creatureType) {
const self = this
return function (prop) {
return function (value) {
self[creatureType][prop] = value
}
}
}
}
Your setValue() method will need to be generic if you want it to place strong restrictions on which properties and values go with which, uh, "creature type". Because the type of the object's setValue() method will be dependent on the type of the other properties of object, the compiler will give up trying to infer types for it; it's too circular for something that isn't a class. Either you could manually annotate all the types, which would be annoying, or you could split object into two pieces, say plainObject holding just the data, and then merge in the setValue() method which will be dependent on the type of plainObject, like this:
const plainObject = {
john: { name: '', age: 18 },
alien: { height: 20, power: 100 }
}
type PlainObject = typeof plainObject;
const object = {
...plainObject,
setValue<K extends keyof PlainObject>(creatureType: K) {
const self: PlainObject = this;
return function <P extends keyof PlainObject[K]>(prop: P) {
return function (value: PlainObject[K][P]) {
self[creatureType][prop] = value;
}
}
}
}
And you can verify that the compiler behaves as you want:
object.setValue("john")("age")(19); // okay
object.setValue("alien")("height")("pretty tall"); // error!
// "pretty tall" isn't numeric --> ~~~~~~~~~~~~~
object.setValue("john")("power")(9000); // error!
// "power" is wrong --> ~~~~~~~
object.setValue("elaine")("name")("elaine"); // error!
// "elaine"? -> ~~~~~~~~
Okay, hope that helps; good luck!
Link to code in Playground

Is there a GraphQLObject or GraphQLAny type in GraphQL? [duplicate]

Is it possible to specify that a field in GraphQL should be a blackbox, similar to how Flow has an "any" type? I have a field in my schema that should be able to accept any arbitrary value, which could be a String, Boolean, Object, Array, etc.
I've come up with a middle-ground solution. Rather than trying to push this complexity onto GraphQL, I'm opting to just use the String type and JSON.stringifying my data before setting it on the field. So everything gets stringified, and later in my application when I need to consume this field, I JSON.parse the result to get back the desired object/array/boolean/ etc.
#mpen's answer is great, but I opted for a more compact solution:
const { GraphQLScalarType } = require('graphql')
const { Kind } = require('graphql/language')
const ObjectScalarType = new GraphQLScalarType({
name: 'Object',
description: 'Arbitrary object',
parseValue: (value) => {
return typeof value === 'object' ? value
: typeof value === 'string' ? JSON.parse(value)
: null
},
serialize: (value) => {
return typeof value === 'object' ? value
: typeof value === 'string' ? JSON.parse(value)
: null
},
parseLiteral: (ast) => {
switch (ast.kind) {
case Kind.STRING: return JSON.parse(ast.value)
case Kind.OBJECT: throw new Error(`Not sure what to do with OBJECT for ObjectScalarType`)
default: return null
}
}
})
Then my resolvers looks like:
{
Object: ObjectScalarType,
RootQuery: ...
RootMutation: ...
}
And my .gql looks like:
scalar Object
type Foo {
id: ID!
values: Object!
}
Yes. Just create a new GraphQLScalarType that allows anything.
Here's one I wrote that allows objects. You can extend it a bit to allow more root types.
import {GraphQLScalarType} from 'graphql';
import {Kind} from 'graphql/language';
import {log} from '../debug';
import Json5 from 'json5';
export default new GraphQLScalarType({
name: "Object",
description: "Represents an arbitrary object.",
parseValue: toObject,
serialize: toObject,
parseLiteral(ast) {
switch(ast.kind) {
case Kind.STRING:
return ast.value.charAt(0) === '{' ? Json5.parse(ast.value) : null;
case Kind.OBJECT:
return parseObject(ast);
}
return null;
}
});
function toObject(value) {
if(typeof value === 'object') {
return value;
}
if(typeof value === 'string' && value.charAt(0) === '{') {
return Json5.parse(value);
}
return null;
}
function parseObject(ast) {
const value = Object.create(null);
ast.fields.forEach((field) => {
value[field.name.value] = parseAst(field.value);
});
return value;
}
function parseAst(ast) {
switch (ast.kind) {
case Kind.STRING:
case Kind.BOOLEAN:
return ast.value;
case Kind.INT:
case Kind.FLOAT:
return parseFloat(ast.value);
case Kind.OBJECT:
return parseObject(ast);
case Kind.LIST:
return ast.values.map(parseAst);
default:
return null;
}
}
For most use cases, you can use a JSON scalar type to achieve this sort of functionality. There's a number of existing libraries you can just import rather than writing your own scalar -- for example, graphql-type-json.
If you need a more fine-tuned approach, than you'll want to write your own scalar type. Here's a simple example that you can start with:
const { GraphQLScalarType, Kind } = require('graphql')
const Anything = new GraphQLScalarType({
name: 'Anything',
description: 'Any value.',
parseValue: (value) => value,
parseLiteral,
serialize: (value) => value,
})
function parseLiteral (ast) {
switch (ast.kind) {
case Kind.BOOLEAN:
case Kind.STRING:
return ast.value
case Kind.INT:
case Kind.FLOAT:
return Number(ast.value)
case Kind.LIST:
return ast.values.map(parseLiteral)
case Kind.OBJECT:
return ast.fields.reduce((accumulator, field) => {
accumulator[field.name.value] = parseLiteral(field.value)
return accumulator
}, {})
case Kind.NULL:
return null
default:
throw new Error(`Unexpected kind in parseLiteral: ${ast.kind}`)
}
}
Note that scalars are used both as outputs (when returned in your response) and as inputs (when used as values for field arguments). The serialize method tells GraphQL how to serialize a value returned in a resolver into the data that's returned in the response. The parseLiteral method tells GraphQL what to do with a literal value that's passed to an argument (like "foo", or 4.2 or [12, 20]). The parseValue method tells GraphQL what to do with the value of a variable that's passed to an argument.
For parseValue and serialize we can just return the value we're given. Because parseLiteral is given an AST node object representing the literal value, we have to do a little bit of work to convert it into the appropriate format.
You can take the above scalar and customize it to your needs by adding validation logic as needed. In any of the three methods, you can throw an error to indicate an invalid value. For example, if we want to allow most values but don't want to serialize functions, we can do something like:
if (typeof value == 'function') {
throw new TypeError('Cannot serialize a function!')
}
return value
Using the above scalar in your schema is simple. If you're using vanilla GraphQL.js, then use it just like you would any of the other scalar types (GraphQLString, GraphQLInt, etc.) If you're using Apollo, you'll need to include the scalar in your resolver map as well as in your SDL:
const resolvers = {
...
// The property name here must match the name you specified in the constructor
Anything,
}
const typeDefs = `
# NOTE: The name here must match the name you specified in the constructor
scalar Anything
# the rest of your schema
`
Just send a stringified value via GraphQL and parse it on the other side, e.g. use this wrapper class.
export class Dynamic {
#Field(type => String)
private value: string;
getValue(): any {
return JSON.parse(this.value);
}
setValue(value: any) {
this.value = JSON.stringify(value);
}
}
For similar problem I've created schema like this:
"""`MetadataEntry` model"""
type MetadataEntry {
"""Key of the entry"""
key: String!
"""Value of the entry"""
value: String!
}
"""Object with metadata"""
type MyObjectWithMetadata {
"""
... rest of my object fields
"""
"""
Key-value entries that you can attach to an object. This can be useful for
storing additional information about the object in a structured format
"""
metadata: [MetadataEntry!]!
"""Returns value of `MetadataEntry` for given key if it exists"""
metadataValue(
"""`MetadataEntry` key"""
key: String!
): String
}
And my queries can look like this:
query {
listMyObjects {
# fetch meta values by key
meta1Value: metadataValue(key: "meta1")
meta2Value: metadataValue(key: "meta2")
# ... or list them all
metadata {
key
value
}
}
}

Categories