diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 10a2b9d1..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,46 +0,0 @@ -const path = require('path'); - -module.exports = { - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'prettier'], - extends: ['plugin:@typescript-eslint/recommended', 'prettier', 'plugin:prettier/recommended'], - parserOptions: { - sourceType: 'module', - useJSXTextNode: true, - project: [path.resolve(__dirname, 'tsconfig.json')], - }, - rules: { - 'no-underscore-dangle': 0, - 'arrow-body-style': 0, - 'no-unused-expressions': 0, - 'no-plusplus': 0, - 'no-console': 0, - 'func-names': 0, - 'comma-dangle': [ - 'error', - { - arrays: 'always-multiline', - objects: 'always-multiline', - imports: 'always-multiline', - exports: 'always-multiline', - functions: 'ignore', - }, - ], - 'no-prototype-builtins': 0, - 'prefer-destructuring': 0, - 'no-else-return': 0, - 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], - '@typescript-eslint/explicit-member-accessibility': 0, - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/no-inferrable-types': 0, - '@typescript-eslint/explicit-function-return-type': 0, - '@typescript-eslint/no-use-before-define': 0, - '@typescript-eslint/no-empty-function': 0, - '@typescript-eslint/camelcase': 0, - '@typescript-eslint/ban-ts-comment': 0, - }, - env: { - jasmine: true, - jest: true, - }, -}; diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index ab09e960..888dfb40 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,10 +1,14 @@ -# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# This workflow will do a clean installation of node dependencies, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: Node.js CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: tests: runs-on: ubuntu-latest @@ -13,17 +17,13 @@ jobs: contents: write strategy: matrix: - node-version: [16.x, 18.x, 20.x] + node-version: [20.x, 22.x, 24.x] steps: - run: echo "🎉 The job was triggered by a ${{ github.event_name }} event." - - uses: styfle/cancel-workflow-action@0.12.0 - with: - workflow_id: nodejs.yml - access_token: ${{ secrets.GITHUB_TOKEN }} - uses: FranzDiebold/github-env-vars-action@v2 - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} - name: Install node_modules @@ -32,6 +32,10 @@ jobs: run: yarn test env: CI: true + - name: Testing with previous mongoose versions 8 + run: yarn test-prev-vers-8 + env: + CI: true - name: Testing with previous mongoose versions 7 run: yarn test-prev-vers-7 env: @@ -41,7 +45,7 @@ jobs: env: CI: true - name: Send codecov.io stats - if: matrix.node-version == '18.x' + if: matrix.node-version == '20.x' run: bash <(curl -s https://codecov.io/bash) || echo '' publish: @@ -53,11 +57,11 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@v4 - - name: Use Node.js 18 - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + - name: Use Node.js 24.x + uses: actions/setup-node@v6 with: - node-version: 18.x + node-version: 24.x - name: Install node_modules run: yarn install - name: Build diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..f5dca0d9 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,83 @@ +const { + defineConfig, + globalIgnores, +} = require("eslint/config"); + +const tsParser = require("@typescript-eslint/parser"); +const typescriptEslint = require("@typescript-eslint/eslint-plugin"); +const prettier = require("eslint-plugin-prettier"); +const globals = require("globals"); +const js = require("@eslint/js"); + +const { + FlatCompat, +} = require("@eslint/eslintrc"); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}); +const path = require("path"); + +module.exports = defineConfig([{ + languageOptions: { + parser: tsParser, + sourceType: "module", + + parserOptions: { + useJSXTextNode: true, + project: [path.resolve(__dirname, "tsconfig.json")], + }, + + globals: { + ...globals.jasmine, + ...globals.jest, + }, + }, + + plugins: { + "@typescript-eslint": typescriptEslint, + prettier, + }, + + extends: compat.extends( + "plugin:@typescript-eslint/recommended", + "prettier", + "plugin:prettier/recommended", + ), + + rules: { + "no-underscore-dangle": 0, + "arrow-body-style": 0, + "no-unused-expressions": 0, + "no-plusplus": 0, + "no-console": 0, + "func-names": 0, + + "comma-dangle": ["error", { + arrays: "always-multiline", + objects: "always-multiline", + imports: "always-multiline", + exports: "always-multiline", + functions: "ignore", + }], + + "no-prototype-builtins": 0, + "prefer-destructuring": 0, + "no-else-return": 0, + + "lines-between-class-members": ["error", "always", { + exceptAfterSingleLine: true, + }], + + "@typescript-eslint/explicit-member-accessibility": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-inferrable-types": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/no-use-before-define": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/camelcase": 0, + "@typescript-eslint/ban-ts-comment": 0, + }, +}, globalIgnores(["lib/*", "es/*", "mjs/*", "node8/*", "**/jest.config.js"])]); diff --git a/jest.config.js b/jest.config.js index fcb3f76e..fb7bbcd2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,7 +7,6 @@ module.exports = { 'ts-jest', { tsconfig: '/tsconfig.json', - isolatedModules: true, diagnostics: false, }, ], diff --git a/package.json b/package.json index 61f2baa7..885811d1 100644 --- a/package.json +++ b/package.json @@ -24,33 +24,33 @@ }, "homepage": "https://github.com/graphql-compose/graphql-compose-mongoose", "dependencies": { - "dataloader": "^2.2.2", + "dataloader": "^2.2.3", "graphql-compose-connection": "8.2.1", "graphql-compose-pagination": "8.3.0" }, "peerDependencies": { "graphql-compose": "^7.21.4 || ^8.0.0 || ^9.0.0", - "mongoose": "^8.0.0 || ^7.0.0 || ^6.0.0" + "mongoose": "^9.0.0 || ^8.0.0 || ^7.0.0 || ^6.0.0" }, "devDependencies": { - "@types/jest": "29.5.8", - "@typescript-eslint/eslint-plugin": "6.11.0", - "@typescript-eslint/parser": "6.11.0", - "eslint": "8.53.0", + "@types/jest": "30.0.0", + "@typescript-eslint/eslint-plugin": "8.49.0", + "@typescript-eslint/parser": "8.49.0", + "eslint": "9.39.1", "eslint-config-airbnb-base": "15.0.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-import": "2.29.0", - "eslint-plugin-prettier": "5.0.1", - "graphql": "16.8.1", - "graphql-compose": "9.0.10", - "jest": "29.7.0", - "mongodb-memory-server": "9.0.1", - "mongoose": "8.0.0", - "prettier": "3.1.0", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-import": "2.32.0", + "eslint-plugin-prettier": "5.5.4", + "graphql": "16.12.0", + "graphql-compose": "9.1.0", + "jest": "30.2.0", + "mongodb-memory-server": "10.4.1", + "mongoose": "9.0.1", + "prettier": "3.7.4", "request": "2.88.2", - "rimraf": "5.0.5", - "ts-jest": "29.1.1", - "typescript": "5.2.2" + "rimraf": "6.1.2", + "ts-jest": "29.4.6", + "typescript": "5.9.3" }, "scripts": { "prepare": "tsc -p ./tsconfig.build.json", @@ -64,10 +64,12 @@ "link": "yarn build && yarn link graphql-compose && yarn link graphql-compose-connection && yarn link graphql-compose-pagination && yarn link mongoose && yarn link", "unlink": "rimraf node_modules && yarn install", "semantic-release": "semantic-release", + "test-prev-vers-8": "yarn add mongoose@8.20.2 --dev --ignore-scripts && yarn coverage && git checkout HEAD -- package.json yarn.lock", "test-prev-vers-7": "yarn add mongoose@7.6.4 --dev --ignore-scripts && yarn coverage && git checkout HEAD -- package.json yarn.lock", "test-prev-vers-6": "yarn add mongoose@6.1.2 --dev --ignore-scripts && yarn coverage && git checkout HEAD -- package.json yarn.lock" }, "engines": { - "node": ">=16.0.0" - } + "node": ">=20.0.0" + }, + "packageManager": "yarn@1.22.22" } diff --git a/src/__mocks__/mongooseCommon.ts b/src/__mocks__/mongooseCommon.ts index 8af4b9f1..82a1b343 100644 --- a/src/__mocks__/mongooseCommon.ts +++ b/src/__mocks__/mongooseCommon.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-param-reassign, no-console */ - import mongoose from 'mongoose'; import MongoMemoryServer from 'mongodb-memory-server-core'; import net, { AddressInfo } from 'net'; diff --git a/src/__mocks__/userModel.ts b/src/__mocks__/userModel.ts index 31e1b9c0..345371da 100644 --- a/src/__mocks__/userModel.ts +++ b/src/__mocks__/userModel.ts @@ -1,8 +1,10 @@ -import { mongoose, Schema } from './mongooseCommon'; +import mongoose, { Document, Schema } from 'mongoose'; import ContactsSchema from './contactsSchema'; import enumEmployment from './enumEmployment'; import LanguageSchema from './languageSchema'; -import { Document } from 'mongoose'; +// @ts-ignore +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import type { bsonType } from 'bson'; const UserSchema = new Schema( { @@ -36,7 +38,7 @@ const UserSchema = new Schema( description: 'Full years', required() { // in graphql this field should be Nullable - return (this as any).name === 'Something special'; + return this.name === 'Something special'; }, }, @@ -128,6 +130,18 @@ const UserSchema = new Schema( ], }, + statusCode: { + type: Number, + enum: Object.values({ + ACTIVE: 1, + INACTIVE: 2, + }), + }, + + numTest: { + type: Number, + }, + // createdAt, created via option `timestamp: true` (see bottom) // updatedAt, created via option `timestamp: true` (see bottom) }, @@ -141,13 +155,13 @@ const UserSchema = new Schema( UserSchema.set('autoIndex', false); UserSchema.index({ n: 1, age: -1 }); -// eslint-disable-next-line UserSchema.virtual('nameVirtual').get(function (this: any) { return `VirtualFieldValue${this._id}`; }); export interface IUser extends Document { - _id: any; + id: mongoose.Types.ObjectId; + n?: string; name?: string; age?: number; gender?: string; @@ -158,6 +172,7 @@ export interface IUser extends Document { email: string; skype?: string; }; + salary?: mongoose.Types.Decimal128; } const UserModel = mongoose.model('User', UserSchema); diff --git a/src/__tests__/__snapshots__/integration-test.ts.snap b/src/__tests__/__snapshots__/integration-test.ts.snap index f426d0e9..ae181a11 100644 --- a/src/__tests__/__snapshots__/integration-test.ts.snap +++ b/src/__tests__/__snapshots__/integration-test.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`integration tests projection should request all fields to rawData field: projection from all fields 1`] = ` [ diff --git a/src/__tests__/composeWithMongoose-test.ts b/src/__tests__/composeWithMongoose-test.ts index 317c33da..7bd8acd4 100644 --- a/src/__tests__/composeWithMongoose-test.ts +++ b/src/__tests__/composeWithMongoose-test.ts @@ -1,7 +1,5 @@ -/* eslint-disable no-unused-expressions */ - import mongoose from 'mongoose'; -import { ObjectTypeComposer, InputTypeComposer, schemaComposer } from 'graphql-compose'; +import { InputTypeComposer, ObjectTypeComposer, schemaComposer } from 'graphql-compose'; import { GraphQLNonNull } from 'graphql-compose/lib/graphql'; import { UserModel } from '../__mocks__/userModel'; import { composeWithMongoose } from '../composeWithMongoose'; diff --git a/src/__tests__/fieldConverter-test.ts b/src/__tests__/fieldConverter-test.ts index 25c730d4..2f3c2788 100644 --- a/src/__tests__/fieldConverter-test.ts +++ b/src/__tests__/fieldConverter-test.ts @@ -1,20 +1,18 @@ -/* eslint-disable no-unused-expressions, no-template-curly-in-string */ - import { EnumTypeComposer, schemaComposer, SchemaComposer } from 'graphql-compose'; import { UserModel } from '../__mocks__/userModel'; import { mongoose } from '../__mocks__/mongooseCommon'; import { - deriveComplexType, - getFieldsFromModel, - convertFieldToGraphQL, - ComplexTypes, - scalarToGraphQL, arrayToGraphQL, + ComplexTypes, + convertFieldToGraphQL, + convertModelToGraphQL, + deriveComplexType, + documentArrayToGraphQL, embeddedToGraphQL, enumToGraphQL, - documentArrayToGraphQL, + getFieldsFromModel, referenceToGraphQL, - convertModelToGraphQL, + scalarToGraphQL, } from '../fieldsConverter'; import GraphQLMongoID from '../types/MongoID'; import GraphQLBSONDecimal from '../types/BSONDecimal'; @@ -38,12 +36,12 @@ describe('fieldConverter', () => { const wrongArgs: any = [{ a: 1 }]; // @ts-expect-error getFieldsFromModel(...wrongArgs); - }).toThrowError(/incorrect mongoose model/); + }).toThrow(/incorrect mongoose model/); expect(() => { const wrongArgs: any = [{ schema: {} }]; // @ts-expect-error getFieldsFromModel(...wrongArgs); - }).toThrowError(/incorrect mongoose model/); + }).toThrow(/incorrect mongoose model/); }); }); @@ -54,25 +52,25 @@ describe('fieldConverter', () => { const wrongArgs: any = []; // @ts-expect-error deriveComplexType(...wrongArgs); - }).toThrowError(err); + }).toThrow(err); expect(() => { const wrongArgs = [123]; // @ts-expect-error deriveComplexType(...wrongArgs); - }).toThrowError(err); + }).toThrow(err); expect(() => { const wrongArgs = [{ a: 1 }]; // @ts-expect-error deriveComplexType(...wrongArgs); - }).toThrowError(err); + }).toThrow(err); expect(() => { const wrongArgs = [{ path: 'name' }]; // @ts-expect-error deriveComplexType(...wrongArgs); - }).toThrowError(err); + }).toThrow(err); expect(() => { deriveComplexType({ path: 'name', instance: 'Abc' }); - }).not.toThrowError(err); + }).not.toThrow(err); }); it('should derive DOCUMENT_ARRAY', () => { @@ -118,6 +116,14 @@ describe('fieldConverter', () => { it('should derive MIXED mongoose type', () => { expect(deriveComplexType(fields.someDynamic)).toBe(ComplexTypes.MIXED); }); + + it('should derive Number mongoose type', () => { + expect(deriveComplexType(fields.numTest)).toBe(ComplexTypes.SCALAR); + }); + + it('should derive Enum Number mongoose type', () => { + expect(deriveComplexType(fields.statusCode)).toBe(ComplexTypes.SCALAR); + }); }); describe('convertFieldToGraphQL()', () => { @@ -165,6 +171,17 @@ describe('fieldConverter', () => { expect(schemaComposer.has('BSONDecimal')).toBeTruthy(); expect(schemaComposer.get('BSONDecimal').getType()).toBe(GraphQLBSONDecimal); }); + + it('should use Scalar[float] for Enum Numbers', () => { + schemaComposer.clear(); + expect(schemaComposer.has('statusCode')).toBeFalsy(); + const mongooseField = { + path: 'statusCode', + instance: 'Number', + enumValues: [1, 2, 3], + }; + expect(convertFieldToGraphQL(mongooseField, '', schemaComposer)).toBe('Float'); + }); }); describe('scalarToGraphQL()', () => { diff --git a/src/__tests__/github_issues/117-test.ts b/src/__tests__/github_issues/117-test.ts index d96bc22a..b5b960f2 100644 --- a/src/__tests__/github_issues/117-test.ts +++ b/src/__tests__/github_issues/117-test.ts @@ -60,7 +60,7 @@ describe('issue #117', () => { const player1 = await PlayerModel.create({ name: '1', surname: '1', sex: 'm' }); const player2 = await PlayerModel.create({ name: '2', surname: '2', sex: 'f' }); - const game = await GameModel.create({ players: [player1, player2] }); + const game = await GameModel.create({ players: [player1._id, player2._id] }); const id = game._id; const g1 = await GameModel.findOne({ _id: id }).populate('players'); diff --git a/src/__tests__/github_issues/120-test.ts b/src/__tests__/github_issues/120-test.ts index b51b699a..d67a4c88 100644 --- a/src/__tests__/github_issues/120-test.ts +++ b/src/__tests__/github_issues/120-test.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-await-in-loop */ - import mongoose from 'mongoose'; import { MongoMemoryServer } from 'mongodb-memory-server'; import { composeWithMongoose } from '../../index'; diff --git a/src/__tests__/github_issues/128-test.ts b/src/__tests__/github_issues/128-test.ts index 254b9ef1..d00062fb 100644 --- a/src/__tests__/github_issues/128-test.ts +++ b/src/__tests__/github_issues/128-test.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-await-in-loop */ - import mongoose from 'mongoose'; import { MongoMemoryServer } from 'mongodb-memory-server'; import { composeWithMongoose } from '../../index'; diff --git a/src/__tests__/github_issues/135-test.ts b/src/__tests__/github_issues/135-test.ts index 793ce061..ff77f2fc 100644 --- a/src/__tests__/github_issues/135-test.ts +++ b/src/__tests__/github_issues/135-test.ts @@ -1,8 +1,6 @@ -/* eslint-disable no-await-in-loop */ - import mongoose from 'mongoose'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import { schemaComposer, graphql } from 'graphql-compose'; +import { graphql, schemaComposer } from 'graphql-compose'; import { composeWithMongoose } from '../../index'; import { getPortFree } from '../../__mocks__/mongooseCommon'; diff --git a/src/__tests__/github_issues/136-test.ts b/src/__tests__/github_issues/136-test.ts index 4b985ce7..f27aaa1a 100644 --- a/src/__tests__/github_issues/136-test.ts +++ b/src/__tests__/github_issues/136-test.ts @@ -1,8 +1,6 @@ -/* eslint-disable no-await-in-loop */ - import mongoose from 'mongoose'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import { schemaComposer, graphql } from 'graphql-compose'; +import { graphql, schemaComposer } from 'graphql-compose'; import { composeWithMongoose } from '../../index'; import { getPortFree } from '../../__mocks__/mongooseCommon'; @@ -49,7 +47,7 @@ describe('issue #136 - Mongoose virtuals', () => { return updateManyFiltered .wrapResolve((next) => async (rp) => { - console.log(rp.args); // eslint-disable-line + console.log(rp.args); return next(rp); }) .debug(); diff --git a/src/__tests__/github_issues/141-test.ts b/src/__tests__/github_issues/141-test.ts index 30f6cc65..aed5dab5 100644 --- a/src/__tests__/github_issues/141-test.ts +++ b/src/__tests__/github_issues/141-test.ts @@ -10,7 +10,7 @@ const UserSchema = new mongoose.Schema({ _id: { type: Number }, name: { type: String, required: true }, }); -interface IUser extends Document { +interface IUser extends Document { _id: number; name: string; } diff --git a/src/__tests__/github_issues/157-test.ts b/src/__tests__/github_issues/157-test.ts index 945a8889..104cc654 100644 --- a/src/__tests__/github_issues/157-test.ts +++ b/src/__tests__/github_issues/157-test.ts @@ -1,8 +1,6 @@ -/* eslint-disable no-await-in-loop */ - import mongoose from 'mongoose'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import { schemaComposer, graphql } from 'graphql-compose'; +import { graphql, schemaComposer } from 'graphql-compose'; import { composeWithMongoose } from '../../index'; import { getPortFree } from '../../__mocks__/mongooseCommon'; diff --git a/src/__tests__/github_issues/194-test.ts b/src/__tests__/github_issues/194-test.ts index a10402c8..01c18e20 100644 --- a/src/__tests__/github_issues/194-test.ts +++ b/src/__tests__/github_issues/194-test.ts @@ -1,8 +1,6 @@ -/* eslint-disable no-await-in-loop */ - import mongoose from 'mongoose'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import { schemaComposer, graphql } from 'graphql-compose'; +import { graphql, schemaComposer } from 'graphql-compose'; import { composeWithMongoose } from '../../index'; import { getPortFree } from '../../__mocks__/mongooseCommon'; diff --git a/src/__tests__/github_issues/219-test.ts b/src/__tests__/github_issues/219-test.ts index 64dbd1b5..798bf720 100644 --- a/src/__tests__/github_issues/219-test.ts +++ b/src/__tests__/github_issues/219-test.ts @@ -1,6 +1,4 @@ -/* eslint-disable no-param-reassign */ - -import { schemaComposer, graphql } from 'graphql-compose'; +import { graphql, schemaComposer } from 'graphql-compose'; import { composeWithMongoose } from '../../index'; import { UserModel } from '../../__mocks__/userModel'; diff --git a/src/__tests__/github_issues/250-test.ts b/src/__tests__/github_issues/250-test.ts index 921ac2bd..20594ff8 100644 --- a/src/__tests__/github_issues/250-test.ts +++ b/src/__tests__/github_issues/250-test.ts @@ -1,6 +1,4 @@ -/* eslint-disable no-param-reassign */ - -import { schemaComposer, graphql } from 'graphql-compose'; +import { graphql, schemaComposer } from 'graphql-compose'; import { composeWithMongoose } from '../../index'; import { UserModel } from '../../__mocks__/userModel'; diff --git a/src/__tests__/github_issues/253-test.ts b/src/__tests__/github_issues/253-test.ts index 4a1d2a14..74033679 100644 --- a/src/__tests__/github_issues/253-test.ts +++ b/src/__tests__/github_issues/253-test.ts @@ -9,12 +9,14 @@ const CarSchema = new Schema( { s: { type: Number, required: true, alias: 'speed' }, aaa: SubCarSchema }, { discriminatorKey: 't' } ); + const Car = mongoose.model('Car', CarSchema); const TimeMachineSchema = new Schema( { f: { type: Number, required: true, alias: 'fluxCompensatorVersion' } }, { discriminatorKey: 't' } ); + const TimeMachine = Car.discriminator('TimeMachine', TimeMachineSchema); const CarDTC = composeWithMongooseDiscriminators(Car); @@ -28,8 +30,12 @@ schemaComposer.Query.addFields({ beforeAll(async () => { await mongoose.createConnection(); - await TimeMachine.create({ speed: 300, fluxCompensatorVersion: 5 }); + // todo find away to get the alias types? + // await TimeMachine.create({ speed: 300, fluxCompensatorVersion: 5 }); + const o = new TimeMachine({ speed: 300, fluxCompensatorVersion: 5 }); + await o.save(); }); + afterAll(() => mongoose.disconnect()); describe('issue #253 - Consider aliases from discriminators during preparation', () => { diff --git a/src/__tests__/github_issues/261-test.ts b/src/__tests__/github_issues/261-test.ts index 3c4c2cb2..b45fa88b 100644 --- a/src/__tests__/github_issues/261-test.ts +++ b/src/__tests__/github_issues/261-test.ts @@ -20,7 +20,7 @@ const UserSchema = new mongoose.Schema({ }, periods: [{ from: Number, to: Number, _id: false }], }); -interface IUser extends Document { +interface IUser extends Document { _id: number; name: string; age?: number; @@ -71,11 +71,11 @@ describe('issue #261 - Non-nullability for mongoose fields that have a default v analytics: UserAnalytics! periods: [UserPeriods]! } - + type UserAnalytics { isEnabled: Boolean! } - + type UserPeriods { from: Float to: Float @@ -97,11 +97,11 @@ describe('issue #261 - Non-nullability for mongoose fields that have a default v analytics: UserWithoutDefaultsAnalytics periods: [UserWithoutDefaultsPeriods] } - + type UserWithoutDefaultsAnalytics { isEnabled: Boolean } - + type UserWithoutDefaultsPeriods { from: Float to: Float @@ -122,9 +122,9 @@ describe('issue #261 - Non-nullability for mongoose fields that have a default v analytics { isEnabled } - periods { - from - to + periods { + from + to } }`, }) diff --git a/src/__tests__/github_issues/268-test.ts b/src/__tests__/github_issues/268-test.ts index dd4e6291..29c65212 100644 --- a/src/__tests__/github_issues/268-test.ts +++ b/src/__tests__/github_issues/268-test.ts @@ -10,8 +10,7 @@ const UserSchema = new mongoose.Schema({ name: { type: String, required: true }, age: { type: Number }, }); -interface IUser extends Document { - _id: number; +interface IUser extends Document { name: string; age?: number; } diff --git a/src/__tests__/github_issues/271-test.ts b/src/__tests__/github_issues/271-test.ts index 0a51edd6..b4ba53b3 100644 --- a/src/__tests__/github_issues/271-test.ts +++ b/src/__tests__/github_issues/271-test.ts @@ -29,8 +29,7 @@ interface IAuthor { isAlive: boolean; } -interface IBook extends Document { - _id: number; +interface IBook extends Document { title: string; author: IAuthor; pageCount?: number; @@ -158,7 +157,7 @@ describe('nested projections with aliases - issue #271', () => { query { booksMany { bookSize - author { + author { isAbove100 } } diff --git a/src/__tests__/github_issues/286-test.ts b/src/__tests__/github_issues/286-test.ts index 571b238c..69a059a5 100644 --- a/src/__tests__/github_issues/286-test.ts +++ b/src/__tests__/github_issues/286-test.ts @@ -10,8 +10,7 @@ const UserSchema = new mongoose.Schema({ name: { type: String, required: true }, age: { type: Number }, }); -interface IUser extends Document { - _id: number; +interface IUser extends Document { name: string; age?: number; } diff --git a/src/__tests__/github_issues/304-test.ts b/src/__tests__/github_issues/304-test.ts index ec71d59f..f13acc08 100644 --- a/src/__tests__/github_issues/304-test.ts +++ b/src/__tests__/github_issues/304-test.ts @@ -42,9 +42,9 @@ beforeAll(async () => { await OrderModel.base.createConnection(); await OrderModel.create([ { orderStatus: 'PAID', inbound: { timeStamp: null } }, - { orderStatus: 'PAID', inbound: { timeStamp: 123 } }, + { orderStatus: 'PAID', inbound: { timeStamp: new Date(123) } }, { orderStatus: 'UNPAID', inbound: { timeStamp: null } }, - { orderStatus: 'UNPAID', inbound: { timeStamp: 456 } }, + { orderStatus: 'UNPAID', inbound: { timeStamp: new Date(456) } }, ]); }); afterAll(() => { diff --git a/src/__tests__/github_issues/315-test.ts b/src/__tests__/github_issues/315-test.ts index 63e3f02c..563619f2 100644 --- a/src/__tests__/github_issues/315-test.ts +++ b/src/__tests__/github_issues/315-test.ts @@ -24,7 +24,7 @@ const booksFindMany = BookTC.mongooseResolvers.findMany().addFilterArg({ $gte: value, ...(rawQuery.date && typeof rawQuery.date != 'object' ? { $eq: rawQuery.date } - : rawQuery.date ?? {}), + : (rawQuery.date ?? {})), }; }, }); diff --git a/src/__tests__/github_issues/gc282-test.ts b/src/__tests__/github_issues/gc282-test.ts index 8b168927..8b6a62e9 100644 --- a/src/__tests__/github_issues/gc282-test.ts +++ b/src/__tests__/github_issues/gc282-test.ts @@ -1,6 +1,4 @@ -/* eslint-disable no-param-reassign */ - -import { schemaComposer, graphql } from 'graphql-compose'; +import { graphql, schemaComposer } from 'graphql-compose'; import { composeWithMongoose } from '../../index'; import { mongoose } from '../../__mocks__/mongooseCommon'; diff --git a/src/composeMongoose.ts b/src/composeMongoose.ts index ca199724..bfe51501 100644 --- a/src/composeMongoose.ts +++ b/src/composeMongoose.ts @@ -105,7 +105,7 @@ export type ObjectTypeComposerWithMongooseResolvers< mongooseResolvers: GenerateResolverType; }; -export function composeMongoose( +export function composeMongoose, TContext = any>( model: Model, opts: ComposeMongooseOpts = {} ): ObjectTypeComposerWithMongooseResolvers { diff --git a/src/composeWithMongoose.ts b/src/composeWithMongoose.ts index c4fd24eb..de66c70a 100644 --- a/src/composeWithMongoose.ts +++ b/src/composeWithMongoose.ts @@ -1,13 +1,11 @@ -/* 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 { Model, Document } from 'mongoose'; +import type { Document, Model } from 'mongoose'; import { convertModelToGraphQL } from './fieldsConverter'; -import { resolverFactory, AllResolversOpts } from './resolvers'; +import { AllResolversOpts, resolverFactory } from './resolvers'; import MongoID from './types/MongoID'; import { GraphQLResolveInfo } from 'graphql'; -import { TypeConverterInputTypeOpts, prepareFields, createInputType } from './composeMongoose'; +import { createInputType, prepareFields, TypeConverterInputTypeOpts } from './composeMongoose'; export type ComposeWithMongooseOpts = { schemaComposer?: SchemaComposer; diff --git a/src/discriminators/DiscriminatorTypeComposer.ts b/src/discriminators/DiscriminatorTypeComposer.ts index 8b02397f..cc7c437d 100644 --- a/src/discriminators/DiscriminatorTypeComposer.ts +++ b/src/discriminators/DiscriminatorTypeComposer.ts @@ -17,8 +17,9 @@ import { prepareBaseResolvers } from './prepareBaseResolvers'; import { reorderFields } from './utils/reorderFields'; import { GraphQLFieldConfig, GraphQLObjectType } from 'graphql-compose/lib/graphql'; -export interface ComposeWithMongooseDiscriminatorsOpts - extends ComposeWithMongooseOpts { +export interface ComposeWithMongooseDiscriminatorsOpts< + TContext, +> extends ComposeWithMongooseOpts { reorderFields?: boolean | string[]; // true order: _id, DKey, DInterfaceFields, DiscriminatorFields } diff --git a/src/discriminators/prepareChildResolvers.ts b/src/discriminators/prepareChildResolvers.ts index 18230f4d..4ebbdb8e 100644 --- a/src/discriminators/prepareChildResolvers.ts +++ b/src/discriminators/prepareChildResolvers.ts @@ -1,4 +1,4 @@ -import type { ResolverResolveParams, Resolver, ObjectTypeComposer } from 'graphql-compose'; +import type { ObjectTypeComposer, Resolver, ResolverResolveParams } from 'graphql-compose'; import { ComposeWithMongooseDiscriminatorsOpts, DiscriminatorTypeComposer, @@ -35,7 +35,6 @@ function setQueryDKey( } resolve.projection[DKey] = 1; - /* eslint no-param-reassign: 1 */ return next(resolve); }); diff --git a/src/discriminators/utils/mergeTypeConverterResolversOpts.ts b/src/discriminators/utils/mergeTypeConverterResolversOpts.ts index 7c6f62b2..7b4c1bb0 100644 --- a/src/discriminators/utils/mergeTypeConverterResolversOpts.ts +++ b/src/discriminators/utils/mergeTypeConverterResolversOpts.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { MergeAbleHelperArgsOpts } from '../../resolvers/helpers'; import { mergeStringAndStringArraysFields } from './mergeCustomizationOptions'; import { AllResolversOpts } from 'src/resolvers'; @@ -39,21 +38,17 @@ export function mergeFilterOperatorsOptsMap( baseOptsTypes[key] = 'string[]'; } - /* eslint-disable */ childFilterOperatorField = mergeMapTypeFields( baseFilterOperatorField, childFilterOperatorField, baseOptsTypes ); - /* eslint-enable */ return childFilterOperatorField; } export function mergeArraysTypeFields( - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types baseField: any, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types childField: any, argOptsType: TypeFieldMap ): TypeFieldMap { diff --git a/src/fieldsConverter.ts b/src/fieldsConverter.ts index 7e1d2053..b2f998b2 100644 --- a/src/fieldsConverter.ts +++ b/src/fieldsConverter.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-use-before-define */ - import type { Model, Schema } from 'mongoose'; import mongoose, { Document } from 'mongoose'; import type { @@ -17,7 +15,10 @@ import GraphQLBSONDecimal from './types/BSONDecimal'; type MongooseFieldT = { path?: string; instance: string; + // mongoose 8 caster?: any; + // mongoose 9 + embeddedSchemaType?: any; options?: { description?: string; alias?: string; @@ -25,7 +26,7 @@ type MongooseFieldT = { originalRequiredValue?: string | (() => any); isRequired?: boolean; defaultValue?: any; - enumValues?: string[]; + enumValues?: string[] | number[]; schema?: Schema; _index?: { [optionName: string]: any }; }; @@ -68,7 +69,7 @@ function _getFieldDescription(field: MongooseFieldT): string | undefined { return undefined; } -function _getFieldEnums(field: MongooseFieldT): string[] | undefined { +function _getFieldEnums(field: MongooseFieldT): string[] | number[] | undefined { if (field.enumValues && field.enumValues.length > 0) { return field.enumValues; } @@ -183,7 +184,7 @@ export function convertModelToGraphQL( let type = convertFieldToGraphQL(mongooseField, typeName, sc); - // in mongoose schema we use javascript `Number` object which casted to `Float` type + // in mongoose schema we use JavaScript `Number` object which cast to `Float` type // so in most cases _id field is `Int` if (fieldName === '_id' && type === 'Float') { type = 'Int'; @@ -293,6 +294,8 @@ export function deriveComplexType(field: MongooseFieldT): ComplexTypes { return ComplexTypes.REFERENCE; } else if (fieldType === 'Decimal128') { return ComplexTypes.DECIMAL; + } else if (fieldType === 'Number') { + return ComplexTypes.SCALAR; } const enums = _getFieldEnums(field); @@ -330,14 +333,14 @@ export function arrayToGraphQL( prefix: string = '', schemaComposer: SchemaComposer ): ComposeOutputTypeDefinition { - if (!field || !field.caster) { + if (!field || (!field.caster && !field.embeddedSchemaType)) { throw new Error( 'You provide incorrect mongoose field to `arrayToGraphQL()`. ' + 'Correct field should contain `caster` property.' ); } - const unwrappedField = { ...field.caster }; + const unwrappedField = { ...(field.caster || field.embeddedSchemaType) } as MongooseFieldT; const outputType: any = convertFieldToGraphQL(unwrappedField, prefix, schemaComposer); return [outputType]; @@ -386,7 +389,7 @@ export function enumToGraphQL( const desc = _getFieldDescription(field); if (desc) etc.setDescription(desc); - const fields = valueList.reduce( + const fields = valueList?.reduce( (result, value) => { let key; if (value === null) { @@ -394,7 +397,10 @@ export function enumToGraphQL( } else if (value === '') { key = 'EMPTY_STRING'; } else { - key = value.replace(/[^_a-zA-Z0-9]/g, '_').replace(/(^[0-9])(.*)/g, 'a_$1$2'); + key = value + ?.toString() + ?.replace(/[^_a-zA-Z0-9]/g, '_') + ?.replace(/(^[0-9])(.*)/g, 'a_$1$2'); } result[key] = { value }; return result; diff --git a/src/resolvers/__tests__/createMany-test.ts b/src/resolvers/__tests__/createMany-test.ts index 1954dc84..cb03bf9b 100644 --- a/src/resolvers/__tests__/createMany-test.ts +++ b/src/resolvers/__tests__/createMany-test.ts @@ -1,9 +1,7 @@ -/* eslint-disable no-param-reassign,func-names */ - -import { Resolver, schemaComposer, ObjectTypeComposer } from 'graphql-compose'; +import { ObjectTypeComposer, Resolver, schemaComposer } from 'graphql-compose'; import { GraphQLList, GraphQLNonNull } from 'graphql-compose/lib/graphql'; import { mongoose } from '../../__mocks__/mongooseCommon'; -import { UserModel, IUser } from '../../__mocks__/userModel'; +import { IUser, UserModel, UserSchema } from '../../__mocks__/userModel'; import { convertModelToGraphQL } from '../../fieldsConverter'; import { createMany } from '../createMany'; import { ExtendedResolveParams } from '..'; @@ -133,9 +131,7 @@ describe('createMany() ->', () => { }); // should throw error if error not requested in graphql query - await expect(resolver.resolve({})).rejects.toThrowError( - 'requires args.records to be an Array' - ); + await expect(resolver.resolve({})).rejects.toThrow('requires args.records to be an Array'); }); it('should save documents to database', async () => { @@ -212,12 +208,11 @@ describe('createMany() ->', () => { it('should execute hooks on save', async () => { schemaComposer.clear(); - const ClonedUserSchema = UserModel.schema.clone(); + const ClonedUserSchema: typeof UserSchema = UserModel.schema.clone(); - ClonedUserSchema.pre('save', function (next) { + ClonedUserSchema.pre('save', function (this: IUser) { this.name = 'ChangedAgain'; this.age = 18; - return next(); }); const ClonedUserModel = mongoose.model('UserClone', ClonedUserSchema); diff --git a/src/resolvers/__tests__/createOne-test.ts b/src/resolvers/__tests__/createOne-test.ts index 336eb935..df25c4fc 100644 --- a/src/resolvers/__tests__/createOne-test.ts +++ b/src/resolvers/__tests__/createOne-test.ts @@ -1,6 +1,4 @@ -/* eslint-disable no-param-reassign */ - -import { Resolver, schemaComposer, ObjectTypeComposer } from 'graphql-compose'; +import { ObjectTypeComposer, Resolver, schemaComposer } from 'graphql-compose'; import { UserModel } from '../../__mocks__/userModel'; import { createOne } from '../createOne'; import { convertModelToGraphQL } from '../../fieldsConverter'; @@ -115,9 +113,7 @@ describe('createOne() ->', () => { }); // should throw error if error not requested in graphql query - await expect(resolver.resolve({})).rejects.toThrowError( - 'requires at least one value in args' - ); + await expect(resolver.resolve({})).rejects.toThrow('requires at least one value in args'); }); it('should return validation error in payload.error', async () => { @@ -153,7 +149,7 @@ describe('createOne() ->', () => { record: { valid: 'AlwaysFails', contacts: { email: 'mail' } }, }, }) - ).rejects.toThrowError( + ).rejects.toThrow( 'User validation failed: n: Path `n` is required., valid: this is a validate message' ); }); diff --git a/src/resolvers/__tests__/removeById-test.ts b/src/resolvers/__tests__/removeById-test.ts index 563e738f..607e6ae2 100644 --- a/src/resolvers/__tests__/removeById-test.ts +++ b/src/resolvers/__tests__/removeById-test.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-param-reassign */ - import { ObjectTypeComposer, Resolver, schemaComposer } from 'graphql-compose'; import { GraphQLNonNull } from 'graphql-compose/lib/graphql'; import { IUser, UserModel } from '../../__mocks__/userModel'; @@ -90,7 +88,7 @@ describe('removeById() ->', () => { }); // should throw error if error not requested in graphql query - await expect(resolver.resolve({})).rejects.toThrowError('resolver requires args._id'); + await expect(resolver.resolve({})).rejects.toThrow('resolver requires args._id'); }); it('should remove document in database', async () => { diff --git a/src/resolvers/__tests__/removeMany-test.ts b/src/resolvers/__tests__/removeMany-test.ts index b24502e9..ce33be6a 100644 --- a/src/resolvers/__tests__/removeMany-test.ts +++ b/src/resolvers/__tests__/removeMany-test.ts @@ -124,7 +124,7 @@ describe('removeMany() ->', () => { }); // should throw error if error not requested in graphql query - await expect(resolver.resolve({})).rejects.toThrowError( + await expect(resolver.resolve({})).rejects.toThrow( 'requires at least one value in args.filter' ); }); diff --git a/src/resolvers/__tests__/removeOne-test.ts b/src/resolvers/__tests__/removeOne-test.ts index 1e7fe1ab..917cdc3f 100644 --- a/src/resolvers/__tests__/removeOne-test.ts +++ b/src/resolvers/__tests__/removeOne-test.ts @@ -1,7 +1,5 @@ -/* eslint-disable no-param-reassign */ - -import { Resolver, schemaComposer, ObjectTypeComposer } from 'graphql-compose'; -import { UserModel, IUser } from '../../__mocks__/userModel'; +import { ObjectTypeComposer, Resolver, schemaComposer } from 'graphql-compose'; +import { IUser, UserModel } from '../../__mocks__/userModel'; import { removeOne } from '../removeOne'; import { convertModelToGraphQL } from '../../fieldsConverter'; import { ExtendedResolveParams } from '..'; @@ -113,7 +111,7 @@ describe('removeOne() ->', () => { }); // should throw error if error not requested in graphql query - await expect(resolver.resolve({})).rejects.toThrowError( + await expect(resolver.resolve({})).rejects.toThrow( 'requires at least one value in args.filter' ); }); diff --git a/src/resolvers/__tests__/updateById-test.ts b/src/resolvers/__tests__/updateById-test.ts index aa2e2206..080bf17d 100644 --- a/src/resolvers/__tests__/updateById-test.ts +++ b/src/resolvers/__tests__/updateById-test.ts @@ -1,8 +1,6 @@ -/* eslint-disable no-param-reassign */ - -import { Resolver, schemaComposer, ObjectTypeComposer } from 'graphql-compose'; +import { ObjectTypeComposer, Resolver, schemaComposer } from 'graphql-compose'; import { GraphQLNonNull } from 'graphql-compose/lib/graphql'; -import { UserModel, IUser } from '../../__mocks__/userModel'; +import { IUser, UserModel } from '../../__mocks__/userModel'; import { updateById } from '../updateById'; import GraphQLMongoID from '../../types/MongoID'; import { convertModelToGraphQL } from '../../fieldsConverter'; @@ -104,7 +102,7 @@ describe('updateById() ->', () => { }); // should throw error if error not requested in graphql query - await expect(resolver.resolve({})).rejects.toThrowError('requires args.record'); + await expect(resolver.resolve({})).rejects.toThrow('requires args.record'); }); it('should return empty payload.error', async () => { @@ -148,7 +146,7 @@ describe('updateById() ->', () => { record: { name: 'some name', valid: 'AlwaysFails' }, }, }) - ).rejects.toThrowError('User validation failed: valid: this is a validate message'); + ).rejects.toThrow('User validation failed: valid: this is a validate message'); }); it('should change data via args.record in model', async () => { diff --git a/src/resolvers/__tests__/updateMany-test.ts b/src/resolvers/__tests__/updateMany-test.ts index b1f8c351..486d97fd 100644 --- a/src/resolvers/__tests__/updateMany-test.ts +++ b/src/resolvers/__tests__/updateMany-test.ts @@ -133,7 +133,7 @@ describe('updateMany() ->', () => { }); // should throw error if error not requested in graphql query - await expect(resolver.resolve({})).rejects.toThrowError('at least one value in args.record'); + await expect(resolver.resolve({})).rejects.toThrow('at least one value in args.record'); }); it('should call `beforeQuery` method with non-executed `query` as arg', async () => { diff --git a/src/resolvers/__tests__/updateOne-test.ts b/src/resolvers/__tests__/updateOne-test.ts index c3f8ad34..eaf6411d 100644 --- a/src/resolvers/__tests__/updateOne-test.ts +++ b/src/resolvers/__tests__/updateOne-test.ts @@ -1,8 +1,6 @@ -/* eslint-disable no-param-reassign */ - -import { Resolver, schemaComposer, ObjectTypeComposer } from 'graphql-compose'; +import { ObjectTypeComposer, Resolver, schemaComposer } from 'graphql-compose'; import { GraphQLNonNull } from 'graphql-compose/lib/graphql'; -import { UserModel, IUser } from '../../__mocks__/userModel'; +import { IUser, UserModel } from '../../__mocks__/userModel'; import { updateOne } from '../updateOne'; import { convertModelToGraphQL } from '../../fieldsConverter'; import { ExtendedResolveParams } from '..'; @@ -152,7 +150,7 @@ describe('updateOne() ->', () => { }); // should throw error if error not requested in graphql query - await expect(resolver.resolve({})).rejects.toThrowError( + await expect(resolver.resolve({})).rejects.toThrow( 'requires at least one value in args.filter' ); }); @@ -196,7 +194,7 @@ describe('updateOne() ->', () => { record: { valid: 'AlwaysFails' }, }, }) - ).rejects.toThrowError('User validation failed: valid: this is a validate message'); + ).rejects.toThrow('User validation failed: valid: this is a validate message'); }); it('should skip records', async () => { diff --git a/src/resolvers/count.ts b/src/resolvers/count.ts index 82fd539a..c1d941a1 100644 --- a/src/resolvers/count.ts +++ b/src/resolvers/count.ts @@ -1,5 +1,5 @@ import type { Resolver, ObjectTypeComposer, InterfaceTypeComposer } from 'graphql-compose'; -import type { Model, Document } from 'mongoose'; +import type { Model, HydratedDocument } from 'mongoose'; import { filterHelper, filterHelperArgs, @@ -20,7 +20,7 @@ type TArgs = { filter?: any; }; -export function count( +export function count = any>( model: Model, tc: ObjectTypeComposer | InterfaceTypeComposer, opts?: CountResolverOpts @@ -56,6 +56,7 @@ export function count( +export function findById = any>( model: Model, tc: ObjectTypeComposer | InterfaceTypeComposer, opts?: FindByIdResolverOpts diff --git a/src/resolvers/findMany.ts b/src/resolvers/findMany.ts index 96398cd1..6f2b0526 100644 --- a/src/resolvers/findMany.ts +++ b/src/resolvers/findMany.ts @@ -1,5 +1,5 @@ import type { Resolver, ObjectTypeComposer, InterfaceTypeComposer } from 'graphql-compose'; -import type { Model, Document } from 'mongoose'; +import type { Model, HydratedDocument } from 'mongoose'; import { limitHelper, limitHelperArgs, @@ -49,7 +49,7 @@ type TArgs = { sort?: string | string[] | Record; }; -export function findMany( +export function findMany = any>( model: Model, tc: ObjectTypeComposer | InterfaceTypeComposer, opts?: FindManyResolverOpts diff --git a/src/resolvers/findOne.ts b/src/resolvers/findOne.ts index f591c2f4..aef00268 100644 --- a/src/resolvers/findOne.ts +++ b/src/resolvers/findOne.ts @@ -1,5 +1,5 @@ import type { Resolver, ObjectTypeComposer, InterfaceTypeComposer } from 'graphql-compose'; -import type { Model, Document } from 'mongoose'; +import type { Model, HydratedDocument } from 'mongoose'; import { skipHelper, skipHelperArgs, @@ -44,7 +44,7 @@ type TArgs = { skip?: number; }; -export function findOne( +export function findOne = any>( model: Model, tc: ObjectTypeComposer | InterfaceTypeComposer, opts?: FindOneResolverOpts diff --git a/src/resolvers/helpers/__tests__/filter-test.ts b/src/resolvers/helpers/__tests__/filter-test.ts index b190e446..416e0703 100644 --- a/src/resolvers/helpers/__tests__/filter-test.ts +++ b/src/resolvers/helpers/__tests__/filter-test.ts @@ -25,7 +25,7 @@ describe('Resolver helper `filter` ->', () => { const wrongArgs: any = [{}]; // @ts-expect-error filterHelperArgs(...wrongArgs); - }).toThrowError('should be instance of ObjectTypeComposer'); + }).toThrow('should be instance of ObjectTypeComposer'); }); it('should throw error if second arg is not MongooseModel', () => { @@ -33,11 +33,11 @@ describe('Resolver helper `filter` ->', () => { const wrongArgs: any = [UserTC, {}]; // @ts-expect-error filterHelperArgs(...wrongArgs); - }).toThrowError('should be instance of MongooseModel'); + }).toThrow('should be instance of MongooseModel'); }); it('should throw error if `opts` is not provided', () => { - expect(() => filterHelperArgs(UserTC, UserModel)).toThrowError('provide non-empty options'); + expect(() => filterHelperArgs(UserTC, UserModel)).toThrow('provide non-empty options'); }); it('should return filter field', () => { @@ -146,7 +146,7 @@ describe('Resolver helper `filter` ->', () => { it('should not call query.where if args.filter is empty', () => { filterHelper(resolveParams, aliases); - expect(spyWhereFn).not.toBeCalled(); + expect(spyWhereFn).not.toHaveBeenCalled(); }); it('should call query.where if args.filter is provided', () => { @@ -154,7 +154,7 @@ describe('Resolver helper `filter` ->', () => { filter: { name: 'nodkz' }, }; filterHelper(resolveParams, aliases); - expect(spyWhereFn).toBeCalledWith({ n: 'nodkz' }); + expect(spyWhereFn).toHaveBeenCalledWith({ n: 'nodkz' }); }); it('should convert deep object in args.filter to dotted object', () => { @@ -167,7 +167,7 @@ describe('Resolver helper `filter` ->', () => { }, }; filterHelper(resolveParams, aliases); - expect(spyWhereFn).toBeCalledWith({ + expect(spyWhereFn).toHaveBeenCalledWith({ 'n.first': 'Pavel', age: 30, }); @@ -180,7 +180,7 @@ describe('Resolver helper `filter` ->', () => { }, }; filterHelper(resolveParams, aliases); - expect(spyWhereFn).toBeCalledWith({ age: { $gt: 10, $lt: 20 } }); + expect(spyWhereFn).toHaveBeenCalledWith({ age: { $gt: 10, $lt: 20 } }); }); it('should add rawQuery to query', () => { diff --git a/src/resolvers/helpers/__tests__/limit-test.ts b/src/resolvers/helpers/__tests__/limit-test.ts index 213c8cfd..d0d810e5 100644 --- a/src/resolvers/helpers/__tests__/limit-test.ts +++ b/src/resolvers/helpers/__tests__/limit-test.ts @@ -33,17 +33,17 @@ describe('Resolver helper `limit` ->', () => { it('should not call query.limit if args.limit is empty', () => { limitHelper(resolveParams); - expect(spyFn).not.toBeCalled(); + expect(spyFn).not.toHaveBeenCalled(); }); it('should call query.limit if args.limit is provided', () => { resolveParams.args = { limit: 333 }; limitHelper(resolveParams); - expect(spyFn).toBeCalledWith(333); + expect(spyFn).toHaveBeenCalledWith(333); }); it('should convert string to int in args.limit', () => { resolveParams.args = { limit: '444' }; limitHelper(resolveParams); - expect(spyFn).toBeCalledWith(444); + expect(spyFn).toHaveBeenCalledWith(444); }); }); }); diff --git a/src/resolvers/helpers/__tests__/projection-test.ts b/src/resolvers/helpers/__tests__/projection-test.ts index 49fbc86e..61f4bfea 100644 --- a/src/resolvers/helpers/__tests__/projection-test.ts +++ b/src/resolvers/helpers/__tests__/projection-test.ts @@ -16,31 +16,31 @@ describe('Resolver helper `projection` ->', () => { it('should not call query.select if projection is empty', () => { projectionHelper(resolveParams); - expect(spyFn).not.toBeCalled(); + expect(spyFn).not.toHaveBeenCalled(); }); it('should call query.select if projection is provided', () => { resolveParams.projection = { name: 1, age: 1 }; projectionHelper(resolveParams, { name: 'n' }); - expect(spyFn).toBeCalledWith({ n: true, age: true }); + expect(spyFn).toHaveBeenCalledWith({ n: true, age: true }); }); it('should make projection fields flat', () => { resolveParams.projection = { name: { first: 1, last: 1 } }; projectionHelper(resolveParams, { name: 'n' }); - expect(spyFn).toBeCalledWith({ 'n.first': true, 'n.last': true }); + expect(spyFn).toHaveBeenCalledWith({ 'n.first': true, 'n.last': true }); }); it('should make projection fields flat with nested aliases', () => { resolveParams.projection = { name: { first: 1, last: 1 } }; projectionHelper(resolveParams, { name: { __selfAlias: 'n', first: 'f', last: 'l' } }); - expect(spyFn).toBeCalledWith({ 'n.f': true, 'n.l': true }); + expect(spyFn).toHaveBeenCalledWith({ 'n.f': true, 'n.l': true }); }); it('should not call query.select if projection has * key', () => { resolveParams.projection = { '*': true }; projectionHelper(resolveParams); - expect(spyFn).not.toBeCalled(); + expect(spyFn).not.toHaveBeenCalled(); }); describe('projection operators', () => { @@ -48,19 +48,19 @@ describe('Resolver helper `projection` ->', () => { it('should pass $meta non-flatten', () => { resolveParams.projection = { score: { $meta: 'textScore' } }; projectionHelper(resolveParams); - expect(spyFn).toBeCalledWith({ score: { $meta: 'textScore' } }); + expect(spyFn).toHaveBeenCalledWith({ score: { $meta: 'textScore' } }); }); it('should pass $slice non-flatten', () => { resolveParams.projection = { comments: { $slice: 5 } }; projectionHelper(resolveParams); - expect(spyFn).toBeCalledWith({ comments: { $slice: 5 } }); + expect(spyFn).toHaveBeenCalledWith({ comments: { $slice: 5 } }); }); it('should pass $elemMatch non-flatten', () => { resolveParams.projection = { students: { $elemMatch: { school: 102 } } }; projectionHelper(resolveParams); - expect(spyFn).toBeCalledWith({ students: { $elemMatch: { school: 102 } } }); + expect(spyFn).toHaveBeenCalledWith({ students: { $elemMatch: { school: 102 } } }); }); }); }); diff --git a/src/resolvers/helpers/__tests__/record-test.ts b/src/resolvers/helpers/__tests__/record-test.ts index 2374f350..dba2041a 100644 --- a/src/resolvers/helpers/__tests__/record-test.ts +++ b/src/resolvers/helpers/__tests__/record-test.ts @@ -18,7 +18,7 @@ describe('Resolver helper `record` ->', () => { describe('recordHelperArgs()', () => { it('should throw error if provided empty opts', () => { - expect(() => recordHelperArgs(UserTC)).toThrowError('provide non-empty options'); + expect(() => recordHelperArgs(UserTC)).toThrow('provide non-empty options'); }); it('should return input field', () => { diff --git a/src/resolvers/helpers/__tests__/skip-test.ts b/src/resolvers/helpers/__tests__/skip-test.ts index 9628b87f..8f83b630 100644 --- a/src/resolvers/helpers/__tests__/skip-test.ts +++ b/src/resolvers/helpers/__tests__/skip-test.ts @@ -23,17 +23,17 @@ describe('Resolver helper `skip` ->', () => { it('should not call query.skip if args.skip is empty', () => { skipHelper(resolveParams); - expect(spyFn).not.toBeCalled(); + expect(spyFn).not.toHaveBeenCalled(); }); it('should call query.skip if args.skip is provided', () => { resolveParams.args = { skip: 333 }; skipHelper(resolveParams); - expect(spyFn).toBeCalledWith(333); + expect(spyFn).toHaveBeenCalledWith(333); }); it('should convert skip to int in args.skip', () => { resolveParams.args = { skip: '444' }; skipHelper(resolveParams); - expect(spyFn).toBeCalledWith(444); + expect(spyFn).toHaveBeenCalledWith(444); }); }); }); diff --git a/src/resolvers/helpers/__tests__/sort-test.ts b/src/resolvers/helpers/__tests__/sort-test.ts index b9288f97..e35c5832 100644 --- a/src/resolvers/helpers/__tests__/sort-test.ts +++ b/src/resolvers/helpers/__tests__/sort-test.ts @@ -55,7 +55,7 @@ describe('Resolver helper `sort` ->', () => { const wrongArgs: any = [{}]; // @ts-expect-error sortHelperArgs(...wrongArgs); - }).toThrowError('should be instance of ObjectTypeComposer'); + }).toThrow('should be instance of ObjectTypeComposer'); }); it('should throw error if second arg is not Mongoose model', () => { @@ -63,13 +63,11 @@ describe('Resolver helper `sort` ->', () => { const wrongArgs: any = [UserTC, {}]; // @ts-expect-error sortHelperArgs(...wrongArgs); - }).toThrowError('should be instance of Mongoose Model'); + }).toThrow('should be instance of Mongoose Model'); }); it('should throw error if `sortTypeName` not provided in opts', () => { - expect(() => sortHelperArgs(UserTC, UserModel)).toThrowError( - 'provide non-empty `sortTypeName`' - ); + expect(() => sortHelperArgs(UserTC, UserModel)).toThrow('provide non-empty `sortTypeName`'); }); it('should return sort field', () => { @@ -111,7 +109,7 @@ describe('Resolver helper `sort` ->', () => { const sortValue = { _id: 1 }; resolveParams.args = { sort: sortValue }; sortHelper(resolveParams); - expect(spyFn).toBeCalledWith(sortValue); + expect(spyFn).toHaveBeenCalledWith(sortValue); }); }); }); diff --git a/src/resolvers/helpers/filter.ts b/src/resolvers/helpers/filter.ts index db33d2cd..9d9d037b 100644 --- a/src/resolvers/helpers/filter.ts +++ b/src/resolvers/helpers/filter.ts @@ -1,17 +1,15 @@ -/* eslint-disable no-use-before-define */ - import { - ObjectTypeComposer, + InputTypeComposer, InterfaceTypeComposer, + ObjectTypeComposer, ObjectTypeComposerArgumentConfigMap, - InputTypeComposer, } from 'graphql-compose'; -import type { Model, Document } from 'mongoose'; -import { isObject, toMongoFilterDottedObject, getIndexedFieldNamesForGraphQL } from '../../utils'; +import type { HydratedDocument, Model } from 'mongoose'; +import { getIndexedFieldNamesForGraphQL, isObject, toMongoFilterDottedObject } from '../../utils'; import type { ExtendedResolveParams } from '../index'; import { - FieldsOperatorsConfig, addFilterOperators, + FieldsOperatorsConfig, processFilterOperators, } from './filterOperators'; import type { NestedAliasesMap } from './aliases'; @@ -66,7 +64,7 @@ export const getFilterHelperArgOptsMap = (): Record = removeFields: ['string', 'string[]'], }); -export function filterHelperArgs( +export function filterHelperArgs = any>( typeComposer: ObjectTypeComposer | InterfaceTypeComposer, model: Model, opts?: FilterHelperArgsOpts diff --git a/src/resolvers/helpers/record.ts b/src/resolvers/helpers/record.ts index 0271aec9..d85e4dcf 100644 --- a/src/resolvers/helpers/record.ts +++ b/src/resolvers/helpers/record.ts @@ -5,7 +5,7 @@ import { InputTypeComposer, } from 'graphql-compose'; import { makeFieldsRecursiveNullable } from '../../utils/makeFieldsRecursiveNullable'; -import { Document } from 'mongoose'; +import { HydratedDocument } from 'mongoose'; export type RecordHelperArgsOpts = { /** @@ -42,7 +42,7 @@ export const getRecordHelperArgsOptsMap = (): Record => ({ requiredFields: 'string[]', }); -export function recordHelperArgs( +export function recordHelperArgs = any>( tc: ObjectTypeComposer | InterfaceTypeComposer, opts?: RecordHelperArgsOpts ): ObjectTypeComposerArgumentConfigMapDefinition<{ record: any }> { diff --git a/src/resolvers/helpers/sort.ts b/src/resolvers/helpers/sort.ts index a10f31f2..17598670 100644 --- a/src/resolvers/helpers/sort.ts +++ b/src/resolvers/helpers/sort.ts @@ -1,6 +1,4 @@ -/* eslint-disable no-use-before-define */ - -import type { Document, Model } from 'mongoose'; +import type { HydratedDocument, Model } from 'mongoose'; import { EnumTypeComposer, InterfaceTypeComposer, @@ -24,7 +22,7 @@ export type SortHelperArgsOpts = { sortTypeName?: string; }; -export function sortHelperArgs( +export function sortHelperArgs = any>( tc: ObjectTypeComposer | InterfaceTypeComposer, model: Model, opts?: SortHelperArgsOpts diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index 14f4802c..b0e60997 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -1,5 +1,5 @@ import type { ResolverResolveParams } from 'graphql-compose'; -import type { Query, Model, Document } from 'mongoose'; +import type { Query, Model, HydratedDocument } from 'mongoose'; import { count, CountResolverOpts } from './count'; import { findById, FindByIdResolverOpts } from './findById'; import { findByIds, FindByIdsResolverOpts } from './findByIds'; @@ -38,9 +38,10 @@ export type AllResolversOpts = { pagination?: false | PaginationResolverOpts; }; -export type ExtendedResolveParams = Partial< - ResolverResolveParams -> & { +export type ExtendedResolveParams< + TDoc extends HydratedDocument = any, + TContext = any, +> = Partial> & { query: Query; rawQuery: { [optName: string]: any }; beforeQuery?: (query: Query, rp: ExtendedResolveParams) => Promise; @@ -68,7 +69,7 @@ export const resolverFactory = { connection, }; -export { +export type { CountResolverOpts, FindByIdResolverOpts, FindByIdsResolverOpts, diff --git a/src/resolvers/updateMany.ts b/src/resolvers/updateMany.ts index 488f0545..b7539440 100644 --- a/src/resolvers/updateMany.ts +++ b/src/resolvers/updateMany.ts @@ -1,20 +1,20 @@ -import type { Resolver, ObjectTypeComposer, InterfaceTypeComposer } from 'graphql-compose'; -import type { Model, Document } from 'mongoose'; +import type { InterfaceTypeComposer, ObjectTypeComposer, Resolver } from 'graphql-compose'; +import type { Document, Model } from 'mongoose'; import { + filterHelper, + filterHelperArgs, + FilterHelperArgsOpts, limitHelper, limitHelperArgs, + LimitHelperArgsOpts, + prepareNestedAliases, + recordHelperArgs, + RecordHelperArgsOpts, skipHelper, skipHelperArgs, - recordHelperArgs, - filterHelper, - filterHelperArgs, sortHelper, sortHelperArgs, - prepareNestedAliases, - RecordHelperArgsOpts, - FilterHelperArgsOpts, SortHelperArgsOpts, - LimitHelperArgsOpts, } from './helpers'; import { toMongoDottedObject } from '../utils/toMongoDottedObject'; import type { ExtendedResolveParams } from './index'; @@ -121,9 +121,12 @@ export function updateMany