What is the syntax for typing a mongoose populated field in Typescript?

I am faced with a field that can either be an ID or have a value, and I am attempting to type this field using TypeScript

import { InferSchemaType, Schema, Types, model } from 'mongoose';

const personSchema = new Schema({
  _id: Schema.Types.ObjectId,
  name: String,
  age: Number,
});

const storySchema = new Schema({
  author: { type: Schema.Types.ObjectId, ref: 'Person' },
  title: String,
});

export type PersonDao = { _id: Types.ObjectId } & InferSchemaType<typeof personSchema>;
export type StoryDao = { _id: Types.ObjectId; author: Types.ObjectId | PersonDao } & Omit<InferSchemaType<typeof storySchema>, 'author'>;;

export const Person = model('Person', personSchema);
export const Story = model('Story', storySchema);

Story.findOne({ _id: 'fakeId' })
  .populate<StoryDao>('author')
  .exec()
  .then((story: StoryDao) => {
    console.log(story.author.name); // error
  });

When trying to access the 'name' attribute in the console.log, I encounter this error

Property 'name' does not exist on type 'ObjectId | PersonDao'.
  Property 'name' does not exist on type 'ObjectId'.ts(2339)

If I define the type as follows

export type StoryDao = { _id: Types.ObjectId; author: PersonDao } & Omit<InferSchemaType<typeof storySchema>, 'author'>;;

export const story: StoryDao = {
  _id: new Types.ObjectId(),
  author: new Types.ObjectId(), // error
  title: 'fakeTitle',
};

The error regarding 'name' is resolved, but now I face it regarding 'author' when creating a StoryDao object

'ObjectId' is not assignable to type 'PersonDao'.
  Type 'ObjectId' is missing the following properties from type '{ createdAt: NativeDate; updatedAt: NativeDate; }': createdAt, updatedAtts(2322)

This appears to be a limitation of TypeScript with unions, as I encounter a similar issue here

type Test = number | { name: string }
const test: Test = {} as Test;
console.log('DEBUG: ', test.name); // error

How would you approach typing this particular field?

Answer №1

During runtime, TypeScript struggles to infer the type of the subdocument. This leads to a situation where the author variable, defined as either ObjectId or PersonDao, is assumed to be an ObjectId even after being populated. Since an ObjectId does not have a name property, TypeScript's behavior in this scenario is technically correct. Handling union types like TypeA | TypeB can pose challenges for TypeScript because it lacks awareness of the underlying logic determining the specific type at any given time.

To address this issue, you have two main options:

Firstly, you can utilize a type assertion to inform TypeScript that the story object is fully populated:

const author = story.author as PersonDao;
console.log(author.name);

Alternatively, if you prefer a more robust approach, consider implementing a typeguard function to validate access to potentially undefined properties. A basic implementation could resemble the following example:

const isPopulatedAuthor = (x: any): x is PersonDao => {
    return x.name !== undefined
}

