Search code examples
node.jstypescriptmongodbcircular-referencecyclic-dependency

Should I refrain from circular imports when using typescript?


I just spent 4 hours troubleshooting typescript error

Object literal may only specify known properties, and 'details'
does not exist in type 'Readonly<{ [x: `details.${string}.value`]:
{ value: string; type: string; } | undefined; [x: `details.${string}.type`]:
{ value: string; type: string; } | undefined;

and clearly, I can see there is 'details' in the object whatever tsc is complaining on. Literally, I have other functions on the same file working without typescript errors.

Long story short, Controller will invoke Service with a single parameter and its type is defined on Controller file but then Service needs to know what type of data its getting so Service file has import statement pointing to Controller

Controller

import service from './Service'
export type DataType = {...}

const data:DataType = fetch()
service(data)

Service

import DataType from './controller'

export function service(data:DataType) {
  // typescript error
  someOtherFunctionCall<DataType>(data)
}

I think it's a pretty basic pattern. When I deleted import statement from service file, the error went away and of course, copy/pasted type definition. Weird thing is that error didn't show up again when I pasted exactly the same line. Is it expected behavior? I've heard nodejs can handle not very complicated circular imports. But this happened, should I not use circular imports?

Below are code from my project

Category.ts

export default interface Category {
  _id: ObjectId
  details: Record<
    string,
    {
      value: string
      type: string
    }
  >
  ...
}

controller.ts

import { updateItem } from './service'

export interface ScrapItem {
  _id: ObjectId
  details: Record<
    string,
    {
      value: string
      type: string
    }
  >
  ...
}

const item:ScrapItem = scrapSomeItem()
updateItem(item)

service.ts

import Category from 'models/category'
import ScrapItem from './controller'
import db from 'MongodbHelper'

export async function updateItem(item: ScrapItem): Promise<void> {
  const db = await getDb()
  const categoryCollection = db.collection<Category>(category)

  await categoryCollection.updateOne(
    {
      _id
    },
    {
      $set: {
        // THIS IS WHERE I GET ERROR
        // Object literal may only specify known properties, and 'details' does not exist in type ...
        details,
        ...
      }
    },
    {
      upsert: true
    }
  )
}

I'm getting typescript error on service.ts where I call one of mongodb driver method updateOne. The only workaround here is to delete Category from const categoryCollection = db.collection<Category>(category) or add | any to details type definition on category.ts.


Solution

  • This is actually a bug in the mongodb version 4.8 typing declaration files. It has been fixed already on the main branch 3 days ago (2022-07-20) but there is no release yet. The latest release, the 4.8 version at the time of this writing, is from 10 days ago (2022-07-13).

    To fix the problem downgrade the version to 4.7:

    npm i mongodb@~4.7.0
    

    Or install from the main branch where the fix is already inplace:

    npm i "https://github.com/mongodb/node-mongodb-native#05e007b0b5ff98151c3ff972ee2f881b4203639e"