Why doesn't my NEAR smart-contract function return an object (AssemblyScript)? - nearprotocol

I have a function in my smart contract that looks like this
index.ts
class MsgCode {
msg: string;
code: i32;
}
#nearBindgen
export class Contract {
getFoo(): MsgCode {
return {code: 0, msg: 'foo'};
}
getBar(): string {
return 'bar'
}
}
When I call this function through the near-api-js library like this, I'm not receiving anything from the result
contract = await new Contract(account, CONTRACT_NAME, {
viewMethods: ['getFoo', 'getBar'],
});
const getData = () => {
return contract.getFoo().then(res => console.log(res)); // prints nothing. Expect to print {msg:'foo', code:0}
return contract.getBar().then(res => console.log(res)); // prints bar
};
I'm expecting getFoo() to return {msg:'foo', code:0} when I call it on the client, but I receive nothing. What am I doing wrong?

The class type that we want to return from our function also needs to use the #nearBindgen annotation. Otherwise, it won't be serialized. https://docs.near.org/docs/develop/contracts/as/intro#models-define-new-types
#nearBindgen // Added annotation
class MsgCode {
msg: string;
code: i32;
}

Related

How to filter the data from dynamo db when the key is not a partition or Sort key with Node.js and typescript?

My table looks like [alias, inheritedLdap, LdapGroup ] here alias is the string and the LdapGroup is the List form eg: [{S:aws}]. So basically my use case is to get the list of aliases whose ldapGroup is aws. Here the alias is the partition key, we don't have the sort key. So I need to write a method which takes the ldapGroup as the parameter and filter the list of the alias when the ldapGroup is aws. But ldapGroup doesn't contain scalar values. I tried to implement the code but its failing when I try to compile,
public async getMemberList(): Promise<any> {
const input: any = {
TableName: UserInfoDao.TABLE_NAME, // use this from the constants
ProjectionExpression: "alias",
FilterExpression: "#l = :ldapGroups",
ExpressionAttributeNames: {
"#l": "ldapGroups"
},
ExpressionAttributeValues: {
":ldapGroups": "PPOA"
}
};
try {
const ddbClient = DynamDBClient.getInstance();
return await ddbClient.scan(input);
} catch (error) {
const message = `ERROR: Failed to retrieve alias for given ldapGroups:
ERROR: ${JSON.stringify(error)}`;
error.message = message;
throw error;
}
}
But when I use the ScanCommandOutput and ScanCommadInput in my code instead of any, its shows the error that the
Type 'Record<string, AttributeValue>[] | undefined' is not assignable to type 'ScanCommandInput'. Type 'undefined' is not assignable to type 'ScanCommandInput'
Property '$metadata' is missing in type 'Request<ScanOutput, AWSError>' but required in type 'ScanCommandOutput'.
Can someone help me with this one.
I am expecting whether my approach is correct or not
This works for me, I made some edits you your example:
import { DynamoDBClient } from "#aws-sdk/client-dynamodb";
import { ScanCommand, ScanCommandInput } from "#aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({
region: 'eu-west-1',
});
class MyClass {
public getMemberList(): Promise<any> {
const input: ScanCommandInput = {
TableName: 'Test1',
// ProjectionExpression: "alias",
FilterExpression: "contains(#l, :ldapGroups)",
ExpressionAttributeNames: {
"#l": "ldapGroups"
},
ExpressionAttributeValues: {
":ldapGroups": "aws"
}
};
try {
return client.send(new ScanCommand(input))
} catch (error) {
const message = `ERROR: Failed to retrieve alias for given ldapGroups: ERROR: ${JSON.stringify(error)}`;
error.message = message;
throw error;
}
}
}
const c = new MyClass();
c.getMemberList().then(res => console.log(res)).catch(err => console.log(err));

TypeGraphQL - Not able to match all the interfaces of a union

