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

Typescript: Group interface for type PipelineStage has conflicting index signatures #11059

Closed
meteohr opened this issue Dec 8, 2021 · 21 comments · Fixed by #11124
Closed

Typescript: Group interface for type PipelineStage has conflicting index signatures #11059

meteohr opened this issue Dec 8, 2021 · 21 comments · Fixed by #11124
Labels
typescript Types or Types-test related issue / Pull Request

Comments

@meteohr
Copy link

meteohr commented Dec 8, 2021

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

If you provide a "group" stage with the mandatory _id property in your aggregration pipeline, typescript will throw an error:

TS2322: Type '{ _id: string; imageResource: { $first: string; }; }'
is not assignable to type
'{ [key: string]: { $count?: any; $accumulator?: any; $addToSet?: any; $avg?: any; $first?: any; $last?: any; $max?: any; $mergeObjects?: any; $min?: any; $push?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }; _id: any; }'.
Property '_id' is incompatible with index signature.
Type 'string' has no properties in common with type '{ $count?: any; $accumulator?: any; $addToSet?: any; $avg?: any; $first?: any; $last?: any; $max?: any; $mergeObjects?: any; $min?: any; $push?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }'.

If the current behavior is a bug, please provide the steps to reproduce.

 const aggregateSomething: PipelineStage = {
        $group: {
            _id: 'name',
            imageResource: { $first: 'something' },
        }
    }

tsconfig:

{
    "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "moduleResolution": "node",
        "outDir": "./dist",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "sourceMap": true,
        "pretty": true,
        "allowJs": false,
        "strict": true,
        "esModuleInterop": true,
        "isolatedModules": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noUncheckedIndexedAccess": true,
        "noFallthroughCasesInSwitch": true,
        "noImplicitReturns": true,
        "types": ["jest"],
        "lib": ["esnext"],
        "importHelpers": true,
        "resolveJsonModule": true
    },
    "include": ["src/**/*", "tests/**/*"],
    "exclude": ["node_modules"]
}

What is the expected behavior?
Property _id accepts any, instead of only accepting the object with AccumulatorOperator as type.

Typescript suggests to use a union of all property types, see: https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures

This is most probably connected to #10971

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.
node version: 14
mongoose version: 6.1.0
mongo version: 4.2.12
typescript version: 4.5.2

@rongem
Copy link

rongem commented Dec 9, 2021

I get the same problem with node version 16. Sticking to 6.0.14 for the moment as workaround.

@IslandRhythms IslandRhythms added the needs repro script Maybe a bug, but no repro script. The issue reporter should create a script that demos the issue label Dec 9, 2021
@ahmedelshenawy25
Copy link
Contributor

ahmedelshenawy25 commented Dec 10, 2021

import mongoose, { Types } from 'mongoose';
const { Schema } = mongoose;

run().catch(console.error);
async function run() {
  await mongoose.connect('mongodb://localhost:27017/test');

  interface IUser {
    _id: Types.ObjectId | string;
    name?: string;
  }

  const userSchema = new Schema<IUser>({ name: String });
  const User = mongoose.model<IUser>('User', userSchema);

  await User.aggregate([
    {
      $group: { _id: '$name' }
    }
  ])
}

@XopoIII
Copy link

XopoIII commented Dec 10, 2021

@ahmedelshenawy25 did not help

@rongem
Copy link

rongem commented Dec 10, 2021

If you want to reproduce, please try out my project at
https://github.com/rongem/cmdb/tree/master/njs-backend
If you run tsc, it works. If you update to mongoose 6.1.0 and run tsc, it fails to compile with above error.

@orgads
Copy link
Contributor

orgads commented Dec 10, 2021

I worked around it by casting the pipeline to any.

@rongem
Copy link

rongem commented Dec 10, 2021

Casting to any did not work for me.
Just tested 6.1.1, problem persists.

@meteohr
Copy link
Author

meteohr commented Dec 10, 2021

@XopoIII I think @ahmedelshenawy25 just wanted to provide a reproduction script. This is just a minimalistic setup for getting the error.
You can work around that error by doing $group: { _id: '$name' as any } in @ahmedelshenawy25 s example.

@ahmedelshenawy25
Copy link
Contributor

@XopoIII ye as @meteohr that's minimal reproduction script

@XopoIII
Copy link

XopoIII commented Dec 12, 2021

@ahmedelshenawy25 @meteohr thx!

@DavideViolante
Copy link
Contributor

DavideViolante commented Dec 13, 2021

Upgrading from 6.0.14 to 6.1.1, all those TS errors appeared in aggregate pipelines:

1

{ $group: { _id: { $month: '$date' }, count: { $sum: 1 } } },
Type '{ _id: { $month: string; }; count: { $sum: number; }; }' is not assignable to type '{ [key: string]: { $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }; _id: any; }'.
  Property '_id' is incompatible with index signature.
    Type '{ $month: string; }' is not assignable to type '{ $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }'.
      Object literal may only specify known properties, and '$month' does not exist in type '{ $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }'.ts(2322)

2

{ $group: { _id: '$activity', count: { $sum: 1 } } },
Type '{ _id: string; count: { $sum: number; }; }' is not assignable to type '{ [key: string]: { $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }; _id: any; }'.
  Property '_id' is incompatible with index signature.
    Type 'string' has no properties in common with type '{ $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }'.ts(2322)

3 (solved here)

{ $project: { _id: 1, name: 1, count: 1 } },
(property) _id?: false | 0
Type '1' is not assignable to type 'false | 0'.ts(2322)
index.d.ts(3106, 19): The expected type comes from property '_id' which is declared here on type '{ [field: string]: any; _id?: false | 0; }'

4

