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: Unable to correctly represent subdocument array _id fields, other default properties #10947

Closed
ajwootto opened this issue Nov 1, 2021 · 1 comment
Labels
typescript Types or Types-test related issue / Pull Request
Milestone

Comments

@ajwootto
Copy link

ajwootto commented Nov 1, 2021

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

What is the current behavior?
When trying to represent an array of subdocuments in a schema, you have to include the "_id" field in order for that field to exist in the type of the query response. However, including that field in the type means that any "update" call needs to provide an _id for the update to work.

Any other field which has a default on the subdocument array is also required to be provided when pushing new documents to an array

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

import { Schema, model, connect, Types, connection } from 'mongoose'

interface NestedChild {
    name: string
}
const nestedChildSchema: Schema = new Schema({ name: String })

interface NestedChildWithId {
    name: string
    _id: Types.ObjectId
}
const nestedChildWithIdSchema: Schema = new Schema({ name: String })

interface NestedChildWithDefault {
    name: string
}
const nestedChildWithDefaultSchema: Schema = new Schema({ name: { type: String, default: () => 'defaulted' } })

interface Parent {
    nestedChildren: NestedChild[]
    nestedChildrenWithId: NestedChildWithId[]
    nestedChildrenWithDefault: NestedChildWithDefault[]
    name?: string
}

const ParentModel = model<Parent>('Parent', new Schema({
    nestedChildren: { type: [nestedChildSchema] },
    nestedChildrenWithId: { type: [nestedChildWithIdSchema] },
    nestedChildrenWithDefault: { type: [nestedChildWithDefaultSchema] },
    name: String
}))

async function run() {
    await connect('mongodb://localhost:27017/')
    await connection.dropDatabase()
    await ParentModel.create({
        nestedChildren: [{ name: 'nestedChild' }],
        nestedChildrenWithId: [{ name: 'nestedChild' }],
        nestedChildrenWIthDefault: [{ name: 'nestedChild' }],
        name: 'Parent'
    })

    const parent = await ParentModel.findOne().exec()

    // works, but type is "any" instead of ObjectId
    console.log(parent._id)
    // does not work, property "does not exist"
    console.log(parent.nestedChildren[0]._id)

    //works, and also has the correct ObjectId type
    console.log(parent.nestedChildrenWithId[0]._id)

    //works
    parent.nestedChildren.push({ name: 'test' })

    // type error, need to provide _id
    parent.nestedChildrenWithId.push({ name: 'test' })

    // type error, need to provide name
    parent.nestedChildrenWithDefault.push({})

    await ParentModel.findOneAndUpdate({}, {
        // this one works
        nestedChildren: [{
            name: 'test'
        }],

        // this one doesn't, complains the _id is missing
        nestedChildrenWithId: [{
            name: 'test'
        }],

        // type error, need to provide name
        nestedChildrenWithDefault: [{}]
    })
}

run()

What is the expected behavior?
I'm actually unclear on whether there's just something I'm doing wrong here, but this case isn't mentioned in the docs. I'd expect that the subdocuments would get their _id field added automatically the way a top-level document does, since in the docs _ids are never explicitly defined in the type.

In my interfaces, the defaulted fields are set to required because that's how they look in the DB. Maybe there's some kind of helper type I'm missing here to represent the embedded array.

When creating a new document, mongoose basically lets you provide anything as input data and doesn't attempt to enforce typing on it (which is by design AFAIK). By that same logic, it seems like "pushing" a new subdocument during an update would need to follow the same logic, since it is creating a new document with defaults applied.

As an aside, the top-level _id field of Parent is type any, when I would expect that its type should be ObjectId

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.
Mongoose: 6.0.11
Node: 14.15

@ajwootto ajwootto changed the title Typescript: Unable to correctly represent subdocument _id fields Typescript: Unable to correctly represent subdocument _id fields, other default properties Nov 1, 2021
@ajwootto ajwootto changed the title Typescript: Unable to correctly represent subdocument _id fields, other default properties Typescript: Unable to correctly represent subdocument array _id fields, other default properties Nov 1, 2021
@IslandRhythms IslandRhythms added the typescript Types or Types-test related issue / Pull Request label Nov 1, 2021
@vkarpov15 vkarpov15 added this to the 6.0.15 milestone Nov 10, 2021
@vkarpov15
Copy link
Collaborator

NestedChildWithId is the most correct approach, and you should use Types.DocumentArray<> in your document interface:

interface Parent {
    nestedChildrenWithId: Types.DocumentArray<NestedChildWithId>
    name?: string
}

This will allow you to push a new subdocument without specifying _id explicitly.

We added a note about this to the docs, and made a couple of fixes. findOneAndUpdate() also needs to allow updating a subdocument with the above syntax without specifying _id, and Types.DocumentArray<> should allow passing in an object with no extends Document

@vkarpov15 vkarpov15 modified the milestones: 6.0.16, 6.0.15 Dec 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
typescript Types or Types-test related issue / Pull Request
Projects
None yet
Development

No branches or pull requests

3 participants