Summary
The goal is to declare the return type of a mutation using a union in order to express multiple states: Success and user errors
Being able to select concrete types according to the use cases:
mutation($data: CreateUserInput!) {
createUser(data: $data){
... on CreateUserSuccess {
user {
id
}
}
... on EmailTakenError {
emailWasTaken
}
... on UserError {
code
message
}
}
}
Implementation using TypeGraphQ:
#ObjectType()
class CreateUserSuccess {
#Field(() => User)
user: User
}
#ObjectType()
class EmailTakenError {
#Field()
emailWasTaken: boolean
}
const mapMutationValueKeyToObjectType = {
user: CreateUserSuccess,
code: UserError,
emailWasTaken: EmailTakenError
}
const CreateUserPayload = createUnionType({
name: 'CreateUserPayload',
types: () => [CreateUserSuccess, EmailTakenError, UserError] as const,
resolveType: mutationValue => {
const mapperKeys = Object.keys(mapMutationValueKeyToObjectType)
const mutationValueKey = mapperKeys.find((key) => key in mutationValue)
return mapMutationValueKeyToObjectType[mutationValueKey]
}
})
#InputType()
class CreateUserInput implements Partial<User> {
#Field()
name: string
#Field()
email: string
}
#Resolver(User)
export class UserResolver {
#Mutation(() => CreateUserPayload)
createUser (#Arg('data', {
description: 'Represents the input data needed to create a new user'
}) createUserInput: CreateUserInput) {
const { name, email } = createUserInput
return createUser({ name, email })
}
}
Data layer
export const createUser = async ({
name, email
}: { name: string; email: string; }) => {
const existingUser = await dbClient.user.findUnique({
where: {
email
}
})
if (existingUser) {
return {
code: ErrorCode.DUPLICATE_ENTRY,
message: "There's an existing user with the provided email.",
emailWasTaken: true
}
}
return dbClient.user.create({
data: {
name,
email
}
})
}
Issue
The response doesn't resolve all of the selected fields according to their unions, even by returning fields that are related to different types
if (existingUser) {
return {
code: ErrorCode.DUPLICATE_ENTRY,
message: "There's an existing user with the provided email.",
emailWasTaken: true
}
}
My doubt is this case is, why emailWasTaken is not being returned within the response if the EmailTakenError type is being selected?
This was an interpretation mistake on my part
The reasoning is that resolvers with a union type as the return definition should indeed just return one of those, in the case above, UserError and EmailTakenError wouldn't be returned on the same response
More info on this GitHub discussion

Object still frozen when using Immer and NGXS

I am implementing ngxs into an existing codebase.
I have imported Immer and then ngxs bridge in hopes to handle side effects easier.
I've followed every example that I can find through google, I always get:
core.js:6014 ERROR TypeError: Cannot assign to read only property 'savedPrograms' of object '[object Object]'
I've tried using the #ImmutableContext() decorator to accont for this, but I get the exact same error. I also tried using the produce method, but when i give draft.savedPrograms a new value, it throws the error above.
#Action(UserActions.AddProgram)
#ImmutableContext()
public addProgram(ctx: StateContext<UserStateModel>, action) {
ctx.setState(
produce((draft: UserStateModel) => {
draft.user.savedPrograms = action.payload;
})
);
}
The only way i can get this to work is if i use JSON parse/stringify to create a copy of the user and then update the user object.
#Action(UserActions.AddProgram)
public addProgram(ctx: StateContext<UserStateModel>, action) {
const state = produce(draft => {
const copy = JSON.parse(JSON.stringify(draft));
copy.user.savedPrograms.push(action.payload);
draft = copy;
});
ctx.setState(state);
}
I'm not quite sure why ImmutableContext doesn't work out of the box
From immer-adapter documentation:
import { ImmutableContext, ImmutableSelector } from '#ngxs-labs/immer-adapter';
#State<AnimalsStateModel>({
name: 'animals',
defaults: {
zebra: {
food: [],
name: 'zebra'
},
panda: {
food: [],
name: 'panda'
}
}
})
export class AnimalState {
#Selector()
#ImmutableSelector()
public static zebraFood(state: AnimalsStateModel): string[] {
return state.zebra.food.reverse();
}
#Action(Add)
#ImmutableContext()
public add({ setState }: StateContext<AnimalsStateModel>, { payload }: Add): void {
setState((state: AnimalsStateModel) => ({
state.zebra.food.push(payload);
return state;
}));
}
}
As I understand you need to remove produce from your code:
#Action(UserActions.AddProgram)
#ImmutableContext()
public addProgram(ctx: StateContext<UserStateModel>, action) {
ctx.setState(
(draft: UserStateModel) => {
draft.user.savedPrograms = action.payload;
return draft;
}
);
}

Graphql directives doesn't work for mutation input arguments when pass the arguments as external variables