{ $group: { _id: { $ifNull: [ '$name', 'ND' ] }, count: { $sum: 1 } } },
Type '{ _id: { $ifNull: string[]; }; count: { $sum: number; }; }' is not assignable to type '{ [key: string]: { $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }; _id: any; }'.
  Property '_id' is incompatible with index signature.
    Type '{ $ifNull: string[]; }' is not assignable to type '{ $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }'.
      Object literal may only specify known properties, and '$ifNull' does not exist in type '{ $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }'.ts(2322)

5

{ $group: { _id: { $isoWeek: '$date' }, count: { $sum: 1 } } },
Type '{ _id: { $isoWeek: string; }; count: { $sum: number; }; }' is not assignable to type '{ [key: string]: { $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }; _id: any; }'.
  Property '_id' is incompatible with index signature.
    Type '{ $isoWeek: string; }' is not assignable to type '{ $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }'.
      Object literal may only specify known properties, and '$isoWeek' does not exist in type '{ $min?: any; $max?: any; $addToSet?: any; $push?: any; $count?: any; $accumulator?: any; $avg?: any; $first?: any; $last?: any; $mergeObjects?: any; $stdDevPop?: any; $stdDevSamp?: any; $sum?: any; }'.ts(2322)

@meteohr
Copy link
Author

meteohr commented Dec 13, 2021

@DavideViolante I would say cases 1, 2, 4 and 5 are coming from the reported issue with the Group interface. About number 3 I'm not sure, maybe open another ticket if you are sure, that this is a real bug.

@rpenido
Copy link
Contributor

rpenido commented Dec 15, 2021

@DavideViolante In Case 3, you can ajust you code. _id will always shows on projection. you only use {_id: 0} to remove it

You can use de code bellow with the same effect:

{ $project: { name: 1, count: 1 } },

@SergioSuarezDev
Copy link

I have the same errors 1, 2 and 4

@DavideViolante
Copy link
Contributor

I think this PR #10971 caused these TS errors. Maybe @jeremyben can help to fix it.

@jeremyben
Copy link
Contributor

@DavideViolante Thanks for the heads up.
I tried to solve this problem, and this is what I came up with:

First this is the Group stage signature right now, following the mongodb documentation itself:

interface Group {
	$group: { 
		_id: any; 
		[key: string]: { [op in AccumulatorOperator]?: any }
	}
}

It seems, based on errors, that the compiler is unable to pin down the _id key to any and instead uses the index signature for it.

So we unfortunately have to lose the narrowing of operators when _id is defined, and this is the fallback I came up with while tinkering:

interface Group {
	$group: { _id: any; [key: string]: any } | { [key: string]: { [op in AccumulatorOperator]?: any } }
}

Or more radically we just go back to a mere index signature with any for everything:

interface Group {
	$group: { [key: string]: any }
}

@rpenido
Copy link
Contributor

rpenido commented Dec 16, 2021

Hi @jeremyben !

interface Group {
	$group: { _id: any; [key: string]: any } | { [key: string]: { [op in AccumulatorOperator]?: any } }
}

This will allow ommit the _id, like in $group: { someField: {$sum: 1}}, and will generate an error on runtime.

It's a trick problem.

There is a discusion here #11104

For some reason (that I don't understand) this works (and the compiler reconizes the operator):

interface Group {
  $group: { _id: any} | {_id: any, [key: string]: { [op in AccumulatorOperator]?: any } }
}

@jeremyben
Copy link
Contributor

jeremyben commented Dec 17, 2021

@rpenido Bravo, this is the one signature.
I forgot the mandatory _id while trying to find the solution no matter what.

But this is it !

interface Group {
  $group: { _id: any } | { _id: any, [key: string]: { [op in AccumulatorOperator]?: any } }
}

I honestly don't know either why this would work and not the first one.

@DavideViolante
Copy link
Contributor

I tried the proposed solution in my code and it fixes the TS errors. I just sent a PR. ^
@vkarpov15 this should be high priority and next release due to number of people affected.

@Uzlopak
Copy link
Collaborator

Uzlopak commented Dec 20, 2021

Shame. I thought I could call myself after my PR being merged contributor of mongoose. Well :D

@vkarpov15 vkarpov15 removed this from the 6.1.4 milestone Dec 21, 2021
@vkarpov15 vkarpov15 added typescript Types or Types-test related issue / Pull Request and removed needs repro script Maybe a bug, but no repro script. The issue reporter should create a script that demos the issue labels Dec 21, 2021
@ollyde
Copy link

ollyde commented Jul 4, 2022

I have this issue upgrading from node 16.13.0 -> 16.15.1

Casting any doesn't seem to help, we have to place @ts-ignore everywhere, not good.

Specifically, pipeline stages like this no longer work, no matter what we do.

Using Mongoose 6.4.1

{
     $set: {
         roomMembers: "$$ROOT",
     },
},

@vkarpov15 vkarpov15 reopened this Jul 10, 2022
@vkarpov15 vkarpov15 added this to the 6.4.5 milestone Jul 10, 2022
@vkarpov15
Copy link
Collaborator

@OllyDixon the below script compiles fine in Mongoose 6.4.1. Please open a new issue, follow the issue template, and modify the below script to demonstrate the issue you're seeing.

import mongoose from 'mongoose';
  
run().catch(err => console.log(err));

async function run() {
  const schema = new mongoose.Schema({ name: String });
  const Test = mongoose.model('Test', schema);

  await Test.aggregate([
    {
       $set: {
         roomMembers: "$$ROOT",
       },
     },
  ]);
}

@vkarpov15 vkarpov15 removed this from the 6.4.5 milestone Jul 16, 2022
@Automattic Automattic locked and limited conversation to collaborators Jul 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
typescript Types or Types-test related issue / Pull Request
Projects
None yet
Development

Successfully merging a pull request may close this issue.