Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] - Feature: Discriminator Support on composeMongoose #340

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
643bae0
Switch type check to use 'instanceof' instead of constructor.name for…
jhbuchanan45 May 10, 2021
afe10e6
Fix linting error in case block
jhbuchanan45 May 12, 2021
8d9bdc6
Merge branch 'graphql-compose:master' into master
jhbuchanan45 May 21, 2021
7d11ae9
Implement optional discriminator handling inside composeMongoose func…
jhbuchanan45 May 24, 2021
0799c4e
Add unit test cases for enhanced discriminator handling
jhbuchanan45 May 24, 2021
fc20db7
Refactor input TC field adding into class method
jhbuchanan45 May 25, 2021
e602693
Export EDiscriminatorTypeComposer index exports and fix type error wi…
jhbuchanan45 May 25, 2021
d586798
Ensure correct types for DTCs are referenced in resolvers using tc.ge…
jhbuchanan45 May 25, 2021
16a5140
Fix bugs with discrimTC resolvers, such as preferring model over scha…
jhbuchanan45 May 26, 2021
425fbb4
Add test case for getDiscriminatorTCs()
jhbuchanan45 May 26, 2021
3cd5c33
Override makeFieldNullable/NonNull() for eDTC with tests
jhbuchanan45 May 27, 2021
01a7d71
Merge branch 'graphql-compose:master' into master
jhbuchanan45 Jun 10, 2021
1523524
Refactor calls to schemaComposer.getAnyTC(...) to use only tc.getType…
jhbuchanan45 Jun 10, 2021
e4b22a5
Merge branch 'graphql-compose:master' into master
jhbuchanan45 Jul 9, 2021
595b147
Merge changes from upstream (Especially allowing resolvers to accept …
jhbuchanan45 Jul 27, 2021
5c8bde5
Merge branch 'master' of https://github.com/graphql-compose/graphql-c…
jhbuchanan45 Jul 27, 2021
14d82de
Change eDTC to extend InterfaceTypeComposer and fix various resulting…
jhbuchanan45 Jul 29, 2021
7d29a85
Tidy eDTC code
jhbuchanan45 Jul 29, 2021
0167cb2
Revert resolvers using tc.getTypeName() instead of directly passing t…
jhbuchanan45 Jul 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
useJSXTextNode: true,
project: [path.resolve(__dirname, 'tsconfig.json')],
},
root: true,
rules: {
'no-underscore-dangle': 0,
'arrow-body-style': 0,
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"typescript.format.enable": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"eslint-plugin-import": "2.23.4",
"eslint-plugin-prettier": "3.4.0",
"graphql": "15.5.1",
"graphql-compose": "9.0.1",
"graphql-compose": "9.0.2",
"jest": "27.0.6",
"mongodb-memory-server": "7.3.2",
"mongoose": "5.13.3",
Expand Down
2 changes: 1 addition & 1 deletion src/__mocks__/mongooseCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mongoose.Promise = Promise;
const originalConnect = mongoose.connect;
mongoose.createConnection = (async () => {
const mongoServer = await MongoMemoryServer.create();
const mongoUri = mongoServer.getUri();
const mongoUri = await mongoServer.getUri();

// originalConnect.bind(mongoose)(mongoUri, { useMongoClient: true }); // mongoose 4
originalConnect.bind(mongoose)(mongoUri, {
Expand Down
6 changes: 5 additions & 1 deletion src/__tests__/github_issues/260-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { schemaComposer, graphql } from 'graphql-compose';
import { schemaComposer, graphql, ObjectTypeComposer } from 'graphql-compose';
import { composeMongoose } from '../../index';
import { mongoose } from '../../__mocks__/mongooseCommon';
import { Document } from 'mongoose';
Expand Down Expand Up @@ -28,6 +28,10 @@ const PostModel = mongoose.model<IPost>('Post', PostSchema);
const UserTC = composeMongoose(UserModel);
const PostTC = composeMongoose(PostModel);

if (!(UserTC instanceof ObjectTypeComposer) || !(PostTC instanceof ObjectTypeComposer)) {
throw new Error('TCs should return ObjectTypeComposers');
}

PostTC.addRelation('author', {
resolver: UserTC.mongooseResolvers.dataLoader({
lean: true, // <---- `Lean` loads record from DB without support of mongoose getters & virtuals
Expand Down
6 changes: 5 additions & 1 deletion src/__tests__/github_issues/263-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { schemaComposer, graphql } from 'graphql-compose';
import { schemaComposer, graphql, ObjectTypeComposer } from 'graphql-compose';
import { composeMongoose } from '../../index';
import { mongoose } from '../../__mocks__/mongooseCommon';

Expand Down Expand Up @@ -28,6 +28,10 @@ const PostModel = mongoose.model<IPost>('Post', PostSchema);
const UserTC = composeMongoose(UserModel);
const PostTC = composeMongoose(PostModel);

if (!(UserTC instanceof ObjectTypeComposer) || !(PostTC instanceof ObjectTypeComposer)) {
throw new Error('TCs should return ObjectTypeComposers');
}

PostTC.addRelation('author', {
resolver: UserTC.mongooseResolvers.dataLoader({ lean: true }),
prepareArgs: {
Expand Down
27 changes: 21 additions & 6 deletions src/composeMongoose.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import type { SchemaComposer, Resolver, InputTypeComposer } from 'graphql-compose';
import type {
SchemaComposer,
Resolver,
InputTypeComposer,
InterfaceTypeComposer,
} from 'graphql-compose';
import { schemaComposer as globalSchemaComposer, ObjectTypeComposer } from 'graphql-compose';
import type { Model, Document } from 'mongoose';
import { EDiscriminatorTypeComposer } from './enhancedDiscriminators';
import { convertModelToGraphQL } from './fieldsConverter';
import { resolverFactory } from './resolvers';
import MongoID from './types/MongoID';
Expand Down Expand Up @@ -71,6 +77,15 @@ export type ComposeMongooseOpts<TContext = any> = {
* You can make fields as NonNull if they have default value in mongoose model.
*/
defaultsAsNonNull?: boolean;
/**
* Support discriminators on base models
*/
includeBaseDiscriminators?: boolean;
/**
* EXPERIMENTAL - Support discriminated fields on nested fields
* May not work as expected on input types for mutations
*/
includeNestedDiscriminators?: boolean;

/** @deprecated */
fields?: {
Expand Down Expand Up @@ -101,7 +116,7 @@ export type GenerateResolverType<TDoc extends Document, TContext = any> = {
export function composeMongoose<TDoc extends Document, TContext = any>(
model: Model<TDoc>,
opts: ComposeMongooseOpts<TContext> = {}
): ObjectTypeComposer<TDoc, TContext> & {
): (ObjectTypeComposer<TDoc, TContext> | EDiscriminatorTypeComposer<TDoc, TContext>) & {
mongooseResolvers: GenerateResolverType<TDoc, TContext>;
} {
const m: Model<any> = model;
Expand All @@ -121,7 +136,7 @@ export function composeMongoose<TDoc extends Document, TContext = any>(
sc.delete(m.schema);
}

const tc = convertModelToGraphQL(m, name, sc);
const tc = convertModelToGraphQL(m, name, sc, { ...opts });

if (opts.description) {
tc.setDescription(opts.description);
Expand Down Expand Up @@ -152,7 +167,7 @@ export function composeMongoose<TDoc extends Document, TContext = any>(
}

function makeFieldsNonNullWithDefaultValues(
tc: ObjectTypeComposer,
tc: ObjectTypeComposer | InterfaceTypeComposer,
alreadyWorked = new Set()
): void {
if (alreadyWorked.has(tc)) return;
Expand Down Expand Up @@ -188,7 +203,7 @@ function makeFieldsNonNullWithDefaultValues(
}

export function prepareFields(
tc: ObjectTypeComposer<any, any>,
tc: ObjectTypeComposer<any, any> | InterfaceTypeComposer<any, any>,
opts: ComposeMongooseOpts<any> = {}
): void {
const onlyFields = opts?.onlyFields || opts?.fields?.only;
Expand All @@ -202,7 +217,7 @@ export function prepareFields(
}

export function createInputType(
tc: ObjectTypeComposer<any, any>,
tc: ObjectTypeComposer<any, any> | InterfaceTypeComposer<any, any>,
inputTypeOpts: TypeConverterInputTypeOpts = {}
): void {
const inputTypeComposer = tc.getInputTypeComposer();
Expand Down
11 changes: 9 additions & 2 deletions src/composeWithMongoose.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-use-before-define, no-param-reassign, global-require */

import type { ObjectTypeComposer, SchemaComposer } from 'graphql-compose';
import { schemaComposer as globalSchemaComposer } from 'graphql-compose';
import type { SchemaComposer } from 'graphql-compose';
import { ObjectTypeComposer, schemaComposer as globalSchemaComposer } from 'graphql-compose';
import type { Model, Document } from 'mongoose';
import { convertModelToGraphQL } from './fieldsConverter';
import { resolverFactory, AllResolversOpts } from './resolvers';
Expand Down Expand Up @@ -53,6 +53,13 @@ export function composeWithMongoose<TDoc extends Document, TContext = any>(

const tc = convertModelToGraphQL(m, name, sc);

if (!(tc instanceof ObjectTypeComposer)) {
// should be impossible I hope
throw new Error(
'composeWithMongoose does not support discriminator mode, use composeMongoose or composeWithMongooseDiscriminators instead'
);
}

if (opts.description) {
tc.setDescription(opts.description);
}
Expand Down
4 changes: 3 additions & 1 deletion src/discriminators/__mocks__/characterModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export const CharacterObject = {
const CharacterSchema = new Schema(CharacterObject);
const ACharacterSchema = new Schema({ ...CharacterObject });

export function getCharacterModels(DKey: string): {
export function getCharacterModels(
DKey: string
): {
CharacterModel: Model<any>;
PersonModel: Model<any>;
DroidModel: Model<any>;
Expand Down
3 changes: 2 additions & 1 deletion src/discriminators/prepareBaseResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function prepareBaseResolvers(baseTC: DiscriminatorTypeComposer<any, any>
});
break;

case 'connection':
case 'connection': {
const edgesTC = resolver
.getOTC()
.getFieldOTC('edges')
Expand All @@ -91,6 +91,7 @@ export function prepareBaseResolvers(baseTC: DiscriminatorTypeComposer<any, any>

resolver.getOTC().setField('edges', edgesTC.NonNull.List.NonNull);
break;
}

default:
}
Expand Down
97 changes: 97 additions & 0 deletions src/enhancedDiscriminators/__mocks__/characterDiscrimModels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import type { Model } from 'mongoose';
import { mongoose, Schema, Types } from '../../__mocks__/mongooseCommon';

export const PersonSchema = new Schema({
dob: Number,
primaryFunction: [String],
starShips: [String],
totalCredits: Number,
});

export const DroidSchema = new Schema({
makeDate: Date,
modelNumber: Number,
primaryFunction: [String],
});

const MovieSchema = new Schema({
_id: String,

characters: {
type: [String], // redundant but i need it.
description: 'A character in the Movie, Person or Droid.',
},

director: {
type: String, // id of director
description: 'Directed the movie.',
},

imdbRatings: String,
releaseDate: String,
});

export const MovieModel = mongoose.model('Movie', MovieSchema);

const enumCharacterType = {
PERSON: 'Person',
DROID: 'Droid',
};

export const CharacterObject = {
_id: {
type: String,
default: (): any => new Types.ObjectId(),
},
name: String,

type: {
type: String,
require: true,
enum: Object.keys(enumCharacterType),
},
kind: {
type: String,
require: true,
enum: Object.keys(enumCharacterType),
},

friends: [String], // another Character
appearsIn: [String], // movie
};

export function getCharacterModels(
DKey?: string,
name: string = 'Character'
): { CharacterModel: Model<any>; PersonModel: Model<any>; DroidModel: Model<any> } {
const CharacterSchema = DKey
? new Schema(CharacterObject, { discriminatorKey: DKey })
: new Schema(CharacterObject);

const CharacterModel: Model<any> = mongoose.model(name, CharacterSchema);

const PersonModel: Model<any> = CharacterModel.discriminator(
name + enumCharacterType.PERSON,
PersonSchema
);

const DroidModel: Model<any> = CharacterModel.discriminator(
name + enumCharacterType.DROID,
DroidSchema
);

return { CharacterModel, PersonModel, DroidModel };
}

export function getCharacterModelClone(): { NoDKeyCharacterModel: Model<any> } {
const ACharacterSchema = new Schema({ ...CharacterObject });
const NoDKeyCharacterModel = mongoose.model('NoDKeyCharacter', ACharacterSchema);

/*
const APersonModel = ACharacterModel.discriminator('A' + enumCharacterType.PERSON, PersonSchema.clone());

const ADroidModel = ACharacterModel.discriminator('A' + enumCharacterType.DROID, DroidSchema.clone());
*/

return { NoDKeyCharacterModel }; // APersonModel, ADroidModel };
}
Loading