graphql-compose / graphql-compose-mongoose

Mongoose model converter to GraphQL types with resolvers for graphql-compose https://github.com/nodkz/graphql-compose
MIT License
708 stars 94 forks source link

wrapResolverResolve does not work with composeMongoose #375

Open rafaneri opened 2 years ago

rafaneri commented 2 years ago

I'm trying to use wrapResolverResolve for an ObjectTypeComposer created using composeMongoose function, but I get the error:

Error: Type User does not have resolver with name 'createOne'
import { graphql } from 'graphql';
import { Schema, model, connect, connection, disconnect } from 'mongoose';
import { composeMongoose } from 'graphql-compose-mongoose';
import { schemaComposer } from 'graphql-compose';

// STEP 1: DEFINE MONGOOSE SCHEMA AND MODEL
const LanguagesSchema = new Schema({
    language: String,
    skill: {
        type: String,
        enum: ['basic', 'fluent', 'native'],
    },
});

const UserSchema = new Schema({
    name: String, // standard types
    age: {
        type: Number,
        index: true,
    },
    ln: {
        type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
        default: [],
        alias: 'languages', // in schema `ln` will be named as `languages`
    },
    contacts: { // another mongoose way for providing embedded documents
        email: String,
        phones: [String], // array of strings
    },
    gender: { // enum field with values
        type: String,
        enum: ['male', 'female'],
    },
    someMixed: {
        type: Schema.Types.Mixed,
        description: 'Can be any mixed type, that will be treated as JSON GraphQL Scalar Type',
    },
});
const User = model('User', UserSchema);

// STEP 2: CONVERT MONGOOSE MODEL TO GraphQL PIECES
const customizationOptions = {}; // left it empty for simplicity, described below
const UserTC = composeMongoose(User, customizationOptions);

// STEP 3: ADD NEEDED CRUD USER OPERATIONS TO THE GraphQL SCHEMA
// via graphql-compose it will be much much easier, with less typing
schemaComposer.Query.addFields({
    userById: UserTC.mongooseResolvers.findById(),
    userByIds: UserTC.mongooseResolvers.findByIds(),
    userMany: UserTC.mongooseResolvers.findMany(),
});

schemaComposer.Mutation.addFields({
    userCreateOne: UserTC.mongooseResolvers.createOne(),
    userUpdateOne: UserTC.mongooseResolvers.updateOne(),
});

UserTC.wrapResolverResolve('createOne', next => async rp => {

    // extend resolve params with hook
    rp.beforeRecordMutate = async (doc: any, resolveParams: any) => {
        console.log(doc, resolveParams)
    };

    return next(rp);
});

// STEP 4: BUILD GraphQL SCHEMA OBJECT
const schema = schemaComposer.buildSchema();
export default schema;

// STEP 5: DEMO USE OF GraphQL SCHEMA OBJECT
// Just a demo, normally you'd pass schema object to server such as Apollo server.

(async () => {
    await connect('mongodb://localhost:27017/test');
    await connection.dropDatabase();

    await User.create({ name: 'alice', age: 29, gender: 'female' });
    await User.create({ name: 'maria', age: 31, gender: 'female' });
    const bob = await User.create({ name: 'bob', age: 30, gender: 'male' });

    const response1 = await graphql({
        schema,
        source: 'query { userMany { _id name } }',
    });
    console.dir(response1, { depth: 5 });

    const response2 = await graphql({
        schema,
        source: 'query($id: MongoID!) { userById(_id: $id) { _id name } }',
        variableValues: { id: bob._id },
    });
    console.dir(response2, { depth: 5 });

    const response3 = await graphql({
        schema,
        source: 'mutation($id: MongoID!, $name: String) { userUpdateOne(filter: {_id: $id}, record: { name: $name }) { record { _id name } } }',
        variableValues: { id: bob._id, name: 'bill' },
    });
    console.dir(response3, { depth: 5 });

    disconnect();
})();