if (isPopulatedAuthor(story.author)) {
    console.log(story.author.name)
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is Validators.required a necessity in Angular 2?

Is there a way to dynamically require a form field based on conditions? I created a custom validator, but the conditional variables passed to it remain static. How can I update these conditional values within the custom validator function? Is it possible t ...

Using Angular Material to dynamically hide columns in a table that is being created on the fly

Is there a way to hide columns in my table based on the ID value sent to the page upon opening it? While researching, I found a method for tables with dynamically generated columns in this post: https://github.com/angular/components/issues/9940. However, t ...

Utilizing TypeScript in Kendo UI for JQuery

I have implemented KendoUI for JQuery using TypeScript. Here is an excerpt from my "package.json" file: "dependencies": { "@progress/kendo-theme-material": "^3.19.2", "@progress/kendo-ui": "^2020.3.915 ...

Having difficulty displaying data in the proper format with two-way binding

In the realm of my webpage, I have a plethora of headings, paragraphs, images, and other data at my disposal. From the backend, a dataset is provided to me that includes an array with various properties housing the desired information. The challenge lies i ...

Implementing onClick event handling in Material UI components using Typescript

I am attempting to pass a function that returns another function to material UI's onTouchTap event: <IconButton onTouchTap={onObjectClick(object)} style={iconButtonStyle} > <img alt={customer.name} className="object-img" src={obj ...

Understanding how to use TypeScript modules with system exports in the browser using systemjs

I’m facing an issue while using systemjs. I compiled this code with tsc and exported it: https://github.com/quantumjs/solar-popup/blob/master/package.json#L10 { "compilerOptions": { "module": "system", "target": "es5", "sourceMap": true, ...

Is there a method to run code in the parent class right after the child constructor is called in two ES6 Parent-Child classes?

For instance: class Parent { constructor() {} } class Child { constructor() { super(); someChildCode(); } } I need to run some additional code after the execution of someChildCode(). Although I could insert it directly there, the requirement is not to ...

Compiling Vue with TypeScript: Troubleshooting common errors

Using Vue Components with Templates Multiple Times in TypeScript I am working on utilizing a component with a template multiple times within another component. The code is split between a .html file and a .ts file. The .html file structure is as follows: ...

The first argument passed to CollectionReference.doc() must be a string that is not empty

I'm facing an issue with my Ionic app while attempting to update records in Firebase. The error message I keep encountering has me stumped as to where I might be going wrong. Error: Uncaught (in promise): FirebaseError: [code=invalid-argument]: Functi ...

Using TypeScript to type styled-system props

Currently, I am utilizing styled-system and one of the main features of this library is its shorthand props that allow for simple and quick theming. Although I have streamlined my component, a significant aspect lies here: import React from 'react&a ...

Atom-typescript does not always successfully compile all typescript files to JavaScript

Currently, I am in the process of learning how to implement routing in Angular2 by following a tutorial. The tutorial involves creating partial pages using .ts files along with companion .js files for each page. While my Atom editor, equipped with atom-typ ...

Proper positioning of try/catch block in scenarios involving delayed async/await operations

For the past six months, I have been utilizing async/await and have truly enjoyed the convenience it provides. Typically, I adhere to the traditional usage like so: try { await doSomethingAsync() } catch (e) {} Lately, I've delved into experimenti ...

How to simulate a particular class from a node package using Jest mocks

In my project, I'm looking to specifically mock the Socket class from the net node module. The documentation for this can be found here. Within my codebase, there is a class structured similar to the following... import { Socket } from 'net&apo ...

``Error Message: TypeORM - could not establish database connection

I encountered an issue while running my project built with Typescript, Typeorm, and Express. The error message received when running the dev script was: connectionNotFoundError: Connection "default" was not found The content of my ormconfig.json ...

In my attempt to assess the correlation between value 1 and a value in the preceding object, I am utilizing the *ngFor directive

Attempting to compare 2 entries in an *ngFor loop. The code should compare the value at the current object to a value at the previous object. <ng-container *ngFor="let item of s_1.comments[0]; index as b"> <article class="message i ...

A unique Angular service that is private and initialized with a specific parameter

My Angular Service (myService) is injected into multiple components and services through their constructors. I want each usage of myService to have its own instance to ensure no data is shared among them. Additionally, I would like myService to be initia ...

Building a resolver to modify a DynamoDB item via AppSync using the AWS Cloud Development Kit (CDK)

After successfully creating a resolver to add an item in the table using the code provided below, I am now seeking assistance for replicating the same functionality for an update operation. const configSettingsDS = api.addDynamoDbDataSource('configSet ...

The code encountered an issue with TS2554 error, as it was anticipating two arguments but was only provided with one when

Before, I utilized ViewChild like this: @ViewChild("InternalMedia") localStream; @ViewChild("emoji") mEmoji; Everything was functioning well up until angular-7.x. However, after updating to angular-8.x, the following error started popping up: .../call_ ...

How can we create a versatile Action type in typescript that can accommodate varying numbers of arguments and argument types?

When working with Typescript, encountering a duplicate type error can be frustrating. For instance, consider the following code snippet: export type Action<T> = (arg:T) => void export type Action<T1,T2> = (arg1:T1, arg2:T2) => void How c ...

When attempting to run the yarn build dist command, an error of type TypeError is encountered, stating that it is not possible to set the constructor property

Upon executing the following command in a GitHub workflow, I encountered this error: npm-run-all -p types transpile @internal_package: $ swc src --out-dir dist @internal_package: /home/runner/work/repo-name/repo-name/node_modules/ttypescript/lib/loadTypesc ...