I am implementing custom Graphql directives to validate client input. A sample code as below, I referred to the official examples here: https://www.apollographql.com/docs/apollo-server/schema/creating-directives/#enforcing-value-restrictions
const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
const { GraphQLScalarType, GraphQLNonNull } = require('graphql');
const typeDefs = gql`
directive #validateInput on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION
type Mutation {
sampleMutation(
test1: String #validateInput
nestedInput: SampleMutationInput
): String
}
input SampleMutationInput {
test2: String #validateInput
}
`;
The implementation of the directive logic:
class ValidateInputDirective extends SchemaDirectiveVisitor {
visitInputFieldDefinition(field) {
this.wrapType(field);
}
visitFieldDefinition(field) {
this.wrapType(field);
}
visitArgumentDefinition(argument) {
console.log('visitArgumentDefinition', argument);
this.wrapType(argument);
}
wrapType(field) {
console.log('wrapType', field);
if (
field.type instanceof GraphQLNonNull &&
field.type.ofType instanceof GraphQLScalarType
) {
field.type = new GraphQLNonNull(
new ValidateInputType(field.type.ofType)
);
} else if (field.type instanceof GraphQLScalarType) {
field.type = new ValidateInputType(field.type);
} else {
throw new Error(`Not a scalar type: ${field.type}`);
}
}
}
class ValidateInputType extends GraphQLScalarType {
constructor(type) {
super({
name: 'ValidatedInput',
serialize(value) {
return value;
},
parseValue(value) {
const result = type.parseValue(value);
if (/[?!]/.test(result)) {
throw new Error('Invalid characters');
}
return result;
},
parseLiteral(ast) {
const result = type.parseLiteral(ast);
if (/[?!]/.test(result)) {
throw new Error('Invalid characters');
}
return result;
},
});
}
}
export default { validateInput: ValidateInputDirective };
It works as expected for the input field 'test2', but for the argument 'test1', it works when the String value is directly passed to the mutation, then the method "parseLiteral" is called and the validation logic applied to the input value. However, when I pass the 'test1' value as external variables (via JSON format), the directive doesn't work and the method "parserValue" never be called.
What I found so far:
"parserValue" is used when the input comes from variable JSON. "parseLiteral" is used when the input comes directly from the query/mutation.
It seems a bug in Graphql tools according to https://github.com/ardatan/graphql-tools/issues/789
I want to understand:
what's the real difference between an argument passed by variable and directly pass to mutation?
is there an alternate way to apply the directives to an argument to avoid this issue?
If this is really a bug with Graphql, does it fixed now? Which version should I use to resolve the issue?

Retrieve data from RxJS observable in HttpModule

I am failing to understand how to map the data properties out of HttpService in my NestJS application. To my understanding, this Observable just wraps axios. Here's some example code:
interface Todo {
task: string,
completed: false
}
import {
Injectable,
HttpService,
Logger,
NotFoundException,
} from '#nestjs/common'
import { map } from 'rxjs/operators
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp
}
resp in this case seems to be of type Observable. How do I retrieve just the data properties I want using map on this request to return my Todo interface?
Nest by default will subscribe to the observable for you it your return the Observable from your service. As this can be the case you can do something like
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Observable<Todo> {
return this.http.get(todoUrl).pipe(
map(resp => resp.data),
);
}
}
And so long as you have a controller class calling this.todoSerivce.getTodos(todoUrl) and returning it, the response will be sent out.
However, if you want to instead make it a promise as you are more accustomed to them, you can tack on a .toPromise() method to the observable chain and now it it awaitable (though it will be slower because it has to wait for the observable to emit its complete event).
Example with .toPromise():
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Todo {
const myTodo = await this.http.get(todoUrl).pipe(
map(resp => resp.data),
).toPromise();
return myTodo;
}
}
Edit 1/20/22
In RxJS#^7, toPromise() is deprecated and will be removed in v8. Instead, you can use lastValueFrom to wrap the entire observable
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Todo {
const myTodo = await lastValueFrom(this.http.get(todoUrl).pipe(
map(resp => resp.data),
));
return myTodo;
}
}
Looking at your code:
import { map } from 'rxjs/operators'
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp
}
getTodo returns an Observable, not the response. So your return value should be Observable<Todo>.
The code should look more like this:
getTodo(): Observable<Todo> {
return this.http.get<Todo>('https://example.com/todo_json')
.pipe(
map(response => response.data),
catchError(this.handleError)
);
}
EDIT: You can't just return the data from this method because it is asynchronous. It does not have the data yet. The method returns an Observable ... which is basically a contract saying that it will (at some later time) return the data for you.
Async functions need to return a promise, you can call toPromise on an observable to return a promise.
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp.toPromise();
}
async getTodo(todoUrl: string): Todo {
const resp = await this.httpService
.get('https://example.com/todo_json')
.toPromise();
return resp.data;
}

Resources