But when I use the same but with a composeWithMongoose it works. (example bellow)

import { graphql } from 'graphql';
import { Schema, model, connect, connection, disconnect } from 'mongoose';
import { composeWithMongoose } from 'graphql-compose-mongoose';
import { schemaComposer } from 'graphql-compose';

// STEP 1: DEFINE MONGOOSE SCHEMA AND MODEL
const LanguagesSchema = new Schema({
    language: String,
    skill: {
        type: String,
        enum: ['basic', 'fluent', 'native'],
    },
});

const UserSchema = new Schema({
    name: String, // standard types
    age: {
        type: Number,
        index: true,
    },
    ln: {
        type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
        default: [],
        alias: 'languages', // in schema `ln` will be named as `languages`
    },
    contacts: { // another mongoose way for providing embedded documents
        email: String,
        phones: [String], // array of strings
    },
    gender: { // enum field with values
        type: String,
        enum: ['male', 'female'],
    },
    someMixed: {
        type: Schema.Types.Mixed,
        description: 'Can be any mixed type, that will be treated as JSON GraphQL Scalar Type',
    },
});
const User = model('User', UserSchema);

// STEP 2: CONVERT MONGOOSE MODEL TO GraphQL PIECES
const customizationOptions = {}; // left it empty for simplicity, described below
const UserTC = composeWithMongoose(User, customizationOptions);

// STEP 3: ADD NEEDED CRUD USER OPERATIONS TO THE GraphQL SCHEMA
// via graphql-compose it will be much much easier, with less typing
schemaComposer.Query.addFields({
    userById: UserTC.getResolver('findById'),
    userByIds: UserTC.getResolver('findByIds'),
    userMany: UserTC.getResolver('findMany'),
});

schemaComposer.Mutation.addFields({
    userCreateOne: UserTC.getResolver('createOne'),
    userUpdateOne: UserTC.getResolver('updateOne'),
});

UserTC.wrapResolverResolve('createOne', next => async rp => {

    // extend resolve params with hook
    rp.beforeRecordMutate = async (doc: any, resolveParams: any) => {
        console.log(doc, resolveParams)
    };

    return next(rp);
});

// STEP 4: BUILD GraphQL SCHEMA OBJECT
const schema = schemaComposer.buildSchema();
export default schema;

// STEP 5: DEMO USE OF GraphQL SCHEMA OBJECT
// Just a demo, normally you'd pass schema object to server such as Apollo server.

(async () => {
    await connect('mongodb://localhost:27017/test');
    await connection.dropDatabase();

    await User.create({ name: 'alice', age: 29, gender: 'female' });
    await User.create({ name: 'maria', age: 31, gender: 'female' });
    const bob = await User.create({ name: 'bob', age: 30, gender: 'male' });

    const response1 = await graphql({
        schema,
        source: 'query { userMany { _id name } }',
    });
    console.dir(response1, { depth: 5 });

    const response2 = await graphql({
        schema,
        source: 'query($id: MongoID!) { userById(_id: $id) { _id name } }',
        variableValues: { id: bob._id },
    });
    console.dir(response2, { depth: 5 });

    const response3 = await graphql({
        schema,
        source: 'mutation($id: MongoID!, $name: String) { userUpdateOne(filter: {_id: $id}, record: { name: $name }) { record { _id name } } }',
        variableValues: { id: bob._id, name: 'bill' },
    });
    console.dir(response3, { depth: 5 });

    disconnect();
})();
m-lyon commented 5 months ago

For those viewing this issue, a workaround is to call the .wrapResolve method on the returned resolver, e.g.

UserTC.mongooseResolvers.createOne().wrapResolve((next) => async (rp) => {

    // extend resolve params with hook
    rp.beforeRecordMutate = async (doc: any, resolveParams: any) => {
        console.log(doc, resolveParams)
    };

    return next(rp);
});